changes added in company profile

This commit is contained in:
Malini 2026-01-21 18:17:15 +05:30
parent a13ea7ffef
commit e00e7a1a38
5 changed files with 281 additions and 160 deletions

View File

@ -15,6 +15,20 @@ import HeaderBar from '@/components/layout/HeaderBar';
// return { quarter, year }; // return { quarter, year };
// }; // };
const formatName = (name = '') => {
return name
// camelCase add space
.replace(/([a-z])([A-Z])/g, '$1 $2')
// split by space
.split(' ')
// capitalize each word
.map(
word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
)
.join(' ');
};
export const WelcomeSection = ({ data = null, loading = false, error = '' }) => { export const WelcomeSection = ({ data = null, loading = false, error = '' }) => {
// const { quarter: currentQuarter, year: currentYear } = getCurrentQuarterAndYear(); // const { quarter: currentQuarter, year: currentYear } = getCurrentQuarterAndYear();
@ -32,18 +46,20 @@ export const WelcomeSection = ({ data = null, loading = false, error = '' }) =>
<HeaderBar /> <HeaderBar />
<div className="max-w-[1280px] mx-auto px-4 pt-2 pb-1"> <div className="max-w-[1280px] mx-auto px-4 pt-2 pb-1">
<div className="space-y-1"> <div className="space-y-1">
<h2 className="w-[360px] text-[18px] pt-2 leading-[28px] font-medium text-[#232528]"> <h2 className="w-[360px] text-[18px] pt-2 leading-[28px] font-medium text-[#232528]">
Welcome{' '} Welcome{' '}
{(() => { {(() => {
try { try {
const userProfile = JSON.parse(localStorage.getItem('user_profile')); const userProfile = JSON.parse(localStorage.getItem('user_profile'));
return userProfile?.name ? userProfile.name : ''; return userProfile?.name ? formatName(userProfile.name) : '';
} catch (error) { } catch (error) {
console.error('Error reading user_profile from sessionStorage:', error); console.error('Error reading user_profile from localStorage:', error);
return ''; return '';
} }
})()}! })()}
</h2> !
</h2>
{/* <p {/* <p
className={`w-[360px] text-[14px] leading-[24px] font-normal ${ className={`w-[360px] text-[14px] leading-[24px] font-normal ${
error ? 'text-red-600' : 'text-[#5F646D]' error ? 'text-red-600' : 'text-[#5F646D]'

View File

@ -6,6 +6,8 @@ import apiClient from '@/services/api/apiClient';
import { getBeforePreviousData, getQuarterPeriods } from '@/services/submissions/submissionService'; import { getBeforePreviousData, getQuarterPeriods } from '@/services/submissions/submissionService';
import { getProductSubmissionHistory } from '@/services/submissions/submissionService'; import { getProductSubmissionHistory } from '@/services/submissions/submissionService';
import { Line } from 'react-chartjs-2'; import { Line } from 'react-chartjs-2';
import { IoIosArrowBack } from "react-icons/io";
// Helper function to get months for quarter // Helper function to get months for quarter
const getMonthsForQuarter = (quarter, year) => { const getMonthsForQuarter = (quarter, year) => {
@ -931,12 +933,29 @@ setAdditionalForecastMonths([
<AdminHeader /> <AdminHeader />
<div className="max-w-[1280px] mx-auto px-4 py-6 space-y-6"> <div className="max-w-[1280px] mx-auto px-4 py-6 space-y-6">
<nav className="flex items-center gap-2 text-sm text-[#8F9299] mb-6"> <nav className="flex items-center px-3 justify-between text-sm ">
<Link to="/admin/validations" className="flex items-center gap-2 text-[#232528] hover:underline"> {/* LEFT SIDE */}
Manage Submissions <img src={caretUpSrc} alt="" className="h-3 w-3 " /> <div className="flex items-center gap-2 text-[#8F9299]">
</Link> <Link
<span className="text-[#7A7F87] font-medium">View Submission</span> to="/admin/validations"
</nav> className="flex items-center gap-2 text-[#232528] hover:underline"
>
Manage Submissions
<img src={caretUpSrc} alt="" className="h-3 w-3" />
</Link>
<span className="text-[#7A7F87] font-medium">
View Submission
</span>
</div>
{/* RIGHT SIDE */}
{/* <button
onClick={() => navigate(-1)}
className="flex items-center gap-1 text-sm text-white px-2 py-1 rounded-md bg-[#B68A35] hover:underline"
>
Back
</button> */}
</nav>
<section className="min-h-[219px] rounded-[16px] border border-[#E6EAF5] bg-white px-0 py-6 shadow-[0_16px_32px_rgba(15,23,42,0.08)]"> <section className="min-h-[219px] rounded-[16px] border border-[#E6EAF5] bg-white px-0 py-6 shadow-[0_16px_32px_rgba(15,23,42,0.08)]">
<div className="grid gap-1 lg:grid-cols-2"> <div className="grid gap-1 lg:grid-cols-2">
@ -1888,25 +1907,52 @@ setAdditionalForecastMonths([
</div> </div>
)} )}
<div className="border-t border-[#E5E7EB] bg-white pt-2 pb-4 px-6 max-w-[1240px] mx-auto"> <div className="border-t border-[#E5E7EB] bg-white pt-2 pb-4 px-6 max-w-[1240px] mx-auto my-auto">
<div className="mx-auto flex max-w-[1280px] justify-end gap-4"> <div className="mx-auto flex max-w-[1280px] items-center justify-between">
<button {/* LEFT SIDE - Back */}
type="button" <button
className="inline-flex h-11 items-center justify-center rounded-[10px] border border-[#B52520] px-6 text-sm font-semibold text-[#B52520] transition-colors hover:bg-[#FEE2E2] disabled:opacity-50 disabled:cursor-not-allowed" onClick={() => navigate(-1)}
onClick={() => setIsRejectOpen(true)} className="flex items-center gap-1 text-[#B68A35] px-4 py-1 rounded-md border border-[#B68A35] hover:underline"
disabled={actionLoading || submissionData?.status === 'Rejected' || submissionData?.status === 'Approved'} >
> <IoIosArrowBack /> Back
{submissionData?.status === 'Rejected' ? 'Already Rejected' : 'Reject'} </button>
</button>
<button {/* RIGHT SIDE - Actions */}
type="button" <div className="flex gap-4">
className="inline-flex h-11 items-center justify-center rounded-[10px] bg-[#92722A] px-6 text-sm font-semibold text-white transition-colors hover:bg-[#B68A35] disabled:opacity-50 disabled:cursor-not-allowed" <button
onClick={() => setIsApproveOpen(true)} type="button"
disabled={actionLoading || submissionData?.status === 'Approved' || submissionData?.status === 'Rejected'} className="inline-flex h-11 items-center justify-center rounded-[10px] border border-[#B52520] px-6 text-sm font-semibold text-[#B52520] transition-colors hover:bg-[#FEE2E2] disabled:opacity-50 disabled:cursor-not-allowed"
> onClick={() => setIsRejectOpen(true)}
{actionLoading ? 'Processing...' : submissionData?.status === 'Approved' ? 'Already Approved' : 'Approve'} disabled={
</button> actionLoading ||
</div> submissionData?.status === 'Rejected' ||
submissionData?.status === 'Approved'
}
>
{submissionData?.status === 'Rejected'
? 'Already Rejected'
: 'Reject'}
</button>
<button
type="button"
className="inline-flex h-11 items-center justify-center rounded-[10px] bg-[#92722A] px-6 text-sm font-semibold text-white transition-colors hover:bg-[#B68A35] disabled:opacity-50 disabled:cursor-not-allowed"
onClick={() => setIsApproveOpen(true)}
disabled={
actionLoading ||
submissionData?.status === 'Approved' ||
submissionData?.status === 'Rejected'
}
>
{actionLoading
? 'Processing...'
: submissionData?.status === 'Approved'
? 'Already Approved'
: 'Approve'}
</button>
</div>
</div>
</div> </div>
{isApproveOpen && ( {isApproveOpen && (
@ -1938,11 +1984,16 @@ setAdditionalForecastMonths([
> >
{actionLoading ? 'Processing...' : 'Approve'} {actionLoading ? 'Processing...' : 'Approve'}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
)} )}
</div> </div>
); );
}; };

