Neah version mail stable

This commit is contained in:
alma 2025-04-16 17:22:48 +02:00
parent 5288f7a72b
commit 5fd06cad00
2 changed files with 106 additions and 57 deletions

View File

@ -101,10 +101,12 @@ export async function GET(request: Request) {
);
}
// Get the current folder from the URL
// Get pagination parameters from URL
const url = new URL(request.url);
const folder = url.searchParams.get('folder') || 'INBOX';
const limit = 50; // Limit number of emails per folder
const page = parseInt(url.searchParams.get('page') || '1');
const limit = parseInt(url.searchParams.get('limit') || '24');
const offset = (page - 1) * limit;
return new Promise((resolve) => {
const imap = new Imap({
@ -159,37 +161,45 @@ export async function GET(request: Request) {
return;
}
// Search for emails in this folder, limited to the most recent ones
imap.search(['ALL'], (err, results) => {
if (err) {
console.error(`Error searching in ${folder}:`, err);
clearTimeout(timeout);
imap.end();
resolve(NextResponse.json({ emails: [], error: `Failed to search in ${folder}` }));
return;
}
// Get the specified folder
const totalMessages = box.messages.total;
// Calculate the range of messages to fetch
const start = Math.max(1, totalMessages - offset - limit + 1);
const end = totalMessages - offset;
if (start > end) {
clearTimeout(timeout);
imap.end();
resolve(NextResponse.json({
emails: [],
folders: availableMailboxes,
mailUrl: process.env.NEXT_PUBLIC_IFRAME_MAIL_URL
}));
return;
}
if (!results || results.length === 0) {
// Fetch messages in the calculated range
imap.searchAsync(['ALL'], {
bodies: ['HEADER', 'TEXT'],
struct: true,
byUid: true,
start,
end
}).then((messages) => {
if (!messages || messages.length === 0) {
clearTimeout(timeout);
imap.end();
resolve(NextResponse.json({
emails: [],
folders: availableMailboxes,
mailUrl: process.env.NEXTCLOUD_URL ? `${process.env.NEXTCLOUD_URL}/apps/mail/` : null
mailUrl: process.env.NEXT_PUBLIC_IFRAME_MAIL_URL
}));
return;
}
// Take only the most recent emails up to the limit
const recentResults = results.slice(-limit);
const emails: any[] = [];
const fetch = imap.fetch(recentResults, {
bodies: ['HEADER', 'TEXT'],
struct: true
});
fetch.on('message', (msg) => {
// Process messages
const emails = messages.map((msg) => {
let header = '';
let text = '';
let messageId: number | null = null;
@ -231,26 +241,25 @@ export async function GET(request: Request) {
folder: folder,
flags: messageFlags
};
emails.push(email);
return email;
});
});
fetch.on('error', (err) => {
console.error(`Error fetching emails from ${folder}:`, err);
clearTimeout(timeout);
imap.end();
resolve(NextResponse.json({ emails: [], error: `Failed to fetch emails from ${folder}` }));
});
// Sort emails by date (most recent first)
emails.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
fetch.on('end', () => {
clearTimeout(timeout);
imap.end();
resolve(NextResponse.json({
emails: emails,
folders: availableMailboxes,
mailUrl: process.env.NEXTCLOUD_URL ? `${process.env.NEXTCLOUD_URL}/apps/mail/` : null
}));
});
clearTimeout(timeout);
imap.end();
resolve(NextResponse.json({
emails,
folders: availableMailboxes,
mailUrl: process.env.NEXT_PUBLIC_IFRAME_MAIL_URL
}));
}).catch((err) => {
console.error(`Error fetching emails from ${folder}:`, err);
clearTimeout(timeout);
imap.end();
resolve(NextResponse.json({ emails: [], error: `Failed to fetch emails from ${folder}` }));
});
});
});
@ -259,11 +268,11 @@ export async function GET(request: Request) {
imap.connect();
});
} catch (error) {
console.error('Error in mail API:', error);
return NextResponse.json({
emails: [],
error: error instanceof Error ? error.message : 'Unknown error'
});
console.error('Error in GET /api/mail:', error);
return NextResponse.json(
{ error: 'Failed to fetch emails' },
{ status: 500 }
);
}
}

View File

