import { useState, useCallback } from 'react'; import { toast } from '@/hooks/use-toast'; interface RetryOptions { maxAttempts?: number; delayMs?: number; exponentialBackoff?: boolean; onProgress?: (attempt: number, maxAttempts: number) => void; } export function useRetryProgress() { const [isRetrying, setIsRetrying] = useState(false); const [currentAttempt, setCurrentAttempt] = useState(0); const [abortController, setAbortController] = useState(null); const retryWithProgress = useCallback( async ( operation: () => Promise, options: RetryOptions = {} ): Promise => { const { maxAttempts = 3, delayMs = 1000, exponentialBackoff = true, onProgress, } = options; setIsRetrying(true); const controller = new AbortController(); setAbortController(controller); let lastError: Error | null = null; let toastId: string | undefined; for (let attempt = 1; attempt <= maxAttempts; attempt++) { if (controller.signal.aborted) { throw new Error('Operation cancelled'); } setCurrentAttempt(attempt); onProgress?.(attempt, maxAttempts); // Show progress toast if (attempt > 1) { const delay = exponentialBackoff ? delayMs * Math.pow(2, attempt - 2) : delayMs; const countdown = Math.ceil(delay / 1000); toast({ title: `Retrying (${attempt}/${maxAttempts})`, description: `Waiting ${countdown}s before retry...`, duration: delay, }); await new Promise(resolve => setTimeout(resolve, delay)); } try { const result = await operation(); setIsRetrying(false); setCurrentAttempt(0); setAbortController(null); // Show success toast toast({ title: "Success", description: attempt > 1 ? `Operation succeeded on attempt ${attempt}` : 'Operation completed successfully', duration: 3000, }); return result; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (attempt < maxAttempts) { toast({ title: `Attempt ${attempt} Failed`, description: `${lastError.message}. Retrying...`, duration: 2000, }); } } } // All attempts failed setIsRetrying(false); setCurrentAttempt(0); setAbortController(null); toast({ variant: 'destructive', title: "All Retries Failed", description: `Failed after ${maxAttempts} attempts: ${lastError?.message}`, duration: 5000, }); throw lastError; }, [] ); const cancel = useCallback(() => { if (abortController) { abortController.abort(); setAbortController(null); setIsRetrying(false); setCurrentAttempt(0); toast({ title: 'Cancelled', description: 'Retry operation cancelled', duration: 2000, }); } }, [abortController]); return { retryWithProgress, isRetrying, currentAttempt, cancel, }; }