diff --git a/src/hooks/use-subscription-realtime.ts b/src/hooks/use-subscription-realtime.ts new file mode 100644 index 0000000..d946923 --- /dev/null +++ b/src/hooks/use-subscription-realtime.ts @@ -0,0 +1,42 @@ +"use client"; + +import { useEffect } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { supabase } from "@/integrations/supabase/client"; +import { useAuth } from "@/hooks/use-auth"; + +/** + * Subscribes to realtime changes on the user's subscription row. + * When the payments webhook upserts a subscription, the affected + * queries are invalidated so the UI updates without a page reload. + */ +export function useSubscriptionRealtime() { + const { user } = useAuth(); + const queryClient = useQueryClient(); + + useEffect(() => { + if (!user?.id) return; + + const channel = supabase + .channel(`subscriptions:user:${user.id}`) + .on( + "postgres_changes", + { + event: "*", + schema: "public", + table: "subscriptions", + filter: `user_id=eq.${user.id}`, + }, + () => { + queryClient.invalidateQueries({ queryKey: ["subscription", user.id] }); + queryClient.invalidateQueries({ queryKey: ["has-active-subscription", user.id] }); + queryClient.invalidateQueries({ queryKey: ["plan"] }); + }, + ) + .subscribe(); + + return () => { + supabase.removeChannel(channel); + }; + }, [user?.id, queryClient]); +} diff --git a/src/routes/painel.tsx b/src/routes/painel.tsx index cd7d44b..85754b3 100644 --- a/src/routes/painel.tsx +++ b/src/routes/painel.tsx @@ -17,6 +17,7 @@ import { useQuery } from "@tanstack/react-query"; import { supabase } from "@/integrations/supabase/client"; import { useAuth } from "@/hooks/use-auth"; import { useProfile } from "@/hooks/use-profile"; +import { useSubscriptionRealtime } from "@/hooks/use-subscription-realtime"; const PADDLE_ENV = (import.meta.env.VITE_PADDLE_ENVIRONMENT as string) === "production" ? "live" : "sandbox"; import { Logo } from "@/components/mika/Logo"; @@ -83,6 +84,9 @@ function PainelLayout() { const location = useLocation(); const [mobileOpen, setMobileOpen] = useState(false); + // Atualiza queries quando o webhook gravar/alterar a assinatura + useSubscriptionRealtime(); + // Faturamento permanece acessível mesmo sem assinatura ativa // (para que o usuário possa ver o estado e assinar / gerenciar). const skipSubGuard = location.pathname.startsWith("/painel/faturamento"); diff --git a/supabase/migrations/20260417175349_c5290736-3871-4f0d-aa72-316b65ce6bca.sql b/supabase/migrations/20260417175349_c5290736-3871-4f0d-aa72-316b65ce6bca.sql new file mode 100644 index 0000000..6bb2057 --- /dev/null +++ b/supabase/migrations/20260417175349_c5290736-3871-4f0d-aa72-316b65ce6bca.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.subscriptions REPLICA IDENTITY FULL; +ALTER PUBLICATION supabase_realtime ADD TABLE public.subscriptions; \ No newline at end of file