diff --git a/Automation/src/test/java/Utils.java b/Automation/src/test/java/Utils.java
index 149e3ef2a537ce9178435b50ab619a8ee59c5095..74b8cb31429e4f0b97371b0b00b5f9b8b1c78064 100644
--- a/Automation/src/test/java/Utils.java
+++ b/Automation/src/test/java/Utils.java
@@ -81,7 +81,7 @@ public class Utils {
 
         driver.findElement(By.name("email")).sendKeys(email);
         driver.findElement(By.name("password")).sendKeys(password);
-        driver.findElement(By.name("confirmPassword")).sendKeys(password);
+        driver.findElement(By.name("confirmNewPassword")).sendKeys(password);
 
         driver.findElement(By.xpath(".//button[contains(text(),'"+"CONFIRM"+"')]")).click();
 
diff --git a/packages/component-email-templating/src/templates/partials/invButtons.hbs b/packages/component-email-templating/src/templates/partials/invButtons.hbs
index 7611dc03e966d4e9146cda5bd9b6a535b17ace3d..8deceffee7a2080bd626f746fc7959d815da5a67 100644
--- a/packages/component-email-templating/src/templates/partials/invButtons.hbs
+++ b/packages/component-email-templating/src/templates/partials/invButtons.hbs
@@ -12,7 +12,7 @@
       <td width="300.000px" valign="top" style="padding: 0px 0px 0px 0px;border-collapse: collapse;" >
     <![endif]-->
 
-      <table width="300.000" style="width:300.000px;border-spacing:0;border-collapse:collapse;margin:0px 0px 0px 0px;"
+      <table width="300.000" style="width:'50%';border-spacing:0;border-collapse:collapse;margin:0px 0px 0px 0px;"
         cellpadding="0" cellspacing="0" align="left" border="0" bgcolor="" class="column column-0 of-2
                   empty">
         <tr>
@@ -21,7 +21,7 @@
               role="module" style="table-layout:fixed" width="100%">
               <tbody>
                 <tr>
-                  <td align="center" class="outer-td" style="padding:0px 0px 0px 50px">
+                  <td align="center" class="outer-td padding-decline">
                     <table border="0" cellPadding="0" cellSpacing="0" class="button-css__deep-table___2OZyb wrapper-mobile"
                       style="text-align:center">
                       <tbody>
@@ -47,7 +47,7 @@
       <td width="300.000px" valign="top" style="padding: 0px 0px 0px 0px;border-collapse: collapse;" >
     <![endif]-->
 
-      <table width="300.000" style="width:300.000px;border-spacing:0;border-collapse:collapse;margin:0px 0px 0px 0px;"
+      <table width="300.000" style="width:'50%';border-spacing:0;border-collapse:collapse;margin:0px 0px 0px 0px;"
         cellpadding="0" cellspacing="0" align="left" border="0" bgcolor="" class="column column-1 of-2
                   empty">
         <tr>
@@ -56,7 +56,7 @@
               role="module" style="table-layout:fixed" width="100%">
               <tbody>
                 <tr>
-                  <td align="center" class="outer-td" style="padding:0px 50px 0px 0px">
+                  <td align="center" class="outer-td padding-agree">
                     <table border="0" cellPadding="0" cellSpacing="0" class="button-css__deep-table___2OZyb wrapper-mobile"
                       style="text-align:center">
                       <tbody>
diff --git a/packages/component-email-templating/src/templates/partials/invHeader.hbs b/packages/component-email-templating/src/templates/partials/invHeader.hbs
index d01a40ad4f0645427c966da91a011dbf8ea226a1..473d7b913b24bea5163c4e73aada31056acf8fe3 100644
--- a/packages/component-email-templating/src/templates/partials/invHeader.hbs
+++ b/packages/component-email-templating/src/templates/partials/invHeader.hbs
@@ -76,6 +76,14 @@
       text-decoration: none;
     }
 
+    .padding-decline {
+      padding:0px 0px 0px 50px;
+    }
+
+    .padding-agree {
+      padding:0px 50px 0px 0px;
+    }
+
     @media screen and (max-width:480px) {
 
       .preheader .rightColumnContent,
@@ -126,6 +134,14 @@
         margin-left: 0 !important;
         margin-right: 0 !important;
       }
+
+      .padding-decline {
+        padding: 0px;
+      }
+
+      .padding-agree {
+        padding: 10px 0px 0px 0px;
+      }
     }
   </style>
   <!--user entered Head Start-->
diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js
index 728b2e28a08eb1572980a1879a27e4a6e5eb98f5..3c48b4f1aaf15ce372a53952f776dd1049bb49bb 100644
--- a/packages/component-faraday-selectors/src/index.js
+++ b/packages/component-faraday-selectors/src/index.js
@@ -1,6 +1,5 @@
 import { selectCurrentUser } from 'xpub-selectors'
