notifications big

This commit is contained in:
alma 2026-01-11 22:50:17 +01:00
parent 477a0f324d
commit cc1bab76c7
2 changed files with 144 additions and 5 deletions

View File

@ -1,6 +1,6 @@
import React, { memo, useState, useEffect } from 'react';
import Link from 'next/link';
import { Bell, Check, ExternalLink, AlertCircle, LogIn, Kanban, MessageSquare } from 'lucide-react';
import { Bell, Check, ExternalLink, AlertCircle, LogIn, Kanban, MessageSquare, Mail } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { useNotifications } from '@/hooks/use-notifications';
import { Button } from '@/components/ui/button';
@ -215,6 +215,12 @@ export const NotificationBadge = memo(function NotificationBadge({ className }:
Parole
</Badge>
)}
{notification.source === 'email' && (
<Badge variant="outline" className="ml-2 text-[10px] py-0 px-1.5 bg-green-50 text-green-700 border-green-200 flex items-center">
<Mail className="mr-1 h-2.5 w-2.5" />
Courrier
</Badge>
)}
</div>
<p className="text-xs text-muted-foreground">
{formatDistanceToNow(new Date(notification.timestamp), { addSuffix: true })}

View File

@ -205,10 +205,143 @@ export class EmailAdapter implements NotificationAdapter {
}
async getNotifications(userId: string, page = 1, limit = 20): Promise<Notification[]> {
// For now, return empty array - we can implement this later if needed
// The notification count is what matters for the badge
// In the future, we could return unread emails as notifications
return [];
logger.debug('[EMAIL_ADAPTER] getNotifications called', { userId, page, limit });
try {
// Get all accounts from the database
const accounts = await prisma.mailCredentials.findMany({
where: { userId },
select: {
id: true,
email: true
}
});
if (accounts.length === 0) {
return [];
}
const notifications: Notification[] = [];
// For each account, get unread emails from INBOX
// Use the same flow as getEmails() but filter for unread only
for (const account of accounts) {
try {
const client = await getImapConnection(userId, account.id);
// Use the same approach as getEmails() - open mailbox first
const mailboxInfo = await client.mailboxOpen('INBOX');
if (!mailboxInfo || typeof mailboxInfo === 'boolean') {
logger.debug('[EMAIL_ADAPTER] Could not open INBOX', {
userId,
accountId: account.id,
});
continue;
}
const totalEmails = mailboxInfo.exists || 0;
if (totalEmails === 0) {
continue;
}
// Search for unread emails (same as getNotificationCount logic)
const searchResult = await client.search({ seen: false });
if (!searchResult || searchResult.length === 0) {
continue;
}
// Limit the number of results for performance (get more than limit to have enough after filtering)
const limitedResults = searchResult.slice(0, limit * 3);
// Fetch email metadata using the same structure as getEmails()
const messages = await client.fetch(limitedResults, {
envelope: true,
flags: true,
uid: true
});
// Convert to notifications (same format as getEmails() processes emails)
for await (const message of messages) {
// Filter: only process if truly unread (double-check flags)
if (message.flags && message.flags.has('\\Seen')) {
continue;
}
const envelope = message.envelope;
const from = envelope.from?.[0];
const subject = envelope.subject || '(Sans objet)';
const fromName = from?.name || from?.address || 'Expéditeur inconnu';
const fromAddress = from?.address || '';
const notification: Notification = {
id: `email-${account.id}-${message.uid}`,
source: 'email',
sourceId: message.uid.toString(),
type: 'email',
title: 'Email',
message: `${fromName}${fromAddress ? ` <${fromAddress}>` : ''}: ${subject}`,
link: `/courrier?accountId=${account.id}&folder=INBOX&emailId=${message.uid}`,
isRead: false,
timestamp: envelope.date || new Date(),
priority: 'normal',
user: {
id: fromAddress,
name: fromName,
},
metadata: {
accountId: account.id,
accountEmail: account.email,
folder: 'INBOX',
emailId: message.uid.toString(),
}
};
notifications.push(notification);
}
// Close mailbox (same as getEmails() does implicitly via connection pool)
try {
await client.mailboxClose();
} catch (closeError) {
// Non-fatal, connection pool will handle it
logger.debug('[EMAIL_ADAPTER] Error closing mailbox (non-fatal)', {
error: closeError instanceof Error ? closeError.message : String(closeError),
});
}
} catch (accountError) {
logger.error('[EMAIL_ADAPTER] Error processing account for notifications', {
userId,
accountId: account.id,
error: accountError instanceof Error ? accountError.message : String(accountError),
});
continue;
}
}
// Sort by timestamp (newest first) and apply pagination
notifications.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
const startIndex = (page - 1) * limit;
const endIndex = startIndex + limit;
const paginatedNotifications = notifications.slice(startIndex, endIndex);
logger.debug('[EMAIL_ADAPTER] getNotifications result', {
total: notifications.length,
returned: paginatedNotifications.length,
page,
limit,
});
return paginatedNotifications;
} catch (error) {
logger.error('[EMAIL_ADAPTER] Error getting notifications', {
error: error instanceof Error ? error.message : String(error),
});
return [];
}
}
async markAsRead(userId: string, notificationId: string): Promise<boolean> {