Introduce group creation.
authorAlexandre Segura <mex.zktk@gmail.com>
Tue, 27 Jun 2017 18:12:10 +0200
changeset 100 6fd752d98933
parent 99 18fa4a1fa9e9
child 101 e165aa89ac82
Introduce group creation.
client/src/actions/authActions.js
client/src/api/WebAnnotationSerializer.js
client/src/components/SessionForm.js
client/src/constants/actionTypes.js
client/src/reducers/authReducer.js
client/src/reducers/index.js
client/src/sagas/groupSaga.js
client/src/store/configureStore.js
--- a/client/src/actions/authActions.js	Tue Jun 27 18:11:40 2017 +0200
+++ b/client/src/actions/authActions.js	Tue Jun 27 18:12:10 2017 +0200
@@ -23,3 +23,14 @@
     password2
   };
 }
+
+export const createGroup = (name) => {
+  const group = {
+    name
+  };
+
+  return {
+    type: types.GROUP_CREATE_ASYNC,
+    group,
+  };
+}
--- a/client/src/api/WebAnnotationSerializer.js	Tue Jun 27 18:11:40 2017 +0200
+++ b/client/src/api/WebAnnotationSerializer.js	Tue Jun 27 18:12:10 2017 +0200
@@ -1,5 +1,3 @@
-import Note from '../store/noteRecord';
-
 class WebAnnotationSerializer {
 
   static serialize = (note) => {
--- a/client/src/components/SessionForm.js	Tue Jun 27 18:11:40 2017 +0200
+++ b/client/src/components/SessionForm.js	Tue Jun 27 18:12:10 2017 +0200
@@ -1,13 +1,30 @@
 import React, { Component } from 'react';
 import { connect } from 'react-redux';
 import { bindActionCreators } from 'redux';
-import { Panel, FormGroup, ControlLabel, FormControl } from 'react-bootstrap';
+import { Panel, FormGroup, ControlLabel, FormControl, Button, InputGroup, HelpBlock } from 'react-bootstrap';
 import '../App.css';
 import * as sessionsActions from '../actions/sessionsActions';
+import * as authActions from '../actions/authActions';
 import _ from 'lodash';
 
 class SessionForm extends Component {
 
+  state = {
+    createGroup: false
+  }
+
+  toggleCreateGroup = (e) => {
+    e.preventDefault();
+    const { createGroup } = this.state;
+    this.setState({ createGroup: !createGroup });
+  }
+
+  onClickCreateGroup = (e) => {
+    e.preventDefault();
+    const groupName = this.groupName.value;
+    this.props.authActions.createGroup(groupName);
+  }
+
   onChange = (e) => {
     const { name, value } = e.target;
     const changes = { [name]: value }
@@ -18,6 +35,46 @@
     this.props.sessionsActions.updateSession(this.props.currentSession, changes);
   }, 750)
 
+  renderCreateGroup = () => {
+    const { createGroup } = this.props;
+    const hasErrors = true === createGroup.get('error') && createGroup.get('errorMessages').has('name');
+
+    let errors = [];
+    if (hasErrors) {
+      const errorMessages = createGroup.get('errorMessages').toArray();
+      errors = errorMessages.map((message, key) => {
+        return (
+          <HelpBlock key={ key }>{ message }</HelpBlock>
+        )
+      })
+    }
+
+    if (this.state.createGroup) {
+      return (
+        <FormGroup validationState={ hasErrors ? 'error' : null }>
+          <InputGroup>
+            <FormControl
+              type="text"
+              placeholder="Enter a name for your group"
+              inputRef={ ref => { this.groupName = ref; } } />
+            <InputGroup.Button>
+              <Button bsStyle="primary" onClick={ this.onClickCreateGroup }>Create</Button>
+            </InputGroup.Button>
+          </InputGroup>
+          { errors }
+          <hr />
+          <Button onClick={ this.toggleCreateGroup }>Cancel</Button>
+        </FormGroup>
+      )
+    }
+
+    return (
+      <FormGroup>
+        <Button onClick={ this.toggleCreateGroup }>Create a group</Button>
+      </FormGroup>
+    )
+  }
+
   render() {
 
     if (!this.props.currentSession) {
@@ -51,6 +108,9 @@
               inputRef={ ref => { this.description = ref; } }
             />
           </FormGroup>
+          <FormGroup>
+            { this.renderCreateGroup() }
+          </FormGroup>
         </form>
       </Panel>
     );
@@ -60,12 +120,14 @@
 function mapStateToProps(state, props) {
   return {
     currentSession: props.session,
+    createGroup: state.createGroup
   };
 }
 
 function mapDispatchToProps(dispatch) {
   return {
     sessionsActions: bindActionCreators(sessionsActions, dispatch),
+    authActions: bindActionCreators(authActions, dispatch),
   }
 }
 
--- a/client/src/constants/actionTypes.js	Tue Jun 27 18:11:40 2017 +0200
+++ b/client/src/constants/actionTypes.js	Tue Jun 27 18:12:10 2017 +0200
@@ -25,6 +25,10 @@
 export const AUTH_LOGOUT = 'AUTH_LOGOUT';
 export const AUTH_DEAUTHENTICATE = 'AUTH_DEAUTHENTICATE';
 
+export const GROUP_CREATE_ASYNC = 'GROUP_CREATE_ASYNC';
+export const GROUP_CREATE_SUCCESS = 'GROUP_CREATE_SUCCESS';
+export const GROUP_CREATE_ERROR = 'GROUP_CREATE_ERROR';
+
 export const USER_UPDATE_SETTINGS_ASYNC = 'USER_UPDATE_SETTINGS_ASYNC';
 export const USER_UPDATE_SETTINGS = 'USER_UPDATE_SETTINGS'
 export const USER_TOGGLE_AUTO_SUBMIT = 'USER_TOGGLE_AUTO_SUBMIT';
--- a/client/src/reducers/authReducer.js	Tue Jun 27 18:11:40 2017 +0200
+++ b/client/src/reducers/authReducer.js	Tue Jun 27 18:12:10 2017 +0200
@@ -85,3 +85,31 @@
       return state
   }
 }
+
+export const groups = (state = Immutable.List([]), action) => {
+  switch (action.type) {
+    default:
+      return state
+  }
+}
+
+export const createGroup = (state = asyncRequest, action) => {
+  switch (action.type) {
+    case types.GROUP_CREATE_ASYNC:
+      return Immutable.Map({
+        loading: true,
+        success: false,
+        error: false,
+      })
+    case types.GROUP_CREATE_SUCCESS:
+    case types.GROUP_CREATE_ERROR:
+      return Immutable.Map({
+        loading: false,
+        success: action.type === types.GROUP_CREATE_SUCCESS,
+        error: action.type === types.GROUP_CREATE_ERROR,
+        errorMessages: action.type === types.GROUP_CREATE_ERROR ? Immutable.Map(action.error) : Immutable.Map({})
+      })
+    default:
+      return state
+  }
+}
--- a/client/src/reducers/index.js	Tue Jun 27 18:11:40 2017 +0200
+++ b/client/src/reducers/index.js	Tue Jun 27 18:12:10 2017 +0200
@@ -4,7 +4,7 @@
 
 import notes from './notesReducer';
 import { sessions } from './sessionsReducer';
-import { isAuthenticated, currentUser, login, register, token } from './authReducer';
+import { isAuthenticated, currentUser, login, register, token, groups, createGroup } from './authReducer';
 import { autoSubmit } from './miscReducer';
 
 const rootReducer = combineReducers({
@@ -16,7 +16,9 @@
   register,
   token,
   router: routerReducer,
-  autoSubmit
+  autoSubmit,
+  groups,
+  createGroup
 });
 
 export default rootReducer;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/sagas/groupSaga.js	Tue Jun 27 18:12:10 2017 +0200
@@ -0,0 +1,21 @@
+import { put, take, all } from 'redux-saga/effects'
+import * as types from '../constants/actionTypes';
+
+function* watchCreateGroup(context) {
+  while (true) {
+    const { group } = yield take(types.GROUP_CREATE_ASYNC);
+    const client = context.client;
+    try {
+      const response = yield client.post('/api/auth/group/', group);
+      yield put({ type: types.GROUP_CREATE_SUCCESS, group: response });
+    } catch (e) {
+      yield put({ type: types.GROUP_CREATE_ERROR, error: e });
+    }
+  }
+}
+
+export default function* rootSaga(context) {
+  yield all([
+    watchCreateGroup(context),
+  ])
+}
--- a/client/src/store/configureStore.js	Tue Jun 27 18:11:40 2017 +0200
+++ b/client/src/store/configureStore.js	Tue Jun 27 18:12:10 2017 +0200
@@ -1,5 +1,6 @@
 import rootReducer from '../reducers';
 import rootAuthSaga from '../sagas/authSaga';
+import rootGroupSaga from '../sagas/groupSaga';
 import networkSaga from '../sagas/networkSaga';
 import { compose, createStore, applyMiddleware } from 'redux';
 import { routerMiddleware } from 'react-router-redux';
@@ -28,12 +29,14 @@
 const defaultState = {
   sessions: Immutable.List([]),
   notes: Immutable.List([]),
+  groups: Immutable.List([]),
   isAuthenticated: false,
   currentUser: null,
   token: '',
   autoSubmit: false,
   login: asyncRequest,
   register: asyncRequest,
+  createGroup: asyncRequest
 };
 
 const immutableTransformConfig = {
@@ -77,6 +80,7 @@
   }
 
   saga.run(rootAuthSaga, context);
+  saga.run(rootGroupSaga, context);
 
   store.dispatch(offlineConfigInitialized({ client: apiClient }))