diff --git a/ipi-survey-platform/src/App.jsx b/ipi-survey-platform/src/App.jsx index cb058d7..a95194b 100644 --- a/ipi-survey-platform/src/App.jsx +++ b/ipi-survey-platform/src/App.jsx @@ -9,7 +9,7 @@ import Configuration from '@/pages/Admin/Configuration'; import Survey from '@/pages/Survey/Survey'; import Overview from '@/pages/Overview/Overview'; import Login from '@/pages/Login/Login'; -// import OTPVerification from '@/pages/OTPVerification/OTPVerification'; +import OTPVerification from '@/pages/OTPVerification/OTPVerification'; import History from '@/pages/History/History'; import ChangePassword from '@/pages/ChangePassword/ChangePassword'; import ForgotPassword from '@/pages/ForgotPassword/ForgotPassword'; @@ -187,14 +187,14 @@ function App() { } /> - {/* } - /> */} + /> {
Factory Name
+
+ City/Town +
User Name
@@ -3701,6 +3704,9 @@ const handleImport = async () => {
HS Code
+
+ Industry Code (Current Production) +
diff --git a/ipi-survey-platform/src/pages/Login/Login.jsx b/ipi-survey-platform/src/pages/Login/Login.jsx index 61fc805..98995f3 100644 --- a/ipi-survey-platform/src/pages/Login/Login.jsx +++ b/ipi-survey-platform/src/pages/Login/Login.jsx @@ -206,20 +206,56 @@ const submit = async (e) => { countdownIntervalRef.current = null; } - // Show success message - showToast('success', 'Login successful!'); - - // Add a small delay before navigation to allow toast to be visible - setTimeout(() => { - try { - // Redirect based on user role - const redirectPath = userData.role === 'Admin' ? '/admin/dashboard' : '/dashboard'; - navigate(location.state?.from || redirectPath); - } catch (err) { - console.error('Navigation failed:', err); - showToast('error', 'Failed to redirect. Please try again.'); + try { + // Request OTP after successful login + const otpResponse = await requestOtp(email); + + if (otpResponse.status === 'success' && otpResponse.message === 'OTP sent successfully to your registered email') { + // Store user data in localStorage + localStorage.setItem('user_role', userData.role); + localStorage.setItem('user_profile', JSON.stringify({ + id: userData.id, + name: userData.name, + email: userData.email + })); + + // Store establishment_id if user is an EstablishmentUser + if (userData.role === 'EstablishmentUser' && userData.establishment_id) { + localStorage.setItem('establishment_id', userData.establishment_id); + } + + // Show success message + showToast('success', 'OTP sent successfully to your registered email'); + + // Add a small delay before navigation to allow toast to be visible + setTimeout(() => { + // Calculate OTP expiration time (3 minutes from now) + const otpExpiryTime = Date.now() + (3 * 60 * 1000); + + // Navigate to OTP verification with email and expiry time + navigate('/verify-otp', { + state: { + email, + otpExpiryTime, + from: location.state?.from || (userData.role === 'Admin' ? '/admin/dashboard' : '/dashboard') + } + }); + }, 1000); // 1 second delay + } else { + throw new Error('Failed to send OTP'); } - }, 1000); // 1 second delay + } catch (otpError) { + console.error('OTP request failed:', otpError); + showToast('error', 'Login successful but failed to send OTP. Please try again.'); + // Still navigate to OTP page but show error + navigate('/verify-otp', { + state: { + email, + from: location.state?.from || (userData.role === 'Admin' ? '/admin/dashboard' : '/dashboard'), + error: 'Failed to send OTP. Please request a new one.' + } + }); + } } else { throw new Error(response?.message || 'Invalid credentials'); } diff --git a/ipi-survey-platform/src/pages/OTPVerification/OTPVerification.jsx b/ipi-survey-platform/src/pages/OTPVerification/OTPVerification.jsx index 375fbab..6eff3b8 100644 --- a/ipi-survey-platform/src/pages/OTPVerification/OTPVerification.jsx +++ b/ipi-survey-platform/src/pages/OTPVerification/OTPVerification.jsx @@ -1,402 +1,402 @@ -// import React, { useState, useEffect, useRef, useCallback } from 'react'; -// import { requestOtp, verifyOtp } from '@/services/auth/authService'; -// import { useNavigate, useLocation } from 'react-router-dom'; -// import { AlertCircle, Clock, CheckCircle } from "lucide-react"; +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { requestOtp, verifyOtp } from '@/services/auth/authService'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { AlertCircle, Clock, CheckCircle } from "lucide-react"; -// const logoSrc = '/assets/images/FCSCLogo.svg'; +const logoSrc = '/assets/images/FCSCLogo.svg'; -// const OTPVerification = () => { -// const navigate = useNavigate(); -// const location = useLocation(); +const OTPVerification = () => { + const navigate = useNavigate(); + const location = useLocation(); -// const [otp, setOtp] = useState(['', '', '', '', '', '']); -// const [error, setError] = useState(''); -// const [loading, setLoading] = useState(false); -// const [toast, setToast] = useState(null); -// const [resendDisabled, setResendDisabled] = useState(false); -// const [countdown, setCountdown] = useState(600); -// const [attemptsRemaining, setAttemptsRemaining] = useState(3); + const [otp, setOtp] = useState(['', '', '', '', '', '']); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const [toast, setToast] = useState(null); + const [resendDisabled, setResendDisabled] = useState(false); + const [countdown, setCountdown] = useState(600); + const [attemptsRemaining, setAttemptsRemaining] = useState(3); -// const inputRefs = useRef(Array(6).fill(null).map(() => React.createRef())); -// const toastTimeoutRef = useRef(null); -// const countdownRef = useRef(null); + const inputRefs = useRef(Array(6).fill(null).map(() => React.createRef())); + const toastTimeoutRef = useRef(null); + const countdownRef = useRef(null); -// // Get email from location state or use a default -// const maskedEmail = location.state?.email ? -// `${location.state.email.split('@')[0].substring(0, 2)}***@${location.state.email.split('@')[1]}` : -// 'na***@company.com'; + // Get email from location state or use a default + const maskedEmail = location.state?.email ? + `${location.state.email.split('@')[0].substring(0, 2)}***@${location.state.email.split('@')[1]}` : + 'na***@company.com'; -// // Toast handling -// const closeToast = useCallback(() => { -// if (toastTimeoutRef.current) { -// clearTimeout(toastTimeoutRef.current); -// toastTimeoutRef.current = null; -// } -// setToast(null); -// }, []); + // Toast handling + const closeToast = useCallback(() => { + if (toastTimeoutRef.current) { + clearTimeout(toastTimeoutRef.current); + toastTimeoutRef.current = null; + } + setToast(null); + }, []); -// const showToast = useCallback((type, message) => { -// if (!message) return; -// if (toastTimeoutRef.current) { -// clearTimeout(toastTimeoutRef.current); -// toastTimeoutRef.current = null; -// } -// setToast({ type, message }); -// toastTimeoutRef.current = setTimeout(() => { -// setToast(null); -// toastTimeoutRef.current = null; -// }, 4000); -// }, []); + const showToast = useCallback((type, message) => { + if (!message) return; + if (toastTimeoutRef.current) { + clearTimeout(toastTimeoutRef.current); + toastTimeoutRef.current = null; + } + setToast({ type, message }); + toastTimeoutRef.current = setTimeout(() => { + setToast(null); + toastTimeoutRef.current = null; + }, 4000); + }, []); -// // Handle OTP input change -// const handleOtpChange = (e, index) => { -// const value = e.target.value; + // Handle OTP input change + const handleOtpChange = (e, index) => { + const value = e.target.value; -// // Only allow numbers and limit to 1 character -// if (value && !/^\d*$/.test(value)) return; + // Only allow numbers and limit to 1 character + if (value && !/^\d*$/.test(value)) return; -// const newOtp = [...otp]; -// newOtp[index] = value.slice(-1); -// setOtp(newOtp); + const newOtp = [...otp]; + newOtp[index] = value.slice(-1); + setOtp(newOtp); -// // Move to next input if there's a value and we're not on the last input -// if (value && index < 5 && inputRefs.current[index + 1]?.current) { -// inputRefs.current[index + 1].current.focus(); -// } -// }; + // Move to next input if there's a value and we're not on the last input + if (value && index < 5 && inputRefs.current[index + 1]?.current) { + inputRefs.current[index + 1].current.focus(); + } + }; -// // Handle backspace -// const handleKeyDown = (e, index) => { -// if (e.key === 'Backspace' && !otp[index] && index > 0 && inputRefs.current[index - 1]?.current) { -// // Move to previous input on backspace -// inputRefs.current[index - 1].current.focus(); -// } -// }; + // Handle backspace + const handleKeyDown = (e, index) => { + if (e.key === 'Backspace' && !otp[index] && index > 0 && inputRefs.current[index - 1]?.current) { + // Move to previous input on backspace + inputRefs.current[index - 1].current.focus(); + } + }; -// // Handle paste -// const handlePaste = (e) => { -// e.preventDefault(); -// const pasteData = e.clipboardData.getData('text/plain').trim(); -// if (/^\d{6}$/.test(pasteData)) { -// const newOtp = pasteData.split('').slice(0, 6); -// setOtp([...newOtp, ...Array(6 - newOtp.length).fill('')]); -// } -// }; + // Handle paste + const handlePaste = (e) => { + e.preventDefault(); + const pasteData = e.clipboardData.getData('text/plain').trim(); + if (/^\d{6}$/.test(pasteData)) { + const newOtp = pasteData.split('').slice(0, 6); + setOtp([...newOtp, ...Array(6 - newOtp.length).fill('')]); + } + }; -// // Handle verify OTP -// const handleVerify = async (e) => { -// e.preventDefault(); -// const otpValue = otp.join(''); + // Handle verify OTP + const handleVerify = async (e) => { + e.preventDefault(); + const otpValue = otp.join(''); -// if (otpValue.length !== 6) { -// setError('Please enter a valid 6-digit code'); -// return; -// } + if (otpValue.length !== 6) { + setError('Please enter a valid 6-digit code'); + return; + } -// setLoading(true); -// setError(''); + setLoading(true); + setError(''); -// try { -// const response = await verifyOtp({ -// registered_email: location.state?.email, -// otp: otpValue -// }); + try { + const response = await verifyOtp({ + registered_email: location.state?.email, + otp: otpValue + }); -// if (response?.status === 'success') { -// // Show success message immediately -// showToast('success', 'Verified! You are signed in. Redirecting to your dashboard.'); + if (response?.status === 'success') { + // Show success message immediately + showToast('success', 'Verified! You are signed in. Redirecting to your dashboard.'); -// // Check for admin role in multiple possible locations -// const userRole = localStorage.getItem('user_role') || -// response?.data?.user?.role || -// response?.data?.role; + // Check for admin role in multiple possible locations + const userRole = localStorage.getItem('user_role') || + response?.data?.user?.role || + response?.data?.role; -// // Check if user is admin (case-insensitive check) -// const isAdmin = userRole?.toLowerCase() === 'admin' || -// response?.data?.isAdmin === true; + // Check if user is admin (case-insensitive check) + const isAdmin = userRole?.toLowerCase() === 'admin' || + response?.data?.isAdmin === true; -// // Use the redirect path from location state if available, otherwise determine based on role -// const redirectPath = location.state?.from || -// (isAdmin ? '/admin/dashboard' : '/dashboard'); + // Use the redirect path from location state if available, otherwise determine based on role + const redirectPath = location.state?.from || + (isAdmin ? '/admin/dashboard' : '/dashboard'); -// // Wait for 5 seconds before redirecting -// setLoading(true); -// setTimeout(() => { -// navigate(redirectPath, { replace: true }); -// }, 1000); -// } else { -// throw new Error(response?.message || 'Invalid OTP'); -// } + // Wait for 5 seconds before redirecting + setLoading(true); + setTimeout(() => { + navigate(redirectPath, { replace: true }); + }, 1000); + } else { + throw new Error(response?.message || 'Invalid OTP'); + } -// } catch (err) { -// console.error('OTP verification failed', err); -// const errorMessage = err.response?.data?.message || -// err.message || -// 'An error occurred during OTP verification. Please try again.'; + } catch (err) { + console.error('OTP verification failed', err); + const errorMessage = err.response?.data?.message || + err.message || + 'An error occurred during OTP verification. Please try again.'; -// const newAttempts = attemptsRemaining - 1; -// setAttemptsRemaining(newAttempts); + const newAttempts = attemptsRemaining - 1; + setAttemptsRemaining(newAttempts); -// // Set the error message for incorrect OTP -// const displayError = errorMessage || 'Invalid verification code'; -// setError(displayError); + // Set the error message for incorrect OTP + const displayError = errorMessage || 'Invalid verification code'; + setError(displayError); -// if (newAttempts <= 0) { -// setOtp(Array(6).fill('')); -// // Show error message and redirect to login after a short delay -// setError('Maximum attempts reached. Redirecting to login...'); -// setTimeout(() => { -// navigate('/login', { replace: true }); -// }, 2000); -// } else { -// setOtp(Array(6).fill('')); -// // Focus the first input if it exists -// if (inputRefs.current[0]) { -// inputRefs.current[0].focus(); -// } -// } -// } finally { -// setLoading(false); -// } -// }; + if (newAttempts <= 0) { + setOtp(Array(6).fill('')); + // Show error message and redirect to login after a short delay + setError('Maximum attempts reached. Redirecting to login...'); + setTimeout(() => { + navigate('/login', { replace: true }); + }, 2000); + } else { + setOtp(Array(6).fill('')); + // Focus the first input if it exists + if (inputRefs.current[0]) { + inputRefs.current[0].focus(); + } + } + } finally { + setLoading(false); + } + }; -// // Handle resend OTP -// const handleResend = async () => { -// if (resendDisabled) return; + // Handle resend OTP + const handleResend = async () => { + if (resendDisabled) return; -// // Show loading state immediately -// setResendDisabled(true); -// setLoading(true); + // Show loading state immediately + setResendDisabled(true); + setLoading(true); -// try { -// // Call the API to resend OTP -// const response = await requestOtp(location.state?.email); + try { + // Call the API to resend OTP + const response = await requestOtp(location.state?.email); -// // Update state and show success toast immediately -// const tenMinutesInSeconds = 10 * 60; -// setCountdown(tenMinutesInSeconds); -// setAttemptsRemaining(3); -// setError(''); -// setOtp(Array(6).fill('')); + // Update state and show success toast immediately + const tenMinutesInSeconds = 10 * 60; + setCountdown(tenMinutesInSeconds); + setAttemptsRemaining(3); + setError(''); + setOtp(Array(6).fill('')); -// // Use setTimeout to ensure the toast shows up in the next tick -// setTimeout(() => { -// showToast('success', response.message || 'A new OTP has been sent to your email.'); -// }, 0); + // Use setTimeout to ensure the toast shows up in the next tick + setTimeout(() => { + showToast('success', response.message || 'A new OTP has been sent to your email.'); + }, 0); -// } catch (error) { -// console.error('Error resending OTP:', error); -// showToast('error', 'An error occurred while sending OTP. Please try again.'); -// } finally { -// setLoading(false); + } catch (error) { + console.error('Error resending OTP:', error); + showToast('error', 'An error occurred while sending OTP. Please try again.'); + } finally { + setLoading(false); -// // Re-enable resend after 60 seconds -// const timer = setTimeout(() => { -// setResendDisabled(false); -// }, 60000); + // Re-enable resend after 60 seconds + const timer = setTimeout(() => { + setResendDisabled(false); + }, 60000); -// // Cleanup timer on component unmount -// return () => clearTimeout(timer); -// } -// }; + // Cleanup timer on component unmount + return () => clearTimeout(timer); + } + }; -// // Start countdown on component mount -// useEffect(() => { -// // Always use 10 minutes for the countdown -// const tenMinutesInMs = 10 * 60 * 1000; -// const expiryTime = Date.now() + tenMinutesInMs; + // Start countdown on component mount + useEffect(() => { + // Always use 10 minutes for the countdown + const tenMinutesInMs = 10 * 60 * 1000; + const expiryTime = Date.now() + tenMinutesInMs; -// const updateCountdown = () => { -// const now = Date.now(); -// const remainingSeconds = Math.max(0, Math.floor((expiryTime - now) / 1000)); + const updateCountdown = () => { + const now = Date.now(); + const remainingSeconds = Math.max(0, Math.floor((expiryTime - now) / 1000)); -// setCountdown(remainingSeconds); + setCountdown(remainingSeconds); -// if (remainingSeconds <= 0) { -// clearInterval(countdownRef.current); -// setResendDisabled(false); -// } -// }; + if (remainingSeconds <= 0) { + clearInterval(countdownRef.current); + setResendDisabled(false); + } + }; -// // Initial update -// updateCountdown(); + // Initial update + updateCountdown(); -// // Set up interval for countdown updates -// countdownRef.current = setInterval(updateCountdown, 1000); + // Set up interval for countdown updates + countdownRef.current = setInterval(updateCountdown, 1000); -// return () => { -// if (countdownRef.current) clearInterval(countdownRef.current); -// }; -// }, []); + return () => { + if (countdownRef.current) clearInterval(countdownRef.current); + }; + }, []); -// // Cleanup on unmount -// useEffect(() => { -// return () => { -// if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current); -// if (countdownRef.current) clearInterval(countdownRef.current); -// }; -// }, []); + // Cleanup on unmount + useEffect(() => { + return () => { + if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current); + if (countdownRef.current) clearInterval(countdownRef.current); + }; + }, []); -// const minutes = Math.floor(countdown / 60); -// const seconds = countdown % 60; + const minutes = Math.floor(countdown / 60); + const seconds = countdown % 60; -// return ( -//
-// {/* Toast Notification */} -// {toast && ( -//
-//
-//
-// {toast.type === 'error' ? ( -// -// ) : toast.type === 'success' ? ( -// -// ) : null} -// {toast.message} -//
-// -//
-//
-// )} + return ( +
+ {/* Toast Notification */} + {toast && ( +
+
+
+ {toast.type === 'error' ? ( + + ) : toast.type === 'success' ? ( + + ) : null} + {toast.message} +
+ +
+
+ )} -//
-//
-// {/* Logo Section */} -//
-//
-// FCSC Logo -//
-//

-// IIP (Index of Industrial Production) -//

-//
+
+
+ {/* Logo Section */} +
+
+ FCSC Logo +
+

+ IIP (Index of Industrial Production) +

+
-//
-// {/*
-//

Email OTP

-//

Step 2 of 2

-//
*/} +
+ {/*
+

Email OTP

+

Step 2 of 2

+
*/} -//
-//
-//
-//

