diff --git a/components/email/EmailSidebar.tsx b/components/email/EmailSidebar.tsx index bc6166d3..b5fbf30b 100644 --- a/components/email/EmailSidebar.tsx +++ b/components/email/EmailSidebar.tsx @@ -142,9 +142,6 @@ export default function EmailSidebar({ // Improve the renderFolderButton function to ensure consistent handling const renderFolderButton = (folder: string, accountId: string) => { - // Add extra logging to debug folder rendering issues - console.log(`Rendering folder button: ${folder} for account: ${accountId}`); - // Ensure folder has a consistent format let prefixedFolder = folder; let baseFolderName = folder; @@ -155,17 +152,13 @@ export default function EmailSidebar({ 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) { - console.log(`Skipping folder ${folder} - belongs to different account (${folderAccountId})`); return null; } @@ -176,13 +169,6 @@ export default function EmailSidebar({ (selectedFolders[accountId] === baseFolderName) || (selectedFolders[accountId]?.split(':')[1] === baseFolderName); - if (isSelected) { - console.log(`Folder ${baseFolderName} is selected for account ${accountId}`); - } - - // Debug unread count structure - console.log('Unread count map structure:', unreadCount); - // Get unread count - check all possible formats let folderUnreadCount = 0; @@ -191,12 +177,10 @@ export default function EmailSidebar({ // Try the base folder name first if (typeof unreadCount[accountId][baseFolderName] === 'number') { folderUnreadCount = unreadCount[accountId][baseFolderName]; - console.log(`Found unread count for ${baseFolderName}: ${folderUnreadCount}`); } // Then try the prefixed folder name else if (typeof unreadCount[accountId][prefixedFolder] === 'number') { folderUnreadCount = unreadCount[accountId][prefixedFolder]; - console.log(`Found unread count for ${prefixedFolder}: ${folderUnreadCount}`); } // Finally try with uppercase/lowercase variations else { @@ -206,7 +190,6 @@ export default function EmailSidebar({ if (key.toLowerCase() === baseFolderName.toLowerCase() || key.toLowerCase() === prefixedFolder.toLowerCase()) { folderUnreadCount = folderMap[key]; - console.log(`Found unread count with case-insensitive match ${key}: ${folderUnreadCount}`); break; } } @@ -219,13 +202,10 @@ export default function EmailSidebar({ variant="ghost" className={`w-full justify-start text-xs py-1 h-7 ${isSelected ? 'bg-gray-100' : ''}`} onClick={() => { - console.log(`Folder button clicked: folder=${prefixedFolder}, accountId=${accountId}, normalized=${baseFolderName}`); - // Always ensure the folder name includes the account ID prefix const fullyPrefixedFolder = folder.includes(':') ? folder : `${accountId}:${folder}`; // Make sure we pass the EXACT accountId parameter here, not the folder's extracted account ID - console.log(`Calling onFolderChange with folder=${fullyPrefixedFolder}, accountId=${accountId}`); onFolderChange(fullyPrefixedFolder, accountId); }} > diff --git a/hooks/use-email-state.ts b/hooks/use-email-state.ts index f85e71fc..58d61c06 100644 --- a/hooks/use-email-state.ts +++ b/hooks/use-email-state.ts @@ -338,63 +338,8 @@ export const useEmailState = () => { payload: { emailId, isRead, accountId: effectiveAccountId } }); - // If email is being marked as read, update the unread count for this folder - if (isRead && email && !email.flags.seen) { - // Get current count for this folder - const currentCount = state.unreadCountMap[effectiveAccountId]?.[normalizedFolder] || 0; - if (currentCount > 0) { - // Decrement the unread count (minimum 0) - dispatch({ - type: 'UPDATE_UNREAD_COUNT', - payload: { - accountId: effectiveAccountId, - folder: normalizedFolder, - count: Math.max(0, currentCount - 1) - } - }); - - // Also update the prefixed version if needed - const prefixedFolder = `${effectiveAccountId}:${normalizedFolder}`; - const prefixedCount = state.unreadCountMap[effectiveAccountId]?.[prefixedFolder] || 0; - if (prefixedCount > 0) { - dispatch({ - type: 'UPDATE_UNREAD_COUNT', - payload: { - accountId: effectiveAccountId, - folder: prefixedFolder, - count: Math.max(0, prefixedCount - 1) - } - }); - } - } - } - // If email is being marked as unread, increment the count - else if (!isRead && email && email.flags.seen) { - // Get current count for this folder - const currentCount = state.unreadCountMap[effectiveAccountId]?.[normalizedFolder] || 0; - - // Increment the unread count - dispatch({ - type: 'UPDATE_UNREAD_COUNT', - payload: { - accountId: effectiveAccountId, - folder: normalizedFolder, - count: currentCount + 1 - } - }); - - // Also update the prefixed version - const prefixedFolder = `${effectiveAccountId}:${normalizedFolder}`; - const prefixedCount = state.unreadCountMap[effectiveAccountId]?.[prefixedFolder] || 0; - dispatch({ - type: 'UPDATE_UNREAD_COUNT', - payload: { - accountId: effectiveAccountId, - folder: prefixedFolder, - count: prefixedCount + 1 - } - }); - } + // NOTE: Don't update unread counts here - that's now handled by the updateUnreadCounts function + // which is triggered by the email update above via the useEffect // Make API call to update on server const response = await fetch(`/api/courrier/${emailId}/mark-read`, { @@ -739,21 +684,46 @@ export const useEmailState = () => { // Log the unread counts for debugging logEmailOp('UNREAD_COUNTS', 'Updated unread counts:', tempUnreadMap); - // Update the unread count map in the state + // Check if the unread counts have actually changed before updating state + let hasChanged = false; + + // Compare with current unread count map Object.entries(tempUnreadMap).forEach(([accountId, folderCounts]) => { Object.entries(folderCounts).forEach(([folder, count]) => { - dispatch({ - type: 'UPDATE_UNREAD_COUNT', - payload: { accountId, folder, count } - }); + if (state.unreadCountMap[accountId]?.[folder] !== count) { + hasChanged = true; + } }); }); - }, [state.emails, state.accounts, state.unreadCountMap, logEmailOp]); + + // Only update if there are actual changes to avoid infinite updates + if (hasChanged) { + // Create a single dispatch to update all counts at once + const updatedMap = { ...state.unreadCountMap }; + + Object.entries(tempUnreadMap).forEach(([accountId, folderCounts]) => { + updatedMap[accountId] = { ...updatedMap[accountId] || {}, ...folderCounts }; + }); + + // Replace the entire unread count map with one action + dispatch({ + type: 'SET_UNREAD_COUNTS', + payload: updatedMap + }); + } + }, [state.emails, state.accounts, state.unreadCountMap, dispatch, logEmailOp]); - // Call updateUnreadCounts when emails change + // Call updateUnreadCounts when relevant state changes useEffect(() => { - updateUnreadCounts(); - }, [state.emails, updateUnreadCounts]); + // Only update unread counts when emails or flag status changes + // NOT when the unreadCountMap itself changes (that would cause infinite loop) + const updateCountsWithDebounce = setTimeout(() => { + updateUnreadCounts(); + }, 300); // Debounce to handle multiple email updates + + return () => clearTimeout(updateCountsWithDebounce); + // Deliberately exclude unreadCountMap to prevent infinite loops + }, [updateUnreadCounts, state.emails]); // Fetch unread counts from API const fetchUnreadCounts = useCallback(async () => { @@ -774,43 +744,80 @@ export const useEmailState = () => { if (data.counts && typeof data.counts === 'object') { logEmailOp('UNREAD_API', 'Received unread counts from API', data.counts); + // Merge with existing unread counts rather than replacing + const mergedCounts = { ...state.unreadCountMap }; + // Update unread counts in state Object.entries(data.counts).forEach(([accountId, folderCounts]: [string, any]) => { + if (!mergedCounts[accountId]) { + mergedCounts[accountId] = {}; + } + Object.entries(folderCounts).forEach(([folder, count]: [string, any]) => { - dispatch({ - type: 'UPDATE_UNREAD_COUNT', - payload: { - accountId, - folder, - count: typeof count === 'number' ? count : 0 - } - }); + mergedCounts[accountId][folder] = typeof count === 'number' ? count : 0; }); }); + + // Only dispatch if there are actual changes + let hasChanges = false; + + Object.entries(mergedCounts).forEach(([accountId, folderCounts]) => { + Object.entries(folderCounts).forEach(([folder, count]) => { + if (state.unreadCountMap[accountId]?.[folder] !== count) { + hasChanges = true; + } + }); + }); + + if (hasChanges) { + dispatch({ + type: 'SET_UNREAD_COUNTS', + payload: mergedCounts + }); + } } } catch (error) { logEmailOp('ERROR', `Failed to fetch unread counts: ${error instanceof Error ? error.message : String(error)}`); // Don't show toast for this error as it's not critical } - }, [session?.user?.id, dispatch, logEmailOp]); + }, [session?.user?.id, state.unreadCountMap, dispatch, logEmailOp]); - // Fetch unread counts when accounts are loaded or when folders change + // Fetch unread counts when accounts are loaded useEffect(() => { + let isMounted = true; + if (state.accounts.length > 0) { - fetchUnreadCounts(); + fetchUnreadCounts().then(() => { + if (!isMounted) return; + }).catch(error => { + if (!isMounted) return; + logEmailOp('ERROR', `Background unread count fetch failed: ${String(error)}`); + }); } - }, [state.accounts, state.currentFolder, fetchUnreadCounts]); + + return () => { + isMounted = false; + }; + }, [state.accounts.length, fetchUnreadCounts, logEmailOp]); // Set up periodic refresh of unread counts (every 60 seconds) useEffect(() => { + let isMounted = true; + const intervalId = setInterval(() => { - if (state.accounts.length > 0) { - fetchUnreadCounts(); - } + if (!isMounted || state.accounts.length === 0) return; + + fetchUnreadCounts().catch(error => { + if (!isMounted) return; + logEmailOp('ERROR', `Periodic unread count fetch failed: ${String(error)}`); + }); }, 60000); - return () => clearInterval(intervalId); - }, [state.accounts, fetchUnreadCounts]); + return () => { + isMounted = false; + clearInterval(intervalId); + }; + }, [state.accounts.length, fetchUnreadCounts, logEmailOp]); // Return all state values and actions return { diff --git a/lib/reducers/emailReducer.ts b/lib/reducers/emailReducer.ts index e575f757..f869ae11 100644 --- a/lib/reducers/emailReducer.ts +++ b/lib/reducers/emailReducer.ts @@ -47,6 +47,7 @@ export type EmailAction = | { type: 'SET_TOTAL_EMAILS', payload: number } | { type: 'SET_MAILBOXES', payload: string[] } | { type: 'UPDATE_UNREAD_COUNT', payload: { accountId: string, folder: string, count: number } } + | { type: 'SET_UNREAD_COUNTS', payload: Record> } | { type: 'TOGGLE_SHOW_FOLDERS', payload: boolean } | { type: 'MARK_EMAIL_AS_READ', payload: { emailId: string, isRead: boolean, accountId?: string } }; @@ -332,6 +333,12 @@ export function emailReducer(state: EmailState, action: EmailAction): EmailState }; } + case 'SET_UNREAD_COUNTS': + return { + ...state, + unreadCountMap: action.payload + }; + case 'TOGGLE_SHOW_FOLDERS': return { ...state,