'use client';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'next/navigation';
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';
import { Input } from '@/components/ui/input';
import {
fetchBot,
getApiErrorMessage,
revokeManagedBotToken,
updateManagedBot,
updateManagedBotProfile
} from '@/lib/api';
import { useMiniAppAuth } from '@/hooks/use-mini-app-auth';
import { parseComposerMenuButtonJson } from '@/lib/bot-menu-button';
function FieldTextarea({
label,
hint,
value,
onChange,
maxLength,
rows = 3
}: {
label: string;
hint?: string;
value: string;
onChange: (value: string) => void;
maxLength?: number;
rows?: number;
}) {
return (
{hint ?
{hint}
: null}
);
}
function SectionCard({
icon: Icon,
title,
description,
children
}: {
icon: React.ComponentType<{ className?: string }>;
title: string;
description: string;
children: React.ReactNode;
}) {
return (
);
}
export function BotManageMiniAppContent({
botId: botIdProp,
onBack,
botHint
}: {
botId?: string;
onBack?: () => void;
botHint?: string;
} = {}) {
const searchParams = useSearchParams();
const botId = botIdProp ?? searchParams.get('botId') ?? '';
const { projectName } = usePublicSettings();
const { effectiveToken, canUse: canUseSession, authReady, waitingForBridge, isLoading } = useMiniAppAuth();
const { showToast } = useToast();
const [loading, setLoading] = useState(true);
const [savingBasic, setSavingBasic] = useState(false);
const [savingProfile, setSavingProfile] = useState(false);
const [revoking, setRevoking] = useState(false);
const [name, setName] = useState('');
const [username, setUsername] = useState('');
const [description, setDescription] = useState('');
const [aboutText, setAboutText] = useState('');
const [botPicUrl, setBotPicUrl] = useState('');
const [menuButtonUrl, setMenuButtonUrl] = useState('');
const [menuButtonText, setMenuButtonText] = useState('App');
const [tokenPrefix, setTokenPrefix] = useState('');
const [newToken, setNewToken] = useState(null);
const canUse = Boolean(canUseSession && botId);
const loadBot = useCallback(async () => {
if (!canUse || !effectiveToken) return;
setLoading(true);
try {
const bot = await fetchBot(botId, effectiveToken);
setName(bot.name);
setUsername(bot.username);
setDescription(bot.description ?? '');
setAboutText(bot.aboutText ?? '');
setBotPicUrl(bot.botPicUrl ?? '');
setTokenPrefix(bot.tokenPrefix ?? '');
const menuButton = parseComposerMenuButtonJson(bot.menuButtonJson);
if (menuButton) {
setMenuButtonUrl(menuButton.web_app.url);
setMenuButtonText(menuButton.text);
} else {
setMenuButtonUrl('');
setMenuButtonText('App');
}
} catch (error) {
showToast(getApiErrorMessage(error, 'Не удалось загрузить бота') ?? 'Ошибка');
} finally {
setLoading(false);
}
}, [botId, canUse, effectiveToken, showToast]);
useEffect(() => {
void loadBot();
}, [loadBot]);
const usernameWithoutSuffix = useMemo(() => username.replace(/_bot$/i, ''), [username]);
async function handleSaveBasic() {
if (!canUse || !effectiveToken) return;
setSavingBasic(true);
try {
const bot = await updateManagedBot(
botId,
{
name: name.trim(),
username: usernameWithoutSuffix.trim()
},
effectiveToken
);
setName(bot.name);
setUsername(bot.username);
showToast('Основные настройки сохранены');
} catch (error) {
showToast(getApiErrorMessage(error, 'Не удалось сохранить настройки') ?? 'Ошибка');
} finally {
setSavingBasic(false);
}
}
async function handleSaveProfile() {
if (!canUse || !effectiveToken) return;
setSavingProfile(true);
try {
const bot = await updateManagedBotProfile(
botId,
{
description: description.trim(),
aboutText: aboutText.trim(),
botPicUrl: botPicUrl.trim(),
menuButtonUrl: menuButtonUrl.trim(),
menuButtonText: menuButtonText.trim() || 'App'
},
effectiveToken
);
setDescription(bot.description ?? '');
setAboutText(bot.aboutText ?? '');
setBotPicUrl(bot.botPicUrl ?? '');
const menuButton = parseComposerMenuButtonJson(bot.menuButtonJson);
if (menuButton) {
setMenuButtonUrl(menuButton.web_app.url);
setMenuButtonText(menuButton.text);
}
showToast('Профиль бота обновлён');
} catch (error) {
showToast(getApiErrorMessage(error, 'Не удалось обновить профиль') ?? 'Ошибка');
} finally {
setSavingProfile(false);
}
}
async function handleRevokeToken() {
if (!canUse || !effectiveToken) return;
setRevoking(true);
try {
const response = await revokeManagedBotToken(botId, effectiveToken);
setNewToken(response.token ?? null);
setTokenPrefix(response.bot?.tokenPrefix ?? tokenPrefix);
showToast('Токен перевыпущен');
} catch (error) {
showToast(getApiErrorMessage(error, 'Не удалось перевыпустить токен') ?? 'Ошибка');
} finally {
setRevoking(false);
}
}
async function copyToken() {
if (!newToken) return;
await navigator.clipboard.writeText(newToken);
showToast('Токен скопирован');
}
if (!botId) {
return Не указан botId
;
}
if (isLoading && !effectiveToken) {
return (
Загрузка...
);
}
if ((waitingForBridge || !authReady) && !effectiveToken) {
return (
Загрузка...
);
}
if (!canUse) {
return (
Войдите в {projectName}, чтобы управлять ботом
);
}
return (
{onBack ? (
) : null}
Настройки бота
{botHint ? `@${botHint.replace(/_bot$/i, '')}_bot · ` : ''}
Полное управление как в BotFather
{loading ? (
Загрузка...
) : (
<>
Токен Bot API
Текущий префикс: {tokenPrefix || '—'}…
{newToken ? (
) : null}
Эквивалент команд BotFather
- /setdescription — поле «Описание»
- /setabouttext — поле «О боте»
- /setuserpic — URL аватара
- /setmenubutton — URL и текст кнопки меню
>
)}
);
}