'use client'; import { useState, useRef, useEffect } from 'react'; import { X, Paperclip, SendHorizontal, Loader2, Plus, ChevronDown } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import DOMPurify from 'isomorphic-dompurify'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import RichEmailEditor from '@/components/email/RichEmailEditor'; import { detectTextDirection } from '@/lib/utils/text-direction'; // Import from the centralized utils import { formatReplyEmail, formatForwardedEmail } from '@/lib/utils/email-utils'; import { EmailMessage } from '@/types/email'; /** * Email composer component * Handles new emails, replies, and forwards with a clean UI */ 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; fromAccount?: string; attachments?: Array<{ name: string; content: string; type: string; }>; }) => Promise; accounts?: Array<{ id: string; email: string; display_name?: string; }>; } export default function ComposeEmail(props: ComposeEmailProps) { const { initialEmail, type = 'new', onClose, onSend, accounts = [] } = props; // State for email form const [selectedAccount, setSelectedAccount] = useState(accounts[0]); const [to, setTo] = useState(''); const [cc, setCc] = useState(''); const [bcc, setBcc] = useState(''); const [subject, setSubject] = useState(''); const [emailContent, setEmailContent] = useState(''); const [showCc, setShowCc] = useState(false); const [showBcc, setShowBcc] = useState(false); const [sending, setSending] = useState(false); const [attachments, setAttachments] = useState>([]); // Reference to editor const editorRef = useRef(null); // Helper function to get formatted info from email function getFormattedInfoForEmail(email: any) { // Format the subject const subject = email.subject || ''; // Format the date const dateStr = email.date ? new Date(email.date).toLocaleString() : 'Unknown Date'; // Format sender const fromStr = Array.isArray(email.from) ? email.from.map((addr: any) => { if (typeof addr === 'string') return addr; return addr.name ? `${addr.name} <${addr.address}>` : addr.address; }).join(', ') : typeof email.from === 'string' ? email.from : email.from?.address ? email.from.name ? `${email.from.name} <${email.from.address}>` : email.from.address : 'Unknown Sender'; // Format recipients const toStr = Array.isArray(email.to) ? email.to.map((addr: any) => { if (typeof addr === 'string') return addr; return addr.name ? `${addr.name} <${addr.address}>` : addr.address; }).join(', ') : typeof email.to === 'string' ? email.to : ''; // Format CC const ccStr = Array.isArray(email.cc) ? email.cc.map((addr: any) => { if (typeof addr === 'string') return addr; return addr.name ? `${addr.name} <${addr.address}>` : addr.address; }).join(', ') : typeof email.cc === 'string' ? email.cc : ''; return { fromStr, toStr, ccStr, dateStr, subject }; } // Initialize email form based on initial email and type useEffect(() => { if (initialEmail) { try { console.log('Initializing compose with email:', { id: initialEmail.id, subject: initialEmail.subject, hasContent: !!initialEmail.content, contentType: initialEmail.content ? typeof initialEmail.content : 'none' }); // Set default account from original email - use type assertion since accountId might be custom property const emailAny = initialEmail as any; if (emailAny.accountId && accounts?.length) { const account = accounts.find(a => a.id === emailAny.accountId); if (account) { setSelectedAccount(account); } } // Get recipients based on type if (type === 'reply' || type === 'reply-all') { // Get formatted data for reply const formatted = formatReplyEmail(initialEmail, type); // Set reply addresses setTo(formatted.to); if (formatted.cc) { setShowCc(true); setCc(formatted.cc); } // Set subject setSubject(formatted.subject); // Set content with original email - ensure we have content const content = formatted.content.html || formatted.content.text || ''; if (!content) { console.warn('Reply content is empty, falling back to a basic template'); // Provide a basic template if the content is empty const { fromStr, dateStr } = getFormattedInfoForEmail(initialEmail); const fallbackContent = `
On ${dateStr}, ${fromStr} wrote:
[Original message content could not be loaded]
`; setEmailContent(fallbackContent); } else { console.log('Setting reply content:', { length: content.length, isHtml: formatted.content.isHtml, startsWithHtml: content.trim().startsWith('<'), contentType: typeof content }); setEmailContent(content); } // Handle any attachments from reply (e.g., inline images extracted as attachments) if (formatted.attachments && formatted.attachments.length > 0) { const formattedAttachments = formatted.attachments.map(att => ({ name: att.filename || 'attachment', type: att.contentType || 'application/octet-stream', content: att.content || '' })); setAttachments(formattedAttachments); } } else if (type === 'forward') { // Get formatted data for forward const formatted = formatForwardedEmail(initialEmail); // Set subject setSubject(formatted.subject); // Set content with original email - ensure we have content const content = formatted.content.html || formatted.content.text || ''; if (!content) { console.warn('Forward content is empty, falling back to a basic template'); // Provide a basic template if the content is empty const { fromStr, dateStr, subject: origSubject, toStr, ccStr } = getFormattedInfoForEmail(initialEmail); console.log('Creating forward fallback with:', { fromStr, dateStr, origSubject }); const fallbackContent = `
---------------------------- Forwarded Message ----------------------------
${ccStr ? ` ` : ''}
From: ${fromStr}
Date: ${dateStr}
Subject: ${origSubject || ''}
To: ${toStr}
Cc: ${ccStr}
----------------------------------------------------------------------
[Original message content could not be loaded]
`; setEmailContent(fallbackContent); } else { console.log('Setting forward content:', { length: content.length, isHtml: formatted.content.isHtml }); setEmailContent(content); } // Handle attachments for forward (original attachments + extracted inline images) if (formatted.attachments && formatted.attachments.length > 0) { console.log(`Processing ${formatted.attachments.length} attachments for forwarded email`); const formattedAttachments = formatted.attachments.map(att => ({ name: att.filename || 'attachment', type: att.contentType || 'application/octet-stream', content: att.content || '' })); setAttachments(formattedAttachments); } } } catch (error) { console.error('Error initializing compose form:', error); // Provide a fallback in case of error setEmailContent('

Error loading email content

'); } } }, [initialEmail, type, accounts]); // Place cursor at beginning and ensure content is scrolled to top useEffect(() => { if (editorRef.current && type !== 'new') { // Small delay to ensure DOM is ready setTimeout(() => { if (editorRef.current) { // Focus the editor editorRef.current.focus(); // Also make sure editor container is scrolled to top editorRef.current.scrollTop = 0; // Find parent scrollable containers and scroll them to top let parent = editorRef.current.parentElement; while (parent) { if (parent.classList.contains('overflow-y-auto')) { parent.scrollTop = 0; } parent = parent.parentElement; } } }, 100); } }, [emailContent, type]); // Handle file attachments const handleAttachmentAdd = async (files: FileList) => { const newAttachments = Array.from(files).map(file => ({ name: file.name, type: file.type, content: URL.createObjectURL(file) })); setAttachments(prev => [...prev, ...newAttachments]); }; const handleAttachmentRemove = (index: number) => { setAttachments(prev => prev.filter((_, i) => i !== index)); }; // Handle sending email const handleSend = async () => { if (!to) { alert('Please specify at least one recipient'); return; } setSending(true); try { await onSend({ to, cc: cc || undefined, bcc: bcc || undefined, subject, body: emailContent, fromAccount: selectedAccount?.id, attachments }); // Reset form and close onClose(); } catch (error) { console.error('Error sending email:', error); alert('Failed to send email. Please try again.'); } finally { setSending(false); } }; // Get compose title based on type const getComposeTitle = () => { switch(type) { case 'reply': return 'Reply'; case 'reply-all': return 'Reply All'; case 'forward': return 'Forward'; default: return 'New Message'; } }; return (
{/* Header */}

{getComposeTitle()}

{/* Email Form */}
{/* From */}
From: Select account {accounts.length > 0 ? ( accounts.map(account => ( setSelectedAccount(account)} className="cursor-pointer hover:bg-blue-50 focus:bg-blue-50" > {account.display_name ? `${account.display_name} <${account.email}>` : account.email} )) ) : ( No accounts available )}
{/* Recipients */}
To: setTo(e.target.value)} placeholder="recipient@example.com" className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 h-8 bg-white text-gray-800" />
{showCc && (
Cc: setCc(e.target.value)} placeholder="cc@example.com" className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 h-8 bg-white text-gray-800" />
)} {showBcc && (
Bcc: setBcc(e.target.value)} placeholder="bcc@example.com" className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 h-8 bg-white text-gray-800" />
)} {/* CC/BCC Toggle Links */}
{!showCc && ( )} {!showBcc && ( )}
{/* Subject */}
Subject: setSubject(e.target.value)} placeholder="Subject" className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 h-8 bg-white text-gray-800" />
{/* Message Body */} { setEmailContent(html); }} placeholder="Write your message here..." minHeight="320px" /> {/* Attachments */} {attachments.length > 0 && (
Attachments:
{attachments.map((file, index) => (
{file.name}
))}
)}
{/* Footer */}
{/* File Input for Attachments - simpler version */} { if (e.target.files && e.target.files.length > 0) { handleAttachmentAdd(e.target.files); } }} />
{/* Styles for email content */}
); }