Add slots based per class, admin courses mgmt, admin roles, horizontal nav/landing, glove favicon

This commit is contained in:
Wycz
2026-09-08 09:10:37 +02:00
parent 3f4fdeba99
commit 7556b2cd97
15 changed files with 439 additions and 74 deletions
+125 -30
View File
@@ -28,7 +28,7 @@ export default function AdminUsers({ users }: { users: any[] }) {
</button>
</div>
<div className="flex gap-2">
<div className="flex flex-wrap gap-2">
{(
[
["ALL", "Tous"],
@@ -70,6 +70,7 @@ function CreateUserForm({ onDone }: { onDone: () => void }) {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [role, setRole] = useState("MEMBER");
const [isMuayThai, setIsMuayThai] = useState(true);
const [isYoga, setIsYoga] = useState(false);
const [error, setError] = useState("");
@@ -82,7 +83,14 @@ function CreateUserForm({ onDone }: { onDone: () => void }) {
const res = await fetch("/api/admin/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, email, password, isMuayThai, isYoga }),
body: JSON.stringify({
name,
email,
password,
role,
isMuayThai: role === "MEMBER" ? isMuayThai : false,
isYoga: role === "MEMBER" ? isYoga : false,
}),
});
const data = await res.json();
setSaving(false);
@@ -95,7 +103,7 @@ function CreateUserForm({ onDone }: { onDone: () => void }) {
return (
<form onSubmit={handleSubmit} className="card space-y-4 border-primary-500/30">
<h3 className="font-semibold">✨ Créer un compte membre</h3>
<h3 className="font-semibold">✨ Créer un compte</h3>
<div>
<label className="label">Nom complet</label>
<input className="input" value={name} onChange={(e) => setName(e.target.value)} placeholder="Jean Dupont" required />
@@ -108,10 +116,39 @@ function CreateUserForm({ onDone }: { onDone: () => void }) {
<label className="label">Mot de passe initial</label>
<input className="input" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Mot de passe temporaire" required />
</div>
<div className="flex gap-3">
<Checkbox label="🥊 Muay Thai" checked={isMuayThai} onChange={setIsMuayThai} />
<Checkbox label="🧘 Yoga" checked={isYoga} onChange={setIsYoga} />
<div>
<label className="label">Rôle</label>
<div className="flex gap-2">
<button
type="button"
onClick={() => setRole("MEMBER")}
className={`flex-1 rounded-xl px-3 py-2 text-sm font-semibold ${
role === "MEMBER"
? "bg-primary-600 text-white"
: "bg-white/5 text-white/60"
}`}
>
👤 Membre
</button>
<button
type="button"
onClick={() => setRole("ADMIN")}
className={`flex-1 rounded-xl px-3 py-2 text-sm font-semibold ${
role === "ADMIN"
? "bg-amber-600 text-white"
: "bg-white/5 text-white/60"
}`}
>
⭐ Admin
</button>
</div>
</div>
{role === "MEMBER" && (
<div className="flex gap-3">
<Checkbox label="🥊 Muay Thai" checked={isMuayThai} onChange={setIsMuayThai} />
<Checkbox label="🧘 Yoga" checked={isYoga} onChange={setIsYoga} />
</div>
)}
{error && <p className="text-sm text-red-400">{error}</p>}
<button type="submit" className="btn-primary w-full" disabled={saving}>
{saving ? "Création..." : "✅ Créer le compte"}
@@ -133,19 +170,27 @@ function MemberCard({ user, onUpdate }: { user: any; onUpdate: () => void }) {
const [expanded, setExpanded] = useState(false);
const [editName, setEditName] = useState(user.name);
const [editPassword, setEditPassword] = useState("");
const router = useRouter();
const [editRole, setEditRole] = useState(user.role);
const [error, setError] = useState("");
const [saving, setSaving] = useState(false);
const certStatus = user.certificates?.find((c: any) => c.status !== "REJECTED")?.status;
async function updateUser(data: any) {
setSaving(true);
setError("");
const res = await fetch(`/api/admin/users/${user.id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
setSaving(false);
if (res.ok) {
setEditPassword("");
onUpdate();
setExpanded(false);
} else {
const d = await res.json().catch(() => ({}));
setError(d.error || "Erreur lors de la mise à jour");
}
}
@@ -159,17 +204,31 @@ function MemberCard({ user, onUpdate }: { user: any; onUpdate: () => void }) {
if (res.ok) onUpdate();
}
const isAdmin = user.role === "ADMIN";
const canDelete = !isAdmin;
return (
<div className="card">
<div className="flex items-start justify-between gap-3">
<div className="flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary-600/20 text-lg font-bold text-primary-300">
<div
className={`flex h-12 w-12 items-center justify-center rounded-full text-lg font-bold ${
isAdmin
? "bg-amber-500/20 text-amber-300"
: "bg-primary-600/20 text-primary-300"
}`}
>
{user.name.charAt(0).toUpperCase()}
</div>
<div>
<p className="font-semibold">{user.name}</p>
<p className="text-xs text-white/50">{user.email}</p>
<div className="mt-1 flex gap-1.5">
<div className="mt-1 flex flex-wrap gap-1.5">
{isAdmin && (
<span className="rounded-full bg-amber-500/20 px-2 py-0.5 text-xs text-amber-300">
⭐ Admin
</span>
)}
{user.isMuayThai && (
<span className="rounded-full bg-primary-600/20 px-2 py-0.5 text-xs text-primary-300">
🥊 Muay Thai
@@ -180,11 +239,6 @@ function MemberCard({ user, onUpdate }: { user: any; onUpdate: () => void }) {
🧘 Yoga
</span>
)}
{user.role === "ADMIN" && (
<span className="rounded-full bg-white/10 px-2 py-0.5 text-xs text-white/70">
⭐ Admin
</span>
)}
</div>
</div>
</div>
@@ -212,33 +266,74 @@ function MemberCard({ user, onUpdate }: { user: any; onUpdate: () => void }) {
</div>
)}
{expanded && user.role !== "ADMIN" && (
{expanded && (
<div className="mt-4 space-y-3 border-t border-white/10 pt-4">
<h4 className="text-sm font-semibold">Modifier</h4>
<div className="grid grid-cols-1 gap-3">
<div>
<label className="label">Nom</label>
<input className="input" value={editName} onChange={(e) => setEditName(e.target.value)} />
</div>
<div>
<label className="label">Nouveau mot de passe (optionnel)</label>
<input className="input" value={editPassword} onChange={(e) => setEditPassword(e.target.value)} placeholder="Laisser vide pour ne pas changer" />
<h4 className="text-sm font-semibold">
⚙️ Configuration — {isAdmin ? "compte admin" : "compte membre"}
</h4>
<div>
<label className="label">Nom</label>
<input className="input" value={editName} onChange={(e) => setEditName(e.target.value)} />
</div>
<div>
<label className="label">Rôle</label>
<div className="flex gap-2">
<button
type="button"
onClick={() => setEditRole("MEMBER")}
className={`flex-1 rounded-xl px-3 py-2 text-sm font-semibold ${
editRole === "MEMBER"
? "bg-primary-600 text-white"
: "bg-white/5 text-white/60"
}`}
>
👤 Membre
</button>
<button
type="button"
onClick={() => setEditRole("ADMIN")}
className={`flex-1 rounded-xl px-3 py-2 text-sm font-semibold ${
editRole === "ADMIN"
? "bg-amber-600 text-white"
: "bg-white/5 text-white/60"
}`}
>
⭐ Admin
</button>
</div>
</div>
<div>
<label className="label">Réinitialiser le mot de passe</label>
<input
className="input"
value={editPassword}
onChange={(e) => setEditPassword(e.target.value)}
placeholder="Nouveau mot de passe (laisser vide pour ne pas changer)"
/>
</div>
{error && <p className="text-sm text-red-400">{error}</p>}
<div className="flex gap-2">
<button
onClick={() => {
const data: any = { name: editName };
const data: any = { name: editName, role: editRole };
if (editPassword) data.password = editPassword;
updateUser(data);
}}
className="btn-primary flex-1 !py-2 text-sm"
disabled={saving}
>
💾 Enregistrer
</button>
<button onClick={deleteUser} className="btn-danger">
🗑️
{saving ? "Enregistrement..." : "💾 Enregistrer"}
</button>
{canDelete && (
<button onClick={deleteUser} className="btn-danger" title="Supprimer">
🗑️
</button>
)}
</div>
</div>
)}