From de3a72ca3543a35e1bf93eaf43955efcca983d3b Mon Sep 17 00:00:00 2001 From: Malini Date: Fri, 21 Nov 2025 13:14:45 +0530 Subject: [PATCH] removed captacha --- .../Admin/configuration/CompanyProfile.jsx | 2 +- ipi-survey-platform/src/pages/Login/Login.jsx | 244 ++++++------------ .../src/pages/PublicContactForm.jsx | 160 ++++-------- 3 files changed, 129 insertions(+), 277 deletions(-) diff --git a/ipi-survey-platform/src/pages/Admin/configuration/CompanyProfile.jsx b/ipi-survey-platform/src/pages/Admin/configuration/CompanyProfile.jsx index 3fad0c0..6a3918d 100644 --- a/ipi-survey-platform/src/pages/Admin/configuration/CompanyProfile.jsx +++ b/ipi-survey-platform/src/pages/Admin/configuration/CompanyProfile.jsx @@ -1782,7 +1782,7 @@ const CompanyProfile = () => { try { const countResponse = await fetchEstablishments({ params: countParams, signal: controller.signal }); const countPayload = countResponse?.data ?? countResponse; - const totalRecords = countPayload?.pagination?.total_records || 0; + const totalRecords = countResponse?.pagination?.total_records || 0; const params = { search: debouncedSearch || undefined, diff --git a/ipi-survey-platform/src/pages/Login/Login.jsx b/ipi-survey-platform/src/pages/Login/Login.jsx index 1189f28..5ae7946 100644 --- a/ipi-survey-platform/src/pages/Login/Login.jsx +++ b/ipi-survey-platform/src/pages/Login/Login.jsx @@ -1,50 +1,36 @@ import React from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { login } from '@/services/auth/authService.js'; -import { RefreshCw, AlertCircle, Clock } from "lucide-react"; - +import { AlertCircle, Clock } from "lucide-react"; + const logoSrc = '/assets/images/FCSCLogo.svg'; - + const MAX_FAILED_ATTEMPTS = 3; -const SHOW_CAPTCHA_AFTER = 0; -const LOCK_DURATION_MINUTES = 2; - +const LOCK_DURATION_MINUTES = 2; + const Login = () => { const navigate = useNavigate(); const location = useLocation(); - + const [email, setEmail] = React.useState(''); const [password, setPassword] = React.useState(''); const [showPassword, setShowPassword] = React.useState(false); const [rememberMe, setRememberMe] = React.useState(true); const [loading, setLoading] = React.useState(false); const [toast, setToast] = React.useState(null); - + const [emailError, setEmailError] = React.useState(''); const [passwordError, setPasswordError] = React.useState(''); - - const [captcha, setCaptcha] = React.useState(''); - const [userCaptcha, setUserCaptcha] = React.useState(''); - const [captchaError, setCaptchaError] = React.useState(''); - + const [isLocked, setIsLocked] = React.useState(false); const [lockExpiry, setLockExpiry] = React.useState(null); const [failedAttempts, setFailedAttempts] = React.useState(0); const [remainingTime, setRemainingTime] = React.useState(0); - + const toastTimeoutRef = React.useRef(null); const navigateTimeoutRef = React.useRef(null); const countdownIntervalRef = React.useRef(null); - - const generateCaptcha = React.useCallback(() => { - const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; - let code = ''; - for (let i = 0; i < 6; i++) { - code += chars.charAt(Math.floor(Math.random() * chars.length)); - } - setCaptcha(code); - }, []); - + const closeToast = React.useCallback(() => { if (toastTimeoutRef.current) { clearTimeout(toastTimeoutRef.current); @@ -52,7 +38,7 @@ const Login = () => { } setToast(null); }, []); - + const showToast = React.useCallback((type, message) => { if (!message) return; if (toastTimeoutRef.current) { @@ -65,7 +51,7 @@ const Login = () => { toastTimeoutRef.current = null; }, 4000); }, []); - + const scheduleNavigate = React.useCallback( (path) => { if (!path) return; @@ -80,18 +66,18 @@ const Login = () => { }, [navigate] ); - + const startCountdown = React.useCallback((lockUntil) => { if (countdownIntervalRef.current) { clearInterval(countdownIntervalRef.current); } - + const updateTimer = () => { const now = Date.now(); const remaining = Math.max(0, Math.ceil((parseInt(lockUntil, 10) - now) / 1000)); - + setRemainingTime(remaining); - + if (remaining <= 0) { clearInterval(countdownIntervalRef.current); countdownIntervalRef.current = null; @@ -101,20 +87,21 @@ const Login = () => { showToast('success', 'Account unlocked. You can try logging in again.'); } }; - + updateTimer(); countdownIntervalRef.current = setInterval(updateTimer, 1000); }, [showToast]); - + + // Check lock status when email changes React.useEffect(() => { - generateCaptcha(); - + if (!email) return; + // Get the stored attempts and lock status for the current email const storedAttempts = JSON.parse(localStorage.getItem('login_attempts') || '{}'); const emailAttempts = storedAttempts[email] || { attempts: 0, lockUntil: null }; - + setFailedAttempts(emailAttempts.attempts); - + if (emailAttempts.lockUntil && Date.now() < emailAttempts.lockUntil) { setIsLocked(true); setLockExpiry(emailAttempts.lockUntil); @@ -128,8 +115,8 @@ const Login = () => { } else { setIsLocked(false); } - }, [generateCaptcha, startCountdown, email]); - + }, [email, startCountdown]); + React.useEffect( () => () => { if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current); @@ -138,29 +125,17 @@ const Login = () => { }, [] ); - - const handleCaptchaChange = (e) => { - setUserCaptcha(e.target.value.toUpperCase()); - if (captchaError) setCaptchaError(''); - }; - - const refreshCaptcha = () => { - generateCaptcha(); - setUserCaptcha(''); - setCaptchaError(''); - }; - + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - + const validateForm = () => { let valid = true; setEmailError(''); setPasswordError(''); - setCaptchaError(''); - + const trimmedEmail = email.trim(); const trimmedPassword = password.trim(); - + if (!trimmedEmail) { setEmailError('Email is required'); valid = false; @@ -168,47 +143,39 @@ const Login = () => { setEmailError('Enter a valid email address.'); valid = false; } - + if (!trimmedPassword) { setPasswordError('Password is required'); valid = false; } - - if (!userCaptcha.trim()) { - setCaptchaError('Please enter the CAPTCHA.'); - valid = false; - } else if (userCaptcha !== captcha) { - setCaptchaError('Invalid CAPTCHA. Try again or refresh for a new code.'); - valid = false; - } - + return valid; }; - + const submit = async (e) => { e.preventDefault(); - + if (isLocked) { const minutes = Math.floor(remainingTime / 60); const seconds = remainingTime % 60; showToast('error', `Account locked. Please wait ${minutes}m ${seconds}s or contact support.`); return; } - + if (!validateForm()) return; - + setLoading(true); - + try { const response = await login({ email, password }); - + if (response?.status !== 'success' || !response?.data) { throw new Error('Invalid credentials'); } - + const token = response.data; sessionStorage.setItem('auth_token', token); - + let payload; try { const [, base64Payload] = token.split('.'); @@ -216,29 +183,29 @@ const Login = () => { } catch { throw new Error('Invalid token format.'); } - + const role = payload?.role ?? (payload?.is_admin ? 'Admin' : undefined); if (role) sessionStorage.setItem('user_role', role); - + const profile = { id: payload?.id || payload?.user_id || '', name: payload?.name || payload?.username || '', email: payload?.email || '', }; sessionStorage.setItem('user_profile', JSON.stringify(profile)); - + const establishmentId = payload?.establishment_id ?? payload?.establishmentId; const isAdmin = String(role || '').toLowerCase() === 'admin' || payload?.is_admin === true; const successMessage = response?.message || 'Login successful.'; - + // Clear login attempts for this email const storedAttempts = JSON.parse(localStorage.getItem('login_attempts') || '{}'); if (storedAttempts[email]) { delete storedAttempts[email]; localStorage.setItem('login_attempts', JSON.stringify(storedAttempts)); } - + setFailedAttempts(0); setIsLocked(false); setRemainingTime(0); @@ -246,14 +213,14 @@ const Login = () => { clearInterval(countdownIntervalRef.current); countdownIntervalRef.current = null; } - + if (isAdmin) { sessionStorage.removeItem('establishment_id'); showToast('success', successMessage); scheduleNavigate('/admin/dashboard'); return; } - + if (rememberMe) { sessionStorage.setItem('remember_me', 'true'); sessionStorage.setItem('remembered_email', email); @@ -263,7 +230,7 @@ const Login = () => { sessionStorage.removeItem('remembered_email'); sessionStorage.removeItem('remembered_password'); } - + if (establishmentId) { sessionStorage.setItem('establishment_id', String(establishmentId)); showToast('success', successMessage); @@ -273,26 +240,24 @@ const Login = () => { } } catch (err) { console.error('Login failed', err); - + const storedAttempts = JSON.parse(localStorage.getItem('login_attempts') || '{}'); const attempts = (storedAttempts[email]?.attempts || 0) + 1; - + // Update attempts for this email const updatedAttempts = { ...storedAttempts, [email]: { attempts, - lockUntil: attempts >= MAX_FAILED_ATTEMPTS - ? Date.now() + LOCK_DURATION_MINUTES * 60 * 1000 + lockUntil: attempts >= MAX_FAILED_ATTEMPTS + ? Date.now() + LOCK_DURATION_MINUTES * 60 * 1000 : null } }; - + localStorage.setItem('login_attempts', JSON.stringify(updatedAttempts)); setFailedAttempts(attempts); - - refreshCaptcha(); - + if (attempts >= MAX_FAILED_ATTEMPTS) { const lockUntil = Date.now() + LOCK_DURATION_MINUTES * 60 * 1000; setIsLocked(true); @@ -307,13 +272,13 @@ const Login = () => { setLoading(false); } }; - + const formatTime = (seconds) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; }; - + return (
{loading && ( @@ -321,7 +286,7 @@ const Login = () => {
)} - + {toast && (
{
)} - +
@@ -356,7 +321,7 @@ const Login = () => { Industrial Production Index (IPI) Survey Portal
- +
{isLocked && remainingTime > 0 && (
@@ -376,7 +341,7 @@ const Login = () => {
)} - + {!isLocked && failedAttempts > 0 && failedAttempts < MAX_FAILED_ATTEMPTS && (
@@ -387,7 +352,7 @@ const Login = () => {
)} - +
- +
{passwordError &&

{passwordError}

}
- -
- - -
-
- {captcha} -
- - -
- -

- Enter the characters shown above. Can't read it? Click refresh for a new code. -

- - - {captchaError &&

{captchaError}

} -
- - +
- + @@ -555,5 +470,6 @@ const Login = () => {
); }; - -export default Login; \ No newline at end of file + +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 96775ca..68d0da7 100644 --- a/ipi-survey-platform/src/pages/PublicContactForm.jsx +++ b/ipi-survey-platform/src/pages/PublicContactForm.jsx @@ -1,48 +1,32 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import apiClient from '@/services/api/apiClient'; -import { RefreshCw } from "lucide-react"; + const logoSrc = '/assets/images/FCSCLogo.svg'; - + const PublicContactForm = () => { const navigate = useNavigate(); - + const [formData, setFormData] = useState({ userType: "establishment_user", establishmentName: "", establishmentId: "", email: "", }); - + const [errors, setErrors] = useState({}); const [isValid, setIsValid] = useState(false); - const [captcha, setCaptcha] = useState(""); - const [userCaptcha, setUserCaptcha] = useState(""); - const [captchaError, setCaptchaError] = useState(""); 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 [otpStep, setOtpStep] = useState(false); + const [otp, setOtp] = useState(""); + const [otpMsg, setOtpMsg] = useState(""); const [otpError, setOtpError] = useState(""); const [toast, setToast] = useState(null); - + const toastTimeoutRef = React.useRef(null); - - const generateCaptcha = () => { - const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; - let code = ""; - for (let i = 0; i < 6; i++) { - code += chars.charAt(Math.floor(Math.random() * chars.length)); - } - setCaptcha(code); - }; - - useEffect(() => { - generateCaptcha(); - }, []); - + useEffect(() => { return () => { if (toastTimeoutRef.current) { @@ -50,7 +34,7 @@ const PublicContactForm = () => { } }; }, []); - + const showToast = (type, message) => { if (!message) return; if (toastTimeoutRef.current) { @@ -63,7 +47,7 @@ const PublicContactForm = () => { toastTimeoutRef.current = null; }, 4000); }; - + const closeToast = () => { if (toastTimeoutRef.current) { window.clearTimeout(toastTimeoutRef.current); @@ -71,52 +55,41 @@ const PublicContactForm = () => { } setToast(null); }; - + const validate = (data = formData) => { const newErrors = {}; - + if (!data.userType) { newErrors.userType = "Please select user type"; } else if (data.userType === 'establishment_user') { if (!data.establishmentName.trim()) newErrors.establishmentName = "Required."; if (!data.establishmentId.trim()) newErrors.establishmentId = "Required."; } - + if (!data.email.trim()) newErrors.email = "Required."; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) newErrors.email = "Enter a valid email address."; - + setErrors(newErrors); setIsValid(Object.keys(newErrors).length === 0); return newErrors; }; - + const handleChange = (e) => { const { name, value } = e.target; const updated = { ...formData, [name]: value }; setFormData(updated); validate(updated); }; - - const handleCaptchaChange = (e) => { - setUserCaptcha(e.target.value.toUpperCase()); - setCaptchaError(""); - }; - + const handleSubmit = async (e) => { e.preventDefault(); setErrorMsg(""); setSuccess(""); - + const validationErrors = validate(); if (Object.keys(validationErrors).length > 0) return; - - if (userCaptcha !== captcha) { - setCaptchaError("Incorrect CAPTCHA. Please try again."); - generateCaptcha(); - return; - } - + setLoading(true); try { const payload = { @@ -125,14 +98,14 @@ const PublicContactForm = () => { establishment_code: formData.establishmentId.trim(), registered_email: formData.email.trim(), }; - + const response = await apiClient.post("/forgot-password/request-otp", payload); - + if (response?.data) { showToast('success', 'Verification Code sent to your registered email successfully!'); setTimeout(() => { navigate("/change-password", { - state: { + state: { email: formData.email.trim(), user_type: formData.userType }, @@ -151,38 +124,38 @@ const PublicContactForm = () => { setLoading(false); } }; - + const handleConfirmOtp = async () => { if (!otp.trim()) { setOtpError("Verification code is required"); return; } - + setOtpError(""); setLoading(true); setErrorMsg(""); setSuccess(""); - + try { const payload = { registered_email: formData.email.trim(), otp: otp.trim(), user_type: formData.userType // Add user_type to the verification request }; - + const response = await apiClient.post("/forgot-password/verify-otp", payload); - + if (response?.data?.status === "success") { setSuccess("Verification Code verified successfully. Redirecting to change password..."); - setTimeout(() => navigate("/change-password", { - state: { + setTimeout(() => navigate("/change-password", { + state: { email: formData.email.trim(), user_type: formData.userType // Ensure user_type is passed - } + } }), 1500); } else { 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")) { @@ -193,7 +166,7 @@ const PublicContactForm = () => { } } catch (err) { 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")) { @@ -205,12 +178,12 @@ const PublicContactForm = () => { setLoading(false); } }; - + const handleResendOtp = () => { setOtpMsg("A new Verification Code has been sent to your registered email."); setOtp(""); }; - + return (
{toast && ( @@ -235,7 +208,7 @@ const PublicContactForm = () => {
)} - +
@@ -249,7 +222,7 @@ const PublicContactForm = () => { : "Enter the verification code sent to your registered email."}

- + {!otpStep ? (
{errorMsg && ( @@ -257,7 +230,7 @@ const PublicContactForm = () => { {errorMsg}
)} - +
)} - + {formData.userType === 'establishment_user' && (
@@ -303,7 +276,7 @@ const PublicContactForm = () => {

{errors.establishmentName}

)}
- +
)} - + { onChange={handleChange} error={errors.email} /> - -
- -
- - -
-

- Enter the characters in the image. Can't read it? Refresh for a new code. - -

- - {captchaError &&

{captchaError}

} -
- + - +
)} - + { placeholder="Enter the 6-digit code" error={otpError} /> - +
); }; - + const InputField = ({ label, name, @@ -458,5 +394,5 @@ const InputField = ({ {error &&

{error}

} ); - + export default PublicContactForm; \ No newline at end of file