notifications big
This commit is contained in:
parent
477a0f324d
commit
cc1bab76c7
@ -1,6 +1,6 @@
|
|||||||
import React, { memo, useState, useEffect } from 'react';
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
import Link from 'next/link';
|
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 { Badge } from '@/components/ui/badge';
|
||||||
import { useNotifications } from '@/hooks/use-notifications';
|
import { useNotifications } from '@/hooks/use-notifications';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -215,6 +215,12 @@ export const NotificationBadge = memo(function NotificationBadge({ className }:
|
|||||||
Parole
|
Parole
|
||||||
</Badge>
|
</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>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{formatDistanceToNow(new Date(notification.timestamp), { addSuffix: true })}
|
{formatDistanceToNow(new Date(notification.timestamp), { addSuffix: true })}
|
||||||
|
|||||||
@ -205,10 +205,143 @@ export class EmailAdapter implements NotificationAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getNotifications(userId: string, page = 1, limit = 20): Promise<Notification[]> {
|
async getNotifications(userId: string, page = 1, limit = 20): Promise<Notification[]> {
|
||||||
// For now, return empty array - we can implement this later if needed
|
logger.debug('[EMAIL_ADAPTER] getNotifications called', { userId, page, limit });
|
||||||
// The notification count is what matters for the badge
|
|
||||||
// In the future, we could return unread emails as notifications
|
try {
|
||||||
return [];
|
// 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> {
|
async markAsRead(userId: string, notificationId: string): Promise<boolean> {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user