Files
2026-06-26 11:18:07 +03:00

208 lines
8.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useMemo, useState } from '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';
import { Input } from '@/components/ui/input';
import { createManagedBot, getApiErrorMessage } from '@/lib/api';
import { useMiniAppAuth } from '@/hooks/use-mini-app-auth';
import { cn } from '@/lib/utils';
type Step = 'name' | 'username' | 'done';
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();
const [step, setStep] = useState<Step>('name');
const [name, setName] = useState('');
const [username, setUsername] = useState('');
const [creating, setCreating] = useState(false);
const [createdBot, setCreatedBot] = useState<{ username: string; token: string; botId: string } | null>(null);
const canUseSession = canUse;
const usernamePreview = useMemo(() => `${username.replace(/_bot$/i, '').trim()}_bot`, [username]);
async function handleCreate() {
if (!canUseSession || !effectiveToken) return;
const trimmedName = name.trim();
const trimmedUsername = username.replace(/_bot$/i, '').trim();
if (!trimmedName) {
showToast('Укажите название бота');
setStep('name');
return;
}
if (trimmedUsername.length < 5) {
showToast('Username должен содержать минимум 5 символов');
return;
}
setCreating(true);
try {
const response = await createManagedBot({ name: trimmedName, username: trimmedUsername }, effectiveToken);
setCreatedBot({
username: response.bot?.username ?? usernamePreview,
token: response.token ?? '',
botId: response.bot?.id ?? ''
});
setStep('done');
showToast('Бот успешно создан');
} catch (error) {
showToast(getApiErrorMessage(error, 'Не удалось создать бота') ?? 'Ошибка');
} finally {
setCreating(false);
}
}
async function copyToken() {
if (!createdBot?.token) return;
await navigator.clipboard.writeText(createdBot.token);
showToast('Токен скопирован');
}
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 (!canUseSession) {
return (
<div className="flex min-h-screen items-center justify-center p-6 text-center text-sm text-[#667085]">
Войдите в {projectName}, чтобы создать бота
</div>
);
}
return (
<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>
<div>
<h1 className="text-lg font-semibold">Создание бота</h1>
<p className="text-sm text-[#8b93a7]">Как в BotFather Telegram</p>
</div>
</div>
<div className="flex items-center gap-2 text-xs text-[#8b93a7]">
{(['name', 'username', 'done'] as Step[]).map((item, index) => (
<div key={item} className="flex items-center gap-2">
<span
className={cn(
'flex h-6 w-6 items-center justify-center rounded-full',
step === item || (step === 'done' && item !== 'done') || (item === 'name' && step !== 'name')
? 'bg-[#3390ec] text-white'
: 'bg-[#17212b] text-[#8b93a7]'
)}
>
{step === 'done' && item !== 'done' ? <Check className="h-3.5 w-3.5" /> : index + 1}
</span>
{index < 2 ? <span className="h-px w-8 bg-[#2a3544]" /> : null}
</div>
))}
</div>
{step === 'name' ? (
<div className="space-y-4">
<p className="text-sm leading-relaxed text-[#c5cad3]">
Alright, a new bot. How are we going to call it? Please choose a name for your bot.
</p>
<p className="text-sm text-[#8b93a7]">Хорошо, новый бот. Как мы его назовём? Выберите название.</p>
<Input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="Например: Сервис уведомлений"
className="rounded-xl border-[#2a3544] bg-[#17212b] text-white placeholder:text-[#667085]"
autoFocus
/>
<Button className="w-full rounded-xl" disabled={!name.trim()} onClick={() => setStep('username')}>
Далее
</Button>
</div>
) : null}
{step === 'username' ? (
<div className="space-y-4">
<p className="text-sm leading-relaxed text-[#c5cad3]">
Good. Now let&apos;s choose a username for your bot. It must end in `bot`.
</p>
<p className="text-sm text-[#8b93a7]">Username должен заканчиваться на `_bot` (суффикс добавится автоматически).</p>
<div className="flex items-center gap-2">
<span className="text-[#8b93a7]">@</span>
<Input
value={username}
onChange={(event) => setUsername(event.target.value.replace(/[^a-zA-Z0-9_]/g, ''))}
placeholder="notify_service"
className="rounded-xl border-[#2a3544] bg-[#17212b] text-white placeholder:text-[#667085]"
autoFocus
/>
</div>
<p className="text-xs text-[#8b93a7]">Будет: @{usernamePreview || 'your_bot'}</p>
<div className="flex gap-2">
<Button variant="secondary" className="flex-1 rounded-xl bg-[#17212b] text-white hover:bg-[#1c2733]" onClick={() => setStep('name')}>
Назад
</Button>
<Button className="flex-1 rounded-xl" disabled={creating || username.replace(/_bot$/i, '').trim().length < 5} onClick={() => void handleCreate()}>
{creating ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
Создать бота
</Button>
</div>
</div>
) : null}
{step === 'done' && createdBot ? (
<div className="space-y-4">
<div className="rounded-2xl bg-[#17212b] p-4">
<p className="text-sm text-[#8b93a7]">Done! Congratulations on your new bot.</p>
<p className="mt-2 text-base font-semibold">@{createdBot.username}</p>
<p className="mt-3 text-sm text-red-300">Сохраните токен он больше не будет показан:</p>
<div className="mt-2 flex items-center gap-2">
<Input value={createdBot.token} readOnly className="rounded-xl border-[#2a3544] bg-[#242f3d] font-mono text-xs text-white" />
<Button type="button" variant="secondary" className="rounded-xl bg-[#3390ec] text-white hover:bg-[#2b7fd4]" onClick={() => void copyToken()}>
<Copy className="h-4 w-4" />
</Button>
</div>
</div>
<Button
className="w-full rounded-xl"
variant="secondary"
onClick={() => {
setStep('name');
setName('');
setUsername('');
setCreatedBot(null);
}}
>
Создать ещё одного бота
</Button>
{onCreated && createdBot.botId ? (
<Button className="w-full rounded-xl" onClick={() => onCreated(createdBot.botId)}>
Настройки бота
</Button>
) : null}
</div>
) : null}
</div>
</div>
);
}