diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js
index ae7b54ace427f4e6af74297ad94dfcbefe76496a..5acb64f2ceef380248c7ff7a478ede58e9cc60a6 100644
--- a/packages/component-faraday-selectors/src/index.js
+++ b/packages/component-faraday-selectors/src/index.js
@@ -136,6 +136,17 @@ export const userNotConfirmed = ({ currentUser }) =>
   !currentUserIs({ currentUser }, 'staff') &&
   !get(currentUser, 'user.isConfirmed')
 
+export const pendingReviewerInvitation = (state, fragmentId) =>
+  chain(state)
+    .get(`fragments.${fragmentId}.invitations`, [])
+    .find(
+      inv =>
+        inv.userId === get(state, 'currentUser.user.id', '') &&
+        !inv.hasAnswer &&
+        inv.role === 'reviewer',
+    )
+    .value()
+
 export const currentUserIsReviewer = (state, fragmentId) => {
   const currentUser = selectCurrentUser(state)
   const invitations = get(state, `fragments.${fragmentId}.invitations`, [])
diff --git a/packages/component-faraday-ui/src/AuthorTagList.js b/packages/component-faraday-ui/src/AuthorTagList.js
index ff585788d514a0a504b2a16ec7449601e3635e05..2d8268460f6b195f97862ad37951c025ae6915b1 100644
--- a/packages/component-faraday-ui/src/AuthorTagList.js
+++ b/packages/component-faraday-ui/src/AuthorTagList.js
@@ -45,7 +45,7 @@ const AuthorTagList = ({
   authors = [],
   affiliationList,
   separator = `, `,
-  authorKey = 'email',
+  authorKey = 'id',
   withTooltip = false,
   withAffiliations = false,
   showAffiliation = false,
diff --git a/packages/component-faraday-ui/src/manuscriptDetails/HandlingEditorAnswer.js b/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.js
similarity index 58%
rename from packages/component-faraday-ui/src/manuscriptDetails/HandlingEditorAnswer.js
rename to packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.js
index 53f4ca16822becf9cc9eeb0927ad6021ec45a9aa..b0f85ce73dad5312410b2161fadd3469497f691e 100644
--- a/packages/component-faraday-ui/src/manuscriptDetails/HandlingEditorAnswer.js
+++ b/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import { reduxForm } from 'redux-form'
-import { get, has, capitalize } from 'lodash'
 import { required } from 'xpub-validators'
-import { compose, withHandlers, withProps } from 'recompose'
+import { get, has, capitalize } from 'lodash'
+import { compose, withProps } from 'recompose'
 import { Button, RadioGroup, ValidatedField } from '@pubsweet/ui'
+import { withModal } from 'pubsweet-component-modal/src/components'
 
 import {
   Row,
@@ -11,7 +12,7 @@ import {
   Text,
   Label,
   Textarea,
-  OpenModal,
+  MultiAction,
   ContextualBox,
   RowOverrideAlert,
   withFetching,
@@ -22,28 +23,28 @@ const options = [
   { label: 'Decline', value: 'decline' },
 ]
 
-const HandlingEditorAnswer = ({
+const ResponseToInvitation = ({
+  label,
+  title,
   toggle,
   expanded,
-  decision,
   isFetching,
   handleSubmit,
   onSubmitForm,
   shouldShowComments,
+  buttonLabel = 'RESPOND TO INVITATION',
 }) => (
   <ContextualBox
     expanded={expanded}
     highlight
-    label="Respond to Editorial Invitation"
+    label={title}
     mb={2}
     scrollIntoView
     toggle={toggle}
   >
     <RowOverrideAlert justify="flex-start" ml={1} mt={1}>
       <Item vertical>
-        <Label required>
-          Do you agree to be the handling editor for this manuscript?
-        </Label>
+        <Label required>{label}</Label>
         <ValidatedField
           component={input => (
             <RadioGroup inline name="decision" options={options} {...input} />
@@ -69,44 +70,34 @@ const HandlingEditorAnswer = ({
     )}
 
     <Row justify="flex-end" mb={1} pr={1}>
-      <OpenModal
-        cancelText="Close"
-        confirmText={decision}
-        isFetching={isFetching}
-        onConfirm={modalProps => handleSubmit()(modalProps)}
-        title={`${decision} this invitation?`}
-      >
-        {showModal => (
-          <Button onClick={onSubmitForm(showModal)} primary size="medium">
-            RESPOND TO INVITATION
-          </Button>
-        )}
-      </OpenModal>
+      <Button onClick={handleSubmit} primary size="medium">
+        {buttonLabel}
+      </Button>
     </Row>
   </ContextualBox>
 )
 
 export default compose(
   withFetching,
-  withProps(({ formValues }) => ({
+  withModal(({ isFetching, modalKey }) => ({
+    modalKey,
+    isFetching,
+    modalComponent: MultiAction,
+  })),
+  withProps(({ formValues = {}, commentsOn }) => ({
     disabled: !has(formValues, 'decision'),
-    decision: capitalize(get(formValues, 'decision')),
-    shouldShowComments: get(formValues, 'decision', 'agree') === 'decline',
+    shouldShowComments: get(formValues, 'decision', 'agree') === commentsOn,
   })),
   reduxForm({
-    form: 'he-answer-invitation',
+    form: 'answer-invitation',
     destroyOnUnmount: false,
-    onSubmit: (values, dispatch, { onResponse, setFetching }) => modalProps => {
-      onResponse(values, { ...modalProps, setFetching })
-    },
-  }),
-  withHandlers({
-    onSubmitForm: ({ disabled, handleSubmit }) => showModal => () => {
-      if (!disabled) {
-        showModal()
-      } else {
-        handleSubmit()
-      }
+    onSubmit: (values, dispatch, { showModal, onResponse, setFetching }) => {
+      showModal({
+        title: `${capitalize(values.decision)} this invitation?`,
+        onConfirm: modalProps => {
+          onResponse(values, { ...modalProps, setFetching })
+        },
+      })
     },
   }),
-)(HandlingEditorAnswer)
+)(ResponseToInvitation)
diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.md b/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.md
new file mode 100644
index 0000000000000000000000000000000000000000..e7f3a21c9d5f65814365bd1e87054d925a0fb775
--- /dev/null
+++ b/packages/component-faraday-ui/src/manuscriptDetails/ResponseToInvitation.md
@@ -0,0 +1,42 @@
+A Handling Editor response to an invitation.
+
+```js
+const formValues = {
+  decision: 'accept',
+}
+;<RemoteOpener>
+  {({ toggle, expanded }) => (
+    <ResponseToInvitation
+      commentsOn="decline"
+      expanded={expanded}
+      label="Do you agree to be the handling editor for this manuscript?"
+      formValues={formValues}
+      onResponse={(values, { setFetching }) => {
+        console.log('on response: ', values)
+        setFetching(true)
+      }}
+      title="Respond to Editorial Invitation"
+      toggle={toggle}
+    />
+  )}
+</RemoteOpener>
+```
+
+A Reviewer response to an invitation.
+
+```js
+<RemoteOpener>
+  {({ toggle, expanded }) => (
+    <ResponseToInvitation
+      expanded={expanded}
+      label="Do you agree to review this manuscript?"
+      onResponse={(values, { setFetching }) => {
+        console.log('on response: ', values)
+        setFetching(true)
+      }}
+      title="Respond to Invitation to Review"
+      toggle={toggle}
+    />
+  )}
+</RemoteOpener>
+```
diff --git a/packages/component-faraday-ui/src/manuscriptDetails/index.js b/packages/component-faraday-ui/src/manuscriptDetails/index.js
index f19644c15e01a55ec224789fa52896398f7a4afd..c32284d507c22c2d5d4f105916caefc456ef3ea4 100644
--- a/packages/component-faraday-ui/src/manuscriptDetails/index.js
+++ b/packages/component-faraday-ui/src/manuscriptDetails/index.js
@@ -1,4 +1,3 @@
-export { default as HandlingEditorAnswer } from './HandlingEditorAnswer'
 export { default as ManuscriptDetailsTop } from './ManuscriptDetailsTop'
 export { default as ManuscriptVersion } from './ManuscriptVersion'
 export { default as ManuscriptHeader } from './ManuscriptHeader'
@@ -7,3 +6,4 @@ export { default as ManuscriptFileList } from './ManuscriptFileList'
 export { default as ManuscriptFileSection } from './ManuscriptFileSection'
 export { default as ManuscriptAssignHE } from './ManuscriptAssignHE'
 export { default as ManuscriptEicDecision } from './ManuscriptEicDecision'
+export { default as ResponseToInvitation } from './ResponseToInvitation'
diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js
index d5dc9fe1283200362165b066eb17153234ec756b..e33c34c50a599b8b990deb66f3b006de864023c7 100644
--- a/packages/component-manuscript/src/components/ManuscriptLayout.js
+++ b/packages/component-manuscript/src/components/ManuscriptLayout.js
@@ -7,9 +7,9 @@ import {
   ManuscriptHeader,
   ManuscriptAssignHE,
   ManuscriptMetadata,
-  HandlingEditorAnswer,
   ManuscriptDetailsTop,
   ManuscriptEicDecision,
+  ResponseToInvitation,
   paddingHelper,
 } from 'pubsweet-component-faraday-ui'
 
@@ -50,6 +50,9 @@ const ManuscriptLayout = ({
   invitationsWithReviewers,
   onResendReviewerInvite,
   onRevokeReviewerInvite,
+  toggleReviewerResponse,
+  reviewerResponseExpanded,
+  onReviewerResponse,
 }) => (
   <Root pb={1}>
     {!isEmpty(collection) && !isEmpty(fragment) ? (
@@ -82,14 +85,27 @@ const ManuscriptLayout = ({
         />
 
         {get(currentUser, 'isInvitedHE', false) && (
-          <HandlingEditorAnswer
+          <ResponseToInvitation
+            commentsOn="decline"
             expanded={heResponseExpanded}
-            formValues={formValues.heInvitation}
+            formValues={formValues.responseToInvitation}
+            label="Do you agree to be the handling editor for this manuscript?"
             onResponse={onHEResponse}
+            title="Respond to Editorial Invitation"
             toggle={toggleHEResponse}
           />
         )}
 
+        {get(currentUser, 'isInvitedToReview', false) && (
+          <ResponseToInvitation
+            expanded={reviewerResponseExpanded}
+            label="Do you agree to review this manuscript?"
+            onResponse={onReviewerResponse}
+            title="Respond to Invitation to Review"
+            toggle={toggleReviewerResponse}
+          />
+        )}
+
         <ManuscriptAssignHE
           assignHE={assignHE}
           currentUser={currentUser}
diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js
index b928e99ebce45ed1993264ed16394029992f3f6b..655284ee3e9bbff207e7c7b37c3051e1561941f5 100644
--- a/packages/component-manuscript/src/components/ManuscriptPage.js
+++ b/packages/component-manuscript/src/components/ManuscriptPage.js
@@ -43,6 +43,7 @@ import {
   currentUserIsReviewer,
   canMakeRecommendation,
   parseCollectionDetails,
+  pendingReviewerInvitation,
   canOverrideTechnicalChecks,
   getInvitationsWithReviewersForFragment,
 } from 'pubsweet-component-faraday-selectors'
@@ -85,6 +86,10 @@ export default compose(
         selectCollection(state, match.params.project),
       ),
       pendingHEInvitation: pendingHEInvitation(state, match.params.project),
+      pendingReviewerInvitation: pendingReviewerInvitation(
+        state,
+        match.params.version,
+      ),
       editorialRecommendations: selectEditorialRecommendations(
         state,
         match.params.version,
@@ -94,7 +99,6 @@ export default compose(
       replace,
       getSignedUrl,
       clearCustomError,
-      reviewerDecision,
       assignHandlingEditor,
       createRecommendation,
       revokeHandlingEditor,
@@ -107,15 +111,23 @@ export default compose(
   connect(
     (
       state,
-      { pendingHEInvitation, currentUser, match, collection, fragment },
+      {
+        match,
+        fragment,
+        collection,
+        currentUser,
+        pendingHEInvitation,
+        pendingReviewerInvitation,
+      },
     ) => ({
       currentUser: {
         ...currentUser,
         token: getUserToken(state),
-        isEIC: currentUserIs(state, 'adminEiC'),
         isHE: currentUserIs(state, 'isHE'),
+        isEIC: currentUserIs(state, 'adminEiC'),
         isReviewer: currentUserIsReviewer(state),
         isInvitedHE: !isUndefined(pendingHEInvitation),
+        isInvitedToReview: !isUndefined(pendingReviewerInvitation),
         permissions: {
           canAssignHE: canAssignHE(state, match.params.project),
           canInviteReviewers: canInviteReviewers(state, collection),
@@ -136,7 +148,7 @@ export default compose(
       },
       formValues: {
         eicDecision: getFormValues('eic-decision')(state),
-        heInvitation: getFormValues('he-answer-invitation')(state),
+        responseToInvitation: getFormValues('answer-invitation')(state),
       },
       invitationsWithReviewers: getInvitationsWithReviewersForFragment(
         state,
@@ -251,6 +263,35 @@ export default compose(
           setModalError('Something went wrong...')
         })
     },
+    onReviewerResponse: ({
+      history,
+      fragment,
+      collection,
+      fetchUpdatedCollection,
+      pendingReviewerInvitation,
+    }) => (values, { hideModal, setModalError, setFetching }) => {
+      const isAccepted = get(values, 'decision', 'decline') === 'accept'
+      setFetching(true)
+      reviewerDecision({
+        agree: isAccepted,
+        fragmentId: fragment.id,
+        collectionId: collection.id,
+        invitationId: pendingReviewerInvitation.id,
+      })
+        .then(() => {
+          setFetching(false)
+          hideModal()
+          if (isAccepted) {
+            fetchUpdatedCollection()
+          } else {
+            history.replace('/')
+          }
+        })
+        .catch(() => {
+          setFetching(false)
+          setModalError('Something went wrong...')
+        })
+    },
     onInviteReviewer: ({ collection, fragment, fetchUpdatedCollection }) => (
       values,
       { hideModal, setModalError, setFetching },
@@ -325,6 +366,10 @@ export default compose(
     toggleHEResponse: toggle,
     heResponseExpanded: expanded,
   })),
+  fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({
+    toggleReviewerResponse: toggle,
+    reviewerResponseExpanded: expanded,
+  })),
   lifecycle({
     componentDidMount() {
       const {
@@ -332,13 +377,11 @@ export default compose(
         replace,
         history,
         location,
-        getFragment,
-        getCollection,
-        reviewerDecision,
         setEditorInChief,
         clearCustomError,
         hasManuscriptFailure,
-        currentUser: { isInvitedHE },
+        fetchUpdatedCollection,
+        currentUser: { isInvitedHE, isInvitedToReview },
       } = this.props
       if (hasManuscriptFailure) {
         history.push('/not-found')
@@ -350,11 +393,8 @@ export default compose(
       const { agree, invitationId } = parseSearchParams(location.search)
       if (agree === 'true') {
         replace(location.pathname)
-        reviewerDecision(invitationId, collectionId, fragmentId, true)
-          .then(() => {
-            getCollection({ id: collectionId })
-            getFragment({ id: collectionId }, { id: fragmentId })
-          })
+        reviewerDecision({ invitationId, collectionId, fragmentId })
+          .then(fetchUpdatedCollection)
           .catch(redirectToError(replace))
       }
 
@@ -365,6 +405,10 @@ export default compose(
       if (isInvitedHE) {
         this.props.toggleHEResponse()
       }
+
+      if (isInvitedToReview) {
+        this.props.toggleReviewerResponse()
+      }
     },
   }),
   withProps(({ fragment }) => ({
diff --git a/packages/component-manuscript/src/redux/editors.js b/packages/component-manuscript/src/redux/editors.js
index 794f4266b2b07ebe31d66f3300895fb7c54596c8..d5f7a8e70c2eee905bc2c7d77999362b816bdf80 100644
--- a/packages/component-manuscript/src/redux/editors.js
+++ b/packages/component-manuscript/src/redux/editors.js
@@ -84,10 +84,10 @@ export const revokeHandlingEditor = ({
 }
 
 export const handlingEditorDecision = ({
-  invitationId,
-  collectionId,
-  isAccepted,
   reason,
+  isAccepted,
+  collectionId,
+  invitationId,
 }) =>
   update(`/collections/${collectionId}/invitations/${invitationId}`, {
     isAccepted,
diff --git a/packages/components-faraday/src/redux/reviewers.js b/packages/components-faraday/src/redux/reviewers.js
index 204911271f2ae06fc53298afd4bbe7912705a493..fdafed5c33010c2e569e9efc7bb830f8e9139300 100644
--- a/packages/components-faraday/src/redux/reviewers.js
+++ b/packages/components-faraday/src/redux/reviewers.js
@@ -127,29 +127,18 @@ export const revokeReviewer = ({ fragmentId, collectionId, invitationId }) =>
 // #endregion
 
 // #region Actions - decision
-export const reviewerDecision = (
-  invitationId,
-  collectionId,
+export const reviewerDecision = ({
   fragmentId,
   agree = true,
-) => dispatch => {
-  dispatch(reviewerDecisionRequest())
-  return update(
+  collectionId,
+  invitationId,
+}) =>
+  update(
     `/collections/${collectionId}/fragments/${fragmentId}/invitations/${invitationId}`,
     {
       isAccepted: agree,
     },
-  ).then(
-    res => {
-      dispatch(reviewerDecisionSuccess())
-      return res
-    },
-    err => {
-      dispatch(reviewerDecisionError(err.message))
-      throw err
-    },
   )
-}
 
 export const reviewerDecline = (
   invitationId,