'use client'; import { useState, useRef, useEffect } from 'react'; // Remove direct import of server components import { X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2, AlignLeft, AlignRight } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'; import DOMPurify from 'isomorphic-dompurify'; // Define EmailMessage interface locally instead of importing from server-only file 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?: any[]; folder?: string; size?: number; contentFetched?: boolean; } // Simplified formatEmailForReplyOrForward that doesn't rely on server code function formatEmailForReplyOrForward( email: EmailMessage, type: 'reply' | 'reply-all' | 'forward' ): { to: string; cc?: string; subject: string; body: string; } { // Format subject let subject = email.subject || ''; if (type === 'reply' || type === 'reply-all') { if (!subject.startsWith('Re:')) { subject = `Re: ${subject}`; } } else if (type === 'forward') { if (!subject.startsWith('Fwd:')) { subject = `Fwd: ${subject}`; } } // Create quote header const date = typeof email.date === 'string' ? new Date(email.date) : email.date; const formattedDate = date.toLocaleString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); const sender = email.from[0]; const fromText = sender?.name ? `${sender.name} <${sender.address}>` : sender?.address || 'Unknown sender'; const quoteHeader = `
On ${formattedDate}, ${fromText} wrote:
`; // Format content const quotedContent = email.html || email.content || email.text || ''; // Format recipients let to = ''; let cc = ''; if (type === 'reply') { // Reply to sender only to = email.from.map(addr => `${addr.name} <${addr.address}>`).join(', '); } else if (type === 'reply-all') { // Reply to sender and all recipients to = email.from.map(addr => `${addr.name} <${addr.address}>`).join(', '); // Add all original recipients to CC const allRecipients = [ ...(email.to || []), ...(email.cc || []) ]; cc = allRecipients .map(addr => `${addr.name} <${addr.address}>`) .join(', '); } else if (type === 'forward') { // Forward doesn't set recipients to = ''; // Format forward differently const formattedDate = typeof email.date === 'string' ? new Date(email.date).toLocaleString() : email.date.toLocaleString(); const fromText = email.from.map(f => f.name ? `${f.name} <${f.address}>` : f.address).join(', '); const toText = email.to.map(t => t.name ? `${t.name} <${t.address}>` : t.address).join(', '); return { to: '', subject, body: `

---------- Forwarded message ---------

From: ${fromText}

Date: ${formattedDate}

Subject: ${email.subject || ''}

To: ${toText}

