import React, { useState, useEffect } from "react"; import PostalMime from "postal-mime"; import DOMPurify from "dompurify"; import { Mail, User, Calendar, Paperclip, Download, FileText, } from "lucide-react"; import { Button } from "@/components/ds/ui/button"; import { Separator } from "@/components/ds/ui/separator"; import { useTranslate } from "ra-core"; interface EmailViewerProps { content: ArrayBuffer | string; onError?: (error: string) => void; } interface EmailAddress { name?: string; address: string; } interface ParsedEmail { from?: EmailAddress; to?: EmailAddress[]; cc?: EmailAddress[]; subject?: string; date?: string; html?: string; text?: string; attachments?: Array<{ filename?: string; mimeType?: string; content?: Uint8Array; size?: number; }>; } export const EmailViewer = ({ content, onError }: EmailViewerProps) => { const [email, setEmail] = useState(null); const [loading, setLoading] = useState(true); const [showHtml, setShowHtml] = useState(true); const translate = useTranslate(); useEffect(() => { const parseEmail = async () => { try { setLoading(true); const parser = new PostalMime(); // Convert content to ArrayBuffer if it's a string let buffer: ArrayBuffer; if (typeof content === "string") { const encoder = new TextEncoder(); buffer = encoder.encode(content).buffer; } else { buffer = content; } const parsed = await parser.parse(buffer); setEmail({ from: parsed.from as EmailAddress | undefined, to: parsed.to as EmailAddress[] | undefined, cc: parsed.cc as EmailAddress[] | undefined, subject: parsed.subject, date: parsed.date, html: parsed.html, text: parsed.text, attachments: parsed.attachments?.map((att: any) => ({ filename: att.filename, mimeType: att.mimeType, content: att.content, size: att.content?.length, })), }); } catch (err) { console.error("Failed to parse email:", err); const errorMsg = err instanceof Error ? err.message : translate("crm.email_viewer.error.parse"); onError?.(errorMsg); } finally { setLoading(false); } }; parseEmail(); }, [content, onError, translate]); const formatEmailAddress = (addr?: EmailAddress) => { if (!addr) return translate("crm.email_viewer.field.unknown"); return addr.name ? `${addr.name} <${addr.address}>` : addr.address; }; const formatEmailAddressList = (addrs?: EmailAddress[]) => { if (!addrs || addrs.length === 0) return translate("crm.email_viewer.field.none"); return addrs.map(formatEmailAddress).join(", "); }; const formatDate = (dateStr?: string) => { if (!dateStr) return translate("crm.email_viewer.field.unknown"); try { return new Date(dateStr).toLocaleString(); } catch { return dateStr; } }; const downloadAttachment = ( attachment: NonNullable[0], ) => { if (!attachment.content) return; const blob = new Blob([attachment.content], { type: attachment.mimeType || "application/octet-stream", }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = attachment.filename || "attachment"; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }; const formatFileSize = (bytes?: number) => { if (!bytes) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`; }; if (loading) { return (
); } if (!email) { return (

{translate("crm.email_viewer.error.load")}

); } return (
{/* Email Headers */}

{email.subject || translate("crm.email_viewer.field.subject_none")}

{translate("crm.email_viewer.field.from")}: {" "} {formatEmailAddress(email.from)}
{email.to && email.to.length > 0 && (
{translate("crm.email_viewer.field.to")}: {" "} {formatEmailAddressList(email.to)}
)} {email.cc && email.cc.length > 0 && (
{translate("crm.email_viewer.field.cc")}: {" "} {formatEmailAddressList(email.cc)}
)}
{translate("crm.email_viewer.field.date")}: {" "} {formatDate(email.date)}
{/* Toggle HTML/Text view if both are available */} {email.html && email.text && (
)}
{/* Email Body */}
{showHtml && email.html ? (
) : email.text ? (
            {email.text}
          
) : (

{translate("crm.email_viewer.field.content_none")}

)}
{/* Attachments */} {email.attachments && email.attachments.length > 0 && (
{email.attachments.length}{" "} {translate( email.attachments.length !== 1 ? "crm.email_viewer.field.attachments" : "crm.email_viewer.field.attachment", )}
{email.attachments.map((attachment, index) => (

{attachment.filename || `attachment-${index + 1}`}

{attachment.mimeType || translate("crm.email_viewer.field.unknown_type")}{" "} • {formatFileSize(attachment.size)}

))}
)}
); };