Complete admin dashboard with user management, config and settings tabs

This commit is contained in:
2025-11-30 22:44:10 +01:00
parent d31a132808
commit 80318a8d43
5 changed files with 840 additions and 398 deletions

View File

@@ -0,0 +1,131 @@
"use client";
import { useState, Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useTranslationStore } from "@/lib/store";
import { Shield, Lock, Eye, EyeOff, AlertCircle } from "lucide-react";
function AdminLoginContent() {
const router = useRouter();
const searchParams = useSearchParams();
const { setAdminToken } = useTranslationStore();
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const response = await fetch(`${API_BASE}/admin/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password }),
});
if (!response.ok) {
const data = await response.json();
throw new Error(data.detail || "Mot de passe incorrect");
}
const data = await response.json();
setAdminToken(data.access_token);
const redirect = searchParams.get("redirect") || "/admin";
router.push(redirect);
} catch (err: any) {
setError(err.message || "Erreur de connexion");
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Logo */}
<div className="text-center mb-8">
<div className="inline-flex items-center justify-center w-16 h-16 bg-purple-600/20 rounded-2xl mb-4">
<Shield className="w-8 h-8 text-purple-400" />
</div>
<h1 className="text-2xl font-bold text-white">Administration</h1>
<p className="text-gray-400 mt-2">Connexion requise</p>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="bg-black/30 backdrop-blur-xl rounded-2xl border border-white/10 p-8">
{error && (
<div className="flex items-center gap-3 p-4 mb-6 bg-red-500/10 border border-red-500/20 rounded-xl text-red-400">
<AlertCircle className="w-5 h-5 flex-shrink-0" />
<p>{error}</p>
</div>
)}
<div className="space-y-6">
<div>
<label className="block text-gray-400 text-sm mb-2">Mot de passe administrateur</label>
<div className="relative">
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type={showPassword ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
className="w-full pl-12 pr-12 py-3 bg-black/30 border border-white/10 rounded-xl text-white placeholder:text-gray-500 focus:outline-none focus:border-purple-500 transition-all"
required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition-colors"
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
</div>
<button
type="submit"
disabled={loading || !password}
className="w-full py-3 bg-purple-600 hover:bg-purple-700 disabled:bg-purple-600/50 disabled:cursor-not-allowed text-white font-medium rounded-xl transition-all flex items-center justify-center gap-2"
>
{loading ? (
<>
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
Connexion...
</>
) : (
<>
<Shield className="w-5 h-5" />
Accéder au panneau admin
</>
)}
</button>
</div>
</form>
<p className="text-center text-gray-500 text-sm mt-6">
Accès réservé aux administrateurs
</p>
</div>
</div>
);
}
export default function AdminLoginPage() {
return (
<Suspense fallback={
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 flex items-center justify-center">
<div className="text-white text-xl">Chargement...</div>
</div>
}>
<AdminLoginContent />
</Suspense>
);
}

File diff suppressed because it is too large Load Diff