diff --git a/hooks/use-email-state.ts b/hooks/use-email-state.ts index 846f6638..088e01e9 100644 --- a/hooks/use-email-state.ts +++ b/hooks/use-email-state.ts @@ -155,194 +155,407 @@ export const useEmailState = () => { dispatch({ type: 'SET_LOADING', payload: true }); try { - // Get normalized parameters using helper function with proper account ID handling - const accountId = state.selectedAccount ? state.selectedAccount.id : undefined; - const { normalizedFolder, effectiveAccountId, prefixedFolder } = - normalizeFolderAndAccount(state.currentFolder, accountId); - - logEmailOp('LOAD_EMAILS', `Loading emails for ${prefixedFolder} (account: ${effectiveAccountId}, isLoadMore: ${isLoadMore}, page: ${page})`); - - // Construct query parameters - const queryParams = new URLSearchParams({ - folder: normalizedFolder, - page: page.toString(), - perPage: perPage.toString(), - accountId: effectiveAccountId - }); - - // Debug log existing emails count - if (isLoadMore) { - console.log(`[DEBUG-PAGINATION] Loading more emails. Current page: ${page}, existing emails: ${state.emails.length}`); + // CRITICAL FIX: Add more robust validation to prevent "toString of undefined" error + if (!state.currentFolder) { + logEmailOp('ERROR', 'Current folder is undefined, cannot load emails'); + dispatch({ + type: 'SET_ERROR', + payload: 'Invalid folder configuration' + }); + dispatch({ type: 'SET_LOADING', payload: false }); + return; } - // Try to get cached emails first - logEmailOp('CACHE_CHECK', `Checking cache for ${prefixedFolder}, page: ${page}`); - const cachedEmails = await getCachedEmailsWithTimeout( - session.user.id, - prefixedFolder, - page, - perPage, - 100, - effectiveAccountId - ); + // Get normalized parameters using helper function with proper account ID handling + const accountId = state.selectedAccount ? state.selectedAccount.id : undefined; - if (cachedEmails) { - logEmailOp('CACHE_HIT', `Using cached data for ${prefixedFolder}, page: ${page}, emails: ${cachedEmails.emails?.length || 0}, isLoadMore: ${isLoadMore}`); - - // Ensure cached data has emails array property - if (Array.isArray(cachedEmails.emails)) { - // CRITICAL FIX: Double check we're using the right action type based on isLoadMore param - console.log(`[DEBUG-CACHE_HIT] Dispatching ${isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS'} with ${cachedEmails.emails.length} emails`); + // Additional validation for accountId + if (accountId === undefined && state.currentFolder.includes(':')) { + // Try to extract accountId from folder string as fallback + const extractedAccountId = state.currentFolder.split(':')[0]; + if (extractedAccountId) { + console.log(`[DEBUG-LOAD_EMAILS] Using extracted accountId ${extractedAccountId} from folder path as fallback`); + const { normalizedFolder, effectiveAccountId, prefixedFolder } = + normalizeFolderAndAccount(state.currentFolder, extractedAccountId); + + logEmailOp('LOAD_EMAILS', `Loading emails for ${prefixedFolder} (account: ${effectiveAccountId}, isLoadMore: ${isLoadMore}, page: ${page})`); - // Dispatch appropriate action based on if we're loading more - DO NOT OVERRIDE isLoadMore! - dispatch({ - type: isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS', - payload: cachedEmails.emails + // Continue with the extracted account ID... + // Construct query parameters + const queryParams = new URLSearchParams({ + folder: normalizedFolder, + page: page.toString(), + perPage: perPage.toString(), + accountId: effectiveAccountId }); - // Set pagination info from cache if available - if (cachedEmails.totalEmails) { - dispatch({ type: 'SET_TOTAL_EMAILS', payload: cachedEmails.totalEmails }); + // Debug log existing emails count + if (isLoadMore) { + console.log(`[DEBUG-PAGINATION] Loading more emails. Current page: ${page}, existing emails: ${state.emails.length}`); } - if (cachedEmails.totalPages) { - dispatch({ type: 'SET_TOTAL_PAGES', payload: cachedEmails.totalPages }); + // Try to get cached emails first + logEmailOp('CACHE_CHECK', `Checking cache for ${prefixedFolder}, page: ${page}`); + const cachedEmails = await getCachedEmailsWithTimeout( + session.user.id, + prefixedFolder, + page, + perPage, + 100, + effectiveAccountId + ); + + if (cachedEmails) { + logEmailOp('CACHE_HIT', `Using cached data for ${prefixedFolder}, page: ${page}, emails: ${cachedEmails.emails?.length || 0}, isLoadMore: ${isLoadMore}`); + + // Ensure cached data has emails array property + if (Array.isArray(cachedEmails.emails)) { + // CRITICAL FIX: Double check we're using the right action type based on isLoadMore param + console.log(`[DEBUG-CACHE_HIT] Dispatching ${isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS'} with ${cachedEmails.emails.length} emails`); + + // Dispatch appropriate action based on if we're loading more - DO NOT OVERRIDE isLoadMore! + dispatch({ + type: isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS', + payload: cachedEmails.emails + }); + + // Set pagination info from cache if available + if (cachedEmails.totalEmails) { + dispatch({ type: 'SET_TOTAL_EMAILS', payload: cachedEmails.totalEmails }); + } + + if (cachedEmails.totalPages) { + dispatch({ type: 'SET_TOTAL_PAGES', payload: cachedEmails.totalPages }); + } + + // Update available mailboxes if provided + if (cachedEmails.mailboxes && cachedEmails.mailboxes.length > 0) { + dispatch({ type: 'SET_MAILBOXES', payload: cachedEmails.mailboxes }); + } + } + + // CRITICAL FIX: If this was a loadMore operation, check the result after the dispatch + if (isLoadMore) { + setTimeout(() => { + console.log(`[DEBUG-CACHE_HIT_APPEND] After ${isLoadMore ? 'APPEND' : 'SET'}, email count is now: ${state.emails.length}`); + }, 0); + } + + return; + } + + // Fetch emails from API if no cache hit + logEmailOp('API_FETCH', `Fetching emails from API: ${queryParams.toString()}, isLoadMore: ${isLoadMore}`); + console.log(`[DEBUG-API_FETCH] Fetching from /api/courrier?${queryParams.toString()}`); + const response = await fetch(`/api/courrier?${queryParams.toString()}`); + + if (!response.ok) { + // CRITICAL FIX: Try to recover from fetch errors by retrying with different pagination + if (isLoadMore && page > 1) { + logEmailOp('ERROR_RECOVERY', `Failed to fetch emails for page ${page}, attempting to recover by decrementing page`); + console.log(`[DEBUG-ERROR] API returned ${response.status} for page ${page}`); + // If we're loading more and there's an error, just decrement the page to avoid getting stuck + dispatch({ type: 'SET_PAGE', payload: page - 1 }); + dispatch({ type: 'SET_LOADING', payload: false }); + // Also reset total pages to try again + dispatch({ type: 'SET_TOTAL_PAGES', payload: page }); + return; + } + + const errorData = await response.json(); + throw new Error(errorData.error || 'Failed to fetch emails'); + } + + const data = await response.json(); + console.log(`[DEBUG-API_RESPONSE] Got response with ${data.emails?.length || 0} emails, totalPages: ${data.totalPages}, totalEmails: ${data.totalEmails}, isLoadMore: ${isLoadMore}`); + + // CRITICAL FIX: Enhanced empty results handling + if (!data.emails || data.emails.length === 0) { + console.log(`[DEBUG-EMPTY] No emails in response for page ${page}`); + // If we're at a page > 1 and got no results, the paging is off, so try again with page 1 + if (page > 1 && !isLoadMore) { + logEmailOp('EMPTY_RESULTS', `No emails returned for page ${page}, resetting to page 1`); + dispatch({ type: 'SET_PAGE', payload: 1 }); + dispatch({ type: 'SET_LOADING', payload: false }); + return; + } + + // If we're already at page 1, just update the state with no emails + if (!isLoadMore) { + logEmailOp('EMPTY_RESULTS', `No emails found in ${state.currentFolder}`); + dispatch({ type: 'SET_EMAILS', payload: [] }); + dispatch({ type: 'SET_TOTAL_EMAILS', payload: 0 }); + dispatch({ type: 'SET_TOTAL_PAGES', payload: 0 }); + } else { + // For load more, just set loading to false but keep existing emails + dispatch({ type: 'SET_LOADING', payload: false }); + } + return; + } + + // Ensure all emails have proper account ID and folder format + if (Array.isArray(data.emails)) { + // Log email dates for debugging + if (data.emails.length > 0) { + logEmailOp('EMAIL_DATES', `First few email dates before processing:`, + data.emails.slice(0, 5).map((e: any) => ({ + id: e.id.substring(0, 8), + subject: e.subject?.substring(0, 20), + date: e.date, + dateObj: new Date(e.date), + timestamp: new Date(e.date).getTime() + })) + ); + } + + data.emails.forEach((email: 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}`; + } + + // Ensure date is a valid Date object (handle strings or timestamps) + if (email.date && !(email.date instanceof Date)) { + try { + // Convert to a proper Date object if it's a string or number + const dateObj = new Date(email.date); + // Verify it's a valid date + if (!isNaN(dateObj.getTime())) { + email.date = dateObj; + } + } catch (err) { + // If conversion fails, log and use current date as fallback + console.error(`Invalid date format for email ${email.id}: ${email.date}`); + email.date = new Date(); + } + } + }); + } + + // CRITICAL FIX: Log what we're about to do + console.log(`[DEBUG-DISPATCH] About to dispatch ${isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS'} with ${data.emails?.length || 0} emails`); + + // Update state with fetched data + dispatch({ + type: isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS', + payload: Array.isArray(data.emails) ? data.emails : [] + }); + + // Double-check that we've updated the email list correctly after dispatch + setTimeout(() => { + console.log(`[DEBUG-AFTER-DISPATCH] Email count is now: ${state.emails.length}, should include the ${data.emails?.length || 0} new emails we just loaded`); + }, 0); + + if (data.totalEmails) { + dispatch({ type: 'SET_TOTAL_EMAILS', payload: data.totalEmails }); + } + + if (data.totalPages) { + dispatch({ type: 'SET_TOTAL_PAGES', payload: data.totalPages }); } // Update available mailboxes if provided - if (cachedEmails.mailboxes && cachedEmails.mailboxes.length > 0) { - dispatch({ type: 'SET_MAILBOXES', payload: cachedEmails.mailboxes }); + if (data.mailboxes && data.mailboxes.length > 0) { + dispatch({ type: 'SET_MAILBOXES', payload: data.mailboxes }); } - } - - // CRITICAL FIX: If this was a loadMore operation, check the result after the dispatch - if (isLoadMore) { - setTimeout(() => { - console.log(`[DEBUG-CACHE_HIT_APPEND] After ${isLoadMore ? 'APPEND' : 'SET'}, email count is now: ${state.emails.length}`); - }, 0); - } - - return; - } - - // Fetch emails from API if no cache hit - logEmailOp('API_FETCH', `Fetching emails from API: ${queryParams.toString()}, isLoadMore: ${isLoadMore}`); - console.log(`[DEBUG-API_FETCH] Fetching from /api/courrier?${queryParams.toString()}`); - const response = await fetch(`/api/courrier?${queryParams.toString()}`); - - if (!response.ok) { - // CRITICAL FIX: Try to recover from fetch errors by retrying with different pagination - if (isLoadMore && page > 1) { - logEmailOp('ERROR_RECOVERY', `Failed to fetch emails for page ${page}, attempting to recover by decrementing page`); - console.log(`[DEBUG-ERROR] API returned ${response.status} for page ${page}`); - // If we're loading more and there's an error, just decrement the page to avoid getting stuck - dispatch({ type: 'SET_PAGE', payload: page - 1 }); - dispatch({ type: 'SET_LOADING', payload: false }); - // Also reset total pages to try again - dispatch({ type: 'SET_TOTAL_PAGES', payload: page }); - return; - } - - const errorData = await response.json(); - throw new Error(errorData.error || 'Failed to fetch emails'); - } - - const data = await response.json(); - console.log(`[DEBUG-API_RESPONSE] Got response with ${data.emails?.length || 0} emails, totalPages: ${data.totalPages}, totalEmails: ${data.totalEmails}, isLoadMore: ${isLoadMore}`); - - // CRITICAL FIX: Enhanced empty results handling - if (!data.emails || data.emails.length === 0) { - console.log(`[DEBUG-EMPTY] No emails in response for page ${page}`); - // If we're at a page > 1 and got no results, the paging is off, so try again with page 1 - if (page > 1 && !isLoadMore) { - logEmailOp('EMPTY_RESULTS', `No emails returned for page ${page}, resetting to page 1`); - dispatch({ type: 'SET_PAGE', payload: 1 }); - dispatch({ type: 'SET_LOADING', payload: false }); - return; - } - - // If we're already at page 1, just update the state with no emails - if (!isLoadMore) { - logEmailOp('EMPTY_RESULTS', `No emails found in ${state.currentFolder}`); - dispatch({ type: 'SET_EMAILS', payload: [] }); - dispatch({ type: 'SET_TOTAL_EMAILS', payload: 0 }); - dispatch({ type: 'SET_TOTAL_PAGES', payload: 0 }); } else { - // For load more, just set loading to false but keep existing emails - dispatch({ type: 'SET_LOADING', payload: false }); + // If we can't extract a valid accountId, throw an error + throw new Error("Cannot determine account ID for loading emails"); } - return; - } - - // Ensure all emails have proper account ID and folder format - if (Array.isArray(data.emails)) { - // Log email dates for debugging - if (data.emails.length > 0) { - logEmailOp('EMAIL_DATES', `First few email dates before processing:`, - data.emails.slice(0, 5).map((e: any) => ({ - id: e.id.substring(0, 8), - subject: e.subject?.substring(0, 20), - date: e.date, - dateObj: new Date(e.date), - timestamp: new Date(e.date).getTime() - })) - ); + } else { + // Normal flow with valid accountId + const { normalizedFolder, effectiveAccountId, prefixedFolder } = + normalizeFolderAndAccount(state.currentFolder, accountId); + + logEmailOp('LOAD_EMAILS', `Loading emails for ${prefixedFolder} (account: ${effectiveAccountId}, isLoadMore: ${isLoadMore}, page: ${page})`); + + // Construct query parameters + const queryParams = new URLSearchParams({ + folder: normalizedFolder, + page: page.toString(), + perPage: perPage.toString(), + accountId: effectiveAccountId + }); + + // Debug log existing emails count + if (isLoadMore) { + console.log(`[DEBUG-PAGINATION] Loading more emails. Current page: ${page}, existing emails: ${state.emails.length}`); } - data.emails.forEach((email: Email) => { - // If email doesn't have an accountId, set it to the effective one - if (!email.accountId) { - email.accountId = effectiveAccountId; - } + // Try to get cached emails first + logEmailOp('CACHE_CHECK', `Checking cache for ${prefixedFolder}, page: ${page}`); + const cachedEmails = await getCachedEmailsWithTimeout( + session.user.id, + prefixedFolder, + page, + perPage, + 100, + effectiveAccountId + ); + + if (cachedEmails) { + logEmailOp('CACHE_HIT', `Using cached data for ${prefixedFolder}, page: ${page}, emails: ${cachedEmails.emails?.length || 0}, isLoadMore: ${isLoadMore}`); - // Ensure folder has the proper prefix format - if (email.folder && !email.folder.includes(':')) { - email.folder = `${email.accountId}:${email.folder}`; - } - - // Ensure date is a valid Date object (handle strings or timestamps) - if (email.date && !(email.date instanceof Date)) { - try { - // Convert to a proper Date object if it's a string or number - const dateObj = new Date(email.date); - // Verify it's a valid date - if (!isNaN(dateObj.getTime())) { - email.date = dateObj; - } - } catch (err) { - // If conversion fails, log and use current date as fallback - console.error(`Invalid date format for email ${email.id}: ${email.date}`); - email.date = new Date(); + // Ensure cached data has emails array property + if (Array.isArray(cachedEmails.emails)) { + // CRITICAL FIX: Double check we're using the right action type based on isLoadMore param + console.log(`[DEBUG-CACHE_HIT] Dispatching ${isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS'} with ${cachedEmails.emails.length} emails`); + + // Dispatch appropriate action based on if we're loading more - DO NOT OVERRIDE isLoadMore! + dispatch({ + type: isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS', + payload: cachedEmails.emails + }); + + // Set pagination info from cache if available + if (cachedEmails.totalEmails) { + dispatch({ type: 'SET_TOTAL_EMAILS', payload: cachedEmails.totalEmails }); + } + + if (cachedEmails.totalPages) { + dispatch({ type: 'SET_TOTAL_PAGES', payload: cachedEmails.totalPages }); + } + + // Update available mailboxes if provided + if (cachedEmails.mailboxes && cachedEmails.mailboxes.length > 0) { + dispatch({ type: 'SET_MAILBOXES', payload: cachedEmails.mailboxes }); } } + + // CRITICAL FIX: If this was a loadMore operation, check the result after the dispatch + if (isLoadMore) { + setTimeout(() => { + console.log(`[DEBUG-CACHE_HIT_APPEND] After ${isLoadMore ? 'APPEND' : 'SET'}, email count is now: ${state.emails.length}`); + }, 0); + } + + return; + } + + // Fetch emails from API if no cache hit + logEmailOp('API_FETCH', `Fetching emails from API: ${queryParams.toString()}, isLoadMore: ${isLoadMore}`); + console.log(`[DEBUG-API_FETCH] Fetching from /api/courrier?${queryParams.toString()}`); + const response = await fetch(`/api/courrier?${queryParams.toString()}`); + + if (!response.ok) { + // CRITICAL FIX: Try to recover from fetch errors by retrying with different pagination + if (isLoadMore && page > 1) { + logEmailOp('ERROR_RECOVERY', `Failed to fetch emails for page ${page}, attempting to recover by decrementing page`); + console.log(`[DEBUG-ERROR] API returned ${response.status} for page ${page}`); + // If we're loading more and there's an error, just decrement the page to avoid getting stuck + dispatch({ type: 'SET_PAGE', payload: page - 1 }); + dispatch({ type: 'SET_LOADING', payload: false }); + // Also reset total pages to try again + dispatch({ type: 'SET_TOTAL_PAGES', payload: page }); + return; + } + + const errorData = await response.json(); + throw new Error(errorData.error || 'Failed to fetch emails'); + } + + const data = await response.json(); + console.log(`[DEBUG-API_RESPONSE] Got response with ${data.emails?.length || 0} emails, totalPages: ${data.totalPages}, totalEmails: ${data.totalEmails}, isLoadMore: ${isLoadMore}`); + + // CRITICAL FIX: Enhanced empty results handling + if (!data.emails || data.emails.length === 0) { + console.log(`[DEBUG-EMPTY] No emails in response for page ${page}`); + // If we're at a page > 1 and got no results, the paging is off, so try again with page 1 + if (page > 1 && !isLoadMore) { + logEmailOp('EMPTY_RESULTS', `No emails returned for page ${page}, resetting to page 1`); + dispatch({ type: 'SET_PAGE', payload: 1 }); + dispatch({ type: 'SET_LOADING', payload: false }); + return; + } + + // If we're already at page 1, just update the state with no emails + if (!isLoadMore) { + logEmailOp('EMPTY_RESULTS', `No emails found in ${state.currentFolder}`); + dispatch({ type: 'SET_EMAILS', payload: [] }); + dispatch({ type: 'SET_TOTAL_EMAILS', payload: 0 }); + dispatch({ type: 'SET_TOTAL_PAGES', payload: 0 }); + } else { + // For load more, just set loading to false but keep existing emails + dispatch({ type: 'SET_LOADING', payload: false }); + } + return; + } + + // Ensure all emails have proper account ID and folder format + if (Array.isArray(data.emails)) { + // Log email dates for debugging + if (data.emails.length > 0) { + logEmailOp('EMAIL_DATES', `First few email dates before processing:`, + data.emails.slice(0, 5).map((e: any) => ({ + id: e.id.substring(0, 8), + subject: e.subject?.substring(0, 20), + date: e.date, + dateObj: new Date(e.date), + timestamp: new Date(e.date).getTime() + })) + ); + } + + data.emails.forEach((email: 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}`; + } + + // Ensure date is a valid Date object (handle strings or timestamps) + if (email.date && !(email.date instanceof Date)) { + try { + // Convert to a proper Date object if it's a string or number + const dateObj = new Date(email.date); + // Verify it's a valid date + if (!isNaN(dateObj.getTime())) { + email.date = dateObj; + } + } catch (err) { + // If conversion fails, log and use current date as fallback + console.error(`Invalid date format for email ${email.id}: ${email.date}`); + email.date = new Date(); + } + } + }); + } + + // CRITICAL FIX: Log what we're about to do + console.log(`[DEBUG-DISPATCH] About to dispatch ${isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS'} with ${data.emails?.length || 0} emails`); + + // Update state with fetched data + dispatch({ + type: isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS', + payload: Array.isArray(data.emails) ? data.emails : [] }); - } - - // CRITICAL FIX: Log what we're about to do - console.log(`[DEBUG-DISPATCH] About to dispatch ${isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS'} with ${data.emails?.length || 0} emails`); - - // Update state with fetched data - dispatch({ - type: isLoadMore ? 'APPEND_EMAILS' : 'SET_EMAILS', - payload: Array.isArray(data.emails) ? data.emails : [] - }); - - // Double-check that we've updated the email list correctly after dispatch - setTimeout(() => { - console.log(`[DEBUG-AFTER-DISPATCH] Email count is now: ${state.emails.length}, should include the ${data.emails?.length || 0} new emails we just loaded`); - }, 0); - - if (data.totalEmails) { - dispatch({ type: 'SET_TOTAL_EMAILS', payload: data.totalEmails }); - } - - if (data.totalPages) { - dispatch({ type: 'SET_TOTAL_PAGES', payload: data.totalPages }); - } - - // Update available mailboxes if provided - if (data.mailboxes && data.mailboxes.length > 0) { - dispatch({ type: 'SET_MAILBOXES', payload: data.mailboxes }); + + // Double-check that we've updated the email list correctly after dispatch + setTimeout(() => { + console.log(`[DEBUG-AFTER-DISPATCH] Email count is now: ${state.emails.length}, should include the ${data.emails?.length || 0} new emails we just loaded`); + }, 0); + + if (data.totalEmails) { + dispatch({ type: 'SET_TOTAL_EMAILS', payload: data.totalEmails }); + } + + if (data.totalPages) { + dispatch({ type: 'SET_TOTAL_PAGES', payload: data.totalPages }); + } + + // Update available mailboxes if provided + if (data.mailboxes && data.mailboxes.length > 0) { + dispatch({ type: 'SET_MAILBOXES', payload: data.mailboxes }); + } } } catch (err) { logEmailOp('ERROR', `Failed to load emails: ${err instanceof Error ? err.message : String(err)}`);