Neah version mail stable
This commit is contained in:
parent
5288f7a72b
commit
5fd06cad00
@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user