diff --git a/app/api/mail/[id]/route.ts b/app/api/mail/[id]/route.ts new file mode 100644 index 00000000..08a57256 --- /dev/null +++ b/app/api/mail/[id]/route.ts @@ -0,0 +1,99 @@ +import { NextResponse } from 'next/server'; +import { ImapFlow } from 'imapflow'; +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/app/api/auth/[...nextauth]/route'; +import { prisma } from '@/lib/prisma'; + +export async function GET(request: Request, { params }: { params: { id: string } }) { + try { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json( + { error: 'Unauthorized' }, + { status: 401 } + ); + } + + // Get credentials from database + const credentials = await prisma.mailCredentials.findUnique({ + where: { + userId: session.user.id + } + }); + + if (!credentials) { + return NextResponse.json( + { error: 'No mail credentials found. Please configure your email account.' }, + { status: 401 } + ); + } + + // Get the current folder from the request URL + const url = new URL(request.url); + const folder = url.searchParams.get('folder') || 'INBOX'; + + // Connect to IMAP server + const client = new ImapFlow({ + host: credentials.host, + port: credentials.port, + secure: true, + auth: { + user: credentials.email, + pass: credentials.password, + }, + logger: false, + emitLogs: false, + tls: { + rejectUnauthorized: false + } + }); + + try { + await client.connect(); + await client.mailboxOpen(folder); + + // Fetch the full email content + const message = await client.fetchOne(params.id, { + source: true, + envelope: true, + flags: true + }); + + if (!message) { + return NextResponse.json( + { error: 'Email not found' }, + { status: 404 } + ); + } + + // Extract email content + const result = { + id: message.uid.toString(), + from: message.envelope.from[0].address, + subject: message.envelope.subject || '(No subject)', + date: message.envelope.date.toISOString(), + read: message.flags.has('\\Seen'), + starred: message.flags.has('\\Flagged'), + folder: folder, + body: message.source.toString(), + to: message.envelope.to?.map(addr => addr.address).join(', ') || '', + cc: message.envelope.cc?.map(addr => addr.address).join(', ') || '', + bcc: message.envelope.bcc?.map(addr => addr.address).join(', ') || '', + }; + + return NextResponse.json(result); + } finally { + try { + await client.logout(); + } catch (e) { + console.error('Error during logout:', e); + } + } + } catch (error) { + console.error('Error fetching email:', error); + return NextResponse.json( + { error: 'An unexpected error occurred' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index cda5d66f..9dfcbd8b 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -40,7 +40,7 @@ interface Email { id: number; accountId: number; from: string; - fromName?: string; + fromName: string; to: string; subject: string; body: string; @@ -484,16 +484,21 @@ function renderEmailContent(email: Email) { }); // If parsing failed, try direct content extraction - let content = null; + let content = ''; + let isHtml = false; + if (parsed.html) { content = parsed.html; + isHtml = true; } else if (parsed.text) { content = parsed.text; + isHtml = false; } else { // Try to extract content directly from body const htmlMatch = email.body.match(/]*>[\s\S]*?<\/html>/i); if (htmlMatch) { content = htmlMatch[0]; + isHtml = true; } else { content = email.body .replace(/<[^>]+>/g, '') @@ -507,6 +512,7 @@ function renderEmailContent(email: Email) { .replace(/=3D/g, '=') .replace(/=09/g, '\t') .trim(); + isHtml = false; } } @@ -527,7 +533,11 @@ function renderEmailContent(email: Email) { return (
-
+ {isHtml ? ( +
+ ) : ( +
{content}
+ )} {attachmentElements}
); @@ -755,25 +765,47 @@ export default function MailPage() { }; // Update handleEmailSelect to set selectedEmail correctly - const handleEmailSelect = (emailId: number) => { - const email = emails.find(e => e.id === emailId); - if (email) { - setSelectedEmail(email); - if (!email.read) { - // Mark as read in state - setEmails(emails.map(e => - e.id === emailId ? { ...e, read: true } : e - )); - - // Update read status on server - fetch('/api/mail/mark-read', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ emailId }) - }).catch(error => { - console.error('Error marking email as read:', error); - }); + const handleEmailSelect = async (emailId: number) => { + try { + // Mark email as read + const markReadResponse = await fetch(`/api/mail/mark-read`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + emailId, + isRead: true, + }), + }); + + if (!markReadResponse.ok) { + console.error('Failed to mark email as read'); } + + // Fetch full email content + const response = await fetch(`/api/mail/${emailId}?folder=${currentView}`); + if (!response.ok) { + throw new Error('Failed to fetch email content'); + } + + const fullEmail = await response.json(); + + // Update the email in the list with full content + setEmails(prevEmails => + prevEmails.map(email => + email.id === emailId + ? { ...email, ...fullEmail, read: true } + : email + ) + ); + + // Update selected email + setSelectedEmail(fullEmail); + + } catch (error) { + console.error('Error selecting email:', error); + setError('Failed to load email content'); } }; @@ -1290,10 +1322,12 @@ export default function MailPage() { preview += '...'; } + console.log('Final preview:', preview); return preview; + } catch (e) { console.error('Error generating preview:', e); - return '(No preview available)'; + return 'No preview available'; } };