diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 933ed776..21938c49 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -244,43 +244,119 @@ function formatDate(date: Date | null): string { }).format(date); } -async function getReplyBody(email: Email, type: 'reply' | 'reply-all' | 'forward' = 'reply') { - if (!email.body) return ''; +function ReplyContent({ email, type }: { email: Email; type: 'reply' | 'reply-all' | 'forward' }) { + const [content, setContent] = useState(''); + const [error, setError] = useState(null); - try { - const decoded = await decodeEmail(email.body); - - // Format the reply/forward content with proper structure and direction - let formattedContent = ''; - - if (type === 'forward') { - formattedContent = ` -
-

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

-

From: ${decoded.from || ''}

-

Date: ${formatDate(decoded.date)}

-

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

-

To: ${decoded.to || ''}

-
- ${decoded.html || `
${decoded.text || ''}
`} -
- `; - } else { - formattedContent = ` -
-

On ${formatDate(decoded.date)}, ${decoded.from || ''} wrote:

-
- ${decoded.html || `
${decoded.text || ''}
`} -
-
- `; + useEffect(() => { + let mounted = true; + + async function loadReplyContent() { + try { + if (!email.body) { + if (mounted) setContent(''); + return; + } + + const decoded = await decodeEmail(email.body); + + if (mounted) { + let formattedContent = ''; + + if (type === 'forward') { + formattedContent = ` +
+

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

+

From: ${decoded.from || ''}

+

Date: ${formatDate(decoded.date)}

+

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

+

To: ${decoded.to || ''}

+
+ ${decoded.html || `
${decoded.text || ''}
`} +
+ `; + } else { + formattedContent = ` +
+

On ${formatDate(decoded.date)}, ${decoded.from || ''} wrote:

+
+ ${decoded.html || `
${decoded.text || ''}
`} +
+
+ `; + } + + setContent(formattedContent); + setError(null); + } + } catch (err) { + console.error('Error generating reply body:', err); + if (mounted) { + setError('Error generating reply content. Please try again.'); + setContent(''); + } + } } - return formattedContent; - } catch (error) { - console.error('Error generating reply body:', error); - return ''; + loadReplyContent(); + + return () => { + mounted = false; + }; + }, [email.body, type]); + + if (error) { + return
{error}
; } + + return
; +} + +// Update the getReplyBody function to use the new component +function getReplyBody(email: Email, type: 'reply' | 'reply-all' | 'forward' = 'reply') { + return ; +} + +function EmailPreview({ email }: { email: Email }) { + const [preview, setPreview] = useState(''); + const [error, setError] = useState(null); + + useEffect(() => { + let mounted = true; + + async function loadPreview() { + try { + const decoded = await decodeEmail(email.body); + if (mounted) { + setPreview(decoded.text || cleanHtml(decoded.html || '')); + setError(null); + } + } catch (err) { + console.error('Error generating email preview:', err); + if (mounted) { + setError('Error generating preview'); + setPreview(''); + } + } + } + + loadPreview(); + + return () => { + mounted = false; + }; + }, [email.body]); + + if (error) { + return {error}; + } + + return {preview}; +} + +// Update the generateEmailPreview function to use the new component +function generateEmailPreview(email: Email) { + return ; } export default function CourrierPage() { @@ -988,239 +1064,6 @@ export default function CourrierPage() { ); }; - const generateEmailPreview = async (email: Email): Promise => { - try { - const decoded = await decodeEmail(email.body); - return decoded.text || cleanHtml(decoded.html || ''); - } catch (error) { - console.error('Error generating email preview:', error); - return ''; - } - }; - - // Render the sidebar navigation - const renderSidebarNav = () => ( - - ); - - // Add attachment handling functions - const handleFileAttachment = async (e: React.ChangeEvent) => { - if (!e.target.files) return; - - const newAttachments: Attachment[] = []; - const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB in bytes - const oversizedFiles: string[] = []; - - for (const file of e.target.files) { - if (file.size > MAX_FILE_SIZE) { - oversizedFiles.push(file.name); - continue; - } - - try { - // Read file as base64 - const base64Content = await new Promise((resolve) => { - const reader = new FileReader(); - reader.onloadend = () => { - const base64 = reader.result as string; - resolve(base64.split(',')[1]); // Remove data URL prefix - }; - reader.readAsDataURL(file); - }); - - newAttachments.push({ - name: file.name, - type: file.type, - content: base64Content, - encoding: 'base64' - }); - } catch (error) { - console.error('Error processing attachment:', error); - } - } - - if (oversizedFiles.length > 0) { - alert(`The following files exceed the 10MB size limit and were not attached:\n${oversizedFiles.join('\n')}`); - } - - if (newAttachments.length > 0) { - setAttachments([...attachments, ...newAttachments]); - } - }; - - // Add handleSend function for email composition - const handleSend = async () => { - if (!composeTo) { - alert('Please specify at least one recipient'); - return; - } - - try { - const response = await fetch('/api/courrier/send', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - to: composeTo, - cc: composeCc, - bcc: composeBcc, - subject: composeSubject, - body: composeBody, - attachments: attachments, - }), - }); - - const data = await response.json(); - - if (!response.ok) { - if (data.error === 'Attachment size limit exceeded') { - alert(`Error: ${data.error}\nThe following files are too large:\n${data.details.oversizedFiles.join('\n')}`); - } else { - alert(`Error sending email: ${data.error}`); - } - return; - } - - // Clear compose form and close modal - setComposeTo(''); - setComposeCc(''); - setComposeBcc(''); - setComposeSubject(''); - setComposeBody(''); - setAttachments([]); - setShowCompose(false); - } catch (error) { - console.error('Error sending email:', error); - alert('Failed to send email. Please try again.'); - } - }; - - // Add toggleStarred function - const toggleStarred = async (emailId: number, e?: React.MouseEvent) => { - if (e) { - e.stopPropagation(); - } - - const email = emails.find(e => e.id === emailId); - if (!email) return; - - try { - const response = await fetch('/api/courrier/toggle-star', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ emailId, starred: !email.starred }), - }); - - if (!response.ok) { - throw new Error('Failed to toggle star'); - } - - // Update email in state - setEmails(emails.map(e => - e.id === emailId ? { ...e, starred: !e.starred } : e - )); - } catch (error) { - console.error('Error toggling star:', error); - } - }; - - // Add handleReply function - const handleReply = (type: 'reply' | 'reply-all' | 'forward') => { - if (!selectedEmail) return; - - const getReplyTo = () => { - if (type === 'forward') return ''; - return selectedEmail.from; - }; - - const getReplyCc = () => { - if (type !== 'reply-all') return ''; - return selectedEmail.cc || ''; - }; - - const getReplySubject = () => { - const subject = selectedEmail.subject || ''; - if (type === 'forward') { - return subject.startsWith('Fwd:') ? subject : `Fwd: ${subject}`; - } - return subject.startsWith('Re:') ? subject : `Re: ${subject}`; - }; - - // Get the formatted original email content - const originalContent = getReplyBody(selectedEmail, type); - - // Create a clean structure with clear separation - const formattedContent = ` -
-
- ${originalContent} -
- `; - - // Update the compose form - setComposeTo(getReplyTo()); - setComposeCc(getReplyCc()); - setComposeSubject(getReplySubject()); - setComposeBody(formattedContent); - setComposeBcc(''); - - // Show the compose form and CC field for Reply All - setShowCompose(true); - setShowCc(type === 'reply-all'); - setShowBcc(false); - setAttachments([]); - }; - - // Add the confirmation dialog component - const renderDeleteConfirmDialog = () => ( - - - - Delete Emails - - Are you sure you want to delete {selectedEmails.length} selected email{selectedEmails.length > 1 ? 's' : ''}? This action cannot be undone. - - - - Cancel - Delete - - - - ); - const handleMailboxChange = async (newMailbox: string) => { setCurrentView(newMailbox); setSelectedEmails([]);