import { Check, FolderOpen, ImageIcon, Loader2, Search, X } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "@/contexts/i18n-context"; import { apiListFiles, formatHeaders, getFileDownloadUrl, getFileThumbnailUrl, type UserFile, } from "@/lib/api"; import { cn } from "@/lib/utils"; function AuthImage({ src, alt, className }: { src: string; alt: string; className?: string }) { const [blobUrl, setBlobUrl] = useState(null); const [failed, setFailed] = useState(false); useEffect(() => { let revoked = false; fetch(src, { headers: formatHeaders() }) .then((res) => { if (!res.ok) throw new Error(); return res.blob(); }) .then((blob) => { if (revoked) return; setBlobUrl(URL.createObjectURL(blob)); }) .catch(() => { if (!revoked) setFailed(true); }); return () => { revoked = true; setBlobUrl((prev) => { if (prev) URL.revokeObjectURL(prev); return null; }); }; }, [src]); if (failed) { return (
); } if (!blobUrl) { return (
); } return {alt}; } interface FileLibraryModalProps { open: boolean; onClose: () => void; onImport: (files: File[]) => void; } export function FileLibraryModal({ open, onClose, onImport }: FileLibraryModalProps) { const { t } = useTranslation(); const [files, setFiles] = useState([]); const [loading, setLoading] = useState(false); const [importing, setImporting] = useState(false); const [checkedIds, setCheckedIds] = useState>(new Set()); const [searchQuery, setSearchQuery] = useState(""); const debounceRef = useRef | null>(null); const fetchFiles = useCallback(async (search?: string) => { setLoading(true); try { const result = await apiListFiles({ search: search || undefined, limit: 200 }); setFiles(result.files); } catch { setFiles([]); } finally { setLoading(false); } }, []); useEffect(() => { if (open) { fetchFiles(); setCheckedIds(new Set()); setSearchQuery(""); } }, [open, fetchFiles]); function handleSearchChange(e: React.ChangeEvent) { const val = e.target.value; setSearchQuery(val); if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => fetchFiles(val), 300); } function toggleCheck(id: string) { setCheckedIds((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); } function toggleAll() { if (checkedIds.size === files.length) { setCheckedIds(new Set()); } else { setCheckedIds(new Set(files.map((f) => f.id))); } } async function handleImport() { if (checkedIds.size === 0) return; setImporting(true); try { const toDownload = files.filter((f) => checkedIds.has(f.id)); const downloaded = await Promise.all( toDownload.map(async (f) => { const res = await fetch(getFileDownloadUrl(f.id), { headers: formatHeaders() }); if (!res.ok) return null; const blob = await res.blob(); return new File([blob], f.originalName, { type: f.mimeType }); }), ); const valid = downloaded.filter((f): f is File => f !== null); if (valid.length > 0) { onImport(valid); onClose(); } } finally { setImporting(false); } } if (!open) return null; const allChecked = files.length > 0 && checkedIds.size === files.length; return (