courrier multi account restore compose
This commit is contained in:
parent
7a33f610c6
commit
d34bf5202c
@ -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) => {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user