Skip to content

Commit 5d6cd09

Browse files
committed
496: Add ChangePasswordDialog
* Update buildPasswordValidation to take username param * Add selectedUser state * Some styling
1 parent 08ba0b2 commit 5d6cd09

File tree

7 files changed

+150
-10
lines changed

7 files changed

+150
-10
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { yupResolver } from "@hookform/resolvers/yup";
2+
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField, Typography } from '@material-ui/core';
3+
import React from 'react';
4+
import { useForm } from 'react-hook-form';
5+
import * as Yup from 'yup';
6+
import { updateUser } from "../../../../utils/api";
7+
import { buildPasswordValidation } from '../../Validations';
8+
9+
10+
export default function ChangePasswordDialog(props) {
11+
const { onClose, notifyResult, token, user } = props;
12+
const { username } = user;
13+
14+
const validationSchema = Yup.object().shape({
15+
password: buildPasswordValidation(username),
16+
confirmPassword: Yup.string().oneOf([Yup.ref("password")], "Passwords must match"),
17+
});
18+
19+
const { register, handleSubmit, formState: { errors }, trigger } = useForm({
20+
resolver: yupResolver(validationSchema),
21+
});
22+
23+
const onSubmitHandler = (data) => {
24+
const { password } = data;
25+
26+
updateUser({ username, password }, token)
27+
.then((res) => {
28+
if (res === "Updated") {
29+
notifyResult({ success: true, message: `Password for user ${username} successfully changed!` });
30+
} else {
31+
notifyResult({ success: false, message: res });
32+
}
33+
})
34+
.catch(e => {
35+
console.warn(e)
36+
notifyResult({ success: false, message: e })
37+
});
38+
onClose();
39+
}
40+
41+
return (
42+
<Dialog
43+
hideBackdrop
44+
fullWidth
45+
open
46+
>
47+
<DialogTitle style={{ fontSize: "20px" }}>Change Password</DialogTitle>
48+
<form onSubmit={handleSubmit(onSubmitHandler)}>
49+
<DialogContent>
50+
<Typography>
51+
{`Fill out the fields below to update the password for user with username: ${user.username}.`}
52+
</Typography>
53+
<TextField
54+
{...register("password")}
55+
margin="dense"
56+
id="password-input"
57+
label="Password"
58+
onBlur={() => trigger("password")}
59+
type="password"
60+
fullWidth
61+
/>
62+
{errors.password &&
63+
<Typography color="error">{errors.password.message}</Typography>
64+
}
65+
<TextField
66+
{...register("confirmPassword")}
67+
margin="dense"
68+
id="confirm-password-input"
69+
label="Confirm Password"
70+
onBlur={() => trigger("confirmPassword")}
71+
type="password"
72+
fullWidth
73+
/>
74+
{errors.confirmPassword &&
75+
<Typography color="error">{errors.confirmPassword.message}</Typography>
76+
}
77+
<DialogActions>
78+
<Button
79+
onClick={onClose}
80+
>
81+
Cancel
82+
</Button>
83+
<Button
84+
type="submit"
85+
>
86+
Submit
87+
</Button>
88+
</DialogActions>
89+
</DialogContent>
90+
</form>
91+
</Dialog>
92+
)
93+
94+
}

src/client/src/pages/UserManagement/Components/Dialog/NewUserDialog.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default function NewUserDialog(props) {
1919
confirmPassword: Yup.string().oneOf([Yup.ref("password")], "Passwords must match"),
2020
});
2121

