a3c1fa0c65
/register: полноценная страница регистрации с валидацией email, пароль (6+ символов), подтверждение, имя (optional) После регистрации → /onboarding (создать первый канал) 50 кредитов при регистрации /landing: публичный лендинг для незалогиненных Hero, Features (6), How it works (3 шага), Pricing (4 тарифа), CTA, Footer page.js: незалогиненный → redirect /landing (не /login) Header: Settings2 в импорт, PublicHeader экспорт для лендинга
122 lines
5.3 KiB
JavaScript
122 lines
5.3 KiB
JavaScript
'use client';
|
|
import { useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import { Loader2, Eye, EyeOff, Sparkles } from 'lucide-react';
|
|
|
|
export default function RegisterPage() {
|
|
const router = useRouter();
|
|
const [email, setEmail] = useState('');
|
|
const [pass, setPass] = useState('');
|
|
const [pass2, setPass2] = useState('');
|
|
const [name, setName] = useState('');
|
|
const [show, setShow] = useState(false);
|
|
const [busy, setBusy] = useState(false);
|
|
const [error, setError] = useState('');
|
|
|
|
async function submit() {
|
|
if (!email.trim() || !pass) { setError('Заполните email и пароль'); return; }
|
|
if (pass.length < 6) { setError('Пароль минимум 6 символов'); return; }
|
|
if (pass !== pass2) { setError('Пароли не совпадают'); return; }
|
|
setBusy(true); setError('');
|
|
try {
|
|
const res = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email: email.trim(), password: pass, name: name.trim() || undefined, mode: 'register' }),
|
|
}).then(r => r.json());
|
|
if (!res.ok) { setError(res.error || 'Ошибка'); setBusy(false); return; }
|
|
router.push(res.isNew ? '/onboarding' : '/');
|
|
} catch { setError('Ошибка соединения'); setBusy(false); }
|
|
}
|
|
|
|
return (
|
|
<main className="min-h-screen flex items-center justify-center p-4 bg-background">
|
|
{/* Background glow */}
|
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
|
<div className="absolute top-1/3 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full bg-accent/5 blur-3xl" />
|
|
</div>
|
|
|
|
<div className="w-full max-w-md relative">
|
|
{/* Logo */}
|
|
<div className="text-center mb-8">
|
|
<Link href="/" className="inline-flex items-center gap-2 text-2xl font-bold">
|
|
<Sparkles className="w-7 h-7 text-accent" />
|
|
ZeroPost
|
|
</Link>
|
|
<p className="text-gray-400 text-sm mt-2">Создайте аккаунт — это бесплатно</p>
|
|
</div>
|
|
|
|
<div className="card p-6 sm:p-8 space-y-4">
|
|
<h1 className="font-bold text-xl text-center">Регистрация</h1>
|
|
|
|
<div>
|
|
<label className="label mb-1.5">Имя <span className="text-gray-500 text-xs">(необязательно)</span></label>
|
|
<input value={name} onChange={e => setName(e.target.value)}
|
|
placeholder="Алексей"
|
|
className="input w-full" autoFocus />
|
|
</div>
|
|
|
|
<div>
|
|
<label className="label mb-1.5">Email</label>
|
|
<input type="email" value={email} onChange={e => setEmail(e.target.value)}
|
|
onKeyDown={e => e.key === 'Enter' && submit()}
|
|
placeholder="you@example.com"
|
|
className="input w-full" />
|
|
</div>
|
|
|
|
<div>
|
|
<label className="label mb-1.5">Пароль</label>
|
|
<div className="relative">
|
|
<input type={show ? 'text' : 'password'}
|
|
value={pass} onChange={e => setPass(e.target.value)}
|
|
placeholder="Минимум 6 символов"
|
|
className="input w-full pr-10" />
|
|
<button onClick={() => setShow(s => !s)}
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500">
|
|
{show ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="label mb-1.5">Повторите пароль</label>
|
|
<input type="password" value={pass2}
|
|
onChange={e => setPass2(e.target.value)}
|
|
onKeyDown={e => e.key === 'Enter' && submit()}
|
|
placeholder="Ещё раз"
|
|
className="input w-full" />
|
|
</div>
|
|
|
|
{error && <p className="text-red-400 text-sm">{error}</p>}
|
|
|
|
<button onClick={submit} disabled={busy}
|
|
className="btn-primary w-full py-3 text-base font-medium flex items-center justify-center gap-2">
|
|
{busy ? <Loader2 className="w-5 h-5 animate-spin" /> : <><Sparkles className="w-4 h-4" />Зарегистрироваться</>}
|
|
</button>
|
|
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex-1 h-px bg-border" />
|
|
<span className="text-xs text-gray-500">или</span>
|
|
<div className="flex-1 h-px bg-border" />
|
|
</div>
|
|
|
|
<Link href="/login" className="btn-ghost w-full py-2.5 text-center text-sm">
|
|
Уже есть аккаунт? Войти →
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Бонус */}
|
|
<div className="mt-4 text-center text-xs text-gray-500">
|
|
🎁 При регистрации — <span className="text-accent">50 бесплатных кредитов</span>
|
|
</div>
|
|
|
|
<p className="text-center text-xs text-gray-600 mt-3">
|
|
Регистрируясь, вы принимаете{' '}
|
|
<Link href="/terms" className="hover:text-gray-400">условия использования</Link>
|
|
</p>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|