fixing platform admin

This commit is contained in:
root
2026-05-06 22:58:23 -04:00
parent 695a7f7cc7
commit 750ae56a29
175 changed files with 31249 additions and 328 deletions
+212
View File
@@ -0,0 +1,212 @@
'use client'
import Link from 'next/link'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { useAdminI18n } from '@/components/I18nProvider'
import PublicShell from '@/components/PublicShell'
const API_BASE = '/api/v1'
export default function AdminLoginPage() {
const { language } = useAdminI18n()
const dict = {
en: {
loginFailed: 'Login failed',
verifyFailed: '2FA verification failed',
brand: 'Admin console',
access: 'Platform operations access',
email: 'Email',
password: 'Password',
signIn: 'Sign in',
signingIn: 'Signing in…',
enterCode: 'Enter the 6-digit code from your authenticator app',
authCode: 'Authentication code',
verify: 'Verify',
verifying: 'Verifying…',
back: 'Back to login',
},
fr: {
loginFailed: 'Échec de connexion',
verifyFailed: 'Échec de la vérification 2FA',
brand: 'Console admin',
access: 'Accès opérations plateforme',
email: 'Email',
password: 'Mot de passe',
signIn: 'Connexion',
signingIn: 'Connexion…',
enterCode: 'Entrez le code à 6 chiffres de votre application dauthentification',
authCode: 'Code dauthentification',
verify: 'Vérifier',
verifying: 'Vérification…',
back: 'Retour à la connexion',
},
ar: {
loginFailed: 'فشل تسجيل الدخول',
verifyFailed: 'فشل التحقق الثنائي',
brand: 'لوحة الإدارة',
access: 'وصول عمليات المنصة',
email: 'البريد الإلكتروني',
password: 'كلمة المرور',
signIn: 'تسجيل الدخول',
signingIn: 'جارٍ تسجيل الدخول…',
enterCode: 'أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة',
authCode: 'رمز المصادقة',
verify: 'تحقق',
verifying: 'جارٍ التحقق…',
back: 'العودة إلى تسجيل الدخول',
},
}[language]
const router = useRouter()
const [step, setStep] = useState<'credentials' | 'totp'>('credentials')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [totp, setTotp] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
async function handleCredentials(e: React.FormEvent) {
e.preventDefault()
setLoading(true)
setError(null)
try {
const res = await fetch(`${API_BASE}/admin/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
const json = await res.json()
if (res.status === 401 && json?.error === 'totp_required') {
setStep('totp')
return
}
if (!res.ok) throw new Error(json?.message ?? dict.loginFailed)
if (json.data?.token) {
localStorage.setItem('admin_token', json.data.token)
router.push('/dashboard')
}
} catch (err: any) {
setError(err.message)
} finally {
setLoading(false)
}
}
async function handleTotp(e: React.FormEvent) {
e.preventDefault()
setLoading(true)
setError(null)
try {
const res = await fetch(`${API_BASE}/admin/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password, totpCode: totp }),
})
const json = await res.json()
if (!res.ok) throw new Error(json?.message ?? dict.verifyFailed)
if (json.data?.token) {
localStorage.setItem('admin_token', json.data.token)
router.push('/dashboard')
}
} catch (err: any) {
setError(err.message)
} finally {
setLoading(false)
}
}
return (
<PublicShell>
<main className="flex flex-1 items-center justify-center px-4">
<div className="w-full max-w-md">
<div className="mb-8 text-center">
<Link href="/" className="text-xs font-semibold uppercase tracking-[0.2em] text-emerald-400">RentalDriveGo</Link>
<h1 className="mt-3 text-3xl font-black tracking-tight">{dict.brand}</h1>
<p className="mt-1 text-sm text-zinc-400">{dict.access}</p>
</div>
<div className="panel p-8">
{error && (
<div className="mb-6 p-3 rounded-xl border border-red-900/50 bg-red-950/50 text-sm text-red-400">
{error}
</div>
)}
{step === 'credentials' ? (
<form onSubmit={handleCredentials} className="space-y-5">
<div>
<label className="block text-sm font-medium text-zinc-300 mb-1.5">{dict.email}</label>
<input
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2.5 rounded-xl bg-zinc-800 border border-zinc-700 text-zinc-100 placeholder:text-zinc-500 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
placeholder="admin@rentaldrivego.com"
/>
</div>
<div>
<label className="block text-sm font-medium text-zinc-300 mb-1.5">{dict.password}</label>
<input
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2.5 rounded-xl bg-zinc-800 border border-zinc-700 text-zinc-100 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
placeholder="••••••••"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-2.5 px-4 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-semibold transition-colors disabled:opacity-50"
>
{loading ? dict.signingIn : dict.signIn}
</button>
</form>
) : (
<form onSubmit={handleTotp} className="space-y-5">
<div className="text-center mb-2">
<div className="inline-flex h-12 w-12 items-center justify-center rounded-full bg-emerald-900/40 mb-3">
<svg className="h-6 w-6 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</div>
<p className="text-sm text-zinc-300">{dict.enterCode}</p>
</div>
<div>
<label className="block text-sm font-medium text-zinc-300 mb-1.5">{dict.authCode}</label>
<input
type="text"
inputMode="numeric"
pattern="[0-9]{6}"
maxLength={6}
required
value={totp}
onChange={(e) => setTotp(e.target.value.replace(/\D/g, ''))}
className="w-full px-3 py-2.5 rounded-xl bg-zinc-800 border border-zinc-700 text-zinc-100 text-center text-2xl tracking-[0.5em] placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:border-transparent"
placeholder="000000"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-2.5 px-4 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-semibold transition-colors disabled:opacity-50"
>
{loading ? dict.verifying : dict.verify}
</button>
<button
type="button"
onClick={() => { setStep('credentials'); setTotp(''); setError(null) }}
className="w-full text-sm text-zinc-500 hover:text-zinc-300 transition-colors"
>
{dict.back}
</button>
</form>
)}
</div>
</div>
</main>
</PublicShell>
)
}