Adicionou banner no painel

X-Lovable-Edit-ID: edt-0489c80d-cc37-4b11-a6ea-ffc102ed392e
Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot] 2026-04-17 18:05:19 +00:00
commit f739ae2121
2 changed files with 97 additions and 0 deletions

View file

@ -0,0 +1,94 @@
"use client";
import { useState } from "react";
import { AlertTriangle, ExternalLink, Loader2 } from "lucide-react";
import { toast } from "sonner";
import { supabase } from "@/integrations/supabase/client";
import { useSubscription } from "@/hooks/use-profile";
import { Button } from "@/components/ui/button";
/**
* Persistent banner shown at the top of the dashboard when the user's
* subscription status indicates a payment problem (past_due / unpaid).
*
* Provides a one-click action to open the Paddle customer portal so the
* user can update their payment method.
*/
export function PaymentIssueBanner() {
const { data: subscription } = useSubscription();
const [loading, setLoading] = useState(false);
const status = subscription?.status;
const showBanner = status === "past_due" || status === "unpaid";
if (!showBanner) return null;
const isUnpaid = status === "unpaid";
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 (
<div
role="alert"
aria-live="polite"
className={
isUnpaid
? "border-b border-destructive/30 bg-destructive/10"
: "border-b border-amber-500/30 bg-amber-500/10"
}
>
<div className="mx-auto max-w-6xl px-4 sm:px-6 py-3 flex flex-col sm:flex-row sm:items-center gap-3">
<div className="flex items-start gap-3 flex-1 min-w-0">
<AlertTriangle
className={
"h-5 w-5 shrink-0 mt-0.5 " +
(isUnpaid ? "text-destructive" : "text-amber-600 dark:text-amber-500")
}
/>
<div className="min-w-0">
<p
className={
"text-sm font-semibold " +
(isUnpaid ? "text-destructive" : "text-amber-700 dark:text-amber-400")
}
>
{isUnpaid
? "Sua assinatura está sem pagamento"
: "Pagamento pendente na sua assinatura"}
</p>
<p className="text-xs sm:text-sm text-muted-foreground mt-0.5">
{isUnpaid
? "Regularize o pagamento para reativar o acesso completo aos recursos."
: "Atualize seu método de pagamento para evitar a suspensão do serviço."}
</p>
</div>
</div>
<Button
size="sm"
onClick={openPortal}
disabled={loading}
className="shrink-0 rounded-lg bg-primary hover:bg-primary-dark text-primary-foreground"
>
{loading ? (
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
) : (
<ExternalLink className="h-4 w-4 mr-2" />
)}
Atualizar pagamento
</Button>
</div>
</div>
);
}

View file

@ -21,6 +21,7 @@ import { useSubscriptionRealtime } from "@/hooks/use-subscription-realtime";
const PADDLE_ENV = (import.meta.env.VITE_PADDLE_ENVIRONMENT as string) === "production" ? "live" : "sandbox"; const PADDLE_ENV = (import.meta.env.VITE_PADDLE_ENVIRONMENT as string) === "production" ? "live" : "sandbox";
import { Logo } from "@/components/mika/Logo"; import { Logo } from "@/components/mika/Logo";
import { PaymentIssueBanner } from "@/components/mika/PaymentIssueBanner";
import { ThemeToggle } from "@/components/mika/ThemeToggle"; import { ThemeToggle } from "@/components/mika/ThemeToggle";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -153,6 +154,8 @@ function PainelLayout() {
<UserMenu /> <UserMenu />
</header> </header>
<PaymentIssueBanner />
<main className="flex-1 px-4 sm:px-6 py-6 sm:py-8"> <main className="flex-1 px-4 sm:px-6 py-6 sm:py-8">
<div className="mx-auto max-w-6xl"> <div className="mx-auto max-w-6xl">
<Outlet /> <Outlet />