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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | 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 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 1x 1x | 'use client';
/**
* useNotificationsWithFallback Hook
*
* Hybrid notifications hook that uses WebSocket for real-time updates
* when available, falling back to polling when WebSocket is unavailable.
*
* This is the recommended hook to use in components that need notifications,
* as it provides seamless fallback behavior.
*/
import { useEffect, useCallback } from 'react';
import { useRealtimeNotifications } from './useRealtimeNotifications';
import { useNotifications } from './useNotifications';
import { useAppDispatch, useAppSelector } from '@/redux/store';
import {
fetchNotifications,
markNotificationAsRead,
markAllNotificationsAsRead,
selectNotifications,
selectUnreadCount,
selectNotificationsLoading,
selectNotificationsError,
} from '@/redux/features/notificationsSlice';
import type { NotificationPayload } from '@/lib/notifications/types';
export type NotificationMode = 'realtime' | 'polling';
export interface UseNotificationsWithFallbackReturn {
/** All notifications (from Redux state) */
notifications: NotificationPayload[];
/** Unread notification count */
unreadCount: number;
/** Whether notifications are loading */
isLoading: boolean;
/** Error message if any */
error: string | null;
/** Mark a single notification as read */
markAsRead: (id: string) => Promise<void>;
/** Mark all notifications as read */
markAllAsRead: () => Promise<void>;
/** Manually refresh notifications */
refresh: () => Promise<void>;
/** Current notification mode */
mode: NotificationMode;
/** Whether connected (realtime) or polling active */
isActive: boolean;
/** Whether WebSocket is connected */
isRealtimeConnected: boolean;
}
export interface UseNotificationsWithFallbackOptions {
/** Polling interval when in fallback mode (default: 30000ms) */
pollingInterval?: number;
/** Whether to show browser notifications (default: true) */
showBrowserNotifications?: boolean;
}
const DEFAULT_OPTIONS: UseNotificationsWithFallbackOptions = {
pollingInterval: 30000,
showBrowserNotifications: true,
};
export function useNotificationsWithFallback(
options: UseNotificationsWithFallbackOptions = {}
): UseNotificationsWithFallbackReturn {
const opts = { ...DEFAULT_OPTIONS, ...options };
const dispatch = useAppDispatch();
// Real-time notifications via WebSocket
const {
isConnected: isRealtimeConnected,
markAsRead: realtimeMarkAsRead,
} = useRealtimeNotifications({
showBrowserNotifications: opts.showBrowserNotifications,
});
// Polling-based notifications (as fallback)
const polling = useNotifications({
// Disable polling when connected to real-time
pollingInterval: isRealtimeConnected ? 0 : opts.pollingInterval,
// Only auto-fetch if not using realtime
autoFetch: !isRealtimeConnected,
});
// Redux state (populated by either realtime or polling)
const notifications = useAppSelector(selectNotifications);
const unreadCount = useAppSelector(selectUnreadCount);
const isLoading = useAppSelector(selectNotificationsLoading);
const reduxError = useAppSelector(selectNotificationsError);
// Determine current mode
const mode: NotificationMode = isRealtimeConnected ? 'realtime' : 'polling';
/**
* Sync polling results to Redux when in polling mode
*/
useEffect(() => {
if (!isRealtimeConnected && polling.notifications.length > 0) {
// When in polling mode and we have data, dispatch to Redux
// This is handled by the fetchNotifications thunk when polling
}
}, [isRealtimeConnected, polling.notifications]);
/**
* Initial fetch to populate Redux state when realtime connects
*/
useEffect(() => {
if (isRealtimeConnected) {
// When realtime connects, do an initial fetch to get existing notifications
dispatch(fetchNotifications({}));
}
}, [isRealtimeConnected, dispatch]);
/**
* Mark a notification as read
*/
const markAsRead = useCallback(
async (id: string): Promise<void> => {
if (isRealtimeConnected) {
// Use realtime for cross-device sync
realtimeMarkAsRead([id]);
// Also update via API for persistence
dispatch(markNotificationAsRead(id));
} else {
// Use polling-based approach
await polling.markAsRead(id);
}
},
[isRealtimeConnected, realtimeMarkAsRead, dispatch, polling]
);
/**
* Mark all notifications as read
*/
const markAllAsRead = useCallback(async (): Promise<void> => {
if (isRealtimeConnected) {
// Update via Redux thunk for API call
dispatch(markAllNotificationsAsRead());
} else {
await polling.markAllAsRead();
}
}, [isRealtimeConnected, dispatch, polling]);
/**
* Manually refresh notifications
*/
const refresh = useCallback(async (): Promise<void> => {
if (isRealtimeConnected) {
// Refetch from Redux
dispatch(fetchNotifications({}));
} else {
await polling.refresh();
}
}, [isRealtimeConnected, dispatch, polling]);
return {
// Use Redux state when in realtime mode, local state when polling
notifications: isRealtimeConnected ? notifications : polling.notifications,
unreadCount: isRealtimeConnected ? unreadCount : polling.unreadCount,
isLoading: isRealtimeConnected ? isLoading : polling.isLoading,
error: isRealtimeConnected ? reduxError : polling.error,
markAsRead,
markAllAsRead,
refresh,
mode,
isActive: isRealtimeConnected || polling.isActive,
isRealtimeConnected,
};
}
export default useNotificationsWithFallback;
|