diff --git a/components/email/ComposeEmail.tsx b/components/email/ComposeEmail.tsx index ebbe8e9a..dbc4bdd0 100644 --- a/components/email/ComposeEmail.tsx +++ b/components/email/ComposeEmail.tsx @@ -1,77 +1,13 @@ 'use client'; import { useState, useRef, useEffect } from 'react'; -import { - X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2 -} from 'lucide-react'; +import { formatEmailForReplyOrForward, EmailMessage, EmailAddress } 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 { 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'; @@ -90,63 +26,18 @@ interface ComposeEmailProps { }) => 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; - +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 [emailContent, setEmailContent] = useState(''); + const [body, setBody] = useState(''); const [showCc, setShowCc] = useState(false); const [showBcc, setShowBcc] = useState(false); const [sending, setSending] = useState(false); @@ -156,170 +47,320 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { type: string; }>>([]); + // Refs + const editorRef = useRef(null); + const attachmentInputRef = useRef(null); + // 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); - - // Format the reply content with the quoted message included directly - const content = initialEmail.content || initialEmail.html || initialEmail.text || ''; - const sender = initialEmail.from && initialEmail.from.length > 0 - ? initialEmail.from[0].name || initialEmail.from[0].address - : 'Unknown sender'; - const date = initialEmail.date ? - (typeof initialEmail.date === 'string' ? new Date(initialEmail.date) : initialEmail.date) : - new Date(); - - // Format date for display - const formattedDate = date.toLocaleString('en-US', { - weekday: 'short', - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - - // Create reply content with quote - const replyContent = ` -

-

-

-

-
On ${formattedDate}, ${sender} wrote:
-
-
- ${content} -
-
- `; - - setEmailContent(replyContent); - - // 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 with the original email included directly - const content = initialEmail.content || initialEmail.html || initialEmail.text || ''; - const fromString = formatEmailAddresses(initialEmail.from || []); - const toString = formatEmailAddresses(initialEmail.to || []); - const date = initialEmail.date ? - (typeof initialEmail.date === 'string' ? new Date(initialEmail.date) : initialEmail.date) : - new Date(); - - // Format date for display - const formattedDate = date.toLocaleString('en-US', { - weekday: 'short', - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - - // Create forwarded content - const forwardContent = ` -

-

-

-

-
-
-
-
---------- Forwarded message ---------
-
From: ${fromString}
-
Date: ${formattedDate}
-
Subject: ${initialEmail.subject || ''}
-
To: ${toString}
-
- -
-
- `; - - setEmailContent(forwardContent); - - // 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); - } + 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); } - } catch (error) { - console.error('Error initializing compose form:', error); + + 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]); + + // 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(); + } + }; - // Handle file attachments - const handleAttachmentAdd = async (files: FileList) => { + // 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; + } + + // Debug the email object structure + console.log('Forwarding email object:', { + id: initialEmail.id, + subject: initialEmail.subject, + fromLength: initialEmail.from?.length, + from: initialEmail.from, + to: initialEmail.to, + date: initialEmail.date, + hasContent: Boolean(initialEmail.content), + contentLength: initialEmail.content?.length, + hasHtml: Boolean(initialEmail.html), + htmlLength: initialEmail.html?.length + }); + + 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); + + // Format the forwarded message with a well-structured header + const fromString = Array.isArray(initialEmail.from) && initialEmail.from.length > 0 + ? initialEmail.from.map(addr => addr.name + ? `${addr.name} <${addr.address}>` + : addr.address).join(', ') + : 'Unknown'; + + const toString = Array.isArray(initialEmail.to) && initialEmail.to.length > 0 + ? 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() + : new Date().toLocaleString(); + + // Create a clean wrapper that won't interfere with the original email's styling + // Use inline styles for the header to avoid CSS conflicts + const headerHtml = ` +
+
+
---------- Forwarded message ---------
+
From: ${fromString}
+
Date: ${dateString}
+
Subject: ${subjectBase}
+
To: ${toString}
+
+
+ `; + + // Process the original content + let originalContent = ''; + + // First try to use the API to parse and sanitize the email content + try { + // Use server-side parsing via fetch API to properly handle complex emails + const response = await fetch('/api/parse-email', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email: initialEmail.content || initialEmail.html || initialEmail.text || '' + }), + }); + + if (response.ok) { + const parsedEmail = await response.json(); + + if (parsedEmail.html && parsedEmail.html.trim()) { + console.log('Using parsed HTML content for forward'); + + // Create an iframe-like containment for the email content + // This prevents CSS from the original email leaking into our compose view + originalContent = ` + + `; + } else if (parsedEmail.text && parsedEmail.text.trim()) { + console.log('Using parsed text content for forward'); + originalContent = `
${parsedEmail.text}
`; + } else { + console.log('No content available from parser'); + originalContent = '
No content available
'; + } + } else { + throw new Error('Failed to parse email content'); + } + } catch (parseError) { + console.error('Error parsing email content:', parseError); + + // Fall back to direct content handling if API parsing fails + if (initialEmail.html && initialEmail.html.trim()) { + console.log('Falling back to HTML content for forward'); + // Use DOMPurify to sanitize HTML and remove dangerous elements + originalContent = DOMPurify.sanitize(initialEmail.html, { + ADD_TAGS: ['style', 'div', 'span', 'p', 'br', 'hr', 'h1', 'h2', 'h3', 'img', 'table', 'tr', 'td', 'th'], + ADD_ATTR: ['style', 'class', 'id', 'src', 'alt', 'href', 'target'], + FORBID_TAGS: ['script', 'iframe', 'object', 'embed'], + FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover'] + }); + } else if (initialEmail.content && initialEmail.content.trim()) { + console.log('Falling back to content field for forward'); + originalContent = DOMPurify.sanitize(initialEmail.content); + } else if (initialEmail.text && initialEmail.text.trim()) { + console.log('Falling back to text content for forward'); + originalContent = `
${initialEmail.text}
`; + } else { + console.log('No content available for forward'); + originalContent = '
No content available
'; + } + } + + // Preserve all original structure by wrapping, not modifying the original content + // Important: We add a style scope to prevent CSS leakage + const forwardedContent = ` + ${headerHtml} + + + + `; + + console.log('Setting body with forwarded content'); + setBody(forwardedContent); + + } catch (error) { + console.error('Error initializing forwarded email:', error); + + // Even in error case, provide a usable template with empty values + setBody(` +
+
+
---------- Forwarded message ---------
+
From: ${initialEmail.from ? formatSender(initialEmail.from) : 'Unknown'}
+
Date: ${new Date().toLocaleString()}
+
Subject: ${initialEmail.subject || '(No subject)'}
+
To: ${initialEmail.to ? formatRecipients(initialEmail.to) : ''}
+
+
+
+ Error loading original message content. The original message may still be viewable in your inbox. +
+ `); + } + }; + + // 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 => ({ - name: file.name, - type: file.type, - content: URL.createObjectURL(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); + } - setAttachments(prev => [...prev, ...newAttachments]); + // Reset file input + if (e.target) { + e.target.value = ''; + } }; - - const handleAttachmentRemove = (index: number) => { - setAttachments(prev => prev.filter((_, i) => i !== index)); + + // Remove attachment + const removeAttachment = (index: number) => { + setAttachments(current => current.filter((_, i) => i !== index)); }; - - // Handle sending email + + // Send the email const handleSend = async () => { if (!to) { alert('Please specify at least one recipient'); return; } - - setSending(true); - + try { + setSending(true); + await onSend({ to, cc: cc || undefined, bcc: bcc || undefined, subject, - body: emailContent, + body: editorRef.current?.innerHTML || body, attachments }); - // Reset form and close onClose(); } catch (error) { console.error('Error sending email:', error); @@ -328,591 +369,183 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { 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); - } - }, []); - + + // Handle editor input + const handleEditorInput = (e: React.FormEvent) => { + // Store the HTML content for use in the send function + setBody(e.currentTarget.innerHTML); + }; + 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 */} -
- -
- -
-
- - {/* 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); - } - }} + + + + {/* 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" /> - - {sending && Preparing attachment...}
-
-
+ )} + + {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 */} +
+ - -
-
-
-
- ); -} - -// 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'; - }; - - // Format legacy content on mount if necessary - useEffect(() => { - // Only format if we have original email and no content was set yet - if ((originalEmail || replyTo || forwardFrom) && - (!composeBody || composeBody === '

' || composeBody === '
')) { - - const type = determineType(); - - if (type === 'reply' || type === 'reply-all') { - // For reply, format with sender info and original content - const content = originalEmail?.content || ''; - const sender = replyTo?.name || replyTo?.email || 'Unknown sender'; - const date = new Date().toLocaleString('en-US', { - weekday: 'short', - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - - const replyContent = ` -

-

-

-

-
On ${date}, ${sender} wrote:
-
-
- ${content} -
-
- `; - - setComposeBody(replyContent); - } - else if (type === 'forward') { - // For forward, format with original message details - const content = originalEmail?.content || ''; - const fromString = forwardFrom?.name || forwardFrom?.email || 'Unknown'; - const toString = 'Recipients'; - const date = new Date().toLocaleString('en-US', { - weekday: 'short', - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - - const forwardContent = ` -

-

-

-

-
-
-
-
---------- Forwarded message ---------
-
From: ${fromString}
-
Date: ${date}
-
Subject: ${composeSubject || ''}
-
To: ${toString}
-
- -
-
- `; - - setComposeBody(forwardContent); - } - } - }, [originalEmail, replyTo, forwardFrom, composeBody, determineType, composeSubject]); - - // 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 */} -
- -
- -
-
+ - {/* 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...} -
-
- - + +
+ +
+ 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} + +
+ ))} +
+
+ )} + + + +
+ + +
+ + +
+ ); } \ No newline at end of file