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