diff --git a/Dockerfile b/Dockerfile
index 88b5a40e2e356674b3875599e610feece9c8c5cc..d4c54e7bc25a8d80fbf948e73db3523cd524c7b6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,7 +16,6 @@ RUN [ "rm", "-rf", "/npm-packages-offline-cache"]
 
 WORKDIR ${HOME}/packages/xpub-faraday
 ENV NODE_ENV "production"
-RUN echo $NODE_ENV
 
 RUN [ "npx", "pubsweet", "build"]
 
diff --git a/docker-compose.yml b/docker-compose.yml
index b4aa1dc1be2196cb8b1290417b8af9e915fac345..fd007f79c56accb3f01b7c7d21839d5ffdd12903 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,11 +5,11 @@ services:
     build:
       context: .
       dockerfile: ./Dockerfile-development
-    command: sh -c "cd packages/xpub-faraday && yarn install --frozen-lockfile && ./scripts/wait-for-it.sh postgres:5432 -s -t 40 -- npx pubsweet server"
+    command: sh -c "./scripts/wait-for-it.sh postgres:5432 -s -t 40 -- npx pubsweet server"
     ports:
       - ${PORT:-3000}:3000
     volumes:
-      - ./:/home/xpub
+      - /home/xpub/node_modules
     depends_on:
       - postgres
     environment:
diff --git a/packages/component-email-templating/package.json b/packages/component-email-templating/package.json
index 4fee496276eb29c32a0a3399c91fc3b7cdc195fc..6213511389e1790492814e3306657251f4dc4209 100644
--- a/packages/component-email-templating/package.json
+++ b/packages/component-email-templating/package.json
@@ -15,11 +15,6 @@
   "peerDependencies": {
     "@pubsweet/logger": "^0.0.1"
   },
