Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot] 2026-04-17 17:48:30 +00:00
parent 6bdca7b7b2
commit db49d48413
2 changed files with 60 additions and 14 deletions

View file

@ -1,8 +1,8 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { Link } from "@tanstack/react-router"; import { Link, useNavigate } from "@tanstack/react-router";
import { Check } from "lucide-react"; import { Check, Loader2 } from "lucide-react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { supabase } from "@/integrations/supabase/client"; import { supabase } from "@/integrations/supabase/client";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -18,6 +18,8 @@ import {
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { EnterpriseLeadForm } from "./EnterpriseLeadForm"; import { EnterpriseLeadForm } from "./EnterpriseLeadForm";
import { useAuth } from "@/hooks/use-auth";
import { usePaddleCheckout } from "@/hooks/use-paddle-checkout";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
interface PlanRow { interface PlanRow {
@ -57,6 +59,26 @@ function usePlans() {
export function PlansSection() { export function PlansSection() {
const [yearly, setYearly] = useState(false); const [yearly, setYearly] = useState(false);
const { data: plans, isLoading } = usePlans(); const { data: plans, isLoading } = usePlans();
const { user } = useAuth();
const navigate = useNavigate();
const { openCheckout, loading: checkoutLoading } = usePaddleCheckout();
const [pendingSlug, setPendingSlug] = useState<string | null>(null);
const handleSubscribe = async (slug: string) => {
const cycle = yearly ? "yearly" : "monthly";
if (!user) {
navigate({ to: "/signup", search: { plan: slug, cycle } });
return;
}
const priceId = `${slug}_${cycle}`;
setPendingSlug(slug);
await openCheckout({
priceId,
userId: user.id,
customerEmail: user.email || undefined,
});
setPendingSlug(null);
};
return ( return (
<section id="planos" className="py-20 sm:py-28 bg-background scroll-mt-20"> <section id="planos" className="py-20 sm:py-28 bg-background scroll-mt-20">
@ -179,7 +201,8 @@ export function PlansSection() {
</Dialog> </Dialog>
) : ( ) : (
<Button <Button
asChild onClick={() => handleSubscribe(plan.slug)}
disabled={checkoutLoading && pendingSlug === plan.slug}
className={cn( className={cn(
"w-full rounded-lg transition-all duration-150 active:scale-[0.98]", "w-full rounded-lg transition-all duration-150 active:scale-[0.98]",
plan.highlighted plan.highlighted
@ -187,12 +210,14 @@ export function PlansSection() {
: "bg-foreground hover:bg-foreground/90 text-background", : "bg-foreground hover:bg-foreground/90 text-background",
)} )}
> >
<Link {checkoutLoading && pendingSlug === plan.slug ? (
to="/signup" <>
search={{ plan: plan.slug, cycle: yearly ? "yearly" : "monthly" }} <Loader2 className="h-4 w-4 mr-2 animate-spin" />
> Abrindo checkout
Assinar agora </>
</Link> ) : (
"Assinar agora"
)}
</Button> </Button>
)} )}
<p className="mt-3 text-xs text-muted-foreground text-center"> <p className="mt-3 text-xs text-muted-foreground text-center">

View file

@ -1,10 +1,12 @@
"use client"; "use client";
import { useState } from "react";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { format } from "date-fns"; import { format } from "date-fns";
import { ptBR } from "date-fns/locale"; import { ptBR } from "date-fns/locale";
import { CreditCard, ExternalLink } from "lucide-react"; import { CreditCard, ExternalLink, Loader2 } from "lucide-react";
import { useSubscription, useProfile } from "@/hooks/use-profile"; import { toast } from "sonner";
import { useSubscription } from "@/hooks/use-profile";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { supabase } from "@/integrations/supabase/client"; import { supabase } from "@/integrations/supabase/client";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -16,7 +18,7 @@ export const Route = createFileRoute("/painel/faturamento")({
function BillingPage() { function BillingPage() {
const { data: subscription, isLoading } = useSubscription(); const { data: subscription, isLoading } = useSubscription();
const { data: profile } = useProfile(); const [portalLoading, setPortalLoading] = useState(false);
const { data: plan } = useQuery({ const { data: plan } = useQuery({
queryKey: ["plan", subscription?.plan_id], queryKey: ["plan", subscription?.plan_id],
@ -31,6 +33,20 @@ function BillingPage() {
}, },
}); });
const openPortal = async () => {
setPortalLoading(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 {
setPortalLoading(false);
}
};
return ( return (
<div className="space-y-8"> <div className="space-y-8">
<header> <header>
@ -75,10 +91,15 @@ function BillingPage() {
</p> </p>
)} )}
<Button <Button
disabled={!profile?.stripe_customer_id} disabled={!subscription?.paddle_subscription_id || portalLoading}
onClick={openPortal}
className="mt-6 rounded-lg bg-primary hover:bg-primary-dark text-primary-foreground" className="mt-6 rounded-lg bg-primary hover:bg-primary-dark text-primary-foreground"
> >
{portalLoading ? (
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
) : (
<ExternalLink className="h-4 w-4 mr-2" /> <ExternalLink className="h-4 w-4 mr-2" />
)}
Gerenciar assinatura Gerenciar assinatura
</Button> </Button>
</section> </section>