Neah/components/email/EmailPreview.tsx
2025-04-30 22:25:48 +02:00

303 lines
12 KiB
TypeScript

'use client';
import { useRef, useMemo } from 'react';
import { Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Card } from '@/components/ui/card';
import { EmailMessage, EmailAddress } from '@/types/email';
import { formatEmailAddresses, formatEmailDate } from '@/lib/utils/email-utils';
import { adaptLegacyEmail } from '@/lib/utils/email-adapters';
import EmailContentDisplay from './EmailContentDisplay';
interface EmailPreviewProps {
email: EmailMessage | any;
loading?: boolean;
onReply?: (type: 'reply' | 'reply-all' | 'forward') => void;
}
export default function EmailPreview({ email, loading = false, onReply }: EmailPreviewProps) {
// Add editorRef to match ComposeEmail exactly
const editorRef = useRef<HTMLDivElement>(null);
// Convert legacy email to standardized format if needed
const standardizedEmail = useMemo(() => {
if (!email) return null;
try {
// Log input email details for debugging
console.log('EmailPreview: Input email type:', typeof email);
console.log('EmailPreview: Input email properties:', Object.keys(email));
// Check if from field is an array or string and log it
if (email.from) {
console.log('EmailPreview: From field type:', Array.isArray(email.from) ?
'array' : typeof email.from);
if (Array.isArray(email.from)) {
console.log('EmailPreview: From array length:', email.from.length);
if (email.from.length > 0) {
console.log('EmailPreview: First from item type:', typeof email.from[0]);
}
}
}
if (email.content) {
console.log('EmailPreview: Content type:', typeof email.content);
if (typeof email.content === 'object') {
console.log('EmailPreview: Content properties:', Object.keys(email.content));
} else {
console.log('EmailPreview: Content first 100 chars:', email.content.substring(0, 100));
}
}
// Check if the email is already in the standardized format
if (
email.content &&
typeof email.content === 'object' &&
'isHtml' in email.content &&
'text' in email.content
) {
console.log('EmailPreview: Email is already in standardized format');
return email as EmailMessage;
}
// Otherwise, adapt it
console.log('EmailPreview: Adapting legacy email format');
const adapted = adaptLegacyEmail(email);
// Log the adapted email structure for debugging
console.log('EmailPreview: Adapted email:', {
id: adapted.id,
subject: adapted.subject,
from: adapted.from,
fromType: typeof adapted.from,
isFromArray: Array.isArray(adapted.from),
content: adapted.content ? {
isHtml: adapted.content.isHtml,
direction: adapted.content.direction,
textLength: adapted.content.text?.length,
htmlExists: !!adapted.content.html
} : 'No content'
});
return adapted;
} catch (error) {
console.error('Error adapting email:', error);
// Instead of returning null, try to create a minimal valid email to display the error
return {
id: email?.id || 'error',
subject: email?.subject || 'Error processing email',
from: email?.from || '',
to: email?.to || '',
date: email?.date || new Date().toISOString(),
flags: [],
content: {
text: `Error processing email: ${error instanceof Error ? error.message : 'Unknown error'}`,
isHtml: false,
direction: 'ltr'
}
} as EmailMessage;
}
}, [email]);
// Get sender initials for avatar
const getSenderInitials = (name: string) => {
if (!name) return '';
return name
.split(" ")
.map((n) => n?.[0] || '')
.join("")
.toUpperCase()
.slice(0, 2);
};
// Display loading state
if (loading) {
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...</p>
</div>
</div>
);
}
// No email selected
if (!standardizedEmail) {
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>
);
}
// Debug output for content structure
console.log('EmailPreview: Standardized Email Content:', standardizedEmail.content);
// Extract sender from various possible formats - handle both string and array formats
let senderName = '';
let senderEmail = '';
// Handle 'from' field which might be a string or an array of EmailAddress objects
if (standardizedEmail.from) {
if (Array.isArray(standardizedEmail.from)) {
// If it's an array of EmailAddress objects
if (standardizedEmail.from.length > 0) {
const sender = standardizedEmail.from[0];
if (typeof sender === 'object') {
senderName = sender.name || sender.address || '';
senderEmail = sender.address || '';
} else {
// Handle case where array contains strings
senderName = String(sender);
senderEmail = String(sender);
}
}
} else if (typeof standardizedEmail.from === 'string') {
// If it's a string, try to extract name and email with regex
const senderInfo = standardizedEmail.from.match(/^(?:"?([^"]*)"?\s)?<?([^\s>]+@[^\s>]+)>?$/);
senderName = senderInfo ? senderInfo[1] || senderInfo[2] : standardizedEmail.from;
senderEmail = senderInfo ? senderInfo[2] : standardizedEmail.from;
}
}
// Check for attachments
const hasAttachments = standardizedEmail.attachments && standardizedEmail.attachments.length > 0;
return (
<Card className="flex flex-col h-full overflow-hidden border-0 shadow-none">
{/* Email header */}
<div className="p-6 border-b">
<div className="mb-4">
<h2 className="text-xl font-semibold mb-4">{standardizedEmail.subject}</h2>
<div className="flex items-start gap-3 mb-4">
<Avatar className="h-10 w-10">
<AvatarFallback>{getSenderInitials(senderName)}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="font-medium">{senderName}</div>
<span className="text-sm text-muted-foreground">{formatEmailDate(standardizedEmail.date)}</span>
</div>
<div className="text-sm text-muted-foreground truncate mt-1">
To: {standardizedEmail.to}
</div>
{standardizedEmail.cc && (
<div className="text-sm text-muted-foreground truncate mt-1">
Cc: {standardizedEmail.cc}
</div>
)}
</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>
)}
</div>
{/* Attachments list */}
{hasAttachments && standardizedEmail.attachments && (
<div className="px-6 py-3 border-b bg-muted/20">
<h3 className="text-sm font-medium mb-2">Attachments ({standardizedEmail.attachments.length})</h3>
<div className="flex flex-wrap gap-2">
{standardizedEmail.attachments.map((attachment, index) => (
<div
key={index}
className="flex items-center gap-2 bg-background rounded-md px-3 py-1.5 text-sm border"
>
<div className="flex-shrink-0">
<svg className="h-4 w-4 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
</div>
<span>{attachment.filename}</span>
</div>
))}
</div>
</div>
)}
</div>
{/* Email body */}
<ScrollArea className="flex-1">
<div className="p-6">
{/* Render the email content using the new standardized component */}
<div
ref={editorRef}
className="email-content-container rounded-lg overflow-hidden bg-white shadow-sm"
style={{
minHeight: '300px',
border: '1px solid #e2e8f0'
}}
>
<EmailContentDisplay
content={standardizedEmail.content}
type="auto"
className="p-6"
debug={process.env.NODE_ENV === 'development'}
/>
</div>
{/* Always show debugging info in development mode */}
{process.env.NODE_ENV === 'development' && (
<details className="mt-4 text-xs text-muted-foreground border rounded-md p-2" open>
<summary className="cursor-pointer">Email Debug Info</summary>
<div className="mt-2 overflow-auto max-h-80 p-2 bg-gray-50 rounded">
<p><strong>Email ID:</strong> {standardizedEmail.id}</p>
<p><strong>Content Type:</strong> {standardizedEmail.content.isHtml ? 'HTML' : 'Plain Text'}</p>
<p><strong>Text Direction:</strong> {standardizedEmail.content.direction || 'ltr'}</p>
<p><strong>Content Size:</strong>
HTML: {standardizedEmail.content.html?.length || 0} chars,
Text: {standardizedEmail.content.text?.length || 0} chars
</p>
<p><strong>Content Structure:</strong> {JSON.stringify(standardizedEmail.content, null, 2)}</p>
<hr className="my-2" />
<p><strong>Original Email Type:</strong> {typeof email}</p>
<p><strong>Original Content Type:</strong> {typeof email.content}</p>
{email && typeof email.content === 'object' && (
<p><strong>Original Content Keys:</strong> {Object.keys(email.content).join(', ')}</p>
)}
{email && email.html && (
<p><strong>Has HTML property:</strong> {email.html.length} chars</p>
)}
{email && email.text && (
<p><strong>Has Text property:</strong> {email.text.length} chars</p>
)}
</div>
</details>
)}
</div>
</ScrollArea>
</Card>
);
}