-// eslint-disable-next-line no-unused-vars
-import { get, has, last, chain, some, isEmpty, flatten } from 'lodash'
+import { get, has, last, chain, some, isEmpty, slice, find } from 'lodash'
 
 export const isHEToManuscript = (state, collectionId = '') => {
   const { id = '', isAccepted = false } = chain(state)
@@ -49,6 +48,29 @@ export const canInviteReviewers = (state, collection = {}) => {
   return isAccepted && (userId === heId || isAdminEiC)
 }
 
+const canViewContextualBoxOnOldVersionStatuses = [
+  'submitted',
+  'heInvited',
+  'heAssigned',
+]
+const canViewContextualBoxOnOldVersion = (collection, fragmentId) => {
+  const fragments = get(collection, 'fragments', [])
+  const oldVersions = slice(fragments, 0, fragments.length - 1)
+  const isOldVersion = !!find(oldVersions, fragment => fragment === fragmentId)
+  return (
+    isOldVersion &&
+    canViewContextualBoxOnOldVersionStatuses.includes(
+      get(collection, 'status', 'draft'),
+    )
+  )
+}
+
+const canHEViewContextualBoxOnOldVersion = (collection, fragmentId) => {
+  const fragments = get(collection, 'fragments', [])
+  const oldVersions = slice(fragments, 0, fragments.length - 1)
+  const isOldVersion = !!find(oldVersions, fragment => fragment === fragmentId)
+  return isOldVersion && get(collection, 'status', 'draft') === 'heInvited'
+}
 const cannotViewReviewersDetails = [
   'draft',
   'technicalChecks',
@@ -67,12 +89,14 @@ export const canViewReviewersDetails = (state, collection = {}) => {
   return canViewReports(state, get(collection, 'id', ''))
 }
 
-const authorCanViewReportsDetailsStatuses = [
+const authorAndReviewersCanViewReportsDetailsStatuses = [
   'revisionRequested',
+  'underReview',
   'pendingApproval',
   'rejected',
   'accepted',
   'reviewCompleted',
+  'reviewersInvited',
   'inQa',
 ]
 
@@ -83,9 +107,27 @@ export const authorCanViewReportsDetails = (
 ) => {
   const isAuthor = currentUserIsAuthor(state, fragmentId)
   return (
-    authorCanViewReportsDetailsStatuses.includes(
+    isAuthor &&
+    (authorAndReviewersCanViewReportsDetailsStatuses.includes(
       get(collection, 'status', 'draft'),
-    ) && isAuthor
+    ) ||
+      canViewContextualBoxOnOldVersion(collection, fragmentId))
+  )
+}
+
+export const reviewersCanViewReviewerReports = (
+  state,
+  collection = {},
+  fragmentId,
+) => {
+  const isReviewer = currentUserIsReviewer(state, fragmentId)
+  const ownRecommendation = getOwnRecommendations(state, fragmentId)
+  return (
+    isReviewer &&
+    authorAndReviewersCanViewReportsDetailsStatuses.includes(
+      get(collection, 'status', 'draft'),
+    ) &&
+    get(ownRecommendation[0], 'submittedOn', false)
   )
 }
 
@@ -125,6 +167,7 @@ const canReviewerViewEditorialCommentsStatuses = [
   'reviewCompleted',
   'pendingApproval',
   'revisionRequested',
+  'reviewersInvited',
 ]
 export const canReviewerViewEditorialComments = (
   state,
@@ -176,8 +219,13 @@ export const canViewEditorialComments = (
     state,
     fragmentId,
   )
+  const isHE = currentUserIs(state, 'isHE')
+  const canViewEditorialCommentsOnOldVersion = isHE
+    ? !canHEViewContextualBoxOnOldVersion(collection, fragmentId)
+    : canViewContextualBoxOnOldVersion(collection, fragmentId)
   return (
-    (canHeViewEditorialComments(state, collection) ||
+    (canViewEditorialCommentsOnOldVersion ||
+      canHeViewEditorialComments(state, collection) ||
       canEICViewEditorialComments(state, collection) ||
       canReviewerViewEditorialComments(state, collection, fragment) ||
       canAuthorViewEditorialComments(state, collection, fragmentId)) &&
@@ -185,17 +233,22 @@ export const canViewEditorialComments = (
   )
 }
 
-const cannotViewResponseFromAuthorStatuses = ['reviewersInvited']
 export const canViewResponseFromAuthor = (state, collection, fragmentId) => {
   const authorResponseToRevisonRequest = getFragmentAuthorResponse(
     state,
     fragmentId,
   )
+  const canHEViewResponseFromAuthor =
+    currentUserIs(state, 'isHE') &&
+    get(collection, 'status', 'draft') === 'heInvited'
+
+  const canReviewerViewResponsefromAuthor =
+    currentUserIsReviewerInPending(state, fragmentId) &&
+    get(collection, 'status', 'draft') === 'reviewersInvited'
   return (
     !isEmpty(authorResponseToRevisonRequest) &&
-    !cannotViewResponseFromAuthorStatuses.includes(
-      get(collection, 'status', 'draft'),
-    )
+    !canHEViewResponseFromAuthor &&
+    !canReviewerViewResponsefromAuthor
   )
 }
 
@@ -217,17 +270,9 @@ export const getHERecommendation = (state, collectionId, fragmentId) => {
   )
 }
 
-const canMakeDecisionStatuses = [
-  'submitted',
-  'pendingApproval',
-  'underReview',
-  'reviewCompleted',
-]
 export const canMakeDecision = (state, collection = {}) => {
-  const status = get(collection, 'status', 'draft')
-
   const isEIC = currentUserIs(state, 'adminEiC')
-  return isEIC && canMakeDecisionStatuses.includes(status)
+  return isEIC
 }
 
 const collectionReviewerReports = state =>
@@ -326,6 +371,13 @@ export const pendingReviewerInvitation = (state, fragmentId) =>
     )
     .value()
 
+export const currentUserIsReviewerInPending = (state, fragmentId) => {
+  const currentUser = selectCurrentUser(state)
+  const invitations = get(state, `fragments.${fragmentId}.invitations`, [])
+  return !!invitations.find(
+    i => i.userId === currentUser.id && i.role === 'reviewer' && !i.isAccepted,
+  )
+}
 export const currentUserIsReviewer = (state, fragmentId) => {
   const currentUser = selectCurrentUser(state)
   const invitations = get(state, `fragments.${fragmentId}.invitations`, [])
@@ -496,11 +548,15 @@ export const getVersionOptions = (state, collection = {}) => {
 
 export const canReview = (state, collection = {}, fragment = {}) => {
   const fragmentId = get(fragment, 'id', false)
-
   if (!fragmentId) return false
-
+  const ownRecommendation = getOwnRecommendations(state, fragmentId)
   const isReviewer = currentUserIsReviewer(state, fragmentId)
   if (!isReviewer) return false
-
-  return get(collection, 'status', 'draft') === 'underReview'
+  return (
+    get(collection, 'status', 'draft') === 'underReview' &&
+    !get(ownRecommendation[0], 'submittedOn', false)
+  )
 }
+
+export const isFetchingFromAutosave = state =>
+  get(state.autosave, 'isFetching', false)
diff --git a/packages/component-faraday-ui/src/AuthorCard.js b/packages/component-faraday-ui/src/AuthorCard.js
index 2ace7fa985268bb535cdeff598a1a5ac7a030433..36f74f2d439a408fe6024014e11d409564fec1d4 100644
--- a/packages/component-faraday-ui/src/AuthorCard.js
+++ b/packages/component-faraday-ui/src/AuthorCard.js
@@ -4,14 +4,7 @@ import styled from 'styled-components'
 import { th } from '@pubsweet/ui-toolkit'
 import { required } from 'xpub-validators'
 import { reduxForm, Field } from 'redux-form'
-import {
-  Menu,
-  H3,
-  ValidatedField,
-  TextField,
-  Checkbox,
-  Spinner,
-} from '@pubsweet/ui'
+import { H3, ValidatedField, TextField, Checkbox, Spinner } from '@pubsweet/ui'
 import {
   compose,
   withState,
@@ -20,7 +13,7 @@ import {
   setDisplayName,
 } from 'recompose'
 
-import { withCountries } from 'pubsweet-component-faraday-ui'
+import { MenuCountry } from 'pubsweet-component-faraday-ui'
 import { Tag, Label, Row, Item, PersonInfo, IconButton, OpenModal } from './'
 import { validators } from './helpers'
 
@@ -129,7 +122,6 @@ const AuthorTitle = ({
 
 // #region AuthorEdit
 const AuthorEdit = ({
-  countries,
   author,
   editMode,
   listIndex,
@@ -196,7 +188,7 @@ const AuthorEdit = ({
         <Label required>Country</Label>
         <ValidatedField
           component={input => (
-            <Menu {...input} options={countries} placeholder="Please select" />
+            <MenuCountry {...input} placeholder="Please select" />
           )}
           data-test-id="author-card-country"
           name="country"
@@ -208,7 +200,6 @@ const AuthorEdit = ({
 // #endregion
 
 const EnhancedAuthorEdit = compose(
-  withCountries,
   withProps(({ author }) => ({
     initialValues: author,
   })),
diff --git a/packages/component-faraday-ui/src/AuthorReply.js b/packages/component-faraday-ui/src/AuthorReply.js
index 4bbb8d22b3218279bbedaedb1af03b7bd7f1bca0..872a4be66501eac3ca9dcf4aadcb4c18d94178ad 100644
--- a/packages/component-faraday-ui/src/AuthorReply.js
+++ b/packages/component-faraday-ui/src/AuthorReply.js
@@ -26,7 +26,7 @@ const AuthorReply = ({
         <Row mb={1}>
           <Item vertical>
             <Label mb={1 / 2}>Author Reply</Label>
-            <Text>{replyContent}</Text>
+            <Text whiteSpace="pre-wrap">{replyContent}</Text>
           </Item>
         </Row>
         <Text ml={1} mr={1} whiteSpace="nowrap">
diff --git a/packages/component-faraday-ui/src/EditorialReportCard.js b/packages/component-faraday-ui/src/EditorialReportCard.js
index 1f394ee94dd7a7af80267eadfc9e59aeb07161b1..f43ddb1072b43579b889eaffcbe8b34226258e3d 100644
--- a/packages/component-faraday-ui/src/EditorialReportCard.js
+++ b/packages/component-faraday-ui/src/EditorialReportCard.js
@@ -51,7 +51,7 @@ const EditorialReportCard = ({
       <Row mb={2}>
         <Item vertical>
           <Label mb={1 / 2}>{publicLabel}</Label>
-          <Text>{publicReport}</Text>
+          <Text whiteSpace="pre-wrap">{publicReport}</Text>
         </Item>
       </Row>
     )}
@@ -60,7 +60,7 @@ const EditorialReportCard = ({
       <Row mb={2}>
         <Item vertical>
           <Label mb={1 / 2}>{privateLabel}</Label>
-          <Text>{privateReport}</Text>
+          <Text whiteSpace="pre-wrap">{privateReport}</Text>
         </Item>
       </Row>
     )}
diff --git a/packages/component-faraday-ui/src/IconButton.js b/packages/component-faraday-ui/src/IconButton.js
index 5e4b4acfc0f531676ed49913b60bd8b960e871ee..ac1ef02ba87f4616ca9219694b41852b9bd1e5b5 100644
--- a/packages/component-faraday-ui/src/IconButton.js
+++ b/packages/component-faraday-ui/src/IconButton.js
@@ -1,7 +1,6 @@
 import React from 'react'
 import { Icon } from '@pubsweet/ui'
 import styled from 'styled-components'
-
 import { positionHelper, marginHelper, paddingHelper } from './styledHelpers'
 
 const IconButton = styled.div`
@@ -10,7 +9,7 @@ const IconButton = styled.div`
   display: flex;
   justify-content: center;
   opacity: ${props => (props.disabled ? 0.7 : 1)};
-
+  pointer-events: ${props => (props.unclickable ? 'none' : 'auto')};
   &:hover {
     opacity: 0.7;
   }
@@ -18,16 +17,23 @@ const IconButton = styled.div`
   &[disabled] {
     cursor: not-allowed;
   }
-
   ${marginHelper};
   ${paddingHelper};
   ${positionHelper};
 `
 
-export default ({ icon, onClick, iconSize = 3, disabled, ...props }) => (
+export default ({
+  icon,
+  onClick,
+  unclickable,
+  iconSize = 3,
+  disabled,
+  ...props
+}) => (
   <IconButton
     disabled={disabled}
     onClick={!disabled ? onClick : null}
+    unclickable={unclickable}
     {...props}
   >
     <Icon size={iconSize} {...props}>
diff --git a/packages/component-faraday-ui/src/InviteReviewers.js b/packages/component-faraday-ui/src/InviteReviewers.js
index 7c93ebb316f9a1d7a10c664548a6ee94c6864cdd..7f07b70cba1b4e7944aea14142d5bcd1632a1fad 100644
--- a/packages/component-faraday-ui/src/InviteReviewers.js
+++ b/packages/component-faraday-ui/src/InviteReviewers.js
@@ -5,7 +5,9 @@ import { reduxForm } from 'redux-form'
 import { th } from '@pubsweet/ui-toolkit'
 import { required } from 'xpub-validators'
 import { withModal } from 'pubsweet-component-modal/src/components'
-import { Button, H4, Menu, TextField, ValidatedField } from '@pubsweet/ui'
+import { Button, H4, TextField, ValidatedField } from '@pubsweet/ui'
+
+import { MenuCountry } from 'pubsweet-component-faraday-ui'
 
 import {
   Row,
@@ -15,10 +17,9 @@ import {
   ItemOverrideAlert,
   withFetching,
   validators,
-  withCountries,
 } from '../'
 
-const InviteReviewers = ({ countries, handleSubmit, reset }) => (
+const InviteReviewers = ({ handleSubmit, reset }) => (
   <Root>
     <Row justify="space-between" mb={2}>
       <H4>Invite reviewer</H4>
@@ -82,7 +83,9 @@ const InviteReviewers = ({ countries, handleSubmit, reset }) => (
       <ItemOverrideAlert vertical>
         <Label required>Country</Label>
         <ValidatedField
-          component={input => <Menu options={countries} {...input} />}
+          component={input => (
+            <MenuCountry {...input} placeholder="Please select" />
+          )}
           name="country"
           validate={[required]}
         />
@@ -93,7 +96,6 @@ const InviteReviewers = ({ countries, handleSubmit, reset }) => (
 
 export default compose(
   withFetching,
-  withCountries,
   withModal(({ isFetching, modalKey }) => ({
     modalKey,
     isFetching,
diff --git a/packages/component-faraday-ui/src/ManuscriptCard.js b/packages/component-faraday-ui/src/ManuscriptCard.js
index fb471ea96a51267573dcf00e32d8fe422379b18d..17e9e176354403406b6066cf5b09f589a9a93cfa 100644
--- a/packages/component-faraday-ui/src/ManuscriptCard.js
+++ b/packages/component-faraday-ui/src/ManuscriptCard.js
@@ -76,7 +76,7 @@ const ManuscriptCard = ({
         <Row alignItems="center" justify="flex-start" mb={1}>
           <H4>Handling editor</H4>
           <Text ml={1} mr={3} whiteSpace="nowrap">
-            {get(handlingEditor, 'name', 'Undefined')}
+            {get(handlingEditor, 'name', 'Unassigned')}
           </Text>
           {canViewReports && (
             <Fragment>
diff --git a/packages/component-faraday-ui/src/MenuCountry.js b/packages/component-faraday-ui/src/MenuCountry.js
new file mode 100644
index 0000000000000000000000000000000000000000..8ca9f618bb2612ebdf04dea4a5c0e781ba2a39ad
--- /dev/null
+++ b/packages/component-faraday-ui/src/MenuCountry.js
@@ -0,0 +1,81 @@
+import React from 'react'
+import { Menu } from '@pubsweet/ui'
+import { startsWith, toLower, get } from 'lodash'
+import { compose, withState, withHandlers } from 'recompose'
+import styled from 'styled-components'
+import { th } from '@pubsweet/ui-toolkit'
+
+import { withCountries } from 'pubsweet-component-faraday-ui'
+
+const filteredCountries = (countries, userInput) =>
+  countries.filter(o => startsWith(toLower(o.label), toLower(userInput)))
+
+const firstFilteredCountry = props =>
+  filteredCountries(props.countries, props.userInput)[0]
+
+const CustomOpener = ({
+  selected,
+  userInput,
+  toggleMenu,
+  placeholder,
+  optionLabel,
+  onChange,
+  onEnter,
+}) => (
+  <Input
+    onChange={onChange}
+    onClick={toggleMenu}
+    onKeyUp={onEnter}
+    placeholder={selected ? optionLabel(selected) : placeholder}
+    value={userInput}
+  />
+)
+
+const MenuCountry = ({ countries = [], ...input }) => (
+  <Menu
+    {...input}
+    options={filteredCountries(countries, input.userInput)}
+    placeholder="Please select"
+    renderOpener={CustomOpener}
+  />
+)
+
+const enhance = compose(
+  withCountries,
+  withState('userInput', 'updateUserInput', ''),
+  withHandlers({
+    onChange: ({ updateUserInput, onChange }) => value => {
+      // this value is an input DOM event while typing and a dropdown value when
+      // selected
+      if (typeof value === 'string') {
+        onChange(value)
+      }
+      updateUserInput(get(value, 'target.value', ''))
+    },
+    onEnter: props => event => {
+      if (event.which === 13) {
+        props.onChange(firstFilteredCountry(props).value)
+        props.updateUserInput(firstFilteredCountry(props).label)
+      }
+    },
+  }),
+)
+
+export default enhance(MenuCountry)
+
+const Input = styled.input`
+  width: 100%;
+  height: calc(${th('gridUnit')} * 4);
+  border: ${th('accordion.border')};
+  border-radius: ${th('borderRadius')};
+  padding: 0 ${th('gridUnit')};
+  ::placeholder {
+    color: ${th('colorText')};
+    opacity: 1;
+    font-family: ${th('fontWriting')};
+  }
+  :focus {
+    border-color: ${th('action.colorActive')}
+    outline: none;
+  }
+`
diff --git a/packages/component-faraday-ui/src/PasswordValidation.js b/packages/component-faraday-ui/src/PasswordValidation.js
new file mode 100644
index 0000000000000000000000000000000000000000..6dacbd6349dbae4446a0c68407b4d4d486dd6deb
--- /dev/null
+++ b/packages/component-faraday-ui/src/PasswordValidation.js
@@ -0,0 +1,182 @@
+import React, { Fragment } from 'react'
+import PropTypes from 'prop-types'
+import { required } from 'xpub-validators'
+import { connect } from 'react-redux'
+import { getFormValues } from 'redux-form'
+import { ValidatedField, TextField } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui-toolkit'
+import { Row, Item, Label, IconButton } from 'pubsweet-component-faraday-ui'
+import styled, { css } from 'styled-components'
+import { get } from 'lodash'
+import { withProps, compose } from 'recompose'
+import {
+  minLength,
+  atLeastOneDigit,
+  atLeastOneUppercase,
+  atLeastOneLowerrcase,
+  atLeastOnePunctuation,
+} from './Utils.js'
+
+const PasswordField = input => <TextField {...input} type="password" />
+const PasswordValidation = ({
+  formName,
+  formLabel,
+  minLength,
+  atLeastOneDigit,
+  atLeastOneUppercase,
+  submitFailed,
+  atLeastOneLowerrcase,
+  atLeastOnePunctuation,
+}) => (
+  <Fragment>
+    <Row mb={2}>
+      <Item data-test-id="sign-up-password" vertical>
+        <Label required>{formLabel}</Label>
+        <ValidatedField
+          component={PasswordField}
+          name="password"
+          validate={[required]}
+        />
+      </Item>
+    </Row>
+
+    <Row mb={1}>
+      <Item data-test-id="sign-up-confirm-password" vertical>
+        <Label required>Retype Password</Label>
+        <ValidatedField
+          component={PasswordField}
+          name="confirmNewPassword"
+          validate={[required]}
+        />
+      </Item>
+    </Row>
+
+    <Row alignItems="center" justify="flex-start" mb={-1 / 2}>
+      <IconButton icon="info" iconSize={2} ml={-1 / 2} primary unclickable />
+      <RulesTitle>The password must contain: </RulesTitle>
+    </Row>
+
+    <Rules>
+      <Row justify="flex-start">
+        <Rule error={submitFailed && !minLength} fulfilled={minLength}>
+          at least 6 characters
+        </Rule>
+      </Row>
+
+      <Row justify="flex-start">
+        <Rule
+          error={submitFailed && !atLeastOneUppercase}
+          fulfilled={atLeastOneUppercase}
+        >
+          at least 1 uppercase character (A-Z)
+        </Rule>
+      </Row>
+
+      <Row justify="flex-start">
+        <Rule
+          error={submitFailed && !atLeastOneLowerrcase}
+          fulfilled={atLeastOneLowerrcase}
+        >
+          at least 1 lowercase character (a-z)
+        </Rule>
+      </Row>
+
+      <Row justify="flex-start">
+        <Rule
+          error={submitFailed && !atLeastOneDigit}
+          fulfilled={atLeastOneDigit}
+        >
+          at least 1 digit (0-9)
+        </Rule>
+      </Row>
+
+      <Row justify="flex-start" mb={3}>
+        <Rule
+          error={submitFailed && !atLeastOnePunctuation}
+          fulfilled={atLeastOnePunctuation}
+        >
+          at least 1 special character (punctuation)
+        </Rule>
+      </Row>
+    </Rules>
+  </Fragment>
+)
+PasswordValidation.propTypes = {
+  /** Name of the redux form. */
+  formName: PropTypes.string,
+  /** Label name for password input */
+  formLabel: PropTypes.string,
+  /** If the password requirements are not fulfilled it will return true. */
+  submitFailed: PropTypes.bool,
+  /** If the password's length is greater or equal than the minimum value, it will return true. */
+  minLength: PropTypes.bool,
+  /** If the password has at least one digit it will return true. */
+  atLeastOneDigit: PropTypes.bool,
+  /** If the password has at least one uppercase it will return true. */
+  atLeastOneUppercase: PropTypes.bool,
+  /** If the password has at least one lowercase it will return true. */
+  atLeastOneLowerrcase: PropTypes.bool,
+  /** If the password has at least one punctuation it will return true. */
+  atLeastOnePunctuation: PropTypes.bool,
+}
+PasswordValidation.defaultProps = {
+  formName: undefined,
+  formLabel: undefined,
+  submitFailed: false,
+  minLength: undefined,
+  atLeastOneDigit: undefined,
+  atLeastOneUppercase: undefined,
+  atLeastOneLowerrcase: undefined,
+  atLeastOnePunctuation: undefined,
+}
+
+export default compose(
+  connect((state, props) => ({
+    formValues: getFormValues(props.formName)(state),
+    submitFailed: get(state, `form.${props.formName}.submitFailed`, false),
+  })),
+  withProps(({ formValues }) => ({
+    minLength: minLength(get(formValues, 'password', ''), 6),
+    atLeastOneUppercase: atLeastOneUppercase(get(formValues, 'password', '')),
+    atLeastOneLowerrcase: atLeastOneLowerrcase(get(formValues, 'password', '')),
+    atLeastOneDigit: atLeastOneDigit(get(formValues, 'password', '')),
+    atLeastOnePunctuation: atLeastOnePunctuation(
+      get(formValues, 'password', ''),
+    ),
+  })),
+)(PasswordValidation)
+
+const RulesTitle = styled.p`
+  margin: 0px;
+  background-color: ${th('colorBackgroundHue')};
+  font-family: ${th('fontReading')};
+  font-size: ${th('fontSizeBaseMedium')};
+  line-height: ${th('lineHeightHeading3')};
+  color: ${th('colorSecondary')};
+`
+const Rules = styled.div`
+  font-family: ${th('fontReading')};
+  font-size: ${th('fontSizeBaseMedium')};
+  line-height: ${th('lineHeightHeading4')};
+  color: ${th('colorText')};
+`
+const ruleHelper = props => {
+  const textDecoration = props.fulfilled ? 'line-through' : 'none'
+
+  let textColor = 'inherit'
+  if (props.error) {
+    textColor = '#FC6A4B'
+  } else if (props.fulfilled) {
+    textColor = '#63A945'
+  }
+
+  return css`
+    text-decoration: ${textDecoration};
+    color: ${textColor};
+  `
+}
+
+const Rule = styled.p`
+  margin: 0px;
+  ${ruleHelper};
+`
diff --git a/packages/component-faraday-ui/src/PasswordValidation.md b/packages/component-faraday-ui/src/PasswordValidation.md
new file mode 100644
index 0000000000000000000000000000000000000000..8a09ec872a3a0e02aa10eb86bd20ae1263274b7e
--- /dev/null
+++ b/packages/component-faraday-ui/src/PasswordValidation.md
@@ -0,0 +1,16 @@
+```js
+const React = require('react')
+const reduxForm = require('redux-form').reduxForm;
+
+const PasswordField = () => (
+  <PasswordValidation formLabel="Password" formName="signUpInvitation" />
+)
+
+const Form = reduxForm({
+  form: 'signUpInvitation',
+  destroyOnUnmount: false,
+  forceUnregisterOnUnmount: true,
+})(PasswordField);
+
+<Form />
+```
diff --git a/packages/component-faraday-ui/src/PersonInvitation.js b/packages/component-faraday-ui/src/PersonInvitation.js
index 5b1da63fab1b7fb1b129b175fce6cd0518f4b146..492d7cbdab15ab168f2a84f8c470105d1b24bb15 100644
--- a/packages/component-faraday-ui/src/PersonInvitation.js
+++ b/packages/component-faraday-ui/src/PersonInvitation.js
@@ -9,6 +9,7 @@ const PersonInvitation = ({
   withName,
   hasAnswer,
   isFetching,
+  isLatestVersion,
   revokeInvitation,
   resendInvitation,
   person: { name, email },
@@ -57,6 +58,29 @@ const PersonInvitation = ({
           </OpenModal>
         </Fragment>
       )}
+    {hasAnswer &&
+      isLatestVersion && (
+        <Fragment>
+          <OpenModal
+            confirmText="Revoke"
+            isFetching={isFetching}
+            modalKey={`remove-${id}`}
+            onConfirm={revokeInvitation}
+            subtitle="Deleting the handling editor at this moment will also remove all his work."
+            title="Revoke invitation?"
+          >
+            {showModal => (
+              <IconButton
+                icon="x-circle"
+                iconSize={2}
+                ml={2}
+                onClick={showModal}
+                secondary
+              />
+            )}
+          </OpenModal>
+        </Fragment>
+      )}
   </Root>
 )
 
diff --git a/packages/component-faraday-ui/src/ReviewerReport.js b/packages/component-faraday-ui/src/ReviewerReport.js
index 12391694d8b8032f53d43932629ba1f4d20a12aa..36c67144b3e092102fe16e27a26c2228c0809402 100644
--- a/packages/component-faraday-ui/src/ReviewerReport.js
+++ b/packages/component-faraday-ui/src/ReviewerReport.js
@@ -11,10 +11,11 @@ const ReviewerReport = ({
   onPreview,
   onDownload,
   reportFile,
+  currentUser,
   publicReport,
   privateReport,
   reviewerName,
-  reviewerIndex,
+  reviewerNumber,
   recommendation,
   showOwner = false,
   report: { submittedOn },
@@ -27,14 +28,12 @@ const ReviewerReport = ({
       </Item>
 
       <Item justify="flex-end">
-        {showOwner && (
-          <Fragment>
-            <Text>{reviewerName}</Text>
-            <Text customId ml={1} mr={1}>
-              {`Reviewer ${reviewerIndex}`}
-            </Text>
-          </Fragment>
-        )}
+        <Fragment>
+          {showOwner && <Text>{reviewerName}</Text>}
+          <Text customId ml={1} mr={1}>
+            {`Reviewer ${reviewerNumber}`}
+          </Text>
+        </Fragment>
         <DateParser timestamp={submittedOn}>
           {date => <Text>{date}</Text>}
         </DateParser>
@@ -45,7 +44,7 @@ const ReviewerReport = ({
       <Row mb={2}>
         <Item vertical>
           <Label mb={1 / 2}>Report</Label>
-          <Text>{publicReport}</Text>
+          <Text whiteSpace="pre-wrap">{publicReport}</Text>
         </Item>
       </Row>
     )}
@@ -69,27 +68,29 @@ const ReviewerReport = ({
       <Row mb={2}>
         <Item vertical>
           <Label mb={1 / 2}>Confidential note for the Editorial Team</Label>
-          <Text>{privateReport}</Text>
+          <Text whiteSpace="pre-wrap">{privateReport}</Text>
         </Item>
       </Row>
     )}
   </Root>
 )
 
-export default withProps(({ report, journal: { recommendations = [] } }) => ({
-  recommendation: get(
-    recommendations.find(r => r.value === report.recommendation),
-    'label',
-  ),
-  reportFile: get(report, 'comments.0.files.0'),
-  publicReport: get(report, 'comments.0.content'),
-  privateReport: get(report, 'comments.1.content'),
-  reviewerName: `${get(report, 'reviewer.firstName', '')} ${get(
-    report,
-    'reviewer.lastName',
-    '',
-  )}`,
-}))(ReviewerReport)
+export default withProps(
+  ({ report, currentUser, journal: { recommendations = [] } }) => ({
+    recommendation: get(
+      recommendations.find(r => r.value === report.recommendation),
+      'label',
+    ),
+    reportFile: get(report, 'comments.0.files.0'),
+    publicReport: get(report, 'comments.0.content'),
+    privateReport: get(report, 'comments.1.content'),
+    reviewerName: `${get(currentUser, 'firstName', '')} ${get(
+      currentUser,
+      'lastName',
+      '',
+    )}`,
+  }),
+)(ReviewerReport)
 
 // #region styles
 const Root = styled.div`
diff --git a/packages/component-faraday-ui/src/ReviewerReportAuthor.js b/packages/component-faraday-ui/src/ReviewerReportAuthor.js
index 4df5745b8ce86a109bad547466f3fe5ff57f94de..b921052a43f28c03404f823a5244fd301cbb19dc 100644
--- a/packages/component-faraday-ui/src/ReviewerReportAuthor.js
+++ b/packages/component-faraday-ui/src/ReviewerReportAuthor.js
@@ -21,7 +21,7 @@ const ReviewerReportAuthor = ({
   downloadFile,
   publicReport,
   reviewerName,
-  reviewerIndex,
+  reviewerNumber,
   recommendation,
   showOwner = false,
   report: { submittedOn },
@@ -33,12 +33,12 @@ const ReviewerReportAuthor = ({
           <Row mb={1}>
             <Item vertical>
               <Label mb={1 / 2}>Report</Label>
-              <Text>{publicReport}</Text>
+              <Text whiteSpace="pre-wrap">{publicReport}</Text>
             </Item>
           </Row>
         )}
         <Text customId ml={1} mr={1} whiteSpace="nowrap">
-          {`Reviewer ${reviewerIndex}`}
+          {`Reviewer ${reviewerNumber}`}
         </Text>
         <DateParser timestamp={submittedOn}>
           {date => <Text>{date}</Text>}
@@ -78,7 +78,7 @@ export default compose(
       'reviewer.lastName',
       '',
     )}`,
-    reviewerIndex: get(report, 'reviewerIndex', ''),
+    reviewerNumber: get(report, 'reviewerNumber', ''),
   })),
 )(ReviewerReportAuthor)
 
diff --git a/packages/component-faraday-ui/src/ReviewerReportAuthor.md b/packages/component-faraday-ui/src/ReviewerReportAuthor.md
index 48fdf8f4faefd2db6af22906b08d22899c94edaa..0ee98012afcf5a17ddccc438eecce6524d3f195b 100644
--- a/packages/component-faraday-ui/src/ReviewerReportAuthor.md
+++ b/packages/component-faraday-ui/src/ReviewerReportAuthor.md
@@ -29,7 +29,7 @@ const report = {
   submittedOn: 1538053600624,
   recommendation: 'publish',
   recommendationType: 'review',
-  reviewerIndex: 1
+  reviewerNumber: 1
 }
 
 const journal = {
diff --git a/packages/component-faraday-ui/src/ReviewersTable.js b/packages/component-faraday-ui/src/ReviewersTable.js
index 9f9e2aaa75309afd25084e94a65cbcf65e2c646b..e2197d20c406eeff827b6b4051f48062d7e7493e 100644
--- a/packages/component-faraday-ui/src/ReviewersTable.js
+++ b/packages/component-faraday-ui/src/ReviewersTable.js
@@ -41,9 +41,9 @@ const ReviewersTable = ({
                 invitation,
                 'person.lastName',
               )}`}</Text>
-              {invitation.isAccepted && (
+              {invitation.reviewerNumber && (
                 <Text customId ml={1}>
-                  {renderAcceptedLabel(index)}
+                  Reviewer {invitation.reviewerNumber}
                 </Text>
               )}
             </td>
@@ -102,12 +102,7 @@ export default compose(
   withProps(({ invitations = [] }) => ({
     invitations: orderBy(invitations, orderInvitations),
   })),
-  withProps(({ invitations = [] }) => ({
-    firstAccepted: invitations.findIndex(i => i.hasAnswer && i.isAccepted),
-  })),
   withHandlers({
-    renderAcceptedLabel: ({ firstAccepted, invitations }) => index =>
-      `Reviewer ${index - firstAccepted + 1}`,
     getInvitationStatus: () => ({ hasAnswer, isAccepted }) => {
       if (!hasAnswer) return 'PENDING'
       if (isAccepted) return 'ACCEPTED'
diff --git a/packages/component-faraday-ui/src/ShadowedBox.js b/packages/component-faraday-ui/src/ShadowedBox.js
index 62de72cb0638cd9e3f95d112c6559ddf6709b792..f30f7ef8a6e2d26597b9ce402ec378c83672d54d 100644
--- a/packages/component-faraday-ui/src/ShadowedBox.js
+++ b/packages/component-faraday-ui/src/ShadowedBox.js
@@ -3,24 +3,29 @@ import { H2 } from '@pubsweet/ui'
 import styled, { css } from 'styled-components'
 import { th } from '@pubsweet/ui-toolkit'
 
-import { marginHelper } from './'
+import { marginHelper, paddingHelper } from './'
 
 const width = props => css`
   width: calc(${th('gridUnit')} * ${get(props, 'width', 50)});
 `
 
-export default styled.div`
+export default styled.div.attrs({
+  pt: props => get(props, 'pt', 2),
+  pr: props => get(props, 'pr', 2),
+  pb: props => get(props, 'pb', 2),
+  pl: props => get(props, 'pl', 2),
+})`
   background-color: ${th('colorBackgroundHue')};
   border-radius: ${th('borderRadius')};
   box-shadow: ${th('boxShadow')};
   display: flex;
   flex-direction: column;
 
-  padding: calc(${th('gridUnit')} * 2);
   position: ${props => get(props, 'position', 'initial')};
 
   ${width};
   ${marginHelper};
+  ${paddingHelper};
 
   ${H2} {
     text-align: center;
diff --git a/packages/component-faraday-ui/src/UserProfile.js b/packages/component-faraday-ui/src/UserProfile.js
index 292c04822b7ee0bdbcfb2662a12a91bb961cc8ca..e1bdc2074f60d3a1810ab5e1f595b1289b564c00 100644
--- a/packages/component-faraday-ui/src/UserProfile.js
+++ b/packages/component-faraday-ui/src/UserProfile.js
@@ -7,7 +7,7 @@ import { th } from '@pubsweet/ui-toolkit'
 import { required as requiredValidator } from 'xpub-validators'
 import { compose, withStateHandlers, withProps } from 'recompose'
 import { H3, Spinner, ValidatedField, TextField, Menu } from '@pubsweet/ui'
-import { withCountries } from 'pubsweet-component-faraday-ui'
+import { withCountries, MenuCountry } from 'pubsweet-component-faraday-ui'
 
 import {
   Row,
@@ -179,7 +179,9 @@ const EditUserProfile = compose(
           <Item ml={1} vertical>
             <Label required>Country</Label>
             <ValidatedField
-              component={input => <Menu {...input} options={countries} />}
+              component={input => (
+                <MenuCountry {...input} placeholder="Please select" />
+              )}
               name="country"
               validate={[requiredValidator]}
             />
diff --git a/packages/component-faraday-ui/src/Utils.js b/packages/component-faraday-ui/src/Utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..95db294769acf7d5ee57e8e3be6c6321aaa388a6
--- /dev/null
+++ b/packages/component-faraday-ui/src/Utils.js
@@ -0,0 +1,75 @@
+export const minLength = (value, min) => !!(value && value.length >= min)
+
+export const atLeastOneUppercase = value => {
+  const uppercaseRegex = new RegExp(/([A-Z])+/)
+  return uppercaseRegex.test(value)
+}
+export const atLeastOneLowerrcase = value => {
+  const lowercaseRegex = new RegExp(/([a-z])+/)
+  return lowercaseRegex.test(value)
+}
+export const atLeastOneDigit = value => {
+  const digitRegex = new RegExp(/([0-9])+/)
+  return digitRegex.test(value)
+}
+export const atLeastOnePunctuation = value => {
+  const punctuationRegex = new RegExp(/([,'!@#$%^&*=(){}[\]<>?/\\|.:;_-])+/)
+  return punctuationRegex.test(value)
+}
+
+export const passwordValidator = values => {
+  const errors = {}
+  const { password, confirmNewPassword } = values
+  if (
+    !(
+      minLength(password, 6) &&
+      atLeastOneUppercase(password) &&
+      atLeastOneLowerrcase(password) &&
+      atLeastOnePunctuation(password) &&
+      atLeastOneDigit(password)
+    )
+  ) {
+    errors.password = 'Password criteria not met'
+  }
+  if (!password) {
+    errors.password = 'Required'
+  }
+  if (!confirmNewPassword) {
+    errors.confirmNewPassword = 'Required'
+  } else if (confirmNewPassword !== password) {
+    errors.confirmNewPassword = "Passwords don't match."
+  }
+
+  return errors
+}
+
+export const changePasswordValidator = values => {
+  const {
+    currentPassword = '',
+    confirmNewPassword = '',
+    password = '',
+  } = values
+  const errors = {}
+  if (
+    !(
+      minLength(password, 6) &&
+      atLeastOneUppercase(password) &&
+      atLeastOneLowerrcase(password) &&
+      atLeastOnePunctuation(password) &&
+      atLeastOneDigit(password)
+    )
+  ) {
+    errors.password = 'Password criteria not met'
+  }
+  if (!currentPassword) {
+    errors.currentPassword = 'Required'
+  }
+
+  if (!password) {
+    errors.password = 'Required'
+  } else if (password !== confirmNewPassword) {
+    errors.confirmNewPassword = "Passwords don't match."
+  }
+
+  return errors
+}
diff --git a/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.js b/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.js
index 5518b3ebfa9c3803816c871f4f8b32b717c468d3..696e8a4e405ca71b5e6844cfedf81994863060a2 100644
--- a/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.js
+++ b/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.js
@@ -1,7 +1,14 @@
 import React from 'react'
 import { withProps, compose } from 'recompose'
+import { get } from 'lodash'
 
-import { ContextualBox, ReviewerReportAuthor, Row, Text } from '../'
+import {
+  ContextualBox,
+  ReviewerReportAuthor,
+  Row,
+  Text,
+  indexReviewers,
+} from '../'
 
 const SubmittedReportsNumberForAuthorReviews = ({ reports }) => (
   <Row fitContent justify="flex-end">
@@ -16,12 +23,13 @@ const SubmittedReportsNumberForAuthorReviews = ({ reports }) => (
 )
 
 const AuthorReviews = ({
-  invitations,
+  token,
   journal,
   reports,
   fragment,
-  token,
+  invitations,
   getSignedUrl,
+  reviewerReports,
 }) =>
   reports.length > 0 && (
     <ContextualBox
@@ -43,4 +51,24 @@ const AuthorReviews = ({
     </ContextualBox>
   )
 
-export default compose(withProps())(AuthorReviews)
+export default compose(
+  withProps(
+    ({
+      invitations = [],
+      publonReviewers = [],
+      reviewerReports = [],
+      currentUser,
+    }) => ({
+      token: get(currentUser, 'token', ''),
+      publonReviewers,
+      invitations: invitations.map(i => ({
+        ...i,
+        review: reviewerReports.find(r => r.userId === i.userId),
+      })),
+      reports: indexReviewers(
+        reviewerReports.filter(r => r.submittedOn),
+        invitations,
+      ),
+    }),
+  ),
+)(AuthorReviews)
diff --git a/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.md b/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.md
index aa15139b436a8238511b82742f9202d74516f9ce..6bb32f81d1495f66987b964ce3451a619b57d2ea 100644
--- a/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.md
+++ b/packages/component-faraday-ui/src/contextualBoxes/AuthorReviews.md
@@ -28,7 +28,7 @@ const reports = [
     submittedOn: 1539339580826,
     recommendation: 'minor',
     recommendationType: 'review',
-    reviewerIndex: 1,
+    reviewerNumber: 1,
   },
   {
     id: '21258b47-aba5-4597-926e-765458c4fda2',
@@ -45,7 +45,7 @@ const reports = [
     submittedOn: 1539689169611,
     recommendation: 'publish',
     recommendationType: 'review',
-    reviewerIndex: 2,
+    reviewerNumber: 2,
   },
 ]
 
diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js
index d8a41b821abe0639bbb6f9cfbe6ffe17b9f75189..987aa4a775f7eed363cfcfe0793c5e4baeb1468c 100644
--- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js
+++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js
@@ -13,6 +13,7 @@ import {
   ContextualBox,
   ReviewersTable,
   PublonsTable,
+  indexReviewers,
   ReviewerReport,
   InviteReviewers,
   ReviewerBreakdown,
@@ -118,14 +119,14 @@ const ReviewerDetails = ({
                 {reports.length === 0 && (
                   <Text align="center">No reports submitted yet.</Text>
                 )}
-                {reports.map((report, index) => (
+                {reports.map(report => (
                   <ReviewerReport
                     journal={journal}
                     key={report.id}
                     onDownload={downloadFile}
                     onPreview={previewFile}
                     report={report}
-                    reviewerIndex={index + 1}
+                    reviewerNumber={report.reviewerNumber}
                     showOwner
                   />
                 ))}
@@ -154,7 +155,10 @@ export default compose(
         ...i,
         review: reviewerReports.find(r => r.userId === i.userId),
       })),
-      reports: reviewerReports.filter(r => r.submittedOn),
+      reports: indexReviewers(
+        reviewerReports.filter(r => r.submittedOn),
+        invitations,
+      ),
     }),
   ),
   withProps(({ currentUser }) => ({
diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerReportForm.js b/packages/component-faraday-ui/src/contextualBoxes/ReviewerReportForm.js
index acd472f65bebb023a945a626abc54357b33555d7..8d66da2f9edae85b6e2cb7236b5883f23136d0ce 100644
--- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerReportForm.js
+++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerReportForm.js
@@ -3,6 +3,7 @@ import styled from 'styled-components'
 import { th } from '@pubsweet/ui-toolkit'
 import { required } from 'xpub-validators'
 import { Button, FilePicker, Menu, Spinner, ValidatedField } from '@pubsweet/ui'
+import { initial } from 'lodash'
 
 import {
   Row,
@@ -34,6 +35,7 @@ const ReviewerReportForm = ({
   review = {},
   formValues = {},
   journal: { recommendations },
+  isFetchingFromAutosave,
 }) => (
   <ContextualBox
     expanded={expanded}
@@ -51,7 +53,9 @@ const ReviewerReportForm = ({
         >
           <Label required>Recommendation</Label>
           <ValidatedField
-            component={input => <Menu {...input} options={recommendations} />}
+            component={input => (
+              <Menu {...input} options={initial(recommendations)} />
+            )}
             name="recommendation"
             validate={[required]}
           />
@@ -145,7 +149,7 @@ const ReviewerReportForm = ({
         </Row>
       )}
       <Row justify="flex-end" mt={1}>
-        {isFetching ? (
+        {isFetching || isFetchingFromAutosave ? (
           <Spinner />
         ) : (
           <Button
diff --git a/packages/component-faraday-ui/src/helpers/formValidators.js b/packages/component-faraday-ui/src/helpers/formValidators.js
index 3007139196800247ef5057ea5da9e8c9eeb27e4d..b3a6f6a56d39df8c0ed49732ebf311291ea2cbe9 100644
--- a/packages/component-faraday-ui/src/helpers/formValidators.js
+++ b/packages/component-faraday-ui/src/helpers/formValidators.js
@@ -10,10 +10,10 @@ export const passwordValidator = values => {
   if (!values.password) {
     errors.password = 'Required'
   }
-  if (!values.confirmPassword) {
-    errors.confirmPassword = 'Required'
-  } else if (values.confirmPassword !== values.password) {
-    errors.confirmPassword = "Passwords don't match."
+  if (!values.confirmNewPassword) {
+    errors.confirmNewPassword = 'Required'
+  } else if (values.confirmNewPassword !== values.password) {
+    errors.confirmNewPassword = "Passwords don't match."
   }
 
   return errors
diff --git a/packages/component-faraday-ui/src/helpers/utils.js b/packages/component-faraday-ui/src/helpers/utils.js
index 898fd3ebc62282763a1331fd359f9d8da85a732c..8b798e34e7b73272c922ab6201019b5b4b221fb9 100644
--- a/packages/component-faraday-ui/src/helpers/utils.js
+++ b/packages/component-faraday-ui/src/helpers/utils.js
@@ -1,4 +1,4 @@
-import { get, chain } from 'lodash'
+import { get, chain, find } from 'lodash'
 
 export const handleError = fn => e => {
   fn(get(JSON.parse(e.response), 'error', 'Oops! Something went wrong!'))
@@ -10,3 +10,14 @@ export const getReportComments = ({ report, isPublic = false }) =>
     .find(c => c.public === isPublic)
     .get('content')
     .value()
+
+export const indexReviewers = (reports = [], invitations = []) => {
+  reports.forEach(report => {
+    report.reviewerNumber = get(
+      find(invitations, ['userId', report.userId]),
+      'reviewerNumber',
+      0,
+    )
+  })
+  return reports
+}
diff --git a/packages/component-faraday-ui/src/index.js b/packages/component-faraday-ui/src/index.js
index a99b60e4c1b2e18f15810beb84255aecd4ad4260..2d3859c78c7a2eda863ea5cef314771995ffa6b6 100644
--- a/packages/component-faraday-ui/src/index.js
+++ b/packages/component-faraday-ui/src/index.js
@@ -1,5 +1,6 @@
 export * from './styledHelpers'
 export * from './helpers'
+export * from './Utils'
 // modals
 export * from './modals'
 export * from './gridItems'
@@ -48,6 +49,8 @@ export { default as WizardFiles } from './WizardFiles'
 export { default as TextTooltip } from './TextTooltip'
 export { default as EditorialReportCard } from './EditorialReportCard'
 export { default as ReviewerReportAuthor } from './ReviewerReportAuthor'
+export { default as PasswordValidation } from './PasswordValidation'
+export { default as MenuCountry } from './MenuCountry'
 
 export { SubmitRevision } from './submissionRevision'
 
diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js
index dc5314f613c215bb949c5735f25c395f83e8b5f6..07fcac0087453c6943265160174beb18b23f95f0 100644
--- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js
+++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js
@@ -1,5 +1,5 @@
 import React from 'react'
-import { get, last } from 'lodash'
+import { get, initial } from 'lodash'
 import { compose, withProps } from 'recompose'
 import styled from 'styled-components'
 import { reduxForm } from 'redux-form'
@@ -39,11 +39,24 @@ const eicDecisions = [
     modalTitle: 'Reject Manuscript',
     modalSubtitle: 'A rejection decision is final',
   },
+  {
+    value: 'revision',
+    label: 'Request Revision',
+    modalTitle: 'Request Revision',
+  },
 ]
 
+const filterOptions = (eicDecisions, status) => {
+  if (status === 'submitted') return eicDecisions.slice(2)
+  if (status === 'pendingApproval') return initial(eicDecisions)
+  return eicDecisions.slice(2, 3)
+}
 const ManuscriptEicDecision = ({
-  isFetching,
+  status,
+  options,
+  decision,
   formValues,
+  isFetching,
   handleSubmit,
   messagesLabel,
   collection = {},
@@ -55,42 +68,33 @@ const ManuscriptEicDecision = ({
     {...rest}
   >
     <Root>
-      <Row justify="flex-start">
+      <Row justify="flex-start" pl={1} pt={1}>
         <ItemOverrideAlert flex={0} vertical>
           <Label required>Decision</Label>
           <ValidatedField
-            component={input => (
-              <Menu
-                {...input}
-                options={
-                  get(collection, 'status', 'submitted') !== 'pendingApproval'
-                    ? [last(eicDecisions)]
-                    : eicDecisions
-                }
-              />
-            )}
+            component={input => <Menu {...input} options={options} />}
             name="decision"
             validate={[required]}
           />
         </ItemOverrideAlert>
       </Row>
 
-      {get(formValues, 'decision') !== 'publish' && (
-        <Row mt={2}>
+      {decision !== 'publish' && (
+        <Row mt={2} pl={1} pr={1}>
           <Item vertical>
-            <Label required>
+            <Label required={decision !== 'reject'}>
               {messagesLabel[get(formValues, 'decision', 'reject')]}
             </Label>
             <ValidatedField
               component={ValidatedTextArea}
               name="message"
-              validate={[required]}
+              validate={decision !== 'reject' ? [required] : undefined}
             />
           </Item>
         </Row>
       )}
 
-      <Row justify="flex-end" mt={4}>
+      <Row justify="flex-end" mt={1} pr={1}>
         <Button onClick={handleSubmit} primary size="medium">
           SUBMIT DECISION
         </Button>
@@ -106,13 +110,18 @@ export default compose(
     modalKey: 'eic-decision',
     modalComponent: MultiAction,
   })),
-  withProps(({ formValues }) => ({
+  withProps(({ formValues, collection }) => ({
     modalTitle: eicDecisions.find(
       o => o.value === get(formValues, 'decision', 'publish'),
     ).modalTitle,
     modalSubtitle: eicDecisions.find(
       o => o.value === get(formValues, 'decision', 'publish'),
     ).modalSubtitle,
+    decision: get(formValues, 'decision'),
+    options: filterOptions(
+      eicDecisions,
+      get(collection, 'status', 'submitted'),
+    ),
   })),
   reduxForm({
     form: 'eic-decision',
diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js
index 699edec06e90cfc92695a9cbec10bc2ca8e81d70..296bebafcfc99f9f765f677a90d97d74f630b3e1 100644
--- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js
+++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js
@@ -1,5 +1,5 @@
 import React, { Fragment } from 'react'
-import { get, chain } from 'lodash'
+import { get, chain, isEmpty } from 'lodash'
 import { H2, H4, DateParser, Button } from '@pubsweet/ui'
 import {
   compose,
@@ -121,6 +121,7 @@ export default compose(
       revokeInvitation,
       pendingInvitation = {},
       handlingEditors = [],
+      isLatestVersion,
       currentUser: {
         permissions: { canAssignHE },
         id: currentUserId,
@@ -128,31 +129,45 @@ export default compose(
         editorInChief,
       },
       collection: { handlingEditor },
+      collection,
       currentUser,
     }) => () => {
       if (pendingInvitation.userId === currentUserId) {
         return <Text ml={1}>Invited</Text>
       }
-      if (pendingInvitation.userId && (admin || editorInChief)) {
+      const invitedHeId =
+        get(pendingInvitation, 'userId', false) ||
+        get(heInvitation, 'userId', false)
+
+      if (invitedHeId && (admin || editorInChief)) {
         const person = chain(handlingEditors)
-          .filter(he => he.id === pendingInvitation.userId)
+          .filter(he => he.id === invitedHeId)
           .map(he => ({ ...he, name: `${he.firstName} ${he.lastName}` }))
           .first()
           .value()
 
+        let invitedHe = {}
+        if (get(pendingInvitation, 'userId', false)) {
+          invitedHe = pendingInvitation
+        } else if (get(heInvitation, 'userId', false)) {
+          invitedHe = heInvitation
+        }
         return (
           <PersonInvitation
             isFetching={isFetching}
+            isLatestVersion={isLatestVersion}
             ml={1}
             withName
-            {...pendingInvitation}
+            {...invitedHe}
             onResend={resendInvitation}
             onRevoke={revokeInvitation}
             person={person}
           />
         )
       }
-
+      if (!isEmpty(pendingInvitation)) {
+        return <Text ml={1}>{handlingEditor.name}</Text>
+      }
       if (heInvitation) {
         return <Text ml={1}>{handlingEditor.name}</Text>
       }
@@ -170,7 +185,7 @@ export default compose(
           </Button>
         )
       }
-      return <Text ml={1}>Assigned</Text>
+      return <Text ml={1}>Unassigned</Text>
     },
   }),
   setDisplayName('ManuscriptHeader'),
diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptMetadata.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptMetadata.js
index f86208b45f71994aaf8ae38bec91a88df103ed40..69d79027af59245a32fbd5d180b8555cf926ce50 100644
--- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptMetadata.js
+++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptMetadata.js
@@ -5,6 +5,7 @@ import { withProps } from 'recompose'
 import {
   Text,
   Item,
+  Row,
   ContextualBox,
   ManuscriptFileList,
 } from 'pubsweet-component-faraday-ui'
@@ -24,7 +25,7 @@ const ManuscriptMetadata = ({
           startExpanded
           transparent
         >
-          <Text mb={1} mt={1}>
+          <Text mb={1} mt={1} whiteSpace="pre-wrap">
             {abstract}
           </Text>
         </ContextualBox>
@@ -37,9 +38,30 @@ const ManuscriptMetadata = ({
           label="Conflict of Interest"
           transparent
         >
-          <Text mb={1} mt={1}>
-            {get(conflicts, 'message', '')}
-          </Text>
+          <Row alignItems="center" justify="flex-start">
+            <Text mb={1} mt={1}>
+              Conflicts of interest:
+            </Text>
+            <Text ml={1 / 2}>{get(conflicts, 'message', '')}</Text>
+          </Row>
+          {get(conflicts, 'dataAvailabilityMessage', '') && (
+            <Row alignItems="center" justify="flex-start">
+              <Text mb={1} mt={1}>
+                Data availability statment:
+              </Text>
+              <Text ml={1 / 2}>
+                {get(conflicts, 'dataAvailabilityMessage', '')}
+              </Text>
+            </Row>
+          )}
+          {get(conflicts, 'fundingMessage', '') && (
+            <Row alignItems="center" justify="flex-start">
+              <Text mb={1} mt={1}>
+                Funding statment:
+              </Text>
+              <Text ml={1 / 2}>{get(conflicts, 'fundingMessage', '')}</Text>
+            </Row>
+          )}
         </ContextualBox>
       </Item>
     )}
diff --git a/packages/component-faraday-ui/src/modals/FormModal.js b/packages/component-faraday-ui/src/modals/FormModal.js
index e9ab27640d22bd0783add2574425f16103b0b55a..a139df8e733baa7732263b383c066ba776220ed0 100644
--- a/packages/component-faraday-ui/src/modals/FormModal.js
+++ b/packages/component-faraday-ui/src/modals/FormModal.js
@@ -32,7 +32,6 @@ const FormModal = ({
   countries,
   confirmText = 'OK',
   cancelText = 'Cancel',
-  //
   onSubmit,
   initialValues,
 }) => (
diff --git a/packages/component-fixture-manager/src/fixtures/collectionIDs.js b/packages/component-fixture-manager/src/fixtures/collectionIDs.js
index e66bb71a232758a7de0746033ba2493745157250..a6082362b1083a2123f8013aea6451fdb4b23023 100644
--- a/packages/component-fixture-manager/src/fixtures/collectionIDs.js
+++ b/packages/component-fixture-manager/src/fixtures/collectionIDs.js
@@ -4,8 +4,13 @@ const chance = new Chance()
 
 module.exports = {
   standardCollID: chance.guid(),
-  collectionReviewCompletedID: chance.guid(),
   collectionNoInvitesID: chance.guid(),
   twoVersionsCollectionId: chance.guid(),
+  minorRevisionCollectionID: chance.guid(),
+  majorRevisionCollectionID: chance.guid(),
+  collectionReviewCompletedID: chance.guid(),
+  oneReviewedFragmentCollectionID: chance.guid(),
   noEditorRecomedationCollectionID: chance.guid(),
+  minorRevisionWithoutReviewCollectionID: chance.guid(),
+  majorRevisionWithoutReviewCollectionID: chance.guid(),
 }
diff --git a/packages/component-fixture-manager/src/fixtures/collections.js b/packages/component-fixture-manager/src/fixtures/collections.js
index 7d1df81e4f6379f9bb4d21bc243a2ed97a9e9e2d..4d92999127af2422edb3c3edd927ec744f25ea92 100644
--- a/packages/component-fixture-manager/src/fixtures/collections.js
+++ b/packages/component-fixture-manager/src/fixtures/collections.js
@@ -1,23 +1,35 @@
 const Chance = require('chance')
+
 const {
   user,
   handlingEditor,
   answerHE,
   noRecommendationHE,
 } = require('./userData')
+
 const {
   fragment,
   fragment1,
-  reviewCompletedFragment,
   noInvitesFragment,
+  noInvitesFragment1,
+  minorRevisionWithReview,
+  majorRevisionWithReview,
+  reviewCompletedFragment,
+  minorRevisionWithoutReview,
   noEditorRecomedationFragment,
 } = require('./fragments')
+
 const {
   standardCollID,
-  collectionReviewCompletedID,
   collectionNoInvitesID,
   twoVersionsCollectionId,
+  minorRevisionCollectionID,
+  majorRevisionCollectionID,
+  collectionReviewCompletedID,
+  oneReviewedFragmentCollectionID,
   noEditorRecomedationCollectionID,
+  minorRevisionWithoutReviewCollectionID,
+  majorRevisionWithoutReviewCollectionID,
 } = require('./collectionIDs')
 
 const chance = new Chance()
@@ -30,6 +42,7 @@ const collections = {
     fragments: [fragment.id],
     owners: [user.id],
     save: jest.fn(() => collections.collection),
+    getFragments: jest.fn(() => [fragment]),
     invitations: [
       {
         id: chance.guid(),
@@ -73,6 +86,7 @@ const collections = {
     fragments: [fragment.id],
     owners: [user.id],
     save: jest.fn(() => collections.collection),
+    getFragments: jest.fn(() => [fragment]),
     invitations: [
       {
         id: chance.guid(),
@@ -115,6 +129,7 @@ const collections = {
     fragments: [fragment1.id, noInvitesFragment.id],
     owners: [user.id],
     save: jest.fn(() => collections.collection2),
+    getFragments: jest.fn(() => [fragment1, noInvitesFragment]),
     invitations: [
       {
         id: chance.guid(),
@@ -159,6 +174,7 @@ const collections = {
     created: chance.timestamp(),
     customId: '0000001',
     fragments: [reviewCompletedFragment.id],
+    getFragments: jest.fn(() => [reviewCompletedFragment]),
     invitations: [
       {
         id: chance.guid(),
@@ -189,6 +205,7 @@ const collections = {
     fragments: [fragment.id, reviewCompletedFragment.id],
     owners: [user.id],
     save: jest.fn(() => collections.collection),
+    getFragments: jest.fn(() => [fragment, reviewCompletedFragment]),
     invitations: [
       {
         id: chance.guid(),
@@ -219,6 +236,7 @@ const collections = {
     fragments: [],
     owners: [user.id],
     save: jest.fn(() => collections.collection),
+    getFragments: jest.fn(() => []),
     customId: chance.natural({ min: 999999, max: 9999999 }),
   },
   noEditorRecomedationCollection: {
@@ -228,6 +246,7 @@ const collections = {
     fragments: [noEditorRecomedationFragment.id],
     owners: [user.id],
     save: jest.fn(() => collections.noEditorRecomedationCollection),
+    getFragments: jest.fn(() => [noEditorRecomedationFragment]),
     invitations: [
       {
         id: chance.guid(),
@@ -263,6 +282,183 @@ const collections = {
     },
     status: 'reviewCompleted',
   },
+  oneReviewedFragmentCollection: {
+    id: oneReviewedFragmentCollectionID,
+    title: chance.sentence(),
+    type: 'collection',
+    fragments: [reviewCompletedFragment.id, noInvitesFragment.id],
+    owners: [user.id],
+    save: jest.fn(() => collections.collection),
+    getFragments: jest.fn(() => [reviewCompletedFragment, noInvitesFragment]),
+    invitations: [
+      {
+        id: chance.guid(),
+        role: 'handlingEditor',
+        hasAnswer: true,
+        isAccepted: false,
+        userId: handlingEditor.id,
+        invitedOn: chance.timestamp(),
+        respondedOn: null,
+      },
+    ],
+    handlingEditor: {
+      id: handlingEditor.id,
+      hasAnswer: false,
+      isAccepted: false,
+      email: handlingEditor.email,
+      invitedOn: chance.timestamp(),
+      respondedOn: null,
+      name: `${handlingEditor.firstName} ${handlingEditor.lastName}`,
+    },
+    status: 'revisionRequested',
+    customId: chance.natural({ min: 999999, max: 9999999 }),
+  },
+  minorRevisionCollection: {
+    id: minorRevisionCollectionID,
+    delete: jest.fn(),
+    title: chance.sentence(),
+    type: 'collection',
+    fragments: [minorRevisionWithReview.id, noInvitesFragment1.id],
+    owners: [user.id],
+    save: jest.fn(() => collections.minorRevisionCollection),
+    getFragments: jest.fn(() => [minorRevisionWithReview, noInvitesFragment1]),
+    invitations: [
+      {
+        id: chance.guid(),
+        role: 'handlingEditor',
+        hasAnswer: true,
+        isAccepted: true,
+        userId: handlingEditor.id,
+        invitedOn: chance.timestamp(),
+        respondedOn: null,
+      },
+    ],
+    handlingEditor: {
+      id: handlingEditor.id,
+      hasAnswer: true,
+      isAccepted: true,
+      email: handlingEditor.email,
+      invitedOn: chance.timestamp(),
+      respondedOn: chance.timestamp(),
+      name: `${handlingEditor.firstName} ${handlingEditor.lastName}`,
+    },
+    technicalChecks: {
+      token: chance.guid(),
+    },
+    status: 'reviewCompleted',
+    customId: chance.natural({ min: 999999, max: 9999999 }),
+  },
+  minorRevisionWithoutReviewCollection: {
+    id: minorRevisionWithoutReviewCollectionID,
+    delete: jest.fn(),
+    title: chance.sentence(),
+    type: 'collection',
+    fragments: [minorRevisionWithoutReview.id, noInvitesFragment1.id],
+    owners: [user.id],
+    save: jest.fn(() => collections.minorRevisionWithoutReviewCollection),
+    getFragments: jest.fn(() => [
+      minorRevisionWithoutReview,
+      noInvitesFragment1,
+    ]),
+    invitations: [
+      {
+        id: chance.guid(),
+        role: 'handlingEditor',
+        hasAnswer: true,
+        isAccepted: true,
+        userId: handlingEditor.id,
+        invitedOn: chance.timestamp(),
+        respondedOn: null,
+      },
+    ],
+    handlingEditor: {
+      id: handlingEditor.id,
+      hasAnswer: true,
+      isAccepted: true,
+      email: handlingEditor.email,
+      invitedOn: chance.timestamp(),
+      respondedOn: chance.timestamp(),
+      name: `${handlingEditor.firstName} ${handlingEditor.lastName}`,
+    },
+    technicalChecks: {
+      token: chance.guid(),
+    },
+    status: 'reviewCompleted',
+    customId: chance.natural({ min: 999999, max: 9999999 }),
+  },
+  majorRevisionCollection: {
+    id: majorRevisionCollectionID,
+    delete: jest.fn(),
+    title: chance.sentence(),
+    type: 'collection',
+    fragments: [majorRevisionWithReview.id, reviewCompletedFragment.id],
+    owners: [user.id],
+    save: jest.fn(() => collections.minorRevisionCollection),
+    getFragments: jest.fn(() => [
+      majorRevisionWithReview,
+      reviewCompletedFragment,
+    ]),
+    invitations: [
+      {
+        id: chance.guid(),
+        role: 'handlingEditor',
+        hasAnswer: true,
+        isAccepted: true,
+        userId: handlingEditor.id,
+        invitedOn: chance.timestamp(),
+        respondedOn: null,
+      },
+    ],
+    handlingEditor: {
+      id: handlingEditor.id,
+      hasAnswer: true,
+      isAccepted: true,
+      email: handlingEditor.email,
+      invitedOn: chance.timestamp(),
+      respondedOn: chance.timestamp(),
+      name: `${handlingEditor.firstName} ${handlingEditor.lastName}`,
+    },
+    technicalChecks: {
+      token: chance.guid(),
+    },
+    status: 'reviewCompleted',
+    customId: chance.natural({ min: 999999, max: 9999999 }),
+  },
+  majorRevisionWithoutReviewCollection: {
+    id: majorRevisionWithoutReviewCollectionID,
+    delete: jest.fn(),
+    title: chance.sentence(),
+    type: 'collection',
+    fragments: [majorRevisionWithReview.id, noInvitesFragment1.id],
+    owners: [user.id],
+    save: jest.fn(() => collections.majorRevisionWithoutReviewCollection),
+    getFragments: jest.fn(() => [majorRevisionWithReview, noInvitesFragment1]),
+    invitations: [
+      {
+        id: chance.guid(),
+        role: 'handlingEditor',
+        hasAnswer: true,
+        isAccepted: true,
+        userId: handlingEditor.id,
+        invitedOn: chance.timestamp(),
+        respondedOn: null,
+      },
+    ],
+    handlingEditor: {
+      id: handlingEditor.id,
+      hasAnswer: true,
+      isAccepted: true,
+      email: handlingEditor.email,
+      invitedOn: chance.timestamp(),
+      respondedOn: chance.timestamp(),
+      name: `${handlingEditor.firstName} ${handlingEditor.lastName}`,
+    },
+    technicalChecks: {
+      token: chance.guid(),
+    },
+    status: 'reviewCompleted',
+    customId: chance.natural({ min: 999999, max: 9999999 }),
+  },
 }
 
 module.exports = collections
diff --git a/packages/component-fixture-manager/src/fixtures/fragments.js b/packages/component-fixture-manager/src/fixtures/fragments.js
index 026712ff89a6764daef0f2af34120f5ac7870741..475a277b97116a14d17d41e0c92bbdb6daba8c75 100644
--- a/packages/component-fixture-manager/src/fixtures/fragments.js
+++ b/packages/component-fixture-manager/src/fixtures/fragments.js
@@ -99,6 +99,69 @@ const fragments = {
         createdOn: chance.timestamp(),
         updatedOn: chance.timestamp(),
       },
+      {
+        recommendation: 'publish',
+        recommendationType: 'editorRecommendation',
+        comments: [
+          {
+            content: chance.paragraph(),
+            public: true,
+            files: [
+              {
+                id: chance.guid(),
+                name: 'file.pdf',
+                size: chance.natural(),
+              },
+            ],
+          },
+        ],
+        id: chance.guid(),
+        userId: handlingEditor.id,
+        createdOn: 1542361074012,
+        updatedOn: chance.timestamp(),
+      },
+      {
+        recommendation: 'return-to-handling-editor',
+        recommendationType: 'editorRecommendation',
+        comments: [
+          {
+            content: chance.paragraph(),
+            public: true,
+            files: [
+              {
+                id: chance.guid(),
+                name: 'file.pdf',
+                size: chance.natural(),
+              },
+            ],
+          },
+        ],
+        id: chance.guid(),
+        userId: admin.id,
+        createdOn: 1542361115749,
+        updatedOn: chance.timestamp(),
+      },
+      {
+        recommendation: 'publish',
+        recommendationType: 'editorRecommendation',
+        comments: [
+          {
+            content: chance.paragraph(),
+            public: chance.bool(),
+            files: [
+              {
+                id: chance.guid(),
+                name: 'file.pdf',
+                size: chance.natural(),
+              },
+            ],
+          },
+        ],
+        id: chance.guid(),
+        userId: handlingEditor.id,
+        createdOn: 1542361115750,
+        updatedOn: chance.timestamp(),
+      },
       {
         recommendation: 'publish',
         recommendationType: 'editorRecommendation',
@@ -117,6 +180,27 @@ const fragments = {
         ],
         id: chance.guid(),
         userId: admin.id,
+        createdOn: 1542361115751,
+        updatedOn: chance.timestamp(),
+      },
+      {
+        recommendation: 'revision',
+        recommendationType: 'editorRecommendation',
+        comments: [
+          {
+            content: chance.paragraph(),
+            public: true,
+            files: [
+              {
+                id: chance.guid(),
+                name: 'file.pdf',
+                size: chance.natural(),
+              },
+            ],
+          },
+        ],
+        id: chance.guid(),
+        userId: admin.id,
         createdOn: chance.timestamp(),
         updatedOn: chance.timestamp(),
       },
@@ -277,6 +361,7 @@ const fragments = {
         invitedOn: chance.timestamp(),
         isAccepted: true,
         respondedOn: chance.timestamp(),
+        reviewerNumber: 2,
       },
       {
         id: chance.guid(),
@@ -457,6 +542,48 @@ const fragments = {
         updatedOn: chance.timestamp(),
         submittedOn: chance.timestamp(),
       },
+      {
+        recommendation: 'publish',
+        recommendationType: 'editorRecommendation',
+        comments: [
+          {
+            content: chance.paragraph(),
+            public: true,
+            files: [
+              {
+                id: chance.guid(),
+                name: 'file.pdf',
+                size: chance.natural(),
+              },
+            ],
+          },
+        ],
+        id: chance.guid(),
+        userId: handlingEditor.id,
+        createdOn: 1542361074012,
+        updatedOn: chance.timestamp(),
+      },
+      {
+        recommendation: 'return-to-handling-editor',
+        recommendationType: 'editorRecommendation',
+        comments: [
+          {
+            content: chance.paragraph(),
+            public: true,
+            files: [
+              {
+                id: chance.guid(),
+                name: 'file.pdf',
+                size: chance.natural(),
+              },
+            ],
+          },
+        ],
+        id: chance.guid(),
+        userId: admin.id,
+        createdOn: 1542361115749,
+        updatedOn: chance.timestamp(),
+      },
     ],
     authors: [
       {
diff --git a/packages/component-fixture-manager/src/fixtures/teamIDs.js b/packages/component-fixture-manager/src/fixtures/teamIDs.js
index 162cdb391585ed6789018579dbb1469271f8d2f2..1de0429354609073c2809e77c3f470ccc8094562 100644
--- a/packages/component-fixture-manager/src/fixtures/teamIDs.js
+++ b/packages/component-fixture-manager/src/fixtures/teamIDs.js
@@ -5,9 +5,10 @@ const chance = new Chance()
 module.exports = {
   heTeamID: chance.guid(),
   revTeamID: chance.guid(),
+  rev1TeamID: chance.guid(),
   authorTeamID: chance.guid(),
+  majorRevisionHeTeamID: chance.guid(),
   revRecommendationTeamID: chance.guid(),
-  rev1TeamID: chance.guid(),
   heNoRecommendationTeamID: chance.guid(),
   revNoEditorRecommendationTeamID: chance.guid(),
 }
diff --git a/packages/component-fixture-manager/src/fixtures/teams.js b/packages/component-fixture-manager/src/fixtures/teams.js
index dbd51a4aca783c1315e45fe3749b7c2bd1f8145c..79b650069ccefcad84384488bc42b4294186fde9 100644
--- a/packages/component-fixture-manager/src/fixtures/teams.js
+++ b/packages/component-fixture-manager/src/fixtures/teams.js
@@ -5,21 +5,28 @@ const fragments = require('./fragments')
 const {
   heTeamID,
   revTeamID,
+  rev1TeamID,
   authorTeamID,
+  majorRevisionHeTeamID,
   revRecommendationTeamID,
-  rev1TeamID,
   heNoRecommendationTeamID,
   revNoEditorRecommendationTeamID,
 } = require('./teamIDs')
 const { submittingAuthor } = require('./userData')
 
-const { collection, noEditorRecomedationCollection } = collections
+const {
+  collection,
+  majorRevisionCollection,
+  noEditorRecomedationCollection,
+} = collections
+
 const {
   fragment,
-  reviewCompletedFragment,
   fragment1,
+  reviewCompletedFragment,
   noEditorRecomedationFragment,
 } = fragments
+
 const {
   handlingEditor,
   reviewer,
@@ -148,5 +155,22 @@ const teams = {
     updateProperties: jest.fn(() => teams.revNoEditorRecommendationTeam),
     id: revNoEditorRecommendationTeamID,
   },
+  majorRevisionHeTeam: {
+    teamType: {
+      name: 'handlingEditor',
+      permissions: 'handlingEditor',
+    },
+    group: 'handlingEditor',
+    name: 'HandlingEditor',
+    object: {
+      type: 'collection',
+      id: majorRevisionCollection.id,
+    },
+    members: [handlingEditor.id],
+    save: jest.fn(() => teams.majorRevisionHeTeam),
+    delete: jest.fn(),
+    updateProperties: jest.fn(() => teams.majorRevisionHeTeam),
+    id: majorRevisionHeTeamID,
+  },
 }
 module.exports = teams
diff --git a/packages/component-fixture-manager/src/fixtures/users.js b/packages/component-fixture-manager/src/fixtures/users.js
index 0ec653aab24e414415d7a11904367c2fdf94ede3..2b03c59776ada663e00e2a9ac58f8c73a4060db7 100644
--- a/packages/component-fixture-manager/src/fixtures/users.js
+++ b/packages/component-fixture-manager/src/fixtures/users.js
@@ -5,9 +5,10 @@ const chance = new Chance()
 const {
   heTeamID,
   revTeamID,
+  rev1TeamID,
   authorTeamID,
+  majorRevisionHeTeamID,
   revRecommendationTeamID,
-  rev1TeamID,
   heNoRecommendationTeamID,
   revNoEditorRecommendationTeamID,
 } = require('./teamIDs')
@@ -24,7 +25,7 @@ users = keys.reduce((obj, item) => {
       teams = [heTeamID]
       break
     case 'handlingEditor':
-      teams = [heTeamID]
+      teams = [heTeamID, majorRevisionHeTeamID]
       break
     case 'noRecommendationHE':
       teams = [heNoRecommendationTeamID]
diff --git a/packages/component-helper-service/src/services/Collection.js b/packages/component-helper-service/src/services/Collection.js
index 6e05d9a584f61eddc5a63a7ad309ac81bea1edb6..e95d5e8666a94464384fac61f693d199fe3c154b 100644
--- a/packages/component-helper-service/src/services/Collection.js
+++ b/packages/component-helper-service/src/services/Collection.js
@@ -1,4 +1,17 @@
-const { findLast, get } = require('lodash')
+const config = require('config')
+const { v4 } = require('uuid')
+const logger = require('@pubsweet/logger')
+
+const {
+  findLast,
+  isEmpty,
+  maxBy,
+  get,
+  flatMap,
+  last,
+  has,
+  set,
+} = require('lodash')
 
 const Fragment = require('./Fragment')
 
@@ -33,25 +46,6 @@ class Collection {
     return this.updateStatus({ newStatus })
   }
 
-  async updateFinalStatusByRecommendation({ recommendation }) {
-    let newStatus
-    switch (recommendation) {
-      case 'reject':
-        newStatus = 'rejected'
-        break
-      case 'publish':
-        newStatus = 'accepted'
-        break
-      case 'return-to-handling-editor':
-        newStatus = 'reviewCompleted'
-        break
-      default:
-        break
-    }
-
-    await this.updateStatus({ newStatus })
-  }
-
   async updateStatus({ newStatus }) {
     this.collection.status = newStatus
     await this.collection.save()
@@ -89,26 +83,33 @@ class Collection {
       await this.updateStatus({ newStatus: 'heAssigned' })
   }
 
-  async updateStatusOnRecommendation({ isEditorInChief, recommendation }) {
-    if (isEditorInChief) {
-      if (recommendation === 'return-to-handling-editor') {
-        return this.updateStatus({ newStatus: 'reviewCompleted' })
-      }
-      return this.updateFinalStatusByRecommendation({
-        recommendation,
-      })
-    }
-    return this.updateStatusByRecommendation({
-      recommendation,
-      isHandlingEditor: true,
-    })
-  }
-
   getHELastName() {
     const [firstName, lastName] = this.collection.handlingEditor.name.split(' ')
     return lastName || firstName
   }
 
+  async getReviewerNumber({ userId }) {
+    const allCollectionFragments = await this.collection.getFragments()
+    const allCollectionInvitations = flatMap(
+      allCollectionFragments,
+      fragment => fragment.invitations,
+    ).filter(Boolean)
+
+    const allNumberedInvitationsForUser = allCollectionInvitations
+      .filter(invite => invite.userId === userId)
+      .filter(invite => invite.reviewerNumber)
+
+    if (isEmpty(allNumberedInvitationsForUser)) {
+      const maxReviewerNumber = get(
+        maxBy(allCollectionInvitations, 'reviewerNumber'),
+        'reviewerNumber',
+        0,
+      )
+      return maxReviewerNumber + 1
+    }
+    return allNumberedInvitationsForUser[0].reviewerNumber
+  }
+
   // eslint-disable-next-line class-methods-use-this
   hasAtLeastOneReviewReport(fragments) {
     return fragments.some(fragment =>
@@ -126,17 +127,21 @@ class Collection {
       [],
     )
 
-    const lastRecommendationByHE = findLast(
+    const lastEditorRecommendation = findLast(
       previousVersionRecommendations,
       recommendation =>
-        recommendation.userId === this.collection.handlingEditor.id &&
         recommendation.recommendationType === 'editorRecommendation',
     )
-    if (lastRecommendationByHE.recommendation === 'minor') {
+
+    if (lastEditorRecommendation.recommendation === 'minor') {
       return this.hasAtLeastOneReviewReport(fragments)
-    } else if (lastRecommendationByHE.recommendation === 'major') {
+    } else if (
+      ['major', 'revision'].includes(lastEditorRecommendation.recommendation)
+    ) {
       return fragmentHelper.hasReviewReport()
     }
+
+    return false
   }
 
   async getAllFragments({ FragmentModel }) {
@@ -146,6 +151,92 @@ class Collection {
       ),
     )
   }
+
+  isLatestVersion(fragmentId) {
+    return last(this.collection.fragments) === fragmentId
+  }
+
+  hasEQA() {
+    const technicalChecks = get(this.collection, 'technicalChecks', {})
+    return has(technicalChecks, 'eqa')
+  }
+
+  async setTechnicalChecks() {
+    set(this.collection, 'technicalChecks.token', v4())
+    set(this.collection, 'technicalChecks.eqa', false)
+    await this.collection.save()
+  }
+
+  async sendToMTS({ FragmentModel, UserModel, fragmentHelper }) {
+    await Promise.all(
+      this.collection.fragments.map(async fragmentId => {
+        const fragment = await FragmentModel.find(fragmentId)
+
+        let fragmentUsers = []
+        try {
+          fragmentUsers = await fragmentHelper.getReviewersAndEditorsData({
+            collection: this.collection,
+            UserModel,
+          })
+
+          await sendMTSPackage({
+            collection: this.collection,
+            fragment,
+            isEQA: true,
+            fragmentUsers,
+          })
+        } catch (e) {
+          logger.error(e)
+        }
+      }),
+    ).catch(e => {
+      throw new Error('Something went wrong.')
+    })
+  }
+
+  async removeTechnicalChecks() {
+    this.collection.technicalChecks = {}
+    await this.collection.save()
+  }
+
+  hasHandlingEditor() {
+    return has(this.collection, 'handlingEditor')
+  }
+
+  async addFragment(newFragmentId) {
+    this.collection.fragments.push(newFragmentId)
+    await this.collection.save()
+  }
+}
+
+const sendMTSPackage = async ({
+  fragment,
+  collection,
+  isEQA = false,
+  fragmentUsers = [],
+}) => {
+  const s3Config = get(config, 'pubsweet-component-aws-s3', {})
+  const mtsConfig = get(config, 'mts-service', {})
+  const { sendPackage } = require('pubsweet-component-mts-package')
+
+  const { journal, xmlParser, ftp } = mtsConfig
+  const packageFragment = {
+    ...fragment,
+    metadata: {
+      ...fragment.metadata,
+      customId: collection.customId,
+    },
+  }
+
+  await sendPackage({
+    isEQA,
+    s3Config,
+    fragmentUsers,
+    ftpConfig: ftp,
+    config: journal,
+    options: xmlParser,
+    fragment: packageFragment,
+  })
 }
 
 module.exports = Collection
diff --git a/packages/component-helper-service/src/services/Fragment.js b/packages/component-helper-service/src/services/Fragment.js
index a11783f269aebde945aefbef266d7d3d3fb09e88..8cdd85f609bc51bbf045a3fc3007746b7bf09470 100644
--- a/packages/component-helper-service/src/services/Fragment.js
+++ b/packages/component-helper-service/src/services/Fragment.js
@@ -1,4 +1,5 @@
-const { get, remove } = require('lodash')
+const { get, remove, findLast, pick, chain, omit } = require('lodash')
+
 const config = require('config')
 const User = require('./User')
 
@@ -123,13 +124,12 @@ class Fragment {
 
   getLatestHERequestToRevision() {
     const { fragment: { recommendations = [] } } = this
-    return recommendations
-      .filter(
-        rec =>
-          rec.recommendationType === 'editorRecommendation' &&
-          (rec.recommendation === 'minor' || rec.recommendation === 'major'),
-      )
-      .sort((a, b) => b.createdOn - a.createdOn)[0]
+    return findLast(
+      recommendations,
+      rec =>
+        rec.recommendationType === 'editorRecommendation' &&
+        (rec.recommendation === 'minor' || rec.recommendation === 'major'),
+    )
   }
 
   async getReviewers({ UserModel, type }) {
@@ -150,6 +150,18 @@ class Fragment {
     )
   }
 
+  canHEMakeAnotherRecommendation(lastHERecommendation) {
+    const { fragment: { recommendations = [] } } = this
+
+    const returnToHERecommendation = findLast(
+      recommendations,
+      r => r.recommendation === 'return-to-handling-editor',
+    )
+
+    if (!returnToHERecommendation) return false
+    return returnToHERecommendation.createdOn > lastHERecommendation.createdOn
+  }
+
   async getReviewersAndEditorsData({ collection, UserModel }) {
     const {
       invitations = [],
@@ -210,6 +222,71 @@ class Fragment {
 
     return revAndEditorData
   }
+
+  async addRecommendation(newRecommendation) {
+    this.fragment.recommendations = this.fragment.recommendations || []
+
+    this.fragment.recommendations.push(newRecommendation)
+    await this.fragment.save()
+  }
+
+  async addRevision() {
+    this.fragment.revision = pick(this.fragment, [
+      'authors',
+      'files',
+      'metadata',
+    ])
+    await this.fragment.save()
+  }
+
+  hasReviewers() {
+    const { fragment: invitations = [] } = this
+    return invitations.length > 0
+  }
+
+  getLatestUserRecommendation(userId) {
+    return findLast(this.fragment.recommendations, r => r.userId === userId)
+  }
+
+  getLatestRecommendation() {
+    return chain(this.fragment)
+      .get('recommendations', [])
+      .last()
+      .value()
+  }
+
+  async createFragmentFromRevision(FragmentModel) {
+    const newFragmentBody = {
+      ...omit(this.fragment, ['revision', 'recommendations', 'id']),
+      ...this.fragment.revision,
+      invitations: this.getInvitations({
+        isAccepted: true,
+        type: 'submitted',
+      }),
+      version: this.fragment.version + 1,
+      created: new Date(),
+    }
+
+    let newFragment = new FragmentModel(newFragmentBody)
+    newFragment = await newFragment.save()
+
+    return newFragment
+  }
+
+  async removeRevision() {
+    delete this.fragment.revision
+    await this.fragment.save()
+  }
+
+  getLatestEiCRequestToRevision() {
+    const { fragment: { recommendations = [] } } = this
+    return findLast(
+      recommendations,
+      rec =>
+        rec.recommendationType === 'editorRecommendation' &&
+        rec.recommendation === 'revision',
+    )
+  }
 }
 
 module.exports = Fragment
diff --git a/packages/component-helper-service/src/services/User.js b/packages/component-helper-service/src/services/User.js
index dbd0eb1f2bf7b6e5995a27df258efbb6a532fb3d..1878092704ad027560176e161e3a526367ea3363 100644
--- a/packages/component-helper-service/src/services/User.js
+++ b/packages/component-helper-service/src/services/User.js
@@ -67,7 +67,7 @@ class User {
   async updateUserTeams({ userId, teamId }) {
     const user = await this.UserModel.find(userId)
     user.teams.push(teamId)
-    user.save()
+    await user.save()
   }
 
   async getActiveAuthors({ fragmentAuthors }) {
diff --git a/packages/component-helper-service/src/tests/collection.test.js b/packages/component-helper-service/src/tests/collection.test.js
index 41c9f0ed6481079f790df50fc83584af62f2d1c4..167153af8c03fc7149c36a9e8c149de351c0f7db 100644
--- a/packages/component-helper-service/src/tests/collection.test.js
+++ b/packages/component-helper-service/src/tests/collection.test.js
@@ -10,11 +10,65 @@ const { Collection, Fragment } = require('../Helper')
 describe('Collection helper', () => {
   let testFixtures = {}
   let models
+
   beforeEach(() => {
     testFixtures = cloneDeep(fixtures)
     models = Model.build(testFixtures)
   })
 
+  describe('getReviewerNumber', () => {
+    it('should assign reviewer number 1 on invitation if no other reviewer numbers exist', async () => {
+      const { collection } = testFixtures.collections
+      const { reviewer } = testFixtures.users
+      const collectionHelper = new Collection({ collection })
+
+      const reviewerNumber = await collectionHelper.getReviewerNumber({
+        userId: reviewer.id,
+      })
+
+      expect(reviewerNumber).toBe(1)
+    })
+    it('should assign next reviewer number on invitation if another reviewer numbers exist', async () => {
+      const { collectionReviewCompleted } = testFixtures.collections
+      const { reviewer } = testFixtures.users
+      const collectionHelper = new Collection({
+        collection: collectionReviewCompleted,
+      })
+
+      const reviewerNumber = await collectionHelper.getReviewerNumber({
+        userId: reviewer.id,
+      })
+
+      expect(reviewerNumber).toBe(3)
+    })
+    it('should keep reviewer number across fragment versions', async () => {
+      const { oneReviewedFragmentCollection } = testFixtures.collections
+      const { answerReviewer } = testFixtures.users
+      const collectionHelper = new Collection({
+        collection: oneReviewedFragmentCollection,
+      })
+
+      const reviewerNumber = await collectionHelper.getReviewerNumber({
+        userId: answerReviewer.id,
+      })
+
+      expect(reviewerNumber).toBe(2)
+    })
+    it('should assign next reviewer number across fragment versions', async () => {
+      const { oneReviewedFragmentCollection } = testFixtures.collections
+      const { reviewer } = testFixtures.users
+      const collectionHelper = new Collection({
+        collection: oneReviewedFragmentCollection,
+      })
+
+      const reviewerNumber = await collectionHelper.getReviewerNumber({
+        userId: reviewer.id,
+      })
+
+      expect(reviewerNumber).toBe(3)
+    })
+  })
+
   describe('hasAtLeastOneReviewReport', () => {
     it('should return true if collection has at least one report from reviewers.', async () => {
       const { collection } = testFixtures.collections
diff --git a/packages/component-helper-service/src/tests/fragment.test.js b/packages/component-helper-service/src/tests/fragment.test.js
index 26598cfe8bd73fc40a8966a03ab51aa3552f3668..ec7fbb6073f311f844c2ff574c936dfe00b9ff35 100644
--- a/packages/component-helper-service/src/tests/fragment.test.js
+++ b/packages/component-helper-service/src/tests/fragment.test.js
@@ -15,6 +15,8 @@ const { recommendations: configRecommendations } = config
 const acceptedReviewerId = chance.guid()
 const submittedReviewerId1 = chance.guid()
 const submittedReviewerId2 = chance.guid()
+const handlingEditorId = chance.guid()
+const editorInChiefId = chance.guid()
 const fragment = {
   invitations: [
     {
@@ -281,4 +283,94 @@ describe('Fragment helper', () => {
       }
     })
   })
+  describe('canHEMakeAnotherRecommendation', () => {
+    it('should return true when He makes a recommendation after EIC decision was to return to HE', async () => {
+      testFragment.recommendations = [
+        {
+          recommendation: 'publish',
+          recommendationType: 'editorRecommendation',
+          comments: [
+            {
+              content: chance.paragraph(),
+              public: true,
+              files: [
+                {
+                  id: chance.guid(),
+                  name: 'file.pdf',
+                  size: chance.natural(),
+                },
+              ],
+            },
+          ],
+          id: chance.guid(),
+          userId: handlingEditorId,
+          createdOn: 1542361074012,
+          updatedOn: chance.timestamp(),
+        },
+        {
+          recommendation: 'return-to-handling-editor',
+          recommendationType: 'editorRecommendation',
+          comments: [
+            {
+              content: chance.paragraph(),
+              public: true,
+              files: [
+                {
+                  id: chance.guid(),
+                  name: 'file.pdf',
+                  size: chance.natural(),
+                },
+              ],
+            },
+          ],
+          id: chance.guid(),
+          userId: editorInChiefId,
+          createdOn: 1542361115749,
+          updatedOn: chance.timestamp(),
+        },
+      ]
+      const fragmentHelper = new Fragment({ fragment: testFragment })
+      const latestUserRecommendation = fragmentHelper.getLatestUserRecommendation(
+        handlingEditorId,
+      )
+
+      const canHEMakeAnotherRecommendation = await fragmentHelper.canHEMakeAnotherRecommendation(
+        latestUserRecommendation,
+      )
+      expect(canHEMakeAnotherRecommendation).toBe(true)
+    })
+    it('should return false when He makes another recommendation', async () => {
+      testFragment.recommendations = [
+        {
+          recommendation: 'publish',
+          recommendationType: 'editorRecommendation',
+          comments: [
+            {
+              content: chance.paragraph(),
+              public: true,
+              files: [
+                {
+                  id: chance.guid(),
+                  name: 'file.pdf',
+                  size: chance.natural(),
+                },
+              ],
+            },
+          ],
+          id: chance.guid(),
+          userId: handlingEditorId,
+          createdOn: 1542361074012,
+          updatedOn: chance.timestamp(),
+        },
+      ]
+      const fragmentHelper = new Fragment({ fragment: testFragment })
+      const latestUserRecommendation = fragmentHelper.getLatestUserRecommendation(
+        handlingEditorId,
+      )
+      const canHEMakeAnotherRecommendation = await fragmentHelper.canHEMakeAnotherRecommendation(
+        latestUserRecommendation,
+      )
+      expect(canHEMakeAnotherRecommendation).toBe(false)
+    })
+  })
 })
diff --git a/packages/component-invite/src/CollectionsInvitations.js b/packages/component-invite/src/CollectionsInvitations.js
index 2f80dd1b70e3089a7ab7f9068a4e12dbb84985e5..0b72a54da398154f0b1e82b5f78601d57cf86064 100644
--- a/packages/component-invite/src/CollectionsInvitations.js
+++ b/packages/component-invite/src/CollectionsInvitations.js
@@ -39,7 +39,7 @@ const CollectionsInvitations = app => {
     require(`${routePath}/post`)(app.locals.models),
   )
   /**
-   * @api {delete} /api/collections/:collectionId/invitations/:invitationId Delete invitation
+   * @api {delete} /api/collections/:collectionId/invitations/:invitationId Delete invitation (or revoke HE if invitation is accepted)
    * @apiGroup CollectionsInvitations
    * @apiParam {collectionId} collectionId Collection id
    * @apiParam {invitationId} invitationId Invitation id
diff --git a/packages/component-invite/src/routes/collectionsInvitations/delete.js b/packages/component-invite/src/routes/collectionsInvitations/delete.js
index 6b3b6764bc654b1f05ea8b4402645649abf7243a..c93402add18d6180d6f47bb688bd2b77fb7a1f4f 100644
--- a/packages/component-invite/src/routes/collectionsInvitations/delete.js
+++ b/packages/component-invite/src/routes/collectionsInvitations/delete.js
@@ -1,8 +1,18 @@
+const config = require('config')
+
 const {
   Team,
+  Fragment,
   services,
   authsome: authsomeHelper,
 } = require('pubsweet-component-helper-service')
+const {
+  deleteFilesS3,
+} = require('pubsweet-component-mts-package/src/PackageManager')
+
+const { last, get, chain, difference } = require('lodash')
+
+const s3Config = get(config, 'pubsweet-component-aws-s3', {})
 
 const notifications = require('./emails/notifications')
 
@@ -56,13 +66,107 @@ module.exports = models => async (req, res) => {
     user.teams = user.teams.filter(userTeamId => team.id !== userTeamId)
     await user.save()
 
-    notifications.sendInvitedHEEmail({
-      models,
-      collection,
-      invitedHE: user,
-      isCanceled: true,
-      baseUrl: services.getBaseUrl(req),
-    })
+    if (invitation.hasAnswer && invitation.isAccepted) {
+      const FragmentModel = models.Fragment
+      const fragment = await FragmentModel.find(
+        last(get(collection, 'fragments', [])),
+      )
+      const fragmentHelper = new Fragment({ fragment })
+
+      const fragmentId = fragment.id
+      const teamHelperForFragment = new Team({
+        TeamModel: models.Team,
+        collectionId,
+        fragmentId,
+      })
+
+      const teams = await teamHelperForFragment.getTeams('fragment')
+      const reviewerTeam = teams.find(
+        team => team.object.id === fragmentId && team.group === 'reviewer',
+      )
+      if (reviewerTeam) {
+        reviewerTeam.delete()
+      }
+
+      const fileKeys = []
+      fragment.recommendations &&
+        fragment.recommendations.forEach(recommendation => {
+          recommendation.comments.forEach(comment => {
+            comment.files &&
+              comment.files.forEach(file => {
+                fileKeys.push(file.id)
+              })
+          })
+        })
+
+      const revision = get(fragment, 'revision', false)
+      if (revision) {
+        const fragmentFilesIds = chain(get(fragment, 'files', []))
+          .flatMap(item => item)
+          .map(item => item.id)
+          .value()
+        const revisionFilesIds = chain(get(fragment, 'revision.files', []))
+          .flatMap(item => item)
+          .map(item => item.id)
+          .value()
+        const revisionFileIds = difference(revisionFilesIds, fragmentFilesIds)
+        fileKeys.concat(revisionFileIds)
+      }
+      if (fileKeys.length > 1) {
+        await deleteFilesS3({ fileKeys, s3Config })
+      }
+
+      let shouldAuthorBeNotified
+      if (fragment.invitations.length > 0) {
+        shouldAuthorBeNotified = true
+      }
+
+      const reviewers = [
+        ...(await fragmentHelper.getReviewers({
+          UserModel,
+          type: 'accepted',
+        })),
+        ...(await fragmentHelper.getReviewers({
+          UserModel,
+          type: 'submitted',
+        })),
+      ]
+
+      fragment.invitations = []
+      fragment.recommendations = []
+      fragment.revision && delete fragment.revision
+      await fragment.save()
+
+      notifications.notifyInvitedHEWhenRemoved({
+        models,
+        collection,
+        invitedHE: user,
+        baseUrl: services.getBaseUrl(req),
+      })
+
+      notifications.notifyReviewersWhenHERemoved({
+        models,
+        collection,
+        reviewers,
+        baseUrl: services.getBaseUrl(req),
+      })
+
+      if (shouldAuthorBeNotified) {
+        notifications.notifyAuthorWhenHERemoved({
+          models,
+          collection,
+          baseUrl: services.getBaseUrl(req),
+        })
+      }
+    } else {
+      notifications.sendInvitedHEEmail({
+        models,
+        collection,
+        invitedHE: user,
+        isCanceled: true,
+        baseUrl: services.getBaseUrl(req),
+      })
+    }
 
     return res.status(200).json({})
   } catch (e) {
diff --git a/packages/component-invite/src/routes/collectionsInvitations/emails/emailCopy.js b/packages/component-invite/src/routes/collectionsInvitations/emails/emailCopy.js
index c7c665749f3f3bb53a1743c6843753186201620d..66a72b51a80df466da1511014dc702a0132b035b 100644
--- a/packages/component-invite/src/routes/collectionsInvitations/emails/emailCopy.js
+++ b/packages/component-invite/src/routes/collectionsInvitations/emails/emailCopy.js
@@ -1,6 +1,7 @@
 const config = require('config')
 
 const staffEmail = config.get('journal.staffEmail')
+const journalName = config.get('journal.name')
 
 const getEmailCopy = ({ emailType, titleText, targetUserName, comments }) => {
   let paragraph
@@ -34,6 +35,30 @@ const getEmailCopy = ({ emailType, titleText, targetUserName, comments }) => {
       paragraph = `${targetUserName} has removed you from the role of Handling Editor for ${titleText}.<br/><br/>
         The manuscript will no longer appear in your dashboard. Please contact ${staffEmail} if you have any questions about this change.`
       break
+    case 'author-he-removed':
+      hasIntro = true
+      hasLink = false
+      hasSignature = true
+      paragraph = `We had to replace the handling editor of your manuscript ${titleText}. We apologise for any inconvenience, but it was necessary in order to move your manuscript forward.<br/><br/>
+        If you have questions please email them to ${staffEmail}.<br/><br/>
+        Thank you for your submission to ${journalName}.`
+      break
+    case 'he-he-removed':
+      hasIntro = true
+      hasLink = false
+      hasSignature = true
+      paragraph = `The editor in chief removed you from the manuscript "${titleText}".<br/><br/>
+        If you have any questions regarding this action, please let us know at ${staffEmail}.<br/><br/>
+        Thank you for reviewing ${journalName}.`
+      break
+    case 'reviewer-he-removed':
+      hasIntro = true
+      hasLink = false
+      hasSignature = true
+      paragraph = `We had to replace the handling editor of the manuscript "${titleText}". We apologise for any inconvenience this may cause.<br/><br/>
+        If you have started the review process please email the content to ${staffEmail}.<br/><br/>
+        Thank you for reviewing ${journalName}.`
+      break
     default:
       throw new Error(`The ${emailType} email type is not defined.`)
   }
diff --git a/packages/component-invite/src/routes/collectionsInvitations/emails/notifications.js b/packages/component-invite/src/routes/collectionsInvitations/emails/notifications.js
index 2b85380e873f1a686ed362c33c0b991eabac3aef..d1378dca273dcb3be93426c1841c71f4685e085d 100644
--- a/packages/component-invite/src/routes/collectionsInvitations/emails/notifications.js
+++ b/packages/component-invite/src/routes/collectionsInvitations/emails/notifications.js
@@ -35,9 +35,7 @@ module.exports = {
     } ${submittingAuthor.lastName}`
 
     const userHelper = new User({ UserModel })
-    const eics = await userHelper.getEditorsInChief()
-    const eic = eics[0]
-    const eicName = `${eic.firstName} ${eic.lastName}`
+    const eicName = await userHelper.getEiCName()
     const { customId } = collection
 
     const { paragraph, ...bodyProps } = getEmailCopy({
@@ -73,6 +71,136 @@ module.exports = {
 
     return email.sendEmail()
   },
+  notifyAuthorWhenHERemoved: async ({
+    baseUrl,
+    collection,
+    models: { User: UserModel, Fragment: FragmentModel },
+  }) => {
+    const fragmentId = last(collection.fragments)
+    const fragment = await FragmentModel.find(fragmentId)
+    const fragmentHelper = new Fragment({ fragment })
+    const { title: titleText } = await fragmentHelper.getFragmentData()
+    const { submittingAuthor } = await fragmentHelper.getAuthorData({
+      UserModel,
+    })
+
+    const userHelper = new User({ UserModel })
+    const eicName = await userHelper.getEiCName()
+    const { customId } = collection
+
+    const { paragraph, ...bodyProps } = getEmailCopy({
+      titleText,
+      emailType: 'author-he-removed',
+    })
+
+    const email = new Email({
+      type: 'user',
+      fromEmail: `${eicName} <${staffEmail}>`,
+      toUser: {
+        email: submittingAuthor.email,
+        name: `${submittingAuthor.lastName}`,
+      },
+      content: {
+        subject: `${customId}:  Your manuscript's editor was changed`,
+        paragraph,
+        signatureName: eicName,
+        signatureJournal: journalName,
+        unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, {
+          id: submittingAuthor.id,
+          token: submittingAuthor.accessTokens.unsubscribe,
+        }),
+      },
+      bodyProps,
+    })
+
+    return email.sendEmail()
+  },
+  notifyInvitedHEWhenRemoved: async ({
+    baseUrl,
+    invitedHE,
+    collection,
+    models: { User: UserModel, Fragment: FragmentModel },
+  }) => {
+    const fragmentId = last(collection.fragments)
+    const fragment = await FragmentModel.find(fragmentId)
+    const fragmentHelper = new Fragment({ fragment })
+    const { title: titleText } = await fragmentHelper.getFragmentData()
+
+    const userHelper = new User({ UserModel })
+    const eicName = await userHelper.getEiCName()
+    const { customId } = collection
+
+    const { paragraph, ...bodyProps } = getEmailCopy({
+      titleText,
+      emailType: 'he-he-removed',
+    })
+
+    const email = new Email({
+      type: 'user',
+      fromEmail: `${eicName} <${staffEmail}>`,
+      toUser: {
+        email: invitedHE.email,
+        name: `${invitedHE.lastName}`,
+      },
+      content: {
+        subject: `${customId}: The editor in chief removed you from ${titleText}`,
+        paragraph,
+        signatureName: eicName,
+        signatureJournal: journalName,
+        unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, {
+          id: invitedHE.id,
+          token: invitedHE.accessTokens.unsubscribe,
+        }),
+      },
+      bodyProps,
+    })
+
+    return email.sendEmail()
+  },
+  notifyReviewersWhenHERemoved: async ({
+    baseUrl,
+    collection,
+    reviewers,
+    models: { User: UserModel, Fragment: FragmentModel },
+  }) => {
+    const fragmentId = last(collection.fragments)
+    const fragment = await FragmentModel.find(fragmentId)
+    const fragmentHelper = new Fragment({ fragment })
+    const { title: titleText } = await fragmentHelper.getFragmentData()
+
+    const userHelper = new User({ UserModel })
+    const eicName = await userHelper.getEiCName()
+    const { customId } = collection
+
+    const { paragraph, ...bodyProps } = getEmailCopy({
+      titleText,
+      emailType: 'reviewer-he-removed',
+    })
+
+    reviewers.forEach(reviewer => {
+      const email = new Email({
+        type: 'user',
+        toUser: {
+          email: reviewer.email,
+          name: reviewer.lastName,
+        },
+        fromEmail: `${eicName} <${staffEmail}>`,
+        content: {
+          subject: `${customId}: The handling editor of a manuscript that you were reviewing was changed`,
+          paragraph,
+          signatureName: eicName,
+          signatureJournal: journalName,
+          unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, {
+            id: reviewer.id,
+            token: reviewer.accessTokens.unsubscribe,
+          }),
+        },
+        bodyProps,
+      })
+
+      return email.sendEmail()
+    })
+  },
   sendEiCEmail: async ({
     reason,
     baseUrl,
diff --git a/packages/component-invite/src/routes/collectionsInvitations/post.js b/packages/component-invite/src/routes/collectionsInvitations/post.js
index 7d404e0cec4a91d3a74808722332a7266963aa78..8e354cf84a9b9b912b10980435ea169741ff994e 100644
--- a/packages/component-invite/src/routes/collectionsInvitations/post.js
+++ b/packages/component-invite/src/routes/collectionsInvitations/post.js
@@ -50,7 +50,6 @@ module.exports = models => async (req, res) => {
       error: 'Unauthorized.',
     })
 
-  // check collection status
   if (!['submitted', 'heInvited'].includes(collection.status)) {
     return res.status(400).json({
       error: `Cannot invite HE while collection is in the status: ${
diff --git a/packages/component-invite/src/routes/fragmentsInvitations/post.js b/packages/component-invite/src/routes/fragmentsInvitations/post.js
index 0e45b83faac29aef7b05b2ca588cf07b6dc7fb0e..df347226ef738bab6131bf4f1f63738fe0310e9f 100644
--- a/packages/component-invite/src/routes/fragmentsInvitations/post.js
+++ b/packages/component-invite/src/routes/fragmentsInvitations/post.js
@@ -13,18 +13,8 @@ const emailInvitations = require('./emails/invitations')
 const { last } = require('lodash')
 
 module.exports = models => async (req, res) => {
-  const { email, role, firstName, lastName, affiliation, country } = req.body
-
-  if (
-    !services.checkForUndefinedParams(
-      email,
-      role,
-      firstName,
-      lastName,
-      affiliation,
-      country,
-    )
-  ) {
+  const { email, role } = req.body
+  if (!services.checkForUndefinedParams(email, role)) {
     res.status(400).json({ error: 'Missing parameters.' })
     return
   }
@@ -112,6 +102,17 @@ module.exports = models => async (req, res) => {
       await fragment.save()
       resend = true
     } else {
+      const { firstName, lastName, affiliation, country } = req.body
+      if (
+        !services.checkForUndefinedParams(
+          firstName,
+          lastName,
+          affiliation,
+          country,
+        )
+      ) {
+        res.status(400).json({ error: 'Missing parameters.' })
+      }
       invitation = await invitationHelper.createInvitation({
         parentObject: fragment,
       })
diff --git a/packages/component-invite/src/tests/collectionsInvitations/delete.test.js b/packages/component-invite/src/tests/collectionsInvitations/delete.test.js
index c6e6089b02c4946f6a624911036cf2736a905d77..184f8818018ca0bab8fb24db9a5e844cc1273ed3 100644
--- a/packages/component-invite/src/tests/collectionsInvitations/delete.test.js
+++ b/packages/component-invite/src/tests/collectionsInvitations/delete.test.js
@@ -9,6 +9,9 @@ const { Model, fixtures } = fixturesService
 jest.mock('@pubsweet/component-send-email', () => ({
   send: jest.fn(),
 }))
+jest.mock('pubsweet-component-mts-package/src/PackageManager', () => ({
+  deleteFilesS3: jest.fn(),
+}))
 
 const path = '../routes/collectionsInvitations/delete'
 const route = {
@@ -86,4 +89,19 @@ describe('Delete Collections Invitations route handler', () => {
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('Unauthorized.')
   })
+  it('should return success when the EiC revokes a HE', async () => {
+    const { editorInChief } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const res = await requests.sendRequest({
+      userId: editorInChief.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: collection.id,
+        invitationId: collection.invitations[1].id,
+      },
+    })
+    expect(res.statusCode).toBe(200)
+  })
 })
diff --git a/packages/component-invite/src/tests/collectionsInvitations/post.test.js b/packages/component-invite/src/tests/collectionsInvitations/post.test.js
index de48dc4f84033d9090d303bd4ce1563c1298a992..94dc618a3165e9535d1cb6fce0d4fa92e23eb354 100644
--- a/packages/component-invite/src/tests/collectionsInvitations/post.test.js
+++ b/packages/component-invite/src/tests/collectionsInvitations/post.test.js
@@ -192,7 +192,36 @@ describe('Post collections invitations route handler', () => {
       },
     })
 
-    // expect(res.statusCode).toBe(200)
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+
+    expect(data.error).toEqual(
+      `Cannot invite HE while collection is in the status: ${
+        collection.status
+      }.`,
+    )
+  })
+  it('should return an error when the collection is in the revision requested status', async () => {
+    const { user, editorInChief } = testFixtures.users
+    const { collection } = testFixtures.collections
+    collection.status = 'revisionRequested'
+
+    body = {
+      email: user.email,
+      role: 'handlingEditor',
+    }
+    const res = await requests.sendRequest({
+      body,
+      userId: editorInChief.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: collection.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(400)
     const data = JSON.parse(res._getData())
 
     expect(data.error).toEqual(
diff --git a/packages/component-invite/src/tests/fragmentsInvitations/post.test.js b/packages/component-invite/src/tests/fragmentsInvitations/post.test.js
index 0683f0aaf927ed7e6b810bd1103aaa75b4267955..cf4b990bb539ca664bb997a79bd136142908ac57 100644
--- a/packages/component-invite/src/tests/fragmentsInvitations/post.test.js
+++ b/packages/component-invite/src/tests/fragmentsInvitations/post.test.js
@@ -52,6 +52,7 @@ describe('Post fragments invitations route handler', () => {
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('Missing parameters.')
   })
+
   it('should return success when a reviewer is invited', async () => {
     const { user, editorInChief } = testFixtures.users
     const { collection } = testFixtures.collections
@@ -78,6 +79,61 @@ describe('Post fragments invitations route handler', () => {
     const data = JSON.parse(res._getData())
     expect(data.role).toEqual(body.role)
   })
+
+  it('should return success when resending an invitation to an existing reviewer', async () => {
+    const { editorInChief, reviewer } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    const body = {
+      email: reviewer.email,
+      role: 'reviewer',
+      isPublons: false,
+    }
+
+    const res = await requests.sendRequest({
+      body,
+      userId: editorInChief.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(200)
+    const data = JSON.parse(res._getData())
+    expect(data.role).toEqual(body.role)
+  })
+
+  it('should return an error when resending an invitation to reviewer and the params are missing.', async () => {
+    const { editorInChief, reviewer } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    const body = {
+      email: reviewer.email,
+    }
+
+    const res = await requests.sendRequest({
+      body,
+      userId: editorInChief.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Missing parameters.')
+  })
+
   it('should return an error when inviting his self', async () => {
     const { editorInChief } = testFixtures.users
     body.email = editorInChief.email
diff --git a/packages/component-manuscript-manager/src/notifications/emailCopy.js b/packages/component-manuscript-manager/src/notifications/emailCopy.js
index 3aa154fcdd356c251d191cf3d38b02778e1379cf..67bfe04a10871271dec8db264da90e0b619fb7b0 100644
--- a/packages/component-manuscript-manager/src/notifications/emailCopy.js
+++ b/packages/component-manuscript-manager/src/notifications/emailCopy.js
@@ -11,6 +11,7 @@ const getEmailCopy = ({
   comments = '',
   targetUserName = '',
   eicName = 'Editor in Chief',
+  expectedDate = new Date(),
 }) => {
   let paragraph
   let hasLink = true
@@ -131,6 +132,29 @@ const getEmailCopy = ({
       paragraph = `We regret to inform you that ${titleText} has been returned with comments. Please click the link below to access the manuscript.<br/><br/>
         Comments: ${comments}<br/><br/>`
       break
+    case 'submitted-reviewers-after-revision':
+      paragraph = `The authors have submitted a new version of ${titleText}, which you reviewed for ${journalName}.<br/><br/>
+        As you reviewed the previous version of this manuscript, I would be grateful if you could review this revision and submit a new report by ${expectedDate}.
+        To download the updated PDF and proceed with the review process, please visit the manuscript details page.<br/><br/>
+        Thank you again for reviewing for ${journalName}.`
+      break
+    case 'he-new-version-submitted':
+      hasIntro = false
+      hasSignature = false
+      paragraph = `The authors of ${titleText} have submitted a revised version. <br/><br/>
+        To review this new submission and proceed with the review process, please visit the manuscript details page.`
+      break
+    case 'eic-revision-published':
+      hasIntro = false
+      hasSignature = false
+      paragraph = `The authors of ${titleText} have submitted a revised version. <br/><br/>
+        To review this new submission and proceed with the review process, please visit the manuscript details page.`
+      break
+    case 'author-request-to-revision-from-eic':
+      paragraph = `In order for ${titleText} to proceed to the review process, there needs to be a revision. <br/><br/>
+        ${comments}<br/><br/>
+        For more information about what is required, please click the link below.<br/><br/>`
+      break
     default:
       throw new Error(`The ${emailType} email type is not defined.`)
   }
diff --git a/packages/component-manuscript-manager/src/notifications/notification.js b/packages/component-manuscript-manager/src/notifications/notification.js
index 506e9ba191a51fd7a1e34142dac3f5a03895de23..c80f5c29a2efa52a545033d57ca6929d5365ccd5 100644
--- a/packages/component-manuscript-manager/src/notifications/notification.js
+++ b/packages/component-manuscript-manager/src/notifications/notification.js
@@ -407,6 +407,53 @@ class Notification {
     return email.sendEmail()
   }
 
+  async notifySAWhenEiCRequestsRevision() {
+    const {
+      eicName,
+      submittingAuthor,
+      titleText,
+    } = await this._getNotificationProperties()
+
+    const authorNoteText = helpers.getPrivateNoteTextForAuthor({
+      newRecommendation: this.newRecommendation,
+    })
+
+    const { paragraph, ...bodyProps } = getEmailCopy({
+      emailType: 'author-request-to-revision-from-eic',
+      titleText,
+      comments: authorNoteText,
+    })
+
+    const email = new Email({
+      type: 'user',
+      toUser: {
+        email: submittingAuthor.email,
+        name: submittingAuthor.lastName,
+      },
+      fromEmail: `${eicName} <${staffEmail}>`,
+      content: {
+        subject: `${this.collection.customId}: Revision requested`,
+        paragraph,
+        signatureName: eicName,
+        ctaText: 'MANUSCRIPT DETAILS',
+        signatureJournal: journalName,
+        unsubscribeLink: services.createUrl(this.baseUrl, unsubscribeSlug, {
+          id: submittingAuthor.id,
+          token: submittingAuthor.accessTokens.unsubscribe,
+        }),
+        ctaLink: services.createUrl(
+          this.baseUrl,
+          `/projects/${this.collection.id}/versions/${
+            this.fragment.id
+          }/details`,
+        ),
+      },
+      bodyProps,
+    })
+
+    return email.sendEmail()
+  }
+
   async notifyReviewersWhenHEMakesRecommendation() {
     const {
       eicName,
@@ -629,6 +676,144 @@ class Notification {
     })
   }
 
+  async notifyEditorInChiefWhenAuthorSubmitsRevision(newFragment) {
+    const { titleText } = await this._getNotificationProperties()
+
+    const userHelper = new User({ UserModel: this.UserModel })
+    const editors = await userHelper.getEditorsInChief()
+
+    const { paragraph, ...bodyProps } = getEmailCopy({
+      titleText,
+      emailType: 'eic-revision-published',
+    })
+
+    editors.forEach(eic => {
+      const email = new Email({
+        type: 'user',
+        fromEmail: `${journalName} <${staffEmail}>`,
+        toUser: {
+          email: eic.email,
+        },
+        content: {
+          subject: `${this.collection.customId}: Revision submitted`,
+          paragraph,
+          signatureName: '',
+          signatureJournal: journalName,
+          ctaLink: services.createUrl(
+            this.baseUrl,
+            `/projects/${this.collection.id}/versions/${
+              newFragment.id
+            }/details`,
+          ),
+          ctaText: 'MANUSCRIPT DETAILS',
+          unsubscribeLink: services.createUrl(this.baseUrl, unsubscribeSlug, {
+            id: eic.id,
+            token: eic.accessTokens.unsubscribe,
+          }),
+        },
+        bodyProps,
+      })
+
+      return email.sendEmail()
+    })
+  }
+
+  async notifyReviewersWhenAuthorSubmitsMajorRevision(newFragmentId) {
+    const { fragmentHelper } = await this._getNotificationProperties()
+    const { collection, UserModel } = this
+
+    const handlingEditor = get(collection, 'handlingEditor')
+    const parsedFragment = await fragmentHelper.getFragmentData({
+      handlingEditor,
+    })
+
+    const reviewers = await fragmentHelper.getReviewers({
+      UserModel,
+      type: 'submitted',
+    })
+
+    const { paragraph, ...bodyProps } = getEmailCopy({
+      emailType: 'submitted-reviewers-after-revision',
+      titleText: `the manuscript titled "${parsedFragment.title}"`,
+      expectedDate: services.getExpectedDate({ daysExpected: 14 }),
+    })
+
+    reviewers.forEach(reviewer => {
+      const email = new Email({
+        type: 'user',
+        fromEmail: `${handlingEditor.name} <${staffEmail}>`,
+        toUser: {
+          email: reviewer.email,
+          name: `${reviewer.lastName}`,
+        },
+        content: {
+          subject: `${
+            collection.customId
+          }: A manuscript you reviewed has been revised`,
+          paragraph,
+          signatureName: handlingEditor.name,
+          signatureJournal: journalName,
+          ctaLink: services.createUrl(
+            this.baseUrl,
+            `/projects/${collection.id}/versions/${newFragmentId}/details`,
+          ),
+          ctaText: 'MANUSCRIPT DETAILS',
+          unsubscribeLink: services.createUrl(this.baseUrl, unsubscribeSlug, {
+            id: reviewer.id,
+            token: reviewer.accessTokens.unsubscribe,
+          }),
+        },
+        bodyProps,
+      })
+
+      return email.sendEmail()
+    })
+  }
+
+  async notifyHandlingEditorWhenAuthorSubmitsRevision(newFragment) {
+    const { collection, UserModel } = this
+
+    const handlingEditor = get(collection, 'handlingEditor')
+
+    const fragmentHelper = new Fragment({ fragment: newFragment })
+    const parsedFragment = await fragmentHelper.getFragmentData({
+      handlingEditor,
+    })
+
+    const { paragraph, ...bodyProps } = getEmailCopy({
+      emailType: 'he-new-version-submitted',
+      titleText: `the manuscript titled "${parsedFragment.title}"`,
+    })
+
+    const heUser = await UserModel.find(handlingEditor.id)
+
+    const email = new Email({
+      type: 'user',
+      fromEmail: `${journalName} <${staffEmail}>`,
+      toUser: {
+        email: heUser.email,
+      },
+      content: {
+        subject: `${collection.customId}: Revision submitted`,
+        paragraph,
+        signatureName: '',
+        signatureJournal: journalName,
+        ctaLink: services.createUrl(
+          this.baseUrl,
+          `/projects/${collection.id}/versions/${newFragment.id}/details`,
+        ),
+        ctaText: 'MANUSCRIPT DETAILS',
+        unsubscribeLink: services.createUrl(this.baseUrl, unsubscribeSlug, {
+          id: heUser.id,
+          token: heUser.accessTokens.unsubscribe,
+        }),
+      },
+      bodyProps,
+    })
+
+    return email.sendEmail()
+  }
+
   async _getNotificationProperties() {
     const fragmentHelper = new Fragment({ fragment: this.fragment })
     const parsedFragment = await fragmentHelper.getFragmentData({
diff --git a/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js b/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js
index ecfadb0a4ae20fb38e8e9511b10120fb33fabfaf..e6ab3f502678fcf09ecd40557b7d9b63607f9fea 100644
--- a/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js
+++ b/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js
@@ -5,21 +5,9 @@ const journalName = config.get('journal.name')
 const getEmailCopy = ({ emailType, titleText, expectedDate, customId }) => {
   let paragraph
   const hasLink = true
-  let hasIntro = true
-  let hasSignature = true
+  const hasIntro = true
+  const hasSignature = true
   switch (emailType) {
-    case 'he-new-version-submitted':
-      hasIntro = false
-      hasSignature = false
-      paragraph = `The authors of ${titleText} have submitted a revised version. <br/><br/>
-        To review this new submission and proceed with the review process, please visit the manuscript details page.`
-      break
-    case 'submitted-reviewers-after-revision':
-      paragraph = `The authors have submitted a new version of ${titleText}, which you reviewed for ${journalName}.<br/><br/>
-        As you reviewed the previous version of this manuscript, I would be grateful if you could review this revision and submit a new report by ${expectedDate}.
-        To download the updated PDF and proceed with the review process, please visit the manuscript details page.<br/><br/>
-        Thank you again for reviewing for ${journalName}.`
-      break
     case 'eqs-manuscript-submitted':
       paragraph = `Manuscript ID ${customId} has been submitted and a package has been sent. Please click on the link below to either approve or reject the manuscript:`
       break
diff --git a/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js b/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js
index dcbe3c22f7278a55af53a9e9477f80860ea4609f..bc2841276ba9300ce4c39b51068989b5b879b0ce 100644
--- a/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js
+++ b/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js
@@ -15,106 +15,11 @@ const unsubscribeSlug = config.get('unsubscribe.url')
 const { getEmailCopy } = require('./emailCopy')
 
 module.exports = {
-  async sendHandlingEditorEmail({ baseUrl, fragment, UserModel, collection }) {
-    const fragmentHelper = new Fragment({ fragment })
-    const handlingEditor = get(collection, 'handlingEditor')
-    const parsedFragment = await fragmentHelper.getFragmentData({
-      handlingEditor,
-    })
-
-    const { paragraph, ...bodyProps } = getEmailCopy({
-      emailType: 'he-new-version-submitted',
-      titleText: `the manuscript titled "${parsedFragment.title}"`,
-    })
-
-    const heUser = await UserModel.find(handlingEditor.id)
-
-    const email = new Email({
-      type: 'user',
-      fromEmail: `${journalName} <${staffEmail}>`,
-      toUser: {
-        email: heUser.email,
-      },
-      content: {
-        subject: `${collection.customId}: Revision submitted`,
-        paragraph,
-        signatureName: '',
-        signatureJournal: journalName,
-        ctaLink: services.createUrl(
-          baseUrl,
-          `/projects/${collection.id}/versions/${fragment.id}/details`,
-        ),
-        ctaText: 'MANUSCRIPT DETAILS',
-        unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, {
-          id: heUser.id,
-          token: heUser.accessTokens.unsubscribe,
-        }),
-      },
-      bodyProps,
-    })
-
-    return email.sendEmail()
-  },
-
-  async sendReviewersEmail({
-    baseUrl,
-    fragment,
-    UserModel,
-    collection,
-    previousVersion,
-  }) {
-    const fragmentHelper = new Fragment({ fragment: previousVersion })
-    const handlingEditor = get(collection, 'handlingEditor')
-    const parsedFragment = await fragmentHelper.getFragmentData({
-      handlingEditor,
-    })
-
-    const reviewers = await fragmentHelper.getReviewers({
-      UserModel,
-      type: 'submitted',
-    })
-
-    const { paragraph, ...bodyProps } = getEmailCopy({
-      emailType: 'submitted-reviewers-after-revision',
-      titleText: `the manuscript titled "${parsedFragment.title}"`,
-      expectedDate: services.getExpectedDate({ daysExpected: 14 }),
-    })
-
-    reviewers.forEach(reviewer => {
-      const email = new Email({
-        type: 'user',
-        fromEmail: `${handlingEditor.name} <${staffEmail}>`,
-        toUser: {
-          email: reviewer.email,
-          name: `${reviewer.lastName}`,
-        },
-        content: {
-          subject: `${
-            collection.customId
-          }: A manuscript you reviewed has been revised`,
-          paragraph,
-          signatureName: handlingEditor.name,
-          signatureJournal: journalName,
-          ctaLink: services.createUrl(
-            baseUrl,
-            `/projects/${collection.id}/versions/${fragment.id}/details`,
-          ),
-          ctaText: 'MANUSCRIPT DETAILS',
-          unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, {
-            id: reviewer.id,
-            token: reviewer.accessTokens.unsubscribe,
-          }),
-        },
-        bodyProps,
-      })
-
-      return email.sendEmail()
-    })
-  },
-
   async sendEQSEmail({ baseUrl, fragment, UserModel, collection }) {
     const userHelper = new User({ UserModel })
     const eicName = await userHelper.getEiCName()
+    const fragmentHelper = new Fragment({ fragment })
+    const { title } = await fragmentHelper.getFragmentData({})
 
     const { paragraph, ...bodyProps } = getEmailCopy({
       emailType: 'eqs-manuscript-submitted',
@@ -134,6 +39,7 @@ module.exports = {
         signatureName: eicName,
         signatureJournal: journalName,
         ctaLink: services.createUrl(baseUrl, config.get('eqs-decision.url'), {
+          title,
           collectionId: collection.id,
           customId: collection.customId,
           token: collection.technicalChecks.token,
@@ -231,7 +137,7 @@ module.exports = {
           firstName: author.firstName,
           lastName: author.lastName,
           affiliation: author.affiliation,
-          title: author.title,
+          title: author.title.toLowerCase(),
           country: author.country,
         })
         email.content.ctaText = 'CONFIRM ACCOUNT'
diff --git a/packages/component-manuscript-manager/src/routes/fragments/patch.js b/packages/component-manuscript-manager/src/routes/fragments/patch.js
index acd037254ddb9ada6417d99980a2a30588799d08..4f975c391c34232517734c2c3e920ad4bfefe7ca 100644
--- a/packages/component-manuscript-manager/src/routes/fragments/patch.js
+++ b/packages/component-manuscript-manager/src/routes/fragments/patch.js
@@ -1,14 +1,16 @@
-const { union, omit } = require('lodash')
-
 const {
   Team,
+  User,
   services,
   Fragment,
   Collection,
   authsome: authsomeHelper,
 } = require('pubsweet-component-helper-service')
 
-const notifications = require('./notifications/notifications')
+const Notification = require('../../notifications/notification')
+
+const eicRequestRevision = require('./strategies/eicRequestRevision')
+const heRequestRevision = require('./strategies/heRequestRevision')
 
 module.exports = models => async (req, res) => {
   const { collectionId, fragmentId } = req.params
@@ -24,7 +26,8 @@ module.exports = models => async (req, res) => {
     fragment = await models.Fragment.find(fragmentId)
     if (!fragment.revision) {
       return res.status(400).json({
-        error: 'No revision has been found.',
+        error:
+          'Your Handling Editor was changed. A new handling editor will be assigned to your manuscript soon. Sorry for the inconvenience.',
       })
     }
 
@@ -41,100 +44,35 @@ module.exports = models => async (req, res) => {
 
     const collectionHelper = new Collection({ collection })
     const fragmentHelper = new Fragment({ fragment })
-    const heRecommendation = fragmentHelper.getLatestHERequestToRevision()
-    if (!heRecommendation) {
-      return res.status(400).json({
-        error: 'No Handling Editor request to revision has been found.',
-      })
-    }
-
-    const newFragmentBody = {
-      ...omit(fragment, ['revision', 'recommendations', 'id']),
-      ...fragment.revision,
-      invitations: fragmentHelper.getInvitations({
-        isAccepted: true,
-        type: 'submitted',
-      }),
-      version: fragment.version + 1,
-      created: new Date(),
-    }
-
-    let newFragment = new models.Fragment(newFragmentBody)
-    newFragment = await newFragment.save()
-    const teamHelper = new Team({
-      TeamModel: models.Team,
-      collectionId,
-      fragmentId: newFragment.id,
-    })
-    delete fragment.revision
-    fragment.save()
-
-    if (heRecommendation.recommendation === 'major') {
-      const reviewerIds = newFragment.invitations.map(inv => inv.userId)
-
-      teamHelper.createTeam({
-        role: 'reviewer',
-        members: reviewerIds,
-        objectType: 'fragment',
-      })
-    } else {
-      delete newFragment.invitations
-      await newFragment.save()
-    }
-
-    const authorIds = newFragment.authors.map(auth => {
-      const { id } = auth
-      return id
-    })
-
-    let authorsTeam = await teamHelper.getTeam({
-      role: 'author',
-      objectType: 'fragment',
-    })
+    const userHelper = new User({ UserModel: models.User })
 
-    if (!authorsTeam) {
-      authorsTeam = await teamHelper.createTeam({
-        role: 'author',
-        members: authorIds,
-        objectType: 'fragment',
-      })
-    } else {
-      authorsTeam.members = union(authorsTeam.members, authorIds)
-      await authorsTeam.save()
+    const strategies = {
+      he: heRequestRevision,
+      eic: eicRequestRevision,
     }
 
-    const fragments = await collectionHelper.getAllFragments({
-      FragmentModel: models.Fragment,
-    })
-
-    await collectionHelper.updateStatusByRecommendation({
-      recommendation: heRecommendation.recommendation,
-      fragments,
-    })
-
-    newFragment.submitted = Date.now()
-    newFragment = await newFragment.save()
-    collection.fragments.push(newFragment.id)
-    collection.save()
+    const role = collection.handlingEditor ? 'he' : 'eic'
 
-    notifications.sendHandlingEditorEmail({
-      baseUrl: services.getBaseUrl(req),
-      fragment: newFragment,
-      UserModel: models.User,
+    const notification = new Notification({
+      fragment,
       collection,
+      UserModel: models.User,
+      baseUrl: services.getBaseUrl(req),
     })
 
-    if (heRecommendation.recommendation === 'major') {
-      notifications.sendReviewersEmail({
-        baseUrl: services.getBaseUrl(req),
-        fragment: newFragment,
-        UserModel: models.User,
-        collection,
-        previousVersion: fragment,
+    try {
+      const newFragment = await strategies[role].execute({
+        models,
+        userHelper,
+        notification,
+        fragmentHelper,
+        collectionHelper,
+        TeamHelper: Team,
       })
+      return res.status(200).json(newFragment)
+    } catch (e) {
+      return res.status(400).json({ error: e.message })
     }
-
-    return res.status(200).json(newFragment)
   } catch (e) {
     const notFoundError = await services.handleNotFoundError(e, 'Item')
     return res.status(notFoundError.status).json({
diff --git a/packages/component-manuscript-manager/src/routes/fragments/strategies/eicRequestRevision.js b/packages/component-manuscript-manager/src/routes/fragments/strategies/eicRequestRevision.js
new file mode 100644
index 0000000000000000000000000000000000000000..eb99d655a56a86d441b9746b51aaac4af9810501
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragments/strategies/eicRequestRevision.js
@@ -0,0 +1,48 @@
+module.exports = {
+  execute: async ({
+    models,
+    TeamHelper,
+    fragmentHelper,
+    collectionHelper,
+    notification,
+    userHelper,
+  }) => {
+    const eicRequestToRevision = fragmentHelper.getLatestEiCRequestToRevision()
+    if (!eicRequestToRevision) {
+      throw new Error('No Editor in Chief request to revision has been found.')
+    }
+
+    let newFragment = await fragmentHelper.createFragmentFromRevision(
+      models.Fragment,
+    )
+
+    await fragmentHelper.removeRevision()
+
+    const teamHelper = new TeamHelper({
+      TeamModel: models.Team,
+      fragmentId: newFragment.id,
+    })
+
+    const authorIds = newFragment.authors.map(auth => auth.id)
+
+    const { id: teamId } = await teamHelper.createTeam({
+      role: 'author',
+      members: authorIds,
+      objectType: 'fragment',
+    })
+    authorIds.forEach(id => {
+      userHelper.updateUserTeams({ userId: id, teamId })
+    })
+
+    await collectionHelper.updateStatus({ newStatus: 'submitted' })
+
+    newFragment.submitted = Date.now()
+    newFragment = await newFragment.save()
+
+    await collectionHelper.addFragment(newFragment.id)
+
+    await notification.notifyEditorInChiefWhenAuthorSubmitsRevision(newFragment)
+
+    return newFragment
+  },
+}
diff --git a/packages/component-manuscript-manager/src/routes/fragments/strategies/heRequestRevision.js b/packages/component-manuscript-manager/src/routes/fragments/strategies/heRequestRevision.js
new file mode 100644
index 0000000000000000000000000000000000000000..2a20da7ba12d003e43ddd97e7e0e814bc1ed225e
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragments/strategies/heRequestRevision.js
@@ -0,0 +1,75 @@
+module.exports = {
+  execute: async ({
+    models,
+    userHelper,
+    TeamHelper,
+    notification,
+    fragmentHelper,
+    collectionHelper,
+  }) => {
+    const heRequestToRevision = fragmentHelper.getLatestHERequestToRevision()
+    if (!heRequestToRevision) {
+      throw new Error('No Handling Editor request to revision has been found.')
+    }
+
+    let newFragment = await fragmentHelper.createFragmentFromRevision(
+      models.Fragment,
+    )
+    await fragmentHelper.removeRevision()
+
+    const teamHelper = new TeamHelper({
+      TeamModel: models.Team,
+      fragmentId: newFragment.id,
+    })
+
+    if (heRequestToRevision.recommendation === 'major') {
+      const reviewerIds = newFragment.invitations.map(inv => inv.userId)
+
+      teamHelper.createTeam({
+        role: 'reviewer',
+        members: reviewerIds,
+        objectType: 'fragment',
+      })
+    } else {
+      delete newFragment.invitations
+      await newFragment.save()
+    }
+
+    const authorIds = newFragment.authors.map(auth => auth.id)
+
+    const { id: teamId } = await teamHelper.createTeam({
+      role: 'author',
+      members: authorIds,
+      objectType: 'fragment',
+    })
+    authorIds.forEach(id => {
+      userHelper.updateUserTeams({ userId: id, teamId })
+    })
+
+    const fragments = await collectionHelper.getAllFragments({
+      FragmentModel: models.Fragment,
+    })
+
+    await collectionHelper.updateStatusByRecommendation({
+      recommendation: heRequestToRevision.recommendation,
+      fragments,
+    })
+
+    newFragment.submitted = Date.now()
+    newFragment = await newFragment.save()
+
+    await collectionHelper.addFragment(newFragment.id)
+
+    await notification.notifyHandlingEditorWhenAuthorSubmitsRevision(
+      newFragment,
+    )
+
+    if (heRequestToRevision.recommendation === 'major') {
+      await notification.notifyReviewersWhenAuthorSubmitsMajorRevision(
+        newFragment.id,
+      )
+    }
+
+    return newFragment
+  },
+}
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js
index 7846722ed2f83db255a59ff80f95c142ccf2d3ec..7cd5b2ae49984d23fff26a53ae1bfb38a19f8244 100644
--- a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js
@@ -1,3 +1,4 @@
+const { find } = require('lodash')
 const {
   services,
   authsome: authsomeHelper,
@@ -8,6 +9,7 @@ const Notification = require('../../notifications/notification')
 
 module.exports = models => async (req, res) => {
   const { collectionId, fragmentId, recommendationId } = req.params
+  const userId = req.user
   let collection, fragment
   try {
     collection = await models.Collection.find(collectionId)
@@ -25,7 +27,7 @@ module.exports = models => async (req, res) => {
     if (!recommendation)
       return res.status(404).json({ error: 'Recommendation not found.' })
 
-    if (recommendation.userId !== req.user)
+    if (recommendation.userId !== userId)
       return res.status(403).json({
         error: 'Unauthorized.',
       })
@@ -35,14 +37,14 @@ module.exports = models => async (req, res) => {
       fragment,
       path: req.route.path,
     }
-    const canPatch = await authsome.can(req.user, 'PATCH', target)
+    const canPatch = await authsome.can(userId, 'PATCH', target)
     if (!canPatch)
       return res.status(403).json({
         error: 'Unauthorized.',
       })
 
     const UserModel = models.User
-    const reviewer = await UserModel.find(req.user)
+    const reviewer = await UserModel.find(userId)
 
     Object.assign(recommendation, req.body)
     recommendation.updatedOn = Date.now()
@@ -62,6 +64,16 @@ module.exports = models => async (req, res) => {
         const collectionHelper = new Collection({ collection })
         collectionHelper.updateStatus({ newStatus: 'reviewCompleted' })
       }
+
+      const collectionHelper = new Collection({ collection })
+      const reviewerNumber = await collectionHelper.getReviewerNumber({
+        userId,
+      })
+
+      find(fragment.invitations, [
+        'userId',
+        userId,
+      ]).reviewerNumber = reviewerNumber
     }
 
     fragment.save()
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js
index 2531241656d85e7d5090e4b9256e00dcb01f0ae6..abfd90990a00b6eef6bcea5a764b4e3f028d03b3 100644
--- a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js
@@ -1,8 +1,5 @@
-const uuid = require('uuid')
-const { pick, get, set, has, isEmpty, last } = require('lodash')
 const config = require('config')
 const { v4 } = require('uuid')
-const logger = require('@pubsweet/logger')
 
 const {
   services,
@@ -11,21 +8,31 @@ const {
   authsome: authsomeHelper,
 } = require('pubsweet-component-helper-service')
 
-const { features = {}, recommendations } = config
+const { recommendations } = config
 
+const rejectAsHE = require('./strategies/heReject')
+const publishAsHE = require('./strategies/hePublish')
+const rejectAsEiC = require('./strategies/eicReject')
+const publishAsEiC = require('./strategies/eicPublish')
+const returnToHE = require('./strategies/eicReturnToHE')
 const Notification = require('../../notifications/notification')
+const createReview = require('./strategies/reviewerCreateReview')
+const requestRevisionAsHE = require('./strategies/heRequestRevision')
+const requestRevisionAsEiC = require('./strategies/eicRequestRevision')
 
 module.exports = models => async (req, res) => {
-  const { recommendation, comments, recommendationType } = req.body
-  if (!services.checkForUndefinedParams(recommendationType))
+  const { recommendation, comments = [], recommendationType } = req.body
+  if (!services.checkForUndefinedParams(recommendationType, recommendation))
     return res.status(400).json({ error: 'Recommendation type is required.' })
 
   const reqUser = await models.User.find(req.user)
+  const userId = reqUser.id
+
   const isEditorInChief = reqUser.editorInChief || reqUser.admin
 
   const { collectionId, fragmentId } = req.params
 
-  let collection, fragment, fragments
+  let collection, fragment
 
   try {
     collection = await models.Collection.find(collectionId)
@@ -43,21 +50,6 @@ module.exports = models => async (req, res) => {
 
   const collectionHelper = new Collection({ collection })
 
-  try {
-    fragments = await collectionHelper.getAllFragments({
-      FragmentModel: models.Fragment,
-    })
-  } catch (e) {
-    const notFoundError = await services.handleNotFoundError(e, 'Item')
-    fragments = []
-    return res.status(notFoundError.status).json({
-      error: notFoundError.message,
-    })
-  }
-  const currentUserRecommendation = get(fragment, 'recommendations', []).filter(
-    r => r.userId === req.user,
-  )
-
   const authsome = authsomeHelper.getAuthsome(models)
   const target = {
     fragment,
@@ -70,203 +62,83 @@ module.exports = models => async (req, res) => {
     })
 
   const fragmentHelper = new Fragment({ fragment })
-  if (
-    recommendationType === recommendations.type.editor &&
-    last(collection.fragments) !== fragmentId
-  ) {
-    return res
-      .status(400)
-      .json({ error: 'Cannot make a recommendation on an older version.' })
-  }
 
-  if (
-    recommendationType === recommendations.type.review &&
-    last(collection.fragments) !== fragmentId
-  ) {
-    return res
-      .status(400)
-      .json({ error: 'Cannot write a review on an older version.' })
-  }
-  if (
-    last(collection.fragments) === fragmentId &&
-    !isEmpty(currentUserRecommendation)
-  ) {
-    if (recommendationType === recommendations.type.review) {
-      return res
-        .status(400)
-        .json({ error: 'Cannot write another review on this version.' })
-    }
-    return res
-      .status(400)
-      .json({ error: 'Cannot make another recommendation on this version.' })
+  if (!collectionHelper.isLatestVersion(fragmentId)) {
+    const error =
+      recommendationType === recommendations.type.editor
+        ? 'Cannot make a recommendation on an older version.'
+        : 'Cannot write a review on an older version.'
+    return res.status(400).json({ error })
   }
 
-  if (
-    recommendation === recommendations.publish &&
-    recommendationType === recommendations.type.editor &&
-    collection.handlingEditor &&
-    collection.handlingEditor.id === req.user
-  ) {
-    if (!collectionHelper.canHEMakeRecommendation(fragments, fragmentHelper)) {
-      return res.status(400).json({
-        error: 'Cannot publish without at least one reviewer report.',
-      })
-    }
-  }
-
-  fragment.recommendations = fragment.recommendations || []
   const newRecommendation = {
-    id: uuid.v4(),
-    userId: reqUser.id,
+    userId,
+    id: v4(),
+    comments,
+    recommendation,
+    recommendationType,
     createdOn: Date.now(),
     updatedOn: Date.now(),
-    recommendationType,
   }
 
-  newRecommendation.recommendation = recommendation || undefined
-  newRecommendation.comments = comments || undefined
-
-  if (recommendationType === 'editorRecommendation') {
-    await collectionHelper.updateStatusOnRecommendation({
-      isEditorInChief,
-      recommendation,
-    })
-
-    if (!isEditorInChief && ['minor', 'major'].includes(recommendation)) {
-      fragment.revision = pick(fragment, ['authors', 'files', 'metadata'])
-    }
-
-    const technicalChecks = get(collection, 'technicalChecks', {})
-    const hasEQA = has(technicalChecks, 'eqa')
-    // the manuscript has not yet passed through the EQA process so we need to upload it to the FTP server
-    if (isEditorInChief && recommendation === 'publish' && !hasEQA) {
-      if (features.mts) {
-        await Promise.all(
-          collection.fragments.map(async fragmentId => {
-            const fragment = await models.Fragment.find(fragmentId)
-            const fragmentHelper = new Fragment({ fragment })
+  const notification = new Notification({
+    fragment,
+    collection,
+    newRecommendation,
+    UserModel: models.User,
+    baseUrl: services.getBaseUrl(req),
+  })
 
-            let fragmentUsers = []
-            try {
-              fragmentUsers = await fragmentHelper.getReviewersAndEditorsData({
-                collection,
-                UserModel: models.User,
-              })
+  const strategies = {
+    he: {
+      reject: rejectAsHE,
+      publish: publishAsHE,
+      major: requestRevisionAsHE,
+      minor: requestRevisionAsHE,
+    },
+    eic: {
+      reject: rejectAsEiC,
+      publish: publishAsEiC,
+      revision: requestRevisionAsEiC,
+      'return-to-handling-editor': returnToHE,
+    },
+  }
 
-              await sendMTSPackage({
-                collection,
-                fragment,
-                isEQA: true,
-                fragmentUsers,
-              })
-            } catch (e) {
-              logger.error(e)
-            }
-          }),
-        ).catch(e =>
-          res.status(500).json({
-            error: 'Something went wrong.',
-          }),
-        )
+  let role = ''
+  switch (recommendationType) {
+    case 'review':
+      role = 'reviewer'
+      try {
+        await createReview.execute({
+          userId,
+          fragmentHelper,
+          newRecommendation,
+        })
+      } catch (e) {
+        return res.status(400).json({ error: e.message })
       }
+      return res.status(200).json(newRecommendation)
+    case 'editorRecommendation':
+      role = isEditorInChief ? 'eic' : 'he'
+      break
+    default:
+      return res.status(400).json({
+        error: `Recommendation ${recommendation} is not defined.`,
+      })
+  }
 
-      collection.status = 'inQA'
-      set(collection, 'technicalChecks.token', v4())
-      set(collection, 'technicalChecks.eqa', false)
-      await collection.save()
-    }
-
-    /* if the EiC returns the manuscript to the HE after the EQA has been performed
-       then remove all properties from the technicalChecks property so that the manuscript
-       can go through the EQA process again
-    */
-    if (
-      isEditorInChief &&
-      recommendation === 'return-to-handling-editor' &&
-      hasEQA
-    ) {
-      collection.technicalChecks = {}
-      await collection.save()
-    }
-
-    const notification = new Notification({
-      fragment,
-      collection,
+  try {
+    await strategies[role][recommendation].execute({
+      userId,
+      models,
+      notification,
+      fragmentHelper,
+      collectionHelper,
       newRecommendation,
-      UserModel: models.User,
-      baseUrl: services.getBaseUrl(req),
     })
-
-    const hasPeerReview = !isEmpty(collection.handlingEditor)
-
-    if (isEditorInChief) {
-      if (recommendation === 'publish' && collection.status === 'inQA') {
-        notification.notifyEAWhenEiCRequestsEQAApproval()
-      }
-
-      if (recommendation === 'publish' && collection.status === 'accepted') {
-        notification.notifyEAWhenEiCMakesFinalDecision()
-      }
-
-      if (hasPeerReview && (recommendation !== 'publish' || hasEQA)) {
-        if (recommendation === 'return-to-handling-editor') {
-          notification.notifyHEWhenEiCReturnsToHE()
-        } else {
-          notification.notifyHEWhenEiCMakesDecision()
-          notification.notifyReviewersWhenEiCMakesDecision()
-        }
-      }
-
-      if (
-        recommendation !== 'return-to-handling-editor' &&
-        (recommendation !== 'publish' || hasEQA)
-      ) {
-        notification.notifyAuthorsWhenEiCMakesDecision()
-      }
-    } else {
-      if (collection.status === 'revisionRequested') {
-        notification.notifySAWhenHERequestsRevision()
-      }
-
-      if (hasPeerReview) {
-        notification.notifyReviewersWhenHEMakesRecommendation()
-        notification.notifyEiCWhenHEMakesRecommendation()
-      }
-    }
+  } catch (e) {
+    return res.status(400).json({ error: e.message })
   }
 
-  fragment.recommendations.push(newRecommendation)
-  fragment.save()
-
   return res.status(200).json(newRecommendation)
 }
-
-const sendMTSPackage = async ({
-  fragment,
-  collection,
-  isEQA = false,
-  fragmentUsers = [],
-}) => {
-  const s3Config = get(config, 'pubsweet-component-aws-s3', {})
-  const mtsConfig = get(config, 'mts-service', {})
-  const { sendPackage } = require('pubsweet-component-mts-package')
-
-  const { journal, xmlParser, ftp } = mtsConfig
-  const packageFragment = {
-    ...fragment,
-    metadata: {
-      ...fragment.metadata,
-      customId: collection.customId,
-    },
-  }
-
-  await sendPackage({
-    isEQA,
-    s3Config,
-    fragmentUsers,
-    ftpConfig: ftp,
-    config: journal,
-    options: xmlParser,
-    fragment: packageFragment,
-  })
-}
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicPublish.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicPublish.js
new file mode 100644
index 0000000000000000000000000000000000000000..6915c54324dbaa41b6d1aea25c4634c333940d5a
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicPublish.js
@@ -0,0 +1,45 @@
+const config = require('config')
+
+const { features = {} } = config
+
+module.exports = {
+  execute: async ({
+    models,
+    notification,
+    fragmentHelper,
+    collectionHelper,
+    newRecommendation,
+  }) => {
+    const latestRecommendation = fragmentHelper.getLatestRecommendation()
+    if (latestRecommendation.recommendation === 'return-to-handling-editor') {
+      throw new Error(
+        'Cannot make decision to publish after the manuscript has been returned to Handling Editor.',
+      )
+    }
+
+    await fragmentHelper.addRecommendation(newRecommendation)
+
+    let newStatus = ''
+    if (collectionHelper.hasEQA()) {
+      newStatus = 'accepted'
+      notification.notifyEAWhenEiCMakesFinalDecision()
+      notification.notifyAuthorsWhenEiCMakesDecision()
+      notification.notifyHEWhenEiCMakesDecision()
+      notification.notifyReviewersWhenEiCMakesDecision()
+    } else {
+      if (features.mts) {
+        await collectionHelper.sendToMTS({
+          fragmentHelper,
+          UserModel: models.User,
+          FragmentModel: models.Fragment,
+        })
+      }
+
+      newStatus = 'inQA'
+      await collectionHelper.setTechnicalChecks()
+      notification.notifyEAWhenEiCRequestsEQAApproval()
+    }
+
+    await collectionHelper.updateStatus({ newStatus })
+  },
+}
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicReject.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicReject.js
new file mode 100644
index 0000000000000000000000000000000000000000..b10ff0e2a46b93c6d56e85ab0d30f2e345d7f1dd
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicReject.js
@@ -0,0 +1,19 @@
+module.exports = {
+  execute: async ({
+    notification,
+    fragmentHelper,
+    collectionHelper,
+    newRecommendation,
+  }) => {
+    await fragmentHelper.addRecommendation(newRecommendation)
+    await collectionHelper.updateStatus({ newStatus: 'rejected' })
+
+    notification.notifyAuthorsWhenEiCMakesDecision()
+    if (collectionHelper.hasHandlingEditor()) {
+      notification.notifyHEWhenEiCMakesDecision()
+    }
+    if (fragmentHelper.hasReviewers()) {
+      notification.notifyReviewersWhenEiCMakesDecision()
+    }
+  },
+}
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicRequestRevision.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicRequestRevision.js
new file mode 100644
index 0000000000000000000000000000000000000000..0f66d8af7163765974379ceee97f12b887cbae5b
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicRequestRevision.js
@@ -0,0 +1,19 @@
+module.exports = {
+  execute: async ({
+    fragmentHelper,
+    collectionHelper,
+    newRecommendation,
+    notification,
+  }) => {
+    if (collectionHelper.hasHandlingEditor()) {
+      throw new Error(
+        'Cannot make request a revision after a Handling Editor has been assigned.',
+      )
+    }
+
+    await fragmentHelper.addRevision()
+    await collectionHelper.updateStatus({ newStatus: 'revisionRequested' })
+    await fragmentHelper.addRecommendation(newRecommendation)
+    await notification.notifySAWhenEiCRequestsRevision()
+  },
+}
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicReturnToHE.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicReturnToHE.js
new file mode 100644
index 0000000000000000000000000000000000000000..633d9c1b8c61065ed65841f9c43c9c250cfa7424
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/eicReturnToHE.js
@@ -0,0 +1,22 @@
+module.exports = {
+  execute: async ({
+    notification,
+    fragmentHelper,
+    collectionHelper,
+    newRecommendation,
+  }) => {
+    const latestRecommendation = fragmentHelper.getLatestRecommendation()
+    if (latestRecommendation.recommendation === 'return-to-handling-editor') {
+      throw new Error('Cannot return to Handling Editor again.')
+    }
+
+    if (collectionHelper.hasEQA()) {
+      await collectionHelper.removeTechnicalChecks()
+    }
+    await collectionHelper.updateStatus({ newStatus: 'reviewCompleted' })
+
+    await fragmentHelper.addRecommendation(newRecommendation)
+
+    notification.notifyHEWhenEiCReturnsToHE()
+  },
+}
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/hePublish.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/hePublish.js
new file mode 100644
index 0000000000000000000000000000000000000000..238efbc141b40fda46e5c048afb3e9031a8213fb
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/hePublish.js
@@ -0,0 +1,31 @@
+module.exports = {
+  execute: async ({
+    userId,
+    notification,
+    fragmentHelper,
+    collectionHelper,
+    newRecommendation,
+  }) => {
+    const fragments = await collectionHelper.collection.getFragments()
+
+    if (!collectionHelper.canHEMakeRecommendation(fragments, fragmentHelper)) {
+      throw new Error('Cannot publish without at least one reviewer report.')
+    }
+
+    const latestUserRecommendation = fragmentHelper.getLatestUserRecommendation(
+      userId,
+    )
+    if (
+      latestUserRecommendation &&
+      !fragmentHelper.canHEMakeAnotherRecommendation(latestUserRecommendation)
+    ) {
+      throw new Error('Cannot make another recommendation on this version.')
+    }
+
+    await fragmentHelper.addRecommendation(newRecommendation)
+    await collectionHelper.updateStatus({ newStatus: 'pendingApproval' })
+
+    notification.notifyReviewersWhenHEMakesRecommendation()
+    notification.notifyEiCWhenHEMakesRecommendation()
+  },
+}
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/heReject.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/heReject.js
new file mode 100644
index 0000000000000000000000000000000000000000..cd76a8beba735d8ef668dec8c3baef86ebbcaa41
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/heReject.js
@@ -0,0 +1,27 @@
+module.exports = {
+  execute: async ({
+    userId,
+    notification,
+    fragmentHelper,
+    collectionHelper,
+    newRecommendation,
+  }) => {
+    const latestUserRecommendation = fragmentHelper.getLatestUserRecommendation(
+      userId,
+    )
+    if (
+      latestUserRecommendation &&
+      !fragmentHelper.canHEMakeAnotherRecommendation(latestUserRecommendation)
+    ) {
+      throw new Error('Cannot make another recommendation on this version.')
+    }
+
+    await fragmentHelper.addRecommendation(newRecommendation)
+    await collectionHelper.updateStatus({ newStatus: 'pendingApproval' })
+
+    if (fragmentHelper.hasReviewers()) {
+      notification.notifyReviewersWhenHEMakesRecommendation()
+    }
+    notification.notifyEiCWhenHEMakesRecommendation()
+  },
+}
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/heRequestRevision.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/heRequestRevision.js
new file mode 100644
index 0000000000000000000000000000000000000000..7daeb25d9e400a02de63c8693d1c99a48e02e0c5
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/heRequestRevision.js
@@ -0,0 +1,30 @@
+module.exports = {
+  execute: async ({
+    userId,
+    notification,
+    fragmentHelper,
+    collectionHelper,
+    newRecommendation,
+  }) => {
+    const latestUserRecommendation = fragmentHelper.getLatestUserRecommendation(
+      userId,
+    )
+    if (
+      latestUserRecommendation &&
+      !fragmentHelper.canHEMakeAnotherRecommendation(latestUserRecommendation)
+    ) {
+      throw new Error('Cannot make another recommendation on this version.')
+    }
+
+    await fragmentHelper.addRevision()
+    await collectionHelper.updateStatus({ newStatus: 'revisionRequested' })
+    await fragmentHelper.addRecommendation(newRecommendation)
+
+    notification.notifySAWhenHERequestsRevision()
+    notification.notifyEiCWhenHEMakesRecommendation()
+
+    if (fragmentHelper.hasReviewers()) {
+      notification.notifyReviewersWhenHEMakesRecommendation()
+    }
+  },
+}
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/reviewerCreateReview.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/reviewerCreateReview.js
new file mode 100644
index 0000000000000000000000000000000000000000..9b8f450e33a9597a1b25b59f964392cc2befe3b9
--- /dev/null
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/strategies/reviewerCreateReview.js
@@ -0,0 +1,9 @@
+module.exports = {
+  execute: async ({ fragmentHelper, newRecommendation, userId }) => {
+    if (fragmentHelper.getLatestUserRecommendation(userId)) {
+      throw new Error('Cannot write another review on this version.')
+    }
+
+    await fragmentHelper.addRecommendation(newRecommendation)
+  },
+}
diff --git a/packages/component-manuscript-manager/src/tests/collections/get.test.js b/packages/component-manuscript-manager/src/tests/collections/get.test.js
index fb88e5658444b5c3bad2f2cf5ac7760bf9900537..7ad0c1284762c4173c9b7e948fb2d273233eb166 100644
--- a/packages/component-manuscript-manager/src/tests/collections/get.test.js
+++ b/packages/component-manuscript-manager/src/tests/collections/get.test.js
@@ -36,7 +36,7 @@ describe('Get collections route handler', () => {
     expect(res.statusCode).toBe(200)
     const data = JSON.parse(res._getData())
 
-    expect(data).toHaveLength(1)
+    expect(data).toHaveLength(handlingEditor.teams.length)
     expect(data[0].type).toEqual('collection')
     expect(data[0]).toHaveProperty('currentVersion')
     expect(data[0]).toHaveProperty('visibleStatus')
@@ -58,10 +58,8 @@ describe('Get collections route handler', () => {
 
     expect(res.statusCode).toBe(200)
     const data = JSON.parse(res._getData())
-
     expect(data).toHaveLength(2)
     expect(data[0].type).toEqual('collection')
-    expect(data[0].currentVersion.recommendations).toHaveLength(3)
     expect(data[0].currentVersion.authors[0]).not.toHaveProperty('email')
   })
 
diff --git a/packages/component-manuscript-manager/src/tests/fragments/patch.test.js b/packages/component-manuscript-manager/src/tests/fragments/patch.test.js
index f4e2147a5604bf4f77e0782fdd1f33cb28ec9f58..b6cb60fb809b12c4b399ef3b38f912ecd7c99974 100644
--- a/packages/component-manuscript-manager/src/tests/fragments/patch.test.js
+++ b/packages/component-manuscript-manager/src/tests/fragments/patch.test.js
@@ -83,7 +83,7 @@ describe('Patch fragments route handler', () => {
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('Item not found')
   })
-  it('should return an error when no HE recommendation exists', async () => {
+  it('should return an error when no HE request to revision exists', async () => {
     const { user } = testFixtures.users
     const { fragment } = testFixtures.fragments
     const { collection } = testFixtures.collections
@@ -148,7 +148,9 @@ describe('Patch fragments route handler', () => {
 
     expect(res.statusCode).toBe(400)
     const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('No revision has been found.')
+    expect(data.error).toEqual(
+      'Your Handling Editor was changed. A new handling editor will be assigned to your manuscript soon. Sorry for the inconvenience.',
+    )
   })
   it('should return an error when the user is inactive', async () => {
     const { inactiveUser } = testFixtures.users
@@ -169,4 +171,53 @@ describe('Patch fragments route handler', () => {
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('Unauthorized.')
   })
+  it('should return an error when no EiC request to revision exists', async () => {
+    const { user } = testFixtures.users
+    const { fragment } = testFixtures.fragments
+    const { collection } = testFixtures.collections
+    fragment.recommendations.length = 0
+    delete collection.handlingEditor
+
+    const res = await requests.sendRequest({
+      body,
+      userId: user.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      'No Editor in Chief request to revision has been found.',
+    )
+  })
+  it('should return success when an EiC request to revision exists', async () => {
+    const { user } = testFixtures.users
+    const { fragment } = testFixtures.fragments
+    const { collection } = testFixtures.collections
+
+    delete collection.handlingEditor
+
+    const res = await requests.sendRequest({
+      body,
+      userId: user.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(200)
+    const data = JSON.parse(res._getData())
+    expect(data).toHaveProperty('submitted')
+    expect(collection.status).toBe('submitted')
+  })
 })
diff --git a/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js b/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js
index df52effe144daf17b1a9ec16e546c4f189b62187..409e5238ef0062715a53031633e33413f27dcbf5 100644
--- a/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js
+++ b/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js
@@ -63,6 +63,7 @@ describe('Post fragments recommendations route handler', () => {
     const { reviewer } = testFixtures.users
     const { collection } = testFixtures.collections
     const { fragment } = testFixtures.fragments
+    body.recommendationType = 'review'
 
     const res = await requests.sendRequest({
       body,
@@ -102,7 +103,7 @@ describe('Post fragments recommendations route handler', () => {
     expect(data.userId).toEqual(noRecommendationHE.id)
   })
 
-  it('should return an error when creating a recommendation with publish as a HE when there is a single version and there are no reviews.', async () => {
+  it('should return an error when recommending to publish as HE when there is a single version and there are no reviews.', async () => {
     const { handlingEditor } = testFixtures.users
     const { collection } = testFixtures.collections
     const { fragment } = testFixtures.fragments
@@ -129,52 +130,45 @@ describe('Post fragments recommendations route handler', () => {
   })
 
   it('should return success when creating a recommendation as a HE after minor revision and we have at least one review on collection.', async () => {
-    const { handlingEditor } = testFixtures.users
-    const { collection } = testFixtures.collections
+    const { handlingEditor: { id: userId } } = testFixtures.users
     const {
-      minorRevisionWithReview,
-      noInvitesFragment1,
-    } = testFixtures.fragments
+      minorRevisionCollection: { id: collectionId },
+    } = testFixtures.collections
+    const { noInvitesFragment1: { id: fragmentId } } = testFixtures.fragments
 
-    collection.fragments = [minorRevisionWithReview.id, noInvitesFragment1.id]
     const res = await requests.sendRequest({
       body,
-      userId: handlingEditor.id,
+      userId,
       models,
       route,
       path,
       params: {
-        collectionId: collection.id,
-        fragmentId: noInvitesFragment1.id,
+        collectionId,
+        fragmentId,
       },
     })
 
     expect(res.statusCode).toBe(200)
     const data = JSON.parse(res._getData())
-    expect(data.userId).toEqual(handlingEditor.id)
+    expect(data.userId).toEqual(userId)
   })
 
   it('should return error when creating a recommendation as a HE after minor revision and there are no reviews.', async () => {
-    const { handlingEditor } = testFixtures.users
-    const { collection } = testFixtures.collections
+    const { handlingEditor: { id: userId } } = testFixtures.users
     const {
-      minorRevisionWithoutReview,
-      noInvitesFragment1,
-    } = testFixtures.fragments
-
-    collection.fragments = [
-      minorRevisionWithoutReview.id,
-      noInvitesFragment1.id,
-    ]
+      minorRevisionWithoutReviewCollection: { id: collectionId },
+    } = testFixtures.collections
+    const { noInvitesFragment1: { id: fragmentId } } = testFixtures.fragments
+
     const res = await requests.sendRequest({
       body,
-      userId: handlingEditor.id,
+      userId,
       models,
       route,
       path,
       params: {
-        collectionId: collection.id,
-        fragmentId: noInvitesFragment1.id,
+        collectionId,
+        fragmentId,
       },
     })
 
@@ -186,53 +180,46 @@ describe('Post fragments recommendations route handler', () => {
   })
 
   it('should return success when creating a recommendation as a HE after major revision and there are least one review on fragment.', async () => {
-    const { handlingEditor } = testFixtures.users
-    const { collection } = testFixtures.collections
+    const { handlingEditor: { id: userId } } = testFixtures.users
     const {
-      majorRevisionWithReview,
-      reviewCompletedFragment,
-    } = testFixtures.fragments
-
-    reviewCompletedFragment.collectionId = collection.id
-    collection.fragments = [
-      majorRevisionWithReview.id,
-      reviewCompletedFragment.id,
-    ]
+      majorRevisionCollection: { id: collectionId },
+    } = testFixtures.collections
+    const { reviewCompletedFragment } = testFixtures.fragments
+
+    reviewCompletedFragment.collectionId = collectionId
     const res = await requests.sendRequest({
       body,
-      userId: handlingEditor.id,
+      userId,
       models,
       route,
       path,
       params: {
-        collectionId: collection.id,
+        collectionId,
         fragmentId: reviewCompletedFragment.id,
       },
     })
 
     expect(res.statusCode).toBe(200)
     const data = JSON.parse(res._getData())
-    expect(data.userId).toEqual(handlingEditor.id)
+    expect(data.userId).toEqual(userId)
   })
 
   it('should return error when creating a recommendation as a HE after major revision there are no reviews on fragment.', async () => {
-    const { handlingEditor } = testFixtures.users
-    const { collection } = testFixtures.collections
+    const { handlingEditor: { id: userId } } = testFixtures.users
     const {
-      majorRevisionWithReview,
-      noInvitesFragment1,
-    } = testFixtures.fragments
+      majorRevisionWithoutReviewCollection: { id: collectionId },
+    } = testFixtures.collections
+    const { noInvitesFragment1: { id: fragmentId } } = testFixtures.fragments
 
-    collection.fragments = [majorRevisionWithReview.id, noInvitesFragment1.id]
     const res = await requests.sendRequest({
       body,
-      userId: handlingEditor.id,
+      userId,
       models,
       route,
       path,
       params: {
-        collectionId: collection.id,
-        fragmentId: noInvitesFragment1.id,
+        collectionId,
+        fragmentId,
       },
     })
 
@@ -445,9 +432,6 @@ describe('Post fragments recommendations route handler', () => {
     body.recommendationType = 'editorRecommendation'
     body.comments = 'This needs more work'
 
-    delete fragment.recommendations
-    delete fragment.revision
-    delete fragment.invitations
     collection.technicalChecks.eqa = false
 
     const res = await requests.sendRequest({
@@ -464,7 +448,6 @@ describe('Post fragments recommendations route handler', () => {
 
     expect(res.statusCode).toBe(200)
     const data = JSON.parse(res._getData())
-
     expect(collection.status).toBe('reviewCompleted')
     expect(collection.technicalChecks).not.toHaveProperty('token')
     expect(collection.technicalChecks).not.toHaveProperty('eqa')
@@ -571,6 +554,52 @@ describe('Post fragments recommendations route handler', () => {
     expect(data.error).toEqual('Cannot write another review on this version.')
   })
 
+  it('should return success when creating another recommendation as a HE on the same version when EiC returned manuscript to He ', async () => {
+    const { noRecommendationHE } = testFixtures.users
+    const { noEditorRecomedationCollection } = testFixtures.collections
+    const { noEditorRecomedationFragment } = testFixtures.fragments
+
+    const res = await requests.sendRequest({
+      body,
+      userId: noRecommendationHE.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: noEditorRecomedationCollection.id,
+        fragmentId: noEditorRecomedationFragment.id,
+      },
+    })
+    expect(res.statusCode).toBe(200)
+    const data = JSON.parse(res._getData())
+    expect(data.userId).toEqual(noRecommendationHE.id)
+  })
+
+  it('should return an error when creating another recommendation as a HE on the same version after EiC made decision to publish', async () => {
+    const { handlingEditor } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+    body.recommendation = 'publish'
+    body.recommendationType = 'editorRecommendation'
+
+    const res = await requests.sendRequest({
+      body,
+      userId: handlingEditor.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      'Cannot make another recommendation on this version.',
+    )
+  })
+
   it('should return an error when an EiC makes a decision on an older version of a manuscript', async () => {
     const { editorInChief } = testFixtures.users
     const { twoVersionsCollection } = testFixtures.collections
@@ -597,4 +626,55 @@ describe('Post fragments recommendations route handler', () => {
       'Cannot make a recommendation on an older version.',
     )
   })
+  it('should return success when an EiC requests a revision before the Handling Editor is assigned', async () => {
+    const { editorInChief } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+    body.recommendation = 'revision'
+    body.recommendationType = 'editorRecommendation'
+    delete collection.handlingEditor
+
+    const res = await requests.sendRequest({
+      body,
+      userId: editorInChief.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(200)
+    const data = JSON.parse(res._getData())
+    expect(data.userId).toEqual(editorInChief.id)
+    expect(collection.status).toEqual('revisionRequested')
+    expect(fragment).toHaveProperty('revision')
+  })
+  it('should return an error when an EiC requests a revision after a Handling Editor is assigned', async () => {
+    const { editorInChief } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+    body.recommendation = 'revision'
+    body.recommendationType = 'editorRecommendation'
+
+    const res = await requests.sendRequest({
+      body,
+      userId: editorInChief.id,
+      models,
+      route,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      'Cannot make request a revision after a Handling Editor has been assigned.',
+    )
+  })
 })
diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js
index 4953fa278e884980a5bfe28168c62a6a0e97a455..589bf76083c0852480e0d3bde0e3a62ed13bfbf9 100644
--- a/packages/component-manuscript/src/components/ManuscriptLayout.js
+++ b/packages/component-manuscript/src/components/ManuscriptLayout.js
@@ -17,13 +17,14 @@ import {
   ResponseToRevisionRequest,
 } from 'pubsweet-component-faraday-ui'
 
-import ReviewerReportCard from './ReviewReportCard'
 import ReviewerReportForm from './ReviewerReportForm'
 import EditorialCommentCard from './EditorialCommentCard'
+import ReviewerReports from './ReviewerReports'
 
 const messagesLabel = {
   'return-to-handling-editor': 'Comments for Handling Editor',
   reject: 'Comments for Author',
+  revision: 'Comments for Author',
 }
 
 const cannotViewReviewersDetails = ['revisionRequested', 'pendingApproval']
@@ -57,6 +58,7 @@ const ManuscriptLayout = ({
   heResponseExpanded,
   inviteHandlingEditor,
   toggleReviewerDetails,
+  isFetchingFromAutosave,
   recommendationHandler,
   toggleReviewerResponse,
   reviewerDetailsExpanded,
@@ -122,17 +124,25 @@ const ManuscriptLayout = ({
           <AuthorReviews
             currentUser={currentUser}
             getSignedUrl={getSignedUrl}
+            invitations={invitationsWithReviewers}
             journal={journal}
-            reports={reviewerReports}
+            reviewerReports={reviewerReports}
             token={get(currentUser, 'token')}
           />
         )}
 
-        {submittedOwnRecommendation && (
-          <ReviewerReportCard
+        {get(
+          currentUser,
+          'permissions.reviewersCanViewReviewerReports',
+          false,
+        ) && (
+          <ReviewerReports
+            currentUser={currentUser}
             getSignedUrl={getSignedUrl}
+            invitations={invitationsWithReviewers}
+            isLatestVersion={isLatestVersion}
             journal={journal}
-            report={submittedOwnRecommendation}
+            reviewerReports={reviewerRecommendations}
             token={get(currentUser, 'token')}
           />
         )}
@@ -154,6 +164,7 @@ const ManuscriptLayout = ({
               changeForm={changeForm}
               expanded={reviewerRecommendationExpanded}
               formValues={get(formValues, 'reviewerReport', {})}
+              isFetchingFromAutosave={isFetchingFromAutosave}
               modalKey="reviewer-report"
               project={collection}
               review={pendingOwnRecommendation}
@@ -163,17 +174,18 @@ const ManuscriptLayout = ({
             />
           )}
 
-        {get(currentUser, 'isInvitedHE', false) && (
-          <ResponseToInvitation
-            commentsOn="decline"
-            expanded={heResponseExpanded}
-            formValues={formValues.responseToInvitation}
-            label="Do you agree to be the handling editor for this manuscript?"
-            onResponse={inviteHandlingEditor.onHEResponse}
-            title="Respond to Editorial Invitation"
-            toggle={toggleHEResponse}
-          />
-        )}
+        {isLatestVersion &&
+          get(currentUser, 'isInvitedHE', false) && (
+            <ResponseToInvitation
+              commentsOn="decline"
+              expanded={heResponseExpanded}
+              formValues={formValues.responseToInvitation}
+              label="Do you agree to be the handling editor for this manuscript?"
+              onResponse={inviteHandlingEditor.onHEResponse}
+              title="Respond to Editorial Invitation"
+              toggle={toggleHEResponse}
+            />
+          )}
 
         {get(currentUser, 'isInvitedToReview', false) && (
           <ResponseToInvitation
@@ -185,14 +197,16 @@ const ManuscriptLayout = ({
           />
         )}
 
-        <ManuscriptAssignHE
-          assignHE={inviteHandlingEditor.assignHE}
-          currentUser={currentUser}
-          expanded={heExpanded}
-          handlingEditors={handlingEditors}
-          isFetching={isFetchingData.editorsFetching}
-          toggle={toggleAssignHE}
-        />
+        {isLatestVersion && (
+          <ManuscriptAssignHE
+            assignHE={inviteHandlingEditor.assignHE}
+            currentUser={currentUser}
+            expanded={heExpanded}
+            handlingEditors={handlingEditors}
+            isFetching={isFetchingData.editorsFetching}
+            toggle={toggleAssignHE}
+          />
+        )}
 
         {get(currentUser, 'permissions.canViewReviewersDetails', false) && (
           <ReviewerDetails
diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js
index 89b5dadefcc611d56684ad1b442b326f2f3870ab..928d9077cc3d789097545716b34e5431ce9d0b14 100644
--- a/packages/component-manuscript/src/components/ManuscriptPage.js
+++ b/packages/component-manuscript/src/components/ManuscriptPage.js
@@ -46,6 +46,7 @@ import {
   pendingHEInvitation,
   currentUserIsReviewer,
   parseCollectionDetails,
+  isFetchingFromAutosave,
   canMakeHERecommendation,
   canViewReviewersDetails,
   canViewEditorialComments,
@@ -57,6 +58,7 @@ import {
   getOwnPendingRecommendation,
   getOwnSubmittedRecommendation,
   canAuthorViewEditorialComments,
+  reviewersCanViewReviewerReports,
   canHEMakeRecommendationToPublish,
   getFragmentReviewerRecommendations,
   getInvitationsWithReviewersForFragment,
@@ -177,7 +179,11 @@ export default compose(
             collection,
             statuses: get(journal, 'statuses', {}),
           }),
-          canAssignHE: canAssignHE(state, collection),
+          canAssignHE: canAssignHE(
+            state,
+            collection,
+            isLatestVersion(collection, fragment),
+          ),
           canViewReports: canViewReports(state, match.params.project),
           canViewEditorialComments: canViewEditorialComments(
             state,
@@ -195,6 +201,11 @@ export default compose(
             collection,
             get(fragment, 'id', ''),
           ),
+          reviewersCanViewReviewerReports: reviewersCanViewReviewerReports(
+            state,
+            collection,
+            get(fragment, 'id', ''),
+          ),
           canOverrideTechChecks: canOverrideTechnicalChecks(state, collection),
           canAuthorViewEditorialComments: canAuthorViewEditorialComments(
             state,
@@ -217,6 +228,7 @@ export default compose(
         editorsFetching: selectFetching(state),
         publonsFetching: isFetching,
       },
+      isFetchingFromAutosave: isFetchingFromAutosave(state),
       formValues: {
         revision: getFormValues('revision')(state),
         eicDecision: getFormValues('eic-decision')(state),
diff --git a/packages/component-manuscript/src/components/ReviewerReports.js b/packages/component-manuscript/src/components/ReviewerReports.js
new file mode 100644
index 0000000000000000000000000000000000000000..9d838360390efb16be9bb8a837d299b866c55c8a
--- /dev/null
+++ b/packages/component-manuscript/src/components/ReviewerReports.js
@@ -0,0 +1,88 @@
+import React from 'react'
+import { compose, withProps } from 'recompose'
+import { get } from 'lodash'
+import {
+  ReviewerReport,
+  ContextualBox,
+  withFilePreview,
+  withFileDownload,
+  Text,
+  Row,
+  indexReviewers,
+} from 'pubsweet-component-faraday-ui'
+
+const SubmittedReports = ({ reports }) => (
+  <Row fitContent justify="flex-end">
+    <Text customId mr={1 / 2}>
+      {reports}
+    </Text>
+    <Text mr={1 / 2} pr={1 / 2} secondary>
+      {' '}
+      submitted
+    </Text>
+  </Row>
+)
+
+const ReviewerReports = ({
+  journal,
+  reports,
+  previewFile,
+  downloadFile,
+  isLatestVersion,
+  currentUser,
+  token,
+  invitations,
+  reviwerReports,
+}) => (
+  <ContextualBox
+    label={isLatestVersion ? 'Your Report' : 'Reviewer Reports'}
+    mb={2}
+    rightChildren={<SubmittedReports reports={reports.length} />}
+    startExpanded
+  >
+    {reports.map(report => (
+      <ReviewerReport
+        currentUser={currentUser}
+        journal={journal}
+        key={report.id}
+        onDownload={downloadFile}
+        onPreview={previewFile}
+        report={report}
+        reviewerNumber={report.reviewerNumber}
+        showOwner={report.userId === currentUser.id}
+      />
+    ))}
+  </ContextualBox>
+)
+
+export default compose(
+  withFileDownload,
+  withFilePreview,
+  withProps(
+    ({
+      invitations = [],
+      publonReviewers = [],
+      reviewerReports = [],
+      currentUser,
+      isLatestVersion,
+    }) => ({
+      token: get(currentUser, 'token', ''),
+      publonReviewers,
+      invitations: invitations.map(i => ({
+        ...i,
+        review: reviewerReports.find(r => r.userId === i.userId),
+      })),
+      reports: isLatestVersion
+        ? indexReviewers(
+            reviewerReports.filter(
+              r => r.submittedOn && r.userId === currentUser.id,
+            ),
+            invitations,
+          )
+        : indexReviewers(
+            reviewerReports.filter(r => r.submittedOn),
+            invitations,
+          ),
+    }),
+  ),
+)(ReviewerReports)
diff --git a/packages/component-manuscript/src/components/SubmitRevision.js b/packages/component-manuscript/src/components/SubmitRevision.js
index 0fa81380877fdf5ff9c2be286c62971c01139fe8..ae6e333fd324b1d97cb7185afec66b71b14d54fc 100644
--- a/packages/component-manuscript/src/components/SubmitRevision.js
+++ b/packages/component-manuscript/src/components/SubmitRevision.js
@@ -106,7 +106,7 @@ const SubmitRevision = ({
         </CustomValidatedField>
       </Expandable>
       {!isEmpty(reviews) && (
-        <Expandable label="RESPONSE TO REVIEWER COMMENTS" startExpanded>
+        <Expandable label="RESPONSE TO REVISION REQUEST" startExpanded>
           <Title>Reply text*</Title>
           <Row>
             <FullWidth className="full-width">
diff --git a/packages/component-manuscript/src/redux/editors.js b/packages/component-manuscript/src/redux/editors.js
index e28c0dd29ff392776fa2092639ec8fceda19026a..2ff500a7c82c6d94db4b74faaa837b961b6c1b04 100644
--- a/packages/component-manuscript/src/redux/editors.js
+++ b/packages/component-manuscript/src/redux/editors.js
@@ -24,13 +24,14 @@ export const selectHandlingEditors = state =>
     .value()
 
 const canAssignHEStatuses = ['submitted']
-export const canAssignHE = (state, collection = {}) => {
+export const canAssignHE = (state, collection = {}, isLatestVersion) => {
   const isEIC = currentUserIs(state, 'adminEiC')
   const hasHE = get(collection, 'handlingEditor', false)
 
   return (
     isEIC &&
     !hasHE &&
+    isLatestVersion &&
     canAssignHEStatuses.includes(get(collection, 'status', 'draft'))
   )
 }
diff --git a/packages/component-manuscript/src/submitRevision/utils.js b/packages/component-manuscript/src/submitRevision/utils.js
index bee60515f02ca64d53e90cb8478ebe32de58ea8b..970d5303370d5be965c0c4ad64bae08d7e1ec5d9 100644
--- a/packages/component-manuscript/src/submitRevision/utils.js
+++ b/packages/component-manuscript/src/submitRevision/utils.js
@@ -5,7 +5,7 @@ import { autosaveRequest } from 'pubsweet-component-wizard/src/redux/autosave'
 import { submitRevision } from 'pubsweet-component-wizard/src/redux/conversion'
 
 const parseRevision = (values, fragment) => ({
-  ...fragment,
+  ...omit(fragment, 'recommendations'),
   revision: {
     ...values,
   },
diff --git a/packages/component-mts-package/src/MTS.js b/packages/component-mts-package/src/MTS.js
index 3f9e503df78bbf5d3273f0e7e19dd4d3c764b1e1..4b9cdadfcd4ce88fcebde24378b9b5ab6c527a7d 100644
--- a/packages/component-mts-package/src/MTS.js
+++ b/packages/component-mts-package/src/MTS.js
@@ -38,17 +38,21 @@ module.exports = {
       fragment,
       xmlFile,
       isEQA,
-    }).then(() => {
-      const packageName = get(xmlFile, 'name', '').replace('.xml', '')
-      const filename = isEQA
-        ? `ACCEPTED_${packageName}.${fragment.version}.zip`
-        : `${packageName}.zip`
+    })
+      .then(() => {
+        const packageName = get(xmlFile, 'name', '').replace('.xml', '')
+        const filename = isEQA
+          ? `ACCEPTED_${packageName}.${fragment.version}.zip`
+          : `${packageName}.zip`
 
-      return PackageManager.uploadFiles({
-        filename,
-        s3Config,
-        config: ftpConfig,
+        return PackageManager.uploadFiles({
+          filename,
+          s3Config,
+          config: ftpConfig,
+        })
+      })
+      .catch(e => {
+        throw new Error(e)
       })
-    })
   },
 }
diff --git a/packages/component-user-manager/src/routes/users/changePassword.js b/packages/component-user-manager/src/routes/users/changePassword.js
index 30073accccd603baa60cdea466dc3a35ded680f1..7630d4374bdd826352a8db87a41cd1fb4b3d5761 100644
--- a/packages/component-user-manager/src/routes/users/changePassword.js
+++ b/packages/component-user-manager/src/routes/users/changePassword.js
@@ -1,24 +1,24 @@
 const { services } = require('pubsweet-component-helper-service')
 const { token } = require('pubsweet-server/src/authentication')
+const { passwordStrengthRegex } = require('config')
 
 module.exports = models => async (req, res) => {
-  const { password, newPassword } = req.body
-  if (!services.checkForUndefinedParams(password, newPassword))
+  const { currentPassword, password } = req.body
+  if (!services.checkForUndefinedParams(currentPassword, password))
     return res.status(400).json({ error: 'Missing required params.' })
-
-  if (newPassword.length < 7)
-    return res
-      .status(400)
-      .json({ error: 'Password needs to be at least 7 characters long.' })
+  if (!passwordStrengthRegex.test(password))
+    return res.status(400).json({
+      error: 'Password is too weak. Please check password requirements.',
+    })
 
   let user
   try {
     user = await models.User.find(req.user)
 
-    if (!await user.validPassword(password)) {
+    if (!await user.validPassword(currentPassword)) {
       return res.status(400).json({ error: 'Wrong username or password.' })
     }
-    user.password = newPassword
+    user.password = password
     user = await user.save()
 
     return res.status(200).json({
diff --git a/packages/component-user-manager/src/routes/users/post.js b/packages/component-user-manager/src/routes/users/post.js
index 8150e2ca77f46131c69325b591e5a58dcd93ad1c..44595c0ae46cb5549f0b95f1c0e315df3666f3e2 100644
--- a/packages/component-user-manager/src/routes/users/post.js
+++ b/packages/component-user-manager/src/routes/users/post.js
@@ -1,5 +1,6 @@
 const { pick } = require('lodash')
 const Chance = require('chance')
+const { passwordStrengthRegex } = require('config')
 
 const chance = new Chance()
 
@@ -15,6 +16,10 @@ module.exports = models => async (req, res) => {
         error: 'Terms & Conditions must be read and approved.',
       })
     }
+    if (!passwordStrengthRegex.test(req.body.password))
+      return res.status(400).json({
+        error: 'Password is too weak. Please check password requirements.',
+      })
     req.body = pick(req.body, [
       'email',
       'title',
diff --git a/packages/component-user-manager/src/routes/users/resetPassword.js b/packages/component-user-manager/src/routes/users/resetPassword.js
index b46c9cafdccf18ddab7facd6e629cc4a0f92a065..c42dd4cc1037f15b22acb0f0899b1cf97e420eae 100644
--- a/packages/component-user-manager/src/routes/users/resetPassword.js
+++ b/packages/component-user-manager/src/routes/users/resetPassword.js
@@ -1,14 +1,16 @@
 const { services } = require('pubsweet-component-helper-service')
 
+const { passwordStrengthRegex } = require('config')
+
 module.exports = models => async (req, res) => {
   const { email, password, token } = req.body
   if (!services.checkForUndefinedParams(email, password, token))
     return res.status(400).json({ error: 'missing required params' })
 
-  if (password.length < 7)
-    return res
-      .status(400)
-      .json({ error: 'password needs to be at least 7 characters long' })
+  if (!passwordStrengthRegex.test(req.body.password))
+    return res.status(400).json({
+      error: 'Password is too weak. Please check password requirements.',
+    })
 
   const validateResponse = await services.validateEmailAndToken({
     email,
diff --git a/packages/component-user-manager/src/tests/users/changePassword.test.js b/packages/component-user-manager/src/tests/users/changePassword.test.js
index a97b1e0fe30e1c5605f7a30167d0034c9fd8e9d9..c4118fb54422b3d17377400da3401df9ddd2a90a 100644
--- a/packages/component-user-manager/src/tests/users/changePassword.test.js
+++ b/packages/component-user-manager/src/tests/users/changePassword.test.js
@@ -13,8 +13,8 @@ jest.mock('@pubsweet/component-send-email', () => ({
 }))
 
 const reqBody = {
-  password: 'password',
-  newPassword: 'newPassword',
+  currentPassword: 'password',
+  password: 'N3wPassword!',
 }
 
 const notFoundError = new Error()
@@ -44,7 +44,7 @@ describe('Users password reset route handler', () => {
     expect(data.error).toEqual('Missing required params.')
   })
   it('should return an error when the password is too small', async () => {
-    body.newPassword = 'small'
+    body.password = 'small'
     const req = httpMocks.createRequest({ body })
     req.user = user.id
 
@@ -53,7 +53,7 @@ describe('Users password reset route handler', () => {
     expect(res.statusCode).toBe(400)
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual(
-      'Password needs to be at least 7 characters long.',
+      'Password is too weak. Please check password requirements.',
     )
   })
   it('should return an error when user is not found', async () => {
@@ -67,7 +67,7 @@ describe('Users password reset route handler', () => {
     expect(data.error).toEqual('User not found')
   })
   it('should return an error when the current password is incorrect', async () => {
-    body.password = 'invalid-password'
+    body.currentPassword = 'invalid-password'
     const req = httpMocks.createRequest({ body })
     req.user = user.id
 
@@ -90,6 +90,6 @@ describe('Users password reset route handler', () => {
     savedUser.validPassword = user.validPassword
     expect(savedUser.token).not.toEqual(user.token)
 
-    expect(savedUser.validPassword(body.newPassword)).toBeTruthy()
+    expect(savedUser.validPassword(body.password)).toBeTruthy()
   })
 })
diff --git a/packages/component-user-manager/src/tests/users/resetPassword.test.js b/packages/component-user-manager/src/tests/users/resetPassword.test.js
index 6b9dfc7e9fcae7d466155e1ad227cb8fce07da07..a802f929d1704c907cfaeaec3d546720e324d1d4 100644
--- a/packages/component-user-manager/src/tests/users/resetPassword.test.js
+++ b/packages/component-user-manager/src/tests/users/resetPassword.test.js
@@ -20,7 +20,7 @@ const reqBody = {
   lastName: user.lastName,
   title: user.title,
   affiliation: user.affiliation,
-  password: 'password',
+  password: 'P4ssword!',
   token: user.accessTokens.passwordReset,
   isConfirmed: false,
 }
@@ -57,7 +57,7 @@ describe('Users password reset route handler', () => {
     expect(res.statusCode).toBe(400)
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual(
-      'password needs to be at least 7 characters long',
+      'Password is too weak. Please check password requirements.',
     )
   })
   it('should return an error when user is not found', async () => {
diff --git a/packages/components-faraday/src/components/Login/LoginPage.js b/packages/components-faraday/src/components/Login/LoginPage.js
index 55893a41090cf2a321853163a28edad365e6e8c1..329c6038d6814236ae137b8fe15e2e0fbde48cc5 100644
--- a/packages/components-faraday/src/components/Login/LoginPage.js
+++ b/packages/components-faraday/src/components/Login/LoginPage.js
@@ -14,11 +14,12 @@ import {
   Text,
   Label,
   ActionLink,
+  withFetching,
 } from 'pubsweet-component-faraday-ui'
 
 const PasswordField = input => <TextField {...input} type="password" />
 
-const Login = ({ handleSubmit, loginError }) => (
+const Login = ({ handleSubmit, fetchingError }) => (
   <Root onSubmit={handleSubmit}>
     <CustomH2>Login</CustomH2>
     <Row mt={3}>
@@ -53,9 +54,9 @@ const Login = ({ handleSubmit, loginError }) => (
       LOG IN
     </Button>
 
-    {loginError && (
+    {fetchingError && (
       <Row justify="flex-start" mt={1}>
-        <Text error>{loginError}</Text>
+        <Text error>{fetchingError}</Text>
       </Row>
     )}
 
@@ -71,14 +72,10 @@ const Login = ({ handleSubmit, loginError }) => (
 )
 
 const LoginPage = compose(
-  connect(
-    state => ({
-      loginError: state.error,
-    }),
-    {
-      logoutUser,
-    },
-  ),
+  withFetching,
+  connect(null, {
+    logoutUser,
+  }),
   withProps({ passwordReset: true }),
   lifecycle({
     componentDidMount() {
@@ -89,9 +86,9 @@ const LoginPage = compose(
   reduxForm({
     form: 'login',
     enableReinitialize: false,
-    onSubmit: (values, dispatch, { location }) => {
+    onSubmit: (values, dispatch, { location, setError }) => {
       const redirectTo = get(location, 'state.from.pathname', '/dashboard')
-      dispatch(loginUser(values, redirectTo))
+      dispatch(loginUser(values, redirectTo, setError))
     },
   }),
 )(Login)
diff --git a/packages/components-faraday/src/components/SignUp/ReviewerInviteDecision.js b/packages/components-faraday/src/components/SignUp/ReviewerInviteDecision.js
index 917b0073bed22be7d1955dc933f35dc949ca9c03..90a234ae4ed34d1a692bf295cebf7ac4ae431cfc 100644
--- a/packages/components-faraday/src/components/SignUp/ReviewerInviteDecision.js
+++ b/packages/components-faraday/src/components/SignUp/ReviewerInviteDecision.js
@@ -1,29 +1,25 @@
 import React from 'react'
 import { connect } from 'react-redux'
 import { reduxForm } from 'redux-form'
-import { required, minChars } from 'xpub-validators'
+import { Button, H2, Spinner } from '@pubsweet/ui'
 import { compose, withState, lifecycle } from 'recompose'
 import { loginUser } from 'pubsweet-component-login/actions'
-import { Button, ValidatedField, H2, TextField, Spinner } from '@pubsweet/ui'
 import {
   Row,
-  Item,
   Text,
-  Label,
   ShadowedBox,
+  PasswordValidation,
   handleError,
   withFetching,
+  passwordValidator,
 } from 'pubsweet-component-faraday-ui'
 
-import { redirectToError, passwordValidator } from '../utils'
+import { redirectToError } from '../utils'
 import { reviewerDecision, setReviewerPassword } from '../../redux/reviewers'
 
 const agreeText = `You have been invited to review a manuscript on the Hindawi platform. Please set a password and proceed to the manuscript.`
 const declineText = `You have decline to work on a manuscript.`
 
-const PasswordField = input => <TextField {...input} type="password" />
-const min8Chars = minChars(8)
-
 const ReviewerInviteDecision = ({
   agree,
   error,
@@ -43,27 +39,7 @@ const ReviewerInviteDecision = ({
       <Text align="center">{agree === 'true' ? agreeText : declineText}</Text>
     </Row>
 
-    <Row mt={2}>
-      <Item vertical>
-        <Label required>Password</Label>
-        <ValidatedField
-          component={PasswordField}
-          name="password"
-          validate={[required, min8Chars]}
-        />
-      </Item>
-    </Row>
-
-    <Row mt={2}>
-      <Item vertical>
-        <Label required>Confirm password</Label>
-        <ValidatedField
-          component={PasswordField}
-          name="confirmPassword"
-          validate={[required]}
-        />
-      </Item>
-    </Row>
+    <PasswordValidation formLabel="Password" formName="invite-reviewer" />
 
     {fetchingError && (
       <Row mt={2}>
diff --git a/packages/components-faraday/src/components/SignUp/SignUpInvitationForm.js b/packages/components-faraday/src/components/SignUp/SignUpInvitationForm.js
index b4f9d3f014d0c61fa7cf83f048044a5b5cf9121a..bc87c22263ce048c3d4d0bd7c4381221b8d2f609 100644
--- a/packages/components-faraday/src/components/SignUp/SignUpInvitationForm.js
+++ b/packages/components-faraday/src/components/SignUp/SignUpInvitationForm.js
@@ -5,6 +5,13 @@ import { Text, ShadowedBox } from 'pubsweet-component-faraday-ui'
 import Step0 from './SignUpStep0'
 import Step1 from './SignUpStep1'
 
+const containerPadding = {
+  pt: 4,
+  pb: 4,
+  pl: 4,
+  pr: 4,
+}
+
 const SignUpInvitation = ({
   type,
   step,
@@ -18,8 +25,8 @@ const SignUpInvitation = ({
   initialValues,
   title = 'Add New Account Details',
 }) => (
-  <ShadowedBox center mb={3} mt={10}>
-    <H2>{title}</H2>
+  <ShadowedBox center mb={3} mt={10} {...containerPadding}>
+    <H2 mb={step === 0 ? 0 : 2}>{title}</H2>
     {error && <Text error>Token expired or Something went wrong.</Text>}
     {step === 0 && (
       <Step0
diff --git a/packages/components-faraday/src/components/SignUp/SignUpStep0.js b/packages/components-faraday/src/components/SignUp/SignUpStep0.js
index 4cb4f094d06d1585a6a814e4cf1fda0af3b3d9bf..dc60d273a5a3ea675d019a15be37db17a0016c85 100644
--- a/packages/components-faraday/src/components/SignUp/SignUpStep0.js
+++ b/packages/components-faraday/src/components/SignUp/SignUpStep0.js
@@ -11,8 +11,8 @@ import {
   Item,
   Label,
   ActionLink,
+  MenuCountry,
   ItemOverrideAlert,
-  withCountries,
 } from 'pubsweet-component-faraday-ui'
 
 const AgreeCheckbox = ({ value, onChange }) => (
@@ -21,20 +21,13 @@ const AgreeCheckbox = ({ value, onChange }) => (
     <Text>
       I agree with the{' '}
       <ActionLink to="https://www.hindawi.com/terms/">
-        Terms of Services
+        Terms of Service
       </ActionLink>{' '}
     </Text>
   </Row>
 )
 
-const Step0 = ({
-  type,
-  error,
-  journal,
-  countries,
-  handleSubmit,
-  initialValues,
-}) =>
+const Step0 = ({ type, error, journal, handleSubmit, initialValues }) =>
   !isUndefined(initialValues) ? (
     <Fragment>
       <Row mb={2} mt={3}>
@@ -75,11 +68,7 @@ const Step0 = ({
           <Label required>Country</Label>
           <ValidatedField
             component={input => (
-              <Menu
-                {...input}
-                options={countries}
-                placeholder="Please select"
-              />
+              <MenuCountry {...input} placeholder="Please select" />
             )}
             name="country"
             validate={[requiredValidator]}
@@ -142,7 +131,6 @@ const Step0 = ({
   )
 
 export default compose(
-  withCountries,
   reduxForm({
     form: 'signUpInvitation',
     destroyOnUnmount: false,
diff --git a/packages/components-faraday/src/components/SignUp/SignUpStep1.js b/packages/components-faraday/src/components/SignUp/SignUpStep1.js
index 21177e76eb9fa5006d88c02b432cf0057e3fa2a5..207ee958c0fe78e568b8b3ef3e64155940f618c2 100644
--- a/packages/components-faraday/src/components/SignUp/SignUpStep1.js
+++ b/packages/components-faraday/src/components/SignUp/SignUpStep1.js
@@ -2,11 +2,17 @@ import React, { Fragment } from 'react'
 import { reduxForm } from 'redux-form'
 import { required } from 'xpub-validators'
 import { Button, ValidatedField, TextField } from '@pubsweet/ui'
-import { Row, Item, Label, Text } from 'pubsweet-component-faraday-ui'
+import {
+  Row,
+  Item,
+  Label,
+  Text,
+  PasswordValidation,
+  passwordValidator,
+} from 'pubsweet-component-faraday-ui'
 
-import { passwordValidator, emailValidator } from '../utils'
+import { emailValidator } from '../utils'
 
-const PasswordField = input => <TextField {...input} type="password" />
 const EmailField = input => <TextField {...input} type="email" />
 
 const SignUpForm = () => (
@@ -21,52 +27,12 @@ const SignUpForm = () => (
         />
       </Item>
     </Row>
-    <Row mb={2}>
-      <Item data-test-id="sign-up-password" vertical>
-        <Label required>Password</Label>
-        <ValidatedField
-          component={PasswordField}
-          name="password"
-          validate={[required]}
-        />
-      </Item>
-    </Row>
-    <Row mb={2}>
-      <Item data-test-id="sign-up-confirm-password" vertical>
-        <Label required>Confirm password</Label>
-        <ValidatedField
-          component={PasswordField}
-          name="confirmPassword"
-          validate={[required]}
-        />
-      </Item>
-    </Row>
+    <PasswordValidation formLabel="Password" formName="signUpInvitation" />
   </Fragment>
 )
 
 const InviteForm = () => (
-  <Fragment>
-    <Row mb={2} mt={2}>
-      <Item vertical>
-        <Label required>Password</Label>
-        <ValidatedField
-          component={PasswordField}
-          name="password"
-          validate={[required]}
-        />
-      </Item>
-    </Row>
-    <Row mb={2}>
-      <Item vertical>
-        <Label required>Confirm password</Label>
-        <ValidatedField
-          component={PasswordField}
-          name="confirmPassword"
-          validate={[required]}
-        />
-      </Item>
-    </Row>
-  </Fragment>
+  <PasswordValidation formLabel="Password" formName="signUpInvitation" />
 )
 
 const ForgotEmailForm = () => (
diff --git a/packages/components-faraday/src/components/SignUp/utils.js b/packages/components-faraday/src/components/SignUp/utils.js
index 3a9fd998810f022e1f270db62c10ab020a8bf29b..b7cf7a37f3991b5b01254f063403143e417e09be 100644
--- a/packages/components-faraday/src/components/SignUp/utils.js
+++ b/packages/components-faraday/src/components/SignUp/utils.js
@@ -5,7 +5,11 @@ import { loginUser } from 'pubsweet-component-login/actions'
 
 import { handleFormError } from '../utils'
 
-export const parseSignupAuthor = ({ token, confirmPassword, ...values }) => ({
+export const parseSignupAuthor = ({
+  token,
+  confirmNewPassword,
+  ...values
+}) => ({
   ...values,
 })
 
@@ -28,7 +32,10 @@ export const login = (dispatch, values, history) =>
 export const confirmUser = (email, token, history) => (values, dispatch) => {
   const request = { ...values, email, token }
   if (values) {
-    return create('/users/reset-password', omit(request, ['confirmPassword']))
+    return create(
+      '/users/reset-password',
+      omit(request, ['confirmNewPassword']),
+    )
       .then(r => {
         const { username } = r
         const { password } = values
diff --git a/packages/components-faraday/src/components/UserProfile/ChangePasswordPage.js b/packages/components-faraday/src/components/UserProfile/ChangePasswordPage.js
index 21ab0285bcfe8956990d035f028a604cd58ba332..03df4ad73115052c960a36eb02adb78244426a2a 100644
--- a/packages/components-faraday/src/components/UserProfile/ChangePasswordPage.js
+++ b/packages/components-faraday/src/components/UserProfile/ChangePasswordPage.js
@@ -11,48 +11,34 @@ import {
   Text,
   Label,
   ShadowedBox,
+  PasswordValidation,
+  changePasswordValidator,
 } from 'pubsweet-component-faraday-ui'
 
-import {
-  changePasswordValidator,
-  onSubmitChangePassword as onSubmit,
-} from '../utils'
+import { onSubmitChangePassword as onSubmit } from '../utils'
 
 const PasswordField = input => <TextField {...input} type="password" />
 
+const containerPadding = {
+  pt: 4,
+  pb: 4,
+  pl: 4,
+  pr: 4,
+}
 const ChangePassword = ({ history, handleSubmit, error }) => (
-  <ShadowedBox center mt={10}>
+  <ShadowedBox center mt={10} {...containerPadding}>
     <H2>Change Password</H2>
-    <Row mt={3}>
+    <Row mb={2} mt={3}>
       <Item vertical>
         <Label required>Current Password</Label>
         <ValidatedField
           component={PasswordField}
-          name="password"
-          validate={[required]}
-        />
-      </Item>
-    </Row>
-    <Row mt={2}>
-      <Item vertical>
-        <Label required>New Password</Label>
-        <ValidatedField
-          component={PasswordField}
-          name="newPassword"
-          validate={[required]}
-        />
-      </Item>
-    </Row>
-    <Row mt={2}>
-      <Item vertical>
-        <Label required>Re-type password</Label>
-        <ValidatedField
-          component={PasswordField}
-          name="confirmNewPassword"
+          name="currentPassword"
           validate={[required]}
         />
       </Item>
     </Row>
+    <PasswordValidation formLabel="New Password" formName="changePassword" />
     {error && (
       <Row mt={1}>
         <Item>
@@ -61,7 +47,7 @@ const ChangePassword = ({ history, handleSubmit, error }) => (
       </Row>
     )}
     <Row />
-    <Row justify="space-between" mt={3}>
+    <Row justify="space-between">
       <Button onClick={history.goBack}>Back</Button>
       <Button onClick={handleSubmit} primary>
         Update password
diff --git a/packages/components-faraday/src/components/utils.js b/packages/components-faraday/src/components/utils.js
index b6393a018f20e8bcf17c64e156e4aa68fed40adc..0f1f81c6b4cbb2fba9962d68d2040ceed2e0e1c5 100644
--- a/packages/components-faraday/src/components/utils.js
+++ b/packages/components-faraday/src/components/utils.js
@@ -101,35 +101,6 @@ export const redirectToError = redirectFn => err => {
     errorText || 'Something went wrong. Please try again.',
   )
 }
-
-export const passwordValidator = values => {
-  const errors = {}
-  if (!values.password) {
-    errors.password = 'Required'
-  }
-  if (!values.confirmPassword) {
-    errors.confirmPassword = 'Required'
-  } else if (values.confirmPassword !== values.password) {
-    errors.confirmPassword = "Passwords don't match."
-  }
-
-  return errors
-}
-
-export const changePasswordValidator = values => {
-  const errors = {}
-  if (!values.password) {
-    errors.password = 'Required'
-  }
-  if (!values.newPassword) {
-    errors.newPassword = 'Required'
-  } else if (values.newPassword !== values.confirmNewPassword) {
-    errors.confirmNewPassword = "Passwords don't match."
-  }
-
-  return errors
-}
-
 export const parseSearchParams = url => {
   const params = new URLSearchParams(url)
   const parsedObject = {}
@@ -175,11 +146,11 @@ export const saveUserDetails = (userId, values) => dispatch =>
   })
 
 export const onSubmitChangePassword = (
-  { password, newPassword },
+  { currentPassword, password },
   dispatch,
   { history },
 ) =>
-  create(`/users/change-password`, { password, newPassword })
+  create(`/users/change-password`, { currentPassword, password })
     .then(() => {
       history.goBack()
     })
diff --git a/packages/hindawi-theme/src/index.js b/packages/hindawi-theme/src/index.js
index 39b27b8678e151241a52176e2faeb3ca55074292..fa72ae39d8eb011a3f3f752d89f6c74295f1558d 100644
--- a/packages/hindawi-theme/src/index.js
+++ b/packages/hindawi-theme/src/index.js
@@ -129,6 +129,7 @@ const hindawiTheme = {
 
   // font sizes
   fontSizeBase: '14px',
+  fontSizeBaseMedium: '13px',
   fontSizeBaseSmall: '10px',
 
   fontSizeHeading1: '30px',
diff --git a/packages/pubsweet-component-login/CHANGELOG.md b/packages/pubsweet-component-login/CHANGELOG.md
deleted file mode 100644
index 053b9a9ceb89f38e0ccc02a60386a493de816d58..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/CHANGELOG.md
+++ /dev/null
@@ -1,204 +0,0 @@
-# Change Log
-
-All notable changes to this project will be documented in this file.
-See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-<a name="1.2.0"></a>
-# [1.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.18...pubsweet-component-login@1.2.0) (2018-11-05)
-
-
-### Features
-
-* GraphQL Login component ([70df3de](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/70df3de))
-* GraphQL Xpub submit component ([ba07060](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ba07060))
-
-
-
-
-<a name="1.1.18"></a>
-## [1.1.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.17...pubsweet-component-login@1.1.18) (2018-10-08)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.17"></a>
-## [1.1.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.16...pubsweet-component-login@1.1.17) (2018-09-27)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.16"></a>
-## [1.1.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.15...pubsweet-component-login@1.1.16) (2018-09-19)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.15"></a>
-## [1.1.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.14...pubsweet-component-login@1.1.15) (2018-09-06)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.14"></a>
-## [1.1.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.13...pubsweet-component-login@1.1.14) (2018-09-04)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.13"></a>
-## [1.1.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.12...pubsweet-component-login@1.1.13) (2018-08-20)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.12"></a>
-## [1.1.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.11...pubsweet-component-login@1.1.12) (2018-08-17)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.11"></a>
-## [1.1.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.10...pubsweet-component-login@1.1.11) (2018-08-02)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.10"></a>
-## [1.1.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.9...pubsweet-component-login@1.1.10) (2018-07-27)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.9"></a>
-## [1.1.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.8...pubsweet-component-login@1.1.9) (2018-07-12)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.8"></a>
-## [1.1.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.7...pubsweet-component-login@1.1.8) (2018-07-09)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.7"></a>
-## [1.1.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.6...pubsweet-component-login@1.1.7) (2018-07-03)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.6"></a>
-## [1.1.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.5...pubsweet-component-login@1.1.6) (2018-07-02)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.5"></a>
-## [1.1.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.4...pubsweet-component-login@1.1.5) (2018-06-28)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.4"></a>
-## [1.1.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.3...pubsweet-component-login@1.1.4) (2018-06-28)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.3"></a>
-## [1.1.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.2...pubsweet-component-login@1.1.3) (2018-06-19)
-
-
-### Bug Fixes
-
-* **pubsweet-ui:** tests are failing ([0e57798](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0e57798))
-
-
-
-
-<a name="1.1.2"></a>
-## [1.1.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.1...pubsweet-component-login@1.1.2) (2018-04-03)
-
-
-
-
-**Note:** Version bump only for package pubsweet-component-login
-
-<a name="1.1.1"></a>
-## [1.1.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.0...pubsweet-component-login@1.1.1) (2018-03-15)
-
-
-### Bug Fixes
-
-* **login:** add missing recompose dependency ([a3b5a80](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a3b5a80)), closes [#353](https://gitlab.coko.foundation/pubsweet/pubsweet/issues/353)
-
-
-
-
-<a name="1.1.0"></a>
-# [1.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.0.1...pubsweet-component-login@1.1.0) (2018-03-05)
-
-
-### Bug Fixes
-
-* **components:** login example ([6dfd66c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/6dfd66c))
-* **components:** login tests were failing after refactor ([62be047](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/62be047))
-* **components:** signup and login error examples ([3f991ec](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3f991ec))
-
-
-### Features
-
-* **elife-theme:** add elife theme ([e406e0d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e406e0d))
-
-
-
-
-<a name="1.0.1"></a>
-
-## [1.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.0.0...pubsweet-component-login@1.0.1) (2018-02-08)
-
-### Bug Fixes
-
-* **components:** update react-router-redux version to match client ([3d257ef](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3d257ef))
-
-<a name="1.0.0"></a>
-
-# [1.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@0.6.0...pubsweet-component-login@1.0.0) (2018-02-02)
-
-### Features
-
-* **client:** upgrade React to version 16 ([626cf59](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/626cf59)), closes [#65](https://gitlab.coko.foundation/pubsweet/pubsweet/issues/65)
-
-### BREAKING CHANGES
-
-* **client:** Upgrade React to version 16
diff --git a/packages/pubsweet-component-login/Login.jsx b/packages/pubsweet-component-login/Login.jsx
deleted file mode 100644
index 6cc5a2d8597fa53e053e4c5615f2502093e17280..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/Login.jsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { Field } from 'formik'
-import { isEmpty } from 'lodash'
-import {
-  CenteredColumn,
-  ErrorText,
-  H1,
-  Link,
-  Button,
-  TextField,
-} from '@pubsweet/ui'
-import styled from 'styled-components'
-
-// These enable tests to select components
-const Signup = styled.div``
-const ResetPassword = styled.div``
-
-const UsernameInput = props => <TextField label="Username" {...props.field} />
-const PasswordInput = props => (
-  <TextField label="Password" {...props.field} type="password" />
-)
-
-const Login = ({
-  errors,
-  handleSubmit,
-  signup = true,
-  passwordReset = true,
-}) => (
-  <CenteredColumn small>
-    <H1>Login</H1>
-
-    {!isEmpty(errors) && <ErrorText>{errors}</ErrorText>}
-    <form onSubmit={handleSubmit}>
-      <Field component={UsernameInput} name="username" />
-      <Field component={PasswordInput} name="password" />
-      <Button primary type="submit">
-        Login
-      </Button>
-    </form>
-
-    {signup && (
-      <Signup>
-        <span>Don&apos;t have an account? </span>
-        <Link to="/signup">Sign up</Link>
-      </Signup>
-    )}
-
-    {passwordReset && (
-      <ResetPassword>
-        <span>Forgot your password? </span>
-        <Link to="/password-reset">Reset password</Link>
-      </ResetPassword>
-    )}
-  </CenteredColumn>
-)
-
-Login.propTypes = {
-  error: PropTypes.string,
-  actions: PropTypes.object,
-  location: PropTypes.object,
-  signup: PropTypes.bool,
-  passwordReset: PropTypes.bool,
-}
-
-// used by tests
-export { Login, ErrorText, Signup, ResetPassword }
-
-// used by consumers
-export default Login
diff --git a/packages/pubsweet-component-login/Login.md b/packages/pubsweet-component-login/Login.md
deleted file mode 100644
index 8efa765dd57c83028d3d4e6d9885797bac8245b7..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/Login.md
+++ /dev/null
@@ -1,40 +0,0 @@
-A login form
-
-```js
-const { withFormik } = require('formik')
-
-const LoginForm = withFormik({
-  initialValues: {
-    username: '',
-    password: '',
-  },
-  mapPropsToValues: props => ({
-    username: props.username,
-    password: props.password,
-  }),
-  displayName: 'login',
-  handleSubmit: val => console.log(val),
-})(Login)
-;<LoginForm />
-```
-
-Which can have an error message:
-
-```js
-const { withFormik } = require('formik')
-
-const LoginForm = withFormik({
-  initialValues: {
-    username: '',
-    password: '',
-  },
-  mapPropsToValues: props => ({
-    username: props.username,
-    password: props.password,
-  }),
-  displayName: 'login',
-  handleSubmit: (values, { setErrors }) =>
-    setErrors('Wrong username or password.'),
-})(Login)
-;<LoginForm />
-```
diff --git a/packages/pubsweet-component-login/Login.test.jsx b/packages/pubsweet-component-login/Login.test.jsx
deleted file mode 100644
index f696b5eaa420beee8fb6fdf3311461b1ed28f97a..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/Login.test.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { shallow } from 'enzyme'
-import React from 'react'
-
-import { Login, ErrorText, Signup, ResetPassword } from './Login'
-
-describe('<Login/>', () => {
-  const makeWrapper = (props = {}) => shallow(<Login {...props} />)
-
-  it('renders the login form', () => {
-    expect(makeWrapper()).toMatchSnapshot()
-  })
-
-  it('shows error', () => {
-    const wrapper = makeWrapper({ errors: 'Yikes!' })
-    expect(wrapper.find(ErrorText)).toHaveLength(1)
-  })
-
-  it('can hide sign up link', () => {
-    const wrapper1 = makeWrapper()
-    const wrapper2 = makeWrapper({ signup: false })
-    expect(wrapper1.find(Signup)).toHaveLength(1)
-    expect(wrapper2.find(Signup)).toHaveLength(0)
-  })
-
-  it('can hide password reset link', () => {
-    const wrapper1 = makeWrapper()
-    const wrapper2 = makeWrapper({ passwordReset: false })
-    expect(wrapper1.find(ResetPassword)).toHaveLength(1)
-    expect(wrapper2.find(ResetPassword)).toHaveLength(0)
-  })
-
-  it('triggers submit handler', () => {
-    const handleSubmit = jest.fn()
-    const wrapper = makeWrapper({ handleSubmit })
-    wrapper.find('form').simulate('submit')
-    expect(handleSubmit).toHaveBeenCalled()
-  })
-})
diff --git a/packages/pubsweet-component-login/LoginContainer.js b/packages/pubsweet-component-login/LoginContainer.js
deleted file mode 100644
index 250dd2d75b9adeb1686805b4a9d8ecd7d8b4291b..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/LoginContainer.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { withFormik } from 'formik'
-import { compose } from 'recompose'
-import { connect } from 'react-redux'
-import { loginUser } from './actions'
-
-import Login from './Login'
-import redirectPath from './redirect'
-
-const handleSubmit = (values, { props: { dispatch, location }, setErrors }) => {
-  dispatch(loginUser(values, redirectPath({ location }), setErrors))
-}
-
-const enhancedFormik = withFormik({
-  initialValues: {
-    username: '',
-    password: '',
-  },
-  mapPropsToValues: props => ({
-    username: props.username,
-    password: props.password,
-  }),
-  displayName: 'login',
-  handleSubmit,
-})(Login)
-
-export default compose(connect(state => state))(enhancedFormik)
diff --git a/packages/pubsweet-component-login/__snapshots__/Login.test.jsx.snap b/packages/pubsweet-component-login/__snapshots__/Login.test.jsx.snap
deleted file mode 100644
index c37d2096241bcb88782ac550b9bd8c8f121e1e52..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/__snapshots__/Login.test.jsx.snap
+++ /dev/null
@@ -1,473 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`<Login/> renders the login form 1`] = `
-ShallowWrapper {
-  Symbol(enzyme.__root__): [Circular],
-  Symbol(enzyme.__unrendered__): <Login />,
-  Symbol(enzyme.__renderer__): Object {
-    "batchedUpdates": [Function],
-    "getNode": [Function],
-    "render": [Function],
-    "simulateError": [Function],
-    "simulateEvent": [Function],
-    "unmount": [Function],
-  },
-  Symbol(enzyme.__node__): Object {
-    "instance": null,
-    "key": undefined,
-    "nodeType": "class",
-    "props": Object {
-      "children": Array [
-        <styled.h1>
-          Login
-        </styled.h1>,
-        false,
-        <form>
-          <C
-            component={[Function]}
-            name="username"
-          />
-          <C
-            component={[Function]}
-            name="password"
-          />
-          <styled.button
-            primary={true}
-            type="submit"
-          >
-            Login
-          </styled.button>
-        </form>,
-        <styled.div>
-          <span>
-            Don't have an account? 
-          </span>
-          <Styled(Link)
-            to="/signup"
-          >
-            Sign up
-          </Styled(Link)>
-        </styled.div>,
-        <styled.div>
-          <span>
-            Forgot your password? 
-          </span>
-          <Styled(Link)
-            to="/password-reset"
-          >
-            Reset password
-          </Styled(Link)>
-        </styled.div>,
-      ],
-      "small": true,
-    },
-    "ref": null,
-    "rendered": Array [
-      Object {
-        "instance": null,
-        "key": undefined,
-        "nodeType": "class",
-        "props": Object {
-          "children": "Login",
-        },
-        "ref": null,
-        "rendered": "Login",
-        "type": [Function],
-      },
-      false,
-      Object {
-        "instance": null,
-        "key": undefined,
-        "nodeType": "host",
-        "props": Object {
-          "children": Array [
-            <C
-              component={[Function]}
-              name="username"
-            />,
-            <C
-              component={[Function]}
-              name="password"
-            />,
-            <styled.button
-              primary={true}
-              type="submit"
-            >
-              Login
-            </styled.button>,
-          ],
-          "onSubmit": undefined,
-        },
-        "ref": null,
-        "rendered": Array [
-          Object {
-            "instance": null,
-            "key": undefined,
-            "nodeType": "function",
-            "props": Object {
-              "component": [Function],
-              "name": "username",
-            },
-            "ref": null,
-            "rendered": null,
-            "type": [Function],
-          },
-          Object {
-            "instance": null,
-            "key": undefined,
-            "nodeType": "function",
-            "props": Object {
-              "component": [Function],
-              "name": "password",
-            },
-            "ref": null,
-            "rendered": null,
-            "type": [Function],
-          },
-          Object {
-            "instance": null,
-            "key": undefined,
-            "nodeType": "class",
-            "props": Object {
-              "children": "Login",
-              "primary": true,
-              "type": "submit",
-            },
-            "ref": null,
-            "rendered": "Login",
-            "type": [Function],
-          },
-        ],
-        "type": "form",
-      },
-      Object {
-        "instance": null,
-        "key": undefined,
-        "nodeType": "class",
-        "props": Object {
-          "children": Array [
-            <span>
-              Don't have an account? 
-            </span>,
-            <Styled(Link)
-              to="/signup"
-            >
-              Sign up
-            </Styled(Link)>,
-          ],
-        },
-        "ref": null,
-        "rendered": Array [
-          Object {
-            "instance": null,
-            "key": undefined,
-            "nodeType": "host",
-            "props": Object {
-              "children": "Don't have an account? ",
-            },
-            "ref": null,
-            "rendered": "Don't have an account? ",
-            "type": "span",
-          },
-          Object {
-            "instance": null,
-            "key": undefined,
-            "nodeType": "class",
-            "props": Object {
-              "children": "Sign up",
-              "to": "/signup",
-            },
-            "ref": null,
-            "rendered": "Sign up",
-            "type": [Function],
-          },
-        ],
-        "type": [Function],
-      },
-      Object {
-        "instance": null,
-        "key": undefined,
-        "nodeType": "class",
-        "props": Object {
-          "children": Array [
-            <span>
-              Forgot your password? 
-            </span>,
-            <Styled(Link)
-              to="/password-reset"
-            >
-              Reset password
-            </Styled(Link)>,
-          ],
-        },
-        "ref": null,
-        "rendered": Array [
-          Object {
-            "instance": null,
-            "key": undefined,
-            "nodeType": "host",
-            "props": Object {
-              "children": "Forgot your password? ",
-            },
-            "ref": null,
-            "rendered": "Forgot your password? ",
-            "type": "span",
-          },
-          Object {
-            "instance": null,
-            "key": undefined,
-            "nodeType": "class",
-            "props": Object {
-              "children": "Reset password",
-              "to": "/password-reset",
-            },
-            "ref": null,
-            "rendered": "Reset password",
-            "type": [Function],
-          },
-        ],
-        "type": [Function],
-      },
-    ],
-    "type": [Function],
-  },
-  Symbol(enzyme.__nodes__): Array [
-    Object {
-      "instance": null,
-      "key": undefined,
-      "nodeType": "class",
-      "props": Object {
-        "children": Array [
-          <styled.h1>
-            Login
-          </styled.h1>,
-          false,
-          <form>
-            <C
-              component={[Function]}
-              name="username"
-            />
-            <C
-              component={[Function]}
-              name="password"
-            />
-            <styled.button
-              primary={true}
-              type="submit"
-            >
-              Login
-            </styled.button>
-          </form>,
-          <styled.div>
-            <span>
-              Don't have an account? 
-            </span>
-            <Styled(Link)
-              to="/signup"
-            >
-              Sign up
-            </Styled(Link)>
-          </styled.div>,
-          <styled.div>
-            <span>
-              Forgot your password? 
-            </span>
-            <Styled(Link)
-              to="/password-reset"
-            >
-              Reset password
-            </Styled(Link)>
-          </styled.div>,
-        ],
-        "small": true,
-      },
-      "ref": null,
-      "rendered": Array [
-        Object {
-          "instance": null,
-          "key": undefined,
-          "nodeType": "class",
-          "props": Object {
-            "children": "Login",
-          },
-          "ref": null,
-          "rendered": "Login",
-          "type": [Function],
-        },
-        false,
-        Object {
-          "instance": null,
-          "key": undefined,
-          "nodeType": "host",
-          "props": Object {
-            "children": Array [
-              <C
-                component={[Function]}
-                name="username"
-              />,
-              <C
-                component={[Function]}
-                name="password"
-              />,
-              <styled.button
-                primary={true}
-                type="submit"
-              >
-                Login
-              </styled.button>,
-            ],
-            "onSubmit": undefined,
-          },
-          "ref": null,
-          "rendered": Array [
-            Object {
-              "instance": null,
-              "key": undefined,
-              "nodeType": "function",
-              "props": Object {
-                "component": [Function],
-                "name": "username",
-              },
-              "ref": null,
-              "rendered": null,
-              "type": [Function],
-            },
-            Object {
-              "instance": null,
-              "key": undefined,
-              "nodeType": "function",
-              "props": Object {
-                "component": [Function],
-                "name": "password",
-              },
-              "ref": null,
-              "rendered": null,
-              "type": [Function],
-            },
-            Object {
-              "instance": null,
-              "key": undefined,
-              "nodeType": "class",
-              "props": Object {
-                "children": "Login",
-                "primary": true,
-                "type": "submit",
-              },
-              "ref": null,
-              "rendered": "Login",
-              "type": [Function],
-            },
-          ],
-          "type": "form",
-        },
-        Object {
-          "instance": null,
-          "key": undefined,
-          "nodeType": "class",
-          "props": Object {
-            "children": Array [
-              <span>
-                Don't have an account? 
-              </span>,
-              <Styled(Link)
-                to="/signup"
-              >
-                Sign up
-              </Styled(Link)>,
-            ],
-          },
-          "ref": null,
-          "rendered": Array [
-            Object {
-              "instance": null,
-              "key": undefined,
-              "nodeType": "host",
-              "props": Object {
-                "children": "Don't have an account? ",
-              },
-              "ref": null,
-              "rendered": "Don't have an account? ",
-              "type": "span",
-            },
-            Object {
-              "instance": null,
-              "key": undefined,
-              "nodeType": "class",
-              "props": Object {
-                "children": "Sign up",
-                "to": "/signup",
-              },
-              "ref": null,
-              "rendered": "Sign up",
-              "type": [Function],
-            },
-          ],
-          "type": [Function],
-        },
-        Object {
-          "instance": null,
-          "key": undefined,
-          "nodeType": "class",
-          "props": Object {
-            "children": Array [
-              <span>
-                Forgot your password? 
-              </span>,
-              <Styled(Link)
-                to="/password-reset"
-              >
-                Reset password
-              </Styled(Link)>,
-            ],
-          },
-          "ref": null,
-          "rendered": Array [
-            Object {
-              "instance": null,
-              "key": undefined,
-              "nodeType": "host",
-              "props": Object {
-                "children": "Forgot your password? ",
-              },
-              "ref": null,
-              "rendered": "Forgot your password? ",
-              "type": "span",
-            },
-            Object {
-              "instance": null,
-              "key": undefined,
-              "nodeType": "class",
-              "props": Object {
-                "children": "Reset password",
-                "to": "/password-reset",
-              },
-              "ref": null,
-              "rendered": "Reset password",
-              "type": [Function],
-            },
-          ],
-          "type": [Function],
-        },
-      ],
-      "type": [Function],
-    },
-  ],
-  Symbol(enzyme.__options__): Object {
-    "adapter": ReactSixteenAdapter {
-      "options": Object {
-        "enableComponentDidUpdateOnSetState": true,
-        "lifecycles": Object {
-          "componentDidUpdate": Object {
-            "onSetState": true,
-          },
-          "getDerivedStateFromProps": true,
-          "getSnapshotBeforeUpdate": true,
-          "setState": Object {
-            "skipsComponentDidUpdateOnNullish": true,
-          },
-        },
-      },
-    },
-  },
-}
-`;
diff --git a/packages/pubsweet-component-login/actions.js b/packages/pubsweet-component-login/actions.js
deleted file mode 100644
index e74909742d021bb2f66d46674e968b5a3eed2919..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/actions.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import * as api from 'pubsweet-client/src/helpers/api'
-import {
-  LOGIN_REQUEST,
-  LOGIN_SUCCESS,
-  LOGIN_FAILURE,
-  LOGOUT_SUCCESS,
-  LOGOUT_REQUEST,
-} from 'pubsweet-client/src/actions/types'
-
-import { push } from 'react-router-redux'
-
-// TODO: This will break when rendered on a server
-const localStorage = window.localStorage || undefined
-
-// There are three possible states for our login
-// process and we need actions for each of them
-function loginRequest(credentials) {
-  return {
-    type: LOGIN_REQUEST,
-    credentials,
-  }
-}
-
-function loginSuccess(user) {
-  return {
-    type: LOGIN_SUCCESS,
-    token: user.token,
-    user,
-  }
-}
-
-function loginFailure(message) {
-  return {
-    type: LOGIN_FAILURE,
-    error: message,
-  }
-}
-
-// Calls the API to get a token and
-// dispatches actions along the way
-export function loginUser(credentials, redirectTo, setErrors) {
-  return dispatch => {
-    dispatch(loginRequest(credentials))
-    return api.create('/users/authenticate', credentials).then(
-      user => {
-        localStorage.setItem('token', user.token)
-        dispatch(loginSuccess(user))
-        if (redirectTo) dispatch(push(redirectTo))
-      },
-      err => {
-        setErrors(JSON.parse(err.response).message)
-        dispatch(loginFailure(err))
-      },
-    )
-  }
-}
-
-function logoutRequest() {
-  return {
-    type: LOGOUT_REQUEST,
-    isFetching: true,
-    isAuthenticated: true,
-  }
-}
-
-function logoutSuccess() {
-  return {
-    type: LOGOUT_SUCCESS,
-    isFetching: false,
-    isAuthenticated: false,
-  }
-}
-
-// Logs the user out
-// Since we are using JWTs, we just need to remove the token
-// from localStorage.
-export function logoutUser(redirectTo) {
-  return dispatch => {
-    dispatch(logoutRequest())
-    localStorage.removeItem('token')
-    dispatch(logoutSuccess())
-    if (redirectTo) dispatch(push(redirectTo))
-  }
-}
diff --git a/packages/pubsweet-component-login/graphql/LoginContainer.js b/packages/pubsweet-component-login/graphql/LoginContainer.js
deleted file mode 100644
index 83715d8019cbd8eb24bf086d9b7ebdb733daf80e..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/graphql/LoginContainer.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import { compose } from 'recompose'
-import { withFormik } from 'formik'
-import { graphql } from 'react-apollo'
-
-import mutations from './mutations'
-import Login from '../Login'
-import redirectPath from '../redirect'
-
-const localStorage = window.localStorage || undefined
-
-const handleSubmit = (values, { props, setSubmitting, setErrors }) =>
-  props
-    .loginUser({ variables: { input: values } })
-    .then(({ data, errors }) => {
-      if (!errors) {
-        localStorage.setItem('token', data.loginUser.token)
-        props.history.push(redirectPath({ location: props.location }))
-        setSubmitting(true)
-      }
-    })
-    .catch(e => {
-      if (e.graphQLErrors) {
-        setSubmitting(false)
-        setErrors(e.graphQLErrors[0].message)
-      }
-    })
-
-const enhancedFormik = withFormik({
-  initialValues: {
-    username: '',
-    password: '',
-  },
-  mapPropsToValues: props => ({
-    username: props.username,
-    password: props.password,
-  }),
-  displayName: 'login',
-  handleSubmit,
-})(Login)
-
-export default compose(graphql(mutations.LOGIN_USER, { name: 'loginUser' }))(
-  enhancedFormik,
-)
diff --git a/packages/pubsweet-component-login/graphql/mutations/index.js b/packages/pubsweet-component-login/graphql/mutations/index.js
deleted file mode 100644
index ee44b493da283da233533820994539bc15529084..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/graphql/mutations/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import gql from 'graphql-tag'
-
-const LOGIN_USER = gql`
-  mutation($input: LoginUserInput) {
-    loginUser(input: $input) {
-      token
-    }
-  }
-`
-
-module.exports = {
-  LOGIN_USER,
-}
diff --git a/packages/pubsweet-component-login/index.js b/packages/pubsweet-component-login/index.js
deleted file mode 100644
index 155554e77ad3dfe5c27ec6c2d4b5b623dcb3d135..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = {
-  frontend: {
-    components: [() => require('./LoginContainer')],
-    actions: () => require('./actions'),
-    reducers: () => require('./reducers'),
-  },
-}
diff --git a/packages/pubsweet-component-login/package.json b/packages/pubsweet-component-login/package.json
deleted file mode 100644
index 5f5efb1eead08bc87ed1a89e24fb76ab094a9ffb..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/package.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "name": "pubsweet-component-login",
-  "version": "1.2.0",
-  "description": "Basic login component for PubSweet",
-  "main": "index.js",
-  "author": "Collaborative Knowledge Foundation",
-  "license": "MIT",
-  "dependencies": {
-    "@pubsweet/ui": "^9.0.2",
-    "formik": "1.3.0",
-    "prop-types": "^15.5.10",
-    "react-redux": "^5.0.6",
-    "react-router-dom": "^4.2.2",
-    "react-router-redux": "^5.0.0-alpha.9",
-    "recompose": "^0.26.0"
-  },
-  "peerDependencies": {
-    "pubsweet-client": ">=1.0.0",
-    "react": ">=15"
-  },
-  "repository": {
-    "type": "git",
-    "url": "https://gitlab.coko.foundation/pubsweet/pubsweet",
-    "path": "Login"
-  }
-}
diff --git a/packages/pubsweet-component-login/redirect.js b/packages/pubsweet-component-login/redirect.js
deleted file mode 100644
index 53d58f1e3e4d1fe5a5d53584048d7a389c340887..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/redirect.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { get } from 'lodash'
-import config from 'config'
-
-const allowedRedirect = pathname =>
-  !['/logout', '/login', '/signup'].includes(pathname)
-
-export default ({ location: { state } }) =>
-  state && state.from && allowedRedirect(state.from.pathname)
-    ? state.from.pathname
-    : get(config, 'pubsweet-client.login-redirect', '/')
diff --git a/packages/pubsweet-component-login/reducers.js b/packages/pubsweet-component-login/reducers.js
deleted file mode 100644
index d1f637a01e310491d842ac045d78ac1a0a879527..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/reducers.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import {
-  LOGIN_REQUEST,
-  LOGIN_SUCCESS,
-  LOGIN_FAILURE,
-  LOGOUT_SUCCESS,
-  LOGOUT_REQUEST,
-} from 'pubsweet-client/src/actions/types'
-
-// TODO: This will break when rendered on a server
-const localStorage = window.localStorage || undefined
-
-export default function userLogin(
-  state = {
-    isFetching: false,
-    isAuthenticated: false,
-    token: localStorage.getItem('token'),
-  },
-  action,
-) {
-  switch (action.type) {
-    case LOGIN_REQUEST:
-      return {
-        ...state,
-        isFetching: true,
-        isAuthenticated: false,
-        username: action.credentials.username,
-      }
-    case LOGIN_SUCCESS:
-      return {
-        ...state,
-        isFetching: false,
-        isAuthenticated: true,
-        user: action.user,
-        token: action.token,
-      }
-    case LOGIN_FAILURE:
-      return {
-        ...state,
-        isFetching: false,
-        isAuthenticated: false,
-        error: action.error,
-      }
-    case LOGOUT_SUCCESS:
-      return {
-        ...state,
-        isFetching: false,
-        isAuthenticated: false,
-      }
-    case LOGOUT_REQUEST:
-      return {
-        ...state,
-        isFetching: false,
-        isAuthenticated: false,
-      }
-    default:
-      return state
-  }
-}
diff --git a/packages/pubsweet-component-login/reducers.test.js b/packages/pubsweet-component-login/reducers.test.js
deleted file mode 100644
index a3733cf35bff6ae0ac033e66fdec726ce557d509..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/reducers.test.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import {
-  LOGIN_FAILURE,
-  LOGIN_REQUEST,
-  LOGIN_SUCCESS,
-  LOGOUT_REQUEST,
-  LOGOUT_SUCCESS,
-} from 'pubsweet-client/src/actions/types'
-
-jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => undefined)
-
-const reducer = require('./reducers').default
-
-describe('Login reducer', () => {
-  it('returns initial state', () => {
-    const newState = reducer(undefined, {})
-    expect(newState).toEqual({
-      isFetching: false,
-      isAuthenticated: false,
-      token: undefined,
-    })
-  })
-
-  it('stores username on login request', () => {
-    const action = {
-      type: LOGIN_REQUEST,
-      credentials: { username: 'milo minderbinder' },
-    }
-    const newState = reducer(undefined, action)
-    expect(newState).toMatchObject({
-      isFetching: true,
-      username: 'milo minderbinder',
-    })
-  })
-
-  it('stores user and token on login success', () => {
-    const action = {
-      type: LOGIN_SUCCESS,
-      user: { username: 'nurse duckett' },
-      token: 't0k3n',
-    }
-    const newState = reducer(undefined, action)
-    expect(newState).toMatchObject({
-      isAuthenticated: true,
-      user: action.user,
-      token: action.token,
-    })
-  })
-
-  it('stores error on login failure', () => {
-    const action = { type: LOGIN_FAILURE, error: new Error('Flies in eyes') }
-    const newState = reducer({ isAuthenticated: true }, action)
-    expect(newState).toMatchObject({
-      isAuthenticated: false,
-      error: action.error,
-    })
-  })
-
-  it('logs out on request', () => {
-    const action = { type: LOGOUT_REQUEST }
-    const newState = reducer({ isAuthenticated: true }, action)
-    expect(newState).toMatchObject({
-      isAuthenticated: false,
-    })
-  })
-
-  it('logs out on logout success', () => {
-    const action = { type: LOGOUT_SUCCESS }
-    const newState = reducer({ isAuthenticated: true }, action)
-    expect(newState).toMatchObject({
-      isAuthenticated: false,
-    })
-  })
-})
diff --git a/packages/pubsweet-component-login/types.js b/packages/pubsweet-component-login/types.js
deleted file mode 100644
index 3b66211c7193bd42a3e8aa9f3c63b46a57172a43..0000000000000000000000000000000000000000
--- a/packages/pubsweet-component-login/types.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export const LOGIN_REQUEST = 'LOGIN_REQUEST'
-export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
-export const LOGIN_FAILURE = 'LOGIN_FAILURE'
-
-export const LOGOUT_REQUEST = 'LOGOUT_REQUEST'
-export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'
-export const LOGOUT_FAILURE = 'LOGOUT_FAILURE'
diff --git a/packages/styleguide/src/webpack-config.js b/packages/styleguide/src/webpack-config.js
index b5132f279680d6c8bc932dfa0a0e460e91961b35..29f13854e2cb65c8674d4f916868ec908960429f 100644
--- a/packages/styleguide/src/webpack-config.js
+++ b/packages/styleguide/src/webpack-config.js
@@ -4,8 +4,6 @@ process.env.NODE_ENV = 'development'
 const webpack = require('webpack')
 const path = require('path')
 
-// const nodeExternals = require('webpack-node-externals')
-
 module.exports = dir => {
   const include = [
     path.join(dir, 'src'),
diff --git a/packages/xpub-faraday/app/FaradayApp.js b/packages/xpub-faraday/app/FaradayApp.js
index 565963319b75d0616677c2bb2a53cbb96b7e6eb0..6cea06bca888a569f619f63c23a46f7db80e930d 100644
--- a/packages/xpub-faraday/app/FaradayApp.js
+++ b/packages/xpub-faraday/app/FaradayApp.js
@@ -3,10 +3,10 @@ import { get } from 'lodash'
 import { connect } from 'react-redux'
 import styled, { css } from 'styled-components'
 import { th } from '@pubsweet/ui-toolkit'
-import { actions } from 'pubsweet-client'
 import { withJournal } from 'xpub-journal'
 import { withRouter } from 'react-router-dom'
 import { compose, withHandlers } from 'recompose'
+import { logoutUser } from 'pubsweet-component-login/actions'
 import {
   Logo,
   AppBar,
@@ -70,7 +70,7 @@ export default compose(
       canCreateDraft: !userNotConfirmed(state),
     }),
     (dispatch, { history }) => ({
-      logout: () => dispatch(actions.logoutUser()),
+      logout: () => dispatch(logoutUser()),
       createDraft: () => dispatch(createDraftSubmission(history)),
     }),
   ),
diff --git a/packages/xpub-faraday/app/config/journal/recommendations.js b/packages/xpub-faraday/app/config/journal/recommendations.js
index 736effd199a5801309f3fe4e1a4f622c88cc33ad..99b50aa2d2eba35e06e7a127dd19a6631eed2b69 100644
--- a/packages/xpub-faraday/app/config/journal/recommendations.js
+++ b/packages/xpub-faraday/app/config/journal/recommendations.js
@@ -15,4 +15,8 @@ module.exports = [
     value: 'reject',
     label: 'Reject',
   },
+  {
+    value: 'revision',
+    label: 'Revision',
+  },
 ]
diff --git a/packages/xpub-faraday/app/config/journal/statuses.js b/packages/xpub-faraday/app/config/journal/statuses.js
index 7170160ee9bea0077972884445c298422eb1e461..daa87a04be839c0aadd01a46c595705604b966aa 100644
--- a/packages/xpub-faraday/app/config/journal/statuses.js
+++ b/packages/xpub-faraday/app/config/journal/statuses.js
@@ -7,7 +7,7 @@ module.exports = {
     },
     admin: {
       label: 'Complete Submission',
-      needsAttention: true,
+      needsAttention: false,
     },
   },
   technicalChecks: {
diff --git a/packages/xpub-faraday/config/authsome-helpers.js b/packages/xpub-faraday/config/authsome-helpers.js
index 08e583c721fa6103de2f4729e0f2e64a348567a7..39f753c539a754d22829a00a0397277236eb40b1 100644
--- a/packages/xpub-faraday/config/authsome-helpers.js
+++ b/packages/xpub-faraday/config/authsome-helpers.js
@@ -4,8 +4,6 @@ const { omit, get, last, chain } = require('lodash')
 
 const statuses = config.get('statuses')
 
-const keysToOmit = [`email`, `id`]
-const authorCannotViewHENameStatuses = ['heInvited']
 const authorAllowedStatuses = ['revisionRequested', 'rejected', 'accepted']
 
 const getTeamsByPermissions = async (
@@ -72,23 +70,57 @@ const filterAuthorRecommendations = (recommendations, status, isLast) => {
   return []
 }
 
+const filterRecommendationsFromLastVersion = (recommendations, user) =>
+  recommendations
+    .filter(
+      r =>
+        r.userId === user.id || r.recommendationType === 'editorRecommendation',
+    )
+    .map(r => ({
+      ...r,
+      comments: r.comments.filter(c => c.public === true),
+    }))
+
+const filterRecommendationsFromOlderVersions = (recommendations, user) => {
+  const ownRecommendation = recommendations.find(r => r.userId === user.id)
+  if (ownRecommendation) {
+    return recommendations
+      .filter(
+        r => r.submittedOn || r.recommendationType === 'editorRecommendation',
+      )
+      .map(
+        r =>
+          r.userId !== ownRecommendation.userId
+            ? { ...r, comments: r.comments.filter(c => c.public === true) }
+            : { ...r },
+      )
+  }
+  return []
+}
+
 const stripeCollectionByRole = ({ collection = {}, role = '' }) => {
   if (role === 'author') {
-    const { handlingEditor } = collection
-
-    if (authorCannotViewHENameStatuses.includes(collection.status)) {
+    if (collection.status === 'heInvited') {
       return {
         ...collection,
-        handlingEditor: handlingEditor &&
-          !handlingEditor.isAccepted && {
-            ...omit(handlingEditor, keysToOmit),
-            name: 'Assigned',
-          },
+        handlingEditor: {
+          name: 'Assigned',
+        },
+      }
+    }
+    if (collection.status === 'submitted') {
+      return {
+        ...collection,
+        handlingEditor: {
+          name: 'Unassigned',
+        },
       }
     }
   }
+
   return collection
 }
+
 const stripeFragmentByRole = ({
   fragment = {},
   role = '',
@@ -96,7 +128,8 @@ const stripeFragmentByRole = ({
   user = {},
   isLast = false,
 }) => {
-  const { recommendations, files, authors } = fragment
+  const { files, authors, recommendations = [] } = fragment
+  let recommendationsFromFragment = []
   switch (role) {
     case 'author':
       return {
@@ -106,22 +139,22 @@ const stripeFragmentByRole = ({
           : [],
       }
     case 'reviewer':
+      if (isLast) {
+        recommendationsFromFragment = filterRecommendationsFromLastVersion(
+          recommendations,
+          user,
+        )
+      } else {
+        recommendationsFromFragment = filterRecommendationsFromOlderVersions(
+          recommendations,
+          user,
+        )
+      }
       return {
         ...fragment,
         files: omit(files, ['coverLetter']),
         authors: authors.map(a => omit(a, ['email'])),
-        recommendations: recommendations
-          ? recommendations
-              .filter(
-                r =>
-                  r.userId === user.id ||
-                  r.recommendationType === 'editorRecommendation',
-              )
-              .map(r => ({
-                ...r,
-                comments: r.comments.filter(c => c.public === true),
-              }))
-          : [],
+        recommendations: recommendationsFromFragment,
       }
     case 'handlingEditor':
       return {
@@ -175,10 +208,13 @@ const getCollections = async ({ user, models }) => {
         } else {
           fragment = await models.Fragment.find(userPermission.objectId)
           collection = await models.Collection.find(fragment.collectionId)
+          const latestFragmentId =
+            collection.fragments[collection.fragments.length - 1]
           collection.currentVersion = stripeFragmentByRole({
             fragment,
             role: userPermission.role,
             user,
+            isLast: latestFragmentId === fragment.id,
           })
         }
       } catch (e) {
diff --git a/packages/xpub-faraday/config/authsome-mode.js b/packages/xpub-faraday/config/authsome-mode.js
index fe678f981257b9e3a67647f062f3f15ef0f87f31..df9ecde417ba4e3a4b01692629cbb64c9027bf47 100644
--- a/packages/xpub-faraday/config/authsome-mode.js
+++ b/packages/xpub-faraday/config/authsome-mode.js
@@ -93,8 +93,13 @@ async function applyAuthenticatedUserPolicy(user, operation, object, context) {
             FragmentModel: context.models.Fragment,
           })
 
+          const parsedCollection = helpers.stripeCollectionByRole({
+            collection,
+            role,
+          })
+
           return {
-            ...collection,
+            ...parsedCollection,
             ...parsedStatuses,
             fragments:
               role !== 'reviewer'
@@ -230,7 +235,10 @@ async function applyAuthenticatedUserPolicy(user, operation, object, context) {
       })
     }
 
-    if (get(object, 'type') === 'user' && get(object, 'id') === user.id) {
+    if (
+      get(object, 'current.type') === 'user' &&
+      get(object, 'current.id') === user.id
+    ) {
       return {
         filter: body =>
           pick(body, [
diff --git a/packages/xpub-faraday/config/components.json b/packages/xpub-faraday/config/components.json
index aebff0aa7d8026a5b1ac2ca58f598261ba3c5d8d..3083694230c19476ca18ffaac0a8d84ed7bf67e5 100644
--- a/packages/xpub-faraday/config/components.json
+++ b/packages/xpub-faraday/config/components.json
@@ -1,5 +1,4 @@
 [
-  "pubsweet-component-login",
   "pubsweet-component-wizard",
   "pubsweet-component-modal",
   "pubsweet-components-faraday",
diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js
index 295d5346f74489e99d806bd4f79ad97215c6f368..13eed68fde3e75c0cea41cde6d77d128783590d3 100644
--- a/packages/xpub-faraday/config/default.js
+++ b/packages/xpub-faraday/config/default.js
@@ -148,4 +148,7 @@ module.exports = {
       editor: 'editorRecommendation',
     },
   },
+  passwordStrengthRegex: new RegExp(
+    '^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&,.?;\'*><)([}{}":`~+=_-\\|/])(?=.{6,128})',
+  ),
 }
diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js
index 8afc3c8a092b585a8e5499802e9b33fc993d550e..562ea5242cef0bcd0c3268496b72e70be656cf4a 100644
--- a/packages/xpub-faraday/config/validations.js
+++ b/packages/xpub-faraday/config/validations.js
@@ -126,6 +126,7 @@ module.exports = {
             'reject',
             'publish',
             'revise',
+            'revision',
             'major',
             'minor',
             'return-to-handling-editor',
diff --git a/packages/xpub-faraday/tests/config/authsome-helpers.test.js b/packages/xpub-faraday/tests/config/authsome-helpers.test.js
index c5634168fdf6ab29134874c7c3fa9ee96183b2d0..10571579af13e2834aa58b9ca6a4f7fe72971397 100644
--- a/packages/xpub-faraday/tests/config/authsome-helpers.test.js
+++ b/packages/xpub-faraday/tests/config/authsome-helpers.test.js
@@ -19,7 +19,7 @@ describe('Authsome Helpers', () => {
       expect(newCollection).toBeTruthy()
     })
 
-    it('Author should not see HE name on dashboard before HE accepts invitation', () => {
+    it('Author should see Assigned instead of HE name on dashboard before HE accepts invitation', () => {
       const { collection } = testFixtures.collections
       collection.handlingEditor = {
         ...collection.handlingEditor,
@@ -86,6 +86,19 @@ describe('Authsome Helpers', () => {
       const { handlingEditor = {} } = newCollection
       expect(handlingEditor.name).not.toEqual('Assigned')
     })
+
+    it('Author should see Unassigned insted of HE name before HE is invited', () => {
+      const { collection } = testFixtures.collections
+      collection.status = 'submitted'
+      const role = 'author'
+      const newCollection = ah.stripeCollectionByRole({
+        collection,
+        role,
+      })
+      const { handlingEditor = {} } = newCollection
+      expect(handlingEditor.name).toEqual('Unassigned')
+    })
+
     it('stripeCollection - returns if collection does not have HE', () => {
       const { collection } = testFixtures.collections
       delete collection.handlingEditor
@@ -116,7 +129,7 @@ describe('Authsome Helpers', () => {
       const { files = {} } = result
       expect(files.coverLetter).toBeFalsy()
     })
-    it('reviewer should not see private comments', () => {
+    it('reviewer should not see private comments on the last version of the manuscript', () => {
       const { fragment } = testFixtures.fragments
       fragment.recommendations = [
         {
@@ -132,13 +145,59 @@ describe('Authsome Helpers', () => {
           ],
         },
       ]
-      const result = ah.stripeFragmentByRole({ fragment, role: 'reviewer' })
+      const result = ah.stripeFragmentByRole({
+        fragment,
+        role: 'reviewer',
+        isLast: true,
+      })
       const { recommendations } = result
       expect(recommendations).toHaveLength(1)
       expect(recommendations[0].comments).toHaveLength(1)
       expect(recommendations[0].comments[0].public).toEqual(true)
     })
 
+    it('reviewer should see other reviewers recommendations on previous version if he submitted a review on that fragment', () => {
+      const { fragment } = testFixtures.fragments
+      const { answerReviewer } = testFixtures.users
+
+      const result = ah.stripeFragmentByRole({
+        fragment,
+        role: 'reviewer',
+        isLast: false,
+        user: answerReviewer,
+      })
+      const { recommendations } = result
+      expect(recommendations).toHaveLength(8)
+    })
+
+    it('reviewer should not see other reviewers recommendations on latest fragment', () => {
+      const { fragment } = testFixtures.fragments
+      const { answerReviewer } = testFixtures.users
+
+      const result = ah.stripeFragmentByRole({
+        fragment,
+        role: 'reviewer',
+        isLast: true,
+        user: answerReviewer,
+      })
+      const { recommendations } = result
+      expect(recommendations).toHaveLength(7)
+    })
+
+    it('reviewer should not see any reviewer recommendation on previous version if he did not submit a review on that fragment', () => {
+      const { fragment } = testFixtures.fragments
+      const { inactiveReviewer } = testFixtures.users
+
+      const result = ah.stripeFragmentByRole({
+        fragment,
+        role: 'reviewer',
+        isLast: false,
+        user: inactiveReviewer,
+      })
+      const { recommendations } = result
+      expect(recommendations).toHaveLength(0)
+    })
+
     it('author should not see recommendations if a decision has not been made', () => {
       const { fragment } = testFixtures.fragments
       fragment.recommendations = [
diff --git a/yarn.lock b/yarn.lock
index bc890f487d57965d8cc122f57628532144c91457..0800a7fa664a6c4a705bd6b8d6c6648f94854b68 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10842,6 +10842,19 @@ pubsweet-client@^7.0.0:
     styled-normalize "^8.0.4"
     subscriptions-transport-ws "^0.9.12"
 
+pubsweet-component-login@^1.2.0:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/pubsweet-component-login/-/pubsweet-component-login-1.2.3.tgz#4e6046c793c53db806a3ac3b84947c23257da27d"
+  integrity sha512-SzyuDG2Kk4Wrd7dG5cd2dD3pMhExWo/O891cMCqTYRBXJ/Qg2RG7g8/8Dbsolzg45phtQIjCjDRBDLDQK1OMfg==
+  dependencies:
+    "@pubsweet/ui" "^9.0.2"
+    formik "1.3.0"
+    prop-types "^15.5.10"
+    react-redux "^5.0.6"
+    react-router-dom "^4.2.2"
+    react-router-redux "^5.0.0-alpha.9"
+    recompose "^0.26.0"
+
 pubsweet-server@^10.0.0, pubsweet-server@^10.1.3:
   version "10.1.3"
   resolved "https://registry.yarnpkg.com/pubsweet-server/-/pubsweet-server-10.1.3.tgz#f3286608ecc2f0280eb459412ff134897ddfaa37"