"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
);
}
if (kind === "video") {
return (
);
}
if (kind === "audio") {
return (
);
}
if (kind === "pdf") {
return (
);
}
// doc / other: the download card
return (
{detail.name}
{[detail.mimeType, humanSize(detail.size)].join(" · ")}
No inline preview — use Download.
);
}