|
|
@@ -2,23 +2,100 @@
|
|
|
|
|
|
import Link from 'next/link'
|
|
|
import { usePathname, useRouter } from 'next/navigation'
|
|
|
-import { useEffect } from 'react'
|
|
|
+import { useState, useEffect } from 'react'
|
|
|
import { useAuth } from '@/hooks/useAuth'
|
|
|
+import { api, setAccessToken } from '@/lib/api'
|
|
|
|
|
|
const ALLOWED_ROLES = ['moderator', 'superadmin']
|
|
|
|
|
|
+function SetupForm({ onDone }: { onDone: () => void }) {
|
|
|
+ const [email, setEmail] = useState('')
|
|
|
+ const [password, setPassword] = useState('')
|
|
|
+ const [name, setName] = useState('')
|
|
|
+ const [error, setError] = useState('')
|
|
|
+ const [loading, setLoading] = useState(false)
|
|
|
+
|
|
|
+ const handleSubmit = async (e: React.FormEvent) => {
|
|
|
+ e.preventDefault()
|
|
|
+ setError('')
|
|
|
+ setLoading(true)
|
|
|
+ try {
|
|
|
+ const res = await api.post<{ user: any; access_token: string }>('/admin/setup', { email, password, name })
|
|
|
+ setAccessToken(res.access_token)
|
|
|
+ onDone()
|
|
|
+ } catch (err: any) {
|
|
|
+ setError(err.message || 'Ошибка создания администратора')
|
|
|
+ } finally {
|
|
|
+ setLoading(false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="flex min-h-screen items-center justify-center bg-[#0f172a]">
|
|
|
+ <form onSubmit={handleSubmit} className="w-full max-w-sm rounded-lg border border-white/10 bg-white/5 p-6">
|
|
|
+ <h1 className="mb-6 text-xl font-bold text-white">Создание первого администратора</h1>
|
|
|
+ <p className="mb-4 text-sm text-white/40">В системе ещё нет суперадминистратора. Создайте первую учётную запись.</p>
|
|
|
+
|
|
|
+ {error && <p className="mb-4 rounded bg-red-500/10 px-3 py-2 text-sm text-red-400">{error}</p>}
|
|
|
+
|
|
|
+ <div className="space-y-4">
|
|
|
+ <div>
|
|
|
+ <label className="mb-1 block text-sm text-white/60">Email</label>
|
|
|
+ <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required
|
|
|
+ className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-white outline-none focus:border-green-500" />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label className="mb-1 block text-sm text-white/60">Имя (необязательно)</label>
|
|
|
+ <input type="text" value={name} onChange={(e) => setName(e.target.value)}
|
|
|
+ className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-white outline-none focus:border-green-500" />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label className="mb-1 block text-sm text-white/60">Пароль</label>
|
|
|
+ <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required minLength={8}
|
|
|
+ className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm text-white outline-none focus:border-green-500" />
|
|
|
+ </div>
|
|
|
+ <button type="submit" disabled={loading}
|
|
|
+ className="w-full rounded-lg bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-500 transition disabled:opacity-50"
|
|
|
+ >
|
|
|
+ {loading ? 'Создание...' : 'Создать администратора'}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
export default function AdminLayout({ children }: { children: React.ReactNode }) {
|
|
|
const pathname = usePathname()
|
|
|
const router = useRouter()
|
|
|
- const { user, isLoading } = useAuth()
|
|
|
+ const { user, isLoading: authLoading } = useAuth()
|
|
|
+ const [setupStatus, setSetupStatus] = useState<'loading' | 'needed' | 'done'>('loading')
|
|
|
|
|
|
useEffect(() => {
|
|
|
- if (!isLoading && (!user || !ALLOWED_ROLES.includes(user.role))) {
|
|
|
+ api.get<{ has_superadmin: boolean }>('/admin/setup-status')
|
|
|
+ .then((res) => setSetupStatus(res.has_superadmin ? 'done' : 'needed'))
|
|
|
+ .catch(() => setSetupStatus('done'))
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (setupStatus === 'done' && !authLoading && (!user || !ALLOWED_ROLES.includes(user.role))) {
|
|
|
router.replace('/')
|
|
|
}
|
|
|
- }, [user, isLoading, router])
|
|
|
+ }, [setupStatus, user, authLoading, router])
|
|
|
+
|
|
|
+ if (setupStatus === 'loading') {
|
|
|
+ return (
|
|
|
+ <div className="flex min-h-screen items-center justify-center bg-[#0f172a]">
|
|
|
+ <p className="text-white/40">Загрузка...</p>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ if (setupStatus === 'needed') {
|
|
|
+ return <SetupForm onDone={() => setSetupStatus('done')} />
|
|
|
+ }
|
|
|
|
|
|
- if (isLoading || !user || !ALLOWED_ROLES.includes(user.role)) {
|
|
|
+ if (authLoading || !user || !ALLOWED_ROLES.includes(user.role)) {
|
|
|
return (
|
|
|
<div className="flex min-h-screen items-center justify-center bg-[#0f172a]">
|
|
|
<p className="text-white/40">Загрузка...</p>
|