Files
thrilltrack-explorer/src-old/hooks/useRetryProgress.ts

126 lines
3.2 KiB
TypeScript

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<AbortController | null>(null);
const retryWithProgress = useCallback(
async <T,>(
operation: () => Promise<T>,
options: RetryOptions = {}
): Promise<T> => {
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,
};
}