removed otp
This commit is contained in:
parent
45906a55bc
commit
e2da3f439d
@ -9,7 +9,7 @@ import Configuration from '@/pages/Admin/Configuration';
|
|||||||
import Survey from '@/pages/Survey/Survey';
|
import Survey from '@/pages/Survey/Survey';
|
||||||
import Overview from '@/pages/Overview/Overview';
|
import Overview from '@/pages/Overview/Overview';
|
||||||
import Login from '@/pages/Login/Login';
|
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 History from '@/pages/History/History';
|
||||||
import ChangePassword from '@/pages/ChangePassword/ChangePassword';
|
import ChangePassword from '@/pages/ChangePassword/ChangePassword';
|
||||||
import ForgotPassword from '@/pages/ForgotPassword/ForgotPassword';
|
import ForgotPassword from '@/pages/ForgotPassword/ForgotPassword';
|
||||||
@ -187,14 +187,14 @@ function App() {
|
|||||||
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route
|
{/* <Route
|
||||||
path="/verify-otp"
|
path="/verify-otp"
|
||||||
element={
|
element={
|
||||||
<RequireRole allowedRoles={['EstablishmentUser', 'Admin']}>
|
<RequireRole allowedRoles={['EstablishmentUser', 'Admin']}>
|
||||||
<OTPVerification />
|
<OTPVerification />
|
||||||
</RequireRole>
|
</RequireRole>
|
||||||
}
|
}
|
||||||
/>
|
/> */}
|
||||||
<Route
|
<Route
|
||||||
path="/history"
|
path="/history"
|
||||||
element={
|
element={
|
||||||
|
|||||||
@ -206,56 +206,20 @@ const submit = async (e) => {
|
|||||||
countdownIntervalRef.current = null;
|
countdownIntervalRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Show success message
|
||||||
// Request OTP after successful login
|
showToast('success', 'Login successful!');
|
||||||
const otpResponse = await requestOtp(email);
|
|
||||||
|
|
||||||
if (otpResponse.status === 'success' && otpResponse.message === 'OTP sent successfully to your registered email') {
|
// Add a small delay before navigation to allow toast to be visible
|
||||||
// Store user data in localStorage
|
setTimeout(() => {
|
||||||
localStorage.setItem('user_role', userData.role);
|
try {
|
||||||
localStorage.setItem('user_profile', JSON.stringify({
|
// Redirect based on user role
|
||||||
id: userData.id,
|
const redirectPath = userData.role === 'Admin' ? '/admin/dashboard' : '/dashboard';
|
||||||
name: userData.name,
|
navigate(location.state?.from || redirectPath);
|
||||||
email: userData.email
|
} catch (err) {
|
||||||
}));
|
console.error('Navigation failed:', err);
|
||||||
|
showToast('error', 'Failed to redirect. Please try again.');
|
||||||
// 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');
|
|
||||||
}
|
}
|
||||||
} catch (otpError) {
|
}, 1000); // 1 second delay
|
||||||
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 {
|
} else {
|
||||||
throw new Error(response?.message || 'Invalid credentials');
|
throw new Error(response?.message || 'Invalid credentials');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,402 +1,402 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
// import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { requestOtp, verifyOtp } from '@/services/auth/authService';
|
// import { requestOtp, verifyOtp } from '@/services/auth/authService';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
// import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { AlertCircle, Clock, CheckCircle } from "lucide-react";
|
// import { AlertCircle, Clock, CheckCircle } from "lucide-react";
|
||||||
|
|
||||||
const logoSrc = '/assets/images/FCSCLogo.svg';
|
// const logoSrc = '/assets/images/FCSCLogo.svg';
|
||||||
|
|
||||||
const OTPVerification = () => {
|
// const OTPVerification = () => {
|
||||||
const navigate = useNavigate();
|
// const navigate = useNavigate();
|
||||||
const location = useLocation();
|
// const location = useLocation();
|
||||||
|
|
||||||
const [otp, setOtp] = useState(['', '', '', '', '', '']);
|
// const [otp, setOtp] = useState(['', '', '', '', '', '']);
|
||||||
const [error, setError] = useState('');
|
// const [error, setError] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
// const [loading, setLoading] = useState(false);
|
||||||
const [toast, setToast] = useState(null);
|
// const [toast, setToast] = useState(null);
|
||||||
const [resendDisabled, setResendDisabled] = useState(false);
|
// const [resendDisabled, setResendDisabled] = useState(false);
|
||||||
const [countdown, setCountdown] = useState(600);
|
// const [countdown, setCountdown] = useState(600);
|
||||||
const [attemptsRemaining, setAttemptsRemaining] = useState(3);
|
// const [attemptsRemaining, setAttemptsRemaining] = useState(3);
|
||||||
|
|
||||||
const inputRefs = useRef(Array(6).fill(null).map(() => React.createRef()));
|
// const inputRefs = useRef(Array(6).fill(null).map(() => React.createRef()));
|
||||||
const toastTimeoutRef = useRef(null);
|
// const toastTimeoutRef = useRef(null);
|
||||||
const countdownRef = useRef(null);
|
// const countdownRef = useRef(null);
|
||||||
|
|
||||||
// Get email from location state or use a default
|
// // Get email from location state or use a default
|
||||||
const maskedEmail = location.state?.email ?
|
// const maskedEmail = location.state?.email ?
|
||||||
`${location.state.email.split('@')[0].substring(0, 2)}***@${location.state.email.split('@')[1]}` :
|
// `${location.state.email.split('@')[0].substring(0, 2)}***@${location.state.email.split('@')[1]}` :
|
||||||
'na***@company.com';
|
// 'na***@company.com';
|
||||||
|
|
||||||
// Toast handling
|
// // Toast handling
|
||||||
const closeToast = useCallback(() => {
|
// const closeToast = useCallback(() => {
|
||||||
if (toastTimeoutRef.current) {
|
// if (toastTimeoutRef.current) {
|
||||||
clearTimeout(toastTimeoutRef.current);
|
// clearTimeout(toastTimeoutRef.current);
|
||||||
toastTimeoutRef.current = null;
|
// toastTimeoutRef.current = null;
|
||||||
}
|
// }
|
||||||
setToast(null);
|
// setToast(null);
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
const showToast = useCallback((type, message) => {
|
// const showToast = useCallback((type, message) => {
|
||||||
if (!message) return;
|
// if (!message) return;
|
||||||
if (toastTimeoutRef.current) {
|
// if (toastTimeoutRef.current) {
|
||||||
clearTimeout(toastTimeoutRef.current);
|
// clearTimeout(toastTimeoutRef.current);
|
||||||
toastTimeoutRef.current = null;
|
// toastTimeoutRef.current = null;
|
||||||
}
|
// }
|
||||||
setToast({ type, message });
|
// setToast({ type, message });
|
||||||
toastTimeoutRef.current = setTimeout(() => {
|
// toastTimeoutRef.current = setTimeout(() => {
|
||||||
setToast(null);
|
// setToast(null);
|
||||||
toastTimeoutRef.current = null;
|
// toastTimeoutRef.current = null;
|
||||||
}, 4000);
|
// }, 4000);
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
// Handle OTP input change
|
// // Handle OTP input change
|
||||||
const handleOtpChange = (e, index) => {
|
// const handleOtpChange = (e, index) => {
|
||||||
const value = e.target.value;
|
// const value = e.target.value;
|
||||||
|
|
||||||
// Only allow numbers and limit to 1 character
|
// // Only allow numbers and limit to 1 character
|
||||||
if (value && !/^\d*$/.test(value)) return;
|
// if (value && !/^\d*$/.test(value)) return;
|
||||||
|
|
||||||
const newOtp = [...otp];
|
// const newOtp = [...otp];
|
||||||
newOtp[index] = value.slice(-1);
|
// newOtp[index] = value.slice(-1);
|
||||||
setOtp(newOtp);
|
// setOtp(newOtp);
|
||||||
|
|
||||||
// Move to next input if there's a value and we're not on the last input
|
// // 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) {
|
// if (value && index < 5 && inputRefs.current[index + 1]?.current) {
|
||||||
inputRefs.current[index + 1].current.focus();
|
// inputRefs.current[index + 1].current.focus();
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Handle backspace
|
// // Handle backspace
|
||||||
const handleKeyDown = (e, index) => {
|
// const handleKeyDown = (e, index) => {
|
||||||
if (e.key === 'Backspace' && !otp[index] && index > 0 && inputRefs.current[index - 1]?.current) {
|
// if (e.key === 'Backspace' && !otp[index] && index > 0 && inputRefs.current[index - 1]?.current) {
|
||||||
// Move to previous input on backspace
|
// // Move to previous input on backspace
|
||||||
inputRefs.current[index - 1].current.focus();
|
// inputRefs.current[index - 1].current.focus();
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Handle paste
|
// // Handle paste
|
||||||
const handlePaste = (e) => {
|
// const handlePaste = (e) => {
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
const pasteData = e.clipboardData.getData('text/plain').trim();
|
// const pasteData = e.clipboardData.getData('text/plain').trim();
|
||||||
if (/^\d{6}$/.test(pasteData)) {
|
// if (/^\d{6}$/.test(pasteData)) {
|
||||||
const newOtp = pasteData.split('').slice(0, 6);
|
// const newOtp = pasteData.split('').slice(0, 6);
|
||||||
setOtp([...newOtp, ...Array(6 - newOtp.length).fill('')]);
|
// setOtp([...newOtp, ...Array(6 - newOtp.length).fill('')]);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Handle verify OTP
|
// // Handle verify OTP
|
||||||
const handleVerify = async (e) => {
|
// const handleVerify = async (e) => {
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
const otpValue = otp.join('');
|
// const otpValue = otp.join('');
|
||||||
|
|
||||||
if (otpValue.length !== 6) {
|
// if (otpValue.length !== 6) {
|
||||||
setError('Please enter a valid 6-digit code');
|
// setError('Please enter a valid 6-digit code');
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
setLoading(true);
|
// setLoading(true);
|
||||||
setError('');
|
// setError('');
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
const response = await verifyOtp({
|
// const response = await verifyOtp({
|
||||||
registered_email: location.state?.email,
|
// registered_email: location.state?.email,
|
||||||
otp: otpValue
|
// otp: otpValue
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (response?.status === 'success') {
|
// if (response?.status === 'success') {
|
||||||
// Show success message immediately
|
// // Show success message immediately
|
||||||
showToast('success', 'Verified! You are signed in. Redirecting to your dashboard.');
|
// showToast('success', 'Verified! You are signed in. Redirecting to your dashboard.');
|
||||||
|
|
||||||
// Check for admin role in multiple possible locations
|
// // Check for admin role in multiple possible locations
|
||||||
const userRole = localStorage.getItem('user_role') ||
|
// const userRole = localStorage.getItem('user_role') ||
|
||||||
response?.data?.user?.role ||
|
// response?.data?.user?.role ||
|
||||||
response?.data?.role;
|
// response?.data?.role;
|
||||||
|
|
||||||
// Check if user is admin (case-insensitive check)
|
// // Check if user is admin (case-insensitive check)
|
||||||
const isAdmin = userRole?.toLowerCase() === 'admin' ||
|
// const isAdmin = userRole?.toLowerCase() === 'admin' ||
|
||||||
response?.data?.isAdmin === true;
|
// response?.data?.isAdmin === true;
|
||||||
|
|
||||||
// Use the redirect path from location state if available, otherwise determine based on role
|
// // Use the redirect path from location state if available, otherwise determine based on role
|
||||||
const redirectPath = location.state?.from ||
|
// const redirectPath = location.state?.from ||
|
||||||
(isAdmin ? '/admin/dashboard' : '/dashboard');
|
// (isAdmin ? '/admin/dashboard' : '/dashboard');
|
||||||
|
|
||||||
// Wait for 5 seconds before redirecting
|
// // Wait for 5 seconds before redirecting
|
||||||
setLoading(true);
|
// setLoading(true);
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
navigate(redirectPath, { replace: true });
|
// navigate(redirectPath, { replace: true });
|
||||||
}, 1000);
|
// }, 1000);
|
||||||
} else {
|
// } else {
|
||||||
throw new Error(response?.message || 'Invalid OTP');
|
// throw new Error(response?.message || 'Invalid OTP');
|
||||||
}
|
// }
|
||||||
|
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
console.error('OTP verification failed', err);
|
// console.error('OTP verification failed', err);
|
||||||
const errorMessage = err.response?.data?.message ||
|
// const errorMessage = err.response?.data?.message ||
|
||||||
err.message ||
|
// err.message ||
|
||||||
'An error occurred during OTP verification. Please try again.';
|
// 'An error occurred during OTP verification. Please try again.';
|
||||||
|
|
||||||
const newAttempts = attemptsRemaining - 1;
|
// const newAttempts = attemptsRemaining - 1;
|
||||||
setAttemptsRemaining(newAttempts);
|
// setAttemptsRemaining(newAttempts);
|
||||||
|
|
||||||
// Set the error message for incorrect OTP
|
// // Set the error message for incorrect OTP
|
||||||
const displayError = errorMessage || 'Invalid verification code';
|
// const displayError = errorMessage || 'Invalid verification code';
|
||||||
setError(displayError);
|
// setError(displayError);
|
||||||
|
|
||||||
if (newAttempts <= 0) {
|
// if (newAttempts <= 0) {
|
||||||
setOtp(Array(6).fill(''));
|
// setOtp(Array(6).fill(''));
|
||||||
// Show error message and redirect to login after a short delay
|
// // Show error message and redirect to login after a short delay
|
||||||
setError('Maximum attempts reached. Redirecting to login...');
|
// setError('Maximum attempts reached. Redirecting to login...');
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
navigate('/login', { replace: true });
|
// navigate('/login', { replace: true });
|
||||||
}, 2000);
|
// }, 2000);
|
||||||
} else {
|
// } else {
|
||||||
setOtp(Array(6).fill(''));
|
// setOtp(Array(6).fill(''));
|
||||||
// Focus the first input if it exists
|
// // Focus the first input if it exists
|
||||||
if (inputRefs.current[0]) {
|
// if (inputRefs.current[0]) {
|
||||||
inputRefs.current[0].focus();
|
// inputRefs.current[0].focus();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} finally {
|
// } finally {
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Handle resend OTP
|
// // Handle resend OTP
|
||||||
const handleResend = async () => {
|
// const handleResend = async () => {
|
||||||
if (resendDisabled) return;
|
// if (resendDisabled) return;
|
||||||
|
|
||||||
// Show loading state immediately
|
// // Show loading state immediately
|
||||||
setResendDisabled(true);
|
// setResendDisabled(true);
|
||||||
setLoading(true);
|
// setLoading(true);
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
// Call the API to resend OTP
|
// // Call the API to resend OTP
|
||||||
const response = await requestOtp(location.state?.email);
|
// const response = await requestOtp(location.state?.email);
|
||||||
|
|
||||||
// Update state and show success toast immediately
|
// // Update state and show success toast immediately
|
||||||
const tenMinutesInSeconds = 10 * 60;
|
// const tenMinutesInSeconds = 10 * 60;
|
||||||
setCountdown(tenMinutesInSeconds);
|
// setCountdown(tenMinutesInSeconds);
|
||||||
setAttemptsRemaining(3);
|
// setAttemptsRemaining(3);
|
||||||
setError('');
|
// setError('');
|
||||||
setOtp(Array(6).fill(''));
|
// setOtp(Array(6).fill(''));
|
||||||
|
|
||||||
// Use setTimeout to ensure the toast shows up in the next tick
|
// // Use setTimeout to ensure the toast shows up in the next tick
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
showToast('success', response.message || 'A new OTP has been sent to your email.');
|
// showToast('success', response.message || 'A new OTP has been sent to your email.');
|
||||||
}, 0);
|
// }, 0);
|
||||||
|
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('Error resending OTP:', error);
|
// console.error('Error resending OTP:', error);
|
||||||
showToast('error', 'An error occurred while sending OTP. Please try again.');
|
// showToast('error', 'An error occurred while sending OTP. Please try again.');
|
||||||
} finally {
|
// } finally {
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
|
|
||||||
// Re-enable resend after 60 seconds
|
// // Re-enable resend after 60 seconds
|
||||||
const timer = setTimeout(() => {
|
// const timer = setTimeout(() => {
|
||||||
setResendDisabled(false);
|
// setResendDisabled(false);
|
||||||
}, 60000);
|
// }, 60000);
|
||||||
|
|
||||||
// Cleanup timer on component unmount
|
// // Cleanup timer on component unmount
|
||||||
return () => clearTimeout(timer);
|
// return () => clearTimeout(timer);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Start countdown on component mount
|
// // Start countdown on component mount
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
// Always use 10 minutes for the countdown
|
// // Always use 10 minutes for the countdown
|
||||||
const tenMinutesInMs = 10 * 60 * 1000;
|
// const tenMinutesInMs = 10 * 60 * 1000;
|
||||||
const expiryTime = Date.now() + tenMinutesInMs;
|
// const expiryTime = Date.now() + tenMinutesInMs;
|
||||||
|
|
||||||
const updateCountdown = () => {
|
// const updateCountdown = () => {
|
||||||
const now = Date.now();
|
// const now = Date.now();
|
||||||
const remainingSeconds = Math.max(0, Math.floor((expiryTime - now) / 1000));
|
// const remainingSeconds = Math.max(0, Math.floor((expiryTime - now) / 1000));
|
||||||
|
|
||||||
setCountdown(remainingSeconds);
|
// setCountdown(remainingSeconds);
|
||||||
|
|
||||||
if (remainingSeconds <= 0) {
|
// if (remainingSeconds <= 0) {
|
||||||
clearInterval(countdownRef.current);
|
// clearInterval(countdownRef.current);
|
||||||
setResendDisabled(false);
|
// setResendDisabled(false);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Initial update
|
// // Initial update
|
||||||
updateCountdown();
|
// updateCountdown();
|
||||||
|
|
||||||
// Set up interval for countdown updates
|
// // Set up interval for countdown updates
|
||||||
countdownRef.current = setInterval(updateCountdown, 1000);
|
// countdownRef.current = setInterval(updateCountdown, 1000);
|
||||||
|
|
||||||
return () => {
|
// return () => {
|
||||||
if (countdownRef.current) clearInterval(countdownRef.current);
|
// if (countdownRef.current) clearInterval(countdownRef.current);
|
||||||
};
|
// };
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
// Cleanup on unmount
|
// // Cleanup on unmount
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
return () => {
|
// return () => {
|
||||||
if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current);
|
// if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current);
|
||||||
if (countdownRef.current) clearInterval(countdownRef.current);
|
// if (countdownRef.current) clearInterval(countdownRef.current);
|
||||||
};
|
// };
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
const minutes = Math.floor(countdown / 60);
|
// const minutes = Math.floor(countdown / 60);
|
||||||
const seconds = countdown % 60;
|
// const seconds = countdown % 60;
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<div className="min-h-screen bg-[#F7F7F7] flex items-center justify-center px-4">
|
// <div className="min-h-screen bg-[#F7F7F7] flex items-center justify-center px-4">
|
||||||
{/* Toast Notification */}
|
// {/* Toast Notification */}
|
||||||
{toast && (
|
// {toast && (
|
||||||
<div className="fixed top-6 inset-x-0 z-50 flex justify-center px-4">
|
// <div className="fixed top-6 inset-x-0 z-50 flex justify-center px-4">
|
||||||
<div
|
// <div
|
||||||
className={`text-sm rounded-lg px-4 py-3 border flex items-center justify-between gap-3 shadow-lg max-w-md ${
|
// className={`text-sm rounded-lg px-4 py-3 border flex items-center justify-between gap-3 shadow-lg max-w-md ${
|
||||||
toast.type === 'success'
|
// toast.type === 'success'
|
||||||
? 'bg-green-100 border-green-400 text-green-800'
|
// ? 'bg-green-100 border-green-400 text-green-800'
|
||||||
: toast.type === 'error'
|
// : toast.type === 'error'
|
||||||
? 'bg-[#FEF2F2] border-[#FECACA] text-[#B91C1C]'
|
// ? 'bg-[#FEF2F2] border-[#FECACA] text-[#B91C1C]'
|
||||||
: 'bg-blue-100 border-blue-200 text-blue-800'
|
// : 'bg-blue-100 border-blue-200 text-blue-800'
|
||||||
}`}
|
// }`}
|
||||||
>
|
// >
|
||||||
<div className="flex items-center gap-2">
|
// <div className="flex items-center gap-2">
|
||||||
{toast.type === 'error' ? (
|
// {toast.type === 'error' ? (
|
||||||
<AlertCircle className="h-5 w-5 flex-shrink-0" />
|
// <AlertCircle className="h-5 w-5 flex-shrink-0" />
|
||||||
) : toast.type === 'success' ? (
|
// ) : toast.type === 'success' ? (
|
||||||
<CheckCircle className="h-5 w-5 flex-shrink-0 text-green-600" />
|
// <CheckCircle className="h-5 w-5 flex-shrink-0 text-green-600" />
|
||||||
) : null}
|
// ) : null}
|
||||||
<span>{toast.message}</span>
|
// <span>{toast.message}</span>
|
||||||
</div>
|
// </div>
|
||||||
<button
|
// <button
|
||||||
type="button"
|
// type="button"
|
||||||
className="flex h-6 w-6 items-center justify-center rounded hover:bg-black/5"
|
// className="flex h-6 w-6 items-center justify-center rounded hover:bg-black/5"
|
||||||
onClick={closeToast}
|
// onClick={closeToast}
|
||||||
>
|
// >
|
||||||
✕
|
// ✕
|
||||||
</button>
|
// </button>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
)}
|
// )}
|
||||||
|
|
||||||
<div className="w-full max-w-md">
|
// <div className="w-full max-w-md">
|
||||||
<div className="bg-white rounded-lg shadow-sm ring-1 ring-gray-200 overflow-hidden">
|
// <div className="bg-white rounded-lg shadow-sm ring-1 ring-gray-200 overflow-hidden">
|
||||||
{/* Logo Section */}
|
// {/* Logo Section */}
|
||||||
<div className="px-7 py-6 border-b border-gray-200 bg-[#F2ECCF] text-center">
|
// <div className="px-7 py-6 border-b border-gray-200 bg-[#F2ECCF] text-center">
|
||||||
<div className="mb-2">
|
// <div className="mb-2">
|
||||||
<img src={logoSrc} alt="FCSC Logo" className="mx-auto h-16 w-auto" />
|
// <img src={logoSrc} alt="FCSC Logo" className="mx-auto h-16 w-auto" />
|
||||||
</div>
|
// </div>
|
||||||
<h1 className="text-[17px] font-semibold text-[#1F2937] mt-0">
|
// <h1 className="text-[17px] font-semibold text-[#1F2937] mt-0">
|
||||||
IIP (Index of Industrial Production)
|
// IIP (Index of Industrial Production)
|
||||||
</h1>
|
// </h1>
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
<div className="p-7">
|
// <div className="p-7">
|
||||||
{/* <div className="text-center mb-6">
|
// {/* <div className="text-center mb-6">
|
||||||
<h2 className="text-xl font-bold text-gray-800">Email OTP</h2>
|
// <h2 className="text-xl font-bold text-gray-800">Email OTP</h2>
|
||||||
<p className="text-gray-600 mt-1">Step 2 of 2</p>
|
// <p className="text-gray-600 mt-1">Step 2 of 2</p>
|
||||||
</div> */}
|
// </div> */}
|
||||||
|
|
||||||
<form onSubmit={handleVerify}>
|
// <form onSubmit={handleVerify}>
|
||||||
<div className="space-y-6">
|
// <div className="space-y-6">
|
||||||
<div className="text-center">
|
// <div className="text-center">
|
||||||
<h3 className="text-lg font-medium text-gray-800">Verify your sign-in</h3>
|
// <h3 className="text-lg font-medium text-gray-800">Verify your sign-in</h3>
|
||||||
<p className="text-gray-600 mt-1">
|
// <p className="text-gray-600 mt-1">
|
||||||
We sent a 6-digit authentication code to <span className="font-medium">{maskedEmail}</span>
|
// We sent a 6-digit authentication code to <span className="font-medium">{maskedEmail}</span>
|
||||||
</p>
|
// </p>
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
{/* OTP Inputs */}
|
// {/* OTP Inputs */}
|
||||||
<div className="space-y-4">
|
// <div className="space-y-4">
|
||||||
<div className="flex justify-center space-x-3">
|
// <div className="flex justify-center space-x-3">
|
||||||
{otp.map((digit, index) => (
|
// {otp.map((digit, index) => (
|
||||||
<input
|
// <input
|
||||||
key={index}
|
// key={index}
|
||||||
ref={(el) => { inputRefs.current[index] = { current: el } }}
|
// ref={(el) => { inputRefs.current[index] = { current: el } }}
|
||||||
type="text"
|
// type="text"
|
||||||
inputMode="numeric"
|
// inputMode="numeric"
|
||||||
pattern="[0-9]*"
|
// pattern="[0-9]*"
|
||||||
maxLength={1}
|
// maxLength={1}
|
||||||
value={digit}
|
// value={digit}
|
||||||
onChange={(e) => handleOtpChange(e, index)}
|
// onChange={(e) => handleOtpChange(e, index)}
|
||||||
onKeyDown={(e) => handleKeyDown(e, index)}
|
// onKeyDown={(e) => handleKeyDown(e, index)}
|
||||||
onPaste={handlePaste}
|
// onPaste={handlePaste}
|
||||||
className={`w-12 h-12 text-center text-xl bg-transparent border-2 rounded focus:outline-none focus:ring-0 ${
|
// className={`w-12 h-12 text-center text-xl bg-transparent border-2 rounded focus:outline-none focus:ring-0 ${
|
||||||
error ? 'border-red-500' :
|
// error ? 'border-red-500' :
|
||||||
digit ? 'border-[#92722A]' :
|
// digit ? 'border-[#92722A]' :
|
||||||
'border-gray-300 hover:border-gray-400 focus:border-[#92722A] focus:border-b-2 focus:border-b-[#92722A]'
|
// 'border-gray-300 hover:border-gray-400 focus:border-[#92722A] focus:border-b-2 focus:border-b-[#92722A]'
|
||||||
}`}
|
// }`}
|
||||||
autoFocus={index === 0}
|
// autoFocus={index === 0}
|
||||||
disabled={loading || attemptsRemaining <= 0}
|
// disabled={loading || attemptsRemaining <= 0}
|
||||||
/>
|
// />
|
||||||
))}
|
// ))}
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
{error && (
|
// {error && (
|
||||||
<div className="text-red-600 text-sm flex items-center justify-center">
|
// <div className="text-red-600 text-sm flex items-center justify-center">
|
||||||
<AlertCircle className="h-4 w-4 mr-1" />
|
// <AlertCircle className="h-4 w-4 mr-1" />
|
||||||
{error}
|
// {error}
|
||||||
</div>
|
// </div>
|
||||||
)}
|
// )}
|
||||||
|
|
||||||
<div className="flex justify-between items-center text-sm text-gray-600">
|
// <div className="flex justify-between items-center text-sm text-gray-600">
|
||||||
<div className="flex items-center space-x-2 leading-none">
|
// <div className="flex items-center space-x-2 leading-none">
|
||||||
<Clock className="h-4 w-4 flex-shrink-0 -mt-0.5" />
|
// <Clock className="h-4 w-4 flex-shrink-0 -mt-0.5" />
|
||||||
<span className="text-sm -ml-1">
|
// <span className="text-sm -ml-1">
|
||||||
Code expires in {minutes}:{seconds < 10 ? `0${seconds}` : seconds}
|
// Code expires in {minutes}:{seconds < 10 ? `0${seconds}` : seconds}
|
||||||
</span>
|
// </span>
|
||||||
</div>
|
// </div>
|
||||||
<span>Attempts: {attemptsRemaining}/3</span>
|
// <span>Attempts: {attemptsRemaining}/3</span>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
// <div className="space-y-4">
|
||||||
<button
|
// <button
|
||||||
type="submit"
|
// type="submit"
|
||||||
disabled={loading || otp.join('').length !== 6 || attemptsRemaining <= 0}
|
// disabled={loading || otp.join('').length !== 6 || attemptsRemaining <= 0}
|
||||||
className={`w-full py-3 px-4 rounded-md font-medium bg-[#92722A] text-white ${
|
// className={`w-full py-3 px-4 rounded-md font-medium bg-[#92722A] text-white ${
|
||||||
loading || attemptsRemaining <= 0 || otp.join('').length !== 6
|
// loading || attemptsRemaining <= 0 || otp.join('').length !== 6
|
||||||
? 'opacity-50 cursor-not-allowed'
|
// ? 'opacity-50 cursor-not-allowed'
|
||||||
: 'hover:bg-[#7a5e23] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#92722A]'
|
// : 'hover:bg-[#7a5e23] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#92722A]'
|
||||||
}`}
|
// }`}
|
||||||
>
|
// >
|
||||||
{loading ? 'Verifying...' : 'Verify'}
|
// {loading ? 'Verifying...' : 'Verify'}
|
||||||
</button>
|
// </button>
|
||||||
<button
|
// <button
|
||||||
type="button"
|
// type="button"
|
||||||
onClick={() => navigate('/login')}
|
// onClick={() => navigate('/login')}
|
||||||
className="w-full py-3 px-4 rounded-md font-medium border border-[#92722A] bg-white text-[#92722A] hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#92722A]"
|
// className="w-full py-3 px-4 rounded-md font-medium border border-[#92722A] bg-white text-[#92722A] hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#92722A]"
|
||||||
>
|
// >
|
||||||
Cancel
|
// Cancel
|
||||||
</button>
|
// </button>
|
||||||
|
|
||||||
<div className="text-center">
|
// <div className="text-center">
|
||||||
<button
|
// <button
|
||||||
type="button"
|
// type="button"
|
||||||
onClick={handleResend}
|
// onClick={handleResend}
|
||||||
disabled={resendDisabled}
|
// disabled={resendDisabled}
|
||||||
className={`text-sm font-medium ${
|
// className={`text-sm font-medium ${
|
||||||
resendDisabled ? 'text-gray-400' : 'text-[#92722A] hover:text-[#7a5e23]'
|
// resendDisabled ? 'text-gray-400' : 'text-[#92722A] hover:text-[#7a5e23]'
|
||||||
}`}
|
// }`}
|
||||||
>
|
// >
|
||||||
{resendDisabled ? `Resend code (${Math.ceil(countdown/60)}:${(countdown%60).toString().padStart(2, '0')})` : 'Resend code'}
|
// {resendDisabled ? `Resend code (${Math.ceil(countdown/60)}:${(countdown%60).toString().padStart(2, '0')})` : 'Resend code'}
|
||||||
</button>
|
// </button>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</form>
|
// </form>
|
||||||
|
|
||||||
<div className="mt-6 pt-6 border-t border-gray-200 text-center">
|
// <div className="mt-6 pt-6 border-t border-gray-200 text-center">
|
||||||
|
|
||||||
<p className="mt-2 text-sm text-gray-600">
|
// <p className="mt-2 text-sm text-gray-600">
|
||||||
Having trouble?{' '}
|
// Having trouble?{' '}
|
||||||
<button
|
// <button
|
||||||
onClick={() => navigate('/')}
|
// onClick={() => navigate('/')}
|
||||||
className="text-[#92722A] hover:text-[#7a5e23] font-medium focus:outline-none"
|
// className="text-[#92722A] hover:text-[#7a5e23] font-medium focus:outline-none"
|
||||||
>
|
// >
|
||||||
Contact support
|
// Contact support
|
||||||
</button>
|
// </button>
|
||||||
</p>
|
// </p>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
||||||
export default OTPVerification;
|
// export default OTPVerification;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user