-
Notifications
You must be signed in to change notification settings - Fork 3
/
PasswordChangeForm.jsx
143 lines (137 loc) · 3.66 KB
/
PasswordChangeForm.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import {
Box,
Button,
CircularProgress,
FormHelperText,
Grid2 as Grid,
TextField
} from "@mui/material";
import PropTypes from "prop-types";
import React from "react";
import { useMaterialForm } from "../utils/form";
import zxcvbn from "zxcvbn";
/**
* Password change form
*/
export default function PasswordChangeForm({ loading, onSubmit }) {
// form state
const {
register,
formState: { errors },
handleSubmit,
watch
} = useMaterialForm({
mode: "onTouched"
});
// watch password so we can validate its duplicate
const password = React.useRef({});
password.current = watch("password", "");
// create score ref so that we avoid multiple calculations
const score = React.useRef();
React.useEffect(() => {
if (!password.current.length) {
score.current = null;
}
});
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<Grid container spacing={2}>
<Grid size={12}>
<TextField
variant="outlined"
required
fullWidth
id="password"
label="New password"
type="password"
autoComplete="new-password"
slotProps={{ input: { "aria-label": "password" } }}
error={Boolean(errors.password)}
helperText={errors?.password?.message}
disabled={loading}
{...register("password", {
required: true,
validate: value => {
const result = zxcvbn(value);
score.current = result.score;
return (
result.score > 2 ||
result.feedback.warning ||
result.feedback.suggestions[0]
);
}
})}
/>
{score.current != null && (
<FormHelperText
style={{ marginLeft: 14, marginRight: 14 }}
error={score.current <= 2}
>
Password strength: {score.current}/4
{score.current <= 2 && ". Minimum required 3+."}
</FormHelperText>
)}
</Grid>
<Grid size={12}>
<TextField
variant="outlined"
required
fullWidth
name="passwordRepeat"
id="passwordRepeat"
label="Confirm new password"
type="password"
autoComplete="new-password"
slotProps={{ input: { "aria-label": "passwordRepeat" } }}
error={Boolean(errors.passwordRepeat)}
helperText={errors?.passwordRepeat?.message}
disabled={loading}
{...register("passwordRepeat", {
required: true,
validate: value =>
value === password.current || "Password does not match"
})}
/>
</Grid>
</Grid>
<Box
sx={{
mb: 1,
mt: 2
}}
>
<Button
type="submit"
fullWidth
id="submit"
variant="contained"
color="primary"
disabled={loading}
endIcon={loading ? <CircularProgress size={24} /> : null}
>
Update password
</Button>
</Box>
</form>
);
}
// prop types
PasswordChangeForm.propTypes = {
/**
* Loading state of the login form. Disables submit button and shows loading indicator.
*/
loading: PropTypes.bool,
/**
* Callback function fired when user submits form.
*
* **Signature**
* ```
* function(data, event) => void
* ```
*
* _data_: Object containing _password, passwordRepeat_
*
* _event_: Synthetic event
*/
onSubmit: PropTypes.func.isRequired
};