From 2ba6a2717b2698e6f0b75766203579f617bca5cd Mon Sep 17 00:00:00 2001 From: alma Date: Sun, 27 Apr 2025 11:50:36 +0200 Subject: [PATCH] courrier refactor rebuild 2 --- app/api/user/email/route.ts | 38 ------ app/courrier/page.tsx | 33 +---- components/email/ComposeEmail.tsx | 166 +++++++++++++++++------- components/email/QuotedEmailContent.tsx | 20 +-- components/email/RichEmailEditor.tsx | 77 +++++++---- 5 files changed, 178 insertions(+), 156 deletions(-) delete mode 100644 app/api/user/email/route.ts diff --git a/app/api/user/email/route.ts b/app/api/user/email/route.ts deleted file mode 100644 index 5b114a1b..00000000 --- a/app/api/user/email/route.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { NextResponse } from 'next/server'; -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/app/api/auth/[...nextauth]/route'; -import { getUserEmailCredentials } from '@/lib/services/email-service'; - -export async function GET() { - try { - // Authenticate user - const session = await getServerSession(authOptions); - if (!session || !session.user?.id) { - return NextResponse.json( - { error: "Not authenticated" }, - { status: 401 } - ); - } - - // Get user's email credentials - const credentials = await getUserEmailCredentials(session.user.id); - - if (!credentials) { - return NextResponse.json( - { error: "No email credentials found" }, - { status: 404 } - ); - } - - // Return the email address - return NextResponse.json({ - email: credentials.email - }); - } catch (error: any) { - console.error("Error fetching user email:", error); - return NextResponse.json( - { error: "Failed to fetch user email", message: error.message }, - { status: 500 } - ); - } -} \ No newline at end of file diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 5612c60b..0dd141cb 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -101,49 +101,24 @@ export default function CourrierPage() { const [currentView, setCurrentView] = useState('INBOX'); const [unreadCount, setUnreadCount] = useState(0); const [loading, setLoading] = useState(false); - const [userEmail, setUserEmail] = useState(''); - // Get user's email from session data - useEffect(() => { - const getUserEmail = async () => { - try { - // Try to get user email from API - const response = await fetch('/api/user/email'); - if (response.ok) { - const data = await response.json(); - if (data.email) { - setUserEmail(data.email); - } - } - } catch (error) { - console.error('Error fetching user email:', error); - } - }; - - getUserEmail(); - }, []); - - // Accounts for the sidebar (using the actual user email) + // Mock accounts for the sidebar const [accounts, setAccounts] = useState([ { id: 0, name: 'All', email: '', color: 'bg-gray-500' }, - { id: 1, name: userEmail || 'Loading...', email: userEmail || '', color: 'bg-blue-500', folders: mailboxes } + { id: 1, name: 'Mail', email: 'user@example.com', color: 'bg-blue-500', folders: mailboxes } ]); const [selectedAccount, setSelectedAccount] = useState(null); - // Update account folders and email when mailboxes or user email changes + // Update account folders when mailboxes change useEffect(() => { setAccounts(prev => { const updated = [...prev]; if (updated[1]) { updated[1].folders = mailboxes; - if (userEmail) { - updated[1].name = userEmail; - updated[1].email = userEmail; - } } return updated; }); - }, [mailboxes, userEmail]); + }, [mailboxes]); // Calculate unread count (this would be replaced with actual data in production) useEffect(() => { diff --git a/components/email/ComposeEmail.tsx b/components/email/ComposeEmail.tsx index 4ed24f26..1c77745c 100644 --- a/components/email/ComposeEmail.tsx +++ b/components/email/ComposeEmail.tsx @@ -146,7 +146,7 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { const [cc, setCc] = useState(''); const [bcc, setBcc] = useState(''); const [subject, setSubject] = useState(''); - const [emailContent, setEmailContent] = useState('
'); + const [emailContent, setEmailContent] = useState(''); const [showCc, setShowCc] = useState(false); const [showBcc, setShowBcc] = useState(false); const [sending, setSending] = useState(false); @@ -158,36 +158,129 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { // Initialize the form when replying to or forwarding an email useEffect(() => { - if (initialEmail && (type === 'reply' || type === 'reply-all' || type === 'forward')) { + if (initialEmail && type !== 'new') { try { - // Handle reply and reply all + // Set recipients based on type if (type === 'reply' || type === 'reply-all') { - const result = formatReplyEmail(initialEmail, type === 'reply-all' ? 'reply-all' : 'reply'); + // Reply goes to the original sender + setTo(formatEmailAddresses(initialEmail.from || [])); - setTo(result.to || ''); - if (type === 'reply-all' && result.cc) { - setCc(result.cc); - setShowCc(Boolean(result.cc)); + // 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)); } - setSubject(result.subject || `Re: ${initialEmail.subject || ''}`); - setEmailContent('
'); - } - - // Handle forward - if (type === 'forward') { - const result = formatForwardedEmail(initialEmail); - setSubject(result.subject || `Fwd: ${initialEmail.subject || ''}`); - setEmailContent('
'); + // 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); + } } } catch (error) { console.error('Error initializing compose form:', error); - // Set default subject if we couldn't format it - if (initialEmail.subject) { - setSubject(type === 'forward' - ? `Fwd: ${initialEmail.subject}` - : `Re: ${initialEmail.subject}`); - } } } }, [initialEmail, type]); @@ -360,34 +453,15 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { {/* Message Body */}
-
+
- - {/* Use QuotedEmailContent component */} - {initialEmail && (type === 'reply' || type === 'reply-all' || type === 'forward') && ( - 0 ? initialEmail.from[0].name : undefined, - email: initialEmail.from && initialEmail.from.length > 0 ? initialEmail.from[0].address : 'unknown@example.com' - }} - date={initialEmail.date || new Date()} - type={type === 'forward' ? 'forward' : 'reply'} - subject={initialEmail.subject} - recipients={initialEmail.to?.map(to => ({ - name: to.name, - email: to.address - }))} - className="mt-4" - /> - )}
{/* Attachments */} @@ -742,12 +816,12 @@ function LegacyAdapter({ {/* Message Body */}
-
+
diff --git a/components/email/QuotedEmailContent.tsx b/components/email/QuotedEmailContent.tsx index 9d76b42f..87f49820 100644 --- a/components/email/QuotedEmailContent.tsx +++ b/components/email/QuotedEmailContent.tsx @@ -12,8 +12,6 @@ interface QuotedEmailContentProps { date: Date | string; type: 'reply' | 'forward'; className?: string; - subject?: string; - recipients?: Array<{name?: string; email: string}>; } /** @@ -24,9 +22,7 @@ const QuotedEmailContent: React.FC = ({ sender, date, type, - className = '', - subject, - recipients + className = '' }) => { // Format the date const formatDate = (date: Date | string) => { @@ -52,16 +48,6 @@ const QuotedEmailContent: React.FC = ({ const senderName = sender.name || sender.email; const formattedDate = formatDate(date); - // Format recipients for display - const formatRecipientList = (recipients?: Array<{name?: string; email: string}>) => { - if (!recipients || recipients.length === 0) return ''; - - return recipients.map(r => { - const displayName = r.name || r.email; - return `${displayName} <${r.email}>`; - }).join(', '); - }; - // Create header based on type const renderQuoteHeader = () => { if (type === 'reply') { @@ -76,8 +62,8 @@ const QuotedEmailContent: React.FC = ({
---------- Forwarded message ---------
From: {senderName} <{sender.email}>
Date: {formattedDate}
-
Subject: {subject || 'No Subject'}
-
To: {formatRecipientList(recipients) || 'No Recipients'}
+
Subject: {/* Subject would be passed as a prop if needed */}
+
To: {/* Recipients would be passed as a prop if needed */}
); } diff --git a/components/email/RichEmailEditor.tsx b/components/email/RichEmailEditor.tsx index 2c3eeff0..1e3d7d62 100644 --- a/components/email/RichEmailEditor.tsx +++ b/components/email/RichEmailEditor.tsx @@ -34,6 +34,21 @@ const RichEmailEditor: React.FC = ({ const Quill = (await import('quill')).default; + // Import quill-better-table + try { + const QuillBetterTable = await import('quill-better-table'); + + // Register the table module if available + if (QuillBetterTable && QuillBetterTable.default) { + Quill.register({ + 'modules/better-table': QuillBetterTable.default + }, true); + console.log('Better Table module registered successfully'); + } + } catch (err) { + console.warn('Table module not available:', err); + } + // Define custom formats/modules with table support const emailToolbarOptions = [ ['bold', 'italic', 'underline', 'strike'], @@ -55,6 +70,15 @@ const RichEmailEditor: React.FC = ({ // Add any custom toolbar handlers here } }, + 'better-table': preserveFormatting ? { + operationMenu: { + items: { + unmergeCells: { + text: 'Unmerge cells' + } + } + } + } : false, }, placeholder: placeholder, theme: 'snow', @@ -71,34 +95,41 @@ const RichEmailEditor: React.FC = ({ // If we're specifically trying to preserve complex HTML like tables if (preserveFormatting) { - // For tables and complex formatting, use direct HTML setting + // For tables and complex formatting, we may need to manually preserve some elements + // Get all table elements from the original content const tempDiv = document.createElement('div'); tempDiv.innerHTML = preservedContent; - if (tempDiv.querySelectorAll('table').length > 0) { - // This handles tables better than the Quill Delta format - quillRef.current.root.innerHTML = preservedContent; - } - - // Force refresh after a delay + // Force better table rendering in Quill setTimeout(() => { + // This ensures tables are properly rendered by forcing a refresh quillRef.current.update(); + // Additional step: directly set HTML if tables aren't rendering properly + if (tempDiv.querySelectorAll('table').length > 0 && + !quillRef.current.root.querySelectorAll('table').length) { + console.log('Using HTML fallback for tables'); + quillRef.current.root.innerHTML = preservedContent; + } + // Ensure the cursor and scroll position is at the top of the editor quillRef.current.setSelection(0, 0); // Also scroll the container to the top if (editorRef.current) { editorRef.current.scrollTop = 0; - const containers = [ - editorRef.current.closest('.ql-container'), - editorRef.current.closest('.rich-email-editor-container') - ]; - containers.forEach(container => { - if (container) { - (container as HTMLElement).scrollTop = 0; - } - }); + + // Also find and scroll parent containers that might have scroll + const scrollContainer = editorRef.current.closest('.ql-container'); + if (scrollContainer) { + scrollContainer.scrollTop = 0; + } + + // One more check for nested scroll containers (like overflow divs) + const parentScrollContainer = editorRef.current.closest('.rich-email-editor-container'); + if (parentScrollContainer) { + parentScrollContainer.scrollTop = 0; + } } }, 50); } else { @@ -114,16 +145,10 @@ const RichEmailEditor: React.FC = ({ } } - // Add change listener (with debounce to prevent infinite updates) - let debounceTimeout: NodeJS.Timeout; + // Add change listener quillRef.current.on('text-change', () => { - clearTimeout(debounceTimeout); - debounceTimeout = setTimeout(() => { - const html = quillRef.current?.root.innerHTML; - if (html !== initialContent) { - onChange(html); - } - }, 300); + const html = quillRef.current.root.innerHTML; + onChange(html); }); // Improve editor layout @@ -178,7 +203,7 @@ const RichEmailEditor: React.FC = ({ } } } - }, [initialContent, isReady, preserveFormatting]); + }, [initialContent, isReady]); return (