diff --git a/apps/web/src/components/common/url-import-modal.tsx b/apps/web/src/components/common/url-import-modal.tsx new file mode 100644 index 00000000..3c6a7f98 --- /dev/null +++ b/apps/web/src/components/common/url-import-modal.tsx @@ -0,0 +1,215 @@ +import { AlertCircle, Check, Clock, Link, Loader2, RotateCw, X } from "lucide-react"; +import { useCallback, useState } from "react"; +import { type UrlImportEntry, useUrlImport } from "@/hooks/use-url-import"; +import { extractUrls } from "@/lib/url-parser"; + +// ── Types ────────────────────────────────────────────────────── + +interface UrlImportModalProps { + onClose: () => void; + onImport: (files: File[]) => void; +} + +// ── Helpers ──────────────────────────────────────────────────── + +function StatusIcon({ status }: { status: UrlImportEntry["status"] }) { + switch (status) { + case "pending": + return ; + case "fetching": + return ; + case "ready": + return ; + case "failed": + return ; + } +} + +function formatSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; +} + +function filenameFromUrl(url: string): string { + try { + return new URL(url).pathname.split("/").pop() || url; + } catch { + return url; + } +} + +// ── Component ────────────────────────────────────────────────── + +export function UrlImportModal({ onClose, onImport }: UrlImportModalProps) { + const [text, setText] = useState(""); + const [adding, setAdding] = useState(false); + + const { entries, importing, importUrls, addReadyFiles, retryUrl, cancel, reset, readyCount } = + useUrlImport(); + + const hasResults = entries.length > 0; + + const handleImport = useCallback(() => { + const urls = extractUrls(text); + if (urls.length === 0) return; + importUrls(urls); + }, [text, importUrls]); + + const handleAdd = useCallback(async () => { + if (readyCount === 0) return; + setAdding(true); + try { + const files = await addReadyFiles(); + if (files.length > 0) { + onImport(files); + onClose(); + } + } finally { + setAdding(false); + } + }, [readyCount, addReadyFiles, onImport, onClose]); + + const handleBack = useCallback(() => { + reset(); + }, [reset]); + + const handleClose = useCallback(() => { + cancel(); + onClose(); + }, [cancel, onClose]); + + return ( +
+ {/* Overlay */} +