diff --git a/packages/component-app/src/components/App.js b/packages/component-app/src/components/App.js index 3e86309b0e7b3c6daac3b67a13a3e0c676d81328..6a2a93b13962bd12d28418f330fb9b8247b33931 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 3279c74e11e2c06e8c667b5942c3b466de0b7da6..f2a62da9d85013e274b441e6b4e5fb7e4aa67471 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 23ad332d1cbf2c1f150522b610464705161772bd..6747e67bb8bc672086f7951289687b70dcd97909 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 53944b07e31dac1b5f714818eb88cfeeb19d2d84..f12210a15d6af7f2e9a0d962effdaf0f684fa2e7 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 ec226a3abf4205416362fc45dd559339d35a0c5a..266f83d766355f91a2cda332ce5d2014b31bedf9 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 0528213bd1727e6587dd48f851deb7c03ce25a2e..d85789102d2414fdeb7983236ddaff8e6d7eddba 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 ee7dba2315cbe848026d243951aa45c13ddf5c12..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 c6313ef4c6820977b9951af7eb184a47c822b94e..b6f85aeecb911148003d1c4ecd5fff1ba4f305f2 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 243220c28587a64c83710316cdaa8ec04ba8495d..c75b39da150fbfbf63739f313d084648a3416a71 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 0000000000000000000000000000000000000000..776b49c7c462e7e6498bad204ee664668f727b4c --- /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 0000000000000000000000000000000000000000..4434d36dfce249f7b9e605285cbe3322a8107f1e --- /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 0000000000000000000000000000000000000000..f0ab01b75a96966b9ee54b3e87f6f1be62860191 --- /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 0000000000000000000000000000000000000000..ea97faf8fd33cb20c7edad5ef2ba4657fc76fdd8 --- /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 0000000000000000000000000000000000000000..823a2698766a19de182ff2aba5ba27d53faefc8d --- /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 e2e0ed4b8d6f43bd7e84d65d22e945b6373c24e3..7678bccb9f8e1d5899839e33530918732597b5eb 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 4e0f0b2f82759fa0d970a6352c6f582a1579002b..9da243271b757ad42f45a7ee918c8305ad30befb 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 161a3f259628685470e898b0b7665542edcfa253..acae52041eb7e39247eec4294b04b05f79669c7c 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",