improvement: notification feature polished. feature tabs following the same notification socket emmited data to be updated.

This commit is contained in:
2026-07-18 16:57:13 +03:30
parent 9f6ec193d2
commit 9941dca849
23 changed files with 506 additions and 63 deletions

View File

@@ -12,6 +12,9 @@ import {
} from 'react';
import { io, type Socket } from 'socket.io-client';
import { useAuth } from '@/lib/hooks/useAuth';
import { accessTokenRefreshedEventName } from '@/lib/auth/accessTokenEvents';
import { notifyTabBadgesChanged } from '@/lib/tabBadgeUtils';
import { notifyPendingConnectionsChanged } from '@/lib/hooks/usePendingConnectionsCount';
import type { UserNotificationItem } from '@/types/notifications';
type RealtimeContextValue = {
@@ -37,13 +40,33 @@ function apiOrigin(): string {
}
}
function invalidateSidebarBadges(notification?: UserNotificationItem | null) {
// Sidebar Cases/Tasks/Treatment badges + open tab soft-refetch (via window event).
notifyTabBadgesChanged();
// Orgs pending badge/list — only for connection-related inbox events.
if (
notification?.type === 'CONNECTION_REQUEST' ||
notification?.href?.startsWith('/organizations')
) {
notifyPendingConnectionsChanged();
}
}
export function RealtimeProvider({ children }: { children: ReactNode }) {
const { user, currentOrganization, isAuthReady } = useAuth();
const [connected, setConnected] = useState(false);
const [lastNotification, setLastNotification] = useState<UserNotificationItem | null>(null);
const [unreadCount, setUnreadCount] = useState<number | null>(null);
/** Bumped after access-token refresh so the socket reconnects with the new cookie. */
const [socketEpoch, setSocketEpoch] = useState(0);
const socketRef = useRef<Socket | null>(null);
useEffect(() => {
const onRefreshed = () => setSocketEpoch((n) => n + 1);
window.addEventListener(accessTokenRefreshedEventName(), onRefreshed);
return () => window.removeEventListener(accessTokenRefreshedEventName(), onRefreshed);
}, []);
useEffect(() => {
if (!isAuthReady || !user || !currentOrganization?.id) {
socketRef.current?.disconnect();
@@ -63,6 +86,9 @@ export function RealtimeProvider({ children }: { children: ReactNode }) {
socket.on('notification.created', (payload: { notification?: UserNotificationItem }) => {
if (payload?.notification) {
setLastNotification(payload.notification);
invalidateSidebarBadges(payload.notification);
} else {
invalidateSidebarBadges(null);
}
});
socket.on('notification.unreadCount', (payload: { count?: number }) => {
@@ -76,7 +102,7 @@ export function RealtimeProvider({ children }: { children: ReactNode }) {
socketRef.current = null;
setConnected(false);
};
}, [isAuthReady, user, currentOrganization?.id]);
}, [isAuthReady, user, currentOrganization?.id, socketEpoch]);
const setUnreadCountStable = useCallback((count: number) => {
setUnreadCount(count);