diff --git a/src/components/mika/EnterpriseLeadForm.tsx b/src/components/mika/EnterpriseLeadForm.tsx index dd86d4f..3b06129 100644 --- a/src/components/mika/EnterpriseLeadForm.tsx +++ b/src/components/mika/EnterpriseLeadForm.tsx @@ -6,6 +6,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { IMaskInput } from "react-imask"; import { toast } from "sonner"; +import { supabase } from "@/integrations/supabase/client"; +import { translateAuthError } from "@/lib/auth-errors"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; @@ -37,17 +39,22 @@ export function EnterpriseLeadForm() { const onSubmit = async (data: FormValues) => { setSubmitting(true); - try { - // Etapa 2: gravar em enterprise_leads via supabase.from(...).insert(...) - // Por ora: feedback otimista para o usuário. - await new Promise((r) => setTimeout(r, 700)); - toast.success("Recebemos seu contato! Falamos com você em até 1 dia útil."); - reset(); - } catch { - toast.error("Algo deu errado do nosso lado. Já estamos investigando."); - } finally { - setSubmitting(false); + const { error } = await supabase.from("enterprise_leads").insert({ + company_name: data.company_name, + contact_name: data.contact_name, + email: data.email, + phone: data.phone, + team_size: data.team_size, + message: data.message || null, + status: "new", + }); + setSubmitting(false); + if (error) { + toast.error(translateAuthError(error.message)); + return; } + toast.success("Recebemos seu contato! Falamos com você em até 1 dia útil."); + reset(); }; const phone = watch("phone") || ""; diff --git a/src/components/mika/PlansSection.tsx b/src/components/mika/PlansSection.tsx index f06b2be..cedaa4c 100644 --- a/src/components/mika/PlansSection.tsx +++ b/src/components/mika/PlansSection.tsx @@ -3,91 +3,63 @@ import { useState } from "react"; import { Link } from "@tanstack/react-router"; import { Check } from "lucide-react"; +import { useQuery } from "@tanstack/react-query"; +import { supabase } from "@/integrations/supabase/client"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Badge } from "@/components/ui/badge"; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogTrigger } from "@/components/ui/dialog"; +import { Skeleton } from "@/components/ui/skeleton"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogTrigger, +} from "@/components/ui/dialog"; import { EnterpriseLeadForm } from "./EnterpriseLeadForm"; import { cn } from "@/lib/utils"; -// Fallback estático — na Etapa 2 será substituído por query Supabase à tabela `plans`. -const STATIC_PLANS = [ - { - slug: "basic", - name: "Basic", - description: "Para começar a usar IA no dia a dia.", - monthly: 69.9, - yearly: 671.04, - highlighted: false, - is_enterprise: false, - features: [ - "1 agente pessoal no Telegram", - "Memória persistente básica", - "Integração Google Workspace", - "5 skills personalizadas", - "Suporte por e-mail", - ], - }, - { - slug: "starter", - name: "Starter", - description: "Para quem usa IA todos os dias.", - monthly: 199.9, - yearly: 1919.04, - highlighted: false, - is_enterprise: false, - features: [ - "Tudo do Basic", - "Memória avançada e contextual", - "Skills ilimitadas", - "Agendamentos automáticos", - "Suporte prioritário", - ], - }, - { - slug: "professional", - name: "Professional", - description: "Para profissionais e times pequenos.", - monthly: 399.9, - yearly: 3839.04, - highlighted: true, - is_enterprise: false, - features: [ - "Tudo do Starter", - "VPS dedicada de alta performance", - "Modelos de IA premium", - "Integrações personalizadas", - "Onboarding 1:1", - "Suporte em horário estendido", - ], - }, - { - slug: "enterprise", - name: "Enterprise", - description: "Para empresas com necessidades específicas.", - monthly: null, - yearly: null, - highlighted: false, - is_enterprise: true, - features: [ - "Tudo do Professional", - "Múltiplos agentes", - "SSO e gestão de equipe", - "SLA contratual", - "Treinamento da equipe", - "Account manager dedicado", - ], - }, -] as const; +interface PlanRow { + id: string; + slug: string; + name: string; + description: string | null; + price_monthly_brl: number | null; + price_yearly_brl: number | null; + features: string[]; + highlighted: boolean; + is_enterprise: boolean; + display_order: number; +} const fmtBRL = (v: number) => v.toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); +function usePlans() { + return useQuery({ + queryKey: ["plans"], + staleTime: 5 * 60_000, + queryFn: async (): Promise => { + const { data, error } = await supabase + .from("plans") + .select("*") + .order("display_order", { ascending: true }); + if (error) throw error; + return (data || []).map((p) => ({ + ...p, + features: Array.isArray(p.features) ? (p.features as string[]) : [], + })) as PlanRow[]; + }, + }); +} + export function PlansSection() { const [yearly, setYearly] = useState(false); + const { data: plans, isLoading } = usePlans(); return ( -
+

Planos para cada estágio

@@ -96,110 +68,140 @@ export function PlansSection() {

- + Mensal - - + + Anual - 20% off + + 20% off +
- {STATIC_PLANS.map((plan) => { - const price = yearly ? plan.yearly : plan.monthly; - const monthlyEquivalent = yearly && plan.yearly ? plan.yearly / 12 : null; - return ( -
- {plan.highlighted && ( - - Mais popular - - )} + {isLoading + ? Array.from({ length: 4 }).map((_, i) => ( + + )) + : (plans || []).map((plan) => { + const monthly = plan.price_monthly_brl ?? 0; + const yearlyPrice = plan.price_yearly_brl ?? 0; + const monthlyEquivalent = yearly && yearlyPrice ? yearlyPrice / 12 : monthly; + return ( +
+ {plan.highlighted && ( + + Mais popular + + )} -
-

{plan.name}

-

{plan.description}

-
+
+

{plan.name}

+

{plan.description}

+
-
- {plan.is_enterprise ? ( -

Sob consulta

- ) : ( - <> -
- R$ - - {fmtBRL(yearly && monthlyEquivalent ? monthlyEquivalent : (price as number))} - - /mês -
- {yearly && ( -

- R$ {fmtBRL(plan.yearly!)} cobrados anualmente -

+
+ {plan.is_enterprise ? ( +

Sob consulta

+ ) : ( + <> +
+ R$ + + {fmtBRL(monthlyEquivalent)} + + /mês +
+ {yearly && yearlyPrice > 0 && ( +

+ R$ {fmtBRL(yearlyPrice)} cobrados anualmente +

+ )} + )} - - )} -
+
-
    - {plan.features.map((f) => ( -
  • - - {f} -
  • - ))} -
+
    + {plan.features.map((f) => ( +
  • + + {f} +
  • + ))} +
-
- {plan.is_enterprise ? ( - - - - - - - Plano Enterprise - - Conte um pouco sobre sua empresa. Nosso time entra em contato em até 1 dia útil. - - - - - - ) : ( - + + + + Plano Enterprise + + Conte um pouco sobre sua empresa. Nosso time entra em contato em até + 1 dia útil. + + + + + + ) : ( + )} - > - - Assinar agora - - - )} -

- Sujeito à política de uso justo. Veja termos. -

-
-
- ); - })} +

+ Sujeito à política de uso justo. Veja termos. +

+
+ + ); + })}
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index dbc2b0c..8c8dbaa 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -236,12 +236,3 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() - -import type { getRouter } from './router.tsx' -import type { createStart } from '@tanstack/react-start' -declare module '@tanstack/react-start' { - interface Register { - ssr: true - router: Awaited> - } -}