Skip to content

Commit ee30fe9

Browse files
author
Martin Krulis
committed
Dialog form for creating users added. Dialog is triggered by a button on Users page, visible only to superadmin.
1 parent 88fc8a7 commit ee30fe9

File tree

9 files changed

+349
-20
lines changed

9 files changed

+349
-20
lines changed
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { FormattedMessage } from 'react-intl';
4+
import { reduxForm, Field, change } from 'redux-form';
5+
import { Alert } from 'react-bootstrap';
6+
import isEmail from 'validator/lib/isEmail';
7+
8+
import SubmitButton from '../SubmitButton';
9+
import { validateRegistrationData } from '../../../redux/modules/users';
10+
import { TextField, PasswordField, PasswordStrength } from '../Fields';
11+
12+
const CreateUserForm = ({
13+
submitting,
14+
handleSubmit,
15+
onSubmit,
16+
dirty,
17+
submitFailed = false,
18+
submitSucceeded = false,
19+
asyncValidating,
20+
invalid,
21+
reset,
22+
}) => (
23+
<div>
24+
<Field
25+
name="firstName"
26+
component={TextField}
27+
maxLength={100}
28+
required
29+
ignoreDirty
30+
label={<FormattedMessage id="app.editUserProfile.firstName" defaultMessage="Given Name:" />}
31+
/>
32+
33+
<Field
34+
name="lastName"
35+
component={TextField}
36+
maxLength={255}
37+
required
38+
ignoreDirty
39+
label={<FormattedMessage id="app.editUserProfile.lastName" defaultMessage="Surname:" />}
40+
/>
41+
42+
<Field
43+
name="email"
44+
component={TextField}
45+
autoComplete="off"
46+
maxLength={255}
47+
ignoreDirty
48+
label={<FormattedMessage id="app.changePasswordForm.email" defaultMessage="Email:" />}
49+
/>
50+
51+
<Field
52+
name="password"
53+
component={PasswordField}
54+
autoComplete="off"
55+
ignoreDirty
56+
label={<FormattedMessage id="app.changePasswordForm.password" defaultMessage="New Password:" />}
57+
/>
58+
59+
<Field
60+
name="passwordStrength"
61+
component={PasswordStrength}
62+
label={<FormattedMessage id="app.changePasswordForm.passwordStrength" defaultMessage="Password Strength:" />}
63+
/>
64+
65+
<Field
66+
name="passwordConfirm"
67+
component={PasswordField}
68+
ignoreDirty
69+
label={<FormattedMessage id="app.changePasswordForm.passwordCheck" defaultMessage="New Password (again):" />}
70+
/>
71+
72+
{submitFailed && (
73+
<Alert bsStyle="danger">
74+
<FormattedMessage id="generic.operationFailed" defaultMessage="Operation failed. Please try again later." />
75+
</Alert>
76+
)}
77+
78+
<div className="text-center">
79+
<SubmitButton
80+
id="createUser"
81+
handleSubmit={handleSubmit(data => onSubmit(data).then(reset))}
82+
submitting={submitting}
83+
dirty={dirty}
84+
invalid={invalid}
85+
hasSucceeded={submitSucceeded}
86+
hasFailed={submitFailed}
87+
asyncValidating={asyncValidating}
88+
messages={{
89+
submit: <FormattedMessage id="generic.create" defaultMessage="Create" />,
90+
submitting: <FormattedMessage id="generic.creating" defaultMessage="Creating..." />,
91+
success: <FormattedMessage id="generic.created" defaultMessage="Created" />,
92+
}}
93+
/>
94+
</div>
95+
</div>
96+
);
97+
98+
CreateUserForm.propTypes = {
99+
handleSubmit: PropTypes.func.isRequired,
100+
onSubmit: PropTypes.func.isRequired,
101+
asyncValidate: PropTypes.func.isRequired,
102+
submitFailed: PropTypes.bool,
103+
submitSucceeded: PropTypes.bool,
104+
dirty: PropTypes.bool,
105+
submitting: PropTypes.bool,
106+
asyncValidating: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
107+
invalid: PropTypes.bool,
108+
pristine: PropTypes.bool,
109+
reset: PropTypes.func,
110+
};
111+
112+
const validate = ({ firstName, lastName, email, password, passwordConfirm }) => {
113+
const errors = {};
114+
115+
if (!firstName) {
116+
errors.firstName = (
117+
<FormattedMessage
118+
id="app.editUserProfile.validation.emptyFirstName"
119+
defaultMessage="First name cannot be empty."
120+
/>
121+
);
122+
}
123+
124+
if (firstName && firstName.length < 2) {
125+
errors.firstName = (
126+
<FormattedMessage
127+
id="app.editUserProfile.validation.shortFirstName"
128+
defaultMessage="First name must contain at least 2 characters."
129+
/>
130+
);
131+
}
132+
133+
if (!lastName) {
134+
errors.lastName = (
135+
<FormattedMessage id="app.editUserProfile.validation.emptyLastName" defaultMessage="Last name cannot be empty." />
136+
);
137+
}
138+
139+
if (lastName && lastName.length < 2) {
140+
errors.lastName = (
141+
<FormattedMessage
142+
id="app.editUserProfile.validation.shortLastName"
143+
defaultMessage="Last name must contain at least 2 characters."
144+
/>
145+
);
146+
}
147+
148+
if (email && isEmail(email) === false) {
149+
errors.email = (
150+
<FormattedMessage
151+
id="app.editUserProfile.validation.emailNotValid"
152+
defaultMessage="E-mail address is not valid."
153+
/>
154+
);
155+
} else if (!email) {
156+
errors.email = (
157+
<FormattedMessage
158+
id="app.editUserProfile.validation.emptyEmail"
159+
defaultMessage="E-mail address cannot be empty."
160+
/>
161+
);
162+
}
163+
164+
if (!password) {
165+
errors.password = (
166+
<FormattedMessage
167+
id="app.createUserForm.validation.emptyPassword"
168+
defaultMessage="The password cannot be empty."
169+
/>
170+
);
171+
}
172+
173+
if (!passwordConfirm) {
174+
errors.passwordConfirm = (
175+
<FormattedMessage
176+
id="app.createUserForm.validation.emptyPassword"
177+
defaultMessage="The password cannot be empty."
178+
/>
179+
);
180+
}
181+
182+
if (password !== passwordConfirm) {
183+
errors.passwordConfirm = (
184+
<FormattedMessage
185+
id="app.editUserProfile.validation.passwordsDontMatch"
186+
defaultMessage="Passwords do not match."
187+
/>
188+
);
189+
}
190+
191+
return errors;
192+
};
193+
194+
const asyncValidate = ({ email, password = '' }, dispatch) => {
195+
if (password === '') {
196+
dispatch(change('edit-user-profile', 'passwordStrength', null));
197+
return Promise.resolve();
198+
}
199+
200+
return new Promise((resolve, reject) =>
201+
dispatch(validateRegistrationData(email, password))
202+
.then(res => res.value)
203+
.then(({ usernameIsFree, passwordScore }) => {
204+
var errors = {};
205+
if (!usernameIsFree) {
206+
errors['email'] = (
207+
<FormattedMessage
208+
id="app.createUserForm.validation.emailTaken"
209+
defaultMessage="This email address is already taken by someone else."
210+
/>
211+
);
212+
}
213+
214+
dispatch(change('edit-user-profile', 'passwordStrength', passwordScore));
215+
216+
if (Object.keys(errors).length > 0) {
217+
throw errors;
218+
}
219+
})
220+
.then(resolve())
221+
.catch(errors => reject(errors))
222+
);
223+
};
224+
225+
export default reduxForm({
226+
form: 'edit-user-profile',
227+
validate,
228+
asyncValidate,
229+
asyncBlurFields: ['email', 'password', 'passwordConfirm'],
230+
enableReinitialize: true,
231+
keepDirtyOnReinitialize: false,
232+
})(CreateUserForm);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import CreateUserForm from './CreateUserForm';
2+
export default CreateUserForm;

