'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 { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'; 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 legacy props 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; } // 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; } // Union type for handling both types of props type ComposeEmailAllProps = ComposeEmailProps | LegacyComposeEmailProps; // Type guard to check if props are legacy function isLegacyProps( props: ComposeEmailAllProps ): props is LegacyComposeEmailProps { return 'showCompose' in props; } // Helper function to adapt EmailMessage to QuotedEmailContent props format function EmailMessageToQuotedContentAdapter({ email, type }: { email: EmailMessage, type: 'reply' | 'reply-all' | 'forward' }) { // Get the email content const content = email.content || email.html || email.text || ''; // Get the sender const sender = email.from && email.from.length > 0 ? { name: email.from[0].name, email: email.from[0].address } : { email: 'unknown@example.com' }; // Map the type to what QuotedEmailContent expects const mappedType = type === 'reply-all' ? 'reply' : type; return ( ); } 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 [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 || []) ]; // Filter out the current user if they were a recipient // This would need some user context to properly implement 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); // Set an empty content with proper spacing before the quoted content setEmailContent('


'); // 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); // Set an empty content with proper spacing before the quoted content setEmailContent('


'); // 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); } }; return (
{/* Modal Header */}

{type === 'reply' ? 'Reply' : type === 'forward' ? 'Forward' : type === 'reply-all' ? 'Reply All' : 'New Message'}

{/* Modal Body */}
{/* 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 */}
{/* Add QuotedEmailContent for replies and forwards */} {initialEmail && (type === 'reply' || type === 'reply-all' || type === 'forward') && (
)} {/* Attachments */} {attachments.length > 0 && (

Attachments

{attachments.map((file, index) => (
{file.name}
))}
)}
{/* Modal Footer */}
{/* File Input for Attachments */} { if (e.target.files && e.target.files.length > 0) { handleAttachmentAdd(e.target.files); } }} /> {sending && Preparing attachment...}
); } // Legacy adapter to maintain backward compatibility 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) { const [sending, setSending] = useState(false); // Determine the type based on legacy props const determineType = (): 'new' | 'reply' | 'reply-all' | 'forward' => { if (originalEmail?.type === 'forward') return 'forward'; if (originalEmail?.type === 'reply-all') return 'reply-all'; if (originalEmail?.type === 'reply') return 'reply'; if (replyTo) return 'reply'; if (forwardFrom) return 'forward'; return 'new'; }; // Converts attachments to the expected format const convertAttachments = () => { return attachments.map(att => ({ name: att.name || att.filename || 'attachment', content: att.content || '', type: att.type || att.contentType || 'application/octet-stream' })); }; // Handle sending in the legacy format const handleLegacySend = async () => { setSending(true); try { if (onSend) { // New API await onSend({ to: composeTo, cc: composeCc, bcc: composeBcc, subject: composeSubject, body: composeBody, attachments: convertAttachments() }); } else if (handleSend) { // Old API await handleSend(); } // Close compose window setShowCompose(false); } catch (error) { console.error('Error sending email:', error); alert('Failed to send email. Please try again.'); } finally { setSending(false); } }; // Handle file selection for legacy interface const handleFileSelection = (files: FileList) => { const newAttachments = Array.from(files).map(file => ({ name: file.name, type: file.type, content: URL.createObjectURL(file), size: file.size })); setAttachments([...attachments, ...newAttachments]); }; if (!showCompose) return null; return (
{/* Modal Header */}

{determineType() === 'reply' ? 'Reply' : determineType() === 'forward' ? 'Forward' : determineType() === 'reply-all' ? 'Reply All' : 'New Message'}

{/* Modal Body */}
{/* To Field */}
setComposeTo(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 && (
setComposeCc(e.target.value)} placeholder="cc@example.com" className="w-full mt-1 bg-white border-gray-300 text-gray-900" />
)} {/* BCC Field */} {showBcc && (
setComposeBcc(e.target.value)} placeholder="bcc@example.com" className="w-full mt-1 bg-white border-gray-300 text-gray-900" />
)} {/* Subject Field */}
setComposeSubject(e.target.value)} placeholder="Enter subject" className="w-full mt-1 bg-white border-gray-300 text-gray-900" />
{/* Message Body */}
{/* Add QuotedEmailContent for replies and forwards */} {(originalEmail || replyTo || forwardFrom) && (determineType() === 'reply' || determineType() === 'reply-all' || determineType() === 'forward') && (
{/* For legacy adapter, we'd need to convert the different formats */} {/* Since we don't have the full implementation for this, we'll add a placeholder */}

Original message content would appear here

)} {/* Attachments */} {attachments.length > 0 && (

Attachments

{attachments.map((file, index) => (
{file.name || file.filename}
))}
)}
{/* Modal Footer */}
{/* File Input for Attachments */} { if (e.target.files && e.target.files.length > 0) { handleFileSelection(e.target.files); } }} /> {sending && Preparing attachment...}
); }