From defeacee5df51c7829b714c41951ffe8754ef565 Mon Sep 17 00:00:00 2001 From: Matthew Meszaros Date: Mon, 8 Jun 2026 15:05:13 +0200 Subject: [PATCH] feat: show realtime notifications Adds the dashboard notification bell, marks feed items read, and refreshes the feed from realtime notification events. --- web/src/components/layout/AppHeader.tsx | 2 + .../components/layout/NotificationBell.tsx | 104 ++++++++++++++++++ web/src/hooks/useRealtimeEvents.ts | 7 ++ 3 files changed, 113 insertions(+) create mode 100644 web/src/components/layout/NotificationBell.tsx diff --git a/web/src/components/layout/AppHeader.tsx b/web/src/components/layout/AppHeader.tsx index 607bcc152..b428b284e 100644 --- a/web/src/components/layout/AppHeader.tsx +++ b/web/src/components/layout/AppHeader.tsx @@ -16,6 +16,7 @@ import { ChevronRight, Menu, Search } from "lucide-react"; import { Logo } from "@/components/svg"; import { useAppStore } from "@/stores"; import { ConnectionIndicator } from "@/components/shared/ConnectionIndicator"; +import { NotificationBell } from "./NotificationBell"; import { OrgSwitcher } from "./OrgSwitcher"; import { PlanPill } from "./PlanPill"; @@ -130,6 +131,7 @@ export function AppHeader({ onMenu }: { onMenu?: () => void }) {
+ + + {open && ( +
+
+ Notifications + {unread > 0 && ( + + )} +
+
+ {items.length === 0 ? ( +
You're all caught up.
+ ) : ( + items.map((n) => { + const click = () => { + if (!n.read_at) markOne.mutate(n.id); + setOpen(false); + }; + return n.link ? ( + + {itemBody(n)} + + ) : ( + + ); + }) + )} +
+
+ )} + + ); +} diff --git a/web/src/hooks/useRealtimeEvents.ts b/web/src/hooks/useRealtimeEvents.ts index a1b03a19f..7bca89f2e 100644 --- a/web/src/hooks/useRealtimeEvents.ts +++ b/web/src/hooks/useRealtimeEvents.ts @@ -175,6 +175,13 @@ export function useRealtimeEvents() { return } + // A new in-app notification for this user: refresh the bell feed live + // (invalidate-only — the badge updates without a toast, to avoid burst spam). + if (includes('NOTIFICATION')) { + invalidate([['notifications', 'feed']]) + return + } + // An automation was created/updated/deleted or fired: refresh the list and, // for a specific automation, its detail + run history (live in the builder). if (includes('AUTOMATION')) {