courrier multi account restore compose
This commit is contained in:
parent
96fc0fe33e
commit
9b41fcbc01
@ -142,9 +142,6 @@ export default function EmailSidebar({
|
|||||||
|
|
||||||
// Improve the renderFolderButton function to ensure consistent handling
|
// Improve the renderFolderButton function to ensure consistent handling
|
||||||
const renderFolderButton = (folder: string, accountId: string) => {
|
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
|
// Ensure folder has a consistent format
|
||||||
let prefixedFolder = folder;
|
let prefixedFolder = folder;
|
||||||
let baseFolderName = folder;
|
let baseFolderName = folder;
|
||||||
@ -155,17 +152,13 @@ export default function EmailSidebar({
|
|||||||
const parts = folder.split(':');
|
const parts = folder.split(':');
|
||||||
folderAccountId = parts[0];
|
folderAccountId = parts[0];
|
||||||
baseFolderName = parts[1];
|
baseFolderName = parts[1];
|
||||||
|
|
||||||
console.log(`Folder has prefix, extracted: accountId=${folderAccountId}, baseFolder=${baseFolderName}`);
|
|
||||||
} else {
|
} else {
|
||||||
// Add account prefix if missing
|
// Add account prefix if missing
|
||||||
prefixedFolder = `${accountId}:${folder}`;
|
prefixedFolder = `${accountId}:${folder}`;
|
||||||
console.log(`Added prefix to folder: ${prefixedFolder}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show folders that belong to this account
|
// Only show folders that belong to this account
|
||||||
if (folderAccountId !== accountId) {
|
if (folderAccountId !== accountId) {
|
||||||
console.log(`Skipping folder ${folder} - belongs to different account (${folderAccountId})`);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,13 +169,6 @@ export default function EmailSidebar({
|
|||||||
(selectedFolders[accountId] === baseFolderName) ||
|
(selectedFolders[accountId] === baseFolderName) ||
|
||||||
(selectedFolders[accountId]?.split(':')[1] === 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
|
// Get unread count - check all possible formats
|
||||||
let folderUnreadCount = 0;
|
let folderUnreadCount = 0;
|
||||||
|
|
||||||
@ -191,12 +177,10 @@ export default function EmailSidebar({
|
|||||||
// Try the base folder name first
|
// Try the base folder name first
|
||||||
if (typeof unreadCount[accountId][baseFolderName] === 'number') {
|
if (typeof unreadCount[accountId][baseFolderName] === 'number') {
|
||||||
folderUnreadCount = unreadCount[accountId][baseFolderName];
|
folderUnreadCount = unreadCount[accountId][baseFolderName];
|
||||||
console.log(`Found unread count for ${baseFolderName}: ${folderUnreadCount}`);
|
|
||||||
}
|
}
|
||||||
// Then try the prefixed folder name
|
// Then try the prefixed folder name
|
||||||
else if (typeof unreadCount[accountId][prefixedFolder] === 'number') {
|
else if (typeof unreadCount[accountId][prefixedFolder] === 'number') {
|
||||||
folderUnreadCount = unreadCount[accountId][prefixedFolder];
|
folderUnreadCount = unreadCount[accountId][prefixedFolder];
|
||||||
console.log(`Found unread count for ${prefixedFolder}: ${folderUnreadCount}`);
|
|
||||||
}
|
}
|
||||||
// Finally try with uppercase/lowercase variations
|
// Finally try with uppercase/lowercase variations
|
||||||
else {
|
else {
|
||||||
@ -206,7 +190,6 @@ export default function EmailSidebar({
|
|||||||
if (key.toLowerCase() === baseFolderName.toLowerCase() ||
|
if (key.toLowerCase() === baseFolderName.toLowerCase() ||
|
||||||
key.toLowerCase() === prefixedFolder.toLowerCase()) {
|
key.toLowerCase() === prefixedFolder.toLowerCase()) {
|
||||||
folderUnreadCount = folderMap[key];
|
folderUnreadCount = folderMap[key];
|
||||||
console.log(`Found unread count with case-insensitive match ${key}: ${folderUnreadCount}`);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,13 +202,10 @@ export default function EmailSidebar({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={`w-full justify-start text-xs py-1 h-7 ${isSelected ? 'bg-gray-100' : ''}`}
|
className={`w-full justify-start text-xs py-1 h-7 ${isSelected ? 'bg-gray-100' : ''}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log(`Folder button clicked: folder=${prefixedFolder}, accountId=${accountId}, normalized=${baseFolderName}`);
|
|
||||||
|
|
||||||
// Always ensure the folder name includes the account ID prefix
|
// Always ensure the folder name includes the account ID prefix
|
||||||
const fullyPrefixedFolder = folder.includes(':') ? folder : `${accountId}:${folder}`;
|
const fullyPrefixedFolder = folder.includes(':') ? folder : `${accountId}:${folder}`;
|
||||||
|
|
||||||
// Make sure we pass the EXACT accountId parameter here, not the folder's extracted account ID
|
// 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);
|
onFolderChange(fullyPrefixedFolder, accountId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -338,63 +338,8 @@ export const useEmailState = () => {
|
|||||||
payload: { emailId, isRead, accountId: effectiveAccountId }
|
payload: { emailId, isRead, accountId: effectiveAccountId }
|
||||||
});
|
});
|
||||||
|
|
||||||
// If email is being marked as read, update the unread count for this folder
|
// NOTE: Don't update unread counts here - that's now handled by the updateUnreadCounts function
|
||||||
if (isRead && email && !email.flags.seen) {
|
// which is triggered by the email update above via the useEffect
|
||||||
// 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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make API call to update on server
|
// Make API call to update on server
|
||||||
const response = await fetch(`/api/courrier/${emailId}/mark-read`, {
|
const response = await fetch(`/api/courrier/${emailId}/mark-read`, {
|
||||||
@ -739,21 +684,46 @@ export const useEmailState = () => {
|
|||||||
// Log the unread counts for debugging
|
// Log the unread counts for debugging
|
||||||
logEmailOp('UNREAD_COUNTS', 'Updated unread counts:', tempUnreadMap);
|
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(tempUnreadMap).forEach(([accountId, folderCounts]) => {
|
||||||
Object.entries(folderCounts).forEach(([folder, count]) => {
|
Object.entries(folderCounts).forEach(([folder, count]) => {
|
||||||
dispatch({
|
if (state.unreadCountMap[accountId]?.[folder] !== count) {
|
||||||
type: 'UPDATE_UNREAD_COUNT',
|
hasChanged = true;
|
||||||
payload: { accountId, folder, count }
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [state.emails, state.accounts, state.unreadCountMap, logEmailOp]);
|
|
||||||
|
|
||||||
// Call updateUnreadCounts when emails change
|
// 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 relevant state changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateUnreadCounts();
|
// Only update unread counts when emails or flag status changes
|
||||||
}, [state.emails, updateUnreadCounts]);
|
// 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
|
// Fetch unread counts from API
|
||||||
const fetchUnreadCounts = useCallback(async () => {
|
const fetchUnreadCounts = useCallback(async () => {
|
||||||
@ -774,43 +744,80 @@ export const useEmailState = () => {
|
|||||||
if (data.counts && typeof data.counts === 'object') {
|
if (data.counts && typeof data.counts === 'object') {
|
||||||
logEmailOp('UNREAD_API', 'Received unread counts from API', data.counts);
|
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
|
// Update unread counts in state
|
||||||
Object.entries(data.counts).forEach(([accountId, folderCounts]: [string, any]) => {
|
Object.entries(data.counts).forEach(([accountId, folderCounts]: [string, any]) => {
|
||||||
|
if (!mergedCounts[accountId]) {
|
||||||
|
mergedCounts[accountId] = {};
|
||||||
|
}
|
||||||
|
|
||||||
Object.entries(folderCounts).forEach(([folder, count]: [string, any]) => {
|
Object.entries(folderCounts).forEach(([folder, count]: [string, any]) => {
|
||||||
dispatch({
|
mergedCounts[accountId][folder] = typeof count === 'number' ? count : 0;
|
||||||
type: 'UPDATE_UNREAD_COUNT',
|
|
||||||
payload: {
|
|
||||||
accountId,
|
|
||||||
folder,
|
|
||||||
count: 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) {
|
} catch (error) {
|
||||||
logEmailOp('ERROR', `Failed to fetch unread counts: ${error instanceof Error ? error.message : String(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
|
// 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(() => {
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
|
|
||||||
if (state.accounts.length > 0) {
|
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)
|
// Set up periodic refresh of unread counts (every 60 seconds)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
|
|
||||||
const intervalId = setInterval(() => {
|
const intervalId = setInterval(() => {
|
||||||
if (state.accounts.length > 0) {
|
if (!isMounted || state.accounts.length === 0) return;
|
||||||
fetchUnreadCounts();
|
|
||||||
}
|
fetchUnreadCounts().catch(error => {
|
||||||
|
if (!isMounted) return;
|
||||||
|
logEmailOp('ERROR', `Periodic unread count fetch failed: ${String(error)}`);
|
||||||
|
});
|
||||||
}, 60000);
|
}, 60000);
|
||||||
|
|
||||||
return () => clearInterval(intervalId);
|
return () => {
|
||||||
}, [state.accounts, fetchUnreadCounts]);
|
isMounted = false;
|
||||||
|
clearInterval(intervalId);
|
||||||
|
};
|
||||||
|
}, [state.accounts.length, fetchUnreadCounts, logEmailOp]);
|
||||||
|
|
||||||
// Return all state values and actions
|
// Return all state values and actions
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -47,6 +47,7 @@ export type EmailAction =
|
|||||||
| { type: 'SET_TOTAL_EMAILS', payload: number }
|
| { type: 'SET_TOTAL_EMAILS', payload: number }
|
||||||
| { type: 'SET_MAILBOXES', payload: string[] }
|
| { type: 'SET_MAILBOXES', payload: string[] }
|
||||||
| { type: 'UPDATE_UNREAD_COUNT', payload: { accountId: string, folder: string, count: number } }
|
| { type: 'UPDATE_UNREAD_COUNT', payload: { accountId: string, folder: string, count: number } }
|
||||||
|
| { type: 'SET_UNREAD_COUNTS', payload: Record<string, Record<string, number>> }
|
||||||
| { type: 'TOGGLE_SHOW_FOLDERS', payload: boolean }
|
| { type: 'TOGGLE_SHOW_FOLDERS', payload: boolean }
|
||||||
| { type: 'MARK_EMAIL_AS_READ', payload: { emailId: string, isRead: boolean, accountId?: string } };
|
| { 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':
|
case 'TOGGLE_SHOW_FOLDERS':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user