import { useCallback, useEffect, useState, type FormEvent } from "react"; import { KeyRound, Loader2, Shield, Trash2, User, UserPlus } from "lucide-react"; import { getUsers, createUser, deleteUser, updatePassword } from "../api/user-actions"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input, Label, Select } from "@/components/ui/form-controls"; import { EmptyState, ErrorState, Skeleton } from "@/components/ui/states"; import { confirmer, toast } from "@/components/ui/feedback"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { isStaleServerActionError, STALE_ACTION_MESSAGE } from "@/lib/stale-action"; type Utilisateur = Awaited>[number]; type Role = "admin" | "user"; const ROLES = [ { value: "user", label: "Utilisateur" }, { value: "admin", label: "Administrateur" }, ] as const; function messageErreur(e: unknown): string { if (isStaleServerActionError(e)) return STALE_ACTION_MESSAGE; return e instanceof Error ? e.message : String(e); } function fmtJour(valeur: Date | string | null | undefined): string { if (!valeur) return "—"; const d = new Date(valeur); return isNaN(d.getTime()) ? "—" : d.toLocaleDateString("fr-FR"); } /** * Changement du mot de passe d'un compte. Monté à l'ouverture (clé = compte) : * les champs repartent vides à chaque fois. */ function DialogueMotDePasse({ cible, onFermer }: { cible: Utilisateur; onFermer: () => void }) { const [motDePasse, setMotDePasse] = useState(""); const [confirmation, setConfirmation] = useState(""); const [erreur, setErreur] = useState(null); const [envoi, setEnvoi] = useState(false); const valider = async (e: FormEvent) => { e.preventDefault(); setErreur(null); if (motDePasse.length < 4) { setErreur("Le mot de passe doit faire au moins 4 caractères."); return; } if (motDePasse !== confirmation) { setErreur("Les deux mots de passe ne sont pas identiques."); return; } setEnvoi(true); try { const res = await updatePassword(cible.id, motDePasse); if (!res.success) { setErreur(res.error ?? "Erreur lors de la mise à jour."); return; } toast.succes(`Le mot de passe de « ${cible.username} » a été modifié.`); onFermer(); } catch (err) { setErreur(messageErreur(err)); } finally { setEnvoi(false); } }; return ( { if (!open && !envoi) onFermer(); }}>
Changer le mot de passe Compte « {cible.username} ». Le nouveau mot de passe sera demandé dès la prochaine connexion.
setMotDePasse(e.target.value)} />

Au moins 4 caractères.

setConfirmation(e.target.value)} />
{erreur && (

{erreur}

)}
); } export function UserManagement() { const [users, setUsers] = useState([]); const [chargement, setChargement] = useState(true); const [erreurChargement, setErreurChargement] = useState(null); const [creation, setCreation] = useState(false); const [newUsername, setNewUsername] = useState(""); const [newPassword, setNewPassword] = useState(""); const [newRole, setNewRole] = useState("user"); const [suppression, setSuppression] = useState(null); const [cibleMotDePasse, setCibleMotDePasse] = useState(null); // Aucun setState avant le premier await : appelable depuis l'effet de montage. const charger = useCallback(async () => { try { setUsers(await getUsers()); setErreurChargement(null); } catch (e) { setErreurChargement(messageErreur(e)); } finally { setChargement(false); } }, []); useEffect(() => { void charger(); }, [charger]); const reessayer = () => { setChargement(true); void charger(); }; const handleCreate = async (e: FormEvent) => { e.preventDefault(); setCreation(true); try { const res = await createUser(newUsername, newPassword, newRole); if (res.success) { toast.succes(`Le compte « ${newUsername} » a été créé : il peut se connecter dès maintenant.`); setNewUsername(""); setNewPassword(""); void charger(); } else { toast.erreur(res.error ?? "Le compte n'a pas pu être créé."); } } catch (err) { toast.erreur(`Le compte n'a pas pu être créé : ${messageErreur(err)}`); } finally { setCreation(false); } }; const handleDelete = async (u: Utilisateur) => { const ok = await confirmer({ titre: `Supprimer le compte « ${u.username} » ?`, message: "Cette personne ne pourra plus se connecter à CollectFlow. La suppression est définitive.", libelleConfirmer: "Supprimer", danger: true, }); if (!ok) return; setSuppression(u.id); try { const res = await deleteUser(u.id); if (res.success) { setUsers((prev) => prev.filter((x) => x.id !== u.id)); toast.succes(`Le compte « ${u.username} » a été supprimé.`); } else { toast.erreur(res.error ?? "Le compte n'a pas pu être supprimé."); } } catch (err) { toast.erreur(`Le compte n'a pas pu être supprimé : ${messageErreur(err)}`); } finally { setSuppression(null); } }; return ( <> {/* Création */}
setNewUsername(e.target.value)} className="w-full" placeholder="ex. jean.dupont" autoComplete="off" />
setNewPassword(e.target.value)} className="w-full" placeholder="••••••••" autoComplete="new-password" />