src/locales/cs.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@
150150
"app.createGroup.isPublic": "Veřejná (skupinu vidí všichni uživatelé a můžou se do ní přidat)",
151151
"app.createGroup.publicStats": "Studenti mohou vidět dosažené body ostatních",
152152
"app.createGroup.threshold": "Minimální procentuální hranice potřebná ke splnění tohoto kurzu:",
153+
"app.createUserForm.validation.emailTaken": "Tato emailová adresa již patří jinému uživateli.",
154+
"app.createUserForm.validation.emptyPassword": "Heslo nemůže zůstat prázdné.",
153155
"app.dashboard.memberOf": "Skupiny kde jste členem",
154156
"app.dashboard.sisGroupsStudent": "Přihlásit se do skupin asociovaných s kurzy ze SISu UK",
155157
"app.dashboard.sisGroupsStudentExplain": "SIS kurzy, které máte zapsané v určitém semestru a které mají odpovídající skupiny v ReCodExu.",
@@ -1224,6 +1226,7 @@
12241226
"app.userList.userDeactivated": "Uživatelský účet byl deaktivován. Uživatel se nemůže přihlásit.",
12251227
"app.userName.externalIds": "Externí identifikátory",
12261228
"app.userSwitching.loginAs": "Přihlásit jako",
1229+
"app.users.createUser": "Vytvořit uživatele",
12271230
"app.users.description": "Procházení všech uživatelů ReCodExu.",
12281231
"app.users.listTitle": "Uživatelé",
12291232
"app.users.takeOver": "Přihlásit jako",
@@ -1238,8 +1241,10 @@
12381241
"generic.acknowledge": "Beru na vědomí",
12391242
"generic.author": "Autor",
12401243
"generic.close": "Zavřít",
1244+
"generic.create": "Vytvořit",
12411245
"generic.created": "Vytvořeno",
12421246
"generic.createdAt": "Vytvořeno",
1247+
"generic.creating": "Vytváření...",
12431248
"generic.delete": "Smazat",
12441249
"generic.deleteFailed": "Smazání selhalo",
12451250
"generic.deleted": "Smazáno",

