diff --git a/ipi-survey-platform/src/pages/ChangePassword/ChangePassword.jsx b/ipi-survey-platform/src/pages/ChangePassword/ChangePassword.jsx index 0224608..4d2d294 100644 --- a/ipi-survey-platform/src/pages/ChangePassword/ChangePassword.jsx +++ b/ipi-survey-platform/src/pages/ChangePassword/ChangePassword.jsx @@ -1,208 +1,192 @@ -import React from 'react'; -import Logo from '../../assets/images/FCSCLogo.svg'; -import { useNavigate } from 'react-router-dom'; -import { changePassword } from '../../services/auth/authService.js'; +import React from "react"; +import Logo from "../../assets/images/FCSCLogo.svg"; +import { useNavigate, useLocation } from "react-router-dom"; +import apiClient from "../../services/api/apiClient.js"; const ChangePassword = () => { const navigate = useNavigate(); - const [currentPassword, setCurrentPassword] = React.useState(''); - const [newPassword, setNewPassword] = React.useState(''); - const [confirmPassword, setConfirmPassword] = React.useState(''); - const [showCurrentPassword, setShowCurrentPassword] = React.useState(false); + const location = useLocation(); + + const registeredEmail = location.state?.email || ""; + + const [otp, setOtp] = React.useState(""); + const [newPassword, setNewPassword] = React.useState(""); + const [confirmPassword, setConfirmPassword] = React.useState(""); + const [showOtp, setShowOtp] = React.useState(false); const [showNewPassword, setShowNewPassword] = React.useState(false); const [showConfirmPassword, setShowConfirmPassword] = React.useState(false); const [loading, setLoading] = React.useState(false); - const [error, setError] = React.useState(''); - const [success, setSuccess] = React.useState(''); + const [error, setError] = React.useState(""); + const [success, setSuccess] = React.useState(""); - const handleSubmit = async (event) => { - event.preventDefault(); - setError(''); - setSuccess(''); + const handleSubmit = async (e) => { + e.preventDefault(); + setError(""); + setSuccess(""); - if (!currentPassword || !newPassword) { - setError('Please fill in all required fields.'); + if (!registeredEmail) { + setError("Missing registered email. Please restart the process."); + return; + } + + if (!otp || !newPassword || !confirmPassword) { + setError("Please fill in all fields."); return; } if (newPassword !== confirmPassword) { - setError('New password and confirmation do not match.'); + setError("Passwords do not match."); return; } setLoading(true); try { - const response = await changePassword({ currentPassword, newPassword }); - const status = response?.status ?? 'success'; - const message = response?.message || 'Password updated successfully. Please sign in again with your new password.'; - if (status !== 'success') { - throw new Error(message); + 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"), 1500); + } else { + setError("Something went wrong. Please try again."); } - try { - sessionStorage.removeItem('auth_token'); - sessionStorage.removeItem('user_profile'); - sessionStorage.removeItem('user_role'); - sessionStorage.removeItem('establishment_id'); - } catch (storageError) { - console.warn('Unable to clear stored session', storageError); - } - setSuccess(message); - setTimeout(() => { - navigate('/login'); - }, 1500); } catch (err) { - const message = err?.response?.data?.message || err?.message || 'Unable to change password.'; - setError(message); + const msg = + err.response?.data?.message || + err.message || + "Password change failed. Please verify the details."; + setError(msg); } finally { setLoading(false); } }; + const ToggleableInput = ({ label, type = "password", value, onChange, show, toggleShow, placeholder }) => ( +
+ +
+ + +
+
+ ); + return (
FCSC -

Change Password

-

Keep your account secure by updating your password.

+

Reset Password

+

+ Enter the verification code sent to your registered email and set a new password. +

+
- {error &&
{error}
} - {success &&
{success}
} + {error && ( +
+ {error} +
+ )} + {success && ( +
+ {success} +
+ )}
- -
- setCurrentPassword(e.target.value)} - placeholder="Enter current password" - 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" - required - /> - -
+ +
-
- -
- setNewPassword(e.target.value)} - placeholder="Enter new password" - 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" - required - /> - -
-
+ setOtp(e.target.value)} + show={showOtp} + toggleShow={() => setShowOtp((v) => !v)} + placeholder="Enter 6-digit code" + /> -
- -
- setConfirmPassword(e.target.value)} - placeholder="Re-enter new password" - 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" - required - /> - -
-
+ setNewPassword(e.target.value)} + show={showNewPassword} + toggleShow={() => setShowNewPassword((v) => !v)} + placeholder="Enter new password" + /> + + setConfirmPassword(e.target.value)} + show={showConfirmPassword} + toggleShow={() => setShowConfirmPassword((v) => !v)} + placeholder="Re-enter new password" + />
)} +
-
-
- FCSC -

