From a18751fe2a071bdfc0ef4673b6da4fe9c07ed7aa Mon Sep 17 00:00:00 2001 From: alma Date: Tue, 29 Apr 2025 09:47:29 +0200 Subject: [PATCH] courrier multi account restore compose --- app/courrier/page.tsx | 244 +++++++----------------------- app/hooks/use-courrier.ts | 105 +++++++++++++ components/email/EmailSidebar.tsx | 16 +- hooks/use-courrier.ts | 50 ++++-- 4 files changed, 206 insertions(+), 209 deletions(-) create mode 100644 app/hooks/use-courrier.ts diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 5a5856dd..6ae2d10a 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -562,32 +562,62 @@ export default function CourrierPage() { setShowComposeModal(true); }; - // Update handleMailboxChange to properly handle per-account folders + // Update handleMailboxChange to ensure consistent folder naming and prevent race conditions const handleMailboxChange = (folder: string, accountId?: string) => { - if (accountId && accountId !== 'loading-account') { - const account = accounts.find(a => a.id === accountId); - if (!account) { - toast({ - title: "Account not found", - description: `The account ${accountId} could not be found.`, - variant: "destructive", - }); - return; - } - // Only allow navigation to folders in selectedAccount.folders - if (!account.folders.includes(folder)) { - toast({ - title: "Folder not found", - description: `The folder ${folder} does not exist for this account.`, - variant: "destructive", - }); - return; - } - setSelectedFolders(prev => ({ ...prev, [accountId]: folder })); - changeFolder(folder, accountId); - } else { - changeFolder(folder, accountId); + if (!accountId || accountId === 'loading-account') { + // Use a default behavior if no valid accountId is provided + console.warn('No valid accountId provided for folder change'); + changeFolder(folder); + return; } + + const account = accounts.find(a => a.id === accountId); + if (!account) { + toast({ + title: "Account not found", + description: `The account ${accountId} could not be found.`, + variant: "destructive", + }); + return; + } + + // Ensure folder has account prefix + const prefixedFolder = folder.includes(':') ? folder : `${accountId}:${folder}`; + + // Ensure folder exists in account.folders (either in prefixed or unprefixed form) + const folderExists = account.folders?.some(f => + f === prefixedFolder || f === folder || + // Handle case where folder is base name and account folders are prefixed + (folder.includes(':') ? false : `${accountId}:${folder}` === f) + ); + + if (!folderExists && folder !== 'INBOX') { + toast({ + title: "Folder not found", + description: `The folder ${folder} does not exist for this account.`, + variant: "destructive", + }); + return; + } + + // Update UI state first to prevent flickering + setSelectedAccount(account); + + // Use a callback to ensure we have the latest state when updating selectedFolders + setSelectedFolders(prev => { + const updated = { ...prev, [accountId]: prefixedFolder }; + console.log('Updated selectedFolders:', updated); + return updated; + }); + + // Set loading state to provide feedback + setLoading(true); + + // Reset page when changing folders + setPage(1); + + // Make sure we pass the prefixed folder to change folder + changeFolder(prefixedFolder, accountId); }; // Update the folder button rendering to show selected state based on account @@ -899,168 +929,4 @@ export default function CourrierPage() {

{searchQuery ? `No results found for "${searchQuery}"` - : `Your ${currentFolder.toLowerCase()} is empty`} -

