changes added in company profile
This commit is contained in:
parent
a13ea7ffef
commit
e00e7a1a38
@ -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]'
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -13,8 +13,10 @@ const OTPVerification = () => {
|
|||||||
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()));
|
||||||
@ -22,9 +24,31 @@ const OTPVerification = () => {
|
|||||||
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(() => {
|
||||||
@ -185,6 +209,51 @@ const OTPVerification = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
@ -197,62 +266,26 @@ const OTPVerification = () => {
|
|||||||
// 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(() => {
|
||||||
@ -262,8 +295,11 @@ const OTPVerification = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
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">
|
||||||
@ -362,10 +398,10 @@ const OTPVerification = () => {
|
|||||||
<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>
|
||||||
|
|
||||||
@ -395,10 +431,12 @@ const OTPVerification = () => {
|
|||||||
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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user