Verify your sign-in

-//

-// We sent a 6-digit authentication code to {maskedEmail} -//

-//
+ +
+
+

Verify your sign-in

+

+ We sent a 6-digit authentication code to {maskedEmail} +

+
-// {/* OTP Inputs */} -//
-//
-// {otp.map((digit, index) => ( -// { inputRefs.current[index] = { current: el } }} -// type="text" -// inputMode="numeric" -// pattern="[0-9]*" -// maxLength={1} -// value={digit} -// onChange={(e) => handleOtpChange(e, index)} -// onKeyDown={(e) => handleKeyDown(e, index)} -// onPaste={handlePaste} -// className={`w-12 h-12 text-center text-xl bg-transparent border-2 rounded focus:outline-none focus:ring-0 ${ -// error ? 'border-red-500' : -// digit ? 'border-[#92722A]' : -// 'border-gray-300 hover:border-gray-400 focus:border-[#92722A] focus:border-b-2 focus:border-b-[#92722A]' -// }`} -// autoFocus={index === 0} -// disabled={loading || attemptsRemaining <= 0} -// /> -// ))} -//
+ {/* OTP Inputs */} +
+
+ {otp.map((digit, index) => ( + { inputRefs.current[index] = { current: el } }} + type="text" + inputMode="numeric" + pattern="[0-9]*" + maxLength={1} + value={digit} + onChange={(e) => handleOtpChange(e, index)} + onKeyDown={(e) => handleKeyDown(e, index)} + onPaste={handlePaste} + className={`w-12 h-12 text-center text-xl bg-transparent border-2 rounded focus:outline-none focus:ring-0 ${ + error ? 'border-red-500' : + digit ? 'border-[#92722A]' : + 'border-gray-300 hover:border-gray-400 focus:border-[#92722A] focus:border-b-2 focus:border-b-[#92722A]' + }`} + autoFocus={index === 0} + disabled={loading || attemptsRemaining <= 0} + /> + ))} +
-// {error && ( -//
-// -// {error} -//
-// )} + {error && ( +
+ + {error} +
+ )} -//
-//
-// -// -// Code expires in {minutes}:{seconds < 10 ? `0${seconds}` : seconds} -// -//
-// Attempts: {attemptsRemaining}/3 -//
-//
+
+
+ + + Code expires in {minutes}:{seconds < 10 ? `0${seconds}` : seconds} + +
+ Attempts: {attemptsRemaining}/3 +
+
-//
-// -// +
+ + -//
-// -//
-//
-//
-// +
+ +
+
+
+ -//
+
-//

-// Having trouble?{' '} -// -//

-//
-//
-//
-//
-//
-// ); -// }; +

+ Having trouble?{' '} + +

+
+
+
+
+
+ ); +}; -// export default OTPVerification; +export default OTPVerification;