From e2ca7ad4f7a0ddc707a1938a430cde949416dc4e Mon Sep 17 00:00:00 2001
From: Alf Eaton <eaton.alf@gmail.com>
Date: Wed, 30 Aug 2017 10:28:36 +0100
Subject: [PATCH] Use new authentication component

---
 packages/component-app/src/components/App.js  | 17 +---
 .../src/components/AuthenticatedPage.js       |  4 +-
 .../src/components/Login.js                   | 12 +--
 .../src/components/LoginPage.js               | 26 ++++--
 .../src/components/LogoutPage.js              | 10 +--
 .../src/components/Signup.js                  | 10 +--
 .../src/components/Signup.local.css           |  3 -
 .../src/components/SignupPage.js              | 26 ++++--
 .../component-authentication/src/index.js     |  7 +-
 .../src/redux/currentUser.js                  | 87 +++++++++++++++++++
 .../src/redux/index.js                        |  3 +
 .../src/redux/login.js                        | 75 ++++++++++++++++
 .../src/redux/logout.js                       | 17 ++++
 .../src/redux/signup.js                       | 70 +++++++++++++++
 packages/xpub-collabra/app/routes.js          |  4 +-
 packages/xpub-collabra/config/components.json |  5 +-
 packages/xpub-collabra/package.json           |  4 -
 17 files changed, 314 insertions(+), 66 deletions(-)
 create mode 100644 packages/component-authentication/src/redux/currentUser.js
 create mode 100644 packages/component-authentication/src/redux/index.js
 create mode 100644 packages/component-authentication/src/redux/login.js
 create mode 100644 packages/component-authentication/src/redux/logout.js
 create mode 100644 packages/component-authentication/src/redux/signup.js

diff --git a/packages/component-app/src/components/App.js b/packages/component-app/src/components/App.js
index 3e86309b0..6a2a93b13 100644
--- a/packages/component-app/src/components/App.js
+++ b/packages/component-app/src/components/App.js
@@ -31,7 +31,7 @@ class App extends React.Component  {
           brandLink="/"
           userName={currentUser ? currentUser.username : null}
           loginLink="/login"
-          logoutLink="/signout"/>
+          logoutLink="/logout"/>
 
         <div className={classes.main}>
           {children}
@@ -41,21 +41,6 @@ class App extends React.Component  {
   }
 }
 
