Files
Ник (Claude) ab4e340db9 feat: onboarding + topic bank UI + channel limit handling
/onboarding: 3-шаговый вайзард (платформа → название/ниша → готово)
login/page.js: новый пользователь → /onboarding, существующий → /
TopicBank.js: просмотр/пополнение/добавление/удаление тем
ChannelEdit AI-стиль: TopicBank компонент внизу вкладки
channels/new: при 402 CHANNEL_LIMIT_REACHED → ошибка + redirect /plans
lib/engine.js: ENGINE_URL дефолт 3040 → 3030
API routes: /api/topics-bank/[channelId]/{refill,add}, /item/[id]
2026-06-12 11:50:22 +03:00

179 lines
8.2 KiB
JavaScript
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 { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Sparkles, CheckCircle, ArrowRight, Loader2, Bot, Hash, Zap } from 'lucide-react';
const PLATFORMS = [
{ v: 'telegram', label: 'Telegram', icon: '✈️', desc: 'Канал или группа' },
{ v: 'vk', label: 'ВКонтакте', icon: '🔵', desc: 'Группа или паблик' },
{ v: 'max', label: 'MAX', icon: '🟣', desc: 'Мессенджер MAX' },
];
const NICHES = [
'Технологии и ИИ', 'Бизнес и финансы', 'Маркетинг и SMM',
'Здоровье и спорт', 'Образование', 'Развлечения', 'Новости',
'Другое',
];
export default function OnboardingPage() {
const router = useRouter();
const [step, setStep] = useState(1);
const [platform, setPlatform] = useState('telegram');
const [name, setName] = useState('');
const [niche, setNiche] = useState('');
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const [done, setDone] = useState(false);
const [channel, setChannel] = useState(null);
async function createChannel() {
if (!name.trim()) { setError('Введите название канала'); return; }
setBusy(true);
setError('');
try {
const res = await fetch('/api/channels', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: name.trim(), platform, niche }),
}).then(r => r.json());
if (res.error) { setError(res.error); setBusy(false); return; }
setChannel(res);
setDone(true);
setStep(3);
} catch { setError('Ошибка соединения'); }
setBusy(false);
}
return (
<main className="min-h-screen flex items-center justify-center p-4">
<div className="w-full max-w-xl">
{/* Progress */}
<div className="flex items-center justify-center gap-2 mb-8">
{[1,2,3].map(n => (
<div key={n} className="flex items-center gap-2">
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold transition-colors ${
step > n ? 'bg-green-500 text-white' :
step === n ? 'bg-accent text-white' :
'bg-surface2 text-gray-500'
}`}>
{step > n ? <CheckCircle className="w-4 h-4" /> : n}
</div>
{n < 3 && <div className={`w-12 h-0.5 ${step > n ? 'bg-green-500' : 'bg-surface2'}`} />}
</div>
))}
</div>
<div className="card p-6 sm:p-8">
{/* Шаг 1 — платформа */}
{step === 1 && (
<>
<div className="text-center mb-6">
<div className="text-3xl mb-2">👋</div>
<h1 className="text-xl font-bold">Добро пожаловать!</h1>
<p className="text-gray-400 text-sm mt-1">Создадим первый канал за пару минут</p>
</div>
<p className="text-sm font-medium mb-3">Выберите платформу:</p>
<div className="grid grid-cols-3 gap-3 mb-6">
{PLATFORMS.map(p => (
<button key={p.v} onClick={() => setPlatform(p.v)}
className={`p-4 rounded-xl border-2 text-center transition-all ${
platform === p.v ? 'border-accent bg-accent/10' : 'border-border hover:border-accent/40'
}`}>
<div className="text-2xl mb-1">{p.icon}</div>
<div className="text-sm font-medium">{p.label}</div>
<div className="text-xs text-gray-500 mt-0.5">{p.desc}</div>
</button>
))}
</div>
<button onClick={() => setStep(2)} className="btn-primary w-full py-3 flex items-center justify-center gap-2">
Далее <ArrowRight className="w-4 h-4" />
</button>
</>
)}
{/* Шаг 2 — название и ниша */}
{step === 2 && (
<>
<div className="text-center mb-6">
<h1 className="text-xl font-bold">Расскажите о канале</h1>
<p className="text-gray-400 text-sm mt-1">AI будет генерировать контент в нужном стиле</p>
</div>
<div className="space-y-4 mb-6">
<div>
<label className="label mb-1.5">Название канала *</label>
<input
value={name}
onChange={e => setName(e.target.value)}
className="input w-full"
placeholder="Например: Tech Insider RU"
autoFocus
/>
</div>
<div>
<label className="label mb-1.5">Ниша / тематика</label>
<div className="flex flex-wrap gap-2">
{NICHES.map(n => (
<button key={n} onClick={() => setNiche(n === niche ? '' : n)}
className={`text-xs px-3 py-1.5 rounded-full border transition-colors ${
niche === n ? 'border-accent bg-accent/10 text-accent' : 'border-border hover:border-accent/40'
}`}>
{n}
</button>
))}
</div>
</div>
</div>
{error && <p className="text-red-400 text-sm mb-3">{error}</p>}
<div className="flex gap-3">
<button onClick={() => setStep(1)} className="btn-ghost px-4">Назад</button>
<button onClick={createChannel} disabled={busy || !name.trim()}
className="btn-primary flex-1 py-3 flex items-center justify-center gap-2">
{busy ? <Loader2 className="w-4 h-4 animate-spin" /> : <><Sparkles className="w-4 h-4" />Создать канал</>}
</button>
</div>
</>
)}
{/* Шаг 3 — готово */}
{step === 3 && (
<>
<div className="text-center mb-6">
<div className="text-4xl mb-3">🎉</div>
<h1 className="text-xl font-bold">Канал создан!</h1>
<p className="text-gray-400 text-sm mt-1">Что делать дальше:</p>
</div>
<div className="space-y-3 mb-6">
{[
{ icon: Bot, text: 'Подключите бота Telegram в настройках канала', color: 'text-blue-400' },
{ icon: Hash, text: 'AI сгенерирует темы постов автоматически', color: 'text-purple-400' },
{ icon: Zap, text: 'Напишите первый пост с помощью AI', color: 'text-yellow-400' },
].map(({ icon: Icon, text, color }) => (
<div key={text} className="flex items-start gap-3 p-3 rounded-lg bg-surface2">
<Icon className={`w-5 h-5 shrink-0 mt-0.5 ${color}`} />
<span className="text-sm">{text}</span>
</div>
))}
</div>
<div className="space-y-2">
<button onClick={() => router.push(channel ? `/channels/${channel.id}` : '/')}
className="btn-primary w-full py-3 flex items-center justify-center gap-2">
Начать работу <ArrowRight className="w-4 h-4" />
</button>
<button onClick={() => router.push(channel ? `/channels/${channel.id}/edit` : '/')}
className="btn-ghost w-full py-2.5 text-sm text-gray-400">
Настроить канал подробнее
</button>
</div>
</>
)}
</div>
<p className="text-center text-xs text-gray-600 mt-4">
У вас 50 бесплатных кредитов для старта
</p>
</div>
</main>
);
}