Formulaire public : astérisque rouge sur les champs obligatoires

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 <noreply@anthropic.com>
This commit is contained in:
AdamThomas
2026-10-03 13:47:24 +02:00
co-authored by Claude Opus 5.5
parent 096b7cd26a
commit 5675e1d220
4 changed files with 55 additions and 36 deletions
+11 -1
View File
@@ -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 (
<span aria-hidden="true" className="ml-[3px] text-red-600">
*
</span>
);
}
export function FieldLabel({ children, required }: { children: ReactNode; required?: boolean }) {
return (
<span className="mb-[5px] block font-display text-[9px] font-extrabold uppercase tracking-widest">
{children}
{required ? <RequiredMark /> : null}
</span>
);
}
+2 -1
View File
@@ -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.",
+41 -34
View File
@@ -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')}
</p>
<label className="block">
<FieldLabel>{t('publicForm.mecenat.address')}</FieldLabel>
<FieldLabel required>{t('publicForm.mecenat.address')}</FieldLabel>
<TextInput value={address} onChange={setAddress} />
</label>
<div className="grid grid-cols-2 gap-3">
<label className="block">
<FieldLabel>{t('publicForm.mecenat.postalCode')}</FieldLabel>
<FieldLabel required>{t('publicForm.mecenat.postalCode')}</FieldLabel>
<TextInput mono value={postalCode} onChange={setPostalCode} />
</label>
<label className="block">
<FieldLabel>{t('publicForm.mecenat.city')}</FieldLabel>
<FieldLabel required>{t('publicForm.mecenat.city')}</FieldLabel>
<TextInput value={city} onChange={setCity} />
</label>
</div>
<label className="block">
<FieldLabel>{t('publicForm.mecenat.country')}</FieldLabel>
<FieldLabel required>{t('publicForm.mecenat.country')}</FieldLabel>
<TextInput value={country} onChange={setCountry} />
</label>
{error ? (
@@ -516,27 +516,31 @@ function PublicSignupForm({ accessToken }: { accessToken: string }) {
</p>
) : null}
<form onSubmit={handleSubmit} className="flex flex-col gap-4 p-4">
<p className="font-mono text-[10.5px] opacity-70">
<span className="mr-[3px] text-red-600">*</span>
{t('publicForm.requiredLegend')}
</p>
<div className="grid grid-cols-2 gap-3">
<label className="block">
<FieldLabel>{t('config.vol.field.firstName')}</FieldLabel>
<FieldLabel required>{t('config.vol.field.firstName')}</FieldLabel>
<TextInput value={firstName} onChange={setFirstName} />
</label>
<label className="block">
<FieldLabel>{t('config.vol.field.lastName')}</FieldLabel>
<FieldLabel required>{t('config.vol.field.lastName')}</FieldLabel>
<TextInput value={lastName} onChange={setLastName} />
</label>
</div>
{fields.has('EMAIL') ? (
<label className="block">
<FieldLabel>{t('form.field.email')}</FieldLabel>
<FieldLabel required>{t('form.field.email')}</FieldLabel>
<TextInput mono value={email} onChange={setEmail} />
</label>
) : null}
{fields.has('PHONE') ? (
<label className="block">
<FieldLabel>{t('form.field.phone')}</FieldLabel>
<FieldLabel required>{t('form.field.phone')}</FieldLabel>
<TextInput mono value={phone} onChange={setPhone} />
</label>
) : null}
@@ -607,6 +611,26 @@ function PublicSignupForm({ accessToken }: { accessToken: string }) {
</label>
) : null}
{fields.has('PHOTO') ? (
<label className="block">
<FieldLabel>{t('form.field.photo')}</FieldLabel>
{photoPreviewUrl ? (
<img
src={photoPreviewUrl}
alt=""
className="mb-[8px] h-[180px] w-[180px] border-2 border-ink object-cover shadow-brutal-sm"
/>
) : null}
<input
type="file"
accept="image/*"
capture="environment"
onChange={(e) => handlePhotoChange(e.target.files?.[0] ?? null)}
className="w-full border-2 border-ink bg-cream p-[8px] font-mono text-[11px]"
/>
</label>
) : null}
{fields.has('COMMENT') ? (
<label className="block">
<FieldLabel>{t('form.field.comment')}</FieldLabel>
@@ -627,7 +651,10 @@ function PublicSignupForm({ accessToken }: { accessToken: string }) {
onChange={(e) => setAvailable(e.target.checked)}
className="h-[16px] w-[16px] accent-ink"
/>
<span className="font-mono text-[12px]">{t('publicForm.field.available')}</span>
<span className="font-mono text-[12px]">
{t('publicForm.field.available')}
<RequiredMark />
</span>
</label>
) : null}
@@ -678,30 +705,10 @@ function PublicSignupForm({ accessToken }: { accessToken: string }) {
onChange={(e) => setWillingToPayFee(e.target.checked)}
className="h-[16px] w-[16px] accent-ink"
/>
<span className="font-mono text-[12px]">{t('publicForm.field.willingToPayFee')}</span>
</label>
) : null}
{fields.has('PHOTO') ? (
<label className="block">
<FieldLabel>{t('form.field.photo')}</FieldLabel>
{photoPreviewUrl ? (
<img
src={photoPreviewUrl}
alt=""
className="mb-[8px] h-[180px] w-[180px] border-2 border-ink object-cover shadow-brutal-sm"
/>
) : null}
<input
type="file"
accept="image/*"
capture="environment"
onChange={(e) => handlePhotoChange(e.target.files?.[0] ?? null)}
className="w-full border-2 border-ink bg-cream p-[8px] font-mono text-[11px]"
/>
<p className="mt-[4px] font-mono text-[9.5px] opacity-50">
{t('publicForm.photo.squareHint')}
</p>
<span className="font-mono text-[12px]">
{t('publicForm.field.willingToPayFee')}
<RequiredMark />
</span>
</label>
) : null}