diff --git a/packages/component-user/app/AdminRoute.js b/packages/component-user/app/AdminRoute.js new file mode 100644 index 0000000000000000000000000000000000000000..b243ce527f1a372210da8b1cfa73bbf3a5502942 --- /dev/null +++ b/packages/component-user/app/AdminRoute.js @@ -0,0 +1,32 @@ +import React from 'react' +import { get } from 'lodash' +import { compose } from 'recompose' +import { connect } from 'react-redux' +import { AuthenticatedComponent } from 'pubsweet-client' +import { Redirect, withRouter, Route } from 'react-router-dom' + +const AdminRoute = ({ + currentUser, + redirectPath = '/', + component: Component, + ...rest +}) => { + const isAdmin = get(currentUser, 'user.admin', false) + return ( + <Route + {...rest} + render={props => ( + <AuthenticatedComponent> + {isAdmin ? <Component {...props} /> : <Redirect to="/" />} + </AuthenticatedComponent> + )} + /> + ) +} + +export default compose( + withRouter, + connect(state => ({ + currentUser: state.currentUser, + })), +)(AdminRoute) diff --git a/packages/component-user/app/components/AdminUserForm.js b/packages/component-user/app/components/AdminUserForm.js new file mode 100644 index 0000000000000000000000000000000000000000..6b29d7dc0b611646f0184009e70a299e64cb36a5 --- /dev/null +++ b/packages/component-user/app/components/AdminUserForm.js @@ -0,0 +1,204 @@ +import React, { Fragment } from 'react' +import { get } from 'lodash' +import { Formik } from 'formik' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { required } from 'xpub-validators' +import { compose, setDisplayName, withHandlers, withProps } from 'recompose' +import { + H2, + Button, + Spinner, + TextField, + ValidatedFieldFormik, +} from '@pubsweet/ui' +import { + Row, + Item, + Text, + Label, + IconButton, + RowOverrideAlert, + ItemOverrideAlert, + ValidatedMenuField, + withRoles, + withFetching, + withCountries, +} from 'pubsweet-component-faraday-ui' + +const FormModal = ({ + roles, + title, + titles, + onClose, + onSubmit, + onConfirm, + countries, + isFetching, + fetchingError, + initialValues, + confirmText = 'OK', + cancelText = 'Cancel', +}) => ( + <Root> + <IconButton icon="x" onClick={onClose} right={5} secondary top={5} /> + <H2>{title}</H2> + <Formik + initialValues={initialValues} + onSubmit={onSubmit} + validate={values => { + const errors = {} + + if (get(values, 'email', '') === '') { + errors.email = 'Required' + } + + if (get(values, 'affiliation', '') === '') { + errors.affiliation = 'Required' + } + + return errors + }} + > + {({ handleSubmit, ...rest }) => ( + <Fragment> + <Row alignItems="baseline" mb={1} mt={1}> + <ItemOverrideAlert mr={1} vertical> + <Label required>Email</Label> + <ValidatedFieldFormik + component={TextField} + inline + name="email" + validate={[required]} + /> + </ItemOverrideAlert> + <ItemOverrideAlert ml={1} vertical> + <Label required>Role</Label> + <ValidatedMenuField name="role" options={roles} /> + </ItemOverrideAlert> + </Row> + + <Row mb={2}> + <Item mr={1} vertical> + <Label>First Name</Label> + <ValidatedFieldFormik + component={TextField} + inline + name="firstName" + /> + </Item> + <Item ml={1} vertical> + <Label>Last Name</Label> + <ValidatedFieldFormik + component={TextField} + inline + name="lastName" + /> + </Item> + </Row> + + <RowOverrideAlert alignItems="center" mb={2}> + <ItemOverrideAlert mr={1} vertical> + <Label>Title</Label> + <ValidatedMenuField name="title" options={titles} /> + </ItemOverrideAlert> + <ItemOverrideAlert ml={1} vertical> + <Label>Country</Label> + <ValidatedMenuField name="country" options={countries} /> + </ItemOverrideAlert> + </RowOverrideAlert> + + <Row mb={3}> + <Item vertical> + <Label required>Affiliation</Label> + <ValidatedFieldFormik + component={TextField} + inline + name="affiliation" + /> + </Item> + </Row> + + {fetchingError && ( + <Row mb={1}> + <Text error>{fetchingError}</Text> + </Row> + )} + + {isFetching ? ( + <Spinner /> + ) : ( + <Row> + <Button onClick={onClose}>Cancel</Button> + <Button onClick={handleSubmit} primary> + {confirmText} + </Button> + </Row> + )} + </Fragment> + )} + </Formik> + </Root> +) + +const setInitialRole = a => { + if (get(a, 'admin', false)) { + return 'admin' + } + if (get(a, 'handlingEditor', false)) { + return 'handlingEditor' + } + if (get(a, 'editorInChief', false)) { + return 'editorInChief' + } + return 'author' +} + +export default compose( + withRoles, + withFetching, + withCountries, + withProps(({ user, edit }) => ({ + initialValues: { + ...user, + role: setInitialRole(user), + }, + confirmText: edit ? 'EDIT USER' : 'SAVE USER', + title: edit ? 'Edit User' : 'Add User', + })), + withHandlers({ + onSubmit: ({ onSubmit, ...props }) => (values, formProps) => { + if (typeof onSubmit === 'function') { + onSubmit(values, { ...formProps, ...props }) + } + }, + onClose: ({ onCancel, ...props }) => () => { + if (typeof onCancel === 'function') { + onCancel(props) + } + props.hideModal() + }, + }), + + setDisplayName('AdminUserForm'), +)(FormModal) + +// #region styles +const Root = styled.div` + align-items: center; + background: ${th('colorBackgroundHue')}; + border: ${th('borderWidth')} ${th('borderStyle')} transparent; + border-radius: ${th('borderRadius')}; + box-shadow: ${th('boxShadow')}; + display: flex; + flex-direction: column; + position: relative; + padding: calc(${th('gridUnit')} * 3); + width: calc(${th('gridUnit')} * 60); + + ${H2} { + margin: 0; + text-align: center; + } +` +// #endregion diff --git a/packages/component-user/app/components/OpenStatusModal.js b/packages/component-user/app/components/OpenStatusModal.js new file mode 100644 index 0000000000000000000000000000000000000000..f97ea6a7ec8b6c945b9959cc0f5735fdab35124f --- /dev/null +++ b/packages/component-user/app/components/OpenStatusModal.js @@ -0,0 +1,46 @@ +import React from 'react' +import { Button } from '@pubsweet/ui' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { withHandlers, compose, setDisplayName } from 'recompose' +import { OpenModal, withFetching } from 'pubsweet-component-faraday-ui' + +const OpenStatusModal = ({ + onConfirm, + isFetching, + user: { id, firstName = '', lastName = '', isActive }, +}) => ( + <OpenModal + isFetching={isFetching} + modalKey={`deactivate-${id}`} + onConfirm={onConfirm} + subtitle={`${firstName} ${lastName}`} + title={`Are you sure you want to ${ + !isActive ? 'activate' : 'deactivate' + } user?`} + > + {showModal => ( + <ActivateButton ml={1} mr={1} onClick={showModal} size="small"> + {!isActive ? 'ACTIVATE' : 'DEACTIVATE'} + </ActivateButton> + )} + </OpenModal> +) + +export default compose( + withFetching, + withHandlers({ + onConfirm: ({ user, onConfirm, ...props }) => modalProps => { + if (typeof onConfirm === 'function') { + onConfirm(user, { ...props, ...modalProps }) + } + }, + }), + setDisplayName('AdminStatusModal'), +)(OpenStatusModal) + +// #region styles +const ActivateButton = styled(Button)` + width: calc(${th('gridUnit')} * 10); +` +// #endregion diff --git a/packages/component-user/app/components/OpenUserForm.js b/packages/component-user/app/components/OpenUserForm.js new file mode 100644 index 0000000000000000000000000000000000000000..ae406374e15c9bb5b2696faf626c5d94c12a5820 --- /dev/null +++ b/packages/component-user/app/components/OpenUserForm.js @@ -0,0 +1,31 @@ +import React from 'react' + +import { + OpenModal, + ActionLink, + IconButton, +} from 'pubsweet-component-faraday-ui' + +import AdminUserForm from './AdminUserForm' + +const OpenUserForm = ({ edit, user, onSubmit, modalKey }) => ( + <OpenModal + component={AdminUserForm} + edit={edit} + modalKey={modalKey} + onSubmit={onSubmit} + user={user} + > + {showModal => + edit ? ( + <IconButton icon="edit-2" iconSize={2} onClick={showModal} pt={1 / 2} /> + ) : ( + <ActionLink icon="plus" onClick={showModal}> + ADD USER + </ActionLink> + ) + } + </OpenModal> +) + +export default OpenUserForm diff --git a/packages/component-user/app/components/index.js b/packages/component-user/app/components/index.js new file mode 100644 index 0000000000000000000000000000000000000000..900efc4da7fce9eeff226effb58fff0f6e4305f6 --- /dev/null +++ b/packages/component-user/app/components/index.js @@ -0,0 +1,3 @@ +export { default as AdminUserForm } from './AdminUserForm' +export { default as OpenStatusModal } from './OpenStatusModal' +export { default as OpenUserForm } from './OpenUserForm' diff --git a/packages/component-user/app/graphql/fragments.js b/packages/component-user/app/graphql/fragments.js new file mode 100644 index 0000000000000000000000000000000000000000..36282982757d503eeb0a8da401ea3693c59a6ecb --- /dev/null +++ b/packages/component-user/app/graphql/fragments.js @@ -0,0 +1,18 @@ +import gql from 'graphql-tag' + +export const userFragment = gql` + fragment userDetails on User { + id + admin + email + title + country + username + lastName + isActive + firstName + affiliation + editorInChief + handlingEditor + } +` diff --git a/packages/component-user/app/graphql/index.js b/packages/component-user/app/graphql/index.js new file mode 100644 index 0000000000000000000000000000000000000000..cf105b664973c3d29668b05ebce1ccc06b981c21 --- /dev/null +++ b/packages/component-user/app/graphql/index.js @@ -0,0 +1,6 @@ +import * as fragments from './fragments' +import * as queries from './queries' +import * as mutations from './mutations' + +export { fragments, queries, mutations } +export { default } from './withUsersGQL' diff --git a/packages/component-user/app/graphql/mutations.js b/packages/component-user/app/graphql/mutations.js new file mode 100644 index 0000000000000000000000000000000000000000..fed993f4c47abed820d098295348c30adb5e5cbb --- /dev/null +++ b/packages/component-user/app/graphql/mutations.js @@ -0,0 +1,30 @@ +import gql from 'graphql-tag' + +import { userFragment } from './fragments' + +export const addUserAsAdmin = gql` + mutation addUserAsAdmin($input: UserInput!) { + addUserAsAdmin(input: $input) { + ...userDetails + } + } + ${userFragment} +` + +export const editUserAsAdmin = gql` + mutation editUserAsAdmin($id: ID!, $input: UserInput!) { + editUserAsAdmin(id: $id, input: $input) { + ...userDetails + } + } + ${userFragment} +` + +export const activateUserAsAdmin = gql` + mutation activateUserAsAdmin($id: ID!, $input: ActivateUserInput) { + activateUserAsAdmin(id: $id, input: $input) { + ...userDetails + } + } + ${userFragment} +` diff --git a/packages/component-user/app/graphql/queries.js b/packages/component-user/app/graphql/queries.js new file mode 100644 index 0000000000000000000000000000000000000000..9e65b0e68fb74d888b2d7d82c1d0929977bbf979 --- /dev/null +++ b/packages/component-user/app/graphql/queries.js @@ -0,0 +1,12 @@ +import gql from 'graphql-tag' + +import { userFragment } from './fragments' + +export const getUsers = gql` + { + users { + ...userDetails + } + } + ${userFragment} +` diff --git a/packages/component-user/app/graphql/withUsersGQL.js b/packages/component-user/app/graphql/withUsersGQL.js new file mode 100644 index 0000000000000000000000000000000000000000..aceee93b7866cb01e3d55256cdad7b479723ee71 --- /dev/null +++ b/packages/component-user/app/graphql/withUsersGQL.js @@ -0,0 +1,24 @@ +import { graphql } from 'react-apollo' +import { compose, withProps } from 'recompose' + +import * as queries from './queries' +import * as mutations from './mutations' + +export default compose( + graphql(queries.getUsers), + graphql(mutations.addUserAsAdmin, { + name: 'addUser', + options: { + refetchQueries: [{ query: queries.getUsers }], + }, + }), + graphql(mutations.editUserAsAdmin, { + name: 'updateUser', + }), + graphql(mutations.activateUserAsAdmin, { + name: 'activateUser', + }), + withProps(({ data }) => ({ + users: data.users, + })), +) diff --git a/packages/component-user/app/index.js b/packages/component-user/app/index.js new file mode 100644 index 0000000000000000000000000000000000000000..5cf2fee3273bda71f3524edd593d282797a0802b --- /dev/null +++ b/packages/component-user/app/index.js @@ -0,0 +1,3 @@ +export { default as AdminRoute } from './AdminRoute' +export { default as AdminUsers } from './pages/AdminUsers' +export { default as AdminDashboard } from './pages/AdminDashboard' diff --git a/packages/component-user/app/pages/AdminDashboard.js b/packages/component-user/app/pages/AdminDashboard.js new file mode 100644 index 0000000000000000000000000000000000000000..f07b7c426db7ecf2156cb9bf2542ea133957b156 --- /dev/null +++ b/packages/component-user/app/pages/AdminDashboard.js @@ -0,0 +1,19 @@ +import React, { Fragment } from 'react' +import { H1 } from '@pubsweet/ui' +import { Row, IconCard } from 'pubsweet-component-faraday-ui' + +const AdminDashboard = ({ history }) => ( + <Fragment> + <H1 mt={2}>Admin dashboard</H1> + <Row justify="flex-start" mt={2}> + <IconCard + icon="users" + iconSize={6} + label="Users" + onClick={() => history.push('/admin/users')} + /> + </Row> + </Fragment> +) + +export default AdminDashboard diff --git a/packages/component-user/app/pages/AdminUsers.js b/packages/component-user/app/pages/AdminUsers.js new file mode 100644 index 0000000000000000000000000000000000000000..568ffd9c15a7ea9c48f9ab69bc82015c74b8c411 --- /dev/null +++ b/packages/component-user/app/pages/AdminUsers.js @@ -0,0 +1,264 @@ +import React, { Fragment } from 'react' +import { get } from 'lodash' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { withJournal } from 'xpub-journal' +import { compose, withHandlers, withProps } from 'recompose' + +import { + Row, + Text, + Item, + Label, + Pagination, + ActionLink, + withPagination, +} from 'pubsweet-component-faraday-ui' + +import withUsersGQL from '../graphql' +import { OpenUserForm, OpenStatusModal } from '../components' + +const Users = ({ + page, + users, + theme, + history, + journal, + getUsers, + isFetching, + getUserName, + getUserRoles, + itemsPerPage, + deactivateUser, + getStatusLabel, + paginatedItems, + // + addUser, + updateUser, + toggleUserStatus, + ...rest +}) => ( + <Fragment> + <Row alignItems="center" justify="space-between" mb={1}> + <Item alignItems="center"> + <ActionLink + data-test-id="go-to-dashboard" + icon="arrow-left" + mr={2} + onClick={history.goBack} + > + Admin Dashboard + </ActionLink> + <OpenUserForm modalKey="addUser" onSubmit={addUser} /> + </Item> + + <Pagination {...rest} itemsPerPage={itemsPerPage} page={page} /> + </Row> + + <Table> + <thead> + <tr> + <th> + <Label>Full Name</Label> + </th> + <th colSpan={2}> + <Label>Email</Label> + </th> + <th> + <Label>Affiliation</Label> + </th> + <th> + <Label>Roles</Label> + </th> + <th> + <Label>Status</Label> + </th> + <th> </th> + </tr> + </thead> + <tbody> + {paginatedItems.map(user => ( + <UserRow key={user.id}> + <td> + <Text pl={1}>{getUserName(user)}</Text> + </td> + <td colSpan={2}> + <Text>{user.email}</Text> + </td> + <td> + <Text>{user.affiliation}</Text> + </td> + <td> + <Text customId>{getUserRoles(user)}</Text> + </td> + <td> + <Text secondary>{getStatusLabel(user)}</Text> + </td> + + <HiddenCell> + <Item alignItems="center" justify="flex-end"> + <OpenUserForm + edit + modalKey={`edit-${user.id}`} + onSubmit={updateUser} + user={user} + /> + + <OpenStatusModal onConfirm={toggleUserStatus} user={user} /> + </Item> + </HiddenCell> + </UserRow> + ))} + </tbody> + </Table> + </Fragment> +) + +export default compose( + withJournal, + withUsersGQL, + withProps(({ journal: { roles = {} }, users }) => ({ + roles: Object.keys(roles), + items: users, + })), + withPagination, + withHandlers({ + getStatusLabel: () => ({ admin, isConfirmed, isActive = true }) => { + if (admin) return 'ACTIVE' + if (!isActive) { + return 'INACTIVE' + } + return isConfirmed ? 'ACTIVE' : 'INVITED' + }, + addUser: ({ addUser }) => ( + { + __typename, + id, + admin, + isActive, + editorInChief, + handlingEditor, + ...input + }, + { setFetching, hideModal, setError }, + ) => { + setFetching(true) + addUser({ + variables: { + input: { + ...input, + username: input.email, + }, + }, + }) + .then(() => { + setFetching(false) + hideModal() + }) + .catch(e => { + setFetching(false) + setError(e.message) + }) + }, + updateUser: ({ updateUser }) => ( + { + __typename, + id, + admin, + isActive, + handlingEditor, + editorInChief, + ...input + }, + { setFetching, hideModal, setError }, + ) => { + setFetching(true) + updateUser({ + variables: { + id, + input, + }, + }) + .then(() => { + setFetching(false) + hideModal() + }) + .catch(e => { + setFetching(false) + setError(e.message) + }) + }, + toggleUserStatus: ({ activateUser }) => ( + { id, email, username, isActive }, + { setFetching, hideModal, setModalError }, + ) => { + setFetching(true) + activateUser({ + variables: { + id, + input: { + email, + username, + isActive, + }, + }, + }) + .then(() => { + setFetching(false) + hideModal() + }) + .catch(e => { + setFetching(false) + setModalError(e.message) + }) + }, + getUserName: () => user => { + if (user.admin) { + return 'Admin' + } + return `${get(user, 'firstName', '')} ${get(user, 'lastName', '')}` + }, + getUserRoles: ({ journal: { roles = {} } }) => user => { + const parsedRoles = Object.entries(roles) + .reduce((acc, role) => (user[role[0]] ? [...acc, role[1]] : acc), []) + .join(', ') + + return parsedRoles || 'Author' + }, + }), +)(Users) + +// #region styled-components +const Table = styled.table` + border-collapse: collapse; + + & th, + & td { + border: none; + text-align: start; + vertical-align: middle; + height: calc(${th('gridUnit')} * 5); + } +` + +const HiddenCell = styled.td` + opacity: 0; +` + +const UserRow = styled.tr` + background-color: ${th('colorBackgroundHue2')}; + border-bottom: 1px solid ${th('colorBorder')}; + + &:hover { + background-color: ${th('colorBackgroundHue3')}; + + ${HiddenCell} { + opacity: 1; + } + } +` +// #endregion + +// withHandlers({ + +// }), diff --git a/packages/component-user/index.js b/packages/component-user/index.js index d48a221456d0a04ef43c702afd0e6b8b7a83ae31..c9b9c509b1ff10cbe0cd4af9b5e99c83429a9450 100644 --- a/packages/component-user/index.js +++ b/packages/component-user/index.js @@ -1,8 +1,7 @@ -const resolvers = require('./resolvers') -const typeDefs = require('./typeDefs') +const resolvers = require('./server/resolvers') +const typeDefs = require('./server/typeDefs') module.exports = { - resolvers, typeDefs, - // modelName: 'User', + resolvers, } diff --git a/packages/component-user/package.json b/packages/component-user/package.json index 86aff30234d973a9ddc5a1b9bf4ce96ab39afc92..169e9f92164fc787f93ba0b4d82f3ad6c343f6ae 100644 --- a/packages/component-user/package.json +++ b/packages/component-user/package.json @@ -1,7 +1,7 @@ { "name": "pubsweet-component-user", "version": "0.0.1", - "description": "user component for faraday", + "description": "User component for Hindawi peer review app.", "license": "MIT", "author": "Collaborative Knowledge Foundation", "files": [ @@ -19,12 +19,26 @@ "path": "component-user" }, "dependencies": { - "chance": "^1.0.13" + "@pubsweet/ui": "^9.0.3", + "@pubsweet/ui-toolkit": "^2.0.3", + "chance": "^1.0.13", + "formik": "^1.4.0", + "graphql-tag": "^2.10.0", + "lodash": "^4.17.11", + "pubsweet-client": "^7.0.0", + "react": "^16.6.0", + "react-apollo": "^2.3.2", + "react-dom": "^16.6.0", + "react-redux": "^5.0.2", + "react-router-dom": "^4.2.2", + "recompose": "^0.30.0", + "styled-components": "^4.1.2", + "xpub-validators": "^0.0.6" }, "peerDependencies": { + "@pubsweet/component-send-email": "0.2.4", "@pubsweet/logger": "^0.0.1", "pubsweet-component-helper-service": "0.0.1", - "@pubsweet/component-send-email": "0.2.4", "pubsweet-server": "^10.0.0" }, "devDependencies": { diff --git a/packages/component-user/notifications/emailCopy.js b/packages/component-user/server/notifications/emailCopy.js similarity index 100% rename from packages/component-user/notifications/emailCopy.js rename to packages/component-user/server/notifications/emailCopy.js diff --git a/packages/component-user/notifications/notification.js b/packages/component-user/server/notifications/notification.js similarity index 100% rename from packages/component-user/notifications/notification.js rename to packages/component-user/server/notifications/notification.js diff --git a/packages/component-user/resolvers.js b/packages/component-user/server/resolvers.js similarity index 100% rename from packages/component-user/resolvers.js rename to packages/component-user/server/resolvers.js diff --git a/packages/component-user/typeDefs.js b/packages/component-user/server/typeDefs.js similarity index 93% rename from packages/component-user/typeDefs.js rename to packages/component-user/server/typeDefs.js index 805d717734f16f8157086cf7f620792a938dc66d..b10f93aa670507a190294092a54a77732a7ffbe5 100644 --- a/packages/component-user/typeDefs.js +++ b/packages/component-user/server/typeDefs.js @@ -27,7 +27,7 @@ module.exports = ` } extend type Mutation { - addUserAsAdmin(id:ID!, input: UserInput!): User + addUserAsAdmin(input: UserInput!): User editUserAsAdmin(id: ID!, input: UserInput!): User activateUserAsAdmin(id: ID!, input: ActivateUserInput): User } diff --git a/packages/component-user/user.js b/packages/component-user/server/user.js similarity index 100% rename from packages/component-user/user.js rename to packages/component-user/server/user.js diff --git a/packages/xpub-faraday/app/routes.js b/packages/xpub-faraday/app/routes.js index 95a60cdedf33e26ba516608667d3c78959b63bcc..cbc1a4804a6816c416e6bd83deff04b4aa891be5 100644 --- a/packages/xpub-faraday/app/routes.js +++ b/packages/xpub-faraday/app/routes.js @@ -11,6 +11,12 @@ import { UserProfilePage, ChangePasswordPage, } from 'pubsweet-components-faraday/src/components' + +import { + AdminRoute, + AdminUsers, + AdminDashboard, +} from 'pubsweet-component-user/app' import { ManuscriptPage } from 'pubsweet-component-manuscript/src/components' import DashboardPage from 'pubsweet-components-faraday/src/components/Dashboard' import LoginPage from 'pubsweet-components-faraday/src/components/Login/LoginPage' @@ -22,11 +28,6 @@ import { EQSDecisionPage, EQADecisionPage, } from 'pubsweet-components-faraday/src/components/UIComponents/' -import { - AdminUsers, - AdminRoute, - AdminDashboard, -} from 'pubsweet-components-faraday/src/components/Admin' import { ConfirmAccount, ReviewerSignUp, diff --git a/yarn.lock b/yarn.lock index 0800a7fa664a6c4a705bd6b8d6c6648f94854b68..5782fb361f52f2bcd630d7d2d82eda1d07e4acff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -278,6 +278,15 @@ lodash "^4.17.4" styled-components "^4.1.1" +"@pubsweet/ui-toolkit@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@pubsweet/ui-toolkit/-/ui-toolkit-2.0.3.tgz#673be0f68bfe3fb8c0a43401a3a032a6cfde75f7" + integrity sha512-zINDKK+A6kXllcYr8+hBdBij8pxF3uOWFGmQjctLBVHzgE6DTV0mPj2iv2WKezO1nE1EbJD5EyXqF4/72gQqQg== + dependencies: + color "^3.0.0" + lodash "^4.17.4" + styled-components "^4.1.1" + "@pubsweet/ui-toolkit@latest": version "1.0.0" resolved "https://registry.yarnpkg.com/@pubsweet/ui-toolkit/-/ui-toolkit-1.0.0.tgz#df05b54e7bbfabcb10c7afc2991752e1087d2298" @@ -337,6 +346,31 @@ redux-form "^7.0.3" styled-components "^4.1.1" +"@pubsweet/ui@^9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@pubsweet/ui/-/ui-9.0.3.tgz#aac4746d6febecd88935e9b2ab1c9d0581baab29" + integrity sha512-8mfvxzNwTZIM0lgzHniJEn/fiyW7W5LyJh2pAYpc+Su8AuCDAKOCpQjeJlgtYzpJuZMoYJrRxfTbuLIz3c7q1g== + dependencies: + "@pubsweet/ui-toolkit" "^2.0.3" + babel-jest "^21.2.0" + classnames "^2.2.5" + enzyme "^3.7.0" + enzyme-adapter-react-16 "^1.1.1" + invariant "^2.2.3" + lodash "^4.17.4" + moment "^2.22.1" + prop-types "^15.5.10" + react "^16.2.0" + react-dom "^16.2.0" + react-feather "^1.0.8" + react-redux "^5.0.2" + react-router-dom "^4.2.2" + react-tag-autocomplete "^5.5.0" + recompose "^0.26.0" + redux "^3.6.0" + redux-form "^7.0.3" + styled-components "^4.1.1" + "@types/async@2.0.49": version "2.0.49" resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.49.tgz#92e33d13f74c895cb9a7f38ba97db8431ed14bc0" @@ -5516,6 +5550,21 @@ formik@^1.3.2: tslib "^1.9.3" warning "^3.0.0" +formik@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/formik/-/formik-1.4.0.tgz#4261769f765dd41b7e791958fde7a08516d5920a" + integrity sha512-HOlb4cEgjTZ+5VMCYDlXt1r5Bt9wLhIH6uvJCAhJaIvqehmIM1RdzhYel8tCFPXzCcCx8QeZh3UcWKye5rsJmw== + dependencies: + create-react-context "^0.2.2" + deepmerge "^2.1.1" + hoist-non-react-statics "^2.5.5" + lodash "^4.17.11" + lodash-es "^4.17.11" + prop-types "^15.6.1" + react-fast-compare "^2.0.1" + tslib "^1.9.3" + warning "^3.0.0" + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -8240,6 +8289,11 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +lodash-es@^4.17.11: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0" + integrity sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q== + lodash-es@^4.17.3, lodash-es@^4.17.5, lodash-es@^4.2.1: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.5.tgz#9fc6e737b1c4d151d8f9cae2247305d552ce748f" @@ -11246,6 +11300,11 @@ react-fast-compare@^1.0.0: resolved "http://registry.npmjs.org/react-fast-compare/-/react-fast-compare-1.0.0.tgz#813a039155e49b43ceffe99528fe5e9d97a6c938" integrity sha512-dcQpdWr62flXQJuM8/bVEY5/10ad2SYBUafp8H4q4WHR3fTA/MMlp8mpzX12I0CCoEJc1P6QdiMg7U+7lFS6Rw== +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + react-feather@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-1.0.8.tgz#69b13d5c729949f194d33201dee91bab67fa31a2" @@ -14726,6 +14785,13 @@ xpub-validators@^0.0.5: dependencies: striptags "^3.1.0" +xpub-validators@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/xpub-validators/-/xpub-validators-0.0.6.tgz#42ebc2d722b9ec6801cc44b7177eaa71029c1514" + integrity sha512-zkayEdxC3NQERycoTu8YUUPNlBuN153ESbIax9exmgrO0yt4LG3be+FirXcbkgIPYmgPAB0nnhPofOCTMTJx1g== + dependencies: + striptags "^3.1.0" + xregexp@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"