From fdd91c4b3d6754954d08796c0c53ad8e1f6cb46b Mon Sep 17 00:00:00 2001 From: alma Date: Mon, 28 Apr 2025 18:52:24 +0200 Subject: [PATCH] courrier multi account restore compose --- app/api/courrier/login/route.ts | 131 -------------------------------- app/courrier/login/page.tsx | 116 ---------------------------- app/courrier/page.tsx | 73 +++--------------- 3 files changed, 12 insertions(+), 308 deletions(-) delete mode 100644 app/api/courrier/login/route.ts delete mode 100644 app/courrier/login/page.tsx diff --git a/app/api/courrier/login/route.ts b/app/api/courrier/login/route.ts deleted file mode 100644 index 34706a67..00000000 --- a/app/api/courrier/login/route.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { NextResponse } from 'next/server'; -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/app/api/auth/[...nextauth]/route'; -import { - saveUserEmailCredentials, - getUserEmailCredentials, - testEmailConnection -} from '@/lib/services/email-service'; -import { prefetchUserEmailData } from '@/lib/services/prefetch-service'; -import { - cacheEmailCredentials, - invalidateUserEmailCache, - getCachedEmailCredentials -} from '@/lib/redis'; -import { prisma } from '@/lib/prisma'; - -export async function POST(request: Request) { - try { - // Authenticate user - const session = await getServerSession(authOptions); - if (!session?.user?.id) { - return NextResponse.json( - { error: 'Unauthorized' }, - { status: 401 } - ); - } - - // Get credentials from request - const { email, password, host, port } = await request.json(); - - // Validate required fields - if (!email || !password || !host || !port) { - return NextResponse.json( - { error: 'Missing required fields' }, - { status: 400 } - ); - } - - // Test connection before saving - const connectionSuccess = await testEmailConnection({ - email, - password, - host, - port: parseInt(port) - }); - - if (!connectionSuccess) { - return NextResponse.json( - { error: 'Failed to connect to email server. Please check your credentials.' }, - { status: 401 } - ); - } - - // Invalidate all cached data for this user as they are changing their credentials - await invalidateUserEmailCache(session.user.id); - - // Create credentials object with required fields - const credentials = { - email, - password, - host, - port: parseInt(port), - secure: true // Default to secure connection - }; - - // Save credentials in the database and Redis - // Use email as the accountId since it's unique per user - await saveUserEmailCredentials(session.user.id, email, credentials); - - // Start prefetching email data in the background - // We don't await this to avoid blocking the response - prefetchUserEmailData(session.user.id).catch(err => { - console.error('Background prefetch error:', err); - }); - - return NextResponse.json({ success: true }); - } catch (error) { - console.error('Error in login handler:', error); - return NextResponse.json( - { error: 'An unexpected error occurred' }, - { status: 500 } - ); - } -} - -export async function GET() { - try { - const session = await getServerSession(authOptions); - if (!session?.user?.id) { - return NextResponse.json( - { error: 'Unauthorized' }, - { status: 401 } - ); - } - - // First try to get from Redis cache - let credentials = await getCachedEmailCredentials(session.user.id, 'default'); - - // If not in cache, get from database - if (!credentials) { - credentials = await prisma.mailCredentials.findUnique({ - where: { - userId: session.user.id - }, - select: { - email: true, - host: true, - port: true - } - }); - } else { - // Remove password from response - const { password, ...safeCredentials } = credentials; - credentials = safeCredentials; - } - - if (!credentials) { - return NextResponse.json( - { error: 'No stored credentials found' }, - { status: 404 } - ); - } - - return NextResponse.json(credentials); - } catch (error) { - return NextResponse.json( - { error: 'Failed to retrieve credentials' }, - { status: 500 } - ); - } -} \ No newline at end of file diff --git a/app/courrier/login/page.tsx b/app/courrier/login/page.tsx deleted file mode 100644 index 8726f4ba..00000000 --- a/app/courrier/login/page.tsx +++ /dev/null @@ -1,116 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; - -export default function MailLoginPage() { - const router = useRouter(); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [host, setHost] = useState('mail.infomaniak.com'); - const [port, setPort] = useState('993'); - const [error, setError] = useState(''); - const [loading, setLoading] = useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(''); - setLoading(true); - - try { - const response = await fetch('/api/courrier/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - email, - password, - host, - port, - }), - }); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.error || 'Failed to connect to email server'); - } - - // Redirect to mail page - router.push('/mail'); - } catch (err) { - setError(err instanceof Error ? err.message : 'An error occurred'); - } finally { - setLoading(false); - } - }; - - return ( -
- - - Email Configuration - - -
-
- - setEmail(e.target.value)} - required - /> -
-
- - setPassword(e.target.value)} - required - /> -
-
- - setHost(e.target.value)} - required - /> -
-
- - setPort(e.target.value)} - required - /> -
- {error && ( -
{error}
- )} - -
-
-
-
- ); -} \ No newline at end of file diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index d8e3426d..6a6f29e4 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -39,7 +39,7 @@ import EmailList from '@/components/email/EmailList'; import EmailSidebarContent from '@/components/email/EmailSidebarContent'; import EmailDetailView from '@/components/email/EmailDetailView'; import ComposeEmail from '@/components/email/ComposeEmail'; -import { DeleteConfirmDialog, LoginNeededAlert } from '@/components/email/EmailDialogs'; +import { DeleteConfirmDialog } from '@/components/email/EmailDialogs'; // Import the custom hook import { useCourrier, EmailData } from '@/hooks/use-courrier'; @@ -322,7 +322,6 @@ export default function CourrierPage() { } else { console.error('Max retries reached for session request'); // Instead of throwing, redirect to login - router.push('/courrier/login'); return; } } @@ -595,9 +594,7 @@ export default function CourrierPage() { const handleMailboxChange = (folder: string, accountId?: string) => { if (accountId && accountId !== 'loading-account') { const account = accounts.find(a => a.id === accountId); - if (!account) { - console.warn(`Account ${accountId} not found`); toast({ title: "Account not found", description: `The account ${accountId} could not be found.`, @@ -605,43 +602,18 @@ export default function CourrierPage() { }); return; } - - // Ensure the account has initialized folders - if (!account.folders || account.folders.length === 0) { - console.warn(`No folders initialized for account ${accountId}`); - // Set default folders if none are initialized - account.folders = ['INBOX', 'SENT', 'DRAFTS', 'TRASH']; - } - - // Use the full prefixed folder name for existence check - const fullFolder = folder.includes(':') ? folder : `${accountId}:${folder}`; - - if (!account.folders.includes(fullFolder)) { - console.warn(`Folder ${fullFolder} not found in account ${accountId}, defaulting to INBOX`); + // Only allow navigation to folders in selectedAccount.folders + if (!account.folders.includes(folder)) { toast({ title: "Folder not found", - description: `The folder ${fullFolder} does not exist for this account. Defaulting to INBOX.`, + description: `The folder ${folder} does not exist for this account.`, variant: "destructive", }); - // Default to INBOX with prefix - setSelectedFolders(prev => ({ - ...prev, - [accountId]: `${accountId}:INBOX` - })); - changeFolder(`${accountId}:INBOX`, accountId); return; } - - // Update selected folders state with the full prefixed folder name - setSelectedFolders(prev => ({ - ...prev, - [accountId]: fullFolder - })); - - // Change to the selected folder with account prefix - changeFolder(fullFolder, accountId); + setSelectedFolders(prev => ({ ...prev, [accountId]: folder })); + changeFolder(folder, accountId); } else { - // For all accounts view, use the original folder name changeFolder(folder, accountId); } }; @@ -1046,7 +1018,6 @@ export default function CourrierPage() { >
{account.name} - {/* More options button */} {account.id !== 'loading-account' && ( @@ -1071,16 +1042,12 @@ export default function CourrierPage() { )} - {/* Show folders for this account if expanded */} - {(() => { - const isExpanded = expandedAccounts[account.id]; - const hasFolders = account.folders && account.folders.length > 0; - return isExpanded && account.id !== 'loading-account' && hasFolders && ( -
- {account.folders.map((folder) => renderFolderButton(folder, account.id))} -
- ); - })()} + {/* Show only selectedAccount.folders for the selected account if expanded */} + {selectedAccount && account.id === selectedAccount.id && expandedAccounts[account.id] && account.folders && account.folders.length > 0 && ( +
+ {account.folders.map((folder) => renderFolderButton(folder, account.id))} +
+ )} ))} @@ -1161,16 +1128,6 @@ export default function CourrierPage() { Error {error} - {(error?.includes('Not authenticated') || error?.includes('No email credentials found')) && ( - - )} @@ -1260,12 +1217,6 @@ export default function CourrierPage() { onConfirm={handleDeleteConfirm} onCancel={() => setShowDeleteConfirm(false)} /> - - setShowLoginNeeded(false)} - /> {/* Compose Email Dialog */} !open && setShowComposeModal(false)}>