courrier multi account restore compose
This commit is contained in:
parent
e4dbc6a5c9
commit
e33be95516
8
.env
8
.env
@ -77,6 +77,12 @@ IMAP_HOST=mail.infomaniak.com
|
|||||||
IMAP_PORT=993
|
IMAP_PORT=993
|
||||||
|
|
||||||
NEWS_API_URL="http://172.16.0.104:8000"
|
NEWS_API_URL="http://172.16.0.104:8000"
|
||||||
|
|
||||||
|
|
||||||
|
# Required Redis environment variables to add to .env
|
||||||
|
REDIS_URL=redis://:mySecretPassword@localhost:6379
|
||||||
|
REDIS_PASSWORD=mySecretPassword
|
||||||
REDIS_HOST=localhost
|
REDIS_HOST=localhost
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_PASSWORD=mySecretPassword
|
# Add a secret key for encrypting sensitive data in Redis
|
||||||
|
REDIS_ENCRYPTION_KEY=your-random-32-char-encryption-key
|
||||||
@ -105,28 +105,28 @@ export async function GET(request: Request) {
|
|||||||
const accountsWithFolders = await Promise.all(accounts.map(async (account) => {
|
const accountsWithFolders = await Promise.all(accounts.map(async (account) => {
|
||||||
try {
|
try {
|
||||||
// Connect to IMAP server for this specific account
|
// Connect to IMAP server for this specific account
|
||||||
const client = new ImapFlow({
|
const client = new ImapFlow({
|
||||||
host: account.host,
|
host: account.host,
|
||||||
port: account.port,
|
port: account.port,
|
||||||
secure: true,
|
secure: true,
|
||||||
auth: {
|
auth: {
|
||||||
user: account.email,
|
user: account.email,
|
||||||
pass: account.password,
|
pass: account.password,
|
||||||
},
|
},
|
||||||
logger: false,
|
logger: false,
|
||||||
tls: {
|
tls: {
|
||||||
rejectUnauthorized: false
|
rejectUnauthorized: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await client.connect();
|
await client.connect();
|
||||||
|
|
||||||
// Get folders for this account
|
// Get folders for this account
|
||||||
const folders = await getMailboxes(client);
|
const folders = await getMailboxes(client);
|
||||||
|
|
||||||
// Close connection
|
// Close connection
|
||||||
await client.logout();
|
await client.logout();
|
||||||
|
|
||||||
// Add display_name and color from database
|
// Add display_name and color from database
|
||||||
const metadata = await prisma.$queryRaw`
|
const metadata = await prisma.$queryRaw`
|
||||||
SELECT display_name, color
|
SELECT display_name, color
|
||||||
@ -143,7 +143,7 @@ export async function GET(request: Request) {
|
|||||||
color: displayMetadata.color || "#0082c9",
|
color: displayMetadata.color || "#0082c9",
|
||||||
folders
|
folders
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching folders for account ${account.email}:`, error);
|
console.error(`Error fetching folders for account ${account.email}:`, error);
|
||||||
// Return fallback folders on error
|
// Return fallback folders on error
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
MoreHorizontal, FolderOpen, X, Paperclip, MessageSquare, Copy, EyeOff,
|
MoreHorizontal, FolderOpen, X, Paperclip, MessageSquare, Copy, EyeOff,
|
||||||
AlertOctagon, Archive, RefreshCw, Menu
|
AlertOctagon, Archive, RefreshCw, Menu
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Dialog, DialogContent } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
@ -143,13 +143,13 @@ export default function CourrierPage() {
|
|||||||
const updated = [...prev];
|
const updated = [...prev];
|
||||||
if (updated.length > 1) {
|
if (updated.length > 1) {
|
||||||
// Only update folders, preserve other properties including ID
|
// Only update folders, preserve other properties including ID
|
||||||
if (updated[1]) {
|
if (updated[1]) {
|
||||||
updated[1] = {
|
updated[1] = {
|
||||||
...updated[1],
|
...updated[1],
|
||||||
folders: mailboxes
|
folders: mailboxes
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
console.log('Updated accounts with new mailboxes:', updated);
|
console.log('Updated accounts with new mailboxes:', updated);
|
||||||
}
|
}
|
||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
@ -265,7 +265,7 @@ export default function CourrierPage() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
|
|
||||||
if (data.authenticated) {
|
if (data.authenticated) {
|
||||||
@ -309,11 +309,11 @@ export default function CourrierPage() {
|
|||||||
// Update the loading account in place to maintain references
|
// Update the loading account in place to maintain references
|
||||||
updatedAccounts[1] = {
|
updatedAccounts[1] = {
|
||||||
id: account.id, // Use the real account ID
|
id: account.id, // Use the real account ID
|
||||||
name: account.display_name || account.email,
|
name: account.display_name || account.email,
|
||||||
email: account.email,
|
email: account.email,
|
||||||
color: account.color || 'bg-blue-500',
|
color: account.color || 'bg-blue-500',
|
||||||
folders: accountFolders
|
folders: accountFolders
|
||||||
};
|
};
|
||||||
console.log(`[DEBUG] Updated loading account to real account: ${account.email} with ID ${account.id}`);
|
console.log(`[DEBUG] Updated loading account to real account: ${account.email} with ID ${account.id}`);
|
||||||
} else {
|
} else {
|
||||||
// Add additional accounts as new entries
|
// Add additional accounts as new entries
|
||||||
@ -346,16 +346,16 @@ export default function CourrierPage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (data.email) {
|
} else if (data.email) {
|
||||||
// Fallback to single account if allAccounts is not available
|
// Fallback to single account if allAccounts is not available
|
||||||
console.log(`[DEBUG] Fallback to single account: ${data.email}`);
|
console.log(`[DEBUG] Fallback to single account: ${data.email}`);
|
||||||
|
|
||||||
// Force include some hardcoded folders if none are present
|
// Force include some hardcoded folders if none are present
|
||||||
const fallbackFolders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'];
|
const fallbackFolders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'];
|
||||||
|
|
||||||
// Prioritize mailboxes from IMAP if available
|
// Prioritize mailboxes from IMAP if available
|
||||||
const folderList = (data.mailboxes && data.mailboxes.length > 0) ?
|
const folderList = (data.mailboxes && data.mailboxes.length > 0) ?
|
||||||
data.mailboxes : fallbackFolders;
|
data.mailboxes : fallbackFolders;
|
||||||
|
|
||||||
// Update the loading account if it exists
|
// Update the loading account if it exists
|
||||||
if (updatedAccounts.length > 1) {
|
if (updatedAccounts.length > 1) {
|
||||||
updatedAccounts[1] = {
|
updatedAccounts[1] = {
|
||||||
@ -677,195 +677,195 @@ export default function CourrierPage() {
|
|||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Display all accounts */}
|
{/* Display all accounts */}
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
{/* Form for adding a new account */}
|
{/* Form for adding a new account */}
|
||||||
{showAddAccountForm && (
|
{showAddAccountForm && (
|
||||||
<div className="mb-3 p-2 border border-gray-200 rounded-md bg-gray-50">
|
<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>
|
<h4 className="text-xs font-medium mb-2 text-gray-700">Add IMAP Account</h4>
|
||||||
<form onSubmit={async (e) => {
|
<form onSubmit={async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
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>
|
||||||
|
|
||||||
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">
|
<TabsContent value="imap" className="mt-2 space-y-2">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="email" className="text-xs">Email</Label>
|
<Label htmlFor="email" className="text-xs">Email</Label>
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
id="email"
|
||||||
name="email"
|
name="email"
|
||||||
placeholder="email@example.com"
|
placeholder="email@example.com"
|
||||||
className="h-7 text-xs"
|
className="h-7 text-xs"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="password" className="text-xs">Password</Label>
|
<Label htmlFor="password" className="text-xs">Password</Label>
|
||||||
<Input
|
<Input
|
||||||
id="password"
|
id="password"
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="•••••••••"
|
placeholder="•••••••••"
|
||||||
className="h-7 text-xs"
|
className="h-7 text-xs"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="display_name" className="text-xs">Display Name</Label>
|
<Label htmlFor="display_name" className="text-xs">Display Name</Label>
|
||||||
<Input
|
<Input
|
||||||
id="display_name"
|
id="display_name"
|
||||||
name="display_name"
|
name="display_name"
|
||||||
placeholder="John Doe"
|
placeholder="John Doe"
|
||||||
className="h-7 text-xs"
|
className="h-7 text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="host" className="text-xs">IMAP Server</Label>
|
<Label htmlFor="host" className="text-xs">IMAP Server</Label>
|
||||||
<Input
|
<Input
|
||||||
id="host"
|
id="host"
|
||||||
name="host"
|
name="host"
|
||||||
placeholder="imap.example.com"
|
placeholder="imap.example.com"
|
||||||
className="h-7 text-xs"
|
className="h-7 text-xs"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Label htmlFor="port" className="text-xs">Port</Label>
|
<Label htmlFor="port" className="text-xs">Port</Label>
|
||||||
<Input
|
<Input
|
||||||
id="port"
|
id="port"
|
||||||
name="port"
|
name="port"
|
||||||
placeholder="993"
|
placeholder="993"
|
||||||
className="h-7 text-xs"
|
className="h-7 text-xs"
|
||||||
defaultValue="993"
|
defaultValue="993"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-end pb-1">
|
<div className="flex items-end pb-1">
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<Checkbox id="secure" name="secure" defaultChecked />
|
<Checkbox id="secure" name="secure" defaultChecked />
|
||||||
<Label htmlFor="secure" className="text-xs">SSL</Label>
|
<Label htmlFor="secure" className="text-xs">SSL</Label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="smtp" className="mt-2 space-y-2">
|
<TabsContent value="smtp" className="mt-2 space-y-2">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="smtp_host" className="text-xs">SMTP Server</Label>
|
<Label htmlFor="smtp_host" className="text-xs">SMTP Server</Label>
|
||||||
<Input
|
<Input
|
||||||
id="smtp_host"
|
id="smtp_host"
|
||||||
name="smtp_host"
|
name="smtp_host"
|
||||||
placeholder="smtp.example.com"
|
placeholder="smtp.example.com"
|
||||||
className="h-7 text-xs"
|
className="h-7 text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Label htmlFor="smtp_port" className="text-xs">Port</Label>
|
<Label htmlFor="smtp_port" className="text-xs">Port</Label>
|
||||||
<Input
|
<Input
|
||||||
id="smtp_port"
|
id="smtp_port"
|
||||||
name="smtp_port"
|
name="smtp_port"
|
||||||
placeholder="587"
|
placeholder="587"
|
||||||
className="h-7 text-xs"
|
className="h-7 text-xs"
|
||||||
defaultValue="587"
|
defaultValue="587"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-end pb-1">
|
<div className="flex items-end pb-1">
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<Checkbox id="smtp_secure" name="smtp_secure" defaultChecked />
|
<Checkbox id="smtp_secure" name="smtp_secure" defaultChecked />
|
||||||
@ -875,10 +875,10 @@ export default function CourrierPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500 italic">
|
<div className="text-xs text-gray-500 italic">
|
||||||
Note: SMTP settings are only needed for sending emails
|
Note: SMTP settings are only needed for sending emails
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<div className="flex gap-2 pt-2">
|
<div className="flex gap-2 pt-2">
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
@ -888,20 +888,20 @@ export default function CourrierPage() {
|
|||||||
{loading ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : null}
|
{loading ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : null}
|
||||||
Test & Add
|
Test & Add
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-7 text-xs"
|
className="h-7 text-xs"
|
||||||
onClick={() => setShowAddAccountForm(false)}
|
onClick={() => setShowAddAccountForm(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</form>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{accounts.map((account) => (
|
{accounts.map((account) => (
|
||||||
<div key={account.id} className="mb-1">
|
<div key={account.id} className="mb-1">
|
||||||
<Button
|
<Button
|
||||||
@ -909,7 +909,7 @@ export default function CourrierPage() {
|
|||||||
className={`w-full justify-start text-xs ${selectedAccount?.id === account.id ? 'bg-gray-100' : ''}`}
|
className={`w-full justify-start text-xs ${selectedAccount?.id === account.id ? 'bg-gray-100' : ''}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedAccount(account);
|
setSelectedAccount(account);
|
||||||
setShowFolders(true);
|
setShowFolders(true);
|
||||||
// Auto-expand this account when selected
|
// Auto-expand this account when selected
|
||||||
if (account.id !== 'all-accounts') {
|
if (account.id !== 'all-accounts') {
|
||||||
setExpandedAccounts(prev => ({
|
setExpandedAccounts(prev => ({
|
||||||
@ -949,22 +949,22 @@ export default function CourrierPage() {
|
|||||||
{expandedAccounts[account.id] && account.id !== 'all-accounts' && account.folders && (
|
{expandedAccounts[account.id] && account.id !== 'all-accounts' && account.folders && (
|
||||||
<div className="pl-4">
|
<div className="pl-4">
|
||||||
{account.folders.map((folder) => (
|
{account.folders.map((folder) => (
|
||||||
<Button
|
<Button
|
||||||
key={folder}
|
key={folder}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={`w-full justify-start text-xs py-1 h-7 ${currentFolder === folder ? 'bg-gray-100' : ''}`}
|
className={`w-full justify-start text-xs py-1 h-7 ${currentFolder === folder ? 'bg-gray-100' : ''}`}
|
||||||
onClick={() => handleMailboxChange(folder, account.id !== 'all-accounts' ? account.id : undefined)}
|
onClick={() => handleMailboxChange(folder, account.id !== 'all-accounts' ? account.id : undefined)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center w-full">
|
<div className="flex items-center w-full">
|
||||||
{getFolderIcon(folder)}
|
{getFolderIcon(folder)}
|
||||||
<span className="ml-2 truncate text-gray-700">{formatFolderName(folder)}</span>
|
<span className="ml-2 truncate text-gray-700">{formatFolderName(folder)}</span>
|
||||||
{folder === 'INBOX' && unreadCount > 0 && (
|
{folder === 'INBOX' && unreadCount > 0 && (
|
||||||
<span className="ml-auto bg-blue-500 text-white text-[10px] px-1.5 rounded-full">
|
<span className="ml-auto bg-blue-500 text-white text-[10px] px-1.5 rounded-full">
|
||||||
{unreadCount}
|
{unreadCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -973,14 +973,14 @@ export default function CourrierPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content Area - conditionally show email list or detail view */}
|
{/* Main Content Area - conditionally show email list or detail view */}
|
||||||
<div className="flex-1 flex flex-col overflow-hidden" style={{ maxWidth: 'calc(100% - 240px)' }}>
|
<div className="flex-1 flex flex-col overflow-hidden">
|
||||||
{/* Header bar with search */}
|
{/* Header bar with search */}
|
||||||
<div className="p-2 border-b border-gray-100 bg-white flex items-center justify-between">
|
<div className="p-2 border-b border-gray-100 bg-white flex items-center justify-between">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="md:hidden h-9 w-9"
|
className="md:hidden h-9 w-9"
|
||||||
onClick={() => setMobileSidebarOpen(!mobileSidebarOpen)}
|
onClick={() => setMobileSidebarOpen(!mobileSidebarOpen)}
|
||||||
@ -996,7 +996,7 @@ export default function CourrierPage() {
|
|||||||
placeholder="Search emails..."
|
placeholder="Search emails..."
|
||||||
className="pl-8 h-9 bg-gray-50 border-gray-200"
|
className="pl-8 h-9 bg-gray-50 border-gray-200"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{selectedEmailIds.length > 0 && (
|
{selectedEmailIds.length > 0 && (
|
||||||
@ -1024,8 +1024,8 @@ export default function CourrierPage() {
|
|||||||
onClick={() => handleBulkAction('archive')}
|
onClick={() => handleBulkAction('archive')}
|
||||||
>
|
>
|
||||||
<Archive className="h-4 w-4 text-gray-500" />
|
<Archive className="h-4 w-4 text-gray-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{session?.user && (
|
{session?.user && (
|
||||||
@ -1036,8 +1036,8 @@ export default function CourrierPage() {
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Email List or Detail View */}
|
{/* Email List or Detail View */}
|
||||||
<div className="flex-1 overflow-hidden bg-white">
|
<div className="flex-1 overflow-hidden bg-white">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@ -1072,11 +1072,7 @@ export default function CourrierPage() {
|
|||||||
) : selectedEmail ? (
|
) : selectedEmail ? (
|
||||||
<EmailDetailView
|
<EmailDetailView
|
||||||
email={selectedEmail}
|
email={selectedEmail}
|
||||||
onBack={() => {
|
onBack={() => handleEmailSelect('')}
|
||||||
handleEmailSelect('');
|
|
||||||
// Ensure sidebar stays visible
|
|
||||||
setSidebarOpen(true);
|
|
||||||
}}
|
|
||||||
onReply={handleReply}
|
onReply={handleReply}
|
||||||
onReplyAll={handleReplyAll}
|
onReplyAll={handleReplyAll}
|
||||||
onForward={handleForward}
|
onForward={handleForward}
|
||||||
@ -1084,7 +1080,7 @@ export default function CourrierPage() {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full overflow-hidden flex flex-col">
|
<div className="h-full overflow-hidden flex flex-col">
|
||||||
{/* Email List */}
|
{/* Email List */}
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
{emails.length === 0 ? (
|
{emails.length === 0 ? (
|
||||||
<div className="h-full flex items-center justify-center">
|
<div className="h-full flex items-center justify-center">
|
||||||
@ -1099,15 +1095,15 @@ export default function CourrierPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<EmailList
|
<EmailList
|
||||||
emails={emails}
|
emails={emails}
|
||||||
selectedEmailIds={selectedEmailIds}
|
selectedEmailIds={selectedEmailIds}
|
||||||
selectedEmail={selectedEmail}
|
selectedEmail={selectedEmail}
|
||||||
onSelectEmail={handleEmailSelect}
|
onSelectEmail={handleEmailSelect}
|
||||||
onToggleSelect={toggleEmailSelection}
|
onToggleSelect={toggleEmailSelection}
|
||||||
onToggleSelectAll={toggleSelectAll}
|
onToggleSelectAll={toggleSelectAll}
|
||||||
onToggleStarred={toggleStarred}
|
onToggleStarred={toggleStarred}
|
||||||
onLoadMore={handleLoadMore}
|
onLoadMore={handleLoadMore}
|
||||||
hasMoreEmails={page < totalPages}
|
hasMoreEmails={page < totalPages}
|
||||||
currentFolder={currentFolder}
|
currentFolder={currentFolder}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
@ -1140,13 +1136,14 @@ export default function CourrierPage() {
|
|||||||
|
|
||||||
<Dialog open={showComposeModal} onOpenChange={setShowComposeModal}>
|
<Dialog open={showComposeModal} onOpenChange={setShowComposeModal}>
|
||||||
<DialogContent className="sm:max-w-[800px] h-[80vh] p-0 overflow-hidden">
|
<DialogContent className="sm:max-w-[800px] h-[80vh] p-0 overflow-hidden">
|
||||||
<ComposeEmail
|
<DialogTitle className="sr-only">Compose Email</DialogTitle>
|
||||||
type={composeType}
|
<ComposeEmail
|
||||||
|
type={composeType}
|
||||||
initialEmail={composeType !== 'new' ? selectedEmail : undefined}
|
initialEmail={composeType !== 'new' ? selectedEmail : undefined}
|
||||||
onSend={handleSendEmail}
|
onSend={handleSendEmail}
|
||||||
onClose={() => setShowComposeModal(false)}
|
onClose={() => setShowComposeModal(false)}
|
||||||
isSending={isSending}
|
isSending={isSending}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -915,4 +915,4 @@ function LegacyAdapter({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user