import React, { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { toast } from "@/hooks/use-toast"; import { Download, Maximize2, Minimize2, Trash2, X } from "lucide-react"; import { DeleteConfirmationDialog } from "../ui/deleteDialog"; import { QueryClient } from "@tanstack/react-query"; import { cloudFilesQueryKeyRoot } from "./files-section"; type Props = { fileId: number | null; isOpen: boolean; onClose: () => void; onDeleted?: () => void; }; export default function FilePreviewModal({ fileId, isOpen, onClose, onDeleted, }: Props) { const [loading, setLoading] = useState(false); const [meta, setMeta] = useState(null); const [blobUrl, setBlobUrl] = useState(null); const [error, setError] = useState(null); const [isFullscreen, setIsFullscreen] = useState(false); const [deleting, setDeleting] = useState(false); const [isDeleteOpen, setIsDeleteOpen] = useState(false); useEffect(() => { if (!isOpen || !fileId) return; let cancelled = false; let createdUrl: string | null = null; async function load() { setLoading(true); setError(null); setMeta(null); setBlobUrl(null); try { const metaRes = await apiRequest( "GET", `/api/cloud-storage/files/${fileId}` ); const metaJson = await metaRes.json(); if (!metaRes.ok) { throw new Error(metaJson?.message || "Failed to load file metadata"); } if (cancelled) return; setMeta(metaJson.data); const contentRes = await apiRequest( "GET", `/api/cloud-storage/files/${fileId}/content` ); if (!contentRes.ok) { let msg = `Preview request failed (${contentRes.status})`; try { const j = await contentRes.json(); msg = j?.message ?? msg; } catch (e) {} throw new Error(msg); } const blob = await contentRes.blob(); if (cancelled) return; createdUrl = URL.createObjectURL(blob); setBlobUrl(createdUrl); } catch (err: any) { if (!cancelled) setError(err?.message ?? String(err)); } finally { if (!cancelled) setLoading(false); } } load(); return () => { cancelled = true; if (createdUrl) { URL.revokeObjectURL(createdUrl); } }; }, [isOpen, fileId]); if (!isOpen) return null; const mime = meta?.mimeType ?? ""; async function handleDownload() { if (!fileId) return; try { const res = await apiRequest( "GET", `/api/cloud-storage/files/${fileId}/download` ); if (!res.ok) { const j = await res.json().catch(() => ({})); throw new Error(j?.message || `Download failed (${res.status})`); } const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = meta?.name ?? `file-${fileId}`; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 5000); } catch (err: any) { toast({ title: "Download failed", description: err?.message ?? String(err), variant: "destructive", }); } } async function confirmDelete() { if (!fileId) return; setIsDeleteOpen(false); setDeleting(true); try { const res = await apiRequest( "DELETE", `/api/cloud-storage/files/${fileId}` ); const json = await res.json().catch(() => ({})); if (!res.ok) { throw new Error(json?.message || `Delete failed (${res.status})`); } toast({ title: "Deleted", description: `File "${meta?.name ?? `file-${fileId}`}" deleted.`, }); // notify parent to refresh lists if they provided callback if (typeof onDeleted === "function") { try { onDeleted(); } catch (e) { // ignore parent errors } } // close modal onClose(); } catch (err: any) { toast({ title: "Delete failed", description: err?.message ?? String(err), variant: "destructive", }); } finally { setDeleting(false); } } // container sizing classes const containerBase = "bg-white rounded-md p-3 flex flex-col overflow-hidden shadow-xl"; const sizeClass = isFullscreen ? "w-[95vw] h-[95vh]" : "w-[min(1200px,95vw)] h-[85vh]"; return (
{/* header */}

{meta?.name ?? "Preview"}

{meta?.mimeType ?? ""}
{/* body */}
{/* loading / error */} {loading && (
Loading preview…
)} {error &&
{error}
} {/* image */} {!loading && !error && blobUrl && mime.startsWith("image/") && (
{meta?.name}
)} {/* pdf */} {!loading && !error && blobUrl && (mime === "application/pdf" || mime.endsWith("/pdf")) && (