From f4f59a2485bb02ca36d0fd6254feea629ccbb32c Mon Sep 17 00:00:00 2001 From: Bogdan Cochior <bogdan.cochior@thinslices.com> Date: Wed, 15 Aug 2018 10:34:12 +0300 Subject: [PATCH] feat(eqa): implement EQA decision page --- .../component-faraday-selectors/src/index.js | 2 +- .../notifications/notifications.js | 4 +- .../UIComponents/EQADecisionPage.js | 231 ++++++++++++++++++ .../UIComponents/EQSDecisionPage.js | 4 +- .../src/components/UIComponents/index.js | 1 + .../src/redux/technicalCheck.js | 13 +- packages/xpub-faraday/app/routes.js | 2 + packages/xpub-faraday/config/default.js | 2 +- packages/xpub-faraday/config/validations.js | 1 + 9 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 packages/components-faraday/src/components/UIComponents/EQADecisionPage.js diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index 82d6b27ce..199161edf 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -79,7 +79,7 @@ export const canMakeDecision = (state, collection, fragment = {}) => { return isEIC && canMakeDecisionStatuses.includes(status) } -const canEditManuscriptStatuses = ['draft', 'technicalChecks'] +const canEditManuscriptStatuses = ['draft', 'technicalChecks', 'inQA'] export const canEditManuscript = (state, collection, fragment = {}) => { if (fragment.id !== last(collection.fragments)) return false const status = get(collection, 'status') diff --git a/packages/component-manuscript-manager/src/routes/technicalChecks/notifications/notifications.js b/packages/component-manuscript-manager/src/routes/technicalChecks/notifications/notifications.js index bff64b56e..e63386ac9 100644 --- a/packages/component-manuscript-manager/src/routes/technicalChecks/notifications/notifications.js +++ b/packages/component-manuscript-manager/src/routes/technicalChecks/notifications/notifications.js @@ -40,11 +40,13 @@ module.exports = { const subjectBaseText = `${collection.customId}: Manuscript` const userHelper = new User({ UserModel }) + const subject = `${subjectBaseText} ${!agree && + 'Not '}Passed Technical Checks` const email = new Email({ type: 'user', content: { - subject: `${subjectBaseText} Passed Technical Checks`, + subject, signatureName: 'EQA Team', ctaLink: services.createUrl( baseUrl, diff --git a/packages/components-faraday/src/components/UIComponents/EQADecisionPage.js b/packages/components-faraday/src/components/UIComponents/EQADecisionPage.js new file mode 100644 index 000000000..c1fb89f34 --- /dev/null +++ b/packages/components-faraday/src/components/UIComponents/EQADecisionPage.js @@ -0,0 +1,231 @@ +import React from 'react' +import { isEmpty } from 'lodash' +import { connect } from 'react-redux' +import { Button } from '@pubsweet/ui' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { + compose, + withState, + lifecycle, + withHandlers, + setDisplayName, +} from 'recompose' + +import { + withModal, + ConfirmationModal, +} from 'pubsweet-component-modal/src/components' + +import { Err, Subtitle } from './FormItems' +import { parseSearchParams } from '../utils' +import { + technicalDecision, + technicalCheckFetching, +} from '../../redux/technicalCheck' + +const EQADecisionPage = ({ + params, + showEQAModal, + errorMessage, + successMessage, +}) => ( + <Root> + <Title> + Take a decision for manuscript <b>{params.customId}</b>. + </Title> + {errorMessage && <Err>{errorMessage}</Err>} + {successMessage && <Subtitle>{successMessage}</Subtitle>} + {isEmpty(errorMessage) && + isEmpty(successMessage) && ( + <ButtonContainer> + <Button onClick={showEQAModal(false)}>RETURN TO EiC</Button> + <Button onClick={showEQAModal(true)} primary> + ACCEPT + </Button> + </ButtonContainer> + )} + </Root> +) + +const DeclineModal = compose( + withState('reason', 'setReason', ''), + withHandlers({ + changeReason: ({ setReason }) => e => { + setReason(e.target.value) + }, + }), +)(({ reason, changeReason, hideModal, onConfirm, modalError }) => ( + <DeclineRoot> + <span>Return Manuscript to Editor in Chief</span> + <textarea + onChange={changeReason} + placeholder="Return reason*" + value={reason} + /> + {modalError && <ErrorMessage>{modalError}</ErrorMessage>} + <ButtonContainer data-test="eqa-buttons"> + <Button onClick={hideModal}>Cancel</Button> + <Button disabled={!reason} onClick={onConfirm(reason)} primary> + Send + </Button> + </ButtonContainer> + </DeclineRoot> +)) + +const ModalComponent = ({ type, ...rest }) => + type === 'decline' ? ( + <DeclineModal {...rest} /> + ) : ( + <ConfirmationModal {...rest} /> + ) + +export default compose( + setDisplayName('EQA Decision page'), + connect( + state => ({ + isFetching: technicalCheckFetching(state), + }), + { technicalDecision }, + ), + withModal(({ isFetching }) => ({ + isFetching, + modalComponent: ModalComponent, + })), + withState('params', 'setParams', { + token: null, + customId: null, + collectionId: null, + }), + withState('successMessage', 'setSuccess', ''), + lifecycle({ + componentDidMount() { + const { location, setParams } = this.props + const { customId, collectionId, token } = parseSearchParams( + location.search, + ) + setParams({ customId, collectionId, token }) + }, + }), + withHandlers({ + showEQAModal: ({ + showModal, + hideModal, + setSuccess, + setModalError, + technicalDecision, + params: { collectionId, token }, + }) => decision => () => { + const acceptConfig = { + title: `Are you sure you want to accept this EQA package?`, + onConfirm: () => { + technicalDecision({ + step: 'eqa', + agree: decision, + collectionId, + token, + }).then(() => { + setSuccess( + `Manuscript accepted. Thank you for your technical check!`, + ) + hideModal() + }, setModalError) + }, + onCancel: hideModal, + } + const declineConfig = { + type: 'decline', + title: 'Return Manuscript to Editor in Chief', + onConfirm: reason => () => { + technicalDecision({ + step: 'eqa', + agree: decision, + comments: reason, + collectionId, + token, + }).then(() => { + setSuccess( + `Manuscript returned with comments. An email has been sent to Editor In Chief. Thank you for your technical check!`, + ) + hideModal() + }, setModalError) + }, + } + + const cfg = decision ? acceptConfig : declineConfig + showModal(cfg) + }, + }), +)(EQADecisionPage) + +// #region styles +const Root = styled.div` + align-items: center; + color: ${th('colorText')}; + display: flex; + flex-direction: column; + justify-content: flex-start; + margin: 0 auto; + text-align: center; + width: 70vw; + + a { + color: ${th('colorText')}; + } +` + +const Title = styled.div` + color: ${th('colorPrimary')}; + font-size: ${th('fontSizeHeading5')}; + font-family: ${th('fontHeading')}; + margin: 10px auto; +` + +const ButtonContainer = styled.div` + align-items: center; + display: flex; + justify-content: space-around; + padding: calc(${th('gridUnit')} / 2); + width: calc(${th('gridUnit')} * 15); +` +const ErrorMessage = styled.div` + color: ${th('colorError')}; + margin: ${th('subGridUnit')}; + text-align: center; +` +const DeclineRoot = styled.div` + align-items: center; + background-color: ${th('backgroundColor')}; + display: flex; + flex-direction: column; + height: calc(${th('gridUnit')} * 13); + justify-content: space-between; + padding: calc(${th('subGridUnit')} * 7); + width: calc(${th('gridUnit')} * 24); + + & span { + color: ${th('colorPrimary')}; + font-size: ${th('fontSizeHeading5')}; + font-family: ${th('fontHeading')}; + margin-bottom: ${th('gridUnit')}; + } + + & textarea { + height: 100%; + padding: calc(${th('subGridUnit')} * 2); + width: 100%; + } + + & textarea:focus, + & textarea:active { + outline: none; + } + + & div { + display: flex; + justify-content: space-evenly; + margin: ${th('gridUnit')} auto 0; + width: 100%; + } +` +// #endregion diff --git a/packages/components-faraday/src/components/UIComponents/EQSDecisionPage.js b/packages/components-faraday/src/components/UIComponents/EQSDecisionPage.js index 6aaa02bb3..165361b6f 100644 --- a/packages/components-faraday/src/components/UIComponents/EQSDecisionPage.js +++ b/packages/components-faraday/src/components/UIComponents/EQSDecisionPage.js @@ -21,7 +21,7 @@ import { Err, Subtitle } from './FormItems' import { parseSearchParams } from '../utils' import { technicalDecision, - technicalCheckFetcing, + technicalCheckFetching, } from '../../redux/technicalCheck' const EQSDecisionPage = ({ @@ -52,7 +52,7 @@ export default compose( setDisplayName('EQS Decision page'), connect( state => ({ - isFetching: technicalCheckFetcing(state), + isFetching: technicalCheckFetching(state), }), { technicalDecision }, ), diff --git a/packages/components-faraday/src/components/UIComponents/index.js b/packages/components-faraday/src/components/UIComponents/index.js index a619f97e1..ff001b8fb 100644 --- a/packages/components-faraday/src/components/UIComponents/index.js +++ b/packages/components-faraday/src/components/UIComponents/index.js @@ -8,5 +8,6 @@ export { default as InfoPage } from './InfoPage' export { default as ErrorPage } from './ErrorPage' export { default as DateParser } from './DateParser' export { default as EQSDecisionPage } from './EQSDecisionPage' +export { default as EQADecisionPage } from './EQADecisionPage' export { default as ConfirmationPage } from './ConfirmationPage' export { default as BreadcrumbsHeader } from './BreadcrumbsHeader' diff --git a/packages/components-faraday/src/redux/technicalCheck.js b/packages/components-faraday/src/redux/technicalCheck.js index 8cb3a074b..5c8d7b9c2 100644 --- a/packages/components-faraday/src/redux/technicalCheck.js +++ b/packages/components-faraday/src/redux/technicalCheck.js @@ -22,6 +22,7 @@ export const technicalDecision = ({ step, agree, token, + comments, collectionId, }) => dispatch => { dispatch(decisionRequest()) @@ -29,19 +30,25 @@ export const technicalDecision = ({ step, token, agree, + comments, }).then( r => { dispatch(decisionSuccess()) return r }, err => { - dispatch(decisionError(err)) - throw err + const errorMessage = get( + JSON.parse(err.response), + 'error', + 'Oops! Something went wrong!', + ) + dispatch(decisionError(errorMessage)) + throw errorMessage }, ) } -export const technicalCheckFetcing = state => +export const technicalCheckFetching = state => get(state, 'technicalCheck.fetching', false) export default (state = {}, action = {}) => { diff --git a/packages/xpub-faraday/app/routes.js b/packages/xpub-faraday/app/routes.js index 05e3106eb..e3cb4ba8b 100644 --- a/packages/xpub-faraday/app/routes.js +++ b/packages/xpub-faraday/app/routes.js @@ -16,6 +16,7 @@ import { InfoPage, ErrorPage, EQSDecisionPage, + EQADecisionPage, ConfirmationPage, } from 'pubsweet-components-faraday/src/components/UIComponents/' import { @@ -120,6 +121,7 @@ const Routes = () => ( path="/projects/:project/versions/:version/details" /> <Route component={EQSDecisionPage} exact path="/eqs-decision" /> + <Route component={EQADecisionPage} exact path="/eqa-decision" /> <Route component={ErrorPage} exact path="/error-page" /> <Route component={InfoPage} exact path="/info-page" /> <Route component={NotFound} /> diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js index c9370cd79..b3789882f 100644 --- a/packages/xpub-faraday/config/default.js +++ b/packages/xpub-faraday/config/default.js @@ -115,7 +115,7 @@ module.exports = { mailer: { from: 'hindawi@thinslices.com', path: `${__dirname}/mailer`, - editorialAssistant: 'hindawi+editorial@thinslices.com', + editorialAssistant: 'bogdan.cochior+editorial@thinslices.com', }, publicKeys: ['pubsweet-client', 'authsome', 'validations'], statuses: { diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js index 4a96afcdd..25f2501d3 100644 --- a/packages/xpub-faraday/config/validations.js +++ b/packages/xpub-faraday/config/validations.js @@ -13,6 +13,7 @@ module.exports = { handlingEditor: Joi.object(), technicalChecks: Joi.object({ token: Joi.string(), + hasEQA: Joi.boolean(), }), }, fragment: [ -- GitLab