mail page connected with folders 3
This commit is contained in:
parent
4413b267fa
commit
b9c1425002
@ -101,20 +101,28 @@ export async function GET() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const imap = new Imap({
|
const availableMailboxes: string[] = [];
|
||||||
user: credentials.email,
|
const emailsByFolder: { [key: string]: any[] } = {};
|
||||||
password: credentials.password,
|
|
||||||
host: credentials.host,
|
|
||||||
port: credentials.port,
|
|
||||||
tls: true,
|
|
||||||
tlsOptions: { rejectUnauthorized: false },
|
|
||||||
authTimeout: 30000,
|
|
||||||
connTimeout: 30000
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// Create a map to store emails by folder
|
const imap = new Imap({
|
||||||
const emailsByFolder: { [key: string]: any[] } = {};
|
user: credentials.email,
|
||||||
|
password: credentials.password,
|
||||||
|
host: credentials.host,
|
||||||
|
port: credentials.port,
|
||||||
|
tls: true,
|
||||||
|
tlsOptions: { rejectUnauthorized: false },
|
||||||
|
authTimeout: 30000,
|
||||||
|
connTimeout: 30000
|
||||||
|
});
|
||||||
|
|
||||||
|
imap.once('error', (err: Error) => {
|
||||||
|
console.error('IMAP error:', err);
|
||||||
|
resolve(NextResponse.json({
|
||||||
|
emails: [],
|
||||||
|
error: 'IMAP connection error'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
imap.once('ready', () => {
|
imap.once('ready', () => {
|
||||||
imap.getBoxes((err, boxes) => {
|
imap.getBoxes((err, boxes) => {
|
||||||
@ -125,81 +133,77 @@ export async function GET() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const availableMailboxes = Object.keys(boxes);
|
// Process mailboxes
|
||||||
|
Object.keys(boxes).forEach((box) => {
|
||||||
|
availableMailboxes.push(box);
|
||||||
|
});
|
||||||
|
|
||||||
console.log('Available mailboxes:', availableMailboxes);
|
console.log('Available mailboxes:', availableMailboxes);
|
||||||
|
|
||||||
// Process only the mailboxes we want to use
|
// Process each mailbox
|
||||||
const foldersToCheck = ['INBOX', 'Sent', 'Trash', 'Spam', 'Drafts', 'Archives', 'Archive'];
|
const foldersToProcess = availableMailboxes;
|
||||||
let foldersProcessed = 0;
|
let processedFolders = 0;
|
||||||
|
|
||||||
const processFolder = (folderName: string) => {
|
function finishProcessing() {
|
||||||
console.log(`Processing folder: ${folderName}`);
|
// Combine all emails from all folders
|
||||||
|
const allEmails = Object.entries(emailsByFolder).flatMap(([folder, emails]) => emails);
|
||||||
|
|
||||||
|
console.log('Emails by folder:', Object.fromEntries(
|
||||||
|
Object.entries(emailsByFolder).map(([folder, emails]) => [folder, emails.length])
|
||||||
|
));
|
||||||
|
console.log('All folders processed, total emails:', allEmails.length);
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
emails: allEmails,
|
||||||
|
folders: availableMailboxes,
|
||||||
|
mailUrl: process.env.NEXTCLOUD_URL ? `${process.env.NEXTCLOUD_URL}/apps/mail/` : null
|
||||||
|
};
|
||||||
|
imap.end();
|
||||||
|
resolve(NextResponse.json(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
foldersToProcess.forEach((folderName) => {
|
||||||
// Initialize array for this folder
|
// Initialize array for this folder
|
||||||
emailsByFolder[folderName] = [];
|
emailsByFolder[folderName] = [];
|
||||||
|
|
||||||
imap.openBox(folderName, false, (err, box) => {
|
imap.openBox(folderName, false, (err, box) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(`Error opening ${folderName}:`, err);
|
console.error(`Error opening box ${folderName}:`, err);
|
||||||
foldersProcessed++;
|
processedFolders++;
|
||||||
if (foldersProcessed === foldersToCheck.length) {
|
if (processedFolders === foldersToProcess.length) {
|
||||||
finishProcessing();
|
finishProcessing();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Successfully opened ${folderName}, total messages: ${box.messages.total}`);
|
// Search for all emails in this folder
|
||||||
|
imap.search(['ALL'], (err, results) => {
|
||||||
if (box.messages.total === 0) {
|
|
||||||
foldersProcessed++;
|
|
||||||
if (foldersProcessed === foldersToCheck.length) {
|
|
||||||
finishProcessing();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for messages in this folder
|
|
||||||
const searchCriteria = ['ALL'];
|
|
||||||
|
|
||||||
imap.search(searchCriteria, (err, results) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(`Search error in ${folderName}:`, err);
|
console.error(`Error searching in ${folderName}:`, err);
|
||||||
foldersProcessed++;
|
processedFolders++;
|
||||||
if (foldersProcessed === foldersToCheck.length) {
|
if (processedFolders === foldersToProcess.length) {
|
||||||
finishProcessing();
|
finishProcessing();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageNumbers = results
|
if (!results || results.length === 0) {
|
||||||
.sort((a, b) => b - a)
|
processedFolders++;
|
||||||
.slice(0, 20);
|
if (processedFolders === foldersToProcess.length) {
|
||||||
|
|
||||||
if (messageNumbers.length === 0) {
|
|
||||||
foldersProcessed++;
|
|
||||||
if (foldersProcessed === foldersToCheck.length) {
|
|
||||||
finishProcessing();
|
finishProcessing();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const f = imap.fetch(messageNumbers, {
|
// Fetch emails
|
||||||
bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)', 'TEXT'],
|
const fetch = imap.fetch(results, {
|
||||||
|
bodies: ['HEADER', 'TEXT'],
|
||||||
struct: true
|
struct: true
|
||||||
});
|
});
|
||||||
|
|
||||||
f.on('message', (msg) => {
|
fetch.on('message', (msg) => {
|
||||||
const email: any = {
|
let header = '';
|
||||||
id: '',
|
let text = '';
|
||||||
from: '',
|
|
||||||
subject: '',
|
|
||||||
date: new Date(),
|
|
||||||
read: true,
|
|
||||||
starred: false,
|
|
||||||
body: '',
|
|
||||||
to: '',
|
|
||||||
folder: folderName
|
|
||||||
};
|
|
||||||
|
|
||||||
msg.on('body', (stream, info) => {
|
msg.on('body', (stream, info) => {
|
||||||
let buffer = '';
|
let buffer = '';
|
||||||
@ -207,75 +211,46 @@ export async function GET() {
|
|||||||
buffer += chunk.toString('utf8');
|
buffer += chunk.toString('utf8');
|
||||||
});
|
});
|
||||||
stream.on('end', () => {
|
stream.on('end', () => {
|
||||||
if (info.which === 'HEADER.FIELDS (FROM TO SUBJECT DATE)') {
|
if (info.which === 'HEADER') {
|
||||||
const headers = Imap.parseHeader(buffer);
|
header = buffer;
|
||||||
email.from = headers.from?.[0] || '';
|
} else if (info.which === 'TEXT') {
|
||||||
email.to = headers.to?.[0] || '';
|
text = buffer;
|
||||||
email.subject = headers.subject?.[0] || '(No subject)';
|
|
||||||
email.date = headers.date?.[0] || new Date().toISOString();
|
|
||||||
} else {
|
|
||||||
email.body = buffer;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
msg.once('attributes', (attrs) => {
|
msg.on('end', () => {
|
||||||
email.id = attrs.uid;
|
const parsedHeader = Imap.parseHeader(header);
|
||||||
email.read = attrs.flags?.includes('\\Seen') || false;
|
const email = {
|
||||||
email.starred = attrs.flags?.includes('\\Flagged') || false;
|
id: msg.attributes.uid,
|
||||||
});
|
from: parsedHeader.from[0],
|
||||||
|
to: parsedHeader.to[0],
|
||||||
msg.once('end', () => {
|
subject: parsedHeader.subject[0],
|
||||||
// Add email to its folder's array
|
date: parsedHeader.date[0],
|
||||||
|
body: text,
|
||||||
|
folder: folderName,
|
||||||
|
flags: msg.attributes.flags
|
||||||
|
};
|
||||||
emailsByFolder[folderName].push(email);
|
emailsByFolder[folderName].push(email);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
f.once('error', (err) => {
|
fetch.on('error', (err) => {
|
||||||
console.error(`Fetch error in ${folderName}:`, err);
|
console.error(`Error fetching emails from ${folderName}:`, err);
|
||||||
});
|
});
|
||||||
|
|
||||||
f.once('end', () => {
|
fetch.on('end', () => {
|
||||||
console.log(`Finished processing ${folderName}, emails found: ${emailsByFolder[folderName].length}`);
|
processedFolders++;
|
||||||
foldersProcessed++;
|
if (processedFolders === foldersToProcess.length) {
|
||||||
if (foldersProcessed === foldersToCheck.length) {
|
|
||||||
finishProcessing();
|
finishProcessing();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|
||||||
// Process each folder sequentially
|
|
||||||
foldersToCheck.forEach(folder => processFolder(folder));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function finishProcessing() {
|
|
||||||
// Combine all emails from all folders
|
|
||||||
const allEmails = Object.entries(emailsByFolder).flatMap(([folder, emails]) => emails);
|
|
||||||
|
|
||||||
console.log('Emails by folder:', Object.fromEntries(
|
|
||||||
Object.entries(emailsByFolder).map(([folder, emails]) => [folder, emails.length])
|
|
||||||
));
|
|
||||||
console.log('All folders processed, total emails:', allEmails.length);
|
|
||||||
|
|
||||||
const response = {
|
|
||||||
emails: allEmails,
|
|
||||||
mailUrl: process.env.NEXTCLOUD_URL ? `${process.env.NEXTCLOUD_URL}/apps/mail/` : null
|
|
||||||
};
|
|
||||||
imap.end();
|
|
||||||
resolve(NextResponse.json(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
imap.once('error', (err) => {
|
|
||||||
console.error('IMAP error:', err);
|
|
||||||
resolve(NextResponse.json({
|
|
||||||
emails: [],
|
|
||||||
error: 'IMAP connection error'
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
imap.connect();
|
imap.connect();
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -429,7 +429,7 @@ function cleanEmailContent(content: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Define the exact folder names from IMAP
|
// Define the exact folder names from IMAP
|
||||||
type MailFolder = 'INBOX' | 'Sent' | 'Trash' | 'Spam' | 'Drafts' | 'Archives' | 'Archive';
|
type MailFolder = string;
|
||||||
type MailView = MailFolder | 'starred';
|
type MailView = MailFolder | 'starred';
|
||||||
|
|
||||||
// Map the exact IMAP folders to our sidebar items
|
// Map the exact IMAP folders to our sidebar items
|
||||||
@ -445,42 +445,6 @@ const sidebarNavItems = [
|
|||||||
label: 'Starred',
|
label: 'Starred',
|
||||||
icon: Star,
|
icon: Star,
|
||||||
folder: null // Special case for starred items
|
folder: null // Special case for starred items
|
||||||
},
|
|
||||||
{
|
|
||||||
view: 'Sent' as MailView,
|
|
||||||
label: 'Sent',
|
|
||||||
icon: Send,
|
|
||||||
folder: 'Sent'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
view: 'Drafts' as MailView,
|
|
||||||
label: 'Drafts',
|
|
||||||
icon: Edit,
|
|
||||||
folder: 'Drafts'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
view: 'Spam' as MailView,
|
|
||||||
label: 'Spam',
|
|
||||||
icon: AlertOctagon,
|
|
||||||
folder: 'Spam'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
view: 'Trash' as MailView,
|
|
||||||
label: 'Trash',
|
|
||||||
icon: Trash,
|
|
||||||
folder: 'Trash'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
view: 'Archives' as MailView,
|
|
||||||
label: 'Archives',
|
|
||||||
icon: Archive,
|
|
||||||
folder: 'Archives'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
view: 'Archive' as MailView,
|
|
||||||
label: 'Archive',
|
|
||||||
icon: Archive,
|
|
||||||
folder: 'Archive'
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -523,6 +487,7 @@ export default function MailPage() {
|
|||||||
const [attachments, setAttachments] = useState<Attachment[]>([]);
|
const [attachments, setAttachments] = useState<Attachment[]>([]);
|
||||||
const [folders, setFolders] = useState<string[]>([]);
|
const [folders, setFolders] = useState<string[]>([]);
|
||||||
const [unreadCount, setUnreadCount] = useState(0);
|
const [unreadCount, setUnreadCount] = useState(0);
|
||||||
|
const [availableFolders, setAvailableFolders] = useState<string[]>([]);
|
||||||
|
|
||||||
// Debug logging for email distribution
|
// Debug logging for email distribution
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -583,6 +548,11 @@ export default function MailPage() {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('Raw email data:', data);
|
console.log('Raw email data:', data);
|
||||||
|
|
||||||
|
// Get available folders from the API response
|
||||||
|
if (data.folders) {
|
||||||
|
setAvailableFolders(data.folders);
|
||||||
|
}
|
||||||
|
|
||||||
// Process emails keeping exact folder names
|
// Process emails keeping exact folder names
|
||||||
const processedEmails = data.emails.map((email: any) => ({
|
const processedEmails = data.emails.map((email: any) => ({
|
||||||
id: Number(email.id),
|
id: Number(email.id),
|
||||||
@ -955,14 +925,6 @@ export default function MailPage() {
|
|||||||
});
|
});
|
||||||
}, [currentView, emails]);
|
}, [currentView, emails]);
|
||||||
|
|
||||||
// Filter emails based on current view
|
|
||||||
const filteredEmails = useMemo(() => {
|
|
||||||
if (currentView === 'starred') {
|
|
||||||
return emails.filter(email => email.starred);
|
|
||||||
}
|
|
||||||
return emails.filter(email => email.folder === currentView);
|
|
||||||
}, [emails, currentView]);
|
|
||||||
|
|
||||||
// Add a function to move to trash
|
// Add a function to move to trash
|
||||||
const moveToTrash = async (emailId: number) => {
|
const moveToTrash = async (emailId: number) => {
|
||||||
// Update the email in state
|
// Update the email in state
|
||||||
@ -1064,7 +1026,7 @@ export default function MailPage() {
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
{filteredEmails.length} emails
|
{emails.length} emails
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1075,7 +1037,7 @@ export default function MailPage() {
|
|||||||
<div className="flex items-center justify-center h-64">
|
<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 className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
|
||||||
</div>
|
</div>
|
||||||
) : filteredEmails.length === 0 ? (
|
) : emails.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center h-64">
|
<div className="flex flex-col items-center justify-center h-64">
|
||||||
<Mail className="h-8 w-8 text-gray-400 mb-2" />
|
<Mail className="h-8 w-8 text-gray-400 mb-2" />
|
||||||
<p className="text-gray-500 text-sm">
|
<p className="text-gray-500 text-sm">
|
||||||
@ -1096,7 +1058,7 @@ export default function MailPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="divide-y divide-gray-100">
|
<div className="divide-y divide-gray-100">
|
||||||
{filteredEmails.map((email) => (
|
{emails.map((email) => (
|
||||||
<div
|
<div
|
||||||
key={email.id}
|
key={email.id}
|
||||||
className={`flex flex-col p-3 hover:bg-gray-50 cursor-pointer ${
|
className={`flex flex-col p-3 hover:bg-gray-50 cursor-pointer ${
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user