import { useEffect, useMemo, useState } from "react";
import { Download, FileText, Loader2, Lock, MailOpen, Shield, Trash2 } from "lucide-react";
import { toast } from "@/client/components/toast";
import { Button, Card, EmptyState } from "@/client/components/ui";
import { prepareEmailHtml } from "@/client/lib/email-html";
import {
downloadAttachment,
getRawEmailSource,
getErrorMessage,
type AuthDescriptor,
type EmailAttachment,
type EmailDetail as EmailDetailType,
} from "@/client/lib/api";
import { fullDate } from "@/client/lib/time";
interface EmailDetailProps {
address: string;
auth: AuthDescriptor;
email: EmailDetailType | null;
loading: boolean;
canDelete: boolean;
canViewRaw: boolean;
onDelete: (emailId: string) => void;
}
function formatSize(sizeBytes: number) {
if (sizeBytes < 1024) {
return `${sizeBytes} B`;
}
if (sizeBytes < 1024 * 1024) {
return `${(sizeBytes / 1024).toFixed(1)} KB`;
}
return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
}
function buildSrcDoc(html: string, bodyAttributes = "", headHtml = "") {
return `
${headHtml}
${html}
`;
}
export function EmailDetail({ address, auth, email, loading, canDelete, canViewRaw, onDelete }: EmailDetailProps) {
const [downloadingId, setDownloadingId] = useState(null);
const [downloadingRaw, setDownloadingRaw] = useState(false);
const [allowRemoteContent, setAllowRemoteContent] = useState(false);
useEffect(() => {
setAllowRemoteContent(false);
}, [email?.id]);
const preparedHtml = useMemo(
() => (email?.html ? prepareEmailHtml(email.html, allowRemoteContent) : null),
[allowRemoteContent, email?.html, email?.id],
);
const handleDownload = async (attachment: EmailAttachment) => {
if (!email) {
return;
}
setDownloadingId(attachment.id);
try {
const blob = await downloadAttachment(address, email.id, attachment.id, auth);
const fileName = attachment.filename ?? "attachment.bin";
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = fileName;
link.click();
URL.revokeObjectURL(url);
} catch (error) {
toast.error(getErrorMessage(error));
} finally {
setDownloadingId(null);
}
};
const handleViewRaw = async () => {
if (!email) return;
const rawWindow = window.open("about:blank", "_blank");
if (!rawWindow) {
toast.error("Browser blocked the raw source tab");
return;
}
rawWindow.document.title = `Raw email ${email.id}`;
rawWindow.document.body.textContent = "Loading raw source...";
rawWindow.document.body.style.cssText =
"margin:0;padding:16px;background:#0f172a;color:#cbd5e1;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;";
rawWindow.document.documentElement.style.colorScheme = "dark";
setDownloadingRaw(true);
try {
const rawSource = await getRawEmailSource(address, email.id, auth);
const pre = rawWindow.document.createElement("pre");
pre.style.cssText = "margin:0;white-space:pre-wrap;word-break:break-word;";
pre.textContent = rawSource;
rawWindow.document.body.textContent = "";
rawWindow.document.body.appendChild(pre);
} catch (error) {
rawWindow.close();
toast.error(getErrorMessage(error));
} finally {
setDownloadingRaw(false);
}
};
if (loading && !email) {
return (
Loading message...
);
}
if (!email) {
return (
}
caption="Viewer"
heading="Select an email"
description="Pick a message from the list to view its contents."
/>
);
}
return (
{/* Header */}
{email.subject}
{canViewRaw ? (
: }
disabled={loading || downloadingRaw}
onClick={() => void handleViewRaw()}
>
View raw
) : null}
{canDelete ? (
}
disabled={loading}
onClick={() => onDelete(email.id)}
>
Delete
) : (
Read-only
)}
{email.fromName || email.fromAddress}
{email.fromName ? <{email.fromAddress}> : null}
·
{fullDate(email.receivedAt)}
·
{formatSize(email.sizeBytes)}
{email.recipientAddress ? (
Delivered to {email.recipientAddress}
) : null}
{/* Attachments */}
{email.attachments.length > 0 ? (
{email.attachments.map((attachment) => {
const busy = downloadingId === attachment.id;
return (
handleDownload(attachment)}
disabled={busy || loading}
>
{busy ? (
) : (
)}
{attachment.filename || "attachment.bin"}
{formatSize(attachment.sizeBytes)}
);
})}
) : null}
{/* Body */}
{email.html && preparedHtml ? (
Sandboxed view
{preparedHtml.blockedRemoteContent && !allowRemoteContent ? (
setAllowRemoteContent(true)}
>
Load remote content
) : preparedHtml.blockedRemoteContent && allowRemoteContent ? (
Remote content loaded
) : null}
) : (
Plain text
{email.text || "This email has no text content."}
)}
);
}