-  "repository": {
-    "type": "git",
-    "url": "https://gitlab.coko.foundation/pubsweet/pubsweet",
-    "path": "SendEmail"
-  },
   "jest": {
     "verbose": true,
     "testRegex": "/tests/.*.test.js$"
diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js
index c948890e60bff98dd99aeeeb1c9c7ee062e0b62c..d2f185a9fbddc740533870307d2d9b5653e9b59b 100644
--- a/packages/component-faraday-selectors/src/index.js
+++ b/packages/component-faraday-selectors/src/index.js
@@ -42,10 +42,10 @@ export const canInviteReviewers = (state, collection = {}) => {
     return false
 
   const { id: userId } = selectCurrentUser(state)
-  const isAdmin = currentUserIs(state, 'isAdmin')
+  const isAdminEiC = currentUserIs(state, 'adminEiC')
   const { isAccepted, id: heId } = get(collection, 'handlingEditor', {})
 
-  return isAccepted && (userId === heId || isAdmin)
+  return isAccepted && (userId === heId || isAdminEiC)
 }
 
 const cannotViewReviewersDetails = [
@@ -71,6 +71,7 @@ const authorCanViewReportsDetailsStatuses = [
   'pendingApproval',
   'rejected',
   'accepted',
+  'reviewCompleted',
   'inQa',
 ]
 
@@ -198,7 +199,12 @@ export const getHERecommendation = (state, collectionId, fragmentId) => {
   )
 }
 
-const canMakeDecisionStatuses = ['submitted', 'pendingApproval']
+const canMakeDecisionStatuses = [
+  'submitted',
+  'pendingApproval',
+  'underReview',
+  'reviewCompleted',
+]
 export const canMakeDecision = (state, collection = {}) => {
   const status = get(collection, 'status', 'draft')
 
diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js
index f0e5d8a3fd2392f511108c906a7861d73a73cec8..dc5314f613c215bb949c5735f25c395f83e8b5f6 100644
--- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js
+++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js
@@ -63,7 +63,7 @@ const ManuscriptEicDecision = ({
               <Menu
                 {...input}
                 options={
-                  get(collection, 'status', 'submitted') === 'submitted'
+                  get(collection, 'status', 'submitted') !== 'pendingApproval'
                     ? [last(eicDecisions)]
                     : eicDecisions
                 }
diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js
index fd01d44a1b4009c558fb8900d8991e9bc47f7b04..6d08c78bb1b32a5c536ab1b63b3a1807ab241799 100644
--- a/packages/component-manuscript/src/components/ManuscriptLayout.js
+++ b/packages/component-manuscript/src/components/ManuscriptLayout.js
@@ -146,19 +146,20 @@ const ManuscriptLayout = ({
           />
         )}
 
-        {get(currentUser, 'permissions.canReview', false) && (
-          <ReviewerReportForm
-            changeForm={changeForm}
-            expanded={reviewerRecommendationExpanded}
-            formValues={get(formValues, 'reviewerReport', {})}
-            modalKey="reviewer-report"
-            project={collection}
-            review={pendingOwnRecommendation}
-            toggle={toggleReviewerRecommendations}
-            token={get(currentUser, 'token')}
-            version={fragment}
-          />
-        )}
+        {isLatestVersion &&
+          get(currentUser, 'permissions.canReview', false) && (
+            <ReviewerReportForm
+              changeForm={changeForm}
+              expanded={reviewerRecommendationExpanded}
+              formValues={get(formValues, 'reviewerReport', {})}
+              modalKey="reviewer-report"
+              project={collection}
+              review={pendingOwnRecommendation}
+              toggle={toggleReviewerRecommendations}
+              token={get(currentUser, 'token')}
+              version={fragment}
+            />
+          )}
 
         {get(currentUser, 'isInvitedHE', false) && (
           <ResponseToInvitation
@@ -200,6 +201,7 @@ const ManuscriptLayout = ({
             getSignedUrl={getSignedUrl}
             highlight={
               reviewerReports.length === 0 &&
+              currentUser.handlingEditor &&
               !cannotViewReviewersDetails.includes(
                 get(collection, 'status', 'draft'),
               )
diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js
index 84d6059a9c3187ed2c5c5c648a1f4fe33af233a4..685daeab9d5d245ca9caa4f966bebe64aab6adf7 100644
--- a/packages/component-manuscript/src/components/ManuscriptPage.js
+++ b/packages/component-manuscript/src/components/ManuscriptPage.js
@@ -377,7 +377,10 @@ export default compose(
         this.props.toggleEditorialComments()
       }
 
-      if (get(fragment, 'responseToReviewers.content', false)) {
+      if (
+        get(fragment, 'responseToReviewers.content', false) &&
+        !editorialRecommendations.length
+      ) {
         this.props.toggleResponseToRevisionRequest()
       }
     },
diff --git a/packages/pubsweet-component-login/CHANGELOG.md b/packages/pubsweet-component-login/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..053b9a9ceb89f38e0ccc02a60386a493de816d58
--- /dev/null
+++ b/packages/pubsweet-component-login/CHANGELOG.md
@@ -0,0 +1,204 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+<a name="1.2.0"></a>
+# [1.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.18...pubsweet-component-login@1.2.0) (2018-11-05)
+
+
+### Features
+
+* GraphQL Login component ([70df3de](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/70df3de))
+* GraphQL Xpub submit component ([ba07060](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ba07060))
+
+
+
+
+<a name="1.1.18"></a>
+## [1.1.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.17...pubsweet-component-login@1.1.18) (2018-10-08)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.17"></a>
+## [1.1.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.16...pubsweet-component-login@1.1.17) (2018-09-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.16"></a>
+## [1.1.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.15...pubsweet-component-login@1.1.16) (2018-09-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.15"></a>
+## [1.1.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.14...pubsweet-component-login@1.1.15) (2018-09-06)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.14"></a>
+## [1.1.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.13...pubsweet-component-login@1.1.14) (2018-09-04)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.13"></a>
+## [1.1.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.12...pubsweet-component-login@1.1.13) (2018-08-20)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.12"></a>
+## [1.1.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.11...pubsweet-component-login@1.1.12) (2018-08-17)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.11"></a>
+## [1.1.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.10...pubsweet-component-login@1.1.11) (2018-08-02)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.10"></a>
+## [1.1.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.9...pubsweet-component-login@1.1.10) (2018-07-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.9"></a>
+## [1.1.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.8...pubsweet-component-login@1.1.9) (2018-07-12)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.8"></a>
+## [1.1.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.7...pubsweet-component-login@1.1.8) (2018-07-09)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.7"></a>
+## [1.1.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.6...pubsweet-component-login@1.1.7) (2018-07-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.6"></a>
+## [1.1.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.5...pubsweet-component-login@1.1.6) (2018-07-02)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.5"></a>
+## [1.1.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.4...pubsweet-component-login@1.1.5) (2018-06-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.4"></a>
+## [1.1.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.3...pubsweet-component-login@1.1.4) (2018-06-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.3"></a>
+## [1.1.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.2...pubsweet-component-login@1.1.3) (2018-06-19)
+
+
+### Bug Fixes
+
+* **pubsweet-ui:** tests are failing ([0e57798](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0e57798))
+
+
+
+
+<a name="1.1.2"></a>
+## [1.1.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.1...pubsweet-component-login@1.1.2) (2018-04-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-login
+
+<a name="1.1.1"></a>
+## [1.1.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.0...pubsweet-component-login@1.1.1) (2018-03-15)
+
+
+### Bug Fixes
+
+* **login:** add missing recompose dependency ([a3b5a80](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a3b5a80)), closes [#353](https://gitlab.coko.foundation/pubsweet/pubsweet/issues/353)
+
+
+
+
+<a name="1.1.0"></a>
+# [1.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.0.1...pubsweet-component-login@1.1.0) (2018-03-05)
+
+
+### Bug Fixes
+
+* **components:** login example ([6dfd66c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/6dfd66c))
+* **components:** login tests were failing after refactor ([62be047](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/62be047))
+* **components:** signup and login error examples ([3f991ec](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3f991ec))
+
+
+### Features
+
+* **elife-theme:** add elife theme ([e406e0d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e406e0d))
+
+
+
+
+<a name="1.0.1"></a>
+
+## [1.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.0.0...pubsweet-component-login@1.0.1) (2018-02-08)
+
+### Bug Fixes
+
+* **components:** update react-router-redux version to match client ([3d257ef](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3d257ef))
+
+<a name="1.0.0"></a>
+
+# [1.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@0.6.0...pubsweet-component-login@1.0.0) (2018-02-02)
+
+### Features
+
+* **client:** upgrade React to version 16 ([626cf59](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/626cf59)), closes [#65](https://gitlab.coko.foundation/pubsweet/pubsweet/issues/65)
+
+### BREAKING CHANGES
+
+* **client:** Upgrade React to version 16
diff --git a/packages/pubsweet-component-login/Login.jsx b/packages/pubsweet-component-login/Login.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6cc5a2d8597fa53e053e4c5615f2502093e17280
--- /dev/null
+++ b/packages/pubsweet-component-login/Login.jsx
@@ -0,0 +1,70 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { Field } from 'formik'
+import { isEmpty } from 'lodash'
+import {
+  CenteredColumn,
+  ErrorText,
+  H1,
+  Link,
+  Button,
+  TextField,
+} from '@pubsweet/ui'
+import styled from 'styled-components'
+
+// These enable tests to select components
+const Signup = styled.div``
+const ResetPassword = styled.div``
+
+const UsernameInput = props => <TextField label="Username" {...props.field} />
+const PasswordInput = props => (
+  <TextField label="Password" {...props.field} type="password" />
+)
+
+const Login = ({
+  errors,
+  handleSubmit,
+  signup = true,
+  passwordReset = true,
+}) => (
+  <CenteredColumn small>
+    <H1>Login</H1>
+
+    {!isEmpty(errors) && <ErrorText>{errors}</ErrorText>}
+    <form onSubmit={handleSubmit}>
+      <Field component={UsernameInput} name="username" />
+      <Field component={PasswordInput} name="password" />
+      <Button primary type="submit">
+        Login
+      </Button>
+    </form>
+
+    {signup && (
+      <Signup>
+        <span>Don&apos;t have an account? </span>
+        <Link to="/signup">Sign up</Link>
+      </Signup>
+    )}
+
+    {passwordReset && (
+      <ResetPassword>
+        <span>Forgot your password? </span>
+        <Link to="/password-reset">Reset password</Link>
+      </ResetPassword>
+    )}
+  </CenteredColumn>
+)
+
+Login.propTypes = {
+  error: PropTypes.string,
+  actions: PropTypes.object,
+  location: PropTypes.object,
+  signup: PropTypes.bool,
+  passwordReset: PropTypes.bool,
+}
+
+// used by tests
+export { Login, ErrorText, Signup, ResetPassword }
+
+// used by consumers
+export default Login
diff --git a/packages/pubsweet-component-login/Login.md b/packages/pubsweet-component-login/Login.md
new file mode 100644
index 0000000000000000000000000000000000000000..8efa765dd57c83028d3d4e6d9885797bac8245b7
--- /dev/null
+++ b/packages/pubsweet-component-login/Login.md
@@ -0,0 +1,40 @@
+A login form
+
+```js
+const { withFormik } = require('formik')
+
+const LoginForm = withFormik({
+  initialValues: {
+    username: '',
+    password: '',
+  },
+  mapPropsToValues: props => ({
+    username: props.username,
+    password: props.password,
+  }),
+  displayName: 'login',
+  handleSubmit: val => console.log(val),
+})(Login)
+;<LoginForm />
+```
+
+Which can have an error message:
+
+```js
+const { withFormik } = require('formik')
+
+const LoginForm = withFormik({
+  initialValues: {
+    username: '',
+    password: '',
+  },
+  mapPropsToValues: props => ({
+    username: props.username,
+    password: props.password,
+  }),
+  displayName: 'login',
+  handleSubmit: (values, { setErrors }) =>
+    setErrors('Wrong username or password.'),
+})(Login)
+;<LoginForm />
+```
diff --git a/packages/pubsweet-component-login/Login.test.jsx b/packages/pubsweet-component-login/Login.test.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f696b5eaa420beee8fb6fdf3311461b1ed28f97a
--- /dev/null
+++ b/packages/pubsweet-component-login/Login.test.jsx
@@ -0,0 +1,38 @@
+import { shallow } from 'enzyme'
+import React from 'react'
+
+import { Login, ErrorText, Signup, ResetPassword } from './Login'
+
+describe('<Login/>', () => {
+  const makeWrapper = (props = {}) => shallow(<Login {...props} />)
+
+  it('renders the login form', () => {
+    expect(makeWrapper()).toMatchSnapshot()
+  })
+
+  it('shows error', () => {
+    const wrapper = makeWrapper({ errors: 'Yikes!' })
+    expect(wrapper.find(ErrorText)).toHaveLength(1)
+  })
+
+  it('can hide sign up link', () => {
+    const wrapper1 = makeWrapper()
+    const wrapper2 = makeWrapper({ signup: false })
+    expect(wrapper1.find(Signup)).toHaveLength(1)
+    expect(wrapper2.find(Signup)).toHaveLength(0)
+  })
+
+  it('can hide password reset link', () => {
+    const wrapper1 = makeWrapper()
+    const wrapper2 = makeWrapper({ passwordReset: false })
+    expect(wrapper1.find(ResetPassword)).toHaveLength(1)
+    expect(wrapper2.find(ResetPassword)).toHaveLength(0)
+  })
+
+  it('triggers submit handler', () => {
+    const handleSubmit = jest.fn()
+    const wrapper = makeWrapper({ handleSubmit })
+    wrapper.find('form').simulate('submit')
+    expect(handleSubmit).toHaveBeenCalled()
+  })
+})
diff --git a/packages/pubsweet-component-login/LoginContainer.js b/packages/pubsweet-component-login/LoginContainer.js
new file mode 100644
index 0000000000000000000000000000000000000000..250dd2d75b9adeb1686805b4a9d8ecd7d8b4291b
--- /dev/null
+++ b/packages/pubsweet-component-login/LoginContainer.js
@@ -0,0 +1,26 @@
+import { withFormik } from 'formik'
+import { compose } from 'recompose'
+import { connect } from 'react-redux'
+import { loginUser } from './actions'
+
+import Login from './Login'
+import redirectPath from './redirect'
+
+const handleSubmit = (values, { props: { dispatch, location }, setErrors }) => {
+  dispatch(loginUser(values, redirectPath({ location }), setErrors))
+}
+
+const enhancedFormik = withFormik({
+  initialValues: {
+    username: '',
+    password: '',
+  },
+  mapPropsToValues: props => ({
+    username: props.username,
+    password: props.password,
+  }),
+  displayName: 'login',
+  handleSubmit,
+})(Login)
+
+export default compose(connect(state => state))(enhancedFormik)
diff --git a/packages/pubsweet-component-login/__snapshots__/Login.test.jsx.snap b/packages/pubsweet-component-login/__snapshots__/Login.test.jsx.snap
new file mode 100644
index 0000000000000000000000000000000000000000..c37d2096241bcb88782ac550b9bd8c8f121e1e52
--- /dev/null
+++ b/packages/pubsweet-component-login/__snapshots__/Login.test.jsx.snap
@@ -0,0 +1,473 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<Login/> renders the login form 1`] = `
+ShallowWrapper {
+  Symbol(enzyme.__root__): [Circular],
+  Symbol(enzyme.__unrendered__): <Login />,
+  Symbol(enzyme.__renderer__): Object {
+    "batchedUpdates": [Function],
+    "getNode": [Function],
+    "render": [Function],
+    "simulateError": [Function],
+    "simulateEvent": [Function],
+    "unmount": [Function],
+  },
+  Symbol(enzyme.__node__): Object {
+    "instance": null,
+    "key": undefined,
+    "nodeType": "class",
+    "props": Object {
+      "children": Array [
+        <styled.h1>
+          Login
+        </styled.h1>,
+        false,
+        <form>
+          <C
+            component={[Function]}
+            name="username"
+          />
+          <C
+            component={[Function]}
+            name="password"
+          />
+          <styled.button
+            primary={true}
+            type="submit"
+          >
+            Login
+          </styled.button>
+        </form>,
+        <styled.div>
+          <span>
+            Don't have an account? 
+          </span>
+          <Styled(Link)
+            to="/signup"
+          >
+            Sign up
+          </Styled(Link)>
+        </styled.div>,
+        <styled.div>
+          <span>
+            Forgot your password? 
+          </span>
+          <Styled(Link)
+            to="/password-reset"
+          >
+            Reset password
+          </Styled(Link)>
+        </styled.div>,
+      ],
+      "small": true,
+    },
+    "ref": null,
+    "rendered": Array [
+      Object {
+        "instance": null,
+        "key": undefined,
+        "nodeType": "class",
+        "props": Object {
+          "children": "Login",
+        },
+        "ref": null,
+        "rendered": "Login",
+        "type": [Function],
+      },
+      false,
+      Object {
+        "instance": null,
+        "key": undefined,
+        "nodeType": "host",
+        "props": Object {
+          "children": Array [
+            <C
+              component={[Function]}
+              name="username"
+            />,
+            <C
+              component={[Function]}
+              name="password"
+            />,
+            <styled.button
+              primary={true}
+              type="submit"
+            >
+              Login
+            </styled.button>,
+          ],
+          "onSubmit": undefined,
+        },
+        "ref": null,
+        "rendered": Array [
+          Object {
+            "instance": null,
+            "key": undefined,
+            "nodeType": "function",
+            "props": Object {
+              "component": [Function],
+              "name": "username",
+            },
+            "ref": null,
+            "rendered": null,
+            "type": [Function],
+          },
+          Object {
+            "instance": null,
+            "key": undefined,
+            "nodeType": "function",
+            "props": Object {
+              "component": [Function],
+              "name": "password",
+            },
+            "ref": null,
+            "rendered": null,
+            "type": [Function],
+          },
+          Object {
+            "instance": null,
+            "key": undefined,
+            "nodeType": "class",
+            "props": Object {
+              "children": "Login",
+              "primary": true,
+              "type": "submit",
+            },
+            "ref": null,
+            "rendered": "Login",
+            "type": [Function],
+          },
+        ],
+        "type": "form",
+      },
+      Object {
+        "instance": null,
+        "key": undefined,
+        "nodeType": "class",
+        "props": Object {
+          "children": Array [
+            <span>
+              Don't have an account? 
+            </span>,
+            <Styled(Link)
+              to="/signup"
+            >
+              Sign up
+            </Styled(Link)>,
+          ],
+        },
+        "ref": null,
+        "rendered": Array [
+          Object {
+            "instance": null,
+            "key": undefined,
+            "nodeType": "host",
+            "props": Object {
+              "children": "Don't have an account? ",
+            },
+            "ref": null,
+            "rendered": "Don't have an account? ",
+            "type": "span",
+          },
+          Object {
+            "instance": null,
+            "key": undefined,
+            "nodeType": "class",
+            "props": Object {
+              "children": "Sign up",
+              "to": "/signup",
+            },
+            "ref": null,
+            "rendered": "Sign up",
+            "type": [Function],
+          },
+        ],
+        "type": [Function],
+      },
+      Object {
+        "instance": null,
+        "key": undefined,
+        "nodeType": "class",
+        "props": Object {
+          "children": Array [
+            <span>
+              Forgot your password? 
+            </span>,
+            <Styled(Link)
+              to="/password-reset"
+            >
+              Reset password
+            </Styled(Link)>,
+          ],
+        },
+        "ref": null,
+        "rendered": Array [
+          Object {
+            "instance": null,
+            "key": undefined,
+            "nodeType": "host",
+            "props": Object {
+              "children": "Forgot your password? ",
+            },
+            "ref": null,
+            "rendered": "Forgot your password? ",
+            "type": "span",
+          },
+          Object {
+            "instance": null,
+            "key": undefined,
+            "nodeType": "class",
+            "props": Object {
+              "children": "Reset password",
+              "to": "/password-reset",
+            },
+            "ref": null,
+            "rendered": "Reset password",
+            "type": [Function],
+          },
+        ],
+        "type": [Function],
+      },
+    ],
+    "type": [Function],
+  },
+  Symbol(enzyme.__nodes__): Array [
+    Object {
+      "instance": null,
+      "key": undefined,
+      "nodeType": "class",
+      "props": Object {
+        "children": Array [
+          <styled.h1>
+            Login
+          </styled.h1>,
+          false,
+          <form>
+            <C
+              component={[Function]}
+              name="username"
+            />
+            <C
+              component={[Function]}
+              name="password"
+            />
+            <styled.button
+              primary={true}
+              type="submit"
+            >
+              Login
+            </styled.button>
+          </form>,
+          <styled.div>
+            <span>
+              Don't have an account? 
+            </span>
+            <Styled(Link)
+              to="/signup"
+            >
+              Sign up
+            </Styled(Link)>
+          </styled.div>,
+          <styled.div>
+            <span>
+              Forgot your password? 
+            </span>
+            <Styled(Link)
+              to="/password-reset"
+            >
+              Reset password
+            </Styled(Link)>
+          </styled.div>,
+        ],
+        "small": true,
+      },
+      "ref": null,
+      "rendered": Array [
+        Object {
+          "instance": null,
+          "key": undefined,
+          "nodeType": "class",
+          "props": Object {
+            "children": "Login",
+          },
+          "ref": null,
+          "rendered": "Login",
+          "type": [Function],
+        },
+        false,
+        Object {
+          "instance": null,
+          "key": undefined,
+          "nodeType": "host",
+          "props": Object {
+            "children": Array [
+              <C
+                component={[Function]}
+                name="username"
+              />,
+              <C
+                component={[Function]}
+                name="password"
+              />,
+              <styled.button
+                primary={true}
+                type="submit"
+              >
+                Login
+              </styled.button>,
+            ],
+            "onSubmit": undefined,
+          },
+          "ref": null,
+          "rendered": Array [
+            Object {
+              "instance": null,
+              "key": undefined,
+              "nodeType": "function",
+              "props": Object {
+                "component": [Function],
+                "name": "username",
+              },
+              "ref": null,
+              "rendered": null,
+              "type": [Function],
+            },
+            Object {
+              "instance": null,
+              "key": undefined,
+              "nodeType": "function",
+              "props": Object {
+                "component": [Function],
+                "name": "password",
+              },
+              "ref": null,
+              "rendered": null,
+              "type": [Function],
+            },
+            Object {
+              "instance": null,
+              "key": undefined,
+              "nodeType": "class",
+              "props": Object {
+                "children": "Login",
+                "primary": true,
+                "type": "submit",
+              },
+              "ref": null,
+              "rendered": "Login",
+              "type": [Function],
+            },
+          ],
+          "type": "form",
+        },
+        Object {
+          "instance": null,
+          "key": undefined,
+          "nodeType": "class",
+          "props": Object {
+            "children": Array [
+              <span>
+                Don't have an account? 
+              </span>,
+              <Styled(Link)
+                to="/signup"
+              >
+                Sign up
+              </Styled(Link)>,
+            ],
+          },
+          "ref": null,
+          "rendered": Array [
+            Object {
+              "instance": null,
+              "key": undefined,
+              "nodeType": "host",
+              "props": Object {
+                "children": "Don't have an account? ",
+              },
+              "ref": null,
+              "rendered": "Don't have an account? ",
+              "type": "span",
+            },
+            Object {
+              "instance": null,
+              "key": undefined,
+              "nodeType": "class",
+              "props": Object {
+                "children": "Sign up",
+                "to": "/signup",
+              },
+              "ref": null,
+              "rendered": "Sign up",
+              "type": [Function],
+            },
+          ],
+          "type": [Function],
+        },
+        Object {
+          "instance": null,
+          "key": undefined,
+          "nodeType": "class",
+          "props": Object {
+            "children": Array [
+              <span>
+                Forgot your password? 
+              </span>,
+              <Styled(Link)
+                to="/password-reset"
+              >
+                Reset password
+              </Styled(Link)>,
+            ],
+          },
+          "ref": null,
+          "rendered": Array [
+            Object {
+              "instance": null,
+              "key": undefined,
+              "nodeType": "host",
+              "props": Object {
+                "children": "Forgot your password? ",
+              },
+              "ref": null,
+              "rendered": "Forgot your password? ",
+              "type": "span",
+            },
+            Object {
+              "instance": null,
+              "key": undefined,
+              "nodeType": "class",
+              "props": Object {
+                "children": "Reset password",
+                "to": "/password-reset",
+              },
+              "ref": null,
+              "rendered": "Reset password",
+              "type": [Function],
+            },
+          ],
+          "type": [Function],
+        },
+      ],
+      "type": [Function],
+    },
+  ],
+  Symbol(enzyme.__options__): Object {
+    "adapter": ReactSixteenAdapter {
+      "options": Object {
+        "enableComponentDidUpdateOnSetState": true,
+        "lifecycles": Object {
+          "componentDidUpdate": Object {
+            "onSetState": true,
+          },
+          "getDerivedStateFromProps": true,
+          "getSnapshotBeforeUpdate": true,
+          "setState": Object {
+            "skipsComponentDidUpdateOnNullish": true,
+          },
+        },
+      },
+    },
+  },
+}
+`;
diff --git a/packages/pubsweet-component-login/actions.js b/packages/pubsweet-component-login/actions.js
new file mode 100644
index 0000000000000000000000000000000000000000..e74909742d021bb2f66d46674e968b5a3eed2919
--- /dev/null
+++ b/packages/pubsweet-component-login/actions.js
@@ -0,0 +1,84 @@
+import * as api from 'pubsweet-client/src/helpers/api'
+import {
+  LOGIN_REQUEST,
+  LOGIN_SUCCESS,
+  LOGIN_FAILURE,
+  LOGOUT_SUCCESS,
+  LOGOUT_REQUEST,
+} from 'pubsweet-client/src/actions/types'
+
+import { push } from 'react-router-redux'
+
+// TODO: This will break when rendered on a server
+const localStorage = window.localStorage || undefined
+
+// There are three possible states for our login
+// process and we need actions for each of them
+function loginRequest(credentials) {
+  return {
+    type: LOGIN_REQUEST,
+    credentials,
+  }
+}
+
+function loginSuccess(user) {
+  return {
+    type: LOGIN_SUCCESS,
+    token: user.token,
+    user,
+  }
+}
+
+function loginFailure(message) {
+  return {
+    type: LOGIN_FAILURE,
+    error: message,
+  }
+}
+
+// Calls the API to get a token and
+// dispatches actions along the way
+export function loginUser(credentials, redirectTo, setErrors) {
+  return dispatch => {
+    dispatch(loginRequest(credentials))
+    return api.create('/users/authenticate', credentials).then(
+      user => {
+        localStorage.setItem('token', user.token)
+        dispatch(loginSuccess(user))
+        if (redirectTo) dispatch(push(redirectTo))
+      },
+      err => {
+        setErrors(JSON.parse(err.response).message)
+        dispatch(loginFailure(err))
+      },
+    )
+  }
+}
+
+function logoutRequest() {
+  return {
+    type: LOGOUT_REQUEST,
+    isFetching: true,
+    isAuthenticated: true,
+  }
+}
+
+function logoutSuccess() {
+  return {
+    type: LOGOUT_SUCCESS,
+    isFetching: false,
+    isAuthenticated: false,
+  }
+}
+
+// Logs the user out
+// Since we are using JWTs, we just need to remove the token
+// from localStorage.
+export function logoutUser(redirectTo) {
+  return dispatch => {
+    dispatch(logoutRequest())
+    localStorage.removeItem('token')
+    dispatch(logoutSuccess())
+    if (redirectTo) dispatch(push(redirectTo))
+  }
+}
diff --git a/packages/pubsweet-component-login/graphql/LoginContainer.js b/packages/pubsweet-component-login/graphql/LoginContainer.js
new file mode 100644
index 0000000000000000000000000000000000000000..83715d8019cbd8eb24bf086d9b7ebdb733daf80e
--- /dev/null
+++ b/packages/pubsweet-component-login/graphql/LoginContainer.js
@@ -0,0 +1,43 @@
+import { compose } from 'recompose'
+import { withFormik } from 'formik'
+import { graphql } from 'react-apollo'
+
+import mutations from './mutations'
+import Login from '../Login'
+import redirectPath from '../redirect'
+
+const localStorage = window.localStorage || undefined
+
+const handleSubmit = (values, { props, setSubmitting, setErrors }) =>
+  props
+    .loginUser({ variables: { input: values } })
+    .then(({ data, errors }) => {
+      if (!errors) {
+        localStorage.setItem('token', data.loginUser.token)
+        props.history.push(redirectPath({ location: props.location }))
+        setSubmitting(true)
+      }
+    })
+    .catch(e => {
+      if (e.graphQLErrors) {
+        setSubmitting(false)
+        setErrors(e.graphQLErrors[0].message)
+      }
+    })
+
+const enhancedFormik = withFormik({
+  initialValues: {
+    username: '',
+    password: '',
+  },
+  mapPropsToValues: props => ({
+    username: props.username,
+    password: props.password,
+  }),
+  displayName: 'login',
+  handleSubmit,
+})(Login)
+
+export default compose(graphql(mutations.LOGIN_USER, { name: 'loginUser' }))(
+  enhancedFormik,
+)
diff --git a/packages/pubsweet-component-login/graphql/LoginContainer.test.jsx b/packages/pubsweet-component-login/graphql/LoginContainer.test.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..7df5518babca59528ec37fa596338e687f192365
--- /dev/null
+++ b/packages/pubsweet-component-login/graphql/LoginContainer.test.jsx
@@ -0,0 +1,144 @@
+import React from 'react'
+import { mount } from 'enzyme'
+import { MockedProvider } from 'react-apollo/test-utils'
+import { ThemeProvider } from 'styled-components'
+import { MemoryRouter, Route } from 'react-router-dom'
+
+import wait from 'waait'
+import { LOGIN_USER } from './mutations'
+import LoginContainer from './LoginContainer'
+
+const user1 = {
+  id: 'user1',
+  username: 'admin',
+  password: 'adminadmin',
+  admin: true,
+  teams: [],
+}
+
+const mocks = currentUser => [
+  {
+    request: {
+      query: LOGIN_USER,
+      variables: {
+        input: {
+          username: currentUser.username,
+          password: currentUser.password,
+        },
+      },
+    },
+    result: {
+      data: {
+        loginUser: {
+          user: currentUser,
+          token: 'greatToken',
+        },
+      },
+    },
+  },
+  {
+    request: {
+      query: LOGIN_USER,
+      variables: {
+        input: {
+          username: currentUser.username,
+          password: 'wrongPassword',
+        },
+      },
+    },
+    result: {
+      data: { loginUser: null },
+      errors: [{ message: 'Wrong username or password.' }],
+    },
+  },
+]
+
+let globalLocation
+
+function makeDeepWrapper(currentUser, props = {}) {
+  // A theme is needed because some components use colors
+  // specified in the theme to render themselves (e.g. Link)
+  const theme = {
+    colorPrimary: '#fff',
+    colorSecondary: '#fff',
+  }
+
+  return mount(
+    <ThemeProvider theme={theme}>
+      <MockedProvider addTypename={false} mocks={mocks(currentUser)}>
+        <MemoryRouter initialEntries={['/login']}>
+          <Route
+            {...props}
+            render={p => {
+              globalLocation = p.location
+              return <LoginContainer {...p} />
+            }}
+          />
+        </MemoryRouter>
+      </MockedProvider>
+    </ThemeProvider>,
+  )
+}
+
+describe('LoginContainer', () => {
+  beforeEach(() => {
+    window.localStorage.clear()
+    globalLocation = undefined
+  })
+
+  it('renders the login form', () => {
+    const wrapper = makeDeepWrapper(user1)
+    wrapper.update()
+
+    const fields = wrapper.find('Login')
+    expect(fields).toHaveLength(1)
+  })
+
+  it('submits login information and logs the user in', async () => {
+    const wrapper = makeDeepWrapper(user1)
+    wrapper.update()
+
+    const usernameField = wrapper.find('TextField[label="Username"] input')
+    usernameField.getDOMNode().value = user1.username
+    usernameField.simulate('change')
+    const passwordField = wrapper.find('TextField[label="Password"] input')
+    passwordField.getDOMNode().value = user1.password
+    passwordField.simulate('change')
+
+    wrapper.update()
+    const button = wrapper.find('button')
+    button.simulate('submit')
+
+    wrapper.update()
+    await wait(50)
+
+    expect(window.localStorage.token).toEqual('greatToken')
+    expect(globalLocation.pathname).toEqual('/testRedirect')
+  })
+
+  it('does not log in user with incorrect credentials', async () => {
+    const wrapper = makeDeepWrapper(user1)
+    wrapper.update()
+
+    const usernameField = wrapper.find('TextField[label="Username"] input')
+    usernameField.getDOMNode().value = user1.username
+    usernameField.simulate('change')
+    const passwordField = wrapper.find('TextField[label="Password"] input')
+    passwordField.getDOMNode().value = 'wrongPassword'
+    passwordField.simulate('change')
+
+    wrapper.update()
+    const button = wrapper.find('button')
+
+    button.simulate('submit')
+
+    wrapper.update()
+    await wait(50)
+
+    expect(wrapper.find('Login').text()).toContain(
+      'Wrong username or password.',
+    )
+    expect(window.localStorage.token).toEqual(undefined)
+    expect(globalLocation.pathname).toEqual('/login')
+  })
+})
diff --git a/packages/pubsweet-component-login/graphql/mutations/index.js b/packages/pubsweet-component-login/graphql/mutations/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee44b493da283da233533820994539bc15529084
--- /dev/null
+++ b/packages/pubsweet-component-login/graphql/mutations/index.js
@@ -0,0 +1,13 @@
+import gql from 'graphql-tag'
+
+const LOGIN_USER = gql`
+  mutation($input: LoginUserInput) {
+    loginUser(input: $input) {
+      token
+    }
+  }
+`
+
+module.exports = {
+  LOGIN_USER,
+}
diff --git a/packages/pubsweet-component-login/index.js b/packages/pubsweet-component-login/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..155554e77ad3dfe5c27ec6c2d4b5b623dcb3d135
--- /dev/null
+++ b/packages/pubsweet-component-login/index.js
@@ -0,0 +1,7 @@
+module.exports = {
+  frontend: {
+    components: [() => require('./LoginContainer')],
+    actions: () => require('./actions'),
+    reducers: () => require('./reducers'),
+  },
+}
diff --git a/packages/pubsweet-component-login/package.json b/packages/pubsweet-component-login/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..66896d9c43c7e256d5522d2e692e2d92e58a2654
--- /dev/null
+++ b/packages/pubsweet-component-login/package.json
@@ -0,0 +1,26 @@
+{
+  "name": "pubsweet-component-login",
+  "version": "1.2.0",
+  "description": "Basic login component for PubSweet",
+  "main": "index.js",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "dependencies": {
+    "@pubsweet/ui": "^8.8.0",
+    "formik": "1.3.0",
+    "prop-types": "^15.5.10",
+    "react-redux": "^5.0.6",
+    "react-router-dom": "^4.2.2",
+    "react-router-redux": "^5.0.0-alpha.9",
+    "recompose": "^0.26.0"
+  },
+  "peerDependencies": {
+    "pubsweet-client": ">=1.0.0",
+    "react": ">=15"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://gitlab.coko.foundation/pubsweet/pubsweet",
+    "path": "Login"
+  }
+}
diff --git a/packages/pubsweet-component-login/redirect.js b/packages/pubsweet-component-login/redirect.js
new file mode 100644
index 0000000000000000000000000000000000000000..53d58f1e3e4d1fe5a5d53584048d7a389c340887
--- /dev/null
+++ b/packages/pubsweet-component-login/redirect.js
@@ -0,0 +1,10 @@
+import { get } from 'lodash'
+import config from 'config'
+
+const allowedRedirect = pathname =>
+  !['/logout', '/login', '/signup'].includes(pathname)
+
+export default ({ location: { state } }) =>
+  state && state.from && allowedRedirect(state.from.pathname)
+    ? state.from.pathname
+    : get(config, 'pubsweet-client.login-redirect', '/')
diff --git a/packages/pubsweet-component-login/reducers.js b/packages/pubsweet-component-login/reducers.js
new file mode 100644
index 0000000000000000000000000000000000000000..d1f637a01e310491d842ac045d78ac1a0a879527
--- /dev/null
+++ b/packages/pubsweet-component-login/reducers.js
@@ -0,0 +1,58 @@
+import {
+  LOGIN_REQUEST,
+  LOGIN_SUCCESS,
+  LOGIN_FAILURE,
+  LOGOUT_SUCCESS,
+  LOGOUT_REQUEST,
+} from 'pubsweet-client/src/actions/types'
+
+// TODO: This will break when rendered on a server
+const localStorage = window.localStorage || undefined
+
+export default function userLogin(
+  state = {
+    isFetching: false,
+    isAuthenticated: false,
+    token: localStorage.getItem('token'),
+  },
+  action,
+) {
+  switch (action.type) {
+    case LOGIN_REQUEST:
+      return {
+        ...state,
+        isFetching: true,
+        isAuthenticated: false,
+        username: action.credentials.username,
+      }
+    case LOGIN_SUCCESS:
+      return {
+        ...state,
+        isFetching: false,
+        isAuthenticated: true,
+        user: action.user,
+        token: action.token,
+      }
+    case LOGIN_FAILURE:
+      return {
+        ...state,
+        isFetching: false,
+        isAuthenticated: false,
+        error: action.error,
+      }
+    case LOGOUT_SUCCESS:
+      return {
+        ...state,
+        isFetching: false,
+        isAuthenticated: false,
+      }
+    case LOGOUT_REQUEST:
+      return {
+        ...state,
+        isFetching: false,
+        isAuthenticated: false,
+      }
+    default:
+      return state
+  }
+}
diff --git a/packages/pubsweet-component-login/reducers.test.js b/packages/pubsweet-component-login/reducers.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..a3733cf35bff6ae0ac033e66fdec726ce557d509
--- /dev/null
+++ b/packages/pubsweet-component-login/reducers.test.js
@@ -0,0 +1,73 @@
+import {
+  LOGIN_FAILURE,
+  LOGIN_REQUEST,
+  LOGIN_SUCCESS,
+  LOGOUT_REQUEST,
+  LOGOUT_SUCCESS,
+} from 'pubsweet-client/src/actions/types'
+
+jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => undefined)
+
+const reducer = require('./reducers').default
+
+describe('Login reducer', () => {
+  it('returns initial state', () => {
+    const newState = reducer(undefined, {})
+    expect(newState).toEqual({
+      isFetching: false,
+      isAuthenticated: false,
+      token: undefined,
+    })
+  })
+
+  it('stores username on login request', () => {
+    const action = {
+      type: LOGIN_REQUEST,
+      credentials: { username: 'milo minderbinder' },
+    }
+    const newState = reducer(undefined, action)
+    expect(newState).toMatchObject({
+      isFetching: true,
+      username: 'milo minderbinder',
+    })
+  })
+
+  it('stores user and token on login success', () => {
+    const action = {
+      type: LOGIN_SUCCESS,
+      user: { username: 'nurse duckett' },
+      token: 't0k3n',
+    }
+    const newState = reducer(undefined, action)
+    expect(newState).toMatchObject({
+      isAuthenticated: true,
+      user: action.user,
+      token: action.token,
+    })
+  })
+
+  it('stores error on login failure', () => {
+    const action = { type: LOGIN_FAILURE, error: new Error('Flies in eyes') }
+    const newState = reducer({ isAuthenticated: true }, action)
+    expect(newState).toMatchObject({
+      isAuthenticated: false,
+      error: action.error,
+    })
+  })
+
+  it('logs out on request', () => {
+    const action = { type: LOGOUT_REQUEST }
+    const newState = reducer({ isAuthenticated: true }, action)
+    expect(newState).toMatchObject({
+      isAuthenticated: false,
+    })
+  })
+
+  it('logs out on logout success', () => {
+    const action = { type: LOGOUT_SUCCESS }
+    const newState = reducer({ isAuthenticated: true }, action)
+    expect(newState).toMatchObject({
+      isAuthenticated: false,
+    })
+  })
+})
diff --git a/packages/pubsweet-component-login/types.js b/packages/pubsweet-component-login/types.js
new file mode 100644
index 0000000000000000000000000000000000000000..3b66211c7193bd42a3e8aa9f3c63b46a57172a43
--- /dev/null
+++ b/packages/pubsweet-component-login/types.js
@@ -0,0 +1,7 @@
+export const LOGIN_REQUEST = 'LOGIN_REQUEST'
+export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
+export const LOGIN_FAILURE = 'LOGIN_FAILURE'
+
+export const LOGOUT_REQUEST = 'LOGOUT_REQUEST'
+export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'
+export const LOGOUT_FAILURE = 'LOGOUT_FAILURE'
diff --git a/packages/pubsweet-component-signup/CHANGELOG.md b/packages/pubsweet-component-signup/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..6e2faa066f967fc6027e4223c8bef79a09ba016b
--- /dev/null
+++ b/packages/pubsweet-component-signup/CHANGELOG.md
@@ -0,0 +1,278 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+<a name="1.0.33"></a>
+## [1.0.33](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.32...pubsweet-component-signup@1.0.33) (2018-11-05)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.32"></a>
+## [1.0.32](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.31...pubsweet-component-signup@1.0.32) (2018-10-08)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.31"></a>
+## [1.0.31](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.30...pubsweet-component-signup@1.0.31) (2018-09-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.30"></a>
+## [1.0.30](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.29...pubsweet-component-signup@1.0.30) (2018-09-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.29"></a>
+## [1.0.29](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.28...pubsweet-component-signup@1.0.29) (2018-09-06)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.28"></a>
+## [1.0.28](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.27...pubsweet-component-signup@1.0.28) (2018-09-04)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.27"></a>
+## [1.0.27](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.26...pubsweet-component-signup@1.0.27) (2018-08-20)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.26"></a>
+## [1.0.26](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.25...pubsweet-component-signup@1.0.26) (2018-08-17)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.25"></a>
+## [1.0.25](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.24...pubsweet-component-signup@1.0.25) (2018-08-02)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.24"></a>
+## [1.0.24](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.23...pubsweet-component-signup@1.0.24) (2018-07-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.23"></a>
+## [1.0.23](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.22...pubsweet-component-signup@1.0.23) (2018-07-12)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.22"></a>
+## [1.0.22](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.21...pubsweet-component-signup@1.0.22) (2018-07-09)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.21"></a>
+## [1.0.21](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.20...pubsweet-component-signup@1.0.21) (2018-07-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.20"></a>
+## [1.0.20](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.19...pubsweet-component-signup@1.0.20) (2018-07-02)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.19"></a>
+## [1.0.19](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.18...pubsweet-component-signup@1.0.19) (2018-06-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.18"></a>
+## [1.0.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.17...pubsweet-component-signup@1.0.18) (2018-06-28)
+
+
+### Bug Fixes
+
+* **monorepo:** fix versions of ui across repo ([72ada07](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/72ada07))
+
+
+
+
+<a name="1.0.17"></a>
+## [1.0.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.16...pubsweet-component-signup@1.0.17) (2018-05-21)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.16"></a>
+## [1.0.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.15...pubsweet-component-signup@1.0.16) (2018-05-18)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.15"></a>
+## [1.0.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.14...pubsweet-component-signup@1.0.15) (2018-05-10)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.14"></a>
+## [1.0.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.13...pubsweet-component-signup@1.0.14) (2018-05-09)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.13"></a>
+## [1.0.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.12...pubsweet-component-signup@1.0.13) (2018-05-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.12"></a>
+## [1.0.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.11...pubsweet-component-signup@1.0.12) (2018-04-24)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.11"></a>
+## [1.0.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.10...pubsweet-component-signup@1.0.11) (2018-04-11)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.10"></a>
+## [1.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.9...pubsweet-component-signup@1.0.10) (2018-04-03)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.9"></a>
+## [1.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.8...pubsweet-component-signup@1.0.9) (2018-03-30)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.8"></a>
+## [1.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.7...pubsweet-component-signup@1.0.8) (2018-03-28)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.7"></a>
+## [1.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.6...pubsweet-component-signup@1.0.7) (2018-03-27)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.6"></a>
+## [1.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.5...pubsweet-component-signup@1.0.6) (2018-03-19)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.5"></a>
+## [1.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.4...pubsweet-component-signup@1.0.5) (2018-03-15)
+
+
+
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.4"></a>
+
+## [1.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.3...pubsweet-component-signup@1.0.4) (2018-03-09)
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.3"></a>
+
+## [1.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.2...pubsweet-component-signup@1.0.3) (2018-03-06)
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.2"></a>
+
+## [1.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.1...pubsweet-component-signup@1.0.2) (2018-03-05)
+
+### Bug Fixes
+
+* **components:** add dependency on pubsweet/ui ([f0a1926](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f0a1926))
+* **components:** signup and login error examples ([3f991ec](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3f991ec))
+
+<a name="1.0.1"></a>
+
+## [1.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.0...pubsweet-component-signup@1.0.1) (2018-02-16)
+
+**Note:** Version bump only for package pubsweet-component-signup
+
+<a name="1.0.0"></a>
+
+# [1.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@0.5.0...pubsweet-component-signup@1.0.0) (2018-02-02)
+
+### Features
+
+* **client:** upgrade React to version 16 ([626cf59](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/626cf59)), closes [#65](https://gitlab.coko.foundation/pubsweet/pubsweet/issues/65)
+
+### BREAKING CHANGES
+
+* **client:** Upgrade React to version 16
diff --git a/packages/pubsweet-component-signup/Signup.jsx b/packages/pubsweet-component-signup/Signup.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e6c5bc700d0f2415cecf50ec3f8201d9042dcb28
--- /dev/null
+++ b/packages/pubsweet-component-signup/Signup.jsx
@@ -0,0 +1,42 @@
+import React from 'react'
+import { Field } from 'redux-form'
+import {
+  CenteredColumn,
+  Link,
+  H1,
+  ErrorText,
+  Button,
+  TextField,
+} from '@pubsweet/ui'
+
+const UsernameInput = props => <TextField label="Username" {...props.input} />
+const EmailInput = props => (
+  <TextField label="Email" {...props.input} type="email" />
+)
+const PasswordInput = props => (
+  <TextField label="Password" {...props.input} type="password" />
+)
+
+const Signup = ({ error, handleSubmit }) => (
+  <CenteredColumn small>
+    <H1>Sign up</H1>
+
+    {error && <ErrorText>{error}</ErrorText>}
+
+    <form onSubmit={handleSubmit}>
+      <Field component={UsernameInput} name="username" />
+      <Field component={EmailInput} name="email" />
+      <Field component={PasswordInput} name="password" />
+      <Button primary type="submit">
+        Sign up
+      </Button>
+    </form>
+
+    <div>
+      <span>Already have an account? </span>
+      <Link to="/login">Login</Link>
+    </div>
+  </CenteredColumn>
+)
+
+export default Signup
diff --git a/packages/pubsweet-component-signup/Signup.md b/packages/pubsweet-component-signup/Signup.md
new file mode 100644
index 0000000000000000000000000000000000000000..6ba7e53aba41eb4e195bf18eeeb1a3197de0f3d0
--- /dev/null
+++ b/packages/pubsweet-component-signup/Signup.md
@@ -0,0 +1,26 @@
+A login form
+
+```js
+const { reduxForm } = require('redux-form')
+
+const SignupForm = reduxForm({
+  form: 'signup',
+  onChange: values => console.log(values),
+})(Signup)
+;<SignupForm />
+```
+
+Which can have an error message:
+
+```js
+const { reduxForm, SubmissionError } = require('redux-form')
+
+const SignupForm = reduxForm({
+  form: 'signup-error',
+  onSubmit: val => {
+    console.log(val)
+    return Promise.reject(new SubmissionError({ _error: 'Error message' }))
+  },
+})(Signup)
+;<SignupForm />
+```
diff --git a/packages/pubsweet-component-signup/SignupContainer.js b/packages/pubsweet-component-signup/SignupContainer.js
new file mode 100644
index 0000000000000000000000000000000000000000..a1cd2947b2b339ac0de9dd67703f5d9f2c5ca64a
--- /dev/null
+++ b/packages/pubsweet-component-signup/SignupContainer.js
@@ -0,0 +1,20 @@
+import { connect } from 'react-redux'
+import { compose } from 'recompose'
+import { reduxForm } from 'redux-form'
+
+import { signupUser } from './actions'
+import Signup from './Signup'
+
+const onSubmit = (values, dispatch) => {
+  dispatch(signupUser(values))
+}
+
+export default compose(
+  reduxForm({
+    form: 'signup',
+    onSubmit,
+  }),
+  connect(state => ({
+    error: state.error,
+  })),
+)(Signup)
diff --git a/packages/pubsweet-component-signup/actions.js b/packages/pubsweet-component-signup/actions.js
new file mode 100644
index 0000000000000000000000000000000000000000..3c47ea703cb29ba051eaaf04d9bf5e744d2e6b29
--- /dev/null
+++ b/packages/pubsweet-component-signup/actions.js
@@ -0,0 +1,36 @@
+import { push } from 'react-router-redux'
+import * as api from 'pubsweet-client/src/helpers/api'
+import * as T from './types'
+
+function signupRequest() {
+  return {
+    type: T.SIGNUP_REQUEST,
+  }
+}
+
+function signupSuccess(user) {
+  return {
+    type: T.SIGNUP_SUCCESS,
+    user,
+  }
+}
+
+function signupFailure(message) {
+  return {
+    type: T.SIGNUP_FAILURE,
+    error: message,
+  }
+}
+
+export function signupUser(user) {
+  return dispatch => {
+    dispatch(signupRequest())
+    return api.create('/users', user).then(
+      user => {
+        dispatch(signupSuccess(user))
+        dispatch(push('/login'))
+      },
+      err => dispatch(signupFailure(err)),
+    )
+  }
+}
diff --git a/packages/pubsweet-component-signup/index.js b/packages/pubsweet-component-signup/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..97d462d9b9beb987d214e03505412ba1e7389ef5
--- /dev/null
+++ b/packages/pubsweet-component-signup/index.js
@@ -0,0 +1,7 @@
+module.exports = {
+  frontend: {
+    components: [() => require('./SignupContainer')],
+    actions: () => require('./actions'),
+    reducers: () => require('./reducers'),
+  },
+}
diff --git a/packages/pubsweet-component-signup/package.json b/packages/pubsweet-component-signup/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..40c23e43373c1e2f500ccb787ca3b06126574663
--- /dev/null
+++ b/packages/pubsweet-component-signup/package.json
@@ -0,0 +1,25 @@
+{
+  "name": "pubsweet-component-signup",
+  "version": "1.0.33",
+  "description": "Basic signup form component for PubSweet",
+  "main": "index.js",
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "dependencies": {
+    "@pubsweet/ui": "^8.8.0",
+    "prop-types": "^15.5.10",
+    "react-bootstrap": "^0.32.0",
+    "react-redux": "^5.0.6",
+    "react-router": "^4.2.0",
+    "redux": "^3.7.2"
+  },
+  "peerDependencies": {
+    "pubsweet-client": ">=1.0.0",
+    "react": ">=16"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://gitlab.coko.foundation/pubsweet/pubsweet",
+    "path": "Signup"
+  }
+}
diff --git a/packages/pubsweet-component-signup/reducers.js b/packages/pubsweet-component-signup/reducers.js
new file mode 100644
index 0000000000000000000000000000000000000000..3b1268c9e07d333e59170867cf9dd481df7dd64d
--- /dev/null
+++ b/packages/pubsweet-component-signup/reducers.js
@@ -0,0 +1,21 @@
+import { SIGNUP_SUCCESS } from './types'
+
+export default function userSignup(
+  state = {
+    isFetching: false,
+    isAuthenticated: false,
+  },
+  action,
+) {
+  switch (action.type) {
+    case SIGNUP_SUCCESS:
+      return {
+        ...state,
+        isFetching: false,
+        isAuthenticated: true,
+        user: action.user,
+      }
+    default:
+      return state
+  }
+}
diff --git a/packages/pubsweet-component-signup/types.js b/packages/pubsweet-component-signup/types.js
new file mode 100644
index 0000000000000000000000000000000000000000..8cea7b558eb965d7997e7abb8fa05c42429942d3
--- /dev/null
+++ b/packages/pubsweet-component-signup/types.js
@@ -0,0 +1,5 @@
+// Action types
+
+export const SIGNUP_REQUEST = 'SIGNUP_REQUEST'
+export const SIGNUP_SUCCESS = 'SIGNUP_SUCCESS'
+export const SIGNUP_FAILURE = 'SIGNUP_FAILURE'
diff --git a/packages/xpub-faraday/config/components.json b/packages/xpub-faraday/config/components.json
index b6f94243de5cfd48a8c828c21cda98ddfef5a6b4..01db85a3f575c70a8ad95fd3625ef8383ba1d27d 100644
--- a/packages/xpub-faraday/config/components.json
+++ b/packages/xpub-faraday/config/components.json
@@ -1,6 +1,6 @@
 [
-  "pubsweet-component-signup",
   "pubsweet-component-login",
+  "pubsweet-component-signup",
   "pubsweet-component-wizard",
   "pubsweet-component-modal",
   "pubsweet-components-faraday",
diff --git a/packages/xpub-faraday/package.json b/packages/xpub-faraday/package.json
index b764cfc8cf4f6be99e1b433106621f44f04e4f6b..3f7c75d27236aafd60a30c2d22b6ce2fbfbc3760 100644
--- a/packages/xpub-faraday/package.json
+++ b/packages/xpub-faraday/package.json
@@ -10,6 +10,7 @@
   "dependencies": {
     "@pubsweet/component-aws-s3": "^1.2.0",
     "@pubsweet/component-send-email": "0.2.4",
+    "@pubsweet/component-email-templating": "0.0.1",
     "@pubsweet/styleguide": "3.1.4",
     "@pubsweet/ui": "^8.6.0",
     "@pubsweet/ui-toolkit": "latest",
@@ -29,7 +30,7 @@
     "pubsweet": "3.0.6",
     "pubsweet-client": "^4.0.4",
     "pubsweet-component-login": "^1.2.0",
-    "pubsweet-component-signup": "^1.0.0",
+    "pubsweet-component-signup": "^1.0.33",
     "pubsweet-server": "10.0.1",
     "react": "^16.4.2",
     "react-dnd": "^2.5.4",
@@ -81,7 +82,8 @@
     "start": "pubsweet start",
     "start:services": "docker-compose up postgres",
     "server": "pubsweet server",
-    "start-now": "echo $secret > config/local-development.json && npm run server",
+    "start-now":
+      "echo $secret > config/local-development.json && npm run server",
     "build": "NODE_ENV=production pubsweet build",
     "clean": "rm -rf node_modules",
     "debug": "pgrep -f startup/start.js | xargs kill -sigusr1",
diff --git a/yarn.lock b/yarn.lock
index d518be423a9302e25dd44b161213a68fb535e779..e3c5febd641a145da11845c9776b9ae98481342e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9297,10 +9297,11 @@ pubsweet-component-login@^1.2.0:
     react-router-redux "^5.0.0-alpha.9"
     recompose "^0.26.0"
 
-pubsweet-component-signup@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/pubsweet-component-signup/-/pubsweet-component-signup-1.0.1.tgz#f18f3a18940c440615af41bc11a155eff91c9b2d"
+pubsweet-component-signup@^1.0.33:
+  version "1.0.33"
+  resolved "https://registry.yarnpkg.com/pubsweet-component-signup/-/pubsweet-component-signup-1.0.33.tgz#a7696cb43b309a0901588736e228c5acef153e62"
   dependencies:
+    "@pubsweet/ui" "^8.8.0"
     prop-types "^15.5.10"
     react-bootstrap "^0.32.0"
     react-redux "^5.0.6"