Add registration page.
authorAlexandre Segura <mex.zktk@gmail.com>
Mon, 26 Jun 2017 15:45:50 +0200
changeset 89 06f609adfbf8
parent 88 2a861fed6bde
child 90 990f2c928b15
Add registration page.
client/src/App.scss
client/src/actions/authActions.js
client/src/components/Login.js
client/src/components/Register.js
client/src/constants/actionTypes.js
client/src/index.js
client/src/reducers/authReducer.js
client/src/reducers/index.js
client/src/sagas/authSaga.js
client/src/store/configureStore.js
--- a/client/src/App.scss	Mon Jun 26 16:05:47 2017 +0200
+++ b/client/src/App.scss	Mon Jun 26 15:45:50 2017 +0200
@@ -169,7 +169,7 @@
     .start {
         top: 0;
         left: 0;
-        padding: 10px 0 0 10px;
+        padding: 20px 0 0 10px;
     }
     .finish {
         bottom: 0;
@@ -198,3 +198,8 @@
     background-color: #fff;
     padding: 20px 0;
 }
+
+.panel-login,
+.panel-register {
+    margin-top: 60px;
+}
--- a/client/src/actions/authActions.js	Mon Jun 26 16:05:47 2017 +0200
+++ b/client/src/actions/authActions.js	Mon Jun 26 15:45:50 2017 +0200
@@ -13,3 +13,13 @@
     type: types.AUTH_LOGOUT
   };
 }
+
+export const registerSubmit = (username, email, password1, password2) => {
+  return {
+    type: types.AUTH_REGISTER_SUBMIT,
+    username,
+    email,
+    password1,
+    password2
+  };
+}
--- a/client/src/components/Login.js	Mon Jun 26 16:05:47 2017 +0200
+++ b/client/src/components/Login.js	Mon Jun 26 15:45:50 2017 +0200
@@ -15,6 +15,11 @@
     this.props.authActions.loginSubmit(username, password);
   }
 