${quotedContent ? quotedContent : '

No content available

'}
` }; } // Format body with improved styling for replies const body = `

${quoteHeader}
${quotedContent}
`; return { to, cc: cc || undefined, subject, body }; } // Legacy interface for backward compatibility with old ComposeEmail component interface LegacyComposeEmailProps { showCompose: boolean; setShowCompose: (show: boolean) => void; composeTo: string; setComposeTo: (to: string) => void; composeCc: string; setComposeCc: (cc: string) => void; composeBcc: string; setComposeBcc: (bcc: string) => void; composeSubject: string; setComposeSubject: (subject: string) => void; composeBody: string; setComposeBody: (body: string) => void; showCc: boolean; setShowCc: (show: boolean) => void; showBcc: boolean; setShowBcc: (show: boolean) => void; attachments: any[]; setAttachments: (attachments: any[]) => void; handleSend: () => Promise; originalEmail?: { content: string; type: 'reply' | 'reply-all' | 'forward'; }; onSend: (email: any) => Promise; onCancel: () => void; replyTo?: any | null; forwardFrom?: any | null; } // New interface for the modern ComposeEmail component interface ComposeEmailProps { initialEmail?: EmailMessage | null; type?: 'new' | 'reply' | 'reply-all' | 'forward'; onClose: () => void; onSend: (emailData: { to: string; cc?: string; bcc?: string; subject: string; body: string; attachments?: Array<{ name: string; content: string; type: string; }>; }) => Promise; } // Union type to handle both new and legacy props type ComposeEmailAllProps = ComposeEmailProps | LegacyComposeEmailProps; // Type guard to check if props are legacy function isLegacyProps(props: ComposeEmailAllProps): props is LegacyComposeEmailProps { return 'showCompose' in props && 'setShowCompose' in props; } export default function ComposeEmail(props: ComposeEmailAllProps) { // Handle legacy props by adapting them to new component if (isLegacyProps(props)) { return ; } // Continue with modern implementation for new props const { initialEmail, type = 'new', onClose, onSend } = props; // Email form state const [to, setTo] = useState(''); const [cc, setCc] = useState(''); const [bcc, setBcc] = useState(''); const [subject, setSubject] = useState(''); const [body, setBody] = useState(''); const [userMessage, setUserMessage] = useState(''); const [originalContent, setOriginalContent] = useState(''); const [editingOriginalContent, setEditingOriginalContent] = useState(false); const [showCc, setShowCc] = useState(false); const [showBcc, setShowBcc] = useState(false); const [sending, setSending] = useState(false); const [isRTL, setIsRTL] = useState(false); const [attachments, setAttachments] = useState>([]); // Refs const editorRef = useRef(null); const originalContentRef = useRef(null); const attachmentInputRef = useRef(null); // Initialize the form when replying to or forwarding an email useEffect(() => { if (initialEmail && type !== 'new') { if (type === 'forward') { initializeForwardedEmail(); } else { // For reply/reply-all, continue using formatEmailForReplyOrForward const formattedEmail = formatEmailForReplyOrForward(initialEmail, type as 'reply' | 'reply-all'); setTo(formattedEmail.to); if (formattedEmail.cc) { setCc(formattedEmail.cc); setShowCc(true); } setSubject(formattedEmail.subject); // Make the entire content editable, just like with forwarded emails const bodyContent = formattedEmail.body; setUserMessage(bodyContent); setBody(bodyContent); } // Focus editor after initializing setTimeout(() => { if (editorRef.current) { editorRef.current.focus(); // Place cursor at the beginning of the content const selection = window.getSelection(); const range = document.createRange(); range.setStart(editorRef.current, 0); range.collapse(true); selection?.removeAllRanges(); selection?.addRange(range); } }, 100); } }, [initialEmail, type]); // Format date for the forwarded message header const formatDate = (date: Date | null): string => { if (!date) return ''; try { return date.toLocaleString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch (e) { return date.toString(); } }; // Format sender address in a readable format const formatSender = (from: Array<{name?: string, address: string}> | undefined): string => { if (!from || from.length === 0) return 'Unknown'; return from.map(sender => sender.name && sender.name !== sender.address ? `${sender.name} <${sender.address}>` : sender.address ).join(', '); }; // Format recipient addresses in a readable format const formatRecipients = (recipients: Array<{name?: string, address: string}> | undefined): string => { if (!recipients || recipients.length === 0) return ''; return recipients.map(recipient => recipient.name && recipient.name !== recipient.address ? `${recipient.name} <${recipient.address}>` : recipient.address ).join(', '); }; // Initialize forwarded email with clear structure and style preservation const initializeForwardedEmail = async () => { console.log('Starting initializeForwardedEmail'); if (!initialEmail) { console.error('No email available for forwarding'); setBody('
No email available for forwarding
'); return; } try { // Format subject with Fwd: prefix if needed const subjectBase = initialEmail.subject || '(No subject)'; const subjectRegex = /^(Fwd|FW|Forward):\s*/i; const subject = subjectRegex.test(subjectBase) ? subjectBase : `Fwd: ${subjectBase}`; setSubject(subject); // For forwarded emails, we'll use a completely different approach // Just save the original HTML content and use it directly // This preserves all formatting without trying to parse it // Set message parts for the editor const content = initialEmail.content || initialEmail.html || initialEmail.text || ''; setOriginalContent(content); setUserMessage(''); // Start with empty user message setBody(''); // Will be constructed when sending // Log for debugging console.log('Set originalContent:', content.substring(0, 100) + '...'); } catch (error) { console.error('Error formatting forwarded email:', error); setBody('
Error formatting forwarded email content
'); } }; // Handle attachment selection const handleAttachmentClick = () => { attachmentInputRef.current?.click(); }; // Process selected files const handleFileSelection = async (e: React.ChangeEvent) => { const files = e.target.files; if (!files || files.length === 0) return; // Convert selected files to attachments const newAttachments = Array.from(files).map(file => ({ file, uploading: true })); // Read files as data URLs for (const file of files) { const reader = new FileReader(); reader.onload = (event) => { const content = event.target?.result as string; setAttachments(current => [ ...current, { name: file.name, content: content.split(',')[1], // Remove data:mime/type;base64, prefix type: file.type } ]); }; reader.readAsDataURL(file); } // Reset file input if (e.target) { e.target.value = ''; } }; // Remove attachment const removeAttachment = (index: number) => { setAttachments(current => current.filter((_, i) => i !== index)); }; // Handle editing of original content const handleOriginalContentInput = (e: React.FormEvent) => { if (originalContentRef.current) { const content = originalContentRef.current.innerHTML; setOriginalContent(content); } }; // Toggle original content editing const toggleEditOriginalContent = () => { setEditingOriginalContent(!editingOriginalContent); // If we're starting to edit, make sure the content is ready and focused if (!editingOriginalContent) { setTimeout(() => { if (originalContentRef.current) { originalContentRef.current.focus(); // Place cursor at the beginning const selection = window.getSelection(); const range = document.createRange(); if (originalContentRef.current.firstChild) { range.setStart(originalContentRef.current.firstChild, 0); } else { range.setStart(originalContentRef.current, 0); } range.collapse(true); selection?.removeAllRanges(); selection?.addRange(range); } }, 100); } }; // Handling click on original content even when not in edit mode const handleOriginalContentClick = () => { if (!editingOriginalContent) { toggleEditOriginalContent(); } }; // Modified send handler to combine user message with forwarded content const handleSend = async () => { if (!to) { alert('Please specify at least one recipient'); return; } try { setSending(true); // Prepare the complete email body let finalBody = body; if (type === 'forward' && originalContent) { // For forwarded emails, construct the email in a standard format const fromString = initialEmail?.from?.map(addr => addr.name ? `${addr.name} <${addr.address}>` : addr.address ).join(', ') || ''; const toString = initialEmail?.to?.map(addr => addr.name ? `${addr.name} <${addr.address}>` : addr.address ).join(', ') || ''; const dateString = initialEmail?.date ? typeof initialEmail.date === 'string' ? new Date(initialEmail.date).toLocaleString() : initialEmail.date.toLocaleString() : ''; // Combine user message with forwarded header and content finalBody = ` ${userMessage}

---------- Forwarded message ---------
From: ${fromString}
Date: ${dateString}
Subject: ${initialEmail?.subject || ''}
To: ${toString}
${originalContent}
`; } await onSend({ to, cc: cc || undefined, bcc: bcc || undefined, subject, body: finalBody, attachments }); onClose(); } catch (error) { console.error('Error sending email:', error); alert('Failed to send email. Please try again.'); } finally { setSending(false); } }; // Handle editor input const handleEditorInput = (e: React.FormEvent) => { if (editorRef.current) { const content = editorRef.current.innerHTML; setUserMessage(content); setBody(content); } }; // Toggle text direction const toggleTextDirection = () => { setIsRTL(!isRTL); if (editorRef.current) { editorRef.current.dir = !isRTL ? 'rtl' : 'ltr'; } }; return ( {type === 'new' ? 'New Message' : type === 'forward' ? 'Forward Email' : 'Reply to Email'} {/* Recipients, Subject fields remain the same */}
To: setTo(e.target.value)} className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0" placeholder="recipient@example.com" />
{showCc && (
Cc: setCc(e.target.value)} className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0" placeholder="cc@example.com" />
)} {showBcc && (
Bcc: setBcc(e.target.value)} className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0" placeholder="bcc@example.com" />
)} {/* CC/BCC controls */}
Subject: setSubject(e.target.value)} className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0" placeholder="Subject" />
{/* Updated Email Body Editor */}
{/* Email editor with clear separation between user message and original content */}
{/* User's editable message area */}
{/* Original content display with visual separation */} {type !== 'new' && originalContent && (
{type === 'forward' ? 'Forwarded content (original)' : 'Original message'}
{editingOriginalContent ? (
) : (
)}
)}
{/* Attachments section remains the same */} {attachments.length > 0 && (
Attachments:
{attachments.map((attachment, index) => (
{attachment.name}
))}
)}
); } // Adapter component for legacy props function LegacyAdapter({ showCompose, setShowCompose, composeTo, setComposeTo, composeCc, setComposeCc, composeBcc, setComposeBcc, composeSubject, setComposeSubject, composeBody, setComposeBody, showCc, setShowCc, showBcc, setShowBcc, attachments, setAttachments, handleSend, originalEmail, onSend, onCancel, replyTo, forwardFrom }: LegacyComposeEmailProps) { // Determine the type from the original email or subject const determineType = (): 'new' | 'reply' | 'reply-all' | 'forward' => { if (originalEmail) { return originalEmail.type; } if (composeSubject.startsWith('Re:')) { return 'reply'; } if (composeSubject.startsWith('Fwd:')) { return 'forward'; } return 'new'; }; // Convert legacy attachments format to new format const convertAttachments = () => { return (attachments || []).map((att: any) => ({ name: att.name || 'attachment', content: typeof att.content === 'string' ? att.content : '', type: att.type || 'application/octet-stream' })); }; // Create an EmailMessage compatible object from composeBody // This is crucial for displaying original content in replies/forwards const createEmailMessageFromContent = (): EmailMessage | null => { const type = determineType(); // Only create an email object if we're replying or forwarding if (type === 'new' || !composeBody) { return null; } // For forwarded content, we need to preserve all the original formatting // The composeBody already contains the formatted message with headers return { id: 'temp-id', messageId: '', subject: composeSubject, from: [{ name: '', address: '' }], to: [{ name: '', address: '' }], date: new Date(), // Always use the full composeBody to ensure nested forwards are preserved content: composeBody, html: composeBody, hasAttachments: false }; }; // If not showing compose, return null if (!showCompose) { return null; } // Create email message from content if available const emailForCompose = createEmailMessageFromContent(); const type = determineType(); return (
{ onCancel?.(); setShowCompose(false); }} onSend={async (emailData: { to: string; cc?: string; bcc?: string; subject: string; body: string; attachments?: Array<{ name: string; content: string; type: string; }>; }) => { // Update legacy state before sending setComposeTo(emailData.to); if (emailData.cc) setComposeCc(emailData.cc); if (emailData.bcc) setComposeBcc(emailData.bcc); setComposeSubject(emailData.subject); setComposeBody(emailData.body); // Call the legacy onSend function await onSend(emailData); }} />
); }