mirror of
https://github.com/domfelipe/mika-agent-assist.git
synced 2026-08-07 10:16:43 +00:00
Changes
Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com>
This commit is contained in:
parent
b5254cf33d
commit
faa10d9a72
9 changed files with 782 additions and 9 deletions
119
src/components/mika/EnterpriseLeadForm.tsx
Normal file
119
src/components/mika/EnterpriseLeadForm.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { IMaskInput } from "react-imask";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
company_name: z.string().min(2, "Informe o nome da empresa").max(120),
|
||||||
|
contact_name: z.string().min(2, "Informe seu nome").max(120),
|
||||||
|
email: z.string().email("Formato de e-mail inválido."),
|
||||||
|
phone: z.string().min(14, "Telefone inválido").max(20),
|
||||||
|
team_size: z.enum(["1-10", "11-50", "51-200", "200+"], { message: "Selecione o tamanho da equipe" }),
|
||||||
|
message: z.string().max(1000).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormValues = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
export function EnterpriseLeadForm() {
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
watch,
|
||||||
|
reset,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<FormValues>({ resolver: zodResolver(schema) });
|
||||||
|
|
||||||
|
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 phone = watch("phone") || "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="company_name">Empresa</Label>
|
||||||
|
<Input id="company_name" {...register("company_name")} placeholder="Acme S/A" />
|
||||||
|
{errors.company_name && <p className="text-xs text-destructive">{errors.company_name.message}</p>}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="contact_name">Seu nome</Label>
|
||||||
|
<Input id="contact_name" {...register("contact_name")} placeholder="Maria Silva" />
|
||||||
|
{errors.contact_name && <p className="text-xs text-destructive">{errors.contact_name.message}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="email">E-mail corporativo</Label>
|
||||||
|
<Input id="email" type="email" {...register("email")} placeholder="voce@empresa.com" />
|
||||||
|
{errors.email && <p className="text-xs text-destructive">{errors.email.message}</p>}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="phone">Telefone</Label>
|
||||||
|
<IMaskInput
|
||||||
|
id="phone"
|
||||||
|
mask="(00) 00000-0000"
|
||||||
|
value={phone}
|
||||||
|
onAccept={(value) => setValue("phone", value as string, { shouldValidate: true })}
|
||||||
|
placeholder="(11) 99999-9999"
|
||||||
|
className="flex h-10 w-full rounded-lg border border-input bg-transparent px-3 py-2 text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20"
|
||||||
|
/>
|
||||||
|
{errors.phone && <p className="text-xs text-destructive">{errors.phone.message}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="team_size">Tamanho da equipe</Label>
|
||||||
|
<Select onValueChange={(v) => setValue("team_size", v as FormValues["team_size"], { shouldValidate: true })}>
|
||||||
|
<SelectTrigger id="team_size"><SelectValue placeholder="Selecione" /></SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="1-10">1-10 pessoas</SelectItem>
|
||||||
|
<SelectItem value="11-50">11-50 pessoas</SelectItem>
|
||||||
|
<SelectItem value="51-200">51-200 pessoas</SelectItem>
|
||||||
|
<SelectItem value="200+">200+ pessoas</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{errors.team_size && <p className="text-xs text-destructive">{errors.team_size.message}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="message">Mensagem (opcional)</Label>
|
||||||
|
<Textarea id="message" {...register("message")} rows={3} placeholder="Conta um pouco do seu caso de uso…" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={submitting}
|
||||||
|
className="w-full rounded-lg bg-primary hover:bg-primary-dark text-primary-foreground transition-all duration-150 active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
{submitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
|
Enviar
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
58
src/components/mika/FaqSection.tsx
Normal file
58
src/components/mika/FaqSection.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||||
|
|
||||||
|
const faqs = [
|
||||||
|
{
|
||||||
|
q: "O que é o Mika?",
|
||||||
|
a: "Mika é uma plataforma brasileira que entrega para você um agente de IA pessoal hospedado em VPS gerenciada, acessível direto pelo Telegram. Ele aprende com você, lembra de contexto e executa tarefas com integrações reais.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Como funciona o Telegram?",
|
||||||
|
a: "Após assinar, você recebe um QR code para conectar seu agente Mika ao seu Telegram. A partir daí, basta conversar com ele como faria com um assistente humano — ele responde direto no chat.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Posso cancelar quando quiser?",
|
||||||
|
a: "Sim, a qualquer momento, sem multa. O acesso continua até o fim do período já pago e não há cobranças adicionais depois disso.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Meus dados ficam seguros?",
|
||||||
|
a: "Seu agente roda em uma VPS gerenciada exclusivamente para você, com criptografia em trânsito e em repouso. Nunca usamos seus dados para treinar modelos. Estamos em conformidade com a LGPD.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Qual a diferença entre os planos?",
|
||||||
|
a: "Os planos diferem em capacidade de memória, número de skills, performance da VPS, modelos de IA disponíveis e nível de suporte. O Professional inclui VPS dedicada e modelos premium; o Enterprise inclui múltiplos agentes, SSO e SLA.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Como funciona o Skill Studio?",
|
||||||
|
a: "É onde você cria automações personalizadas em linguagem natural — basta descrever o que quer e o Mika gera a skill. Sem código.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "O Mika funciona em grupo do Telegram?",
|
||||||
|
a: "Sim. Você pode adicionar o Mika a grupos e mencioná-lo para receber respostas, mantendo a privacidade das outras conversas.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Tem trial grátis?",
|
||||||
|
a: "Não oferecemos trial, mas garantimos reembolso integral nos primeiros 7 dias caso o produto não atenda suas expectativas.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function FaqSection() {
|
||||||
|
return (
|
||||||
|
<section id="faq" className="py-20 sm:py-28 bg-muted/40">
|
||||||
|
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold tracking-tight">Perguntas frequentes</h2>
|
||||||
|
<p className="mt-4 text-lg text-muted-foreground">As dúvidas mais comuns sobre o Mika.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Accordion type="single" collapsible className="mt-12 bg-card rounded-xl border border-border shadow-soft px-2">
|
||||||
|
{faqs.map((f, i) => (
|
||||||
|
<AccordionItem key={f.q} value={`item-${i}`} className="border-border last:border-b-0">
|
||||||
|
<AccordionTrigger className="px-4 text-left font-medium hover:no-underline">{f.q}</AccordionTrigger>
|
||||||
|
<AccordionContent className="px-4 text-muted-foreground leading-relaxed">{f.a}</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
44
src/components/mika/FeaturesSection.tsx
Normal file
44
src/components/mika/FeaturesSection.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { Brain, Sparkles, Cable, Clock, MessageCircleHeart, ShieldCheck, type LucideIcon } from "lucide-react";
|
||||||
|
|
||||||
|
type Feature = { icon: LucideIcon; title: string; description: string };
|
||||||
|
|
||||||
|
const features: Feature[] = [
|
||||||
|
{ icon: Brain, title: "Memória Persistente", description: "O agente lembra tudo sobre você entre sessões — preferências, contatos e contexto contínuo." },
|
||||||
|
{ icon: Sparkles, title: "Skills Personalizadas", description: "Crie automações em linguagem natural no Skill Studio. Sem código." },
|
||||||
|
{ icon: Cable, title: "Integração Google Workspace", description: "Gmail, Calendar e Drive conectados em 1 clique e prontos para usar." },
|
||||||
|
{ icon: Clock, title: "Agendamentos Automáticos", description: "Cron em linguagem natural — ex: \"toda segunda às 9h me envie resumo da semana\"." },
|
||||||
|
{ icon: MessageCircleHeart, title: "Suporte em Português", description: "Time brasileiro respondendo em horário comercial. Sem tradutor automático." },
|
||||||
|
{ icon: ShieldCheck, title: "Privacidade Total", description: "Seu agente roda na sua VPS. Sem treinamento com seus dados." },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function FeaturesSection() {
|
||||||
|
return (
|
||||||
|
<section id="recursos" className="py-20 sm:py-28 bg-background">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="max-w-2xl mx-auto text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold tracking-tight text-balance">
|
||||||
|
Tudo que um assistente pessoal deveria ter
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 text-lg text-muted-foreground">
|
||||||
|
Recursos pensados para profissionais brasileiros que querem produtividade sem complicação.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-14 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{features.map(({ icon: Icon, title, description }) => (
|
||||||
|
<article
|
||||||
|
key={title}
|
||||||
|
className="group rounded-xl border border-border bg-card p-6 shadow-soft hover:shadow-lg hover:border-primary/30 transition-all duration-200"
|
||||||
|
>
|
||||||
|
<div className="h-12 w-12 rounded-full bg-primary/10 grid place-items-center text-primary">
|
||||||
|
<Icon className="h-6 w-6" aria-hidden />
|
||||||
|
</div>
|
||||||
|
<h3 className="mt-5 text-lg font-semibold">{title}</h3>
|
||||||
|
<p className="mt-2 text-sm text-muted-foreground leading-relaxed">{description}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
148
src/components/mika/HeroSection.tsx
Normal file
148
src/components/mika/HeroSection.tsx
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import { ArrowRight, Send } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
const messages = [
|
||||||
|
{ who: "user", text: "Resuma meus e-mails de hoje" },
|
||||||
|
{
|
||||||
|
who: "bot",
|
||||||
|
text: "Você tem 3 e-mails importantes:",
|
||||||
|
bullets: [
|
||||||
|
"📅 Carla — confirmar reunião de quinta às 15h",
|
||||||
|
"💰 Financeiro — fatura do servidor vence amanhã",
|
||||||
|
"📝 Time — feedback no documento do Q2",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ who: "user", text: "Marca a reunião com a Carla" },
|
||||||
|
{ who: "bot", text: "Pronto ✅ — agendado para quinta, 15h. Calendário atualizado." },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function HeroSection() {
|
||||||
|
return (
|
||||||
|
<section className="relative min-h-[85vh] pt-32 pb-16 overflow-hidden bg-gradient-hero">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="grid lg:grid-cols-2 gap-12 lg:gap-8 items-center">
|
||||||
|
{/* Left: copy */}
|
||||||
|
<div className="text-center lg:text-left">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
className="inline-flex items-center gap-2 bg-primary/10 text-primary rounded-full px-3 py-1 text-sm font-medium"
|
||||||
|
>
|
||||||
|
🇧🇷 Feito no Brasil · Suporte em português
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: 16 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.1 }}
|
||||||
|
className="mt-6 text-4xl sm:text-5xl lg:text-[3rem] xl:text-[3.5rem] leading-[1.1] font-bold tracking-tight text-balance"
|
||||||
|
>
|
||||||
|
Seu assistente pessoal de IA, sempre disponível no{" "}
|
||||||
|
<span className="bg-gradient-primary bg-clip-text text-transparent">Telegram</span>
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: 16 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.2 }}
|
||||||
|
className="mt-6 text-lg text-muted-foreground max-w-xl mx-auto lg:mx-0 text-balance"
|
||||||
|
>
|
||||||
|
O Mika entrega um agente de IA próprio que aprende com você, gerencia sua agenda,
|
||||||
|
seus e-mails e suas tarefas — tudo em português e conversando direto no Telegram.
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 16 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.3 }}
|
||||||
|
className="mt-8 flex flex-col sm:flex-row gap-3 justify-center lg:justify-start"
|
||||||
|
>
|
||||||
|
<Button asChild size="lg" className="rounded-lg bg-primary hover:bg-primary-dark text-primary-foreground transition-all duration-150 active:scale-[0.98] shadow-glow">
|
||||||
|
<Link to="/signup">
|
||||||
|
Começar agora <ArrowRight className="ml-1 h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<Button asChild variant="outline" size="lg" className="rounded-lg">
|
||||||
|
<a href="#planos">Ver planos</a>
|
||||||
|
</Button>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right: Telegram mockup */}
|
||||||
|
<TelegramMockup />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TelegramMockup() {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial="hidden"
|
||||||
|
animate="show"
|
||||||
|
variants={{
|
||||||
|
hidden: {},
|
||||||
|
show: { transition: { staggerChildren: 0.3, delayChildren: 0.4 } },
|
||||||
|
}}
|
||||||
|
className="relative mx-auto w-full max-w-md"
|
||||||
|
aria-label="Demonstração de conversa no Telegram com o agente Mika"
|
||||||
|
>
|
||||||
|
<div className="absolute -inset-6 bg-gradient-primary opacity-20 blur-3xl rounded-full" aria-hidden />
|
||||||
|
<div className="relative bg-card border border-border rounded-2xl shadow-soft overflow-hidden">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="bg-gradient-primary text-primary-foreground px-4 py-3 flex items-center gap-3">
|
||||||
|
<div className="h-9 w-9 rounded-full bg-white/20 grid place-items-center font-bold">M</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="font-semibold leading-tight">Mika</p>
|
||||||
|
<p className="text-xs text-white/80">online · seu agente pessoal</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Messages */}
|
||||||
|
<div className="p-4 space-y-3 bg-muted/30 min-h-[420px]">
|
||||||
|
{messages.map((m, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={i}
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, y: 8 },
|
||||||
|
show: { opacity: 1, y: 0 },
|
||||||
|
}}
|
||||||
|
className={m.who === "user" ? "flex justify-end" : "flex justify-start"}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
m.who === "user"
|
||||||
|
? "max-w-[80%] rounded-2xl rounded-br-sm px-3 py-2 text-sm bg-primary text-primary-foreground"
|
||||||
|
: "max-w-[85%] rounded-2xl rounded-bl-sm px-3 py-2 text-sm bg-card border border-border text-foreground"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p>{m.text}</p>
|
||||||
|
{m.bullets && (
|
||||||
|
<ul className="mt-2 space-y-1 text-xs text-muted-foreground">
|
||||||
|
{m.bullets.map((b) => (
|
||||||
|
<li key={b}>{b}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* Input */}
|
||||||
|
<div className="border-t border-border p-3 flex items-center gap-2 bg-card">
|
||||||
|
<div className="flex-1 h-9 rounded-full bg-muted/60 px-3 grid items-center text-xs text-muted-foreground">
|
||||||
|
Mensagem…
|
||||||
|
</div>
|
||||||
|
<div className="h-9 w-9 rounded-full bg-primary grid place-items-center text-primary-foreground">
|
||||||
|
<Send className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
37
src/components/mika/HowItWorksSection.tsx
Normal file
37
src/components/mika/HowItWorksSection.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
const steps = [
|
||||||
|
{ n: 1, title: "Escolha seu plano", description: "4 planos pensados para diferentes níveis de uso." },
|
||||||
|
{ n: 2, title: "Conecte seu Telegram", description: "Em 30 segundos. Escaneie um QR code e pronto." },
|
||||||
|
{ n: 3, title: "Personalize seu agente", description: "Defina nome, tom de voz, integrações e skills." },
|
||||||
|
{ n: 4, title: "Converse e produza mais", description: "Mande mensagens como faria com um assistente humano." },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function HowItWorksSection() {
|
||||||
|
return (
|
||||||
|
<section id="como-funciona" className="py-20 sm:py-28 bg-muted/40">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="max-w-2xl mx-auto text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold tracking-tight">Como funciona</h2>
|
||||||
|
<p className="mt-4 text-lg text-muted-foreground">Do cadastro à primeira conversa em menos de 5 minutos.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ol className="mt-16 grid grid-cols-1 lg:grid-cols-4 gap-10 lg:gap-4 relative">
|
||||||
|
{steps.map((s, i) => (
|
||||||
|
<li key={s.n} className="relative flex flex-col items-center text-center lg:px-4">
|
||||||
|
{i < steps.length - 1 && (
|
||||||
|
<div
|
||||||
|
className="hidden lg:block absolute top-8 left-[calc(50%+2.5rem)] right-[-50%] h-px bg-primary/30"
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="h-16 w-16 rounded-full bg-primary text-primary-foreground grid place-items-center font-bold text-xl shadow-glow z-10">
|
||||||
|
{s.n}
|
||||||
|
</div>
|
||||||
|
<h3 className="mt-5 text-lg font-semibold">{s.title}</h3>
|
||||||
|
<p className="mt-2 text-sm text-muted-foreground max-w-xs">{s.description}</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
67
src/components/mika/LandingFooter.tsx
Normal file
67
src/components/mika/LandingFooter.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { Logo } from "./Logo";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
|
const cols = [
|
||||||
|
{
|
||||||
|
title: "Produto",
|
||||||
|
items: [
|
||||||
|
{ label: "Recursos", href: "#recursos" },
|
||||||
|
{ label: "Planos", href: "#planos" },
|
||||||
|
{ label: "Skills", href: "#" },
|
||||||
|
{ label: "Mudanças", href: "#" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Empresa",
|
||||||
|
items: [
|
||||||
|
{ label: "Sobre", href: "#" },
|
||||||
|
{ label: "Blog", href: "#" },
|
||||||
|
{ label: "Contato", href: "#" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Legal",
|
||||||
|
items: [
|
||||||
|
{ label: "Termos de Uso", href: "#" },
|
||||||
|
{ label: "Política de Privacidade", href: "#" },
|
||||||
|
{ label: "LGPD", href: "#" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function LandingFooter() {
|
||||||
|
return (
|
||||||
|
<footer className="bg-background border-t border-border">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-14">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-10">
|
||||||
|
<div>
|
||||||
|
<Logo />
|
||||||
|
<p className="mt-4 text-sm text-muted-foreground max-w-xs">
|
||||||
|
Seu assistente de IA pessoal no Telegram, gerenciado e em português.
|
||||||
|
</p>
|
||||||
|
<Badge className="mt-4 bg-secondary/15 text-secondary hover:bg-secondary/15 border-0 font-semibold">
|
||||||
|
Feito no Brasil 🇧🇷
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
{cols.map((c) => (
|
||||||
|
<div key={c.title}>
|
||||||
|
<h4 className="text-sm font-semibold text-foreground">{c.title}</h4>
|
||||||
|
<ul className="mt-4 space-y-3">
|
||||||
|
{c.items.map((it) => (
|
||||||
|
<li key={it.label}>
|
||||||
|
<a href={it.href} className="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
||||||
|
{it.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-12 pt-6 border-t border-border text-center text-xs text-muted-foreground">
|
||||||
|
© 2026 DOMCO — Todos os direitos reservados. Mika é um produto da DOMCO (AI Solutions On Demand).
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
102
src/components/mika/LandingHeader.tsx
Normal file
102
src/components/mika/LandingHeader.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import { Menu } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Sheet, SheetContent, SheetTrigger, SheetTitle, SheetHeader } from "@/components/ui/sheet";
|
||||||
|
import { Logo } from "./Logo";
|
||||||
|
import { ThemeToggle } from "./ThemeToggle";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const NAV = [
|
||||||
|
{ href: "#recursos", label: "Recursos" },
|
||||||
|
{ href: "#planos", label: "Planos" },
|
||||||
|
{ href: "#como-funciona", label: "Como Funciona" },
|
||||||
|
{ href: "#faq", label: "FAQ" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function LandingHeader() {
|
||||||
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onScroll = () => setScrolled(window.scrollY > 8);
|
||||||
|
onScroll();
|
||||||
|
window.addEventListener("scroll", onScroll, { passive: true });
|
||||||
|
return () => window.removeEventListener("scroll", onScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
className={cn(
|
||||||
|
"fixed top-0 left-0 right-0 z-50 transition-all duration-200",
|
||||||
|
scrolled
|
||||||
|
? "bg-background/80 backdrop-blur-md border-b border-border"
|
||||||
|
: "bg-transparent border-b border-transparent",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between gap-4">
|
||||||
|
<Logo />
|
||||||
|
|
||||||
|
<nav className="hidden md:flex items-center gap-8" aria-label="Navegação principal">
|
||||||
|
{NAV.map((item) => (
|
||||||
|
<a
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className="hidden md:flex items-center gap-2">
|
||||||
|
<ThemeToggle />
|
||||||
|
<Button asChild variant="ghost" className="rounded-lg">
|
||||||
|
<Link to="/login">Entrar</Link>
|
||||||
|
</Button>
|
||||||
|
<Button asChild className="rounded-lg bg-primary hover:bg-primary-dark text-primary-foreground transition-all duration-150 active:scale-[0.98]">
|
||||||
|
<Link to="/signup">Começar agora</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex md:hidden items-center gap-1">
|
||||||
|
<ThemeToggle />
|
||||||
|
<Sheet open={open} onOpenChange={setOpen}>
|
||||||
|
<SheetTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" aria-label="Abrir menu">
|
||||||
|
<Menu className="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
<SheetContent side="right" className="w-[85vw] max-w-sm">
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle><Logo /></SheetTitle>
|
||||||
|
</SheetHeader>
|
||||||
|
<nav className="mt-8 flex flex-col gap-1" aria-label="Navegação móvel">
|
||||||
|
{NAV.map((item) => (
|
||||||
|
<a
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
className="text-base font-medium text-foreground py-3 px-2 rounded-lg hover:bg-muted transition-colors"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
<div className="mt-6 flex flex-col gap-3">
|
||||||
|
<Button asChild variant="outline" className="rounded-lg w-full">
|
||||||
|
<Link to="/login" onClick={() => setOpen(false)}>Entrar</Link>
|
||||||
|
</Button>
|
||||||
|
<Button asChild className="rounded-lg w-full bg-primary hover:bg-primary-dark text-primary-foreground">
|
||||||
|
<Link to="/signup" onClick={() => setOpen(false)}>Começar agora</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
207
src/components/mika/PlansSection.tsx
Normal file
207
src/components/mika/PlansSection.tsx
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import { Check } from "lucide-react";
|
||||||
|
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 { 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;
|
||||||
|
|
||||||
|
const fmtBRL = (v: number) =>
|
||||||
|
v.toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||||
|
|
||||||
|
export function PlansSection() {
|
||||||
|
const [yearly, setYearly] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="planos" className="py-20 sm:py-28 bg-background">
|
||||||
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="max-w-2xl mx-auto text-center">
|
||||||
|
<h2 className="text-3xl sm:text-4xl font-bold tracking-tight">Planos para cada estágio</h2>
|
||||||
|
<p className="mt-4 text-lg text-muted-foreground">
|
||||||
|
Sem fidelidade. Cancele quando quiser. Reembolso integral nos primeiros 7 dias.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-8 inline-flex items-center gap-3 bg-muted rounded-full px-4 py-2">
|
||||||
|
<span className={cn("text-sm font-medium", !yearly && "text-foreground", yearly && "text-muted-foreground")}>
|
||||||
|
Mensal
|
||||||
|
</span>
|
||||||
|
<Switch checked={yearly} onCheckedChange={setYearly} aria-label="Alternar entre mensal e anual" />
|
||||||
|
<span className={cn("text-sm font-medium flex items-center gap-2", yearly && "text-foreground", !yearly && "text-muted-foreground")}>
|
||||||
|
Anual
|
||||||
|
<Badge className="bg-secondary/15 text-secondary hover:bg-secondary/15 border-0 font-semibold">20% off</Badge>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-14 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 items-stretch">
|
||||||
|
{STATIC_PLANS.map((plan) => {
|
||||||
|
const price = yearly ? plan.yearly : plan.monthly;
|
||||||
|
const monthlyEquivalent = yearly && plan.yearly ? plan.yearly / 12 : null;
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
key={plan.slug}
|
||||||
|
className={cn(
|
||||||
|
"relative flex flex-col rounded-xl border bg-card p-6 shadow-soft transition-all",
|
||||||
|
plan.highlighted
|
||||||
|
? "border-2 border-primary shadow-glow lg:scale-[1.03]"
|
||||||
|
: "border-border hover:border-primary/30 hover:shadow-lg",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{plan.highlighted && (
|
||||||
|
<Badge className="absolute -top-3 left-1/2 -translate-x-1/2 bg-accent text-accent-foreground hover:bg-accent border-0 font-semibold">
|
||||||
|
Mais popular
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold">{plan.name}</h3>
|
||||||
|
<p className="mt-1 text-sm text-muted-foreground">{plan.description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 min-h-[88px]">
|
||||||
|
{plan.is_enterprise ? (
|
||||||
|
<p className="text-3xl font-bold">Sob consulta</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="flex items-baseline gap-1">
|
||||||
|
<span className="text-sm text-muted-foreground">R$</span>
|
||||||
|
<span className="text-4xl font-bold tracking-tight">
|
||||||
|
{fmtBRL(yearly && monthlyEquivalent ? monthlyEquivalent : (price as number))}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-muted-foreground">/mês</span>
|
||||||
|
</div>
|
||||||
|
{yearly && (
|
||||||
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
|
R$ {fmtBRL(plan.yearly!)} cobrados anualmente
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="mt-6 space-y-3 flex-1">
|
||||||
|
{plan.features.map((f) => (
|
||||||
|
<li key={f} className="flex items-start gap-2 text-sm">
|
||||||
|
<Check className="h-4 w-4 text-secondary mt-0.5 flex-shrink-0" aria-hidden />
|
||||||
|
<span>{f}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
{plan.is_enterprise ? (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant="outline" className="w-full rounded-lg">Falar com vendas</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-w-lg">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Plano Enterprise</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Conte um pouco sobre sua empresa. Nosso time entra em contato em até 1 dia útil.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<EnterpriseLeadForm />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
className={cn(
|
||||||
|
"w-full rounded-lg transition-all duration-150 active:scale-[0.98]",
|
||||||
|
plan.highlighted
|
||||||
|
? "bg-primary hover:bg-primary-dark text-primary-foreground"
|
||||||
|
: "bg-foreground hover:bg-foreground/90 text-background",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Link to="/signup" search={{ plan: plan.slug, cycle: yearly ? "yearly" : "monthly" } as never}>
|
||||||
|
Assinar agora
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<p className="mt-3 text-xs text-muted-foreground text-center">
|
||||||
|
Sujeito à política de uso justo. Veja termos.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -57,12 +57,3 @@ const rootRouteChildren: RootRouteChildren = {
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
._addFileTypes<FileRouteTypes>()
|
._addFileTypes<FileRouteTypes>()
|
||||||
|
|
||||||
import type { getRouter } from './router.tsx'
|
|
||||||
import type { createStart } from '@tanstack/react-start'
|
|
||||||
declare module '@tanstack/react-start' {
|
|
||||||
interface Register {
|
|
||||||
ssr: true
|
|
||||||
router: Awaited<ReturnType<typeof getRouter>>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue