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