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,
placeholder,
required = false,
error,
}) => (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
@ -25,8 +26,9 @@ const ToggleableInput = ({
value={value}
onChange={onChange}
placeholder={placeholder}
required={required}
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"
className={`block w-full h-10 rounded-md border-2 ${
error ? "border-red-500" : "border-[#92722A]"
} focus:border-[#92722A] focus:ring-0 px-4 pr-11 text-sm bg-white`}
/>
<button
type="button"
@ -36,12 +38,8 @@ const ToggleableInput = ({
>
{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">
<path
strokeLinecap="round"
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="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" />
</svg>
) : (
@ -52,10 +50,10 @@ const ToggleableInput = ({
)}
</button>
</div>
{error && <p className="text-xs text-red-600 mt-1">{error}</p>}
</div>
);
const ChangePassword = () => {
const navigate = useNavigate();
const location = useLocation();
@ -71,88 +69,95 @@ const ChangePassword = () => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = 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) => {
const minLength = /.{8,}/;
const uppercase = /[A-Z]/;
const lowercase = /[a-z]/;
const number = /[0-9]/;
const specialChar = /[!@#$%^&*(),.?":{}|<>]/;
if (!isStrong) {
return "Password must meet complexity requirements.";
}
return "";
};
if (!minLength.test(password))
return "Password must be at least 8 characters long.";
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.";
const handleSubmit = async (e) => {
e.preventDefault();
return "";
};
setError("");
setSuccess("");
const handleSubmit = async (e) => {
e.preventDefault();
setError("");
setSuccess("");
setOtpError("");
setNewPasswordError("");
setConfirmPasswordError("");
if (!registeredEmail) {
setError("Missing registered email. Please restart the process.");
return;
let hasError = false;
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.");
}
if (!otp || !newPassword || !confirmPassword) {
setError("Please fill in all fields.");
return;
}
const passwordError = validatePassword(newPassword);
if (passwordError) {
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);
}
};
} catch (err) {
const msg =
err.response?.data?.message ||
err.message ||
"Password change failed. Please verify the details.";
setError(msg);
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen bg-[#F7F7F7] flex items-center justify-center px-4">
@ -193,12 +198,12 @@ const ChangePassword = () => {
<ToggleableInput
label="Verification Code"
required
type="password"
value={otp}
onChange={(e) => setOtp(e.target.value)}
show={showOtp}
toggleShow={() => setShowOtp((v) => !v)}
placeholder="Enter 6-digit code"
error={otpError}
/>
<ToggleableInput
@ -209,6 +214,7 @@ const ChangePassword = () => {
show={showNewPassword}
toggleShow={() => setShowNewPassword((v) => !v)}
placeholder="Enter new password"
error={newPasswordError}
/>
<ToggleableInput
@ -219,6 +225,7 @@ const ChangePassword = () => {
show={showConfirmPassword}
toggleShow={() => setShowConfirmPassword((v) => !v)}
placeholder="Re-enter new password"
error={confirmPasswordError}
/>
{newPassword && (
<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 navigateTimeoutRef = React.useRef(null);
// Generate CAPTCHA
const generateCaptcha = React.useCallback(() => {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
let code = '';
@ -37,7 +36,6 @@ const Login = () => {
}, []);
// Toast helpers
const closeToast = React.useCallback(() => {
if (toastTimeoutRef.current) {
clearTimeout(toastTimeoutRef.current);
@ -59,7 +57,6 @@ const Login = () => {
}, 4000);
}, []);
// Navigate delay
const scheduleNavigate = React.useCallback(
(path) => {
if (!path) return;
@ -105,7 +102,6 @@ const Login = () => {
setCaptchaError('');
};
// Email format check
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const validateForm = () => {
@ -113,20 +109,23 @@ const Login = () => {
setEmailError('');
setPasswordError('');
setCaptchaError('');
if (!email.trim()) {
setEmailError('Enter your email address.');
const trimmedEmail = email.trim();
const trimmedPassword = password.trim();
if (!trimmedEmail) {
setEmailError('Email is required');
valid = false;
} else if (!emailRegex.test(email)) {
} else if (!emailRegex.test(trimmedEmail)) {
setEmailError('Enter a valid email address.');
valid = false;
}
if (!password.trim()) {
setPasswordError('Enter your password.');
if (!trimmedPassword) {
setPasswordError('Password is required');
valid = false;
}
if (!userCaptcha.trim()) {
setCaptchaError('Please enter the CAPTCHA.');
valid = false;
@ -134,9 +133,9 @@ const Login = () => {
setCaptchaError('Invalid. Try again or refresh for a new code.');
valid = false;
}
return valid;
};
};
const submit = async (e) => {
e.preventDefault();
@ -260,7 +259,6 @@ const Login = () => {
<form onSubmit={submit} className="px-7 py-8 space-y-4">
{/* Email */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Email address</label>
<input
@ -268,7 +266,7 @@ const Login = () => {
value={email}
onChange={(e) => {
setEmail(e.target.value);
if (emailError) setEmailError(''); // 👈 clears error while typing
if (emailError) setEmailError('');
}}
placeholder="name@company.com"
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>}
</div>
{/* Password */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Password</label>
<div className="relative">
@ -312,19 +309,16 @@ const Login = () => {
{passwordError && <p className="text-xs text-red-600 mt-1">{passwordError}</p>}
</div>
{/* CAPTCHA */}
<div className="mt-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
CAPTCHA Verification <span className="text-red-500">*</span>
</label>
<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">
{captcha}
</div>
{/* Refresh icon + text */}
<button
type="button"
onClick={refreshCaptcha}
@ -336,12 +330,9 @@ const Login = () => {
</button>
</div>
{/* Info text */}
<p className="text-xs text-gray-500 mt-1">
Enter the characters in the image. Can't read it? Refresh for a new code.
</p>
{/* </div> */}
<input
type="text"
@ -357,7 +348,6 @@ const Login = () => {
{captchaError && <p className="text-xs text-red-600 mt-1">{captchaError}</p>}
</div>
{/* Remember me */}
<div className="flex items-center justify-between text-sm">
<label className="inline-flex items-center gap-2 cursor-pointer">
<input
@ -377,7 +367,6 @@ const Login = () => {
</button>
</div>
{/* Submit */}
<button
type="submit"
disabled={loading || !userCaptcha.trim()}

View File

@ -143,7 +143,7 @@ const PublicContactForm = () => {
const handleConfirmOtp = async () => {
if (!otp.trim()) {
setOtpError("Please enter the verification code.");
setOtpError("Verification code is required");
return;
}
@ -160,22 +160,34 @@ const PublicContactForm = () => {
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...");
setTimeout(() => navigate("/change-password"), 1500);
} 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) {
const msg =
err.response?.data?.message ||
err.message ||
"Verification failed. Please check the code and try again.";
setOtpError(msg);
const serverMsg = err.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("Verification code is required");
}
} finally {
setLoading(false);
}
};
};
const handleResendOtp = () => {
setOtpMsg("A new OTP has been sent to your registered email.");