diff --git a/src/app/backend/actions.ts b/src/app/backend/actions.ts index 96f5e49..a6581e6 100644 --- a/src/app/backend/actions.ts +++ b/src/app/backend/actions.ts @@ -4,7 +4,7 @@ import { randomInt } from "node:crypto"; import { revalidatePath } from "next/cache"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; -import { and, eq, sql } from "drizzle-orm"; +import { and, eq, ne, sql } from "drizzle-orm"; import bcrypt from "bcryptjs"; import { signOut } from "@/auth"; import { getSession } from "@/lib/session"; @@ -1133,6 +1133,17 @@ export async function approveMembershipRequest( const [req] = await db.select().from(membershipRequests).where(eq(membershipRequests.id, id)); if (!req) throw new Error("Demande introuvable."); + // Le bouton disparaît dès qu'une demande est traitée, mais un onglet resté + // ouvert peut encore poster : on refuse ici plutôt que de créer un doublon. + if (req.status !== "new") { + return { + error: + req.status === "approved" + ? "Cette demande a déjà été approuvée." + : "Cette demande a été rejetée : rouvrez-la avant de l'approuver.", + }; + } + const email = (req.email ?? "").trim().toLowerCase(); if (!email) { return { @@ -1178,10 +1189,12 @@ export async function setRequestStatus(formData: FormData) { const id = Number(formData.get("id")); const status = String(formData.get("status") ?? ""); if (!id || !["new", "approved", "rejected"].includes(status)) return; + // Une demande approuvée a créé un compte : elle ne se rejoue pas depuis un + // onglet resté ouvert sur l'ancien état. await db .update(membershipRequests) .set({ status: status as "new" | "approved" | "rejected" }) - .where(eq(membershipRequests.id, id)); + .where(and(eq(membershipRequests.id, id), ne(membershipRequests.status, "approved"))); revalidatePath("/backend/demandes"); revalidatePath("/backend"); } diff --git a/src/app/backend/demandes/ApproveRequestForm.tsx b/src/app/backend/demandes/ApproveRequestForm.tsx index 0d00ce7..59af171 100644 --- a/src/app/backend/demandes/ApproveRequestForm.tsx +++ b/src/app/backend/demandes/ApproveRequestForm.tsx @@ -10,8 +10,19 @@ import { approveMembershipRequest, type CreatedMemberAccount } from "../actions" * Approuve une demande et affiche une seule fois les identifiants du compte * créé. On reste sur la page : une redirection ferait perdre le mot de passe, * qui n'est conservé nulle part. + * + * Le bouton ne s'affiche que pour une demande encore à traiter, mais c'est ce + * composant — et non la page — qui en décide : après l'approbation, + * `router.refresh()` renvoie la demande en « approuvée », et un masquage côté + * serveur démonterait le bloc d'identifiants avant qu'il ait été lu. */ -export function ApproveRequestForm({ requestId }: { requestId: number }) { +export function ApproveRequestForm({ + requestId, + status, +}: { + requestId: number; + status: string; +}) { const [created, setCreated] = useState(null); const [pending, setPending] = useState(false); const [error, setError] = useState(null); @@ -28,6 +39,8 @@ export function ApproveRequestForm({ requestId }: { requestId: number }) { ); } + if (status !== "new") return null; + return (
{ diff --git a/src/app/backend/demandes/page.tsx b/src/app/backend/demandes/page.tsx index c1be110..89780ee 100644 --- a/src/app/backend/demandes/page.tsx +++ b/src/app/backend/demandes/page.tsx @@ -72,13 +72,25 @@ export default async function DemandesPage() { + {/* Une demande traitée ne garde que la sortie qui a du sens : + « Rejeter » disparaît une fois l'adhérent créé, et un rejet + reste réversible tant qu'aucun compte n'existe. */}
- - - - - Rejeter - + + {r.status === "new" && ( +
+ + + Rejeter +
+ )} + {r.status === "rejected" && ( +
+ + + Rouvrir +
+ )}
{r.message && ( @@ -109,17 +121,30 @@ export default async function DemandesPage() { + {/* Chaque bouton fait avancer le message d'un cran : on ne + propose plus « Marquer lu » sur un message déjà lu ni + « Archiver » sur un message archivé. */}
-
- - - Marquer lu -
-
- - - Archiver -
+ {m.status === "new" && ( +
+ + + Marquer lu +
+ )} + {m.status !== "archived" ? ( +
+ + + Archiver +
+ ) : ( +
+ + + Désarchiver +
+ )}

{m.message}