-/*const App = ({ children, currentUser }) => (
-  <div className={classes.root}>
-    <AppBar
-      appName={journal.metadata.name}
-      appLink="/projects" // TODO: make configurable
-      userName={currentUser ? currentUser.username : null}
-      loginLink="/signin"
-      logoutLink="/signout"/>
-
-    <div className={classes.main}>
-      {children}
-    </div>
-  </div>
-)*/
-
 App.propTypes = {
   children: PropTypes.node,
   currentUser: PropTypes.object
diff --git a/packages/component-authentication/src/components/AuthenticatedPage.js b/packages/component-authentication/src/components/AuthenticatedPage.js
index 3279c74e1..f2a62da9d 100644
--- a/packages/component-authentication/src/components/AuthenticatedPage.js
+++ b/packages/component-authentication/src/components/AuthenticatedPage.js
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types'
 import { compose } from 'recompose'
 import { connect } from 'react-redux'
 import { push } from 'react-router-redux'
-import actions from 'pubsweet-client/src/actions'
 import { withRouter } from 'react-router'
+import { getCurrentUser } from '../redux/currentUser'
 
 class AuthenticatedPage extends React.Component {
   componentDidMount () {
@@ -52,7 +52,7 @@ export default compose(
       isFetching: state.currentUser.isFetching
     }),
     {
-      getCurrentUser: actions.getCurrentUser,
+      getCurrentUser,
       push
     }
   ),
diff --git a/packages/component-authentication/src/components/Login.js b/packages/component-authentication/src/components/Login.js
index 23ad332d1..6747e67bb 100644
--- a/packages/component-authentication/src/components/Login.js
+++ b/packages/component-authentication/src/components/Login.js
@@ -1,19 +1,17 @@
 import React from 'react'
-import classnames from 'classnames'
 import { Field } from 'redux-form'
+import { Link } from 'react-router'
 import classes from './Login.local.css'
 
-const Login = ({ loginError, handleSubmit }) => (
+const Login = ({ error, handleSubmit }) => (
   <div className={classes.root}>
     <div className={classes.title}>
       Login
     </div>
 
-    {loginError && <div className={classes.error}>{loginError}</div>}
+    {error && <div className={classes.error}>{error.message}</div>}
 
-    <form
-      onSubmit={handleSubmit}
-      className={classnames({ error: !!loginError, success: !loginError })}>
+    <form onSubmit={handleSubmit}>
       <div>
         <label>
           Username
@@ -32,6 +30,8 @@ const Login = ({ loginError, handleSubmit }) => (
         <button type="submit" className={classes.button}>Login</button>
       </div>
     </form>
+
+    <div>or <Link to="/signup">sign up</Link></div>
   </div>
 )
 
diff --git a/packages/component-authentication/src/components/LoginPage.js b/packages/component-authentication/src/components/LoginPage.js
index 53944b07e..f12210a15 100644
--- a/packages/component-authentication/src/components/LoginPage.js
+++ b/packages/component-authentication/src/components/LoginPage.js
@@ -1,21 +1,29 @@
-import { connect } from 'react-redux'
-import { loginUser } from 'pubsweet-component-login/actions'
-import { reduxForm } from 'redux-form'
+import { reduxForm, SubmissionError } from 'redux-form'
 import { compose } from 'recompose'
+import { connect } from 'react-redux'
+import { login } from '../redux/login'
 import Login from './Login'
 
 // TODO: const redirect = this.props.location.query.next | CONFIG['pubsweet-client']['login-redirect']
 
+const onSubmit = (values, dispatch) => {
+  dispatch(login(values)).catch(error => {
+    if (error.validationErrors) {
+      throw new SubmissionError(error.validationErrors)
+    } else {
+      //
+    }
+  })
+}
+
 export default compose(
   reduxForm({
-    form: 'login'
+    form: 'login',
+    onSubmit
   }),
   connect(
     state => ({
-      loginError: state.error
-    }),
-    {
-      onSubmit: loginUser
-    }
+      error: state.login.error
+    })
   )
 )(Login)
diff --git a/packages/component-authentication/src/components/LogoutPage.js b/packages/component-authentication/src/components/LogoutPage.js
index ec226a3ab..266f83d76 100644
--- a/packages/component-authentication/src/components/LogoutPage.js
+++ b/packages/component-authentication/src/components/LogoutPage.js
@@ -1,21 +1,21 @@
 import React from 'react'
 import { compose } from 'recompose'
 import { connect } from 'react-redux'
-import { logoutUser } from 'pubsweet-component-login/actions'
+import { logout } from '../redux/logout'
 
 class Logout extends React.Component {
   componentDidMount () {
-    const { isAuthenticated, logoutUser } = this.props
+    const { isAuthenticated, logout } = this.props
 
     if (isAuthenticated)  {
-      logoutUser()
+      logout()
     }
   }
 
   render () {
     const { isAuthenticated } = this.props
 
-    return isAuthenticated ? 'Signed out' : 'Signing out…'
+    return isAuthenticated ? <div>Signing out…</div> : <div>Signed out</div>
   }
 }
 
@@ -25,7 +25,7 @@ export default compose(
       isAuthenticated: state.currentUser.isAuthenticated,
     }),
     {
-      logoutUser
+      logout
     }
   )
 )(Logout)
diff --git a/packages/component-authentication/src/components/Signup.js b/packages/component-authentication/src/components/Signup.js
index 0528213bd..d85789102 100644
--- a/packages/component-authentication/src/components/Signup.js
+++ b/packages/component-authentication/src/components/Signup.js
@@ -1,6 +1,6 @@
 import React from 'react'
-import classnames from 'classnames'
 import { Field } from 'redux-form'
