courrier multi account restore compose
This commit is contained in:
parent
a18751fe2a
commit
98fe6f5eae
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user