mirror of
https://github.com/domfelipe/mika-agent-assist.git
synced 2026-08-07 09:36:43 +00:00
Changes
Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com>
This commit is contained in:
parent
ce0108cec3
commit
93ee695c0e
4 changed files with 466 additions and 0 deletions
76
src/components/mika/telegram/DisconnectTelegramDialog.tsx
Normal file
76
src/components/mika/telegram/DisconnectTelegramDialog.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Desconectar Telegram?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
Tem certeza? Você precisará criar um novo bot no BotFather para reconectar.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel disabled={loading}>Cancelar</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleDisconnect();
|
||||||
|
}}
|
||||||
|
disabled={loading}
|
||||||
|
className="bg-destructive hover:bg-destructive/90 text-destructive-foreground"
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Desconectando...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Desconectar"
|
||||||
|
)}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
73
src/components/mika/telegram/TelegramConnectionBanner.tsx
Normal file
73
src/components/mika/telegram/TelegramConnectionBanner.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<div className="sticky top-16 z-30 -mx-4 sm:mx-0 sm:rounded-lg border-y sm:border border-destructive bg-destructive/10 px-4 py-3 text-sm">
|
||||||
|
<div className="flex items-start sm:items-center gap-3 flex-wrap">
|
||||||
|
<AlertCircle className="h-4 w-4 text-destructive shrink-0" />
|
||||||
|
<span className="flex-1 text-destructive-foreground">
|
||||||
|
Seu agente está suspenso. Regularize sua assinatura em Faturamento.
|
||||||
|
</span>
|
||||||
|
<Button asChild size="sm" variant="destructive">
|
||||||
|
<Link to="/painel/faturamento">Ir para Faturamento</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (agent.telegram_token_invalid) {
|
||||||
|
return (
|
||||||
|
<div className="sticky top-16 z-30 -mx-4 sm:mx-0 sm:rounded-lg border-y sm:border border-destructive bg-destructive/10 px-4 py-3 text-sm">
|
||||||
|
<div className="flex items-start sm:items-center gap-3 flex-wrap">
|
||||||
|
<AlertCircle className="h-4 w-4 text-destructive shrink-0" />
|
||||||
|
<span className="flex-1 text-destructive-foreground">
|
||||||
|
Seu token Telegram foi revogado. Desconecte e reconecte o bot.
|
||||||
|
</span>
|
||||||
|
<Button asChild size="sm" variant="destructive">
|
||||||
|
<Link to="/painel/agente">Ir para Meu Agente</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (agent.telegram_bot_username) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="sticky top-16 z-30 -mx-4 sm:mx-0 sm:rounded-lg border-y sm:border border-warning bg-warning/10 px-4 py-3 text-sm">
|
||||||
|
<div className="flex items-start sm:items-center gap-3 flex-wrap">
|
||||||
|
<AlertTriangle className="h-4 w-4 text-warning shrink-0" />
|
||||||
|
<span className="flex-1">
|
||||||
|
Conecte seu Telegram para começar a conversar com o Mika.
|
||||||
|
</span>
|
||||||
|
<Button size="sm" onClick={() => setOpen(true)}>
|
||||||
|
Conectar agora
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TelegramOnboardingWizard open={open} onOpenChange={setOpen} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
212
src/components/mika/telegram/TelegramOnboardingWizard.tsx
Normal file
212
src/components/mika/telegram/TelegramOnboardingWizard.tsx
Normal file
|
|
@ -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<ValidatedBot | null>(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 (
|
||||||
|
<DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogPrimitive.Portal>
|
||||||
|
<DialogPrimitive.Overlay className="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
className={cn(
|
||||||
|
"fixed z-50 bg-background shadow-2xl",
|
||||||
|
// Mobile: full-screen
|
||||||
|
"inset-0 sm:inset-auto",
|
||||||
|
// Desktop: dialog grande centralizado
|
||||||
|
"sm:left-[50%] sm:top-[50%] sm:translate-x-[-50%] sm:translate-y-[-50%]",
|
||||||
|
"sm:w-full sm:max-w-2xl sm:rounded-2xl sm:max-h-[90vh] sm:overflow-y-auto",
|
||||||
|
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<DialogPrimitive.Title className="sr-only">
|
||||||
|
Conectar Telegram ao Mika
|
||||||
|
</DialogPrimitive.Title>
|
||||||
|
<DialogPrimitive.Description className="sr-only">
|
||||||
|
Wizard guiado de 6 passos para conectar seu agente Mika ao Telegram.
|
||||||
|
</DialogPrimitive.Description>
|
||||||
|
|
||||||
|
{/* Header com progress + close */}
|
||||||
|
<div className="sticky top-0 z-10 bg-background/95 backdrop-blur-sm border-b border-border">
|
||||||
|
<div className="flex items-center justify-between px-4 sm:px-6 py-3">
|
||||||
|
<span className="text-xs font-medium text-muted-foreground">
|
||||||
|
Passo {step} de {TOTAL_STEPS}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={handleClose}
|
||||||
|
aria-label="Fechar"
|
||||||
|
className="h-8 w-8 rounded-md hover:bg-muted flex items-center justify-center transition-colors"
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="h-1 w-full bg-muted overflow-hidden">
|
||||||
|
<motion.div
|
||||||
|
className="h-full bg-primary"
|
||||||
|
initial={false}
|
||||||
|
animate={{ width: `${(step / TOTAL_STEPS) * 100}%` }}
|
||||||
|
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Conteúdo dos steps */}
|
||||||
|
<div className="relative overflow-hidden">
|
||||||
|
<AnimatePresence mode="wait" initial={false}>
|
||||||
|
<motion.div
|
||||||
|
key={step}
|
||||||
|
initial={{ opacity: 0, x: 40 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0, x: -40 }}
|
||||||
|
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
{step === 1 && <StepWelcome onNext={() => setStep(2)} />}
|
||||||
|
{step === 2 && <StepCreateBot onNext={() => setStep(3)} />}
|
||||||
|
{step === 3 && (
|
||||||
|
<StepNaming
|
||||||
|
suggestedName={suggestedName}
|
||||||
|
suggestedUsername={suggestedUsername}
|
||||||
|
onNext={() => setStep(4)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{step === 4 && (
|
||||||
|
<StepToken
|
||||||
|
validated={validated}
|
||||||
|
onValidated={handleValidated}
|
||||||
|
onNext={() => setStep(5)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{step === 5 && (
|
||||||
|
<StepConfiguring
|
||||||
|
onConfigured={handleConfigured}
|
||||||
|
onSkip={() => setStep(6)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{step === 6 && agent && botUsername && (
|
||||||
|
<StepWaiting
|
||||||
|
agentInstanceId={agent.id}
|
||||||
|
botUsername={botUsername}
|
||||||
|
connectedAt={connectedAt}
|
||||||
|
onFinish={handleFinish}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{step === 6 && (!agent || !botUsername) && (
|
||||||
|
<div className="px-6 py-12 text-center text-sm text-muted-foreground">
|
||||||
|
Conecte o bot primeiro para receber a primeira mensagem.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPrimitive.Portal>
|
||||||
|
</DialogPrimitive.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
105
src/components/mika/telegram/TelegramStatusCard.tsx
Normal file
105
src/components/mika/telegram/TelegramStatusCard.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<div className="rounded-xl border border-border bg-card p-6 shadow-soft">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<TelegramIcon className="h-5 w-5 text-primary" />
|
||||||
|
<h3 className="font-semibold">Telegram</h3>
|
||||||
|
{connected && (
|
||||||
|
<Badge variant="success" className="ml-auto">
|
||||||
|
Conectado
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!connected ? (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<TelegramIcon className="h-8 w-8 text-muted-foreground" />
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">Telegram não conectado</p>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Conecte seu bot para começar a conversar com o Mika.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={() => setWizardOpen(true)}
|
||||||
|
disabled={agent?.status === "suspended" || agent?.status === "error"}
|
||||||
|
>
|
||||||
|
Conectar agora
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="font-mono text-sm">@{agent!.telegram_bot_username}</p>
|
||||||
|
{agent!.telegram_connected_at && (
|
||||||
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
|
Conectado em {formatPtBR(agent!.telegram_connected_at)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{agent!.telegram_token_invalid && (
|
||||||
|
<div className="flex items-start gap-2 rounded-md border border-destructive bg-destructive/10 p-3 text-sm">
|
||||||
|
<AlertCircle className="h-4 w-4 text-destructive mt-0.5 shrink-0" />
|
||||||
|
<p className="text-destructive-foreground">
|
||||||
|
Token revogado — desconecte e reconecte.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Button asChild variant="outline" size="sm">
|
||||||
|
<a
|
||||||
|
href={`https://t.me/${agent!.telegram_bot_username}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Abrir bot <ExternalLink className="ml-2 h-3 w-3" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => setDisconnectOpen(true)}>
|
||||||
|
Desconectar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TelegramOnboardingWizard open={wizardOpen} onOpenChange={setWizardOpen} />
|
||||||
|
<DisconnectTelegramDialog open={disconnectOpen} onOpenChange={setDisconnectOpen} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue