Skip to main content

Notifications

Notifications use a polling strategy rather than WebSocket subscriptions for reliability.

NotificationContext

File: contexts/NotificationContext.tsx

Provides notification state across the application:

StateTypePurpose
unreadCountnumberTotal unread notifications
isConnectedbooleanPolling active flag
lastNotificationNotificationMost recent notification

Polling Behavior

  • UNREAD_NOTIFICATION_COUNT query polled every 30 seconds
  • Document title updated with unread badge count (e.g., (3) AIQLick)
  • Toast notification shown when new notifications arrive — historical unread items are silenced on cold load (see below)
  • Activity monitoring pauses polling when the user is idle

Cold-load backlog suppression

When the page loads with a non-zero unreadCount, those items are part of the user's backlog, not real-time arrivals. Replaying them as toasts on every refresh produced visible-but-stale popups (e.g. "Support Reply on SUP-XXXXX" appearing on the public welcome page).

The current behaviour is:

SourceToast on cold load?Toast in real time?Auto-marked read?
Pre-existing unread (backlog)Non/aNo — stays unread, badge keeps the count
Notification arriving while page is open (poll or SSE)n/aYesYesMARK_NOTIFICATION_AS_READ fires when the toast surfaces
DND enabledNoNoNo

Implementation:

  • The NotificationContext mount effect calls refetchUnreadCount() (network-only) before any polling-effect side effects fire. The fresh count is treated as the backlog baseline.
  • A suppressToastsRef flag tells the useLazyQuery onCompleted handler to seed the dedup set without invoking setLastNotification.
  • The polling effect early-returns until isInitialLoadRef.current === false, so neither the cached emission (count=0 from a previous session) nor the first network emission triggers the new-arrival branch.
  • When NotificationToastHandler actually surfaces a toast, it fires the existing MARK_NOTIFICATION_AS_READ mutation and refetches UnreadNotificationCount so the badge stays in sync. DND short-circuits before the mark-read call.

Individual Notifications

Notifications are not pre-fetched. They are loaded on demand when the user opens the notification panel.

Badge Counts

HookFilePurpose
useBadgeCountslib/hooks/useBadgeCounts.tsGlobal badge count management
useCompanyBadgeCountslib/hooks/useCompanyBadgeCounts.tsCompany-specific badge counts (unread, pending)
useUnreadMessageCountlib/hooks/useUnreadMessageCount.tsUnread message count for messaging badge

Notification UI

Component: components/navigation/Notification.tsx

The notification panel is accessible from the navbar bell icon. It shows:

  • Unread count badge on the bell
  • List of recent notifications (fetched on open)
  • Mark as read / mark all as read actions

Notification Settings

Page: app/(shared)/notifications/settings/

Users can configure notification preferences (which event types to receive, delivery channels).

Toast System

The application uses a custom toast system (TWToast) for transient notifications:

  • Success / error / warning / info variants
  • Auto-dismiss with configurable duration
  • Stacks multiple toasts vertically
  • BillingErrorToastHandler auto-shows billing-related toasts