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 && (
}
onClick={handleAction}
>
View Release
)}
{/* Generic action button for notifications with action_url/action_text */}
{notification.action_data?.action_url &&
notification.action_data?.action_text && (
}
onClick={handleAction}
>
{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;