@ -1,6 +1,6 @@
'use client';
import { useEffect, useState, useMemo } from 'react';
import { useEffect, useState, useMemo, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
@ -510,6 +510,10 @@ export default function MailPage() {
const [unreadCount, setUnreadCount] = useState(0);
const [availableFolders, setAvailableFolders] = useState<string[]>([]);
const [sidebarItems, setSidebarItems] = useState(initialSidebarItems);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [isLoadingMore, setIsLoadingMore] = useState(false);
const emailsPerPage = 24;
// Debug logging for email distribution
useEffect(() => {
@ -557,18 +561,21 @@ export default function MailPage() {
}, [router]);
// Update the loadEmails function
const loadEmails = async () => {
const loadEmails = async (isLoadMore = false) => {
try {
setLoading(true);
if (isLoadMore) {
setIsLoadingMore(true);
} else {
setLoading(true);
}
setError(null);
const response = await fetch(`/api/mail?folder=${currentView}`);
const response = await fetch(`/api/mail?folder=${currentView}&page=${page}&limit=${emailsPerPage}`);
if (!response.ok) {
throw new Error('Failed to load emails');
}
const data = await response.json();
console.log('Raw email data:', data);
// Get available folders from the API response
if (data.folders) {
@ -578,7 +585,7 @@ export default function MailPage() {
// Process emails keeping exact folder names
const processedEmails = data.emails.map((email: any) => ({
id: Number(email.id),
accountId: 1, // Default account ID
accountId: 1,
from: email.from || '',
fromName: email.from?.split('@')[0] || '',
to: email.to || '',
@ -587,7 +594,7 @@ export default function MailPage() {
date: email.date || new Date().toISOString(),
read: email.read || false,
starred: email.starred || false,
folder: email.folder || 'INBOX', // Use the folder from API response
folder: email.folder || 'INBOX',
cc: email.cc,
bcc: email.bcc,
flags: email.flags || []
@ -595,21 +602,33 @@ export default function MailPage() {
// Update unread count for INBOX
const unreadInboxEmails = processedEmails.filter(
email => !email.read && email.folder === 'INBOX'
(email: Email) => !email.read && email.folder === 'INBOX'
).length;
setUnreadCount(unreadInboxEmails);
setEmails(processedEmails);
// Update emails state based on whether we're loading more
if (isLoadMore) {
setEmails(prev => [...prev, ...processedEmails]);
} else {
setEmails(processedEmails);
}
// Update hasMore state based on the number of emails received
setHasMore(processedEmails.length === emailsPerPage);
} catch (err) {
console.error('Error loading emails:', err);
setError(err instanceof Error ? err.message : 'Failed to load emails');
} finally {
setLoading(false);
setIsLoadingMore(false);
}
};
// Add an effect to reload emails when the view changes
useEffect(() => {
setPage(1); // Reset page when view changes
setHasMore(true);
loadEmails();
}, [currentView]);
@ -1024,6 +1043,19 @@ export default function MailPage() {
});
}, [emails]);
// Add infinite scroll handler
const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
const target = e.currentTarget;
if (
target.scrollHeight - target.scrollTop === target.clientHeight &&
!isLoadingMore &&
hasMore
) {
setPage(prev => prev + 1);
loadEmails(true);
}
}, [isLoadingMore, hasMore]);
// Render the email list using sorted emails
const renderEmailList = () => (
<div className="w-[320px] bg-white/95 backdrop-blur-sm border-r border-gray-100 flex flex-col">
@ -1038,18 +1070,21 @@ export default function MailPage() {
</h2>
</div>
<div className="text-sm text-gray-500">
{sortedEmails.length} emails
{emails.length} emails
</div>
</div>
</div>
{/* Email list */}
<div className="flex-1 overflow-y-auto">
{/* Email list with scroll handler */}
<div
className="flex-1 overflow-y-auto"
onScroll={handleScroll}
>
{loading ? (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
</div>
) : sortedEmails.length === 0 ? (
) : emails.length === 0 ? (
<div className="flex flex-col items-center justify-center h-64">
<Mail className="h-8 w-8 text-gray-400 mb-2" />
<p className="text-gray-500 text-sm">
@ -1125,6 +1160,11 @@ export default function MailPage() {
)}
</div>
))}
{isLoadingMore && (
<div className="flex items-center justify-center p-4">
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-blue-500"></div>
</div>
)}
</div>
)}
</div>