diff --git a/.gitignore b/.gitignore
index 1a7ebace04c0de469ef72c307c33ab2e300c40b7..82fedcb8fd2d087b874481324ec78e2a61f23cb6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,3 @@ uploads/
 config/local*.*
 .vscode/launch.json
 .DS_Store
-cypress/videos
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e2be730f05ccabfb13de8afe062496e7f37368d9..637cf1c84c3abdba95088f07cdd79297d80c8d61 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,9 +1,9 @@
 # CONTRIBUTING
 
-SimpleJ is a manuscript submission system, based on the discontinued [xpub-collabra](https://gitlab.coko.foundation/xpub/xpub) project.
+Kotahi is a manuscript submission system, based on the discontinued [xpub-collabra](https://gitlab.coko.foundation/xpub/xpub) project.
 It is currently under development by the [Coko Foundation](https://coko.foundation/) and is being built with [PubSweet](https://gitlab.coko.foundation/pubsweet/pubsweet). We welcome people of all kinds to join the community and contribute with knowledge, skills, expertise. Everyone is welcome in our chat room (https://mattermost.coko.foundation/coko/channels/town-square).
 
-In order to contribute to SimpleJ, you're expected to follow a few sensible guidelines.
+In order to contribute to Kotahi, you're expected to follow a few sensible guidelines.
 
 ## Discuss your contribution before you build
 
@@ -13,7 +13,7 @@ For contributions made as discussions and suggestions, you can at any time open
 
 ## Branches
 
-We maintain master as the production branch and tag it with release names. If you wish to contribute to SimpleJ then you need to make a branch and then issue a pull request following this procedure:
+We maintain master as the production branch and tag it with release names. If you wish to contribute to Kotahi then you need to make a branch and then issue a pull request following this procedure:
 Create a user account on Coko's GitLab: http://gitlab.coko.foundation
 Clone master with `git clone git@gitlab.coko.foundation:simplej/simplej.git`
 Create a new branch and work off of that. Please name the branch which sensibly identifies the feature you are working on. You can push the branch to GitLab at anytime.
@@ -39,6 +39,6 @@ We use conventional commits and verify that commit messages match the pattern, y
 ## Bug reports, feature requests, support questions
 
 This is all done through GitLab using their native issue tracker
-Visit the master issue tracker for SimpleJ (https://gitlab.coko.foundation/simplej/simplej/issues)
+Visit the master issue tracker for Kotahi (https://gitlab.coko.foundation/simplej/simplej/issues)
 Tag the issue with 'support', 'bug', or 'feature' to identify the nature of your issue
 
diff --git a/README.md b/README.md
index 4a30fa537414abe5a1499bec49a0fd65a647a061..35136847ac41c3869172da55dcae412e11664046 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
-# SimpleJ
-SimpleJ is a manuscript submission system, based on the discontinued [xpub-collabra](https://gitlab.coko.foundation/xpub/xpub) project.
+# Kotahi
+
+Kotahi is a manuscript submission system, based on the discontinued [xpub-collabra](https://gitlab.coko.foundation/xpub/xpub) project.
 It is currently under development by the [Coko Foundation](https://coko.foundation/) and is being built with [Pubsweet](https://gitlab.coko.foundation/pubsweet/pubsweet).
 
 ## Installation
@@ -9,6 +10,7 @@ Developer beware! This project is currently under very heavy development, so thi
 ### Running the app
 
 Start with installing the dependencies:
+
 ```
 yarn
 ```
@@ -17,9 +19,9 @@ Create the file `local-development.json` inside the `config` folder.
 
 ```json
 {
-    "pubsweet-server": {
-        "secret": "<your-secret-here>"
-    }
+  "pubsweet-server": {
+    "secret": "<your-secret-here>"
+  }
 }
 ```
 
@@ -30,6 +32,7 @@ yarn start:services
 ```
 
 Now (in a separate terminal) run the server (backend PubSweet app):
+
 ```
 yarn pubsweet start:server
 ```
@@ -41,6 +44,11 @@ docker run -e DATABASE_URL="postgres://yourusername@host.docker.internal/yourdat
 ```
 
 And in another terminal run the client (webpack-based PubSweet app):
+
 ```
 yarn pubsweet start:client
 ```
+
+# Other credits
+
+The real-time chat functionality was heavily inspired by https://github.com/withspectrum/spectrum, from data model approach to copying and adapting certain bits of their React app. Thank you, Spectrum!
diff --git a/app/Root.jsx b/app/Root.jsx
index a9129436c9989311c4458aa6cb64dbf087af9709..339ec005f06e24d58dbfd3ca3153d3061c96ccf1 100644
--- a/app/Root.jsx
+++ b/app/Root.jsx
@@ -1,33 +1,9 @@
 /* eslint-disable no-param-reassign */
 import React from 'react'
-// import { BrowserRouter } from 'react-router-dom'
-// import PropTypes from 'prop-types'
-// import { ThemeProvider } from 'styled-components'
-// import { ApolloProvider } from '@apollo/react-components'
-// import { ApolloClient } from 'apollo-client'
-// import { WebSocketLink } from 'apollo-link-ws'
-// import { split, ApolloLink } from 'apollo-link'
-// import { getMainDefinition } from 'apollo-utilities'
-// import { setContext } from 'apollo-link-context'
-// import {
-//   InMemoryCache,
-//   IntrospectionFragmentMatcher,
-// } from 'apollo-cache-inmemory'
-// import { createUploadLink } from 'apollo-upload-client'
-import GlobalStyle from './theme/elements/GlobalStyle'
-
-// import introspectionQueryResultData from './fragmentTypes.json'
-
 import { BrowserRouter } from 'react-router-dom'
 import PropTypes from 'prop-types'
 import { ThemeProvider } from 'styled-components'
-import {
-  ApolloProvider,
-  ApolloClient,
-  ApolloLink,
-  split,
-  gql,
-} from '@apollo/client'
+import { ApolloProvider, ApolloClient, ApolloLink, split } from '@apollo/client'
 // import { ApolloClient } from 'apollo-client'
 import { WebSocketLink } from '@apollo/client/link/ws'
 // import { split, ApolloLink } from 'apollo-link'
@@ -36,7 +12,7 @@ import { setContext } from '@apollo/client/link/context'
 import { InMemoryCache } from '@apollo/client/cache'
 import { createUploadLink } from 'apollo-upload-client'
 
-import { GET_CURRENT_USER } from './queries'
+import GlobalStyle from './theme/elements/GlobalStyle'
 import currentRolesVar from './shared/currentRolesVar'
 
 // See https://github.com/apollographql/apollo-feature-requests/issues/6#issuecomment-465305186
@@ -101,9 +77,6 @@ const makeApolloClient = (makeConfig, connectToWebSocket) => {
   const config = {
     link,
     cache: new InMemoryCache({
-      possibleTypes: {
-        Identity: ['LocalIdentity', 'ExternalIdentity'],
-      },
       typePolicies: {
         Manuscript: {
           fields: {
diff --git a/app/components/AdminPage.js b/app/components/AdminPage.js
index 114eee5766afe34b8d420fe83fb91e5dca6f6b41..e5b275977e4ca9e4d08720b8d3140435643bce1a 100644
--- a/app/components/AdminPage.js
+++ b/app/components/AdminPage.js
@@ -70,7 +70,9 @@ const PrivateRoute = ({ component: Component, ...rest }) => (
 )
 
 const updateStuff = data => {
-  currentRolesVar(data.currentUser._currentRoles)
+  if (data?.currentUser) {
+    return currentRolesVar(data.currentUser._currentRoles)
+  }
 }
 
 const AdminPage = ({ children, history, match }) => {
diff --git a/app/components/component-chat/src/MentionsInput/MentionsInput.jsx b/app/components/component-chat/src/MentionsInput/MentionsInput.jsx
index 3334f25748058f166fb005bc0fbef852d3b97c53..b7a0113fa1a7e0b300c86dab2ab637433c396967 100644
--- a/app/components/component-chat/src/MentionsInput/MentionsInput.jsx
+++ b/app/components/component-chat/src/MentionsInput/MentionsInput.jsx
@@ -29,7 +29,8 @@ const cleanSuggestionUserObject = user => {
     ...user,
     id: user.username,
     display: user.username,
-    filterName: (user.name && user.name.toLowerCase()) || user.username.toLowerCase(),
+    filterName:
+      (user.name && user.name.toLowerCase()) || user.username.toLowerCase(),
   }
 }
 
@@ -82,8 +83,9 @@ const CustomMentionsInput = props => {
       return
     }
 
-    const cleanSearchUsers = rawSearchUsers.map(user => cleanSuggestionUserObject(user))
-
+    const cleanSearchUsers = rawSearchUsers.map(user =>
+      cleanSuggestionUserObject(user),
+    )
 
     // Prepend the filtered participants in case a user is tabbing down right now
     const fullResults = [...staticSuggestions, ...cleanSearchUsers]
@@ -116,9 +118,9 @@ const CustomMentionsInput = props => {
     >
       <Mention
         appendSpaceOnAdd
+        data={searchUsers}
         displayTransform={username => `@${username}`}
         markup="@[__id__]"
-        data={searchUsers}
         renderSuggestion={(
           entry,
           search,
diff --git a/app/components/component-chat/src/Messages/MessageRenderer.jsx b/app/components/component-chat/src/Messages/MessageRenderer.jsx
index 03c2f6624f2abfe5113f60d896a2eae51c499844..4e801f4ecc199367a7943299e1b3c43dc0d81929 100644
--- a/app/components/component-chat/src/Messages/MessageRenderer.jsx
+++ b/app/components/component-chat/src/Messages/MessageRenderer.jsx
@@ -1,118 +1,8 @@
 import React from 'react'
 import ReactMarkdown from 'react-markdown'
-import htmlParser from 'react-markdown/plugins/html-parser'
-import { useQuery } from '@apollo/client'
-import gql from 'graphql-tag'
-import styled from 'styled-components'
-import { th } from '@pubsweet/ui-toolkit'
 
-import PaperEmbed from './PaperEmbed'
-
-import { AspectRatio, EmbedContainer, EmbedComponent } from './style'
-
-const ExternalEmbed = props => {
-  let { aspectratio, url, src, width = '100%', height = 200 } = props
-
-  if (!src && url) src = url
-  if (typeof src !== 'string') return null
-
-  // if an aspect ratio is passed in, we need to use the EmbedComponent which does some trickery with padding to force an aspect ratio. Otherwise we should just use a regular iFrame
-  if (aspectratio && aspectratio !== undefined) {
-    return (
-      <AspectRatio ratio={aspectratio} style={{ height }}>
-        <EmbedComponent
-          allowFullScreen
-          frameBorder="0"
-          height={height}
-          src={src}
-          title={`iframe-${src}`}
-          width={width}
-        />
-      </AspectRatio>
-    )
-  }
-  return (
-    <EmbedContainer style={{ height }}>
-      <iframe
-        allowFullScreen
-        frameBorder="0"
-        height={height}
-        src={src}
-        title={`iframe-${src}`}
-        width={width}
-      />
-    </EmbedContainer>
-  )
-}
-
-const InternalEmbed = props => {
-  if (props.entity !== 'thread') return null
-  return 'INTERNAL'
-  // return <ThreadAttachment id={props.id} />
-}
-
-const Embed = props => {
-  // if (props.type === 'internal') {
-  //   return <InternalEmbed {...props} />
-  // }
-
-  // if (props.type === 'paper') {
-  //   return <PaperEmbed {...props} />
-  // }
-  return <></>
-  // return <ExternalEmbed {...props} />
-}
-
-// var preprocessingInstructions = [
-//   {
-//     shouldPreprocessNode: function (node) {
-//       return node.attribs && node.attribs['data-process'] === 'shared';
-//     },
-//     preprocessNode: function (node) {
-//       node.attribs = {id: `preprocessed-${node.attribs.id}`,};
-//     },
-//   }
-// ];
-const processingInstructions = [
-  {
-    shouldProcessNode(node) {
-      return node.name === 'embed'
-    },
-    processNode(node, children, index) {
-      return <Embed {...node.attribs} id={node.attribs.id} key={index} />
-    },
-  },
-  {
-    shouldProcessNode(node) {
-      return node.name === 'p'
-    },
-    processNode(node, children, index) {
-      return (
-        <div {...node.attribs} id={node.attribs.id} key={index}>
-          {children}
-        </div>
-      )
-    },
-  },
-]
-
-const parseHtml = htmlParser({
-  processingInstructions,
-})
-
-const MessageRenderer = React.memo(({ message }) => {
-  const p = props => <div>{props.children}</div>
-
-  return message.enhanced ? (
-    <ReactMarkdown
-      astPlugins={[parseHtml]}
-      escapeHtml={false}
-      renderers={{ paragraph: p }}
-      source={message.enhanced}
-    />
-  ) : (
-    <ReactMarkdown source={message.content} />
-  )
-})
+const MessageRenderer = React.memo(({ message }) => (
+  <ReactMarkdown source={message.content} />
+))
 
 export default MessageRenderer
diff --git a/app/components/component-chat/src/Messages/Messages.jsx b/app/components/component-chat/src/Messages/Messages.jsx
index 8ca81aaf86f06c4cd7ae419a9e21f30d83cad1f7..89a2fa0bbe7c97685619927bb5e269b808568420 100644
--- a/app/components/component-chat/src/Messages/Messages.jsx
+++ b/app/components/component-chat/src/Messages/Messages.jsx
@@ -35,12 +35,8 @@ const GET_MESSAGES = gql`
           profilePicture
           online
           defaultIdentity {
-            ... on ExternalIdentity {
-              identifier
-            }
-            ... on LocalIdentity {
-              email
-            }
+            identifier
+            email
             type
             aff
             id
@@ -69,15 +65,11 @@ const MESSAGES_SUBSCRIPTION = gql`
         profilePicture
         online
         defaultIdentity {
-          id
-          ... on ExternalIdentity {
-            identifier
-          }
-          ... on LocalIdentity {
-            email
-          }
+          identifier
+          email
           type
           aff
+          id
           name
         }
       }
@@ -85,7 +77,6 @@ const MESSAGES_SUBSCRIPTION = gql`
   }
 `
 
-
 const subscribeToNewMessages = (subscribeToMore, channelId) =>
   subscribeToMore({
     document: MESSAGES_SUBSCRIPTION,
@@ -183,7 +174,10 @@ const Messages = ({ channelId }) => {
         </NextPageButton>
       )}
       {messages && !messages.length && (
-        <Placeholder>No discussion for this manuscript yet. Start by typing a message below.</Placeholder>
+        <Placeholder>
+          No discussion for this manuscript yet. Start by typing a message
+          below.
+        </Placeholder>
       )}
       {messages.map(group => {
         const initialMessage = group[0]
@@ -210,7 +204,9 @@ const Messages = ({ channelId }) => {
                   {index === 0 && <UserAvatar user={message.user} />}
                 </GutterContainer>
                 <InnerMessageContainer>
-                  {index === 0 && <Byline>{message.user.defaultIdentity.name}</Byline>}
+                  {index === 0 && (
+                    <Byline>{message.user.defaultIdentity.name}</Byline>
+                  )}
                   <Bubble>
                     <MessageRenderer message={message} />
                   </Bubble>
diff --git a/app/components/component-chat/src/SuperChatInput/SuperChatInput.jsx b/app/components/component-chat/src/SuperChatInput/SuperChatInput.jsx
index eff4db7973a2a3ebc1cba86ba2ba4a15b15a40d3..f84d21a878c6d863cef3989323a7206bba7d0968 100644
--- a/app/components/component-chat/src/SuperChatInput/SuperChatInput.jsx
+++ b/app/components/component-chat/src/SuperChatInput/SuperChatInput.jsx
@@ -23,7 +23,7 @@ import {
   RemovePreviewButton,
 } from './style'
 
-import { CREATE_MESSAGE, GET_MESSAGE_BY_ID } from '../../../../queries'
+import { CREATE_MESSAGE } from '../../../../queries'
 
 // import sendDirectMessage from 'shared/graphql/mutations/message/sendDirectMessage'
 // import { getMessageById } from 'shared/graphql/queries/message/getMessage'
@@ -101,7 +101,7 @@ export const cleanSuggestionUserObject = user => {
 const SuperChatInput = props => {
   const currentUser = useCurrentUser()
   const [sendChannelMessage] = useMutation(CREATE_MESSAGE)
-  const [sendDirectMessage] = useMutation(CREATE_MESSAGE)
+  // const [sendDirectMessage] = useMutation(CREATE_MESSAGE)
 
   const cacheKey = `last-content-${props.channelId}`
   const [text, changeText] = React.useState('')
@@ -155,6 +155,7 @@ const SuperChatInput = props => {
       // If backspace is pressed on the empty
       case 'Backspace': {
         if (text.length === 0) removeAttachments()
+        break
       }
       default:
     }
@@ -239,9 +240,9 @@ const SuperChatInput = props => {
           setMediaPreview(null)
           setAttachedMediaFile(null)
         })
-        .catch(err => {
+        .catch(_ => {
           setIsSendingMediaMessage(false)
-          props.dispatch(addToastWithTimeout('error', err.message))
+          // props.dispatch(addToastWithTimeout('error', err.message))
         })
     }
 
@@ -271,36 +272,38 @@ const SuperChatInput = props => {
   }
 
   // $FlowFixMe
+  // eslint-disable-next-line no-unused-vars
   const [isSendingMediaMessage, setIsSendingMediaMessage] = React.useState(
     false,
   )
+
   // $FlowFixMe
   const [mediaPreview, setMediaPreview] = React.useState(null)
   // $FlowFixMe
   const [mediaFile, setAttachedMediaFile] = React.useState(null)
 
-  const previewMedia = blob => {
-    if (isSendingMediaMessage) return
-    setIsSendingMediaMessage(true)
-    setAttachedMediaFile(blob)
-    inputRef && inputRef.focus()
+  // const previewMedia = blob => {
+  //   if (isSendingMediaMessage) return
+  //   setIsSendingMediaMessage(true)
+  //   setAttachedMediaFile(blob)
+  //   inputRef && inputRef.focus()
 
-    const reader = new FileReader()
-    reader.onload = () => {
-      setMediaPreview(reader.result.toString())
-      setIsSendingMediaMessage(false)
-    }
+  //   const reader = new FileReader()
+  //   reader.onload = () => {
+  //     setMediaPreview(reader.result.toString())
+  //     setIsSendingMediaMessage(false)
+  //   }
 
-    if (blob) {
-      reader.readAsDataURL(blob)
-    }
-  }
+  //   if (blob) {
+  //     reader.readAsDataURL(blob)
+  //   }
+  // }
 
   const removeQuotedMessage = () => {
-    if (props.quotedMessage)
-      props.dispatch(
-        replyToMessage({ threadId: props.threadId, messageId: null }),
-      )
+    // if (props.quotedMessage)
+    //   props.dispatch(
+    //     replyToMessage({ threadId: props.threadId, messageId: null }),
+    //   )
   }
 
   const networkDisabled =
diff --git a/app/components/component-dashboard/src/components/Dashboard.js b/app/components/component-dashboard/src/components/Dashboard.js
index 221cb872ba1a6a73da87ae3ed866fdd4e5fead50..899780e5f54c8e17dc9f24885d091077f2a20d64 100644
--- a/app/components/component-dashboard/src/components/Dashboard.js
+++ b/app/components/component-dashboard/src/components/Dashboard.js
@@ -5,7 +5,7 @@ import { Button } from '@pubsweet/ui'
 
 import queries from '../graphql/queries/'
 import mutations from '../graphql/mutations/'
-import { Container, Heading, HeadingWithAction, Placeholder } from '../style'
+import { Container, Placeholder } from '../style'
 import EditorItem from './sections/EditorItem'
 import OwnerItem from './sections/OwnerItem'
 import ReviewerItem from './sections/ReviewerItem'
@@ -15,48 +15,28 @@ import {
   Title,
   SectionRow,
   SectionContent,
+  Heading,
+  HeadingWithAction,
 } from '../../../shared'
 
 import hasRole from '../../../../shared/hasRole'
 
-const updateReviewer = (proxy, { data: { reviewerResponse } }) => {
-  const id = reviewerResponse.object.objectId
-  const data = proxy.readQuery({
-    query: queries.dashboard,
-    variables: {
-      id,
-    },
-  })
-
-  const manuscriptIndex = data.manuscripts.findIndex(manu => manu.id === id)
-  const teamIndex = data.manuscripts[manuscriptIndex].teams.findIndex(
-    team => team.id === reviewerResponse.id,
-  )
-
-  data.manuscripts[manuscriptIndex].teams[teamIndex] = reviewerResponse
-  proxy.writeQuery({ query: queries.dashboard, data })
-}
-
 const Dashboard = ({ history, ...props }) => {
-  // const uploadManuscript = upload()
-  // const [conversion] = useContext(XpubContext)
-
   const { loading, data, error } = useQuery(queries.dashboard)
-  const [reviewerRespond] = useMutation(mutations.reviewerResponseMutation, {
-    // variables: { currentUserId, action, teamId },
-    update: updateReviewer,
-  })
+  const [reviewerRespond] = useMutation(mutations.reviewerResponseMutation)
+
   const [deleteManuscript] = useMutation(mutations.deleteManuscriptMutation, {
-    // variables: { id: manuscript.id },
     update: (proxy, { data: { deleteManuscript } }) => {
       const data = proxy.readQuery({ query: queries.dashboard })
-      const manuscriptIndex = data.manuscripts.findIndex(
-        manuscript => manuscript.id === deleteManuscript,
+      const manuscripts = data.manuscripts.filter(
+        manuscript => manuscript.id !== deleteManuscript,
       )
-      if (manuscriptIndex > -1) {
-        data.manuscripts.splice(manuscriptIndex, 1)
-        proxy.writeQuery({ query: queries.dashboard, data })
-      }
+      proxy.writeQuery({
+        query: queries.dashboard,
+        data: {
+          manuscripts,
+        },
+      })
     },
   })
 
@@ -70,7 +50,12 @@ const Dashboard = ({ history, ...props }) => {
   )
 
   const toReview = dashboard.filter(submission =>
-    hasRole(submission, 'reviewer'),
+    hasRole(submission, [
+      'reviewer',
+      'invited:reviewer',
+      'accepted:reviewer',
+      'completed:reviewer',
+    ]),
   )
 
   const manuscriptsImEditorOf = dashboard.filter(submission =>
@@ -90,7 +75,7 @@ const Dashboard = ({ history, ...props }) => {
         <SectionHeader>
           <Title>My Submissions</Title>
         </SectionHeader>
-        {dashboard.length > 0 ? (
+        {mySubmissions.length > 0 ? (
           mySubmissions.map(submission => (
             <SectionRow key={`submission-${submission.id}`}>
               <OwnerItem
diff --git a/app/components/component-dashboard/src/components/metadata/MetadataSubmittedDate.js b/app/components/component-dashboard/src/components/metadata/MetadataSubmittedDate.js
index dc92f04fedbab3666852d671b5b090a2435a33c3..0cdfd16542f867ab35e77c2f20c3dd7bad16af50 100644
--- a/app/components/component-dashboard/src/components/metadata/MetadataSubmittedDate.js
+++ b/app/components/component-dashboard/src/components/metadata/MetadataSubmittedDate.js
@@ -3,7 +3,7 @@ import Moment from 'react-moment'
 
 const MetadataSubmittedDate = ({ submitted }) => (
   <span>
-    <Moment format="YYYY-MM-DD">{submitted}</Moment>
+    Submitted on <Moment format="YYYY-MM-DD">{submitted}</Moment>.
   </span>
 )
 
diff --git a/app/components/component-dashboard/src/components/sections/EditorItem.js b/app/components/component-dashboard/src/components/sections/EditorItem.js
index dcc6d539ea3deaf6f1b557d0fd67f990d67210d4..b6abd5d49a4f11c221ff25e104bd5bffa70c0754 100644
--- a/app/components/component-dashboard/src/components/sections/EditorItem.js
+++ b/app/components/component-dashboard/src/components/sections/EditorItem.js
@@ -3,15 +3,13 @@ import styled from 'styled-components'
 import { Action, ActionGroup } from '@pubsweet/ui'
 import { Item, StatusBadge } from '../../style'
 import Meta from '../metadata/Meta'
-import MetadataSections from '../metadata/MetadataSections'
-import MetadataType from '../metadata/MetadataType'
-import MetadataReviewType from '../metadata/MetadataReviewType'
 import MetadataSubmittedDate from '../metadata/MetadataSubmittedDate'
 import MetadataAuthors from '../metadata/MetadataAuthors'
 import MetadataStreamLined from '../metadata/MetadataStreamLined'
 import JournalLink from '../JournalLink'
 import Reviews from '../Reviews'
 import VersionTitle from './VersionTitle'
+import prettyRoleText from '../../../../../shared/prettyRoleText'
 
 const VersionTitleLink = styled(JournalLink)`
   text-decoration: none;
@@ -72,13 +70,7 @@ const EditorItem = ({ version }) => (
         {getSubmitedDate(version) ? (
           <MetadataSubmittedDate submitted={getSubmitedDate(version).date} />
         ) : null}
-        <MetadataType type={getMetadataObject(version, 'articleType')} />
-        <MetadataSections
-          sections={getMetadataObject(version, 'articleSections')}
-        />
-        <MetadataReviewType
-          openPeerReview={getDeclarationsObject(version, 'openPeerReview')}
-        />
+        &nbsp;You are {prettyRoleText(version._currentRoles)}.
       </Meta>
     </Item>
     <Item>
diff --git a/app/components/component-dashboard/src/components/sections/ReviewerItem.js b/app/components/component-dashboard/src/components/sections/ReviewerItem.js
index d2fa115295cfd673c9e8e01e02f6df329b8cba84..9b0eb409865a44725d800f6158fe12965faa3da0 100644
--- a/app/components/component-dashboard/src/components/sections/ReviewerItem.js
+++ b/app/components/component-dashboard/src/components/sections/ReviewerItem.js
@@ -26,21 +26,6 @@ const ReviewerItem = ({ version, journals, currentUser, reviewerRespond }) => {
     team.members.find(member => member.user.id === currentUser.id)
   const status = currentMember && currentMember.status
 
-  // Enable that when Team Models is updated
-  // const { status } =
-  //   getUserFromTeam(version, 'reviewer').filter(
-  //     member => member.id === currentUser.id,
-  //   )[0] || {}
-
-  // const review =
-  //   (version.reviews || []).find(
-  //     review =>
-  //       currentUser &&
-  //       review.user &&
-  //       review.user.id === currentUser.id &&
-  //       !review.isDecision,
-  //   ) || {}
-
   return (
     <Item>
       <VersionTitle version={version} />
diff --git a/app/components/component-formbuilder/src/components/ComponentProperties.jsx b/app/components/component-formbuilder/src/components/ComponentProperties.jsx
index 88e837b92b464263460634d273572523af769208..ff375d341d4651690209e96dc3f4e4985c11c232 100644
--- a/app/components/component-formbuilder/src/components/ComponentProperties.jsx
+++ b/app/components/component-formbuilder/src/components/ComponentProperties.jsx
@@ -59,6 +59,7 @@ const ComponentProperties = ({
               onChange={event => {
                 let value = {}
                 if (event.target) {
+                  // eslint-disable-next-line prefer-destructuring
                   value = event.target.value
                 } else {
                   value = event
diff --git a/app/components/component-formbuilder/src/components/FormBuilder.jsx b/app/components/component-formbuilder/src/components/FormBuilder.jsx
index 81eb4d088c96495f99beef64d8d819f84d122c98..b8b6d20202dd47ec8ef3efdb298560e44750ee20 100644
--- a/app/components/component-formbuilder/src/components/FormBuilder.jsx
+++ b/app/components/component-formbuilder/src/components/FormBuilder.jsx
@@ -1,11 +1,4 @@
 import React, { useState } from 'react'
-import {
-  compose,
-  withState,
-  withHandlers,
-  lifecycle,
-  // setDisplayName,
-} from 'recompose'
 import styled, { withTheme } from 'styled-components'
 import { unescape } from 'lodash'
 import { th } from '@pubsweet/ui-toolkit'
@@ -141,27 +134,3 @@ const FormBuilder = ({
 }
 
 export default FormBuilder
-// FormBuilder.displayName = 'FormBuilder'
-
-// export default compose(
-// withState('elements', 'onAddElements', ({ form }) => form.children || []),
-// withHandlers({
-//   addElements: ({ onAddElements, form }) => addElement =>
-//     onAddElements(() => {
-//       const addEl = { children: form.children || [] }
-//       addEl.children = [...addEl.children, addElement]
-//       return addEl.children
-//     }),
-// }),
-// lifecycle({
-//   componentWillReceiveProps(nextProps) {
-//     if (this.props.form.children !== nextProps.form.children) {
-//       this.setState({ elements: nextProps.form.children })
-//     }
-
-//     if (this.props.elements !== nextProps.elements) {
-//       this.setState({ elements: nextProps.elements })
-//     }
-//   },
-// }),
-// )(FormBuilder)
diff --git a/app/components/component-formbuilder/src/redux/FormBuilder.js b/app/components/component-formbuilder/src/redux/FormBuilder.js
index edf54a138bfc7251860e5e3f3e200a684e48433b..26325d6da707cd4c3b480215d436937bdebf7276 100644
--- a/app/components/component-formbuilder/src/redux/FormBuilder.js
+++ b/app/components/component-formbuilder/src/redux/FormBuilder.js
@@ -1,135 +1,135 @@
-import * as api from 'pubsweet-client/src/helpers/api'
-
-export const GET_FORM_REQUEST = 'GET_FORM_REQUEST'
-export const GET_FORM_SUCCESS = 'GET_FORM_SUCCESS'
-export const GET_FORM_FAILURE = 'GET_FORM_FAILURE'
-
-function getFormRequest(project, version) {
-  return {
-    type: GET_FORM_REQUEST,
-  }
-}
-
-function getFormSuccess(forms) {
-  return {
-    type: GET_FORM_SUCCESS,
-    forms,
-  }
-}
-
-function getFormFailure(error) {
-  return {
-    type: GET_FORM_FAILURE,
-    error,
-  }
-}
-
-export function getForms() {
-  return dispatch => {
-    dispatch(getFormRequest())
-
-    return api
-      .get('/get-forms', {})
-      .then(result => {
-        dispatch(getFormSuccess(result))
-      })
-      .catch(error => dispatch(getFormFailure(error)))
-  }
-}
-
-export function getForm(formId) {
-  return dispatch => {
-    dispatch(getFormRequest())
-
-    return api
-      .get(`/get-form/${formId}`, {})
-      .then(result => {
-        dispatch(getFormSuccess(result))
-      })
-      .catch(error => dispatch(getFormFailure(error)))
-  }
-}
-
-export function updateForms(form, properties) {
-  return dispatch => {
-    dispatch(getFormRequest())
-
-    return api
-      .update(`/update-forms/${form.id}`, properties)
-      .then(result => {
-        dispatch(getFormSuccess(result))
-      })
-      .catch(error => dispatch(getFormFailure(error)))
-  }
-}
-
-export function updateElements(form, properties) {
-  return dispatch => {
-    dispatch(getFormRequest())
-
-    return api
-      .update(
-        `/update-forms/${form.id}/element/${properties.children.id}`,
-        properties,
-      )
-      .then(result => {
-        dispatch(getFormSuccess(result))
-      })
-      .catch(error => dispatch(getFormFailure(error)))
-  }
-}
-
-export function deleteForms(form) {
-  return dispatch => {
-    dispatch(getFormRequest())
-
-    return api
-      .remove(`/delete-forms/${form.id}`)
-      .then(result => {
-        dispatch(getFormSuccess(result))
-      })
-      .catch(error => dispatch(getFormFailure(error)))
-  }
-}
-
-export function deleteElements(form, element) {
-  return dispatch => {
-    dispatch(getFormRequest())
-
-    return api
-      .remove(`/delete-forms/${form.id}/elements/${element.id}`)
-      .then(result => {
-        dispatch(getFormSuccess(result))
-      })
-      .catch(error => dispatch(getFormFailure(error)))
-  }
-}
-
-export function createForms(properties) {
-  return dispatch => {
-    dispatch(getFormRequest())
-
-    return api
-      .create('/create-forms', properties)
-      .then(result => {
-        dispatch(getFormSuccess(result))
-      })
-      .catch(error => dispatch(getFormFailure(error)))
-  }
-}
-
-const initialState = {}
-export default (state = initialState, action) => {
-  switch (action.type) {
-    case GET_FORM_SUCCESS:
-      return {
-        forms: action.forms.forms,
-      }
-
-    case GET_FORM_FAILURE:
-      return { error: action.error }
-
-    default:
-      return state
-  }
-}
+// import * as api from 'pubsweet-client/src/helpers/api'
+
+// export const GET_FORM_REQUEST = 'GET_FORM_REQUEST'
+// export const GET_FORM_SUCCESS = 'GET_FORM_SUCCESS'
+// export const GET_FORM_FAILURE = 'GET_FORM_FAILURE'
+
+// function getFormRequest(project, version) {
+//   return {
+//     type: GET_FORM_REQUEST,
+//   }
+// }
+
+// function getFormSuccess(forms) {
+//   return {
+//     type: GET_FORM_SUCCESS,
+//     forms,
+//   }
+// }
+
+// function getFormFailure(error) {
+//   return {
+//     type: GET_FORM_FAILURE,
+//     error,
+//   }
+// }
+
+// export function getForms() {
+//   return dispatch => {
+//     dispatch(getFormRequest())
+
+//     return api
+//       .get('/get-forms', {})
+//       .then(result => {
+//         dispatch(getFormSuccess(result))
+//       })
+//       .catch(error => dispatch(getFormFailure(error)))
+//   }
+// }
+
+// export function getForm(formId) {
+//   return dispatch => {
+//     dispatch(getFormRequest())
+
+//     return api
+//       .get(`/get-form/${formId}`, {})
+//       .then(result => {
+//         dispatch(getFormSuccess(result))
+//       })
+//       .catch(error => dispatch(getFormFailure(error)))
+//   }
+// }
+
+// export function updateForms(form, properties) {
+//   return dispatch => {
+//     dispatch(getFormRequest())
+
+//     return api
+//       .update(`/update-forms/${form.id}`, properties)
+//       .then(result => {
+//         dispatch(getFormSuccess(result))
+//       })
+//       .catch(error => dispatch(getFormFailure(error)))
+//   }
+// }
+
+// export function updateElements(form, properties) {
+//   return dispatch => {
+//     dispatch(getFormRequest())
+
+//     return api
+//       .update(
+//         `/update-forms/${form.id}/element/${properties.children.id}`,
+//         properties,
+//       )
+//       .then(result => {
+//         dispatch(getFormSuccess(result))
+//       })
+//       .catch(error => dispatch(getFormFailure(error)))
+//   }
+// }
+
+// export function deleteForms(form) {
+//   return dispatch => {
+//     dispatch(getFormRequest())
+
+//     return api
+//       .remove(`/delete-forms/${form.id}`)
+//       .then(result => {
+//         dispatch(getFormSuccess(result))
+//       })
+//       .catch(error => dispatch(getFormFailure(error)))
+//   }
+// }
+
+// export function deleteElements(form, element) {
+//   return dispatch => {
+//     dispatch(getFormRequest())
+
+//     return api
+//       .remove(`/delete-forms/${form.id}/elements/${element.id}`)
+//       .then(result => {
+//         dispatch(getFormSuccess(result))
+//       })
+//       .catch(error => dispatch(getFormFailure(error)))
+//   }
+// }
+
+// export function createForms(properties) {
+//   return dispatch => {
+//     dispatch(getFormRequest())
+
+//     return api
+//       .create('/create-forms', properties)
+//       .then(result => {
+//         dispatch(getFormSuccess(result))
+//       })
+//       .catch(error => dispatch(getFormFailure(error)))
+//   }
+// }
+
+// const initialState = {}
+// export default (state = initialState, action) => {
+//   switch (action.type) {
+//     case GET_FORM_SUCCESS:
+//       return {
+//         forms: action.forms.forms,
+//       }
+
+//     case GET_FORM_FAILURE:
+//       return { error: action.error }
+
+//     default:
+//       return state
+//   }
+// }
diff --git a/app/components/component-login/src/Login.jsx b/app/components/component-login/src/Login.jsx
index 9fd99f091f431bfbd4b8bbba646a120f0c80cc52..d04d53a279a966cc029205f90a65debd58ebc2cd 100644
--- a/app/components/component-login/src/Login.jsx
+++ b/app/components/component-login/src/Login.jsx
@@ -1,13 +1,9 @@
 import React, { useState } from 'react'
 import { Redirect } from 'react-router-dom'
-import PropTypes from 'prop-types'
-import { withFormik } from 'formik'
 import config from 'config'
 import { th, grid, lighten } from '@pubsweet/ui-toolkit'
-import { CenteredColumn, H1, Button } from '@pubsweet/ui'
+import { H1, Button } from '@pubsweet/ui'
 import styled from 'styled-components'
-import { Section } from '../../shared'
-import { Placeholder } from '../../component-chat/src/Messages/style'
 
 const getNextUrl = () => {
   const url = new URL(window.location.href)
@@ -21,26 +17,6 @@ const getNextUrl = () => {
   return `${url.searchParams.get('next') || redirectLink}`
 }
 
-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)
-        setTimeout(() => {
-          props.onLoggedIn(getNextUrl())
-        }, 100)
-      }
-    })
-    .catch(e => {
-      if (e.graphQLErrors && e.graphQLErrors.length > 0) {
-        setSubmitting(false)
-        setErrors(e.graphQLErrors[0].message)
-      }
-    })
-
 const getToken = props => {
   const { location } = props
   if (location && location.search && location.search.match(/^\?token=/)) {
@@ -114,18 +90,11 @@ const StyledORCIDIcon = styled(ORCIDIcon)`
 `
 
 const Login = ({ logo = null, ...props }) => {
-  // Is ORCID authentication enabled?
-  const orcid =
-    config['pubsweet-component-login'] &&
-    config['pubsweet-component-login'].orcid
-
   const token = getToken(props)
   // If a JWT token is supplied as a query param (e.g. from OAuth)
   // go ahead and fetch the redirect URL
   const initialRedirectLink = token ? getNextUrl() : null
-  const [redirectLink, setRedirectLink] = useState(initialRedirectLink)
-  // Also set the redirect link upon successful login (via handleSubmit)
-  const onLoggedIn = () => setRedirectLink(getNextUrl())
+  const [redirectLink] = useState(initialRedirectLink)
 
   if (token) {
     window.localStorage.setItem('token', token)
@@ -140,7 +109,7 @@ const Login = ({ logo = null, ...props }) => {
       <Centered>
         <Content>
           {journalName === 'Aperture' && (
-            <img src="/public/logo-aperture.png" />
+            <img alt="Aperture" src="/public/logo-aperture.png" />
           )}
           <H1>Login to {journalName}</H1>
           {journalName} uses ORCID <StyledORCIDIcon /> to identify authors and
diff --git a/app/components/component-manuscripts/src/Manuscripts.jsx b/app/components/component-manuscripts/src/Manuscripts.jsx
index dd3dee928e6f6d45e9e95c9b37c98c6e438ca810..574397c14fe108366d1593f3b0e2ae30c7b07c4f 100644
--- a/app/components/component-manuscripts/src/Manuscripts.jsx
+++ b/app/components/component-manuscripts/src/Manuscripts.jsx
@@ -42,6 +42,7 @@ const GET_MANUSCRIPTS = gql`
           username
           online
           defaultIdentity {
+            id
             name
           }
           profilePicture
diff --git a/app/components/component-profile/src/FormGrid.jsx b/app/components/component-profile/src/FormGrid.jsx
index e9144c8135943ecd708ab0d52327b1eeb93e1837..6c027ed888a3a75d20cef622775de9ecc110c7ce 100644
--- a/app/components/component-profile/src/FormGrid.jsx
+++ b/app/components/component-profile/src/FormGrid.jsx
@@ -38,4 +38,3 @@ export const FormRow = styled.div`
     margin-left: calc(${th('gridUnit')} * 2);
   }
 `
-
diff --git a/app/components/component-profile/src/PageWithHeader.jsx b/app/components/component-profile/src/PageWithHeader.jsx
index 28929e72dceb1885f0a18229fe1f63462e69caef..0570a64ea6b405cae75cdc68de9f1c09771c3fd4 100644
--- a/app/components/component-profile/src/PageWithHeader.jsx
+++ b/app/components/component-profile/src/PageWithHeader.jsx
@@ -50,8 +50,8 @@ const PageWithHeader = ({ children, header }) => (
         <Heading>{header}</Heading>
       </HeaderText>
     </StyledHeader>
-    { children }
+    {children}
   </Settings>
 )
 
-export default PageWithHeader
\ No newline at end of file
+export default PageWithHeader
diff --git a/app/components/component-profile/src/Profile.jsx b/app/components/component-profile/src/Profile.jsx
index a137940b612b238a9e0cd3986bba83dadf09d2be..a3ee135b84a5ada6c1615e9eae8a5b49e6a5ff71 100644
--- a/app/components/component-profile/src/Profile.jsx
+++ b/app/components/component-profile/src/Profile.jsx
@@ -1,5 +1,5 @@
 import React, { useCallback } from 'react'
-import { Button, Action } from '@pubsweet/ui'
+import { Button } from '@pubsweet/ui'
 // import { th } from '@pubsweet/ui-toolkit'
 // import styled from 'styled-components'
 import gql from 'graphql-tag'
@@ -19,15 +19,12 @@ const GET_CURRENT_USER = gql`
       profilePicture
       username
       defaultIdentity {
+        identifier
+        email
+        type
         aff
+        id
         name
-        type
-        ... on ExternalIdentity {
-          identifier
-        }
-        ... on LocalIdentity {
-          email
-        }
       }
     }
   }
@@ -111,7 +108,7 @@ const Profile = () => {
               <ChangeUsername user={data.currentUser} />
             </div>
           </FormRow>
-            <Button onClick={() => logoutUser()}>Logout</Button>
+          <Button onClick={() => logoutUser()}>Logout</Button>
         </FormGrid>
       </PageWithHeader>
     </>
diff --git a/app/components/component-profile/src/ProfileImage.jsx b/app/components/component-profile/src/ProfileImage.jsx
index fbaa86fdcc7eabe886eb3872e1aa8949c07868ea..162c43614fcf99285585085c648c170f0e27175f 100644
--- a/app/components/component-profile/src/ProfileImage.jsx
+++ b/app/components/component-profile/src/ProfileImage.jsx
@@ -14,4 +14,3 @@ export const SmallProfileImage = styled.img`
   object-fit: cover;
   border-radius: 50%;
 `
-
diff --git a/app/components/component-review/src/components/ReviewPage.js b/app/components/component-review/src/components/ReviewPage.js
index 99cb6ff80f674da0003f56d42e6b57b74f988944..db9d9a998945b70ad7acb4a26cb4eef358a47f12 100644
--- a/app/components/component-review/src/components/ReviewPage.js
+++ b/app/components/component-review/src/components/ReviewPage.js
@@ -2,7 +2,7 @@ import React from 'react'
 import { useMutation, useQuery } from '@apollo/client'
 import gql from 'graphql-tag'
 import { Formik } from 'formik'
-import { cloneDeep } from 'lodash'
+// import { cloneDeep } from 'lodash'
 import { getCommentContent } from './review/util'
 import ReviewLayout from '../components/review/ReviewLayout'
 import { Spinner } from '../../../shared'
@@ -34,22 +34,22 @@ const reviewFields = `
   }
 `
 
-const teamFields = `
-  id
-  name
-  role
-  object {
-    objectId
-    objectType
-  }
-  members {
-    id
-    user {
-      id
-      username
-    }
-  }
-`
+// const teamFields = `
+//   id
+//   name
+//   role
+//   object {
+//     objectId
+//     objectType
+//   }
+//   members {
+//     id
+//     user {
+//       id
+//       username
+//     }
+//   }
+// `
 
 const fragmentFields = `
   id
@@ -145,10 +145,11 @@ const query = gql`
   }
 `
 
-const updateTeamMutation = gql`
-  mutation($id: ID!, $input: TeamInput) {
-    updateTeam(id: $id, input: $input) {
-      ${teamFields}
+const completeReviewMutation = gql`
+  mutation($id: ID!) {
+    completeReview(id: $id) {
+      id
+      status
     }
   }
 `
@@ -187,12 +188,11 @@ const createFileMutation = gql`
 export default ({ match, ...props }) => {
   const currentUser = useCurrentUser()
   const [updateReviewMutation] = useMutation(updateReviewMutationQuery)
+  const [completeReview] = useMutation(completeReviewMutation)
 
   // File upload
   // const [uploadReviewFiles] = useMutation(uploadReviewFilesMutation)
 
-  const [updateTeam] = useMutation(updateTeamMutation)
-
   const [createFileM] = useMutation(createFileMutation)
   const createFile = file =>
     createFileM({
@@ -269,24 +269,24 @@ export default ({ match, ...props }) => {
         id: review.id || undefined,
         input: reviewData,
       },
-      update: (proxy, { data: { updateReview } }) => {
-        const data = JSON.parse(
-          JSON.stringify(
-            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 })
-      },
+      // update: (proxy, { data: { updateReview } }) => {
+      //   const data = JSON.parse(
+      //     JSON.stringify(
+      //       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 })
+      // },
     })
   }
 
@@ -308,27 +308,14 @@ export default ({ match, ...props }) => {
       createFile(newFile)
     })
 
-  const completeReview = history => {
-    const team = cloneDeep(manuscript.teams).find(
-      team => team.role === 'reviewer',
-    )
-    team.members = team.members.map(m => {
-      if (m.user.id === currentUser.id) {
-        return { user: { id: m.user.id }, status: 'completed' }
-      }
-      return { user: { id: m.user.id }, status: m.status }
-    })
-
-    updateTeam({
+  const handleSubmit = async ({ reviewId, history }) => {
+    await completeReview({
       variables: {
-        id: team.id,
-        input: {
-          members: team.members,
-        },
+        id: reviewId,
       },
-    }).then(() => {
-      history.push('/journal/dashboard')
     })
+
+    history.push('/journal/dashboard')
   }
 
   return (
@@ -343,7 +330,9 @@ export default ({ match, ...props }) => {
           recommendation: null,
         }
       }
-      onSubmit={values => completeReview(props.history)}
+      onSubmit={values =>
+        handleSubmit({ reviewId: review.id, history: props.history })
+      }
       validateOnMount={review => {
         if (!review.id) return false
         const hasRecommendation = review.recommendation !== null
diff --git a/app/components/component-review/src/components/ReviewersPage.js b/app/components/component-review/src/components/ReviewersPage.js
index 6331ff1f7fb497afcc8ad5f89de5ad5bb67da522..266792ad751c51e2beb4f19aae84294521048539 100644
--- a/app/components/component-review/src/components/ReviewersPage.js
+++ b/app/components/component-review/src/components/ReviewersPage.js
@@ -1,12 +1,9 @@
-import { compose, withProps } from 'recompose'
-import { withFormik } from 'formik'
-import { graphql } from '@apollo/client/react/hoc'
-import gql from 'graphql-tag'
-import { withLoader } from 'pubsweet-client'
-import { omit } from 'lodash'
-
+import React from 'react'
+import { Formik } from 'formik'
+import { gql, useQuery, useMutation } from '@apollo/client'
 import Reviewers from '../components/reviewers/Reviewers'
 import ReviewerContainer from '../components/reviewers/ReviewerContainer'
+import { Spinner } from '../../../shared'
 
 const teamFields = `
   id
@@ -23,6 +20,10 @@ const teamFields = `
       username
       profilePicture
       online
+      defaultIdentity {
+        id
+        name
+      }
     }
     status
   }
@@ -68,17 +69,17 @@ const fragmentFields = `
   status
 `
 
-const createTeamMutation = gql`
-  mutation($input: TeamInput!) {
-    createTeam(input: $input) {
+const addReviewerMutation = gql`
+  mutation($manuscriptId: ID!, $userId: ID!) {
+    addReviewer(manuscriptId: $manuscriptId, userId: $userId) {
       ${teamFields}
     }
   }
 `
 
-const updateTeamMutation = gql`
-  mutation($id: ID, $input: TeamInput) {
-    updateTeam(id: $id, input: $input) {
+const removeReviewerMutation = gql`
+  mutation($manuscriptId: ID!, $userId: ID!) {
+    removeReviewer(manuscriptId: $manuscriptId, userId: $userId) {
       ${teamFields}
     }
   }
@@ -86,18 +87,16 @@ const updateTeamMutation = gql`
 
 const query = gql`
   query($id: ID!) {
-    currentUser {
-      id
-      username
-      admin
-    }
-
     users {
       id
       username
       profilePicture
       online
       admin
+      defaultIdentity {
+        id
+        name
+      }
     }
 
     teams {
@@ -110,121 +109,52 @@ const query = gql`
   }
 `
 
-const update = match => (proxy, { data: { updateTeam, createTeam } }) => {
-  const data = JSON.parse(
-    JSON.stringify(
-      proxy.readQuery({
-        query,
-        variables: {
-          id: match.params.version,
-        },
-      }),
-    ),
-  )
+const ReviewersPage = ({ match, history }) => {
+  const { data, error, loading } = useQuery(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
-  }
+  const [addReviewer] = useMutation(addReviewerMutation)
+  const [removeReviewer] = useMutation(removeReviewerMutation)
 
-  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.role === 'reviewer') || {}
-
-  const teamAdd = {
-    objectId: manuscript.id,
-    objectType: 'Manuscript',
-    // status: [{ user: user.id, status: 'invited' }],
-    name: 'Reviewers',
-    role: 'reviewer',
-    members: [{ user: { id: user.id }, status: 'invited' }],
-  }
-  if (team.id) {
-    const newTeam = {
-      ...omit(team, ['object', 'id', '__typename']),
-      // TODO: Find a cleaner way of updating members
-      members: team.members.map(member => ({
-        user: {
-          id: member.user.id,
-        },
-        status: member.status,
-      })),
-    }
-
-    newTeam.members.push({ user: { id: user.id }, status: 'invited' })
-    // 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),
-    })
+  if (loading) {
+    return <Spinner />
   }
+  if (error) return error
+
+  const { manuscript, teams, users } = data
+  const reviewersTeam =
+    teams.find(
+      team =>
+        team.role === 'reviewer' &&
+        team.object.objectId === manuscript.id &&
+        team.object.objectType === 'Manuscript',
+    ) || {}
+
+  const reviewers = reviewersTeam.members || []
+  return (
+    <Formik
+      displayName="reviewers"
+      initialValues={{ user: undefined }}
+      onSubmit={values =>
+        addReviewer({
+          variables: { userId: values.user.id, manuscriptId: manuscript.id },
+        })
+      }
+    >
+      {props => (
+        <Reviewers
+          {...props}
+          history={history}
+          manuscript={manuscript}
+          removeReviewer={removeReviewer}
+          Reviewer={ReviewerContainer}
+          reviewers={reviewers}
+          reviewerUsers={users}
+        />
+      )}
+    </Formik>
+  )
 }
 
-export default compose(
-  graphql(query, {
-    options: ({ match }) => ({
-      variables: {
-        id: match.params.version,
-      },
-    }),
-  }),
-  graphql(createTeamMutation, { name: 'createTeamMutation' }),
-  graphql(updateTeamMutation, { name: 'updateTeamMutation' }),
-  withLoader(),
-  withProps(
-    ({
-      manuscript,
-      teams = [],
-      users,
-      match: {
-        params: { journal },
-      },
-    }) => {
-      const reviewersTeam =
-        teams.find(
-          team =>
-            team.role === 'reviewer' &&
-            team.object.objectId === manuscript.id &&
-            team.object.objectType === 'Manuscript',
-        ) || {}
-
-      return {
-        reviewers: reviewersTeam.members || [],
-        journal: { id: journal },
-        reviewerUsers: users,
-        Reviewer: ReviewerContainer,
-      }
-    },
-  ),
-  // withHandlers({
-  //   loadOptions: props => props.reviewerUsers, // loadOptions(props),
-  // }),
-  withFormik({
-    mapPropsToValues: () => ({ user: '' }),
-    displayName: 'reviewers',
-    handleSubmit,
-  }),
-)(Reviewers)
+export default ReviewersPage
diff --git a/app/components/component-review/src/components/assignEditors/AssignEditor.js b/app/components/component-review/src/components/assignEditors/AssignEditor.js
index 51cd4ab4d846021f406c697da7a401ad354a768d..6383b954811a57823a7b8481300cbcd52c2a8596 100644
--- a/app/components/component-review/src/components/assignEditors/AssignEditor.js
+++ b/app/components/component-review/src/components/assignEditors/AssignEditor.js
@@ -8,7 +8,7 @@ import gql from 'graphql-tag'
 import { withLoader } from 'pubsweet-client'
 
 const editorOption = user => ({
-  label: user.username, // TODO: name
+  label: user.defaultIdentity.name,
   value: user.id,
 })
 
@@ -35,6 +35,10 @@ const query = gql`
       id
       username
       admin
+      defaultIdentity {
+        id
+        name
+      }
     }
   }
 `
diff --git a/app/components/component-review/src/components/decision/DecisionReviews.js b/app/components/component-review/src/components/decision/DecisionReviews.js
index e889f256ac2e06777a3a760b81c09d791430145f..b7bcab3bd4252b71f56f752800d75ed56aff5d5e 100644
--- a/app/components/component-review/src/components/decision/DecisionReviews.js
+++ b/app/components/component-review/src/components/decision/DecisionReviews.js
@@ -44,12 +44,13 @@ const DecisionReviews = ({ manuscript }) => (
           </SectionRow>
         ))
     ) : (
-      <SectionRow>
-        <Action to={`/journal/versions/${manuscript.id}/reviewers`}>
-          Assign Reviewers
-        </Action>
-      </SectionRow>
+      <SectionRow>No reviews completed yet.</SectionRow>
     )}
+    <SectionRow>
+      <Action to={`/journal/versions/${manuscript.id}/reviewers`}>
+        Manage Reviewers
+      </Action>
+    </SectionRow>
   </Container>
 )
 
diff --git a/app/components/component-review/src/components/reviewers/ReviewerForm.js b/app/components/component-review/src/components/reviewers/ReviewerForm.js
index b2df4cddfeed6af40038ff113ff255a69f8c60ac..595a27a257c74b086321818a44ba8835d54b83cf 100644
--- a/app/components/component-review/src/components/reviewers/ReviewerForm.js
+++ b/app/components/component-review/src/components/reviewers/ReviewerForm.js
@@ -1,9 +1,11 @@
 import React from 'react'
-import Select from 'react-select1'
 import { Field } from 'formik'
 import { Button } from '@pubsweet/ui'
 import { required } from 'xpub-validators'
-import 'react-select1/dist/react-select.css'
+import styled from 'styled-components'
+// import 'react-select1/dist/react-select.css'
+import { grid } from '@pubsweet/ui-toolkit'
+import { Select } from '../../../../shared'
 
 const OptionRenderer = option => (
   <div>
@@ -12,6 +14,11 @@ const OptionRenderer = option => (
   </div>
 )
 
+const FieldAndButton = styled.div`
+  display: grid;
+  grid-template-columns: ${grid(30)} ${grid(10)};
+  grid-gap: ${grid(2)};
+`
 const ReviewerInput = ({
   field,
   form: { values, setFieldValue },
@@ -19,9 +26,10 @@ const ReviewerInput = ({
   replace,
   reviewerUsers,
 }) => (
-  <Select.Creatable
+  <Select
     {...field}
-    labelKey="username"
+    getOptionLabel={option => option.defaultIdentity?.name}
+    getOptionValue={option => option.id}
     onChange={user => {
       setFieldValue('user', user)
     }}
@@ -40,15 +48,17 @@ const ReviewerForm = ({
   reviewerUsers,
 }) => (
   <form onSubmit={handleSubmit}>
-    <Field
-      component={ReviewerInput}
-      name="user"
-      reviewerUsers={reviewerUsers}
-      validate={required}
-    />
-    <Button disabled={!isValid} primary type="submit">
-      Invite reviewer
-    </Button>
+    <FieldAndButton>
+      <Field
+        component={ReviewerInput}
+        name="user"
+        reviewerUsers={reviewerUsers}
+        validate={required}
+      />
+      <Button disabled={!isValid} primary type="submit">
+        Invite reviewer
+      </Button>
+    </FieldAndButton>
   </form>
 )
 
diff --git a/app/components/component-review/src/components/reviewers/Reviewers.js b/app/components/component-review/src/components/reviewers/Reviewers.js
index 2cdf32c8c50f3683e8868ab50414cda853cd712b..b084c6110b6f692deffa1e6a11fe8c683e8cab01 100644
--- a/app/components/component-review/src/components/reviewers/Reviewers.js
+++ b/app/components/component-review/src/components/reviewers/Reviewers.js
@@ -1,20 +1,31 @@
 import React from 'react'
 import styled from 'styled-components'
-import { Link } from '@pubsweet/ui'
+import { Action, Button } from '@pubsweet/ui'
+import { grid } from '@pubsweet/ui-toolkit'
 import ReviewerForm from './ReviewerForm'
-import { Container, PaddedContent } from '../../../../shared'
+import {
+  Container,
+  SectionRow,
+  SectionContent,
+  SectionHeader,
+  Title,
+  Heading,
+  HeadingWithAction,
+  StatusBadge,
+} from '../../../../shared'
 
 // TODO: Make this a proper shared component?
 import { UserAvatar } from '../../../../component-avatar/src'
 
-const Form = styled.div``
 const ReviewersList = styled.div`
-  display: flex;
-  flex-wrap: wrap;
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(${grid(15)}, 1fr));
+  grid-gap: ${grid(2)};
 `
 
+const Reviewer = styled.div``
+
 const Reviewers = ({
-  Reviewer,
   journal,
   isValid,
   loadOptions,
@@ -23,11 +34,30 @@ const Reviewers = ({
   reviewerUsers,
   manuscript,
   handleSubmit,
+  removeReviewer,
   teams,
+  history,
 }) => (
   <Container>
-    <PaddedContent>
-      <Form>
+    <HeadingWithAction>
+      <Heading>Reviewers</Heading>
+      <Button
+        onClick={() =>
+          history.push(
+            `/journal/versions/${manuscript.id}/decisions/${manuscript.id}`,
+          )
+        }
+        primary
+      >
+        Back to control panel
+      </Button>
+    </HeadingWithAction>
+    <SectionContent>
+      <SectionHeader>
+        <Title>Invite reviewers</Title>
+      </SectionHeader>
+
+      <SectionRow>
         <ReviewerForm
           handleSubmit={handleSubmit}
           isValid={isValid}
@@ -35,23 +65,40 @@ const Reviewers = ({
           loadOptions={loadOptions}
           reviewerUsers={reviewerUsers}
         />
-        <Link
-          to={`/journal/versions/${manuscript.id}/decisions/${manuscript.id}`}
-        >
-          Back to Control Panel
-        </Link>
-      </Form>
-    </PaddedContent>
-    <PaddedContent>
-      {reviewers && (
-        <ReviewersList>
-          {reviewers.map(reviewer => (
-            <UserAvatar key={reviewer.id} user={reviewer.user} />
-            // <Reviewer journal={journal} key={reviewer.id} username={reviewer.user.username} />
-          ))}
-        </ReviewersList>
-      )}
-    </PaddedContent>
+      </SectionRow>
+    </SectionContent>
+    <SectionContent>
+      <SectionHeader>
+        <Title>Reviewer status</Title>
+      </SectionHeader>
+      <SectionRow>
+        {reviewers && (
+          <ReviewersList>
+            {reviewers.map(reviewer => (
+              <Reviewer>
+                <StatusBadge minimal status={reviewer.status} />
+                <UserAvatar key={reviewer.id} user={reviewer.user} />
+                {reviewer.user.defaultIdentity.name}
+                <div>
+                  <Action
+                    onClick={() =>
+                      removeReviewer({
+                        variables: {
+                          userId: reviewer.user.id,
+                          manuscriptId: manuscript.id,
+                        },
+                      })
+                    }
+                  >
+                    Delete
+                  </Action>
+                </div>
+              </Reviewer>
+            ))}
+          </ReviewersList>
+        )}
+      </SectionRow>
+    </SectionContent>
   </Container>
 )
 
diff --git a/app/components/component-submit/src/components/Confirm.js b/app/components/component-submit/src/components/Confirm.js
index 95413c68ba357ebce6f3ac1702dc4f303ce7ab38..f2ac2d6a6a2fea6ec4f1564a3d1be9dbdcc65225 100644
--- a/app/components/component-submit/src/components/Confirm.js
+++ b/app/components/component-submit/src/components/Confirm.js
@@ -28,17 +28,29 @@ const createMarkup = encodedHtml => ({
   __html: unescape(encodedHtml),
 })
 
-const Confirm = ({ toggleConfirming, form, submitSubmission }) => (
+const Confirm = ({ toggleConfirming, form, submit, errors }) => (
   <Wrapper>
     <article>
-      <Heading1 dangerouslySetInnerHTML={createMarkup(form.popuptitle)} />
-      <Paragraph
-        dangerouslySetInnerHTML={createMarkup(form.popupdescription)}
-      />
-      <Button onClick={submitSubmission} primary type="submit">
-        Submit your manuscript
-      </Button>
-      <Divider> or </Divider>
+      {Object.keys(errors).length > 0 ? (
+        <>
+          <Heading1>Errors in your submission</Heading1>
+          <Paragraph>
+            There are errors in your submission, please correct the following:{' '}
+            {JSON.stringify(errors)}
+          </Paragraph>
+        </>
+      ) : (
+        <>
+          <Heading1 dangerouslySetInnerHTML={createMarkup(form.popuptitle)} />
+          <Paragraph
+            dangerouslySetInnerHTML={createMarkup(form.popupdescription)}
+          />
+          <Button onClick={submit} primary type="submit">
+            Submit your manuscript
+          </Button>
+          <Divider> or </Divider>
+        </>
+      )}
       <PlainButton onClick={toggleConfirming}>
         get back to your submission
       </PlainButton>
diff --git a/app/components/component-submit/src/components/Confirm.md b/app/components/component-submit/src/components/Confirm.md
deleted file mode 100644
index a5773b2ca69e1727b22fa61800b0969d2ed5ae51..0000000000000000000000000000000000000000
--- a/app/components/component-submit/src/components/Confirm.md
+++ /dev/null
@@ -1,14 +0,0 @@
-A series of confirmation paragraphs that the user must read and agree to before confirming the submission.
-
-The user can confirm submission using the primary button, or return to the submission using a link.
-
-```js
-const form = {
-  haspopup: 'true',
-  id: 'submit',
-  name: 'Submission information',
-  popupdescription: faker.lorem.sentences(50),
-  popuptitle: faker.lorem.words(3),
-}
-;<Confirm form={form} />
-```
diff --git a/app/components/component-submit/src/components/CurrentVersion.md b/app/components/component-submit/src/components/CurrentVersion.md
deleted file mode 100644
index 2dde98ad20be1788b814f5917e4e50725d277746..0000000000000000000000000000000000000000
--- a/app/components/component-submit/src/components/CurrentVersion.md
+++ /dev/null
@@ -1,62 +0,0 @@
-A form for entering information about the submission.
-
-```js
-const journal = {
-  id: faker.random.uuid(),
-}
-
-const manuscript = {
-  id: faker.random.uuid(),
-  meta: {
-    title: faker.lorem.sentence(25),
-    abstract: faker.lorem.sentence(100),
-    articleType: 'original-research',
-    declarations: {
-      openData: 'yes',
-      openPeerReview: 'no',
-      preregistered: 'yes',
-      previouslySubmitted: 'yes',
-      researchNexus: 'no',
-      streamlinedReview: 'no',
-    },
-  },
-  suggestions: {
-    reviewers: {
-      opposed: faker.name.findName(),
-    },
-  },
-  reviews: [
-    {
-      comments: { content: 'this needs review' },
-      created: 'Thu Oct 11 2018',
-      open: false,
-      recommendation: '',
-      user: { identities: [] },
-    },
-  ],
-}
-
-const forms = {
-  children: [
-    {
-      title: faker.lorem.sentence(5),
-      name: 'meta.declarations.openData',
-    },
-    {
-      title: faker.lorem.sentence(5),
-      name: 'meta.declarations.openPeerReview',
-    },
-    {
-      title: faker.lorem.sentence(5),
-      name: 'meta.declarations.previouslySubmitted',
-    },
-    {
-      title: faker.lorem.sentence(5),
-      name: 'meta.declarations.researchNexus',
-    },
-  ],
-}
-;<div style={{ position: 'relative', paddingRight: 100 }}>
-  <CurrentVersion forms={forms} manuscript={manuscript} journal={journal} />
-</div>
-```
diff --git a/app/components/component-submit/src/components/Declarations.md b/app/components/component-submit/src/components/Declarations.md
deleted file mode 100644
index 8bf81ab7e5ee43e83633583a3cd003888e903e24..0000000000000000000000000000000000000000
--- a/app/components/component-submit/src/components/Declarations.md
+++ /dev/null
@@ -1,57 +0,0 @@
-A list of questions that must be answered before submission. The questions are
-configured via the journal config on the context.
-
-```js
-const forms = {
-  children: [
-    {
-      title: faker.lorem.sentence(5),
-      name: 'meta.declarations.openData',
-    },
-    {
-      title: faker.lorem.sentence(5),
-      name: 'meta.declarations.openPeerReview',
-    },
-    {
-      title: faker.lorem.sentence(5),
-      name: 'meta.declarations.previouslySubmitted',
-    },
-    {
-      title: faker.lorem.sentence(5),
-      name: 'meta.declarations.researchNexus',
-    },
-  ],
-}
-
-const manuscript = {
-  id: faker.random.uuid(),
-  meta: {
-    title: faker.lorem.sentence(25),
-    abstract: faker.lorem.sentence(100),
-    articleType: 'original-research',
-    declarations: {
-      openData: 'yes',
-      openPeerReview: 'no',
-      preregistered: 'yes',
-      previouslySubmitted: 'yes',
-      researchNexus: 'no',
-      streamlinedReview: 'no',
-    },
-  },
-  suggestions: {
-    reviewers: {
-      opposed: faker.name.findName(),
-    },
-  },
-  reviews: [
-    {
-      comments: { content: 'this needs review' },
-      created: 'Thu Oct 11 2018',
-      open: false,
-      recommendation: '',
-      user: { identities: [] },
-    },
-  ],
-}
-;<Declarations forms={forms} manuscript={manuscript} />
-```
diff --git a/app/components/component-submit/src/components/FormTemplate.js b/app/components/component-submit/src/components/FormTemplate.js
index 6baed800ae0a1e2957ab05330ddd6472879cf433..ea58c3046db5facfadd0bdc2882fc78f825434d2 100644
--- a/app/components/component-submit/src/components/FormTemplate.js
+++ b/app/components/component-submit/src/components/FormTemplate.js
@@ -11,6 +11,19 @@ import AuthorsInput from './AuthorsInput'
 import Supplementary from './Supplementary'
 import Confirm from './Confirm'
 
+// TODO: https://github.com/formium/formik/issues/146#issuecomment-474775723
+// const useFocusOnError = ({ fieldRef, name }) => {
+//   const formik = useFormikContext()
+//   const prevSubmitCountRef = React.useRef(formik.submitCount)
+//   const firstErrorKey = Object.keys(formik.errors)[0]
+//   React.useEffect(() => {
+//     if (prevSubmitCountRef.current !== formik.submitCount && !formik.isValid) {
+//       if (fieldRef.current && firstErrorKey === name) fieldRef.current.focus()
+//     }
+//     prevSubmitCountRef.current = formik.submitCount
+//   }, [formik.submitCount, formik.isValid, firstErrorKey])
+// }
+
 // const Wrapper = styled.div`
 //   font-family: ${th('fontInterface')};
 //   line-height: 1.3;
@@ -219,11 +232,12 @@ export default ({
   setTouched,
   values,
   setFieldValue,
-  uploadFile,
-  createFile,
+  createSupplementaryFile,
   onChange,
   onSubmit,
   submitSubmission,
+  errors,
+  validateForm,
   ...props
 }) => (
   <Container>
@@ -247,9 +261,8 @@ export default ({
             <Legend dangerouslySetInnerHTML={createMarkup(element.title)} />
             {element.component === 'SupplementaryFiles' && (
               <Supplementary
-                createFile={createFile}
+                createSupplementaryFile={createSupplementaryFile}
                 onChange={onChange}
-                uploadFile={uploadFile}
               />
             )}
             {element.component === 'AuthorsInput' && (
@@ -322,7 +335,11 @@ export default ({
 
       {values.status !== 'submitted' && form.haspopup === 'true' && (
         <div>
-          <Button onClick={toggleConfirming} primary type="button">
+          <Button
+            onClick={() => validateForm() && toggleConfirming()}
+            primary
+            type="button"
+          >
             Submit your research object
           </Button>
         </div>
@@ -330,8 +347,9 @@ export default ({
       {confirming && (
         <ModalWrapper>
           <Confirm
+            errors={errors}
             form={form}
-            submitSubmission={handleSubmit}
+            submit={handleSubmit}
             toggleConfirming={toggleConfirming}
           />
         </ModalWrapper>
diff --git a/app/components/component-submit/src/components/MetadataFields.md b/app/components/component-submit/src/components/MetadataFields.md
deleted file mode 100644
index 30ec5fb3430ca2caa016fc367aaecc246f9a8c4f..0000000000000000000000000000000000000000
--- a/app/components/component-submit/src/components/MetadataFields.md
+++ /dev/null
@@ -1,13 +0,0 @@
-A form for entering the submission's metadata.
-
-```js
-const manuscript = {
-  meta: {
-    title: faker.lorem.sentence(25),
-    abstract: faker.lorem.sentence(50),
-    articleType: 'original-research',
-    keywords: 'test, test1',
-  },
-}
-;<MetadataFields manuscript={manuscript} />
-```
diff --git a/app/components/component-submit/src/components/NewSubmissionPage.jsx b/app/components/component-submit/src/components/NewSubmissionPage.jsx
index 2ce2b53f61730cc79c8d318ae341cf6313c0cdce..922ad7fd029d2d0aa270b17c9c3114682e865b07 100644
--- a/app/components/component-submit/src/components/NewSubmissionPage.jsx
+++ b/app/components/component-submit/src/components/NewSubmissionPage.jsx
@@ -1,13 +1,10 @@
 import React from 'react'
-import { useQuery, useMutation, ApolloConsumer } from '@apollo/client'
-// import Authorize from 'pubsweet-client/src/helpers/Authorize'
-
+import { ApolloConsumer } from '@apollo/client'
 import config from 'config'
-import { Container, Content, Section, Heading, UploadContainer, PageHeading } from '../style'
+import { Container, Content, UploadContainer, PageHeading } from '../style'
 import UploadManuscript from './UploadManuscript'
-import { Spinner } from '../../../shared'
-import { Action } from '@pubsweet/ui'
 import useCurrentUser from '../../../../hooks/useCurrentUser'
+
 const { acceptUploadFiles } = config['pubsweet-component-xpub-dashboard'] || {}
 
 const acceptFiles =
@@ -15,7 +12,6 @@ const acceptFiles =
     ? acceptUploadFiles.join()
     : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
 
-
 const Dashboard = props => {
   const currentUser = useCurrentUser()
 
@@ -23,19 +19,18 @@ const Dashboard = props => {
     <Container>
       <PageHeading level={1}>New submission</PageHeading>
       <Content>
-      <UploadContainer>
-        <ApolloConsumer>
-          {client => (
-            <UploadManuscript
-              acceptFiles={acceptFiles}
-              client={client}
-              currentUser={currentUser}
-              history={props.history}
-            />
-          )}
-        </ApolloConsumer>
-      </UploadContainer>
-
+        <UploadContainer>
+          <ApolloConsumer>
+            {client => (
+              <UploadManuscript
+                acceptFiles={acceptFiles}
+                client={client}
+                currentUser={currentUser}
+                history={props.history}
+              />
+            )}
+          </ApolloConsumer>
+        </UploadContainer>
       </Content>
     </Container>
   )
diff --git a/app/components/component-submit/src/components/Submit.js b/app/components/component-submit/src/components/Submit.js
index 79f7cdf9d1e12a7a7b3768caa9c3591f03f550e7..d1390b346d0e7734b51c183df429cdc3f1008c37 100644
--- a/app/components/component-submit/src/components/Submit.js
+++ b/app/components/component-submit/src/components/Submit.js
@@ -24,7 +24,7 @@ const SubmittedVersionColumns = props => (
   </Container>
 )
 
-const Submit = ({ journal, manuscript, forms, ...formProps }) => {
+const Submit = ({ manuscript, forms, ...formProps }) => {
   const decisionSections = []
   const manuscriptVersions = manuscript.manuscriptVersions || []
   manuscriptVersions.forEach(versionElem => {
@@ -32,11 +32,7 @@ const Submit = ({ journal, manuscript, forms, ...formProps }) => {
     const label = submittedMoment.format('YYYY-MM-DD')
     decisionSections.push({
       content: (
-        <SubmittedVersionColumns
-          forms={forms}
-          journal={journal}
-          manuscript={versionElem}
-        />
+        <SubmittedVersionColumns forms={forms} manuscript={versionElem} />
       ),
       key: versionElem.id,
       label,
@@ -46,12 +42,7 @@ const Submit = ({ journal, manuscript, forms, ...formProps }) => {
   decisionSections.push({
     content: (
       <Content>
-        <FormTemplate
-          {...formProps}
-          form={forms}
-          journal={journal}
-          manuscript={manuscript}
-        />
+        <FormTemplate {...formProps} form={forms} manuscript={manuscript} />
       </Content>
     ),
     key: manuscript.id,
diff --git a/app/components/component-submit/src/components/SubmitPage.js b/app/components/component-submit/src/components/SubmitPage.js
index 6bd3e8adab5575cc305e712363ed75d521aafcf0..5c6ce3ed5bfc80c4b458a073b6829b00b553a2a3 100644
--- a/app/components/component-submit/src/components/SubmitPage.js
+++ b/app/components/component-submit/src/components/SubmitPage.js
@@ -1,16 +1,10 @@
-import { debounce, cloneDeep, isEmpty, set } from 'lodash'
-import { compose, withProps, withState, withHandlers } from 'recompose'
-import { graphql } from '@apollo/client/react/hoc'
-import gql from 'graphql-tag'
-import { withFormik } from 'formik'
-import { withLoader } from 'pubsweet-client'
+import React, { useState } from 'react'
+import { debounce, cloneDeep, set } from 'lodash'
+// import { compose, withProps, withState, withHandlers } from 'recompose'
+import { gql, useQuery, useMutation } from '@apollo/client'
+import { Formik } from 'formik'
 import Submit from './Submit'
-
-const nullToEmpty = obj =>
-  JSON.parse(JSON.stringify(obj, (k, v) => (v === null ? '' : v)))
-
-const emptyToUndefined = obj =>
-  JSON.parse(JSON.stringify(obj, (k, v) => (v === '' ? undefined : v)))
+import { Spinner } from '../../../shared'
 
 const fragmentFields = `
   id
@@ -26,6 +20,7 @@ const fragmentFields = `
     url
   }
   reviews {
+    id
     open
     recommendation
     created
@@ -121,17 +116,17 @@ const updateMutation = gql`
   }
 `
 
-const uploadSuplementaryFilesMutation = gql`
-  mutation($file: Upload!) {
-    upload(file: $file) {
-      url
-    }
-  }
-`
+// const uploadSuplementaryFilesMutation = gql`
+//   mutation($file: Upload!) {
+//     upload(file: $file) {
+//       url
+//     }
+//   }
+// `
 
 const createFileMutation = gql`
-  mutation($file: Upload!) {
-    createFile(file: $file) {
+  mutation($file: Upload!, $meta: FileMetaInput) {
+    createFile(file: $file, meta: $meta) {
       id
       created
       label
@@ -144,116 +139,102 @@ const createFileMutation = gql`
   }
 `
 
-export default compose(
-  graphql(query, {
-    options: ({ match }) => ({
+const SubmitPage = ({ match, history, ...props }) => {
+  const [confirming, setConfirming] = useState(false)
+
+  const toggleConfirming = () => {
+    setConfirming(confirming => !confirming)
+  }
+
+  const { data, loading, error } = useQuery(query, {
+    variables: { id: match.params.version, form: 'submit' },
+  })
+
+  const [createFile] = useMutation(createFileMutation)
+
+  const createSupplementaryFile = file => {
+    const meta = {
+      filename: file.name,
+      mimeType: file.type,
+      size: file.size,
+      fileType: 'supplementary',
+      object: 'Manuscript',
+      objectId: match.params.version,
+    }
+
+    createFile({
       variables: {
-        id: match.params.version,
-        form: 'submit',
+        file,
+        meta,
       },
-    }),
-    props: ({ data }) => ({ data: nullToEmpty(data) }),
-  }),
-  graphql(createFileMutation, {
-    props: ({ mutate, ownProps }) => ({
-      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,
-          },
-        })
+  const [update] = useMutation(updateMutation)
+
+  if (loading) return <Spinner />
+  if (error) return error
+
+  const manuscript = data?.manuscript
+  const getFile = data?.getFile
+
+  const updateManuscript = input =>
+    update({
+      variables: {
+        id: match.params.version,
+        input: JSON.stringify(input),
       },
-    }),
-  }),
-  graphql(uploadSuplementaryFilesMutation, {
-    props: ({ mutate, ownProps }) => ({
-      uploadFile: file =>
-        mutate({
-          variables: {
-            file,
-          },
-        }),
-    }),
-  }),
-  graphql(updateMutation, {
-    props: ({ mutate, ownProps }) => {
-      const debouncers = {}
-      const onChange = (value, path) => {
-        const input = {}
-        set(input, path, value)
-        debouncers[path] = debouncers[path] || debounce(updateManuscript, 300)
-        return debouncers[path](input)
-      }
+    })
 
-      const updateManuscript = input =>
-        mutate({
-          variables: {
-            id: ownProps.match.params.version,
-            input: JSON.stringify(emptyToUndefined(input)),
-          },
-        })
+  const debouncers = {}
 
-      return {
-        onChange,
-      }
-    },
-  }),
-  graphql(updateMutation, {
-    props: ({ mutate, ownProps }) => ({
-      onSubmit: (manuscript, { history }) => {
-        const updateManuscript = {
-          status: 'submitted',
-        }
+  const handleChange = (value, path) => {
+    const input = {}
+    set(input, path, value)
+    debouncers[path] = debouncers[path] || debounce(updateManuscript, 300)
+    return debouncers[path](input)
+  }
 
-        mutate({
-          variables: {
-            id: ownProps.match.params.version,
-            input: JSON.stringify(updateManuscript),
-          },
-        }).then(() => {
-          history.push('/journal/dashboard')
-        })
+  const onSubmit = async manuscript => {
+    const updateManuscript = {
+      status: 'submitted',
+    }
+
+    await update({
+      variables: {
+        id: match.params.version,
+        input: JSON.stringify(updateManuscript),
       },
-    }),
-  }),
-  withLoader(),
-  withProps(({ getFile, manuscript, match: { params: { journal } } }) => ({
-    journal: { id: journal },
-    forms: cloneDeep(getFile),
-    manuscript,
-    submitSubmission: ({ validateForm, setSubmitting, handleSubmit }) =>
-      validateForm().then(props =>
-        isEmpty(props) ? setSubmitting(false) : handleSubmit(),
-      ),
-  })),
-  withFormik({
-    initialValues: {},
-    mapPropsToValues: ({ manuscript }) =>
-      Object.assign({}, manuscript, {
+    })
+    history.push('/journal/dashboard')
+  }
+
+  return (
+    <Formik
+      displayName="submit"
+      handleChange={handleChange}
+      initialValues={Object.assign({}, manuscript, {
         submission: JSON.parse(manuscript.submission),
-      }),
-    displayName: 'submit',
-    handleSubmit: (
-      props,
-      { validateForm, setSubmitting, props: { onSubmit, history } },
-    ) =>
-      validateForm().then(props =>
-        isEmpty(props) ? onSubmit(props, { history }) : setSubmitting(false),
-      ),
-  }),
-  withState('confirming', 'setConfirming', false),
-  withHandlers({
-    toggleConfirming: ({ validateForm, setConfirming, handleSubmit }) => () =>
-      setConfirming(confirming => !confirming),
-  }),
-)(Submit)
+      })}
+      onSubmit={async (values, { validateForm, setSubmitting, ...other }) => {
+        // TODO: Change this to a more Formik idiomatic form
+        const isValid = Object.keys(await validateForm()).length === 0
+        return isValid ? onSubmit(values) : setSubmitting(false)
+      }}
+    >
+      {props => (
+        <Submit
+          confirming={confirming}
+          createSupplementaryFile={createSupplementaryFile}
+          forms={cloneDeep(getFile)}
+          manuscript={manuscript}
+          onChange={handleChange}
+          toggleConfirming={toggleConfirming}
+          {...props}
+        />
+      )}
+    </Formik>
+  )
+}
+
+export default SubmitPage
diff --git a/app/components/component-submit/src/components/Suggestions.md b/app/components/component-submit/src/components/Suggestions.md
deleted file mode 100644
index ee1f366c9dd8c798bd9f341d86e6f515d9c6428d..0000000000000000000000000000000000000000
--- a/app/components/component-submit/src/components/Suggestions.md
+++ /dev/null
@@ -1,12 +0,0 @@
-A list of questions that must be answered before submission.
-
-```js
-const manuscript = {
-  suggestions: {
-    reviewers: {
-      opposed: faker.name.findName(),
-    },
-  },
-}
-;<Suggestions manuscript={manuscript} />
-```
diff --git a/app/components/component-submit/src/components/Supplementary.js b/app/components/component-submit/src/components/Supplementary.js
index 2580f3e0353de173b074856e9b47d4364f72b351..0432c9488da0896b8e7e21522fc7629516d82cc2 100644
--- a/app/components/component-submit/src/components/Supplementary.js
+++ b/app/components/component-submit/src/components/Supplementary.js
@@ -3,7 +3,7 @@ import { cloneDeep } from 'lodash'
 import { FieldArray } from 'formik'
 import { Flexbox, UploadButton, UploadingFile } from '@pubsweet/ui'
 
-const renderFilesUpload = (onChange, uploadFile, createFile) => ({
+const renderFilesUpload = (onChange, createSupplementaryFile) => ({
   form: { values, setFieldValue },
   push,
   insert,
@@ -21,15 +21,7 @@ const renderFilesUpload = (onChange, uploadFile, createFile) => ({
         })
         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)
-          })
+          createSupplementaryFile(file)
         })
       }}
     />
@@ -44,10 +36,10 @@ const renderFilesUpload = (onChange, uploadFile, createFile) => ({
   </>
 )
 
-const Supplementary = ({ onChange, uploadFile, createFile }) => (
+const Supplementary = ({ onChange, createSupplementaryFile }) => (
   <FieldArray
     name="files"
-    render={renderFilesUpload(onChange, uploadFile, createFile)}
+    render={renderFilesUpload(onChange, createSupplementaryFile)}
   />
 )
 
diff --git a/app/components/component-submit/src/components/SupplementaryFiles.md b/app/components/component-submit/src/components/SupplementaryFiles.md
deleted file mode 100644
index 46399c40d007bb451511d6d4058816e54ce18ab5..0000000000000000000000000000000000000000
--- a/app/components/component-submit/src/components/SupplementaryFiles.md
+++ /dev/null
@@ -1,20 +0,0 @@
-A form for entering the submission's supplementary material.
-
-```js
-const file = () => ({
-  file: {
-    url: faker.internet.url(),
-    name: faker.system.commonFileName(),
-  },
-  filename: faker.system.commonFileName(),
-  type: 'supplementary',
-})
-
-const manuscript = {
-  files: [file(), file(), file()],
-}
-;<SupplementaryFiles
-  manuscript={manuscript}
-  onChange={values => console.log(values)}
-/>
-```
diff --git a/app/components/component-submit/src/upload.js b/app/components/component-submit/src/upload.js
index 006a495cdaac5d1b18806b30bc339a414b42605f..2f06bf098e004f8de51fd787423bd4a6da631185 100644
--- a/app/components/component-submit/src/upload.js
+++ b/app/components/component-submit/src/upload.js
@@ -1,10 +1,6 @@
 import config from 'config'
 import request from 'pubsweet-client/src/helpers/api'
 import gql from 'graphql-tag'
-// TOOD: These queries come from the dashboard component,
-// making this a tricky dependency. Should we extract them into
-// a shared component?
-import queries from '../../component-dashboard/src/graphql/queries'
 
 const generateTitle = name =>
   name
@@ -156,17 +152,24 @@ const createManuscriptPromise = (
   return client.mutate({
     mutation: createManuscriptMutation,
     variables: { input: manuscript },
-    update: (proxy, { data: { createManuscript } }) => {
-      let data = proxy.readQuery({ query: queries.dashboard })
-      data.manuscripts.push(createManuscript)
-      proxy.writeQuery({ query: queries.dashboard, data })
-
-      data = proxy.readQuery({
-        query: queries.getUser,
-        variables: { id: currentUser.id },
+    update: (cache, { data: { createManuscript } }) => {
+      cache.modify({
+        fields: {
+          manuscripts(existingManuscriptRefs = []) {
+            // Get the reference for the cache entry generated by useMutation
+            const newManuscriptRef = cache.writeFragment({
+              data: createManuscript,
+              fragment: gql`
+                fragment NewManuscript on Manuscript {
+                  id
+                }
+              `,
+            })
+
+            return [...existingManuscriptRefs, newManuscriptRef]
+          },
+        },
       })
-      data.user.teams.push(createManuscript.teams[0])
-      proxy.writeQuery({ query: queries.getUser, data })
     },
   })
 }
diff --git a/app/components/component-teams-manager/src/components/Team.jsx b/app/components/component-teams-manager/src/components/Team.jsx
index 9a04a9065b620b9720f16010d3fc84019356310d..66535822dfae39ab2d1e612b7141ca26b1721575 100644
--- a/app/components/component-teams-manager/src/components/Team.jsx
+++ b/app/components/component-teams-manager/src/components/Team.jsx
@@ -14,7 +14,8 @@ const Team = ({ team, number, userOptions, deleteTeam, updateTeam }) => (
       {team.name} {team.role}
     </TeamTableCell>
     <TeamTableCell>
-      {team.object && team.object.objectType} {team.object && team.object.objectId}
+      {team.object && team.object.objectType}{' '}
+      {team.object && team.object.objectId}
     </TeamTableCell>
     <TeamTableCell width={40}>
       <StyledMenu
diff --git a/app/components/component-users-manager/src/UsersManager.jsx b/app/components/component-users-manager/src/UsersManager.jsx
index 5a09925c7978a4b2f0839064440df7e8c0540a9f..72869f557d834fe92c44181e9a5138377bf501ef 100644
--- a/app/components/component-users-manager/src/UsersManager.jsx
+++ b/app/components/component-users-manager/src/UsersManager.jsx
@@ -44,7 +44,6 @@ const GET_USERS = gql`
   }
 `
 
-
 const UsersManager = () => {
   const SortHeader = ({ thisSortName, children }) => {
     const changeSort = () => {
diff --git a/app/components/shared/Badge.js b/app/components/shared/Badge.js
index f7f1a2e873fa848a66930d08347af8dc5dbe08f4..29e9cc2a5aa02a7180571370a45ca0a09dd78ff6 100644
--- a/app/components/shared/Badge.js
+++ b/app/components/shared/Badge.js
@@ -59,6 +59,8 @@ const label = status => {
     rejected: 'Rejected',
     submitted: 'Submitted',
     revise: 'Revising',
+    invited: 'Invited', // reviewer status
+    completed: 'Completed', // reviewer status
   }
   return labels[status] || `Unknown (${status})`
 }
diff --git a/app/components/shared/General.js b/app/components/shared/General.js
index f0e4551f704bd66c0477c4f42440388f3a786604..dc6ca58eda32534b3085339d92de8c3087f12d11 100644
--- a/app/components/shared/General.js
+++ b/app/components/shared/General.js
@@ -59,3 +59,22 @@ export const SectionAction = styled.div`
   grid-column: 3;
   justify-self: end;
 `
+
+const Page = styled.div`
+  padding: ${grid(2)};
+`
+
+const Heading = styled.div`
+  color: ${th('colorPrimary')};
+  font-family: ${th('fontReading')};
+  font-size: ${th('fontSizeHeading3')};
+  line-height: ${th('lineHeightHeading3')};
+`
+
+export { Page, Heading }
+
+export const HeadingWithAction = styled.div`
+  display: grid;
+  grid-template-columns: 1fr auto;
+  align-items: center;
+`
diff --git a/app/components/shared/Pagination.jsx b/app/components/shared/Pagination.jsx
index 8a395f37f83c0eb73d95ea7c36680c8b2484b662..e5b5aa3b831b6e3369dafcc130d4273fc54c28de 100644
--- a/app/components/shared/Pagination.jsx
+++ b/app/components/shared/Pagination.jsx
@@ -144,4 +144,3 @@ export const Pagination = ({ setPage, limit, page, totalCount }) => {
     </PaginationContainer>
   )
 }
-
diff --git a/app/components/shared/Select.js b/app/components/shared/Select.js
new file mode 100644
index 0000000000000000000000000000000000000000..a28127cd67ccd28d927b969f02d2d4cc51d97fb8
--- /dev/null
+++ b/app/components/shared/Select.js
@@ -0,0 +1,42 @@
+import React, { useContext } from 'react'
+import ReactSelect from 'react-select'
+import { ThemeContext } from 'styled-components'
+
+const styles = th => ({
+  menu: (provided, state) => ({
+    ...provided,
+    borderRadius: th.borderRadius,
+  }),
+
+  control: (provided, state) => ({
+    ...provided,
+    border: state.isFocused
+      ? `1px solid ${th.colorPrimary}`
+      : `1px solid ${th.colorBorder}`,
+    boxShadow: state.isFocused ? `0 0 0 1px ${th.colorPrimary}` : 'none',
+    borderRadius: th.borderRadius,
+    '&:hover': {
+      boxShadow: `0 0 0 1px ${th.colorPrimary}`,
+    },
+    minHeight: `calc(${th.gridUnit} * 5)`,
+  }),
+
+  singleValue: (provided, state) => {
+    const opacity = state.isDisabled ? 0.5 : 1
+    const transition = 'opacity 300ms'
+
+    return { ...provided, opacity, transition }
+  },
+
+  option: (provided, state) => ({
+    ...provided,
+    backgroundColor:
+      state.isFocused || state.isSelected ? th.colorFurniture : 'white',
+    color: th.colorText,
+  }),
+})
+
+export const Select = props => {
+  const theme = useContext(ThemeContext)
+  return <ReactSelect {...props} styles={styles(theme)} />
+}
diff --git a/app/components/shared/index.js b/app/components/shared/index.js
index 7ac615184caed50bd15b8b63e77bb5142ce2515f..9c03d0839662980df9d89035eb141a227f8a089b 100644
--- a/app/components/shared/index.js
+++ b/app/components/shared/index.js
@@ -7,3 +7,4 @@ export * from './UserCombo'
 export * from './Table'
 export * from './General'
 export * from './Badge'
+export * from './Select'
diff --git a/app/fragmentTypes.json b/app/fragmentTypes.json
deleted file mode 100644
index 571ad559ceacb8cc27ca7155a64e35a74a922336..0000000000000000000000000000000000000000
--- a/app/fragmentTypes.json
+++ /dev/null
@@ -1 +0,0 @@
-{"__schema":{"types":[{"kind":"INTERFACE","name":"Identity","possibleTypes":[{"name":"LocalIdentity"},{"name":"ExternalIdentity"}]},{"kind":"INTERFACE","name":"Object","possibleTypes":[{"name":"Manuscript"},{"name":"ManuscriptVersion"},{"name":"File"},{"name":"Review"},{"name":"Note"}]}]}}
\ No newline at end of file
diff --git a/app/index.html b/app/index.html
index 4fe0d77d5a1c54430fd7c7e3bb6f7442a61afa72..6868ec90f5d475288e4dd90a176dc43155677c8d 100644
--- a/app/index.html
+++ b/app/index.html
@@ -2,7 +2,7 @@
 <html>
 <head>
   <meta charset="utf-8">
-  <title>SimpleJ</title>
+  <title>Kotahi</title>
 </head>
 <body>
   <div id="root"></div>
diff --git a/app/queries/index.js b/app/queries/index.js
index 17d6b3ba206f39b9addd2edd475969fc85f5c51a..6f86f24089190cc2f054069b6b881f52872b5d02 100644
--- a/app/queries/index.js
+++ b/app/queries/index.js
@@ -8,15 +8,12 @@ export const GET_CURRENT_USER = gql`
       username
       admin
       defaultIdentity {
+        identifier
+        email
+        type
         aff
+        id
         name
-        type
-        ... on ExternalIdentity {
-          identifier
-        }
-        ... on LocalIdentity {
-          email
-        }
       }
       online
       _currentRoles {
diff --git a/app/shared/prettyRoleText.js b/app/shared/prettyRoleText.js
new file mode 100644
index 0000000000000000000000000000000000000000..50c243c0dedd05e898d73a1391b358e71a4c03ad
--- /dev/null
+++ b/app/shared/prettyRoleText.js
@@ -0,0 +1,8 @@
+const config = require('config')
+
+module.exports = (roles = []) => {
+  const prettyRoles = config.journal.roles
+
+  const roleText = roles.map(r => prettyRoles[r] || r).join(', ')
+  return roleText
+}
diff --git a/config/journal/roles.js b/config/journal/roles.js
index 95129e0a1ec5d4e512cf548d971ea7f470f04c5b..066d9dad6cbadca7cf8d0b472f083d550948bc1c 100644
--- a/config/journal/roles.js
+++ b/config/journal/roles.js
@@ -3,4 +3,5 @@ module.exports = {
   handlingEditor: 'Handling Editor',
   managingEditor: 'Managing Editor',
   seniorEditor: 'Senior Editor',
+  reviewer: 'Reviewer',
 }
diff --git a/cypress/dumps/3reviewscompleted.sql b/cypress/dumps/3reviewscompleted.sql
deleted file mode 100644
index d2ee088104791b09b11e68681a881c3e5526c9fe..0000000000000000000000000000000000000000
--- a/cypress/dumps/3reviewscompleted.sql
+++ /dev/null
@@ -1,468 +0,0 @@
---
--- PostgreSQL database dump
---
-
--- Dumped from database version 10.5
--- Dumped by pg_dump version 10.5
-
-SET statement_timeout = 0;
-SET lock_timeout = 0;
-SET idle_in_transaction_session_timeout = 0;
-SET client_encoding = 'UTF8';
-SET standard_conforming_strings = on;
-SELECT pg_catalog.set_config('search_path', '', false);
-SET check_function_bodies = false;
-SET client_min_messages = warning;
-SET row_security = off;
-
---
--- Name: pgboss; Type: SCHEMA; Schema: -; Owner: test
---
-
-CREATE SCHEMA pgboss;
-
-
-ALTER SCHEMA pgboss OWNER TO test;
-
---
--- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: 
---
-
-CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
-
-
---
--- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: 
---
-
-COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
-
-
---
--- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: 
---
-
-CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
-
-
---
--- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: 
---
-
-COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';
-
-
-SET default_tablespace = '';
-
-SET default_with_oids = false;
-
---
--- Name: aliases; Type: TABLE; Schema: public; Owner: test
---
-
-CREATE TABLE public.aliases (
-    id uuid NOT NULL,
-    created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
-    updated timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
-    name character varying(255),
-    email character varying(255),
-    aff character varying(255)
-);
-
-
-ALTER TABLE public.aliases OWNER TO test;
-
---
--- Name: entities; Type: TABLE; Schema: public; Owner: test
---
-
-CREATE TABLE public.entities (
-    id uuid NOT NULL,
-    data jsonb
-);
-
-
-ALTER TABLE public.entities OWNER TO test;
-
---
--- Name: files; Type: TABLE; Schema: public; Owner: test
---
-
-CREATE TABLE public.files (
-    id uuid NOT NULL,
-    created timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
-    updated timestamp with time zone,
-    object text,
-    object_id uuid,
-    label text,
-    file_type text,
-    filename text,
-    url text,
-    mime_type text,
-    size integer,
-    type text NOT NULL
-);
-
-
-ALTER TABLE public.files OWNER TO test;
-
---
--- Name: journals; Type: TABLE; Schema: public; Owner: test
---
-
-CREATE TABLE public.journals (
-    id uuid NOT NULL,
-    created timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
-    updated timestamp with time zone,
-    title text,
-    meta jsonb,
-    type text NOT NULL
-);
-
-
-ALTER TABLE public.journals OWNER TO test;
-
---
--- Name: manuscripts; Type: TABLE; Schema: public; Owner: test
---
-
-CREATE TABLE public.manuscripts (
-    id uuid NOT NULL,
-    created timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
-    updated timestamp with time zone,
-    parent_id uuid,
-    status text,
-    decision text,
-    authors jsonb,
-    suggestions jsonb,
-    meta jsonb,
-    type text NOT NULL
-);
-
-
-ALTER TABLE public.manuscripts OWNER TO test;
-
---
--- Name: migrations; Type: TABLE; Schema: public; Owner: test
---
-
-CREATE TABLE public.migrations (
-    id text NOT NULL,
-    run_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP
-);
-
-
-ALTER TABLE public.migrations OWNER TO test;
-
---
--- Name: reviews; Type: TABLE; Schema: public; Owner: test
---
-
-CREATE TABLE public.reviews (
-    id uuid NOT NULL,
-    created timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
-    updated timestamp with time zone,
-    recommendation text,
-    is_decision boolean DEFAULT false,
-    comments jsonb,
-    user_id uuid,
-    manuscript_id uuid,
-    type text NOT NULL
-);
-
-
-ALTER TABLE public.reviews OWNER TO test;
-
---
--- Name: team_members; Type: TABLE; Schema: public; Owner: test
---
-
-CREATE TABLE public.team_members (
-    id uuid NOT NULL,
-    created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
-    updated timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
-    status character varying(255),
-    team_id uuid,
-    user_id uuid,
-    alias_id uuid
-);
-
-
-ALTER TABLE public.team_members OWNER TO test;
-
---
--- Name: teams; Type: TABLE; Schema: public; Owner: test
---
-
-CREATE TABLE public.teams (
-    id uuid NOT NULL,
-    created timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
-    updated timestamp with time zone,
-    name text,
-    role text NOT NULL,
-    owners jsonb,
-    global boolean,
-    type text NOT NULL,
-    object_id uuid,
-    object_type character varying(255)
-);
-
-
-ALTER TABLE public.teams OWNER TO test;
-
---
--- Name: users; Type: TABLE; Schema: public; Owner: test
---
-
-CREATE TABLE public.users (
-    id uuid NOT NULL,
-    created timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
-    updated timestamp with time zone,
-    admin boolean,
-    email text,
-    username text,
-    password_hash text,
-    fragments jsonb,
-    collections jsonb,
-    teams jsonb,
-    password_reset_token text,
-    password_reset_timestamp timestamp with time zone,
-    type text NOT NULL
-);
-
-
-ALTER TABLE public.users OWNER TO test;
-
---
--- Data for Name: aliases; Type: TABLE DATA; Schema: public; Owner: test
---
-
-
-
---
--- Data for Name: entities; Type: TABLE DATA; Schema: public; Owner: test
---
-
-
-
---
--- Data for Name: files; Type: TABLE DATA; Schema: public; Owner: test
---
-
-INSERT INTO public.files (id, created, updated, object, object_id, label, file_type, filename, url, mime_type, size, type) VALUES ('60972cc9-b5b9-42b4-a80f-67616cfa6ac7', '2020-02-28 19:39:59.454+01', '2020-02-28 19:39:59.454+01', 'Manuscript', 'bd429b57-6fc9-4454-a42c-c1f63cfa263c', NULL, 'manuscript', 'test-pdf.pdf', '/0b5e0a112164206f396e61beb28a913b.pdf', 'application/pdf', 106798, 'file');
-
-
---
--- Data for Name: journals; Type: TABLE DATA; Schema: public; Owner: test
---
-
-INSERT INTO public.journals (id, created, updated, title, meta, type) VALUES ('4fe21415-b60c-442e-8748-872f35c2266f', '2020-02-28 19:39:55.414+01', '2020-02-28 19:39:55.414+01', 'My Journal', NULL, 'journal');
-
-
---
--- Data for Name: manuscripts; Type: TABLE DATA; Schema: public; Owner: test
---
-
-INSERT INTO public.manuscripts (id, created, updated, parent_id, status, decision, authors, suggestions, meta, type) VALUES ('bd429b57-6fc9-4454-a42c-c1f63cfa263c', '2020-02-28 19:39:59.308+01', '2020-02-28 19:40:15.15+01', NULL, 'submitted', NULL, NULL, '{"editors": {"opposed": "Gina Ode", "suggested": "John Ode"}, "reviewers": {"opposed": "James Doe", "suggested": "Jane Do"}}', '{"notes": [{"content": "This work was supported by the Trust [grant numbers 393,295]; the Natural Environment Research Council [grant number 49493].", "notesType": "fundingAcknowledgement"}, {"content": "", "notesType": "specialInstructions", "__typename": "Note"}], "title": "A Manuscript For The Ages", "keywords": "quantum, machines, nature", "articleType": "original-research", "declarations": {"openData": "yes", "preregistered": "yes", "researchNexus": "no", "openPeerReview": "yes", "streamlinedReview": "no", "previouslySubmitted": "no"}, "articleSections": ["cognitive-psychology"]}', 'Manuscript');
-
-
---
--- Data for Name: migrations; Type: TABLE DATA; Schema: public; Owner: test
---
-
-INSERT INTO public.migrations (id, run_at) VALUES ('1524494862-entities.sql', '2020-02-28 19:39:52.52103+01');
-INSERT INTO public.migrations (id, run_at) VALUES ('1537450834-files.sql', '2020-02-28 19:39:52.533068+01');
-INSERT INTO public.migrations (id, run_at) VALUES ('1537450834-journals.sql', '2020-02-28 19:39:52.582771+01');
-INSERT INTO public.migrations (id, run_at) VALUES ('1537450834-manuscript.sql', '2020-02-28 19:39:52.628646+01');
-INSERT INTO public.migrations (id, run_at) VALUES ('1537450834-review.sql', '2020-02-28 19:39:52.682145+01');
-INSERT INTO public.migrations (id, run_at) VALUES ('1542276313-initial-user-migration.sql', '2020-02-28 19:39:52.700352+01');
-INSERT INTO public.migrations (id, run_at) VALUES ('1542801241-initial-team-migration.sql', '2020-02-28 19:39:52.713315+01');
-INSERT INTO public.migrations (id, run_at) VALUES ('1547596236-initial-team-member-migration.js', '2020-02-28 19:39:52.732321+01');
-INSERT INTO public.migrations (id, run_at) VALUES ('1548205275-move-members.js', '2020-02-28 19:39:52.743401+01');
-INSERT INTO public.migrations (id, run_at) VALUES ('1548205276-simplify-object.js', '2020-02-28 19:39:52.765508+01');
-INSERT INTO public.migrations (id, run_at) VALUES ('1548328420-add-alias-migration.js', '2020-02-28 19:39:52.783812+01');
-INSERT INTO public.migrations (id, run_at) VALUES ('1560771823-add-unique-constraints-to-users.sql', '2020-02-28 19:39:52.794199+01');
-
-
---
--- Data for Name: reviews; Type: TABLE DATA; Schema: public; Owner: test
---
-
-INSERT INTO public.reviews (id, created, updated, recommendation, is_decision, comments, user_id, manuscript_id, type) VALUES ('622e74ac-f08b-420f-b311-076e69b03c00', '2020-02-28 19:40:33.481+01', '2020-02-28 19:40:40.542+01', 'accepted', false, '[{"type": "note", "content": "Great research into CC bases in the ky289 variant are mutated to TC which results in the truncation of the SAD-1."}, {"type": "confidential", "content": "Not too bad."}]', 'e910b3d3-273d-4492-b68b-b33a5e4cb58d', 'bd429b57-6fc9-4454-a42c-c1f63cfa263c', 'Review');
-INSERT INTO public.reviews (id, created, updated, recommendation, is_decision, comments, user_id, manuscript_id, type) VALUES ('5c8b933e-6410-46f4-8949-85ad3b1fc061', '2020-02-28 19:40:44.503+01', '2020-02-28 19:40:50.2+01', 'revise', false, '[{"type": "note", "content": "Mediocre analysis of Iron-Sulfur ClUster assembly enzyme homolog."}, {"type": "confidential", "content": "It is so so."}]', '1e28bd99-497d-4496-8e1a-169693995b18', 'bd429b57-6fc9-4454-a42c-c1f63cfa263c', 'Review');
-INSERT INTO public.reviews (id, created, updated, recommendation, is_decision, comments, user_id, manuscript_id, type) VALUES ('88d41d50-b647-4654-a8e0-ab5bb6934124', '2020-02-28 19:40:54.824+01', '2020-02-28 19:41:01.495+01', 'rejected', false, '[{"type": "note", "content": "mTOR-Is positively influence the occurrence and course of certain tumors after solid organ transplantation."}, {"type": "confidential", "content": "It is not good."}]', '60adcc92-64a0-4d87-97ef-a31ccf30517a', 'bd429b57-6fc9-4454-a42c-c1f63cfa263c', 'Review');
-
-
---
--- Data for Name: team_members; Type: TABLE DATA; Schema: public; Owner: test
---
-
-INSERT INTO public.team_members (id, created, updated, status, team_id, user_id, alias_id) VALUES ('a5ff0f55-6363-4c0f-a033-27be3b55ff37', '2020-02-28 19:39:59.499+01', '2020-02-28 19:39:59.499+01', NULL, 'ebb4cc52-04ed-4390-ad70-be8f438e6012', 'bbe263a7-3cc5-4b3a-a6ce-d2cc70425406', NULL);
-INSERT INTO public.team_members (id, created, updated, status, team_id, user_id, alias_id) VALUES ('2ef219dd-66b4-45b4-b525-89a6b64e9eeb', '2020-02-28 19:40:21.509+01', '2020-02-28 19:40:21.509+01', NULL, '5848e7e9-112e-41cc-8f93-460560a740aa', '1c162384-ef4c-47fc-a89e-d7a03c13cec8', NULL);
-INSERT INTO public.team_members (id, created, updated, status, team_id, user_id, alias_id) VALUES ('9a362034-2f53-4162-a5e4-d3fa6896ff5e', '2020-02-28 19:40:21.949+01', '2020-02-28 19:40:21.949+01', NULL, '84ef271d-a10c-4d3c-9c44-0568f126e161', '53c1ddca-2770-417d-991d-f386d2d06e57', NULL);
-INSERT INTO public.team_members (id, created, updated, status, team_id, user_id, alias_id) VALUES ('84235add-df75-48cf-8a39-ef18faea1536', '2020-02-28 19:41:01.778+01', '2020-02-28 19:41:01.778+01', 'completed', '9c00c3e3-9d53-48d8-805d-8ffa77f12d82', '1e28bd99-497d-4496-8e1a-169693995b18', NULL);
-INSERT INTO public.team_members (id, created, updated, status, team_id, user_id, alias_id) VALUES ('51b06270-cafd-46d9-af06-9ee5a703c41d', '2020-02-28 19:41:01.778+01', '2020-02-28 19:41:01.778+01', 'completed', '9c00c3e3-9d53-48d8-805d-8ffa77f12d82', '60adcc92-64a0-4d87-97ef-a31ccf30517a', NULL);
-INSERT INTO public.team_members (id, created, updated, status, team_id, user_id, alias_id) VALUES ('6da43ddf-b03c-4c1e-b304-517d563aa3f9', '2020-02-28 19:41:01.778+01', '2020-02-28 19:41:01.778+01', 'completed', '9c00c3e3-9d53-48d8-805d-8ffa77f12d82', 'e910b3d3-273d-4492-b68b-b33a5e4cb58d', NULL);
-
-
---
--- Data for Name: teams; Type: TABLE DATA; Schema: public; Owner: test
---
-
-INSERT INTO public.teams (id, created, updated, name, role, owners, global, type, object_id, object_type) VALUES ('ebb4cc52-04ed-4390-ad70-be8f438e6012', '2020-02-28 19:39:59.452+01', '2020-02-28 19:39:59.452+01', 'Author', 'author', NULL, NULL, 'team', 'bd429b57-6fc9-4454-a42c-c1f63cfa263c', 'Manuscript');
-INSERT INTO public.teams (id, created, updated, name, role, owners, global, type, object_id, object_type) VALUES ('5848e7e9-112e-41cc-8f93-460560a740aa', '2020-02-28 19:40:21.505+01', '2020-02-28 19:40:21.505+01', 'Senior Editor', 'seniorEditor', '["2d88ee35-1fa1-4d49-8f87-0b32bb786c9e"]', NULL, 'team', 'bd429b57-6fc9-4454-a42c-c1f63cfa263c', 'Manuscript');
-INSERT INTO public.teams (id, created, updated, name, role, owners, global, type, object_id, object_type) VALUES ('84ef271d-a10c-4d3c-9c44-0568f126e161', '2020-02-28 19:40:21.947+01', '2020-02-28 19:40:21.947+01', 'Handling Editor', 'handlingEditor', '["2d88ee35-1fa1-4d49-8f87-0b32bb786c9e"]', NULL, 'team', 'bd429b57-6fc9-4454-a42c-c1f63cfa263c', 'Manuscript');
-INSERT INTO public.teams (id, created, updated, name, role, owners, global, type, object_id, object_type) VALUES ('9c00c3e3-9d53-48d8-805d-8ffa77f12d82', '2020-02-28 19:40:27.284+01', '2020-02-28 19:40:54.755+01', 'Reviewer Editor', 'reviewerEditor', '["53c1ddca-2770-417d-991d-f386d2d06e57"]', NULL, 'team', 'bd429b57-6fc9-4454-a42c-c1f63cfa263c', 'Manuscript');
-
-
---
--- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: test
---
-
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, fragments, collections, teams, password_reset_token, password_reset_timestamp, type) VALUES ('2d88ee35-1fa1-4d49-8f87-0b32bb786c9e', '2020-02-28 19:39:53.113+01', '2020-02-28 19:39:53.113+01', true, 'admin@example.com', 'admin', '$2b$12$1qAk620zjS3m0H64vepNc.YZLMHtFyFfoIFLH721MltGTovng9U/m', '[]', '[]', NULL, NULL, NULL, 'user');
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, fragments, collections, teams, password_reset_token, password_reset_timestamp, type) VALUES ('1c162384-ef4c-47fc-a89e-d7a03c13cec8', '2020-02-28 19:39:53.855+01', '2020-02-28 19:39:53.855+01', NULL, 'simone@example.com', 'seditor', '$2b$12$jeVSGVlpc3W0PkcS5HneBezNtqdFMUuJSiOGXXYfr0Xtjm2laKM8y', '[]', '[]', NULL, NULL, NULL, 'user');
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, fragments, collections, teams, password_reset_token, password_reset_timestamp, type) VALUES ('53c1ddca-2770-417d-991d-f386d2d06e57', '2020-02-28 19:39:54.24+01', '2020-02-28 19:39:54.24+01', NULL, 'hector@example.com', 'heditor', '$2b$12$p.p4JZBES6JXCD2lqO9D9ORqiZj64ECCoRBpvzHsLPLjYHcnCM/J6', '[]', '[]', NULL, NULL, NULL, 'user');
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, fragments, collections, teams, password_reset_token, password_reset_timestamp, type) VALUES ('bbe263a7-3cc5-4b3a-a6ce-d2cc70425406', '2020-02-28 19:39:53.504+01', '2020-02-28 19:39:59.54+01', NULL, 'john@example.com', 'author', '$2b$12$Bjr9z5v7nPTSDzHG6YtVQ.9M8QBsbiRbkSAeFiwEPZrTqpkxroxEi', '[]', '[]', NULL, NULL, NULL, 'user');
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, fragments, collections, teams, password_reset_token, password_reset_timestamp, type) VALUES ('1e28bd99-497d-4496-8e1a-169693995b18', '2020-02-28 19:39:55.011+01', '2020-02-28 19:41:01.782+01', NULL, 'robert@example.com', 'reviewer2', '$2b$12$E20/golpY0zs.NCSdziUHOfj7GSz7C.CYSDwJh4lX3hboRAo56wtC', '[]', '[]', NULL, NULL, NULL, 'user');
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, fragments, collections, teams, password_reset_token, password_reset_timestamp, type) VALUES ('60adcc92-64a0-4d87-97ef-a31ccf30517a', '2020-02-28 19:39:55.367+01', '2020-02-28 19:41:01.782+01', NULL, 'remionne@example.com', 'reviewer3', '$2b$12$7JVUD5Rhw5bqXwXYbjgFIuuHLcLaaPafwqMOQCab32x6VCaMnwNqm', '[]', '[]', NULL, NULL, NULL, 'user');
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, fragments, collections, teams, password_reset_token, password_reset_timestamp, type) VALUES ('e910b3d3-273d-4492-b68b-b33a5e4cb58d', '2020-02-28 19:39:54.618+01', '2020-02-28 19:41:01.782+01', NULL, 'regina@example.com', 'reviewer1', '$2b$12$4zYpy2jtXufGR3ib/Sh0mOTZHTeuIu1L9PqZKn7pgMs2zq7dXsDuq', '[]', '[]', NULL, NULL, NULL, 'user');
-
-
---
--- Name: aliases aliases_pkey; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.aliases
-    ADD CONSTRAINT aliases_pkey PRIMARY KEY (id);
-
-
---
--- Name: entities entities_pkey; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.entities
-    ADD CONSTRAINT entities_pkey PRIMARY KEY (id);
-
-
---
--- Name: files files_pkey; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.files
-    ADD CONSTRAINT files_pkey PRIMARY KEY (id);
-
-
---
--- Name: journals journals_pkey; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.journals
-    ADD CONSTRAINT journals_pkey PRIMARY KEY (id);
-
-
---
--- Name: manuscripts manuscripts_pkey; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.manuscripts
-    ADD CONSTRAINT manuscripts_pkey PRIMARY KEY (id);
-
-
---
--- Name: migrations migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.migrations
-    ADD CONSTRAINT migrations_pkey PRIMARY KEY (id);
-
-
---
--- Name: reviews reviews_pkey; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.reviews
-    ADD CONSTRAINT reviews_pkey PRIMARY KEY (id);
-
-
---
--- Name: team_members team_members_pkey; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.team_members
-    ADD CONSTRAINT team_members_pkey PRIMARY KEY (id);
-
-
---
--- Name: teams teams_pkey; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.teams
-    ADD CONSTRAINT teams_pkey PRIMARY KEY (id);
-
-
---
--- Name: users users_email_key; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.users
-    ADD CONSTRAINT users_email_key UNIQUE (email);
-
-
---
--- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.users
-    ADD CONSTRAINT users_pkey PRIMARY KEY (id);
-
-
---
--- Name: users users_username_key; Type: CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.users
-    ADD CONSTRAINT users_username_key UNIQUE (username);
-
-
---
--- Name: team_members_team_id_user_id_index; Type: INDEX; Schema: public; Owner: test
---
-
-CREATE INDEX team_members_team_id_user_id_index ON public.team_members USING btree (team_id, user_id);
-
-
---
--- Name: teams_object_id_object_type_index; Type: INDEX; Schema: public; Owner: test
---
-
-CREATE INDEX teams_object_id_object_type_index ON public.teams USING btree (object_id, object_type);
-
-
---
--- Name: team_members team_members_alias_id_foreign; Type: FK CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.team_members
-    ADD CONSTRAINT team_members_alias_id_foreign FOREIGN KEY (alias_id) REFERENCES public.aliases(id);
-
-
---
--- Name: team_members team_members_team_id_foreign; Type: FK CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.team_members
-    ADD CONSTRAINT team_members_team_id_foreign FOREIGN KEY (team_id) REFERENCES public.teams(id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-
---
--- Name: team_members team_members_user_id_foreign; Type: FK CONSTRAINT; Schema: public; Owner: test
---
-
-ALTER TABLE ONLY public.team_members
-    ADD CONSTRAINT team_members_user_id_foreign FOREIGN KEY (user_id) REFERENCES public.users(id) ON UPDATE CASCADE ON DELETE CASCADE;
-
-
---
--- PostgreSQL database dump complete
---
-
diff --git a/cypress/dumps/initialState.sql b/cypress/dumps/initialState.sql
index 54976467b5bd466564000a01a9e73389f6e596ba..943209725dc5fb3990a02ead7bcb4c509fa0b446 100644
--- a/cypress/dumps/initialState.sql
+++ b/cypress/dumps/initialState.sql
@@ -25,37 +25,118 @@ CREATE SCHEMA pgboss;
 ALTER SCHEMA pgboss OWNER TO test;
 
 --
--- Name: plpgsql; Type: EXTENSION; Schema: -; Owner:
+-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: 
 --
 
 CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
 
 
 --
--- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner:
+-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: 
 --
 
 COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
 
 
 --
--- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner:
+-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: 
 --
 
 CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
 
 
 --
--- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner:
+-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: 
 --
 
 COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';
 
 
+--
+-- Name: job_state; Type: TYPE; Schema: pgboss; Owner: test
+--
+
+CREATE TYPE pgboss.job_state AS ENUM (
+    'created',
+    'retry',
+    'active',
+    'completed',
+    'expired',
+    'cancelled',
+    'failed'
+);
+
+
+ALTER TYPE pgboss.job_state OWNER TO test;
+
 SET default_tablespace = '';
 
 SET default_with_oids = false;
 
+--
+-- Name: archive; Type: TABLE; Schema: pgboss; Owner: test
+--
+
+CREATE TABLE pgboss.archive (
+    id uuid NOT NULL,
+    name text NOT NULL,
+    priority integer NOT NULL,
+    data jsonb,
+    state pgboss.job_state NOT NULL,
+    retrylimit integer NOT NULL,
+    retrycount integer NOT NULL,
+    retrydelay integer NOT NULL,
+    retrybackoff boolean NOT NULL,
+    startafter timestamp with time zone NOT NULL,
+    startedon timestamp with time zone,
+    singletonkey text,
+    singletonon timestamp without time zone,
+    expirein interval NOT NULL,
+    createdon timestamp with time zone NOT NULL,
+    completedon timestamp with time zone,
+    archivedon timestamp with time zone DEFAULT now() NOT NULL
+);
+
+
+ALTER TABLE pgboss.archive OWNER TO test;
+
+--
+-- Name: job; Type: TABLE; Schema: pgboss; Owner: test
+--
+
+CREATE TABLE pgboss.job (
+    id uuid DEFAULT public.gen_random_uuid() NOT NULL,
+    name text NOT NULL,
+    priority integer DEFAULT 0 NOT NULL,
+    data jsonb,
+    state pgboss.job_state DEFAULT 'created'::pgboss.job_state NOT NULL,
+    retrylimit integer DEFAULT 0 NOT NULL,
+    retrycount integer DEFAULT 0 NOT NULL,
+    retrydelay integer DEFAULT 0 NOT NULL,
+    retrybackoff boolean DEFAULT false NOT NULL,
+    startafter timestamp with time zone DEFAULT now() NOT NULL,
+    startedon timestamp with time zone,
+    singletonkey text,
+    singletonon timestamp without time zone,
+    expirein interval DEFAULT '00:15:00'::interval NOT NULL,
+    createdon timestamp with time zone DEFAULT now() NOT NULL,
+    completedon timestamp with time zone
+);
+
+
+ALTER TABLE pgboss.job OWNER TO test;
+
+--
+-- Name: version; Type: TABLE; Schema: pgboss; Owner: test
+--
+
+CREATE TABLE pgboss.version (
+    version text NOT NULL
+);
+
+
+ALTER TABLE pgboss.version OWNER TO test;
+
 --
 -- Name: aliases; Type: TABLE; Schema: public; Owner: test
 --
@@ -287,6 +368,25 @@ CREATE TABLE public.users (
 
 ALTER TABLE public.users OWNER TO test;
 
+--
+-- Data for Name: archive; Type: TABLE DATA; Schema: pgboss; Owner: test
+--
+
+
+
+--
+-- Data for Name: job; Type: TABLE DATA; Schema: pgboss; Owner: test
+--
+
+
+
+--
+-- Data for Name: version; Type: TABLE DATA; Schema: pgboss; Owner: test
+--
+
+INSERT INTO pgboss.version (version) VALUES ('11');
+
+
 --
 -- Data for Name: aliases; Type: TABLE DATA; Schema: public; Owner: test
 --
@@ -326,7 +426,8 @@ INSERT INTO public.identities (id, user_id, created, updated, type, identifier,
 INSERT INTO public.identities (id, user_id, created, updated, type, identifier, name, aff, oauth, is_default) VALUES ('4af83984-6359-47c5-a075-5ddfa9c555d9', '0da0bbec-9261-4706-b990-0c10aa3cc6b4', '2020-07-21 16:35:06.127+02', '2020-07-21 16:35:07.104+02', 'orcid', '0000-0002-7645-9921', 'Sherry Crofoot', NULL, '{"accessToken": "2ad4e130-0775-4e13-87fb-8e8f5a0570ae", "refreshToken": "159933d9-2020-4c02-bdfb-163af41017dc"}', true);
 INSERT INTO public.identities (id, user_id, created, updated, type, identifier, name, aff, oauth, is_default) VALUES ('acfa1777-0aec-4fe1-bc16-92bb9d19e884', '85e1300e-003c-4e96-987b-23812f902477', '2020-07-21 16:35:38.384+02', '2020-07-21 16:35:39.358+02', 'orcid', '0000-0002-9429-4446', 'Elaine Barnes', NULL, '{"accessToken": "dcf07bc7-e59c-41b3-9ce0-924ac20aeeea", "refreshToken": "ae49d6a1-8e62-419d-8767-4a3ec22c1950"}', true);
 INSERT INTO public.identities (id, user_id, created, updated, type, identifier, name, aff, oauth, is_default) VALUES ('88c85115-d83c-42d7-a1a1-0139827977da', '40e3d054-9ac8-4c0f-84ed-e3c6307662cd', '2020-07-21 16:36:24.975+02', '2020-07-21 16:36:26.059+02', 'orcid', '0000-0001-5956-7341', 'Gale Davis', NULL, '{"accessToken": "3e9f6f6c-7cc0-4afa-9fdf-6ed377c36aad", "refreshToken": "80b1e911-df97-43f1-9f11-17b61913f6d7"}', true);
-INSERT INTO public.identities (id, user_id, created, updated, type, identifier, name, aff, oauth, is_default) VALUES ('2ac76834-4ddf-493a-8e50-16c3ecba1b94', '34785737-493d-4819-9982-f522abfaffe6', '2020-07-21 16:39:14.755+02', '2020-07-21 16:39:15.753+02', 'orcid', '0000-0003-1838-2441', 'Joanne Pilger', NULL, '{"accessToken": "fd3da810-1439-4666-ac1d-e737a8ba96bd", "refreshToken": "d11215a2-9921-4a74-be7e-2bec4946d1fb"}', true);
+INSERT INTO public.identities (id, user_id, created, updated, type, identifier, name, aff, oauth, is_default) VALUES ('049f91da-c84e-4b80-be2e-6e0cfca7a136', '231717dd-ba09-43d4-ac98-9d5542b27a0c', '2020-07-22 14:18:36.611+02', '2020-07-22 14:18:37.745+02', 'orcid', '0000-0003-2536-230X', 'Test Account', NULL, '{"accessToken": "eb551178-79e5-4189-8c5f-0a553092a9b5", "refreshToken": "4506fa5f-bd77-4867-afb4-0b07ea5302d6"}', true);
+INSERT INTO public.identities (id, user_id, created, updated, type, identifier, name, aff, oauth, is_default) VALUES ('2fb8359c-239c-43fa-91f5-1ff2058272a6', '1d599f2c-d293-4d5e-b6c1-ba34e81e3fc8', '2020-07-24 15:21:54.604+02', '2020-07-24 15:21:55.7+02', 'orcid', '0000-0003-1838-2441', 'Joanne Pilger', NULL, '{"accessToken": "842de329-ef16-4461-b83b-e8fe57238904", "refreshToken": "524fbdc5-9c67-4b4c-af17-2ce4cf294e88"}', true);
 
 
 --
@@ -387,12 +488,29 @@ INSERT INTO public.migrations (id, run_at) VALUES ('1592915682-change-identities
 -- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: test
 --
 
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('0da0bbec-9261-4706-b990-0c10aa3cc6b4', '2020-07-21 16:35:06.125+02', '2020-07-21 16:35:24.978+02', NULL, NULL, '0000000276459921', NULL, NULL, NULL, NULL, 'user', NULL, false);
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('85e1300e-003c-4e96-987b-23812f902477', '2020-07-21 16:35:38.381+02', '2020-07-21 16:36:08.629+02', NULL, NULL, '0000000294294446', NULL, NULL, NULL, NULL, 'user', NULL, false);
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('40e3d054-9ac8-4c0f-84ed-e3c6307662cd', '2020-07-21 16:36:24.973+02', '2020-07-21 16:39:03.909+02', NULL, NULL, '0000000159567341', NULL, NULL, NULL, NULL, 'user', NULL, false);
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('34785737-493d-4819-9982-f522abfaffe6', '2020-07-21 16:39:14.753+02', '2020-07-21 16:39:29.593+02', NULL, NULL, '0000000318382441', NULL, NULL, NULL, NULL, 'user', NULL, false);
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('027afa6a-edbc-486e-bb31-71e12f8ea1c5', '2020-07-21 16:17:24.734+02', '2020-07-21 16:40:03+02', NULL, NULL, '0000000205642016', NULL, NULL, NULL, NULL, 'user', NULL, false);
-INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('3802b0e7-aadc-45de-9cf9-918fede99b97', '2020-07-21 16:30:45.719+02', '2020-07-21 16:41:40.685+02', true, NULL, '0000000256415729', NULL, NULL, NULL, NULL, 'user', NULL, true);
+INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('85e1300e-003c-4e96-987b-23812f902477', '2020-07-21 16:35:38.381+02', '2020-07-24 16:43:03.114+02', NULL, NULL, '0000000294294446', NULL, NULL, NULL, NULL, 'user', '/static/profiles/testuser1.jpg', false);
+INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('027afa6a-edbc-486e-bb31-71e12f8ea1c5', '2020-07-21 16:17:24.734+02', '2020-07-24 16:43:15.46+02', NULL, NULL, '0000000205642016', NULL, NULL, NULL, NULL, 'user', '/static/profiles/testuser2.jpg', false);
+INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('1d599f2c-d293-4d5e-b6c1-ba34e81e3fc8', '2020-07-24 15:21:54.59+02', '2020-07-24 16:43:26.378+02', NULL, NULL, '0000000318382441', NULL, NULL, NULL, NULL, 'user', '/static/profiles/testuser3.jpg', false);
+INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('40e3d054-9ac8-4c0f-84ed-e3c6307662cd', '2020-07-21 16:36:24.973+02', '2020-07-24 16:43:43.943+02', NULL, NULL, '0000000159567341', NULL, NULL, NULL, NULL, 'user', '/static/profiles/testuser4.jpg', true);
+INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('231717dd-ba09-43d4-ac98-9d5542b27a0c', '2020-07-22 14:18:36.597+02', '2020-07-24 16:43:54.939+02', NULL, NULL, '000000032536230X', NULL, NULL, NULL, NULL, 'user', '/static/profiles/testuser5.jpg', false);
+INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('3802b0e7-aadc-45de-9cf9-918fede99b97', '2020-07-21 16:30:45.719+02', '2020-07-24 16:49:06.488+02', true, NULL, '0000000256415729', NULL, NULL, NULL, NULL, 'user', '/static/profiles/testuser6.jpg', true);
+INSERT INTO public.users (id, created, updated, admin, email, username, password_hash, teams, password_reset_token, password_reset_timestamp, type, profile_picture, online) VALUES ('0da0bbec-9261-4706-b990-0c10aa3cc6b4', '2020-07-21 16:35:06.125+02', '2020-07-24 16:44:59.306+02', NULL, NULL, '0000000276459921', NULL, NULL, NULL, NULL, 'user', '/static/profiles/testuser7.jpg', true);
+
+
+--
+-- Name: job job_pkey; Type: CONSTRAINT; Schema: pgboss; Owner: test
+--
+
+ALTER TABLE ONLY pgboss.job
+    ADD CONSTRAINT job_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: version version_pkey; Type: CONSTRAINT; Schema: pgboss; Owner: test
+--
+
+ALTER TABLE ONLY pgboss.version
+    ADD CONSTRAINT version_pkey PRIMARY KEY (version);
 
 
 --
@@ -515,6 +633,48 @@ ALTER TABLE ONLY public.users
     ADD CONSTRAINT users_username_key UNIQUE (username);
 
 
+--
+-- Name: archive_archivedon_idx; Type: INDEX; Schema: pgboss; Owner: test
+--
+
+CREATE INDEX archive_archivedon_idx ON pgboss.archive USING btree (archivedon);
+
+
+--
+-- Name: archive_id_idx; Type: INDEX; Schema: pgboss; Owner: test
+--
+
+CREATE INDEX archive_id_idx ON pgboss.archive USING btree (id);
+
+
+--
+-- Name: job_name; Type: INDEX; Schema: pgboss; Owner: test
+--
+
+CREATE INDEX job_name ON pgboss.job USING btree (name text_pattern_ops);
+
+
+--
+-- Name: job_singletonkey; Type: INDEX; Schema: pgboss; Owner: test
+--
+
+CREATE UNIQUE INDEX job_singletonkey ON pgboss.job USING btree (name, singletonkey) WHERE ((state < 'completed'::pgboss.job_state) AND (singletonon IS NULL));
+
+
+--
+-- Name: job_singletonkeyon; Type: INDEX; Schema: pgboss; Owner: test
+--
+
+CREATE UNIQUE INDEX job_singletonkeyon ON pgboss.job USING btree (name, singletonon, singletonkey) WHERE (state < 'expired'::pgboss.job_state);
+
+
+--
+-- Name: job_singletonon; Type: INDEX; Schema: pgboss; Owner: test
+--
+
+CREATE UNIQUE INDEX job_singletonon ON pgboss.job USING btree (name, singletonon) WHERE ((state < 'expired'::pgboss.job_state) AND (singletonkey IS NULL));
+
+
 --
 -- Name: channel_members_idx; Type: INDEX; Schema: public; Owner: test
 --
diff --git a/cypress/integration/submission_spec.js b/cypress/integration/submission_spec.js
index e897322d6ba42619225a8cb1a09b6fbec6282827..e3317c21b68d3de567ef6f275cf2d44f2d140ff5 100644
--- a/cypress/integration/submission_spec.js
+++ b/cypress/integration/submission_spec.js
@@ -1,3 +1,43 @@
+describe('URL submission test', () => {
+  it('can submit a URL and some metadata', () => {
+    cy.task('restore', 'initialState')
+
+    cy.task('createToken', 'Emily Clay').then(token => {
+      cy.setToken(token)
+      cy.visit('/journal/dashboard')
+    })
+
+    cy.get('button')
+      .contains('New submission')
+      .click()
+    cy.get('button')
+      .contains('Submit a URL instead')
+      .click()
+
+    cy.get('body').contains('Submission created')
+    cy.get('input[data-testid="meta.title"]')
+      .click()
+      .clear()
+      .type('My URL submission')
+    cy.get('[data-testid="submission.name"]')
+      .click()
+      .type('Emily Clay')
+    cy.get('[data-testid="submission.keywords"]')
+      .click()
+      .type('some, keywords')
+
+    cy.get('button')
+      .contains('Submit your research object')
+      .click()
+    cy.get('button')
+      .contains('Submit your manuscript')
+      .click()
+
+    cy.get('body').contains('My Submissions')
+    cy.get('body').contains('My URL submission')
+  })
+})
+
 // // TODO: What's with the wait?
 
 // const login = (username, password = 'password') => {
@@ -241,3 +281,198 @@
 //     cy.contains('Nothing to do at the moment')
 //   })
 // })
+
+//     cy.task('db:seed')
+
+//     cy.visit('/dashboard')
+
+//     // 1. Log in as author
+//     login('author')
+
+//     // 2. Submit a PDF
+//     cy.fixture('test-pdf.pdf', 'base64').then(fileContent => {
+//       cy.get('[data-testid="dropzone"]').upload(
+//         {
+//           fileContent,
+//           fileName: 'test-pdf.pdf',
+//           encoding: 'base64',
+//           mimeType: 'application/pdf',
+//         },
+//         { subjectType: 'drag-n-drop' },
+//       )
+//     })
+
+//     cy.get('body').contains('Submission information')
+//     cy.get('[data-testid="meta.title"]').contains('test pdf')
+//     cy.get('[data-testid="meta.title"] div[contenteditable="true"]')
+//       .click()
+//       .type('{selectall}{del}A Manuscript For The Ages')
+
+//     cy.get('[data-testid="meta.abstract"] div[contenteditable="true"]')
+//       .click()
+//       .type(
+//         `{selectall}{del}Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem.`,
+//       )
+
+//     cy.get('[data-testid="meta.keywords"]').type('quantum, machines, nature')
+
+//     // TODO: Find a way to not match by partial class
+//     cy.get('[class*="Menu__Root"]').click()
+//     cy.get('[class*="Menu__Root"] [role="option"]:first').click()
+
+//     // TODO: Find a way to remove the forces
+//     cy.get('[name="meta.articleSections"]:first').click({ force: true })
+//     cy.get('[name="meta.declarations.openData"]:first').click({ force: true })
+//     cy.get('[name="meta.declarations.previouslySubmitted"]:nth(1)').click({
+//       force: true,
+//     })
+//     cy.get('[name="meta.declarations.openPeerReview"]:first').click({
+//       force: true,
+//     })
+//     cy.get('[name="meta.declarations.streamlinedReview"]:nth(1)').click({
+//       force: true,
+//     })
+//     cy.get('[name="meta.declarations.researchNexus"]:nth(1)').click({
+//       force: true,
+//     })
+//     cy.get('[name="meta.declarations.preregistered"]:first').click({
+//       force: true,
+//     })
+
+//     cy.get('[data-testid="suggestions.reviewers.suggested"]').type('Jane Doe')
+//     cy.get('[data-testid="suggestions.reviewers.opposed"]').type('James Doe')
+
+//     cy.get('[data-testid="suggestions.editors.suggested"]').type('John Ode')
+//     cy.get('[data-testid="suggestions.editors.opposed"]').type('Gina Ode')
+
+//     cy.get('[name="meta.notes.0.content"] div[contenteditable="true"]')
+//       .click()
+//       .type(
+//         'This work was supported by the Trust [grant numbers 393,295]; the Natural Environment Research Council [grant number 49493].',
+//       )
+//     cy.get('[name="meta.notes.1.content"] div[contenteditable="true"]')
+//       .click()
+//       .type('This is extremely divisive work, choose reviewers with care.')
+
+//     cy.get('form button:last').click()
+//     cy.get('button[type="submit"]').click()
+
+//     cy.visit('/dashboard')
+//     cy.contains('A Manuscript For The Ages')
+
+//     // 3. Logout
+//     cy.get('nav button').click()
+
+//     // 4. And login as admin
+//     login('admin', 'password')
+
+//     cy.get('[data-testid="control-panel"]').click()
+
+//     // 5. Assign senior editor
+//     // TODO: Find a way to not match by partial class
+//     cy.get('[class*="AssignEditor"] [class*="Menu__Root"]:first').click()
+//     cy.get(
+//       '[class*="AssignEditor"] [class*="Menu__Root"]:first [role="option"]:nth(1)',
+//     ).click()
+
+//     // 6. Assign handling editor
+//     cy.get('[class*="AssignEditor"] [class*="Menu__Root"]:nth(1)').click()
+//     cy.get(
+//       '[class*="AssignEditor"] [class*="Menu__Root"]:nth(1) [role="option"]:nth(2)',
+//     ).click()
+
+//     // 7. Logout
+//     cy.get('nav button').click()
+
+//     // 8. And login as handling editor
+//     cy.wait(1000)
+//     login('heditor')
+//     cy.get('[data-testid="control-panel"]').click()
+
+//     // 9. Assign reviewers
+//     cy.get('[class*="AssignEditorsReviewers"] a').click()
+
+//     cy.get('.Select-control').click()
+//     cy.get('.Select-menu[role="listbox"] [role="option"]:nth(3)').click()
+//     cy.get('button[type="submit"]').click()
+//     cy.get('[class*="Reviewer__"]').should('have.length', 1)
+
+//     cy.get('.Select-control').click()
+//     cy.get('.Select-menu[role="listbox"] [role="option"]:nth(4)').click()
+//     cy.get('button[type="submit"]').click()
+//     cy.get('[class*="Reviewer__"]').should('have.length', 2)
+
+//     cy.get('.Select-control').click()
+//     cy.get('.Select-menu[role="listbox"] [role="option"]:nth(5)').click()
+//     cy.get('button[type="submit"]').click()
+//     cy.get('[class*="Reviewer__"]').should('have.length', 3)
+
+//     // 10. Check that 3 reviewers are invited
+//     cy.contains('SimpleJ').click()
+//     cy.get('[data-testid="invited"]').contains('3')
+
+//     // 11. Logout
+//     cy.get('nav button').click()
+
+//     doReview(
+//       'reviewer1',
+//       'Great research into CC bases in the ky289 variant are mutated to TC which results in the truncation of the SAD-1.',
+//       'Not too bad.',
+//       0,
+//     )
+//     doReview(
+//       'reviewer2',
+//       'Mediocre analysis of Iron-Sulfur ClUster assembly enzyme homolog.',
+//       'It is so so.',
+//       1,
+//     )
+//     doReview(
+//       'reviewer3',
+//       'mTOR-Is positively influence the occurrence and course of certain tumors after solid organ transplantation.',
+//       'It is not good.',
+//       2,
+//     )
+
+//     // 12. Log in as handling editor
+//     login('heditor')
+//     cy.get('[data-testid="completed"]').contains('3')
+
+//     cy.task('dump', '3reviewscompleted')
+//   })
+
+//   it('accept a submitted paper', () => {
+//     cy.task('restore', '3reviewscompleted')
+//     cy.visit('/dashboard')
+//     login('heditor')
+//     cy.get('[data-testid="completed"]').contains('3')
+
+//     cy.get('[data-testid="control-panel"]').click()
+//     cy.contains('reviewer1')
+//     cy.contains('reviewer2')
+//     cy.contains('reviewer3')
+
+//     // Write a decision
+//     cy.get('[placeholder*="Write/paste"] div[contenteditable="true"]')
+//       .focus()
+//       .type("Let's do this!")
+//       .blur()
+//       .wait(1000)
+//     cy.get(`[class*=Radio__Label]:nth(0)`)
+//       .click()
+//       .wait(1000)
+
+//     cy.get('button[type=submit]').click()
+//     cy.wait(2000)
+//     cy.visit('/dashboard')
+//     cy.contains('accepted')
+//   })
+
+//   it('can delete a submission', () => {
+//     cy.task('restore', '3reviewscompleted')
+//     cy.visit('/dashboard')
+//     login('admin')
+//     cy.get('button:contains("Delete")').click()
+//     cy.visit('/dashboard')
+//     cy.contains('Nothing to do at the moment')
+//   })
+// })
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
index 2bc079c1db0dc7ed3f50c8d459de4c38bdbff8a5..92a3a481f3c6041a92249012d4901767a50a3a69 100644
--- a/cypress/plugins/index.js
+++ b/cypress/plugins/index.js
@@ -26,6 +26,7 @@ const testUsers = {
   'Joanne Pilger': '0000000318382441',
   'Emily Clay': '0000000205642016',
   'Sinead Sullivan': '0000000256415729', // admin
+  'Test Account': '000000032536230X',
 }
 
 module.exports = (on, config) => {
diff --git a/cypress/videos/login_spec.js.mp4 b/cypress/videos/login_spec.js.mp4
index a17ab4e7d55424434393a05e02f1aaaa37b7f914..1cb7e89d87a778ec752413299ba474fe4ec27672 100644
Binary files a/cypress/videos/login_spec.js.mp4 and b/cypress/videos/login_spec.js.mp4 differ
diff --git a/cypress/videos/submission_spec.js.mp4 b/cypress/videos/submission_spec.js.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..91c8214ea05c2ed0e929ae786e71f1296de36f34
Binary files /dev/null and b/cypress/videos/submission_spec.js.mp4 differ
diff --git a/package.json b/package.json
index e398f0d58051e11a65c262515b47e60876103e94..f7ebd6143fd07e164f4cf737277c00beb97ff14a 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
-  "name": "SimpleJ",
-  "version": "1.0.0",
+  "name": "Kotahi",
+  "version": "1.0.0-alpha",
   "private": true,
-  "description": "SimpleJ - open journals",
+  "description": "Kotahi - open journals",
   "license": "MIT",
   "engines": {
     "node": ">=9",
@@ -113,7 +113,7 @@
     "husky": "^0.14.3",
     "jest-cli": "^22.1.4",
     "joi-browser": "^10.0.6",
-    "lint-staged": "^4.1.3",
+    "lint-staged": "^10.2.11",
     "mini-css-extract-plugin": "^0.8.0",
     "node-dev": "^4.0.0",
     "prettier": "^1.8.2",
@@ -137,7 +137,7 @@
   },
   "scripts": {
     "lint": "npm run lint:js && npm run lint:style",
-    "lint:js": "eslint app/ scripts/ server/ config/",
+    "lint:js": "eslint app/ scripts/ server/ config/ --ext js,jsx",
     "lint:style": "stylelint app/**/*.scss app/**/*.css",
     "precommit": "lint-staged",
     "reset": "pubsweet setupdb --clobber",
@@ -164,12 +164,15 @@
     "setupTestFrameworkScriptFile": "<rootDir>/test/helpers/jest-setup.js"
   },
   "lint-staged": {
-    "*.js": [
-      "prettier --write",
-      "git add"
+    "*.{js,jsx}": [
+      "eslint --fix"
     ],
-    "*.css": "stylelint",
-    "*.scss": "stylelint"
+    "*.{js, jsx}": [
+      "stylelint"
+    ],
+    "*.{json,md}": [
+      "prettier --write"
+    ]
   },
   "resolutions": {
     "bcrypt": "^3.0.6",
diff --git a/profiles/testuser1.jpg b/profiles/testuser1.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5f6a98e8bf6f175b23e370841e367e17d20ba2ac
Binary files /dev/null and b/profiles/testuser1.jpg differ
diff --git a/profiles/testuser2.jpg b/profiles/testuser2.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fe7ee6a9870af88f7feeb23ff9053b0d594ab22e
Binary files /dev/null and b/profiles/testuser2.jpg differ
diff --git a/profiles/testuser3.jpg b/profiles/testuser3.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..75d6e00f13d547b9530bc2388a9e739ae816393e
Binary files /dev/null and b/profiles/testuser3.jpg differ
diff --git a/profiles/testuser4.jpg b/profiles/testuser4.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e7e0202e1f94b76444d6ca38ea22ba142460c38d
Binary files /dev/null and b/profiles/testuser4.jpg differ
diff --git a/profiles/testuser5.jpg b/profiles/testuser5.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..be44ffd971d241f162ce2d1afc6cf44bc5ddeacf
Binary files /dev/null and b/profiles/testuser5.jpg differ
diff --git a/profiles/testuser6.jpg b/profiles/testuser6.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3bbb2c078c7e568b4704e1a481221e014542a3e5
Binary files /dev/null and b/profiles/testuser6.jpg differ
diff --git a/profiles/testuser7.jpg b/profiles/testuser7.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cdcabc0fa587b1025469f8ed6f143ef57153c8da
Binary files /dev/null and b/profiles/testuser7.jpg differ
diff --git a/profiles/testuser8.jpg b/profiles/testuser8.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..27b8456293a420b472b74009a8cf6f3360085dfc
Binary files /dev/null and b/profiles/testuser8.jpg differ
diff --git a/scripts/clearAndSeed.js b/scripts/clearAndSeed.js
index 3f343a85c7b128ff0863c72bc0a7237cb495e8c6..bdcdf51da9173e42dadc8afa08de57cc4cdf0152 100644
--- a/scripts/clearAndSeed.js
+++ b/scripts/clearAndSeed.js
@@ -34,7 +34,7 @@ const seed = async dumpSql => {
     await clearDb()
     await db.raw(dumpSql)
     logger.info('Cleared the database and restored from dump')
-    // TODO: This wait is necessary for the database to settle
+    // TODO: This wait is necessary for the database to "settle".
     await wait(2000)
     return true
   }
diff --git a/server/app.js b/server/app.js
index daf5ac7cb675e63ed57fc5062a4819dfb53ce93e..22ada082520d1dc728d3effe9d194c0bad60343f 100644
--- a/server/app.js
+++ b/server/app.js
@@ -60,8 +60,10 @@ const configureApp = app => {
 
   if (config.has('pubsweet-server.uploads')) {
     app.use(
-      '/uploads',
-      express.static(path.resolve(config.get('pubsweet-server.uploads'))),
+      '/static/uploads',
+      express.static(
+        path.join(__dirname, '..', config.get('pubsweet-server.uploads')),
+      ),
     )
   }
   // Passport strategies
diff --git a/server/model-file/src/resolvers.js b/server/model-file/src/resolvers.js
index 83b647dcdc7021b66cdcdcd56efb651e733894bd..da8ea91c7c9c0b3e9a1423ac232df849defd7a10 100644
--- a/server/model-file/src/resolvers.js
+++ b/server/model-file/src/resolvers.js
@@ -1,10 +1,37 @@
+const crypto = require('crypto')
+const { promisify } = require('util')
+const fs = require('fs-extra')
+const path = require('path')
+const config = require('config')
+
 const File = require('./file')
 
+const randomBytes = promisify(crypto.randomBytes)
+const uploadsPath = config.get('pubsweet-server').uploads
+
+const upload = async file => {
+  const { stream, filename, encoding } = await file
+
+  const raw = await randomBytes(16)
+  const generatedFilename = raw.toString('hex') + path.extname(filename)
+  const outPath = path.join(uploadsPath, generatedFilename)
+
+  await fs.ensureDir(uploadsPath)
+  const outStream = fs.createWriteStream(outPath)
+  stream.pipe(outStream, { encoding })
+
+  return new Promise((resolve, reject) => {
+    outStream.on('finish', () => resolve(outPath))
+    outStream.on('error', reject)
+  })
+}
 const resolvers = {
   Query: {},
   Mutation: {
-    async createFile(_, { id, file }, ctx) {
-      const data = await new File(file).save()
+    async createFile(_, { file, meta }, ctx) {
+      const path = await upload(file)
+      meta.url = `/static/${path}`
+      const data = await new File(meta).save()
       return data
     },
   },
diff --git a/server/model-file/src/typeDefs.js b/server/model-file/src/typeDefs.js
index 9b70a794c1eb119ef525b4dde7be3caa29dd0524..34050c96a91107418bba4e9e5951163a8bc5dc72 100644
--- a/server/model-file/src/typeDefs.js
+++ b/server/model-file/src/typeDefs.js
@@ -1,6 +1,17 @@
 const typeDefs = `
   extend type Mutation {
-    createFile(file: Upload!): File!
+    # Using a separate variable because the Upload type hides other data
+    createFile(file: Upload!, meta: FileMetaInput): File!
+  }
+
+  input FileMetaInput {
+    fileType: String
+    filename: String
+    mimeType: String
+    object: String
+    objectId: ID!
+    label: String
+    size: Int
   }
 
   type File implements Object  {
diff --git a/server/model-manuscript/src/graphql.js b/server/model-manuscript/src/graphql.js
index e615f43bea6c281801fcd8f1e8f7ae9cd7eadf0a..25fdb53ae256593126a639974656061405cd3617 100644
--- a/server/model-manuscript/src/graphql.js
+++ b/server/model-manuscript/src/graphql.js
@@ -143,18 +143,74 @@ const resolvers = {
     },
     async updateManuscript(_, { id, input }, ctx) {
       const data = JSON.parse(input)
-      const manuscript = await ctx.models.Manuscript.findById(id)
+      const manuscript = await ctx.models.Manuscript.query().findById(id)
       const update = merge({}, manuscript, data)
-      return ctx.models.Manuscript.update(id, update, ctx)
+      return ctx.models.Manuscript.query().updateAndFetchById(id, update)
     },
     async makeDecision(_, { id, decision }, ctx) {
-      const manuscript = await ctx.models.Manuscript.findById(id)
+      const manuscript = await ctx.models.Manuscript.query().findById(id)
       manuscript.decision = decision
 
       manuscript.status = decision
 
       return manuscript.save()
     },
+    async addReviewer(_, { manuscriptId, userId }, ctx) {
+      const manuscript = await ctx.models.Manuscript.query().findById(
+        manuscriptId,
+      )
+
+      const existingTeam = await manuscript
+        .$relatedQuery('teams')
+        .where('role', 'reviewer')
+        .first()
+
+      // Add the reviewer to the existing team of reviewers
+      if (existingTeam) {
+        const reviewerExists =
+          (await existingTeam
+            .$relatedQuery('users')
+            .where('users.id', userId)
+            .resultSize()) > 0
+        if (!reviewerExists) {
+          await new ctx.models.TeamMember({
+            teamId: existingTeam.id,
+            status: 'invited',
+            userId,
+          }).save()
+        }
+        return existingTeam.$query().eager('members.[user]')
+      }
+
+      // Create a new team of reviewers if it doesn't exist
+      const newTeam = await new ctx.models.Team({
+        objectId: manuscriptId,
+        objectType: 'Manuscript',
+        members: [{ status: 'invited', userId }],
+        role: 'reviewer',
+      }).saveGraph()
+
+      return newTeam
+    },
+    async removeReviewer(_, { manuscriptId, userId }, ctx) {
+      const manuscript = await ctx.models.Manuscript.query().findById(
+        manuscriptId,
+      )
+
+      const reviewerTeam = await manuscript
+        .$relatedQuery('teams')
+        .where('role', 'reviewer')
+        .first()
+
+      await ctx.models.TeamMember.query()
+        .where({
+          userId,
+          teamId: reviewerTeam.id,
+        })
+        .delete()
+
+      return reviewerTeam.$query().eager('members.[user]')
+    },
   },
   Query: {
     async manuscript(_, { id }, ctx) {
@@ -277,6 +333,8 @@ const typeDefs = `
     deleteManuscript(id: ID!): ID!
     reviewerResponse(currentUserId: ID, action: String, teamId: ID! ): Team
     assignTeamEditor(id: ID!, input: String): [Team]
+    addReviewer(manuscriptId: ID!, userId: ID!): Team
+    removeReviewer(manuscriptId: ID!, userId: ID!): Team
   }
 
   type Manuscript implements Object {
diff --git a/server/model-review/src/resolvers.js b/server/model-review/src/resolvers.js
index 90b611e5b4d76d6fc766d058016f5618cd776adb..9a8fc508e4d9f8d9e78e90e6e26bc7e8a9d8882a 100644
--- a/server/model-review/src/resolvers.js
+++ b/server/model-review/src/resolvers.js
@@ -5,11 +5,13 @@ const resolvers = {
   Mutation: {
     async updateReview(_, { id, input }, ctx) {
       if (id) {
-        const review = await ctx.connectors.Review.fetchOne(id, ctx)
+        const review = await ctx.models.Review.query().findById(id)
         const update = merge({}, review, input)
-        await ctx.connectors.Review.update(id, update, ctx)
         // Load Review
-        const rvw = await new Review(update)
+        const rvw = await ctx.models.Review.query().updateAndFetchById(
+          id,
+          update,
+        )
         rvw.comments = await rvw.getComments()
 
         return rvw
@@ -21,6 +23,25 @@ const resolvers = {
 
       return review
     },
+
+    async completeReview(_, { id }, ctx) {
+      const review = await ctx.models.Review.query().findById(id)
+      const manuscript = await ctx.models.Manuscript.query().findById(
+        review.manuscriptId,
+      )
+      const team = await manuscript
+        .$relatedQuery('teams')
+        .where('role', 'reviewer')
+        .first()
+
+      const member = await team
+        .$relatedQuery('members')
+        .where('userId', ctx.user.id)
+        .first()
+
+      member.status = 'completed'
+      return member.save()
+    },
   },
 }
 
diff --git a/server/model-review/src/typeDefs.js b/server/model-review/src/typeDefs.js
index 0ded633743461e8c3a4fb01fc3d58ca4b5815f43..fb39b5e3903bb16633a2860330a201fa9a683165 100644
--- a/server/model-review/src/typeDefs.js
+++ b/server/model-review/src/typeDefs.js
@@ -1,9 +1,10 @@
 const typeDefs = `
   extend type Mutation {
     updateReview(id: ID, input: ReviewInput): Review!
+    completeReview(id: ID!): TeamMember
   }
 
-  type Review implements Object  {
+  type Review implements Object {
     id: ID!
     created: DateTime!
     updated: DateTime
diff --git a/server/model-team/src/graphql.js b/server/model-team/src/graphql.js
index 0f1b08e636c84e1152f854635976f1877c67c873..ed95c10c5e82b7c21915ba55dc57bd020e5d6c30 100644
--- a/server/model-team/src/graphql.js
+++ b/server/model-team/src/graphql.js
@@ -98,7 +98,7 @@ const typeDefs = `
     id: ID!
     type: String!
     role: String!
-    name: String!
+    name: String
     object: TeamObject
     members: [TeamMember!]
     owners: [User]
diff --git a/server/model-team/src/team_member.js b/server/model-team/src/team_member.js
index 51236eff1e88fb38c0b01d77d90d400a0e7a0812..eff8dd732633a4249c9913b8f2ac221dec14dded 100644
--- a/server/model-team/src/team_member.js
+++ b/server/model-team/src/team_member.js
@@ -43,7 +43,6 @@ class TeamMember extends BaseModel {
         teamId: { type: 'string', format: 'uuid' },
         aliasId: { type: ['string', 'null'], format: 'uuid' },
         status: { type: 'string' },
-        global: { type: ['boolean', 'null'] },
       },
     }
   }
diff --git a/server/model-user/src/graphql.js b/server/model-user/src/graphql.js
index 56565dd4a4645070bf9d86913cd82d012e87b102..241ca795f8d0622f0da7d0ff144ca53a494ea721 100644
--- a/server/model-user/src/graphql.js
+++ b/server/model-user/src/graphql.js
@@ -142,16 +142,16 @@ const resolvers = {
       return identities
     },
   },
-  LocalIdentity: {
-    __isTypeOf: (obj, context, info) => obj.type === 'local',
-    async email(obj, args, ctx, info) {
-      // Emails stored on user, but surfaced in local identity too
-      return (await ctx.loaders.User.load(obj.userId)).email
-    },
-  },
-  ExternalIdentity: {
-    __isTypeOf: (obj, context, info) => obj.type !== 'local',
-  },
+  // LocalIdentity: {
+  //   __isTypeOf: (obj, context, info) => obj.type === 'local',
+  //   async email(obj, args, ctx, info) {
+  //     // Emails stored on user, but surfaced in local identity too
+  //     return (await ctx.loaders.User.load(obj.userId)).email
+  //   },
+  // },
+  // ExternalIdentity: {
+  //   __isTypeOf: (obj, context, info) => obj.type !== 'local',
+  // },
 }
 
 const typeDefs = `
@@ -209,33 +209,34 @@ const typeDefs = `
     roles: [String]
   }
 
-  interface Identity {
+  type Identity {
     id: ID
     name: String
     aff: String # JATS <aff>
     email: String # JATS <aff>
     type: String
+    identifier: String
   }
 
   # union Identity = Local | External
 
   # local identity (not from ORCID, etc.)
-  type LocalIdentity implements Identity {
-    id: ID
-    name: String
-    email: String
-    aff: String
-    type: String
-  }
-
-  type ExternalIdentity implements Identity {
-    id: ID
-    name: String
-    identifier: String
-    email: String
-    aff: String
-    type: String
-  }
+  #type LocalIdentity implements Identity {
+  #  id: ID
+  #  name: String
+  #  email: String
+  #  aff: String
+  #  type: String
+  #}
+  #
+  #type ExternalIdentity implements Identity {
+  #  id: ID
+  #  name: String
+  #  identifier: String
+  #  email: String
+  #  aff: String
+  #  type: String
+  #}
 
   input UserInput {
     username: String!
diff --git a/webpack/plugins.js b/webpack/plugins.js
index 9eac16ed92425ec318a6aa263838c67c17bfa35c..9a2c29675264d7660c14ee29a71d195f44e4c003 100644
--- a/webpack/plugins.js
+++ b/webpack/plugins.js
@@ -18,7 +18,7 @@ module.exports = (opts = {}) => {
   if (opts.html) {
     plugins.push(
       new HtmlWebpackPlugin({
-        title: 'SimpleJ - open journals',
+        title: 'Kotahi - open journals',
         template: '../app/index.ejs', // Load a custom template
       }),
     )
diff --git a/webpack/webpack.config.js b/webpack/webpack.config.js
index 0f9449a6d39e559a60b3ad841f007cafe8077143..5f3ab73a5b07cd0abf21bfe529016907e2e5542d 100644
--- a/webpack/webpack.config.js
+++ b/webpack/webpack.config.js
@@ -37,7 +37,7 @@ module.exports = webpackEnv => {
         '/api': 'http://localhost:3000',
         '/auth': 'http://localhost:3000',
         '/graphql': 'http://localhost:3000',
-        '/uploads': 'http://locahost:3000',
+        '/static/uploads': 'http://locahost:3000',
         '/static/profiles': 'http://localhost:3000',
         '/public': 'http://localhost:3000',
       },
diff --git a/yarn.lock b/yarn.lock
index 7f7ed6a8d66ffc574717b5fb5ba1eafeca60d319..7e8a7f476b6fb1be7ab5bee45bc8a7bbff7cd1b5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1945,6 +1945,11 @@
     "@types/connect" "*"
     "@types/node" "*"
 
+"@types/color-name@^1.1.1":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
+  integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
+
 "@types/connect@*":
   version "3.4.33"
   resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546"
@@ -2496,16 +2501,23 @@ ansi-colors@^3.0.0:
   resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
   integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
 
-ansi-escapes@^1.0.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
-  integrity sha1-06ioOzGapneTZisT52HHkRQiMG4=
+ansi-colors@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
+  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
 
 ansi-escapes@^3.0.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
   integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
 
+ansi-escapes@^4.3.0:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
+  integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
+  dependencies:
+    type-fest "^0.11.0"
+
 ansi-html@0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
@@ -2526,6 +2538,11 @@ ansi-regex@^4.1.0:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
   integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
 
+ansi-regex@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
+  integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
+
 ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -2538,6 +2555,14 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
   dependencies:
     color-convert "^1.9.0"
 
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
+  integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
+  dependencies:
+    "@types/color-name" "^1.1.1"
+    color-convert "^2.0.1"
+
 any-base@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe"
@@ -2827,11 +2852,6 @@ apollo-utilities@1.3.4, apollo-utilities@^1.0.1, apollo-utilities@^1.2.1, apollo
     ts-invariant "^0.4.0"
     tslib "^1.10.0"
 
-app-root-path@^2.0.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a"
-  integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==
-
 append-field@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56"
@@ -3044,6 +3064,11 @@ astral-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
   integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
 
+astral-regex@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
+  integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
+
 async-each@^1.0.0, async-each@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
@@ -3858,7 +3883,7 @@ braces@^2.3.1, braces@^2.3.2:
     split-string "^3.0.2"
     to-regex "^3.0.1"
 
-braces@~3.0.2:
+braces@^3.0.1, braces@~3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
   integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
@@ -4258,7 +4283,7 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
-chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
+chalk@^1.0.0, chalk@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
   integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
@@ -4269,6 +4294,14 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
+chalk@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
+  integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
+  dependencies:
+    ansi-styles "^4.1.0"
+    supports-color "^7.1.0"
+
 change-emitter@^0.1.2:
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515"
@@ -4466,10 +4499,12 @@ cli-cursor@^2.0.0, cli-cursor@^2.1.0:
   dependencies:
     restore-cursor "^2.0.0"
 
-cli-spinners@^0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c"
-  integrity sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=
+cli-cursor@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
+  integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
+  dependencies:
+    restore-cursor "^3.1.0"
 
 cli-table3@0.5.1:
   version "0.5.1"
@@ -4481,6 +4516,14 @@ cli-table3@0.5.1:
   optionalDependencies:
     colors "^1.1.2"
 
+cli-truncate@2.1.0, cli-truncate@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
+  integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
+  dependencies:
+    slice-ansi "^3.0.0"
+    string-width "^4.2.0"
+
 cli-truncate@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"
@@ -4587,12 +4630,19 @@ color-convert@^1.3.0, color-convert@^1.9.0, color-convert@^1.9.1:
   dependencies:
     color-name "1.1.3"
 
+color-convert@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+  integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+  dependencies:
+    color-name "~1.1.4"
+
 color-name@1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
   integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
 
-color-name@^1.0.0:
+color-name@^1.0.0, color-name@~1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
@@ -4685,7 +4735,7 @@ commander@4.1.1:
   resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
   integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
 
-commander@^2.11.0, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.9.0, commander@~2.20.3:
+commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@~2.20.3:
   version "2.20.3"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -4918,20 +4968,6 @@ cors@^2.8.4:
     object-assign "^4"
     vary "^1"
 
-cosmiconfig@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-1.1.0.tgz#0dea0f9804efdfb929fbb1b188e25553ea053d37"
-  integrity sha1-DeoPmATv37kp+7GxiOJVU+oFPTc=
-  dependencies:
-    graceful-fs "^4.1.2"
-    js-yaml "^3.4.3"
-    minimist "^1.2.0"
-    object-assign "^4.0.1"
-    os-homedir "^1.0.1"
-    parse-json "^2.2.0"
-    pinkie-promise "^2.0.0"
-    require-from-string "^1.1.0"
-
 cosmiconfig@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-3.1.0.tgz#640a94bf9847f321800403cd273af60665c73397"
@@ -5365,6 +5401,11 @@ decode-uri-component@^0.2.0:
   resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
   integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
 
+dedent@^0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
+  integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
+
 deep-equal@*:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.3.tgz#cad1c15277ad78a5c01c49c2dee0f54de8a6a7b0"
@@ -5832,6 +5873,11 @@ emoji-regex@^7.0.1:
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
   integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
 
+emoji-regex@^8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+  integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
 emoji-regex@^9.0.0:
   version "9.0.0"
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.0.0.tgz#48a2309cc8a1d2e9d23bc6a67c39b63032e76ea4"
@@ -5893,6 +5939,13 @@ enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1:
     memory-fs "^0.5.0"
     tapable "^1.0.0"
 
+enquirer@^2.3.5:
+  version "2.3.6"
+  resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
+  integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
+  dependencies:
+    ansi-colors "^4.1.1"
+
 entities@^1.1.1, entities@~1.1.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
@@ -6437,18 +6490,20 @@ execa@^0.7.0:
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
-execa@^0.8.0:
-  version "0.8.0"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da"
-  integrity sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=
+execa@^4.0.1:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2"
+  integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==
   dependencies:
-    cross-spawn "^5.0.1"
-    get-stream "^3.0.0"
-    is-stream "^1.1.0"
-    npm-run-path "^2.0.0"
-    p-finally "^1.0.0"
-    signal-exit "^3.0.0"
-    strip-eof "^1.0.0"
+    cross-spawn "^7.0.0"
+    get-stream "^5.0.0"
+    human-signals "^1.1.1"
+    is-stream "^2.0.0"
+    merge-stream "^2.0.0"
+    npm-run-path "^4.0.0"
+    onetime "^5.1.0"
+    signal-exit "^3.0.2"
+    strip-final-newline "^2.0.0"
 
 execall@^1.0.0:
   version "1.0.0"
@@ -6766,6 +6821,13 @@ figures@^2.0.0:
   dependencies:
     escape-string-regexp "^1.0.5"
 
+figures@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
+  integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
+  dependencies:
+    escape-string-regexp "^1.0.5"
+
 file-entry-cache@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361"
@@ -8094,13 +8156,6 @@ imurmurhash@^0.1.4:
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
   integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
 
-indent-string@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
-  integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
-  dependencies:
-    repeating "^2.0.0"
-
 indent-string@^3.0.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
@@ -8449,6 +8504,11 @@ is-fullwidth-code-point@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
   integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
 
+is-fullwidth-code-point@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+  integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
 is-function@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08"
@@ -8970,11 +9030,6 @@ jest-environment-node@^22.4.1:
     jest-mock "^22.4.3"
     jest-util "^22.4.3"
 
-jest-get-type@^21.2.0:
-  version "21.2.0"
-  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.2.0.tgz#f6376ab9db4b60d81e39f30749c6c466f40d4a23"
-  integrity sha512-y2fFw3C+D0yjNSDp7ab1kcd6NUYfy3waPTlD8yWkAtiocJdBRQqNoRqVfMNxgj+IjT0V5cBIHJO0z9vuSSZ43Q==
-
 jest-get-type@^22.1.0, jest-get-type@^22.4.3:
   version "22.4.3"
   resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4"
@@ -9135,16 +9190,6 @@ jest-util@^22.4.1, jest-util@^22.4.3:
     mkdirp "^0.5.1"
     source-map "^0.6.0"
 
-jest-validate@^21.1.0:
-  version "21.2.1"
-  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-21.2.1.tgz#cc0cbca653cd54937ba4f2a111796774530dd3c7"
-  integrity sha512-k4HLI1rZQjlU+EC682RlQ6oZvLrE5SCh3brseQc24vbZTxzT/k/3urar5QMCVgjadmSO7lECeGdc6YxnM3yEGg==
-  dependencies:
-    chalk "^2.0.1"
-    jest-get-type "^21.2.0"
-    leven "^2.1.0"
-    pretty-format "^21.2.1"
-
 jest-validate@^22.4.4:
   version "22.4.4"
   resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-22.4.4.tgz#1dd0b616ef46c995de61810d85f57119dbbcec4d"
@@ -9209,7 +9254,7 @@ js-tokens@^3.0.2:
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
   integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
 
-js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.9.0, js-yaml@^3.9.1:
+js-yaml@^3.7.0, js-yaml@^3.9.0, js-yaml@^3.9.1:
   version "3.14.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
   integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
@@ -9656,46 +9701,32 @@ lines-and-columns@^1.1.6:
   resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
   integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
 
-lint-staged@^4.1.3:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-4.3.0.tgz#ed0779ad9a42c0dc62bb3244e522870b41125879"
-  integrity sha512-C/Zxslg0VRbsxwmCu977iIs+QyrmW2cyRCPUV5NDFYOH/jtRFHH8ch7ua2fH0voI/nVC3Tpg7DykfgMZySliKw==
+lint-staged@^10.2.11:
+  version "10.2.11"
+  resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.11.tgz#713c80877f2dc8b609b05bc59020234e766c9720"
+  integrity sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA==
   dependencies:
-    app-root-path "^2.0.0"
-    chalk "^2.1.0"
-    commander "^2.11.0"
-    cosmiconfig "^1.1.0"
-    execa "^0.8.0"
-    is-glob "^4.0.0"
-    jest-validate "^21.1.0"
-    listr "^0.12.0"
-    lodash "^4.17.4"
-    log-symbols "^2.0.0"
-    minimatch "^3.0.0"
-    npm-which "^3.0.1"
-    p-map "^1.1.1"
-    staged-git-files "0.0.4"
-    stringify-object "^3.2.0"
+    chalk "^4.0.0"
+    cli-truncate "2.1.0"
+    commander "^5.1.0"
+    cosmiconfig "^6.0.0"
+    debug "^4.1.1"
+    dedent "^0.7.0"
+    enquirer "^2.3.5"
+    execa "^4.0.1"
+    listr2 "^2.1.0"
+    log-symbols "^4.0.0"
+    micromatch "^4.0.2"
+    normalize-path "^3.0.0"
+    please-upgrade-node "^3.2.0"
+    string-argv "0.3.1"
+    stringify-object "^3.3.0"
 
 listr-silent-renderer@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
   integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=
 
-listr-update-renderer@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz#ca80e1779b4e70266807e8eed1ad6abe398550f9"
-  integrity sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=
-  dependencies:
-    chalk "^1.1.3"
-    cli-truncate "^0.2.1"
-    elegant-spinner "^1.0.1"
-    figures "^1.7.0"
-    indent-string "^3.0.0"
-    log-symbols "^1.0.2"
-    log-update "^1.0.2"
-    strip-ansi "^3.0.1"
-
 listr-update-renderer@^0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz#4ea8368548a7b8aecb7e06d8c95cb45ae2ede6a2"
@@ -9710,16 +9741,6 @@ listr-update-renderer@^0.5.0:
     log-update "^2.3.0"
     strip-ansi "^3.0.1"
 
-listr-verbose-renderer@^0.4.0:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35"
-  integrity sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=
-  dependencies:
-    chalk "^1.1.3"
-    cli-cursor "^1.0.2"
-    date-fns "^1.27.2"
-    figures "^1.7.0"
-
 listr-verbose-renderer@^0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz#f1132167535ea4c1261102b9f28dac7cba1e03db"
@@ -9730,6 +9751,20 @@ listr-verbose-renderer@^0.5.0:
     date-fns "^1.27.2"
     figures "^2.0.0"
 
+listr2@^2.1.0:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.3.3.tgz#ee9f4d95dd325ad1ae89fe7327f2a4f4498224b5"
+  integrity sha512-vU2eiFEzUEaDjgwRJ/n8RB79K2cBcaTw1DigIGHnXGp/BEeQqxGwiM8R17Itit5l2ykrrST11kw2l9vSpCbqUQ==
+  dependencies:
+    chalk "^4.0.0"
+    cli-truncate "^2.1.0"
+    figures "^3.2.0"
+    indent-string "^4.0.0"
+    log-update "^4.0.0"
+    p-map "^4.0.0"
+    rxjs "^6.5.5"
+    through "^2.3.8"
+
 listr@0.14.3:
   version "0.14.3"
   resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586"
@@ -9745,28 +9780,6 @@ listr@0.14.3:
     p-map "^2.0.0"
     rxjs "^6.3.3"
 
-listr@^0.12.0:
-  version "0.12.0"
-  resolved "https://registry.yarnpkg.com/listr/-/listr-0.12.0.tgz#6bce2c0f5603fa49580ea17cd6a00cc0e5fa451a"
-  integrity sha1-a84sD1YD+klYDqF81qAMwOX6RRo=
-  dependencies:
-    chalk "^1.1.3"
-    cli-truncate "^0.2.1"
-    figures "^1.7.0"
-    indent-string "^2.1.0"
-    is-promise "^2.1.0"
-    is-stream "^1.1.0"
-    listr-silent-renderer "^1.1.1"
-    listr-update-renderer "^0.2.0"
-    listr-verbose-renderer "^0.4.0"
-    log-symbols "^1.0.2"
-    log-update "^1.0.2"
-    ora "^0.2.3"
-    p-map "^1.1.1"
-    rxjs "^5.0.0-beta.11"
-    stream-to-observable "^0.1.0"
-    strip-ansi "^3.0.1"
-
 load-bmfont@^1.3.1, load-bmfont@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.0.tgz#75f17070b14a8c785fe7f5bee2e6fd4f98093b6b"
@@ -9997,13 +10010,12 @@ log-symbols@^2.0.0:
   dependencies:
     chalk "^2.0.1"
 
-log-update@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1"
-  integrity sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=
+log-symbols@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
+  integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
   dependencies:
-    ansi-escapes "^1.0.0"
-    cli-cursor "^1.0.2"
+    chalk "^4.0.0"
 
 log-update@^2.3.0:
   version "2.3.0"
@@ -10014,6 +10026,16 @@ log-update@^2.3.0:
     cli-cursor "^2.0.0"
     wrap-ansi "^3.0.1"
 
+log-update@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
+  integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==
+  dependencies:
+    ansi-escapes "^4.3.0"
+    cli-cursor "^3.1.0"
+    slice-ansi "^4.0.0"
+    wrap-ansi "^6.2.0"
+
 loglevel@^1.6.7, loglevel@^1.6.8:
   version "1.6.8"
   resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171"
@@ -10309,6 +10331,14 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4:
     snapdragon "^0.8.1"
     to-regex "^3.0.2"
 
+micromatch@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
+  integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
+  dependencies:
+    braces "^3.0.1"
+    picomatch "^2.0.5"
+
 miller-rabin@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@@ -10384,7 +10414,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
 
-minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2:
+minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
   integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -10904,13 +10934,6 @@ npm-packlist@^1.1.6:
     npm-bundled "^1.0.1"
     npm-normalize-package-bin "^1.0.1"
 
-npm-path@^2.0.2:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.4.tgz#c641347a5ff9d6a09e4d9bce5580c4f505278e64"
-  integrity sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==
-  dependencies:
-    which "^1.2.10"
-
 npm-run-path@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -10925,15 +10948,6 @@ npm-run-path@^4.0.0:
   dependencies:
     path-key "^3.0.0"
 
-npm-which@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa"
-  integrity sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=
-  dependencies:
-    commander "^2.9.0"
-    npm-path "^2.0.2"
-    which "^1.2.10"
-
 npmlog@^4.0.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
@@ -11223,16 +11237,6 @@ optionator@^0.8.1, optionator@^0.8.2:
     type-check "~0.3.2"
     word-wrap "~1.2.3"
 
-ora@^0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4"
-  integrity sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=
-  dependencies:
-    chalk "^1.1.1"
-    cli-cursor "^1.0.2"
-    cli-spinners "^0.1.2"
-    object-assign "^4.0.1"
-
 orderedmap@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.1.1.tgz#c618e77611b3b21d0fe3edc92586265e0059c789"
@@ -11250,7 +11254,7 @@ os-browserify@^0.3.0:
   resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
   integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=
 
-os-homedir@^1.0.0, os-homedir@^1.0.1:
+os-homedir@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
   integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
@@ -11327,11 +11331,6 @@ p-locate@^4.1.0:
   dependencies:
     p-limit "^2.2.0"
 
-p-map@^1.1.1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
-  integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
-
 p-map@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
@@ -11344,6 +11343,13 @@ p-map@^3.0.0:
   dependencies:
     aggregate-error "^3.0.0"
 
+p-map@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
+  integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
+  dependencies:
+    aggregate-error "^3.0.0"
+
 p-retry@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328"
@@ -11789,7 +11795,7 @@ phin@^2.9.1:
   resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c"
   integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==
 
-picomatch@^2.0.4, picomatch@^2.2.1:
+picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
   integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
@@ -11854,6 +11860,13 @@ pkginfo@0.3.x:
   resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
   integrity sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=
 
+please-upgrade-node@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
+  integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
+  dependencies:
+    semver-compare "^1.0.0"
+
 pluralize@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
@@ -12292,14 +12305,6 @@ pretty-error@^2.0.2:
     renderkid "^2.0.1"
     utila "~0.4"
 
-pretty-format@^21.2.1:
-  version "21.2.1"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-21.2.1.tgz#ae5407f3cf21066cd011aa1ba5fce7b6a2eddb36"
-  integrity sha512-ZdWPGYAnYfcVP8yKA3zFjCn8s4/17TeYH28MXuC8vTp0o21eXjbFGcOAXZEaDaOFJjc3h2qa7HQNHNshhvoh2A==
-  dependencies:
-    ansi-regex "^3.0.0"
-    ansi-styles "^3.2.0"
-
 pretty-format@^22.4.0, pretty-format@^22.4.3:
   version "22.4.3"
   resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.3.tgz#f873d780839a9c02e9664c8a082e9ee79eaac16f"
@@ -13566,11 +13571,6 @@ require-directory@^2.1.1:
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
   integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
 
-require-from-string@^1.1.0:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418"
-  integrity sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=
-
 require-from-string@^2.0.1:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
@@ -13667,6 +13667,14 @@ restore-cursor@^2.0.0:
     onetime "^2.0.0"
     signal-exit "^3.0.2"
 
+restore-cursor@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
+  integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
+  dependencies:
+    onetime "^5.1.0"
+    signal-exit "^3.0.2"
+
 ret@~0.1.10:
   version "0.1.15"
   resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@@ -13746,13 +13754,6 @@ rx-lite@*, rx-lite@^4.0.8:
   resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
   integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=
 
-rxjs@^5.0.0-beta.11:
-  version "5.5.12"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc"
-  integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==
-  dependencies:
-    symbol-observable "1.0.1"
-
 rxjs@^6.3.3:
   version "6.5.5"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
@@ -13882,6 +13883,11 @@ selfsigned@^1.10.7:
   dependencies:
     node-forge "0.9.0"
 
+semver-compare@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
+  integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
+
 "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@@ -14089,6 +14095,24 @@ slice-ansi@1.0.0:
   dependencies:
     is-fullwidth-code-point "^2.0.0"
 
+slice-ansi@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
+  integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==
+  dependencies:
+    ansi-styles "^4.0.0"
+    astral-regex "^2.0.0"
+    is-fullwidth-code-point "^3.0.0"
+
+slice-ansi@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
+  integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
+  dependencies:
+    ansi-styles "^4.0.0"
+    astral-regex "^2.0.0"
+    is-fullwidth-code-point "^3.0.0"
+
 snapdragon-node@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@@ -14332,11 +14356,6 @@ stack-utils@^1.0.1:
   resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
   integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==
 
-staged-git-files@0.0.4:
-  version "0.0.4"
-  resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-0.0.4.tgz#d797e1b551ca7a639dec0237dc6eb4bb9be17d35"
-  integrity sha1-15fhtVHKemOd7AI33G60u5vhfTU=
-
 start-server-and-test@^1.10.6:
   version "1.11.2"
   resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-1.11.2.tgz#9144b7b6f25197148f159f261ae80119afbb17d5"
@@ -14412,11 +14431,6 @@ stream-shift@^1.0.0:
   resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
   integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
 
-stream-to-observable@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe"
-  integrity sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=
-
 streamifier@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/streamifier/-/streamifier-0.1.1.tgz#97e98d8fa4d105d62a2691d1dc07e820db8dfc4f"
@@ -14432,6 +14446,11 @@ strict-uri-encode@^1.0.0:
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
   integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
 
+string-argv@0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
+  integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
+
 string-length@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
@@ -14474,6 +14493,15 @@ string-width@^3.0.0, string-width@^3.1.0:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^5.1.0"
 
+string-width@^4.1.0, string-width@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
+  integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.0"
+
 string.prototype.matchall@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz#48bb510326fb9fdeb6a33ceaa81a6ea04ef7648e"
@@ -14540,7 +14568,7 @@ stringify-entities@^1.0.1:
     is-alphanumerical "^1.0.0"
     is-hexadecimal "^1.0.0"
 
-stringify-object@^3.2.0:
+stringify-object@^3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
   integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==
@@ -14570,6 +14598,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
   dependencies:
     ansi-regex "^4.1.0"
 
+strip-ansi@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
+  integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
+  dependencies:
+    ansi-regex "^5.0.0"
+
 strip-bom@3.0.0, strip-bom@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
@@ -14781,7 +14816,7 @@ supertest@^3.0.0:
     methods "^1.1.2"
     superagent "^3.8.3"
 
-supports-color@7.1.0:
+supports-color@7.1.0, supports-color@^7.1.0:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
   integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
@@ -14832,11 +14867,6 @@ svgo@^0.7.0:
     sax "~1.2.1"
     whet.extend "~0.9.9"
 
-symbol-observable@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
-  integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=
-
 symbol-observable@^1.0.2, symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
@@ -14962,7 +14992,7 @@ through2@^2.0.0:
     readable-stream "~2.3.6"
     xtend "~4.0.1"
 
-through@2, through@^2.3.6, through@~2.3, through@~2.3.1:
+through@2, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
   integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -15204,6 +15234,11 @@ type-check@~0.3.2:
   dependencies:
     prelude-ls "~1.1.2"
 
+type-fest@^0.11.0:
+  version "0.11.0"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
+  integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
+
 type-is@^1.6.16, type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18:
   version "1.6.18"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
@@ -16083,7 +16118,7 @@ which-typed-array@^1.1.2:
     has-symbols "^1.0.1"
     is-typed-array "^1.1.3"
 
-which@^1.2.10, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
+which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
   integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -16188,6 +16223,15 @@ wrap-ansi@^5.1.0:
     string-width "^3.0.0"
     strip-ansi "^5.0.0"
 
+wrap-ansi@^6.2.0:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
+  integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"