Lien d'inscription protégé par jeton, reçus fiscaux mécénat, infobulles et revue de sécurité
- Lien d'inscription protégé par un jeton (FormTemplate.access_token, /inscription/<token>), avec régénération depuis l'écran de configuration. - Reçus fiscaux mécénat (cerfa 11580*05) : un PDF par bénévole ayant payé en mécénat, donateur/montant (chiffres et toutes lettres en français, app/mecenat/number_words.py)/date complétés par-dessus le modèle officiel. - Jeu de démonstration : bénévoles avec cotisation classique ou mécénat payée. - Infobulles (HelpTip et détail de cellule de la grille des besoins) repositionnées en position: fixed via JS, bornées à l'écran. - Revue de sécurité : logo des emails en pièce jointe inline au lieu d'une URL (fuyait le jeton d'inscription), sujet retiré du log de remise des emails (pouvait porter un nom de bénévole), photo du formulaire public revalidée par décodage réel avant stockage, export Excel protégé contre l'injection de formule, sondage du statut de paiement limité en fréquence, pyjwt mis à jour. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a2b748566b
commit
096b7cd26a
+40
-13
@@ -394,6 +394,14 @@ export function useExportVolunteers() {
|
||||
});
|
||||
}
|
||||
|
||||
/** Reçus fiscaux (cerfa) des bénévoles ayant payé en mécénat, un PDF pour
|
||||
* tous les reçus ; même mécanique que `useExportVolunteers`. */
|
||||
export function useExportMecenatReceipts() {
|
||||
return useMutation({
|
||||
mutationFn: () => api.volunteers.exportMecenatReceipts(edition),
|
||||
});
|
||||
}
|
||||
|
||||
/** Remplace toutes les fenêtres de disponibilité d'un bénévole. */
|
||||
export function useReplaceAvailability() {
|
||||
const invalidate = useInvalidate();
|
||||
@@ -645,6 +653,16 @@ export function useUploadFormLogo() {
|
||||
});
|
||||
}
|
||||
|
||||
/** Révoque le lien d'inscription partagé jusqu'ici (nouveau `access_token`)
|
||||
* — voir `FormTemplateRead.access_token`. */
|
||||
export function useRegenerateFormToken() {
|
||||
const invalidate = useInvalidateFormTemplate();
|
||||
return useMutation({
|
||||
mutationFn: () => api.forms.regenerateToken(edition),
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
}
|
||||
|
||||
/** Types de paiement de la cotisation (COORDINATION) — voir `FormPaymentOptionRead`. */
|
||||
export function useFormPaymentOptions() {
|
||||
return useQuery({
|
||||
@@ -684,19 +702,26 @@ export function useDeleteFormPaymentOption() {
|
||||
}
|
||||
|
||||
/** URL objet (aperçu) du logo du formulaire public, ou `null` si absent.
|
||||
* Utilisable sans compte connecté (`GET .../form/logo` n'est pas protégé —
|
||||
* affiché sur `/inscription`). `refreshKey` (ex. `dataUpdatedAt` de
|
||||
* `useFormTemplate`) force un nouveau fetch après un changement de logo. */
|
||||
export function useFormLogoUrl(hasLogo: boolean, refreshKey: number | string) {
|
||||
* `GET .../form/logo` exige `token` (voir `_require_access_token` côté
|
||||
* back-end) — celui de l'édition pour l'écran de configuration
|
||||
* (COORDINATION, `template.access_token`), celui de l'URL pour la page
|
||||
* publique (`/inscription/:token`) ; `null` tant qu'il n'est pas connu.
|
||||
* `refreshKey` (ex. `dataUpdatedAt` de `useFormTemplate`) force un nouveau
|
||||
* fetch après un changement de logo. */
|
||||
export function useFormLogoUrl(
|
||||
hasLogo: boolean,
|
||||
refreshKey: number | string,
|
||||
token: string | null,
|
||||
) {
|
||||
const [url, setUrl] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
let objectUrl: string | null = null;
|
||||
let cancelled = false;
|
||||
if (!hasLogo) {
|
||||
if (!hasLogo || !token) {
|
||||
setUrl(null);
|
||||
return;
|
||||
}
|
||||
api.forms.logoBlob(edition).then((blob) => {
|
||||
api.forms.logoBlob(edition, token).then((blob) => {
|
||||
if (cancelled || !blob) return;
|
||||
objectUrl = URL.createObjectURL(blob);
|
||||
setUrl(objectUrl);
|
||||
@@ -705,22 +730,24 @@ export function useFormLogoUrl(hasLogo: boolean, refreshKey: number | string) {
|
||||
cancelled = true;
|
||||
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
||||
};
|
||||
}, [hasLogo, refreshKey]);
|
||||
}, [hasLogo, refreshKey, token]);
|
||||
return url;
|
||||
}
|
||||
|
||||
/** Configuration publique du formulaire — utilisable sans compte connecté
|
||||
* (écran `/inscription`, hors de `RequireAuth`). */
|
||||
export function usePublicFormConfig() {
|
||||
* (écran `/inscription/:token`, hors de `RequireAuth`). `token` vient de
|
||||
* l'URL (voir `PublicSignupPage`) ; requis par le back-end en plus de
|
||||
* l'édition (`_require_access_token`). */
|
||||
export function usePublicFormConfig(token: string) {
|
||||
return useQuery({
|
||||
queryKey: ['publicFormConfig', edition],
|
||||
queryFn: () => api.forms.getPublicConfig(edition),
|
||||
queryKey: ['publicFormConfig', edition, token],
|
||||
queryFn: () => api.forms.getPublicConfig(edition, token),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSubmitPublicForm() {
|
||||
export function useSubmitPublicForm(token: string) {
|
||||
return useMutation({
|
||||
mutationFn: (body: PublicFormSubmission) => api.forms.submitPublic(edition, body),
|
||||
mutationFn: (body: PublicFormSubmission) => api.forms.submitPublic(edition, token, body),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user