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')) {