From bcffe9684f77756b9b0d4ea6071acb0549fdac1d Mon Sep 17 00:00:00 2001 From: gosangam Date: Sun, 7 Jun 2026 00:23:54 +0530 Subject: [PATCH] feat: add websocket and analytics --- .env.example | 15 ++ .gitignore | 2 +- app/(app)/notifications/page.tsx | 17 +- app/layout.tsx | 16 +- components/notification-bell.tsx | 28 +--- .../notifications/notifications-provider.tsx | 157 ++++++++++++++++++ package.json | 1 + yarn.lock | 12 ++ 8 files changed, 219 insertions(+), 29 deletions(-) create mode 100644 .env.example create mode 100644 components/notifications/notifications-provider.tsx diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..13bc9612 --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# BFF API Server (server-side fetches) +BFF_URL=http://localhost:8080 + +# BFF API Server (browser-side fetches; must be publicly reachable in prod) +NEXT_PUBLIC_BFF_URL=http://localhost:8080 + +# RTF Viewer (development only) +ALLOW_RTF=true +NEXT_PUBLIC_OAUTH_ENABLED=true + +# SOKETI +NEXT_PUBLIC_SOKETI_KEY=spsays +NEXT_PUBLIC_SOKETI_HOST=ws.prabhupadasays.org +NEXT_PUBLIC_SOKETI_PORT=443 +NEXT_PUBLIC_SOKETI_FORCE_TLS=true diff --git a/.gitignore b/.gitignore index 9be524b3..57313438 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) -.env* +.env # vercel .vercel diff --git a/app/(app)/notifications/page.tsx b/app/(app)/notifications/page.tsx index 8f231a83..adf4950b 100644 --- a/app/(app)/notifications/page.tsx +++ b/app/(app)/notifications/page.tsx @@ -19,11 +19,13 @@ import Link from "next/link"; import { Bell } from "lucide-react"; import { useAuth } from "@/components/auth/auth-provider"; import { useAuthGate } from "@/components/auth/auth-gate"; +import { useNotifications } from "@/components/notifications/notifications-provider"; import { notifications, type AppNotification } from "@/lib/social"; export default function NotificationsPage() { const { user, loading } = useAuth(); const { requireAuth } = useAuthGate(); + const { lastEventAt, clearUnread } = useNotifications(); const router = useRouter(); const [items, setItems] = useState([]); @@ -42,7 +44,9 @@ export default function NotificationsPage() { }; }, [loading, user, requireAuth, router]); - // Load the feed + mark everything read once we have a user. + // Load the feed + mark everything read once we have a user, and again + // whenever a realtime notification arrives while the page is open + // (lastEventAt bumps). Marking read here also clears the header badge. useEffect(() => { if (!user) return; let cancelled = false; @@ -57,13 +61,16 @@ export default function NotificationsPage() { .catch(() => { if (!cancelled) setState("error"); }); - notifications.markAllRead().catch(() => { - /* non-critical */ - }); + notifications + .markAllRead() + .then(() => clearUnread()) + .catch(() => { + /* non-critical */ + }); return () => { cancelled = true; }; - }, [user]); + }, [user, lastEventAt, clearUnread]); if (loading || !user) { return ( diff --git a/app/layout.tsx b/app/layout.tsx index 1055ddf9..4174116d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import Script from "next/script"; import { Geist, Geist_Mono, Lora, Bricolage_Grotesque } from "next/font/google"; import { ogDefault } from "@/lib/og"; import { ThemeProvider } from "@/components/theme-provider"; @@ -6,6 +7,7 @@ import { TooltipProvider } from "@/components/ui/tooltip"; import { AudioPlayerProvider, FloatingPlayer } from "@/components/audio-player"; import { AuthProvider } from "@/components/auth/auth-provider"; import { AuthGateProvider } from "@/components/auth/auth-gate"; +import { NotificationsProvider } from "@/components/notifications/notifications-provider"; import { SharePostProvider } from "@/components/social/share-post-provider"; import { Toaster } from "@/components/ui/toaster"; import "./globals.css"; @@ -73,7 +75,8 @@ export default function RootLayout({ disableTransitionOnChange > - + + @@ -86,9 +89,18 @@ export default function RootLayout({ - + + + {/* Rybbit analytics — loads after hydration (afterInteractive is + next/script's defer-equivalent). Site-wide via the root layout, + loaded once across navigations. */} +