260 lines
8.4 KiB
JavaScript
260 lines
8.4 KiB
JavaScript
import { LoadingButton } from '@mui/lab';
|
|
import {
|
|
Box,
|
|
IconButton,
|
|
InputLabel,
|
|
TextField,
|
|
Typography,
|
|
} from '@mui/material';
|
|
import { useFormik } from 'formik';
|
|
import * as Yup from 'yup';
|
|
import { useStyles } from './resetPasswordStyles';
|
|
import { useRef, useState } from 'react';
|
|
import PasswordValidation from '../Login/component/PasswordValidation';
|
|
import { VisibilityOffOutlined, VisibilityOutlined } from '@mui/icons-material';
|
|
import useQuery from '../../hooks/useQuery';
|
|
import {
|
|
passwordLengthRegex,
|
|
passwordLetterRegex,
|
|
passwordNumberRegex,
|
|
passwordSpacesRegex,
|
|
} from '../../utils/regex';
|
|
import { pushNotification } from '../../utils/notification';
|
|
import { NOTIFICATION } from '../../constants';
|
|
import { resetPassword } from '../../services/auth.services';
|
|
|
|
const validationSchema = Yup.object().shape({
|
|
password: Yup.string()
|
|
.matches(passwordLengthRegex, 'Password must be at least 8 characters')
|
|
.matches(passwordLetterRegex, 'Password must contain at least one letter')
|
|
.matches(passwordNumberRegex, 'Password must contain at least one number')
|
|
.matches(passwordSpacesRegex, 'Password must not start or end with a space')
|
|
.required('Password is required'),
|
|
confirmPassword: Yup.string()
|
|
.oneOf([Yup.ref('password'), null], 'Passwords must match')
|
|
.required('Passwords must match'),
|
|
});
|
|
|
|
function ResetPasswordForm({ onSubmit }) {
|
|
const query = useQuery();
|
|
const classes = useStyles();
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
|
|
const fieldRefs = {
|
|
password: useRef(null),
|
|
confirmPassword: useRef(null),
|
|
};
|
|
|
|
const formik = useFormik({
|
|
initialValues: { password: '', confirmPassword: '' },
|
|
validationSchema,
|
|
onSubmit: (values) => handleSubmit(values, formik),
|
|
});
|
|
|
|
const handleSubmit = async (values, formik) => {
|
|
try {
|
|
values.token = query.get('token');
|
|
|
|
const requestBody = {
|
|
token: values.token,
|
|
password: values.password,
|
|
};
|
|
|
|
const response = await resetPassword(requestBody);
|
|
if (response.status === 200) {
|
|
pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
|
|
}
|
|
onSubmit();
|
|
} catch (error) {
|
|
return;
|
|
} finally {
|
|
formik.setSubmitting(false);
|
|
formik.resetForm();
|
|
}
|
|
};
|
|
const handleTogglePasswordVisibility = (field) => {
|
|
if (field === 'password') {
|
|
setShowPassword((prevShowPassword) => !prevShowPassword);
|
|
} else if (field === 'confirmPassword') {
|
|
setShowConfirmPassword(
|
|
(prevShowConfirmPassword) => !prevShowConfirmPassword
|
|
);
|
|
}
|
|
};
|
|
|
|
const handleSubmitClick = async (e) => {
|
|
e.preventDefault();
|
|
const formikErrors = await formik.validateForm();
|
|
const errors = Object.keys(formikErrors);
|
|
|
|
if (errors.length) {
|
|
// Find the first invalid field and focus it
|
|
const firstErrorField = errors[0];
|
|
const firstErrorRef = fieldRefs[firstErrorField]?.current;
|
|
|
|
if (firstErrorRef) {
|
|
// Scroll to the first invalid field smoothly
|
|
if (typeof firstErrorRef?.scrollIntoView === 'function') {
|
|
firstErrorRef?.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'center',
|
|
});
|
|
}
|
|
|
|
// Focus the field after a slight delay (to ensure scrolling completes first)
|
|
setTimeout(() => firstErrorRef.focus(), 300);
|
|
}
|
|
|
|
// Show error notification
|
|
if (formik?.touched[firstErrorField])
|
|
pushNotification(formikErrors[firstErrorField], NOTIFICATION.ERROR);
|
|
}
|
|
|
|
formik.handleSubmit();
|
|
};
|
|
|
|
return (
|
|
<Box className={classes.right}>
|
|
<form onSubmit={handleSubmitClick} className={classes.form}>
|
|
<Box className={classes.formBody}>
|
|
<Box className={classes.formHeader}>
|
|
<Typography className={classes.subHeading}>
|
|
Note: Password must be at least 8 characters, contain 1 letter and
|
|
1 number, with no spaces at the beginning/end.
|
|
</Typography>
|
|
</Box>
|
|
<InputLabel
|
|
shrink={false}
|
|
htmlFor={'password'}
|
|
className={classes.labelStyle}
|
|
>
|
|
<Typography className={classes.inputTitle}>
|
|
Enter New Password
|
|
</Typography>
|
|
</InputLabel>
|
|
<TextField
|
|
color="secondary"
|
|
placeholder="Enter Password"
|
|
variant="outlined"
|
|
margin="normal"
|
|
fullWidth
|
|
type={showPassword ? 'text' : 'password'}
|
|
name="password"
|
|
value={formik.values.password}
|
|
onChange={formik.handleChange}
|
|
onBlur={formik.handleBlur}
|
|
inputRef={fieldRefs.password}
|
|
error={Boolean(formik.errors.password && formik.touched.password)}
|
|
helperText={
|
|
formik.errors.password && formik.touched.password
|
|
? formik.errors.password
|
|
: ''
|
|
}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<IconButton
|
|
onClick={() => handleTogglePasswordVisibility('password')}
|
|
edge="end"
|
|
>
|
|
{!showPassword ? (
|
|
<VisibilityOffOutlined />
|
|
) : (
|
|
<VisibilityOutlined />
|
|
)}
|
|
</IconButton>
|
|
),
|
|
}}
|
|
/>
|
|
{/* Password validation display */}
|
|
{!formik.isValid && (
|
|
<>
|
|
<Typography className={classes.passwordCheckListTitle}>
|
|
Note: Password must be
|
|
</Typography>
|
|
<Box className={classes.passwordCheckList}>
|
|
<PasswordValidation
|
|
isValid={passwordLengthRegex.test(formik.values.password)}
|
|
text="at least 6 characters"
|
|
/>
|
|
<PasswordValidation
|
|
isValid={passwordLetterRegex.test(formik.values.password)}
|
|
text="at least 1 letter"
|
|
/>
|
|
<PasswordValidation
|
|
isValid={passwordNumberRegex.test(formik.values.password)}
|
|
text="at least 1 number"
|
|
/>
|
|
<PasswordValidation
|
|
isValid={passwordSpacesRegex.test(formik.values.password)}
|
|
text="with no spaces at the beginning/end"
|
|
/>
|
|
</Box>
|
|
</>
|
|
)}
|
|
</Box>
|
|
<Box className={classes.confirmResetBox}>
|
|
<InputLabel
|
|
shrink={false}
|
|
htmlFor={'confirmPassword'}
|
|
className={classes.labelStyle}
|
|
>
|
|
<Typography className={classes.inputTitle}>
|
|
Confirm New Password
|
|
</Typography>
|
|
</InputLabel>
|
|
<TextField
|
|
className={classes.resetPasswordTextField}
|
|
color="secondary"
|
|
placeholder="Confirm Password"
|
|
variant="outlined"
|
|
margin="normal"
|
|
fullWidth
|
|
type={showConfirmPassword ? 'text' : 'password'}
|
|
name="confirmPassword"
|
|
value={formik.values.confirmPassword}
|
|
onChange={formik.handleChange}
|
|
onBlur={formik.handleBlur}
|
|
inputRef={fieldRefs.confirmPassword}
|
|
error={Boolean(
|
|
formik.errors.confirmPassword && formik.touched.confirmPassword
|
|
)}
|
|
helperText={
|
|
formik.errors.confirmPassword && formik.touched.confirmPassword
|
|
? formik.errors.confirmPassword
|
|
: ''
|
|
}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<IconButton
|
|
onClick={() =>
|
|
handleTogglePasswordVisibility('confirmPassword')
|
|
}
|
|
edge="end"
|
|
>
|
|
{!showConfirmPassword ? (
|
|
<VisibilityOffOutlined />
|
|
) : (
|
|
<VisibilityOutlined />
|
|
)}
|
|
</IconButton>
|
|
),
|
|
}}
|
|
/>
|
|
</Box>
|
|
<LoadingButton
|
|
size="large"
|
|
type="submit"
|
|
variant="contained"
|
|
className={classes.submitButton}
|
|
loading={formik.isSubmitting}
|
|
>
|
|
Save New Password
|
|
</LoadingButton>
|
|
</form>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
export default ResetPasswordForm;
|