feat: breaking step components and adding custom hook for password validation

This commit is contained in:
Caio Cabral committed 2024-10-31 06:09:59 -04:00
1 parent ea4284a363
commit 686217944d
7 files changed
+545 -471

No files matched your search

+16 -2
View File
@@ -1,8 +1,14 @@
- [x] Change schema to fix validations
- [x] Refactor to apply Single Responsibility Principle to validation (validation is done on joi only)
- [ ] Validation on change
- [ ] Validation on submit
- [ ] Take validation out from the component.
- [x] SetNewPassword
- [ ] Register
- [ ] Refactor on change to deal with step three specificity
- [ ] Fix Input enter not working
- [ ] PasswordPanel
- [ ] Login
- [x] Update check component to accept not highlighted text
- [x] Split some of these user feedbacks? Like:
- [x] Must contain at least one special character,
@@ -11,5 +17,13 @@
- [x] Must contain at least one lower character
- [x] Pass mismatching password user feedback as the other messages, for consistency
- [ ] Get rid of toast message
- [x] Check for confirm password if password changes and confirm is filled, and they do not match anymore
- [ ] Check for confirm password if password changes, and they now match
- [x] Tests
- [x] Valid Password and Matching Confirmation
- [x] Valid Password, Incorrect Confirmation, then Corrects confirmation
- [x] Password Update to Match Confirm Password
- [x] Short Password with Matching Confirmation
- [x] Invalid Password Format, Correct Confirmation
- [x] Empty Password Field with Confirmation Filled
- [x] Confirm Password Left Blank
- [x] Password Change After Successful Match
- [x] Avoid submission in new password if password or confirm empty, and on error
+47 -428
View File
@@ -1,20 +1,19 @@
import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useDispatch } from "react-redux";
import PropTypes from "prop-types";
import { useState, useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { Box, Button, Stack, Typography } from "@mui/material";
import { useDispatch } from "react-redux";
import { StepOne } from "./StepOne";
import { StepTwo } from "./StepTwo";
import { StepThree } from "./StepThree";
import { networkService } from "../../../main";
import { credentials } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { register } from "../../../Features/Auth/authSlice";
import { useParams } from "react-router-dom";
import Background from "../../../assets/Images/background-grid.svg?react";
import Logo from "../../../assets/icons/bwu-icon.svg?react";
import Mail from "../../../assets/icons/mail.svg?react";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import Check from "../../../Components/Check/Check";
import Field from "../../../Components/Inputs/Field";
import { networkService } from "../../../main";
import "../index.css";
/**
@@ -110,389 +109,6 @@ LandingPage.propTypes = {
onSignup: PropTypes.func,
};
/**
* Renders the first step of the sign up process.
*
* @param {Object} props
* @param {Object} props.form - Form state object.
* @param {Object} props.errors - Object containing form validation errors.
* @param {Function} props.onSubmit - Callback function to handle form submission.
* @param {Function} props.onChange - Callback function to handle form input changes.
* @param {Function} props.onBack - Callback function to handle "Back" button click.
* @returns {JSX.Element}
*/
const StepOne = ({ form, errors, onSubmit, onChange, onBack }) => {
const theme = useTheme();
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<>
<Stack
gap={{ xs: theme.spacing(8), sm: theme.spacing(12) }}
textAlign="center"
>
<Box>
<Typography component="h1">Sign Up</Typography>
<Typography>Enter your personal details</Typography>
</Box>
<Box textAlign="left">
<Box
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
mb={theme.spacing(10)}
>
<Field
id="register-firstname-input"
label="Name"
isRequired={true}
placeholder="Jordan"
autoComplete="given-name"
value={form.firstName}
onChange={onChange}
error={errors.firstName}
ref={inputRef}
/>
</Box>
<Box
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
mb={theme.spacing(5)}
>
<Field
id="register-lastname-input"
label="Surname"
isRequired={true}
placeholder="Ellis"
autoComplete="family-name"
value={form.lastName}
onChange={onChange}
error={errors.lastName}
/>
</Box>
</Box>
<Stack
direction="row"
justifyContent="space-between"
>
<Button
variant="outlined"
color="info"
onClick={onBack}
sx={{
px: theme.spacing(5),
"& svg.MuiSvgIcon-root": {
mr: theme.spacing(3),
},
}}
props={{ tabIndex: -1 }}
>
<ArrowBackRoundedIcon />
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={onSubmit}
disabled={(errors.firstName || errors.lastName) && true}
sx={{ width: "30%" }}
>
Continue
</Button>
</Stack>
</Stack>
</>
);
};
StepOne.propTypes = {
form: PropTypes.object,
errors: PropTypes.object,
onSubmit: PropTypes.func,
onChange: PropTypes.func,
onBack: PropTypes.func,
};
/**
* Renders the second step of the sign up process.
*
* @param {Object} props
* @param {Object} props.form - Form state object.
* @param {Object} props.errors - Object containing form validation errors.
* @param {Function} props.onSubmit - Callback function to handle form submission.
* @param {Function} props.onChange - Callback function to handle form input changes.
* @param {Function} props.onBack - Callback function to handle "Back" button click.
* @returns {JSX.Element}
*/
const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => {
const theme = useTheme();
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<>
<Stack
gap={{ xs: theme.spacing(8), sm: theme.spacing(12) }}
textAlign="center"
>
<Box>
<Typography component="h1">Sign Up</Typography>
<Typography>Enter your email address</Typography>
</Box>
<Box textAlign="left">
<Box
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
mb={theme.spacing(5)}
>
<Field
type="email"
id="register-email-input"
label="Email"
isRequired={true}
placeholder="jordan.ellis@domain.com"
autoComplete="email"
value={form.email}
onInput={(e) => (e.target.value = e.target.value.toLowerCase())}
onChange={onChange}
error={errors.email}
ref={inputRef}
/>
</Box>
</Box>
<Stack
direction="row"
justifyContent="space-between"
>
<Button
variant="outlined"
color="info"
onClick={onBack}
sx={{
px: theme.spacing(5),
"& svg.MuiSvgIcon-root": {
mr: theme.spacing(3),
},
}}
props={{ tabIndex: -1 }}
>
<ArrowBackRoundedIcon />
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={onSubmit}
disabled={errors.email && true}
sx={{ width: "30%" }}
>
Continue
</Button>
</Stack>
</Stack>
</>
);
};
StepTwo.propTypes = {
form: PropTypes.object,
errors: PropTypes.object,
onSubmit: PropTypes.func,
onChange: PropTypes.func,
onBack: PropTypes.func,
};
/**
* Renders the third step of the sign up process.
*
* @param {Object} props
* @param {Object} props.form - Form state object.
* @param {Object} props.errors - Object containing form validation errors.
* @param {Function} props.onSubmit - Callback function to handle form submission.
* @param {Function} props.onChange - Callback function to handle form input changes.
* @param {Function} props.onBack - Callback function to handle "Back" button click.
* @returns {JSX.Element}
*/
const StepThree = ({ form, errors, onSubmit, onChange, onBack }) => {
const theme = useTheme();
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<>
<Stack
gap={{ xs: theme.spacing(8), sm: theme.spacing(12) }}
textAlign="center"
>
<Box>
<Typography component="h1">Sign Up</Typography>
<Typography>Create your password</Typography>
</Box>
<Box
textAlign="left"
sx={{
"& .input-error": {
display: "none",
},
}}
>
<Box
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
>
<Field
type="password"
id="register-password-input"
label="Password"
isRequired={true}
placeholder="Create a password"
autoComplete="current-password"
value={form.password}
onChange={onChange}
error={errors.password}
ref={inputRef}
/>
</Box>
<Box
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
>
<Field
type="password"
id="register-confirm-input"
label="Confirm password"
isRequired={true}
placeholder="Confirm your password"
autoComplete="current-password"
value={form.confirm}
onChange={onChange}
error={errors.confirm}
/>
</Box>
<Stack
gap={theme.spacing(4)}
mb={{ xs: theme.spacing(6), sm: theme.spacing(8) }}
>
<Check
text={
<>
<Typography component="span">Must be at least</Typography> 8 characters
long
</>
}
variant={
errors?.password === "Password is required"
? "error"
: form.password === ""
? "info"
: form.password.length < 8
? "error"
: "success"
}
/>
<Check
text={
<>
<Typography component="span">Must contain</Typography> one special
character and a number
</>
}
variant={
errors?.password === "Password is required"
? "error"
: form.password === ""
? "info"
: !/^(?=.*[!@#$%^&*(),.?":{}|])(?=.*\d).+$/.test(form.password)
? "error"
: "success"
}
/>
<Check
text={
<>
<Typography component="span">Must contain at least</Typography> one
upper and lower character
</>
}
variant={
errors?.password === "Password is required"
? "error"
: form.password === ""
? "info"
: !/^(?=.*[A-Z])(?=.*[a-z]).+$/.test(form.password)
? "error"
: "success"
}
/>
</Stack>
</Box>
<Stack
direction="row"
justifyContent="space-between"
>
<Button
variant="outlined"
color="info"
onClick={onBack}
sx={{
px: theme.spacing(5),
"& svg.MuiSvgIcon-root": {
mr: theme.spacing(3),
},
}}
props={{ tabIndex: -1 }}
>
<ArrowBackRoundedIcon />
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={onSubmit}
disabled={errors.email && true}
sx={{ width: "30%" }}
>
Continue
</Button>
</Stack>
</Stack>
</>
);
};
StepThree.propTypes = {
form: PropTypes.object,
errors: PropTypes.object,
onSubmit: PropTypes.func,
onChange: PropTypes.func,
onBack: PropTypes.func,
};
const Register = ({ isSuperAdmin }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
@@ -596,42 +212,45 @@ const Register = ({ isSuperAdmin }) => {
// Attempts account registration
const handleStepThree = async (e) => {
e.preventDefault();
const { password, confirm } = e.target.elements;
console.log(password.value);
console.log(confirm.value);
// let registerForm = {
// ...form,
let registerForm = {
...form,
role: isSuperAdmin ? ["superadmin"] : form.role,
inviteToken: token ? token : "", // Add the token to the request for verification
};
let error = validateForm(registerForm, {
context: { password: form.password },
});
if (error) {
handleError(error);
return;
}
// role: isSuperAdmin ? ["superadmin"] : form.role,
// inviteToken: token ? token : "", // Add the token to the request for verification
// };
// let error = validateForm(registerForm, {
// context: { password: form.password },
// });
// if (error) {
// handleError(error);
// return;
// }
delete registerForm.confirm;
const action = await dispatch(register(registerForm));
if (action.payload.success) {
const authToken = action.payload.data;
localStorage.setItem("token", authToken);
navigate("/");
createToast({
body: "Welcome! Your account was created successfully.",
});
} else {
if (action.payload) {
// dispatch errors
createToast({
body: action.payload.msg,
});
} else {
// unknown errors
createToast({
body: "Unknown error.",
});
}
}
// delete registerForm.confirm;
// const action = await dispatch(register(registerForm));
// if (action.payload.success) {
// const authToken = action.payload.data;
// localStorage.setItem("token", authToken);
// navigate("/");
// createToast({
// body: "Welcome! Your account was created successfully.",
// });
// } else {
// if (action.payload) {
// // dispatch errors
// createToast({
// body: action.payload.msg,
// });
// } else {
// // unknown errors
// createToast({
// body: "Unknown error.",
// });
// }
// }
};
const handleChange = (event) => {
@@ -732,10 +351,10 @@ const Register = ({ isSuperAdmin }) => {
/>
) : step === 3 ? (
<StepThree
form={form}
errors={errors}
/* form={form}
errors={errors} */
onSubmit={handleStepThree}
onChange={handleChange}
/* onChange={handleChange} */
onBack={() => setStep(2)}
/>
) : (
@@ -0,0 +1,121 @@
import { useEffect, useRef } from "react";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Button, Stack, Typography } from "@mui/material";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import Field from "../../../../Components/Inputs/Field";
StepOne.propTypes = {
form: PropTypes.object,
errors: PropTypes.object,
onSubmit: PropTypes.func,
onChange: PropTypes.func,
onBack: PropTypes.func,
};
/**
* Renders the first step of the sign up process.
*
* @param {Object} props
* @param {Object} props.form - Form state object.
* @param {Object} props.errors - Object containing form validation errors.
* @param {Function} props.onSubmit - Callback function to handle form submission.
* @param {Function} props.onChange - Callback function to handle form input changes.
* @param {Function} props.onBack - Callback function to handle "Back" button click.
* @returns {JSX.Element}
*/
function StepOne({ form, errors, onSubmit, onChange, onBack }) {
const theme = useTheme();
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<>
<Stack
gap={{ xs: theme.spacing(8), sm: theme.spacing(12) }}
textAlign="center"
>
<Box>
<Typography component="h1">Sign Up</Typography>
<Typography>Enter your personal details</Typography>
</Box>
<Box textAlign="left">
<Box
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
mb={theme.spacing(10)}
>
<Field
id="register-firstname-input"
label="Name"
isRequired={true}
placeholder="Jordan"
autoComplete="given-name"
value={form.firstName}
onChange={onChange}
error={errors.firstName}
ref={inputRef}
/>
</Box>
<Box
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
mb={theme.spacing(5)}
>
<Field
id="register-lastname-input"
label="Surname"
isRequired={true}
placeholder="Ellis"
autoComplete="family-name"
value={form.lastName}
onChange={onChange}
error={errors.lastName}
/>
</Box>
</Box>
<Stack
direction="row"
justifyContent="space-between"
>
<Button
variant="outlined"
color="info"
onClick={onBack}
sx={{
px: theme.spacing(5),
"& svg.MuiSvgIcon-root": {
mr: theme.spacing(3),
},
}}
props={{ tabIndex: -1 }}
>
<ArrowBackRoundedIcon />
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={onSubmit}
disabled={(errors.firstName || errors.lastName) && true}
sx={{ width: "30%" }}
>
Continue
</Button>
</Stack>
</Stack>
</>
);
}
export { StepOne };
@@ -0,0 +1,162 @@
import { useEffect, useRef /* , useState */ } from "react";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Button, Stack, Typography } from "@mui/material";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import Field from "../../../../Components/Inputs/Field";
import Check from "../../../../Components/Check/Check";
/* import { credentials } from "../../../../Validation/validation"; */
import { useValidatePassword } from "../../hooks/useValidatePassword";
StepThree.propTypes = {
form: PropTypes.object,
errors: PropTypes.object,
onSubmit: PropTypes.func,
/* onChange: PropTypes.func, */
onBack: PropTypes.func,
};
/**
* Renders the third step of the sign up process.
*
* @param {Object} props
* @param {Object} props.form - Form state object.
* @param {Object} props.errors - Object containing form validation errors.
* @param {Function} props.onSubmit - Callback function to handle form submission.
* @param {Function} props.onChange - Callback function to handle form input changes.
* @param {Function} props.onBack - Callback function to handle "Back" button click.
* @returns {JSX.Element}
*/
function StepThree({ onSubmit, onBack }) {
const theme = useTheme();
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
const { handleChange, feedbacks, form, errors } = useValidatePassword();
return (
<>
<Stack
gap={{ xs: theme.spacing(8), sm: theme.spacing(12) }}
textAlign="center"
>
<Box>
<Typography component="h1">Sign Up</Typography>
<Typography>Create your password</Typography>
</Box>
<Box
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
textAlign="left"
sx={{
display: "grid",
gap: theme.spacing(8),
"& .input-error": {
display: "none",
},
}}
>
<Field
type="password"
id="register-password-input"
name="password"
label="Password"
isRequired={true}
placeholder="Create a password"
autoComplete="current-password"
value={form.password}
onChange={handleChange}
error={errors.password}
ref={inputRef}
/>
<Field
type="password"
id="register-confirm-input"
name="confirm"
label="Confirm password"
isRequired={true}
placeholder="Confirm your password"
autoComplete="current-password"
value={form.confirm}
onChange={handleChange}
error={errors.confirm}
/>
<Stack
gap={theme.spacing(4)}
mb={{ xs: theme.spacing(6), sm: theme.spacing(8) }}
>
<Check
noHighlightText={"Must be at least"}
text={"8 characters long"}
variant={feedbacks.length}
/>
<Check
noHighlightText={"Must contain at least"}
text={"one special character"}
variant={feedbacks.special}
/>
<Check
noHighlightText={"Must contain at least"}
text={"one number"}
variant={feedbacks.number}
/>
<Check
noHighlightText={"Must contain at least"}
text={"one upper character"}
variant={feedbacks.uppercase}
/>
<Check
noHighlightText={"Must contain at least"}
text={"one lower character"}
variant={feedbacks.lowercase}
/>
<Check
noHighlightText={"Confirm password and password"}
text={"must match"}
variant={feedbacks.confirm}
/>
</Stack>
<Stack
direction="row"
justifyContent="space-between"
>
<Button
variant="outlined"
color="info"
onClick={onBack}
sx={{
px: theme.spacing(5),
"& svg.MuiSvgIcon-root": {
mr: theme.spacing(3),
},
}}
props={{ tabIndex: -1 }}
>
<ArrowBackRoundedIcon />
Back
</Button>
<Button
type="submit"
variant="contained"
color="primary"
/* onClick={onSubmit} */
disabled={errors.email && true}
sx={{ width: "30%" }}
>
Continue
</Button>
</Stack>
</Box>
</Stack>
</>
);
}
export { StepThree };
@@ -0,0 +1,104 @@
import { useEffect, useRef } from "react";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Button, Stack, Typography } from "@mui/material";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import Field from "../../../../Components/Inputs/Field";
StepTwo.propTypes = {
form: PropTypes.object,
errors: PropTypes.object,
onSubmit: PropTypes.func,
onChange: PropTypes.func,
onBack: PropTypes.func,
};
/**
* Renders the second step of the sign up process.
*
* @param {Object} props
* @param {Object} props.form - Form state object.
* @param {Object} props.errors - Object containing form validation errors.
* @param {Function} props.onSubmit - Callback function to handle form submission.
* @param {Function} props.onChange - Callback function to handle form input changes.
* @param {Function} props.onBack - Callback function to handle "Back" button click.
* @returns {JSX.Element}
*/
function StepTwo({ form, errors, onSubmit, onChange, onBack }) {
const theme = useTheme();
const inputRef = useRef(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<>
<Stack
gap={{ xs: theme.spacing(8), sm: theme.spacing(12) }}
textAlign="center"
>
<Box>
<Typography component="h1">Sign Up</Typography>
<Typography>Enter your email address</Typography>
</Box>
<Box textAlign="left">
<Box
component="form"
noValidate
spellCheck={false}
onSubmit={onSubmit}
mb={theme.spacing(5)}
>
<Field
type="email"
id="register-email-input"
label="Email"
isRequired={true}
placeholder="jordan.ellis@domain.com"
autoComplete="email"
value={form.email}
onInput={(e) => (e.target.value = e.target.value.toLowerCase())}
onChange={onChange}
error={errors.email}
ref={inputRef}
/>
</Box>
</Box>
<Stack
direction="row"
justifyContent="space-between"
>
<Button
variant="outlined"
color="info"
onClick={onBack}
sx={{
px: theme.spacing(5),
"& svg.MuiSvgIcon-root": {
mr: theme.spacing(3),
},
}}
props={{ tabIndex: -1 }}
>
<ArrowBackRoundedIcon />
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={onSubmit}
disabled={errors.email && true}
sx={{ width: "30%" }}
>
Continue
</Button>
</Stack>
</Stack>
</>
);
}
export { StepTwo };
+28 -41
View File
@@ -35,19 +35,12 @@ const SetNewPassword = () => {
const handleSubmit = async (e) => {
e.preventDefault();
const passwordForm = { ...form };
const { error } = credentials.validate(passwordForm, {
const { error } = credentials.validate(form, {
abortEarly: false,
context: { password: form.password },
});
if (error) {
const newErrors = {};
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
setErrors(newErrors);
createToast({
body:
error.details && error.details.length > 0
@@ -55,59 +48,49 @@ const SetNewPassword = () => {
: "Error validating data.",
});
} else {
delete passwordForm.confirm;
const action = await dispatch(setNewPassword({ token: token, form: passwordForm }));
const action = await dispatch(setNewPassword({ token, form }));
if (action.payload.success) {
navigate("/new-password-confirmed");
createToast({
body: "Your password was reset successfully.",
});
} else {
if (action.payload) {
// dispatch errors
createToast({
body: action.payload.msg,
});
} else {
// unknown errors
createToast({
body: "Unknown error.",
});
}
const errorMessage = action.payload ? action.payload.msg : "Unknown error";
createToast({
body: errorMessage,
});
}
}
};
const handleChange = (event) => {
//update form state
const { value, name } = event.target;
setForm((prev) => ({ ...prev, [name]: value }));
//validate
const validateValue = { [name]: value };
const validateOptions = { abortEarly: false, context: { password: form.password } };
if (name === "password" && form.confirm.length > 0) {
validateValue.confirm = form.confirm;
validateOptions.context = value;
validateOptions.context = { password: value };
} else if (name === "confirm") {
validateValue.password = form.password;
}
const { error } = credentials.validate(validateValue, validateOptions);
//update errors
//fazer o map com path e message
const { error } = credentials.validate(validateValue, validateOptions);
const errors = error?.details.map((error) => ({
message: error.message,
path: error.path[0],
message: error.message,
}));
const errorsByPath = errors
? errors.reduce((acc, { path, message }) => {
if (!acc[path]) {
acc[path] = [];
}
acc[path].push(message);
return acc;
}, {})
: { [name]: [] };
setErrors((prev) => ({ ...prev, ...errorsByPath }));
const errorsByPath =
errors &&
errors.reduce((acc, { path, message }) => {
if (!acc[path]) {
acc[path] = [];
}
acc[path].push(message);
return acc;
}, {});
setErrors(() => (errorsByPath ? { ...errorsByPath } : {}));
};
const getFeedbackStatus = (field, criteria) => {
@@ -228,7 +211,7 @@ const SetNewPassword = () => {
placeholder="••••••••"
value={form.password}
onChange={handleChange}
/* error={errors.password} */
error={errors.password}
/>
</Box>
<Box
@@ -246,7 +229,7 @@ const SetNewPassword = () => {
placeholder="••••••••"
value={form.confirm}
onChange={handleChange}
/* error={errors.confirm} */
error={errors.confirm}
/>
</Box>
<Stack
@@ -290,7 +273,11 @@ const SetNewPassword = () => {
color="primary"
loading={isLoading}
onClick={handleSubmit}
disabled={Object.keys(errors).length !== 0}
disabled={
form.password.length === 0 ||
form.confirm.length === 0 ||
Object.keys(errors).length !== 0
}
sx={{ width: "100%", maxWidth: 400 }}
>
Reset password
@@ -0,0 +1,67 @@
import { useState } from "react";
import { credentials } from "../../../Validation/validation";
function useValidatePassword() {
const [errors, setErrors] = useState({});
const [form, setForm] = useState({
password: "",
confirm: "",
});
const handleChange = (event) => {
const { value, name } = event.target;
setForm((prev) => ({ ...prev, [name]: value }));
const validateValue = { [name]: value };
const validateOptions = { abortEarly: false, context: { password: form.password } };
if (name === "password" && form.confirm.length > 0) {
validateValue.confirm = form.confirm;
validateOptions.context = { password: value };
} else if (name === "confirm") {
validateValue.password = form.password;
}
const { error } = credentials.validate(validateValue, validateOptions);
const errors = error?.details.map((error) => ({
path: error.path[0],
message: error.message,
}));
const errorsByPath =
errors &&
errors.reduce((acc, { path, message }) => {
if (!acc[path]) {
acc[path] = [];
}
acc[path].push(message);
return acc;
}, {});
setErrors(() => (errorsByPath ? { ...errorsByPath } : {}));
};
const getFeedbackStatus = (field, criteria) => {
const fieldErrors = errors[field];
const isFieldEmpty = form[field].length === 0;
const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty");
const isCorrect = !isFieldEmpty && !hasError;
if (isCorrect) {
return "success";
} else if (hasError) {
return "error";
} else {
return "info";
}
};
const feedbacks = {
length: getFeedbackStatus("password", "length"),
special: getFeedbackStatus("password", "special"),
number: getFeedbackStatus("password", "number"),
uppercase: getFeedbackStatus("password", "uppercase"),
lowercase: getFeedbackStatus("password", "lowercase"),
confirm: getFeedbackStatus("confirm", "different"),
};
return { handleChange, feedbacks, form, errors };
}
export { useValidatePassword };