Files
PleinR/src/app/backend/administrateurs/InviteAdminForm.tsx
T
Claude 503adcb20e Corrige la page blanche à la création d'un adhérent en doublon
« Application error: a server-side exception has occurred », digest 2117999835,
sur /backend/adherents : l'action levait `Error("Un compte existe déjà avec cet
e-mail.")`. Next masque le message d'une exception de server action en
production et remplace la page entière par son écran d'erreur — un e-mail déjà
pris, cas de saisie parfaitement ordinaire, mettait donc la page par terre.

Ces échecs attendus sont désormais **renvoyés** (`ActionError`) au lieu d'être
jetés, dans `addMember`, `inviteAdmin` et `approveMembershipRequest` :

- Le formulaire affiche la vraie raison, l'e-mail en cause inclus.
- `AddMemberPanel` réinjecte la saisie : React vide un formulaire non contrôlé
  après chaque action, il fallait sinon tout retaper.
- `InviteAdminForm` avait un rattrapage, mais devait deviner le motif faute de
  message ; il affiche maintenant celui de l'action. Son `catch` ne couvre plus
  que l'imprévu.
- `ApproveRequestForm` n'en avait aucun et tombait de la même façon.

Le `throw` reste réservé aux violations d'accès.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
2026-09-07 07:59:25 +00:00

72 lines
2.8 KiB
TypeScript

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { OneTimeCredentials } from "@/components/OneTimeCredentials";
import { inviteAdmin, type IssuedCredentials } from "../actions";
export function InviteAdminForm() {
const [issued, setIssued] = useState<IssuedCredentials | null>(null);
const [error, setError] = useState<string | null>(null);
const [pending, setPending] = useState(false);
const router = useRouter();
return (
<div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
{issued && <OneTimeCredentials items={[issued]} title="Compte créé — identifiants à transmettre" />}
{error && (
<div role="alert" style={{ background: "#fdecea", border: "1px solid #f1c4bd", borderRadius: 10, padding: "10px 14px", fontSize: 13, color: "#a3372e" }}>
{error}
</div>
)}
<form
action={async (fd) => {
setPending(true);
setError(null);
try {
const result = await inviteAdmin(fd);
if (result && "error" in result) {
setError(result.error);
return;
}
setIssued(result ?? null);
router.refresh();
} catch {
// Filet pour l'imprévu : les échecs de saisie, eux, sont renvoyés
// par l'action et affichés tels quels ci-dessus.
setError("Impossible de créer ce compte. Réessayez dans un instant.");
} finally {
setPending(false);
}
}}
>
<label className="field-label">Nom complet</label>
<input name="name" required placeholder="ex : Julie Bernard" className="field" style={{ marginBottom: 14 }} />
<label className="field-label">E-mail</label>
<input name="email" type="email" required placeholder="prenom@plein-r.fr" className="field" style={{ marginBottom: 14 }} />
<label className="field-label">Rôle</label>
<select name="role" defaultValue="Administrateur" className="field" style={{ marginBottom: 18 }}>
<option>Administrateur</option>
<option>Modérateur</option>
<option>Éditeur</option>
</select>
<button
type="submit"
disabled={pending}
className="font-display"
style={{ width: "100%", border: "none", background: "#13324F", color: "#fff", fontWeight: 700, fontSize: 14.5, padding: 13, borderRadius: 11, cursor: "pointer" }}
>
{pending ? "Création…" : "Créer le compte"}
</button>
</form>
<p style={{ fontSize: 11.5, color: "#a99c82", margin: 0, lineHeight: 1.5 }}>
Un mot de passe temporaire est généré et affiché une seule fois ici. L&apos;invité devra le
changer à sa première connexion.
</p>
</div>
);
}