From 93ee695c0e052784dda2535449781615a5303ae9 Mon Sep 17 00:00:00 2001
From: "gpt-engineer-app[bot]"
<159125892+gpt-engineer-app[bot]@users.noreply.github.com>
Date: Fri, 17 Apr 2026 20:03:05 +0000
Subject: [PATCH] Changes
Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com>
---
.../telegram/DisconnectTelegramDialog.tsx | 76 +++++++
.../telegram/TelegramConnectionBanner.tsx | 73 ++++++
.../telegram/TelegramOnboardingWizard.tsx | 212 ++++++++++++++++++
.../mika/telegram/TelegramStatusCard.tsx | 105 +++++++++
4 files changed, 466 insertions(+)
create mode 100644 src/components/mika/telegram/DisconnectTelegramDialog.tsx
create mode 100644 src/components/mika/telegram/TelegramConnectionBanner.tsx
create mode 100644 src/components/mika/telegram/TelegramOnboardingWizard.tsx
create mode 100644 src/components/mika/telegram/TelegramStatusCard.tsx
diff --git a/src/components/mika/telegram/DisconnectTelegramDialog.tsx b/src/components/mika/telegram/DisconnectTelegramDialog.tsx
new file mode 100644
index 0000000..3db7f78
--- /dev/null
+++ b/src/components/mika/telegram/DisconnectTelegramDialog.tsx
@@ -0,0 +1,76 @@
+"use client";
+
+import { useState } from "react";
+import { Loader2 } from "lucide-react";
+import { useQueryClient } from "@tanstack/react-query";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "@/components/ui/alert-dialog";
+import { useAuth } from "@/hooks/use-auth";
+import { invokeFunction } from "@/lib/invoke-function";
+import { toast } from "sonner";
+
+interface Props {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+export function DisconnectTelegramDialog({ open, onOpenChange }: Props) {
+ const { user } = useAuth();
+ const queryClient = useQueryClient();
+ const [loading, setLoading] = useState(false);
+
+ async function handleDisconnect() {
+ setLoading(true);
+ const { error } = await invokeFunction("disconnect-telegram");
+ setLoading(false);
+ if (error) {
+ toast.error(error.message ?? "Falha ao desconectar.");
+ return;
+ }
+ if (user) {
+ await queryClient.invalidateQueries({ queryKey: ["agent-instance", user.id] });
+ }
+ toast.success("Telegram desconectado.");
+ onOpenChange(false);
+ }
+
+ return (
+
+
+
+ Desconectar Telegram?
+
+ Tem certeza? Você precisará criar um novo bot no BotFather para reconectar.
+
+
+
+ Cancelar
+ {
+ e.preventDefault();
+ handleDisconnect();
+ }}
+ disabled={loading}
+ className="bg-destructive hover:bg-destructive/90 text-destructive-foreground"
+ >
+ {loading ? (
+ <>
+ Desconectando...
+ >
+ ) : (
+ "Desconectar"
+ )}
+
+
+
+
+ );
+}
diff --git a/src/components/mika/telegram/TelegramConnectionBanner.tsx b/src/components/mika/telegram/TelegramConnectionBanner.tsx
new file mode 100644
index 0000000..573e0ef
--- /dev/null
+++ b/src/components/mika/telegram/TelegramConnectionBanner.tsx
@@ -0,0 +1,73 @@
+"use client";
+
+import { useState } from "react";
+import { Link } from "@tanstack/react-router";
+import { AlertCircle, AlertTriangle } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { useAgentInstance } from "@/hooks/use-agent-instance";
+import { TelegramOnboardingWizard } from "./TelegramOnboardingWizard";
+
+/**
+ * Banner sticky no topo do painel.
+ * - Suspended/error → vermelho com link p/ faturamento
+ * - token revogado → vermelho com link p/ Meu Agente
+ * - bot ainda não conectado → amber com CTA para abrir o wizard
+ */
+export function TelegramConnectionBanner() {
+ const { data: agent } = useAgentInstance();
+ const [open, setOpen] = useState(false);
+
+ if (!agent) return null;
+
+ if (agent.status === "suspended" || agent.status === "error") {
+ return (
+
+
+
+
+ Seu agente está suspenso. Regularize sua assinatura em Faturamento.
+
+
+
+
+ );
+ }
+
+ if (agent.telegram_token_invalid) {
+ return (
+
+
+
+
+ Seu token Telegram foi revogado. Desconecte e reconecte o bot.
+
+
+
+
+ );
+ }
+
+ if (agent.telegram_bot_username) return null;
+
+ return (
+ <>
+
+
+
+
+ Conecte seu Telegram para começar a conversar com o Mika.
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/mika/telegram/TelegramOnboardingWizard.tsx b/src/components/mika/telegram/TelegramOnboardingWizard.tsx
new file mode 100644
index 0000000..9a1e2c8
--- /dev/null
+++ b/src/components/mika/telegram/TelegramOnboardingWizard.tsx
@@ -0,0 +1,212 @@
+"use client";
+
+import { useEffect, useMemo, useState } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+import { X } from "lucide-react";
+import { useQueryClient } from "@tanstack/react-query";
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { useProfile } from "@/hooks/use-profile";
+import { useAgentInstance } from "@/hooks/use-agent-instance";
+import { supabase } from "@/integrations/supabase/client";
+import { useAuth } from "@/hooks/use-auth";
+import { suggestBotName, suggestBotUsername } from "@/lib/telegram-username";
+import { cn } from "@/lib/utils";
+import { toast } from "sonner";
+
+import { StepWelcome } from "./StepWelcome";
+import { StepCreateBot } from "./StepCreateBot";
+import { StepNaming } from "./StepNaming";
+import { StepToken, type ValidatedBot } from "./StepToken";
+import { StepConfiguring } from "./StepConfiguring";
+import { StepWaiting } from "./StepWaiting";
+
+const STORAGE_KEY = "mika-onboarding-last-step";
+const TOTAL_STEPS = 6;
+
+interface Props {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ /** Step inicial (1-6). Se omitido, lê do localStorage ou começa em 1. */
+ initialStep?: number;
+}
+
+export function TelegramOnboardingWizard({ open, onOpenChange, initialStep }: Props) {
+ const { data: profile } = useProfile();
+ const { data: agent } = useAgentInstance();
+ const { user } = useAuth();
+ const queryClient = useQueryClient();
+
+ const [step, setStep] = useState(1);
+ const [validated, setValidated] = useState(null);
+
+ const suggestedName = useMemo(() => suggestBotName(profile?.full_name), [profile?.full_name]);
+ const suggestedUsername = useMemo(
+ () => suggestBotUsername(profile?.full_name),
+ [profile?.full_name],
+ );
+
+ // Inicializa step ao abrir
+ useEffect(() => {
+ if (!open) return;
+ if (initialStep) {
+ setStep(Math.min(Math.max(initialStep, 1), TOTAL_STEPS));
+ return;
+ }
+ if (typeof window !== "undefined") {
+ const stored = window.localStorage.getItem(STORAGE_KEY);
+ const parsed = stored ? Number(stored) : NaN;
+ if (Number.isFinite(parsed) && parsed >= 1 && parsed <= TOTAL_STEPS) {
+ setStep(parsed);
+ return;
+ }
+ }
+ setStep(1);
+ }, [open, initialStep]);
+
+ // Persiste step ao mudar
+ useEffect(() => {
+ if (!open) return;
+ if (typeof window !== "undefined") {
+ window.localStorage.setItem(STORAGE_KEY, String(step));
+ }
+ }, [step, open]);
+
+ function handleClose() {
+ onOpenChange(false);
+ }
+
+ async function handleFinish() {
+ if (!agent || !user) return handleClose();
+ const { error } = await supabase
+ .from("agent_instances")
+ .update({ telegram_onboarding_completed: true })
+ .eq("id", agent.id);
+ if (error) {
+ toast.error("Não foi possível salvar o status do onboarding.");
+ return;
+ }
+ if (typeof window !== "undefined") {
+ window.localStorage.removeItem(STORAGE_KEY);
+ }
+ await queryClient.invalidateQueries({ queryKey: ["agent-instance", user.id] });
+ toast.success("Onboarding concluído!");
+ onOpenChange(false);
+ }
+
+ // Após validar token, recarrega agent_instance (já tem bot_username persistido)
+ function handleValidated(bot: ValidatedBot) {
+ setValidated(bot);
+ if (user) {
+ queryClient.invalidateQueries({ queryKey: ["agent-instance", user.id] });
+ }
+ }
+
+ function handleConfigured() {
+ if (user) {
+ queryClient.invalidateQueries({ queryKey: ["agent-instance", user.id] });
+ }
+ setStep(6);
+ }
+
+ const botUsername = validated?.bot_username ?? agent?.telegram_bot_username ?? "";
+ const connectedAt = agent?.telegram_connected_at ?? null;
+
+ return (
+
+
+
+
+
+ Conectar Telegram ao Mika
+
+
+ Wizard guiado de 6 passos para conectar seu agente Mika ao Telegram.
+
+
+ {/* Header com progress + close */}
+
+
+
+ Passo {step} de {TOTAL_STEPS}
+
+
+
+
+
+
+
+
+ {/* Conteúdo dos steps */}
+
+
+
+ {step === 1 && setStep(2)} />}
+ {step === 2 && setStep(3)} />}
+ {step === 3 && (
+ setStep(4)}
+ />
+ )}
+ {step === 4 && (
+ setStep(5)}
+ />
+ )}
+ {step === 5 && (
+ setStep(6)}
+ />
+ )}
+ {step === 6 && agent && botUsername && (
+
+ )}
+ {step === 6 && (!agent || !botUsername) && (
+
+ Conecte o bot primeiro para receber a primeira mensagem.
+
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/mika/telegram/TelegramStatusCard.tsx b/src/components/mika/telegram/TelegramStatusCard.tsx
new file mode 100644
index 0000000..74e05d6
--- /dev/null
+++ b/src/components/mika/telegram/TelegramStatusCard.tsx
@@ -0,0 +1,105 @@
+"use client";
+
+import { useState } from "react";
+import { ExternalLink, AlertCircle } from "lucide-react";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { useAgentInstance } from "@/hooks/use-agent-instance";
+import { TelegramIcon } from "./TelegramIcon";
+import { TelegramOnboardingWizard } from "./TelegramOnboardingWizard";
+import { DisconnectTelegramDialog } from "./DisconnectTelegramDialog";
+
+function formatPtBR(date: string | null): string | null {
+ if (!date) return null;
+ try {
+ return new Intl.DateTimeFormat("pt-BR", {
+ day: "2-digit",
+ month: "2-digit",
+ year: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ }).format(new Date(date));
+ } catch {
+ return null;
+ }
+}
+
+export function TelegramStatusCard() {
+ const { data: agent } = useAgentInstance();
+ const [wizardOpen, setWizardOpen] = useState(false);
+ const [disconnectOpen, setDisconnectOpen] = useState(false);
+
+ const connected = !!agent?.telegram_bot_username;
+
+ return (
+
+
+
+
Telegram
+ {connected && (
+
+ Conectado
+
+ )}
+
+
+ {!connected ? (
+
+
+
+
+
Telegram não conectado
+
+ Conecte seu bot para começar a conversar com o Mika.
+
+
+
+
+
+ ) : (
+
+
+
@{agent!.telegram_bot_username}
+ {agent!.telegram_connected_at && (
+
+ Conectado em {formatPtBR(agent!.telegram_connected_at)}
+
+ )}
+
+
+ {agent!.telegram_token_invalid && (
+
+
+
+ Token revogado — desconecte e reconecte.
+
+
+ )}
+
+
+
+
+
+
+ )}
+
+
+
+
+ );
+}