diff --git a/packages/components/Login/Login.jsx b/packages/components/Login/Login.jsx
index 6cc5a2d8597fa53e053e4c5615f2502093e17280..00a613b9690eb79101abf0c81b3144c6b73876a8 100644
--- a/packages/components/Login/Login.jsx
+++ b/packages/components/Login/Login.jsx
@@ -1,4 +1,5 @@
 import React from 'react'
+import { Redirect } from 'react-router'
 import PropTypes from 'prop-types'
 import { Field } from 'formik'
 import { isEmpty } from 'lodash'
@@ -26,34 +27,38 @@ const Login = ({
   handleSubmit,
   signup = true,
   passwordReset = true,
-}) => (
-  <CenteredColumn small>
-    <H1>Login</H1>
+  redirectLink,
+}) =>
+  redirectLink ? (
+    <Redirect to={redirectLink} />
+  ) : (
+    <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>
+      {!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>
-    )}
+      {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>
-)
+      {passwordReset && (
+        <ResetPassword>
+          <span>Forgot your password? </span>
+          <Link to="/password-reset">Reset password</Link>
+        </ResetPassword>
+      )}
+    </CenteredColumn>
+  )
 
 Login.propTypes = {
   error: PropTypes.string,
diff --git a/packages/components/Login/graphql/LoginContainer.js b/packages/components/Login/graphql/LoginContainer.js
index 83715d8019cbd8eb24bf086d9b7ebdb733daf80e..766c9d31812f69ef3697188580e115460b6735d9 100644
--- a/packages/components/Login/graphql/LoginContainer.js
+++ b/packages/components/Login/graphql/LoginContainer.js
@@ -1,25 +1,29 @@
-import { compose } from 'recompose'
+import { compose, withState, withHandlers } from 'recompose'
 import { withFormik } from 'formik'
 import { graphql } from 'react-apollo'
-
 import mutations from './mutations'
 import Login from '../Login'
-import redirectPath from '../redirect'
+
+const getNextUrl = () => {
+  const url = new URL(window.location.href)
+  return `${url.searchParams.get('next') || '/'}`
+}
 
 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)
