'use client' import { useState, useEffect, useCallback } from 'react' import { useRouter } from 'next/navigation' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Bell, Check, X, Clock, AlertCircle, CheckCircle2, Circle, Share2, Bot, Trash2, Download, Pencil, Presentation, Wind } from 'lucide-react' import { Popover, PopoverContent, PopoverTrigger, } from '@/components/ui/popover' import { getPendingShareRequests, respondToShareRequest, getNotesWithReminders, toggleReminderDone } from '@/app/actions/notes' import { getPendingBrainstormShares, respondToBrainstormShare } from '@/app/actions/brainstorm' import { getUnreadNotifications, markNotificationRead, markAllNotificationsRead, type AppNotification } from '@/app/actions/notifications' import { toast } from 'sonner' import { useRefresh } from '@/lib/use-refresh' import { cn } from '@/lib/utils' import { useLanguage } from '@/lib/i18n' import { formatDistanceToNow } from 'date-fns' interface ShareRequest { id: string status: string permission: string createdAt: Date note: { id: string title: string | null content: string color: string createdAt: Date } sharer: { id: string name: string | null email: string image: string | null } } interface ReminderNote { id: string title: string | null content: string reminder: Date | string | null isReminderDone: boolean } // ── Memento brand tokens ────────────────────────────────────────────────────── const C = { blue: '#FDFDFE', gold: '#D4A373', green: '#A3B18A', dark: '#1C1C1C', beige: '#FDFDFE', } export function NotificationPanel() { const { refreshNotes } = useRefresh() const { t } = useLanguage() const router = useRouter() const [requests, setRequests] = useState([]) const [brainstormShares, setBrainstormShares] = useState([]) const [reminders, setReminders] = useState([]) const [appNotifications, setAppNotifications] = useState([]) const [isLoading, setIsLoading] = useState(false) const [open, setOpen] = useState(false) const loadData = useCallback(async () => { setIsLoading(true) try { const [shareData, brainstormData, reminderData, notifData] = await Promise.all([ getPendingShareRequests(), getPendingBrainstormShares(), getNotesWithReminders(), getUnreadNotifications(), ]) setRequests(shareData as any) setBrainstormShares(brainstormData as any || []) setReminders((reminderData as any) || []) setAppNotifications(notifData || []) } catch (error: any) { console.error('Failed to load notifications:', error) } finally { setIsLoading(false) } }, []) useEffect(() => { loadData() const interval = setInterval(loadData, 30000) const onFocus = () => loadData() window.addEventListener('focus', onFocus) return () => { clearInterval(interval) window.removeEventListener('focus', onFocus) } }, [loadData]) const now = new Date() const activeReminders = reminders.filter(r => !r.isReminderDone && r.reminder) const overdueReminders = activeReminders.filter(r => new Date(r.reminder!) < now) const upcomingReminders = activeReminders.filter(r => new Date(r.reminder!) >= now) const pendingCount = requests.length + brainstormShares.length + overdueReminders.length + appNotifications.length const handleAccept = async (shareId: string) => { try { await respondToShareRequest(shareId, 'accept') setRequests(prev => prev.filter(r => r.id !== shareId)) toast.success(t('notification.accepted'), { description: t('collaboration.nowHasAccess', { name: 'Note' }), duration: 3000, }) refreshNotes(null) setOpen(false) } catch (error: any) { toast.error(error.message || t('general.error')) } } const handleDecline = async (shareId: string) => { try { await respondToShareRequest(shareId, 'decline') setRequests(prev => prev.filter(r => r.id !== shareId)) toast.info(t('notification.declined')) if (requests.length <= 1) setOpen(false) } catch (error: any) { toast.error(error.message || t('general.error')) } } const handleToggleReminder = async (noteId: string, done: boolean) => { try { await toggleReminderDone(noteId, done) setReminders(prev => prev.map(r => r.id === noteId ? { ...r, isReminderDone: done } : r)) refreshNotes(null) } catch { toast.error(t('general.error')) } } const handleMarkNotifRead = async (notifId: string) => { await markNotificationRead(notifId) setAppNotifications(prev => prev.filter(n => n.id !== notifId)) } const handleMarkAllRead = async () => { await markAllNotificationsRead() setAppNotifications([]) } const handleAcceptBrainstorm = async (shareId: string) => { try { await respondToBrainstormShare(shareId, 'accept') setBrainstormShares(prev => prev.filter(s => s.id !== shareId)) toast.success(t('notification.accepted') || 'Accepted') setOpen(false) } catch (error: any) { toast.error(error.message || t('general.error')) } } const handleDeclineBrainstorm = async (shareId: string) => { try { await respondToBrainstormShare(shareId, 'decline') setBrainstormShares(prev => prev.filter(s => s.id !== shareId)) toast.info(t('notification.declined') || 'Declined') if (brainstormShares.length <= 1) setOpen(false) } catch (error: any) { toast.error(error.message || t('general.error')) } } const hasContent = requests.length > 0 || brainstormShares.length > 0 || activeReminders.length > 0 || appNotifications.length > 0 // ── icon bg/color per notification type ────────────────────────────────── const notifIconStyle = (type: string) => { if (type === 'agent_success') return { bg: 'rgba(164,113,72,0.12)', color: '#A47148' } if (type === 'agent_slides_ready') return { bg: 'rgba(164,113,72,0.12)', color: '#A47148' } if (type === 'agent_canvas_ready') return { bg: 'rgba(164,113,72,0.12)', color: '#A47148' } if (type === 'agent_failure') return { bg: 'rgba(239,68,68,0.12)', color: '#EF4444' } if (type === 'brainstorm_invite') return { bg: 'rgba(163,177,138,0.12)', color: '#A3B18A' } if (type === 'brainstorm_joined') return { bg: 'rgba(163,177,138,0.12)', color: '#A3B18A' } return { bg: 'rgba(163,177,138,0.12)', color: '#A3B18A' } } const notifLabelColor = (type: string) => { if (type.startsWith('agent')) { if (type === 'agent_failure') return '#EF4444' if (type === 'brainstorm_invite') return '#A3B18A' if (type === 'brainstorm_joined') return '#A3B18A' return '#A47148' } return '#A3B18A' } return ( {/* Header */}
{t('notification.notifications')}
{appNotifications.length > 0 && ( )} {pendingCount > 0 && ( {pendingCount} )}
{isLoading ? (
) : !hasContent ? (

{t('notification.noNotifications') || 'Aucune notification'}

) : (
{/* ── App notifications (agents, system) ── */} {appNotifications.map((notif) => { const isSlides = notif.type === 'agent_slides_ready' const isCanvas = notif.type === 'agent_canvas_ready' const canvasId = notif.relatedId const iconStyle = notifIconStyle(notif.type) return (
{ if (notif.actionUrl) { handleMarkNotifRead(notif.id) setOpen(false) router.push(notif.actionUrl) } }} > {/* Icon badge */}
{isSlides ? : isCanvas ? : notif.type === 'brainstorm_invite' ? : notif.type === 'brainstorm_joined' ? : notif.type.startsWith('agent') ? : }
{notif.type === 'agent_slides_ready' && (t('notification.slidesReady') || 'Présentation prête')} {notif.type === 'agent_canvas_ready' && (t('notification.canvasReady') || 'Diagramme prêt')} {notif.type === 'agent_success' && (t('notification.agentSuccess') || 'Agent terminé')} {notif.type === 'agent_failure' && (t('notification.agentFailed') || 'Agent échoué')} {notif.type === 'brainstorm_invite' && (t('notification.brainstormInvite') || 'Brainstorm')} {notif.type === 'brainstorm_joined' && (t('notification.brainstormJoined') || 'Brainstorm')} {notif.type === 'system' && t('notification.systemNotification')}

{notif.title}

{notif.message && (

{notif.message}

)}
{formatDistanceToNow(new Date(notif.createdAt), { addSuffix: true })}
{/* Download PPTX button */} {isSlides && canvasId && (
)}
) })} {/* ── Overdue reminders ── */} {overdueReminders.map((note) => (
{t('reminders.overdue')}

{note.title || t('notification.untitled')}

{note.reminder && formatDistanceToNow(new Date(note.reminder), { addSuffix: true })}
))} {/* ── Upcoming reminders ── */} {upcomingReminders.slice(0, 5).map((note) => (

{note.title || t('notification.untitled')}

{note.reminder && new Date(note.reminder).toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
))} {/* ── Brainstorm share invites ── */} {brainstormShares.map((share) => (
{(share.sharer?.name || share.sharer?.email || '?')[0].toUpperCase()}
Brainstorm

{share.sharer?.name || share.sharer?.email}

{t('notification.brainstormShared') || 'invited you to a brainstorm'} « {share.session?.seedIdea?.length > 35 ? share.session.seedIdea.substring(0, 35) + '…' : share.session?.seedIdea} »

))} {/* ── Share requests ── */} {requests.map((request) => (
{/* Avatar */}
{(request.sharer.name || request.sharer.email)[0].toUpperCase()}
Partage

{request.sharer.name || request.sharer.email}

{t('notification.shared', { title: request.note.title || t('notification.untitled') })}

))}
)} {/* Footer */} {activeReminders.length > 0 && ( )} ) }