diff --git a/app/[section]/page.tsx b/app/[section]/page.tsx index cc3515e1..185849b3 100644 --- a/app/[section]/page.tsx +++ b/app/[section]/page.tsx @@ -10,8 +10,9 @@ const menuItems = { missions: "https://example.com/missions" } -export default function SectionPage({ params }: { params: { section: string } }) { - const iframeUrl = menuItems[params.section as keyof typeof menuItems] +export default async function SectionPage({ params }: { params: { section: string } }) { + const { section } = await Promise.resolve(params); + const iframeUrl = menuItems[section as keyof typeof menuItems] if (!iframeUrl) { notFound() diff --git a/app/api/courrier/[id]/mark-read/route.ts b/app/api/courrier/[id]/mark-read/route.ts index fa54c2db..51f3c45d 100644 --- a/app/api/courrier/[id]/mark-read/route.ts +++ b/app/api/courrier/[id]/mark-read/route.ts @@ -9,7 +9,7 @@ export async function POST( { params }: { params: { id: string } } ) { try { - const id = params.id; + const { id } = await Promise.resolve(params); // Authentication check const session = await getServerSession(authOptions); diff --git a/app/api/courrier/[id]/route.ts b/app/api/courrier/[id]/route.ts index 50512dbe..4c348a9f 100644 --- a/app/api/courrier/[id]/route.ts +++ b/app/api/courrier/[id]/route.ts @@ -12,7 +12,7 @@ export async function GET( { params }: { params: { id: string } } ) { try { - const id = params.id; + const { id } = await Promise.resolve(params); // Authentication check const session = await getServerSession(authOptions); diff --git a/app/api/courrier/route.ts b/app/api/courrier/route.ts index 13412c7d..f3f8a5f3 100644 --- a/app/api/courrier/route.ts +++ b/app/api/courrier/route.ts @@ -4,13 +4,51 @@ import { getServerSession } from 'next-auth'; import { authOptions } from '@/app/api/auth/[...nextauth]/route'; import { prisma } from '@/lib/prisma'; -// Simple email cache structure +// Email cache structure interface EmailCache { [key: string]: any; } -// Simple in-memory cache -const cache: EmailCache = {}; +// Credentials cache to reduce database queries +interface CredentialsCache { + [userId: string]: { + credentials: any; + timestamp: number; + } +} + +// In-memory caches with expiration +const emailListCache: EmailCache = {}; +const credentialsCache: CredentialsCache = {}; + +// Cache TTL in milliseconds (5 minutes) +const CACHE_TTL = 5 * 60 * 1000; + +// Helper function to get credentials with caching +async function getCredentialsWithCache(userId: string) { + // Check if we have fresh cached credentials + const cachedCreds = credentialsCache[userId]; + const now = Date.now(); + + if (cachedCreds && now - cachedCreds.timestamp < CACHE_TTL) { + return cachedCreds.credentials; + } + + // Otherwise fetch from database + const credentials = await prisma.mailCredentials.findUnique({ + where: { userId } + }); + + // Cache the result + if (credentials) { + credentialsCache[userId] = { + credentials, + timestamp: now + }; + } + + return credentials; +} export async function GET(request: Request) { try { @@ -22,12 +60,28 @@ export async function GET(request: Request) { ); } - // Get credentials from database - const credentials = await prisma.mailCredentials.findUnique({ - where: { - userId: session.user.id + // Get URL parameters + const url = new URL(request.url); + const folder = url.searchParams.get('folder') || 'INBOX'; + const page = parseInt(url.searchParams.get('page') || '1'); + const limit = parseInt(url.searchParams.get('limit') || '20'); + const preview = url.searchParams.get('preview') === 'true'; + const skipCache = url.searchParams.get('skipCache') === 'true'; + + // Generate cache key based on request parameters + const cacheKey = `${session.user.id}:${folder}:${page}:${limit}:${preview}`; + + // Check cache first if not explicitly skipped + if (!skipCache && emailListCache[cacheKey]) { + const { data, timestamp } = emailListCache[cacheKey]; + // Return cached data if it's fresh (less than 1 minute old) + if (Date.now() - timestamp < 60000) { + return NextResponse.json(data); } - }); + } + + // Get credentials from cache or database + const credentials = await getCredentialsWithCache(session.user.id); if (!credentials) { return NextResponse.json( @@ -36,13 +90,6 @@ export async function GET(request: Request) { ); } - // Get query parameters - const url = new URL(request.url); - const folder = url.searchParams.get('folder') || 'INBOX'; - const page = parseInt(url.searchParams.get('page') || '1'); - const limit = parseInt(url.searchParams.get('limit') || '20'); - const preview = url.searchParams.get('preview') === 'true'; - // Calculate start and end sequence numbers const start = (page - 1) * limit + 1; const end = start + limit - 1; @@ -119,12 +166,20 @@ export async function GET(request: Request) { } } - return NextResponse.json({ + const responseData = { emails: result, folders: availableFolders, total: mailbox.exists, hasMore: end < mailbox.exists - }); + }; + + // Cache the result + emailListCache[cacheKey] = { + data: responseData, + timestamp: Date.now() + }; + + return NextResponse.json(responseData); } finally { try { await client.logout(); diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 642b7bd7..bd16a404 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -102,6 +102,7 @@ function EmailContent({ email }: { email: Email }) { const [content, setContent] = useState(null); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); + const [debugInfo, setDebugInfo] = useState(null); useEffect(() => { let mounted = true; @@ -110,10 +111,16 @@ function EmailContent({ email }: { email: Email }) { if (!email) return; setIsLoading(true); + setDebugInfo(null); try { + console.log('Loading content for email:', email.id); + console.log('Email content length:', email.content?.length || 0); + if (!email.content) { + console.log('No content available for email:', email.id); if (mounted) { setContent(
No content available
); + setDebugInfo('Email has no content property'); setIsLoading(false); } return; @@ -121,31 +128,44 @@ function EmailContent({ email }: { email: Email }) { const formattedEmail = email.content.trim(); if (!formattedEmail) { + console.log('Empty content for email:', email.id); if (mounted) { - setContent(
No content available
); + setContent(
Email content is empty
); + setDebugInfo('Email content is empty string'); setIsLoading(false); } return; } + console.log('Parsing email content:', formattedEmail.substring(0, 100) + '...'); const parsedEmail = await decodeEmail(formattedEmail); + console.log('Parsed email result:', { + hasHtml: !!parsedEmail.html, + hasText: !!parsedEmail.text, + htmlLength: parsedEmail.html?.length || 0, + textLength: parsedEmail.text?.length || 0 + }); if (mounted) { if (parsedEmail.html) { + const sanitizedHtml = DOMPurify.sanitize(parsedEmail.html); setContent(
); + setDebugInfo('Rendered HTML content'); } else if (parsedEmail.text) { setContent(
{parsedEmail.text}
); + setDebugInfo('Rendered text content'); } else { - setContent(
No content available
); + setContent(
No displayable content available
); + setDebugInfo('No HTML or text content in parsed email'); } setError(null); setIsLoading(false); @@ -154,6 +174,7 @@ function EmailContent({ email }: { email: Email }) { console.error('Error rendering email content:', err); if (mounted) { setError('Error rendering email content. Please try again.'); + setDebugInfo(err instanceof Error ? err.message : 'Unknown error'); setContent(null); setIsLoading(false); } @@ -165,7 +186,7 @@ function EmailContent({ email }: { email: Email }) { return () => { mounted = false; }; - }, [email?.content]); + }, [email?.id, email?.content]); if (isLoading) { return ( @@ -176,10 +197,28 @@ function EmailContent({ email }: { email: Email }) { } if (error) { - return
{error}
; + return ( +
+
{error}
+ {debugInfo && ( +
+ Debug info: {debugInfo} +
+ )} +
+ ); } - return content ||
No content available
; + return ( + <> + {content ||
No content available
} + {debugInfo && process.env.NODE_ENV !== 'production' && ( +
+ Debug: {debugInfo} +
+ )} + + ); } function renderEmailContent(email: Email) { @@ -523,9 +562,14 @@ export default function CourrierPage() { checkCredentials(); }, [router]); - // Update the loadEmails function + // Update the loadEmails function to prevent redundant API calls const loadEmails = async (isLoadMore = false) => { try { + // Don't reload if we're already loading + if (isLoadingInitial || isLoadingMore) { + return; + } + if (isLoadMore) { setIsLoadingMore(true); } else { @@ -533,7 +577,17 @@ export default function CourrierPage() { } setError(null); - const response = await fetch(`/api/courrier?folder=${encodeURIComponent(currentView)}&page=${page}&limit=${emailsPerPage}`); + // Create a cache key for this request + const cacheKey = `${currentView}-${page}-${emailsPerPage}`; + + // Add timestamp parameter to force fresh data when needed + const timestamp = isLoadMore || page > 1 ? '' : `&_t=${Date.now()}`; + + const response = await fetch( + `/api/courrier?folder=${encodeURIComponent(currentView)}&page=${page}&limit=${emailsPerPage}${timestamp}`, + { cache: 'no-store' } + ); + if (!response.ok) { throw new Error('Failed to load emails'); } @@ -548,13 +602,13 @@ export default function CourrierPage() { // Process emails keeping exact folder names and sort by date const processedEmails = (data.emails || []) .map((email: any) => ({ - id: Number(email.id), + id: email.id, accountId: 1, from: email.from || '', fromName: email.fromName || email.from?.split('@')[0] || '', to: email.to || '', subject: email.subject || '(No subject)', - body: email.body || '', + content: email.preview || '', // Store preview as initial content date: email.date || new Date().toISOString(), read: email.read || false, starred: email.starred || false, @@ -562,7 +616,7 @@ export default function CourrierPage() { cc: email.cc, bcc: email.bcc, flags: email.flags || [], - raw: email.body || '' + hasAttachments: email.hasAttachments || false })); // Sort emails by date, ensuring most recent first @@ -572,6 +626,18 @@ export default function CourrierPage() { return dateB - dateA; // Most recent first }); + // Combine with existing emails when loading more + setEmails(prev => { + if (isLoadMore) { + // Filter out duplicates when appending + const existingIds = new Set(prev.map(email => email.id)); + const uniqueNewEmails = sortedEmails.filter((email: Email) => !existingIds.has(email.id)); + return [...prev, ...uniqueNewEmails]; + } else { + return sortedEmails; + } + }); + // Only update unread count if we're in the Inbox folder if (currentView === 'INBOX') { const unreadInboxEmails = sortedEmails.filter( @@ -580,30 +646,17 @@ export default function CourrierPage() { setUnreadCount(unreadInboxEmails); } - if (isLoadMore) { - // When loading more, merge with existing emails and re-sort - setEmails(prev => { - const combined = [...prev, ...sortedEmails]; - return combined.sort((a: Email, b: Email) => { - const dateA = new Date(a.date).getTime(); - const dateB = new Date(b.date).getTime(); - return dateB - dateA; // Most recent first - }); - }); - setPage(prev => prev + 1); - } else { - // For initial load or refresh, just use the sorted emails - setEmails(sortedEmails); - setPage(1); - } - - // Update hasMore based on API response - setHasMore(data.hasMore || false); + // Update pagination info + setHasMore(data.hasMore); + + setError(null); } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load emails'); + console.error('Error loading emails:', err); + setError('Failed to load emails. Please try again.'); } finally { setLoading(false); setIsLoadingMore(false); + setIsLoadingInitial(false); } };