courrier multi account
This commit is contained in:
parent
8137cd3701
commit
65d75a0213
@ -615,9 +615,8 @@ export default function CourrierPage() {
|
|||||||
<main className="w-full h-screen bg-black">
|
<main className="w-full h-screen bg-black">
|
||||||
<div className="w-full h-full px-4 pt-12 pb-4">
|
<div className="w-full h-full px-4 pt-12 pb-4">
|
||||||
<div className="flex h-full bg-carnet-bg">
|
<div className="flex h-full bg-carnet-bg">
|
||||||
{/* Sidebar */}
|
{/* Sidebar - Force display without conditions */}
|
||||||
<div className={`${sidebarOpen ? 'w-60' : 'w-16'} bg-white/95 backdrop-blur-sm border-r border-gray-100 flex flex-col transition-all duration-300 ease-in-out
|
<div className="w-60 bg-white/95 backdrop-blur-sm border-r border-gray-100 flex flex-col" style={{display: "flex !important"}}>
|
||||||
${mobileSidebarOpen ? 'fixed inset-y-0 left-0 z-40' : ''} md:block`}>
|
|
||||||
{/* Courrier Title */}
|
{/* Courrier Title */}
|
||||||
<div className="p-3 border-b border-gray-100">
|
<div className="p-3 border-b border-gray-100">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -645,10 +644,6 @@ export default function CourrierPage() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
// Reset to page 1 when manually refreshing
|
// Reset to page 1 when manually refreshing
|
||||||
setPage(1);
|
setPage(1);
|
||||||
// Trigger a scroll reset
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.dispatchEvent(new CustomEvent('reset-email-scroll'));
|
|
||||||
}
|
|
||||||
// Load emails
|
// Load emails
|
||||||
loadEmails().finally(() => setLoading(false));
|
loadEmails().finally(() => setLoading(false));
|
||||||
}}
|
}}
|
||||||
@ -658,7 +653,7 @@ export default function CourrierPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Scrollable area for accounts and folders */}
|
{/* Scrollable area for accounts and folders */}
|
||||||
<ScrollArea className="flex-1 h-0">
|
<div className="flex-1 overflow-y-auto">
|
||||||
{/* Accounts Section */}
|
{/* Accounts Section */}
|
||||||
<div className="p-3 border-b border-gray-100">
|
<div className="p-3 border-b border-gray-100">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
@ -687,305 +682,78 @@ export default function CourrierPage() {
|
|||||||
ShowFolders: {String(showFolders)}
|
ShowFolders: {String(showFolders)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Form for adding a new account */}
|
{/* Display all accounts */}
|
||||||
{showAddAccountForm && (
|
<div className="mt-1">
|
||||||
<div className="mb-3 p-2 border border-gray-200 rounded-md bg-gray-50">
|
{accounts.map((account) => (
|
||||||
<h4 className="text-xs font-medium mb-2 text-gray-700">Add IMAP Account</h4>
|
|
||||||
<form onSubmit={async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
const formData = new FormData(e.currentTarget);
|
|
||||||
|
|
||||||
// Pull values from form with proper type handling
|
|
||||||
const formValues = {
|
|
||||||
email: formData.get('email')?.toString() || '',
|
|
||||||
password: formData.get('password')?.toString() || '',
|
|
||||||
host: formData.get('host')?.toString() || '',
|
|
||||||
port: parseInt(formData.get('port')?.toString() || '993'),
|
|
||||||
secure: formData.get('secure') === 'on',
|
|
||||||
display_name: formData.get('display_name')?.toString() || '',
|
|
||||||
smtp_host: formData.get('smtp_host')?.toString() || '',
|
|
||||||
smtp_port: formData.get('smtp_port')?.toString() ?
|
|
||||||
parseInt(formData.get('smtp_port')?.toString() || '587') : undefined,
|
|
||||||
smtp_secure: formData.get('smtp_secure') === 'on'
|
|
||||||
};
|
|
||||||
|
|
||||||
// If display_name is empty, use email
|
|
||||||
if (!formValues.display_name) {
|
|
||||||
formValues.display_name = formValues.email;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// First test the connection
|
|
||||||
const testResponse = await fetch('/api/courrier/test-connection', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: formValues.email,
|
|
||||||
password: formValues.password,
|
|
||||||
host: formValues.host,
|
|
||||||
port: formValues.port,
|
|
||||||
secure: formValues.secure
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const testResult = await testResponse.json();
|
|
||||||
|
|
||||||
if (!testResponse.ok) {
|
|
||||||
throw new Error(testResult.error || 'Connection test failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Connection test successful:', testResult);
|
|
||||||
|
|
||||||
// If connection test is successful, save the account
|
|
||||||
const saveResponse = await fetch('/api/courrier/account', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(formValues)
|
|
||||||
});
|
|
||||||
|
|
||||||
const saveResult = await saveResponse.json();
|
|
||||||
|
|
||||||
if (!saveResponse.ok) {
|
|
||||||
throw new Error(saveResult.error || 'Failed to add account');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update accounts list
|
|
||||||
const newAccountObj = {
|
|
||||||
id: `account-${Date.now()}`, // generate unique string ID
|
|
||||||
name: formValues.display_name,
|
|
||||||
email: formValues.email,
|
|
||||||
color: `bg-blue-500`, // Default color class
|
|
||||||
folders: testResult.details.sampleFolders || ['INBOX', 'Sent', 'Drafts', 'Trash'] // Use discovered folders or defaults
|
|
||||||
};
|
|
||||||
|
|
||||||
setAccounts(prev => [...prev, newAccountObj]);
|
|
||||||
setShowAddAccountForm(false);
|
|
||||||
toast({
|
|
||||||
title: "Account added successfully",
|
|
||||||
description: `Your email account ${formValues.email} has been added.`,
|
|
||||||
duration: 5000
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error adding account:', error);
|
|
||||||
toast({
|
|
||||||
title: "Failed to add account",
|
|
||||||
description: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
variant: "destructive",
|
|
||||||
duration: 5000
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Tabs defaultValue="imap" className="w-full">
|
|
||||||
<TabsList className="grid w-full grid-cols-2 h-7">
|
|
||||||
<TabsTrigger value="imap" className="text-xs">IMAP Settings</TabsTrigger>
|
|
||||||
<TabsTrigger value="smtp" className="text-xs">SMTP Settings</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value="imap" className="mt-2 space-y-2">
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="email" className="text-xs">Email</Label>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
placeholder="email@example.com"
|
|
||||||
className="h-7 text-xs"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="password" className="text-xs">Password</Label>
|
|
||||||
<Input
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
placeholder="•••••••••"
|
|
||||||
className="h-7 text-xs"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="display_name" className="text-xs">Display Name</Label>
|
|
||||||
<Input
|
|
||||||
id="display_name"
|
|
||||||
name="display_name"
|
|
||||||
placeholder="John Doe"
|
|
||||||
className="h-7 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="host" className="text-xs">IMAP Server</Label>
|
|
||||||
<Input
|
|
||||||
id="host"
|
|
||||||
name="host"
|
|
||||||
placeholder="imap.example.com"
|
|
||||||
className="h-7 text-xs"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<div className="flex-1">
|
|
||||||
<Label htmlFor="port" className="text-xs">Port</Label>
|
|
||||||
<Input
|
|
||||||
id="port"
|
|
||||||
name="port"
|
|
||||||
placeholder="993"
|
|
||||||
className="h-7 text-xs"
|
|
||||||
defaultValue="993"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-end pb-1">
|
|
||||||
<div className="flex items-center space-x-1">
|
|
||||||
<Checkbox id="secure" name="secure" defaultChecked />
|
|
||||||
<Label htmlFor="secure" className="text-xs">SSL</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="smtp" className="mt-2 space-y-2">
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="smtp_host" className="text-xs">SMTP Server</Label>
|
|
||||||
<Input
|
|
||||||
id="smtp_host"
|
|
||||||
name="smtp_host"
|
|
||||||
placeholder="smtp.example.com"
|
|
||||||
className="h-7 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<div className="flex-1">
|
|
||||||
<Label htmlFor="smtp_port" className="text-xs">Port</Label>
|
|
||||||
<Input
|
|
||||||
id="smtp_port"
|
|
||||||
name="smtp_port"
|
|
||||||
placeholder="587"
|
|
||||||
className="h-7 text-xs"
|
|
||||||
defaultValue="587"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-end pb-1">
|
|
||||||
<div className="flex items-center space-x-1">
|
|
||||||
<Checkbox id="smtp_secure" name="smtp_secure" defaultChecked />
|
|
||||||
<Label htmlFor="smtp_secure" className="text-xs">SSL</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-500 italic">
|
|
||||||
Note: SMTP settings are only needed for sending emails
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
<div className="flex gap-2 pt-2">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
className="flex-1 h-7 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded-md"
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{loading ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : null}
|
|
||||||
Test & Add
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
className="h-7 text-xs"
|
|
||||||
onClick={() => setShowAddAccountForm(false)}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Accounts List */}
|
|
||||||
{accountsDropdownOpen && (
|
|
||||||
<div className="mt-1">
|
|
||||||
{accounts.map((account) => (
|
|
||||||
<Button
|
|
||||||
key={account.id}
|
|
||||||
variant="ghost"
|
|
||||||
className={`w-full justify-start text-xs mb-1 ${selectedAccount?.id === account.id ? 'bg-gray-100' : ''}`}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedAccount(account);
|
|
||||||
setShowFolders(true);
|
|
||||||
if (account.id === 'all-accounts') {
|
|
||||||
handleMailboxChange('INBOX');
|
|
||||||
} else {
|
|
||||||
handleMailboxChange('INBOX', account.id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className={`w-3 h-3 rounded-full ${account.color || 'bg-blue-500'} mr-2`}></div>
|
|
||||||
<span className="truncate">{account.name}</span>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Folders Section - conditionally show folders when an account is selected */}
|
|
||||||
{showFolders && selectedAccount && selectedAccount.folders && (
|
|
||||||
<div className="p-3">
|
|
||||||
<div className="mb-2">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className="w-full justify-between text-sm font-medium text-gray-500"
|
|
||||||
>
|
|
||||||
<span>Folders</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Debug info */}
|
|
||||||
<div className="text-xs text-blue-500 p-1 border border-blue-300 mb-2 bg-blue-50 rounded">
|
|
||||||
Selected: {selectedAccount.id || 'none'}<br />
|
|
||||||
Has Folders: {String(!!selectedAccount.folders)}<br />
|
|
||||||
Folder Count: {selectedAccount.folders.length || 0}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedAccount.folders.map((folder) => (
|
|
||||||
<Button
|
<Button
|
||||||
key={folder}
|
key={account.id}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={`w-full justify-start text-xs mb-1 ${currentFolder === folder ? 'bg-gray-100' : ''}`}
|
className={`w-full justify-start text-xs mb-1 ${selectedAccount?.id === account.id ? 'bg-gray-100' : ''}`}
|
||||||
onClick={() => handleMailboxChange(folder, selectedAccount.id !== 'all-accounts' ? selectedAccount.id : undefined)}
|
onClick={() => {
|
||||||
|
setSelectedAccount(account);
|
||||||
|
setShowFolders(true);
|
||||||
|
if (account.id === 'all-accounts') {
|
||||||
|
handleMailboxChange('INBOX');
|
||||||
|
} else {
|
||||||
|
handleMailboxChange('INBOX', account.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{getFolderIcon(folder)}
|
<div className={`w-3 h-3 rounded-full ${account.color?.startsWith('#') ? 'bg-blue-500' : account.color || 'bg-blue-500'} mr-2`}></div>
|
||||||
<span className="ml-2 truncate">{formatFolderName(folder)}</span>
|
<span className="truncate">{account.name}</span>
|
||||||
{folder === 'INBOX' && unreadCount > 0 && (
|
|
||||||
<span className="ml-auto bg-blue-500 text-white text-[10px] px-1.5 rounded-full">
|
|
||||||
{unreadCount}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
{/* Fallback for no folders or account */}
|
{/* Folders Section - Always Display */}
|
||||||
{(!selectedAccount || !selectedAccount.folders || selectedAccount.folders.length === 0) && (
|
<div className="p-3">
|
||||||
<div className="p-3">
|
<div className="mb-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full justify-between text-sm font-medium text-gray-500"
|
||||||
|
>
|
||||||
|
<span>Folders</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Debug info */}
|
||||||
|
<div className="text-xs text-blue-500 p-1 border border-blue-300 mb-2 bg-blue-50 rounded">
|
||||||
|
Selected: {selectedAccount?.id || 'none'}<br />
|
||||||
|
Has Folders: {String(!!selectedAccount?.folders)}<br />
|
||||||
|
Folder Count: {selectedAccount?.folders?.length || 0}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Display folders if available */}
|
||||||
|
{selectedAccount?.folders?.map((folder) => (
|
||||||
|
<Button
|
||||||
|
key={folder}
|
||||||
|
variant="ghost"
|
||||||
|
className={`w-full justify-start text-xs mb-1 ${currentFolder === folder ? 'bg-gray-100' : ''}`}
|
||||||
|
onClick={() => handleMailboxChange(folder, selectedAccount.id !== 'all-accounts' ? selectedAccount.id : undefined)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{getFolderIcon(folder)}
|
||||||
|
<span className="ml-2 truncate">{formatFolderName(folder)}</span>
|
||||||
|
{folder === 'INBOX' && unreadCount > 0 && (
|
||||||
|
<span className="ml-auto bg-blue-500 text-white text-[10px] px-1.5 rounded-full">
|
||||||
|
{unreadCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Fallback if no folders */}
|
||||||
|
{(!selectedAccount?.folders || selectedAccount.folders.length === 0) && (
|
||||||
<div className="text-xs text-gray-500 italic p-2">
|
<div className="text-xs text-gray-500 italic p-2">
|
||||||
No folders available for this account.
|
No folders available for this account.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</ScrollArea>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content Area - conditionally show email list or detail view */}
|
{/* Main Content Area - conditionally show email list or detail view */}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user