'use client'; import { useState, useRef, useEffect } from 'react'; import { X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import DOMPurify from 'isomorphic-dompurify'; import { Label } from '@/components/ui/label'; // Import sub-components import ComposeEmailHeader from './ComposeEmailHeader'; import ComposeEmailForm from './ComposeEmailForm'; import ComposeEmailFooter from './ComposeEmailFooter'; import RichEmailEditor from './RichEmailEditor'; import QuotedEmailContent from './QuotedEmailContent'; // Import ONLY from the centralized formatter import { formatReplyEmail, formatForwardedEmail, formatEmailAddresses, type EmailMessage, type EmailAddress } from '@/lib/utils/email-formatter'; /** * CENTRAL EMAIL COMPOSER COMPONENT * * This is the unified, centralized email composer component used throughout the application. * It handles new emails, replies, and forwards with proper text direction. * * All code that needs to compose emails should import this component from: * @/components/email/ComposeEmail * * It uses the centralized email formatter from @/lib/utils/email-formatter.ts * for consistent handling of email content and text direction. */ // Define interface for the modern props 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; } // Add a helper to fix table widths in HTML function fixTableWidths(html: string): string { if (!html) return html; return html.replace(/]*width)/g, '(''); 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>([]); // Initialize the form when replying to or forwarding an email useEffect(() => { if (initialEmail && type !== 'new') { try { // Set recipients based on type if (type === 'reply' || type === 'reply-all') { // Reply goes to the original sender setTo(formatEmailAddresses(initialEmail.from || [])); // For reply-all, include all original recipients in CC if (type === 'reply-all') { const allRecipients = [ ...(initialEmail.to || []), ...(initialEmail.cc || []) ]; setCc(formatEmailAddresses(allRecipients)); } // Set subject with Re: prefix const subjectBase = initialEmail.subject || '(No subject)'; const subject = subjectBase.match(/^Re:/i) ? subjectBase : `Re: ${subjectBase}`; setSubject(subject); // Format the reply content const { content } = formatReplyEmail(initialEmail, type); setEmailContent(content); // Show CC field if there are CC recipients if (initialEmail.cc && initialEmail.cc.length > 0) { setShowCc(true); } } else if (type === 'forward') { // Set subject with Fwd: prefix const subjectBase = initialEmail.subject || '(No subject)'; const subject = subjectBase.match(/^(Fwd|FW|Forward):/i) ? subjectBase : `Fwd: ${subjectBase}`; setSubject(subject); // Format the forward content const { content } = formatForwardedEmail(initialEmail); setEmailContent(content); // If the original email has attachments, we should include them if (initialEmail.attachments && initialEmail.attachments.length > 0) { const formattedAttachments = initialEmail.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); } } }, [initialEmail, 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, 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); } }; // Additional effect to ensure we scroll to the top and focus the editor useEffect(() => { // Focus the editor and ensure it's scrolled to the top const editorContainer = document.querySelector('.ql-editor') as HTMLElement; if (editorContainer) { // Set timeout to ensure DOM is fully rendered setTimeout(() => { // Focus the editor editorContainer.focus(); // Make sure all scroll containers are at the top editorContainer.scrollTop = 0; // Find all possible scrollable parent containers const scrollContainers = [ document.querySelector('.ql-container') as HTMLElement, document.querySelector('.rich-email-editor-container') as HTMLElement, document.querySelector('.h-full.flex.flex-col.p-6') as HTMLElement ]; // Scroll all containers to top scrollContainers.forEach(container => { if (container) { container.scrollTop = 0; } }); }, 100); } }, []); return (
{/* To Field */}
setTo(e.target.value)} placeholder="recipient@example.com" className="w-full mt-1 bg-white border-gray-300 text-gray-900" />
{/* CC/BCC Toggle Buttons */}
{/* CC Field */} {showCc && (
setCc(e.target.value)} placeholder="cc@example.com" className="w-full mt-1 bg-white border-gray-300 text-gray-900" />
)} {/* BCC Field */} {showBcc && (
setBcc(e.target.value)} placeholder="bcc@example.com" className="w-full mt-1 bg-white border-gray-300 text-gray-900" />
)} {/* Subject Field */}
setSubject(e.target.value)} placeholder="Enter subject" className="w-full mt-1 bg-white border-gray-300 text-gray-900" />
{/* Message Body */}
{/* Attachments */} {attachments.length > 0 && (

Attachments

{attachments.map((file, index) => (
{file.name}
))}
)}
{/* Modal Footer - now inside the main modal container and visually attached */}
{/* File Input for Attachments */} { if (e.target.files && e.target.files.length > 0) { handleAttachmentAdd(e.target.files); } }} /> {sending && Preparing attachment...}
); }