- {/* Error Message */}
{importError && (
-
-
-
-
-
-
-
- Import Error
-
-
- {importError}
-
-
-
setImportError(null)}
- className="text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-200"
- >
-
-
-
-
+
setImportError(null)}>
+ {importError}
+
)}
- {/* File detected toast */}
{detectedFile && (
@@ -307,7 +148,6 @@ export const RealTimeImportStats = (): ReactElement => {
)}
- {/* Start Import button — only when idle with new files */}
{hasNewFiles && !importSession.isActive && (
{
)}
- {/* Progress bar — shown while importing and once complete */}
{showProgressBar && (
-
-
-
- {socketImport!.active
- ? `Importing ${socketImport!.completed} / ${socketImport!.total}`
- : `${socketImport!.completed} / ${socketImport!.total} imported`}
-
-
- {socketProgressPct}% complete
-
-
-
-
- {socketImport!.active && (
-
- )}
-
-
-
+
)}
- {/* Stats cards */}
- {/* Total files */}
-
-
{totalFiles}
-
in import folder
-
-
- {/* Imported */}
-
-
- {importedCount}
-
-
- {importSession.isActive ? "imported so far" : "imported in database"}
-
-
-
- {/* Failed — only shown after a session with failures */}
+
+
{showFailedCard && (
-
-
- {failedCount}
-
-
failed
-
+
)}
-
- {/* Missing files — shown when watcher detects moved/deleted files */}
{showMissingCard && (
-
-
- {missingCount}
-
-
missing
-
+
)}
- {/* Missing files detail panel */}
{showMissingCard && (
-
-
-
-
-
- {missingCount} {missingCount === 1 ? "file" : "files"} missing
-
-
- These files were previously imported but can no longer be found
- on disk. Move them back to restore access.
-
- {missingDocs.length > 0 && (
-
- {missingDocs.map((comic, i) => (
-
- {getMissingComicLabel(comic)} is missing
-
- ))}
- {missingCount > 3 && (
-
- and {missingCount - 3} more.
-
- )}
-
+
+ These files were previously imported but can no longer be found on disk. Move them back to restore access.
+ {missingDocs.length > 0 && (
+
+ {missingDocs.map((comic, i) => (
+
+ {getComicDisplayLabel(comic)} is missing
+
+ ))}
+ {missingCount > 3 && (
+
+ and {missingCount - 3} more.
+
)}
-
-
-
-
- View Missing Files In Library
-
-
-
-
-
-
+
+ )}
+
+
+ View Missing Files In Library
+
+
+
)}
);
diff --git a/src/client/components/shared/AlertCard.tsx b/src/client/components/shared/AlertCard.tsx
new file mode 100644
index 0000000..34d9e53
--- /dev/null
+++ b/src/client/components/shared/AlertCard.tsx
@@ -0,0 +1,108 @@
+import { ReactElement, ReactNode } from "react";
+
+export type AlertVariant = "error" | "warning" | "info" | "success";
+
+interface AlertCardProps {
+ /** The visual style variant of the alert */
+ variant: AlertVariant;
+ /** Optional title displayed prominently */
+ title?: string;
+ /** Main message content */
+ children: ReactNode;
+ /** Optional callback when dismiss button is clicked */
+ onDismiss?: () => void;
+ /** Additional CSS classes */
+ className?: string;
+}
+
+const variantStyles: Record