Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | 'use client';
import { useState, useCallback } from 'react';
import { Icon } from '@/components/ui/icons';
import { cn } from '@/lib/core';
import { NotificationPanel } from '../NotificationPanel';
import { ConnectionIndicator } from '../ConnectionIndicator';
import { useNotificationsWithFallback } from '@/hooks/useNotificationsWithFallback';
import type { NotificationPayload } from '@/lib/notifications/types';
export interface NotificationBellProps {
/** Polling interval in milliseconds for fallback mode (default: 30000) */
pollingInterval?: number;
/** Maximum notifications to show in panel (default: 10) */
maxNotifications?: number;
/** Whether to show browser notifications (default: true) */
showBrowserNotifications?: boolean;
/** Whether to show connection indicator (default: true) */
showConnectionStatus?: boolean;
/** Optional class name */
className?: string;
}
/**
* NotificationBell - Bell icon with badge and dropdown panel
*
* Displays a notification bell icon with unread count badge.
* Uses WebSocket for real-time updates with polling fallback.
* Clicking opens a dropdown panel with recent notifications.
*/
export function NotificationBell({
pollingInterval = 30000,
maxNotifications = 10,
showBrowserNotifications = true,
showConnectionStatus = true,
className,
}: NotificationBellProps) {
const [isPanelOpen, setIsPanelOpen] = useState(false);
const {
notifications,
unreadCount,
isLoading,
markAsRead,
markAllAsRead,
mode,
isRealtimeConnected,
} = useNotificationsWithFallback({
pollingInterval,
showBrowserNotifications,
});
// Limit notifications for display
const displayNotifications = notifications.slice(0, maxNotifications);
const handleTogglePanel = useCallback(() => {
setIsPanelOpen((prev) => !prev);
}, []);
const handleClosePanel = useCallback(() => {
setIsPanelOpen(false);
}, []);
const handleMarkAsRead = useCallback(
async (id: string) => {
await markAsRead(id);
},
[markAsRead]
);
const handleMarkAllAsRead = useCallback(async () => {
await markAllAsRead();
}, [markAllAsRead]);
const handleNotificationClick = useCallback(
(notification: NotificationPayload) => {
// If notification has a link, the NotificationItem will handle navigation
// We just close the panel
if (!notification.read) {
markAsRead(notification.id);
}
setIsPanelOpen(false);
},
[markAsRead]
);
return (
<div className={cn('relative', className)}>
{/* Bell Button */}
<button
type="button"
onClick={handleTogglePanel}
className={cn(
'relative p-2 text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800',
isPanelOpen && 'text-gray-900 dark:text-white bg-gray-100 dark:bg-gray-800'
)}
aria-label={`Notifications${unreadCount > 0 ? ` (${unreadCount} unread)` : ''}${mode === 'realtime' ? ' - Live updates active' : ''}`}
aria-expanded={isPanelOpen}
aria-haspopup="dialog"
>
<Icon name="bell" className="w-5 h-5" />
{/* Badge */}
{unreadCount > 0 && (
<span
className="absolute -top-0.5 -right-0.5 flex items-center justify-center min-w-[18px] h-[18px] px-1 text-xs font-bold text-white bg-red-500 rounded-full"
aria-hidden="true"
>
{unreadCount > 99 ? '99+' : unreadCount}
</span>
)}
{/* Connection Status Indicator */}
{showConnectionStatus && (
<ConnectionIndicator
isConnected={isRealtimeConnected}
mode={mode}
size="sm"
showLabel={false}
className="absolute -bottom-0.5 -right-0.5"
/>
)}
</button>
{/* Notification Panel */}
<NotificationPanel
isOpen={isPanelOpen}
onClose={handleClosePanel}
notifications={displayNotifications}
unreadCount={unreadCount}
isLoading={isLoading}
onMarkAsRead={handleMarkAsRead}
onMarkAllAsRead={handleMarkAllAsRead}
onNotificationClick={handleNotificationClick}
/>
</div>
);
}
export default NotificationBell;
|