login, forgot/change password bug fixed
This commit is contained in:
parent
f4c59bf3da
commit
c257cf356c
@ -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">
|
||||
|
||||
@ -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()}
|
||||
|
||||
@ -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.");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user