From d34bf5202cb16eb4f3a2dc7c3bb3ccbbbdd17cb9 Mon Sep 17 00:00:00 2001 From: alma Date: Tue, 29 Apr 2025 10:35:20 +0200 Subject: [PATCH] courrier multi account restore compose --- app/courrier/page.tsx | 208 +++++++++++++++++++++++------- app/hooks/use-courrier.ts | 51 ++++++-- components/email/EmailSidebar.tsx | 60 +++++++-- 3 files changed, 250 insertions(+), 69 deletions(-) diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 98607d70..5b012ff9 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -128,6 +128,15 @@ const colorPalette = [ 'bg-cyan-500', ]; +// Helper function for consistent logging +const logEmailOp = (operation: string, details: string, data?: any) => { + const timestamp = new Date().toISOString().split('T')[1].substring(0, 12); + console.log(`[${timestamp}][EMAIL-APP][${operation}] ${details}`); + if (data) { + console.log(`[${timestamp}][EMAIL-APP][DATA]`, data); + } +}; + export default function CourrierPage() { const router = useRouter(); const { data: session } = useSession(); @@ -267,6 +276,31 @@ export default function CourrierPage() { } }, [accounts, mailboxes]); + // Handle initial loading and folder prefetching + useEffect(() => { + // Only run if we have a selected account and current folder + if (selectedAccount && currentFolder && !isLoading) { + logEmailOp('PREFETCH', `Prefetching emails for folder: ${currentFolder}`); + + // Ensure the UI is updated to reflect current selections + if (selectedAccount.id !== 'loading-account') { + // Show the current folder as selected + setSelectedFolders(prev => { + if (prev[selectedAccount.id] === currentFolder) { + // Already selected, no need to update + return prev; + } + + logEmailOp('FOLDER-SELECT', `Updating selected folder for ${selectedAccount.id} to ${currentFolder}`); + return { + ...prev, + [selectedAccount.id]: currentFolder + }; + }); + } + } + }, [selectedAccount, currentFolder, isLoading]); + // Initialize session and start prefetching useEffect(() => { // Flag to prevent multiple initialization attempts @@ -278,6 +312,7 @@ export default function CourrierPage() { const initSession = async () => { try { if (!isMounted) return; + logEmailOp('SESSION', 'Initializing email session'); setLoading(true); // First check if Redis is ready before making API calls @@ -288,6 +323,7 @@ export default function CourrierPage() { if (!isMounted) return; // Call the session API to check email credentials and start prefetching + logEmailOp('SESSION', 'Fetching session data from API'); const response = await fetch('/api/courrier/session', { credentials: 'include', // Ensure cookies are sent headers: { @@ -572,8 +608,11 @@ export default function CourrierPage() { // Update handleMailboxChange to ensure consistent folder naming and prevent race conditions const handleMailboxChange = (folder: string, accountId?: string) => { + // Enhanced logging to trace the flow + logEmailOp('MAILBOX-CHANGE', `Starting mailbox change to folder=${folder}, accountId=${accountId || 'undefined'}`); + if (!accountId || accountId === 'loading-account') { - console.warn('No valid accountId provided for folder change'); + logEmailOp('MAILBOX-CHANGE', 'No valid accountId provided, using default flow', { folder }); changeFolder(folder); return; } @@ -586,6 +625,7 @@ export default function CourrierPage() { const account = accounts.find(a => a.id.toString() === accountId.toString()); if (!account) { + logEmailOp('MAILBOX-CHANGE', `ERROR: Account not found: ${accountId}`); toast({ title: "Account not found", description: `The account ${accountId} could not be found.`, @@ -594,39 +634,88 @@ export default function CourrierPage() { setLoading(false); return; } - - // Normalize folder name with account prefix - const prefixedFolder = folder.includes(':') ? folder : `${accountId}:${folder}`; - // Verify folder exists in account - const folderExists = account.folders?.some(f => - f === prefixedFolder || f === folder || - (folder.includes(':') ? false : `${accountId}:${folder}` === f) + logEmailOp('MAILBOX-CHANGE', `Found account: ${account.email}`, { + folderCount: account.folders?.length || 0, + folder: folder + }); + + // Extract the base folder name if it already has an account prefix + let baseFolder = folder; + let folderAccountId = accountId; + + if (folder.includes(':')) { + const parts = folder.split(':'); + folderAccountId = parts[0]; + baseFolder = parts[1]; + + logEmailOp('MAILBOX-CHANGE', `Parsed folder: accountId=${folderAccountId}, baseFolder=${baseFolder}`); + + // If the prefixed account doesn't match our account, log a warning + if (folderAccountId !== accountId.toString()) { + logEmailOp('MAILBOX-CHANGE', `WARNING: Folder prefix mismatch`, { + folderAccount: folderAccountId, + targetAccount: accountId, + originalFolder: folder + }); + } + } + + // Always create a consistent folder name with current account prefix + const prefixedFolder = `${accountId}:${baseFolder}`; + logEmailOp('MAILBOX-CHANGE', `Normalized folder name: ${prefixedFolder}`); + + // Find exact folder match in account's folders + const exactMatch = account.folders?.find(f => + f === prefixedFolder || + f === baseFolder || + (f.includes(':') && f.split(':')[1] === baseFolder) ); - if (!folderExists && !folder.includes('INBOX') && folder !== 'INBOX') { + if (exactMatch) { + logEmailOp('MAILBOX-CHANGE', `Found exact folder match: ${exactMatch}`); + } else { + logEmailOp('MAILBOX-CHANGE', `No exact folder match found for ${baseFolder}`); + } + + // If no match and not INBOX, show error + if (!exactMatch && !baseFolder.includes('INBOX') && baseFolder !== 'INBOX') { + logEmailOp('MAILBOX-CHANGE', `ERROR: Folder not found: ${baseFolder}`); toast({ title: "Folder not found", - description: `The folder ${folder} does not exist for this account.`, + description: `The folder "${baseFolder}" does not exist for this account.`, variant: "destructive", }); setLoading(false); return; } + // Use the exact match if found, otherwise use our constructed prefixedFolder + const folderToUse = exactMatch || prefixedFolder; + logEmailOp('MAILBOX-CHANGE', `Proceeding with folder: ${folderToUse}`); + // Update UI state setSelectedAccount(account); - setSelectedFolders(prev => ({ - ...prev, - [accountId]: prefixedFolder - })); + + // Update the selected folders map with the normalized folder name + setSelectedFolders(prev => { + const updated = { + ...prev, + [accountId]: folderToUse + }; + logEmailOp('MAILBOX-CHANGE', `Updated selected folders map`, updated); + return updated; + }); // Use a deliberate delay before changing folder to ensure state is updated + logEmailOp('MAILBOX-CHANGE', `Starting folder change after delay...`); + + // Reduce timeout to improve responsiveness setTimeout(() => { - console.log(`Loading emails for folder: ${prefixedFolder}, account: ${accountId}`); - changeFolder(prefixedFolder, accountId); + logEmailOp('MAILBOX-CHANGE', `Loading emails for folder: ${folderToUse}, account: ${accountId}`); + changeFolder(folderToUse, accountId); setLoading(false); - }, 300); + }, 150); // Reduced from 300ms to 150ms for better responsiveness }; // Update the folder button rendering to show selected state based on account @@ -712,45 +801,74 @@ export default function CourrierPage() { }, [selectedAccount, showFolders]); const handleAccountSelect = (account: Account) => { + // Enhanced logging to trace the flow + logEmailOp('ACCOUNT-SELECT', `Selecting account: ${account.email} (id: ${account.id})`, { + folderCount: account.folders?.length || 0 + }); + // First set loading state to provide visual feedback setLoading(true); // Clear current emails to avoid confusion during transition setEmails([]); - // Update selected account first (use type assertion) - setSelectedAccount(account as any); + // Update selected account + setSelectedAccount(account); + logEmailOp('ACCOUNT-SELECT', `Selected account set to: ${account.email}`); // Initially hide folders during transition setShowFolders(false); - // Use a small delay to ensure UI updates before showing folders and loading emails + // Find an INBOX folder in this account's folders + // Look for exact INBOX or accountId:INBOX pattern + const inboxPatterns = ['INBOX', ':INBOX', 'inbox', 'Inbox']; + + let inboxFolder = account.folders.find(f => + inboxPatterns.some(pattern => f.includes(pattern)) + ); + + // If no inbox found, use the first folder or create a default inbox path + if (!inboxFolder) { + inboxFolder = account.folders.length > 0 + ? account.folders[0] + : `${account.id}:INBOX`; + logEmailOp('ACCOUNT-SELECT', `No INBOX found, using folder: ${inboxFolder}`); + } else { + logEmailOp('ACCOUNT-SELECT', `Found INBOX folder: ${inboxFolder}`); + } + + // Ensure the folder has the account prefix + if (!inboxFolder.includes(':')) { + inboxFolder = `${account.id}:${inboxFolder}`; + logEmailOp('ACCOUNT-SELECT', `Added account prefix to folder: ${inboxFolder}`); + } + + // We don't need to manually set currentFolder as it's managed by changeFolder + // Instead, just log that we'll update it through the proper function + logEmailOp('ACCOUNT-SELECT', `Will update current folder to: ${inboxFolder} through folder change`); + + // Create a streamlined account selection sequence + // Step 1: Show folders immediately for better feedback + setShowFolders(true); + logEmailOp('ACCOUNT-SELECT', `Folders shown for account: ${account.email}`); + + // Step 2: Update the selected folders map immediately + setSelectedFolders(prev => { + const updated = { + ...prev, + [account.id.toString()]: inboxFolder + }; + logEmailOp('ACCOUNT-SELECT', `Updated selected folders map`, updated); + return updated; + }); + + // Step 3: Wait a small delay and then load emails + logEmailOp('ACCOUNT-SELECT', `Starting folder change after short delay`); setTimeout(() => { - if (account.id !== 'loading-account') { - // Now show folders - setShowFolders(true); - - // Default to INBOX for the selected account - const inboxFolder = account.folders.find(f => - f.includes('INBOX') || - (f.includes(':') && f.split(':')[1] === 'INBOX') - ) || (account.id + ':INBOX'); - - // Update the selected folders map - setSelectedFolders(prev => ({ - ...prev, - [account.id.toString()]: inboxFolder - })); - - // Use a slightly longer delay before loading emails to ensure all state is updated - setTimeout(() => { - handleMailboxChange(inboxFolder, account.id.toString()); - setLoading(false); - }, 200); - } else { - setLoading(false); - } - }, 100); + logEmailOp('ACCOUNT-SELECT', `Triggering folder change to: ${inboxFolder}`); + handleMailboxChange(inboxFolder, account.id.toString()); + setLoading(false); + }, 100); // Reduced delay for better responsiveness }; const handleAddAccount = async (accountData: AccountData) => { diff --git a/app/hooks/use-courrier.ts b/app/hooks/use-courrier.ts index ba95a97e..a51fa4ea 100644 --- a/app/hooks/use-courrier.ts +++ b/app/hooks/use-courrier.ts @@ -44,12 +44,32 @@ const loadEmails = async ( accountIdOverride?: string ) => { const folderToUse = folderOverride || currentFolder; + + // Enhanced folder and account handling + let normalizedFolder = folderToUse; + let normalizedAccountId = accountIdOverride; + + // Extract account ID from folder if it has a prefix and no explicit account ID was provided + if (folderToUse.includes(':')) { + const [folderAccountId, baseFolderName] = folderToUse.split(':'); + console.log(`Folder has prefix: accountId=${folderAccountId}, baseName=${baseFolderName}`); + + // If no explicit account ID was provided, use the one from the folder name + if (!normalizedAccountId) { + normalizedAccountId = folderAccountId; + console.log(`Using account ID from folder prefix: ${normalizedAccountId}`); + } + // If both exist but don't match, log a warning + else if (normalizedAccountId !== folderAccountId) { + console.warn(`⚠️ Mismatch between folder account prefix (${folderAccountId}) and provided accountId (${normalizedAccountId})`); + console.warn(`Using provided accountId (${normalizedAccountId}) but this may cause unexpected behavior`); + } + } + const pageToUse = pageOverride || page; const perPageToUse = perPageOverride || perPage; - const accountIdToUse = accountIdOverride !== undefined ? accountIdOverride : - folderToUse.includes(':') ? folderToUse.split(':')[0] : undefined; - console.log(`Loading emails: folder=${folderToUse}, page=${pageToUse}, accountId=${accountIdToUse || 'default'}`); + console.log(`Loading emails: folder=${folderToUse}, page=${pageToUse}, accountId=${normalizedAccountId || 'default'}`); try { setIsLoading(true); @@ -59,8 +79,8 @@ const loadEmails = async ( let url = `/api/courrier/emails?folder=${encodeURIComponent(folderToUse)}&page=${pageToUse}&perPage=${perPageToUse}`; // Add accountId parameter if specified - if (accountIdToUse) { - url += `&accountId=${encodeURIComponent(accountIdToUse)}`; + if (normalizedAccountId) { + url += `&accountId=${encodeURIComponent(normalizedAccountId)}`; } // Add cache-busting timestamp @@ -77,27 +97,38 @@ const loadEmails = async ( } catch { errorText = `HTTP error: ${response.status}`; } + console.error(`API error: ${errorText}`); throw new Error(errorText); } const data = await response.json(); + console.log(`Received ${data.emails?.length || 0} emails from API`); - if (pageOverride === 1 || !pageOverride) { + if (pageToUse === 1 || !pageOverride) { // Replace emails when loading first page - setEmails(data.emails); + console.log(`Setting ${data.emails?.length || 0} emails (replacing existing)`); + setEmails(data.emails || []); } else { // Append emails when loading subsequent pages - setEmails(prev => [...prev, ...data.emails]); + console.log(`Appending ${data.emails?.length || 0} emails to existing list`); + setEmails(prev => [...prev, ...(data.emails || [])]); } // Update pagination info - setTotalPages(data.totalPages); - setMailboxes(data.mailboxes || []); + setTotalPages(data.totalPages || 0); + if (data.mailboxes && data.mailboxes.length > 0) { + console.log(`Received ${data.mailboxes.length} mailboxes from API`); + setMailboxes(data.mailboxes); + } return data; } catch (error) { console.error('Error loading emails:', error); setError(`Failed to load emails: ${error instanceof Error ? error.message : 'Unknown error'}`); + // Set empty emails array on error to prevent UI issues + if (pageToUse === 1) { + setEmails([]); + } return null; } finally { setIsLoading(false); diff --git a/components/email/EmailSidebar.tsx b/components/email/EmailSidebar.tsx index d34ca6ea..355ea64a 100644 --- a/components/email/EmailSidebar.tsx +++ b/components/email/EmailSidebar.tsx @@ -142,32 +142,64 @@ export default function EmailSidebar({ // Improve the renderFolderButton function to ensure consistent handling const renderFolderButton = (folder: string, accountId: string) => { - // Ensure folder always has accountId prefix for consistency - const prefixedFolder = folder.includes(':') ? folder : `${accountId}:${folder}`; + // Add extra logging to debug folder rendering issues + console.log(`Rendering folder button: ${folder} for account: ${accountId}`); - // Extract the base folder name and account ID for display and checking - const [folderAccountId, baseFolder] = prefixedFolder.includes(':') - ? prefixedFolder.split(':') - : [accountId, folder]; + // Ensure folder has a consistent format + let prefixedFolder = folder; + let baseFolderName = folder; + let folderAccountId = accountId; + + // Extract parts if the folder has a prefix + if (folder.includes(':')) { + const parts = folder.split(':'); + folderAccountId = parts[0]; + baseFolderName = parts[1]; + + console.log(`Folder has prefix, extracted: accountId=${folderAccountId}, baseFolder=${baseFolderName}`); + } else { + // Add account prefix if missing + prefixedFolder = `${accountId}:${folder}`; + console.log(`Added prefix to folder: ${prefixedFolder}`); + } // Only show folders that belong to this account - if (folderAccountId !== accountId) return null; + if (folderAccountId !== accountId) { + console.log(`Skipping folder ${folder} - belongs to different account (${folderAccountId})`); + return null; + } - const isSelected = selectedFolders[accountId] === prefixedFolder; + // Check if this folder is selected for this account + // Must handle both prefixed and non-prefixed versions in the selected map + const isSelected = + (selectedFolders[accountId] === prefixedFolder) || + (selectedFolders[accountId] === baseFolderName) || + (selectedFolders[accountId]?.split(':')[1] === baseFolderName); + + if (isSelected) { + console.log(`Folder ${baseFolderName} is selected for account ${accountId}`); + } + + // Get unread count + const folderUnreadCount = unreadCount[accountId]?.[baseFolderName] || + unreadCount[accountId]?.[prefixedFolder] || 0; return (