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; } /** * 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 } = customEvent.detail; setRetries(prev => { const next = new Map(prev); next.set(id, { id, attempt, maxAttempts, delay, type, state: 'retrying', countdown: delay }); 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; return (

Retrying submission...

{retry.attempt}/{retry.maxAttempts}

Network issue detected. Retrying {retry.type} submission in {Math.ceil(retry.countdown / 1000)}s

); }