From 6a931a907ee4df86d43e7c3e406f966a90731d10 Mon Sep 17 00:00:00 2001
From: Alexandru Munteanu <alexandru.munt@gmail.com>
Date: Thu, 5 Jul 2018 10:13:38 +0300
Subject: [PATCH] feat(author-signup): author signup form and confirm api route

---
 packages/component-email/src/helpers/Email.js |  33 ++-
 .../component-email/src/routes/emails/post.js |  33 ++-
 packages/component-mail-service/src/Mail.js   |  18 +-
 packages/component-user-manager/src/Users.js  |   5 +
 .../src/routes/users/confirm.js               |  32 +++
 .../src/components/SignUp/AuthorSignup.js     | 197 ++++++++++++++++++
 .../src/components/SignUp/ConfirmAccount.js   |  19 ++
 .../components/SignUp/SignUpInvitationForm.js |  18 +-
 .../components/SignUp/SignUpInvitationPage.js |  37 +++-
 .../src/components/SignUp/SignUpStep0.js      |   8 +-
 .../src/components/SignUp/SignUpStep1.js      |  29 ++-
 .../src/components/SignUp/index.js            |   2 +
 .../src/components/UIComponents/FormItems.js  |   2 +
 .../src/components/utils.js                   |  28 +++
 .../components-faraday/src/redux/users.js     |   7 +
 packages/xpub-faraday/app/routes.js           |  22 +-
 packages/xpub-faraday/config/default.js       |   3 +
 packages/xpub-faraday/config/validations.js   |   1 +
 18 files changed, 453 insertions(+), 41 deletions(-)
 create mode 100644 packages/component-user-manager/src/routes/users/confirm.js
 create mode 100644 packages/components-faraday/src/components/SignUp/AuthorSignup.js
 create mode 100644 packages/components-faraday/src/components/SignUp/ConfirmAccount.js