+import { Link } from 'react-router'
 import classes from './Signup.local.css'
 
 const Signup = ({ error, handleSubmit }) => (
@@ -9,11 +9,9 @@ const Signup = ({ error, handleSubmit }) => (
       Sign up
     </div>
 
-    {error && <div className={classes.error}>{error}</div>}
+    {error && <div className={classes.error}>{error.message}</div>}
 
-    <form
-      onSubmit={handleSubmit}
-      className={classnames({ error: !!error, success: !error })}>
+    <form onSubmit={handleSubmit}>
       <div>
         <label>
           Username
@@ -39,6 +37,8 @@ const Signup = ({ error, handleSubmit }) => (
         <button type="submit" className={classes.button}>Sign up</button>
       </div>
     </form>
+
+    <div>or <Link to="/login">login</Link></div>
   </div>
 )
 
diff --git a/packages/component-authentication/src/components/Signup.local.css b/packages/component-authentication/src/components/Signup.local.css
index ee7dba231..e69de29bb 100644
--- a/packages/component-authentication/src/components/Signup.local.css
+++ b/packages/component-authentication/src/components/Signup.local.css
@@ -1,3 +0,0 @@
-.error {
-  color: red;
-}
diff --git a/packages/component-authentication/src/components/SignupPage.js b/packages/component-authentication/src/components/SignupPage.js
index c6313ef4c..b6f85aeec 100644
--- a/packages/component-authentication/src/components/SignupPage.js
+++ b/packages/component-authentication/src/components/SignupPage.js
@@ -1,19 +1,27 @@
-import { compose } from 'recompose'
 import { connect } from 'react-redux'
-import { reduxForm } from 'redux-form'
-import { signupUser } from 'pubsweet-component-signup/actions'
+import { compose } from 'recompose'
+import { reduxForm, SubmissionError } from 'redux-form'
+import { signup } from '../redux/signup'
 import Signup from './Signup'
 
+const onSubmit = (values, dispatch) => {
+  dispatch(signup(values)).catch(error => {
+    if (error.validationErrors) {
+      throw new SubmissionError(error.validationErrors)
+    } else {
+      //
+    }
+  })
+}
+
 export default compose(
   reduxForm({
-    form: 'signup'
+    form: 'signup',
+    onSubmit
   }),
   connect(
     state => ({
-      signupError: state.error
-    }),
-    {
-      onSubmit: signupUser
-    }
+      error: state.signup.error
+    })
   )
 )(Signup)
diff --git a/packages/component-authentication/src/index.js b/packages/component-authentication/src/index.js
index 243220c28..c75b39da1 100644
--- a/packages/component-authentication/src/index.js
+++ b/packages/component-authentication/src/index.js
@@ -2,6 +2,11 @@ module.exports = {
   frontend: {
     components: [
       () => require('./components')
-    ]
+    ],
+    reducers: {
+      login: () => require('./redux/login'),
+      signup: () => require('./redux/signup'),
+      currentUser: () => require('./redux/currentUser')
+    }
   },
 }
