Component props.
React element with import confirmation interface.
export function FileReadyContent({
importData,
statusCounts,
previousMatchCount,
isLoading,
onImport,
onReset,
}: Readonly<FileReadyProps>) {
if (isLoading) {
return (
<motion.section
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<Card className="shadow-lg">
<CardHeader className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<div className="bg-primary/10 text-primary flex h-11 w-11 items-center justify-center rounded-full">
<span className="border-primary/30 border-t-primary h-6 w-6 animate-spin rounded-full border-2" />
</div>
<div>
<CardTitle className="text-2xl font-semibold">
Processing your library
</CardTitle>
<CardDescription>
Merging entries and reapplying previous matches...
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid gap-3 sm:grid-cols-2">
{Array.from({ length: 4 }).map(() => (
<div
key={crypto.randomUUID()}
className="border-border/50 bg-muted/30 h-20 rounded-lg border border-dashed"
/>
))}
</div>
<div className="space-y-2">
<Progress className="h-2 animate-pulse" />
<p className="text-muted-foreground text-xs">
Combining your library with previously matched entries...
</p>
</div>
</CardContent>
</Card>
</motion.section>
);
}
const statusEntries = Object.entries(statusCounts).sort(
([, firstCount], [, secondCount]) => secondCount - firstCount,
);
const uniqueStatuses = statusEntries.length;
return (
<motion.section
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<Card className="shadow-lg">
<CardHeader className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<div className="bg-primary/10 text-primary flex h-11 w-11 items-center justify-center rounded-full">
<ListChecks className="h-5 w-5" />
</div>
<div>
<CardTitle className="text-2xl font-semibold">
Review your Kenmei entries
</CardTitle>
<CardDescription>
Confirm totals before heading to match review.
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
<div className="border-border/60 bg-muted/10 flex items-center gap-3 rounded-lg border px-4 py-3 shadow-sm">
<div className="bg-primary/10 text-primary flex h-10 w-10 items-center justify-center rounded-full">
<BookOpen className="h-5 w-5" />
</div>
<div>
<p className="text-muted-foreground text-xs uppercase tracking-wide">
Total entries
</p>
<p className="text-foreground text-lg font-semibold">
{importData.manga.length.toLocaleString()}
</p>
</div>
</div>
<div className="border-border/60 bg-muted/10 flex items-center gap-3 rounded-lg border px-4 py-3 shadow-sm">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
<History className="h-5 w-5" />
</div>
<div>
<p className="text-muted-foreground text-xs uppercase tracking-wide">
Previously matched
</p>
<p className="text-foreground text-lg font-semibold">
{previousMatchCount.toLocaleString()}
</p>
</div>
</div>
<div className="border-border/60 bg-muted/10 flex items-center gap-3 rounded-lg border px-4 py-3 shadow-sm">
<div className="bg-primary/10 text-primary flex h-10 w-10 items-center justify-center rounded-full">
<ListChecks className="h-5 w-5" />
</div>
<div>
<p className="text-muted-foreground text-xs uppercase tracking-wide">
Unique statuses
</p>
<p className="text-foreground text-lg font-semibold">
{uniqueStatuses.toLocaleString()}
</p>
</div>
</div>
</div>
{statusEntries.length > 0 && (
<div className="space-y-3">
<p className="text-foreground text-sm font-medium">
Status overview
</p>
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
{statusEntries.map(([status, count]) => (
<div
key={status}
className="border-border/60 bg-card flex items-center justify-between gap-3 rounded-md border px-3 py-3 shadow-sm"
>
<div className="flex items-center gap-3">
{getStatusIcon(status)}
<span className="text-foreground text-sm font-medium">
{formatStatusLabel(status)}
</span>
</div>
<span className="text-foreground text-lg font-semibold">
{count.toLocaleString()}
</span>
</div>
))}
</div>
</div>
)}
<div className="border-border/60 rounded-lg border">
<DataTable
data={importData.manga}
itemsPerPage={50}
isLoading={isLoading}
/>
</div>
</CardContent>
<CardFooter className="border-border/60 bg-muted/10 flex-col items-stretch gap-4 border-t py-6">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-3">
<Button
onClick={onImport}
disabled={isLoading}
size="lg"
className="gap-2"
>
{isLoading ? (
<>
<span className="flex h-4 w-4 items-center justify-center">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
</span>{" "}
Processing…
</>
) : (
<>
<ListChecks className="h-4 w-4" />
Begin match review
</>
)}
</Button>
<Button
onClick={onReset}
disabled={isLoading}
variant="outline"
size="lg"
className="gap-2"
>
Reset import
</Button>
</div>
<div className="text-muted-foreground space-y-3 text-xs">
<p>We’ll reapply existing match decisions before sync begins.</p>
{previousMatchCount > 0 && (
<Alert className="border-primary/30 bg-primary/5 border">
<Info className="h-4 w-4" />
<AlertDescription className="text-foreground text-sm">
{previousMatchCount.toLocaleString()} previously reviewed
matches carry over automatically.
</AlertDescription>
</Alert>
)}
</div>
</CardFooter>
</Card>
</motion.section>
);
}
Displays import preview with data summary, manga entries table, and action buttons.