diff --git a/packages/component-fixture-manager/src/fixtures/collections.js b/packages/component-fixture-manager/src/fixtures/collections.js index 73aab63ed1139cc27ae14df932c9c49ce855346e..ead1ed38cabd8e5eaa615a1fa7a9e5b84d6d3052 100644 --- a/packages/component-fixture-manager/src/fixtures/collections.js +++ b/packages/component-fixture-manager/src/fixtures/collections.js @@ -1,6 +1,6 @@ const Chance = require('chance') const { user, handlingEditor, answerHE } = require('./userData') -const { fragment, newVersion } = require('./fragments') +const { fragment } = require('./fragments') const { standardCollID } = require('./collectionIDs') const chance = new Chance() @@ -9,7 +9,7 @@ const collections = { id: standardCollID, title: chance.sentence(), type: 'collection', - fragments: [fragment.id, newVersion.id], + fragments: [fragment.id], owners: [user.id], save: jest.fn(), invitations: [ diff --git a/packages/component-fixture-manager/src/fixtures/fragments.js b/packages/component-fixture-manager/src/fixtures/fragments.js index 10fcf908ed077f7ecda22e87cb745af8b0a539f1..bf52d33fa37f801d591111e077e6fac2f1eedcb8 100644 --- a/packages/component-fixture-manager/src/fixtures/fragments.js +++ b/packages/component-fixture-manager/src/fixtures/fragments.js @@ -115,45 +115,23 @@ const fragments = { save: jest.fn(() => fragments.fragment), owners: [user.id], type: 'fragment', - }, - newVersion: { - id: chance.guid(), - collectionId: standardCollID, - metadata: { - title: chance.sentence(), - abstract: chance.paragraph(), - }, - authors: [ - { - email: chance.email(), - id: submittingAuthor.id, - isSubmitting: true, - isCorresponding: false, - }, - ], - invitations: [ - { - id: chance.guid(), - role: 'reviewer', - hasAnswer: false, - isAccepted: false, - userId: reviewer.id, - invitedOn: chance.timestamp(), - respondedOn: null, + revision: { + collectionId: standardCollID, + metadata: { + title: chance.sentence(), + abstract: chance.paragraph(), }, - { - id: chance.guid(), - role: 'reviewer', - hasAnswer: true, - isAccepted: false, - userId: answerReviewer.id, - invitedOn: chance.timestamp(), - respondedOn: chance.timestamp(), - }, - ], - save: jest.fn(() => fragments.fragment), - owners: [user.id], - type: 'fragment', + authors: [ + { + email: chance.email(), + id: submittingAuthor.id, + isSubmitting: true, + isCorresponding: false, + }, + ], + owners: [user.id], + type: 'fragment', + }, }, noParentFragment: { id: chance.guid(), diff --git a/packages/component-fixture-manager/src/helpers/Model.js b/packages/component-fixture-manager/src/helpers/Model.js index 9ff60517c6f1ce111f22a5a4f1e5369737c0a9c6..2edf2e726a863bc6f4ffd38ca806e4b2ab3ccec4 100644 --- a/packages/component-fixture-manager/src/helpers/Model.js +++ b/packages/component-fixture-manager/src/helpers/Model.js @@ -1,5 +1,6 @@ const UserMock = require('../mocks/User') const TeamMock = require('../mocks/Team') +const FragmentMock = require('../mocks/Fragment') const notFoundError = new Error() notFoundError.name = 'NotFoundError' @@ -12,9 +13,7 @@ const build = fixtures => { find: jest.fn(id => findMock(id, 'collections', fixtures)), }, Team: {}, - Fragment: { - find: jest.fn(id => findMock(id, 'fragments', fixtures)), - }, + Fragment: {}, } UserMock.find = jest.fn(id => findMock(id, 'users', fixtures)) @@ -33,8 +32,12 @@ const build = fixtures => { ) TeamMock.all = jest.fn(() => Object.values(fixtures.teams)) + FragmentMock.find = jest.fn(id => findMock(id, 'fragments', fixtures)) + models.User = UserMock models.Team = TeamMock + models.Fragment = FragmentMock + return models } diff --git a/packages/component-fixture-manager/src/mocks/Fragment.js b/packages/component-fixture-manager/src/mocks/Fragment.js new file mode 100644 index 0000000000000000000000000000000000000000..9c938ae733683de3befff59ae55c344b6a3dadde --- /dev/null +++ b/packages/component-fixture-manager/src/mocks/Fragment.js @@ -0,0 +1,20 @@ +/* eslint-disable func-names-any */ + +function Fragment(properties) { + this.type = 'fragment' + this.id = properties.id + this.collectionId = properties.collectionId + this.metadata = properties.metadata + this.recommendations = properties.recommendations + this.authors = properties.authors + this.invitations = properties.invitations + this.owners = properties.owners + this.revision = properties.revision +} + +Fragment.prototype.save = jest.fn(function saveFragment() { + this.id = '111222' + return Promise.resolve(this) +}) + +module.exports = Fragment diff --git a/packages/component-manuscript-manager/src/routes/fragments/patch.js b/packages/component-manuscript-manager/src/routes/fragments/patch.js index 9dd7cc27321b993b62c3d3d0e66ba15ea8774028..92cbaf906c43c46c6b2ef0804a647c32a9d10e92 100644 --- a/packages/component-manuscript-manager/src/routes/fragments/patch.js +++ b/packages/component-manuscript-manager/src/routes/fragments/patch.js @@ -40,6 +40,12 @@ module.exports = models => async (req, res) => { const collectionHelper = new Collection({ collection }) const fragmentHelper = new Fragment({ fragment }) + const heRecommendation = fragmentHelper.getLatestHERequestToRevision() + if (!heRecommendation) { + return res.status(400).json({ + error: 'No Handling Editor request to revision has been found.', + }) + } const userHelper = new User({ UserModel: models.User }) const newFragmentBody = { @@ -47,6 +53,7 @@ module.exports = models => async (req, res) => { ...fragment.revision, invitations: await fragmentHelper.getInvitationsForSubmittingReviewers(), version: fragment.version + 1, + created: new Date(), } let newFragment = new models.Fragment(newFragmentBody) @@ -59,8 +66,6 @@ module.exports = models => async (req, res) => { delete fragment.revision fragment.save() - const heRecommendation = fragmentHelper.getLatestHERequestToRevision() - if (heRecommendation.recommendation === 'major') { const reviewerIds = newFragment.invitations.map(inv => { const { userId } = inv @@ -112,12 +117,6 @@ module.exports = models => async (req, res) => { }), ) - if (!heRecommendation) { - return res.status(400).json({ - error: 'No Handling Editor request to revision has been found.', - }) - } - collectionHelper.updateStatusByRecommendation({ recommendation: heRecommendation.recommendation, }) diff --git a/packages/component-manuscript-manager/src/tests/fragments/patch.test.js b/packages/component-manuscript-manager/src/tests/fragments/patch.test.js index b5afab0749a5c28f77bee042b30aeee90f179345..36f8edad9c27e972390b0f798fac21305c150252 100644 --- a/packages/component-manuscript-manager/src/tests/fragments/patch.test.js +++ b/packages/component-manuscript-manager/src/tests/fragments/patch.test.js @@ -27,7 +27,7 @@ describe('Patch fragments route handler', () => { it('should return success when the parameters are correct', async () => { const { user } = testFixtures.users const { collection } = testFixtures.collections - const { newVersion } = testFixtures.fragments + const { fragment } = testFixtures.fragments const res = await requests.sendRequest({ body, userId: user.id, @@ -36,7 +36,7 @@ describe('Patch fragments route handler', () => { path, params: { collectionId: collection.id, - fragmentId: newVersion.id, + fragmentId: fragment.id, }, }) @@ -87,10 +87,6 @@ describe('Patch fragments route handler', () => { const { user } = testFixtures.users const { fragment } = testFixtures.fragments const { collection } = testFixtures.collections - // const collection = { - // ...fCollection, - // fragments: [...fCollection.fragments, '123'], - // } fragment.recommendations.length = 0 const res = await requests.sendRequest({ @@ -112,19 +108,19 @@ describe('Patch fragments route handler', () => { ) }) it('should return an error when the request user is not the owner', async () => { - const { author } = testFixtures.users - const { newVersion } = testFixtures.fragments + const { handlingEditor } = testFixtures.users + const { fragment } = testFixtures.fragments const { collection } = testFixtures.collections const res = await requests.sendRequest({ body, - userId: author.id, + userId: handlingEditor.id, models, route, path, params: { collectionId: collection.id, - fragmentId: newVersion.id, + fragmentId: fragment.id, }, }) @@ -132,11 +128,11 @@ describe('Patch fragments route handler', () => { const data = JSON.parse(res._getData()) expect(data.error).toEqual('Unauthorized.') }) - it('should return an error when no previous version exists', async () => { + it('should return an error when no revision exists', async () => { const { user } = testFixtures.users const { fragment } = testFixtures.fragments const { collection } = testFixtures.collections - collection.fragments.length = 1 + delete fragment.revision const res = await requests.sendRequest({ body, @@ -152,6 +148,6 @@ describe('Patch fragments route handler', () => { expect(res.statusCode).toBe(400) const data = JSON.parse(res._getData()) - expect(data.error).toEqual('No previous version has been found.') + expect(data.error).toEqual('No revision has been found.') }) }) diff --git a/packages/components-faraday/src/components/UserProfile/AccountDetails.js b/packages/components-faraday/src/components/UserProfile/AccountDetails.js index 5932cb212188af6b01e50312c1fa16c452d798d5..768c8a46ecafb78fb2b7ab543e18792365d92b03 100644 --- a/packages/components-faraday/src/components/UserProfile/AccountDetails.js +++ b/packages/components-faraday/src/components/UserProfile/AccountDetails.js @@ -6,7 +6,7 @@ import { compose, withHandlers, withState } from 'recompose' import AccountDetailsCard from './AccountDetailsCard' import AccountDetailsEdit from './AccountDetailsEdit' -const AccountDetails = ({ user, isEdit, setEditMode, journal }) => ( +const AccountDetails = ({ user, isEdit, setEditMode, journal, history }) => ( <Root> {isEdit ? ( <AccountDetailsEdit @@ -16,6 +16,7 @@ const AccountDetails = ({ user, isEdit, setEditMode, journal }) => ( /> ) : ( <AccountDetailsCard + history={history} journal={journal} setEditMode={setEditMode} user={user} diff --git a/packages/components-faraday/src/components/UserProfile/AccountDetailsCard.js b/packages/components-faraday/src/components/UserProfile/AccountDetailsCard.js index db9172efa60d07a2951ffc92d505042d8f593f7f..22dadc2aa731f7a0406cc81ff7e968b736bc709e 100644 --- a/packages/components-faraday/src/components/UserProfile/AccountDetailsCard.js +++ b/packages/components-faraday/src/components/UserProfile/AccountDetailsCard.js @@ -13,6 +13,7 @@ import { getUserTitle } from '../utils' const AccountDetailsCard = ({ user: { firstName = '', lastName = '', affiliation = '-', title = '' }, journal, + history, setEditMode, }) => ( <Fragment> @@ -48,7 +49,9 @@ const AccountDetailsCard = ({ <Row noMargin> <RowItem> <LinkText onClick={() => setEditMode(true)}>Edit details</LinkText> - <LinkText>Change Password</LinkText> + <LinkText onClick={() => history.push('/profile/change-password')}> + Change Password + </LinkText> </RowItem> </Row> </Fragment> diff --git a/packages/components-faraday/src/components/UserProfile/ChangePasswordPage.js b/packages/components-faraday/src/components/UserProfile/ChangePasswordPage.js new file mode 100644 index 0000000000000000000000000000000000000000..8405473a825dd6ea6de8b55e85e6794e0fd7be37 --- /dev/null +++ b/packages/components-faraday/src/components/UserProfile/ChangePasswordPage.js @@ -0,0 +1,98 @@ +import React from 'react' +import { compose } from 'recompose' +import { connect } from 'react-redux' +import { reduxForm } from 'redux-form' +import styled from 'styled-components' +import { required } from 'xpub-validators' +import { selectCurrentUser } from 'xpub-selectors' +import { Button, th, TextField, ValidatedField } from '@pubsweet/ui' +import { + onSubmitChangePassword as onSubmit, + changePasswordValidator, +} from '../utils' + +import { Row, RowItem, Label, Title, Err } from '../UIComponents/FormItems' + +const PasswordField = input => <TextField {...input} type="password" /> + +const ChangePassword = ({ history, handleSubmit, error }) => ( + <Root> + <Form onSubmit={handleSubmit}> + <Row> + <RowItem vertical> + <Title>Change Password</Title> + </RowItem> + </Row> + <Row> + <RowItem vertical> + <Label>Current Password *</Label> + <ValidatedField + component={PasswordField} + name="password" + validate={[required]} + /> + </RowItem> + </Row> + <Row> + <RowItem vertical> + <Label>New Password *</Label> + <ValidatedField + component={PasswordField} + name="newPassword" + validate={[required]} + /> + </RowItem> + </Row> + <Row> + <RowItem vertical> + <Label>Re-type password *</Label> + <ValidatedField + component={PasswordField} + name="confirmNewPassword" + validate={[required]} + /> + </RowItem> + </Row> + {error && ( + <Row> + <RowItem> + <Err>{error}</Err> + </RowItem> + </Row> + )} + <Row /> + <Row> + <Button onClick={history.goBack}>Cancel</Button> + <Button primary type="submit"> + Update + </Button> + </Row> + </Form> + </Root> +) + +export default compose( + connect(state => ({ + user: selectCurrentUser(state), + })), + reduxForm({ + form: 'changePassword', + onSubmit, + validate: changePasswordValidator, + }), +)(ChangePassword) + +// #region styles +const Root = styled.div` + display: flex; + flex-direction: column; + max-width: 30em; + margin: 0 auto; +` +const Form = styled.form` + background-color: ${th('colorBackground')}; + padding: ${th('gridUnit')}; + border: ${th('borderDefault')}; +` + +// #endregion diff --git a/packages/components-faraday/src/components/UserProfile/UserProfilePage.js b/packages/components-faraday/src/components/UserProfile/UserProfilePage.js index 75f207cfbe9f5f499f07316e96b1c2ce95052a2d..64dd9d28ea62c185a147e141bf307b7fb3d0d084 100644 --- a/packages/components-faraday/src/components/UserProfile/UserProfilePage.js +++ b/packages/components-faraday/src/components/UserProfile/UserProfilePage.js @@ -19,7 +19,7 @@ const UserProfilePage = ({ history, user }) => ( title="Account Settings" underlined /> - <AccountDetails user={user} /> + <AccountDetails history={history} user={user} /> <EmailNotifications subscribed={get(user, 'subscription')} /> <LinkOrcID orcid={get(user, 'orcid')} /> </Root> diff --git a/packages/components-faraday/src/components/index.js b/packages/components-faraday/src/components/index.js index 54fdcb5734f6e26063cb699cc36f1a125707fdc1..ddc29a62c5815c67a17f33fafa4d8cdc1711077d 100644 --- a/packages/components-faraday/src/components/index.js +++ b/packages/components-faraday/src/components/index.js @@ -9,6 +9,7 @@ export { default as AuthorList } from './AuthorList/AuthorList' export { default as withVersion } from './Dashboard/withVersion.js' export { default as SortableList } from './SortableList/SortableList' export { default as UserProfilePage } from './UserProfile/UserProfilePage' +export { default as ChangePasswordPage } from './UserProfile/ChangePasswordPage' export { Decision } export { Components } diff --git a/packages/components-faraday/src/components/utils.js b/packages/components-faraday/src/components/utils.js index e6dbf37dfb89054bb010ff4761149584eed08515..e791983b430f03dc3936ed552c02c846ad6f6534 100644 --- a/packages/components-faraday/src/components/utils.js +++ b/packages/components-faraday/src/components/utils.js @@ -98,6 +98,20 @@ export const passwordValidator = values => { return errors } +export const changePasswordValidator = values => { + const errors = {} + if (!values.password) { + errors.password = 'Required' + } + if (!values.newPassword) { + errors.newPassword = 'Required' + } else if (values.newPassword !== values.confirmNewPassword) { + errors.confirmNewPassword = 'Password mismatched' + } + + return errors +} + export const parseSearchParams = url => { const params = new URLSearchParams(url) const parsedObject = {} @@ -124,6 +138,7 @@ export const parseUpdateUser = values => { return pick(values, valuesToSave) } +// TODO: move to a dataservice export const onSubmitUser = (values, dispatch, { setEditMode }) => update(`/users/${values.id}`, parseUpdateUser(values)) .then(user => { @@ -131,3 +146,11 @@ export const onSubmitUser = (values, dispatch, { setEditMode }) => dispatch(actions.getCurrentUser()) }) .catch(handleFormError) + +// Temporary till API implementation +export const onSubmitChangePassword = (values, dispatch, { history, user }) => + update(`/users/${values.id}`, parseUpdateUser(values)) + .then(() => { + history.goBack() + }) + .catch(handleFormError) diff --git a/packages/xpub-faraday/app/routes.js b/packages/xpub-faraday/app/routes.js index 77dff3d32b1d050dcbb4dee794d1d9d913416960..826ed981b7f01bdab32619dac5de12f1d87e7124 100644 --- a/packages/xpub-faraday/app/routes.js +++ b/packages/xpub-faraday/app/routes.js @@ -3,7 +3,10 @@ import { Route, Switch } from 'react-router-dom' import { AuthenticatedComponent } from 'pubsweet-client' import { Wizard } from 'pubsweet-component-wizard/src/components' -import { UserProfilePage } from 'pubsweet-components-faraday/src/components' +import { + UserProfilePage, + ChangePasswordPage, +} from 'pubsweet-components-faraday/src/components' 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' @@ -83,6 +86,11 @@ const Routes = () => ( <Route component={ConfirmAccount} exact path="/confirm-signup" /> <PrivateRoute component={DashboardPage} exact path="/" /> <PrivateRoute component={UserProfilePage} exact path="/profile" /> + <PrivateRoute + component={ChangePasswordPage} + exact + path="/profile/change-password" + /> <PrivateRoute component={ConfirmationPage} exact