mirror of
https://github.com/domfelipe/mika-agent-assist.git
synced 2026-08-07 13:56:51 +00:00
Changes
Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com>
This commit is contained in:
parent
6bdca7b7b2
commit
db49d48413
2 changed files with 60 additions and 14 deletions
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue