feat: Implement Apple-inspired design system with global dark/light mode styles and utility classes.

This commit is contained in:
Michael committed 2026-02-22 14:32:42 +01:00
1 parent 997abee21a
commit afa7e59c34
5 files changed
+294 -75

No files matched your search

@@ -12,6 +12,7 @@ import {
Key,
} from "lucide-react";
import { SuccessModal } from "@/components/shared/success-modal";
import { ChangePasswordModal } from "@/components/shared/change-password-modal";
export function UserManagement() {
const [users, setUsers] = useState<any[]>([]);
@@ -21,6 +22,9 @@ export function UserManagement() {
const [newPassword, setNewPassword] = useState("");
const [newRole, setNewRole] = useState<"admin" | "user">("user");
const [modal, setModal] = useState({ isOpen: false, title: "", message: "" });
const [pwdModal, setPwdModal] = useState<{ isOpen: boolean; userId: number; username: string }>({
isOpen: false, userId: 0, username: ""
});
const fetchUsers = async () => {
setLoading(true);
@@ -57,24 +61,20 @@ export function UserManagement() {
setIsCreating(false);
};
const handleUpdatePassword = async (id: number, username: string) => {
const pass = window.prompt(`Nouveau mot de passe pour ${username} :`);
if (!pass) return;
const handleUpdatePassword = (id: number, username: string) => {
setPwdModal({ isOpen: true, userId: id, username });
};
if (pass.length < 4) {
alert("Le mot de passe doit faire au moins 4 caractères.");
return;
}
const res = await updatePassword(id, pass);
const handleConfirmPassword = async (newPassword: string) => {
const res = await updatePassword(pwdModal.userId, newPassword);
if (res.success) {
setModal({
isOpen: true,
title: "Mot de passe mis à jour",
message: `Le mot de passe de ${username} a été modifié avec succès.`
message: `Le mot de passe de ${pwdModal.username} a été modifié avec succès.`
});
} else {
alert(res.error);
throw new Error(res.error ?? "Erreur lors de la mise à jour.");
}
};
@@ -91,8 +91,8 @@ export function UserManagement() {
if (loading) {
return (
<div className="flex flex-col items-center justify-center p-12 space-y-4">
<Loader2 className="w-8 h-8 text-indigo-500 animate-spin" />
<p className="text-slate-400 text-sm">Chargement des utilisateurs...</p>
<Loader2 className="w-8 h-8 text-[var(--accent)] animate-spin" />
<p className="text-[var(--text-muted)] text-sm">Chargement des utilisateurs...</p>
</div>
);
}
@@ -100,45 +100,45 @@ export function UserManagement() {
return (
<div className="space-y-8">
{/* Formulaire de création */}
<div className="bg-slate-800/30 border border-slate-700/50 rounded-3xl p-8">
<div className="bg-[var(--bg-elevated)] border border-[var(--border)] rounded-2xl p-6">
<div className="flex items-center gap-4 mb-8">
<div className="w-12 h-12 rounded-2xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center">
<UserPlus className="w-6 h-6 text-indigo-400" />
<div className="w-12 h-12 rounded-2xl bg-[var(--accent-bg)] border border-[var(--accent-border)] flex items-center justify-center">
<UserPlus className="w-6 h-6 text-[var(--accent)]" />
</div>
<div>
<h2 className="text-xl font-black text-slate-100 italic">Nouvel Utilisateur</h2>
<p className="text-slate-500 text-sm">Ajoutez un collaborateur à la plateforme.</p>
<h2 className="text-lg font-bold text-[var(--text-primary)]">Nouvel Utilisateur</h2>
<p className="text-[var(--text-secondary)] text-xs">Ajoutez un collaborateur à la plateforme.</p>
</div>
</div>
<form onSubmit={handleCreate} className="grid grid-cols-1 md:grid-cols-4 gap-6 items-end">
<div className="space-y-2">
<label className="text-[10px] font-black uppercase tracking-widest text-slate-500 ml-1">Utilisateur</label>
<label className="text-[10px] font-black uppercase tracking-widest text-[var(--text-muted)] ml-1">Utilisateur</label>
<input
required
value={newUsername}
onChange={(e) => setNewUsername(e.target.value)}
className="w-full bg-slate-900/50 border border-slate-700/50 rounded-xl px-4 py-3 text-sm text-slate-100 outline-none focus:border-indigo-500 transition-colors"
className="apple-input"
placeholder="ex: jean.dupont"
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black uppercase tracking-widest text-slate-500 ml-1">Mot de passe</label>
<label className="text-[10px] font-black uppercase tracking-widest text-[var(--text-muted)] ml-1">Mot de passe</label>
<input
required
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="w-full bg-slate-900/50 border border-slate-700/50 rounded-xl px-4 py-3 text-sm text-slate-100 outline-none focus:border-indigo-500 transition-colors"
className="apple-input"
placeholder="••••••••"
/>
</div>
<div className="space-y-2">
<label className="text-[10px] font-black uppercase tracking-widest text-slate-500 ml-1">Rôle</label>
<label className="text-[10px] font-black uppercase tracking-widest text-[var(--text-muted)] ml-1">Rôle</label>
<select
value={newRole}
onChange={(e) => setNewRole(e.target.value as any)}
className="w-full bg-slate-900/50 border border-slate-700/50 rounded-xl px-4 py-3 text-sm text-slate-100 outline-none focus:border-indigo-500 transition-colors appearance-none"
className="apple-input"
>
<option value="user">Utilisateur</option>
<option value="admin">Administrateur</option>
@@ -147,7 +147,7 @@ export function UserManagement() {
<button
type="submit"
disabled={isCreating}
className="h-12 bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white text-sm font-black rounded-xl transition-all active:scale-95 flex items-center justify-center gap-2 shadow-lg shadow-indigo-900/20"
className="apple-btn-primary w-full justify-center"
>
{isCreating ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
Créer le compte
@@ -156,30 +156,30 @@ export function UserManagement() {
</div>
{/* Liste des utilisateurs */}
<div className="grid gap-4">
<h3 className="text-xs font-black uppercase tracking-widest text-slate-500 ml-1 mb-2 italic">Liste des comptes</h3>
<div className="grid gap-3">
<h3 className="text-[10px] font-black uppercase tracking-widest text-[var(--text-muted)] ml-1 mb-1">Liste des comptes</h3>
{users.map((u) => (
<div key={u.id} className="flex items-center gap-6 p-4 rounded-2xl bg-slate-800/40 border border-slate-700/50 hover:border-slate-600 transition-all group">
<div className="w-10 h-10 rounded-xl bg-slate-700/50 flex items-center justify-center shrink-0">
{u.role === "admin" ? <Shield className="w-5 h-5 text-amber-400" /> : <User className="w-5 h-5 text-slate-400" />}
<div key={u.id} className="flex items-center gap-5 p-4 rounded-xl bg-[var(--bg-surface)] border border-[var(--border)] hover:border-[var(--text-muted)] transition-all group">
<div className="w-10 h-10 rounded-xl bg-[var(--bg-elevated)] flex items-center justify-center shrink-0 border border-[var(--border)]">
{u.role === "admin" ? <Shield className="w-5 h-5 text-amber-500" /> : <User className="w-5 h-5 text-[var(--text-muted)]" />}
</div>
<div className="flex-1">
<h4 className="text-sm font-bold text-slate-100">{u.username}</h4>
<span className="text-[10px] uppercase font-black text-slate-500 tracking-wider">
<h4 className="text-sm font-bold text-[var(--text-primary)]">{u.username}</h4>
<span className="text-[10px] uppercase font-black text-[var(--text-muted)] tracking-wider">
{u.role === "admin" ? "Administrateur" : "Utilisateur Standard"}
</span>
</div>
<div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<div className="flex items-center gap-1.5 opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={() => handleUpdatePassword(u.id, u.username)}
className="p-2.5 rounded-xl hover:bg-indigo-500/10 text-slate-500 hover:text-indigo-400 transition-colors"
className="p-2.5 rounded-lg hover:bg-[var(--accent-bg)] text-[var(--text-muted)] hover:text-[var(--accent)] transition-colors"
title="Changer le mot de passe"
>
<Key className="w-4 h-4" />
</button>
<button
onClick={() => handleDelete(u.id, u.username)}
className="p-2.5 rounded-xl hover:bg-rose-500/10 text-slate-500 hover:text-rose-500 transition-colors"
className="p-2.5 rounded-lg hover:bg-[var(--accent-error-bg)] text-[var(--text-muted)] hover:text-[var(--accent-error)] transition-colors"
title="Supprimer"
>
<Trash2 className="w-4 h-4" />
@@ -195,6 +195,13 @@ export function UserManagement() {
title={modal.title}
message={modal.message}
/>
<ChangePasswordModal
isOpen={pwdModal.isOpen}
onClose={() => setPwdModal({ ...pwdModal, isOpen: false })}
onConfirm={handleConfirmPassword}
username={pwdModal.username}
/>
</div>
);
}