IPI Survey Platform

-
+
+ FCSC +

IPI Survey Platform

+ {error && ( -
{error}
+
+ {error} +
)}
@@ -255,38 +223,84 @@ const Login = () => {
- -
- setPassword(e.target.value)} - placeholder="Enter password" - className="block w-full h-10 rounded-md border-2 border-[#92722A] focus:border-[#92722A] focus:ring-0 px-4 pr-12 text-sm bg-white" - required - /> + +
+ setPassword(e.target.value)} + placeholder="Enter password" + 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" + required + /> + +
+
+ +
+ +
+
+

+ If you cannot read the code, click "Refresh" or type:{" "} + {captcha} +

+ + + {captchaError && ( +

{captchaError}

+ )}
@@ -301,7 +315,7 @@ const Login = () => { -
@@ -345,4 +340,4 @@ const Login = () => { ); }; -export default Login; +export default Login; \ No newline at end of file diff --git a/ipi-survey-platform/src/pages/PublicContactForm.jsx b/ipi-survey-platform/src/pages/PublicContactForm.jsx index ec64fac..2aadab5 100644 --- a/ipi-survey-platform/src/pages/PublicContactForm.jsx +++ b/ipi-survey-platform/src/pages/PublicContactForm.jsx @@ -10,9 +10,9 @@ const PublicContactForm = () => { establishmentName: "", establishmentId: "", email: "", - contactName: "", - contactPhone: "", - notes: "", + // contactName: "", + // contactPhone: "", + // notes: "", }); const [errors, setErrors] = useState({}); @@ -23,8 +23,11 @@ const PublicContactForm = () => { const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(""); const [errorMsg, setErrorMsg] = useState(""); + const [otpStep, setOtpStep] = useState(false); + const [otp, setOtp] = useState(""); + const [otpMsg, setOtpMsg] = useState(""); + const [otpError, setOtpError] = useState(""); - // Generate simple CAPTCHA code const generateCaptcha = () => { const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; let code = ""; @@ -38,45 +41,40 @@ const PublicContactForm = () => { generateCaptcha(); }, []); - // Validation rules const validate = (data = formData) => { const newErrors = {}; if (!data.establishmentName.trim()) newErrors.establishmentName = "Establishment Name is required."; if (!data.email.trim()) newErrors.email = "Registered Email is required."; - else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) newErrors.email = "Enter a valid email address."; - if (!data.contactName.trim()) newErrors.contactName = "Contact Person Name is required."; - if (!data.contactPhone.trim()) { - newErrors.contactPhone = "Contact Phone is required."; - } else if (!/^\d{10,12}$/.test(data.contactPhone)) { - newErrors.contactPhone = "Enter a valid phone number (10–12 digits)."; - } + else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) + newErrors.email = "Enter a valid email address."; + // if (!data.contactName.trim()) newErrors.contactName = "Contact Person Name is required."; + // if (!data.contactPhone.trim()) { + // newErrors.contactPhone = "Contact Phone is required."; + // } else if (!/^\d{10,12}$/.test(data.contactPhone)) { + // newErrors.contactPhone = "Enter a valid phone number (10–12 digits)."; + // } + setErrors(newErrors); setIsValid(Object.keys(newErrors).length === 0); return newErrors; }; - // Restrict Contact Name: only letters and spaces -const handleNameChange = (e) => { - const { value } = e.target; - const filtered = value.replace(/[^A-Za-z\s]/g, ""); // remove numbers/special chars - setFormData((prev) => ({ ...prev, contactName: filtered })); - validate({ ...formData, contactName: filtered }); - }; - - const handlePhoneChange = (e) => { - let { value } = e.target; - - // Allow only numbers - value = value.replace(/\D/g, ""); // remove all non-numeric characters - - // Limit to 12 digits - if (value.length > 12) value = value.slice(0, 12); - - setFormData((prev) => ({ ...prev, contactPhone: value })); - validate({ ...formData, contactPhone: value }); - }; - +// const handleNameChange = (e) => { +// const { value } = e.target; +// const filtered = value.replace(/[^A-Za-z\s]/g, ""); +// setFormData((prev) => ({ ...prev, contactName: filtered })); +// validate({ ...formData, contactName: filtered }); +// }; + +// const handlePhoneChange = (e) => { +// let { value } = e.target; +// value = value.replace(/\D/g, ""); +// if (value.length > 12) value = value.slice(0, 12); +// setFormData((prev) => ({ ...prev, contactPhone: value })); +// validate({ ...formData, contactPhone: value }); +// }; + const handleChange = (e) => { const { name, value } = e.target; const updated = { ...formData, [name]: value }; @@ -84,13 +82,11 @@ const handleNameChange = (e) => { validate(updated); }; - // CAPTCHA validation const handleCaptchaChange = (e) => { setUserCaptcha(e.target.value.toUpperCase()); setCaptchaError(""); }; - // Form submit const handleSubmit = async (e) => { e.preventDefault(); setErrorMsg(""); @@ -105,196 +101,260 @@ const handleNameChange = (e) => { return; } - // Simulate processing setLoading(true); - setTimeout(() => { + try { + const payload = { + establishment_name: formData.establishmentName.trim(), + establishment_code: formData.establishmentId.trim(), + registered_email: formData.email.trim(), + }; + + const response = await apiClient.post("/forgot-password/request-otp", payload); + + if (response?.data) { + navigate("/change-password", { + state: { email: formData.email.trim() }, + }); + } else { + setErrorMsg("Something went wrong. Please try again."); + } + } catch (err) { + const msg = + err.response?.data?.message || + err.message || + "Failed to send OTP. Please check your details and try again."; + setErrorMsg(msg); + } finally { setLoading(false); + } + }; + + const handleConfirmOtp = async () => { + if (!otp.trim()) { + setOtpError("Please enter the verification code."); + return; + } - // ✅ Show success toast/message - setSuccess("Reset Password request successful."); + setOtpError(""); + setLoading(true); + setErrorMsg(""); + setSuccess(""); - // ✅ Clear fields and regenerate CAPTCHA - setFormData({ - establishmentName: "", - establishmentId: "", - email: "", - contactName: "", - contactPhone: "", - notes: "", - }); - setUserCaptcha(""); - generateCaptcha(); + try { + const payload = { + registered_email: formData.email.trim(), + otp: otp.trim(), + }; - // ✅ After 3 seconds → clear session + route to login - setTimeout(() => { - try { - sessionStorage.removeItem("auth_token"); - sessionStorage.removeItem("user_profile"); - sessionStorage.removeItem("user_role"); - sessionStorage.removeItem("establishment_id"); - } catch { - // ignore storage errors - } - navigate("/login"); - }, 3000); - }, 1000); // small delay to simulate "submitting" + const response = await apiClient.post("/forgot-password/verify-otp", payload); + + if (response?.data) { + setSuccess("OTP verified successfully. Redirecting to change password..."); + setTimeout(() => navigate("/change-password"), 1500); + } else { + setOtpError("Invalid verification code. Please try again."); + } + } catch (err) { + const msg = + err.response?.data?.message || + err.message || + "Verification failed. Please check the code and try again."; + setOtpError(msg); + } finally { + setLoading(false); + } }; + const handleResendOtp = () => { + setOtpMsg("A new OTP has been sent to your registered email."); + setOtp(""); + }; + return (
- {/* Header */}
FCSC

- Contact Establishment Info + {!otpStep ? "Contact Establishment Info" : "Verify Your Email"}

- Provide details to contact your establishment. + {!otpStep + ? "Provide details to contact your establishment." + : "Enter the verification code sent to your registered email."}

-
- {errorMsg && ( -
- {errorMsg} -
- )} - {success && ( -
- {success} -
- )} - - {/* Inputs */} - - - - - - - {/* Notes */} -
- - -
- - {/* CAPTCHA */} -
- -
-
); }; -// Input component -const InputField = ({ label, name, value, onChange, placeholder, required, error, type = "text" }) => ( +const InputField = ({ + label, + name, + value, + onChange, + placeholder, + required, + error, + type = "text", +}) => (