diff --git a/packages/component-manuscript/src/components/Files.js b/packages/component-manuscript/src/components/Files.js
index e48eab69bd603a2d03a5daab1761ed6bbc83116f..100f0563445db1c8838b207f0c91cf81f1893fa0 100644
--- a/packages/component-manuscript/src/components/Files.js
+++ b/packages/component-manuscript/src/components/Files.js
@@ -1,46 +1,8 @@
 import React, { Fragment } from 'react'
-import { last } from 'lodash'
 import styled, { css } from 'styled-components'
-import { th, Icon } from '@pubsweet/ui'
+import { th } from '@pubsweet/ui'
 
-const parseFileSize = (size = 0) => {
-  const kbSize = Math.floor(size / 1000)
-  const mbSize = Math.floor(kbSize / 1000)
-  const gbSize = Math.floor(mbSize / 1000)
-
-  if (gbSize) {
-    return `${gbSize} GB`
-  } else if (mbSize) {
-    return `${mbSize} MB`
-  } else if (kbSize) {
-    return `${kbSize} kB`
-  }
-  return `${size} bytes`
-}
-
-const hasPreview = (name = '') => {
-  const extension = last(name.split('.'))
-  return ['pdf', 'png', 'jpg'].includes(extension)
-}
-
-const File = ({ name = 'filename', size, id, previewFile, downloadFile }) => (
-  <FileRoot>
-    {hasPreview(name) && (
-      <IconButton onClick={previewFile(id)}>
-        <Icon primary size={3}>
-          eye
-        </Icon>
-      </IconButton>
-    )}
-    <IconButton onClick={downloadFile(id, name)}>
-      <Icon primary size={3}>
-        download
-      </Icon>
-    </IconButton>
-    <FileName>{name}</FileName>
-    <FileSize>{parseFileSize(size)}</FileSize>
-  </FileRoot>
-)
+import { FileItem } from 'pubsweet-components-faraday/src/components/Files'
 
 const Files = ({
   previewFile,
@@ -55,7 +17,9 @@ const Files = ({
           <div />
         </Header>
         {manuscripts.map(file => (
-          <File
+          <FileItem
+            compact
+            id={file.id}
             key={file.id}
             {...file}
             downloadFile={downloadFile}
@@ -71,7 +35,9 @@ const Files = ({
           <div />
         </Header>
         {supplementary.map(file => (
-          <File
+          <FileItem
+            compact
+            id={file.id}
             key={file.id}
             {...file}
             downloadFile={downloadFile}
@@ -87,7 +53,9 @@ const Files = ({
           <div />
         </Header>
         {coverLetter.map(file => (
-          <File
+          <FileItem
+            compact
+            id={file.id}
             key={file.id}
             {...file}
             downloadFile={downloadFile}
@@ -108,34 +76,6 @@ const defaultText = css`
   font-size: ${th('fontSizeBaseSmall')};
 `
 
-const FileName = styled.span`
-  ${defaultText};
-  margin: 0 ${th('subGridUnit')};
-`
-const FileSize = FileName.extend`
-  margin-left: ${th('subGridUnit')};
-`
-
-const IconButton = styled.div`
-  align-items: center;
-  cursor: pointer;
-  display: flex;
-  justify-content: center;
-  margin: 0 ${th('subGridUnit')};
-  &:hover {
-    opacity: 0.7;
-  }
-`
-
-const FileRoot = styled.div`
-  align-items: center;
-  border: ${th('borderDefault')};
-  display: flex;
-  flex-direction: row;
-  margin-bottom: ${th('subGridUnit')};
-  padding: ${th('subGridUnit')};
-`
-
 const Header = styled.div`
   align-self: stretch;
   align-items: center;
diff --git a/packages/component-manuscript/src/components/ReviewerReportForm.js b/packages/component-manuscript/src/components/ReviewerReportForm.js
index 2e91779f1489a064817700d2089ddc2ed3eaee99..3f8fe374cb496a6363c9657bc576a1a51f2ec3ad 100644
--- a/packages/component-manuscript/src/components/ReviewerReportForm.js
+++ b/packages/component-manuscript/src/components/ReviewerReportForm.js
@@ -2,8 +2,8 @@ import React, { Fragment } from 'react'
 import { connect } from 'react-redux'
 import { required } from 'xpub-validators'
 import styled, { css } from 'styled-components'
+import { th, Menu, ValidatedField, Icon, Button, ErrorText } from '@pubsweet/ui'
 import { compose, withHandlers, withProps } from 'recompose'
-import { th, Menu, ValidatedField, Icon, Button } from '@pubsweet/ui'
 import {
   reduxForm,
   isSubmitting,
@@ -11,20 +11,33 @@ import {
   getFormValues,
 } from 'redux-form'
 import AutosaveIndicator from 'pubsweet-component-wizard/src/components/AutosaveIndicator'
+
+import {
+  uploadFile,
+  deleteFile,
+  getSignedUrl,
+} from 'pubsweet-components-faraday/src/redux/files'
 import {
-  autosaveRequest,
-  autosaveSuccess,
-} from 'pubsweet-component-wizard/src/redux/autosave'
+  FileItem,
+  FilePicker,
+} from 'pubsweet-components-faraday/src/components/Files'
+
 import {
   ConfirmationModal,
   withModal2,
 } from 'pubsweet-component-modal/src/components'
 import {
-  selectFetching,
   selectError,
+  selectFetching,
+  createRecommendation,
+  updateRecommendation,
 } from 'pubsweet-components-faraday/src/redux/recommendations'
 
-import { parseReviewResponseToForm, parseReviewRequest } from './utils'
+import {
+  parseReviewResponseToForm,
+  onReviewSubmit,
+  onReviewChange,
+} from './utils'
 
 const guidelinesLink =
   'https://about.hindawi.com/authors/peer-review-at-hindawi/'
@@ -47,31 +60,16 @@ const options = [
   },
 ]
 
-const review = {
-  id: 'revuewiuuuid',
-  userId: 'uuuuuuid',
-  recommendation: 'publish',
-  recommendationType: 'review',
-  comments: [
-    {
-      content: 'Here is public text',
-      public: true,
-      files: [],
-    },
-    {
-      content: 'Here is PRIVATE text',
-      public: false,
-      files: [],
-    },
-  ],
-}
-
 const ReviewerReportForm = ({
+  error,
   isSubmitting,
   changeField,
   handleSubmit,
   formValues = {},
-  initialValues,
+  review = {},
+  addFile,
+  removeFile,
+  previewFile,
 }) => (
   <Root>
     <Row>
@@ -96,10 +94,14 @@ const ReviewerReportForm = ({
       />
     </Row>
     <Spacing />
-    <Row>
-      <Label>
-        Report <ActionText left={12}>Upload file</ActionText>
-      </Label>
+    <Row left>
+      <Label>Report</Label>
+      <FilePicker
+        allowedFileExtensions={['pdf', 'doc', 'docx']}
+        onUpload={addFile}
+      >
+        <ActionText left={12}>Upload file</ActionText>
+      </FilePicker>
     </Row>
     <Row>
       <FullWidth>
@@ -117,6 +119,21 @@ const ReviewerReportForm = ({
         />
       </FullWidth>
     </Row>
+    {formValues.files && (
+      <Row>
+        {formValues.files.map(file => (
+          <FileItem
+            compact
+            id={file.id}
+            key={file.id}
+            {...file}
+            downloadFile={previewFile}
+            previewFile={previewFile}
+            removeFile={removeFile}
+          />
+        ))}
+      </Row>
+    )}
     {formValues.hasConfidential ? (
       <Fragment>
         <Row>
@@ -156,16 +173,21 @@ const ReviewerReportForm = ({
     )}
 
     <Spacing />
+    {error && (
+      <Row>
+        <ErrorText>{error}</ErrorText>
+      </Row>
+    )}
     <Row>
       <ActionButton onClick={handleSubmit}> Submit report </ActionButton>
-      <AutosaveIndicator formName="reviewerReport" />
+      <AutosaveIndicator
+        formName="reviewerReport"
+        lastUpdated={review.updatedOn}
+      />
     </Row>
   </Root>
 )
 
-// To be removed
-const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
-
 const ModalWrapper = compose(
   connect(state => ({
     fetching: selectFetching(state),
@@ -181,10 +203,18 @@ export default compose(
       formValues: getFormValues('reviewerReport')(state),
       isSubmitting: isSubmitting('reviewerReport')(state),
     }),
-    { changeForm, getFormValues },
+    {
+      changeForm,
+      getFormValues,
+      createRecommendation,
+      updateRecommendation,
+      uploadFile,
+      deleteFile,
+      getSignedUrl,
+    },
   ),
-  withProps(() => ({
-    initialValues: parseReviewResponseToForm({}),
+  withProps(({ review = {} }) => ({
+    initialValues: parseReviewResponseToForm(review),
   })),
   withModal2(props => ({
     modalComponent: ModalWrapper,
@@ -193,34 +223,51 @@ export default compose(
     changeField: ({ changeForm }) => (field, value) => {
       changeForm('reviewerReport', field, value)
     },
+    addFile: ({
+      formValues: { files = [] },
+      uploadFile,
+      changeForm,
+      version,
+    }) => file => {
+      uploadFile(file, 'review', version.id)
+        .then(file => {
+          const newFiles = [...files, file]
+
+          setTimeout(() => {
+            changeForm('reviewerReport', 'files', newFiles)
+          }, 1000)
+        })
+        .catch(e => console.error(`Couldn't upload file.`, e))
+    },
+    removeFile: ({
+      formValues: { files = [] },
+      changeForm,
+      deleteFile,
+    }) => id => e => {
+      deleteFile(id)
+        .then(r => {
+          const newFiles = files.filter(f => f.id !== id)
+          changeForm('reviewerReport', 'files', newFiles)
+        })
+        .catch(e => console.error(`Couldn't delete the file.`, e))
+
+      const newFiles = files.filter(f => f.id !== id)
+      changeForm('reviewerReport', 'files', newFiles)
+    },
+    previewFile: ({ getSignedUrl }) => fileId => e => {
+      e.preventDefault()
+      const windowReference = window.open()
+      getSignedUrl(fileId).then(({ signedUrl }) => {
+        windowReference.location = signedUrl
+      })
+    },
   }),
   reduxForm({
     form: 'reviewerReport',
     enableReinitialize: true,
-    forceUnregisterOnUnmount: true,
-    onChange: (values, dispatch) => {
-      dispatch(autosaveRequest())
-      sleep(1000).then(() => dispatch(autosaveSuccess(new Date())))
-    },
-    onSubmit: (values, dispatch, { isSubmitting, showModal, hideModal }) => {
-      showModal({
-        title: 'Ready to Submit your Report?',
-        subtitle: 'Once submitted, the report can`t be modified',
-        confirmText: 'Submit report',
-        onConfirm: () => {
-          sleep(1000)
-            .then(hideModal)
-            .then(() => {
-              // TODO: link to backend
-              const review = parseReviewRequest(values)
-              window.alert(
-                `You submitted:\n\n${JSON.stringify(review, null, 2)}`,
-              )
-            })
-        },
-        onCancel: hideModal,
-      })
-    },
+    keepDirtyOnReinitialize: true,
+    onChange: onReviewChange,
+    onSubmit: onReviewSubmit,
   }),
 )(ReviewerReportForm)
 
@@ -292,7 +339,8 @@ const Row = styled.div`
   flex: 1;
   box-sizing: border-box;
   flex-wrap: wrap;
-  justify-content: space-between;
+  justify-content: ${({ left }) => (left ? 'left' : 'space-between')};
+  ${defaultText};
 `
 
 const ActionButton = styled(Button)`
diff --git a/packages/component-manuscript/src/components/ReviewsAndReports.js b/packages/component-manuscript/src/components/ReviewsAndReports.js
index 15fa55f80fddbe236e0cb42015ff8e73145f2d3c..976793bc6823cbcbe9b4a1887d7ee75800c4d8b2 100644
--- a/packages/component-manuscript/src/components/ReviewsAndReports.js
+++ b/packages/component-manuscript/src/components/ReviewsAndReports.js
@@ -1,8 +1,9 @@
 import React, { Fragment } from 'react'
+import { head } from 'lodash'
 import { th } from '@pubsweet/ui'
 import { connect } from 'react-redux'
 import styled from 'styled-components'
-import { compose, withHandlers, lifecycle } from 'recompose'
+import { compose, withHandlers, lifecycle, withProps } from 'recompose'
 import { ReviewerBreakdown } from 'pubsweet-components-faraday/src/components/Invitations'
 import ReviewersDetailsList from 'pubsweet-components-faraday/src/components/Reviewers/ReviewersDetailsList'
 import ReviewerReportForm from 'pubsweet-component-manuscript/src/components/ReviewerReportForm'
@@ -12,6 +13,7 @@ import {
   getCollectionReviewers,
   currentUserIsReviewer,
 } from 'pubsweet-components-faraday/src/redux/reviewers'
+import { selectRecommendations } from 'pubsweet-components-faraday/src/redux/recommendations'
 
 import Tabs from '../molecules/Tabs'
 import Expandable from '../molecules/Expandable'
@@ -33,9 +35,13 @@ const getTabSections = (collectionId, reviewers) => [
 
 const ReviewsAndReports = ({
   project,
+  version,
   reviewers = [],
+  recommendations = [],
   isReviewer,
   currentUserIs,
+  report,
+  review = {},
 }) => (
   <Fragment>
     {currentUserIs('staff') && (
@@ -57,7 +63,18 @@ const ReviewsAndReports = ({
     {isReviewer && (
       <Root id="review-report">
         <Expandable label="Your Report" startExpanded>
-          <ReviewerReportForm />
+          {report ? (
+            <div>
+              You have a submitted report with recommendation{' '}
+              {report.recommendation}
+            </div>
+          ) : (
+            <ReviewerReportForm
+              project={project}
+              review={review}
+              version={version}
+            />
+          )}
         </Expandable>
       </Root>
     )}
@@ -68,16 +85,21 @@ export default compose(
   connect(
     (state, { project }) => ({
       reviewers: selectReviewers(state),
+      recommendations: selectRecommendations(state),
       fetchingReviewers: selectFetchingReviewers(state),
       isReviewer: currentUserIsReviewer(state, project.id),
     }),
     { getCollectionReviewers },
   ),
   withHandlers({
-    getReviewers: ({ project, setReviewers, getCollectionReviewers }) => () => {
+    getReviewers: ({ project, getCollectionReviewers }) => () => {
       getCollectionReviewers(project.id)
     },
   }),
+  withProps(({ recommendations = [] }) => ({
+    report: head(recommendations.filter(r => r.submittedOn)),
+    review: head(recommendations),
+  })),
   lifecycle({
     componentDidMount() {
       const { getReviewers } = this.props
diff --git a/packages/component-manuscript/src/components/utils.js b/packages/component-manuscript/src/components/utils.js
index 17ad97ee832b37c0a6daa0b3a85c45b7be7e4d2b..d6411a9a3b7e81a4d4cf42123d6729c7426e533f 100644
--- a/packages/component-manuscript/src/components/utils.js
+++ b/packages/component-manuscript/src/components/utils.js
@@ -1,5 +1,10 @@
 import moment from 'moment'
-import { get, find, capitalize, omit } from 'lodash'
+import { get, find, capitalize, omit, isEmpty, isEqual, debounce } from 'lodash'
+
+import {
+  autosaveRequest,
+  autosaveSuccess,
+} from 'pubsweet-component-wizard/src/redux/autosave'
 
 export const parseTitle = version => {
   const title = get(version, 'metadata.title')
@@ -82,6 +87,7 @@ export const redirectToError = redirectFn => err => {
 }
 
 export const parseReviewResponseToForm = (review = {}) => {
+  if (isEmpty(review)) return null
   const comments = review.comments || []
   const publicComment = comments.find(c => c.public)
   const privateComment = comments.find(c => !c.public)
@@ -95,10 +101,11 @@ export const parseReviewResponseToForm = (review = {}) => {
 }
 
 export const parseReviewRequest = (review = {}) => {
+  if (isEmpty(review)) return null
   const comments = [
     {
       public: true,
-      content: review.public,
+      content: review.public || '',
       files: review.files || [],
     },
   ]
@@ -111,7 +118,67 @@ export const parseReviewRequest = (review = {}) => {
     })
   }
   return {
-    ...omit(review, ['public', 'confidential', 'hasConfidential', 'files']),
+    ...omit(review, [
+      'public',
+      'confidential',
+      'hasConfidential',
+      'files',
+      'userId',
+    ]),
+    recommendationType: 'review',
     comments,
   }
 }
+
+const onChange = (
+  values,
+  dispatch,
+  { project, version, createRecommendation, updateRecommendation },
+  previousValues,
+) => {
+  const newValues = parseReviewRequest(values)
+  const prevValues = parseReviewRequest(previousValues)
+
+  if (!isEqual(newValues, prevValues) && !isEmpty(prevValues)) {
+    dispatch(autosaveRequest())
+    if (newValues.id) {
+      updateRecommendation(project.id, version.id, newValues).then(r =>
+        dispatch(autosaveSuccess(r.updatedOn)),
+      )
+    } else {
+      createRecommendation(project.id, version.id, newValues).then(r =>
+        dispatch(autosaveSuccess(r.updatedOn)),
+      )
+    }
+  }
+}
+
+export const onReviewChange = debounce(onChange, 1000, { maxWait: 5000 })
+
+export const onReviewSubmit = (
+  values,
+  dispatch,
+  {
+    isSubmitting,
+    showModal,
+    hideModal,
+    project,
+    version,
+    updateRecommendation,
+  },
+) => {
+  showModal({
+    title: 'Ready to Submit your Report?',
+    subtitle: 'Once submitted, the report can`t be modified',
+    confirmText: 'Submit report',
+    onConfirm: () => {
+      const newValues = parseReviewRequest(values)
+      newValues.submittedOn = Date.now()
+      dispatch(autosaveRequest())
+      updateRecommendation(project.id, version.id, newValues)
+        .then(r => dispatch(autosaveSuccess(r.updatedOn)))
+        .then(hideModal)
+    },
+    onCancel: hideModal,
+  })
+}
diff --git a/packages/component-wizard/src/components/AutosaveIndicator.js b/packages/component-wizard/src/components/AutosaveIndicator.js
index 0ddbc2c5b898408ec81d467441bb43e6a461b5e6..67f658c843283ca7aaa0b29bf441aa3d2d8a24a2 100644
--- a/packages/component-wizard/src/components/AutosaveIndicator.js
+++ b/packages/component-wizard/src/components/AutosaveIndicator.js
@@ -24,6 +24,7 @@ const Indicator = ({
   successText,
   autosave: { isFetching, error, lastUpdate },
   theme,
+  lastUpdated,
 }) =>
   isVisibile ? (
     <Root>
@@ -57,7 +58,9 @@ const Indicator = ({
           </AutoSaveContainer>
         )}
     </Root>
-  ) : null
+  ) : (
+    <div>{lastUpdated ? durationParser(lastUpdated) : ''}</div>
+  )
 
 export default compose(
   connect((state, { formName }) => ({
diff --git a/packages/component-wizard/src/components/AutosaveIndicator.md b/packages/component-wizard/src/components/AutosaveIndicator.md
index 86af657d2746b4e2a355c9c061675f13083eadd7..d83ba4fbda481d696db7e5c3ca2a0f6efe632f77 100644
--- a/packages/component-wizard/src/components/AutosaveIndicator.md
+++ b/packages/component-wizard/src/components/AutosaveIndicator.md
@@ -10,6 +10,7 @@ Display the status of the form (Saving in progress, Saved or Error while saving)
 | progressText | Text to show while API returns | false | 'Saving changes... ' | string |
 | errorText | Text to show on error  | false | 'Changes not saved ' | string |
 | successText | Text to show on success  | false | 'Progress saved ${duration.humanize()} ago.' | string |
+| lastUpdated | Show last updated timestamp from the beginning  | false | 'Progress saved ${duration.humanize()} ago.' | date |
 ## Examples
 
 ```js
diff --git a/packages/components-faraday/src/components/Files/FileItem.js b/packages/components-faraday/src/components/Files/FileItem.js
index 618292b0234f5e626ec62ff2b520da834572d764..960781cb067ec9d5228709508a714466067b1cc0 100644
--- a/packages/components-faraday/src/components/Files/FileItem.js
+++ b/packages/components-faraday/src/components/Files/FileItem.js
@@ -1,6 +1,7 @@
-import React from 'react'
-import { Icon } from '@pubsweet/ui'
-import styled, { withTheme } from 'styled-components'
+import React, { Fragment } from 'react'
+import { last } from 'lodash'
+import { Icon, th } from '@pubsweet/ui'
+import styled, { withTheme, css } from 'styled-components'
 
 const parseFileSize = size => {
   const kbSize = size / 1000
@@ -17,6 +18,11 @@ const parseFileSize = size => {
   return `${size} bytes`
 }
 
+const hasPreview = (name = '') => {
+  const extension = last(name.split('.'))
+  return ['pdf', 'png', 'jpg'].includes(extension)
+}
+
 const FileItem = ({
   dragHandle,
   name,
@@ -24,42 +30,82 @@ const FileItem = ({
   id,
   removeFile,
   previewFile,
+  downloadFile,
+  compact = false,
   theme,
   ...rest
 }) => (
-  <Root data-test={`file-${id}`}>
-    {dragHandle}
-    <Info>
-      <span>{name}</span>
-      <span>{parseFileSize(size)}</span>
-    </Info>
-    <Buttons>
-      <button onClick={previewFile(id)}>
-        <Icon color={theme.colorPrimary} size={3}>
-          eye
-        </Icon>
-      </button>
-      <button onClick={removeFile(id)} title="Delete">
-        <Icon color={theme.colorPrimary} size={3}>
-          trash-2
-        </Icon>
-      </button>
-    </Buttons>
-  </Root>
+  <Fragment>
+    {compact ? (
+      <FileRoot data-test={`file-${id}`}>
+        {hasPreview(name) && (
+          <IconButton onClick={previewFile(id)}>
+            <Icon primary size={3}>
+              eye
+            </Icon>
+          </IconButton>
+        )}
+        {downloadFile && (
+          <IconButton onClick={downloadFile(id, name)}>
+            <Icon primary size={3}>
+              download
+            </Icon>
+          </IconButton>
+        )}
+        <FileName>{name}</FileName>
+        <FileSize>{parseFileSize(size)}</FileSize>
+        {removeFile && (
+          <IconButton onClick={removeFile(id)}>
+            <Icon primary size={3}>
+              trash-2
+            </Icon>
+          </IconButton>
+        )}
+      </FileRoot>
+    ) : (
+      <Root data-test={`file-${id}`}>
+        {dragHandle}
+        <Info>
+          <span>{name}</span>
+          <span>{parseFileSize(size)}</span>
+        </Info>
+        <Buttons>
+          <button onClick={previewFile(id)}>
+            <Icon color={theme.colorPrimary} size={3}>
+              eye
+            </Icon>
+          </button>
+          {removeFile && (
+            <button onClick={removeFile(id)} title="Delete">
+              <Icon color={theme.colorPrimary} size={3}>
+                trash-2
+              </Icon>
+            </button>
+          )}
+        </Buttons>
+      </Root>
+    )}
+  </Fragment>
 )
 
 export default withTheme(FileItem)
 
 // #region styles
+const defaultText = css`
+  color: ${th('colorPrimary')};
+  font-family: ${th('fontHeading')};
+  font-size: ${th('fontSizeBaseSmall')};
+`
+
 const Root = styled.div`
   align-items: center;
-  border: ${({ theme }) => theme.borderDefault};
+  border: ${th('borderDefault')};
   display: flex;
   margin: 5px;
 `
 
 const Info = styled.div`
-  border-right: ${({ theme }) => theme.borderDefault};
+  border-right: ${th('borderDefault')};
   display: flex;
   flex: 1;
   justify-content: space-between;
@@ -88,4 +134,31 @@ const Buttons = styled.div`
     }
   }
 `
+const FileName = styled.span`
+  ${defaultText};
+  margin: 0 ${th('subGridUnit')};
+`
+const FileSize = FileName.extend`
+  margin-left: ${th('subGridUnit')};
+`
+
+const IconButton = styled.div`
+  align-items: center;
+  cursor: pointer;
+  display: flex;
+  justify-content: center;
+  margin: 0 ${th('subGridUnit')};
+  &:hover {
+    opacity: 0.7;
+  }
+`
+
+const FileRoot = styled.div`
+  align-items: center;
+  border: ${th('borderDefault')};
+  display: flex;
+  flex-direction: row;
+  margin-bottom: ${th('subGridUnit')};
+  padding: ${th('subGridUnit')};
+`
 // #endregion
diff --git a/packages/components-faraday/src/components/Files/index.js b/packages/components-faraday/src/components/Files/index.js
index 6df727395c45e4df9e5db1d031a4d43146ad8440..ab85f18a55158ad1fc24c64e32e0023158ec7ae2 100644
--- a/packages/components-faraday/src/components/Files/index.js
+++ b/packages/components-faraday/src/components/Files/index.js
@@ -1 +1,3 @@
 export { default as Files } from './Files'
+export { default as FileItem } from './FileItem'
+export { default as FilePicker } from './FilePicker'
diff --git a/packages/components-faraday/src/index.js b/packages/components-faraday/src/index.js
index a73434f5efaa170268c552a69188a1a455e2491b..d21969e7b792fa03fadc8e92f38152c12935abb4 100644
--- a/packages/components-faraday/src/index.js
+++ b/packages/components-faraday/src/index.js
@@ -6,6 +6,7 @@ module.exports = {
       files: () => require('./redux/files').default,
       editors: () => require('./redux/editors').default,
       reviewers: () => require('./redux/reviewers').default,
+      recommendations: () => require('./redux/recommendations').default,
     },
   },
 }
diff --git a/packages/components-faraday/src/redux/recommendations.js b/packages/components-faraday/src/redux/recommendations.js
index b59d6abf4748ff2eb7e966677bfd9be7ae9ab7b2..38c278451bf32d80663a10ed55863b6341739b12 100644
--- a/packages/components-faraday/src/redux/recommendations.js
+++ b/packages/components-faraday/src/redux/recommendations.js
@@ -1,16 +1,13 @@
 import { get } from 'lodash'
-import {
-  get as apiGet,
-  create,
-  remove,
-  update,
-} from 'pubsweet-client/src/helpers/api'
+import { SubmissionError } from 'redux-form'
+import { create, update } from 'pubsweet-client/src/helpers/api'
 
 const REQUEST = 'recommendations/REQUEST'
 const ERROR = 'recommendations/ERROR'
 
+const GET_FRAGMENT_SUCCESS = 'GET_FRAGMENT_SUCCESS'
 const GET_RECOMMENDATIONS_SUCCESS = 'recommendations/GET_SUCCESS'
-const GET_RECOMMENDATION_SUCCESS = 'recommendations/GET_ITEM_SUCCESS'
+const CREATE_RECOMMENDATION_SUCCESS = 'recommendations/CREATE_SUCCESS'
 const UPDATE_RECOMMENDATION_SUCCESS = 'recommendations/UPDATE_SUCCESS'
 
 export const recommendationsRequest = () => ({
@@ -27,8 +24,8 @@ export const getRecommendationsSuccess = recommendations => ({
   payload: { recommendations },
 })
 
-export const getRecommendationSuccess = recommendation => ({
-  type: GET_RECOMMENDATION_SUCCESS,
+export const createRecommendationSuccess = recommendation => ({
+  type: CREATE_RECOMMENDATION_SUCCESS,
   payload: { recommendation },
 })
 
@@ -40,17 +37,72 @@ export const updateRecommendationSuccess = recommendation => ({
 // Selectors
 export const selectFetching = state =>
   get(state, 'recommendations.fetching') || false
-
 export const selectError = state => get(state, 'recommendations.error')
+export const selectRecommendations = state =>
+  get(state, 'recommendations.recommendations') || []
 
 // Actions
+export const createRecommendation = (
+  collId,
+  fragId,
+  recommendation,
+) => dispatch => {
+  dispatch(recommendationsRequest())
+  return create(
+    `/collections/${collId}/fragments/${fragId}/recommendations`,
+    recommendation,
+  ).then(
+    r => {
+      dispatch(getRecommendationsSuccess([r]))
+      return r
+    },
+    err => {
+      const error = get(err, 'response')
+      if (error) {
+        const errorMessage = get(JSON.parse(error), 'error')
+        dispatch(recommendationsError(errorMessage))
+        throw new SubmissionError({
+          _error: errorMessage || 'Something went wrong',
+        })
+      }
+    },
+  )
+}
+
+export const updateRecommendation = (
+  collId,
+  fragId,
+  recommendation,
+) => dispatch => {
+  dispatch(recommendationsRequest())
+  return update(
+    `/collections/${collId}/fragments/${fragId}/recommendations/${
+      recommendation.id
+    }`,
+    recommendation,
+  ).then(
+    r => {
+      dispatch(getRecommendationsSuccess([r]))
+      return r
+    },
+    err => {
+      const error = get(err, 'response')
+      if (error) {
+        const errorMessage = get(JSON.parse(error), 'error')
+        dispatch(recommendationsError(errorMessage))
+        throw new SubmissionError({
+          _error: errorMessage || 'Something went wrong',
+        })
+      }
+    },
+  )
+}
 
 // State
 const initialState = {
   fetching: false,
   error: null,
   recommendations: [],
-  recommendation: {},
 }
 
 export default (state = initialState, action = {}) => {
@@ -59,8 +111,6 @@ export default (state = initialState, action = {}) => {
       return {
         ...state,
         fetching: true,
-        recommendations: [],
-        recommendation: {},
       }
     case ERROR:
       return {
@@ -68,26 +118,27 @@ export default (state = initialState, action = {}) => {
         fetching: false,
         error: action.error,
       }
-    case GET_RECOMMENDATIONS_SUCCESS:
+    case GET_FRAGMENT_SUCCESS:
       return {
         ...state,
         fetching: false,
         error: null,
-        recommendations: action.payload.recommendations,
+        recommendations: get(action, 'fragment.recommendations'),
       }
-    case GET_RECOMMENDATION_SUCCESS:
+    case GET_RECOMMENDATIONS_SUCCESS:
       return {
         ...state,
         fetching: false,
         error: null,
-        recommendation: action.payload.recommendation,
+        recommendations: action.payload.recommendations,
       }
     case UPDATE_RECOMMENDATION_SUCCESS:
+    case CREATE_RECOMMENDATION_SUCCESS:
       return {
         ...state,
         fetching: false,
         error: null,
-        recommendation: action.payload.recommendation,
+        recommendations: [action.payload.recommendation],
       }
     default:
       return state
diff --git a/packages/xpub-faraday/config/upload-validations.js b/packages/xpub-faraday/config/upload-validations.js
index 23fa37d7d5a7b0403731429eb910f5f402799000..21b2412ef38c1f8aea0cf3f3e98faf486a3b7107 100644
--- a/packages/xpub-faraday/config/upload-validations.js
+++ b/packages/xpub-faraday/config/upload-validations.js
@@ -16,4 +16,5 @@ module.exports = {
       'application/msword',
     ])
     .error(new Error('Only Word documents and PDFs are allowed')),
+  review: Joi.any(),
 }