Neah/components/email/EmailPreview.tsx
2025-04-26 21:18:29 +02:00

206 lines
5.9 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import DOMPurify from 'isomorphic-dompurify';
import { Loader2, Paperclip, Download } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { sanitizeHtml } from '@/lib/utils/email-formatter';
interface EmailAddress {
name: string;
address: string;
}
interface EmailMessage {
id: string;
messageId?: string;
subject: string;
from: EmailAddress[];
to: EmailAddress[];
cc?: EmailAddress[];
bcc?: EmailAddress[];
date: Date | string;
flags?: {
seen: boolean;
flagged: boolean;
answered: boolean;
deleted: boolean;
draft: boolean;
};
preview?: string;
content?: string;
html?: string;
text?: string;
hasAttachments?: boolean;
attachments?: Array<{
filename: string;
contentType: string;
size: number;
path?: string;
content?: string;
}>;
folder?: string;
size?: number;
contentFetched?: boolean;
}
interface EmailPreviewProps {
email: EmailMessage | null;
loading?: boolean;
onReply?: (type: 'reply' | 'reply-all' | 'forward') => void;
}
export default function EmailPreview({ email, loading = false, onReply }: EmailPreviewProps) {
const [contentLoading, setContentLoading] = useState<boolean>(false);
// Handle sanitizing and rendering HTML content
const renderContent = () => {
if (!email?.content) return <p>No content available</p>;
try {
// Use the centralized sanitizeHtml function which preserves direction
const sanitizedContent = sanitizeHtml(email.content);
return (
<div
className="email-content prose max-w-none dark:prose-invert"
dangerouslySetInnerHTML={{ __html: sanitizedContent }}
dir="auto"
/>
);
} catch (error) {
console.error('Error rendering email content:', error);
return <p>Error displaying email content</p>;
}
};
// Format the date
const formatDate = (date: Date | string) => {
if (!date) return '';
const dateObj = date instanceof Date ? date : new Date(date);
return dateObj.toLocaleString('en-US', {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
// Format email addresses
const formatEmailAddresses = (addresses: Array<{name: string, address: string}> | undefined) => {
if (!addresses || addresses.length === 0) return '';
return addresses.map(addr =>
addr.name && addr.name !== addr.address
? `${addr.name} <${addr.address}>`
: addr.address
).join(', ');
};
if (loading || contentLoading) {
return (
<div className="flex items-center justify-center h-full p-6">
<div className="text-center">
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-4 text-primary" />
<p>Loading email content...</p>
</div>
</div>
);
}
if (!email) {
return (
<div className="flex items-center justify-center h-full p-6">
<div className="text-center text-muted-foreground">
<p>Select an email to view</p>
</div>
</div>
);
}
return (
<div className="flex flex-col h-full overflow-hidden">
{/* Email header */}
<div className="p-4 border-b">
<div className="mb-3">
<h2 className="text-xl font-semibold mb-2">{email.subject}</h2>
<div className="flex items-center justify-between text-sm">
<div className="flex items-center">
<span className="font-medium mr-1">From:</span>
<span>{formatEmailAddresses(email.from)}</span>
</div>
<span className="text-muted-foreground">{formatDate(email.date)}</span>
</div>
{email.to && email.to.length > 0 && (
<div className="text-sm mt-1">
<span className="font-medium mr-1">To:</span>
<span>{formatEmailAddresses(email.to)}</span>
</div>
)}
{email.cc && email.cc.length > 0 && (
<div className="text-sm mt-1">
<span className="font-medium mr-1">Cc:</span>
<span>{formatEmailAddresses(email.cc)}</span>
</div>
)}
</div>
{/* Action buttons */}
{onReply && (
<div className="flex gap-2 mt-4">
<Button
size="sm"
variant="outline"
onClick={() => onReply('reply')}
>
Reply
</Button>
<Button
size="sm"
variant="outline"
onClick={() => onReply('reply-all')}
>
Reply All
</Button>
<Button
size="sm"
variant="outline"
onClick={() => onReply('forward')}
>
Forward
</Button>
</div>
)}
{/* Attachments */}
{email.attachments && email.attachments.length > 0 && (
<div className="mt-4 border-t pt-2">
<div className="text-sm font-medium mb-2">Attachments:</div>
<div className="flex flex-wrap gap-2">
{email.attachments.map((attachment, index) => (
<Badge key={index} variant="outline" className="flex items-center gap-1">
<Paperclip className="h-3 w-3" />
<span>{attachment.filename}</span>
<span className="text-xs text-muted-foreground ml-1">
({Math.round(attachment.size / 1024)}KB)
</span>
</Badge>
))}
</div>
</div>
)}
</div>
{/* Email content */}
<div className="flex-1 overflow-auto p-4">
{renderContent()}
</div>
</div>
);
}