login, forgot/change password bug fixed

This commit is contained in:
Senthamilselvi 2025-11-04 20:11:19 +05:30
parent f4c59bf3da
commit c257cf356c
3 changed files with 128 additions and 120 deletions

View File

@ -13,6 +13,7 @@ const ToggleableInput = ({
toggleShow, toggleShow,
placeholder, placeholder,
required = false, required = false,
error,
}) => ( }) => (
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">
@ -25,8 +26,9 @@ const ToggleableInput = ({
value={value} value={value}
onChange={onChange} onChange={onChange}
placeholder={placeholder} placeholder={placeholder}
required={required} className={`block w-full h-10 rounded-md border-2 ${
className="block w-full h-10 rounded-md border-2 border-[#92722A] focus:border-[#92722A] focus:ring-0 px-4 pr-11 text-sm bg-white" error ? "border-red-500" : "border-[#92722A]"
} focus:border-[#92722A] focus:ring-0 px-4 pr-11 text-sm bg-white`}
/> />
<button <button
type="button" type="button"
@ -36,12 +38,8 @@ const ToggleableInput = ({
> >
{show ? ( {show ? (
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-[#92722A]" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-[#92722A]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"
strokeLinecap="round" d="M13.875 18.825A10.05 10.05 0 0 1 12 19c-5.523 0-10-4.477-10-10a9.96 9.96 0 0 1 2.122-6.21m3.086-1.955A9.953 9.953 0 0 1 12 3c5.523 0 10 4.477 10 10 0 1.591-.372 3.093-1.034 4.432M9.88 9.88a3 3 0 1 0 4.24 4.24" />
strokeLinejoin="round"
strokeWidth="2"
d="M13.875 18.825A10.05 10.05 0 0 1 12 19c-5.523 0-10-4.477-10-10a9.96 9.96 0 0 1 2.122-6.21m3.086-1.955A9.953 9.953 0 0 1 12 3c5.523 0 10 4.477 10 10 0 1.591-.372 3.093-1.034 4.432M9.88 9.88a3 3 0 1 0 4.24 4.24"
/>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="m3 3 18 18" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="m3 3 18 18" />
</svg> </svg>
) : ( ) : (
@ -52,10 +50,10 @@ const ToggleableInput = ({
)} )}
</button> </button>
</div> </div>
{error && <p className="text-xs text-red-600 mt-1">{error}</p>}
</div> </div>
); );
const ChangePassword = () => { const ChangePassword = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
@ -71,88 +69,95 @@ const ChangePassword = () => {
const [loading, setLoading] = React.useState(false); const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState(""); const [error, setError] = React.useState("");
const [success, setSuccess] = React.useState(""); const [success, setSuccess] = React.useState("");
const [otpError, setOtpError] = React.useState("");
const [newPasswordError, setNewPasswordError] = React.useState("");
const [confirmPasswordError, setConfirmPasswordError] = React.useState("");
const validatePassword = (password) => {
const isStrong =
/.{8,}/.test(password) &&
/[A-Z]/.test(password) &&
/[a-z]/.test(password) &&
/[0-9]/.test(password) &&
/[!@#$%^&*(),.?":{}|<>]/.test(password);
const validatePassword = (password) => { if (!isStrong) {
const minLength = /.{8,}/; return "Password must meet complexity requirements.";
const uppercase = /[A-Z]/; }
const lowercase = /[a-z]/; return "";
const number = /[0-9]/; };
const specialChar = /[!@#$%^&*(),.?":{}|<>]/;
if (!minLength.test(password)) const handleSubmit = async (e) => {
return "Password must be at least 8 characters long."; e.preventDefault();
if (!uppercase.test(password))
return "Password must contain at least one uppercase letter.";
if (!lowercase.test(password))
return "Password must contain at least one lowercase letter.";
if (!number.test(password))
return "Password must contain at least one number.";
if (!specialChar.test(password))
return "Password must contain at least one special character.";
return ""; setError("");
}; setSuccess("");
const handleSubmit = async (e) => { setOtpError("");
e.preventDefault(); setNewPasswordError("");
setError(""); setConfirmPasswordError("");
setSuccess("");
if (!registeredEmail) { let hasError = false;
setError("Missing registered email. Please restart the process.");
return; if (!otp.trim()) {
setOtpError("Verification code is required.");
hasError = true;
}
if (!newPassword.trim()) {
setNewPasswordError("New password is required.");
hasError = true;
}
if (!confirmPassword.trim()) {
setConfirmPasswordError("Confirm password is required.");
hasError = true;
}
if (hasError) return;
const passwordError = validatePassword(newPassword);
if (passwordError) {
setNewPasswordError(passwordError);
}
if (newPassword !== confirmPassword) {
setConfirmPasswordError("Passwords do not match.");
return;
}
setLoading(true);
try {
const payload = {
registered_email: registeredEmail,
otp: otp.trim(),
password: newPassword,
confirm_password: confirmPassword,
};
const response = await apiClient.post("/forgot-password/verify-otp", payload);
if (response?.data) {
setSuccess("Password changed successfully. Redirecting to login...");
setTimeout(() => {
navigate("/login", {
state: {
showSuccessToast: true,
successMessage:
"Password reset successful. Please login with your new password.",
},
});
}, 1500);
} else {
setError("Something went wrong. Please try again.");
} }
} catch (err) {
if (!otp || !newPassword || !confirmPassword) { const msg =
setError("Please fill in all fields."); err.response?.data?.message ||
return; err.message ||
} "Password change failed. Please verify the details.";
setError(msg);
const passwordError = validatePassword(newPassword); } finally {
if (passwordError) { setLoading(false);
setError(passwordError); }
return; };
}
if (newPassword !== confirmPassword) {
setError("Passwords do not match.");
return;
}
setLoading(true);
try {
const payload = {
registered_email: registeredEmail,
otp: otp.trim(),
password: newPassword,
confirm_password: confirmPassword,
};
const response = await apiClient.post("/forgot-password/verify-otp", payload);
if (response?.data) {
setSuccess("Password changed successfully. Redirecting to login...");
setTimeout(() => {
navigate("/login", {
state: {
showSuccessToast: true,
successMessage: "Password reset successful. Please login with your new password."
}
});
}, 1500);
} else {
setError("Something went wrong. Please try again.");
}
} catch (err) {
const msg =
err.response?.data?.message ||
err.message ||
"Password change failed. Please verify the details.";
setError(msg);
} finally {
setLoading(false);
}
};
return ( return (
<div className="min-h-screen bg-[#F7F7F7] flex items-center justify-center px-4"> <div className="min-h-screen bg-[#F7F7F7] flex items-center justify-center px-4">
@ -193,12 +198,12 @@ const ChangePassword = () => {
<ToggleableInput <ToggleableInput
label="Verification Code" label="Verification Code"
required required
type="password"
value={otp} value={otp}
onChange={(e) => setOtp(e.target.value)} onChange={(e) => setOtp(e.target.value)}
show={showOtp} show={showOtp}
toggleShow={() => setShowOtp((v) => !v)} toggleShow={() => setShowOtp((v) => !v)}
placeholder="Enter 6-digit code" placeholder="Enter 6-digit code"
error={otpError}
/> />
<ToggleableInput <ToggleableInput
@ -209,6 +214,7 @@ const ChangePassword = () => {
show={showNewPassword} show={showNewPassword}
toggleShow={() => setShowNewPassword((v) => !v)} toggleShow={() => setShowNewPassword((v) => !v)}
placeholder="Enter new password" placeholder="Enter new password"
error={newPasswordError}
/> />
<ToggleableInput <ToggleableInput
@ -219,6 +225,7 @@ const ChangePassword = () => {
show={showConfirmPassword} show={showConfirmPassword}
toggleShow={() => setShowConfirmPassword((v) => !v)} toggleShow={() => setShowConfirmPassword((v) => !v)}
placeholder="Re-enter new password" placeholder="Re-enter new password"
error={confirmPasswordError}
/> />
{newPassword && ( {newPassword && (
<ul className="text-xs text-gray-600 list-disc pl-5 space-y-1"> <ul className="text-xs text-gray-600 list-disc pl-5 space-y-1">

View File

@ -26,7 +26,6 @@ const Login = () => {
const toastTimeoutRef = React.useRef(null); const toastTimeoutRef = React.useRef(null);
const navigateTimeoutRef = React.useRef(null); const navigateTimeoutRef = React.useRef(null);
// Generate CAPTCHA
const generateCaptcha = React.useCallback(() => { const generateCaptcha = React.useCallback(() => {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
let code = ''; let code = '';
@ -37,7 +36,6 @@ const Login = () => {
}, []); }, []);
// Toast helpers
const closeToast = React.useCallback(() => { const closeToast = React.useCallback(() => {
if (toastTimeoutRef.current) { if (toastTimeoutRef.current) {
clearTimeout(toastTimeoutRef.current); clearTimeout(toastTimeoutRef.current);
@ -59,7 +57,6 @@ const Login = () => {
}, 4000); }, 4000);
}, []); }, []);
// Navigate delay
const scheduleNavigate = React.useCallback( const scheduleNavigate = React.useCallback(
(path) => { (path) => {
if (!path) return; if (!path) return;
@ -105,7 +102,6 @@ const Login = () => {
setCaptchaError(''); setCaptchaError('');
}; };
// Email format check
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const validateForm = () => { const validateForm = () => {
@ -113,20 +109,23 @@ const Login = () => {
setEmailError(''); setEmailError('');
setPasswordError(''); setPasswordError('');
setCaptchaError(''); setCaptchaError('');
if (!email.trim()) { const trimmedEmail = email.trim();
setEmailError('Enter your email address.'); const trimmedPassword = password.trim();
if (!trimmedEmail) {
setEmailError('Email is required');
valid = false; valid = false;
} else if (!emailRegex.test(email)) { } else if (!emailRegex.test(trimmedEmail)) {
setEmailError('Enter a valid email address.'); setEmailError('Enter a valid email address.');
valid = false; valid = false;
} }
if (!password.trim()) { if (!trimmedPassword) {
setPasswordError('Enter your password.'); setPasswordError('Password is required');
valid = false; valid = false;
} }
if (!userCaptcha.trim()) { if (!userCaptcha.trim()) {
setCaptchaError('Please enter the CAPTCHA.'); setCaptchaError('Please enter the CAPTCHA.');
valid = false; valid = false;
@ -134,9 +133,9 @@ const Login = () => {
setCaptchaError('Invalid. Try again or refresh for a new code.'); setCaptchaError('Invalid. Try again or refresh for a new code.');
valid = false; valid = false;
} }
return valid; return valid;
}; };
const submit = async (e) => { const submit = async (e) => {
e.preventDefault(); e.preventDefault();
@ -260,7 +259,6 @@ const Login = () => {
<form onSubmit={submit} className="px-7 py-8 space-y-4"> <form onSubmit={submit} className="px-7 py-8 space-y-4">
{/* Email */}
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1">Email address</label> <label className="block text-sm font-medium text-gray-700 mb-1">Email address</label>
<input <input
@ -268,7 +266,7 @@ const Login = () => {
value={email} value={email}
onChange={(e) => { onChange={(e) => {
setEmail(e.target.value); setEmail(e.target.value);
if (emailError) setEmailError(''); // 👈 clears error while typing if (emailError) setEmailError('');
}} }}
placeholder="name@company.com" placeholder="name@company.com"
className={`block w-full h-10 rounded-md border-2 px-4 text-sm focus:ring-0 ${ className={`block w-full h-10 rounded-md border-2 px-4 text-sm focus:ring-0 ${
@ -278,7 +276,6 @@ const Login = () => {
{emailError && <p className="text-xs text-red-600 mt-1">{emailError}</p>} {emailError && <p className="text-xs text-red-600 mt-1">{emailError}</p>}
</div> </div>
{/* Password */}
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1">Password</label> <label className="block text-sm font-medium text-gray-700 mb-1">Password</label>
<div className="relative"> <div className="relative">
@ -312,19 +309,16 @@ const Login = () => {
{passwordError && <p className="text-xs text-red-600 mt-1">{passwordError}</p>} {passwordError && <p className="text-xs text-red-600 mt-1">{passwordError}</p>}
</div> </div>
{/* CAPTCHA */}
<div className="mt-4"> <div className="mt-4">
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">
CAPTCHA Verification <span className="text-red-500">*</span> CAPTCHA Verification <span className="text-red-500">*</span>
</label> </label>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* CAPTCHA text box */}
<div className="flex justify-center items-center font-mono text-lg tracking-widest bg-gray-100 border-2 border-[#92722A] rounded-md px-4 py-2 select-none h-10"> <div className="flex justify-center items-center font-mono text-lg tracking-widest bg-gray-100 border-2 border-[#92722A] rounded-md px-4 py-2 select-none h-10">
{captcha} {captcha}
</div> </div>
{/* Refresh icon + text */}
<button <button
type="button" type="button"
onClick={refreshCaptcha} onClick={refreshCaptcha}
@ -336,12 +330,9 @@ const Login = () => {
</button> </button>
</div> </div>
{/* Info text */}
<p className="text-xs text-gray-500 mt-1"> <p className="text-xs text-gray-500 mt-1">
Enter the characters in the image. Can't read it? Refresh for a new code. Enter the characters in the image. Can't read it? Refresh for a new code.
</p> </p>
{/* </div> */}
<input <input
type="text" type="text"
@ -357,7 +348,6 @@ const Login = () => {
{captchaError && <p className="text-xs text-red-600 mt-1">{captchaError}</p>} {captchaError && <p className="text-xs text-red-600 mt-1">{captchaError}</p>}
</div> </div>
{/* Remember me */}
<div className="flex items-center justify-between text-sm"> <div className="flex items-center justify-between text-sm">
<label className="inline-flex items-center gap-2 cursor-pointer"> <label className="inline-flex items-center gap-2 cursor-pointer">
<input <input
@ -377,7 +367,6 @@ const Login = () => {
</button> </button>
</div> </div>
{/* Submit */}
<button <button
type="submit" type="submit"
disabled={loading || !userCaptcha.trim()} disabled={loading || !userCaptcha.trim()}

View File

@ -143,7 +143,7 @@ const PublicContactForm = () => {
const handleConfirmOtp = async () => { const handleConfirmOtp = async () => {
if (!otp.trim()) { if (!otp.trim()) {
setOtpError("Please enter the verification code."); setOtpError("Verification code is required");
return; return;
} }
@ -160,22 +160,34 @@ const PublicContactForm = () => {
const response = await apiClient.post("/forgot-password/verify-otp", payload); const response = await apiClient.post("/forgot-password/verify-otp", payload);
if (response?.data) { if (response?.data?.status === "success") {
setSuccess("OTP verified successfully. Redirecting to change password..."); setSuccess("OTP verified successfully. Redirecting to change password...");
setTimeout(() => navigate("/change-password"), 1500); setTimeout(() => navigate("/change-password"), 1500);
} else { } else {
setOtpError("Invalid verification code. Please try again."); const serverMsg = response?.data?.message?.toLowerCase() || "";
if (serverMsg.includes("expired")) {
setOtpError("Verification code has expired. Please request a new one.");
} else if (serverMsg.includes("invalid")) {
setOtpError("Invalid verification code");
} else {
setOtpError("Invalid verification code");
}
} }
} catch (err) { } catch (err) {
const msg = const serverMsg = err.response?.data?.message?.toLowerCase() || "";
err.response?.data?.message ||
err.message || if (serverMsg.includes("expired")) {
"Verification failed. Please check the code and try again."; setOtpError("Verification code has expired. Please request a new one.");
setOtpError(msg); } else if (serverMsg.includes("invalid")) {
setOtpError("Invalid verification code");
} else {
setOtpError("Verification code is required");
}
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
const handleResendOtp = () => { const handleResendOtp = () => {
setOtpMsg("A new OTP has been sent to your registered email."); setOtpMsg("A new OTP has been sent to your registered email.");