{
+ this.handleSubmit(e);
+ };
+
+ handleCancel = () => {
+ const {
+ form,
+ resetUniqueEmail: resetUniqueEmailAction,
+ resetgroup: resetgroupAction,
+ } = this.props;
+ const { resetFields } = form;
+ resetFields();
+ resetUniqueEmailAction();
+ resetgroupAction();
+ };
+
+ handleSubmit = e => {
+ const { allowAddUsedEmail } = this.state;
+ const { form, addTrainerToGroup: addTrainerToGroupAction } = this.props;
+ e.preventDefault();
+ form.validateFieldsAndScroll((err, values) => {
+ if (!err) {
+ addTrainerToGroupAction({
+ ...values,
+ newUser: !allowAddUsedEmail,
+ localLead: values.localLead.key,
+ localLeadName: values.localLead.label,
+ });
+ }
+ });
+ };
+
+ handleEmailBlur = e => {
+ const { checkUniqeEmail: checkUniqeEmailActionCreator } = this.props;
+ const { value } = e.target;
+ if (value) {
+ checkUniqeEmailActionCreator(value);
+ }
+ };
+
+ handleSuccessOk = () => {
+ const {
+ form,
+ resetUniqueEmail: resetUniqueEmailAction,
+ resetgroup: resetgroupAction,
+ } = this.props;
+ const { resetFields } = form;
+ resetFields();
+ resetUniqueEmailAction();
+ resetgroupAction();
+ };
+
+ render() {
+ const {
+ form: { getFieldDecorator },
+ localLeads,
+ checkedUserInfo,
+ isEmailUnique,
+ } = this.props;
+
+ return (
+
+
+
+
+ Good news, {checkedUserInfo.name} (
+ {checkedUserInfo.email}) has created an account for
+ themselves already.
+
+
+ Would you like to add this trainer to your group?
+
+
+
+
+
+
+
+ );
+ }
+}
+
+const LocalLeadSelect = ({ getFieldDecorator, localLeads }) => (
+
+ -
+ {getFieldDecorator('localLead', {
+ rules: [
+ {
+ required: true,
+ message: 'Please select your local lead',
+ },
+ ],
+ })(
+
+ )}
+
+
+);
+
+const mapStateToProps = state => {
+ return {
+ localLeads: state.fetchedData.localLeadsList,
+ isAuthenticated: state.auth.isAuthenticated,
+ isEmailUnique: state.auth.isEmailUnique,
+ checkedUserInfo: state.auth.checkedUserInfo,
+ group: state.groups,
+ };
+};
+
+export default connect(
+ mapStateToProps,
+ {
+ fetchLocalLeads,
+ checkUniqeEmail,
+ addTrainerToGroup,
+ resetgroup,
+ resetUniqueEmail,
+ }
+)(Form.create({ name: 'AddTrainer' })(AddTrainer));
diff --git a/v2/client/src/constants/actionTypes.js b/v2/client/src/constants/actionTypes.js
index 33e5bd6b6..14a94ee4a 100644
--- a/v2/client/src/constants/actionTypes.js
+++ b/v2/client/src/constants/actionTypes.js
@@ -11,7 +11,13 @@ export const FETCH_TRINER_RESULTS_SUCCESS = 'FETCH_TRINER_RESULTS_SUCCESS';
export const FETCH_TRINER_RESULTS_FAILURE = 'FETCH_TRINER_RESULTS_FAILURE';
export const FETCH_BEHAVIORAL_INSIGHT = 'FETCH_BEHAVIORAL_INSIGHT';
export const FETCH_LOCAL_LEADS = 'FETCH_LOCAL_LEADS';
-export const CHECK_UNIQUE_EMAIL = 'CHECK_UNIQUE_EMAIL';
+export const CHECK_UNIQUE_EMAIL_UNIQUE = 'CHECK_UNIQUE_EMAIL_UNIQUE';
+export const CHECK_UNIQUE_EMAIL_UNUNIQUE = 'CHECK_UNIQUE_EMAIL_UNUNIQUE';
export const FETCH_STATS = 'FETCH_STATS';
export const USER_AUTHENTICATED = 'USER_AUTHENTICATED';
export const USER_UNAUTHENTICATED = 'USER_UNAUTHENTICATED';
+export const ADD_TRAINER_TO_GROUP_SUCCESS = 'ADD_TRAINER_TO_GROUP_SUCCESS';
+export const ADD_TRAINER_TO_GROUP_FAIL = 'ADD_TRAINER_TO_GROUP_FAIL';
+export const RESET_GROUPS_STATE = 'RESET_GROUPS_STATE';
+export const RESET_UNIQUE_EMAIL = 'RESET_UNIQUE_EMAIL';
+export const CHECK_UNIQUE_EMAIL_ERROR = 'CHECK_UNIQUE_EMAIL_ERROR';
diff --git a/v2/client/src/index.css b/v2/client/src/index.css
index 8c3053ca6..f36181001 100644
--- a/v2/client/src/index.css
+++ b/v2/client/src/index.css
@@ -56,3 +56,14 @@ html {
font-family: "Roboto", sans-serif;
font-size: 1rem;
}
+
+/* style for the Select component in AddTrainer view */
+.add-trainer__select .ant-select-selection {
+ white-space: nowrap;
+ height: 50px;
+ overflow: auto
+}
+
+.ant-select-selection .ant-select-selection__rendered{
+ line-height: 48px
+}
\ No newline at end of file
diff --git a/v2/client/src/index.js b/v2/client/src/index.js
index efac53776..0c41c1e41 100644
--- a/v2/client/src/index.js
+++ b/v2/client/src/index.js
@@ -10,6 +10,9 @@ import 'antd/lib/button/style/index.css';
import 'antd/lib/collapse/style/index.css';
import 'antd/lib/table/style/index.css';
import 'antd/lib/input/style/index.css';
+import 'antd/lib/form/style/index.css';
+import 'antd/lib/select/style/index.css';
+import 'antd/lib/modal/style/index.css';
import './index.css';
diff --git a/v2/client/src/reducers/authReducer.js b/v2/client/src/reducers/authReducer.js
index c9fb89bfb..6d378978d 100644
--- a/v2/client/src/reducers/authReducer.js
+++ b/v2/client/src/reducers/authReducer.js
@@ -1,16 +1,23 @@
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
- CHECK_UNIQUE_EMAIL,
+ CHECK_UNIQUE_EMAIL_UNIQUE,
+ CHECK_UNIQUE_EMAIL_UNUNIQUE,
USER_AUTHENTICATED,
USER_UNAUTHENTICATED,
+ RESET_UNIQUE_EMAIL,
+ CHECK_UNIQUE_EMAIL_ERROR,
} from '../constants/actionTypes';
const initialState = {
isAuthenticated: null,
isEmailUnique: null,
loaded: false,
+ name: '',
+ email: '',
role: null,
+ checkedUserInfo: {},
+ error: null,
};
export default function(state = initialState, action) {
@@ -33,13 +40,31 @@ export default function(state = initialState, action) {
loaded: true,
};
- case CHECK_UNIQUE_EMAIL:
+ case CHECK_UNIQUE_EMAIL_UNIQUE:
return {
...state,
- isEmailUnique: payload,
+ isEmailUnique: true,
+ checkedUserInfo: {},
loaded: true,
};
+ case CHECK_UNIQUE_EMAIL_UNUNIQUE:
+ return {
+ ...state,
+ isEmailUnique: false,
+ checkedUserInfo: payload,
+ loaded: true,
+ };
+
+ case CHECK_UNIQUE_EMAIL_ERROR:
+ return {
+ ...state,
+ isEmailUnique: false,
+ checkedUserInfo: {},
+ loaded: true,
+ error: payload,
+ };
+
case USER_AUTHENTICATED:
return {
...state,
@@ -55,6 +80,13 @@ export default function(state = initialState, action) {
loaded: true,
};
+ case RESET_UNIQUE_EMAIL:
+ return {
+ ...state,
+ checkedUserInfo: {},
+ isEmailUnique: null,
+ };
+
default:
return state;
}
diff --git a/v2/client/src/reducers/groups.js b/v2/client/src/reducers/groups.js
new file mode 100644
index 000000000..155256a86
--- /dev/null
+++ b/v2/client/src/reducers/groups.js
@@ -0,0 +1,25 @@
+import * as types from '../constants/actionTypes';
+
+const initState = {
+ loaded: false,
+ success: null,
+ error: null,
+ data: null,
+};
+
+export default (state = initState, action) => {
+ const { type, payload } = action;
+ switch (type) {
+ case types.ADD_TRAINER_TO_GROUP_SUCCESS:
+ return { loaded: true, error: false, success: payload.success };
+
+ case types.ADD_TRAINER_TO_GROUP_FAIL:
+ return { loaded: true, error: payload, success: false };
+
+ case types.RESET_GROUPS_STATE:
+ return initState;
+
+ default:
+ return state;
+ }
+};
diff --git a/v2/client/src/reducers/index.js b/v2/client/src/reducers/index.js
index d2531cccf..e9ef09bd5 100644
--- a/v2/client/src/reducers/index.js
+++ b/v2/client/src/reducers/index.js
@@ -6,6 +6,7 @@ import authReducer from './authReducer';
import errorReducer from './errorReducer';
import fetchedDataReducer from './fetchedDataReducer';
import statsReducer from './statsReducer';
+import groupsReducer from './groups';
export default combineReducers({
auth: authReducer,
@@ -13,5 +14,6 @@ export default combineReducers({
fetchedData: fetchedDataReducer,
stats: statsReducer,
results: userResults,
+ groups: groupsReducer,
behavioralInsight: behavioralInsightReducer,
});
diff --git a/v2/server/__test__/api/addTrainerToGroup.js b/v2/server/__test__/api/addTrainerToGroup.js
new file mode 100644
index 000000000..08245b52d
--- /dev/null
+++ b/v2/server/__test__/api/addTrainerToGroup.js
@@ -0,0 +1,138 @@
+const request = require('supertest');
+const mongoose = require('mongoose');
+const User = require('./../../database/models/User');
+
+const buildDB = require('./../../database/data/test');
+const app = require('./../../app');
+
+describe('Tesing for addTrainerToGroup route', () => {
+ beforeAll(async () => {
+ // build dummy data
+ await buildDB();
+ });
+
+ afterAll(async () => {
+ await mongoose.disconnect();
+ });
+
+ beforeEach(async () => {
+ // build dummy data
+ await buildDB();
+ });
+
+ test('test with adding new trainer', async done => {
+ const localLead = await User.findOne({ email: 'nisha.sharma@phe.gov.uk' });
+
+ const localLeadLoginData = {
+ email: 'nisha.sharma@phe.gov.uk',
+ password: '123456',
+ };
+
+ const data = {
+ email: 'new-trainer@connnect5.com',
+ name: 'New',
+ region: 'North East',
+ localLead: localLead._id,
+ localLeadName: localLead.name,
+ newUser: true,
+ };
+
+ request(app)
+ .post('/api/login')
+ .send(localLeadLoginData)
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end((err, result) => {
+ const token = result.headers['set-cookie'][0].split(';')[0];
+ request(app)
+ .post('/api/users/local-leads/group')
+ .set('Cookie', [token])
+ .send(data)
+ .expect(200)
+ .end(async (error, response) => {
+ const updatedLocalLead = await User.findOne({
+ email: 'nisha.sharma@phe.gov.uk',
+ });
+
+ const createdTrainer = await User.findOne({
+ email: 'new-trainer@connnect5.com',
+ });
+
+ // new trainer must be stored in DB
+ expect(createdTrainer.role).toBe('trainer');
+
+ // new trainer must be belong to the local lead we sent
+ expect(createdTrainer.localLead).toEqual(localLead._id);
+
+ // local lead group must hove new additional trainer
+ expect(localLead.trainersGroup).toHaveLength(3);
+ expect(updatedLocalLead.trainersGroup).toHaveLength(4);
+
+ // success message
+ expect(response.body.success).toBe(
+ `New has been added to ${localLead.name}'s group`
+ );
+
+ done(err);
+ });
+ });
+ });
+
+ test('test with exists trainer', async done => {
+ const localLead = await User.findOne({ email: 'clare.baguley@hee.nhs.uk' });
+
+ const localLeadLoginData = {
+ email: 'clare.baguley@hee.nhs.uk',
+ password: '123456',
+ };
+
+ const trainer = await User.findOne({ role: 'trainer' });
+
+ const data = {
+ email: trainer.email,
+ localLead: localLead._id,
+ localLeadName: localLead.name,
+ newUser: false,
+ };
+
+ request(app)
+ .post('/api/login')
+ .send(localLeadLoginData)
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end((err, result) => {
+ const token = result.headers['set-cookie'][0].split(';')[0];
+ request(app)
+ .post('/api/users/local-leads/group')
+ .set('Cookie', [token])
+ .send(data)
+ .expect(200)
+ .end(async (error, response) => {
+ const updatedLocalLead = await User.findOne({
+ email: 'clare.baguley@hee.nhs.uk',
+ });
+
+ const updatedTrainer = await User.findOne({
+ email: trainer.email,
+ });
+
+ // new trainer must be stored in DB
+ expect(updatedTrainer.role).toBe('trainer');
+
+ // new trainer must be belong to the local lead we sent
+ expect(updatedTrainer.localLead).toEqual(localLead._id);
+
+ // local lead group must hove new additional trainer
+ expect(localLead.trainersGroup).toHaveLength(0);
+ expect(updatedLocalLead.trainersGroup).toHaveLength(1);
+
+ // success message
+ expect(response.body.success).toBe(
+ `${trainer.name} has been added to ${localLead.name}'s group`
+ );
+
+ done(err);
+ });
+ });
+ });
+});
diff --git a/v2/server/__test__/api/storeSurvey.js b/v2/server/__test__/api/storeSurvey.js
index e035ad619..fc1b38501 100644
--- a/v2/server/__test__/api/storeSurvey.js
+++ b/v2/server/__test__/api/storeSurvey.js
@@ -26,8 +26,8 @@ describe('Test /survey/submit/', () => {
const questions = await Question.find({ surveyType });
const postcodeQuestion = await Question.findOne({
- surveyType: surveyType,
- text: 'Please enter the postcode where you are active'
+ surveyType,
+ text: 'Please enter the postcode where you are active',
});
const formState = {};
@@ -80,7 +80,7 @@ describe('Test /survey/submit/', () => {
PIN,
sessionId,
surveyType,
- formState
+ formState,
};
request(app)
@@ -114,7 +114,7 @@ describe('Test /survey/submit/', () => {
PIN,
sessionId,
surveyType,
- formState
+ formState,
};
request(app)
@@ -186,7 +186,7 @@ describe('Test /survey/submit/', () => {
PIN,
sessionId,
surveyType,
- formState
+ formState,
};
request(app)
diff --git a/v2/server/controllers/users/addTrainerToGroup.js b/v2/server/controllers/users/addTrainerToGroup.js
new file mode 100644
index 000000000..30588a285
--- /dev/null
+++ b/v2/server/controllers/users/addTrainerToGroup.js
@@ -0,0 +1,47 @@
+const boom = require('boom');
+
+const { createNewTrainer } = require('./../../database/queries/users/trainer');
+
+const {
+ addTrainertoGroup,
+} = require('./../../database/queries/users/localLead');
+
+const { getUserByEmail, update } = require('./../../database/queries/users');
+
+module.exports = async (req, res, next) => {
+ const { name, email, newUser, localLead, region, localLeadName } = req.body;
+ const { user } = req;
+
+ if (user.role !== 'localLead') {
+ return next(boom.unauthorized());
+ }
+
+ try {
+ let trainer = await getUserByEmail(email);
+
+ if (newUser && !trainer) {
+ trainer = await createNewTrainer({
+ name,
+ email,
+ password: '123456',
+ region,
+ localLead,
+ role: 'trainer',
+ });
+ }
+
+ if (!trainer) {
+ return next(boom.notFound('This email is not used'));
+ }
+
+ await addTrainertoGroup(localLead, trainer._id);
+
+ await update(trainer._id, { localLead });
+
+ return res.json({
+ success: `${trainer.name} has been added to ${localLeadName}'s group`,
+ });
+ } catch (error) {
+ return next(boom.badImplementation());
+ }
+};
diff --git a/v2/server/controllers/users/checkUniqueEmail.js b/v2/server/controllers/users/checkUniqueEmail.js
index b90e9eee9..407745a04 100644
--- a/v2/server/controllers/users/checkUniqueEmail.js
+++ b/v2/server/controllers/users/checkUniqueEmail.js
@@ -11,7 +11,13 @@ module.exports = (req, res, next) => {
return getUserByEmail(email)
.then(user => {
if (user) {
- res.json({ isUnique: false });
+ res.json({
+ isUnique: false,
+ id: user._id,
+ role: user.role,
+ name: user.name,
+ email: user.email,
+ });
} else {
res.json({ isUnique: true });
}
diff --git a/v2/server/database/queries/users/index.js b/v2/server/database/queries/users/index.js
index d8875436b..8db410025 100644
--- a/v2/server/database/queries/users/index.js
+++ b/v2/server/database/queries/users/index.js
@@ -2,3 +2,4 @@ const User = require('./../../models/User');
module.exports.getUserByEmail = email => User.findOne({ email });
module.exports.getUserById = id => User.findById(id);
+module.exports.update = (id, data) => User.findByIdAndUpdate(id, data);
diff --git a/v2/server/middlewares/validateSurveyInput.js b/v2/server/middlewares/validateSurveyInput.js
index 07f766bdb..57f937c28 100644
--- a/v2/server/middlewares/validateSurveyInput.js
+++ b/v2/server/middlewares/validateSurveyInput.js
@@ -8,18 +8,17 @@ module.exports = async data => {
// query the Question model using data.surveyType to get a list of the question ids for that survey which are required
const questions = await Question.find({
surveyType: data.surveyType,
- isRequired: true
+ isRequired: true,
});
// Postcode validation
const postcodeQuestion = await Question.findOne({
surveyType: data.surveyType,
- text: 'Please enter the postcode where you are active'
+ text: 'Please enter the postcode where you are active',
});
const validPostcode = postcode => {
- console.log('postcode', postcode);
postcode = postcode.replace(/\s/g, '');
const regex = /^(?:gir(?: *0aa)?|[a-pr-uwyz](?:[a-hk-y]?[0-9]+|[0-9][a-hjkstuw]|[a-hk-y][0-9][abehmnprv-y])(?: *[0-9][abd-hjlnp-uw-z]{2})?)$/gim;
diff --git a/v2/server/router/users.js b/v2/server/router/users.js
index ad8528319..3acf34bd7 100644
--- a/v2/server/router/users.js
+++ b/v2/server/router/users.js
@@ -15,7 +15,9 @@ const {
getListOfTrainers,
getAllTrainersAndLeads,
} = require('../controllers/users/user');
+
const { getDashboardStats } = require('../controllers/users/all');
+const addTrainerToGroup = require('../controllers/users/addTrainerToGroup');
const getUserInfo = require('../controllers/users/getUserInfo');
// check eamil route if the route doen't contain email query
@@ -30,6 +32,7 @@ router.post('/all/dashboard', authentication(), getDashboardStats);
router.post('/trainers', signUpTrainer);
+router.post('/users/local-leads/group', authentication(), addTrainerToGroup);
router.get('/local-leads', getLocalLeads);
router.get('/users/:id/results', getUserResults);