diff --git a/packages/component-email/src/helpers/Email.js b/packages/component-email/src/helpers/Email.js
index 292e4a637..02a9816e0 100644
--- a/packages/component-email/src/helpers/Email.js
+++ b/packages/component-email/src/helpers/Email.js
@@ -3,6 +3,37 @@ const logger = require('@pubsweet/logger')
 const helpers = require('./helpers')
 
 module.exports = {
+  sendSignupEmail: async ({ dashboardUrl, res, email, UserModel }) => {
+    let user
+    try {
+      user = await UserModel.findByEmail(email)
+    } catch (e) {
+      const notFoundError = await helpers.handleNotFoundError(e, 'User')
+      return res.status(notFoundError.status).json({
+        error: notFoundError.message,
+      })
+    }
+    if (!user.confirmationToken) {
+      return res
+        .status(400)
+        .json({ error: 'User does not have a confirmation token.' })
+    }
+    try {
+      await mailService.sendSimpleEmail({
+        toEmail: user.email,
+        user,
+        emailType: 'signup',
+        dashboardUrl,
+        meta: {
+          confirmationToken: user.confirmationToken,
+        },
+      })
+      return res.status(200).json({})
+    } catch (e) {
+      logger.error(e)
+      return res.status(500).json({ error: 'Email could not be sent.' })
+    }
+  },
   setupNewUserEmail: async ({ dashboardUrl, res, email, role, UserModel }) => {
     let user
     try {
@@ -13,7 +44,7 @@ module.exports = {
         error: notFoundError.message,
       })
     }
-    if (user.passwordResetToken === undefined) {
+    if (!user.passwordResetToken) {
       return res
         .status(400)
         .json({ error: 'User does not have a password reset token.' })
diff --git a/packages/component-email/src/routes/emails/post.js b/packages/component-email/src/routes/emails/post.js
index 9ff44cc05..33a149738 100644
--- a/packages/component-email/src/routes/emails/post.js
+++ b/packages/component-email/src/routes/emails/post.js
@@ -3,21 +3,34 @@ const helpers = require('../../helpers/helpers')
 const emailHelper = require('../../helpers/Email')
 
 module.exports = models => async (req, res) => {
-  const { email, type, role } = req.body
+  const { email, type, role = 'author' } = req.body
   if (!helpers.checkForUndefinedParams(email, type, role)) {
     res.status(400).json({ error: 'Email and type are required.' })
     logger.error('User ID and role are missing')
     return
   }
 
-  if (type !== 'invite')
-    return res.status(400).json({ error: `Email type ${type} is not defined.` })
+  // if (type !== 'invite')
+  //   return res.status(400).json({ error: `Email type ${type} is not defined.` })
 
-  return emailHelper.setupNewUserEmail({
-    dashboardUrl: `${req.protocol}://${req.get('host')}`,
-    res,
-    email,
-    role,
-    UserModel: models.User,
-  })
+  if (type === 'signup') {
+    return emailHelper.sendSignupEmail({
+      res,
+      email,
+      UserModel: models.User,
+      dashboardUrl: `${req.protocol}://${req.get('host')}`,
+    })
+  }
+
+  if (type === 'invite') {
+    return emailHelper.setupNewUserEmail({
+      dashboardUrl: `${req.protocol}://${req.get('host')}`,
+      res,
+      email,
+      role,
+      UserModel: models.User,
+    })
+  }
+
+  return res.end()
 }
diff --git a/packages/component-mail-service/src/Mail.js b/packages/component-mail-service/src/Mail.js
index 67d85243b..b8028ff25 100644
--- a/packages/component-mail-service/src/Mail.js
+++ b/packages/component-mail-service/src/Mail.js
@@ -2,8 +2,9 @@ const Email = require('@pubsweet/component-send-email')
 const config = require('config')
 const helpers = require('./helpers/helpers')
 
-const resetPasswordPath = config.get('invite-reviewer.url')
+const confirmSignUp = config.get('confirm-signup.url')
 const resetPath = config.get('invite-reset-password.url')
+const resetPasswordPath = config.get('invite-reviewer.url')
 
 module.exports = {
   sendSimpleEmail: async ({
@@ -43,6 +44,21 @@ module.exports = {
           replacements.url
         } ${replacements.buttonText}`
         break
+      case 'signup':
+        subject = 'Confirm your email address'
+        replacements.headline = ''
+        replacements.paragraph =
+          'Please confirm your account by clicking on the link below.'
+        replacements.previewText = 'Hindawi account confirmation'
+        replacements.buttonText = 'CONFIRM'
+        replacements.url = helpers.createUrl(dashboardUrl, confirmSignUp, {
+          userId: user.id,
+          confirmationToken: meta.confirmationToken,
+        })
+        textBody = `${replacements.headline} ${replacements.paragraph} ${
+          replacements.url
+        } ${replacements.buttonText}`
+        break
       case 'invite-author':
         subject = 'Author Invitation'
         replacements.headline =
diff --git a/packages/component-user-manager/src/Users.js b/packages/component-user-manager/src/Users.js
index 3fdfb1b60..64f74bf44 100644
--- a/packages/component-user-manager/src/Users.js
+++ b/packages/component-user-manager/src/Users.js
@@ -38,6 +38,11 @@ const Invite = app => {
     '/api/users/reset-password',
     require('./routes/users/resetPassword')(app.locals.models),
   )
+
+  app.post(
+    '/api/users/confirm',
+    require('./routes/users/confirm')(app.locals.models),
+  )
 }
 
 module.exports = Invite
diff --git a/packages/component-user-manager/src/routes/users/confirm.js b/packages/component-user-manager/src/routes/users/confirm.js
new file mode 100644
index 000000000..2675e160e
--- /dev/null
+++ b/packages/component-user-manager/src/routes/users/confirm.js
@@ -0,0 +1,32 @@
+const { token } = require('pubsweet-server/src/authentication')
+const { services } = require('pubsweet-component-helper-service')
+
+module.exports = ({ User }) => async (req, res) => {
+  const { userId, confirmationToken } = req.body
+
+  if (!services.checkForUndefinedParams(userId, confirmationToken))
+    return res.status(400).json({ error: 'Missing required params' })
+
+  let user
+  try {
+    user = await User.find(userId)
+
+    if (user.confirmationToken !== confirmationToken) {
+      return res.status(400).json({ error: 'Wrong confirmation token.' })
+    }
+
+    if (user.isConfirmed)
+      return res.status(400).json({ error: 'User is already confirmed' })
+
+    user.isConfirmed = true
+    delete user.confirmationToken
+    await user.save()
+
+    return res.status(200).json({
+      ...user,
+      token: token.create(user),
+    })
+  } catch (e) {
+    throw e
+  }
+}
diff --git a/packages/components-faraday/src/components/SignUp/AuthorSignup.js b/packages/components-faraday/src/components/SignUp/AuthorSignup.js
new file mode 100644
index 000000000..40f08b2fa
--- /dev/null
+++ b/packages/components-faraday/src/components/SignUp/AuthorSignup.js
@@ -0,0 +1,197 @@
+import React, { Fragment } from 'react'
+import { reduxForm } from 'redux-form'
+import { th } from '@pubsweet/ui-toolkit'
+import { required } from 'xpub-validators'
+import { compose, withState } from 'recompose'
+import styled, { css } from 'styled-components'
+import { Icon, Button, TextField, ValidatedField } from '@pubsweet/ui'
+
+import { FormItems } from '../UIComponents'
+
+const { Row, RowItem, Label, RootContainer, FormContainer } = FormItems
+
+const Step1 = ({ handleSubmit }) => (
+  <CustomFormContainer onSubmit={handleSubmit}>
+    <Fragment>
+      <CustomRow noMargin>
+        <CustomRowItem vertical>
+          <Label>Email</Label>
+          <ValidatedField
+            component={TextField}
+            name="email"
+            validate={[required]}
+          />
+        </CustomRowItem>
+      </CustomRow>
+      <CustomRow>
+        <CustomRowItem vertical>
+          <Label>Password</Label>
+          <ValidatedField
+            component={TextField}
+            name="password"
+            validate={[required]}
+          />
+        </CustomRowItem>
+      </CustomRow>
+      <CustomRow>
+        <CustomRowItem vertical>
+          <Label>Confirm password</Label>
+          <ValidatedField
+            component={TextField}
+            name="confirmPassword"
+            validate={[required]}
+          />
+        </CustomRowItem>
+      </CustomRow>
+    </Fragment>
+    <Button primary type="submit">
+      Next
+    </Button>
+  </CustomFormContainer>
+)
+
+const AuthorSignupStep1 = reduxForm({
+  form: 'authorSignup',
+  destroyOnUnmount: false,
+  enableReinitialize: true,
+  forceUnregisterOnUnmount: true,
+})(Step1)
+
+const Step2 = ({ handleSubmit }) => (
+  <CustomFormContainer onSubmit={handleSubmit}>
+    <Fragment>
+      <CustomRow noMargin>
+        <CustomRowItem vertical>
+          <Label>First name</Label>
+          <ValidatedField
+            component={TextField}
+            name="firstName"
+            validate={[required]}
+          />
+        </CustomRowItem>
+      </CustomRow>
+      <CustomRow noMargin>
+        <CustomRowItem vertical>
+          <Label>Last name</Label>
+          <ValidatedField
+            component={TextField}
+            name="lastName"
+            validate={[required]}
+          />
+        </CustomRowItem>
+      </CustomRow>
+      <CustomRow noMargin>
+        <CustomRowItem vertical>
+          <Label>Affiliation</Label>
+          <ValidatedField
+            component={TextField}
+            name="affiliation"
+            validate={[required]}
+          />
+        </CustomRowItem>
+      </CustomRow>
+      <CustomRow noMargin>
+        <CustomRowItem vertical>
+          <Label>Title</Label>
+          <ValidatedField
+            component={TextField}
+            name="title"
+            validate={[required]}
+          />
+        </CustomRowItem>
+      </CustomRow>
+    </Fragment>
+    <Button primary type="submit">
+      Submit
+    </Button>
+  </CustomFormContainer>
+)
+
+const AuthorSignupStep2 = reduxForm({
+  form: 'authorSignup',
+  destroyOnUnmount: false,
+  forceUnregisterOnUnmount: true,
+  onSubmit: null,
+})(Step2)
+
+const AuthorWizard = ({ step, changeStep, history }) => (
+  <CustomRootContainer>
+    <IconButton onClick={history.goBack}>
+      <Icon primary size={3}>
+        x
+      </Icon>
+    </IconButton>
+    <Title>Author Signup</Title>
+    {step === 0 && <AuthorSignupStep1 onSubmit={() => changeStep(1)} />}
+    {step === 1 && <AuthorSignupStep2 />}
+  </CustomRootContainer>
+)
+
+export default compose(withState('step', 'changeStep', 0))(AuthorWizard)
+
+// #region styled-components
+const verticalPadding = css`
+  padding: ${th('subGridUnit')} 0;
+`
+
+const CustomRow = Row.extend`
+  div[role='alert'] {
+    margin-top: 0;
+  }
+`
+
+const CustomRowItem = RowItem.extend`
+  & > div {
+    flex: 1;
+
+    & > div {
+      max-width: 400px;
+      width: 400px;
+    }
+  }
+`
+
+const CustomRootContainer = RootContainer.extend`
+  align-items: center;
+  border: ${th('borderDefault')};
+  position: relative;
+`
+
+const CustomFormContainer = FormContainer.extend`
+  align-items: center;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+`
+
+const Title = styled.span`
+  font-family: ${th('fontHeading')};
+  font-size: ${th('fontSizeHeading5')};
+  ${verticalPadding};
+`
+
+const IconButton = styled.button`
+  align-items: center;
+  background-color: ${th('backgroundColorReverse')};
+  border: none;
+  color: ${th('colorPrimary')};
+  cursor: ${({ hide }) => (hide ? 'auto' : 'pointer')};
+  display: flex;
+  font-family: ${th('fontInterface')};
+  font-size: ${th('fontSizeBaseSmall')};
+  opacity: ${({ hide }) => (hide ? 0 : 1)};
+  text-align: left;
+
+  position: absolute;
+  top: ${th('subGridUnit')};
+  right: ${th('subGridUnit')};
+
+  &:active,
+  &:focus {
+    outline: none;
+  }
+  &:hover {
+    opacity: 0.7;
+  }
+`
+// #endregion
diff --git a/packages/components-faraday/src/components/SignUp/ConfirmAccount.js b/packages/components-faraday/src/components/SignUp/ConfirmAccount.js
new file mode 100644
index 000000000..979c221a0
--- /dev/null
+++ b/packages/components-faraday/src/components/SignUp/ConfirmAccount.js
@@ -0,0 +1,19 @@
+import React from 'react'
+import { compose, lifecycle } from 'recompose'
+
+import { parseSearchParams } from '../utils'
+import { confirmUser } from '../../redux/users'
+
+const ConfirmAccount = ({ location }) => <div>confirm</div>
+
+export default compose(
+  lifecycle({
+    componentDidMount() {
+      const { location } = this.props
+      const { confirmationToken, userId } = parseSearchParams(location.search)
+      if (userId) {
+        confirmUser(userId, confirmationToken)
+      }
+    },
+  }),
+)(ConfirmAccount)
diff --git a/packages/components-faraday/src/components/SignUp/SignUpInvitationForm.js b/packages/components-faraday/src/components/SignUp/SignUpInvitationForm.js
index f7b21c178..4a3f99957 100644
--- a/packages/components-faraday/src/components/SignUp/SignUpInvitationForm.js
+++ b/packages/components-faraday/src/components/SignUp/SignUpInvitationForm.js
@@ -6,22 +6,25 @@ import { FormItems } from '../UIComponents'
 
 const { RootContainer, Title, Subtitle, Email, Err } = FormItems
 
+const defaultSubtitle = `Your details have been pre-filled, please review and confirm before set
+your password.`
+
 const SignUpInvitation = ({
   step,
   email,
   token,
   error,
   journal,
+  onSubmit,
   nextStep,
   initialValues,
-  submitConfirmation,
+  type = 'invitation',
+  subtitle = defaultSubtitle,
+  title = 'Add New Account Details',
 }) => (
   <RootContainer bordered>
-    <Title>Add New Account Details</Title>
-    <Subtitle>
-      Your details have been pre-filled, please review and confirm before set
-      your password.
-    </Subtitle>
+    <Title>{title}</Title>
+    <Subtitle>{subtitle}</Subtitle>
     <Email>{initialValues.email}</Email>
     {error && <Err>Token expired or Something went wrong.</Err>}
     {step === 0 && (
@@ -37,7 +40,8 @@ const SignUpInvitation = ({
         error={error}
         initialValues={initialValues}
         journal={journal}
-        onSubmit={submitConfirmation}
+        onSubmit={onSubmit}
+        type={type}
       />
     )}
   </RootContainer>
diff --git a/packages/components-faraday/src/components/SignUp/SignUpInvitationPage.js b/packages/components-faraday/src/components/SignUp/SignUpInvitationPage.js
index 0ad1a8f2a..9c8b24d62 100644
--- a/packages/components-faraday/src/components/SignUp/SignUpInvitationPage.js
+++ b/packages/components-faraday/src/components/SignUp/SignUpInvitationPage.js
@@ -6,6 +6,7 @@ import { loginUser } from 'pubsweet-component-login/actions'
 import { compose, withState, withProps, withHandlers } from 'recompose'
 
 import SignUpInvitation from './SignUpInvitationForm'
+import { handleError, parseSignupAuthor } from '../utils'
 
 const login = (dispatch, values, history) =>
   dispatch(loginUser(values))
@@ -43,10 +44,34 @@ const confirmUser = (email, token, history) => (values, dispatch) => {
   }
 }
 
+const signUpUser = history => (values, dispatch) =>
+  create('/users', parseSignupAuthor(values))
+    .then(r => {
+      const { username } = r
+      const { password } = values
+      login(dispatch, { username, password }, history).then(() => {
+        create('/emails', {
+          email: values.email,
+          type: 'signup',
+        })
+      })
+    })
+    .catch(handleError)
+
 export default compose(
   withJournal,
   withState('step', 'changeStep', 0),
-  withProps(({ location }) => {
+  withHandlers({
+    nextStep: ({ changeStep }) => () => changeStep(step => step + 1),
+    prevStep: ({ changeStep }) => () => changeStep(step => step - 1),
+    submitConfirmation: ({
+      initialValues: { email, token },
+      history,
+      ...rest
+    }) => confirmUser(email, token, history),
+    signUp: ({ history }) => signUpUser(history),
+  }),
+  withProps(({ location, type, signUp, submitConfirmation }) => {
     const params = new URLSearchParams(location.search)
     const email = params.get('email')
     const token = params.get('token')
@@ -64,15 +89,7 @@ export default compose(
         firstName,
         affiliation,
       },
+      onSubmit: type === 'signup' ? signUp : submitConfirmation,
     }
   }),
-  withHandlers({
-    nextStep: ({ changeStep }) => () => changeStep(step => step + 1),
-    prevStep: ({ changeStep }) => () => changeStep(step => step - 1),
-    submitConfirmation: ({
-      initialValues: { email, token },
-      history,
-      ...rest
-    }) => confirmUser(email, token, history),
-  }),
 )(SignUpInvitation)
diff --git a/packages/components-faraday/src/components/SignUp/SignUpStep0.js b/packages/components-faraday/src/components/SignUp/SignUpStep0.js
index fb342d2ad..237bba74d 100644
--- a/packages/components-faraday/src/components/SignUp/SignUpStep0.js
+++ b/packages/components-faraday/src/components/SignUp/SignUpStep0.js
@@ -12,7 +12,7 @@ const Step0 = ({ journal, handleSubmit, initialValues, error }) =>
   !isUndefined(initialValues) ? (
     <FormContainer onSubmit={handleSubmit}>
       <Row>
-        <RowItem vertical>
+        <RowItem vertical withRightMargin>
           <Label>First name*</Label>
           <ValidatedField
             component={TextField}
@@ -20,7 +20,7 @@ const Step0 = ({ journal, handleSubmit, initialValues, error }) =>
             validate={[required]}
           />
         </RowItem>
-        <RowItem vertical>
+        <RowItem vertical withRightMargin>
           <Label>Last name*</Label>
           <ValidatedField
             component={TextField}
@@ -30,7 +30,7 @@ const Step0 = ({ journal, handleSubmit, initialValues, error }) =>
         </RowItem>
       </Row>
       <Row>
-        <RowItem vertical>
+        <RowItem vertical withRightMargin>
           <Label>Affiliation*</Label>
           <ValidatedField
             component={TextField}
@@ -39,7 +39,7 @@ const Step0 = ({ journal, handleSubmit, initialValues, error }) =>
           />
         </RowItem>
 
-        <RowItem vertical>
+        <RowItem vertical withRightMargin>
           <Label>Title*</Label>
           <ValidatedField
             component={input => <Menu {...input} options={journal.title} />}
diff --git a/packages/components-faraday/src/components/SignUp/SignUpStep1.js b/packages/components-faraday/src/components/SignUp/SignUpStep1.js
index b3e5317e0..ae97bc2a1 100644
--- a/packages/components-faraday/src/components/SignUp/SignUpStep1.js
+++ b/packages/components-faraday/src/components/SignUp/SignUpStep1.js
@@ -7,20 +7,43 @@ import { FormItems } from '../UIComponents'
 
 const { Row, Err, Label, RowItem, FormContainer } = FormItems
 
-const TextFormField = input => <TextField {...input} type="password" />
+const PasswordField = input => <TextField {...input} type="password" />
+const EmailField = input => <TextField {...input} type="email" />
 
-const Step1 = ({ journal, handleSubmit, error }) => (
+const Step1 = ({ journal, handleSubmit, error, type }) => (
   <FormContainer onSubmit={handleSubmit}>
+    {type === 'signup' && (
+      <Row>
+        <RowItem vertical>
+          <Label>Email</Label>
+          <ValidatedField
+            component={EmailField}
+            name="email"
+            validate={[required]}
+          />
+        </RowItem>
+      </Row>
+    )}
     <Row>
       <RowItem vertical>
         <Label>Password</Label>
         <ValidatedField
-          component={TextFormField}
+          component={PasswordField}
           name="password"
           validate={[required]}
         />
       </RowItem>
     </Row>
+    <Row>
+      <RowItem vertical>
+        <Label>Confirm password</Label>
+        <ValidatedField
+          component={PasswordField}
+          name="confirmPassword"
+          validate={[required]}
+        />
+      </RowItem>
+    </Row>
     {error && (
       <Row>
         <RowItem>
diff --git a/packages/components-faraday/src/components/SignUp/index.js b/packages/components-faraday/src/components/SignUp/index.js
index 8fc6a0dd3..d06b74c2b 100644
--- a/packages/components-faraday/src/components/SignUp/index.js
+++ b/packages/components-faraday/src/components/SignUp/index.js
@@ -1,3 +1,5 @@
+export { default as AuthorSignup } from './AuthorSignup'
+export { default as ConfirmAccount } from './ConfirmAccount'
 export { default as ReviewerSignUp } from './ReviewerSignUp'
 export { default as ReviewerDecline } from './ReviewerDecline'
 export { default as SignUpInvitationPage } from './SignUpInvitationPage'
diff --git a/packages/components-faraday/src/components/UIComponents/FormItems.js b/packages/components-faraday/src/components/UIComponents/FormItems.js
index fc91cd922..275aea4be 100644
--- a/packages/components-faraday/src/components/UIComponents/FormItems.js
+++ b/packages/components-faraday/src/components/UIComponents/FormItems.js
@@ -63,6 +63,8 @@ export const RowItem = styled.div`
   flex: ${({ flex }) => flex || 1};
   flex-direction: ${({ vertical }) => (vertical ? 'column' : 'row')};
   justify-content: ${({ centered }) => (centered ? 'center' : 'initial')};
+  margin-right: ${({ withRightMargin }) =>
+    withRightMargin ? th('gridUnit') : 0};
 
   & > div {
     flex: 1;
diff --git a/packages/components-faraday/src/components/utils.js b/packages/components-faraday/src/components/utils.js
index 1780bd7a3..3050fa966 100644
--- a/packages/components-faraday/src/components/utils.js
+++ b/packages/components-faraday/src/components/utils.js
@@ -66,3 +66,31 @@ export const redirectToError = redirectFn => err => {
   const errorText = get(JSON.parse(err.response), 'error')
   redirectFn('/error-page', errorText || 'Oops! Something went wrong.')
 }
+
+const generatePasswordHash = () =>
+  Array.from({ length: 4 }, () =>
+    Math.random()
+      .toString(36)
+      .slice(4),
+  ).join('')
+
+export const parseSignupAuthor = ({ token, confirmPassword, ...values }) => ({
+  ...values,
+  admin: false,
+  isConfirmed: false,
+  editorInChief: false,
+  handlingEditor: false,
+  username: values.email,
+  confirmationToken: generatePasswordHash(),
+})
+
+export const parseSearchParams = url => {
+  const params = new URLSearchParams(url)
+  const parsedObject = {}
+  /* eslint-disable */
+  for ([key, value] of params) {
+    parsedObject[key] = value
+  }
+  /* eslint-enable */
+  return parsedObject
+}
diff --git a/packages/components-faraday/src/redux/users.js b/packages/components-faraday/src/redux/users.js
index dd9cd57ab..e7cfc67be 100644
--- a/packages/components-faraday/src/redux/users.js
+++ b/packages/components-faraday/src/redux/users.js
@@ -1,4 +1,11 @@
 import { get } from 'lodash'
+import { create } from 'pubsweet-client/src/helpers/api'
 
 export const currentUserIs = (state, role) =>
   get(state, `currentUser.user.${role}`)
+
+export const confirmUser = (userId, confirmationToken) =>
+  create(`/users/confirm`, {
+    userId,
+    confirmationToken,
+  })
diff --git a/packages/xpub-faraday/app/routes.js b/packages/xpub-faraday/app/routes.js
index bf161391a..4de1459d8 100644
--- a/packages/xpub-faraday/app/routes.js
+++ b/packages/xpub-faraday/app/routes.js
@@ -3,25 +3,25 @@ import { withProps } from 'recompose'
 import { Route, Switch } from 'react-router-dom'
 import { AuthenticatedComponent } from 'pubsweet-client'
 import Login from 'pubsweet-component-login/LoginContainer'
-import Signup from 'pubsweet-component-signup/SignupContainer'
 
 import { Wizard } from 'pubsweet-component-wizard/src/components'
 import { ManuscriptPage } from 'pubsweet-component-manuscript/src/components'
 import DashboardPage from 'pubsweet-components-faraday/src/components/Dashboard'
 import {
   NotFound,
-  ConfirmationPage,
   ErrorPage,
+  ConfirmationPage,
 } from 'pubsweet-components-faraday/src/components/UIComponents/'
 import {
-  AdminDashboard,
   AdminUsers,
   AdminRoute,
+  AdminDashboard,
 } from 'pubsweet-components-faraday/src/components/Admin'
 import AddEditUser from 'pubsweet-components-faraday/src/components/Admin/AddEditUser'
 import {
-  SignUpInvitationPage,
+  ConfirmAccount,
   ReviewerSignUp,
+  SignUpInvitationPage,
 } from 'pubsweet-components-faraday/src/components/SignUp'
 
 import FaradayApp from './FaradayApp'
@@ -43,7 +43,19 @@ const Routes = () => (
   <FaradayApp>
     <Switch>
       <Route component={LoginPage} exact path="/login" />
-      <Route component={Signup} exact path="/signup" />
+      <Route
+        component={routeParams => (
+          <SignUpInvitationPage
+            subtitle={null}
+            title="Author signup"
+            type="signup"
+            {...routeParams}
+          />
+        )}
+        exact
+        path="/signup"
+      />
+      <Route component={ConfirmAccount} exact path="/confirm-signup" />
       <PrivateRoute component={DashboardPage} exact path="/" />
       <PrivateRoute
         component={ConfirmationPage}
diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js
index 6cb6dc5d3..b04435400 100644
--- a/packages/xpub-faraday/config/default.js
+++ b/packages/xpub-faraday/config/default.js
@@ -69,6 +69,9 @@ module.exports = {
   'invite-reviewer': {
     url: process.env.PUBSWEET_INVITE_REVIEWER_URL || '/invite-reviewer',
   },
+  'confirm-signup': {
+    url: process.env.PUBSWEET_CONFIRM_SIGNUP_URL || '/confirm-signup',
+  },
   roles: {
     global: ['admin', 'editorInChief', 'author', 'handlingEditor'],
     collection: ['handlingEditor', 'reviewer', 'author'],
diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js
index 3f8642489..591ba26b0 100644
--- a/packages/xpub-faraday/config/validations.js
+++ b/packages/xpub-faraday/config/validations.js
@@ -124,6 +124,7 @@ module.exports = {
     editorInChief: Joi.boolean(),
     handlingEditor: Joi.boolean(),
     invitationToken: Joi.string().allow(''),
+    confirmationToken: Joi.string().allow(''),
   },
   team: {
     group: Joi.string(),
-- 
GitLab