fix mini app bot father

This commit is contained in:
lendry
2026-06-26 11:18:07 +03:00
parent 7ed7cbdd16
commit d3ea470d02
12 changed files with 313 additions and 39 deletions

View File

@@ -1,7 +1,7 @@
'use client';
import { useMemo, useState } from 'react';
import { Check, Copy, Loader2, Bot } from 'lucide-react';
import { Check, Copy, Loader2, Bot, ArrowLeft } from 'lucide-react';
import { useToast } from '@/components/id/toast-provider';
import { usePublicSettings } from '@/components/id/public-settings-provider';
import { Button } from '@/components/ui/button';
@@ -12,7 +12,12 @@ import { cn } from '@/lib/utils';
type Step = 'name' | 'username' | 'done';
export function BotCreateMiniAppContent() {
interface BotCreateMiniAppContentProps {
onBack?: () => void;
onCreated?: (botId: string) => void;
}
export function BotCreateMiniAppContent({ onBack, onCreated }: BotCreateMiniAppContentProps = {}) {
const { projectName } = usePublicSettings();
const { effectiveToken, canUse, authReady, waitingForBridge, isLoading } = useMiniAppAuth();
const { showToast } = useToast();
@@ -84,6 +89,11 @@ export function BotCreateMiniAppContent() {
<div className="min-h-screen bg-[#17212b] p-4 text-white">
<div className="mx-auto max-w-md space-y-5 rounded-[24px] bg-[#242f3d] p-5 shadow-xl">
<div className="flex items-center gap-3">
{onBack ? (
<Button type="button" variant="ghost" size="icon" className="h-9 w-9 shrink-0 text-[#8b93a7] hover:bg-[#17212b] hover:text-white" onClick={onBack}>
<ArrowLeft className="h-5 w-5" />
</Button>
) : null}
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-[#3390ec]/20 text-[#3390ec]">
<Bot className="h-6 w-6" />
</div>
@@ -184,6 +194,11 @@ export function BotCreateMiniAppContent() {
>
Создать ещё одного бота
</Button>
{onCreated && createdBot.botId ? (
<Button className="w-full rounded-xl" onClick={() => onCreated(createdBot.botId)}>
Настройки бота
</Button>
) : null}
</div>
) : null}
</div>

View File

@@ -1,16 +1,20 @@
'use client';
import { Suspense } from 'react';
import { BotCreateMiniAppContent } from './content';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { Loader2 } from 'lucide-react';
export default function BotCreateLegacyRedirectPage() {
const router = useRouter();
useEffect(() => {
router.replace('/mini-apps/bot-father?mode=create');
}, [router]);
export default function BotCreateMiniAppPage() {
return (
<Suspense
fallback={
<div className="flex min-h-screen items-center justify-center p-6 text-sm text-[#667085]">Загрузка...</div>
}
>
<BotCreateMiniAppContent />
</Suspense>
<div className="flex min-h-screen items-center justify-center p-6 text-sm text-[#667085]">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Перенаправление...
</div>
);
}

View File

@@ -0,0 +1,171 @@
'use client';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Bot, ChevronRight, Loader2, Plus } from 'lucide-react';
import { usePublicSettings } from '@/components/id/public-settings-provider';
import { Button } from '@/components/ui/button';
import { BotCreateMiniAppContent } from '@/app/mini-apps/bot-create/content';
import { BotManageMiniAppContent } from '@/app/mini-apps/bot-manage/content';
import { useMiniAppAuth } from '@/hooks/use-mini-app-auth';
import { fetchMyBots, getApiErrorMessage, type ManagedBot } from '@/lib/api';
import { useToast } from '@/components/id/toast-provider';
type View = 'list' | 'create' | 'manage';
export function BotFatherMiniAppContent() {
const searchParams = useSearchParams();
const router = useRouter();
const { projectName } = usePublicSettings();
const { showToast } = useToast();
const { effectiveToken, canUse, authReady, waitingForBridge, isLoading } = useMiniAppAuth();
const initialBotId = searchParams.get('botId');
const initialMode = searchParams.get('mode');
const [view, setView] = useState<View>(() => {
if (initialBotId) return 'manage';
if (initialMode === 'create') return 'create';
return 'list';
});
const [selectedBotId, setSelectedBotId] = useState<string | null>(initialBotId);
const [bots, setBots] = useState<ManagedBot[]>([]);
const [loadingBots, setLoadingBots] = useState(false);
const syncUrl = useCallback(
(nextView: View, botId?: string | null) => {
const params = new URLSearchParams();
if (nextView === 'create') params.set('mode', 'create');
if (nextView === 'manage' && botId) params.set('botId', botId);
const query = params.toString();
router.replace(query ? `/mini-apps/bot-father?${query}` : '/mini-apps/bot-father');
},
[router]
);
const loadBots = useCallback(async () => {
if (!effectiveToken) return;
setLoadingBots(true);
try {
const response = await fetchMyBots(effectiveToken);
setBots((response.bots ?? []).filter((bot) => !bot.isSystemBot));
} catch (error) {
showToast(getApiErrorMessage(error, 'Не удалось загрузить список ботов') ?? 'Ошибка');
} finally {
setLoadingBots(false);
}
}, [effectiveToken, showToast]);
useEffect(() => {
if (!canUse) return;
if (view === 'list') void loadBots();
}, [canUse, loadBots, view]);
const selectedBot = useMemo(
() => bots.find((bot) => bot.id === selectedBotId) ?? null,
[bots, selectedBotId]
);
function openManage(botId: string) {
setSelectedBotId(botId);
setView('manage');
syncUrl('manage', botId);
}
function openCreate() {
setView('create');
syncUrl('create');
}
function backToList() {
setSelectedBotId(null);
setView('list');
syncUrl('list');
void loadBots();
}
if ((isLoading || waitingForBridge || !authReady) && !effectiveToken) {
return (
<div className="flex min-h-screen items-center justify-center p-6 text-center text-sm text-[#667085]">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Загрузка...
</div>
);
}
if (!canUse) {
return (
<div className="flex min-h-screen items-center justify-center p-6 text-center text-sm text-[#667085]">
Войдите в {projectName}, чтобы управлять ботами
</div>
);
}
if (view === 'create') {
return (
<BotCreateMiniAppContent
onBack={backToList}
onCreated={(botId) => openManage(botId)}
/>
);
}
if (view === 'manage' && selectedBotId) {
return <BotManageMiniAppContent botId={selectedBotId} onBack={backToList} botHint={selectedBot?.username} />;
}
return (
<div className="min-h-screen bg-[#17212b] p-4 text-white">
<div className="mx-auto max-w-md space-y-4">
<div className="rounded-[24px] bg-[#242f3d] p-5 shadow-xl">
<div className="flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-[#3390ec]/20 text-[#3390ec]">
<Bot className="h-6 w-6" />
</div>
<div>
<h1 className="text-lg font-semibold">BotFather</h1>
<p className="text-sm text-[#8b93a7]">Создание и настройка ваших ботов</p>
</div>
</div>
</div>
<Button className="h-12 w-full rounded-xl text-base" onClick={openCreate}>
<Plus className="mr-2 h-5 w-5" />
Создать нового бота
</Button>
<div className="rounded-[24px] bg-[#242f3d] p-4 shadow-xl">
<p className="mb-3 text-sm font-medium text-[#c5cad3]">Мои боты</p>
{loadingBots ? (
<div className="flex items-center justify-center gap-2 py-8 text-sm text-[#8b93a7]">
<Loader2 className="h-4 w-4 animate-spin" />
Загрузка...
</div>
) : bots.length === 0 ? (
<p className="py-6 text-center text-sm text-[#8b93a7]">У вас пока нет ботов</p>
) : (
<div className="space-y-2">
{bots.map((bot) => (
<button
key={bot.id}
type="button"
onClick={() => openManage(bot.id)}
className="flex w-full items-center gap-3 rounded-2xl bg-[#17212b] px-4 py-3 text-left transition hover:bg-[#1c2733]"
>
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-[#3390ec]/15 text-[#3390ec]">
<Bot className="h-5 w-5" />
</div>
<div className="min-w-0 flex-1">
<p className="truncate font-medium">{bot.name}</p>
<p className="truncate text-sm text-[#8b93a7]">@{bot.username}</p>
</div>
<ChevronRight className="h-5 w-5 shrink-0 text-[#667085]" />
</button>
))}
</div>
)}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,20 @@
'use client';
import { Suspense } from 'react';
import { Loader2 } from 'lucide-react';
import { BotFatherMiniAppContent } from './content';
export default function BotFatherMiniAppPage() {
return (
<Suspense
fallback={
<div className="flex min-h-screen items-center justify-center p-6 text-sm text-[#667085]">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Загрузка...
</div>
}
>
<BotFatherMiniAppContent />
</Suspense>
);
}

View File

@@ -2,7 +2,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { Copy, ImageIcon, LayoutGrid, Loader2, RefreshCw, TextQuote, UserCircle2 } from 'lucide-react';
import { Copy, ImageIcon, LayoutGrid, Loader2, RefreshCw, TextQuote, UserCircle2, ArrowLeft } from 'lucide-react';
import { usePublicSettings } from '@/components/id/public-settings-provider';
import { useToast } from '@/components/id/toast-provider';
import { Button } from '@/components/ui/button';
@@ -75,9 +75,17 @@ function SectionCard({
);
}
export function BotManageMiniAppContent() {
export function BotManageMiniAppContent({
botId: botIdProp,
onBack,
botHint
}: {
botId?: string;
onBack?: () => void;
botHint?: string;
} = {}) {
const searchParams = useSearchParams();
const botId = searchParams.get('botId') ?? '';
const botId = botIdProp ?? searchParams.get('botId') ?? '';
const { projectName } = usePublicSettings();
const { effectiveToken, canUse: canUseSession, authReady, waitingForBridge, isLoading } = useMiniAppAuth();
const { showToast } = useToast();
@@ -239,8 +247,20 @@ export function BotManageMiniAppContent() {
<div className="min-h-screen bg-[#f4f5f8] p-4 pb-8">
<div className="mx-auto max-w-lg space-y-4">
<div className="rounded-[24px] bg-white p-5 shadow-sm">
<h1 className="text-lg font-semibold">Настройки бота</h1>
<p className="text-sm text-[#667085]">Интерактивное управление профилем аналог команд BotFather</p>
<div className="flex items-start gap-3">
{onBack ? (
<Button type="button" variant="ghost" size="icon" className="mt-0.5 h-9 w-9 shrink-0" onClick={onBack}>
<ArrowLeft className="h-5 w-5" />
</Button>
) : null}
<div>
<h1 className="text-lg font-semibold">Настройки бота</h1>
<p className="text-sm text-[#667085]">
{botHint ? `@${botHint.replace(/_bot$/i, '')}_bot · ` : ''}
Полное управление как в BotFather
</p>
</div>
</div>
</div>
{loading ? (

View File

@@ -1,20 +1,40 @@
import { Suspense } from 'react';
import { Loader2 } from 'lucide-react';
import { BotManageMiniAppContent } from './content';
'use client';
import { useEffect } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Loader2 } from 'lucide-react';
import { Suspense } from 'react';
function BotManageLegacyRedirectContent() {
const router = useRouter();
const searchParams = useSearchParams();
const botId = searchParams.get('botId');
useEffect(() => {
const target = botId
? `/mini-apps/bot-father?botId=${encodeURIComponent(botId)}`
: '/mini-apps/bot-father';
router.replace(target);
}, [botId, router]);
function BotManageFallback() {
return (
<div className="flex min-h-screen items-center justify-center p-6 text-sm text-[#667085]">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Загрузка Mini App...
Перенаправление...
</div>
);
}
export default function BotManageMiniAppPage() {
export default function BotManageLegacyRedirectPage() {
return (
<Suspense fallback={<BotManageFallback />}>
<BotManageMiniAppContent />
<Suspense
fallback={
<div className="flex min-h-screen items-center justify-center p-6 text-sm text-[#667085]">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
</div>
}
>
<BotManageLegacyRedirectContent />
</Suspense>
);
}