22-
const { register, handleSubmit, formState: { errors }, reset, trigger } = useForm({
22+
const { register, handleSubmit, formState: { errors }, trigger } = useForm({
2323
resolver: yupResolver(validationSchema),
2424
});
2525

src/client/src/pages/UserManagement/Components/Dialog/UserDialog.jsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import React from 'react';
2+
import ChangePasswordDialog from './ChangePasswordDialog';
23
import NewUserDialog from './NewUserDialog';
34

45
export const DialogTypes = {
56
NewUser: 'new-user',
7+
ChangePassword: 'change-password',
68
}
79

810
export default function UserDialog(props) {
9-
const { notifyResult, onClose, type, token, updateUsers } = props;
11+
const {
12+
notifyResult,
13+
onClose,
14+
selectedUser,
15+
type,
16+
token,
17+
updateUsers
18+
} = props;
1019

1120
switch (type) {
1221
case DialogTypes.NewUser:
@@ -16,9 +25,16 @@ export default function UserDialog(props) {
1625
onClose={onClose}
1726
token={token}
1827
updateUsers={updateUsers}
19-
>
20-
"Hello"
21-
</NewUserDialog>
28+
/>
29+
)
30+
case DialogTypes.ChangePassword:
31+
return (
32+
<ChangePasswordDialog
33+
notifyResult={notifyResult}
34+
onClose={onClose}
35+
user={selectedUser}
36+
token={token}
37+
/>
2238
)
2339
default:
2440
return null

src/client/src/pages/UserManagement/Components/UserRow.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import {
44
TableRow
55
} from "@material-ui/core";
66
import React from 'react';
7+
import { DialogTypes } from "./Dialog/UserDialog";
78

89
export default function UserRow(props) {
910
const { active, full_name: fullName, role, username } = props.user;
11+
const openDialog = props.openDialog;
1012

1113
return (
1214
<TableRow>
@@ -16,7 +18,12 @@ export default function UserRow(props) {
1618
<TableCell>{active === 'Y' ? 'Yes' : 'No'}</TableCell>
1719
<TableCell>
1820
<Button>
19-
Update
21+
Update User
22+
</Button>
23+
</TableCell>
24+
<TableCell>
25+
<Button onClick={() => openDialog({ type: DialogTypes.ChangePassword, user: props.user })}>
26+
Change Password
2027
</Button>
2128
</TableCell>
2229
</TableRow>

src/client/src/pages/UserManagement/UserManagement.jsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default function UserManagement(props) {
2525
const [resultBanner, setResultBanner] = React.useState(undefined);
2626
const [dialogOpen, setDialogOpen] = React.useState(false);
2727
const [dialogType, setDialogType] = React.useState(undefined)
28+
const [selectedUser, setSelectedUser] = React.useState(undefined);
2829
const { access_token: token } = props;
2930

3031
React.useEffect(() => {
@@ -63,13 +64,15 @@ export default function UserManagement(props) {
6364
}
6465

6566
const openDialog = (opts) => {
67+
setSelectedUser(opts.user);
6668
setDialogType(opts.type);
6769
setDialogOpen(true);
6870
}
6971

7072
const closeDialog = () => {
7173
setDialogOpen(false);
7274
setDialogType(null);
75+
setSelectedUser(null);
7376
}
7477

7578
return (
@@ -112,11 +115,12 @@ export default function UserManagement(props) {
112115
<TableCell>Role</TableCell>
113116
<TableCell>Active</TableCell>
114117
<TableCell></TableCell>
118+
<TableCell></TableCell>
115119
</TableRow>
116120
</TableHead>
117121
<TableBody>
118122
{_.map(users, user => {
119-
return <UserRow user={user} key={user.username} />
123+
return <UserRow user={user} openDialog={openDialog} key={user.username} />
120124
})}
121125
</TableBody>
122126
</Table>
@@ -126,6 +130,7 @@ export default function UserManagement(props) {
126130
<UserDialog
127131
notifyResult={notifyResult}
128132
onClose={closeDialog}
133+
selectedUser={selectedUser}
129134
token={token}
130135
type={dialogType}
131136
updateUsers={updateUsers}

src/client/src/pages/UserManagement/Validations.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const buildRoleValidation = () => {
2828
.required("Role is required")
2929
}
3030

31-
export const buildPasswordValidation = () => {
31+
export const buildPasswordValidation = (username) => {
3232
return Yup.string()
3333
.test(
3434
"no-disallowed-words",
@@ -39,7 +39,7 @@ export const buildPasswordValidation = () => {
3939
}
4040

4141
const lowercasePassword = value.toLowerCase();
42-
const lowercaseUsername = context.parent.username.toLowerCase()
42+
const lowercaseUsername = username || context.parent.username.toLowerCase()
4343
return [...DISALLOWED_WORDS, lowercaseUsername].every((word) => !lowercasePassword.includes(word))
4444
})
4545
.min(12, "Password must contain at least 12 letters")

src/client/src/utils/api.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const fetchUsers = async ({ token }) => {
1616
}
1717

1818
export const createUser = async (userData, token) => {
19-
const api = "/api/admin/user/create"
19+
const api = "/api/admin/user/create";
2020
const requestOptions = {
2121
method: 'POST',
2222
headers: {
@@ -25,6 +25,24 @@ export const createUser = async (userData, token) => {
2525
body: JSON.stringify(userData)
2626
};
2727

28+
return fetch(api, requestOptions)
29+
.then((res) => res.json())
30+
.catch((e) => {
31+
console.warn(e)
32+
throw new Error(e);
33+
})
34+
}
35+
36+
export const updateUser = async (userData, token) => {
37+
const api = "/api/admin/user/update";
38+
const requestOptions = {
39+
method: 'POST',
40+
headers: {
41+
'Authorization': 'Bearer ' + token
42+
},
43+
body: JSON.stringify(userData)
44+
}
45+
2846
return fetch(api, requestOptions)
2947
.then((res) => res.json())
3048
.catch((e) => {

0 commit comments

Comments
 (0)