158 lines
4.2 KiB
TypeScript
158 lines
4.2 KiB
TypeScript
// components/ChangePasswordModal.tsx
|
||
import React, { useState, useEffect } from "react";
|
||
import { Typography, TextField, Button, Box } from "@mui/material";
|
||
import toast from "react-hot-toast";
|
||
|
||
interface Props {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
onSubmit: (passwordData: {
|
||
currentPassword?: string;
|
||
newPassword: string;
|
||
}) => void;
|
||
isUserChange?: boolean;
|
||
}
|
||
|
||
export const ChangePassword: React.FC<Props> = ({
|
||
open,
|
||
onSubmit,
|
||
isUserChange = false,
|
||
}) => {
|
||
const [currentPassword, setCurrentPassword] = useState("");
|
||
const [password, setPassword] = useState<string>("");
|
||
const [confirm, setConfirm] = useState<string>("");
|
||
const [errors, setErrors] = useState<string[]>([]);
|
||
const [isValid, setIsValid] = useState(false);
|
||
|
||
// Validate password rules
|
||
useEffect(() => {
|
||
if (!password) {
|
||
setErrors([]);
|
||
setIsValid(false);
|
||
return;
|
||
}
|
||
|
||
const newErrors: string[] = [];
|
||
if (password.length < 8) newErrors.push("At least 8 characters");
|
||
if (!/[A-Z]/.test(password)) newErrors.push("One uppercase letter");
|
||
if (!/[a-z]/.test(password)) newErrors.push("One lowercase letter");
|
||
if (!/[0-9]/.test(password)) newErrors.push("One number");
|
||
if (!/[!@#$%^&*(),.?\":{}|<>]/.test(password))
|
||
newErrors.push("One special character");
|
||
if (password !== confirm) newErrors.push("Passwords do not match");
|
||
|
||
// If user change mode, require current password
|
||
if (isUserChange && !currentPassword)
|
||
newErrors.push("Current password required");
|
||
|
||
setErrors(newErrors);
|
||
setIsValid(newErrors.length === 0);
|
||
}, [confirm, password, currentPassword, isUserChange]);
|
||
|
||
const handleSubmit = () => {
|
||
if (!isValid) {
|
||
toast.error("Please fix the validation errors before continuing");
|
||
return;
|
||
}
|
||
|
||
onSubmit({
|
||
...(isUserChange ? { currentPassword } : {}),
|
||
newPassword: password,
|
||
});
|
||
};
|
||
|
||
return (
|
||
<Box
|
||
sx={{
|
||
backgroundColor: "white",
|
||
p: 3,
|
||
borderRadius: 2,
|
||
width: 400,
|
||
display: open ? "flex" : "none",
|
||
flexDirection: "column",
|
||
gap: 2,
|
||
boxShadow: 3,
|
||
}}
|
||
>
|
||
{isUserChange ? (
|
||
<Typography variant="body2" color="text.secondary">
|
||
Please enter your current password and choose a new one below.
|
||
</Typography>
|
||
) : (
|
||
<Typography variant="body2" color="text.secondary">
|
||
You’re currently logged in with a temporary password. Please set a new
|
||
one to continue.
|
||
</Typography>
|
||
)}
|
||
{isUserChange && (
|
||
<TextField
|
||
label="Current Password"
|
||
type="password"
|
||
value={currentPassword}
|
||
onChange={e => setCurrentPassword(e.target.value)}
|
||
fullWidth
|
||
error={isUserChange && !currentPassword}
|
||
helperText={
|
||
isUserChange && !currentPassword
|
||
? "Current password is required"
|
||
: ""
|
||
}
|
||
/>
|
||
)}
|
||
|
||
<TextField
|
||
label="New Password"
|
||
type="password"
|
||
value={password}
|
||
onChange={e => setPassword(e.target.value)}
|
||
fullWidth
|
||
error={!isValid && password.length > 0}
|
||
/>
|
||
|
||
<TextField
|
||
label="Confirm Password"
|
||
type="password"
|
||
value={confirm}
|
||
onChange={e => setConfirm(e.target.value)}
|
||
fullWidth
|
||
error={confirm.length > 0 && password !== confirm}
|
||
/>
|
||
|
||
<Box sx={{ height: "80px" }}>
|
||
<Typography
|
||
variant="caption"
|
||
color={isValid ? "success.main" : "error.main"}
|
||
sx={{
|
||
fontWeight: 500,
|
||
visibility: password.length > 0 ? "visible" : "hidden",
|
||
}}
|
||
>
|
||
<p>
|
||
{isValid
|
||
? "✓ Password meets all requirements"
|
||
: "Password must contain:"}
|
||
</p>
|
||
</Typography>
|
||
|
||
{!isValid && (
|
||
<Typography
|
||
variant="caption"
|
||
sx={{ color: "error.main", fontSize: "0.8rem" }}
|
||
>
|
||
{errors.join(", ")}
|
||
</Typography>
|
||
)}
|
||
</Box>
|
||
|
||
<Button
|
||
variant="contained"
|
||
sx={{ width: "100%", height: 40, fontSize: 16 }}
|
||
onClick={handleSubmit}
|
||
disabled={!isValid}
|
||
>
|
||
Update Password
|
||
</Button>
|
||
</Box>
|
||
);
|
||
};
|