/**
* EvidencePreviewModal
*
* Shows evidence files inline:
* - Images → rendered directly (same-origin cookie sent automatically)
* - JSON → fetched via authenticated Axios, pretty-printed
* - Text → fetched via authenticated Axios, shown in
*/
import { useEffect, useState, useCallback } from "react";
import { X, Loader2, AlertCircle, Download } from "lucide-react";
import { getEvidenceRawContent } from "../api/evidence";
// ── Helpers ────────────────────────────────────────────────────────
export type PreviewType = "image" | "json" | "text" | null;
const IMAGE_EXTS = new Set([
"png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "ico", "tiff", "tif",
]);
const TEXT_EXTS = new Set([
"txt", "log", "md", "csv", "xml", "html", "htm", "yaml", "yml",
"ini", "cfg", "conf", "sh", "bat", "ps1", "py", "js", "ts",
]);
export function getPreviewType(fileName: string): PreviewType {
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
if (IMAGE_EXTS.has(ext)) return "image";
if (ext === "json") return "json";
if (TEXT_EXTS.has(ext)) return "text";
return null;
}
// ── Component ──────────────────────────────────────────────────────
interface Props {
evidenceId: string;
fileName: string;
previewType: PreviewType;
downloadUrl: string;
onClose: () => void;
onDownload: () => void;
}
export default function EvidencePreviewModal({
evidenceId,
fileName,
previewType,
downloadUrl,
onClose,
onDownload,
}: Props) {
const [text, setText] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Fetch text / JSON content on mount (not needed for images)
useEffect(() => {
if (previewType === "text" || previewType === "json") {
setLoading(true);
getEvidenceRawContent(evidenceId)
.then(({ text: raw }) => {
if (previewType === "json") {
try {
setText(JSON.stringify(JSON.parse(raw), null, 2));
} catch {
setText(raw); // not valid JSON — show raw
}
} else {
setText(raw);
}
})
.catch((e) => setError(e?.message ?? "Failed to load file"))
.finally(() => setLoading(false));
}
}, [evidenceId, previewType]);
// Close on Escape
const handleKey = useCallback(
(e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
},
[onClose],
);
useEffect(() => {
document.addEventListener("keydown", handleKey);
return () => document.removeEventListener("keydown", handleKey);
}, [handleKey]);
return (
/* backdrop */
e.target === e.currentTarget && onClose()}
>
{/* panel */}
{/* header */}
{fileName}
{/* body */}
{/* ── IMAGE ─────────────────────────────────────── */}
{previewType === "image" && (
{
(e.currentTarget as HTMLImageElement).style.display = "none";
setError("Image could not be loaded");
}}
/>
{error && }
)}
{/* ── TEXT / JSON ────────────────────────────────── */}
{(previewType === "text" || previewType === "json") && (
<>
{loading && (
)}
{error && !loading && }
{text !== null && !loading && (
{text}
)}
>
)}
);
}
function ErrorMessage({ message }: { message: string }) {
return (
{message}
);
}