diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index ae7b54ace427f4e6af74297ad94dfcbefe76496a..5acb64f2ceef380248c7ff7a478ede58e9cc60a6 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -136,6 +136,17 @@ export const userNotConfirmed = ({ currentUser }) => !currentUserIs({ currentUser }, 'staff') && !get(currentUser, 'user.isConfirmed') +export const pendingReviewerInvitation = (state, fragmentId) => + chain(state) + .get(`fragments.${fragmentId}.invitations`, []) + .find( + inv => + inv.userId === get(state, 'currentUser.user.id', '') && + !inv.hasAnswer && + inv.role === 'reviewer', + ) + .value() + export const currentUserIsReviewer = (state, fragmentId) => { const currentUser = selectCurrentUser(state) const invitations = get(state, `fragments.${fragmentId}.invitations`, []) diff --git a/packages/component-faraday-ui/src/AuthorTagList.js b/packages/component-faraday-ui/src/AuthorTagList.js index ff585788d514a0a504b2a16ec7449601e3635e05..2d8268460f6b195f97862ad37951c025ae6915b1 100644 --- a/packages/component-faraday-ui/src/AuthorTagList.js +++ b/packages/component-faraday-ui/src/AuthorTagList.js @@ -45,7 +45,7 @@ const AuthorTagList = ({ authors = [], affiliationList, separator = `, `, - authorKey = 'email', + authorKey = 'id', withTooltip = false, withAffiliations = false, showAffiliation = false, diff --git a/packages/component-faraday-ui/src/manuscriptDetails/HandlingEditorAnswer.js b/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.js similarity index 58% rename from packages/component-faraday-ui/src/manuscriptDetails/HandlingEditorAnswer.js rename to packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.js index 53f4ca16822becf9cc9eeb0927ad6021ec45a9aa..b0f85ce73dad5312410b2161fadd3469497f691e 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/HandlingEditorAnswer.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.js @@ -1,9 +1,10 @@ import React from 'react' import { reduxForm } from 'redux-form' -import { get, has, capitalize } from 'lodash' import { required } from 'xpub-validators' -import { compose, withHandlers, withProps } from 'recompose' +import { get, has, capitalize } from 'lodash' +import { compose, withProps } from 'recompose' import { Button, RadioGroup, ValidatedField } from '@pubsweet/ui' +import { withModal } from 'pubsweet-component-modal/src/components' import { Row, @@ -11,7 +12,7 @@ import { Text, Label, Textarea, - OpenModal, + MultiAction, ContextualBox, RowOverrideAlert, withFetching, @@ -22,28 +23,28 @@ const options = [ { label: 'Decline', value: 'decline' }, ] -const HandlingEditorAnswer = ({ +const ResponseToInvitation = ({ + label, + title, toggle, expanded, - decision, isFetching, handleSubmit, onSubmitForm, shouldShowComments, + buttonLabel = 'RESPOND TO INVITATION', }) => ( <ContextualBox expanded={expanded} highlight - label="Respond to Editorial Invitation" + label={title} mb={2} scrollIntoView toggle={toggle} > <RowOverrideAlert justify="flex-start" ml={1} mt={1}> <Item vertical> - <Label required> - Do you agree to be the handling editor for this manuscript? - </Label> + <Label required>{label}</Label> <ValidatedField component={input => ( <RadioGroup inline name="decision" options={options} {...input} /> @@ -69,44 +70,34 @@ const HandlingEditorAnswer = ({ )} <Row justify="flex-end" mb={1} pr={1}> - <OpenModal - cancelText="Close" - confirmText={decision} - isFetching={isFetching} - onConfirm={modalProps => handleSubmit()(modalProps)} - title={`${decision} this invitation?`} - > - {showModal => ( - <Button onClick={onSubmitForm(showModal)} primary size="medium"> - RESPOND TO INVITATION - </Button> - )} - </OpenModal> + <Button onClick={handleSubmit} primary size="medium"> + {buttonLabel} + </Button> </Row> </ContextualBox> ) export default compose( withFetching, - withProps(({ formValues }) => ({ + withModal(({ isFetching, modalKey }) => ({ + modalKey, + isFetching, + modalComponent: MultiAction, + })), + withProps(({ formValues = {}, commentsOn }) => ({ disabled: !has(formValues, 'decision'), - decision: capitalize(get(formValues, 'decision')), - shouldShowComments: get(formValues, 'decision', 'agree') === 'decline', + shouldShowComments: get(formValues, 'decision', 'agree') === commentsOn, })), reduxForm({ - form: 'he-answer-invitation', + form: 'answer-invitation', destroyOnUnmount: false, - onSubmit: (values, dispatch, { onResponse, setFetching }) => modalProps => { - onResponse(values, { ...modalProps, setFetching }) - }, - }), - withHandlers({ - onSubmitForm: ({ disabled, handleSubmit }) => showModal => () => { - if (!disabled) { - showModal() - } else { - handleSubmit() - } + onSubmit: (values, dispatch, { showModal, onResponse, setFetching }) => { + showModal({ + title: `${capitalize(values.decision)} this invitation?`, + onConfirm: modalProps => { + onResponse(values, { ...modalProps, setFetching }) + }, + }) }, }), -)(HandlingEditorAnswer) +)(ResponseToInvitation) diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.md b/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.md new file mode 100644 index 0000000000000000000000000000000000000000..e7f3a21c9d5f65814365bd1e87054d925a0fb775 --- /dev/null +++ b/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.md @@ -0,0 +1,42 @@ +A Handling Editor response to an invitation. + +```js +const formValues = { + decision: 'accept', +} +;<RemoteOpener> + {({ toggle, expanded }) => ( + <ResponseToInvitation + commentsOn="decline" + expanded={expanded} + label="Do you agree to be the handling editor for this manuscript?" + formValues={formValues} + onResponse={(values, { setFetching }) => { + console.log('on response: ', values) + setFetching(true) + }} + title="Respond to Editorial Invitation" + toggle={toggle} + /> + )} +</RemoteOpener> +``` + +A Reviewer response to an invitation. + +```js +<RemoteOpener> + {({ toggle, expanded }) => ( + <ResponseToInvitation + expanded={expanded} + label="Do you agree to review this manuscript?" + onResponse={(values, { setFetching }) => { + console.log('on response: ', values) + setFetching(true) + }} + title="Respond to Invitation to Review" + toggle={toggle} + /> + )} +</RemoteOpener> +``` diff --git a/packages/component-faraday-ui/src/manuscriptDetails/index.js b/packages/component-faraday-ui/src/manuscriptDetails/index.js index f19644c15e01a55ec224789fa52896398f7a4afd..c32284d507c22c2d5d4f105916caefc456ef3ea4 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/index.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/index.js @@ -1,4 +1,3 @@ -export { default as HandlingEditorAnswer } from './HandlingEditorAnswer' export { default as ManuscriptDetailsTop } from './ManuscriptDetailsTop' export { default as ManuscriptVersion } from './ManuscriptVersion' export { default as ManuscriptHeader } from './ManuscriptHeader' @@ -7,3 +6,4 @@ export { default as ManuscriptFileList } from './ManuscriptFileList' export { default as ManuscriptFileSection } from './ManuscriptFileSection' export { default as ManuscriptAssignHE } from './ManuscriptAssignHE' export { default as ManuscriptEicDecision } from './ManuscriptEicDecision' +export { default as ResponseToInvitation } from './ResponseToInvitation' diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index d5dc9fe1283200362165b066eb17153234ec756b..e33c34c50a599b8b990deb66f3b006de864023c7 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -7,9 +7,9 @@ import { ManuscriptHeader, ManuscriptAssignHE, ManuscriptMetadata, - HandlingEditorAnswer, ManuscriptDetailsTop, ManuscriptEicDecision, + ResponseToInvitation, paddingHelper, } from 'pubsweet-component-faraday-ui' @@ -50,6 +50,9 @@ const ManuscriptLayout = ({ invitationsWithReviewers, onResendReviewerInvite, onRevokeReviewerInvite, + toggleReviewerResponse, + reviewerResponseExpanded, + onReviewerResponse, }) => ( <Root pb={1}> {!isEmpty(collection) && !isEmpty(fragment) ? ( @@ -82,14 +85,27 @@ const ManuscriptLayout = ({ /> {get(currentUser, 'isInvitedHE', false) && ( - <HandlingEditorAnswer + <ResponseToInvitation + commentsOn="decline" expanded={heResponseExpanded} - formValues={formValues.heInvitation} + formValues={formValues.responseToInvitation} + label="Do you agree to be the handling editor for this manuscript?" onResponse={onHEResponse} + title="Respond to Editorial Invitation" toggle={toggleHEResponse} /> )} + {get(currentUser, 'isInvitedToReview', false) && ( + <ResponseToInvitation + expanded={reviewerResponseExpanded} + label="Do you agree to review this manuscript?" + onResponse={onReviewerResponse} + title="Respond to Invitation to Review" + toggle={toggleReviewerResponse} + /> + )} + <ManuscriptAssignHE assignHE={assignHE} currentUser={currentUser} diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js index b928e99ebce45ed1993264ed16394029992f3f6b..655284ee3e9bbff207e7c7b37c3051e1561941f5 100644 --- a/packages/component-manuscript/src/components/ManuscriptPage.js +++ b/packages/component-manuscript/src/components/ManuscriptPage.js @@ -43,6 +43,7 @@ import { currentUserIsReviewer, canMakeRecommendation, parseCollectionDetails, + pendingReviewerInvitation, canOverrideTechnicalChecks, getInvitationsWithReviewersForFragment, } from 'pubsweet-component-faraday-selectors' @@ -85,6 +86,10 @@ export default compose( selectCollection(state, match.params.project), ), pendingHEInvitation: pendingHEInvitation(state, match.params.project), + pendingReviewerInvitation: pendingReviewerInvitation( + state, + match.params.version, + ), editorialRecommendations: selectEditorialRecommendations( state, match.params.version, @@ -94,7 +99,6 @@ export default compose( replace, getSignedUrl, clearCustomError, - reviewerDecision, assignHandlingEditor, createRecommendation, revokeHandlingEditor, @@ -107,15 +111,23 @@ export default compose( connect( ( state, - { pendingHEInvitation, currentUser, match, collection, fragment }, + { + match, + fragment, + collection, + currentUser, + pendingHEInvitation, + pendingReviewerInvitation, + }, ) => ({ currentUser: { ...currentUser, token: getUserToken(state), - isEIC: currentUserIs(state, 'adminEiC'), isHE: currentUserIs(state, 'isHE'), + isEIC: currentUserIs(state, 'adminEiC'), isReviewer: currentUserIsReviewer(state), isInvitedHE: !isUndefined(pendingHEInvitation), + isInvitedToReview: !isUndefined(pendingReviewerInvitation), permissions: { canAssignHE: canAssignHE(state, match.params.project), canInviteReviewers: canInviteReviewers(state, collection), @@ -136,7 +148,7 @@ export default compose( }, formValues: { eicDecision: getFormValues('eic-decision')(state), - heInvitation: getFormValues('he-answer-invitation')(state), + responseToInvitation: getFormValues('answer-invitation')(state), }, invitationsWithReviewers: getInvitationsWithReviewersForFragment( state, @@ -251,6 +263,35 @@ export default compose( setModalError('Something went wrong...') }) }, + onReviewerResponse: ({ + history, + fragment, + collection, + fetchUpdatedCollection, + pendingReviewerInvitation, + }) => (values, { hideModal, setModalError, setFetching }) => { + const isAccepted = get(values, 'decision', 'decline') === 'accept' + setFetching(true) + reviewerDecision({ + agree: isAccepted, + fragmentId: fragment.id, + collectionId: collection.id, + invitationId: pendingReviewerInvitation.id, + }) + .then(() => { + setFetching(false) + hideModal() + if (isAccepted) { + fetchUpdatedCollection() + } else { + history.replace('/') + } + }) + .catch(() => { + setFetching(false) + setModalError('Something went wrong...') + }) + }, onInviteReviewer: ({ collection, fragment, fetchUpdatedCollection }) => ( values, { hideModal, setModalError, setFetching }, @@ -325,6 +366,10 @@ export default compose( toggleHEResponse: toggle, heResponseExpanded: expanded, })), + fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({ + toggleReviewerResponse: toggle, + reviewerResponseExpanded: expanded, + })), lifecycle({ componentDidMount() { const { @@ -332,13 +377,11 @@ export default compose( replace, history, location, - getFragment, - getCollection, - reviewerDecision, setEditorInChief, clearCustomError, hasManuscriptFailure, - currentUser: { isInvitedHE }, + fetchUpdatedCollection, + currentUser: { isInvitedHE, isInvitedToReview }, } = this.props if (hasManuscriptFailure) { history.push('/not-found') @@ -350,11 +393,8 @@ export default compose( const { agree, invitationId } = parseSearchParams(location.search) if (agree === 'true') { replace(location.pathname) - reviewerDecision(invitationId, collectionId, fragmentId, true) - .then(() => { - getCollection({ id: collectionId }) - getFragment({ id: collectionId }, { id: fragmentId }) - }) + reviewerDecision({ invitationId, collectionId, fragmentId }) + .then(fetchUpdatedCollection) .catch(redirectToError(replace)) } @@ -365,6 +405,10 @@ export default compose( if (isInvitedHE) { this.props.toggleHEResponse() } + + if (isInvitedToReview) { + this.props.toggleReviewerResponse() + } }, }), withProps(({ fragment }) => ({ diff --git a/packages/component-manuscript/src/redux/editors.js b/packages/component-manuscript/src/redux/editors.js index 794f4266b2b07ebe31d66f3300895fb7c54596c8..d5f7a8e70c2eee905bc2c7d77999362b816bdf80 100644 --- a/packages/component-manuscript/src/redux/editors.js +++ b/packages/component-manuscript/src/redux/editors.js @@ -84,10 +84,10 @@ export const revokeHandlingEditor = ({ } export const handlingEditorDecision = ({ - invitationId, - collectionId, - isAccepted, reason, + isAccepted, + collectionId, + invitationId, }) => update(`/collections/${collectionId}/invitations/${invitationId}`, { isAccepted, diff --git a/packages/components-faraday/src/redux/reviewers.js b/packages/components-faraday/src/redux/reviewers.js index 204911271f2ae06fc53298afd4bbe7912705a493..fdafed5c33010c2e569e9efc7bb830f8e9139300 100644 --- a/packages/components-faraday/src/redux/reviewers.js +++ b/packages/components-faraday/src/redux/reviewers.js @@ -127,29 +127,18 @@ export const revokeReviewer = ({ fragmentId, collectionId, invitationId }) => // #endregion // #region Actions - decision -export const reviewerDecision = ( - invitationId, - collectionId, +export const reviewerDecision = ({ fragmentId, agree = true, -) => dispatch => { - dispatch(reviewerDecisionRequest()) - return update( + collectionId, + invitationId, +}) => + update( `/collections/${collectionId}/fragments/${fragmentId}/invitations/${invitationId}`, { isAccepted: agree, }, - ).then( - res => { - dispatch(reviewerDecisionSuccess()) - return res - }, - err => { - dispatch(reviewerDecisionError(err.message)) - throw err - }, ) -} export const reviewerDecline = ( invitationId,