"use client";
import { use, useEffect, useRef, useState } from "react";
import Link from "next/link";
import {
getMail,
downloadMailAttachment,
downloadMailRaw,
exportMailPDF,
getLabels,
getMailLabelIds,
type MailDetail,
type MailAttachment,
type MailLabel,
} from "@/lib/api";
import { useAuth } from "@/hooks/useAuth";
import { Navbar } from "@/components/navbar";
import { LabelPicker } from "@/components/LabelPicker";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Alert, AlertDescription } from "@/components/ui/alert";
// ── Helpers ────────────────────────────────────────────────────────────────
function formatBytes(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
function formatDate(iso: string): string {
try {
return new Date(iso).toLocaleString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
} catch {
return iso;
}
}
function triggerDownload(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
function blockExternalSrcs(html: string): string {
// Replace src= in img/video/audio tags with data-src= to block loading
return html
.replace(/<(img|video|audio|source)(\s[^>]*?\s)src(\s*=\s*["']https?:)/gi,
"<$1$2data-src$3")
.replace(/<(img|video|audio|source)(\s)src(\s*=\s*["']https?:)/gi,
"<$1$2data-src$3");
}
// ── Sub-components ─────────────────────────────────────────────────────────
function MailHeaderGrid({ mail }: { mail: MailDetail }) {
const [showRaw, setShowRaw] = useState(false);
return (
Von:
{mail.from || "–"}
An:
{mail.to || "–"}
{mail.cc && (
<>
CC:
{mail.cc}
>
)}
Datum:
{formatDate(mail.date)}
Betreff:
{mail.subject || "(kein Betreff)"}
Größe:
{formatBytes(mail.size)}
{/* Verification status */}
Integrität:
{mail.verify_ok === true ? (
Verifiziert
) : mail.verify_ok === false ? (
Manipuliert!
) : (
Noch nicht geprüft
)}
{showRaw && (
{mail.raw_headers}
)}
);
}
function MailBodyView({ mail }: { mail: MailDetail }) {
const iframeRef = useRef(null);
const [showExternal, setShowExternal] = useState(false);
const html = mail.body_html ?? null;
const plain = mail.body_plain ?? null;
// Adjust iframe height to content
function handleIframeLoad() {
const iframe = iframeRef.current;
if (!iframe) return;
try {
const body = iframe.contentDocument?.body;
if (body) {
iframe.style.height = `${body.scrollHeight + 32}px`;
}
} catch {
iframe.style.height = "600px";
}
}
if (!html && !plain) {
return (
Kein Inhalt vorhanden.
);
}
if (html) {
const srcdoc = showExternal ? html : blockExternalSrcs(html);
return (
{!showExternal && (
Externe Inhalte (Bilder, Tracker) sind blockiert.
)}
);
}
// Plain-text fallback
return (
{plain}
);
}
function AttachmentRow({
mailId,
attachment,
}: {
mailId: string;
attachment: MailAttachment;
}) {
const [downloading, setDownloading] = useState(false);
async function handleDownload() {
setDownloading(true);
try {
const { blob, filename } = await downloadMailAttachment(
mailId,
attachment.index
);
triggerDownload(blob, filename || attachment.filename);
} catch (e) {
alert(`Download fehlgeschlagen: ${e instanceof Error ? e.message : e}`);
} finally {
setDownloading(false);
}
}
return (
{attachment.filename}
{attachment.content_type} · {formatBytes(attachment.size)}
);
}
// ── Page ───────────────────────────────────────────────────────────────────
export default function MailViewPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = use(params);
const { user, loading: authLoading } = useAuth();
const [mail, setMail] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
const [downloading, setDownloading] = useState(false);
const [pdfLoading, setPdfLoading] = useState(false);
// Labels state
const [allLabels, setAllLabels] = useState([]);
const [assignedLabelIds, setAssignedLabelIds] = useState([]);
useEffect(() => {
if (!user) return;
getMail(id)
.then(setMail)
.catch((e) =>
setError(e instanceof Error ? e.message : "Unbekannter Fehler")
)
.finally(() => setLoading(false));
// Load labels
getLabels().then(setAllLabels).catch(() => {});
loadMailLabels();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, user]);
function loadMailLabels() {
getMailLabelIds(id).then(setAssignedLabelIds).catch(() => setAssignedLabelIds([]));
}
async function handleEmlDownload() {
setDownloading(true);
try {
const { blob, filename } = await downloadMailRaw(id);
triggerDownload(blob, filename);
} catch (e) {
alert(`Download fehlgeschlagen: ${e instanceof Error ? e.message : e}`);
} finally {
setDownloading(false);
}
}
async function handlePdfDownload() {
setPdfLoading(true);
try {
const { blob, filename } = await exportMailPDF(id);
triggerDownload(blob, filename);
} catch (e) {
alert(`PDF-Export fehlgeschlagen: ${e instanceof Error ? e.message : e}`);
} finally {
setPdfLoading(false);
}
}
return (
{(authLoading || !user) ? (
) : (<>
{/* Back + Actions */}
{mail && (
{id}
)}
{/* Loading */}
{loading && (
)}
{/* Error */}
{error && (
{error}
)}
{/* Mail content */}
{mail && (
<>
{/* Header */}
{/* Labels */}
{allLabels.length > 0 && (
)}
{/* Body */}
{/* Attachments */}
{mail.attachments && mail.attachments.length > 0 && (
Anhänge ({mail.attachments.length})
{mail.attachments.map((att) => (
))}
)}
>
)}
>)}
);
}