mirror of
https://github.com/domfelipe/mika-agent-assist.git
synced 2026-08-07 11:36:42 +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
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._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