From 5a8b14dbffe6e6a63807137d60f65a43cae6478f Mon Sep 17 00:00:00 2001 From: alma Date: Fri, 25 Apr 2025 17:52:43 +0200 Subject: [PATCH] panel 2 courier api restore --- app/api/courrier/login/route.ts | 105 ++++++++++++ app/courrier/page.tsx | 276 ++++++++++++++++++++++++++------ 2 files changed, 332 insertions(+), 49 deletions(-) diff --git a/app/api/courrier/login/route.ts b/app/api/courrier/login/route.ts index 6f153ef4..5fc6d930 100644 --- a/app/api/courrier/login/route.ts +++ b/app/api/courrier/login/route.ts @@ -11,6 +11,111 @@ const emailContentCache = new LRUCache({ ttl: 1000 * 60 * 15, // 15 minutes }); +export async function POST(request: Request) { + try { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Verify user exists + const user = await prisma.user.findUnique({ + where: { id: session.user.id } + }); + + if (!user) { + return NextResponse.json( + { error: 'User not found' }, + { status: 404 } + ); + } + + const { email, password, host, port } = await request.json(); + + if (!email || !password || !host || !port) { + return NextResponse.json( + { error: 'Missing required fields' }, + { status: 400 } + ); + } + + // Test IMAP connection + const client = new ImapFlow({ + host: host, + port: parseInt(port), + secure: true, + auth: { + user: email, + pass: password, + }, + logger: false, + emitLogs: false, + tls: { + rejectUnauthorized: false // Allow self-signed certificates + } + }); + + try { + await client.connect(); + await client.mailboxOpen('INBOX'); + + // Store or update credentials in database + await prisma.mailCredentials.upsert({ + where: { + userId: session.user.id + }, + update: { + email, + password, + host, + port: parseInt(port) + }, + create: { + userId: session.user.id, + email, + password, + host, + port: parseInt(port) + } + }); + + return NextResponse.json({ success: true }); + } catch (error) { + if (error instanceof Error) { + if (error.message.includes('Invalid login')) { + return NextResponse.json( + { error: 'Invalid login or password' }, + { status: 401 } + ); + } + return NextResponse.json( + { error: `IMAP connection error: ${error.message}` }, + { status: 500 } + ); + } + return NextResponse.json( + { error: 'Failed to connect to email server' }, + { status: 500 } + ); + } finally { + try { + await client.logout(); + } catch (e) { + console.error('Error during logout:', e); + } + } + } catch (error) { + console.error('Error in login handler:', error); + return NextResponse.json( + { error: 'An unexpected error occurred' }, + { status: 500 } + ); + } +} + export async function GET( request: Request, { params }: { params: { id: string } } diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index bd16a404..3f1a980c 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -539,6 +539,7 @@ export default function CourrierPage() { try { console.log('Checking for stored credentials...'); const response = await fetch('/api/courrier'); + if (!response.ok) { const errorData = await response.json(); console.log('API response error:', errorData); @@ -549,9 +550,66 @@ export default function CourrierPage() { } throw new Error(errorData.error || 'Failed to check credentials'); } - console.log('Credentials verified, loading emails...'); + + // Process API response to get folders and emails in one go + const data = await response.json(); + + // Update folder list first + if (data.folders && data.folders.length > 0) { + setAvailableFolders(data.folders); + + // Update sidebar items based on folders + const standardFolders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']; + const customFolders = data.folders.filter( + (folder: string) => !standardFolders.includes(folder) + ); + + setFolders(customFolders); + + // Update sidebar items with standard and custom folders + const updatedSidebarItems = [ + ...sidebarItems.filter(item => standardFolders.includes(item.view)), + ...customFolders.map((folder: string) => ({ + view: folder, + label: folder, + icon: getFolderIcon(folder) + })) + ]; + + setSidebarItems(updatedSidebarItems); + } + + // Process emails immediately if available + if (data.emails && data.emails.length > 0) { + const processedEmails = data.emails.map((email: any) => ({ + id: email.id, + accountId: 1, + from: email.from || '', + fromName: email.fromName || email.from?.split('@')[0] || '', + to: email.to || '', + subject: email.subject || '(No subject)', + content: email.preview || '', // Store preview as initial content + date: email.date || new Date().toISOString(), + read: email.read || false, + starred: email.starred || false, + folder: email.folder || currentView, + cc: email.cc, + bcc: email.bcc, + flags: email.flags || [], + hasAttachments: email.hasAttachments || false + })); + + setEmails(processedEmails); + + // Calculate unread count + const unreadInboxEmails = processedEmails.filter( + (email: any) => !email.read && email.folder === 'INBOX' + ).length; + setUnreadCount(unreadInboxEmails); + } + + console.log('Credentials verified, loading complete'); setLoading(false); - loadEmails(); } catch (err) { console.error('Error checking credentials:', err); setError(err instanceof Error ? err.message : 'Failed to check credentials'); @@ -561,6 +619,85 @@ export default function CourrierPage() { checkCredentials(); }, [router]); + + // Add a refreshEmails function to handle fresh data loading + const refreshEmails = async () => { + setIsLoadingRefresh(true); + try { + // Force timestamp to bypass cache + const timestamp = Date.now(); + const response = await fetch( + `/api/courrier?folder=${encodeURIComponent(currentView)}&page=1&limit=${emailsPerPage}&_t=${timestamp}&skipCache=true`, + { cache: 'no-store' } + ); + + if (!response.ok) { + throw new Error('Failed to refresh emails'); + } + + const data = await response.json(); + + // Always update folders on refresh + if (data.folders && data.folders.length > 0) { + setAvailableFolders(data.folders); + + // Update sidebar items based on folders + const standardFolders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']; + const customFolders = data.folders.filter( + (folder: string) => !standardFolders.includes(folder) + ); + + setFolders(customFolders); + + // Update sidebar items with standard and custom folders + const updatedSidebarItems = [ + ...sidebarItems.filter(item => standardFolders.includes(item.view)), + ...customFolders.map((folder: string) => ({ + view: folder, + label: folder, + icon: getFolderIcon(folder) + })) + ]; + + setSidebarItems(updatedSidebarItems); + } + + // Process emails + const processedEmails = (data.emails || []) + .map((email: any) => ({ + id: email.id, + accountId: 1, + from: email.from || '', + fromName: email.fromName || email.from?.split('@')[0] || '', + to: email.to || '', + subject: email.subject || '(No subject)', + content: email.preview || '', + date: email.date || new Date().toISOString(), + read: email.read || false, + starred: email.starred || false, + folder: email.folder || currentView, + cc: email.cc, + bcc: email.bcc, + flags: email.flags || [], + hasAttachments: email.hasAttachments || false + })); + + setEmails(processedEmails); + setHasMore(data.hasMore); + + // Calculate unread count + const unreadInboxEmails = processedEmails.filter( + (email: any) => !email.read && email.folder === 'INBOX' + ).length; + setUnreadCount(unreadInboxEmails); + + } catch (error) { + console.error('Error refreshing emails:', error); + setError(error instanceof Error ? error.message : 'Failed to refresh emails'); + } finally { + setIsLoadingRefresh(false); + } + }; // Update the loadEmails function to prevent redundant API calls const loadEmails = async (isLoadMore = false) => { @@ -595,8 +732,29 @@ export default function CourrierPage() { const data = await response.json(); // Get available folders from the API response - if (data.folders) { + if (data.folders && data.folders.length > 0) { setAvailableFolders(data.folders); + + // Update sidebar items based on folders + const standardFolders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk']; + const customFolders = data.folders.filter( + (folder: string) => !standardFolders.includes(folder) + ); + + // Set folders for display in sidebar + setFolders(customFolders); + + // Update sidebar items with standard and custom folders + const updatedSidebarItems = [ + ...sidebarItems.filter(item => standardFolders.includes(item.view)), + ...customFolders.map((folder: string) => ({ + view: folder, + label: folder, + icon: getFolderIcon(folder) + })) + ]; + + setSidebarItems(updatedSidebarItems); } // Process emails keeping exact folder names and sort by date @@ -675,67 +833,86 @@ export default function CourrierPage() { // Update handleEmailSelect to set selectedEmail correctly and ensure content is loaded const handleEmailSelect = async (emailId: string) => { + // Find the email in the current list + const email = emails.find(e => e.id === emailId); + if (!email) return; + + // Set selected email immediately to show the UI + setSelectedEmail(email); + + // Set loading state + setContentLoading(true); + try { - setContentLoading(true); + // Fetch the full email content in the background + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout - // Find the email in the current list first - const emailInList = emails.find(email => email.id === emailId); - - // Set selected email immediately with what we have - if (emailInList) { - setSelectedEmail(emailInList); + // Mark as read immediately for better UX + if (!email.read) { + try { + fetch(`/api/courrier/${emailId}/mark-read`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }); + + // Update the email in state optimistically + setEmails(emails.map(e => + e.id === emailId ? { ...e, read: true } : e + )); + } catch (error) { + console.error('Error marking as read:', error); + } } // Fetch the full email content - const response = await fetch(`/api/courrier/${emailId}`); + const response = await fetch(`/api/courrier/${emailId}`, { + signal: controller.signal + }); + + clearTimeout(timeoutId); if (!response.ok) { - throw new Error('Failed to fetch full email content'); + throw new Error('Failed to fetch email content'); } - const fullEmail = await response.json(); - console.log('Fetched email content:', fullEmail); + const emailData = await response.json(); - // Create a complete email object by combining what we have - const completeEmail = { - ...(emailInList || {}), - ...fullEmail, - id: emailId, - content: fullEmail.content || '', - }; + // Update the selected email with full content + setSelectedEmail({ + ...email, + content: emailData.content || '', + attachments: emailData.attachments || [] + }); - // Update the email in the list - setEmails(prevEmails => prevEmails.map(email => - email.id === emailId ? completeEmail : email + // Update the email in the list as well + setEmails(emails.map(e => + e.id === emailId + ? { + ...e, + content: emailData.content || '', + read: true, + attachments: emailData.attachments || [] + } + : e )); - - // Update the selected email - setSelectedEmail(completeEmail); - - // Try to mark as read in the background - try { - await fetch(`/api/courrier/${emailId}/mark-read`, { - method: 'POST', - }); - - // Update read status in the list - setEmails(prevEmails => - prevEmails.map(email => - email.id === emailId - ? { ...email, read: true } - : email - ) - ); - } catch (error) { - console.error('Error marking email as read:', error); - } } catch (error) { - console.error('Error fetching email:', error); - setError('Failed to load email content. Please try again.'); + console.error('Error fetching email content:', error); + setError(error instanceof Error ? error.message : 'Failed to fetch email content'); } finally { setContentLoading(false); } }; + + // Add or update the refresh handler + const handleRefresh = () => { + // Clear selected email to avoid stale data + setSelectedEmail(null); + // Use the new refresh function + refreshEmails(); + }; // Add these improved handlers const handleEmailCheckbox = (e: React.ChangeEvent, emailId: number) => { @@ -1509,10 +1686,11 @@ export default function CourrierPage() {