View File

@ -66,18 +66,18 @@ const ManageSubmissions = () => {
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null); const [error, setError] = React.useState(null);
const [search, setSearch] = React.useState(''); const [search, setSearch] = React.useState('');
const [year, setYear] = React.useState(''); const [year, setYear] = React.useState('All');
// Function to get current quarter (1-4) // Function to get current quarter (1-4)
const getCurrentQuarter = () => { const getCurrentQuarter = () => {
const month = new Date().getMonth(); const month = new Date().getMonth();
return `Q${Math.floor(month / 3) + 1}`; return `Q${Math.floor(month / 3) + 1}`;
}; };
const [quarter, setQuarter] = React.useState(getCurrentQuarter()); const [quarter, setQuarter] = React.useState('All');
const [emirate, setEmirate] = React.useState('All'); const [emirate, setEmirate] = React.useState('All');
const [status, setStatus] = React.useState('All'); const [status, setStatus] = React.useState('All');
const [selectedQuarter, setSelectedQuarter] = React.useState(getCurrentQuarter()); const [selectedQuarter, setSelectedQuarter] = React.useState('All');
const [selectedYear, setSelectedYear] = React.useState(new Date().getFullYear().toString()); const [selectedYear, setSelectedYear] = React.useState('All');
const [currentPage, setCurrentPage] = React.useState(1); const [currentPage, setCurrentPage] = React.useState(1);
const [toast, setToast] = React.useState(null); const [toast, setToast] = React.useState(null);
const [showApproveModal, setShowApproveModal] = React.useState(false); const [showApproveModal, setShowApproveModal] = React.useState(false);
@ -91,7 +91,7 @@ const ManageSubmissions = () => {
const [isInitialLoad, setIsInitialLoad] = React.useState(true); const [isInitialLoad, setIsInitialLoad] = React.useState(true);
const [filters, setFilters] = React.useState({ const [filters, setFilters] = React.useState({
search: '', search: '',
year: '', year: 'All',
quarter: 'All', quarter: 'All',
emirate: 'All', emirate: 'All',
status: 'All' status: 'All'
@ -330,10 +330,9 @@ React.useEffect(() => {
params, params,
withCredentials: true withCredentials: true
}); });
const currentQuarter = response?.data?.selected_quarter ; // Always use 'All' as default for both quarter and year
// const currentQuarter = 'All'; const currentQuarter = 'All';
const currentYear = response?.data?.selected_year || new Date().getFullYear().toString(); const currentYear = 'All';
// const currentYear = 'All';
// Set the filter states // Set the filter states
setSelectedQuarter(currentQuarter); setSelectedQuarter(currentQuarter);
setSelectedYear(currentYear); setSelectedYear(currentYear);

View File

@ -3324,6 +3324,23 @@ const handleImport = async () => {
}; };
})} })}
rows={displayedRows} rows={displayedRows}
renderCell={(value, rowIndex, colIndex) => {
// ISIC Code column is at index 3 (0-based)
if (colIndex === 3 && value) {
const truncated = value.length > 10 ? `${value.substring(0, 10)}` : value;
return (
<div className="group relative">
<div className="truncate cursor-pointer hover:no-underline">{truncated}</div>
{value.length > 10 && (
<div className="absolute z-10 hidden group-hover:block bg-gray-800 text-white text-xs rounded px-2 py-1 -top-8 left-1/2 transform -translate-x-1/2 whitespace-nowrap">
{value}
</div>
)}
</div>
);
}
return value;
}}
columnWidths={[ columnWidths={[
300, // Establishment Name (increased from 250) 300, // Establishment Name (increased from 250)
180, // Contact Name (increased from 150) 180, // Contact Name (increased from 150)

View File

@ -4,28 +4,52 @@ 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); // Resend is enabled by default
const [countdown, setCountdown] = useState(600); const [otpExpiryCountdown, setOtpExpiryCountdown] = useState(600); // 10 minutes countdown
const [resendCooldown, setResendCooldown] = useState(0); // No initial cooldown
const [showResendCooldown, setShowResendCooldown] = useState(false); // Only show cooldown after first click
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.split('@')[0].substring(0, 2)}***@${location.state.email.split('@')[1]}` : const email = location.state?.email;
'na***@company.com'; if (!email) return 'na***@company.com';
const [local, domain] = email.split('@');
// local part masking
const maskedLocal =
local.length > 3
? `${local.slice(0, 2)}*****${local.slice(-1)}`
: `${local.slice(0, 1)}***`;
// domain name + extension
const domainParts = domain.split('.');
const domainName = domainParts[0];
const extension = domainParts.slice(1).join('.');
const maskedDomain =
domainName.length > 3
? `${domainName.slice(0, 2)}****${domainName.slice(-1)}`
: `${domainName.slice(0, 1)}***`;
return `${maskedLocal}@${maskedDomain}.${extension}`;
})();
// Toast handling // Toast handling
const closeToast = useCallback(() => { const closeToast = useCallback(() => {
if (toastTimeoutRef.current) { if (toastTimeoutRef.current) {
@ -34,7 +58,7 @@ const OTPVerification = () => {
} }
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) {
@ -47,24 +71,24 @@ const OTPVerification = () => {
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) {
@ -72,7 +96,7 @@ const OTPVerification = () => {
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();
@ -82,40 +106,40 @@ const OTPVerification = () => {
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.');
// Get the user role from the most reliable source // Get the user role from the most reliable source
const userRole = response?.data?.user?.role || const userRole = response?.data?.user?.role ||
response?.data?.role || response?.data?.role ||
localStorage.getItem('user_role'); localStorage.getItem('user_role');
// Set the role in localStorage if not already set // Set the role in localStorage if not already set
if (userRole && !localStorage.getItem('user_role')) { if (userRole && !localStorage.getItem('user_role')) {
localStorage.setItem('user_role', userRole); localStorage.setItem('user_role', userRole);
} }
// Update user profile in local storage with OTP verification status // Update user profile in local storage with OTP verification status
try { try {
const userProfile = JSON.parse(localStorage.getItem('user_profile') || '{}'); const userProfile = JSON.parse(localStorage.getItem('user_profile') || '{}');
@ -128,22 +152,22 @@ const OTPVerification = () => {
} catch (error) { } catch (error) {
console.error('Failed to update user profile:', error); console.error('Failed to update user profile:', error);
} }
// Determine the appropriate dashboard based on user role // Determine the appropriate dashboard based on user role
let redirectPath = '/dashboard'; let redirectPath = '/dashboard';
// Check if user is admin (case-insensitive check) // Check if user is admin (case-insensitive check)
if (userRole && userRole.toLowerCase() === 'admin') { if (userRole && userRole.toLowerCase() === 'admin') {
redirectPath = '/admin/dashboard'; redirectPath = '/admin/dashboard';
console.log('Admin user detected, redirecting to admin dashboard'); console.log('Admin user detected, redirecting to admin dashboard');
} }
// If there's a saved path from before login, use that // If there's a saved path from before login, use that
const savedPath = location.state?.from?.pathname; const savedPath = location.state?.from?.pathname;
if (savedPath) { if (savedPath) {
redirectPath = savedPath; redirectPath = savedPath;
} }
// Redirect after a short delay // Redirect after a short delay
setLoading(true); setLoading(true);
setTimeout(() => { setTimeout(() => {
@ -152,20 +176,20 @@ const OTPVerification = () => {
} 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
@ -184,76 +208,85 @@ const OTPVerification = () => {
setLoading(false); setLoading(false);
} }
}; };
// Handle OTP expiry countdown
useEffect(() => {
const timer = setInterval(() => {
setOtpExpiryCountdown(prev => {
if (prev <= 0) {
clearInterval(timer);
// Redirect to login when OTP expires
navigate('/login', {
state: {
message: 'OTP has expired. Please try again.',
messageType: 'error'
},
replace: true
});
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(timer);
}, [navigate]);
// Handle resend cooldown timer
useEffect(() => {
if (resendCooldown <= 0) {
setResendDisabled(false);
return;
}
setResendDisabled(true);
const cooldownTimer = setInterval(() => {
setResendCooldown(prev => {
if (prev <= 1) {
clearInterval(cooldownTimer);
setResendDisabled(false);
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(cooldownTimer);
}, [resendCooldown]);
// 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 // Reset timers
const tenMinutesInSeconds = 10 * 60; setOtpExpiryCountdown(600); // Reset to 10 minutes
setCountdown(tenMinutesInSeconds); setResendCooldown(60); // Start 60 seconds cooldown
setShowResendCooldown(true); // Show the cooldown timer
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 // Show success message immediately
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);
} 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.');
setResendDisabled(false);
} finally { } finally {
setLoading(false); setLoading(false);
// Re-enable resend after 60 seconds
const timer = setTimeout(() => {
setResendDisabled(false);
}, 60000);
// 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;
const updateCountdown = () => {
const now = Date.now();
const remainingSeconds = Math.max(0, Math.floor((expiryTime - now) / 1000));
setCountdown(remainingSeconds);
if (remainingSeconds <= 0) {
clearInterval(countdownRef.current);
setResendDisabled(false);
}
};
// Initial update
updateCountdown();
// Set up interval for countdown updates
countdownRef.current = setInterval(updateCountdown, 1000);
return () => {
if (countdownRef.current) clearInterval(countdownRef.current);
};
}, []);
// Cleanup on unmount // Cleanup on unmount
useEffect(() => { useEffect(() => {
return () => { return () => {
@ -261,10 +294,13 @@ const OTPVerification = () => {
if (countdownRef.current) clearInterval(countdownRef.current); if (countdownRef.current) clearInterval(countdownRef.current);
}; };
}, []); }, []);
const minutes = Math.floor(countdown / 60); const expiryMinutes = Math.floor(otpExpiryCountdown / 60);
const seconds = countdown % 60; const expirySeconds = otpExpiryCountdown % 60;
// Format time for display (MM:SS)
const formatTime = (time) => time < 10 ? `0${time}` : time;
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 */}
@ -297,7 +333,7 @@ const OTPVerification = () => {
</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 */}
@ -309,13 +345,13 @@ const OTPVerification = () => {
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">
@ -324,7 +360,7 @@ const OTPVerification = () => {
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">
@ -341,8 +377,8 @@ const OTPVerification = () => {
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}
@ -350,25 +386,25 @@ const OTPVerification = () => {
/> />
))} ))}
</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 {formatTime(expiryMinutes)}:{formatTime(expirySeconds)}
</span> </span>
</div> </div>
<span>Attempts: {attemptsRemaining} of 3</span> <span>Attempts used: {attemptsRemaining}/3</span>
</div> </div>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<button <button
type="submit" type="submit"
@ -388,28 +424,30 @@ const OTPVerification = () => {
> >
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 cursor-not-allowed' : 'text-[#92722A] hover:text-[#7a5e23] cursor-pointer'
}`} }`}
> >
{resendDisabled ? `Resend code (${Math.ceil(countdown/60)}:${(countdown%60).toString().padStart(2, '0')})` : 'Resend code'} {showResendCooldown && resendCooldown > 0
? `Resend code (${formatTime(Math.floor(resendCooldown / 60))}:${formatTime(resendCooldown % 60)})`
: '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"
> >
@ -423,5 +461,5 @@ const OTPVerification = () => {
</div> </div>
); );
}; };
export default OTPVerification; export default OTPVerification;