From a9012dec6930a1d956c812b642faa1dabbaf55b7 Mon Sep 17 00:00:00 2001 From: alma Date: Tue, 29 Apr 2025 12:00:36 +0200 Subject: [PATCH] courrier multi account restore compose --- hooks/use-courrier.ts | 232 +++++++++++++++++++++++++++++++++++------- 1 file changed, 193 insertions(+), 39 deletions(-) diff --git a/hooks/use-courrier.ts b/hooks/use-courrier.ts index df8cda85..7b413dc2 100644 --- a/hooks/use-courrier.ts +++ b/hooks/use-courrier.ts @@ -91,9 +91,26 @@ export const useCourrier = () => { setError(null); try { + // CRITICAL FIX: Extract normalized folder name for API requests + // The currentFolder should already have the account prefix in format "accountId:folder" + // We need to extract the base folder name for the API request + let normalizedFolder = currentFolder; + let effectiveAccountId = accountId || 'default'; + + if (currentFolder.includes(':')) { + const parts = currentFolder.split(':'); + // If no explicit accountId was provided, use the one from the folder name + if (!accountId) { + effectiveAccountId = parts[0]; + } + normalizedFolder = parts[1]; + } + + console.log(`Load emails - using normalized folder: ${normalizedFolder}, effectiveAccountId: ${effectiveAccountId}`); + // Construct query parameters const queryParams = new URLSearchParams({ - folder: currentFolder, + folder: normalizedFolder, // Use normalized folder without account prefix for API page: page.toString(), perPage: perPage.toString() }); @@ -102,24 +119,22 @@ export const useCourrier = () => { queryParams.set('search', searchQuery); } - // Add accountId to query params if provided - if (accountId) { - queryParams.set('accountId', accountId); - } + // Always add accountId to query params + queryParams.set('accountId', effectiveAccountId); // Try to get cached emails first const currentRequestPage = page; - // FIXED: Use the correct parameter order for getCachedEmailsWithTimeout + // FIXED: Use the correct parameter order and normalized values // Function signature: (userId: string, folder: string, page: number, perPage: number, timeoutMs: number = 100, accountId?: string) - console.log(`Getting cached emails for user ${session.user.id}, folder ${currentFolder}, page ${currentRequestPage}, accountId ${accountId || 'default'}`); + console.log(`Getting cached emails for user ${session.user.id}, folder ${currentFolder}, normalizedFolder: ${normalizedFolder}, page ${currentRequestPage}, accountId ${effectiveAccountId}`); const cachedEmails = await getCachedEmailsWithTimeout( session.user.id, // userId: string - currentFolder, // folder: string + currentFolder, // folder: string - use full prefixed folder for cache key currentRequestPage, // page: number perPage, // perPage: number 100, // timeoutMs: number - accountId // accountId?: string + effectiveAccountId // accountId?: string - always pass the effective account ID ); if (cachedEmails) { @@ -184,12 +199,14 @@ export const useCourrier = () => { setIsLoading(false); // Still refresh in background for fresh data + // CRITICAL FIX: Use the same effective account ID for background refresh + console.log(`Starting background refresh with folder ${currentFolder}, account ${effectiveAccountId}`); refreshEmailsInBackground( session.user.id, currentFolder, currentRequestPage, perPage, - accountId // Make sure accountId is passed + effectiveAccountId // Always use the effective account ID ).catch(err => { console.error('Background refresh error:', err); }); @@ -197,6 +214,7 @@ export const useCourrier = () => { } // Fetch emails from API + console.log(`Fetching emails from API with params: ${queryParams.toString()}`); const response = await fetch(`/api/courrier?${queryParams.toString()}`); if (!response.ok) { @@ -206,6 +224,21 @@ export const useCourrier = () => { const data: EmailListResult = await response.json(); + // CRITICAL FIX: Ensure all emails have the proper account ID and folder format for consistent lookup + if (Array.isArray(data.emails)) { + data.emails.forEach(email => { + // If email doesn't have an accountId, set it to the effective one + if (!email.accountId) { + email.accountId = effectiveAccountId; + } + + // Ensure folder has the proper prefix format + if (email.folder && !email.folder.includes(':')) { + email.folder = `${email.accountId}:${email.folder}`; + } + }); + } + // Update state with the fetched data if (isLoadMore) { setEmails(prev => { @@ -266,6 +299,7 @@ export const useCourrier = () => { const changeFolder = useCallback(async (folder: string, accountId?: string) => { console.log(`Changing folder to ${folder} for account ${accountId || 'default'}`); try { + // CRITICAL FIX: Better folder and account ID handling // Extract account ID from folder name if present and none was explicitly provided const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId; @@ -275,14 +309,18 @@ export const useCourrier = () => { // Normalize folder name by removing account prefix if present const normalizedFolder = folder.includes(':') ? folder.split(':')[1] : folder; - console.log(`Folder change normalized: ${normalizedFolder}, account: ${effectiveAccountId}`); + // Always create a consistently formatted folder name with account prefix + const prefixedFolder = `${effectiveAccountId}:${normalizedFolder}`; + + console.log(`Folder change: original=${folder}, normalized=${normalizedFolder}, accountId=${effectiveAccountId}, prefixed=${prefixedFolder}`); // Reset selected email setSelectedEmail(null); setSelectedEmailIds([]); - // Record the new folder (preserving account prefix if present) - setCurrentFolder(folder); + // Use the consistently prefixed folder name for state + // CRITICAL FIX: Always use the properly prefixed folder name in state + setCurrentFolder(prefixedFolder); // Reset search query when changing folders setSearchQuery(''); @@ -301,6 +339,8 @@ export const useCourrier = () => { await new Promise(resolve => setTimeout(resolve, 100)); // Call loadEmails with correct boolean parameter type and account ID + // CRITICAL FIX: Pass the properly formatted folder name to the cache lookup functions + console.log(`Loading emails for prefixed folder: ${prefixedFolder} with accountId: ${effectiveAccountId}`); await loadEmails(false, effectiveAccountId); } catch (error) { console.error(`Error changing to folder ${folder}:`, error); @@ -421,56 +461,170 @@ export const useCourrier = () => { // Select an email to view const handleEmailSelect = useCallback(async (emailId: string, accountId: string, folderOverride: string) => { - console.log(`Selecting email ${emailId} from account ${accountId} in folder ${folderOverride}`); + // Enhanced logging for better debugging + console.log(`Selecting email ${emailId} from account [${accountId}] in folder [${folderOverride}]`); + + // Skip processing if emailId is empty (used when deselecting emails) + if (!emailId) { + console.log('No email ID provided, clearing selection'); + setSelectedEmail(null); + return; + } setIsLoading(true); try { // Normalize account ID if not provided const effectiveAccountId = accountId || 'default'; - // Normalize folder name by removing account prefix if present - const normalizedFolder = folderOverride.includes(':') ? folderOverride.split(':')[1] : folderOverride; + // Normalize folder name handling - ensure consistent format + let normalizedFolder: string; + let prefixedFolder: string; - // Find the email in the current list - const email = emails.find(e => + if (folderOverride.includes(':')) { + // Extract parts if folder already has a prefix + const parts = folderOverride.split(':'); + const folderAccountId = parts[0]; + normalizedFolder = parts[1]; + + // CRITICAL FIX: Always use the provided accountId instead of the one in the folder + // This ensures we're looking in the right account when an email is clicked + prefixedFolder = `${effectiveAccountId}:${normalizedFolder}`; + + if (folderAccountId !== effectiveAccountId) { + console.log(`WARNING: Folder account prefix mismatch. Folder has ${folderAccountId}, but using ${effectiveAccountId}`); + } + } else { + // No prefix, add one + normalizedFolder = folderOverride; + prefixedFolder = `${effectiveAccountId}:${normalizedFolder}`; + } + + console.log(`Email selection with normalized values: folder=${normalizedFolder}, prefixed=${prefixedFolder}, accountId=${effectiveAccountId}`); + + // More flexible email finding with detailed logging + console.log(`Looking for email with ID=${emailId}, account=${effectiveAccountId}, normalized folder=${normalizedFolder}, prefixed=${prefixedFolder}`); + + // First, try to find by exact match with account and folder + let email = emails.find(e => e.id === emailId && - (e.accountId === effectiveAccountId) && - (e.folder === normalizedFolder || e.folder === folderOverride) + e.accountId === effectiveAccountId && + ( + e.folder === prefixedFolder || + e.folder === normalizedFolder || + e.folder === folderOverride || + // Also check for case where email folder has its own prefix but with the same normalized folder + (e.folder?.includes(':') && e.folder.split(':')[1] === normalizedFolder) + ) ); + // If not found, try to find just by ID as fallback if (!email) { - console.log(`Email ${emailId} not found in current list. Fetching from API.`); - const fullEmail = await fetchEmailContent(emailId, effectiveAccountId, normalizedFolder); - setSelectedEmail(fullEmail); + console.log(`No exact match found. Looking for email just by ID=${emailId}`); + email = emails.find(e => e.id === emailId); + + if (email) { + console.log(`Found email by ID only. Account=${email.accountId}, folder=${email.folder}`); + } + } + + if (!email) { + console.log(`Email ${emailId} not found in current list (searched ${emails.length} emails). Fetching from API.`); + try { + // CRITICAL FIX: Pass both normalized folder and account ID to fetch + // Using normalized folder (without prefix) for API compatibility + console.log(`Fetching email ${emailId} directly from API with normalizedFolder=${normalizedFolder}, accountId=${effectiveAccountId}`); + const fullEmail = await fetchEmailContent(emailId, effectiveAccountId, normalizedFolder); + + // Ensure the returned email has the proper accountId and prefixed folder name + if (fullEmail) { + if (!fullEmail.accountId) { + fullEmail.accountId = effectiveAccountId; + } + + // Make sure folder has the proper prefix for consistent lookup + if (fullEmail.folder && !fullEmail.folder.includes(':')) { + fullEmail.folder = `${fullEmail.accountId}:${fullEmail.folder}`; + } + } + + console.log(`Successfully fetched email from API:`, { + id: fullEmail.id, + account: fullEmail.accountId, + folder: fullEmail.folder + }); + setSelectedEmail(fullEmail); + } catch (error) { + // Type the error properly + const fetchError = error instanceof Error ? error : new Error(String(error)); + console.error(`Error fetching email from API: ${fetchError.message}`); + throw fetchError; + } return; } // If content is not fetched, get the full content if (!email.contentFetched) { - console.log(`Fetching content for email ${emailId}`); - const fullEmail = await fetchEmailContent(emailId, effectiveAccountId, normalizedFolder); - - // Merge the full content with the email - const updatedEmail = { - ...email, - content: fullEmail.content, - attachments: fullEmail.attachments, - contentFetched: true - }; - - // Update the email in the list - setEmails(emails.map(e => e.id === emailId ? updatedEmail : e)); - setSelectedEmail(updatedEmail); + console.log(`Email found but content not fetched. Getting full content for ${emailId}`); + try { + // CRITICAL FIX: Extract normalized folder from email's folder if it has a prefix + const emailFolder = email.folder || normalizedFolder; + let emailNormalizedFolder = emailFolder; + + if (emailFolder.includes(':')) { + emailNormalizedFolder = emailFolder.split(':')[1]; + } + + // Always use the email's own accountId if available + const emailAccountId = email.accountId || effectiveAccountId; + + console.log(`Fetching content for email ${emailId} with accountId=${emailAccountId}, folder=${emailNormalizedFolder}`); + + // Use the email's own accountId and normalized folder for fetching + const fullEmail = await fetchEmailContent( + emailId, + emailAccountId, + emailNormalizedFolder + ); + + // Ensure the returned email has consistent format + if (fullEmail && !fullEmail.accountId) { + fullEmail.accountId = emailAccountId; + } + + // Merge the full content with the email + const updatedEmail = { + ...email, + accountId: emailAccountId, // Ensure account ID is preserved + folder: email.folder, // Preserve original folder name with prefix + content: fullEmail.content, + attachments: fullEmail.attachments, + contentFetched: true + }; + + // Update the email in the list + setEmails(emails.map(e => e.id === emailId ? updatedEmail : e)); + setSelectedEmail(updatedEmail); + console.log(`Successfully updated email with content`); + } catch (error) { + // Type the error properly + const contentError = error instanceof Error ? error : new Error(String(error)); + console.error(`Error fetching email content: ${contentError.message}`); + throw contentError; + } } else { + console.log(`Email found with content already fetched, selecting directly`); setSelectedEmail(email); } // Mark the email as read if it's not already if (!email.flags.seen) { - markEmailAsRead(emailId, true); + console.log(`Marking email ${emailId} as read`); + markEmailAsRead(emailId, true).catch(err => { + console.error(`Failed to mark email as read: ${err.message}`); + }); } } catch (err) { - console.error('Error selecting email:', err); + console.error(`Error selecting email: ${err instanceof Error ? err.message : String(err)}`); toast({ variant: "destructive", title: "Error",