feat: Add cooldown to resend verification email
This commit is contained in:
@@ -16,6 +16,8 @@ export default function VerifyEmail() {
|
|||||||
const [errorMessage, setErrorMessage] = useState<string>('');
|
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||||
const [isResending, setIsResending] = useState(false);
|
const [isResending, setIsResending] = useState(false);
|
||||||
const [resendMessage, setResendMessage] = useState<string>('');
|
const [resendMessage, setResendMessage] = useState<string>('');
|
||||||
|
const [cooldownExpiry, setCooldownExpiry] = useState<number | null>(null);
|
||||||
|
const [remainingSeconds, setRemainingSeconds] = useState(0);
|
||||||
const processedToken = useRef<string | undefined>(undefined);
|
const processedToken = useRef<string | undefined>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -67,6 +69,45 @@ export default function VerifyEmail() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [router.isReady, token]);
|
}, [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() {
|
async function handleResend() {
|
||||||
setIsResending(true);
|
setIsResending(true);
|
||||||
setResendMessage('');
|
setResendMessage('');
|
||||||
@@ -75,11 +116,20 @@ export default function VerifyEmail() {
|
|||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setResendMessage('Verification email sent! Please check your inbox.');
|
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 {
|
} else {
|
||||||
setResendMessage('Failed to send verification email. Please try again.');
|
setResendMessage('Failed to send verification email. Please try again.');
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
setResendMessage('Failed to send verification email. Please try again.');
|
// 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 {
|
} finally {
|
||||||
setIsResending(false);
|
setIsResending(false);
|
||||||
}
|
}
|
||||||
@@ -119,8 +169,8 @@ export default function VerifyEmail() {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 w-full mt-4">
|
<div className="flex flex-col gap-3 w-full mt-4">
|
||||||
<Button onClick={handleResend} disabled={isResending} className="w-full">
|
<Button onClick={handleResend} disabled={isResending || cooldownExpiry !== null} className="w-full">
|
||||||
{isResending ? 'Sending...' : 'Resend verification email'}
|
{isResending ? 'Sending...' : cooldownExpiry !== null ? `Resend in ${remainingSeconds}s` : 'Resend verification email'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{resendMessage && (
|
{resendMessage && (
|
||||||
@@ -205,8 +255,8 @@ export default function VerifyEmail() {
|
|||||||
<p className="text-neutral-600">{errorMessage}</p>
|
<p className="text-neutral-600">{errorMessage}</p>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 w-full mt-4">
|
<div className="flex flex-col gap-3 w-full mt-4">
|
||||||
<Button onClick={handleResend} disabled={isResending} className="w-full">
|
<Button onClick={handleResend} disabled={isResending || cooldownExpiry !== null} className="w-full">
|
||||||
{isResending ? 'Sending...' : 'Resend verification email'}
|
{isResending ? 'Sending...' : cooldownExpiry !== null ? `Resend in ${remainingSeconds}s` : 'Resend verification email'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{resendMessage && (
|
{resendMessage && (
|
||||||
|
|||||||
Reference in New Issue
Block a user