courrier multi account restore compose

This commit is contained in:
alma 2025-04-29 10:35:20 +02:00
parent 7a33f610c6
commit d34bf5202c
3 changed files with 250 additions and 69 deletions

View File

@ -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) => {

View File

@ -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);

View File

@ -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 (
<Button
key={folder}
key={prefixedFolder}
variant="ghost"
className={`w-full justify-start text-xs py-1 h-7 ${isSelected ? 'bg-gray-100' : ''}`}
onClick={() => onFolderChange(folder, accountId)}
onClick={() => {
console.log(`Folder button clicked: ${prefixedFolder}`);
onFolderChange(prefixedFolder, accountId);
}}
>
<div className="flex items-center w-full">
{getFolderIcon(baseFolder)}
<span className="ml-2 truncate text-gray-700">{formatFolderName(baseFolder)}</span>
{baseFolder === 'INBOX' && unreadCount[accountId]?.[folder] > 0 && (
{getFolderIcon(baseFolderName)}
<span className="ml-2 truncate text-gray-700">{formatFolderName(baseFolderName)}</span>
{folderUnreadCount > 0 && (
<span className="ml-auto bg-blue-500 text-white text-[10px] px-1.5 rounded-full">
{unreadCount[accountId][folder]}
{folderUnreadCount}
</span>
)}
</div>