diff --git a/packages/component-authentication/src/redux/currentUser.js b/packages/component-authentication/src/redux/currentUser.js
new file mode 100644
index 000000000..776b49c7c
--- /dev/null
+++ b/packages/component-authentication/src/redux/currentUser.js
@@ -0,0 +1,87 @@
+import * as api from 'pubsweet-client/src/helpers/api'
+
+import { LOGOUT_SUCCESS } from './logout'
+
+/* constants */
+
+export const GET_CURRENT_USER_REQUEST = 'GET_CURRENT_USER_REQUEST'
+export const GET_CURRENT_USER_SUCCESS = 'GET_CURRENT_USER_SUCCESS'
+export const GET_CURRENT_USER_FAILURE = 'GET_CURRENT_USER_FAILURE'
+
+/* actions */
+
+export const getCurrentUserRequest = () => ({
+  type: GET_CURRENT_USER_REQUEST
+})
+
+export const getCurrentUserSuccess = user => ({
+  type: GET_CURRENT_USER_SUCCESS,
+  user
+})
+
+export const getCurrentUserFailure = error => ({
+  type: GET_CURRENT_USER_FAILURE,
+  error
+})
+
+export const getCurrentUser = () => dispatch => {
+  dispatch(getCurrentUserRequest())
+  return api.get('/users/authenticate').then(
+    user => {
+      dispatch(getCurrentUserSuccess(user))
+    },
+    error => {
+      dispatch(getCurrentUserFailure(error))
+      throw error
+    }
+  )
+}
+
+/* reducer */
+
+const initialState = {
+  isFetching: false,
+  isAuthenticated: false,
+  user: null,
+  error: null
+}
+
+export default (state = initialState, action) => {
+  switch (action.type) {
+    case GET_CURRENT_USER_REQUEST:
+      return {
+        isFetching: true,
+        isAuthenticated: false,
+        user: null,
+        error: null,
+      }
+
+    case GET_CURRENT_USER_FAILURE:
+      return {
+        isFetching: false,
+        isAuthenticated: false,
+        user: null,
+        error: action.error
+      }
+
+    case GET_CURRENT_USER_SUCCESS:
+      return {
+        isFetching: false,
+        isAuthenticated: true,
+        user: action.user,
+        error: null
+      }
+
+    // clear the current user on logout
+    case LOGOUT_SUCCESS:
+      return {
+        isFetching: false,
+        isAuthenticated: false,
+        user: null,
+        error: null,
+      }
+
+    default:
+      return state
+  }
+}
diff --git a/packages/component-authentication/src/redux/index.js b/packages/component-authentication/src/redux/index.js
new file mode 100644
index 000000000..4434d36df
--- /dev/null
+++ b/packages/component-authentication/src/redux/index.js
@@ -0,0 +1,3 @@
+export * as currentUser from './currentUser'
+export * as login from './login'
+export * as signup from './signup'
diff --git a/packages/component-authentication/src/redux/login.js b/packages/component-authentication/src/redux/login.js
new file mode 100644
index 000000000..f0ab01b75
--- /dev/null
+++ b/packages/component-authentication/src/redux/login.js
@@ -0,0 +1,75 @@
+import * as api from 'pubsweet-client/src/helpers/api'
+import { push } from 'react-router-redux'
+import { getCurrentUser } from './currentUser'
+
+// TODO: This will break when rendered on a server
+const localStorage = window.localStorage || undefined
+
+/* constants */
+
+export const LOGIN_REQUEST = 'LOGIN_REQUEST'
+export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
+export const LOGIN_FAILURE = 'LOGIN_FAILURE'
+
+/* actions */
+
+export const loginRequest = credentials => ({
+  type: LOGIN_REQUEST,
+})
+
+export const loginSuccess = user => ({
+  type: LOGIN_SUCCESS,
+})
+
+export const loginFailure = error => ({
+  type: LOGIN_FAILURE,
+  error
+})
+
+export const login = (credentials, redirectTo) => dispatch => {
+  dispatch(loginRequest())
+  return api.create('/users/authenticate', credentials).then(
+    user => {
+      localStorage.setItem('token', user.token)
+      dispatch(loginSuccess())
+      dispatch(getCurrentUser())
+      dispatch(push(redirectTo || '/'))
+    },
+    error => {
+      dispatch(loginFailure(error))
+      throw error
+    }
+  )
+}
+
+/* reducer */
+
+const initialState = {
+  isFetching: false,
+  error: null
+}
+
+export default (state = initialState, action) => {
+  switch (action.type) {
+    case LOGIN_REQUEST:
+      return {
+        isFetching: true,
+        error: null
+      }
+
+    case LOGIN_SUCCESS:
+      return {
+        isFetching: false,
+        error: null
+      }
+
+    case LOGIN_FAILURE:
+      return {
+        isFetching: false,
+        error: action.error
+      }
+
+    default:
+      return state
+  }
+}
diff --git a/packages/component-authentication/src/redux/logout.js b/packages/component-authentication/src/redux/logout.js
new file mode 100644
index 000000000..ea97faf8f
--- /dev/null
+++ b/packages/component-authentication/src/redux/logout.js
@@ -0,0 +1,17 @@
+import { push } from 'react-router-redux'
+
+/* constants */
+
+export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'
+
+/* actions */
+
+export const logoutSuccess = () => ({
+  type: LOGOUT_SUCCESS
+})
+
+export const logout = redirectTo => dispatch => {
+  localStorage.removeItem('token')
+  dispatch(logoutSuccess())
+  if (redirectTo) dispatch(push(redirectTo))
+}
diff --git a/packages/component-authentication/src/redux/signup.js b/packages/component-authentication/src/redux/signup.js
new file mode 100644
index 000000000..823a26987
--- /dev/null
+++ b/packages/component-authentication/src/redux/signup.js
@@ -0,0 +1,70 @@
+import * as api from 'pubsweet-client/src/helpers/api'
+import { login } from './login'
+
+/* constants */
+
+export const SIGNUP_REQUEST = 'SIGNUP_REQUEST'
+export const SIGNUP_SUCCESS = 'SIGNUP_SUCCESS'
+export const SIGNUP_FAILURE = 'SIGNUP_FAILURE'
+
+/* actions */
+
+export const signupRequest = () => ({
+  type: SIGNUP_REQUEST,
+})
+
+export const signupSuccess = user => ({
+  type: SIGNUP_SUCCESS,
+  user
+})
+
+export const signupFailure = error => ({
+  type: SIGNUP_FAILURE,
+  error
+})
+
+export const signup = credentials => dispatch => {
+  dispatch(signupRequest())
+  return api.create('/users', credentials).then(
+    user => {
+      dispatch(signupSuccess(user))
+      dispatch(login(credentials))
+    },
+    error => {
+      dispatch(signupFailure(error))
+      throw error
+    }
+  )
+}
+
+/* reducer */
+
+const initialState = {
+  isFetching: false,
+  error: null,
+}
+
+export default (state = initialState, action) => {
+  switch (action.type) {
+    case SIGNUP_REQUEST:
+      return {
+        isFetching: true,
+        error: null
+      }
+
+    case SIGNUP_SUCCESS:
+      return {
+        isFetching: false,
+        error: null
+      }
+
+    case SIGNUP_FAILURE:
+      return {
+        isFetching: false,
+        error: action.error
+      }
+
+    default:
+      return state
+  }
+}
diff --git a/packages/xpub-collabra/app/routes.js b/packages/xpub-collabra/app/routes.js
index e2e0ed4b8..7678bccb9 100644
--- a/packages/xpub-collabra/app/routes.js
+++ b/packages/xpub-collabra/app/routes.js
@@ -13,11 +13,11 @@ export default (
     <Redirect from="/" to="/dashboard"/>
 
     <Route path="/" component={App}>
-      {/*<Route component={AuthenticatedPage}>*/}
+      <Route component={AuthenticatedPage}>
         <Route path="dashboard" component={DashboardPage}/>
         <Route path="projects/:project/submit" component={SubmitPage}/>
         <Route path="projects/:project/manuscript" component={ManuscriptPage}/>
-      {/*</Route>*/}
+      </Route>
 
       <Route path="signup" component={SignupPage}/>
       <Route path="login" component={LoginPage}/>
diff --git a/packages/xpub-collabra/config/components.json b/packages/xpub-collabra/config/components.json
index 4e0f0b2f8..9da243271 100644
--- a/packages/xpub-collabra/config/components.json
+++ b/packages/xpub-collabra/config/components.json
@@ -1,8 +1,5 @@
 [
-  "pubsweet-component-signup",
-  "pubsweet-component-login",
-  "pubsweet-component-password-reset-frontend",
-  "pubsweet-component-password-reset-backend",
+  "pubsweet-component-xpub-authentication",
   "pubsweet-component-ink-frontend",
   "pubsweet-component-ink-backend"
 ]
diff --git a/packages/xpub-collabra/package.json b/packages/xpub-collabra/package.json
index 161a3f259..acae52041 100644
--- a/packages/xpub-collabra/package.json
+++ b/packages/xpub-collabra/package.json
@@ -17,10 +17,6 @@
     "pubsweet-client": "git+https://gitlab.coko.foundation/pubsweet/pubsweet-client.git",
     "pubsweet-component-ink-backend": "0.0.10",
     "pubsweet-component-ink-frontend": "^0.1.0",
-    "pubsweet-component-login": "^0.3.0",
-    "pubsweet-component-password-reset-backend": "^0.1.0",
-    "pubsweet-component-password-reset-frontend": "^0.1.0",
-    "pubsweet-component-signup": "^0.2.0",
     "pubsweet-component-xpub-app": "^0.0.2",
     "pubsweet-component-xpub-authentication": "^0.0.2",
     "pubsweet-component-xpub-dashboard": "^0.0.2",
-- 
GitLab