From 2e8298b3b6225de4b916090835e765f510f2b6a0 Mon Sep 17 00:00:00 2001 From: Youssef Date: Thu, 11 Jun 2026 23:11:34 +0200 Subject: [PATCH] Feature: Implement secure public pre-registration flow with CAPTCHA and HTMX dashboard - Created V16 Flyway migration for pre_inscription and token_pre_inscription tables. - Added PreInscription and TokenPreInscription entities with respective repositories. - Updated SecurityConfig to allow public access to /inscription-public/**. - Implemented CaptchaValidationService to interact with Cloudflare Turnstile API. - Created PublicInscriptionController for managing the QR code entry, CAPTCHA validation, ephemeral tokens, and public form submission. - Added public Thymeleaf views (demarrer, formulaire, succes, lien-expire) with TailwindCSS. - Developed AdminPreInscriptionController and PreInscriptionService to handle back-office validation and rejection workflows. - Built the admin pre-inscriptions dashboard list view. - Added a dynamic, polling notification badge in the sidebar using HTMX, auto-updating on validation/rejection events. --- .../astalange/core/entity/PreInscription.java | 79 ++++++++++++++ .../core/entity/TokenPreInscription.java | 40 +++++++ .../repository/PreInscriptionRepository.java | 13 +++ .../TokenPreInscriptionRepository.java | 12 +++ .../service/CaptchaValidationService.java | 50 +++++++++ .../core/service/PreInscriptionService.java | 66 ++++++++++++ .../V16__create_pre_inscription_tables.sql | 21 ++++ .../astalange/web/config/SecurityConfig.java | 2 +- .../AdminPreInscriptionController.java | 55 ++++++++++ .../PublicInscriptionController.java | 101 ++++++++++++++++++ .../templates/fragments/sidebar.html | 4 + .../templates/preinscriptions/list.html | 66 ++++++++++++ .../resources/templates/public/demarrer.html | 30 ++++++ .../templates/public/formulaire.html | 85 +++++++++++++++ .../templates/public/lien-expire.html | 24 +++++ .../resources/templates/public/succes.html | 22 ++++ 16 files changed, 669 insertions(+), 1 deletion(-) create mode 100644 as-talange-core/src/main/java/com/astalange/core/entity/PreInscription.java create mode 100644 as-talange-core/src/main/java/com/astalange/core/entity/TokenPreInscription.java create mode 100644 as-talange-core/src/main/java/com/astalange/core/repository/PreInscriptionRepository.java create mode 100644 as-talange-core/src/main/java/com/astalange/core/repository/TokenPreInscriptionRepository.java create mode 100644 as-talange-core/src/main/java/com/astalange/core/service/CaptchaValidationService.java create mode 100644 as-talange-core/src/main/java/com/astalange/core/service/PreInscriptionService.java create mode 100644 as-talange-core/src/main/resources/db/migration/V16__create_pre_inscription_tables.sql create mode 100644 as-talange-web/src/main/java/com/astalange/web/controller/AdminPreInscriptionController.java create mode 100644 as-talange-web/src/main/java/com/astalange/web/controller/PublicInscriptionController.java create mode 100644 as-talange-web/src/main/resources/templates/preinscriptions/list.html create mode 100644 as-talange-web/src/main/resources/templates/public/demarrer.html create mode 100644 as-talange-web/src/main/resources/templates/public/formulaire.html create mode 100644 as-talange-web/src/main/resources/templates/public/lien-expire.html create mode 100644 as-talange-web/src/main/resources/templates/public/succes.html diff --git a/as-talange-core/src/main/java/com/astalange/core/entity/PreInscription.java b/as-talange-core/src/main/java/com/astalange/core/entity/PreInscription.java new file mode 100644 index 0000000..2ab91e6 --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/entity/PreInscription.java @@ -0,0 +1,79 @@ +package com.astalange.core.entity; + +import jakarta.persistence.*; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Entity +@Table(name = "pre_inscription") +public class PreInscription { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 100) + private String nom; + + @Column(nullable = false, length = 100) + private String prenom; + + @Column(name = "date_naissance", nullable = false) + private LocalDate dateNaissance; + + @Column(length = 20) + private String telephone; + + @Column(length = 150) + private String email; + + @Column(columnDefinition = "TEXT") + private String adresse; + + @Column(name = "representant_legal", length = 150) + private String representantLegal; + + @Column(name = "statut_traite", nullable = false) + private Boolean statutTraite = false; + + @Column(name = "date_creation", nullable = false) + private LocalDateTime dateCreation = LocalDateTime.now(); + + @ManyToOne(optional = false, fetch = FetchType.LAZY) + @JoinColumn(name = "saison_id", nullable = false) + private Saison saison; + + // Getters and Setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getNom() { return nom; } + public void setNom(String nom) { this.nom = nom; } + + public String getPrenom() { return prenom; } + public void setPrenom(String prenom) { this.prenom = prenom; } + + public LocalDate getDateNaissance() { return dateNaissance; } + public void setDateNaissance(LocalDate dateNaissance) { this.dateNaissance = dateNaissance; } + + public String getTelephone() { return telephone; } + public void setTelephone(String telephone) { this.telephone = telephone; } + + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + + public String getAdresse() { return adresse; } + public void setAdresse(String adresse) { this.adresse = adresse; } + + public String getRepresentantLegal() { return representantLegal; } + public void setRepresentantLegal(String representantLegal) { this.representantLegal = representantLegal; } + + public Boolean getStatutTraite() { return statutTraite; } + public void setStatutTraite(Boolean statutTraite) { this.statutTraite = statutTraite; } + + public LocalDateTime getDateCreation() { return dateCreation; } + public void setDateCreation(LocalDateTime dateCreation) { this.dateCreation = dateCreation; } + + public Saison getSaison() { return saison; } + public void setSaison(Saison saison) { this.saison = saison; } +} diff --git a/as-talange-core/src/main/java/com/astalange/core/entity/TokenPreInscription.java b/as-talange-core/src/main/java/com/astalange/core/entity/TokenPreInscription.java new file mode 100644 index 0000000..86c6e4e --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/entity/TokenPreInscription.java @@ -0,0 +1,40 @@ +package com.astalange.core.entity; + +import jakarta.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "token_pre_inscription") +public class TokenPreInscription { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "valeur_uuid", nullable = false, unique = true, length = 36) + private String valeurUuid; + + @Column(name = "date_expiration", nullable = false) + private LocalDateTime dateExpiration; + + @Column(name = "est_utilise", nullable = false) + private Boolean estUtilise = false; + + // Getters and Setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getValeurUuid() { return valeurUuid; } + public void setValeurUuid(String valeurUuid) { this.valeurUuid = valeurUuid; } + + public LocalDateTime getDateExpiration() { return dateExpiration; } + public void setDateExpiration(LocalDateTime dateExpiration) { this.dateExpiration = dateExpiration; } + + public Boolean getEstUtilise() { return estUtilise; } + public void setEstUtilise(Boolean estUtilise) { this.estUtilise = estUtilise; } + + @Transient + public boolean isValide() { + return !estUtilise && dateExpiration.isAfter(LocalDateTime.now()); + } +} diff --git a/as-talange-core/src/main/java/com/astalange/core/repository/PreInscriptionRepository.java b/as-talange-core/src/main/java/com/astalange/core/repository/PreInscriptionRepository.java new file mode 100644 index 0000000..e57f472 --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/repository/PreInscriptionRepository.java @@ -0,0 +1,13 @@ +package com.astalange.core.repository; + +import com.astalange.core.entity.PreInscription; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface PreInscriptionRepository extends JpaRepository { + List findByStatutTraiteFalseOrderByDateCreationDesc(); + long countByStatutTraiteFalse(); +} diff --git a/as-talange-core/src/main/java/com/astalange/core/repository/TokenPreInscriptionRepository.java b/as-talange-core/src/main/java/com/astalange/core/repository/TokenPreInscriptionRepository.java new file mode 100644 index 0000000..e181513 --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/repository/TokenPreInscriptionRepository.java @@ -0,0 +1,12 @@ +package com.astalange.core.repository; + +import com.astalange.core.entity.TokenPreInscription; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface TokenPreInscriptionRepository extends JpaRepository { + Optional findByValeurUuid(String valeurUuid); +} diff --git a/as-talange-core/src/main/java/com/astalange/core/service/CaptchaValidationService.java b/as-talange-core/src/main/java/com/astalange/core/service/CaptchaValidationService.java new file mode 100644 index 0000000..1ad5a76 --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/service/CaptchaValidationService.java @@ -0,0 +1,50 @@ +package com.astalange.core.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.Map; + +@Service +public class CaptchaValidationService { + + @Value("${captcha.secret:dummy-secret}") + private String captchaSecret; + + @Value("${captcha.url:https://challenges.cloudflare.com/turnstile/v0/siteverify}") + private String captchaUrl; + + public boolean validateCaptcha(String token) { + if (token == null || token.isEmpty()) { + return false; + } + + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("secret", captchaSecret); + map.add("response", token); + + HttpEntity> request = new HttpEntity<>(map, headers); + + try { + ResponseEntity response = restTemplate.postForEntity(captchaUrl, request, Map.class); + if (response.getBody() != null && Boolean.TRUE.equals(response.getBody().get("success"))) { + return true; + } + } catch (Exception e) { + // Log error + return false; + } + return false; + } +} diff --git a/as-talange-core/src/main/java/com/astalange/core/service/PreInscriptionService.java b/as-talange-core/src/main/java/com/astalange/core/service/PreInscriptionService.java new file mode 100644 index 0000000..458308d --- /dev/null +++ b/as-talange-core/src/main/java/com/astalange/core/service/PreInscriptionService.java @@ -0,0 +1,66 @@ +package com.astalange.core.service; + +import com.astalange.core.entity.Adherent; +import com.astalange.core.entity.Licence; +import com.astalange.core.entity.PreInscription; +import com.astalange.core.repository.AdherentRepository; +import com.astalange.core.repository.LicenceRepository; +import com.astalange.core.repository.PreInscriptionRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class PreInscriptionService { + + private final PreInscriptionRepository preInscriptionRepository; + private final AdherentRepository adherentRepository; + private final LicenceRepository licenceRepository; + + public PreInscriptionService(PreInscriptionRepository preInscriptionRepository, + AdherentRepository adherentRepository, + LicenceRepository licenceRepository) { + this.preInscriptionRepository = preInscriptionRepository; + this.adherentRepository = adherentRepository; + this.licenceRepository = licenceRepository; + } + + @Transactional + public Adherent validerPreInscription(Long preInscriptionId, Long categorieId) { + PreInscription pre = preInscriptionRepository.findById(preInscriptionId) + .orElseThrow(() -> new IllegalArgumentException("Pré-inscription introuvable")); + + if (pre.getStatutTraite()) { + throw new IllegalStateException("Cette pré-inscription a déjà été traitée"); + } + + // Créer l'adhérent + Adherent adherent = new Adherent(); + adherent.setNom(pre.getNom().toUpperCase()); + adherent.setPrenom(pre.getPrenom()); + adherent.setDateNaissance(pre.getDateNaissance()); + adherent.setTelephone(pre.getTelephone()); + adherent.setEmail(pre.getEmail()); + adherent.setAdresse(pre.getAdresse()); + adherent.setRepresentantLegal(pre.getRepresentantLegal()); + // Par défaut + adherent.setSexe("MASCULIN"); + adherent.setTypeMaillot("JOUEUR"); + adherent.setResidentTalange(false); + + adherent = adherentRepository.save(adherent); + + pre.setStatutTraite(true); + preInscriptionRepository.save(pre); + + return adherent; + } + + @Transactional + public void rejeterPreInscription(Long preInscriptionId) { + PreInscription pre = preInscriptionRepository.findById(preInscriptionId) + .orElseThrow(() -> new IllegalArgumentException("Pré-inscription introuvable")); + + pre.setStatutTraite(true); + preInscriptionRepository.save(pre); + } +} diff --git a/as-talange-core/src/main/resources/db/migration/V16__create_pre_inscription_tables.sql b/as-talange-core/src/main/resources/db/migration/V16__create_pre_inscription_tables.sql new file mode 100644 index 0000000..84c6050 --- /dev/null +++ b/as-talange-core/src/main/resources/db/migration/V16__create_pre_inscription_tables.sql @@ -0,0 +1,21 @@ +CREATE TABLE token_pre_inscription ( + id BIGSERIAL PRIMARY KEY, + valeur_uuid VARCHAR(36) NOT NULL UNIQUE, + date_expiration TIMESTAMP NOT NULL, + est_utilise BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE pre_inscription ( + id BIGSERIAL PRIMARY KEY, + nom VARCHAR(100) NOT NULL, + prenom VARCHAR(100) NOT NULL, + date_naissance DATE NOT NULL, + telephone VARCHAR(20), + email VARCHAR(150), + adresse TEXT, + representant_legal VARCHAR(150), + statut_traite BOOLEAN NOT NULL DEFAULT FALSE, + date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + saison_id BIGINT NOT NULL, + CONSTRAINT fk_pre_inscription_saison FOREIGN KEY (saison_id) REFERENCES saison (id) +); diff --git a/as-talange-web/src/main/java/com/astalange/web/config/SecurityConfig.java b/as-talange-web/src/main/java/com/astalange/web/config/SecurityConfig.java index 6632cff..09f0191 100644 --- a/as-talange-web/src/main/java/com/astalange/web/config/SecurityConfig.java +++ b/as-talange-web/src/main/java/com/astalange/web/config/SecurityConfig.java @@ -25,7 +25,7 @@ public class SecurityConfig { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth - .requestMatchers("/css/**", "/js/**", "/images/**").permitAll() + .requestMatchers("/css/**", "/js/**", "/images/**", "/inscription-public/**").permitAll() .requestMatchers("/change-password").authenticated() .requestMatchers("/utilisateurs/**", "/saisons/**", "/categories/**", "/equipes/**", "/modespaiement/**", "/equipements/**").hasRole("ADMIN") .requestMatchers("/paiements/**", "/paiement/**").hasAnyRole("ADMIN", "TRESORERIE") diff --git a/as-talange-web/src/main/java/com/astalange/web/controller/AdminPreInscriptionController.java b/as-talange-web/src/main/java/com/astalange/web/controller/AdminPreInscriptionController.java new file mode 100644 index 0000000..0b84eec --- /dev/null +++ b/as-talange-web/src/main/java/com/astalange/web/controller/AdminPreInscriptionController.java @@ -0,0 +1,55 @@ +package com.astalange.web.controller; + +import com.astalange.core.entity.Adherent; +import com.astalange.core.repository.PreInscriptionRepository; +import com.astalange.core.service.PreInscriptionService; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +@Controller +@RequestMapping("/admin/pre-inscriptions") +public class AdminPreInscriptionController { + + private final PreInscriptionRepository preInscriptionRepository; + private final PreInscriptionService preInscriptionService; + + public AdminPreInscriptionController(PreInscriptionRepository preInscriptionRepository, + PreInscriptionService preInscriptionService) { + this.preInscriptionRepository = preInscriptionRepository; + this.preInscriptionService = preInscriptionService; + } + + @GetMapping + public String listPreInscriptions(Model model) { + model.addAttribute("preInscriptions", preInscriptionRepository.findByStatutTraiteFalseOrderByDateCreationDesc()); + return "preinscriptions/list"; + } + + @PostMapping("/{id}/valider") + public String valider(@PathVariable Long id, HttpServletResponse response) { + Adherent adherent = preInscriptionService.validerPreInscription(id, null); + response.setHeader("HX-Trigger", "refreshBadge"); + // En HTMX, on renvoie une ligne vide pour la faire disparaître, ou on redirige via HX-Redirect + // Mais comme on veut aller sur la page de l'adhérent pour finaliser la licence : + response.setHeader("HX-Redirect", "/adherents/" + adherent.getId() + "/edit"); + return ""; + } + + @PostMapping("/{id}/rejeter") + @ResponseBody + public String rejeter(@PathVariable Long id, HttpServletResponse response) { + preInscriptionService.rejeterPreInscription(id); + response.setHeader("HX-Trigger", "refreshBadge"); + return ""; // HTMX target la ligne avec outerHTML -> supprime la ligne + } + + @GetMapping("/count") + @ResponseBody + public String countBadge() { + long count = preInscriptionRepository.countByStatutTraiteFalse(); + if (count == 0) return ""; + return "" + count + ""; + } +} diff --git a/as-talange-web/src/main/java/com/astalange/web/controller/PublicInscriptionController.java b/as-talange-web/src/main/java/com/astalange/web/controller/PublicInscriptionController.java new file mode 100644 index 0000000..4174c95 --- /dev/null +++ b/as-talange-web/src/main/java/com/astalange/web/controller/PublicInscriptionController.java @@ -0,0 +1,101 @@ +package com.astalange.web.controller; + +import com.astalange.core.entity.PreInscription; +import com.astalange.core.entity.TokenPreInscription; +import com.astalange.core.entity.Saison; +import com.astalange.core.repository.PreInscriptionRepository; +import com.astalange.core.repository.TokenPreInscriptionRepository; +import com.astalange.core.repository.SaisonRepository; +import com.astalange.core.service.CaptchaValidationService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Controller +@RequestMapping("/inscription-public") +public class PublicInscriptionController { + + private final CaptchaValidationService captchaValidationService; + private final TokenPreInscriptionRepository tokenRepository; + private final PreInscriptionRepository preInscriptionRepository; + private final SaisonRepository saisonRepository; + + public PublicInscriptionController(CaptchaValidationService captchaValidationService, + TokenPreInscriptionRepository tokenRepository, + PreInscriptionRepository preInscriptionRepository, + SaisonRepository saisonRepository) { + this.captchaValidationService = captchaValidationService; + this.tokenRepository = tokenRepository; + this.preInscriptionRepository = preInscriptionRepository; + this.saisonRepository = saisonRepository; + } + + @GetMapping("/demarrer") + public String showDemarrer() { + return "public/demarrer"; + } + + @PostMapping("/demarrer") + public String processDemarrer(@RequestParam("cf-turnstile-response") String captchaResponse, RedirectAttributes redirectAttributes) { + // En vrai, captchaResponse dépend du fournisseur. Cloudflare Turnstile = cf-turnstile-response + if (!captchaValidationService.validateCaptcha(captchaResponse)) { + redirectAttributes.addFlashAttribute("error", "Validation CAPTCHA échouée. Veuillez réessayer."); + return "redirect:/inscription-public/demarrer"; + } + + TokenPreInscription token = new TokenPreInscription(); + token.setValeurUuid(UUID.randomUUID().toString()); + token.setDateExpiration(LocalDateTime.now().plusMinutes(10)); + tokenRepository.save(token); + + return "redirect:/inscription-public/formulaire?token=" + token.getValeurUuid(); + } + + @GetMapping("/formulaire") + public String showFormulaire(@RequestParam("token") String tokenUuid, Model model) { + TokenPreInscription token = tokenRepository.findByValeurUuid(tokenUuid).orElse(null); + if (token == null || !token.isValide()) { + return "public/lien-expire"; + } + + model.addAttribute("preInscription", new PreInscription()); + model.addAttribute("tokenUuid", tokenUuid); + return "public/formulaire"; + } + + @PostMapping("/formulaire") + public String processFormulaire(@RequestParam("token") String tokenUuid, + @ModelAttribute("preInscription") PreInscription preInscription, + RedirectAttributes redirectAttributes) { + TokenPreInscription token = tokenRepository.findByValeurUuid(tokenUuid).orElse(null); + if (token == null || !token.isValide()) { + return "public/lien-expire"; + } + + Saison activeSaison = saisonRepository.findByEstActiveTrue().orElse(null); + if (activeSaison == null) { + redirectAttributes.addFlashAttribute("error", "Les inscriptions sont actuellement fermées."); + return "redirect:/inscription-public/demarrer"; + } + + // Sauvegarde de la pré-inscription + preInscription.setSaison(activeSaison); + preInscription.setStatutTraite(false); + preInscriptionRepository.save(preInscription); + + // Invalidation du token + token.setEstUtilise(true); + tokenRepository.save(token); + + return "redirect:/inscription-public/succes"; + } + + @GetMapping("/succes") + public String showSucces() { + return "public/succes"; + } +} diff --git a/as-talange-web/src/main/resources/templates/fragments/sidebar.html b/as-talange-web/src/main/resources/templates/fragments/sidebar.html index 2b24ec1..2d14b61 100644 --- a/as-talange-web/src/main/resources/templates/fragments/sidebar.html +++ b/as-talange-web/src/main/resources/templates/fragments/sidebar.html @@ -12,6 +12,10 @@ Tableau de bord Adhérents + + Pré-inscriptions + + Paiements diff --git a/as-talange-web/src/main/resources/templates/preinscriptions/list.html b/as-talange-web/src/main/resources/templates/preinscriptions/list.html new file mode 100644 index 0000000..fb60d2b --- /dev/null +++ b/as-talange-web/src/main/resources/templates/preinscriptions/list.html @@ -0,0 +1,66 @@ + + + + + Pré-inscriptions - AS Talange + + + + + + + +
+ +
+
+

Dossiers en attente

+
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
DateAdhérentContactActions
Aucune pré-inscription en attente.
+
+
+
+
+
+
+
+ + +
+
+
+
+ + diff --git a/as-talange-web/src/main/resources/templates/public/demarrer.html b/as-talange-web/src/main/resources/templates/public/demarrer.html new file mode 100644 index 0000000..d77eec0 --- /dev/null +++ b/as-talange-web/src/main/resources/templates/public/demarrer.html @@ -0,0 +1,30 @@ + + + + + AS Talange - Pré-inscription + + + + + + +
+ AS Talange +

Inscription au club

+

Veuillez valider le captcha pour accéder au formulaire sécurisé.

+ +
+
Les inscriptions sont actuellement fermées.
+ +
+ +
+ + +
+
+ + diff --git a/as-talange-web/src/main/resources/templates/public/formulaire.html b/as-talange-web/src/main/resources/templates/public/formulaire.html new file mode 100644 index 0000000..8d2c83d --- /dev/null +++ b/as-talange-web/src/main/resources/templates/public/formulaire.html @@ -0,0 +1,85 @@ + + + + + AS Talange - Formulaire d'inscription + + + + + +
+

Formulaire de pré-inscription

+

Veuillez remplir les informations concernant le futur licencié.

+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ + + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ + +
+
+ + + + diff --git a/as-talange-web/src/main/resources/templates/public/lien-expire.html b/as-talange-web/src/main/resources/templates/public/lien-expire.html new file mode 100644 index 0000000..74e008f --- /dev/null +++ b/as-talange-web/src/main/resources/templates/public/lien-expire.html @@ -0,0 +1,24 @@ + + + + + Lien expiré + + + + + +
+
+ + + +
+

Lien invalide ou expiré

+

Votre session a expiré ou le lien utilisé est invalide. Veuillez recommencer la procédure depuis le début.

+ + Recommencer + +
+ + diff --git a/as-talange-web/src/main/resources/templates/public/succes.html b/as-talange-web/src/main/resources/templates/public/succes.html new file mode 100644 index 0000000..f042cb1 --- /dev/null +++ b/as-talange-web/src/main/resources/templates/public/succes.html @@ -0,0 +1,22 @@ + + + + + Inscription réussie + + + + + +
+
+ + + +
+

Pré-inscription envoyée !

+

Votre dossier a bien été transmis au secrétariat de l'AS Talange.

+

Un responsable va traiter votre demande prochainement. Vous pouvez fermer cette page.

+
+ +