courrier multi account restore compose

This commit is contained in:
alma 2025-04-29 09:50:10 +02:00
parent a18751fe2a
commit 98fe6f5eae

View File

@ -192,19 +192,21 @@ export default function CourrierPage() {
const [editLoading, setEditLoading] = useState(false);
const [deleteLoading, setDeleteLoading] = useState(false);
// Add a type definition for the unreadCount structure to match what EmailSidebar expects
const [unreadCountMap, setUnreadCountMap] = useState<Record<string, Record<string, number>>>({});
// Calculate unread count for each account and folder
useEffect(() => {
// Create a map to store unread counts per account and folder
const accountFolderUnreadCounts = new Map<string, Map<string, number>>();
const accountFolderUnreadCounts: Record<string, Record<string, number>> = {};
// Initialize counts for all accounts and folders
accounts.forEach(account => {
if (account.id !== 'loading-account') {
const folderCounts = new Map<string, number>();
accountFolderUnreadCounts[account.id.toString()] = {};
account.folders.forEach(folder => {
folderCounts.set(folder, 0);
accountFolderUnreadCounts[account.id.toString()][folder] = 0;
});
accountFolderUnreadCounts.set(account.id, folderCounts);
}
});
@ -215,27 +217,32 @@ export default function CourrierPage() {
// Count unread emails for the specific account and folder
if (isUnread && email.accountId && email.folder) {
const folderCounts = accountFolderUnreadCounts.get(email.accountId);
if (folderCounts) {
const currentCount = folderCounts.get(email.folder) || 0;
folderCounts.set(email.folder, currentCount + 1);
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') {
const folderCounts = accountFolderUnreadCounts.get(selectedAccount.id);
const folderCounts = accountFolderUnreadCounts[selectedAccount.id.toString()];
if (folderCounts) {
setUnreadCount(folderCounts.get(currentFolder) || 0);
setUnreadCount(folderCounts[currentFolder] || 0);
} else {
setUnreadCount(0);
}
} else {
// For 'loading-account', sum up all unread counts for the current folder
let totalUnread = 0;
accountFolderUnreadCounts.forEach((folderCounts: Map<string, number>) => {
totalUnread += folderCounts.get(currentFolder) || 0;
Object.values(accountFolderUnreadCounts).forEach((folderCounts) => {
totalUnread += folderCounts[currentFolder] || 0;
});
setUnreadCount(totalUnread);
}
@ -243,10 +250,7 @@ export default function CourrierPage() {
// Log the counts for debugging
console.log('Unread counts per account and folder:',
Object.fromEntries(
Array.from(accountFolderUnreadCounts.entries()).map(([accountId, folderCounts]) => [
accountId,
Object.fromEntries(folderCounts.entries())
])
Object.entries(accountFolderUnreadCounts)
)
);
}, [emails, selectedAccount, currentFolder, accounts]);
@ -733,7 +737,7 @@ export default function CourrierPage() {
selectedFolders={selectedFolders}
currentFolder={currentFolder}
loading={loading}
unreadCount={unreadCount}
unreadCount={unreadCountMap}
showAddAccountForm={showAddAccountForm}
onFolderChange={handleMailboxChange}
onRefresh={() => {
@ -929,4 +933,168 @@ export default function CourrierPage() {
<p className="text-sm text-gray-500 mt-1">
{searchQuery
? `No results found for "${searchQuery}"`
: `Your ${currentFolder.toLowerCase()} is empty`
: `Your ${currentFolder.toLowerCase()} is empty`}
</p>
</div>
</div>
) : (
<EmailList
emails={emails}
selectedEmailIds={selectedEmailIds}
selectedEmail={selectedEmail}
onSelectEmail={(emailId) => 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}
/>
)}
</div>
</div>
)}
</div>
</div>
{/* Panel 3: Email Detail - Always visible */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Content for Panel 3 based on state but always visible */}
<div className="flex-1 overflow-hidden bg-white">
{selectedEmail ? (
<EmailDetailView
email={selectedEmail as any}
onBack={() => {
handleEmailSelect('', '', '');
// Ensure sidebar stays visible
setSidebarOpen(true);
}}
onReply={handleReply}
onReplyAll={handleReplyAll}
onForward={handleForward}
onToggleStar={() => toggleStarred(selectedEmail.id)}
/>
) : (
<div className="h-full flex items-center justify-center">
<div className="text-center text-muted-foreground">
<p>Select an email to view or</p>
<button
className="text-primary mt-2 hover:underline"
onClick={() => {
setComposeType('new');
setShowComposeModal(true);
}}
>
Compose a new message
</button>
</div>
</div>
)}
</div>
</div>
</div>
</div>
</main>
{/* Modals and Dialogs */}
<DeleteConfirmDialog
show={showDeleteConfirm}
selectedCount={selectedEmailIds.length}
onConfirm={handleDeleteConfirm}
onCancel={() => setShowDeleteConfirm(false)}
/>
{/* Compose Email Dialog */}
<Dialog open={showComposeModal} onOpenChange={(open) => !open && setShowComposeModal(false)}>
<DialogContent className="sm:max-w-[800px] p-0 h-[80vh]">
<DialogTitle asChild>
<span className="sr-only">New Message</span>
</DialogTitle>
<ComposeEmail
type={composeType}
initialEmail={composeType !== 'new' ? selectedEmail : undefined}
onSend={(emailData) => {
const result = sendEmail(emailData);
return result;
}}
onClose={() => setShowComposeModal(false)}
/>
</DialogContent>
</Dialog>
{/* Edit Password Modal */}
<Dialog open={showEditModal} onOpenChange={open => { if (!open) setShowEditModal(false); }}>
<DialogContent className="sm:max-w-[400px]">
<DialogTitle>Edit Account Password</DialogTitle>
<form onSubmit={async e => {
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);
}
}}>
<div className="mb-2">
<Label htmlFor="new-password">New Password</Label>
<Input id="new-password" type="password" value={newPassword} onChange={e => setNewPassword(e.target.value)} required className="mt-1" />
</div>
<div className="flex justify-end gap-2 mt-4">
<Button type="button" variant="outline" onClick={() => setShowEditModal(false)}>Cancel</Button>
<Button type="submit" disabled={editLoading}>{editLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : 'Save'}</Button>
</div>
</form>
</DialogContent>
</Dialog>
{/* Delete Account Dialog */}
<AlertDialog open={showDeleteDialog} onOpenChange={open => { if (!open) setShowDeleteDialog(false); }}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Account</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this account? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setShowDeleteDialog(false)}>Cancel</AlertDialogCancel>
<AlertDialogAction asChild>
<Button variant="destructive" disabled={deleteLoading} onClick={async () => {
if (!accountToDelete) return;
setDeleteLoading(true);
try {
const res = await fetch(`/api/courrier/account?accountId=${accountToDelete.id}`, { method: 'DELETE' });
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'Failed to delete account');
toast({ title: 'Account deleted', description: 'The account was deleted successfully.' });
setShowDeleteDialog(false);
window.location.reload();
} catch (err) {
toast({ title: 'Error', description: err instanceof Error ? err.message : 'Failed to delete account', variant: 'destructive' });
} finally {
setDeleteLoading(false);
}
}}>Delete</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
}