import React, { useState, useEffect, useRef } from 'react'; import { Bell } from 'lucide-react'; import { toast } from 'react-toastify'; import notificationService, { Notification } from '../../services/api/notificationService'; import { formatDate } from '../../utils/format'; import useAuthStore from '../../store/useAuthStore'; const InAppNotificationBell: React.FC = () => { const { isAuthenticated, token, isLoading } = useAuthStore(); const [notifications, setNotifications] = useState([]); const [unreadCount, setUnreadCount] = useState(0); const [showDropdown, setShowDropdown] = useState(false); const [loading, setLoading] = useState(false); const intervalRef = useRef(null); const [isInitialized, setIsInitialized] = useState(false); // Wait for auth to initialize before checking React.useEffect(() => { // Small delay to ensure auth store is initialized const timer = setTimeout(() => { setIsInitialized(true); }, 100); return () => clearTimeout(timer); }, []); // Helper to check if user is actually authenticated (has valid token) const isUserAuthenticated = (): boolean => { // Don't check if still initializing if (isLoading || !isInitialized) { return false; } // Check both store state and localStorage to ensure consistency const hasToken = !!(token || localStorage.getItem('token')); return !!(isAuthenticated && hasToken); }; useEffect(() => { // Clear any existing interval first if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } // Early return if not authenticated - don't set up polling at all if (!isUserAuthenticated()) { setNotifications([]); setUnreadCount(0); return; } // Only proceed if we have both authentication state and token const currentToken = token || localStorage.getItem('token'); if (!currentToken) { setNotifications([]); setUnreadCount(0); return; } // Load notifications immediately loadNotifications(); // Poll for new notifications every 30 seconds, but only if authenticated intervalRef.current = setInterval(() => { // Re-check authentication on each poll const stillAuthenticated = isUserAuthenticated(); const stillHasToken = !!(token || localStorage.getItem('token')); if (stillAuthenticated && stillHasToken) { loadNotifications(); } else { // Clear interval and state if user becomes unauthenticated if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } setNotifications([]); setUnreadCount(0); } }, 30000); return () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; }, [isAuthenticated, token]); const loadNotifications = async () => { // Don't make API call if user is not authenticated or doesn't have a token // Double-check both store state and localStorage const hasToken = !!(token || localStorage.getItem('token')); if (!isAuthenticated || !hasToken) { // Clear state if not authenticated setNotifications([]); setUnreadCount(0); return; } try { const response = await notificationService.getMyNotifications({ status: 'delivered', limit: 10, }); const notifs = response.data.data || []; setNotifications(notifs); setUnreadCount(notifs.filter(n => !n.read_at).length); } catch (error) { // Silently fail } }; const handleMarkAsRead = async (notificationId: number) => { try { await notificationService.markAsRead(notificationId); setNotifications(notifications.map(n => n.id === notificationId ? { ...n, status: 'read' as any, read_at: new Date().toISOString() } : n )); setUnreadCount(Math.max(0, unreadCount - 1)); } catch (error: any) { toast.error(error.message || 'Failed to mark as read'); } }; const handleMarkAllAsRead = async () => { try { setLoading(true); const unread = notifications.filter(n => !n.read_at); await Promise.all(unread.map(n => notificationService.markAsRead(n.id))); setNotifications(notifications.map(n => ({ ...n, status: 'read' as any, read_at: new Date().toISOString() }))); setUnreadCount(0); } catch (error: any) { toast.error(error.message || 'Failed to mark all as read'); } finally { setLoading(false); } }; // Don't render if still initializing, not authenticated, or doesn't have a token if (isLoading || !isInitialized || !isUserAuthenticated()) { return null; } return (
{showDropdown && ( <>
setShowDropdown(false)} />

Notifications

{unreadCount > 0 && ( )}
{notifications.length === 0 ? (
No notifications
) : ( notifications.map((notification) => (
{ if (!notification.read_at) { handleMarkAsRead(notification.id); } }} >

{notification.subject || notification.notification_type.replace('_', ' ')}

{notification.content}

{formatDate(new Date(notification.created_at), 'short')}

{!notification.read_at && (
)}
)) )}
)}
); }; export default InAppNotificationBell;