feat: add webpush and sadhna tracker

This commit is contained in:
gosangam
2026-06-08 15:48:12 +05:30
parent ecb32d2e85
commit 0334e03c3c
52 changed files with 2118 additions and 545 deletions
@@ -27,6 +27,7 @@ import {
import { toast } from "sonner";
import { useAuth } from "@/components/auth/auth-provider";
import { notifications } from "@/lib/social";
import { sadhna } from "@/lib/sadhna";
type NotificationsContextValue = {
/** Live unread count for the header bell. */
@@ -86,13 +87,26 @@ export function NotificationsProvider({
// Seed the count whenever the signed-in user changes (async — no
// synchronous setState in the effect body).
useEffect(() => {
if (!user) return;
if (!user?.id) return;
refreshUnread();
}, [user?.id, refreshUnread]);
// Open the realtime connection while signed in.
// Report the browser's timezone (once per signed-in session) so sādhana
// reminders fire at the user's local clock time. Best-effort.
useEffect(() => {
if (!user) return;
if (!user?.id) return;
try {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
if (tz) void sadhna.setTimezone(tz).catch(() => {});
} catch {
/* Intl unavailable — leave the server default (UTC) */
}
}, [user?.id]);
// Open the realtime connection while signed in.
const userId = user?.id;
useEffect(() => {
if (!userId) return;
const key = process.env.NEXT_PUBLIC_SOKETI_KEY;
const host = process.env.NEXT_PUBLIC_SOKETI_HOST;
if (!key || !host) return; // realtime not configured
@@ -100,7 +114,7 @@ export function NotificationsProvider({
const port = Number(process.env.NEXT_PUBLIC_SOKETI_PORT || "443");
const forceTLS =
(process.env.NEXT_PUBLIC_SOKETI_FORCE_TLS ?? "true") !== "false";
const channelName = `notifications.${user.id}`;
const channelName = `notifications.${userId}`;
let cancelled = false;
// Dynamic import keeps pusher-js out of SSR (it touches window) and the
@@ -124,6 +138,16 @@ export function NotificationsProvider({
setLastEventAt(Date.now());
toast(messageFor(data ?? {}), { duration: 4000 });
});
// Sādhana reminders are ephemeral nudges — toast only, no unread bump
// (they aren't persisted to the notification center).
channel.bind(
"reminder",
(data: { title?: string; body?: string }) => {
toast(data?.body || data?.title || "A gentle reminder", {
duration: 6000,
});
},
);
cleanup = () => {
channel.unbind_all();
pusher.unsubscribe(channelName);
@@ -135,7 +159,7 @@ export function NotificationsProvider({
cancelled = true;
cleanup?.();
};
}, [user?.id]);
}, [userId]);
return (
<NotificationsContext.Provider