"use client"; /** * TASK-0103 — the file preview modal (file browser, Requirements §5). * * Row click in the browser opens this: image → lightbox (full size), pdf → * sandboxed iframe, video → native player, audio → player, doc/other → the * download card. Signed URLs come from `GET /files/:id` (15-min contract); * the modal closes on Escape / backdrop. */ import { useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; import { Download, ExternalLink, Loader2, ShieldAlert, X } from "lucide-react"; import { filesApi, humanSize, kindForMime, type FileDetail } from "@/lib/files/api"; interface PreviewModalProps { fileId: string; workspaceId?: string; onClose: () => void; } export function PreviewModal({ fileId, workspaceId, onClose }: PreviewModalProps) { const query = useQuery({ queryKey: ["files", fileId, "preview"], queryFn: () => filesApi.get(fileId, { workspaceId }), }); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [onClose]); return (
e.stopPropagation()} >
{query.data?.name ?? "Loading…"}
{query.isLoading || !query.data ? (
) : query.data.status === "VIRUS_DETECTED" ? (
Blocked by virus scan — contact an admin
) : query.data.status !== "READY" ? (
{query.data.status === "SCANNING" ? "Scanning…" : "Processing…"}
) : ( )}
{query.data && query.data.status === "READY" ? (
{kindForMime(query.data.mimeType) === "pdf" ? ( Open in new tab ) : null}
) : null}
); } function ModalBody({ detail }: { detail: FileDetail }) { const kind = kindForMime(detail.mimeType); if (kind === "image") { return ( // eslint-disable-next-line @next/next/no-img-element {detail.name} ); } if (kind === "video") { return (