Add registration page.
--- 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 = {