diff --git a/src/components/mika/telegram/BotFatherWizard.tsx b/src/components/mika/telegram/BotFatherWizard.tsx new file mode 100644 index 0000000..294356f --- /dev/null +++ b/src/components/mika/telegram/BotFatherWizard.tsx @@ -0,0 +1,447 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { + ArrowRight, + Check, + Copy, + ExternalLink, + Loader2, + AlertCircle, +} from "lucide-react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { invokeFunction } from "@/lib/invoke-function"; +import { cn } from "@/lib/utils"; +import { + sanitizeForUsername, + suggestBotUsername, +} from "@/lib/telegram-username"; + +const TOKEN_REGEX = /^\d+:[A-Za-z0-9_-]{35}$/; + +interface Props { + agentName: string; + fullName: string; + onActivated: (bot: { bot_username: string; bot_name: string; bot_id: number }) => void; + onSkip: () => void; +} + +export function BotFatherWizard({ agentName, fullName, onActivated, onSkip }: Props) { + const [step1Done, setStep1Done] = useState(false); + const [step2Done, setStep2Done] = useState(false); + const [step3Done, setStep3Done] = useState(false); + const [token, setToken] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [errorMsg, setErrorMsg] = useState(null); + + const suggestedUsername = useMemo(() => { + // base do agent_name; cai pro firstName se não der + const base = sanitizeForUsername(agentName).replace(/^mikade/, "mika"); + if (base.length >= 5) { + const trimmed = base.slice(0, 28); + return trimmed.endsWith("bot") ? trimmed : `${trimmed}bot`; + } + return suggestBotUsername(fullName); + }, [agentName, fullName]); + + const tokenValid = TOKEN_REGEX.test(token.trim()); + + function handleOpenBotFather() { + window.open("https://t.me/BotFather?start=newbot", "_blank", "noopener,noreferrer"); + setStep1Done(true); + } + + async function copyToClipboard(value: string, onDone: () => void) { + try { + await navigator.clipboard.writeText(value); + toast.success("Copiado!"); + onDone(); + } catch { + toast.error("Não foi possível copiar."); + } + } + + async function handleActivate() { + if (!tokenValid) return; + setSubmitting(true); + setErrorMsg(null); + const { data, error } = await invokeFunction<{ + bot_username: string; + bot_name: string; + bot_id: number; + }>("validate-telegram-bot", { token: token.trim() }); + + if (error || !data?.bot_username) { + setSubmitting(false); + setErrorMsg( + error?.message ?? "Token inválido. Verifique e tente novamente.", + ); + return; + } + onActivated(data); + } + + return ( +
+ {/* COLUNA ESQUERDA — passos */} +
+

+ Crie seu bot em 2 minutos +

+

+ Siga os passos abaixo. Cada um leva alguns segundos. +

+ +
    + + + + + + setStep2Done(true)} + /> + + + + setStep3Done(true)} + /> + + + +
    + { + setToken(e.target.value); + if (errorMsg) setErrorMsg(null); + }} + placeholder="1234567890:ABCdefGHIjklMNOpqrsTUVwxyz" + className={cn( + "h-11 font-mono text-sm bg-white/5 border-white/20 text-white placeholder:text-white/30", + tokenValid && + "border-emerald-400/60 focus-visible:ring-emerald-400/40", + )} + disabled={submitting} + /> + {tokenValid && ( +

    + Token válido! +

    + )} + {errorMsg && ( +

    + + {errorMsg} +

    + )} +
    +
    +
+ +
+ + +
+
+ + {/* COLUNA DIREITA — preview animado (apenas desktop) */} + +
+ ); +} + +function Step({ + number, + emoji, + title, + description, + done, + highlight, + children, +}: { + number: number; + emoji: string; + title: string; + description: string; + done: boolean; + highlight: boolean; + children?: React.ReactNode; +}) { + return ( +
  • +
    +
    + {done ? : number} +
    +
    +

    + {emoji} + {title} +

    +

    {description}

    + {children} +
    +
    +
  • + ); +} + +function CopyChip({ + value, + onCopied, +}: { + value: string; + onCopied: () => void; +}) { + const [copied, setCopied] = useState(false); + async function handleCopy() { + try { + await navigator.clipboard.writeText(value); + setCopied(true); + toast.success("Copiado!"); + onCopied(); + setTimeout(() => setCopied(false), 1500); + } catch { + toast.error("Não foi possível copiar."); + } + } + return ( + + ); +} + +function ChatPreview({ + agentName, + firstName, +}: { + agentName: string; + firstName: string; +}) { + const safeName = (agentName || "Mika").trim(); + const safeFirst = (firstName || "você").trim(); + const reply = `Olá, ${safeFirst}! 👋 Sou a Mika, sua assistente pessoal. Como posso ajudar você hoje?`; + + // Cycle: 0 = só "Oi!", 1 = typing, 2 = resposta digitando, 3 = completa, depois reseta + const [cycle, setCycle] = useState(0); + const [typed, setTyped] = useState(""); + + useEffect(() => { + if (cycle === 0) { + const t = setTimeout(() => setCycle(1), 900); + return () => clearTimeout(t); + } + if (cycle === 1) { + const t = setTimeout(() => setCycle(2), 1100); + return () => clearTimeout(t); + } + if (cycle === 2) { + // typewriter + let i = 0; + setTyped(""); + const interval = setInterval(() => { + i += 1; + setTyped(reply.slice(0, i)); + if (i >= reply.length) { + clearInterval(interval); + setTimeout(() => setCycle(3), 1500); + } + }, 25); + return () => clearInterval(interval); + } + if (cycle === 3) { + const t = setTimeout(() => { + setTyped(""); + setCycle(0); + }, 2500); + return () => clearTimeout(t); + } + }, [cycle, reply]); + + return ( +
    +

    + Prévia do seu bot +

    + +
    +
    + 🤖 +
    +
    +

    {safeName}

    +

    online

    +
    +
    + +
    + {/* Mensagem do usuário */} +
    +
    + Oi! +
    +
    + + {/* Typing indicator */} + + {cycle === 1 && ( + +
    + {[0, 1, 2].map((i) => ( + + ))} +
    +
    + )} +
    + + {/* Resposta do bot */} + {(cycle === 2 || cycle === 3) && ( + +
    + {cycle === 2 ? typed : reply} + {cycle === 2 && ( + + )} +
    +
    + )} +
    +
    + ); +}