diff --git a/components/converter/ConversionPreview.tsx b/components/converter/ConversionPreview.tsx index ea59b3d..050b134 100644 --- a/components/converter/ConversionPreview.tsx +++ b/components/converter/ConversionPreview.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; -import { Download, CheckCircle, XCircle, Loader2, Clock, TrendingUp, FileCheck2, ArrowRight } from 'lucide-react'; +import { Download, CheckCircle, XCircle, Loader2, Clock, TrendingUp, FileCheck2, ArrowRight, RefreshCw } from 'lucide-react'; import { cn } from '@/lib/utils/cn'; import { Button } from '@/components/ui/Button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'; @@ -12,9 +12,10 @@ import type { ConversionJob } from '@/types/conversion'; export interface ConversionPreviewProps { job: ConversionJob; onDownload?: () => void; + onRetry?: () => void; } -export function ConversionPreview({ job, onDownload }: ConversionPreviewProps) { +export function ConversionPreview({ job, onDownload, onRetry }: ConversionPreviewProps) { const [previewUrl, setPreviewUrl] = React.useState(null); const [elapsedTime, setElapsedTime] = React.useState(0); const [estimatedTimeRemaining, setEstimatedTimeRemaining] = React.useState(null); @@ -260,6 +261,14 @@ export function ConversionPreview({ job, onDownload }: ConversionPreviewProps) { )} + {/* Retry button */} + {job.status === 'error' && onRetry && ( + + )} + {/* Preview */} {job.status === 'completed' && renderPreview()} diff --git a/components/converter/FileConverter.tsx b/components/converter/FileConverter.tsx index 59ce4f9..e594006 100644 --- a/components/converter/FileConverter.tsx +++ b/components/converter/FileConverter.tsx @@ -244,6 +244,109 @@ export function FileConverter() { addToast(`Downloaded ${files.length} files as ZIP`, 'success'); }; + const handleRetry = async (jobId: string) => { + const jobIndex = conversionJobs.findIndex(j => j.id === jobId); + if (jobIndex === -1 || !outputFormat) return; + + const job = conversionJobs[jobIndex]; + + try { + // Reset job to loading + setConversionJobs((prev) => + prev.map((j, idx) => idx === jobIndex ? { + ...j, + status: 'loading' as const, + progress: 0, + error: undefined, + startTime: Date.now(), + } : j) + ); + + // Update to processing + setConversionJobs((prev) => + prev.map((j, idx) => idx === jobIndex ? { ...j, status: 'processing' as const, progress: 10 } : j) + ); + + // Call appropriate converter + let result; + + switch (outputFormat.converter) { + case 'ffmpeg': + result = await convertWithFFmpeg(job.inputFile, outputFormat.extension, conversionOptions, (progress) => { + setConversionJobs((prev) => + prev.map((j, idx) => idx === jobIndex ? { ...j, progress } : j) + ); + }); + break; + + case 'imagemagick': + result = await convertWithImageMagick( + job.inputFile, + outputFormat.extension, + conversionOptions, + (progress) => { + setConversionJobs((prev) => + prev.map((j, idx) => idx === jobIndex ? { ...j, progress } : j) + ); + } + ); + break; + + default: + throw new Error(`Unknown converter: ${outputFormat.converter}`); + } + + // Update job with result + if (result.success && result.blob) { + setConversionJobs((prev) => + prev.map((j, idx) => idx === jobIndex ? { + ...j, + status: 'completed' as const, + progress: 100, + result: result.blob, + endTime: Date.now(), + } : j) + ); + + addToast('Conversion completed successfully!', 'success'); + + // Add to history + addToHistory({ + inputFileName: job.inputFile.name, + inputFormat: job.inputFormat.name, + outputFormat: outputFormat.name, + outputFileName: `output.${outputFormat.extension}`, + fileSize: result.blob.size, + result: result.blob, + }); + } else { + setConversionJobs((prev) => + prev.map((j, idx) => idx === jobIndex ? { + ...j, + status: 'error' as const, + error: result.error || 'Unknown error', + endTime: Date.now(), + } : j) + ); + + addToast(result.error || 'Retry failed', 'error'); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + + setConversionJobs((prev) => + prev.map((j, idx) => idx === jobIndex ? { + ...j, + status: 'error' as const, + error: errorMessage, + endTime: Date.now(), + } : j) + ); + + addToast(`Retry failed: ${errorMessage}`, 'error'); + } + }; + const isConverting = conversionJobs.some(job => job.status === 'loading' || job.status === 'processing'); const isConvertDisabled = selectedFiles.length === 0 || !outputFormat || isConverting; const completedCount = conversionJobs.filter(job => job.status === 'completed').length; @@ -356,7 +459,11 @@ export function FileConverter() { {conversionJobs.length > 0 && (
{conversionJobs.map((job) => ( - + handleRetry(job.id)} + /> ))}
)}