All files / src/components NotificationBell.tsx

0% Statements 0/13
0% Branches 0/33
0% Functions 0/5
0% Lines 0/12

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                                                                                                                                                                                                                                                                       
// src/components/NotificationBell.tsx
 
/**
 * Real-time notification bell component
 * Displays WebSocket connection status and unread notification count
 * Integrates with useWebSocket hook for real-time updates
 */
 
import { useState, useCallback } from 'react';
import { Bell, Wifi, WifiOff } from 'lucide-react';
import { useWebSocket } from '../hooks/useWebSocket';
import { useEventBus } from '../hooks/useEventBus';
import type { DealNotificationData } from '../types/websocket';
 
interface NotificationBellProps {
  /**
   * Callback when bell is clicked
   */
  onClick?: () => void;
 
  /**
   * Whether to show the connection status indicator
   * @default true
   */
  showConnectionStatus?: boolean;
 
  /**
   * Custom CSS classes for the bell container
   */
  className?: string;
}
 
export function NotificationBell({
  onClick,
  showConnectionStatus = true,
  className = '',
}: NotificationBellProps) {
  const [unreadCount, setUnreadCount] = useState(0);
  const { isConnected, error } = useWebSocket({ autoConnect: true });
 
  // Handle incoming deal notifications
  const handleDealNotification = useCallback((data?: DealNotificationData) => {
    if (data) {
      setUnreadCount((prev) => prev + 1);
    }
  }, []);
 
  // Listen for deal notifications via event bus
  useEventBus('notification:deal', handleDealNotification);
 
  // Reset count when clicked
  const handleClick = () => {
    setUnreadCount(0);
    onClick?.();
  };
 
  return (
    <div className={`relative inline-block ${className}`}>
      {/* Notification Bell Button */}
      <button
        onClick={handleClick}
        className="relative p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500"
        aria-label={`Notifications${unreadCount > 0 ? ` (${unreadCount} unread)` : ''}`}
        title={
          error
            ? `WebSocket error: ${error}`
            : isConnected
              ? 'Connected to live notifications'
              : 'Connecting...'
        }
      >
        <Bell
          className={`w-6 h-6 ${unreadCount > 0 ? 'text-blue-600 dark:text-blue-400' : 'text-gray-600 dark:text-gray-400'}`}
        />
 
        {/* Unread Badge */}
        {unreadCount > 0 && (
          <span className="absolute top-0 right-0 inline-flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-600 rounded-full transform translate-x-1 -translate-y-1">
            {unreadCount > 99 ? '99+' : unreadCount}
          </span>
        )}
 
        {/* Connection Status Indicator */}
        {showConnectionStatus && (
          <span
            className="absolute bottom-0 right-0 inline-block w-3 h-3 rounded-full border-2 border-white dark:border-gray-900 transform translate-x-1 translate-y-1"
            style={{
              backgroundColor: isConnected ? '#10b981' : error ? '#ef4444' : '#f59e0b',
            }}
            title={isConnected ? 'Connected' : error ? 'Disconnected' : 'Connecting'}
          />
        )}
      </button>
 
      {/* Connection Status Tooltip (shown on hover when disconnected) */}
      {!isConnected && error && (
        <div className="absolute top-full right-0 mt-2 px-3 py-2 bg-gray-900 text-white text-sm rounded-lg shadow-lg whitespace-nowrap z-50 opacity-0 hover:opacity-100 transition-opacity pointer-events-none">
          <div className="flex items-center gap-2">
            <WifiOff className="w-4 h-4 text-red-400" />
            <span>Live notifications unavailable</span>
          </div>
        </div>
      )}
    </div>
  );
}
 
/**
 * Simple connection status indicator (no bell, just status)
 */
export function ConnectionStatus() {
  const { isConnected, error } = useWebSocket({ autoConnect: true });
 
  return (
    <div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-gray-100 dark:bg-gray-800 text-sm">
      {isConnected ? (
        <>
          <Wifi className="w-4 h-4 text-green-600 dark:text-green-400" />
          <span className="text-gray-700 dark:text-gray-300">Live</span>
        </>
      ) : (
        <>
          <WifiOff className="w-4 h-4 text-red-600 dark:text-red-400" />
          <span className="text-gray-700 dark:text-gray-300">
            {error ? 'Offline' : 'Connecting...'}
          </span>
        </>
      )}
    </div>
  );
}