Front : donner un accès de connexion à un bénévole (comptes manquants)

Root cause du « je n'ajoute un bénévole avec son mail, je ne reçois aucun
mail » : renseigner un email sur la fiche d'un bénévole ne crée jamais de
compte (`Account`). `POST .../auth/magic-link` cherche un compte par email
et ne fait rien s'il n'en trouve pas (202 quoi qu'il arrive, par conception,
pour ne jamais révéler si un compte existe) — silencieusement, sans erreur.
L'API `POST/GET/DELETE /editions/{id}/accounts` existait déjà côté backend
(testée) mais n'avait jamais été branchée au frontend.

- `api/schema.ts` / `types.ts` / `http-client.ts` / `hooks.ts` : nouvelle
  surface `accounts` (list/create/remove) + `useAccounts`, `useCreateAccount`,
  `useDeleteAccount`.
- `mock-client.ts` : même surface (règles miroir du backend : email unique,
  mot de passe interdit hors COORDINATOR) ; la suppression d'un bénévole
  supprime aussi son compte (cascade RGPD, alignée sur le backend).
- `ConfigVolunteers.tsx` : section « Accès de connexion » sur la fiche du
  bénévole — bouton pour donner l'accès (crée un compte VOLUNTEER, lien
  magique) si un email est renseigné, ou le révoquer si un accès existe déjà.
- Corrigé au passage : `config.vol.addHint` affirmait qu'un email donnait
  l'accès automatiquement — c'était faux, texte mis à jour.
- CLAUDE.md à jour (section authentification).

Vérifié : typecheck, lint, build, smoke (7 routes) OK.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
AdamThomas
2026-09-15 20:22:56 +02:00
co-authored by Claude Sonnet 5
parent fb6e26eddb
commit e405590bbc
8 changed files with 174 additions and 2 deletions
+22
View File
@@ -7,6 +7,7 @@ import {
import { EDITION_ID, api } from './index';
import type {
AccountCreate,
ArtistCreate,
ArtistUpdate,
AssignmentCreate,
@@ -68,6 +69,10 @@ export function usePositions() {
return useQuery({ queryKey: queryKeys.positions, queryFn: () => api.positions.list(edition) });
}
export function useAccounts() {
return useQuery({ queryKey: ['accounts', edition], queryFn: () => api.accounts.list(edition) });
}
/** Fiche d'un bénévole (un bénévole connecté peut lire la sienne). */
export function useVolunteer(id: string | null | undefined) {
return useQuery({
@@ -151,6 +156,7 @@ function useInvalidate() {
volunteers: () => client.invalidateQueries({ queryKey: ['volunteers', edition] }),
availability: () => client.invalidateQueries({ queryKey: ['availability', edition] }),
artists: () => client.invalidateQueries({ queryKey: ['artists', edition] }),
accounts: () => client.invalidateQueries({ queryKey: ['accounts', edition] }),
positions: () => client.invalidateQueries({ queryKey: ['positions', edition] }),
shifts: () => client.invalidateQueries({ queryKey: ['shifts', edition] }),
planning: () => {
@@ -239,6 +245,22 @@ export function useDeleteArtist() {
});
}
export function useCreateAccount() {
const invalidate = useInvalidate();
return useMutation({
mutationFn: (body: AccountCreate) => api.accounts.create(edition, body),
onSuccess: invalidate.accounts,
});
}
export function useDeleteAccount() {
const invalidate = useInvalidate();
return useMutation({
mutationFn: (id: string) => api.accounts.remove(edition, id),
onSuccess: invalidate.accounts,
});
}
export function useCreateVolunteer() {
const invalidate = useInvalidate();
return useMutation({