+  onClickRegister = (e) => {
+    e.preventDefault();
+    this.props.history.push('/register');
+  }
+
   renderError() {
     return (
       <Alert bsStyle="danger">Bad credentials</Alert>
@@ -22,13 +27,18 @@
   }
 
   render() {
+
+    const panelHeader = (
+      <h4 className="text-uppercase text-center">Login</h4>
+    )
+
     return (
       <div>
         <Navbar history={this.props.history} />
         <Grid fluid>
           <Row>
             <Col md={6} mdOffset={3}>
-              <Panel>
+              <Panel header={ panelHeader } className="panel-login">
                 <form>
                   <FormGroup>
                     <ControlLabel>Username</ControlLabel>
@@ -42,6 +52,9 @@
                   <Button block bsStyle="primary" onClick={this.login}>Login</Button>
                 </form>
               </Panel>
+              <p className="text-center">
+                <a className="text-muted" href="/register" onClick={ this.onClickRegister }>Not registered yet? Create an account.</a>
+              </p>
             </Col>
           </Row>
         </Grid>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Register.js	Mon Jun 26 15:45:50 2017 +0200
@@ -0,0 +1,101 @@
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import { Grid, Row, Col, Panel, FormGroup, ControlLabel, FormControl, Button, Alert, HelpBlock } from 'react-bootstrap';
+import '../App.css';
+import Navbar from './Navbar';
+import * as authActions from '../actions/authActions';
+
+class Register extends Component {
+
+  register = () => {
+    const username = this.username.value;
+    const email = this.email.value;
+    const password1 = this.password1.value;
+    const password2 = this.password2.value;
+
+    this.props.authActions.registerSubmit(username, email, password1, password2);
+  }
+
+  onClickLogin = (e) => {
+    e.preventDefault();
+    this.props.history.push('/login');
+  }
+
+  renderError() {
+    return (
+      <Alert bsStyle="danger">Bad credentials</Alert>
+    )
+  }
+
+  renderErrorMessage(errorMessages, fieldname) {
+    if (errorMessages && errorMessages.has(fieldname)) {
+      return errorMessages.get(fieldname).map((message, key) =>
+        <HelpBlock key={ key }>{ message }</HelpBlock>
+      );
+    }
+  }
+
+  render() {
+
+    const panelHeader = (
+      <h4 className="text-uppercase text-center">Register</h4>
+    )
+
+    const errorMessages = this.props.register.get('errorMessages');
+
+    return (
+      <div>
+        <Navbar history={this.props.history} />
+        <Grid fluid>
+          <Row>
+            <Col md={6} mdOffset={3}>
+              <Panel header={ panelHeader } className="panel-login">
+                <form>
+                  <FormGroup validationState={ errorMessages && errorMessages.has('username') ? 'error' : null }>
+                    <ControlLabel>Username</ControlLabel>
+                    <FormControl componentClass="input" type="text" inputRef={ref => { this.username = ref; }} />
+                    { this.renderErrorMessage(errorMessages, 'username') }
+                  </FormGroup>
+                  <FormGroup validationState={ errorMessages && errorMessages.has('email') ? 'error' : null }>
+                    <ControlLabel>Email</ControlLabel>
+                    <FormControl componentClass="input" type="email" inputRef={ref => { this.email = ref; }} />
+                    { this.renderErrorMessage(errorMessages, 'email') }
+                  </FormGroup>
+                  <FormGroup validationState={ errorMessages && errorMessages.has('password1') ? 'error' : null }>
+                    <ControlLabel>Password</ControlLabel>
+                    <FormControl componentClass="input" type="password" inputRef={ref => { this.password1 = ref; }} />
+                    { this.renderErrorMessage(errorMessages, 'password1') }
+                  </FormGroup>
+                  <FormGroup validationState={ errorMessages && errorMessages.has('password2') ? 'error' : null }>
+                    <ControlLabel>Confirm password</ControlLabel>
+                    <FormControl componentClass="input" type="password" inputRef={ref => { this.password2 = ref; }} />
+                    { this.renderErrorMessage(errorMessages, 'password2') }
+                  </FormGroup>
+                  <Button block bsStyle="primary" onClick={this.register}>Register</Button>
+                </form>
+              </Panel>
+              <p className="text-center">
+                <a className="text-muted" href="/login" onClick={ this.onClickLogin }>Already registered? Sign in.</a>
+              </p>
+            </Col>
+          </Row>
+        </Grid>
+      </div>
+    );
+  }
+}
+
+function mapStateToProps(state, props) {
+  return {
+    register: state['register']
+  };
+}
+
+function mapDispatchToProps(dispatch) {
+  return {
+    authActions: bindActionCreators(authActions, dispatch)
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Register);
--- a/client/src/constants/actionTypes.js	Mon Jun 26 16:05:47 2017 +0200
+++ b/client/src/constants/actionTypes.js	Mon Jun 26 15:45:50 2017 +0200
@@ -12,6 +12,12 @@
 export const AUTH_LOGIN_REQUEST = 'AUTH_LOGIN_REQUEST';
 export const AUTH_LOGIN_SUCCESS = 'AUTH_LOGIN_SUCCESS';
 export const AUTH_LOGIN_ERROR = 'AUTH_LOGIN_ERROR';
+
+export const AUTH_REGISTER_SUBMIT = 'AUTH_REGISTER_SUBMIT';
+export const AUTH_REGISTER_REQUEST = 'AUTH_REGISTER_REQUEST';
+export const AUTH_REGISTER_SUCCESS = 'AUTH_REGISTER_SUCCESS';
+export const AUTH_REGISTER_ERROR = 'AUTH_REGISTER_ERROR';
+
 export const AUTH_STORE_TOKEN_ASYNC = 'AUTH_STORE_TOKEN_ASYNC';
 export const AUTH_STORE_TOKEN = 'AUTH_STORE_TOKEN';
 export const AUTH_LOGOUT = 'AUTH_LOGOUT';
--- a/client/src/index.js	Mon Jun 26 16:05:47 2017 +0200
+++ b/client/src/index.js	Mon Jun 26 15:45:50 2017 +0200
@@ -9,6 +9,7 @@
 import SessionList from './components/SessionList';
 import Session from './components/Session';
 import Login from './components/Login';
+import Register from './components/Register';
 import Settings from './components/Settings';
 import './index.css';
 import registerServiceWorker from './registerServiceWorker';
@@ -27,6 +28,7 @@
         <Route exact path="/settings" component={Settings} />
         <Route exact path="/sessions/:id" component={Session} />
         <Route exact path="/sessions" component={SessionList} />
+        <Route exact path="/register" component={Register} />
         <Route exact path="/login" component={Login} />
         <Route exact path="/" component={App} />
       </div>
--- a/client/src/reducers/authReducer.js	Mon Jun 26 16:05:47 2017 +0200
+++ b/client/src/reducers/authReducer.js	Mon Jun 26 15:45:50 2017 +0200
@@ -8,6 +8,7 @@
     case types.AUTH_LOGOUT:
       return false;
     case types.AUTH_LOGIN_SUCCESS:
+    case types.AUTH_REGISTER_SUCCESS:
       return true;
     default:
       return state;
@@ -20,6 +21,7 @@
     case types.AUTH_LOGOUT:
       return null;
     case types.AUTH_LOGIN_SUCCESS:
+    case types.AUTH_REGISTER_SUCCESS:
       return new UserRecord(action.user);
     case types.USER_UPDATE_SETTINGS:
       return state.merge({
@@ -68,3 +70,31 @@
       return state
   }
 }
+
+const registerState = Immutable.Map({
+  loading: false,
+  success: false,
+  error: false,
+  errorMessages: Immutable.Map({})
+});
+
+export const register = (state = registerState, action) => {
+  switch (action.type) {
+    case types.AUTH_REGISTER_REQUEST:
+      return Immutable.Map({
+        loading: true,
+        success: false,
+        error: false,
+      })
+    case types.AUTH_REGISTER_SUCCESS:
+    case types.AUTH_REGISTER_ERROR:
+      return Immutable.Map({
+        loading: false,
+        success: action.type === types.AUTH_REGISTER_SUCCESS,
+        error: action.type === types.AUTH_REGISTER_ERROR,
+        errorMessages: action.type === types.AUTH_REGISTER_ERROR ? Immutable.Map(action.error) : Immutable.Map({})
+      })
+    default:
+      return state
+  }
+}
--- a/client/src/reducers/index.js	Mon Jun 26 16:05:47 2017 +0200
+++ b/client/src/reducers/index.js	Mon Jun 26 15:45:50 2017 +0200
@@ -4,7 +4,7 @@
 
 import notes from './notesReducer';
 import { sessions } from './sessionsReducer';
-import { isAuthenticated, currentUser, login, token } from './authReducer';
+import { isAuthenticated, currentUser, login, register, token } from './authReducer';
 import { autoSubmit } from './miscReducer';
 
 const rootReducer = combineReducers({
@@ -13,6 +13,7 @@
   isAuthenticated,
   currentUser,
   login,
+  register,
   token,
   router: routerReducer,
   autoSubmit
--- a/client/src/sagas/authSaga.js	Mon Jun 26 16:05:47 2017 +0200
+++ b/client/src/sagas/authSaga.js	Mon Jun 26 15:45:50 2017 +0200
@@ -37,6 +37,45 @@
   }
 }
 
+export function* watchRegisterSubmit() {
+  while (true) {
+    const { username, email, password1, password2 } = yield take(types.AUTH_REGISTER_SUBMIT);
+    yield put({ type: types.AUTH_REGISTER_REQUEST, username, email, password1, password2 });
+  }
+}
+
+function* watchRegisterRequest(context) {
+  while (true) {
+    try {
+
+        const { username, email, password1, password2 } = yield take(types.AUTH_REGISTER_REQUEST);
+
+        const client = context.client;
+        const response = yield client.post('/api/auth/registration/', {
+          username,
+          email,
+          password1,
+          password2
+        });
+
+        const actions = [{
+          type: types.AUTH_STORE_TOKEN_ASYNC,
+          token: response.token,
+        }, {
+          type: types.AUTH_REGISTER_SUCCESS,
+          user: response.user,
+          token: response.token,
+        }];
+
+        yield all(actions.map(action => put(action)));
+        context.history.push('/sessions');
+
+    } catch(e) {
+      yield put({ type: types.AUTH_REGISTER_ERROR, error: e });
+    }
+  }
+}
+
 function* watchStoreToken() {
   while (true) {
     const { token } = yield take(types.AUTH_STORE_TOKEN_ASYNC);
@@ -67,6 +106,8 @@
   yield all([
     watchLoginSubmit(),
     watchLoginRequest(context),
+    watchRegisterSubmit(),
+    watchRegisterRequest(context),
     watchStoreToken(),
     watchUpdateSettings(context),
   ])
--- a/client/src/store/configureStore.js	Mon Jun 26 16:05:47 2017 +0200
+++ b/client/src/store/configureStore.js	Mon Jun 26 15:45:50 2017 +0200
@@ -35,7 +35,13 @@
     loading: false,
     success: false,
     error: false,
-  })
+  }),
+  register: Immutable.Map({
+    loading: false,
+    success: false,
+    error: false,
+    errorMessages: Immutable.Map({})
+  }),
 };
 
 const immutableTransformConfig = {