src/locales/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@
150150
"app.createGroup.isPublic": "Public (everyone can see and join this group)",
151151
"app.createGroup.publicStats": "Students can see statistics of each other",
152152
"app.createGroup.threshold": "Minimum percent of the total points count needed to complete the course:",
153+
"app.createUserForm.validation.emailTaken": "This email address is already taken by someone else.",
154+
"app.createUserForm.validation.emptyPassword": "The password cannot be empty.",
153155
"app.dashboard.memberOf": "Groups you are member of",
154156
"app.dashboard.sisGroupsStudent": "Join Groups Associated with UK SIS Courses",
155157
"app.dashboard.sisGroupsStudentExplain": "SIS courses you are enrolled to in particular semesters and which have correspondig groups in ReCodEx.",
@@ -1224,6 +1226,7 @@
12241226
"app.userList.userDeactivated": "The user account was deactivated. The user may not sign in.",
12251227
"app.userName.externalIds": "External identifiers",
12261228
"app.userSwitching.loginAs": "Login as",
1229+
"app.users.createUser": "Create User",
12271230
"app.users.description": "Browse all ReCodEx users.",
12281231
"app.users.listTitle": "Users",
12291232
"app.users.takeOver": "Login as",
@@ -1238,8 +1241,10 @@
12381241
"generic.acknowledge": "Acknowledge",
12391242
"generic.author": "Author",
12401243
"generic.close": "Close",
1244+
"generic.create": "Create",
12411245
"generic.created": "Created",
12421246
"generic.createdAt": "Created at",
1247+
"generic.creating": "Creating...",
12431248
"generic.delete": "Delete",
12441249
"generic.deleteFailed": "Delete Failed",
12451250
"generic.deleted": "Deleted",

src/locales/whitelist_en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@
150150
"app.createGroup.isPublic",
151151
"app.createGroup.publicStats",
152152
"app.createGroup.threshold",
153+
"app.createUserForm.validation.emailTaken",
154+
"app.createUserForm.validation.emptyPassword",
153155
"app.dashboard.memberOf",
154156
"app.dashboard.sisGroupsStudent",
155157
"app.dashboard.sisGroupsStudentExplain",
@@ -1224,6 +1226,7 @@
12241226
"app.userList.userDeactivated",
12251227
"app.userName.externalIds",
12261228
"app.userSwitching.loginAs",
1229+
"app.users.createUser",
12271230
"app.users.description",
12281231
"app.users.listTitle",
12291232
"app.users.takeOver",
@@ -1238,8 +1241,10 @@
12381241
"generic.acknowledge",
12391242
"generic.author",
12401243
"generic.close",
1244+
"generic.create",
12411245
"generic.created",
12421246
"generic.createdAt",
1247+
"generic.creating",
12431248
"generic.delete",
12441249
"generic.deleteFailed",
12451250
"generic.deleted",

0 commit comments

Comments
 (0)