-      }
+    .loginUser({
+      variables: { input: values },
+    })
+    .then(({ data }) => {
+      localStorage.setItem('token', data.loginUser.token)
+      setTimeout(() => {
+        props.onLoggedIn(getNextUrl())
+      }, 100)
     })
     .catch(e => {
-      if (e.graphQLErrors) {
+      if (e.graphQLErrors.length > 0) {
         setSubmitting(false)
         setErrors(e.graphQLErrors[0].message)
       }
@@ -38,6 +42,12 @@ const enhancedFormik = withFormik({
   handleSubmit,
 })(Login)
 
-export default compose(graphql(mutations.LOGIN_USER, { name: 'loginUser' }))(
-  enhancedFormik,
-)
+export default compose(
+  graphql(mutations.LOGIN_USER, {
+    name: 'loginUser',
+  }),
+  withState('redirectLink', 'loggedIn', null),
+  withHandlers({
+    onLoggedIn: ({ loggedIn }) => returnUrl => loggedIn(() => returnUrl),
+  }),
+)(enhancedFormik)
diff --git a/packages/components/Login/graphql/LoginContainer.test.jsx b/packages/components/Login/graphql/LoginContainer.test.jsx
index 7df5518babca59528ec37fa596338e687f192365..9ce5c596edaac8f69e5bbdad6865a76b41788c47 100644
--- a/packages/components/Login/graphql/LoginContainer.test.jsx
+++ b/packages/components/Login/graphql/LoginContainer.test.jsx
@@ -2,7 +2,7 @@ import React from 'react'
 import { mount } from 'enzyme'
 import { MockedProvider } from 'react-apollo/test-utils'
 import { ThemeProvider } from 'styled-components'
-import { MemoryRouter, Route } from 'react-router-dom'
+import { MemoryRouter, Route, Switch } from 'react-router-dom'
 
 import wait from 'waait'
 import { LOGIN_USER } from './mutations'
@@ -66,14 +66,25 @@ function makeDeepWrapper(currentUser, props = {}) {
   return mount(
     <ThemeProvider theme={theme}>
       <MockedProvider addTypename={false} mocks={mocks(currentUser)}>
-        <MemoryRouter initialEntries={['/login']}>
-          <Route
-            {...props}
-            render={p => {
-              globalLocation = p.location
-              return <LoginContainer {...p} />
-            }}
-          />
+        <MemoryRouter initialEntries={['/login', '/']}>
+          <Switch>
+            <Route
+              {...props}
+              path="/login"
+              render={p => {
+                globalLocation = p.location
+                return <LoginContainer {...p} />
+              }}
+            />
+            <Route
+              {...props}
+              path="/"
+              render={p => {
+                globalLocation = p.location
+                return <p>dashboard</p>
+              }}
+            />
+          </Switch>
         </MemoryRouter>
       </MockedProvider>
     </ThemeProvider>,
@@ -109,11 +120,11 @@ describe('LoginContainer', () => {
     const button = wrapper.find('button')
     button.simulate('submit')
 
+    await wait(500)
     wrapper.update()
-    await wait(50)
 
     expect(window.localStorage.token).toEqual('greatToken')
-    expect(globalLocation.pathname).toEqual('/testRedirect')
+    expect(globalLocation.pathname).toEqual('/')
   })
 
   it('does not log in user with incorrect credentials', async () => {
diff --git a/packages/components/Login/package.json b/packages/components/Login/package.json
index 31818c0dd5651fb81e4f6710e840bc28b7fda560..632b21454b17713773fa8e0852409e4c6f0fc24c 100644
--- a/packages/components/Login/package.json
+++ b/packages/components/Login/package.json
@@ -21,6 +21,7 @@
     "pubsweet-client": ">=1.0.0",
     "react": ">=15",
     "react-apollo": "^2.3.3",
+    "react-router": "^4.3.1",
     "styled-components": "^4.1.3"
   },
   "repository": {
diff --git a/packages/components/xpub-dashboard/src/components/DashboardPage.js b/packages/components/xpub-dashboard/src/components/DashboardPage.js
index bd9f146339a7ce347b02d6c0437d6cd0a63e81d4..85c73cd177936af5860ea636489038a388c24564 100644
--- a/packages/components/xpub-dashboard/src/components/DashboardPage.js
+++ b/packages/components/xpub-dashboard/src/components/DashboardPage.js
@@ -15,16 +15,39 @@ const acceptFiles =
     ? acceptUploadFiles.join()
     : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
 
+const updateReviewer = (proxy, { data: { reviewerResponse } }) => {
+  const id = reviewerResponse.object.objectId
+  const data = proxy.readQuery({
+    query: queries.dashboard,
+    variables: {
+      id,
+    },
+  })
+
+  const manuscriptIndex = data.journals.manuscripts.findIndex(
+    manu => manu.id === id,
+  )
+  const teamIndex = data.journals.manuscripts[manuscriptIndex].teams.findIndex(
+    team => team.id === reviewerResponse.id,
+  )
+
+  data.journals.manuscripts[manuscriptIndex].teams[teamIndex] = reviewerResponse
+
+  proxy.writeQuery({ query: queries.dashboard, data })
+}
+
 export default compose(
   connectToContext(),
   graphql(queries.dashboard, {
-    options: { context: { online: false } },
     props: data => data,
   }),
   graphql(mutations.reviewerResponseMutation, {
     props: ({ mutate }) => ({
-      reviewerResponse: (manuscript, response) =>
-        mutate({ variables: { id: manuscript.id, response } }),
+      reviewerResponse: (currentUserId, action, teamId) =>
+        mutate({
+          variables: { currentUserId, action, teamId },
+          update: updateReviewer,
+        }),
     }),
   }),
   graphql(mutations.deleteManuscriptMutation, {
@@ -36,7 +59,7 @@ export default compose(
       update: (proxy, { data: { deleteManuscript } }) => {
         const data = proxy.readQuery({ query: queries.dashboard })
         const manuscriptIndex = data.journals.manuscripts.findIndex(
-          manuscript => manuscript.id === deleteManuscript.id,
+          manuscript => manuscript.id === deleteManuscript,
         )
         if (manuscriptIndex > -1) {
           data.journals.manuscripts.splice(manuscriptIndex, 1)
diff --git a/packages/components/xpub-dashboard/src/components/sections/EditorItem.test.js b/packages/components/xpub-dashboard/src/components/sections/EditorItem.test.js
index c1bb80977252039b0ca7a5756429b4bfd591ff8a..6b6d90e95ac8bf482c213f50426724c1cd146ad7 100644
--- a/packages/components/xpub-dashboard/src/components/sections/EditorItem.test.js
+++ b/packages/components/xpub-dashboard/src/components/sections/EditorItem.test.js
@@ -125,7 +125,7 @@ describe('EditorItem', () => {
                 },
               },
             ],
-            role: 'author',
+            teamType: 'author',
           },
         ],
         meta: {
diff --git a/packages/components/xpub-dashboard/src/components/sections/OwnerItem.js b/packages/components/xpub-dashboard/src/components/sections/OwnerItem.js
index 33b2896c239dd031dd1cf62b0933b25a63d7006a..96470f3ee1ab1c4e26d9e3728ed97a95f332e063 100644
--- a/packages/components/xpub-dashboard/src/components/sections/OwnerItem.js
+++ b/packages/components/xpub-dashboard/src/components/sections/OwnerItem.js
@@ -46,7 +46,7 @@ const OwnerItem = ({ version, journals, deleteManuscript }) => {
   const actions = (
     <AuthorizeWithGraphQL
       object={version}
-      operation="can delete collection"
+      operation="can delete manuscript"
       unauthorized={unauthorized}
     >
       <ActionGroup>{Object.values(actionButtons)}</ActionGroup>
diff --git a/packages/components/xpub-dashboard/src/components/sections/ReviewerItem.js b/packages/components/xpub-dashboard/src/components/sections/ReviewerItem.js
index 2422ea598ca0a442c5f810239dc17969d0dea14a..8cc01ee47b16f628483e3021e381b994234d4ef3 100644
--- a/packages/components/xpub-dashboard/src/components/sections/ReviewerItem.js
+++ b/packages/components/xpub-dashboard/src/components/sections/ReviewerItem.js
@@ -1,7 +1,8 @@
 import React from 'react'
 import { Button } from '@pubsweet/ui'
 import AuthorizeWithGraphQL from 'pubsweet-client/src/helpers/AuthorizeWithGraphQL'
-import { getUserFromTeam } from 'xpub-selectors'
+// Enable that when Team Models is updated
+// import { getUserFromTeam } from 'xpub-selectors'
 import { Item, Body, Divider } from '../molecules/Item'
 import { Links, LinkContainer } from '../molecules/Links'
 import { Actions, ActionContainer } from '../molecules/Actions'
@@ -14,12 +15,22 @@ import VersionTitle from './VersionTitle'
 // TODO: review id in link
 
 const ReviewerItem = ({ version, journals, currentUser, reviewerResponse }) => {
+  const team =
+    (version.teams || []).find(team => team.teamType === 'reviewerEditor') || {}
   const { status } =
-    getUserFromTeam(version, 'reviewerEditor').filter(
-      member => member.user.id === currentUser.id,
-    )[0] || {}
+    (team.status || []).filter(member => member.user === currentUser.id)[0] ||
+    {}
 
-  const review = version.reviews[0] || {}
+  // Enable that when Team Models is updated
+  // const { status } =
+  //   getUserFromTeam(version, 'reviewerEditor').filter(
+  //     member => member.id === currentUser.id,
+  //   )[0] || {}
+
+  const review =
+    (version.reviews || []).find(
+      review => review.user.id === currentUser.id && !review.isDecision,
+    ) || {}
 
   return (
     <AuthorizeWithGraphQL
@@ -31,7 +42,7 @@ const ReviewerItem = ({ version, journals, currentUser, reviewerResponse }) => {
         <Body>
           <VersionTitle version={version} />
 
-          {status === 'accepted' && (
+          {(status === 'accepted' || status === 'completed') && (
             <Links>
               <LinkContainer>
                 <JournalLink
@@ -40,7 +51,7 @@ const ReviewerItem = ({ version, journals, currentUser, reviewerResponse }) => {
                   page="reviews"
                   version={version}
                 >
-                  {review.recommendation ? 'Completed' : 'Do Review'}
+                  {status === 'completed' ? 'Completed' : 'Do Review'}
                 </JournalLink>
               </LinkContainer>
             </Links>
@@ -51,7 +62,7 @@ const ReviewerItem = ({ version, journals, currentUser, reviewerResponse }) => {
               <ActionContainer>
                 <Button
                   onClick={() => {
-                    reviewerResponse(version, 'accepted')
+                    reviewerResponse(currentUser.id, 'accepted', team.id)
                   }}
                 >
                   accept
@@ -63,7 +74,7 @@ const ReviewerItem = ({ version, journals, currentUser, reviewerResponse }) => {
               <ActionContainer>
                 <Button
                   onClick={() => {
-                    reviewerResponse(version, 'rejected')
+                    reviewerResponse(currentUser.id, 'rejected', team.id)
                   }}
                 >
                   reject
diff --git a/packages/components/xpub-dashboard/src/graphql/DashboardPage.js b/packages/components/xpub-dashboard/src/graphql/DashboardPage.js
deleted file mode 100644
index 75f623dd515209c53abd543b72d7291eaf695b02..0000000000000000000000000000000000000000
--- a/packages/components/xpub-dashboard/src/graphql/DashboardPage.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import { compose, withProps } from 'recompose'
-import { graphql } from 'react-apollo'
-import { withLoader } from 'pubsweet-client'
-import queries from './queries/'
-// import mutations from './mutations/'
-import Dashboard from '../components/Dashboard'
-import upload from '../lib/upload'
-
-export default compose(
-  graphql(queries.myManuscripts, {
-    options: { context: { online: false } },
-  }),
-  // graphql(mutations.deleteProjectMutation, {
-  //   props: ({ mutate }) => ({
-  //     deleteProject: project => mutate({ variables: { id: project.id } }),
-  //   }),
-  //   options: {
-  //     update: (
-  //       proxy,
-  //       {
-  //         data: {
-  //           deleteCollection: { id },
-  //         },
-  //       },
-  //     ) => {
-  //       const data = proxy.readQuery({ query })
-  //       const collectionIndex = data.collections.findIndex(col => col.id === id)
-  //       if (collectionIndex > -1) {
-  //         data.collections.splice(collectionIndex, 1)
-  //         proxy.writeQuery({ query, data })
-  //       }
-  //     },
-  //   },
-  // }),
-  withLoader(),
-  withProps(({ manuscripts: { manuscripts }, currentUser }) => ({
-    dashboard: manuscripts,
-    currentUser,
-  })),
-  upload,
-)(Dashboard)
diff --git a/packages/components/xpub-dashboard/src/graphql/mutations/index.js b/packages/components/xpub-dashboard/src/graphql/mutations/index.js
index 966ec567d147f5edc73fa9ad941950c755995800..5d541248b34c9b493f57f23d938c08b357684b85 100644
--- a/packages/components/xpub-dashboard/src/graphql/mutations/index.js
+++ b/packages/components/xpub-dashboard/src/graphql/mutations/index.js
@@ -2,16 +2,34 @@ import gql from 'graphql-tag'
 
 export default {
   deleteManuscriptMutation: gql`
-    mutation($id: ID) {
-      deleteManuscript(id: $id) {
-        id
-      }
+    mutation($id: ID!) {
+      deleteManuscript(id: $id)
     }
   `,
   reviewerResponseMutation: gql`
-    mutation($id: ID!, $response: String) {
-      reviewerResponse(id: $id, response: $response) {
+    mutation($currentUserId: ID!, $action: String, $teamId: ID!) {
+      reviewerResponse(
+        currentUserId: $currentUserId
+        action: $action
+        teamId: $teamId
+      ) {
         id
+        role
+        teamType
+        name
+        object {
+          objectId
+          objectType
+        }
+        objectType
+        members {
+          id
+          username
+        }
+        status {
+          user
+          status
+        }
       }
     }
   `,
@@ -27,20 +45,27 @@ export default {
       createManuscript(input: $input) {
         id
         created
+        manuscriptVersions {
+          id
+        }
         teams {
+          id
+          role
           name
+          teamType
           object {
-            id
+            objectId
+            objectType
           }
           objectType
           members {
-            user {
-              id
-              username
-            }
+            id
+            username
+          }
+          status {
+            user
             status
           }
-          role
         }
         status
         reviews {
diff --git a/packages/components/xpub-dashboard/src/graphql/queries/index.js b/packages/components/xpub-dashboard/src/graphql/queries/index.js
index 8fc43073bfecb7379dbd6970a082e0852c1ee68c..718ab9d9c353cc98c5335f1cc5558c7dba1bab03 100644
--- a/packages/components/xpub-dashboard/src/graphql/queries/index.js
+++ b/packages/components/xpub-dashboard/src/graphql/queries/index.js
@@ -11,31 +11,39 @@ export default {
 
       journals {
         id
-        journalTitle
+        title
         manuscripts {
           id
+          manuscriptVersions {
+            id
+          }
           reviews {
             open
             recommendation
             created
+            isDecision
             user {
               id
               username
             }
           }
           teams {
+            id
             role
+            teamType
             name
             object {
-              id
+              objectId
+              objectType
             }
             objectType
             members {
+              id
+              username
+            }
+            status {
+              user
               status
-              user {
-                id
-                username
-              }
             }
           }
           status
@@ -60,4 +68,16 @@ export default {
       }
     }
   `,
+  getUser: gql`
+    query GetUser($id: ID!) {
+      user(id: $id) {
+        id
+        username
+        admin
+        teams {
+          id
+        }
+      }
+    }
+  `,
 }
diff --git a/packages/components/xpub-dashboard/src/lib/upload.js b/packages/components/xpub-dashboard/src/lib/upload.js
index 96195ca98d5c5e36d633953bbf8c54d06bad76bb..213c9c921026f0ca43ca3459880b35987a9ddc4b 100644
--- a/packages/components/xpub-dashboard/src/lib/upload.js
+++ b/packages/components/xpub-dashboard/src/lib/upload.js
@@ -32,39 +32,47 @@ const inkConvertPromise = file => ({ data }) => {
   )
 }
 
-const createManuscriptPromise = (file, client) => ({ fileURL, response }) => {
+const createManuscriptPromise = (file, client, currentUser) => ({
+  fileURL,
+  response,
+}) => {
   if (!response.converted) {
     throw new Error('The file was not converted')
   }
 
   const source = response.converted
+
   const title = extractTitle(source) || generateTitle(file.name)
 
   const manuscript = {
-    created: new Date(), // TODO: set on server
     files: [
       {
-        created: new Date(), // TODO: set on server
-        type: 'manuscript',
         filename: file.name,
         url: fileURL,
         mimeType: file.type,
+        size: file.size,
       },
     ],
     meta: {
       title,
-      source,
+      source: source === true ? 'false' : source,
     },
-    status: 'new',
   }
 
   return client.mutate({
     mutation: mutations.createManuscriptMutation,
     variables: { input: manuscript },
     update: (proxy, { data: { createManuscript } }) => {
-      const data = proxy.readQuery({ query: queries.dashboard })
+      let data = proxy.readQuery({ query: queries.dashboard })
       data.journals.manuscripts.push(createManuscript)
       proxy.writeQuery({ query: queries.dashboard, data })
+
+      data = proxy.readQuery({
+        query: queries.getUser,
+        variables: { id: currentUser.id },
+      })
+      data.user.teams.push(createManuscript.teams[0])
+      proxy.writeQuery({ query: queries.getUser, data })
     },
   })
 }
@@ -91,24 +99,26 @@ const skipInkConversion = file =>
 export default compose(
   withApollo,
   withRouter,
-  withProps(({ client, setConversionState, history, journals }) => ({
-    uploadManuscript: files => {
-      const [file] = files
-      setConversionState(() => ({ converting: true }))
-      return Promise.resolve()
-        .then(uploadPromise(files, client))
-        .then(
-          skipInkConversion(file)
-            ? ({ data }) =>
-                Promise.resolve({
-                  fileURL: data.upload.url,
-                  response: { converted: true },
-                })
-            : inkConvertPromise(file),
-        )
-        .then(createManuscriptPromise(file, client))
-        .then(redirectPromise(setConversionState, journals, history))
-        .catch(error => setConversionState(() => ({ error })))
-    },
-  })),
+  withProps(
+    ({ client, setConversionState, history, journals, currentUser }) => ({
+      uploadManuscript: files => {
+        const [file] = files
+        setConversionState(() => ({ converting: true }))
+        return Promise.resolve()
+          .then(uploadPromise(files, client))
+          .then(
+            skipInkConversion(file)
+              ? ({ data }) =>
+                  Promise.resolve({
+                    fileURL: data.upload.url,
+                    response: { converted: true },
+                  })
+              : inkConvertPromise(file),
+          )
+          .then(createManuscriptPromise(file, client, currentUser))
+          .then(redirectPromise(setConversionState, journals, history))
+          .catch(error => setConversionState(() => ({ error })))
+      },
+    }),
+  ),
 )
diff --git a/packages/components/xpub-formbuilder/package.json b/packages/components/xpub-formbuilder/package.json
index c5f2494b519dba588bc9137ec30bd4658fc5ac39..2d2eb2d2e42c5b4900423d57079a732f8c742299 100644
--- a/packages/components/xpub-formbuilder/package.json
+++ b/packages/components/xpub-formbuilder/package.json
@@ -12,6 +12,7 @@
   "dependencies": {
     "@pubsweet/ui": "^9.1.2",
     "@pubsweet/ui-toolkit": "^2.0.6",
+    "formik": "^1.4.2",
     "passport": "^0.4.0",
     "prop-types": "^15.5.10",
     "pubsweet-component-ink-frontend": "^1.0.2",
@@ -30,12 +31,16 @@
     "babel-preset-env": "^1.6.0",
     "babel-preset-react": "^6.24.1",
     "babel-preset-stage-2": "^6.24.1",
+    "enzyme": "^3.7.0",
+    "enzyme-adapter-react-16": "^1.6.0",
     "faker": "^4.1.0"
   },
   "peerDependencies": {
     "config": "^3.0.1",
+    "graphql-tag": "^2.10.0",
     "lodash": "^4.17.11",
     "pubsweet-client": ">=2.1.0",
-    "react": ">=16"
+    "react": ">=16",
+    "react-apollo": "^2.3.3"
   }
 }
diff --git a/packages/components/xpub-formbuilder/src/components/ComponentProperties.jsx b/packages/components/xpub-formbuilder/src/components/ComponentProperties.jsx
index 989f040bb9f535674b97f4fd4541ecf5a7076a2a..49e206ed7373e1bd7f29c4437a67c679768a16bf 100644
--- a/packages/components/xpub-formbuilder/src/components/ComponentProperties.jsx
+++ b/packages/components/xpub-formbuilder/src/components/ComponentProperties.jsx
@@ -1,6 +1,5 @@
 import React from 'react'
 import { map, omitBy } from 'lodash'
-// import styled from 'styled-components'
 import {
   branch,
   renderComponent,
@@ -9,8 +8,8 @@ import {
   withHandlers,
   withProps,
 } from 'recompose'
-import { ValidatedField, Menu, Button } from '@pubsweet/ui'
-import { FormSection, reduxForm } from 'redux-form'
+import { ValidatedFieldFormik, Menu, Button } from '@pubsweet/ui'
+import { withFormik } from 'formik'
 
 import FormProperties from './FormProperties'
 import components from './config/Elements'
@@ -33,31 +32,43 @@ const ComponentProperties = ({
   changeComponent,
   selectComponentValue,
   handleSubmit,
+  setFieldValue,
 }) => (
   <Page>
     <form onSubmit={handleSubmit}>
       <Heading>Component Properties</Heading>
-      <FormSection name="children">
-        <Section>
-          <Legend space>Choose Component</Legend>
-          <ValidatedField
-            component={MenuComponents}
-            name="component"
-            onChange={(value, v) => changeComponent(v)}
-          />
-        </Section>
-        {selectComponentValue &&
-          map(components[selectComponentValue], (value, key) => (
-            <Section>
-              <Legend space>{`Field ${key}`}</Legend>
-              <ValidatedField
-                component={elements[value.component].default}
-                name={key}
-                {...value.props}
-              />
-            </Section>
-          ))}
-      </FormSection>
+      <Section>
+        <Legend space>Choose Component</Legend>
+        <ValidatedFieldFormik
+          component={MenuComponents}
+          name="component"
+          onChange={value => {
+            changeComponent(value)
+            setFieldValue('component', value)
+          }}
+        />
+      </Section>
+      {selectComponentValue &&
+        map(components[selectComponentValue], (value, key) => (
+          <Section>
+            <Legend space>{`Field ${key}`}</Legend>
+            <ValidatedFieldFormik
+              component={elements[value.component].default}
+              key={`${selectComponentValue}-${key}`}
+              name={key}
+              onChange={event => {
+                let value = {}
+                if (event.target) {
+                  value = event.target.value
+                } else {
+                  value = event
+                }
+                setFieldValue(key, value)
+              }}
+              {...value.props}
+            />
+          </Section>
+        ))}
       <Button primary type="submit">
         Update Component
       </Button>
@@ -73,10 +84,10 @@ const UpdateForm = ({ onSubmitFn, properties, changeTabs }) => (
   />
 )
 
-const onSubmit = (values, dispatch, { onSubmitFn, id, properties }) => {
-  if (!values.children.id || !values.children.component) return
+const onSubmit = (values, { onSubmitFn, properties }) => {
+  if (!values.id || !values.component) return
 
-  const children = omitBy(values.children, value => value === '')
+  const children = omitBy(values, value => value === '')
   onSubmitFn({ id: properties.id }, Object.assign({}, { children }))
 }
 
@@ -84,11 +95,11 @@ const ComponentForm = compose(
   withProps(({ properties }) => ({
     initialValues: { children: properties.properties },
   })),
-  reduxForm({
-    form: 'ComponentSubmit',
-    onSubmit,
-    enableReinitialize: true,
-    destroyOnUnmount: false,
+  withFormik({
+    displayName: 'ComponentSubmit',
+    mapPropsToValues: data => data.properties.properties,
+    handleSubmit: (props, { props: { onSubmitFn, id, properties } }) =>
+      onSubmit(props, { onSubmitFn, properties }),
   }),
   withState(
     'selectComponentValue',
diff --git a/packages/components/xpub-formbuilder/src/components/FormBuilder.jsx b/packages/components/xpub-formbuilder/src/components/FormBuilder.jsx
index 1d0e3350ceb53d64a4cdca1a47cc749e3c65d798..25c39956dd32239c31fd3fa5bd3b730b1f16c49b 100644
--- a/packages/components/xpub-formbuilder/src/components/FormBuilder.jsx
+++ b/packages/components/xpub-formbuilder/src/components/FormBuilder.jsx
@@ -87,7 +87,7 @@ const BuilderElement = ({ elements, changeProperties, deleteElement, form }) =>
         <ElementTitle dangerouslySetInnerHTML={createMarkup(value.title)} /> (
         {value.component})
       </Action>
-      <Action onClick={() => deleteElement(form, value)}>x</Action>
+      <Action onClick={() => deleteElement(form.id, value.id)}>x</Action>
     </Element>
   ))
 
diff --git a/packages/components/xpub-formbuilder/src/components/FormBuilderLayout.jsx b/packages/components/xpub-formbuilder/src/components/FormBuilderLayout.jsx
index 955ce625bdddda2909efb7d390651a587d54213e..84d4c667cad1ad59e46edc82c11ed45f715b94ca 100644
--- a/packages/components/xpub-formbuilder/src/components/FormBuilderLayout.jsx
+++ b/packages/components/xpub-formbuilder/src/components/FormBuilderLayout.jsx
@@ -18,7 +18,7 @@ const AdminStyled = styled(Admin)`
 `
 
 const FormBuilderLayout = ({
-  forms,
+  getForms,
   properties,
   deleteForm,
   deleteElement,
@@ -30,7 +30,7 @@ const FormBuilderLayout = ({
   activeTab,
 }) => {
   const Sections = []
-  forEach(forms, (form, key) => {
+  forEach(getForms, (form, key) => {
     Sections.push({
       content: (
         <FormBuilder
@@ -47,7 +47,7 @@ const FormBuilderLayout = ({
           key={`delete-form-${key}`}
           onClick={e => {
             e.preventDefault()
-            deleteForm(form)
+            deleteForm(form.id)
           }}
         >
           x
@@ -68,7 +68,6 @@ const FormBuilderLayout = ({
     key: 'new',
     label: '+ Add Form',
   })
-
   return (
     <Columns>
       <Tabs
@@ -76,7 +75,7 @@ const FormBuilderLayout = ({
         onChange={tab => {
           changeProperties({
             type: 'form',
-            properties: forms[tab],
+            properties: getForms[tab],
           })
           changeTabs(tab)
         }}
diff --git a/packages/components/xpub-formbuilder/src/components/FormBuilderLayout.md b/packages/components/xpub-formbuilder/src/components/FormBuilderLayout.md
index 5d833ec05827e28cbb38d1460a9bbf78198dda9d..ca3d1efb62d3a48bd517d1a319597203229e15af 100644
--- a/packages/components/xpub-formbuilder/src/components/FormBuilderLayout.md
+++ b/packages/components/xpub-formbuilder/src/components/FormBuilderLayout.md
@@ -38,7 +38,7 @@ initialState = {
 }
 ;<div style={{ position: 'relative', height: '100%' }}>
   <FormBuilderLayout
-    forms={forms}
+    getForms={forms}
     activeTab={state.activeTab}
     properties={state.properties}
     changeProperties={value => {
diff --git a/packages/components/xpub-formbuilder/src/components/FormBuilderPage.js b/packages/components/xpub-formbuilder/src/components/FormBuilderPage.js
index 7b3f5455203284a01d4c28b77934ec41798179a2..c9cc7dd795931f559690573575bf62517e64872c 100644
--- a/packages/components/xpub-formbuilder/src/components/FormBuilderPage.js
+++ b/packages/components/xpub-formbuilder/src/components/FormBuilderPage.js
@@ -1,57 +1,122 @@
-import { compose, withState, withHandlers } from 'recompose'
-import { connect } from 'react-redux'
-import { actions } from 'pubsweet-client'
-
-// import config from 'config'
-import { ConnectPage } from 'xpub-connect'
+import { compose, withState, withHandlers, withProps } from 'recompose'
+import { graphql } from 'react-apollo'
+import gql from 'graphql-tag'
+import { withLoader } from 'pubsweet-client'
 
 import FormBuilderLayout from './FormBuilderLayout'
 
-import {
-  createForms,
-  updateForms,
-  deleteForms,
-  deleteElements,
-  getForms,
-  updateElements,
-} from '../redux/FormBuilder'
+const createForm = gql`
+  mutation($form: String!) {
+    createForm(form: $form)
+  }
+`
 
-export default compose(
-  ConnectPage(() => [actions.getUsers(), getForms()]),
-  connect(
-    state => {
-      const { error } = state
-      const { forms } = state.forms
+const updateForm = gql`
+  mutation($form: String!, $id: String!) {
+    updateForm(form: $form, id: $id)
+  }
+`
 
-      return { error, forms: forms.forms }
-    },
-    (dispatch, { history }) => ({
-      deleteForm: form => dispatch(deleteForms(form)),
-      deleteElement: (form, element) => dispatch(deleteElements(form, element)),
-      updateForm: (form, formProperties) =>
-        dispatch(updateForms(form, formProperties)),
-      createForm: formProperties => dispatch(createForms(formProperties)),
-      updateElements: (form, formElements) =>
-        dispatch(updateElements(form, formElements)),
-    }),
-  ),
-  withState('properties', 'onChangeProperties', ({ forms }) => ({
+const updateFormElements = gql`
+  mutation($form: String!, $formId: String!) {
+    updateFormElements(form: $form, formId: $formId)
+  }
+`
+
+const deleteFormElement = gql`
+  mutation($formId: ID!, $elementId: ID!) {
+    deleteFormElement(formId: $formId, elementId: $elementId)
+  }
+`
+
+const deleteForms = gql`
+  mutation($formId: ID!) {
+    deleteForms(formId: $formId)
+  }
+`
+
+const query = gql`
+  query {
+    currentUser {
+      id
+      username
+      admin
+    }
+
+    getForms
+  }
+`
+
+export default compose(
+  graphql(query),
+  graphql(deleteForms, {
+    name: 'deleteForms',
+  }),
+  graphql(deleteFormElement, {
+    name: 'deleteFormElement',
+  }),
+  graphql(updateForm, {
+    name: 'updateForm',
+  }),
+  graphql(createForm, {
+    name: 'createForm',
+  }),
+  graphql(updateFormElements, {
+    name: 'updateFormElements',
+  }),
+  withLoader(),
+  withProps(props => ({
+    deleteForm: formId =>
+      props.deleteForms({
+        variables: {
+          formId,
+        },
+      }),
+    deleteElement: (formId, elementId) =>
+      props.deleteFormElement({
+        variables: {
+          formId,
+          elementId,
+        },
+      }),
+    updateForm: (form, formProperties) =>
+      props.updateForm({
+        variables: {
+          form: JSON.stringify(formProperties),
+          id: form.id,
+        },
+      }),
+    createForm: formProperties =>
+      props.createForm({
+        variables: {
+          form: JSON.stringify(formProperties),
+        },
+      }),
+    updateElements: (form, formElements) =>
+      props.updateFormElements({
+        variables: {
+          form: JSON.stringify(formElements),
+          formId: form.id,
+        },
+      }),
+  })),
+  withState('properties', 'onChangeProperties', ({ getForms }) => ({
     type: 'form',
-    properties: forms[0] || {},
+    properties: getForms[0] || {},
   })),
-  withState('activeTab', 'onChangeTab', ({ forms, activeTab }) =>
-    forms.length === 0 ? 'new' : 0,
+  withState('activeTab', 'onChangeTab', ({ getForms, activeTab }) =>
+    getForms.length === 0 ? 'new' : 0,
   ),
   withHandlers({
     changeProperties: ({
       onChangeProperties,
-      forms,
+      getForms,
       activeTab,
     }) => properties =>
       onChangeProperties(
         () =>
           Object.assign({}, properties, {
-            id: (forms[activeTab] || {}).id,
+            id: (getForms[activeTab] || {}).id,
           }) || undefined,
       ),
     changeTabs: ({ onChangeTab }) => activeTab => {
diff --git a/packages/components/xpub-formbuilder/src/components/FormProperties.jsx b/packages/components/xpub-formbuilder/src/components/FormProperties.jsx
index 507ed90b1d81acbca73a73bb571968cf92e428a7..c50d59fd1eab546ff2db9b483c379673d844eff8 100644
--- a/packages/components/xpub-formbuilder/src/components/FormProperties.jsx
+++ b/packages/components/xpub-formbuilder/src/components/FormProperties.jsx
@@ -1,10 +1,10 @@
 import React from 'react'
+import { withFormik } from 'formik'
 import { pick, isEmpty } from 'lodash'
 import styled from 'styled-components'
 import { compose, withProps, withState, withHandlers } from 'recompose'
-import { Button, TextField, ValidatedField } from '@pubsweet/ui'
+import { Button, TextField, ValidatedFieldFormik } from '@pubsweet/ui'
 import { th } from '@pubsweet/ui-toolkit'
-import { FormSection, reduxForm } from 'redux-form'
 import { AbstractField, RadioBox } from './builderComponents'
 import { Page, Heading } from './molecules/Page'
 
@@ -22,11 +22,7 @@ export const Section = styled.div`
   margin: calc(${th('gridUnit')} * 6) 0;
 `
 
-const onSubmit = (
-  values,
-  dispatch,
-  { onSubmitFn, properties, mode, changeTabs },
-) => {
+const onSubmit = (values, { onSubmitFn, properties, mode }) => {
   if (mode === 'create') {
     onSubmitFn(Object.assign({}, values))
   } else {
@@ -40,6 +36,8 @@ const FormProperties = ({
   mode,
   selectPopup,
   showPopupValue,
+  values,
+  setFieldValue,
 }) =>
   isEmpty(properties.properties) && mode !== 'create' ? (
     <Page>
@@ -49,58 +47,65 @@ const FormProperties = ({
     <Page>
       <form onSubmit={handleSubmit}>
         <Heading>{mode === 'create' ? 'Create Form' : 'Update Form'}</Heading>
-        <FormSection name="">
-          <Section id="form.id" key="form.id">
-            <Legend>ID Form</Legend>
-            <ValidatedField component={idText} name="id" />
-          </Section>
-          <Section id="form.name" key="form.name">
-            <Legend>Form Name</Legend>
-            <ValidatedField component={nameText} name="name" />
-          </Section>
-          <Section id="form.description" key="form.description">
+        <Section id="form.id" key="form.id">
+          <Legend>ID Form</Legend>
+          <ValidatedFieldFormik component={idText} name="id" />
+        </Section>
+        <Section id="form.name" key="form.name">
+          <Legend>Form Name</Legend>
+          <ValidatedFieldFormik component={nameText} name="name" />
+        </Section>
+        <Section id="form.description" key="form.description">
+          <Legend>Description</Legend>
+          <ValidatedFieldFormik
+            component={AbstractField.default}
+            name="description"
+            onChange={val => {
+              setFieldValue('description', val)
+            }}
+          />
+        </Section>
+        <Section id="form.submitpopup" key="form.submitpopup">
+          <Legend>Submit on Popup</Legend>
+          <ValidatedFieldFormik
+            component={RadioBox.default}
+            inline
+            name="haspopup"
+            onChange={(input, value) => {
+              setFieldValue('haspopup', input)
+              selectPopup(input)
+            }}
+            options={[
+              {
+                label: 'Yes',
+                value: 'true',
+              },
+              {
+                label: 'No',
+                value: 'false',
+              },
+            ]}
+          />
+        </Section>
+        {showPopupValue === 'true' && [
+          <Section id="popup.title" key="popup.title">
+            <Legend>Popup Title</Legend>
+            <ValidatedFieldFormik component={nameText} name="popuptitle" />
+          </Section>,
+          <Section id="popup.description" key="popup.description">
             <Legend>Description</Legend>
-            <ValidatedField
+            <ValidatedFieldFormik
               component={AbstractField.default}
-              name="description"
-            />
-          </Section>
-          <Section id="form.submitpopup" key="form.submitpopup">
-            <Legend>Submit on Popup</Legend>
-            <ValidatedField
-              component={RadioBox.default}
-              inline
-              name="haspopup"
-              onChange={(input, value) => selectPopup(value)}
-              options={[
-                {
-                  label: 'Yes',
-                  value: 'true',
-                },
-                {
-                  label: 'No',
-                  value: 'false',
-                },
-              ]}
+              name="popupdescription"
+              onChange={val => {
+                setFieldValue('popupdescription', val)
+              }}
             />
-          </Section>
-          {showPopupValue === 'true' && [
-            <Section id="popup.title" key="popup.title">
-              <Legend>Popup Title</Legend>
-              <ValidatedField component={nameText} name="popuptitle" />
-            </Section>,
-            <Section id="popup.description" key="popup.description">
-              <Legend>Description</Legend>
-              <ValidatedField
-                component={AbstractField.default}
-                name="popupdescription"
-              />
-            </Section>,
-          ]}
-          <Button primary type="submit">
-            {mode === 'create' ? 'Create Form' : 'Update Form'}
-          </Button>
-        </FormSection>
+          </Section>,
+        ]}
+        <Button primary type="submit">
+          {mode === 'create' ? 'Create Form' : 'Update Form'}
+        </Button>
       </form>
     </Page>
   )
@@ -127,10 +132,10 @@ export default compose(
   withHandlers({
     changeShowPopup: ({ selectPopup }) => value => selectPopup(() => value),
   }),
-  reduxForm({
-    form: 'FormSubmit',
-    onSubmit,
-    enableReinitialize: true,
-    destroyOnUnmount: false,
+  withFormik({
+    displayName: 'FormSubmit',
+    mapPropsToValues: data => data.properties.properties,
+    handleSubmit: (props, { props: { mode, onSubmitFn, properties } }) =>
+      onSubmit(props, { mode, onSubmitFn, properties }),
   }),
 )(FormProperties)
diff --git a/packages/components/xpub-formbuilder/src/components/builderComponents/Menu.js b/packages/components/xpub-formbuilder/src/components/builderComponents/Menu.js
index 1e59339f6b770149e55f6c078ecfbe4c161233bd..b64e39bd16a45f047072d7ac6c7da8a0253e6fed 100644
--- a/packages/components/xpub-formbuilder/src/components/builderComponents/Menu.js
+++ b/packages/components/xpub-formbuilder/src/components/builderComponents/Menu.js
@@ -1,5 +1,5 @@
 import React from 'react'
-import { Menu, TextField, ValidatedField } from '@pubsweet/ui'
+import { Menu, TextField, ValidatedFieldFormik } from '@pubsweet/ui'
 import { compose, withState, withHandlers } from 'recompose'
 import { Legend, Section } from '../styles'
 
@@ -14,7 +14,7 @@ const ValidationMenu = input => (
     {input.selectelement && input.selectelement !== 'required' && (
       <Section>
         <Legend space>FIeld Min / Max</Legend>
-        <ValidatedField
+        <ValidatedFieldFormik
           component={TextField}
           name={`validateValue.${input.selectelement}`}
         />
diff --git a/packages/components/xpub-formbuilder/src/components/builderComponents/OptionsField.js b/packages/components/xpub-formbuilder/src/components/builderComponents/OptionsField.js
index 5b2c40877b3c215ea466f5672ecc1c92c8d5b270..f429cf6180950dea655f6681720876a5a7f66c7d 100644
--- a/packages/components/xpub-formbuilder/src/components/builderComponents/OptionsField.js
+++ b/packages/components/xpub-formbuilder/src/components/builderComponents/OptionsField.js
@@ -1,7 +1,7 @@
 import React from 'react'
 import styled from 'styled-components'
-import { FieldArray } from 'redux-form'
-import { TextField, ValidatedField, Button } from '@pubsweet/ui'
+import { FieldArray } from 'formik'
+import { TextField, ValidatedFieldFormik, Button } from '@pubsweet/ui'
 
 const Inline = styled.div`
   display: inline-block;
@@ -29,41 +29,38 @@ const valueInput = input => (
   <TextField label="Value Option" placeholder="Enter value…" {...input} />
 )
 
-const renderOptions = ({
-  fields = {},
-  meta: { touched, error, submitFailed },
-}) => (
+const renderOptions = ({ form: { values }, push, remove }) => (
   <ul>
     <UnbulletedList>
       <li>
-        <Button onClick={() => fields.push()} plain type="button">
+        <Button onClick={() => push()} plain type="button">
           Add another option
         </Button>
       </li>
-      {fields.map((option, index) => (
+      {(values.options || []).map((option, index) => (
         <li>
           <Spacing>
             <Option>
               Option:&nbsp;
-              {fields.length > 1 && (
-                <Button onClick={() => fields.remove(index)} type="button">
+              {values.options.length > 1 && (
+                <Button onClick={() => remove(index)} type="button">
                   Remove
                 </Button>
               )}
             </Option>
             <div>
               <Inline>
-                <ValidatedField
+                <ValidatedFieldFormik
                   component={keyInput}
-                  name={`${option}.label`}
+                  name={`options.${index}.label`}
                   required
                 />
               </Inline>
 
               <Inline>
-                <ValidatedField
+                <ValidatedFieldFormik
                   component={valueInput}
-                  name={`${option}value`}
+                  name={`options.${index}.value`}
                   required
                 />
               </Inline>
diff --git a/packages/components/xpub-formbuilder/src/test/FormBuilderLayout.test.js b/packages/components/xpub-formbuilder/src/test/FormBuilderLayout.test.js
index f91a4369231c6e86d85ed4c8f4041adae433f972..bc7b005ffbe9b1a58319ac5b1f44a411ecd4311a 100644
--- a/packages/components/xpub-formbuilder/src/test/FormBuilderLayout.test.js
+++ b/packages/components/xpub-formbuilder/src/test/FormBuilderLayout.test.js
@@ -1,13 +1,11 @@
 import React from 'react'
+import faker from 'faker'
 import Enzyme, { mount } from 'enzyme'
 import { MemoryRouter } from 'react-router-dom'
-import { Provider } from 'react-redux'
+import { MockedProvider } from 'react-apollo/test-utils'
 import Adapter from 'enzyme-adapter-react-16'
 import { ThemeProvider } from 'styled-components'
-import { combineReducers } from 'redux'
-import configureMockStore from 'redux-mock-store'
-import thunk from 'redux-thunk'
-import { reducers } from 'pubsweet-client'
+import gql from 'graphql-tag'
 
 import FormProperties from '../components/FormProperties'
 import FormBuilderLayout from '../components/FormBuilderLayout'
@@ -43,31 +41,35 @@ jest.mock('config', () => ({
   },
 }))
 
-const reducer = combineReducers(reducers)
+const query = gql`
+  query {
+    currentUser {
+      id
+      username
+      admin
+    }
 
-const middlewares = [thunk]
-const mockStore = () =>
-  configureMockStore(middlewares)(actions =>
-    Object.assign(
-      actions.reduce(reducer, {
-        currentUser: { isAuthenticated: true },
-      }),
-      (actions.users || []).reduce(reducer, {
-        users: {
-          users: [
-            { id: '1', username: 'author' },
-            { id: '2', username: 'managing Editor' },
-          ],
-        },
-      }),
-      { forms: { forms: { noforms } } },
-    ),
-  )
+    getForms
+  }
+`
+
+const mocks = [
+  {
+    request: {
+      query,
+    },
+    result: {
+      data: {
+        currentUser: { id: faker.random.uuid(), username: 'test', admin: true },
+        getForms: noforms,
+      },
+    },
+  },
+]
 
 describe('FormBuilder Layout', () => {
-  const makeWrapper = (props = {}) => {
-    const store = mockStore()
-    return mount(
+  const makeWrapper = (props = {}) =>
+    mount(
       <MemoryRouter>
         <ThemeProvider
           theme={{
@@ -75,13 +77,12 @@ describe('FormBuilder Layout', () => {
             colorSecondary: '#E7E7E7',
           }}
         >
-          <Provider store={store}>
+          <MockedProvider addTypename={false} mocks={mocks}>
             <FormBuilderLayout {...props} />
-          </Provider>
+          </MockedProvider>
         </ThemeProvider>
       </MemoryRouter>,
     )
-  }
 
   it('shows just the create form tab', () => {
     const formbuilder = makeWrapper({
@@ -117,7 +118,7 @@ describe('FormBuilder Layout', () => {
         properties: testforms[0],
       },
       activeTab: 0,
-      forms: testforms,
+      getForms: testforms,
     })
 
     expect(
diff --git a/packages/components/xpub-formbuilder/src/test/FormBuilderPage.integration.test.js b/packages/components/xpub-formbuilder/src/test/FormBuilderPage.integration.test.js
index 9ebe3221ea296f8cd1fde2f46bb0d3e6b6b064e1..11db339fa1177b09a589ee8857b9c096edec5106 100644
--- a/packages/components/xpub-formbuilder/src/test/FormBuilderPage.integration.test.js
+++ b/packages/components/xpub-formbuilder/src/test/FormBuilderPage.integration.test.js
@@ -1,16 +1,14 @@
 import React from 'react'
+import faker from 'faker'
 import { MemoryRouter } from 'react-router-dom'
-import { Provider } from 'react-redux'
-import { combineReducers } from 'redux'
-import configureMockStore from 'redux-mock-store'
-import thunk from 'redux-thunk'
+import { MockedProvider } from 'react-apollo/test-utils'
+import gql from 'graphql-tag'
+
 import Enzyme, { mount } from 'enzyme'
 import Adapter from 'enzyme-adapter-react-16'
 
 import { ThemeProvider } from 'styled-components'
 
-import { reducers } from 'pubsweet-client'
-
 import FormBuilderPage from '../components/FormBuilderPage'
 
 import forms from './config/test.json'
@@ -52,45 +50,43 @@ jest.mock('config', () => {
   }
 })
 
-// Mock out the API
-jest.mock('pubsweet-client/src/helpers/api', () => ({
-  get: jest.fn(url => {
-    // Whatever the request is, return an empty array
-    const response = []
-    return new Promise(resolve => resolve(response))
-  }),
-}))
-
-jest.mock('pubsweet-client/src/helpers/Authorize', () => 'Authorize')
+jest.mock(
+  'pubsweet-client/src/helpers/AuthorizeWithGraphQL',
+  () => 'AuthorizeWithGraphQL',
+)
 
 global.window.localStorage = {
   getItem: jest.fn(() => 'tok123'),
 }
 
-const reducer = combineReducers(reducers)
+const query = gql`
+  query {
+    currentUser {
+      id
+      username
+      admin
+    }
 
-const middlewares = [thunk]
-const mockStore = () =>
-  configureMockStore(middlewares)(actions =>
-    Object.assign(
-      actions.reduce(reducer, {
-        currentUser: { isAuthenticated: true },
-      }),
-      (actions.users || []).reduce(reducer, {
-        users: {
-          users: [
-            { id: '1', username: 'author' },
-            { id: '2', username: 'managing Editor' },
-          ],
-        },
-      }),
-      { forms: { forms: { forms } } },
-    ),
-  )
+    getForms
+  }
+`
+
+const mocks = [
+  {
+    request: {
+      query,
+    },
+    result: {
+      data: {
+        currentUser: { id: faker.random.uuid(), username: 'test', admin: true },
+        getForms: forms,
+      },
+    },
+  },
+]
 
 describe('FormBuilderPage', () => {
   it('runs', done => {
-    const store = mockStore()
     const page = mount(
       <MemoryRouter>
         <ThemeProvider
@@ -99,20 +95,20 @@ describe('FormBuilderPage', () => {
             colorSecondary: '#E7E7E7',
           }}
         >
-          <Provider store={store}>
+          <MockedProvider addTypename={false} mocks={mocks}>
             <FormBuilderPage />
-          </Provider>
+          </MockedProvider>
         </ThemeProvider>
       </MemoryRouter>,
     )
 
-    setImmediate(() => {
+    setTimeout(() => {
       page.update()
       expect(page.find('#builder-element').children()).toHaveLength(
         forms[0].children.length,
       )
       expect(page.find(FormProperties)).toHaveLength(1)
       done()
-    })
+    }, 1000)
   })
 })
diff --git a/packages/components/xpub-manuscript/src/components/Manuscript.js b/packages/components/xpub-manuscript/src/components/Manuscript.js
index cbf7bec6e7c7a8d31e267671e47b6e558d455d4a..679963765faba00b40029710a5a879407168129d 100644
--- a/packages/components/xpub-manuscript/src/components/Manuscript.js
+++ b/packages/components/xpub-manuscript/src/components/Manuscript.js
@@ -28,9 +28,8 @@ const Manuscript = ({
   history,
   updateManuscript,
 }) =>
-  file &&
-  file.fileType ===
-    'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ? (
+  file.mimeType ===
+  'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ? (
     <ManuScript>
       <Wax
         fileUpload={fileUpload}
diff --git a/packages/components/xpub-manuscript/src/components/ManuscriptPage.js b/packages/components/xpub-manuscript/src/components/ManuscriptPage.js
index a292018a7a052e945e584ff0277ba502658db472..9e4d83fa76f2b458d1946fcdd2750e82c3229b65 100644
--- a/packages/components/xpub-manuscript/src/components/ManuscriptPage.js
+++ b/packages/components/xpub-manuscript/src/components/ManuscriptPage.js
@@ -11,8 +11,8 @@ const fragmentFields = `
   status
   files {
     id
-    type
     fileType
+    mimeType
   }
   meta {
     title
@@ -42,9 +42,9 @@ export default compose(
       },
     }),
   }),
-  withProps(({ data }) => ({
-    content: data.manuscript.content,
-    file: data.files.filter(file => file.type === 'manuscript'),
-  })),
   withLoader(),
+  withProps(({ manuscript }) => ({
+    content: manuscript.meta.source,
+    file: manuscript.files.find(file => file.fileType === 'manuscript') || {},
+  })),
 )(Manuscript)
diff --git a/packages/components/xpub-review/package.json b/packages/components/xpub-review/package.json
index b0563b600c108e6d1378d9cb17f27bc842071084..55dac7a471feb4d5e6134b9b888ae765a5e312a9 100644
--- a/packages/components/xpub-review/package.json
+++ b/packages/components/xpub-review/package.json
@@ -48,6 +48,7 @@
   },
   "peerDependencies": {
     "apollo-client-preset": "^1.0.8",
+    "config": "^3.0.1",
     "formik": "^1.4.2",
     "pubsweet-client": ">=2.1.0",
     "react": ">=16",
diff --git a/packages/components/xpub-review/src/components/DecisionPage.js b/packages/components/xpub-review/src/components/DecisionPage.js
index c8599f06e5a22ccdab66da712e9854cfc9c3d469..29127570b5365b34a52140e01c68de1fdb5e428a 100644
--- a/packages/components/xpub-review/src/components/DecisionPage.js
+++ b/packages/components/xpub-review/src/components/DecisionPage.js
@@ -3,10 +3,36 @@ import { graphql } from 'react-apollo'
 import { gql } from 'apollo-client-preset'
 import { withFormik } from 'formik'
 import { withLoader } from 'pubsweet-client'
+import { getCommentContent } from './review/util'
 
-import uploadFile from 'xpub-upload'
 import DecisionLayout from './decision/DecisionLayout'
 
+const reviewFields = `
+  id
+  created
+  updated
+  comments {
+    type
+    content
+    files {
+      id
+      created
+      label
+      filename
+      fileType
+      mimeType
+      size
+      url
+    }
+  }
+  isDecision
+  recommendation
+  user {
+    id
+    username
+  }
+`
+
 const fragmentFields = `
   id
   created
@@ -15,68 +41,37 @@ const fragmentFields = `
     created
     label
     filename
+    fileType
     mimeType
-    type
     size
     url
   }
   reviews {
-    open
-    recommendation
-    created
-    comments {
-      type
-      content
-      files {
-        type
-        id
-        label
-        url
-        filename
-      }
-    }
-    user {
-      id
-      username
-    }
-  }
-  decision {
-    status
-    created
-    comments {
-      type
-      content
-      files {
-        type
-        id
-        label
-        url
-        filename
-      }
-    }
-    user {
-      id
-      username
-    }
+    ${reviewFields}
   }
+  decision
   teams {
     id
-    role
+    name
+    teamType
     object {
-      id
+      objectId
+      objectType
     }
     objectType
     members {
+      id
+      username
+    }
+    status {
+      user
       status
-      user {
-        id
-        username
-      }
     }
   }
   status
   meta {
     title
+    source
     abstract
     declarations {
       openData
@@ -93,8 +88,6 @@ const fragmentFields = `
       date
     }
     notes {
-      id
-      created
       notesType
       content
     }
@@ -129,6 +122,37 @@ const query = gql`
   }
 `
 
+const updateReviewMutation = gql`
+  mutation($id: ID, $input: ReviewInput) {
+    updateReview(id: $id, input: $input) {
+      ${reviewFields}
+    }
+  }
+`
+
+const uploadReviewFilesMutation = gql`
+  mutation($file: Upload!) {
+    upload(file: $file) {
+      url
+    }
+  }
+`
+
+const createFileMutation = gql`
+  mutation($file: Upload!) {
+    createFile(file: $file) {
+      id
+      created
+      label
+      filename
+      fileType
+      mimeType
+      size
+      url
+    }
+  }
+`
+
 const submitMutation = gql`
   mutation($id: ID!, $input: String) {
     submitManuscript(id: $id, input: $input) {
@@ -146,13 +170,51 @@ export default compose(
       },
     }),
   }),
+  graphql(uploadReviewFilesMutation, { name: 'uploadReviewFilesMutation' }),
+  graphql(updateReviewMutation, { name: 'updateReviewMutation' }),
+  graphql(createFileMutation, {
+    props: ({ mutate, ownProps: { match } }) => ({
+      createFile: file => {
+        mutate({
+          variables: {
+            file,
+          },
+          update: (proxy, { data: { createFile } }) => {
+            const data = proxy.readQuery({
+              query,
+              variables: {
+                id: match.params.version,
+              },
+            })
+
+            data.manuscript.reviews.map(review => {
+              if (review.id === file.objectId) {
+                review.comments.map(comment => {
+                  if (comment.type === createFile.fileType) {
+                    comment.files = [createFile]
+                  }
+                  return comment
+                })
+              }
+              return review
+            })
+
+            proxy.writeQuery({ query, data })
+          },
+        })
+      },
+    }),
+  }),
   graphql(submitMutation, {
     props: ({ mutate, ownProps }) => ({
-      onSubmit: (manuscript, { history }) => {
+      completeDecision: ({ history, manuscript }) => {
         mutate({
           variables: {
             id: manuscript.id,
-            input: JSON.stringify({ decision: manuscript.decision }),
+            input: JSON.stringify({
+              decision: manuscript.reviews.find(review => review.isDecision)
+                .recommendation,
+            }),
           },
         }).then(() => {
           history.push('/')
@@ -161,15 +223,104 @@ export default compose(
     }),
   }),
   withLoader(),
-  withProps(({ getFile, manuscript, match: { params: { journal } } }) => ({
-    journal: { id: journal },
-    uploadFile,
-  })),
+  withProps(
+    ({
+      currentUser,
+      manuscript,
+      createFile,
+      updateReviewMutation,
+      uploadReviewFilesMutation,
+      match: {
+        params: { journal },
+      },
+    }) => ({
+      journal: { id: journal },
+      updateReview: (data, file) => {
+        const reviewData = {
+          isDecision: true,
+          manuscriptId: manuscript.id,
+        }
+
+        if (data.comment) {
+          reviewData.comments = [data.comment]
+        }
+
+        if (data.recommendation) {
+          reviewData.recommendation = data.recommendation
+        }
+
+        const review =
+          manuscript.reviews.find(review => review.isDecision) || {}
+        return updateReviewMutation({
+          variables: {
+            id: review.id || undefined,
+            input: reviewData,
+          },
+          update: (proxy, { data: { updateReview } }) => {
+            const data = proxy.readQuery({
+              query,
+              variables: {
+                id: manuscript.id,
+              },
+            })
+            const reviewIndex = data.manuscript.reviews.findIndex(
+              review => review.id === updateReview.id,
+            )
+            if (reviewIndex < 0) {
+              data.manuscript.reviews.push(updateReview)
+            } else {
+              data.manuscript.reviews[reviewIndex] = updateReview
+            }
+            proxy.writeQuery({ query, data })
+          },
+        })
+      },
+      uploadFile: (file, updateReview, type) =>
+        uploadReviewFilesMutation({
+          variables: {
+            file,
+          },
+        }).then(({ data }) => {
+          const newFile = {
+            url: data.upload.url,
+            filename: file.name,
+            size: file.size,
+            object: 'Review',
+            objectId: updateReview.id,
+            fileType: type,
+          }
+          createFile(newFile)
+        }),
+    }),
+  ),
   withFormik({
-    initialValues: {},
-    mapPropsToValues: ({ manuscript }) => manuscript,
+    mapPropsToValues: props =>
+      props.manuscript.reviews.find(review => review.isDecision) || {
+        comments: [],
+        recommendation: null,
+      },
+    isInitialValid: ({ manuscript }) => {
+      const rv = manuscript.reviews.find(review => review.isDecision) || {}
+      const isRecommendation = rv.recommendation != null
+      const isCommented = getCommentContent(rv, 'note') !== ''
+
+      return isCommented && isRecommendation
+    },
+    validate: (values, props) => {
+      const errors = {}
+      if (getCommentContent(values, 'note') === '') {
+        errors.comments = 'Required'
+      }
+
+      if (values.recommendation === null) {
+        errors.recommendation = 'Required'
+      }
+      return errors
+    },
     displayName: 'decision',
-    handleSubmit: (props, { props: { onSubmit, history } }) =>
-      onSubmit(props, { history }),
+    handleSubmit: (
+      props,
+      { props: { completeDecision, history, manuscript } },
+    ) => completeDecision({ history, manuscript }),
   }),
 )(DecisionLayout)
diff --git a/packages/components/xpub-review/src/components/ReviewPage.js b/packages/components/xpub-review/src/components/ReviewPage.js
index d665ca67109367af7d9e803265f3894bb1a74a82..9b95a98603db87f4b4fe230fbd60de4ba31cbe78 100644
--- a/packages/components/xpub-review/src/components/ReviewPage.js
+++ b/packages/components/xpub-review/src/components/ReviewPage.js
@@ -1,12 +1,52 @@
-// import { debounce } from 'lodash'
 import { compose, withProps } from 'recompose'
 import { graphql } from 'react-apollo'
 import { gql } from 'apollo-client-preset'
 import { withFormik } from 'formik'
 import { withLoader } from 'pubsweet-client'
-// import uploadFile from 'xpub-upload'
+import { cloneDeep } from 'lodash'
+import { getCommentContent } from './review/util'
 import ReviewLayout from '../components/review/ReviewLayout'
 
+const reviewFields = `
+  id
+  created
+  updated
+  comments {
+    type
+    content
+    files {
+      id
+      created
+      label
+      filename
+      fileType
+      mimeType
+      size
+      url
+    }
+  }
+  isDecision
+  recommendation
+  user {
+    id
+    username
+  }
+`
+
+const teamFields = `
+  id
+  name
+  teamType
+  object {
+    objectId
+    objectType
+  }
+  members {
+    id
+    username
+  }
+`
+
 const fragmentFields = `
   id
   created
@@ -15,68 +55,37 @@ const fragmentFields = `
     created
     label
     filename
+    fileType
     mimeType
-    type
     size
     url
   }
   reviews {
-    open
-    recommendation
-    created
-    comments {
-      type
-      content
-      files {
-        type
-        id
-        label
-        url
-        filename
-      }
-    }
-    user {
-      id
-      username
-    }
-  }
-  decision {
-    status
-    created
-    comments {
-      type
-      content
-      files {
-        type
-        id
-        label
-        url
-        filename
-      }
-    }
-    user {
-      id
-      username
-    }
+    ${reviewFields}
   }
+  decision
   teams {
     id
-    role
+    name
+    teamType
     object {
-      id
+      objectId
+      objectType
     }
     objectType
     members {
+      id
+      username
+    }
+    status {
+      user
       status
-      user {
-        id
-        username
-      }
     }
   }
   status
   meta {
     title
+    source
     abstract
     declarations {
       openData
@@ -93,8 +102,6 @@ const fragmentFields = `
       date
     }
     notes {
-      id
-      created
       notesType
       content
     }
@@ -129,11 +136,41 @@ const query = gql`
   }
 `
 
-const submitReviewMutation = gql`
-  mutation($id: ID!, $input: String) {
-    updateManuscript(id: $id, input: $input) {
+const updateTeam = gql`
+  mutation($id: ID!, $input: TeamInput) {
+    updateTeam(id: $id, input: $input) {
+      ${teamFields}
+    } 
+  }
+`
+
+const updateReviewMutation = gql`
+  mutation($id: ID, $input: ReviewInput) {
+    updateReview(id: $id, input: $input) {
+      ${reviewFields}
+    }
+  }
+`
+
+const uploadReviewFilesMutation = gql`
+  mutation($file: Upload!) {
+    upload(file: $file) {
+      url
+    }
+  }
+`
+
+const createFileMutation = gql`
+  mutation($file: Upload!) {
+    createFile(file: $file) {
       id
-      ${fragmentFields}
+      created
+      label
+      filename
+      fileType
+      mimeType
+      size
+      url
     }
   }
 `
@@ -143,40 +180,156 @@ export default compose(
     options: ({ match }) => ({
       variables: {
         id: match.params.version,
-        form: 'submit',
       },
     }),
   }),
-  graphql(submitReviewMutation, {
-    props: ({ mutate, ownProps: { data } }) => ({
-      onSubmit: (review, { history }) => {
+  graphql(uploadReviewFilesMutation, { name: 'uploadReviewFilesMutation' }),
+  graphql(updateReviewMutation, { name: 'updateReviewMutation' }),
+  graphql(updateTeam, { name: 'updateTeam' }),
+  graphql(createFileMutation, {
+    props: ({ mutate, ownProps: { match } }) => ({
+      createFile: file => {
         mutate({
           variables: {
-            id: data.manuscript.id,
-            input: JSON.stringify(review),
+            file,
+          },
+          update: (proxy, { data: { createFile } }) => {
+            const data = proxy.readQuery({
+              query,
+              variables: {
+                id: match.params.version,
+              },
+            })
+
+            data.manuscript.reviews.map(review => {
+              if (review.id === file.objectId) {
+                review.comments.map(comment => {
+                  if (comment.type === createFile.fileType) {
+                    comment.files = [createFile]
+                  }
+                  return comment
+                })
+              }
+              return review
+            })
+
+            proxy.writeQuery({ query, data })
           },
-        }).then(() => {
-          history.push('/')
         })
       },
     }),
   }),
   withLoader(),
-  withProps(({ manuscript, currentUser, match: { params: { journal } } }) => ({
-    journal: { id: journal },
-    review: manuscript.reviews.find(
-      review => review.user.id === currentUser.id,
-    ),
-    status: manuscript.teams
-      .find(team => team.role === 'reviewerEditor')
-      .members.find(member => member.user.id === currentUser.id).status,
-  })),
+  withProps(
+    ({
+      manuscript,
+      currentUser,
+      match: {
+        params: { journal },
+      },
+      updateReviewMutation,
+      uploadReviewFilesMutation,
+      updateTeam,
+      createFile,
+    }) => ({
+      journal: { id: journal },
+      review:
+        manuscript.reviews.find(
+          review => review.user.id === currentUser.id && !review.isDecision,
+        ) || {},
+      status: (
+        (
+          (
+            manuscript.teams.find(team => team.teamType === 'reviewerEditor') ||
+            {}
+          ).status || []
+        ).find(status => status.user === currentUser.id) || {}
+      ).status,
+      updateReview: (review, file) => {
+        ;(review.comments || []).map(comment => {
+          delete comment.files
+          delete comment.__typename
+          return comment
+        })
+
+        const reviewData = {
+          recommendation: review.recommendation,
+          comments: review.comments,
+          manuscriptId: manuscript.id,
+        }
+
+        return updateReviewMutation({
+          variables: {
+            id: review.id || undefined,
+            input: reviewData,
+          },
+          update: (proxy, { data: { updateReview } }) => {
+            const data = proxy.readQuery({
+              query,
+              variables: {
+                id: manuscript.id,
+              },
+            })
+            let reviewIndex = data.manuscript.reviews.findIndex(
+              review => review.id === updateReview.id,
+            )
+            reviewIndex = reviewIndex < 0 ? 0 : reviewIndex
+            data.manuscript.reviews[reviewIndex] = updateReview
+            proxy.writeQuery({ query, data })
+          },
+        })
+      },
+      uploadFile: (file, updateReview, type) =>
+        uploadReviewFilesMutation({
+          variables: {
+            file,
+          },
+        }).then(({ data }) => {
+          const newFile = {
+            url: data.upload.url,
+            filename: file.name,
+            mimeType: file.type,
+            size: file.size,
+            object: 'Review',
+            objectId: updateReview.id,
+            fileType: type,
+          }
+          createFile(newFile)
+        }),
+      completeReview: history => {
+        const team = cloneDeep(manuscript.teams).find(
+          team => team.teamType === 'reviewerEditor',
+        )
+
+        team.status.map(status => {
+          if (status.user === currentUser.id) {
+            status.status = 'completed'
+          }
+          delete status.__typename
+          return status
+        })
+        updateTeam({
+          variables: {
+            id: team.id,
+            input: {
+              status: team.status,
+            },
+          },
+        }).then(() => {
+          history.push('/')
+        })
+      },
+    }),
+  ),
   withFormik({
-    initialValues: {},
-    mapPropsToValues: ({ manuscript, currentUser }) =>
-      manuscript.reviews.find(review => review.user.id === currentUser.id),
+    isInitialValid: ({ review }) => {
+      const isRecommendation = review.recommendation !== ''
+      const isCommented = getCommentContent(review, 'note') !== ''
+
+      return isCommented && isRecommendation
+    },
     displayName: 'review',
-    handleSubmit: (props, { props: { onSubmit, history } }) =>
-      onSubmit(props, { history }),
+    handleSubmit: (props, { props: { completeReview, history } }) =>
+      completeReview(history),
   }),
 )(ReviewLayout)
diff --git a/packages/components/xpub-review/src/components/ReviewersPage.js b/packages/components/xpub-review/src/components/ReviewersPage.js
index 825519a37c9e69fe027fcd8413889d40a8354b51..873ec9d69a0a2cff85031514563ca7c470428792 100644
--- a/packages/components/xpub-review/src/components/ReviewersPage.js
+++ b/packages/components/xpub-review/src/components/ReviewersPage.js
@@ -1,12 +1,33 @@
-import { compose, withProps } from 'recompose'
+import { compose, withProps, withHandlers } from 'recompose'
+import { withFormik } from 'formik'
 import { graphql } from 'react-apollo'
 import { gql } from 'apollo-client-preset'
 import { withLoader } from 'pubsweet-client'
+import { cloneDeep, omit } from 'lodash'
 
 import Reviewers from '../components/reviewers/Reviewers'
-import ReviewerFormContainer from '../components/reviewers/ReviewerFormContainer'
 import ReviewerContainer from '../components/reviewers/ReviewerContainer'
 
+const teamFields = `
+  id
+  role
+  teamType
+  name
+  object {
+    objectId
+    objectType
+  }
+  objectType
+  members {
+    id
+    username
+  }
+  status {
+    user
+    status
+  }
+`
+
 const fragmentFields = `
   id
   created
@@ -16,7 +37,7 @@ const fragmentFields = `
     label
     filename
     mimeType
-    type
+    fileType
     size
     url
   }
@@ -28,26 +49,7 @@ const fragmentFields = `
       type
       content
       files {
-        type
-        id
-        label
-        url
-        filename
-      }
-    }
-    user {
-      id
-      username
-    }
-  }
-  decision {
-    status
-    created
-    comments {
-      type
-      content
-      files {
-        type
+        fileType
         id
         label
         url
@@ -59,72 +61,25 @@ const fragmentFields = `
       username
     }
   }
+  decision
   teams {
-    id
-    role
-    object {
-      id
-    }
-    objectType
-    members {
-      status
-      user {
-        id
-        username
-      }
-    }
+    ${teamFields}
   }
   status
-  meta {
-    title
-    abstract
-    declarations {
-      openData
-      openPeerReview
-      preregistered
-      previouslySubmitted
-      researchNexus
-      streamlinedReview
-    }
-    articleSections
-    articleType
-    history {
-      type
-      date
-    }
-    notes {
-      id
-      created
-      notesType
-      content
-    }
-    keywords
-  }
-  suggestions {
-    reviewers {
-      opposed
-      suggested
-    }
-    editors {
-      opposed
-      suggested
+`
+
+const createTeamMutation = gql`
+  mutation($input: TeamInput!) {
+    createTeam(input: $input) {
+      ${teamFields}
     }
   }
 `
 
-const teamFields = `
-  id
-  role
-  name
-  object {
-    id
-  }
-  objectType
-  members {
-    status
-    user {
-      id
-      username
+const updateTeamMutation = gql`
+  mutation($id: ID, $input: TeamInput) {
+    updateTeam(id: $id, input: $input) {
+      ${teamFields}
     }
   }
 `
@@ -153,6 +108,81 @@ const query = gql`
   }
 `
 
+const update = match => (proxy, { data: { updateTeam, createTeam } }) => {
+  const data = proxy.readQuery({
+    query,
+    variables: {
+      id: match.params.version,
+    },
+  })
+
+  if (updateTeam) {
+    const teamIndex = data.teams.findIndex(team => team.id === updateTeam.id)
+    const manuscriptTeamIndex = data.manuscript.teams.findIndex(
+      team => team.id === updateTeam.id,
+    )
+    data.teams[teamIndex] = updateTeam
+    data.manuscript.teams[manuscriptTeamIndex] = updateTeam
+  }
+
+  if (createTeam) {
+    data.teams.push(createTeam)
+    data.manuscript.teams.push(createTeam)
+  }
+  proxy.writeQuery({ query, data })
+}
+
+const handleSubmit = (
+  { user },
+  { props: { manuscript, updateTeamMutation, createTeamMutation, match } },
+) => {
+  const team =
+    manuscript.teams.find(team => team.teamType === 'reviewerEditor') || {}
+
+  const teamAdd = {
+    object: {
+      objectId: manuscript.id,
+      objectType: 'Manuscript',
+    },
+    status: [{ user: user.id, status: 'invited' }],
+    name: 'Reviewer Editor',
+    teamType: 'reviewerEditor',
+    members: [user.id],
+  }
+  if (team.id) {
+    const newTeam = {
+      object: omit(team.object, ['__typename']),
+      status: team.status.map(status => omit(status, ['__typename'])),
+      name: team.name,
+      teamType: team.teamType,
+      members: cloneDeep(team.members).map(member => member.id),
+    }
+
+    newTeam.members.push(user.id)
+    newTeam.status.push({ user: user.id, status: 'invited' })
+    updateTeamMutation({
+      variables: {
+        id: team.id,
+        input: newTeam,
+      },
+      update: update(match),
+    })
+  } else {
+    createTeamMutation({
+      variables: {
+        input: teamAdd,
+      },
+      update: update(match),
+    })
+  }
+}
+
+const loadOptions = props => input => {
+  const options = props.reviewerUsers
+
+  return Promise.resolve({ options })
+}
+
 export default compose(
   graphql(query, {
     options: ({ match }) => ({
@@ -161,22 +191,49 @@ export default compose(
       },
     }),
   }),
+  graphql(createTeamMutation, { name: 'createTeamMutation' }),
+  graphql(updateTeamMutation, { name: 'updateTeamMutation' }),
   withLoader(),
-  withProps(({ manuscript, teams, users, match: { params: { journal } } }) => {
-    const reviewerTeams =
-      manuscript.teams.find(
-        team =>
-          team.role === 'reviewerEditor' &&
-          team.object.id === manuscript.id &&
-          team.objectType === 'manuscript',
-      ) || {}
-
-    return {
-      reviewers: reviewerTeams.members || [],
-      journal: { id: journal },
-      reviewerUsers: users,
-      Reviewer: ReviewerContainer,
-      ReviewerForm: ReviewerFormContainer,
-    }
+  withProps(
+    ({
+      manuscript,
+      teams = [],
+      users,
+      match: {
+        params: { journal },
+      },
+    }) => {
+      const reviewerTeams =
+        teams.find(
+          team =>
+            team.teamType === 'reviewerEditor' &&
+            team.object.objectId === manuscript.id &&
+            team.object.objectType === 'Manuscript',
+        ) || {}
+
+      // Temporary solution until new Team model is back
+      const mem = cloneDeep(reviewerTeams.members || [])
+      mem.map(member => {
+        const status = reviewerTeams.status.find(
+          status => status.user === member.id,
+        )
+        member.status = (status || {}).status
+        return member
+      })
+      return {
+        reviewers: mem || [],
+        journal: { id: journal },
+        reviewerUsers: users,
+        Reviewer: ReviewerContainer,
+      }
+    },
+  ),
+  withHandlers({
+    loadOptions: props => loadOptions(props),
+  }),
+  withFormik({
+    mapPropsToValues: () => ({ user: '' }),
+    displayName: 'reviewers',
+    handleSubmit,
   }),
 )(Reviewers)
diff --git a/packages/components/xpub-review/src/components/assignEditors/AssignEditor.js b/packages/components/xpub-review/src/components/assignEditors/AssignEditor.js
index 43f071d17f079c96694f65d0615b66a599445957..24a82abd96280249c8571d0f943c04095dfbc4eb 100644
--- a/packages/components/xpub-review/src/components/assignEditors/AssignEditor.js
+++ b/packages/components/xpub-review/src/components/assignEditors/AssignEditor.js
@@ -1,10 +1,11 @@
 import React from 'react'
+import config from 'config'
 import { compose, withProps } from 'recompose'
-import { cloneDeep } from 'lodash'
+import { cloneDeep, get } from 'lodash'
 import { Menu } from '@pubsweet/ui'
 import { graphql } from 'react-apollo'
 import { gql } from 'apollo-client-preset'
-// import { addUserToTeam } from '../../redux/teams'
+import { withLoader } from 'pubsweet-client'
 
 const editorOption = user => ({
   label: user.username, // TODO: name
@@ -13,18 +14,15 @@ const editorOption = user => ({
 
 const teamFields = `
   id
-  role
   name
+  teamType
   object {
-    id
+    objectId
+    objectType
   }
-  objectType
   members {
-    status
-    user {
-      id
-      username
-    }
+    id
+    username
   }
 `
 
@@ -35,27 +33,42 @@ const query = gql`
       username
       admin
     }
-
-    teams {
-      ${teamFields}
-    }
   }
 `
 
 const updateTeam = gql`
-  mutation($id: ID!, $input: String) {
-    assignTeamEditor(id: $id, input: $input) {
+  mutation($id: ID!, $input: TeamInput) {
+    updateTeam(id: $id, input: $input) {
       ${teamFields}
     } 
   }
 `
 
+const createTeamMutation = gql`
+  mutation($input: TeamInput!) {
+    createTeam(input: $input) {
+      ${teamFields}
+    }
+  }
+`
+
 // TODO: select multiple editors
-const AssignEditor = ({ updateTeam, teamName, teamRole, value, options }) => (
+const AssignEditor = ({
+  updateTeam,
+  createTeam,
+  teamName,
+  teamRole,
+  value,
+  options,
+}) => (
   <Menu
     label={teamName}
     onChange={user => {
-      updateTeam(user, teamRole)
+      if (value) {
+        updateTeam(user, teamRole)
+      } else {
+        createTeam(user, teamRole)
+      }
     }}
     options={options}
     placeholder="Assign an editor…"
@@ -68,54 +81,63 @@ export default compose(
   graphql(updateTeam, {
     props: ({ mutate, ownProps }) => {
       const updateTeam = (userId, teamRole) => {
-        const teams = cloneDeep(ownProps.data.teams).find(
-          team =>
-            team.role === teamRole &&
-            team.object.id === ownProps.manuscript.id &&
-            team.objectType === 'manuscript',
+        const team = cloneDeep(ownProps.manuscript.teams).find(
+          team => team.teamType === teamRole,
         )
+        mutate({
+          variables: {
+            id: team.id,
+            input: {
+              members: [userId],
+            },
+          },
+        })
+      }
 
-        const member = teams.members.find(member => member.user.id === userId)
-        const team = cloneDeep(teams)
-        team.members = [member]
-
-        const { manuscript } = cloneDeep(ownProps)
-        const replacePrevious = manuscript.teams.filter(
-          team => team.role !== teamRole,
-        )
-        replacePrevious.push(team)
+      return {
+        updateTeam,
+      }
+    },
+  }),
+  graphql(createTeamMutation, {
+    props: ({ mutate, ownProps }) => {
+      const createTeam = (userId, teamRole) => {
+        const input = {
+          object: {
+            objectId: ownProps.manuscript.id,
+            objectType: 'Manuscript',
+          },
+          name:
+            teamRole === 'seniorEditor' ? 'Senior Editor' : 'Handling Editor',
+          teamType: teamRole,
+          members: [userId],
+        }
 
         mutate({
           variables: {
-            id: ownProps.manuscript.id,
-            input: JSON.stringify({ teams: replacePrevious }),
+            input,
           },
         })
       }
 
       return {
-        updateTeam,
+        createTeam,
       }
     },
   }),
-  withProps(({ teamRole, manuscript, data: { users, teams } }) => {
-    const filteredTeams = (teams || []).find(
-      team =>
-        team.role === teamRole &&
-        team.object.id === manuscript.id &&
-        team.objectType === 'manuscript',
-    )
-    const members = ((filteredTeams || {}).members || []).map(
-      members => members.user,
-    )
-    const optionUsers = members.map(user => editorOption(user))
+  withProps(({ teamRole, manuscript, data = {} }) => {
+    const optionUsers = (data.users || []).map(user => editorOption(user))
+
+    const team =
+      (manuscript.teams || []).find(team => team.teamType === teamRole) || {}
 
+    const members = team.members || []
+    const teamName = get(config, `authsome.teams.${teamRole}.name`)
     return {
-      filteredTeams,
-      teams,
-      teamName: (filteredTeams || {}).name,
+      teamName,
       options: optionUsers,
-      value: manuscript.teams.find(team => team.teamRole),
+      value: members.length > 0 ? members[0].id : undefined,
     }
   }),
+  withLoader(),
 )(AssignEditor)
diff --git a/packages/components/xpub-review/src/components/decision/Decision.js b/packages/components/xpub-review/src/components/decision/Decision.js
index bbcf4ef30a210466abcf7fb3c880afaff4ca2c3c..7cdb3ff2e67c3148c8b56ea49fa5968352ef3ce7 100644
--- a/packages/components/xpub-review/src/components/decision/Decision.js
+++ b/packages/components/xpub-review/src/components/decision/Decision.js
@@ -22,17 +22,17 @@ const filesToAttachment = file => ({
   url: file.url,
 })
 
-const Decision = ({ decision }) => (
+const Decision = ({ review }) => (
   <div>
     <div>
-      {findComments(decision, 'note') && [
+      {findComments(review, 'note') && [
         <Heading>Note</Heading>,
         <Note>
           <Content>
-            <NoteViewer value={findComments(decision, 'note').content} />
+            <NoteViewer value={findComments(review, 'note').content} />
           </Content>
-          {findComments(decision, 'note') &&
-            (findComments(decision, 'note').files || []).map(attachment => (
+          {findComments(review, 'note') &&
+            (findComments(review, 'note').files || []).map(attachment => (
               <Attachment
                 file={filesToAttachment(attachment)}
                 key={attachment.url}
@@ -46,7 +46,7 @@ const Decision = ({ decision }) => (
     <div>
       <Heading>Decision</Heading>
 
-      <DecisionStatus>{decision.status}</DecisionStatus>
+      <DecisionStatus>{review.recommendation}</DecisionStatus>
     </div>
   </div>
 )
diff --git a/packages/components/xpub-review/src/components/decision/DecisionForm.js b/packages/components/xpub-review/src/components/decision/DecisionForm.js
index 3cfa79462fd8797e1c2099607f7e7562438be5c0..f03b00ed16f0ac4c71cab367d85065506df056fc 100644
--- a/packages/components/xpub-review/src/components/decision/DecisionForm.js
+++ b/packages/components/xpub-review/src/components/decision/DecisionForm.js
@@ -1,104 +1,153 @@
 import React from 'react'
 import { NoteEditor } from 'xpub-edit'
-import { Button, RadioGroup, FileUploadList, UploadingFile } from '@pubsweet/ui'
+import { cloneDeep, omit } from 'lodash'
 import { FieldArray, Field } from 'formik'
 import { withJournal } from 'xpub-journal'
 import { required } from 'xpub-validators'
+import {
+  Button,
+  Flexbox,
+  RadioGroup,
+  UploadButton,
+  UploadingFile,
+} from '@pubsweet/ui'
 
-import AdminSection from '../atoms/AdminSection'
-
-const stripHtml = htmlString => {
-  const temp = document.createElement('span')
-  temp.innerHTML = htmlString
-  return temp.textContent
-}
+import {
+  getCommentFiles,
+  getCommentContent,
+  stripHtml,
+  createComments,
+} from '../review/util'
 
-const createComments = (values, val) =>
-  Object.assign(
-    {
-      type: 'note',
-      content: '',
-      files: [],
-    },
-    values.decision.comments[0],
-    val,
-  )
+import AdminSection from '../atoms/AdminSection'
 
-const NoteDecision = uploadFile => props => (
+const NoteDecision = (updateReview, uploadFile) => props => (
   <AdminSection>
-    <Field component={NoteInput} validate={required} {...props} />
-    <Field component={AttachmentsInput} uploadFile={uploadFile} {...props} />
+    <Field
+      component={NoteInput}
+      name="comments"
+      updateReview={updateReview}
+      validate={required}
+    />
+    <Field
+      component={AttachmentsInput('note')}
+      updateReview={updateReview}
+      uploadFile={uploadFile}
+    />
   </AdminSection>
 )
 
-const NoteInput = ({ field, form: { values, handleChange }, replace }) => (
+const NoteInput = ({
+  field,
+  form: { values, setFieldValue },
+  updateReview,
+}) => (
   <NoteEditor
-    {...field}
-    onChange={val => {
-      replace(0, createComments(values, { content: stripHtml(val) }))
+    key="note-input"
+    onBlur={value => {
+      const { updateIndex, comment } = createComments(
+        values,
+        {
+          type: 'note',
+          content: stripHtml(value),
+        },
+        'note',
+      )
+
+      setFieldValue(`comments.${updateIndex}`, comment)
+      updateReview(
+        cloneDeep(omit({ comment }, ['comment.files', 'comment.__typename'])),
+      )
     }}
     placeholder="Write/paste your decision letter here, or upload it using the upload button on the right."
     title="Decision"
-    value={field.value.length > 0 ? field.value[0].content : ''}
+    value={getCommentContent({ comments: field.value }, 'note')}
   />
 )
 
-const AttachmentsInput = ({
+const AttachmentsInput = type => ({
   field,
-  form: { values, handleChange },
-  replace,
-}) => (
-  <FileUploadList
+  form: { values, setFieldValue },
+  updateReview,
+  uploadFile,
+}) => [
+  <UploadButton
     buttonText="↑ Upload files"
-    FileComponent={UploadingFile}
-    files={(values.decision.comments[0] || {}).files || []}
-    uploadFile={val => {
-      const file = {
-        filename: val.name,
-        name: val.name,
-        size: val.size,
-        fileType: val.type,
-        type: 'note',
-      }
-      replace(0, createComments(values, { files: [file] }))
+    key="note-attachment"
+    onChange={event => {
+      const val = event.target.files[0]
+      const file = cloneDeep(val)
+      file.filename = val.name
+      file.type = type
+
+      const { updateIndex, comment } = createComments(
+        field.value,
+        { files: [file] },
+        type,
+      )
+
+      setFieldValue(`comments.${updateIndex}.files`, comment.files)
+
+      updateReview({}).then(({ data: { updateReview } }) => {
+        uploadFile(val, updateReview, type)
+      })
     }}
-  />
-)
+  />,
+  <Flexbox>
+    {getCommentFiles(field.value, 'note').map(val => {
+      const file = cloneDeep(val)
+      file.name = file.filename
+      return <UploadingFile file={file} key={file.name} uploaded />
+    })}
+  </Flexbox>,
+]
 
-const RecommendationInput = journal => ({ form, field }) => (
+const RecommendationInput = journal => ({
+  field,
+  form: { setFieldValue },
+  updateReview,
+}) => (
   <RadioGroup
     {...field}
     inline
     onChange={val => {
-      form.setFieldValue(`${field.name}`, val, true)
+      setFieldValue(`recommendation`, val)
+      updateReview({ recommendation: val })
     }}
     options={journal.recommendations}
-    required
+    value={field.value === '' ? null : field.value}
   />
 )
 
-const DecisionForm = ({ journal, handleSubmit, uploadFile, ...props }) => (
+const DecisionForm = ({
+  journal,
+  handleSubmit,
+  uploadFile,
+  updateReview,
+  isValid,
+}) => (
   <form onSubmit={handleSubmit}>
-    <AdminSection>
+    <AdminSection key="note">
       <div name="note">
         <FieldArray
-          component={NoteDecision(uploadFile)}
-          name="decision.comments"
+          component={NoteDecision(updateReview, uploadFile)}
+          key="comments-array"
+          name="comments"
         />
       </div>
     </AdminSection>
 
-    <AdminSection>
+    <AdminSection key="recommendation">
       <Field
         component={RecommendationInput(journal)}
-        name="decision.status"
+        name="recommendation"
+        updateReview={updateReview}
         validate={required}
-        {...props}
       />
     </AdminSection>
 
-    <AdminSection>
-      <Button primary type="submit">
+    <AdminSection key="submit">
+      <Button disabled={!isValid} primary type="submit">
         Submit
       </Button>
     </AdminSection>
diff --git a/packages/components/xpub-review/src/components/decision/DecisionLayout.js b/packages/components/xpub-review/src/components/decision/DecisionLayout.js
index c424fb0a210737691694f7dbef345739dc2320ef..765d1d894a75b523a87136e8c228f684b2cecafa 100644
--- a/packages/components/xpub-review/src/components/decision/DecisionLayout.js
+++ b/packages/components/xpub-review/src/components/decision/DecisionLayout.js
@@ -20,23 +20,27 @@ const addEditor = (manuscript, label) => ({
 
 const DecisionLayout = ({
   handleSubmit,
-  handleChangeFn,
+  updateReview,
   uploadFile,
   manuscript,
   journal,
+  isValid,
 }) => {
   const decisionSections = []
   const editorSections = []
-  manuscript.manuscriptVersions.forEach(manuscript => {
-    const { decision } = manuscript
-    const submittedMoment = moment(decision.submitted)
+  const manuscriptVersions = manuscript.manuscriptVersions || []
+  manuscriptVersions.forEach(manuscript => {
+    const submittedMoment = moment(manuscript.updated)
     const label = submittedMoment.format('YYYY-MM-DD')
+
     decisionSections.push({
       content: (
         <div>
           <ReviewMetadata manuscript={manuscript} />
           <DecisionReviews manuscript={manuscript} />
-          <Decision decision={manuscript.decision} />
+          <Decision
+            review={manuscript.reviews.find(review => review.isDecision)}
+          />
         </div>
       ),
       key: manuscript.id,
@@ -48,36 +52,39 @@ const DecisionLayout = ({
 
   const submittedMoment = moment()
   const label = submittedMoment.format('YYYY-MM-DD')
-  decisionSections.push({
-    content: (
-      <div>
-        <AdminSection>
-          <AssignEditorsReviewers
-            AssignEditor={AssignEditor}
-            journal={journal}
-            manuscript={manuscript}
-          />
-        </AdminSection>
-        <AdminSection>
-          <ReviewMetadata manuscript={manuscript} />
-        </AdminSection>
-        <AdminSection>
-          <DecisionReviews manuscript={manuscript} />
-        </AdminSection>
-        <AdminSection>
-          <DecisionForm
-            handleChangeFn={handleChangeFn}
-            handleSubmit={handleSubmit}
-            uploadFile={uploadFile}
-          />
-        </AdminSection>
-      </div>
-    ),
-    key: manuscript.id,
-    label,
-  })
+  if (manuscript.status !== 'revising') {
+    decisionSections.push({
+      content: (
+        <div>
+          <AdminSection key="assign-editors">
+            <AssignEditorsReviewers
+              AssignEditor={AssignEditor}
+              journal={journal}
+              manuscript={manuscript}
+            />
+          </AdminSection>
+          <AdminSection key="review-metadata">
+            <ReviewMetadata manuscript={manuscript} />
+          </AdminSection>
+          <AdminSection key="decision-review">
+            <DecisionReviews manuscript={manuscript} />
+          </AdminSection>
+          <AdminSection key="decision-form">
+            <DecisionForm
+              handleSubmit={handleSubmit}
+              isValid={isValid}
+              updateReview={updateReview}
+              uploadFile={uploadFile}
+            />
+          </AdminSection>
+        </div>
+      ),
+      key: manuscript.id,
+      label,
+    })
 
-  editorSections.push(addEditor(manuscript, label))
+    editorSections.push(addEditor(manuscript, label))
+  }
 
   return (
     <Columns>
diff --git a/packages/components/xpub-review/src/components/decision/DecisionReviews.js b/packages/components/xpub-review/src/components/decision/DecisionReviews.js
index 502e48d31e6b5b516e6e7171b585a9d87746e617..3ac96155eb105917b9fc0ccecf7e6ca838afc9c6 100644
--- a/packages/components/xpub-review/src/components/decision/DecisionReviews.js
+++ b/packages/components/xpub-review/src/components/decision/DecisionReviews.js
@@ -1,14 +1,19 @@
 import React from 'react'
-import { getUserFromTeam } from 'xpub-selectors'
+// import { getUserFromTeam } from 'xpub-selectors'
 import DecisionReview from './DecisionReview'
 
 // TODO: read reviewer ordinal and name from project reviewer
-
+// const { status } =
+//     getUserFromTeam(manuscript, 'reviewerEditor').filter(
+//       member => member.user.id === currentUser.id,
+//     )[0] || {}
+//   return status
 const getCompletedReviews = (manuscript, currentUser) => {
+  const team =
+    manuscript.teams.find(team => team.teamType === 'reviewerEditor') || {}
   const { status } =
-    getUserFromTeam(manuscript, 'reviewerEditor').filter(
-      member => member.user.id === currentUser.id,
-    )[0] || {}
+    (team.status || []).filter(member => member.user === currentUser.id)[0] ||
+    {}
   return status
 }
 
@@ -18,8 +23,8 @@ const DecisionReviews = ({ manuscript }) => (
       manuscript.reviews
         .filter(
           review =>
-            getCompletedReviews(manuscript, review.user) === 'accepted' &&
-            review.recommendation,
+            getCompletedReviews(manuscript, review.user) === 'completed' &&
+            review.isDecision === false,
         )
         .map((review, index) => (
           <div key={review.id}>
diff --git a/packages/components/xpub-review/src/components/decision/EditorSection.js b/packages/components/xpub-review/src/components/decision/EditorSection.js
index 6489cc73f4e275167371d280e156b098fa4ab726..2c05b01f935474e3d7023458f577cdc2bd3f53e8 100644
--- a/packages/components/xpub-review/src/components/decision/EditorSection.js
+++ b/packages/components/xpub-review/src/components/decision/EditorSection.js
@@ -4,8 +4,8 @@ import { EditorWrapper } from '../molecules/EditorWrapper'
 import { Info } from '../molecules/Info'
 
 export default ({ manuscript }) =>
-  ((manuscript.files || []).find(file => file.type === 'manuscript') || '')
-    .type ===
+  ((manuscript.files || []).find(file => file.fileType === 'manuscript') || '')
+    .mimeType ===
   'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ? (
     <EditorWrapper>
       <Wax key={manuscript.id} readonly value={manuscript.meta.source} />
diff --git a/packages/components/xpub-review/src/components/metadata/ReviewMetadata.js b/packages/components/xpub-review/src/components/metadata/ReviewMetadata.js
index 5f7a4ab9dd3a00e8ff829f8d716ee32d8e4ccc3c..18cbfab494d37d627754e4ab0cb7deb85fee79c5 100644
--- a/packages/components/xpub-review/src/components/metadata/ReviewMetadata.js
+++ b/packages/components/xpub-review/src/components/metadata/ReviewMetadata.js
@@ -34,8 +34,11 @@ const Cell = styled.span`
 const getNote = (notes, type) =>
   notes.find(note => note.notesType === type) || {}
 
-const getSupplementaryFiles = (supplementary = []) =>
-  supplementary.filter(file => file.type === 'supplementary') || []
+const getDeclarations = (manuscript, field) =>
+  ((manuscript.meta || {}).declarations || {})[field]
+
+const getSupplementaryFiles = supplementary =>
+  (supplementary || []).filter(file => file.fileType === 'supplementary') || []
 
 // Due to migration to new Data Model
 // Attachement component needs different data structure to work
@@ -52,13 +55,15 @@ const ReviewMetadata = ({ manuscript }) => (
       <div>
         <Heading>Open Peer Review :</Heading>
         <Cell>
-          {manuscript.meta.declarations.openPeerReview === 'yes' ? 'Yes' : 'No'}
+          {getDeclarations(manuscript, 'openPeerReview') === 'yes'
+            ? 'Yes'
+            : 'No'}
         </Cell>
       </div>
       <div>
         <Heading>Streamlined Review :</Heading>
         <Cell>
-          {manuscript.meta.declarations.streamlinedReview === 'yes'
+          {getDeclarations(manuscript, 'streamlinedReview') === 'yes'
             ? 'Please view supplementary uploaded files'
             : 'No'}
         </Cell>
@@ -66,20 +71,23 @@ const ReviewMetadata = ({ manuscript }) => (
       <div>
         <Heading>Part of Research Nexus :</Heading>
         <Cell>
-          {manuscript.meta.declarations.researchNexus === 'yes' ? 'Yes' : 'No'}
+          {getDeclarations(manuscript, 'researchNexus') === 'yes'
+            ? 'Yes'
+            : 'No'}
         </Cell>
       </div>
       <div>
         <Heading>Pre-registered :</Heading>
         <Cell>
-          {manuscript.meta.declarations.preregistered === 'yes' ? 'Yes' : 'No'}
+          {getDeclarations(manuscript, 'preregistered') === 'yes'
+            ? 'Yes'
+            : 'No'}
         </Cell>
       </div>
       <div>
         <Heading>Suggested Reviewers :</Heading>
         <Cell>
-          {((manuscript.meta.suggestions || {}).reviewers || {}).suggested ||
-            'None'}
+          {((manuscript.suggestions || {}).reviewers || {}).suggested || 'None'}
         </Cell>
       </div>
       <div>
diff --git a/packages/components/xpub-review/src/components/review/Review.js b/packages/components/xpub-review/src/components/review/Review.js
index 944e443715b366e9a856c43f6c216fab67bd6321..6dafc509580015dcbc22d92795af470bdd45d9bb 100644
--- a/packages/components/xpub-review/src/components/review/Review.js
+++ b/packages/components/xpub-review/src/components/review/Review.js
@@ -3,6 +3,7 @@ import styled from 'styled-components'
 import { NoteViewer } from 'xpub-edit'
 import { Attachment } from '@pubsweet/ui'
 import { th } from '@pubsweet/ui-toolkit'
+import { getCommentFiles } from './util'
 
 const Heading = styled.div``
 const Note = styled.div`
@@ -30,14 +31,13 @@ const ReviewComments = (review, type) => (
     <Content>
       <NoteViewer value={findComments(review, type).content} />
     </Content>
-    {findComments(review, type) &&
-      findComments(review, type).files.map(attachment => (
-        <Attachment
-          file={filesToAttachment(attachment)}
-          key={attachment.url}
-          uploaded
-        />
-      ))}
+    {getCommentFiles(review, type).map(attachment => (
+      <Attachment
+        file={filesToAttachment(attachment)}
+        key={attachment.url}
+        uploaded
+      />
+    ))}
   </Note>
 )
 
@@ -57,12 +57,13 @@ const Review = ({ review }) => (
         {ReviewComments(review, 'confidential')}
       </div>
     )}
+    {review.recommendation && (
+      <div>
+        <Heading>Recommendation</Heading>
 
-    <div>
-      <Heading>Recommendation</Heading>
-
-      <Recommendation>{review.recommendation}</Recommendation>
-    </div>
+        <Recommendation>{review.recommendation}</Recommendation>
+      </div>
+    )}
   </div>
 )
 
diff --git a/packages/components/xpub-review/src/components/review/ReviewForm.js b/packages/components/xpub-review/src/components/review/ReviewForm.js
index 1d9c67fd14097a0f0700425a757047576ca11d0c..9902e1f898f4eabaf74a355b0c7951c1b1dc2cb3 100644
--- a/packages/components/xpub-review/src/components/review/ReviewForm.js
+++ b/packages/components/xpub-review/src/components/review/ReviewForm.js
@@ -1,143 +1,182 @@
 import React from 'react'
 import styled from 'styled-components'
-
+import { cloneDeep, set } from 'lodash'
 import { Field, FieldArray } from 'formik'
 import { NoteEditor } from 'xpub-edit'
-import { Button, RadioGroup, FileUploadList, UploadingFile } from '@pubsweet/ui' // Attachments
+import {
+  Button,
+  Flexbox,
+  RadioGroup,
+  UploadButton,
+  UploadingFile,
+} from '@pubsweet/ui'
 
 import { withJournal } from 'xpub-journal'
-import { required } from 'xpub-validators'
-
+import {
+  getCommentFiles,
+  getCommentContent,
+  stripHtml,
+  createComments,
+} from './util'
 import AdminSection from '../atoms/AdminSection'
 
-const stripHtml = htmlString => {
-  const temp = document.createElement('span')
-  temp.innerHTML = htmlString
-  return temp.textContent
-}
-
-const createComments = (values, val, type) => {
-  let updateIndex = values.comments.findIndex(comment => comment.type === type)
-  updateIndex = values.comments.length > 0 && updateIndex < 0 ? 1 : updateIndex
-  updateIndex = updateIndex < 0 ? 0 : updateIndex
-
-  const comment = Object.assign(
-    {
-      type,
-      content: '',
-      files: [],
-    },
-    values.comments[updateIndex],
-    val,
-  )
-
-  return { updateIndex, comment }
-}
-
 const AttachmentsInput = type => ({
   field,
-  form: { values, handleChange },
-  replace,
-}) => (
-  <FileUploadList
+  form: { values },
+  updateReview,
+  uploadFile,
+  review,
+}) => [
+  <UploadButton
     buttonText="↑ Upload files"
-    FileComponent={UploadingFile}
-    files={
-      (values.comments.find(comment => comment.type === type) || {}).files || []
-    }
-    uploadFile={val => {
-      const file = {
-        filename: val.name,
-        name: val.name,
-        size: val.size,
-        fileType: val.type,
-        type: 'attachments',
-      }
+    onChange={event => {
+      const val = event.target.files[0]
+      const file = cloneDeep(val)
+      file.filename = val.name
+      file.type = type
+
       const { updateIndex, comment } = createComments(
-        values,
+        review,
         { files: [file] },
         type,
       )
-      replace(updateIndex, comment)
+
+      const data = cloneDeep(review)
+      set(data, `comments.${updateIndex}`, comment)
+
+      updateReview(data).then(({ data: { updateReview } }) => {
+        uploadFile(val, updateReview, type)
+      })
     }}
-  />
-)
+  />,
+  <Flexbox>
+    {getCommentFiles(review, type).map(val => {
+      const file = cloneDeep(val)
+      file.name = file.filename
+      return <UploadingFile file={file} key={file.name} uploaded />
+    })}
+  </Flexbox>,
+]
 
-const NoteInput = ({ field, form: { values }, replace, push }) => (
+const NoteInput = ({ field, form: { values }, review, updateReview }) => (
   <NoteEditor
     placeholder="Enter your review…"
     title="Comments to the Author"
     {...field}
-    onChange={value => {
-      const { updateIndex, comment } = createComments(values, {
-        type: 'note',
-        content: stripHtml(value),
-      })
-      replace(updateIndex, comment)
+    onBlur={value => {
+      const { updateIndex, comment } = createComments(
+        values,
+        {
+          type: 'note',
+          content: stripHtml(value),
+        },
+        'note',
+      )
+
+      const data = cloneDeep(review)
+      set(data, `comments.${updateIndex}`, comment)
+
+      updateReview(data)
     }}
-    value={
-      (values.comments.find(value => value.type === 'note') || {}).content || ''
-    }
+    value={getCommentContent(review, 'note')}
   />
 )
 
-const ConfidentialInput = ({ field, form: { values }, replace, push }) => (
+const ConfidentialInput = ({ field, review, updateReview }) => (
   <NoteEditor
     placeholder="Enter a confidential note to the editor (optional)…"
     title="Confidential Comments to Editor (Optional)"
     {...field}
-    onChange={value => {
-      const { updateIndex, comment } = createComments(values, {
-        type: 'confidential',
-        content: stripHtml(value),
-      })
-      replace(updateIndex, comment)
+    onBlur={value => {
+      const { updateIndex, comment } = createComments(
+        review,
+        {
+          type: 'confidential',
+          content: stripHtml(value),
+        },
+        'confidential',
+      )
+      const data = cloneDeep(review)
+      set(data, `comments.${updateIndex}`, comment)
+      updateReview(data)
     }}
-    value={
-      (values.comments.find(value => value.type === 'confidential') || {})
-        .content || ''
-    }
+    value={getCommentContent(review, 'confidential')}
   />
 )
 
-const RecommendationInput = journal => ({ form, field }) => (
+const RecommendationInput = journal => ({ field, updateReview, review }) => (
   <RadioGroup
     inline
-    options={journal.recommendations}
-    {...field}
     onChange={val => {
-      form.setFieldValue(`${field.name}`, val, true)
+      const data = cloneDeep(review)
+      set(data, 'recommendation', val)
+      updateReview(data)
     }}
+    options={journal.recommendations}
+    value={review.recommendation}
   />
 )
 
-const ReviewComment = uploadFile => props => [
+const ReviewComment = (updateReview, uploadFile, review) => props => [
   <AdminSection>
     <div name="note">
-      <Field component={NoteInput} validate={required} {...props} />
-      <Field component={AttachmentsInput('note')} {...props} />
+      <Field
+        component={NoteInput}
+        review={review}
+        updateReview={updateReview}
+        {...props}
+      />
+      <Field
+        component={AttachmentsInput('note')}
+        review={review}
+        updateReview={updateReview}
+        uploadFile={uploadFile}
+        {...props}
+      />
     </div>
   </AdminSection>,
   <AdminSection>
     <div name="confidential">
-      <Field component={ConfidentialInput} {...props} />
-      <Field component={AttachmentsInput('confidential')} {...props} />
+      <Field
+        component={ConfidentialInput}
+        review={review}
+        updateReview={updateReview}
+        {...props}
+      />
+      <Field
+        component={AttachmentsInput('confidential')}
+        review={review}
+        updateReview={updateReview}
+        uploadFile={uploadFile}
+        {...props}
+      />
     </div>
   </AdminSection>,
 ]
 
 const Title = styled.div``
 
-const ReviewForm = ({ journal, isValid, handleSubmit, uploadFile }) => (
+const ReviewForm = ({
+  journal,
+  isValid,
+  handleSubmit,
+  updateReview,
+  uploadFile,
+  review,
+}) => (
   <form onSubmit={handleSubmit}>
-    <FieldArray component={ReviewComment(uploadFile)} name="comments" />
+    <FieldArray
+      component={ReviewComment(updateReview, uploadFile, review)}
+      name="comments"
+    />
     <AdminSection>
       <div name="Recommendation">
         <Title>Recommendation</Title>
         <Field
           component={RecommendationInput(journal)}
           name="recommendation"
-          validate={required}
+          review={review}
+          updateReview={updateReview}
         />
       </div>
     </AdminSection>
diff --git a/packages/components/xpub-review/src/components/review/ReviewLayout.js b/packages/components/xpub-review/src/components/review/ReviewLayout.js
index f3868e218ae5a892403398955eb15ed9dcadfcf5..3872593897fe2b7aa6c1f800ddced47b245ac349 100644
--- a/packages/components/xpub-review/src/components/review/ReviewLayout.js
+++ b/packages/components/xpub-review/src/components/review/ReviewLayout.js
@@ -21,23 +21,22 @@ const ReviewLayout = ({
   review,
   reviewer,
   handleSubmit,
-  uploadFile,
   isValid,
   status,
+  updateReview,
+  uploadFile,
 }) => {
   const reviewSections = []
   const editorSections = []
-
-  manuscript.manuscriptVersions.forEach(manuscript => {
+  const manuscriptVersions = manuscript.manuscriptVersions || []
+  manuscriptVersions.forEach(manuscript => {
     const label = moment().format('YYYY-MM-DD')
     reviewSections.push({
       content: (
         <div>
           <ReviewMetadata manuscript={manuscript} />
           <Review
-            review={manuscript.reviews.find(
-              review => review.user.id === currentUser.id,
-            )}
+            review={manuscript.reviews.find(review => !review.isDecision) || {}}
           />
         </div>
       ),
@@ -48,29 +47,31 @@ const ReviewLayout = ({
     editorSections.push(addEditor(manuscript, label))
   }, [])
 
-  const label = moment().format('YYYY-MM-DD')
-
-  reviewSections.push({
-    content: (
-      <div>
-        <ReviewMetadata manuscript={manuscript} />
-        {status === 'accepted' && review.recommendation !== '' ? (
-          <Review review={review} />
-        ) : (
-          <ReviewForm
-            handleSubmit={handleSubmit}
-            isValid={isValid}
-            uploadFile={uploadFile}
-          />
-        )}
-      </div>
-    ),
-    key: manuscript.id,
-    label,
-  })
-
-  editorSections.push(addEditor(manuscript, label))
+  if (manuscript.status !== 'revising') {
+    const label = moment().format('YYYY-MM-DD')
+    reviewSections.push({
+      content: (
+        <div>
+          <ReviewMetadata manuscript={manuscript} />
+          {status === 'completed' ? (
+            <Review review={review} />
+          ) : (
+            <ReviewForm
+              handleSubmit={handleSubmit}
+              isValid={isValid}
+              review={review}
+              updateReview={updateReview}
+              uploadFile={uploadFile}
+            />
+          )}
+        </div>
+      ),
+      key: manuscript.id,
+      label,
+    })
 
+    editorSections.push(addEditor(manuscript, label))
+  }
   return (
     <Columns>
       <Manuscript>
diff --git a/packages/components/xpub-review/src/components/review/util.js b/packages/components/xpub-review/src/components/review/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..91551dc7e458bdadcb7e7f98554ec0aec72c09b4
--- /dev/null
+++ b/packages/components/xpub-review/src/components/review/util.js
@@ -0,0 +1,38 @@
+export const stripHtml = htmlString => {
+  const temp = document.createElement('span')
+  temp.innerHTML = htmlString
+  return temp.textContent
+}
+
+export const getCommentFiles = (review = {}, type) => {
+  const comments =
+    (review.comments || []).find(comment => comment.type === type) || {}
+  return comments.files || []
+}
+
+export const getCommentContent = (review = {}, type) => {
+  const comments =
+    (review.comments || []).find(comment => comment.type === type) || {}
+  return comments.content || ''
+}
+
+export const createComments = (values, val, type) => {
+  let updateIndex = (values.comments || []).findIndex(
+    comment => comment.type === type,
+  )
+  updateIndex =
+    (values.comments || []).length > 0 && updateIndex < 0 ? 1 : updateIndex
+  updateIndex = updateIndex < 0 ? 0 : updateIndex
+
+  const comment = Object.assign(
+    {
+      type,
+      content: '',
+      files: [],
+    },
+    (values.comments || [])[updateIndex],
+    val,
+  )
+
+  return { updateIndex, comment }
+}
diff --git a/packages/components/xpub-review/src/components/reviewers/Reviewer.js b/packages/components/xpub-review/src/components/reviewers/Reviewer.js
index 417172c26fa28fd2bbb8f8a6fac12306858616aa..5367ceb2650694472e5809b274ce85b54985ebaf 100644
--- a/packages/components/xpub-review/src/components/reviewers/Reviewer.js
+++ b/packages/components/xpub-review/src/components/reviewers/Reviewer.js
@@ -11,11 +11,6 @@ const Root = styled.div`
   padding: ${th('gridUnit')};
 `
 
-// const Event = styled.div`
-//   font-size: ${th('fontSizeBaseSmall')};
-//   line-height: ${th('lineHeightBaseSmall')};
-// `
-
 const ordinalLetter = ordinal =>
   ordinal ? String.fromCharCode(96 + ordinal) : null
 
@@ -24,10 +19,10 @@ const Reviewer = ({ reviewer, removeReviewer }) => (
     <Avatar
       height={70}
       reviewerLetter={ordinalLetter(null)}
-      status={reviewer.status}
+      status={reviewer.status || ''}
       width={100}
     />
-    <div>{reviewer.user.username}</div>
+    <div>{reviewer.username}</div>
     {/* <div>
       {map(reviewer.events, (event, key) => (
         <Event key={`${key}-${event}`}>
diff --git a/packages/components/xpub-review/src/components/reviewers/ReviewerForm.js b/packages/components/xpub-review/src/components/reviewers/ReviewerForm.js
index 992fb0fdf2b5feb38566c1139a10bd8971d9d04e..7770e2c95c1c15ddc3e0a56d88e898eb2bd577f6 100644
--- a/packages/components/xpub-review/src/components/reviewers/ReviewerForm.js
+++ b/packages/components/xpub-review/src/components/reviewers/ReviewerForm.js
@@ -1,6 +1,5 @@
 import React from 'react'
 import Select from 'react-select'
-import { cloneDeep } from 'lodash'
 import { Field, FieldArray } from 'formik'
 import { Button } from '@pubsweet/ui'
 import { required } from 'xpub-validators'
@@ -15,40 +14,21 @@ const OptionRenderer = option => (
 
 const ReviewerInput = loadOptions => ({
   field,
-  form: { values },
+  form: { values, setFieldValue },
   push,
   replace,
 }) => (
   <Select.AsyncCreatable
     {...field}
-    // autoload={false}
     filterOption={() => true}
     labelKey="username"
     loadOptions={loadOptions}
     onChange={user => {
-      const teamIndex = (values.teams || []).findIndex(
-        team => team.role === 'reviewerEditor',
-      )
-
-      const member = {
-        status: 'invited',
-        user,
-      }
-
-      if (teamIndex < 0) {
-        const team = {
-          role: 'reviewerEditor',
-          members: [member],
-        }
-        push(team)
-      } else {
-        const newTeam = cloneDeep(values.teams[teamIndex])
-        newTeam.members.push(member)
-        replace(0, newTeam)
-      }
+      setFieldValue('user', user)
     }}
     optionRenderer={OptionRenderer}
     promptTextCreator={label => `Add ${label}?`}
+    value={values.user.id}
     valueKey="id"
   />
 )
@@ -70,7 +50,7 @@ const ReviewerForm = ({
   loadOptions,
 }) => (
   <form onSubmit={handleSubmit}>
-    <FieldArray component={componentFields(loadOptions)} name="teams" />
+    <FieldArray component={componentFields(loadOptions)} />
 
     <Button disabled={!isValid} primary type="submit">
       Invite reviewer
diff --git a/packages/components/xpub-review/src/components/reviewers/ReviewerFormContainer.js b/packages/components/xpub-review/src/components/reviewers/ReviewerFormContainer.js
index 432e24e660dee3c32f44759564bd1312b2d8b925..e0ba787d5a71fa0c6c81b799f6fc0509a695fa24 100644
--- a/packages/components/xpub-review/src/components/reviewers/ReviewerFormContainer.js
+++ b/packages/components/xpub-review/src/components/reviewers/ReviewerFormContainer.js
@@ -1,65 +1,122 @@
 import { compose, withHandlers } from 'recompose'
+import { cloneDeep } from 'lodash'
 import { withFormik } from 'formik'
 import { graphql } from 'react-apollo'
 import { gql } from 'apollo-client-preset'
 import ReviewerForm from './ReviewerForm'
 
-const fragmentFields = `
-  created
-  reviews {
-    open
-    recommendation
-    created
-    comments {
+const createTeamMutation = gql`
+  mutation($input: TeamInput!) {
+    createTeam(input: $input) {
+      id
       type
-      content
-      files {
-        type
+      teamType
+      name
+      object {
+        objectId
+        objectType
+      }
+      members {
         id
-        label
-        url
-        filename
+        username
       }
     }
-    user {
-      id
-      username
-    }
   }
-  teams {
-    id
-    role
-    object {
+`
+
+const updateTeamMutation = gql`
+  mutation($id: ID, $input: TeamInput) {
+    updateTeam(id: $id, input: $input) {
       id
-    }
-    objectType
-    members {
-      status
-      user {
+      type
+      teamType
+      name
+      object {
+        objectId
+        objectType
+      }
+      members {
         id
         username
       }
     }
   }
-  status
 `
 
-const updateMutation = gql`
-  mutation($id: ID!, $input: String) {
-    updateManuscript(id: $id, input: $input) {
+const query = gql`
+  query {
+    teams {
       id
-      ${fragmentFields}
+      teamType
+      name
+      object {
+        objectId
+        objectType
+      }
+      members {
+        id
+      }
+      status {
+        user
+        status
+      }
     }
   }
 `
 
-const handleSubmit = (manuscript, { props }) => {
-  props.updateMutation({
-    variables: {
-      id: manuscript.id,
-      input: JSON.stringify(manuscript),
+const update = (
+  proxy,
+  { data: { updateTeamMutation, createTeamMutation, teams } },
+) => {
+  const data = proxy.readQuery({ query })
+  if (updateTeamMutation) {
+    const teamIndex = teams.findIndex(team => team.id === updateTeamMutation.id)
+    data[teamIndex] = updateTeamMutation
+  }
+
+  if (createTeamMutation) {
+    data.push(createTeamMutation)
+  }
+
+  proxy.writeQuery({ query, data })
+}
+
+const handleSubmit = (
+  { user },
+  { props: { manuscript, updateTeamMutation, createTeamMutation } },
+) => {
+  const team =
+    manuscript.teams.find(team => team.teamType === 'reviewerEditor') || {}
+
+  const teamAdd = {
+    object: {
+      objectId: manuscript.id,
+      objectType: 'Manuscript',
     },
-  })
+    status: [{ user: user.id, status: 'invited' }],
+    name: 'Reviewer Editor',
+    teamType: 'reviewerEditor',
+    members: [user.id],
+  }
+  if (team.id) {
+    const newTeam = cloneDeep(team)
+    newTeam.status.push({ user: user.id, status: 'invited' })
+    newTeam.members.push(user.id)
+    updateTeamMutation({
+      variables: {
+        id: team.id,
+        input: newTeam,
+      },
+      update,
+    })
+  } else {
+    createTeamMutation({
+      variables: {
+        input: teamAdd,
+      },
+      update,
+    })
+  }
 }
 
 const loadOptions = props => input => {
@@ -69,13 +126,13 @@ const loadOptions = props => input => {
 }
 
 export default compose(
-  graphql(updateMutation, { name: 'updateMutation' }),
+  graphql(createTeamMutation, { name: 'createTeamMutation' }),
+  graphql(updateTeamMutation, { name: 'updateTeamMutation' }),
   withHandlers({
     loadOptions: props => loadOptions(props),
   }),
   withFormik({
-    initialValues: {},
-    mapPropsToValues: ({ manuscript }) => manuscript,
+    mapPropsToValues: () => ({ user: '' }),
     displayName: 'reviewers',
     handleSubmit,
   }),
diff --git a/packages/components/xpub-review/src/components/reviewers/Reviewers.js b/packages/components/xpub-review/src/components/reviewers/Reviewers.js
index 301ee2b9aee4eeefe01c8ce376e1f43dcfe390f9..7204e5b271c0ee31c48036d3f3ef3866aa119677 100644
--- a/packages/components/xpub-review/src/components/reviewers/Reviewers.js
+++ b/packages/components/xpub-review/src/components/reviewers/Reviewers.js
@@ -2,6 +2,7 @@ import React from 'react'
 import styled from 'styled-components'
 import { Link } from '@pubsweet/ui'
 import { th } from '@pubsweet/ui-toolkit'
+import ReviewerForm from './ReviewerForm'
 
 const Root = styled.div`
   display: flex;
@@ -14,22 +15,25 @@ const ReviewersList = styled.div`
 `
 
 const Reviewers = ({
-  ReviewerForm,
   Reviewer,
   journal,
+  isValid,
+  loadOptions,
   version,
   reviewers,
   reviewerUsers,
   manuscript,
+  handleSubmit,
   teams,
 }) => (
   <Root>
     <Form>
       <ReviewerForm
+        handleSubmit={handleSubmit}
+        isValid={isValid}
         journal={journal}
-        manuscript={manuscript}
+        loadOptions={loadOptions}
         reviewerUsers={reviewerUsers}
-        teams={teams}
       />
       <Link
         to={`/journals/${journal.id}/versions/${manuscript.id}/decisions/${
diff --git a/packages/components/xpub-selectors/src/index.js b/packages/components/xpub-selectors/src/index.js
index ace90fa25f84189a26e1104f7966136cca343437..7e90b82663e8bd88624e66b36688b6c20657a179 100644
--- a/packages/components/xpub-selectors/src/index.js
+++ b/packages/components/xpub-selectors/src/index.js
@@ -52,7 +52,7 @@ export const getReviewerFromUser = (project, version, currentUser) => {
 export const getUserFromTeam = (version, role) => {
   if (!version.teams) return []
 
-  const teams = version.teams.filter(team => team.role === role)
+  const teams = version.teams.filter(team => team.teamType === role)
   return teams.length ? teams[0].members : []
 }
 
diff --git a/packages/components/xpub-submit/package.json b/packages/components/xpub-submit/package.json
index ae6094de10274b8cbc57c8b60aee9c28ca47fb12..92e0ad07077b2cadbe4823c5c8e6e77f9ae3f0b1 100644
--- a/packages/components/xpub-submit/package.json
+++ b/packages/components/xpub-submit/package.json
@@ -13,15 +13,16 @@
     "@pubsweet/ui-toolkit": "^2.0.6",
     "apollo-client-preset": "^1.0.8",
     "formik": "^1.4.2",
+    "grid-styled": "^4.1.0",
     "lodash": "^4.17.11",
     "moment": "^2.23.0",
     "prop-types": "^15.5.10",
     "react-apollo": "^2.1.0",
+    "react-dropzone": "^4.3.0",
+    "react-feather": "^1.1.5",
     "react-html-parser": "^2.0.2",
-    "react-redux": "^5.0.2",
     "react-router-dom": "^4.2.2",
     "recompose": "^0.26.0",
-    "redux-form": "^7.0.3",
     "striptags": "^3.1.0",
     "styled-components": "^4.1.1",
     "xpub-connect": "^2.0.6",
@@ -37,6 +38,8 @@
     "babel-preset-env": "^1.6.0",
     "babel-preset-react": "^6.24.1",
     "babel-preset-stage-2": "^6.24.1",
+    "enzyme": "^3.7.0",
+    "enzyme-adapter-react-16": "^1.6.0",
     "faker": "^4.1.0"
   },
   "peerDependencies": {
diff --git a/packages/components/xpub-submit/src/components/AuthorsInput.js b/packages/components/xpub-submit/src/components/AuthorsInput.js
index 9fd750f050925a4f9cbb098e5e834bfe582790b8..ac7d255ef7ca84f6a7dd1942ead97db1ff99c8d4 100644
--- a/packages/components/xpub-submit/src/components/AuthorsInput.js
+++ b/packages/components/xpub-submit/src/components/AuthorsInput.js
@@ -1,6 +1,7 @@
 import React from 'react'
 import styled from 'styled-components'
 import { FieldArray } from 'formik'
+import { cloneDeep, set } from 'lodash'
 import { TextField, Button, ValidatedFieldFormik } from '@pubsweet/ui'
 import { minSize, readonly } from 'xpub-validators'
 
@@ -44,7 +45,20 @@ const affiliationInput = input => (
   <TextField label="Affiliation" placeholder="Enter affiliation…" {...input} />
 )
 
-const renderAuthors = ({ form: { values }, insert, remove }) => (
+const onChangeFn = (onChange, setFieldValue, values) => value => {
+  const val = value.target ? value.target.value : value
+  setFieldValue(value.target.name, val, true)
+
+  const data = cloneDeep(values)
+  set(data, value.target.name, val)
+  onChange(data.authors, 'authors')
+}
+
+const renderAuthors = onChange => ({
+  form: { values, setFieldValue },
+  insert,
+  remove,
+}) => (
   <ul>
     <UnbulletedList>
       <li>
@@ -64,7 +78,7 @@ const renderAuthors = ({ form: { values }, insert, remove }) => (
         </Button>
       </li>
       {(values.authors || []).map((author, index) => (
-        <li key={`author.${author.email}`}>
+        <li key={`author-${author}`}>
           <Spacing>
             <Author>
               Author:&nbsp;
@@ -78,7 +92,8 @@ const renderAuthors = ({ form: { values }, insert, remove }) => (
               <Inline>
                 <ValidatedFieldFormik
                   component={firstNameInput}
-                  name={`authors.[${index}].firstName`}
+                  name={`authors.${index}.firstName`}
+                  onChange={onChangeFn(onChange, setFieldValue, values)}
                   readonly={readonly}
                   validate={minSize1}
                 />
@@ -88,6 +103,7 @@ const renderAuthors = ({ form: { values }, insert, remove }) => (
                 <ValidatedFieldFormik
                   component={lastNameInput}
                   name={`authors.[${index}].lastName`}
+                  onChange={onChangeFn(onChange, setFieldValue, values)}
                   readonly={readonly}
                   validate={minSize1}
                 />
@@ -99,6 +115,7 @@ const renderAuthors = ({ form: { values }, insert, remove }) => (
                 <ValidatedFieldFormik
                   component={emailAddressInput}
                   name={`authors.[${index}].email`}
+                  onChange={onChangeFn(onChange, setFieldValue, values)}
                   readonly={readonly}
                   validate={minSize1}
                 />
@@ -108,8 +125,8 @@ const renderAuthors = ({ form: { values }, insert, remove }) => (
                 <ValidatedFieldFormik
                   component={affiliationInput}
                   name={`authors.[${index}].affiliation`}
+                  onChange={onChangeFn(onChange, setFieldValue, values)}
                   readonly={readonly}
-                  required
                   validate={minSize1}
                 />
               </Inline>
@@ -121,8 +138,8 @@ const renderAuthors = ({ form: { values }, insert, remove }) => (
   </ul>
 )
 
-const AuthorsInput = props => (
-  <FieldArray name="authors" render={renderAuthors} />
+const AuthorsInput = ({ onChange }) => (
+  <FieldArray name="authors" render={renderAuthors(onChange)} />
 )
 
 export default AuthorsInput
diff --git a/packages/components/xpub-submit/src/components/DecisionReviewColumn.js b/packages/components/xpub-submit/src/components/DecisionReviewColumn.js
index 0f932bd20cf26778728f507d34b27d6c94a46e85..e349f9eb1a0b450ae2bceb01a2b9faacc02b1dc0 100644
--- a/packages/components/xpub-submit/src/components/DecisionReviewColumn.js
+++ b/packages/components/xpub-submit/src/components/DecisionReviewColumn.js
@@ -48,7 +48,13 @@ const DecisionReviewColumn = ({
       {manuscript.reviews && (
         <Section id="accordion.review">
           <Accordion
-            Component={<ReviewAccordion reviews={manuscript.reviews} />}
+            Component={
+              <ReviewAccordion
+                reviews={manuscript.reviews.filter(
+                  review => !review.isDecision,
+                )}
+              />
+            }
             key="review"
             title="Reviews"
           />
diff --git a/packages/components/xpub-submit/src/components/FormTemplate.js b/packages/components/xpub-submit/src/components/FormTemplate.js
index f96589ee76375f0191b15e1bed6fd0e78bccaeb0..805cec6ca0530f3cbd3ca47c18ba50e105c7bc21 100644
--- a/packages/components/xpub-submit/src/components/FormTemplate.js
+++ b/packages/components/xpub-submit/src/components/FormTemplate.js
@@ -1,15 +1,14 @@
 import React from 'react'
 import styled from 'styled-components'
 import { th } from '@pubsweet/ui-toolkit'
-import { unescape, groupBy, isArray, get, cloneDeep } from 'lodash'
+import { unescape, groupBy, isArray, get, set, cloneDeep } from 'lodash'
 import { FieldArray } from 'formik'
-import ReactHtmlParser from 'react-html-parser'
 import * as elements from '@pubsweet/ui'
 import * as validators from 'xpub-validators'
 import { AbstractEditor } from 'xpub-edit'
 import { Heading1, Section, Legend, SubNote } from '../styles'
 import AuthorsInput from './AuthorsInput'
-// import Notes from './Notes'
+import Supplementary from './Supplementary'
 import Confirm from './Confirm'
 
 const Wrapper = styled.div`
@@ -55,49 +54,31 @@ const stripHtml = htmlString => {
 const filterFileManuscript = files =>
   files.filter(
     file =>
-      file.type === 'manuscript' &&
+      file.fileType === 'manuscript' &&
       file.mimeType !==
         'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
   )
 
-const {
-  ValidatedFieldFormik,
-  Button,
-  Attachment,
-  UploadingFile,
-  FileUploadList,
-} = elements
+const { ValidatedFieldFormik, Button, Attachment } = elements
+
 elements.AbstractEditor = ({
   validationStatus,
   setTouched,
   onChange,
   value,
+  values,
   ...rest
 }) => (
   <AbstractEditor
-    value={value || ''}
+    value={get(values, rest.name) || ''}
     {...rest}
     onChange={val => {
-      setTouched(createObject(rest.name, true))
+      setTouched(set({}, rest.name, true))
       onChange(stripHtml(val))
     }}
   />
 )
 
-elements.SupplementaryFiles = props => (
-  <FileUploadList
-    {...props}
-    buttonText="↑ Upload files"
-    FileComponent={UploadingFile}
-    files={cloneDeep(props.value)
-      .map(val => {
-        val.name = val.filename
-        return val
-      })
-      .filter(val => val.type === 'supplementary')}
-  />
-)
-
 elements.AuthorsInput = AuthorsInput
 
 const rejectProps = (obj, keys) =>
@@ -138,19 +119,6 @@ const composeValidate = (vld = [], valueField = {}) => value => {
   return errors.length > 0 ? errors[0] : undefined
 }
 
-const createObject = (key, value) => {
-  const obj = {}
-  const parts = key.split('.')
-  if (parts.length === 1) {
-    obj[parts[0]] = value
-  } else if (parts.length > 1) {
-    // concat all but the first part of the key
-    const remainingParts = parts.slice(1, parts.length).join('.')
-    obj[parts[0]] = createObject(remainingParts, value)
-  }
-  return obj
-}
-
 const groupElements = elements => {
   const grouped = groupBy(elements, n => n.group || 'default')
 
@@ -174,12 +142,11 @@ const groupElements = elements => {
   return startArr
 }
 
-const renderArray = (
-  elementsComponentArray,
-  setFieldValue,
-  setTouched,
-  onChange,
-) => ({ form: { values }, name }) =>
+const renderArray = (elementsComponentArray, onChange) => ({
+  form: { values, setTouched },
+  replace,
+  name,
+}) =>
   get(values, name).map((elValues, index) => {
     const element = elementsComponentArray.find(elv =>
       Object.values(elValues).includes(elv.type),
@@ -201,17 +168,25 @@ const renderArray = (
             'validateValue',
             'description',
             'order',
+            'value',
           ])}
           component={elements[element.component]}
           key={`notes-validate-${element.id}`}
-          name={`${name}.${index}.${element.name}`}
+          name={`${name}.${index}.content`}
           onChange={value => {
-            setFieldValue(`${name}.[${index}].${element.name}`, value, true)
-            onChange(value, `${name}.${index}.${element.name}`)
+            const data = {
+              notesType: element.type,
+              content: value,
+            }
+            replace(index, data, `${name}.[${index}]`, true)
+            const notes = cloneDeep(values)
+            set(notes, `${name}.[${index}]`, data)
+            onChange(notes.meta.notes, `${name}`)
           }}
           readonly={false}
           setTouched={setTouched}
           validate={composeValidate(element.validate, element.validateValue)}
+          values={values}
         />
         <SubNote dangerouslySetInnerHTML={createMarkup(element.description)} />
       </Section>
@@ -220,18 +195,12 @@ const renderArray = (
 
 const ElementComponentArray = ({
   elementsComponentArray,
-  setFieldValue,
-  setTouched,
   onChange,
+  uploadFile,
 }) => (
   <FieldArray
     name={elementsComponentArray[0].group}
-    render={renderArray(
-      elementsComponentArray,
-      setFieldValue,
-      setTouched,
-      onChange,
-    )}
+    render={renderArray(elementsComponentArray, onChange)}
   />
 )
 
@@ -243,23 +212,24 @@ export default ({
   confirming,
   manuscript,
   setTouched,
+  values,
   setFieldValue,
   uploadFile,
+  createFile,
   onChange,
   onSubmit,
+  ...props
 }) => (
   <Wrapper>
     <Heading1>{form.name}</Heading1>
-    <Intro>
-      <div>
-        {ReactHtmlParser(
-          (form.description || '').replace(
-            '###link###',
-            link(journal, manuscript),
-          ),
-        )}
-      </div>
-    </Intro>
+    <Intro
+      dangerouslySetInnerHTML={createMarkup(
+        (form.description || '').replace(
+          '###link###',
+          link(journal, manuscript),
+        ),
+      )}
+    />
     <form onSubmit={handleSubmit}>
       {groupElements(form.children || []).map(element =>
         !isArray(element) ? (
@@ -268,37 +238,47 @@ export default ({
             key={`${element.id}`}
           >
             <Legend dangerouslySetInnerHTML={createMarkup(element.title)} />
-            {element.component === 'AuthorsInput' && <AuthorsInput />}
-            {element.component !== 'AuthorsInput' && (
-              <ValidatedFieldFormik
-                component={elements[element.component]}
-                key={`validate-${element.id}`}
-                name={element.name}
-                onChange={value => {
-                  const val = value.target ? value.target.value : value
-                  setFieldValue(element.name, val, true)
-                  onChange(val, element.name)
-                }}
-                readonly={false}
-                setTouched={setTouched}
-                {...rejectProps(element, [
-                  'component',
-                  'title',
-                  'sectioncss',
-                  'parse',
-                  'format',
-                  'validate',
-                  'validateValue',
-                  'description',
-                  'order',
-                ])}
+            {element.component === 'SupplementaryFiles' && (
+              <Supplementary
+                createFile={createFile}
+                onChange={onChange}
                 uploadFile={uploadFile}
-                validate={composeValidate(
-                  element.validate,
-                  element.validateValue,
-                )}
               />
             )}
+            {element.component === 'AuthorsInput' && (
+              <AuthorsInput onChange={onChange} />
+            )}
+            {element.component !== 'AuthorsInput' &&
+              element.component !== 'SupplementaryFiles' && (
+                <ValidatedFieldFormik
+                  component={elements[element.component]}
+                  key={`validate-${element.id}`}
+                  name={element.name}
+                  onChange={value => {
+                    const val = value.target ? value.target.value : value
+                    setFieldValue(element.name, val, true)
+                    onChange(val, element.name)
+                  }}
+                  readonly={false}
+                  setTouched={setTouched}
+                  {...rejectProps(element, [
+                    'component',
+                    'title',
+                    'sectioncss',
+                    'parse',
+                    'format',
+                    'validate',
+                    'validateValue',
+                    'description',
+                    'order',
+                  ])}
+                  validate={composeValidate(
+                    element.validate,
+                    element.validateValue,
+                  )}
+                  values={values}
+                />
+              )}
             <SubNote
               dangerouslySetInnerHTML={createMarkup(element.description)}
             />
@@ -313,24 +293,24 @@ export default ({
         ),
       )}
 
-      {filterFileManuscript(manuscript.files).length > 0 ? (
+      {filterFileManuscript(values.files || []).length > 0 ? (
         <Section id="files.manuscript">
           <Legend space>Submitted Manuscript</Legend>
           <Attachment
-            file={filesToAttachment(filterFileManuscript(manuscript.files)[0])}
-            key={filterFileManuscript(manuscript.files)[0].url}
+            file={filesToAttachment(filterFileManuscript(values.files)[0])}
+            key={filterFileManuscript(values.files)[0].url}
             uploaded
           />
         </Section>
       ) : null}
 
-      {!manuscript.status === 'submitted' && form.haspopup === 'false' && (
+      {values.status !== 'submitted' && form.haspopup === 'false' && (
         <Button primary type="submit">
           Submit your manuscript
         </Button>
       )}
 
-      {!manuscript.status !== 'submitted' && form.haspopup === 'true' && (
+      {values.status !== 'submitted' && form.haspopup === 'true' && (
         <div>
           <Button onClick={toggleConfirming} primary type="button">
             Submit your manuscript
diff --git a/packages/components/xpub-submit/src/components/Submit.js b/packages/components/xpub-submit/src/components/Submit.js
index e229de568efdab0042346f46122ddf024b55facf..b11a1578f2afc404b10bc25e8c9c464bc86e1d12 100644
--- a/packages/components/xpub-submit/src/components/Submit.js
+++ b/packages/components/xpub-submit/src/components/Submit.js
@@ -36,8 +36,8 @@ const SubmittedVersionColumns = props => (
 
 const Submit = ({ journal, manuscript, forms, ...formProps }) => {
   const decisionSections = []
-
-  manuscript.manuscriptVersions.forEach(versionElem => {
+  const manuscriptVersions = manuscript.manuscriptVersions || []
+  manuscriptVersions.forEach(versionElem => {
     const submittedMoment = moment(versionElem.submitted)
     const label = submittedMoment.format('YYYY-MM-DD')
     decisionSections.push({
diff --git a/packages/components/xpub-submit/src/components/SubmitPage.js b/packages/components/xpub-submit/src/components/SubmitPage.js
index bed527c01d98a7cae8f92d472b5964d433ca2b5a..b3c2a695bdfa0de53a3c918617d90563f19e6e09 100644
--- a/packages/components/xpub-submit/src/components/SubmitPage.js
+++ b/packages/components/xpub-submit/src/components/SubmitPage.js
@@ -1,4 +1,4 @@
-import { throttle, cloneDeep, isEmpty } from 'lodash'
+import { throttle, cloneDeep, isEmpty, set } from 'lodash'
 import { compose, withProps, withState, withHandlers } from 'recompose'
 import { graphql } from 'react-apollo'
 import { gql } from 'apollo-client-preset'
@@ -14,8 +14,8 @@ const fragmentFields = `
     created
     label
     filename
+    fileType
     mimeType
-    type
     size
     url
   }
@@ -23,37 +23,24 @@ const fragmentFields = `
     open
     recommendation
     created
+    isDecision
     comments {
       content
     }
     user {
-      identities {
-        ... on Local {
-          name {
-            surname
-          }
-        }
-      }
+      id
+      username
     }
   }
   teams {
     id
     role
     members {
-      status
-      user {
-        id
-        username
-        identities {
-          ... on Local {
-            name {
-              surname
-            }
-          }
-        }
-      }
+      id
+      username
     }
   }
+  decision
   status
   meta {
     title
@@ -73,8 +60,6 @@ const fragmentFields = `
       date
     }
     notes {
-      id
-      created
       notesType
       content
     }
@@ -129,66 +114,26 @@ const updateMutation = gql`
 const uploadSuplementaryFilesMutation = gql`
   mutation($file: Upload!) {
     upload(file: $file) {
+      url
+    }
+  }
+`
+
+const createFileMutation = gql`
+  mutation($file: Upload!) {
+    createFile(file: $file) {
       id
       created
-      filename
       label
-      size
+      filename
+      fileType
       mimeType
+      size
       url
     }
   }
 `
 
-const omitSpecialKeysDeep = object => {
-  const output = {}
-
-  Object.entries(object).forEach(([key, value]) => {
-    if (!key.startsWith('_')) {
-      if (
-        typeof value === 'object' &&
-        value !== null &&
-        !Array.isArray(value)
-      ) {
-        output[key] = omitSpecialKeysDeep(value)
-      } else {
-        output[key] = value
-      }
-    }
-  })
-
-  return output
-}
-
-const createObject = (key, value) => {
-  const obj = {}
-  const parts = key.split('.')
-  if (parts.length === 1) {
-    obj[parts[0]] = value
-  } else if (parts.length > 1) {
-    // concat all but the first part of the key
-    const remainingParts = parts.slice(1, parts.length).join('.')
-    obj[parts[0]] = createObject(remainingParts, value)
-  }
-  return obj
-}
-
-// const stripNullsDeep = object => {
-//   const output = {}
-
-//   Object.entries(object).forEach(([key, value]) => {
-//     if (value !== null) {
-//       if (typeof value === 'object' && !Array.isArray(value)) {
-//         output[key] = stripNullsDeep(value)
-//       } else {
-//         output[key] = value
-//       }
-//     }
-//   })
-
-//   return output
-// }
-
 export default compose(
   graphql(query, {
     options: ({ match }) => ({
@@ -199,58 +144,46 @@ export default compose(
     }),
     props: ({ data }) => ({ data }),
   }),
-  graphql(uploadSuplementaryFilesMutation, {
+  graphql(createFileMutation, {
     props: ({ mutate, ownProps }) => ({
-      uploadFile: file => {
+      createFile: value => {
+        const file = {
+          url: value.url,
+          filename: value.filename,
+          mimeType: value.mimeType,
+          size: value.size,
+          fileType: 'supplementary',
+          object: 'Manuscript',
+          objectId: ownProps.match.params.version,
+        }
+
         mutate({
           variables: {
             file,
           },
-          update: (proxy, { data: { upload } }) => {
-            const { manuscript } = cloneDeep(ownProps.data)
-            manuscript.files.push(
-              Object.assign({}, { ...upload }, { type: 'supplementary' }),
-            )
-            proxy.writeQuery({
-              query: gql`
-              query($id: ID!) {
-                manuscript(id: $id) {
-                  ${fragmentFields}
-                }
-              }
-              `,
-              variables: {
-                id: ownProps.match.params.version,
-              },
-              data: { manuscript },
-            })
-          },
         })
       },
     }),
   }),
+  graphql(uploadSuplementaryFilesMutation, {
+    props: ({ mutate, ownProps }) => ({
+      uploadFile: file =>
+        mutate({
+          variables: {
+            file,
+          },
+        }),
+    }),
+  }),
   graphql(updateMutation, {
     props: ({ mutate, ownProps }) => {
       const updateManuscript = (value, path) => {
+        const input = {}
+        set(input, path, value)
         mutate({
           variables: {
             id: ownProps.match.params.version,
-            input: JSON.stringify(createObject(path, value)),
-          },
-          update: (proxy, { data: { updateManuscript } }) => {
-            proxy.writeQuery({
-              query: gql`
-              query($id: ID!) {
-                manuscript(id: $id) {
-                  ${fragmentFields}
-                }
-              }
-              `,
-              variables: {
-                id: ownProps.match.params.version,
-              },
-              data: { manuscript: updateManuscript },
-            })
+            input: JSON.stringify(input),
           },
         })
       }
@@ -264,27 +197,13 @@ export default compose(
   graphql(updateMutation, {
     props: ({ mutate, ownProps }) => ({
       onSubmit: (manuscript, { history }) => {
-        const data = cloneDeep(manuscript)
-        data.status = 'submitted'
+        const updateManuscript = {
+          status: 'submitted',
+        }
         mutate({
           variables: {
             id: ownProps.match.params.version,
-            input: JSON.stringify(data),
-          },
-          update: (proxy, { data: { updateManuscript } }) => {
-            proxy.writeQuery({
-              query: gql`
-              query($id: ID!) {
-                manuscript(id: $id) {
-                  ${fragmentFields}
-                }
-              }
-              `,
-              variables: {
-                id: ownProps.match.params.version,
-              },
-              data: { manuscript: data },
-            })
+            input: JSON.stringify(updateManuscript),
           },
         }).then(() => {
           history.push('/')
@@ -296,6 +215,7 @@ export default compose(
   withProps(({ getFile, manuscript, match: { params: { journal } } }) => ({
     journal: { id: journal },
     forms: cloneDeep(getFile),
+    manuscript,
   })),
   withFormik({
     initialValues: {},
diff --git a/packages/components/xpub-submit/src/components/Supplementary.js b/packages/components/xpub-submit/src/components/Supplementary.js
new file mode 100644
index 0000000000000000000000000000000000000000..e6ce5f04e612560d6e4892fe1e725804d4041757
--- /dev/null
+++ b/packages/components/xpub-submit/src/components/Supplementary.js
@@ -0,0 +1,52 @@
+import React from 'react'
+import { cloneDeep } from 'lodash'
+import { FieldArray } from 'formik'
+import { Flexbox, UploadButton, UploadingFile } from '@pubsweet/ui'
+
+const renderFilesUpload = (onChange, uploadFile, createFile) => ({
+  form: { values, setFieldValue },
+  push,
+  insert,
+}) => [
+  <UploadButton
+    buttonText="↑ Upload files"
+    onChange={event => {
+      const fileArray = Array.from(event.target.files).map(file => {
+        const fileUpload = {
+          fileType: 'supplementary',
+          filename: file.name,
+        }
+        return fileUpload
+      })
+      setFieldValue('files', fileArray.concat(values.files))
+      Array.from(event.target.files).forEach(file => {
+        uploadFile(file).then(({ data }) => {
+          const newFile = {
+            url: data.upload.url,
+            filename: file.name,
+            mimeType: file.type,
+            size: file.size,
+          }
+          createFile(newFile)
+        })
+      })
+    }}
+  />,
+  <Flexbox>
+    {cloneDeep(values.files || [])
+      .filter(val => val.fileType === 'supplementary')
+      .map(val => {
+        val.name = val.filename
+        return <UploadingFile file={val} key={val.name} uploaded />
+      })}
+  </Flexbox>,
+]
+
+const Supplementary = ({ onChange, uploadFile, createFile }) => (
+  <FieldArray
+    name="files"
+    render={renderFilesUpload(onChange, uploadFile, createFile)}
+  />
+)
+
+export default Supplementary
diff --git a/packages/components/xpub-submit/src/components/atoms/Icon.js b/packages/components/xpub-submit/src/components/atoms/Icon.js
new file mode 100644
index 0000000000000000000000000000000000000000..be73f6787298853af0995b343e9c99f306edde52
--- /dev/null
+++ b/packages/components/xpub-submit/src/components/atoms/Icon.js
@@ -0,0 +1,24 @@
+import React from 'react'
+import { withTheme } from 'styled-components'
+import { get, has } from 'lodash'
+import * as reactFeatherIcons from 'react-feather'
+
+const Icon = ({ iconName, overrideName, className, theme, ...props }) => {
+  const isOverrideInTheme = has(theme.icons, overrideName)
+  if (isOverrideInTheme) {
+    const OverrideIcon = get(theme.icons, overrideName)
+    return <OverrideIcon className={className} {...props} />
+  }
+  const isIconInDefaultSet = reactFeatherIcons[iconName]
+  // TODO: conversation with Pubsweet - what should we default to when
+  // there's no obvious react-feather equivalent?
+  if (!isIconInDefaultSet) {
+    // eslint-disable-next-line no-console
+    console.warn("Icon '%s' not found", iconName)
+    return null
+  }
+  const DefaultIcon = reactFeatherIcons[iconName]
+  return <DefaultIcon className={className} {...props} />
+}
+
+export default withTheme(Icon)
diff --git a/packages/components/xpub-teams-manager/package.json b/packages/components/xpub-teams-manager/package.json
index 54a666ef450ab9e24d6578e5de8d678c07184cbe..47bb19f2a54ce1b60bceb6fca3d6c0e8fa3dc05c 100644
--- a/packages/components/xpub-teams-manager/package.json
+++ b/packages/components/xpub-teams-manager/package.json
@@ -15,7 +15,6 @@
     "lodash": "^4.17.11",
     "prop-types": "^15.5.10",
     "react-dom": "^16.2.0",
-    "react-redux": "^5.0.6",
     "recompose": "^0.26.0",
     "redux": "^3.7.2",
     "styled-components": "^4.1.1",
@@ -30,8 +29,11 @@
     "faker": "^4.1.0"
   },
   "peerDependencies": {
+    "apollo-client-preset": "^1.0.8",
     "config": "^3.0.1",
+    "graphql-tag": "^2.10.0",
     "pubsweet-client": ">=2.1.0",
-    "react": ">=16"
+    "react": ">=16",
+    "react-apollo": "^2.3.3"
   }
 }
diff --git a/packages/components/xpub-teams-manager/src/components/Team.jsx b/packages/components/xpub-teams-manager/src/components/Team.jsx
index 8a58d08ece46252dfe0020304195aada7db676c1..56a7bb510c5f5d080687dec801337446751f4203 100644
--- a/packages/components/xpub-teams-manager/src/components/Team.jsx
+++ b/packages/components/xpub-teams-manager/src/components/Team.jsx
@@ -15,7 +15,7 @@ const Team = ({ team, number, userOptions, deleteTeam, updateTeam }) =>
           {team.name} {team.teamType.permissions}
         </TeamTableCell>,
         <TeamTableCell>
-          {team.object.type} {team.object.id}
+          {team.object.objectType} {team.object.objectId}
         </TeamTableCell>,
         <TeamTableCell width={40}>
           <StyledMenu
@@ -24,7 +24,7 @@ const Team = ({ team, number, userOptions, deleteTeam, updateTeam }) =>
             name="members"
             onChange={members => updateTeam(members, team)}
             options={userOptions}
-            value={team.members}
+            value={team.members.map(member => member.id)}
           />
         </TeamTableCell>,
         <TeamTableCell width={15}>
diff --git a/packages/components/xpub-teams-manager/src/components/TeamCreator.jsx b/packages/components/xpub-teams-manager/src/components/TeamCreator.jsx
index 68bfa26d1474aae6442ea5bf095789df1ea09f64..ad777546fe3eee623264d0366ad8054c91bde138 100644
--- a/packages/components/xpub-teams-manager/src/components/TeamCreator.jsx
+++ b/packages/components/xpub-teams-manager/src/components/TeamCreator.jsx
@@ -5,10 +5,10 @@ import { Button, Menu } from '@pubsweet/ui'
 
 const TeamCreator = ({
   teamTypeSelected,
-  collectionSelected,
-  collectionsOptions,
+  manuscriptSelected,
+  manuscriptsOptions,
   typesOptions,
-  onChangeCollection,
+  onChangeManuscript,
   onChangeType,
   onSave,
 }) => (
@@ -23,14 +23,14 @@ const TeamCreator = ({
       reset={teamTypeSelected}
       value={teamTypeSelected}
     />
-    <h4>Collection</h4>
+    <h4>Manuscript</h4>
     <Menu
       name="collection"
-      onChange={onChangeCollection}
-      options={collectionsOptions}
+      onChange={onChangeManuscript}
+      options={manuscriptsOptions}
       required
-      reset={collectionSelected}
-      value={collectionSelected}
+      reset={manuscriptSelected}
+      value={manuscriptSelected}
     />
     <Button primary type="submit">
       Create
@@ -39,20 +39,20 @@ const TeamCreator = ({
 )
 
 export default compose(
-  withState('collectionSelected', 'onCollectionSelect', false),
+  withState('manuscriptSelected', 'onManuscriptSelect', false),
   withState('teamTypeSelected', 'onTeamTypeSelect', false),
   withHandlers({
-    onChangeCollection: ({ onCollectionSelect }) => collectionId =>
-      onCollectionSelect(() => collectionId || false),
+    onChangeManuscript: ({ onManuscriptSelect }) => collectionId =>
+      onManuscriptSelect(() => collectionId || false),
     onChangeType: ({ onTeamTypeSelect }) => teamType =>
       onTeamTypeSelect(() => teamType || false),
     onSave: ({
       teamTypeSelected,
-      collectionSelected,
+      manuscriptSelected,
       create,
       typesOptions,
       onTeamTypeSelect,
-      onCollectionSelect,
+      onManuscriptSelect,
     }) => event => {
       event.preventDefault()
       const teamType = teamTypeSelected
@@ -60,9 +60,9 @@ export default compose(
       let objectId
       let objectType
 
-      if (collectionSelected) {
-        objectId = collectionSelected
-        objectType = 'collection'
+      if (manuscriptSelected) {
+        objectId = manuscriptSelected
+        objectType = 'Manuscript'
       }
 
       if (teamType && objectId && objectType) {
@@ -70,14 +70,14 @@ export default compose(
           name: find(typesOptions, types => types.value === teamType).label,
           teamType,
           object: {
-            id: objectId,
-            type: objectType,
+            objectId,
+            objectType,
           },
           members: [],
         })
 
         onTeamTypeSelect(() => true)
-        onCollectionSelect(() => true)
+        onManuscriptSelect(() => true)
       }
     },
   }),
diff --git a/packages/components/xpub-teams-manager/src/components/TeamsManager.jsx b/packages/components/xpub-teams-manager/src/components/TeamsManager.jsx
index 698bcaaaca60777406f480845cbae84a0f2776d0..64b4a82a13c9285fa0d23ff61da59cc3ccc700a4 100644
--- a/packages/components/xpub-teams-manager/src/components/TeamsManager.jsx
+++ b/packages/components/xpub-teams-manager/src/components/TeamsManager.jsx
@@ -11,7 +11,7 @@ const TeamsManager = ({
   createTeam,
   error,
   userOptions,
-  collectionsOptions,
+  manuscriptsOptions,
   typesOptions,
 }) => (
   <Page>
@@ -37,8 +37,8 @@ const TeamsManager = ({
       </TeamTable>
     )}
     <TeamCreator
-      collectionsOptions={collectionsOptions}
       create={createTeam}
+      manuscriptsOptions={manuscriptsOptions}
       typesOptions={typesOptions}
     />
   </Page>
diff --git a/packages/components/xpub-teams-manager/src/components/TeamsManagerPage.integration.test.js b/packages/components/xpub-teams-manager/src/components/TeamsManagerPage.integration.test.js
index 48d2fbb53b8aab60c69a5cd0151e6e7fdc3c5c88..9f8297208b06efe805b63e48d759b96560e0dba6 100644
--- a/packages/components/xpub-teams-manager/src/components/TeamsManagerPage.integration.test.js
+++ b/packages/components/xpub-teams-manager/src/components/TeamsManagerPage.integration.test.js
@@ -1,15 +1,13 @@
 import React from 'react'
+import faker from 'faker'
 import { MemoryRouter } from 'react-router-dom'
-import { Provider } from 'react-redux'
-import { combineReducers } from 'redux'
-import configureMockStore from 'redux-mock-store'
-import thunk from 'redux-thunk'
+import { MockedProvider } from 'react-apollo/test-utils'
+
 import Enzyme, { mount } from 'enzyme'
 import Adapter from 'enzyme-adapter-react-16'
 
 import { ThemeProvider } from 'styled-components'
-
-import { reducers } from 'pubsweet-client'
+import queries from './graphql/queries'
 
 import TeamsManagerPage from './TeamsManagerPage'
 
@@ -59,29 +57,24 @@ global.window.localStorage = {
   getItem: jest.fn(() => 'tok123'),
 }
 
-const reducer = combineReducers(reducers)
-
-const middlewares = [thunk]
-const mockStore = () =>
-  configureMockStore(middlewares)(actions =>
-    Object.assign(
-      actions.reduce(reducer, {
-        currentUser: { isAuthenticated: true },
-      }),
-      (actions.users || []).reduce(reducer, {
-        users: {
-          users: [
-            { id: '1', username: 'author' },
-            { id: '2', username: 'managing Editor' },
-          ],
-        },
-      }),
-    ),
-  )
+const mocks = [
+  {
+    request: {
+      query: queries.teamManager,
+    },
+    result: {
+      data: {
+        currentUser: { id: faker.random.uuid(), username: 'test', admin: true },
+        teams: [],
+        users: [],
+        manuscripts: [],
+      },
+    },
+  },
+]
 
 describe('TeamsManagerPage', () => {
   it('runs', done => {
-    const store = mockStore()
     const page = mount(
       <MemoryRouter>
         <ThemeProvider
@@ -90,9 +83,9 @@ describe('TeamsManagerPage', () => {
             colorSecondary: '#E7E7E7',
           }}
         >
-          <Provider store={store}>
+          <MockedProvider addTypename={false} mocks={mocks}>
             <TeamsManagerPage />
-          </Provider>
+          </MockedProvider>
         </ThemeProvider>
       </MemoryRouter>,
     )
diff --git a/packages/components/xpub-teams-manager/src/components/TeamsManagerPage.js b/packages/components/xpub-teams-manager/src/components/TeamsManagerPage.js
index a7cea464d47677fca85356a6a8df6b36a1dd65bb..f1e7d5cc1838bb7dbef42300e03a08fb54e31f00 100644
--- a/packages/components/xpub-teams-manager/src/components/TeamsManagerPage.js
+++ b/packages/components/xpub-teams-manager/src/components/TeamsManagerPage.js
@@ -1,46 +1,162 @@
 import { compose } from 'recompose'
-import { connect } from 'react-redux'
-import { actions } from 'pubsweet-client'
+import { cloneDeep, omit } from 'lodash'
 import config from 'config'
-import { ConnectPage } from 'xpub-connect'
+import { graphql } from 'react-apollo'
+import { gql } from 'apollo-client-preset'
 
+import queries from './graphql/queries'
 import TeamsManager from './TeamsManager'
 
+const deleteTeamMutation = gql`
+  mutation($id: ID) {
+    deleteTeam(id: $id) {
+      id
+      type
+      teamType
+      name
+      object {
+        objectId
+        objectType
+      }
+      members {
+        id
+        username
+      }
+    }
+  }
+`
+
+const createTeamMutation = gql`
+  mutation($input: TeamInput!) {
+    createTeam(input: $input) {
+      id
+      type
+      teamType
+      name
+      object {
+        objectId
+        objectType
+      }
+      members {
+        id
+        username
+      }
+    }
+  }
+`
+
+const updateTeamMutation = gql`
+  mutation($id: ID, $input: TeamInput) {
+    updateTeam(id: $id, input: $input) {
+      id
+      type
+      teamType
+      name
+      object {
+        objectId
+        objectType
+      }
+      members {
+        id
+        username
+      }
+    }
+  }
+`
+
 export default compose(
-  ConnectPage(() => [
-    actions.getCollections(),
-    actions.getTeams(),
-    actions.getUsers(),
-  ]),
-  connect(
-    state => {
-      const { collections, teams, error } = state
-      const { users } = state.users
-
-      const userOptions = users.map(user => ({
+  graphql(queries.teamManager, {
+    props: ({ data }) => {
+      const userOptions = ((data || {}).users || []).map(user => ({
         value: user.id,
         label: user.username,
       }))
 
-      const collectionsOptions = collections.map(collection => ({
-        value: collection.id,
-        label: collection.title,
+      const manuscriptsOptions = ((data || {}).manuscripts || []).map(manu => ({
+        value: manu.id,
+        label: manu.meta.title,
       }))
+
       const types = config.authsome.teams
       const typesOptions = Object.keys(types).map(type => ({
         value: type,
         label: `${types[type].name} ${types[type].permissions}`,
       }))
+      return {
+        teams: (data || {}).teams,
+        manuscriptsOptions,
+        userOptions,
+        typesOptions,
+      }
+    },
+  }),
+  graphql(updateTeamMutation, {
+    props: ({ mutate }) => {
+      const updateTeam = (members, team) => {
+        const data = cloneDeep(team)
+        const input = omit(data, ['id', 'object.__typename', '__typename'])
+
+        input.members = members
+        mutate({
+          variables: {
+            id: team.id,
+            input,
+          },
+        })
+      }
 
-      return { teams, collectionsOptions, userOptions, typesOptions, error }
+      return {
+        updateTeam,
+      }
     },
-    (dispatch, { history }) => ({
-      deleteTeam: collection => dispatch(actions.deleteTeam(collection)),
-      updateTeam: (members, team) => {
-        team = Object.assign(team, { members })
-        return dispatch(actions.updateTeam(team))
+  }),
+  graphql(deleteTeamMutation, {
+    props: ({ mutate }) => {
+      const deleteTeam = data => {
+        mutate({
+          variables: {
+            id: data.id,
+          },
+        })
+      }
+
+      return {
+        deleteTeam,
+      }
+    },
+    options: {
+      update: (proxy, { data: { deleteTeam } }) => {
+        const data = proxy.readQuery({ query: queries.teamManager })
+        const teamsIndex = data.teams.findIndex(
+          team => team.id === deleteTeam.id,
+        )
+        if (teamsIndex > -1) {
+          data.teams.splice(teamsIndex, 1)
+          proxy.writeQuery({ query: queries.teamManager, data })
+        }
+      },
+    },
+  }),
+  graphql(createTeamMutation, {
+    props: ({ mutate }) => {
+      const createTeam = input => {
+        mutate({
+          variables: {
+            input,
+          },
+        })
+      }
+
+      return {
+        createTeam,
+      }
+    },
+    options: {
+      update: (proxy, { data: { createTeam } }) => {
+        const data = proxy.readQuery({ query: queries.teamManager })
+        data.teams.push(createTeam)
+        proxy.writeQuery({ query: queries.teamManager, data })
       },
-      createTeam: team => dispatch(actions.createTeam(team)),
-    }),
-  ),
+    },
+  }),
 )(TeamsManager)
diff --git a/packages/components/xpub-teams-manager/src/components/graphql/queries/index.js b/packages/components/xpub-teams-manager/src/components/graphql/queries/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..3e78e3cf1cf032c1299824c02a60d1e6f755dd72
--- /dev/null
+++ b/packages/components/xpub-teams-manager/src/components/graphql/queries/index.js
@@ -0,0 +1,41 @@
+import gql from 'graphql-tag'
+
+const fragmentFields = `
+  id
+  created
+  meta {
+    title
+  }
+`
+
+export default {
+  teamManager: gql`
+  query {
+    teams {
+      id
+      teamType
+      name
+      object {
+        objectId
+        objectType
+      }
+      members {
+        id
+      }
+    }
+
+    users {
+      id
+      username
+      admin
+    }
+
+    manuscripts {
+      ${fragmentFields}
+      manuscriptVersions {
+        ${fragmentFields}
+      }
+    }
+  }
+  `,
+}
diff --git a/yarn.lock b/yarn.lock
index 0c0381f5f4f8d6cdc548ccc498a184894ce5a18e..32e5fb2b48261469682e67f8cacf25edf1d19f60 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3792,6 +3792,15 @@ classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5, classnames@^2.2.6:
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
   integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
 
+clean-tag@^1.0.4:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/clean-tag/-/clean-tag-1.1.0.tgz#83d0d3650d84805f7edeeda15a23d773069bf242"
+  integrity sha512-ly8usTlnKRFrL+D3+vZrX7lsDV+T9prxdL+IxLzjhKRIUYJlMWC4R3mUABWjb8AW33Wijy6UT2UUD2vYjPRF7A==
+  dependencies:
+    html-tags "^2.0.0"
+    react ">=16.0.0"
+    styled-system ">=2.0.0 || >=3.0.0"
+
 clean-webpack-plugin@^0.1.19:
   version "0.1.19"
   resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-0.1.19.tgz#ceda8bb96b00fe168e9b080272960d20fdcadd6d"
@@ -7312,6 +7321,14 @@ graphql@^14.0.2:
   dependencies:
     iterall "^1.2.2"
 
+grid-styled@^4.1.0:
+  version "4.3.3"
+  resolved "https://registry.yarnpkg.com/grid-styled/-/grid-styled-4.3.3.tgz#d3356e2ed5b5bb8433c0cd32e729e2a0d2ddcd03"
+  integrity sha512-aTwrgBrVGHimYIbHzC6fVxWhCjng/7OgHtU+CUq2vLeDk1Yq1EF35Ca3NUjFE3TVFZjAQ1505TqHXirgkUtFyA==
+  dependencies:
+    clean-tag "^1.0.4"
+    system-components "^2.2.3"
+
 growly@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@@ -13010,7 +13027,7 @@ react-dropdown@^1.6.2:
   dependencies:
     classnames "^2.2.3"
 
-react-dropzone@^4.1.2, react-dropzone@^4.2.7:
+react-dropzone@^4.1.2, react-dropzone@^4.2.7, react-dropzone@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-4.3.0.tgz#facdd7db16509772633c9f5200621ac01aa6706f"
   integrity sha512-ULfrLaTSsd8BDa9KVAGCueuq1AN3L14dtMsGGqtP0UwYyjG4Vhf158f/ITSHuSPYkZXbvfcIiOlZsH+e3QWm+Q==
@@ -13028,7 +13045,7 @@ react-fast-compare@^2.0.1:
   resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
   integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
 
-react-feather@^1.0.8:
+react-feather@^1.0.8, react-feather@^1.1.5:
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-1.1.5.tgz#f7f9384c17d2d061b5b8298f46efc0e497f48469"
   integrity sha512-hAPWatSFnhTNp9Ub96B7LMgOnWzXonA/LxqC2ANfUuc57jJocuWyO96yow2flUUDpitodh9mf6iOZzkyGYmAng==
@@ -13259,7 +13276,7 @@ react-transition-group@^2.0.0, react-transition-group@^2.2.0:
     prop-types "^15.6.2"
     react-lifecycles-compat "^3.0.4"
 
-react@^16.2.0:
+react@>=16.0.0, react@^16.2.0:
   version "16.7.0"
   resolved "https://registry.yarnpkg.com/react/-/react-16.7.0.tgz#b674ec396b0a5715873b350446f7ea0802ab6381"
   integrity sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==
@@ -15049,6 +15066,21 @@ styled-normalize@^8.0.4:
   resolved "https://registry.yarnpkg.com/styled-normalize/-/styled-normalize-8.0.4.tgz#6a0885dc16c61d88813dab8f5137da928fe0c947"
   integrity sha512-dXkKPnT+JcpqYnS0iQiBhUCOdheDz9xEm3H+ZARJV7l4WCXmRkwCx20ujkIYfESoYaiefbuX+F+rbtF6Ku5kvA==
 
+"styled-system@>=2.0.0 || >=3.0.0":
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/styled-system/-/styled-system-3.2.0.tgz#db08036035d6c5b8818ba7bccaf125d8711ecd9a"
+  integrity sha512-T/jDWstf++Bx0DFTnUHLMxkhnGZoJhZZO+bERGy5L/9uPnAiKthOZ1XxXcEaNrybM6PATMD/42rHIK+qpIVv+w==
+  dependencies:
+    "@babel/runtime" "^7.1.2"
+    prop-types "^15.6.2"
+
+styled-system@^2.3.1:
+  version "2.3.6"
+  resolved "https://registry.yarnpkg.com/styled-system/-/styled-system-2.3.6.tgz#a38c1ffa04a5c35adec46473984e463c48b16f7c"
+  integrity sha512-lGAh/8tC70f5hBUD7w0UOWCKyOBK2AzzWKu9BGzqla/Yjx8PzrvaciA7uATbm493hXTfRrecSdLdrIUET5IYnA==
+  dependencies:
+    prop-types "^15.6.0"
+
 stylelint-config-prettier@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/stylelint-config-prettier/-/stylelint-config-prettier-2.1.0.tgz#395874225ceef02ea8e31c2f4073098f4505b054"
@@ -15255,6 +15287,14 @@ symbol-tree@^3.2.2:
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
   integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=
 
+system-components@^2.2.3:
+  version "2.2.3"
+  resolved "https://registry.yarnpkg.com/system-components/-/system-components-2.2.3.tgz#3248f7c80affa4b9b61003ecd719a73da4462e59"
+  integrity sha512-zKcZEgqQEOGCTjug1L8Pkbw/ADJ3dI8iuwN6X1/+LLkE7PyCikjHckDFVtSYDLNgV1cjMlIlDPU17tngQoib9Q==
+  dependencies:
+    clean-tag "^1.0.4"
+    styled-system "^2.3.1"
+
 table@4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"