import { useEffect, useState } from 'react'; import { Loader2, CheckCircle2, XCircle } from 'lucide-react'; import { Card } from '@/components/ui/card'; import { Progress } from '@/components/ui/progress'; import { Button } from '@/components/ui/button'; interface RetryStatus { id: string; attempt: number; maxAttempts: number; delay: number; type: string; state: 'retrying' | 'success' | 'failed'; errorId?: string; isRateLimit?: boolean; retryAfter?: number; } /** * Global retry status indicator * Shows visual feedback when submissions are being retried due to transient failures * Supports success/failure states and multiple concurrent retries */ export function RetryStatusIndicator() { const [retries, setRetries] = useState>(new Map()); useEffect(() => { const handleRetry = (event: Event) => { const customEvent = event as CustomEvent>; const { id, attempt, maxAttempts, delay, type, isRateLimit, retryAfter } = customEvent.detail; setRetries(prev => { const next = new Map(prev); next.set(id, { id, attempt, maxAttempts, delay, type, state: 'retrying', countdown: delay, isRateLimit, retryAfter }); return next; }); }; const handleSuccess = (event: Event) => { const customEvent = event as CustomEvent<{ id: string }>; const { id } = customEvent.detail; setRetries(prev => { const retry = prev.get(id); if (!retry) return prev; const next = new Map(prev); next.set(id, { ...retry, state: 'success', countdown: 0 }); return next; }); // Remove after 2 seconds setTimeout(() => { setRetries(prev => { const next = new Map(prev); next.delete(id); return next; }); }, 2000); }; const handleFailure = (event: Event) => { const customEvent = event as CustomEvent<{ id: string; errorId: string }>; const { id, errorId } = customEvent.detail; setRetries(prev => { const retry = prev.get(id); if (!retry) return prev; const next = new Map(prev); next.set(id, { ...retry, state: 'failed', errorId, countdown: 0 }); return next; }); }; window.addEventListener('submission-retry', handleRetry); window.addEventListener('submission-retry-success', handleSuccess); window.addEventListener('submission-retry-failed', handleFailure); return () => { window.removeEventListener('submission-retry', handleRetry); window.removeEventListener('submission-retry-success', handleSuccess); window.removeEventListener('submission-retry-failed', handleFailure); }; }, []); // Countdown timer for retrying state useEffect(() => { const timer = setInterval(() => { setRetries(prev => { let hasChanges = false; const next = new Map(prev); next.forEach((retry, id) => { if (retry.state === 'retrying' && retry.countdown > 0) { const newCountdown = retry.countdown - 100; next.set(id, { ...retry, countdown: Math.max(0, newCountdown) }); hasChanges = true; } }); return hasChanges ? next : prev; }); }, 100); return () => clearInterval(timer); }, []); if (retries.size === 0) return null; return (
{Array.from(retries.values()).map((retry) => ( ))}
); } function RetryCard({ retry }: { retry: RetryStatus & { countdown: number } }) { if (retry.state === 'success') { return (

{retry.type} submitted successfully!

); } if (retry.state === 'failed') { return (

Submission failed

{retry.errorId && ( <>

Error ID: {retry.errorId}

)}
); } // Retrying state const progress = retry.delay > 0 ? ((retry.delay - retry.countdown) / retry.delay) * 100 : 0; // Customize message based on rate limit status const getMessage = () => { if (retry.isRateLimit) { if (retry.retryAfter) { return `Rate limit reached. Waiting ${Math.ceil(retry.countdown / 1000)}s as requested by server...`; } return `Rate limit reached. Using smart backoff - retrying in ${Math.ceil(retry.countdown / 1000)}s...`; } return `Network issue detected. Retrying ${retry.type} submission in ${Math.ceil(retry.countdown / 1000)}s`; }; return (

{retry.isRateLimit ? 'Rate Limited' : 'Retrying submission...'}

{retry.attempt}/{retry.maxAttempts}

{getMessage()}

); }