health-apps-cms/src/views/ResetPassword/ResetPasswordForm.jsx

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;