- - - ) : ( - handleEmailSelect(emailId, selectedAccount?.id || '', currentFolder)} - onToggleSelect={toggleEmailSelection} - onToggleSelectAll={toggleSelectAll} - onToggleStarred={toggleStarred} - onLoadMore={handleLoadMore} - hasMoreEmails={page < totalPages} - currentFolder={currentFolder} - isLoading={isLoading} - totalEmails={emails.length} - onBulkAction={handleBulkAction} - /> - )} - - - )} - - - - {/* Panel 3: Email Detail - Always visible */} -
- {/* Content for Panel 3 based on state but always visible */} -
- {selectedEmail ? ( - { - handleEmailSelect('', '', ''); - // Ensure sidebar stays visible - setSidebarOpen(true); - }} - onReply={handleReply} - onReplyAll={handleReplyAll} - onForward={handleForward} - onToggleStar={() => toggleStarred(selectedEmail.id)} - /> - ) : ( -
-
-

Select an email to view or

- -
-
- )} -
-
- - - - - {/* Modals and Dialogs */} - setShowDeleteConfirm(false)} - /> - - {/* Compose Email Dialog */} - !open && setShowComposeModal(false)}> - - - New Message - - { - const result = sendEmail(emailData); - return result; - }} - onClose={() => setShowComposeModal(false)} - /> - - - - {/* Edit Password Modal */} - { if (!open) setShowEditModal(false); }}> - - Edit Account Password -
{ - e.preventDefault(); - if (!accountToEdit) return; - setEditLoading(true); - try { - const res = await fetch('/api/courrier/account', { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ accountId: accountToEdit.id, newPassword }), - }); - const data = await res.json(); - if (!res.ok) throw new Error(data.error || 'Failed to update password'); - toast({ title: 'Password updated', description: 'Password changed successfully.' }); - setShowEditModal(false); - setNewPassword(''); - window.location.reload(); - } catch (err) { - toast({ title: 'Error', description: err instanceof Error ? err.message : 'Failed to update password', variant: 'destructive' }); - } finally { - setEditLoading(false); - } - }}> -
- - setNewPassword(e.target.value)} required className="mt-1" /> -
-
- - -
-
-
-
- - {/* Delete Account Dialog */} - { if (!open) setShowDeleteDialog(false); }}> - - - Delete Account - - Are you sure you want to delete this account? This action cannot be undone. - - - - setShowDeleteDialog(false)}>Cancel - - - - - - - - ); -} \ No newline at end of file + : `Your ${currentFolder.toLowerCase()} is empty` \ No newline at end of file diff --git a/app/hooks/use-courrier.ts b/app/hooks/use-courrier.ts new file mode 100644 index 00000000..ba95a97e --- /dev/null +++ b/app/hooks/use-courrier.ts @@ -0,0 +1,105 @@ +/** + * Change the current folder and load emails from that folder + */ +const changeFolder = async (folder: string, accountId?: string) => { + console.log(`Changing folder to ${folder} for account ${accountId || 'default'}`); + try { + // Reset selected email + setSelectedEmail(null); + setSelectedEmailIds([]); + + // Record the new folder + setCurrentFolder(folder); + + // Reset search query when changing folders + setSearchQuery(''); + + // Reset to page 1 + setPage(1); + + // Clear existing emails before loading new ones to prevent UI flicker + setEmails([]); + + // Show loading state + setIsLoading(true); + + // Load emails for the new folder with a deliberate delay to allow state to update + await new Promise(resolve => setTimeout(resolve, 100)); + await loadEmails(folder, 1, 20, accountId); + } catch (error) { + console.error(`Error changing to folder ${folder}:`, error); + setError(`Failed to load emails from ${folder}: ${error instanceof Error ? error.message : 'Unknown error'}`); + } finally { + setIsLoading(false); + } +}; + +/** + * Load emails for the current folder + */ +const loadEmails = async ( + folderOverride?: string, + pageOverride?: number, + perPageOverride?: number, + accountIdOverride?: string +) => { + const folderToUse = folderOverride || currentFolder; + 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'}`); + + try { + setIsLoading(true); + setError(''); + + // Construct the API URL with a unique timestamp to prevent caching + let url = `/api/courrier/emails?folder=${encodeURIComponent(folderToUse)}&page=${pageToUse}&perPage=${perPageToUse}`; + + // Add accountId parameter if specified + if (accountIdToUse) { + url += `&accountId=${encodeURIComponent(accountIdToUse)}`; + } + + // Add cache-busting timestamp + url += `&_t=${Date.now()}`; + + console.log(`Fetching emails from API: ${url}`); + const response = await fetch(url); + + if (!response.ok) { + let errorText; + try { + const errorData = await response.json(); + errorText = errorData.error || `Server error: ${response.status}`; + } catch { + errorText = `HTTP error: ${response.status}`; + } + throw new Error(errorText); + } + + const data = await response.json(); + + if (pageOverride === 1 || !pageOverride) { + // Replace emails when loading first page + setEmails(data.emails); + } else { + // Append emails when loading subsequent pages + setEmails(prev => [...prev, ...data.emails]); + } + + // Update pagination info + setTotalPages(data.totalPages); + 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'}`); + return null; + } finally { + setIsLoading(false); + } +}; \ No newline at end of file diff --git a/components/email/EmailSidebar.tsx b/components/email/EmailSidebar.tsx index 80055b2b..d34ca6ea 100644 --- a/components/email/EmailSidebar.tsx +++ b/components/email/EmailSidebar.tsx @@ -140,18 +140,20 @@ export default function EmailSidebar({ return folder.charAt(0).toUpperCase() + folder.slice(1).toLowerCase(); }; - // Render folder button with exact same styling as in courrier page + // Improve the renderFolderButton function to ensure consistent handling const renderFolderButton = (folder: string, accountId: string) => { - // Get the account prefix from the folder name - const folderAccountId = folder.includes(':') ? folder.split(':')[0] : accountId; + // Ensure folder always has accountId prefix for consistency + const prefixedFolder = folder.includes(':') ? folder : `${accountId}:${folder}`; + + // Extract the base folder name and account ID for display and checking + const [folderAccountId, baseFolder] = prefixedFolder.includes(':') + ? prefixedFolder.split(':') + : [accountId, folder]; // Only show folders that belong to this account if (folderAccountId !== accountId) return null; - const isSelected = selectedFolders[accountId] === folder; - - // Get the base folder name for display - const baseFolder = folder.includes(':') ? folder.split(':')[1] : folder; + const isSelected = selectedFolders[accountId] === prefixedFolder; return (