refactor: streamline, refine and polish

This commit is contained in:
2026-02-27 12:35:02 +01:00
parent efe3c81576
commit ee7e5ec06c
21 changed files with 606 additions and 735 deletions

View File

@@ -1,7 +1,7 @@
'use client';
import * as React from 'react';
import { Download, CheckCircle, XCircle, Loader2, Clock, TrendingUp, FileCheck2, ArrowRight, RefreshCw } from 'lucide-react';
import { Download, CheckCircle, XCircle, Loader2, Clock, TrendingUp, 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';
@@ -85,7 +85,7 @@ export function ConversionPreview({ job, onDownload, onRetry }: ConversionPrevie
switch (category) {
case 'image':
return (
<div className="mt-4 rounded-lg overflow-hidden bg-muted/30 flex items-center justify-center p-4">
<div className="mt-3 rounded-lg overflow-hidden bg-muted/30 flex items-center justify-center p-4">
<img
src={previewUrl}
alt="Converted image preview"
@@ -107,7 +107,7 @@ export function ConversionPreview({ job, onDownload, onRetry }: ConversionPrevie
case 'video':
return (
<div className="mt-4 rounded-lg overflow-hidden bg-muted/30">
<div className="mt-3 rounded-lg overflow-hidden bg-muted/30">
<video src={previewUrl} controls className="w-full max-h-64">
Your browser does not support video playback.
</video>
@@ -140,38 +140,38 @@ export function ConversionPreview({ job, onDownload, onRetry }: ConversionPrevie
switch (job.status) {
case 'loading':
return (
<div className="space-y-3">
<div className="flex items-center gap-2 text-info">
<Loader2 className="h-4 w-4 animate-spin" />
<span className="text-sm font-medium">Loading WASM converter...</span>
<div className="space-y-2">
<div className="flex items-center gap-2 text-muted-foreground">
<Loader2 className="h-3.5 w-3.5 animate-spin text-primary" />
<span className="text-xs font-medium">Loading converter...</span>
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<Clock className="h-3.5 w-3.5" />
<span>Elapsed: {formatTime(elapsedTime)}</span>
<div className="flex items-center gap-1.5 text-[10px] text-muted-foreground">
<Clock className="h-3 w-3" />
<span>{formatTime(elapsedTime)}</span>
</div>
</div>
);
case 'processing':
return (
<div className="space-y-3">
<div className="space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-info">
<Loader2 className="h-4 w-4 animate-spin" />
<span className="text-sm font-medium">Converting...</span>
<div className="flex items-center gap-2 text-muted-foreground">
<Loader2 className="h-3.5 w-3.5 animate-spin text-primary" />
<span className="text-xs font-medium">Converting...</span>
</div>
<span className="text-xs text-muted-foreground">{job.progress}%</span>
<span className="text-[10px] text-muted-foreground tabular-nums">{job.progress}%</span>
</div>
<Progress value={job.progress} />
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 text-xs text-muted-foreground">
<div className="flex items-center gap-2">
<Clock className="h-3.5 w-3.5" />
<span>Elapsed: {formatTime(elapsedTime)}</span>
<Progress value={job.progress} className="h-1" />
<div className="flex items-center gap-3 text-[10px] text-muted-foreground">
<div className="flex items-center gap-1.5">
<Clock className="h-3 w-3" />
<span>{formatTime(elapsedTime)}</span>
</div>
{estimatedTimeRemaining && (
<div className="flex items-center gap-2">
<TrendingUp className="h-3.5 w-3.5" />
<span>~{formatTime(estimatedTimeRemaining)} remaining</span>
<div className="flex items-center gap-1.5">
<TrendingUp className="h-3 w-3" />
<span>~{formatTime(estimatedTimeRemaining)} left</span>
</div>
)}
</div>
@@ -184,39 +184,27 @@ export function ConversionPreview({ job, onDownload, onRetry }: ConversionPrevie
const sizeReduction = inputSize > 0 ? ((inputSize - outputSize) / inputSize) * 100 : 0;
return (
<div className="space-y-3">
<div className="flex items-center gap-2 text-success">
<CheckCircle className="h-5 w-5" />
<span className="text-sm font-medium">Conversion complete!</span>
<div className="space-y-2">
<div className="flex items-center gap-2">
<CheckCircle className="h-3.5 w-3.5 text-primary" />
<span className="text-xs font-medium">Complete</span>
</div>
{/* File size comparison */}
<div className="bg-muted/50 rounded-lg p-3 space-y-2">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center gap-2">
<FileCheck2 className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground">Input:</span>
</div>
<span className="font-medium">{formatFileSize(inputSize)}</span>
<div className="bg-muted/50 rounded-lg p-2.5 space-y-1">
<div className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">Input</span>
<span className="font-medium tabular-nums">{formatFileSize(inputSize)}</span>
</div>
<div className="flex items-center justify-center py-1">
<ArrowRight className="h-4 w-4 text-muted-foreground" />
</div>
<div className="flex items-center justify-between text-sm">
<div className="flex items-center gap-2">
<FileCheck2 className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground">Output:</span>
</div>
<div className="flex items-center gap-2">
<span className="font-medium">{formatFileSize(outputSize)}</span>
<div className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">Output</span>
<div className="flex items-center gap-1.5">
<span className="font-medium tabular-nums">{formatFileSize(outputSize)}</span>
{Math.abs(sizeReduction) > 1 && (
<span className={cn(
"text-xs px-2 py-0.5 rounded-full",
"text-[10px] px-1.5 py-0.5 rounded-full",
sizeReduction > 0
? "bg-success/10 text-success"
: "bg-info/10 text-info"
? "bg-primary/10 text-primary"
: "bg-muted text-muted-foreground"
)}>
{sizeReduction > 0 ? '-' : '+'}{Math.abs(sizeReduction).toFixed(0)}%
</span>
@@ -230,8 +218,8 @@ export function ConversionPreview({ job, onDownload, onRetry }: ConversionPrevie
case 'error':
return (
<div className="flex items-center gap-2 text-destructive">
<XCircle className="h-5 w-5" />
<span className="text-sm font-medium">Conversion failed</span>
<XCircle className="h-3.5 w-3.5" />
<span className="text-xs font-medium">Conversion failed</span>
</div>
);
@@ -245,48 +233,41 @@ export function ConversionPreview({ job, onDownload, onRetry }: ConversionPrevie
}
return (
<Card className="animate-fadeIn">
<Card className="animate-fade-in">
<CardHeader>
<CardTitle className="text-lg">Conversion Status</CardTitle>
<CardTitle className="text-sm">Conversion</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{/* Status */}
<div className="space-y-3">
{renderStatus()}
{/* Error message */}
{job.error && (
<div className="bg-destructive/10 border border-destructive/20 rounded-md p-3">
<p className="text-sm text-destructive">{job.error}</p>
<div className="bg-destructive/10 border border-destructive/20 rounded-md p-2.5">
<p className="text-xs text-destructive">{job.error}</p>
</div>
)}
{/* Retry button */}
{job.status === 'error' && onRetry && (
<Button onClick={onRetry} variant="outline" className="w-full gap-2">
<RefreshCw className="h-4 w-4" />
Retry Conversion
<Button onClick={onRetry} variant="outline" className="w-full">
<RefreshCw className="h-3.5 w-3.5 mr-1.5" />
Retry
</Button>
)}
{/* Preview */}
{job.status === 'completed' && renderPreview()}
{/* Download button */}
{job.status === 'completed' && job.result && (
<Button onClick={handleDownload} className="w-full" variant="default" size="lg">
<Download className="h-4 w-4 shrink-0" />
<Button onClick={handleDownload} className="w-full">
<Download className="h-3.5 w-3.5 shrink-0 mr-1.5" />
<span className="truncate min-w-0">
Download{' '}
{generateOutputFilename(job.inputFile.name, job.outputFormat.extension)}
</span>
</Button>
)}
{/* Duration */}
{job.status === 'completed' && job.startTime && job.endTime && (
<p className="text-xs text-muted-foreground text-center">
Completed in {((job.endTime - job.startTime) / 1000).toFixed(2)}s
<p className="text-[10px] text-muted-foreground text-center">
{((job.endTime - job.startTime) / 1000).toFixed(1)}s
</p>
)}
</div>