import React, { useEffect, useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { ActionIcon, Badge, Box, Button, Card, Divider, Group, Indicator, Popover, ScrollArea, Stack, Text, ThemeIcon, Tooltip, useMantineTheme, } from '@mantine/core'; import { Bell, Check, CheckCheck, Download, ExternalLink, Info, Settings, AlertTriangle, Megaphone, X, Eye, EyeOff, ArrowRight, } from 'lucide-react'; import useNotificationsStore from '../store/notifications'; import API from '../api'; // Get icon for notification type const getNotificationIcon = (type) => { switch (type) { case 'version_update': return ; case 'setting_recommendation': return ; case 'announcement': return ; case 'warning': return ; case 'info': default: return ; } }; // Get color for notification priority const getPriorityColor = (priority) => { switch (priority) { case 'critical': return 'red'; case 'high': return 'orange'; case 'normal': return 'blue'; case 'low': default: return 'gray'; } }; // Get color for notification type const getTypeColor = (type) => { switch (type) { case 'version_update': return 'green'; case 'setting_recommendation': return 'blue'; case 'announcement': return 'violet'; case 'warning': return 'orange'; case 'info': default: return 'gray'; } }; // Individual notification item component const NotificationItem = ({ notification, onDismiss, onAction, onClose }) => { const theme = useMantineTheme(); const navigate = useNavigate(); const typeColor = getTypeColor(notification.notification_type); const priorityColor = getPriorityColor(notification.priority); const isDismissed = notification.is_dismissed; const handleDismiss = (e) => { e.stopPropagation(); onDismiss(notification.id, 'dismissed'); }; const handleAction = () => { // Handle action_url from action_data const actionUrl = notification.action_data?.action_url; const releaseUrl = notification.action_data?.release_url; if (actionUrl) { // Internal navigation onClose(); // Close the popover navigate(actionUrl); } else if (releaseUrl) { // External link window.open(releaseUrl, '_blank'); } if (onAction) { onAction(notification); } }; return ( {/* Dismiss button for non-setting notifications (only if not already dismissed) */} {notification.notification_type !== 'setting_recommendation' && !isDismissed && ( )} {getNotificationIcon(notification.notification_type)} {notification.title} {isDismissed && ( Dismissed )} {notification.priority === 'high' || notification.priority === 'critical' ? ( {notification.priority} ) : null} {notification.message} {/* Action buttons for specific notification types */} {notification.notification_type === 'version_update' && notification.action_data?.release_url && ( )} {/* Generic action button for notifications with action_url/action_text */} {notification.action_data?.action_url && notification.action_data?.action_text && ( )} {notification.notification_type === 'setting_recommendation' && !notification.action_data?.action_url && ( )} {new Date(notification.created_at).toLocaleDateString()} ); }; // Main notification center component with bell icon and popover const NotificationCenter = ({ onSettingAction }) => { const [opened, setOpened] = useState(false); const [showDismissed, setShowDismissed] = useState(false); const notifications = useNotificationsStore((s) => s.notifications); const unreadCount = useNotificationsStore((s) => s.unreadCount); const getUnreadNotifications = useNotificationsStore( (s) => s.getUnreadNotifications ); // Fetch notifications on mount and periodically const fetchNotifications = useCallback(async () => { try { await API.getNotifications(showDismissed); } catch (error) { console.error('Failed to fetch notifications:', error); } }, [showDismissed]); useEffect(() => { fetchNotifications(); // Refresh notifications every 5 minutes const interval = setInterval(fetchNotifications, 5 * 60 * 1000); return () => clearInterval(interval); }, [fetchNotifications]); const handleDismiss = async (notificationId, actionTaken = null) => { try { await API.dismissNotification(notificationId, actionTaken); } catch (error) { console.error('Failed to dismiss notification:', error); } }; const handleDismissAll = async () => { try { await API.dismissAllNotifications(); } catch (error) { console.error('Failed to dismiss all notifications:', error); } }; const handleAction = (notification) => { if ( notification.notification_type === 'setting_recommendation' && onSettingAction ) { onSettingAction(notification); } }; const unreadNotifications = getUnreadNotifications(); const displayedNotifications = showDismissed ? notifications : unreadNotifications; return ( 9 ? '9+' : unreadCount} disabled={unreadCount === 0} offset={4} processing={unreadCount > 0} > setOpened((o) => !o)} aria-label="Notifications" > {/* Header */} Notifications {unreadCount > 0 && ( {unreadCount} new )} setShowDismissed((prev) => !prev)} > {showDismissed ? : } {unreadCount > 0 && ( )} {/* Notification list */} {displayedNotifications.length === 0 ? ( {showDismissed ? 'No dismissed notifications' : 'All caught up!'} {showDismissed ? 'Dismissed notifications appear here' : 'No new notifications'} ) : ( {displayedNotifications.map((notification) => ( setOpened(false)} /> ))} )} {/* Footer with info text */} {!showDismissed && notifications.length > unreadNotifications.length && ( <> {notifications.length - unreadNotifications.length} dismissed notification {notifications.length - unreadNotifications.length !== 1 ? 's' : ''} )} ); }; export default NotificationCenter;