From 5675e1d220081dcf232684e7a702dd08712ef4bb Mon Sep 17 00:00:00 2001
From: AdamThomas
Date: Sat, 3 Oct 2026 13:47:24 +0200
Subject: [PATCH] =?UTF-8?q?Formulaire=20public=20:=20ast=C3=A9risque=20rou?=
=?UTF-8?q?ge=20sur=20les=20champs=20obligatoires?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Composant RequiredMark et prop `required` sur FieldLabel ; marqués
uniquement les champs réellement exigés à la soumission (nom, prénom,
email/téléphone/disponibilité/cotisation s'ils sont activés, étape
d'adresse mécénat), avec une légende « * champ obligatoire » en tête du
formulaire. Champ photo déplacé avant la disponibilité, libellé précisé
(« pour ton badge »).
Co-Authored-By: Claude Opus 5.5
---
CLAUDE.md | 1 +
frontend/src/components/ui.tsx | 12 +++-
frontend/src/i18n/locales/fr.json | 3 +-
frontend/src/pages/PublicSignupPage.tsx | 75 ++++++++++++++-----------
4 files changed, 55 insertions(+), 36 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 119fb95..fbe8cc2 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -821,6 +821,7 @@ Aucune dépendance locale requise hors Docker.
| 29 | Jeu de démonstration : 8 bénévoles avec une cotisation classique payée, 4 avec une cotisation mécénat payée (adresse complète) — `app/seed.py:_add_demo_payments` — pour que l'export Excel et les reçus fiscaux mécénat (étape 28) aient de quoi montrer quelque chose d'emblée | à valider |
| 30 | Infobulles (`HelpTip` et détail de cellule de la grille des besoins) repositionnées en `position: fixed` via JS (`lib/useTooltipPosition.ts`), bornées à l'intérieur de l'écran et basculant de côté si la place manque — corrige un centrage `absolute` qui les faisait déborder de l'écran près d'un bord, et une isolation `z-index` qui rendait celle de la grille invisible (coincée sous les en-têtes figés) | à valider |
| 31 | Revue de sécurité : logo des emails en pièce jointe inline au lieu d'une URL (fuyait le `access_token` du formulaire public à tout destinataire — régression de l'étape 27, aucun test ne couvrait le rendu HTML du logo), sujet retiré du log de remise des emails (pouvait porter le nom d'un bénévole via un modèle personnalisé), image du formulaire public revalidée par décodage réel (`PIL`, pas seulement le `Content-Type` déclaré) avant stockage — évite qu'une fausse image fasse échouer la génération des badges/feuilles de poste, export Excel des bénévoles protégé contre l'injection de formule (CWE-1236, `app/exporting/xlsx.py:_safe_text`), sondage du statut de paiement en attente limité en fréquence par jeton (`pending_status_limiter`, évite un abus de l'API SumUp), `pyjwt` mis à jour (CVE listées non exploitables ici — HS256 à secret statique uniquement, jamais de JWKS — mais autant ne pas rester sur une version vulnérable) | à valider |
+| 32 | Formulaire public : astérisque rouge sur les champs obligatoires (`RequiredMark`, `FieldLabel required`) et légende « * champ obligatoire » | à valider |
**Reste hors périmètre** (modèle prêt à les accueillir) : SMS, constructeur
de formulaire, pointage sur site, IA. Frontend : squelette + 6 écrans +
diff --git a/frontend/src/components/ui.tsx b/frontend/src/components/ui.tsx
index ca09dbb..f80f7ce 100644
--- a/frontend/src/components/ui.tsx
+++ b/frontend/src/components/ui.tsx
@@ -225,10 +225,20 @@ export function HelpTip({
);
}
-export function FieldLabel({ children }: { children: ReactNode }) {
+/** Astérisque rouge des champs obligatoires (purement visuel : la règle vit ailleurs). */
+export function RequiredMark() {
+ return (
+
+ *
+
+ );
+}
+
+export function FieldLabel({ children, required }: { children: ReactNode; required?: boolean }) {
return (
{children}
+ {required ? : null}
);
}
diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json
index ff8c041..5cdc316 100644
--- a/frontend/src/i18n/locales/fr.json
+++ b/frontend/src/i18n/locales/fr.json
@@ -390,7 +390,7 @@
"config.vol.feeStatus.failed": "Cotisation : paiement échoué",
"config.vol.feeStatus.expired": "Cotisation : lien de paiement expiré",
"config.vol.mecenatAddress": "Adresse (cerfa)",
- "form.field.photo": "Une magnifique photo de toi :",
+ "form.field.photo": "Une magnifique photo de toi pour ton badge :",
"form.field.comment": "Commentaire",
"form.link.title": "Lien du formulaire",
"form.link.hint": "À partager aux bénévoles potentiels — n'importe qui muni du lien peut s'inscrire, mais lui seul : sans ce lien précis, impossible de tomber sur le formulaire.",
@@ -436,6 +436,7 @@
"publicForm.sumup.afterSubmitHint": "Un lien de paiement en ligne (SumUp) te sera proposé juste après l'envoi de ton inscription.",
"publicForm.captcha.disabled": "(Vérification anti-robot non configurée sur cette instance.)",
"publicForm.submitting": "Envoi…",
+ "publicForm.requiredLegend": "champ obligatoire",
"publicForm.submit": "Envoyer mon inscription",
"publicForm.success.title": "Inscription reçue",
"publicForm.success.body": "Merci ! Ton inscription a bien été enregistrée. La coordination du festival prendra contact avec toi.",
diff --git a/frontend/src/pages/PublicSignupPage.tsx b/frontend/src/pages/PublicSignupPage.tsx
index 84bdb11..2929563 100644
--- a/frontend/src/pages/PublicSignupPage.tsx
+++ b/frontend/src/pages/PublicSignupPage.tsx
@@ -12,7 +12,7 @@ import {
} from '@/api/hooks';
import type { PublicArtistOption } from '@/api/schema';
import { ErrorNote, Loading } from '@/components/state';
-import { Btn, FieldLabel, Panel, PanelHeader, TextInput } from '@/components/ui';
+import { Btn, FieldLabel, Panel, PanelHeader, RequiredMark, TextInput } from '@/components/ui';
import { t } from '@/i18n';
import { ENV } from '@/lib/env';
import { longDayLabel } from '@/lib/time';
@@ -271,21 +271,21 @@ function MecenatInfoStep({
{t('publicForm.mecenat.hint')}