diff options
| author | Fuwn <[email protected]> | 2026-02-07 01:42:57 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-07 01:42:57 -0800 |
| commit | 5c5b1993edd890a80870ee05607ac5f088191d4e (patch) | |
| tree | a721b76bcd49ba10826c53efc87302c7a689512f /apps/web/lib/hooks/use-realtime-entries.ts | |
| download | asa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.tar.xz asa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.zip | |
feat: asa.news RSS reader with developer tier, REST API, and webhooks
Full-stack RSS reader SaaS: Supabase + Next.js + Go worker.
Includes three subscription tiers (free/pro/developer), API key auth,
read-only REST API, webhook push notifications, Stripe billing with
proration, and PWA support.
Diffstat (limited to 'apps/web/lib/hooks/use-realtime-entries.ts')
| -rw-r--r-- | apps/web/lib/hooks/use-realtime-entries.ts | 74 |
1 files changed, 74 insertions, 0 deletions
diff --git a/apps/web/lib/hooks/use-realtime-entries.ts b/apps/web/lib/hooks/use-realtime-entries.ts new file mode 100644 index 0000000..0eaba77 --- /dev/null +++ b/apps/web/lib/hooks/use-realtime-entries.ts @@ -0,0 +1,74 @@ +"use client" + +import { useEffect, useRef } from "react" +import { useQueryClient } from "@tanstack/react-query" +import { createSupabaseBrowserClient } from "@/lib/supabase/client" +import { queryKeys } from "@/lib/queries/query-keys" +import { toast } from "sonner" +import { useNotificationStore } from "@/lib/stores/notification-store" + +const DEBOUNCE_MILLISECONDS = 3000 + +export function useRealtimeEntries() { + const queryClient = useQueryClient() + const supabaseClientReference = useRef(createSupabaseBrowserClient()) + const pendingCountReference = useRef(0) + const debounceTimerReference = useRef<ReturnType<typeof setTimeout> | null>(null) + + useEffect(() => { + function flushPendingNotifications() { + const count = pendingCountReference.current + if (count === 0) return + + pendingCountReference.current = 0 + debounceTimerReference.current = null + + const message = + count === 1 ? "1 new entry" : `${count} new entries` + + useNotificationStore.getState().addNotification(message) + toast(message, { + action: { + label: "refresh", + onClick: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.timeline.all, + }) + }, + }, + }) + } + + const channel = supabaseClientReference.current + .channel("entries-realtime") + .on( + "postgres_changes", + { + event: "INSERT", + schema: "public", + table: "entries", + }, + () => { + pendingCountReference.current++ + + if (debounceTimerReference.current) { + clearTimeout(debounceTimerReference.current) + } + + debounceTimerReference.current = setTimeout( + flushPendingNotifications, + DEBOUNCE_MILLISECONDS + ) + } + ) + .subscribe() + + return () => { + if (debounceTimerReference.current) { + clearTimeout(debounceTimerReference.current) + } + + supabaseClientReference.current.removeChannel(channel) + } + }, [queryClient]) +} |