"use client"; import { useCallback } from "react"; import type { RealtimePostgresChangesPayload } from "@supabase/supabase-js"; import { useQueryClient, type InfiniteData } from "@tanstack/react-query"; import type { Database } from "@/types/database"; import { useRealtimeChannel } from "./use-realtime-channel"; import { buildEqFilter } from "@/lib/realtime/subscription-manager"; import { useCurrentUser } from "@/hooks/use-current-user"; type MovieRow = Database["public"]["Tables"]["movies"]["Row"]; function moviesQueryKey(groupId: string): readonly [string, string] { return ["group-movies", groupId] as const; } /** * Hook that subscribes to Supabase Realtime changes on the movies table * for a specific group_id. Updates the TanStack Query cache directly * on INSERT, UPDATE, and DELETE events. */ export function useRealtimeMovies(groupId: string | null) { const queryClient = useQueryClient(); // Use the current user id so realtime events can dual-invalidate // the cross-list ["all-user-movies", userId] query key alongside the // per-group ["group-movies", groupId] cache update. const { data: currentUser } = useCurrentUser(); const userId = currentUser?.id ?? null; const handlePayload = useCallback( (payload: RealtimePostgresChangesPayload) => { if (!groupId) return; const key = moviesQueryKey(groupId); if (payload.eventType === "INSERT") { const newMovie = payload.new; queryClient.setQueryData>(key, (old) => { if (!old) return undefined; // Avoid duplicates across all pages if (old.pages.some((page) => page.some((m) => m.id === newMovie.id))) return old; // Prepend to first page (ordered by added_at DESC) return { pages: [[newMovie, ...old.pages[0]], ...old.pages.slice(1)], pageParams: old.pageParams, }; }); if (userId) { void queryClient.invalidateQueries({ queryKey: ["all-user-movies", userId] }); } } else if (payload.eventType === "UPDATE") { const updated = payload.new; queryClient.setQueryData>(key, (old) => { if (!old) return undefined; return { pages: old.pages.map((page) => page.map((m) => (m.id === updated.id ? updated : m))), pageParams: old.pageParams, }; }); if (userId) { void queryClient.invalidateQueries({ queryKey: ["all-user-movies", userId] }); } } else if (payload.eventType === "DELETE") { const deleted = payload.old; if (deleted && "id" in deleted) { queryClient.setQueryData>(key, (old) => { if (!old) return undefined; return { pages: old.pages.map((page) => page.filter((m) => m.id !== deleted.id)), pageParams: old.pageParams, }; }); if (userId) { void queryClient.invalidateQueries({ queryKey: ["all-user-movies", userId] }); } } } }, [groupId, queryClient, userId], ); const { status } = useRealtimeChannel({ channelName: groupId ? `movies:${groupId}` : null, config: { event: "*", schema: "public", table: "movies", filter: groupId ? buildEqFilter("group_id", groupId) : undefined, onPayload: handlePayload, }, enabled: !!groupId, }); return { status }; }