diff --git a/packages/component-faraday-ui/src/ReviewersTable.js b/packages/component-faraday-ui/src/ReviewersTable.js index e031cddbe5662310219b6f21d8ee3e8b6781271b..74ddafc17049e76ed80fcc5b799f1f9091c1bec9 100644 --- a/packages/component-faraday-ui/src/ReviewersTable.js +++ b/packages/component-faraday-ui/src/ReviewersTable.js @@ -1,14 +1,16 @@ import React, { Fragment } from 'react' -import { get } from 'lodash' import styled from 'styled-components' -import { shouldUpdate } from 'recompose' import { th } from '@pubsweet/ui-toolkit' import { DateParser } from '@pubsweet/ui' +import { get, isEqual, orderBy } from 'lodash' +import { compose, shouldUpdate, withHandlers, withProps } from 'recompose' import { Label, PersonInvitation, Text } from '../' const ReviewersTable = ({ invitations, + getInvitationStatus, + renderAcceptedLabel, onResendReviewerInvite, onRevokeReviewerInvite, }) => @@ -40,7 +42,9 @@ const ReviewersTable = ({ 'person.lastName', )}`}</Text> {invitation.isAccepted && ( - <Text customId ml={1}>{`Reviewer ${index + 1}`}</Text> + <Text customId ml={1}> + {renderAcceptedLabel(index)} + </Text> )} </td> <td> @@ -49,23 +53,19 @@ const ReviewersTable = ({ </DateParser> </td> <td> - {invitation.respondedOn && ( - <Fragment> + <Fragment> + {invitation.respondedOn && ( <DateParser timestamp={invitation.respondedOn}> {timestamp => <Text>{timestamp}</Text>} </DateParser> - <Text ml={1} secondary> - ACCEPTED - </Text> - </Fragment> - )} + )} + <Text ml={invitation.respondedOn ? 1 : 0} secondary> + {getInvitationStatus(invitation)} + </Text> + </Fragment> </td> <td> - {invitation.respondedOn && ( - <DateParser timestamp={invitation.respondedOn}> - {timestamp => <Text>{timestamp}</Text>} - </DateParser> - )} + <div /> </td> <HiddenCell> {!invitation.hasAnswer && ( @@ -82,7 +82,33 @@ const ReviewersTable = ({ </Table> ) -export default shouldUpdate(() => false)(ReviewersTable) +const orderInvitations = i => { + if (!i.hasAnswer) return -1 + if (i.isAccepted) return 0 + return 1 +} + +export default compose( + shouldUpdate( + ({ invitations }, { invitations: nextInvitations }) => + !isEqual(invitations, nextInvitations), + ), + withProps(({ invitations = [] }) => ({ + invitations: orderBy(invitations, orderInvitations), + })), + withProps(({ invitations = [] }) => ({ + firstAccepted: invitations.findIndex(i => i.hasAnswer && i.isAccepted), + })), + withHandlers({ + renderAcceptedLabel: ({ firstAccepted, invitations }) => index => + `Reviewer ${index - firstAccepted + 1}`, + getInvitationStatus: () => ({ hasAnswer, isAccepted }) => { + if (!hasAnswer) return 'PENDING' + if (isAccepted) return 'ACCEPTED' + return 'DECLINED' + }, + }), +)(ReviewersTable) // #region styles const Table = styled.table` diff --git a/packages/components-faraday/src/components/SignUp/ReviewerDecline.js b/packages/components-faraday/src/components/SignUp/ReviewerDecline.js index 0b8522d14a13737583a2d80f650ec4fa35179dcd..65a3a8ed8f901048e7deb715b318115c42395ba1 100644 --- a/packages/components-faraday/src/components/SignUp/ReviewerDecline.js +++ b/packages/components-faraday/src/components/SignUp/ReviewerDecline.js @@ -1,68 +1,52 @@ import React from 'react' -import { connect } from 'react-redux' -import styled from 'styled-components' -import { th } from '@pubsweet/ui-toolkit' +import { get } from 'lodash' import { withJournal } from 'xpub-journal' -import { replace } from 'react-router-redux' import { compose, lifecycle } from 'recompose' +import { H2 } from '@pubsweet/ui' +import { + Row, + Text, + ActionLink, + ShadowedBox, +} from 'pubsweet-component-faraday-ui' import { redirectToError } from '../utils' -import { FormItems } from '../UIComponents' import { reviewerDecline } from '../../redux/reviewers' -const { RootContainer, Title } = FormItems +const ReviewerDecline = ({ journal }) => ( + <ShadowedBox center mt={2} width={60}> + <H2>Thank you for letting us know</H2> -const ReviewerDecline = ({ journal: { metadata: { email } } }) => ( - <RootContainer bordered> - <Title>Thank you for letting us know.</Title> - <div> - <Description> + <Row mt={2}> + <Text align="center"> We hope you will review for Hindawi in the future. If you want any more information, or would like to submit a review for this article, then please contact us at{' '} - <MailLink href={`mailto:${email}`} target="_blank"> - {email} - </MailLink>. - </Description> - </div> - </RootContainer> + <ActionLink to={`mailto:${get(journal, 'metadata.email')}`}> + {get(journal, 'metadata.email')} + </ActionLink>. + </Text> + </Row> + </ShadowedBox> ) export default compose( withJournal, - connect(null, { reviewerDecline, replace }), lifecycle({ componentDidMount() { const { + history, + fragmentId, collectionId, invitationId, invitationToken, - reviewerDecline, - replace, - fragmentId, } = this.props - reviewerDecline( + reviewerDecline({ + fragmentId, invitationId, collectionId, - fragmentId, invitationToken, - ).catch(redirectToError(replace)) + }).catch(redirectToError(history.replace)) }, }), )(ReviewerDecline) - -// #region styled-components -const MailLink = styled.a` - color: ${th('colorPrimary')}; - - &:visited { - color: ${th('colorTextPlaceholder')}; - } -` - -const Description = styled.span` - color: ${th('colorPrimary')}; - font-family: ${th('fontReading')}; - font-size: ${th('fontSizeBaseSmall')}; -` -// #endregion diff --git a/packages/components-faraday/src/components/SignUp/ReviewerInviteDecision.js b/packages/components-faraday/src/components/SignUp/ReviewerInviteDecision.js index c0d72b3e10d5ecaa808b74f1dfe17456a1ebff68..7a6fa96c4718a6dcb087ab8217bbcb9125d81d8e 100644 --- a/packages/components-faraday/src/components/SignUp/ReviewerInviteDecision.js +++ b/packages/components-faraday/src/components/SignUp/ReviewerInviteDecision.js @@ -1,140 +1,157 @@ import React from 'react' -import { get } from 'lodash' import { connect } from 'react-redux' -import { push, replace } from 'react-router-redux' +import { reduxForm } from 'redux-form' import { required, minChars } from 'xpub-validators' -import { reduxForm, SubmissionError } from 'redux-form' import { compose, withState, lifecycle } from 'recompose' import { loginUser } from 'pubsweet-component-login/actions' -import { Button, ValidatedField, TextField } from '@pubsweet/ui' - -import { redirectToError } from '../utils' -import { FormItems } from '../UIComponents' -import { reviewerDecision, setReviewerPassword } from '../../redux/reviewers' - -const { +import { Button, ValidatedField, H2, TextField, Spinner } from '@pubsweet/ui' +import { Row, - Err, - Title, + Item, + Text, Label, - Email, - RowItem, - Subtitle, - RootContainer, - FormContainer, -} = FormItems + ShadowedBox, + withFetching, +} from 'pubsweet-component-faraday-ui' + +import { redirectToError, passwordValidator } from '../utils' +import { reviewerDecision, setReviewerPassword } from '../../redux/reviewers' const agreeText = `You have been invited to review a manuscript on the Hindawi platform. Please set a password and proceed to the manuscript.` const declineText = `You have decline to work on a manuscript.` const PasswordField = input => <TextField {...input} type="password" /> const min8Chars = minChars(8) + const ReviewerInviteDecision = ({ agree, error, + isFetching, handleSubmit, errorMessage, reviewerEmail, + fetchingError, }) => ( - <RootContainer bordered> - <Title>Reviewer Invitation</Title> - <Subtitle>{agree === 'true' ? agreeText : declineText}</Subtitle> - <Email>{reviewerEmail}</Email> - {agree === 'true' && ( - <FormContainer onSubmit={handleSubmit}> - <Row> - <RowItem vertical> - <Label> Password </Label> - <ValidatedField - component={PasswordField} - name="password" - validate={[required, min8Chars]} - /> - </RowItem> - </Row> - {error && ( - <Row> - <RowItem> - <Err>Token expired or Something went wrong.</Err> - </RowItem> - </Row> - )} - <Row> - <Button primary type="submit"> - CONFIRM - </Button> - </Row> - </FormContainer> + <ShadowedBox center mt={2} width={60}> + <H2>Reviewer Invitation</H2> + <Text align="center" secondary> + {reviewerEmail} + </Text> + + <Row mt={2}> + <Text align="center">{agree === 'true' ? agreeText : declineText}</Text> + </Row> + + <Row mt={2}> + <Item vertical> + <Label required>Password</Label> + <ValidatedField + component={PasswordField} + name="password" + validate={[required, min8Chars]} + /> + </Item> + </Row> + + <Row mt={2}> + <Item vertical> + <Label required>Confirm password</Label> + <ValidatedField + component={PasswordField} + name="confirmPassword" + validate={[required]} + /> + </Item> + </Row> + + {fetchingError && ( + <Row mt={2}> + <Text align="center" error> + {fetchingError} + </Text> + </Row> )} - </RootContainer> + + <Row mt={2}> + {isFetching ? ( + <Spinner /> + ) : ( + <Button onClick={handleSubmit} primary size="medium"> + CONFIRM + </Button> + )} + </Row> + </ShadowedBox> ) export default compose( + withFetching, withState('reviewerEmail', 'setEmail', ''), connect(null, { - push, - replace, loginUser, - reviewerDecision, - setReviewerPassword, }), lifecycle({ componentDidMount() { const { agree, email, - replace, + history, setEmail, fragmentId, collectionId, invitationId, - reviewerDecision, } = this.props setEmail(email) if (agree === 'false') { - reviewerDecision(invitationId, collectionId, fragmentId, false).catch( - redirectToError(replace), - ) + reviewerDecision({ + fragmentId, + agree: false, + collectionId, + invitationId, + }).catch(redirectToError(history.replace)) } }, }), reduxForm({ form: 'invite-reviewer', + validate: passwordValidator, onSubmit: ( { password }, dispatch, { - push, email, token, - location, + setError, loginUser, fragmentId, + setFetching, collectionId, invitationId, - setReviewerPassword, }, - ) => + ) => { + setFetching(true) + setError('') setReviewerPassword({ email, token, password, }) .then(() => { + setError('') + setFetching(false) loginUser( - { username: email, password }, + { + username: email, + password, + }, `/projects/${collectionId}/versions/${fragmentId}/details?agree=${true}&invitationId=${invitationId}`, ) }) - .catch(error => { - const err = get(error, 'response') - if (err) { - const errorMessage = get(JSON.parse(err), 'error') - throw new SubmissionError({ - _error: errorMessage || 'Something went wrong', - }) - } - }), + .catch(() => { + setFetching(false) + setError('Something went wrong...') + }) + }, }), )(ReviewerInviteDecision) diff --git a/packages/components-faraday/src/components/UIComponents/ErrorPage.js b/packages/components-faraday/src/components/UIComponents/ErrorPage.js index fe9ffab27d2aca61eac794b502ec4b1ed68bfb74..ca1052e09a82b2a4f923a7b688f77bfcac8dd841 100644 --- a/packages/components-faraday/src/components/UIComponents/ErrorPage.js +++ b/packages/components-faraday/src/components/UIComponents/ErrorPage.js @@ -1,35 +1,21 @@ import React from 'react' -import { Button } from '@pubsweet/ui' -import styled from 'styled-components' -import { th } from '@pubsweet/ui-toolkit' +import { Button, H2 } from '@pubsweet/ui' +import { Row, ShadowedBox, Text } from 'pubsweet-component-faraday-ui' const ErrorPage = ({ location: { state }, history }) => ( - <Root> - <Title>{state}</Title> - <Button onClick={() => history.push('/')} primary> - Go to Dashboard - </Button> - </Root> -) - -export default ErrorPage + <ShadowedBox center mt={2} width={60}> + <H2>Error</H2> -// #region styles -const Root = styled.div` - color: ${th('colorText')}; - margin: 0 auto; - text-align: center; - width: 70vw; + <Row mt={2}> + <Text align="center">{state}</Text> + </Row> - a { - color: ${th('colorText')}; - } -` + <Row mt={2}> + <Button onClick={() => history.push('/')} primary> + Go to Dashboard + </Button> + </Row> + </ShadowedBox> +) -const Title = styled.div` - color: ${th('colorPrimary')}; - font-size: ${th('fontSizeHeading5')}; - font-family: ${th('fontHeading')}; - margin: calc(${th('subGridUnit')} * 2) auto; -` -// #endregion +export default ErrorPage diff --git a/packages/components-faraday/src/index.js b/packages/components-faraday/src/index.js index db4b6cd565c1db6e4afba19f44272d8200786c50..f2e244a4264d4b2de2337305c35fc60ce134aaa7 100644 --- a/packages/components-faraday/src/index.js +++ b/packages/components-faraday/src/index.js @@ -5,7 +5,6 @@ module.exports = { authors: () => require('./redux/authors').default, customError: () => require('./redux/errors').default, files: () => require('./redux/files').default, - reviewers: () => require('./redux/reviewers').default, technicalCheck: () => require('./redux/technicalCheck').default, }, }, diff --git a/packages/components-faraday/src/redux/reviewers.js b/packages/components-faraday/src/redux/reviewers.js index fdafed5c33010c2e569e9efc7bb830f8e9139300..7542b980da4cdb1aaab21256f0b85bca2cabfb3d 100644 --- a/packages/components-faraday/src/redux/reviewers.js +++ b/packages/components-faraday/src/redux/reviewers.js @@ -1,48 +1,13 @@ -import { get, orderBy } from 'lodash' +import { get } from 'lodash' import { selectCurrentUser } from 'xpub-selectors' import { - get as apiGet, create, remove, update, + get as apiGet, } from 'pubsweet-client/src/helpers/api' -import { orderReviewers } from './utils' - -const GET_REVIEWERS_REQUEST = 'GET_REVIEWERS_REQUEST' -const GET_REVIEWERS_ERROR = 'GET_REVIEWERS_ERROR' -const GET_REVIEWERS_SUCCESS = 'GET_REVIEWERS_SUCCESS' - -export const getReviewersRequest = () => ({ - type: GET_REVIEWERS_REQUEST, -}) - -export const getReviewersError = error => ({ - type: GET_REVIEWERS_ERROR, - error, -}) - -export const getReviewersSuccess = reviewers => ({ - type: GET_REVIEWERS_SUCCESS, - payload: { reviewers }, -}) - -// reviewer invite constants and action creators -const INVITE_REVIEWER_REQUEST = 'INVITE_REVIEWER_REQUEST' -const INVITE_REVIEWER_SUCCESS = 'INVITE_REVIEWER_SUCCESS' -const INVITE_REVIEWER_ERROR = 'INVITE_REVIEWER_ERROR' // reviewer decision constants and action creators -const REVIEWER_DECISION_REQUEST = 'REVIEWER_DECISION_REQUEST' -const REVIEWER_DECISION_ERROR = 'REVIEWER_DECISION_ERROR' -const REVIEWER_DECISION_SUCCESS = 'REVIEWER_DECISION_SUCCESS' - -const reviewerDecisionRequest = () => ({ type: REVIEWER_DECISION_REQUEST }) -const reviewerDecisionError = error => ({ - type: REVIEWER_DECISION_ERROR, - error, -}) -const reviewerDecisionSuccess = () => ({ type: REVIEWER_DECISION_SUCCESS }) - const initialState = { fetching: { decision: false, @@ -88,21 +53,10 @@ export const currentUserIsReviewer = (state, fragmentId) => { ) } -export const getCollectionReviewers = ( - collectionId, - fragmentId, -) => dispatch => { - dispatch(getReviewersRequest()) - return apiGet( +export const getCollectionReviewers = (collectionId, fragmentId) => dispatch => + apiGet( `/collections/${collectionId}/fragments/${fragmentId}/invitations?role=reviewer`, - ).then( - r => dispatch(getReviewersSuccess(orderBy(r, orderReviewers))), - err => { - dispatch(getReviewersError(err)) - throw err - }, ) -} // #endregion export const inviteReviewer = ({ reviewerData, collectionId, fragmentId }) => @@ -112,13 +66,8 @@ export const inviteReviewer = ({ reviewerData, collectionId, fragmentId }) => }) // #region Actions - invitations -export const setReviewerPassword = reviewerBody => dispatch => { - dispatch(reviewerDecisionRequest()) - return create(`/users/reset-password`, reviewerBody).then(r => { - dispatch(reviewerDecisionSuccess()) - return r - }) -} +export const setReviewerPassword = reviewerBody => + create(`/users/reset-password`, reviewerBody) export const revokeReviewer = ({ fragmentId, collectionId, invitationId }) => remove( @@ -140,113 +89,23 @@ export const reviewerDecision = ({ }, ) -export const reviewerDecline = ( - invitationId, - collectionId, +export const reviewerDecline = ({ fragmentId, + collectionId, + invitationId, invitationToken, -) => dispatch => { - dispatch(reviewerDecisionRequest()) - return update( +}) => + update( `/collections/${collectionId}/fragments/${fragmentId}/invitations/${invitationId}/decline`, { invitationToken, }, - ).then( - res => { - dispatch(reviewerDecisionSuccess()) - return res - }, - err => { - dispatch(reviewerDecisionError(err.message)) - throw err - }, ) -} // #endregion // #region Reducer export default (state = initialState, action = {}) => { switch (action.type) { - case GET_REVIEWERS_REQUEST: - return { - ...state, - fetching: { - ...state.fetching, - reviewers: true, - }, - reviewers: [], - } - case GET_REVIEWERS_ERROR: - return { - ...state, - fetching: { - ...state.fetching, - reviewers: false, - }, - error: action.error, - } - case GET_REVIEWERS_SUCCESS: - return { - ...state, - fetching: { - ...state.fetching, - reviewers: false, - }, - reviewers: action.payload.reviewers, - } - case INVITE_REVIEWER_REQUEST: - return { - ...state, - fetching: { - ...state.fetching, - invite: true, - }, - } - case INVITE_REVIEWER_SUCCESS: - return { - ...state, - fetching: { - ...state.fetching, - invite: false, - }, - error: null, - } - case INVITE_REVIEWER_ERROR: - return { - ...state, - fetching: { - ...state.fetching, - invite: false, - }, - error: action.error, - } - case REVIEWER_DECISION_REQUEST: - return { - ...state, - fetching: { - ...state.fetching, - decision: true, - }, - } - case REVIEWER_DECISION_ERROR: - return { - ...state, - fetching: { - ...state.fetching, - decision: false, - }, - error: action.error, - } - case REVIEWER_DECISION_SUCCESS: - return { - ...state, - fetching: { - ...state.fetching, - decision: false, - }, - error: null, - } case CLEAR_ERROR: { return { ...state, diff --git a/packages/styleguide/styleguide.config.js b/packages/styleguide/styleguide.config.js index 299ae2ec11754173694cb59a223d4b75483a6b6c..1a6fe7ba50ad201d0bc3ee0a474e8ac2c01d53d9 100644 --- a/packages/styleguide/styleguide.config.js +++ b/packages/styleguide/styleguide.config.js @@ -33,6 +33,7 @@ module.exports = { components: ['../component-faraday-ui/src/gridItems/[A-Z]*.js'], }, ], + serverPort: process.env.NODE_ENV !== 'production' ? 6060 : 3000, skipComponentsWithoutExample: true, pagePerSection: true, styleguideComponents: {