diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index 82d6b27cecc05dff19ac1f018acf05305e125fde..199161edf3e70f13b62020fc10fbcdf4bdb9509f 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 bff64b56eeed319129822fdad32cdd6a1faf8150..e63386ac9462e926e9c0a1f0e2f89c03f52da3a2 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 0000000000000000000000000000000000000000..c1fb89f349ec68b04e977988e1e0bfea9a5fda79 --- /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 6aaa02bb30d2245e08ac88c503cbc111651d7c63..165361b6ff2853f62f96dbb9082870a66ed87781 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 a619f97e10cfcf1546e5428b5773ed6e6530e336..ff001b8fb3a18548136a1a88e64816e3cde7a4b8 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 8cb3a074bf9eed5220f56f7f476a7510cda8dc57..5c8d7b9c2e5125ecfa487aab80cab12234a3c361 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 05e3106ebac869d37c6b1401cf8764def29ab516..e3cb4ba8b9a232317471c34f98ac6f73b3ce92cd 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 c9370cd7940d906ecb4d36896fd730b1440f4e6c..b3789882f572c85d4814ac2ceeb8732e3cfe4cc8 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 4a96afcddc925807a78fa9495b8be0aa53aedec8..25f2501d38bb6ffaa7619b683bcb3825e28e0883 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: [