From 851183526c10c3ca777cf8d1f23073bcbddf2bde Mon Sep 17 00:00:00 2001
From: Jure Triglav <juretriglav@gmail.com>
Date: Fri, 14 Aug 2020 01:15:38 +0200
Subject: [PATCH] feat: improvements to the decision form

---
 .../src/components/DecisionPage.js            | 47 ++++++------
 .../src/components/decision/DecisionForm.js   | 75 +++++++++++--------
 .../component-review/src/components/style.js  | 26 ++++++-
 cypress/integration/decision_spec.js          | 11 ++-
 4 files changed, 99 insertions(+), 60 deletions(-)

diff --git a/app/components/component-review/src/components/DecisionPage.js b/app/components/component-review/src/components/DecisionPage.js
index cb4a9d57ab..bb7a6c97b8 100644
--- a/app/components/component-review/src/components/DecisionPage.js
+++ b/app/components/component-review/src/components/DecisionPage.js
@@ -180,6 +180,7 @@ const decisionSections = ({
   uploadFile,
   isSubmitting,
   submitCount,
+  dirty,
 }) => {
   const decisionSections = []
   const manuscriptVersions = manuscript.manuscriptVersions || []
@@ -216,6 +217,7 @@ const decisionSections = ({
         </AdminSection>
         <AdminSection key="decision-form">
           <DecisionForm
+            dirty={dirty}
             handleSubmit={handleSubmit}
             isSubmitting={isSubmitting}
             isValid={isValid}
@@ -304,7 +306,7 @@ const DecisionPage = ({ match }) => {
     (manuscript &&
       manuscript.reviews &&
       manuscript.reviews.find(review => review.isDecision)) || {
-      comments: [],
+      decisionComment: {},
       isDecision: true,
       recommendation: null,
     }
@@ -384,6 +386,7 @@ const DecisionPage = ({ match }) => {
       updateReview,
       isSubmitting: props.isSubmitting,
       submitCount: props.submitCount,
+      dirty: props.dirty,
     })
 
   return (
@@ -392,37 +395,33 @@ const DecisionPage = ({ match }) => {
         <Formik
           displayName="decision"
           initialValues={reviewOrInitial(data.manuscript)}
-          // isInitialValid={({ manuscript }) => {
-          //   const rv =
-          //     manuscript.reviews.find(review => review.isDecision) || {}
-          //   const isRecommendation = rv.recommendation != null
-          //   const isCommented = getCommentContent(rv, 'note') !== ''
-
-          //   return isCommented && isRecommendation
-          // }}
-          onSubmit={() =>
+          onSubmit={values =>
             makeDecision({
               variables: {
                 id: manuscript.id,
-                decision: manuscript.reviews.find(review => review.isDecision)
-                  .recommendation,
+                decision: values.recommendation,
               },
             })
           }
-          // validate={(values, props) => {
-          //   const errors = {}
-          //   if (values.decisionComment?.content === '') {
-          //     errors.decisionComment = 'Required'
-          //   }
-
-          //   if (values.recommendation === null) {
-          //     errors.recommendation = 'Required'
-          //   }
-          //   return errors
-          // }}
+          validate={(values, props) => {
+            const errors = {}
+            if (
+              ['', '<p></p>', undefined].includes(
+                values.decisionComment?.content,
+              )
+            ) {
+              errors.decisionComment = 'Decision letter is required'
+            }
+
+            if (values.recommendation === null) {
+              errors.recommendation = 'Decision is required'
+            }
+            return errors
+          }}
+          // validateOnMount
         >
           {props => (
-            // Temp
+            // TODO: Find a nicer way to display the contents of a manuscript
             <>
               {/* <Tabs
                 activeKey={
diff --git a/app/components/component-review/src/components/decision/DecisionForm.js b/app/components/component-review/src/components/decision/DecisionForm.js
index b418778291..cb5ae93c57 100644
--- a/app/components/component-review/src/components/decision/DecisionForm.js
+++ b/app/components/component-review/src/components/decision/DecisionForm.js
@@ -1,7 +1,7 @@
 import React, { useContext } from 'react'
 import { NoteEditor } from 'xpub-edit'
 // import { cloneDeep, omit } from 'lodash'
-import { Field } from 'formik'
+import { Field, ErrorMessage } from 'formik'
 import {
   Button,
   // Flexbox,
@@ -23,8 +23,12 @@ import {
   SectionRow,
   SectionAction,
   FormStatus,
+  ErrorText,
+  ErrorWrap,
 } from '../style'
 
+// import Wax from '../../../../wax-collab/src/Editoria'
+
 const NoteDecision = ({ updateReview }) => (
   <>
     <Field key="noteField" name="decisionComment">
@@ -49,26 +53,12 @@ const NoteDecision = ({ updateReview }) => (
         </>
       )}
     </Field>
-    {/* <Field
-      component={NoteInput}
-      key="commentinput"
-      name="comments"
-      updateReview={updateReview}
-      validate={required}
-    />
-    <FilesUpload
-      objectType="Review"
-      objectId=
-      key="attachmentinput"
-      fileType="note"
-      updateReview={updateReview}
-    /> */}
   </>
 )
 
 const NoteInput = ({
   field,
-  form: { values, setFieldValue },
+  form: { errors, setFieldValue, setFieldTouched },
   updateReview,
 }) => (
   // const review = useState()
@@ -86,13 +76,23 @@ const NoteInput = ({
   // }, [values])
 
   // console.log('Rendering', review.current)
-  <>
+  <ErrorWrap error={errors.decisionComment}>
+    {
+      // TODO: Use the best text editor there is!
+      /* <Wax
+      // fileUpload={fileUpload}
+      // onChange={source => updateManuscript({ source })}
+      content={field.value?.content}
+    /> */
+    }
+
     <NoteEditor
       data-testid="decisionComment"
+      debounceDelay={300}
       key="note-input"
-      onBlur={() => {}}
+      onBlur={() => setFieldTouched('decisionComment')}
       onChange={value => {
-        // review.current.decisionComment.content = value
+        setFieldValue('decisionComment', { content: value })
         updateReview({
           decisionComment: { content: value },
         })
@@ -100,26 +100,32 @@ const NoteInput = ({
       placeholder="Write/paste your decision letter here, or upload it using the upload button on the right."
       value={field.value?.content || ''}
     />
-  </>
+    <ErrorText>
+      <ErrorMessage name="decisionComment" />
+    </ErrorText>
+  </ErrorWrap>
 )
 
 const RecommendationInput = ({
   field,
-  form: { setFieldValue },
+  form: { setFieldValue, errors },
   updateReview,
 }) => {
   const journal = useContext(JournalContext)
   return (
-    <RadioGroup
-      {...field}
-      inline
-      onChange={val => {
-        setFieldValue(`recommendation`, val)
-        updateReview({ recommendation: val })
-      }}
-      options={journal.recommendations}
-      value={field.value === '' ? null : field.value}
-    />
+    <div>
+      <RadioGroup
+        {...field}
+        inline
+        onChange={val => {
+          setFieldValue('recommendation', val)
+          updateReview({ recommendation: val })
+        }}
+        options={journal.recommendations}
+        value={field.value === '' ? null : field.value}
+      />
+      <ErrorMessage name="recommendation" />
+    </div>
   )
 }
 
@@ -129,6 +135,7 @@ const DecisionForm = ({
   isValid,
   isSubmitting,
   submitCount,
+  dirty,
 }) => {
   let status = null
   if (isSubmitting) {
@@ -155,7 +162,11 @@ const DecisionForm = ({
           />
           <FormStatus>{status}</FormStatus>
           <SectionAction key="submit">
-            <Button disabled={!isValid || isSubmitting} primary type="submit">
+            <Button
+              disabled={!isValid || isSubmitting || !dirty}
+              primary
+              type="submit"
+            >
               Submit
             </Button>
           </SectionAction>
diff --git a/app/components/component-review/src/components/style.js b/app/components/component-review/src/components/style.js
index 2d0f1ca3ad..97ac3ec29f 100644
--- a/app/components/component-review/src/components/style.js
+++ b/app/components/component-review/src/components/style.js
@@ -1,4 +1,4 @@
-import styled from 'styled-components'
+import styled, { css } from 'styled-components'
 import { th, grid } from '@pubsweet/ui-toolkit'
 
 export const Columns = styled.div`
@@ -83,6 +83,30 @@ export const FormStatus = styled.div`
   color: ${th('colorSecondary')};
 `
 
+export const ErrorWrap = styled.div`
+  .ProseMirror {
+    margin-bottom: ${grid(4)};
+  }
+  ${({ error }) =>
+    error &&
+    css`
+      .ProseMirror {
+        border-color: red;
+      }
+      ${ErrorText} {
+        margin-top: ${grid(-4)};
+        margin-bottom: ${grid(1)};
+      }
+    `}
+
+  [class*="MenuBar"] {
+    margin-top: 0;
+  }
+`
+export const ErrorText = styled.div`
+  color: red;
+`
+
 export {
   Title,
   SectionHeader,
diff --git a/cypress/integration/decision_spec.js b/cypress/integration/decision_spec.js
index 8780bd7206..018bfae8ba 100644
--- a/cypress/integration/decision_spec.js
+++ b/cypress/integration/decision_spec.js
@@ -6,11 +6,16 @@ describe('Completing a review', () => {
 
     cy.contains('Control Panel').click()
 
-    cy.get('[data-testid="decisionComment"]')
-      .click()
+    cy.get('[data-testid="decisionComment"]').click()
+    cy.focused().blur()
+
+    // Validations run on blur
+    cy.contains('Decision letter is required')
+    cy.get('[data-testid="decisionComment"] div[contenteditable="true"]')
+      .click({ force: true })
       .type(`Great paper, dear authors, congratulations!`)
 
-    cy.contains('Accept').click()
+    cy.contains('Accept').click({ force: true })
     cy.contains('Submit').click()
     cy.contains('Your decision has been saved.')
     cy.visit('/journal/dashboard')
-- 
GitLab