courrier multi account
This commit is contained in:
parent
6516a726e9
commit
9eaa77b525
@ -9,7 +9,7 @@ import {
|
||||
Inbox, Send, Star, Trash, Plus, ChevronLeft, ChevronRight,
|
||||
Search, ChevronDown, Folder, ChevronUp, Reply, Forward, ReplyAll,
|
||||
MoreHorizontal, FolderOpen, X, Paperclip, MessageSquare, Copy, EyeOff,
|
||||
AlertOctagon, Archive, RefreshCw
|
||||
AlertOctagon, Archive, RefreshCw, Menu
|
||||
} from 'lucide-react';
|
||||
import { Dialog, DialogContent } from '@/components/ui/dialog';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
@ -159,6 +159,18 @@ export default function CourrierPage() {
|
||||
}
|
||||
}, [selectedAccount]);
|
||||
|
||||
// Add useEffect for debugging
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
console.log('[DEBUG] Rendering UI with:', {
|
||||
accountsCount: accounts.length,
|
||||
selectedAccountId: selectedAccount?.id,
|
||||
showFolders,
|
||||
currentFolder
|
||||
});
|
||||
}
|
||||
}, [accounts, selectedAccount, showFolders, currentFolder]);
|
||||
|
||||
// Calculate unread count (this would be replaced with actual data in production)
|
||||
useEffect(() => {
|
||||
// Example: counting unread emails in the inbox
|
||||
@ -559,16 +571,6 @@ export default function CourrierPage() {
|
||||
router.push('/courrier/login');
|
||||
};
|
||||
|
||||
// Add this log right before the return statement in the CourrierPage component
|
||||
// Debug accounts and selectedAccount before rendering
|
||||
useEffect(() => {
|
||||
console.log('=== RENDER STATE INSPECTION ===');
|
||||
console.log('Current accounts state:', accounts);
|
||||
console.log('Selected account:', selectedAccount);
|
||||
console.log('Current folder:', currentFolder);
|
||||
console.log('Show folders:', showFolders);
|
||||
}, [accounts, selectedAccount, currentFolder, showFolders]);
|
||||
|
||||
// Extra debugging for folder rendering
|
||||
useEffect(() => {
|
||||
if (selectedAccount && showFolders) {
|
||||
@ -580,17 +582,8 @@ export default function CourrierPage() {
|
||||
}
|
||||
}, [selectedAccount, showFolders]);
|
||||
|
||||
// Add useEffect for debugging near the other useEffect hooks
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
console.log('[DEBUG] Rendering UI with:', {
|
||||
accountsCount: accounts.length,
|
||||
selectedAccountId: selectedAccount?.id,
|
||||
showFolders,
|
||||
currentFolder
|
||||
});
|
||||
}
|
||||
})}
|
||||
return (
|
||||
<>
|
||||
<SimplifiedLoadingFix />
|
||||
<RedisCacheStatus />
|
||||
|
||||
@ -663,387 +656,388 @@ export default function CourrierPage() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Form for adding a new account */}
|
||||
{showAddAccountForm && (
|
||||
<div className="mb-3 p-2 border border-gray-200 rounded-md bg-gray-50">
|
||||
<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
|
||||
})
|
||||
});
|
||||
{/* Form for adding a new account */}
|
||||
{showAddAccountForm && (
|
||||
<div className="mb-3 p-2 border border-gray-200 rounded-md bg-gray-50">
|
||||
<h4 className="text-xs font-medium mb-2 text-gray-700">Add IMAP Account</h4>
|
||||
<form onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
const testResult = await testResponse.json();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
|
||||
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
|
||||
// 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'
|
||||
};
|
||||
|
||||
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="space-y-2 pt-2">
|
||||
<Input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Email address"
|
||||
className="h-8 text-xs"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
className="h-8 text-xs"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
name="display_name"
|
||||
placeholder="Display name (optional)"
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
name="host"
|
||||
placeholder="IMAP server"
|
||||
className="h-8 text-xs"
|
||||
required
|
||||
/>
|
||||
<div className="flex space-x-2">
|
||||
<Input
|
||||
type="number"
|
||||
name="port"
|
||||
placeholder="Port"
|
||||
className="h-8 text-xs w-1/2"
|
||||
defaultValue="993"
|
||||
required
|
||||
/>
|
||||
<div className="flex items-center space-x-2 w-1/2">
|
||||
<Checkbox id="secure" name="secure" defaultChecked />
|
||||
<Label htmlFor="secure" className="text-xs">SSL/TLS</Label>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="smtp" className="space-y-2 pt-2">
|
||||
<Input
|
||||
type="text"
|
||||
name="smtp_host"
|
||||
placeholder="SMTP server (optional)"
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
<div className="flex space-x-2">
|
||||
<Input
|
||||
type="number"
|
||||
name="smtp_port"
|
||||
placeholder="Port"
|
||||
className="h-8 text-xs w-1/2"
|
||||
defaultValue="587"
|
||||
/>
|
||||
<div className="flex items-center space-x-2 w-1/2">
|
||||
<Checkbox id="smtp_secure" name="smtp_secure" />
|
||||
<Label htmlFor="smtp_secure" className="text-xs">SSL/TLS</Label>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
// If display_name is empty, use email
|
||||
if (!formValues.display_name) {
|
||||
formValues.display_name = formValues.email;
|
||||
}
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-1">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 text-xs"
|
||||
onClick={() => setShowAddAccountForm(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
className="h-7 text-xs"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
|
||||
Adding...
|
||||
</>
|
||||
) : "Add Account"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{accountsDropdownOpen && (
|
||||
<div className="space-y-1 pl-2 accounts-dropdown">
|
||||
{accounts.map(account => {
|
||||
// Log outside of the JSX to avoid linter issues
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log('Rendering account:', account.id, account.name, 'has folders:', account.folders?.length || 0);
|
||||
}
|
||||
return (
|
||||
<div key={account.id} className="relative group account-item">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full justify-between px-2 py-1.5 text-sm group"
|
||||
onClick={() => {
|
||||
// Toggle folders for this specific account
|
||||
if (selectedAccount?.id === account.id) {
|
||||
console.log('[DEBUG] Toggling folders visibility for existing selection:', account.id, showFolders ? 'hiding' : 'showing');
|
||||
setShowFolders(!showFolders);
|
||||
} else {
|
||||
// When selecting a new account, make sure we show its folders
|
||||
console.log('[DEBUG] Selecting different account', {
|
||||
oldAccount: selectedAccount ? selectedAccount.id : 'none',
|
||||
newAccount: account.id,
|
||||
folders: account.folders?.length || 0
|
||||
});
|
||||
setShowFolders(true);
|
||||
|
||||
// Reset to the inbox folder of the new account by default
|
||||
if (account.folders && account.folders.length > 0) {
|
||||
// Find INBOX or default to first folder
|
||||
const inboxFolder = account.folders.find(f =>
|
||||
f.toLowerCase() === 'inbox') || account.folders[0];
|
||||
|
||||
console.log('[DEBUG] Switching to folder:', inboxFolder, 'for account', account.id);
|
||||
|
||||
// Change to this account's inbox folder
|
||||
handleMailboxChange(inboxFolder, account.id);
|
||||
} else {
|
||||
console.log('[DEBUG] No folders found for account', account.id);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[DEBUG] Setting selected account to:', account.id, account.email);
|
||||
setSelectedAccount(account);
|
||||
|
||||
// Force the account to have folders if it doesn't already
|
||||
if (account.id !== 'all-accounts' && (!account.folders || account.folders.length === 0)) {
|
||||
console.log('[DEBUG] Account has no folders, fetching folders for account:', account.id);
|
||||
// Fetch folders for this account if not already available
|
||||
fetch(`/api/courrier/account-folders?accountId=${account.id}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
console.log('[DEBUG] Received folders from API:', data);
|
||||
if (data.folders && Array.isArray(data.folders)) {
|
||||
console.log('[DEBUG] Updating account with fetched folders:', data.folders.length);
|
||||
const accountWithFolders = {
|
||||
...account,
|
||||
folders: data.folders
|
||||
};
|
||||
setSelectedAccount(accountWithFolders);
|
||||
|
||||
// Also update the account in the accounts array
|
||||
const newAccounts = accounts.map(a =>
|
||||
a.id === account.id ? accountWithFolders : a
|
||||
);
|
||||
setAccounts(newAccounts);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(`[ERROR] Error fetching folders for account ${account.id}:`, err);
|
||||
// Use default folders if API fails
|
||||
const accountWithFolders = {
|
||||
...account,
|
||||
folders: ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']
|
||||
};
|
||||
setSelectedAccount(accountWithFolders);
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2.5 h-2.5 rounded-full ${account.color}`}></div>
|
||||
<span className="font-medium text-gray-700 truncate">{account.name}</span>
|
||||
</div>
|
||||
{/* Show arrow for all accounts */}
|
||||
<div className="flex items-center">
|
||||
{selectedAccount?.id === account.id && showFolders ?
|
||||
<ChevronDown className="h-3.5 w-3.5 text-gray-500" /> :
|
||||
<ChevronRight className="h-3.5 w-3.5 text-gray-500" />
|
||||
}
|
||||
</div>
|
||||
</Button>
|
||||
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
|
||||
})
|
||||
});
|
||||
|
||||
{/* Show folders for this account if it's selected and folders are shown */}
|
||||
{((selectedAccount?.id === account.id && showFolders) ||
|
||||
(!selectedAccount && account.id !== 'all-accounts' && account.folders)) && (
|
||||
<div className="pl-4 mt-1 mb-2 space-y-0.5 border-l border-gray-200 folder-container">
|
||||
{/* No console.log here - moved to useEffect */}
|
||||
{account.folders && account.folders.length > 0 ? (
|
||||
account.folders.map((folder, folderIndex) => (
|
||||
<Button
|
||||
key={folder}
|
||||
variant="ghost"
|
||||
className={`w-full justify-start py-1 px-2 text-xs folder-item ${
|
||||
currentView === folder ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
// Pass the account ID along with the folder name
|
||||
handleMailboxChange(folder, selectedAccount?.id);
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between w-full gap-1.5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{React.createElement(getFolderIcon(folder), { className: "h-3.5 w-3.5" })}
|
||||
<span className="truncate">{formatFolderName(folder)}</span>
|
||||
</div>
|
||||
{folder === 'INBOX' && unreadCount > 0 && (
|
||||
<span className="inline-flex items-center justify-center px-1.5 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 rounded">
|
||||
{unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
))
|
||||
) : (
|
||||
<div className="text-xs text-gray-500 px-2 py-1">No folders found</div>
|
||||
)}
|
||||
</div>
|
||||
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>
|
||||
|
||||
{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>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Categories Section */}
|
||||
<div className="p-3 border-b border-gray-100">
|
||||
<h4 className="mb-2 text-sm font-medium text-gray-500">Categories</h4>
|
||||
<div className="space-y-1 pl-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={`w-full justify-start text-sm py-1 px-2 ${
|
||||
currentView === 'starred' ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
onClick={() => handleMailboxChange('starred')}
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Star className="h-3.5 w-3.5" />
|
||||
<span>Starred</span>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
{/* Email List and Content View */}
|
||||
<div className="flex-1 flex overflow-hidden">
|
||||
{/* Email List */}
|
||||
<EmailList
|
||||
emails={emails}
|
||||
selectedEmailIds={selectedEmailIds}
|
||||
selectedEmail={selectedEmail}
|
||||
currentFolder={currentFolder}
|
||||
isLoading={isLoading}
|
||||
totalEmails={emails.length}
|
||||
hasMoreEmails={hasMoreEmails}
|
||||
onSelectEmail={handleEmailSelect}
|
||||
onToggleSelect={toggleEmailSelection}
|
||||
onToggleSelectAll={toggleSelectAll}
|
||||
onBulkAction={handleBulkAction}
|
||||
onToggleStarred={toggleStarred}
|
||||
onLoadMore={handleLoadMore}
|
||||
onSearch={searchEmails}
|
||||
/>
|
||||
|
||||
{/* Main Content Area - conditionally show email list or detail view */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Header bar with search */}
|
||||
<div className="p-2 border-b border-gray-100 bg-white flex items-center justify-between">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="md:hidden h-9 w-9"
|
||||
onClick={() => setMobileSidebarOpen(!mobileSidebarOpen)}
|
||||
>
|
||||
<Menu className="h-5 w-5 text-gray-500" />
|
||||
</Button>
|
||||
|
||||
<div className="flex-1 max-w-md relative">
|
||||
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
value={searchQuery}
|
||||
onChange={(e) => searchEmails(e.target.value)}
|
||||
placeholder="Search emails..."
|
||||
className="pl-8 h-9 bg-gray-50 border-gray-200"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
{selectedEmailIds.length > 0 && (
|
||||
<div className="flex items-center mr-2 space-x-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
onClick={() => handleBulkAction('mark-read')}
|
||||
>
|
||||
<MessageSquare className="h-4 w-4 text-gray-500" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
onClick={() => handleBulkAction('delete')}
|
||||
>
|
||||
<Trash className="h-4 w-4 text-gray-500" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
onClick={() => handleBulkAction('archive')}
|
||||
>
|
||||
<Archive className="h-4 w-4 text-gray-500" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{session?.user && (
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarFallback className="bg-blue-100 text-blue-600">
|
||||
{session.user.name?.substring(0, 2) || session.user.email?.substring(0, 2) || 'U'}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email Content View */}
|
||||
<div className="flex-1 bg-white/95 backdrop-blur-sm flex flex-col">
|
||||
{selectedEmail ? (
|
||||
<EmailDetailView
|
||||
email={selectedEmail}
|
||||
{/* Email List or Detail View */}
|
||||
<div className="flex-1 overflow-hidden bg-white">
|
||||
{isLoading ? (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-2" />
|
||||
<p className="text-sm text-gray-500">Loading emails...</p>
|
||||
</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="max-w-md p-4">
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>
|
||||
{error}
|
||||
{(error?.includes('Not authenticated') || error?.includes('No email credentials found')) && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="mt-2"
|
||||
onClick={handleGoToLogin}
|
||||
>
|
||||
Go to login
|
||||
</Button>
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
</div>
|
||||
) : selectedEmail ? (
|
||||
<EmailDetailView
|
||||
email={selectedEmail}
|
||||
onBack={() => handleEmailSelect('')}
|
||||
onReply={handleReply}
|
||||
onReplyAll={handleReplyAll}
|
||||
@ -1051,19 +1045,36 @@ export default function CourrierPage() {
|
||||
onToggleStar={() => toggleStarred(selectedEmail.id)}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex-1 flex flex-col items-center justify-center text-center p-8">
|
||||
<Mail className="h-12 w-12 text-gray-300 mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-1">Select an email to read</h3>
|
||||
<p className="text-sm text-gray-500 max-w-sm">
|
||||
Choose an email from the list or compose a new message to get started.
|
||||
</p>
|
||||
<Button
|
||||
className="mt-6 bg-blue-600 hover:bg-blue-700"
|
||||
onClick={handleComposeNew}
|
||||
>
|
||||
<PlusIcon className="mr-2 h-4 w-4" />
|
||||
Compose New
|
||||
</Button>
|
||||
<div className="h-full overflow-hidden flex flex-col">
|
||||
{/* Email List */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{emails.length === 0 ? (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="text-center p-6">
|
||||
<Inbox className="h-12 w-12 text-gray-300 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-700">No emails found</h3>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
{searchQuery
|
||||
? `No results found for "${searchQuery}"`
|
||||
: `Your ${currentFolder.toLowerCase()} is empty`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<EmailList
|
||||
emails={emails}
|
||||
selectedEmailIds={selectedEmailIds}
|
||||
selectedEmail={selectedEmail}
|
||||
onSelectEmail={handleEmailSelect}
|
||||
onToggleSelect={toggleEmailSelection}
|
||||
onToggleSelectAll={toggleSelectAll}
|
||||
onToggleStar={toggleStarred}
|
||||
onLoadMore={handleLoadMore}
|
||||
hasMoreEmails={page < totalPages}
|
||||
currentFolder={currentFolder}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -1072,32 +1083,31 @@ export default function CourrierPage() {
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Login needed alert */}
|
||||
<LoginNeededAlert
|
||||
show={showLoginNeeded}
|
||||
onLogin={handleGoToLogin}
|
||||
onClose={() => setShowLoginNeeded(false)}
|
||||
/>
|
||||
|
||||
{/* Compose Modal */}
|
||||
{showComposeModal && (
|
||||
<ComposeEmail
|
||||
initialEmail={selectedEmail}
|
||||
type={composeType}
|
||||
onClose={() => setShowComposeModal(false)}
|
||||
onSend={async (emailData: EmailData) => {
|
||||
await sendEmail(emailData);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<DeleteConfirmDialog
|
||||
{/* Modals and Dialogs */}
|
||||
<DeleteConfirmDialog
|
||||
show={showDeleteConfirm}
|
||||
selectedCount={selectedEmailIds.length}
|
||||
open={showDeleteConfirm}
|
||||
onOpenChange={setShowDeleteConfirm}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
onCancel={() => setShowDeleteConfirm(false)}
|
||||
/>
|
||||
|
||||
<LoginNeededAlert
|
||||
open={showLoginNeeded}
|
||||
onOpenChange={setShowLoginNeeded}
|
||||
onGoToLogin={handleGoToLogin}
|
||||
/>
|
||||
|
||||
<Dialog open={showComposeModal} onOpenChange={setShowComposeModal}>
|
||||
<DialogContent className="sm:max-w-[800px] h-[80vh] p-0 overflow-hidden">
|
||||
<ComposeEmail
|
||||
type={composeType}
|
||||
replyToEmail={composeType !== 'new' ? selectedEmail : undefined}
|
||||
onSend={handleSendEmail}
|
||||
onCancel={() => setShowComposeModal(false)}
|
||||
isSending={isSending}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user