'use client'; import { useState, useRef, useEffect } from 'react'; import { formatEmailForReplyOrForward, EmailMessage } from '@/lib/services/email-service'; import { X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2 } 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 { decodeEmail } from '@/lib/mail-parser-wrapper'; import DOMPurify from 'isomorphic-dompurify'; 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; } export default function ComposeEmail({ initialEmail, type = 'new', onClose, onSend }: ComposeEmailProps) { // Email form state const [to, setTo] = useState(''); const [cc, setCc] = useState(''); const [bcc, setBcc] = useState(''); const [subject, setSubject] = useState(''); const [body, setBody] = useState(''); // UI state const [showCc, setShowCc] = useState(false); const [showBcc, setShowBcc] = useState(false); const [sending, setSending] = useState(false); const [attachments, setAttachments] = useState>([]); const editorRef = useRef(null); const attachmentInputRef = useRef(null); // Initialize the form when replying to or forwarding an email useEffect(() => { if (initialEmail && type !== 'new') { // If it's a forward, use the same approach as Panel 3 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); setBody(formattedEmail.body); } // 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]); // Helper functions for formatting the forwarded message - moved outside try block // 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(', '); }; // Handle editor input const handleEditorInput = (e: React.FormEvent) => { // Only store the content of the editable area, not the entire HTML with CSS // This prevents breaking complex CSS when editing if (editorRef.current) { // If we're in forward mode and the editor contains our wrapper structure const editableContent = editorRef.current.querySelector('.editable-content'); if (type === 'forward' && editableContent) { // Only update the editable portion, preserving the CSS and header const headerSection = editorRef.current.querySelector('.forwarded-header'); const styleSection = editorRef.current.querySelector('.email-styles'); // Combine the preserved sections with the updated editable content const updatedContent = (styleSection?.outerHTML || '') + (headerSection?.outerHTML || '') + editableContent.innerHTML; setBody(updatedContent); } else { // For new emails or replies, we can use the entire content setBody(e.currentTarget.innerHTML); } } }; // Modified initializeForwardedEmail to separate CSS from content const initializeForwardedEmail = async () => { if (!initialEmail) { console.error('No email available for forwarding'); setBody('
No email available for forwarding
'); return; } // Helper functions remain the same try { setSending(true); // Use sending state to show loading // Format subject with Fwd: prefix if needed const cleanSubject = initialEmail.subject.replace(/^(Fwd|FW|Forward):\s*/i, '').trim(); const formattedSubject = initialEmail.subject.match(/^(Fwd|FW|Forward):/i) ? initialEmail.subject : `Fwd: ${cleanSubject}`; setSubject(formattedSubject); // Create a forwarded message header with proper formatting const headerContent = `

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

From: ${formatSender(initialEmail.from)}

Date: ${formatDate(initialEmail.date)}

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

To: ${formatRecipients(initialEmail.to)}

`; // Process content let contentBody = ''; let styleContent = ''; // Check if email content exists if (!initialEmail.content || initialEmail.content.trim() === '') { contentBody = '
No content available
'; } else { try { // Parse content to extract styles and make content editable const content = initialEmail.content; // Extract style tags to preserve them const styleRegex = /]*>([\s\S]*?)<\/style>/gi; const styles: string[] = []; let styleMatch; // Find all style tags and collect them while ((styleMatch = styleRegex.exec(content)) !== null) { styles.push(styleMatch[0]); } // Combine all styles into one non-editable section if (styles.length > 0) { styleContent = ``; } // Use DOMPurify to sanitize the rest of the HTML content const sanitizedContent = DOMPurify.sanitize(content, { ADD_TAGS: ['style', 'meta', 'link', 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'hr', 'font', 'div', 'span', 'a', 'img', 'b', 'strong', 'i', 'em', 'u', 'br', 'p', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'code', 'center', 'section', 'header', 'footer', 'article', 'nav', 'keyframes'], ADD_ATTR: ['*', 'colspan', 'rowspan', 'cellpadding', 'cellspacing', 'border', 'bgcolor', 'width', 'height', 'align', 'valign', 'class', 'id', 'style', 'color', 'face', 'size', 'background', 'src', 'href', 'target', 'rel', 'alt', 'title', 'name', 'animation', 'animation-name', 'animation-duration', 'animation-fill-mode'], ALLOW_UNKNOWN_PROTOCOLS: true, WHOLE_DOCUMENT: true, KEEP_CONTENT: true, FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'select', 'option', 'textarea', 'canvas', 'video', 'audio'], FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onchange', 'onsubmit'], USE_PROFILES: { html: true, svg: false, svgFilters: false, mathMl: false }, FORCE_BODY: true }); // Remove style tags from sanitized content (we'll add them back separately) let contentWithoutStyles = sanitizedContent.replace(/]*>[\s\S]*?<\/style>/gi, ''); // Wrap the remaining content in an editable div contentBody = `
${contentWithoutStyles}
`; } catch (e) { console.error('Error sanitizing HTML content:', e); contentBody = '
Error processing original content
'; } } // Set the complete forwarded email with styles preserved separately setBody(styleContent + headerContent + contentBody); } catch (error) { console.error('Error initializing forwarded email:', error); // Still provide the headers even if there's an error with the content const errorHeaderContent = `

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

From: ${initialEmail.from ? formatSender(initialEmail.from) : 'Unknown'}

Date: ${initialEmail.date ? formatDate(initialEmail.date) : ''}

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

To: ${initialEmail.to ? formatRecipients(initialEmail.to) : ''}

Error loading forwarded content
`; setBody(errorHeaderContent); } finally { setSending(false); } }; // 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)); }; // Send the email const handleSend = async () => { if (!to) { alert('Please specify at least one recipient'); return; } try { setSending(true); // Get the email content let emailBody = ''; if (editorRef.current) { // For forwarded emails, make sure to include both the style and content sections if (type === 'forward') { // Gather all parts: styles, header, and editable content const styleSection = editorRef.current.querySelector('.email-styles'); const headerSection = editorRef.current.querySelector('.forwarded-header'); const editableContent = editorRef.current.querySelector('.editable-content'); // Combine all sections for the final email body emailBody = (styleSection?.outerHTML || '') + (headerSection?.outerHTML || '') + (editableContent?.innerHTML || editorRef.current.innerHTML); // Remove contenteditable attributes as they're not needed in the sent email emailBody = emailBody.replace(/contenteditable="[^"]*"/g, ''); } else { // For new emails or replies, use the entire content emailBody = editorRef.current.innerHTML; } } else { // Fallback to using body state emailBody = body; } await onSend({ to, cc: cc || undefined, bcc: bcc || undefined, subject, body: emailBody, attachments }); onClose(); } catch (error) { console.error('Error sending email:', error); alert('Failed to send email. Please try again.'); } finally { setSending(false); } }; return (
{type === 'new' ? 'New Message' : type === 'reply' ? 'Reply' : type === 'reply-all' ? 'Reply All' : 'Forward'}
{/* Email header fields */}
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" />
{/* Email body editor */}
{/* Attachments list */} {attachments.length > 0 && (
Attachments:
{attachments.map((attachment, index) => (
{attachment.name}
))}
)}
); }