From 14bb8e572532993eb4f1dc2691a8e0b7aeeb3bc4 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 18:06:22 +0000 Subject: [PATCH 1/3] Changes Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com> --- .../mika/CancellationScheduledBanner.tsx | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/components/mika/CancellationScheduledBanner.tsx diff --git a/src/components/mika/CancellationScheduledBanner.tsx b/src/components/mika/CancellationScheduledBanner.tsx new file mode 100644 index 0000000..191fe6f --- /dev/null +++ b/src/components/mika/CancellationScheduledBanner.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { useState } from "react"; +import { CalendarClock, Loader2, RotateCcw } from "lucide-react"; +import { format } from "date-fns"; +import { ptBR } from "date-fns/locale"; +import { toast } from "sonner"; +import { supabase } from "@/integrations/supabase/client"; +import { useSubscription } from "@/hooks/use-profile"; +import { Button } from "@/components/ui/button"; + +/** + * Discrete banner shown when the subscription is scheduled to be canceled + * at the end of the current period (cancel_at_period_end = true) but is + * still active. Tells the user until when access remains and offers a + * one-click way to reactivate via the Paddle customer portal. + */ +export function CancellationScheduledBanner() { + const { data: subscription } = useSubscription(); + const [loading, setLoading] = useState(false); + + const isActive = subscription?.status === "active" || subscription?.status === "trialing"; + const scheduled = !!subscription?.cancel_at_period_end && isActive; + + if (!scheduled) return null; + + const endsAt = subscription?.current_period_end + ? format(new Date(subscription.current_period_end), "d 'de' MMMM 'de' yyyy", { + locale: ptBR, + }) + : null; + + const openPortal = async () => { + setLoading(true); + try { + const { data, error } = await supabase.functions.invoke("create-portal-session"); + if (error || !data?.url) { + toast.error("Não foi possível abrir o portal. Tente novamente."); + return; + } + window.open(data.url, "_blank", "noopener,noreferrer"); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+ +

+ Sua assinatura foi cancelada + {endsAt ? ( + <> + {" "}e expira em{" "} + {endsAt}. + + ) : ( + " e expirará no fim do período atual." + )}{" "} + Você ainda tem acesso até lá. +

+
+ +
+
+ ); +} From f7efbdbb00e0dbac1bf95dfc67d43527c9114cd4 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 18:06:29 +0000 Subject: [PATCH 2/3] Changes Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com> --- src/routes/painel.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/painel.tsx b/src/routes/painel.tsx index ad3bac8..eb5e318 100644 --- a/src/routes/painel.tsx +++ b/src/routes/painel.tsx @@ -22,6 +22,7 @@ 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"; import { PaymentIssueBanner } from "@/components/mika/PaymentIssueBanner"; +import { CancellationScheduledBanner } from "@/components/mika/CancellationScheduledBanner"; import { ThemeToggle } from "@/components/mika/ThemeToggle"; import { Button } from "@/components/ui/button"; import { From 95e3cbdf0ca7b2de908cba290d98f2663154af2b Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 18:06:35 +0000 Subject: [PATCH 3/3] Changes Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com> --- src/routes/painel.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/painel.tsx b/src/routes/painel.tsx index eb5e318..f595c50 100644 --- a/src/routes/painel.tsx +++ b/src/routes/painel.tsx @@ -156,6 +156,7 @@ function PainelLayout() { +