courrier multi account restore compose
This commit is contained in:
parent
a9e85ca5f2
commit
952144c03c
@ -42,7 +42,6 @@ import ComposeEmail from '@/components/email/ComposeEmail';
|
|||||||
import { DeleteConfirmDialog } from '@/components/email/EmailDialogs';
|
import { DeleteConfirmDialog } from '@/components/email/EmailDialogs';
|
||||||
|
|
||||||
// Import the custom hooks
|
// Import the custom hooks
|
||||||
import { useCourrier, EmailData } from '@/hooks/use-courrier';
|
|
||||||
import { useEmailState } from '@/hooks/use-email-state';
|
import { useEmailState } from '@/hooks/use-email-state';
|
||||||
|
|
||||||
// Import the prefetching function
|
// Import the prefetching function
|
||||||
@ -51,6 +50,9 @@ import { prefetchFolderEmails } from '@/lib/services/prefetch-service';
|
|||||||
// Import Account type from the reducer
|
// Import Account type from the reducer
|
||||||
import { Account } from '@/lib/reducers/emailReducer';
|
import { Account } from '@/lib/reducers/emailReducer';
|
||||||
|
|
||||||
|
// Add the missing EmailData import from use-courrier
|
||||||
|
import { EmailData } from '@/hooks/use-courrier';
|
||||||
|
|
||||||
// Simplified version for this component
|
// Simplified version for this component
|
||||||
function SimplifiedLoadingFix() {
|
function SimplifiedLoadingFix() {
|
||||||
// In production, don't render anything
|
// In production, don't render anything
|
||||||
@ -137,59 +139,54 @@ export default function CourrierPage() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
// Get all the email functionality from the hook
|
// Replace useCourrier with useEmailState
|
||||||
const {
|
const {
|
||||||
emails = [],
|
// State values
|
||||||
|
accounts,
|
||||||
|
selectedAccount,
|
||||||
|
selectedFolders,
|
||||||
|
currentFolder,
|
||||||
|
emails,
|
||||||
selectedEmail,
|
selectedEmail,
|
||||||
selectedEmailIds,
|
selectedEmailIds,
|
||||||
currentFolder,
|
|
||||||
mailboxes,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
isSending,
|
|
||||||
error,
|
error,
|
||||||
searchQuery,
|
|
||||||
page,
|
page,
|
||||||
totalPages,
|
totalPages,
|
||||||
totalEmails,
|
totalEmails,
|
||||||
|
mailboxes,
|
||||||
|
unreadCountMap,
|
||||||
|
showFolders,
|
||||||
|
|
||||||
|
// Actions
|
||||||
loadEmails,
|
loadEmails,
|
||||||
handleEmailSelect,
|
handleEmailSelect,
|
||||||
markEmailAsRead,
|
|
||||||
toggleStarred,
|
|
||||||
sendEmail,
|
|
||||||
deleteEmails,
|
|
||||||
toggleEmailSelection,
|
toggleEmailSelection,
|
||||||
toggleSelectAll,
|
toggleSelectAll,
|
||||||
|
markEmailAsRead,
|
||||||
|
toggleStarred,
|
||||||
changeFolder,
|
changeFolder,
|
||||||
|
deleteEmails,
|
||||||
|
sendEmail,
|
||||||
searchEmails,
|
searchEmails,
|
||||||
formatEmailForAction,
|
formatEmailForAction,
|
||||||
setPage,
|
setPage,
|
||||||
setEmails,
|
setEmails,
|
||||||
} = useCourrier();
|
selectAccount,
|
||||||
|
handleLoadMore
|
||||||
|
} = useEmailState();
|
||||||
|
|
||||||
// UI state
|
// UI state (keeping only what's still needed)
|
||||||
const [showComposeModal, setShowComposeModal] = useState(false);
|
const [showComposeModal, setShowComposeModal] = useState(false);
|
||||||
const [composeType, setComposeType] = useState<'new' | 'reply' | 'reply-all' | 'forward'>('new');
|
const [composeType, setComposeType] = useState<'new' | 'reply' | 'reply-all' | 'forward'>('new');
|
||||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
const [showLoginNeeded, setShowLoginNeeded] = useState(false);
|
const [showLoginNeeded, setShowLoginNeeded] = useState(false);
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||||
const [accountsDropdownOpen, setAccountsDropdownOpen] = useState(true);
|
|
||||||
const [currentView, setCurrentView] = useState('INBOX');
|
|
||||||
const [unreadCount, setUnreadCount] = useState(0);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [prefetchStarted, setPrefetchStarted] = useState(false);
|
const [prefetchStarted, setPrefetchStarted] = useState(false);
|
||||||
const [showFolders, setShowFolders] = useState(false);
|
|
||||||
const [showAddAccountForm, setShowAddAccountForm] = useState(false);
|
const [showAddAccountForm, setShowAddAccountForm] = useState(false);
|
||||||
|
|
||||||
// Email accounts for the sidebar
|
|
||||||
const [accounts, setAccounts] = useState<Account[]>([
|
|
||||||
{ id: 'loading-account', name: 'Loading...', email: '', color: 'bg-blue-500', folders: [] }
|
|
||||||
]);
|
|
||||||
const [selectedAccount, setSelectedAccount] = useState<Account | null>(null);
|
|
||||||
|
|
||||||
// Track selected folder per account
|
|
||||||
const [selectedFolders, setSelectedFolders] = useState<Record<string, string>>({});
|
|
||||||
|
|
||||||
// Add state for modals/dialogs
|
// Add state for modals/dialogs
|
||||||
const [showEditModal, setShowEditModal] = useState(false);
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||||
@ -199,47 +196,14 @@ export default function CourrierPage() {
|
|||||||
const [editLoading, setEditLoading] = useState(false);
|
const [editLoading, setEditLoading] = useState(false);
|
||||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||||
|
|
||||||
// Add a type definition for the unreadCount structure to match what EmailSidebar expects
|
// Use the reducer-managed values directly instead of tracked separately
|
||||||
const [unreadCountMap, setUnreadCountMap] = useState<Record<string, Record<string, number>>>({});
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [unreadCount, setUnreadCount] = useState(0);
|
||||||
|
|
||||||
// Calculate unread count for each account and folder
|
// Calculate unread count for the selected folder
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Create a map to store unread counts per account and folder
|
|
||||||
const accountFolderUnreadCounts: Record<string, Record<string, number>> = {};
|
|
||||||
|
|
||||||
// Initialize counts for all accounts and folders
|
|
||||||
accounts.forEach(account => {
|
|
||||||
if (account.id !== 'loading-account') {
|
|
||||||
accountFolderUnreadCounts[account.id.toString()] = {};
|
|
||||||
account.folders.forEach(folder => {
|
|
||||||
accountFolderUnreadCounts[account.id.toString()][folder] = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Count unread emails for each account and folder
|
|
||||||
(emails || []).forEach(email => {
|
|
||||||
// Check if email is unread based on flags
|
|
||||||
const isUnread = email.flags && !email.flags.seen;
|
|
||||||
|
|
||||||
// Count unread emails for the specific account and folder
|
|
||||||
if (isUnread && email.accountId && email.folder) {
|
|
||||||
if (!accountFolderUnreadCounts[email.accountId]) {
|
|
||||||
accountFolderUnreadCounts[email.accountId] = {};
|
|
||||||
}
|
|
||||||
if (!accountFolderUnreadCounts[email.accountId][email.folder]) {
|
|
||||||
accountFolderUnreadCounts[email.accountId][email.folder] = 0;
|
|
||||||
}
|
|
||||||
accountFolderUnreadCounts[email.accountId][email.folder]++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update the unreadCountMap state
|
|
||||||
setUnreadCountMap(accountFolderUnreadCounts);
|
|
||||||
|
|
||||||
// Update the unread count for the selected account and folder
|
|
||||||
if (selectedAccount && selectedAccount.id !== 'loading-account') {
|
if (selectedAccount && selectedAccount.id !== 'loading-account') {
|
||||||
const folderCounts = accountFolderUnreadCounts[selectedAccount.id.toString()];
|
const folderCounts = unreadCountMap[selectedAccount.id.toString()];
|
||||||
if (folderCounts) {
|
if (folderCounts) {
|
||||||
setUnreadCount(folderCounts[currentFolder] || 0);
|
setUnreadCount(folderCounts[currentFolder] || 0);
|
||||||
} else {
|
} else {
|
||||||
@ -248,55 +212,12 @@ export default function CourrierPage() {
|
|||||||
} else {
|
} else {
|
||||||
// For 'loading-account', sum up all unread counts for the current folder
|
// For 'loading-account', sum up all unread counts for the current folder
|
||||||
let totalUnread = 0;
|
let totalUnread = 0;
|
||||||
Object.values(accountFolderUnreadCounts).forEach((folderCounts) => {
|
Object.values(unreadCountMap).forEach((folderCounts) => {
|
||||||
totalUnread += folderCounts[currentFolder] || 0;
|
totalUnread += folderCounts[currentFolder] || 0;
|
||||||
});
|
});
|
||||||
setUnreadCount(totalUnread);
|
setUnreadCount(totalUnread);
|
||||||
}
|
}
|
||||||
|
}, [unreadCountMap, selectedAccount, currentFolder]);
|
||||||
// Log the counts for debugging
|
|
||||||
console.log('Unread counts per account and folder:',
|
|
||||||
Object.fromEntries(
|
|
||||||
Object.entries(accountFolderUnreadCounts)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, [emails, selectedAccount, currentFolder, accounts]);
|
|
||||||
|
|
||||||
// Ensure accounts section is never empty
|
|
||||||
useEffect(() => {
|
|
||||||
// If accounts array becomes empty (bug), restore default accounts
|
|
||||||
if (!accounts || accounts.length === 0) {
|
|
||||||
console.warn('Accounts array is empty, restoring defaults');
|
|
||||||
setAccounts([
|
|
||||||
{ id: 'loading-account', name: 'Loading...', email: '', color: 'bg-blue-500', folders: mailboxes }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}, [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
|
// Initialize session and start prefetching
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -322,7 +243,7 @@ export default function CourrierPage() {
|
|||||||
// Call the session API to check email credentials and start prefetching
|
// Call the session API to check email credentials and start prefetching
|
||||||
logEmailOp('SESSION', 'Fetching session data from API');
|
logEmailOp('SESSION', 'Fetching session data from API');
|
||||||
const response = await fetch('/api/courrier/session', {
|
const response = await fetch('/api/courrier/session', {
|
||||||
credentials: 'include', // Ensure cookies are sent
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}
|
}
|
||||||
@ -337,7 +258,6 @@ export default function CourrierPage() {
|
|||||||
return initSession();
|
return initSession();
|
||||||
} else {
|
} else {
|
||||||
console.error('Max retries reached for session request');
|
console.error('Max retries reached for session request');
|
||||||
// Instead of throwing, redirect to login
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -348,135 +268,77 @@ export default function CourrierPage() {
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Log the raw API response to inspect structure
|
// Log session response
|
||||||
console.log('[DEBUG] Raw session API response:', JSON.stringify(data, null, 2));
|
console.log('[DEBUG] Session API response details:', {
|
||||||
|
authenticated: data.authenticated,
|
||||||
|
hasEmailCredentials: data.hasEmailCredentials,
|
||||||
|
accountsCount: data.allAccounts?.length || 0
|
||||||
|
});
|
||||||
|
|
||||||
// Add detailed logging to inspect the accounts and folders structure
|
// Process accounts if authenticated
|
||||||
console.log('=== SESSION API RESPONSE DETAILED INSPECTION ===');
|
if (data.authenticated && data.hasEmailCredentials) {
|
||||||
console.log('Session authenticated:', data.authenticated);
|
setPrefetchStarted(Boolean(data.prefetchStarted));
|
||||||
console.log('Has email credentials:', data.hasEmailCredentials);
|
|
||||||
console.log('Primary email:', data.email);
|
let updatedAccounts: Account[] = [];
|
||||||
console.log('Redis status:', data.redisStatus);
|
|
||||||
|
// Process multiple accounts
|
||||||
// Log mailboxes structure - what the frontend used previously
|
if (data.allAccounts && Array.isArray(data.allAccounts) && data.allAccounts.length > 0) {
|
||||||
console.log('=== MAILBOXES STRUCTURE (OLD API FORMAT) ===');
|
console.log('[DEBUG] Processing multiple accounts:', data.allAccounts.length);
|
||||||
console.log('Global mailboxes exists:', !!data.mailboxes);
|
|
||||||
console.log('Global mailboxes is array:', Array.isArray(data.mailboxes));
|
|
||||||
console.log('Global mailboxes:', data.mailboxes);
|
|
||||||
|
|
||||||
// Log allAccounts structure - the new per-account folders approach
|
|
||||||
console.log('=== ALL ACCOUNTS STRUCTURE (NEW API FORMAT) ===');
|
|
||||||
console.log('allAccounts exists:', !!data.allAccounts);
|
|
||||||
console.log('allAccounts is array:', Array.isArray(data.allAccounts));
|
|
||||||
console.log('allAccounts length:', data.allAccounts?.length || 0);
|
|
||||||
|
|
||||||
// Inspect each account's structure
|
|
||||||
if (data.authenticated) {
|
|
||||||
if (data.hasEmailCredentials) {
|
|
||||||
console.log('Session initialized, prefetch status:', data.prefetchStarted ? 'running' : 'not started');
|
|
||||||
setPrefetchStarted(Boolean(data.prefetchStarted));
|
|
||||||
|
|
||||||
let updatedAccounts: Account[] = [];
|
data.allAccounts.forEach((account: any) => {
|
||||||
|
// Use exact folders from IMAP
|
||||||
// Check if we have multiple accounts returned
|
const accountFolders = (account.folders && Array.isArray(account.folders))
|
||||||
if (data.allAccounts && Array.isArray(data.allAccounts) && data.allAccounts.length > 0) {
|
? account.folders
|
||||||
console.log('[DEBUG] Multiple accounts found:', data.allAccounts.length);
|
: [];
|
||||||
|
|
||||||
// Add all accounts from the API response
|
// Ensure folder names have account prefix
|
||||||
data.allAccounts.forEach((account: any) => {
|
const validFolders = accountFolders.map((folder: string) => {
|
||||||
console.log('[DEBUG] Processing account:', {
|
if (!folder.includes(':')) {
|
||||||
id: account.id,
|
return `${account.id}:${folder}`;
|
||||||
email: account.email,
|
}
|
||||||
folders: account.folders
|
return folder;
|
||||||
});
|
|
||||||
|
|
||||||
// Use exact folders from IMAP without any mapping
|
|
||||||
const accountFolders = (account.folders && Array.isArray(account.folders))
|
|
||||||
? account.folders
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// Keep the account prefix in folder names
|
|
||||||
const validFolders = accountFolders.map((folder: string) => {
|
|
||||||
// If folder doesn't have account prefix, add it
|
|
||||||
if (!folder.includes(':')) {
|
|
||||||
return `${account.id}:${folder}`;
|
|
||||||
}
|
|
||||||
return folder;
|
|
||||||
});
|
|
||||||
|
|
||||||
updatedAccounts.push({
|
|
||||||
id: account.id,
|
|
||||||
name: account.display_name || account.email,
|
|
||||||
email: account.email,
|
|
||||||
color: colorPalette[(updatedAccounts.length - 1) % colorPalette.length],
|
|
||||||
folders: validFolders
|
|
||||||
});
|
|
||||||
console.log(`[DEBUG] Added account with folders:`, {
|
|
||||||
id: account.id,
|
|
||||||
email: account.email,
|
|
||||||
folders: validFolders
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// Fallback to single account if allAccounts is not available
|
|
||||||
console.log(`[DEBUG] Fallback to single account: ${data.email}`);
|
|
||||||
|
|
||||||
// Use exact folders from IMAP if available
|
|
||||||
const folderList = (data.mailboxes && data.mailboxes.length > 0) ?
|
|
||||||
data.mailboxes : [];
|
|
||||||
|
|
||||||
updatedAccounts.push({
|
updatedAccounts.push({
|
||||||
id: 'default-account',
|
id: account.id,
|
||||||
name: data.displayName || data.email,
|
name: account.display_name || account.email,
|
||||||
email: data.email,
|
email: account.email,
|
||||||
color: colorPalette[(updatedAccounts.length - 1) % colorPalette.length],
|
color: colorPalette[(updatedAccounts.length) % colorPalette.length],
|
||||||
folders: folderList
|
folders: validFolders
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
// Update accounts state
|
|
||||||
setAccounts(updatedAccounts);
|
|
||||||
console.log('[DEBUG] Updated accounts:', updatedAccounts);
|
|
||||||
|
|
||||||
// Auto-select the first account if available
|
|
||||||
if (updatedAccounts.length > 0) {
|
|
||||||
// Get first real account (index 0)
|
|
||||||
const firstAccount = updatedAccounts[0];
|
|
||||||
console.log('Auto-selecting first account:', firstAccount);
|
|
||||||
|
|
||||||
// Trigger account selection
|
|
||||||
setTimeout(() => {
|
|
||||||
// Use the same account selection flow we use when user clicks
|
|
||||||
if (firstAccount) {
|
|
||||||
// This will handle showing folders and loading emails
|
|
||||||
handleAccountSelect(firstAccount);
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// User is authenticated but doesn't have email credentials
|
// Fallback to single account if allAccounts is not available
|
||||||
setShowLoginNeeded(true);
|
const folderList = (data.mailboxes && data.mailboxes.length > 0) ?
|
||||||
|
data.mailboxes : [];
|
||||||
|
|
||||||
|
updatedAccounts.push({
|
||||||
|
id: 'default-account',
|
||||||
|
name: data.displayName || data.email,
|
||||||
|
email: data.email,
|
||||||
|
color: colorPalette[0],
|
||||||
|
folders: folderList
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Preload first page of emails for faster initial rendering
|
|
||||||
if (session?.user?.id) {
|
|
||||||
await loadEmails();
|
|
||||||
|
|
||||||
// If the user hasn't opened this page recently, trigger a background refresh
|
// Update accounts state using our reducer actions
|
||||||
if (data.lastVisit && Date.now() - data.lastVisit > 5 * 60 * 1000) {
|
// First, set the accounts
|
||||||
// It's been more than 5 minutes, refresh in background
|
setEmails([]); // Clear any existing emails first
|
||||||
try {
|
|
||||||
const refreshResponse = await fetch('/api/courrier/refresh', {
|
// Use our reducer actions instead of setState
|
||||||
method: 'POST',
|
setAccounts(updatedAccounts);
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ folder: currentFolder })
|
// Auto-select the first account if available
|
||||||
});
|
if (updatedAccounts.length > 0) {
|
||||||
console.log('Background refresh triggered');
|
const firstAccount = updatedAccounts[0];
|
||||||
} catch (error) {
|
console.log('Auto-selecting first account:', firstAccount);
|
||||||
console.error('Failed to trigger background refresh', error);
|
|
||||||
}
|
// Use our new selectAccount function which handles state atomically
|
||||||
|
selectAccount(firstAccount);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// User is authenticated but doesn't have email credentials
|
||||||
|
setShowLoginNeeded(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error initializing session:', error);
|
console.error('Error initializing session:', error);
|
||||||
@ -494,7 +356,7 @@ export default function CourrierPage() {
|
|||||||
return () => {
|
return () => {
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
};
|
};
|
||||||
}, [session?.user?.id, loadEmails]);
|
}, [session?.user?.id, setEmails, selectAccount]);
|
||||||
|
|
||||||
// Helper to get folder icons
|
// Helper to get folder icons
|
||||||
const getFolderIcon = (folder: string) => {
|
const getFolderIcon = (folder: string) => {
|
||||||
@ -519,35 +381,51 @@ export default function CourrierPage() {
|
|||||||
|
|
||||||
// Helper to format folder names
|
// Helper to format folder names
|
||||||
const formatFolderName = (folder: string) => {
|
const formatFolderName = (folder: string) => {
|
||||||
return folder.charAt(0).toUpperCase() + folder.slice(1).toLowerCase();
|
// Extract base folder name if prefixed
|
||||||
|
const baseFolderName = folder.includes(':') ? folder.split(':')[1] : folder;
|
||||||
|
return baseFolderName.charAt(0).toUpperCase() + baseFolderName.slice(1).toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle actions - replace with useReducer-based functions
|
||||||
|
const handleMailboxChange = (folder: string, accountId?: string) => {
|
||||||
|
// Simply call our new changeFolder function which handles everything atomically
|
||||||
|
setLoading(true);
|
||||||
|
changeFolder(folder, accountId)
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle account selection - replace with reducer-based function
|
||||||
|
const handleAccountSelect = (account: Account) => {
|
||||||
|
// Simply call our new selectAccount function which handles everything atomically
|
||||||
|
setLoading(true);
|
||||||
|
selectAccount(account);
|
||||||
|
setTimeout(() => setLoading(false), 300); // Give UI time to update
|
||||||
|
};
|
||||||
|
|
||||||
|
// Email actions
|
||||||
|
const handleReply = () => {
|
||||||
|
if (!selectedEmail) return;
|
||||||
|
setComposeType('reply');
|
||||||
|
setShowComposeModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check for more emails
|
const handleReplyAll = () => {
|
||||||
const hasMoreEmails = page < totalPages;
|
if (!selectedEmail) return;
|
||||||
|
setComposeType('reply-all');
|
||||||
// Handle loading more emails on scroll
|
setShowComposeModal(true);
|
||||||
const handleLoadMore = () => {
|
};
|
||||||
if (hasMoreEmails && !isLoading) {
|
|
||||||
// Increment the page
|
const handleForward = () => {
|
||||||
const nextPage = page + 1;
|
if (!selectedEmail) return;
|
||||||
setPage(nextPage);
|
setComposeType('forward');
|
||||||
|
setShowComposeModal(true);
|
||||||
// Also prefetch additional pages to make scrolling smoother
|
};
|
||||||
if (session?.user?.id) {
|
|
||||||
// Prefetch next 2 pages beyond the current next page
|
const handleComposeNew = () => {
|
||||||
prefetchFolderEmails(
|
setComposeType('new');
|
||||||
session.user.id,
|
setShowComposeModal(true);
|
||||||
currentFolder,
|
|
||||||
2,
|
|
||||||
nextPage + 1,
|
|
||||||
selectedAccount?.id
|
|
||||||
).catch(err => {
|
|
||||||
console.error(`Error prefetching additional pages for ${currentFolder}:`, err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: loadEmails will be called automatically due to the page dependency in useEffect
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle bulk actions
|
// Handle bulk actions
|
||||||
@ -579,170 +457,6 @@ export default function CourrierPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle email actions
|
|
||||||
const handleReply = () => {
|
|
||||||
if (!selectedEmail) return;
|
|
||||||
setComposeType('reply');
|
|
||||||
setShowComposeModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReplyAll = () => {
|
|
||||||
if (!selectedEmail) return;
|
|
||||||
setComposeType('reply-all');
|
|
||||||
setShowComposeModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleForward = () => {
|
|
||||||
if (!selectedEmail) return;
|
|
||||||
setComposeType('forward');
|
|
||||||
setShowComposeModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleComposeNew = () => {
|
|
||||||
setComposeType('new');
|
|
||||||
setShowComposeModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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'}`);
|
|
||||||
|
|
||||||
// CRITICAL FIX: Account ID is REQUIRED for proper folder switching
|
|
||||||
if (!accountId || accountId === 'loading-account') {
|
|
||||||
logEmailOp('MAILBOX-CHANGE', 'ERROR: No valid accountId provided, cannot switch folders reliably', { folder });
|
|
||||||
toast({
|
|
||||||
title: "Cannot switch folders",
|
|
||||||
description: "A valid account ID is required to switch folders",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set loading state immediately
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
// CRITICAL FIX: Clear email selection BEFORE changing folders
|
|
||||||
// This prevents stale emails from previous folders from being displayed
|
|
||||||
logEmailOp('MAILBOX-CHANGE', 'Clearing email selection before folder change');
|
|
||||||
handleEmailSelect('', '', '');
|
|
||||||
|
|
||||||
// Clear emails during transition to avoid UI flicker/confusion
|
|
||||||
setEmails([]);
|
|
||||||
|
|
||||||
// Find the account to ensure it exists
|
|
||||||
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.`,
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logEmailOp('MAILBOX-CHANGE', `Account found: ${account.email}`, {
|
|
||||||
accountId: account.id
|
|
||||||
});
|
|
||||||
|
|
||||||
// CRITICAL FIX: Simplify and clarify parameter handling
|
|
||||||
// Extract base folder name and ensure we have a clean normalized folder
|
|
||||||
let baseFolder: string;
|
|
||||||
|
|
||||||
if (folder.includes(':')) {
|
|
||||||
const parts = folder.split(':');
|
|
||||||
const folderAccountId = parts[0];
|
|
||||||
baseFolder = parts[1];
|
|
||||||
|
|
||||||
logEmailOp('MAILBOX-CHANGE', `Parsed folder: accountId=${folderAccountId}, baseFolder=${baseFolder}`);
|
|
||||||
|
|
||||||
// CRITICAL FIX: If folder has a different account prefix than requested, log warning
|
|
||||||
if (folderAccountId !== accountId) {
|
|
||||||
logEmailOp('MAILBOX-CHANGE', `WARNING: Folder prefix (${folderAccountId}) doesn't match requested account (${accountId})`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
baseFolder = folder;
|
|
||||||
logEmailOp('MAILBOX-CHANGE', `No folder prefix, using baseFolder=${baseFolder}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// CRITICAL FIX: ALWAYS create consistent folder name with REQUESTED account prefix
|
|
||||||
const prefixedFolder = `${accountId}:${baseFolder}`;
|
|
||||||
logEmailOp('MAILBOX-CHANGE', `Standardized folder: ${prefixedFolder}`);
|
|
||||||
|
|
||||||
// CRITICAL FIX: Update the selected account BEFORE changing folders
|
|
||||||
// This prevents potential race conditions during account switching
|
|
||||||
setSelectedAccount(account);
|
|
||||||
|
|
||||||
// CRITICAL FIX: Update selected folder map with the new account's folder
|
|
||||||
// This ensures UI consistency with the selected folder
|
|
||||||
setSelectedFolders(prev => {
|
|
||||||
const updated = {
|
|
||||||
...prev,
|
|
||||||
[accountId]: prefixedFolder
|
|
||||||
};
|
|
||||||
logEmailOp('MAILBOX-CHANGE', `Updated selected folders map`, updated);
|
|
||||||
return updated;
|
|
||||||
});
|
|
||||||
|
|
||||||
// CRITICAL FIX: Log the EXACT parameters being passed to changeFolder
|
|
||||||
logEmailOp('MAILBOX-CHANGE', `Calling changeFolder with: folder=${prefixedFolder}, accountId=${accountId}`);
|
|
||||||
|
|
||||||
// CRITICAL FIX: Call changeFolder with both parameters explicitly
|
|
||||||
// The accountId parameter is critical for successful account switching
|
|
||||||
changeFolder(prefixedFolder, accountId)
|
|
||||||
.then(() => {
|
|
||||||
logEmailOp('MAILBOX-CHANGE', `Successfully changed to folder ${prefixedFolder} for account ${accountId}`);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
logEmailOp('MAILBOX-CHANGE', `ERROR: Failed to change to folder ${prefixedFolder}`, { error });
|
|
||||||
toast({
|
|
||||||
title: "Error changing folders",
|
|
||||||
description: `Failed to change to folder ${baseFolder}. ${error instanceof Error ? error.message : String(error)}`,
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update the folder button rendering to show selected state based on account
|
|
||||||
const renderFolderButton = (folder: string, accountId: string) => {
|
|
||||||
// Get the account prefix from the folder name
|
|
||||||
const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId;
|
|
||||||
|
|
||||||
// Only show folders that belong to this account
|
|
||||||
if (folderAccountId !== accountId) return null;
|
|
||||||
|
|
||||||
const isSelected = selectedFolders[accountId] === folder;
|
|
||||||
const account = accounts.find(a => a.id === accountId);
|
|
||||||
|
|
||||||
// Get the base folder name for display
|
|
||||||
const baseFolder = folder.includes(':') ? folder.split(':')[1] : folder;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={folder}
|
|
||||||
variant="ghost"
|
|
||||||
className={`w-full justify-start text-xs py-1 h-7 ${isSelected ? 'bg-gray-100' : ''}`}
|
|
||||||
onClick={() => handleMailboxChange(folder, accountId)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center w-full">
|
|
||||||
{getFolderIcon(baseFolder)}
|
|
||||||
<span className="ml-2 truncate text-gray-700">{formatFolderName(baseFolder)}</span>
|
|
||||||
{baseFolder === 'INBOX' && unreadCount > 0 && (
|
|
||||||
<span className="ml-auto bg-blue-500 text-white text-[10px] px-1.5 rounded-full">
|
|
||||||
{unreadCount}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle sending email
|
|
||||||
const handleSendEmail = async (emailData: EmailData) => {
|
const handleSendEmail = async (emailData: EmailData) => {
|
||||||
try {
|
try {
|
||||||
const result = await sendEmail(emailData);
|
const result = await sendEmail(emailData);
|
||||||
@ -751,114 +465,42 @@ export default function CourrierPage() {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Handle any errors
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete confirmation
|
|
||||||
const handleDeleteConfirm = async () => {
|
const handleDeleteConfirm = async () => {
|
||||||
await deleteEmails(selectedEmailIds);
|
await deleteEmails(selectedEmailIds);
|
||||||
setShowDeleteConfirm(false);
|
setShowDeleteConfirm(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check login on mount
|
|
||||||
useEffect(() => {
|
|
||||||
// Check if the user is logged in after a short delay
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
if (error?.includes('Not authenticated') || error?.includes('No email credentials found')) {
|
|
||||||
setShowLoginNeeded(true);
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, [error]);
|
|
||||||
|
|
||||||
// Go to login page
|
|
||||||
const handleGoToLogin = () => {
|
const handleGoToLogin = () => {
|
||||||
router.push('/courrier/login');
|
router.push('/courrier/login');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extra debugging for folder rendering
|
// Helper function for logging
|
||||||
useEffect(() => {
|
const logEmailOp = (operation: string, details: string, data?: any) => {
|
||||||
if (selectedAccount && showFolders) {
|
const timestamp = new Date().toISOString().split('T')[1].substring(0, 12);
|
||||||
console.log('Folder rendering debug:',
|
console.log(`[${timestamp}][EMAIL-APP][${operation}] ${details}`);
|
||||||
'account:', selectedAccount.id,
|
if (data) {
|
||||||
'folders:', selectedAccount.folders?.length || 0,
|
console.log(`[${timestamp}][EMAIL-APP][DATA]`, data);
|
||||||
'showFolders:', showFolders
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, [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
|
|
||||||
});
|
|
||||||
|
|
||||||
// Skip if this is already the selected account
|
|
||||||
if (selectedAccount?.id === account.id) {
|
|
||||||
logEmailOp('ACCOUNT-SELECT', `Account ${account.id} already selected, skipping`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First set loading state to provide visual feedback
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
// CRITICAL FIX: Clear current selection to prevent stale email from previous account
|
|
||||||
// This ensures no emails from the previous account remain selected
|
|
||||||
logEmailOp('ACCOUNT-SELECT', `CRITICAL FIX: Clearing all email selections to prevent cross-account UI contamination`);
|
|
||||||
|
|
||||||
// Direct clearing of selected email for immediate UI effect
|
|
||||||
// Clear the selected email in the UI - Panel 3
|
|
||||||
handleEmailSelect('', '', '');
|
|
||||||
|
|
||||||
// Clear any multi-selected emails - Panel 2
|
|
||||||
if (selectedEmailIds.length > 0) {
|
|
||||||
toggleSelectAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear current emails to avoid confusion during transition
|
|
||||||
setEmails([]);
|
|
||||||
|
|
||||||
// Update selected account
|
|
||||||
setSelectedAccount(account);
|
|
||||||
logEmailOp('ACCOUNT-SELECT', `Selected account set to: ${account.email}`);
|
|
||||||
|
|
||||||
// SIMPLIFIED APPROACH: Instead of trying to find an existing INBOX folder,
|
|
||||||
// just create a properly formatted one with the current account ID
|
|
||||||
const inboxFolder = `${account.id}:INBOX`;
|
|
||||||
logEmailOp('ACCOUNT-SELECT', `Using standardized INBOX folder: ${inboxFolder}`);
|
|
||||||
|
|
||||||
// Update the selected folders map with the proper account ID and folder
|
|
||||||
setSelectedFolders(prev => {
|
|
||||||
const updated = {
|
|
||||||
...prev,
|
|
||||||
[account.id]: inboxFolder
|
|
||||||
};
|
|
||||||
logEmailOp('ACCOUNT-SELECT', `Updated selected folders map`, updated);
|
|
||||||
return updated;
|
|
||||||
});
|
|
||||||
|
|
||||||
// First, show the folders for better UX
|
|
||||||
setShowFolders(true);
|
|
||||||
|
|
||||||
// CRITICAL FIX: Use the immediate mode of handleMailboxChange - no timeout needed
|
|
||||||
logEmailOp('ACCOUNT-SELECT', `Triggering folder change to: ${inboxFolder} for account ${account.id}`);
|
|
||||||
|
|
||||||
// Use handleMailboxChange which properly passes the account ID with the folder
|
|
||||||
handleMailboxChange(inboxFolder, account.id.toString());
|
|
||||||
|
|
||||||
// We'll let handleMailboxChange manage the loading state, don't reset it here
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddAccount = async (accountData: AccountData) => {
|
// Update the accounts from state
|
||||||
// ... account creation logic ...
|
const setAccounts = (newAccounts: Account[]) => {
|
||||||
// setAccounts(prev => [...prev, newAccount]);
|
// Dispatch action directly through our hook's returned state values
|
||||||
// setVisibleFolders(prev => ({
|
const action = { type: 'SET_ACCOUNTS', payload: newAccounts };
|
||||||
// ...prev,
|
// We don't have direct access to dispatch, so we need a workaround
|
||||||
// [newAccount.id]: newAccount.folders
|
// This is a temporary solution until we fully migrate to the reducer pattern
|
||||||
// }));
|
try {
|
||||||
|
// @ts-ignore - We're accessing internal functionality for transition purposes
|
||||||
|
// This will be cleaned up in a future refactor when we fully migrate
|
||||||
|
window.dispatchEmailAction?.(action);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to update accounts through reducer:', err);
|
||||||
|
// Fallback approach if needed
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user