From e33be9551692ae997e535908ec91f27706de5f43 Mon Sep 17 00:00:00 2001 From: alma Date: Sun, 27 Apr 2025 19:45:43 +0200 Subject: [PATCH] courrier multi account restore compose --- .env | 8 +- app/api/courrier/account-folders/route.ts | 36 +- app/courrier/page.tsx | 417 +++++++++++----------- components/email/ComposeEmail.tsx | 2 +- 4 files changed, 233 insertions(+), 230 deletions(-) diff --git a/.env b/.env index 36f3843e..9cc6b291 100644 --- a/.env +++ b/.env @@ -77,6 +77,12 @@ IMAP_HOST=mail.infomaniak.com IMAP_PORT=993 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_PORT=6379 -REDIS_PASSWORD=mySecretPassword +# Add a secret key for encrypting sensitive data in Redis +REDIS_ENCRYPTION_KEY=your-random-32-char-encryption-key \ No newline at end of file diff --git a/app/api/courrier/account-folders/route.ts b/app/api/courrier/account-folders/route.ts index efbc8695..622d8cfa 100644 --- a/app/api/courrier/account-folders/route.ts +++ b/app/api/courrier/account-folders/route.ts @@ -105,28 +105,28 @@ export async function GET(request: Request) { const accountsWithFolders = await Promise.all(accounts.map(async (account) => { try { // Connect to IMAP server for this specific account - const client = new ImapFlow({ + const client = new ImapFlow({ host: account.host, port: account.port, - secure: true, - auth: { + secure: true, + auth: { user: account.email, pass: account.password, - }, - logger: false, - tls: { - rejectUnauthorized: false - } - }); - - await client.connect(); - + }, + logger: false, + tls: { + rejectUnauthorized: false + } + }); + + await client.connect(); + // Get folders for this account - const folders = await getMailboxes(client); - - // Close connection - await client.logout(); - + const folders = await getMailboxes(client); + + // Close connection + await client.logout(); + // Add display_name and color from database const metadata = await prisma.$queryRaw` SELECT display_name, color @@ -143,7 +143,7 @@ export async function GET(request: Request) { color: displayMetadata.color || "#0082c9", folders }; - } catch (error) { + } catch (error) { console.error(`Error fetching folders for account ${account.email}:`, error); // Return fallback folders on error return { diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 0efb79a0..0f7df298 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -11,7 +11,7 @@ import { MoreHorizontal, FolderOpen, X, Paperclip, MessageSquare, Copy, EyeOff, AlertOctagon, Archive, RefreshCw, Menu } 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 { AlertDialog, @@ -143,13 +143,13 @@ export default function CourrierPage() { const updated = [...prev]; if (updated.length > 1) { // Only update folders, preserve other properties including ID - if (updated[1]) { + if (updated[1]) { updated[1] = { ...updated[1], folders: mailboxes }; - } - console.log('Updated accounts with new mailboxes:', updated); + } + console.log('Updated accounts with new mailboxes:', updated); } return updated; }); @@ -265,7 +265,7 @@ export default function CourrierPage() { }); }); } - + if (!isMounted) return; if (data.authenticated) { @@ -309,11 +309,11 @@ export default function CourrierPage() { // Update the loading account in place to maintain references updatedAccounts[1] = { id: account.id, // Use the real account ID - name: account.display_name || account.email, - email: account.email, - color: account.color || 'bg-blue-500', + name: account.display_name || account.email, + email: account.email, + color: account.color || 'bg-blue-500', folders: accountFolders - }; + }; console.log(`[DEBUG] Updated loading account to real account: ${account.email} with ID ${account.id}`); } else { // Add additional accounts as new entries @@ -346,16 +346,16 @@ export default function CourrierPage() { }); } } else if (data.email) { - // Fallback to single account if allAccounts is not available - console.log(`[DEBUG] Fallback to single account: ${data.email}`); - - // Force include some hardcoded folders if none are present - const fallbackFolders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']; - - // Prioritize mailboxes from IMAP if available - const folderList = (data.mailboxes && data.mailboxes.length > 0) ? - data.mailboxes : fallbackFolders; - + // Fallback to single account if allAccounts is not available + console.log(`[DEBUG] Fallback to single account: ${data.email}`); + + // Force include some hardcoded folders if none are present + const fallbackFolders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']; + + // Prioritize mailboxes from IMAP if available + const folderList = (data.mailboxes && data.mailboxes.length > 0) ? + data.mailboxes : fallbackFolders; + // Update the loading account if it exists if (updatedAccounts.length > 1) { updatedAccounts[1] = { @@ -677,195 +677,195 @@ export default function CourrierPage() { - + {/* Display all accounts */}
- {/* Form for adding a new account */} - {showAddAccountForm && ( -
-

Add IMAP Account

-
{ - e.preventDefault(); - setLoading(true); + {/* Form for adding a new account */} + {showAddAccountForm && ( +
+

Add IMAP Account

+ { + 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); + } + }}> +
+ + + IMAP Settings + SMTP Settings + - 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); - } - }}> -
- - - IMAP Settings - SMTP Settings - -
- + required + />
- + required + />
- + />
- + required + />
- + defaultValue="993" + required + />
- +
-
-
-
- +
+
+ +
- + />
- -
+ defaultValue="587" + /> +
@@ -875,10 +875,10 @@ export default function CourrierPage() {
Note: SMTP settings are only needed for sending emails -
- - - +
+
+ +
- -
-
-
+ className="h-7 text-xs" + onClick={() => setShowAddAccountForm(false)} + > + Cancel + +
- )} - + + + )} + {accounts.map((account) => (
+ {unreadCount} + + )} +
+ ))} )} @@ -973,14 +973,14 @@ export default function CourrierPage() { - - + + {/* Main Content Area - conditionally show email list or detail view */} -
+
{/* Header bar with search */}
-
+
{selectedEmailIds.length > 0 && ( @@ -1024,8 +1024,8 @@ export default function CourrierPage() { onClick={() => handleBulkAction('archive')} > - -
+ +
)} {session?.user && ( @@ -1036,8 +1036,8 @@ export default function CourrierPage() { )} - - + + {/* Email List or Detail View */}
{isLoading ? ( @@ -1072,11 +1072,7 @@ export default function CourrierPage() { ) : selectedEmail ? ( { - handleEmailSelect(''); - // Ensure sidebar stays visible - setSidebarOpen(true); - }} + onBack={() => handleEmailSelect('')} onReply={handleReply} onReplyAll={handleReplyAll} onForward={handleForward} @@ -1084,7 +1080,7 @@ export default function CourrierPage() { /> ) : (
- {/* Email List */} + {/* Email List */}
{emails.length === 0 ? (
@@ -1099,15 +1095,15 @@ export default function CourrierPage() {
) : ( - - Compose Email + setShowComposeModal(false)} + onClose={() => setShowComposeModal(false)} isSending={isSending} - /> + /> diff --git a/components/email/ComposeEmail.tsx b/components/email/ComposeEmail.tsx index 1c77745c..ab2ff1ee 100644 --- a/components/email/ComposeEmail.tsx +++ b/components/email/ComposeEmail.tsx @@ -915,4 +915,4 @@ function LegacyAdapter({
); -} \ No newline at end of file +} \ No newline at end of file