diff --git a/apps/web/src/pages/auth/verify-email.tsx b/apps/web/src/pages/auth/verify-email.tsx index a6bb54f..03a9a88 100644 --- a/apps/web/src/pages/auth/verify-email.tsx +++ b/apps/web/src/pages/auth/verify-email.tsx @@ -16,6 +16,8 @@ export default function VerifyEmail() { const [errorMessage, setErrorMessage] = useState(''); const [isResending, setIsResending] = useState(false); const [resendMessage, setResendMessage] = useState(''); + const [cooldownExpiry, setCooldownExpiry] = useState(null); + const [remainingSeconds, setRemainingSeconds] = useState(0); const processedToken = useRef(undefined); useEffect(() => { @@ -67,6 +69,45 @@ export default function VerifyEmail() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.isReady, token]); + // Initialize cooldown from localStorage on mount + useEffect(() => { + const storedExpiry = localStorage.getItem('plunk:email-verification-cooldown'); + if (storedExpiry) { + const expiryTime = parseInt(storedExpiry, 10); + // Validate: not NaN, in the future, and within reasonable range (< 1 hour from now) + if (!isNaN(expiryTime) && expiryTime > Date.now() && expiryTime < Date.now() + 3600000) { + setCooldownExpiry(expiryTime); + } else { + // Clean up invalid/expired cooldown + localStorage.removeItem('plunk:email-verification-cooldown'); + } + } + }, []); + + // Countdown timer effect + useEffect(() => { + if (!cooldownExpiry) { + setRemainingSeconds(0); + return; + } + + // Update immediately + const updateRemaining = () => { + const remaining = Math.max(0, Math.ceil((cooldownExpiry - Date.now()) / 1000)); + setRemainingSeconds(remaining); + + if (remaining === 0) { + setCooldownExpiry(null); + localStorage.removeItem('plunk:email-verification-cooldown'); + } + }; + + updateRemaining(); + const interval = setInterval(updateRemaining, 1000); + + return () => clearInterval(interval); + }, [cooldownExpiry]); + async function handleResend() { setIsResending(true); setResendMessage(''); @@ -75,11 +116,20 @@ export default function VerifyEmail() { if (response.success) { setResendMessage('Verification email sent! Please check your inbox.'); + // Set 60-second cooldown + const expiryTime = Date.now() + 60000; + setCooldownExpiry(expiryTime); + localStorage.setItem('plunk:email-verification-cooldown', expiryTime.toString()); } else { setResendMessage('Failed to send verification email. Please try again.'); } - } catch { - setResendMessage('Failed to send verification email. Please try again.'); + } catch (error) { + // Show error message but still apply cooldown to prevent spam + setResendMessage(error instanceof Error ? error.message : 'Failed to send verification email. Please try again.'); + // Apply cooldown even on error to prevent retry spam + const expiryTime = Date.now() + 60000; + setCooldownExpiry(expiryTime); + localStorage.setItem('plunk:email-verification-cooldown', expiryTime.toString()); } finally { setIsResending(false); } @@ -119,8 +169,8 @@ export default function VerifyEmail() {

- {resendMessage && ( @@ -205,8 +255,8 @@ export default function VerifyEmail() {

{errorMessage}

- {resendMessage && (