Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f93d6eafb3 | |||
| e670e38343 | |||
| fe3a1ec36c | |||
| 44ae524626 | |||
| 829a69f1a5 | |||
| f8336fb407 | |||
| ab1094d29a | |||
| 3477461fb2 | |||
| bb48f919b0 | |||
| 5f2bf61829 | |||
| 2df3e792f1 | |||
| 8ecdc54028 |
@@ -58,3 +58,20 @@ jobs:
|
||||
echo "CAPTCHA_SITEKEY=${CAPTCHA_SITEKEY:-1x00000000000000000000AA}" >> .env
|
||||
docker compose down app || true
|
||||
docker compose up -d --build app
|
||||
|
||||
- name: Verify application startup
|
||||
run: |
|
||||
echo "Waiting for application to start..."
|
||||
COUNT=0
|
||||
while [ $COUNT -lt 30 ]; do
|
||||
if docker exec astalange_app wget -qO- http://localhost:8080/login | grep -q "Connexion"; then
|
||||
echo "Application is up and login page is accessible!"
|
||||
exit 0
|
||||
fi
|
||||
echo "Waiting... ($COUNT/30)"
|
||||
sleep 5
|
||||
COUNT=$((COUNT+1))
|
||||
done
|
||||
echo "Application failed to start or login page is not accessible."
|
||||
docker logs astalange_app
|
||||
exit 1
|
||||
|
||||
@@ -50,7 +50,24 @@ jobs:
|
||||
run: |
|
||||
echo "POSTGRES_USER=${DB_USER:-myuser}" > .env
|
||||
echo "POSTGRES_PASSWORD=${DB_PASSWORD:-mypassword}" >> .env
|
||||
echo "CAPTCHA_SECRET=1x0000000000000000000000000000000AA" >> .env
|
||||
echo "CAPTCHA_SITEKEY=1x00000000000000000000AA" >> .env
|
||||
docker compose down app || true
|
||||
docker compose up -d --build app
|
||||
|
||||
|
||||
- name: Verify application startup
|
||||
run: |
|
||||
echo "Waiting for application to start..."
|
||||
COUNT=0
|
||||
while [ $COUNT -lt 30 ]; do
|
||||
if docker exec astalange_app wget -qO- http://localhost:8080/login | grep -q "Connexion"; then
|
||||
echo "Application is up and login page is accessible!"
|
||||
exit 0
|
||||
fi
|
||||
echo "Waiting... ($COUNT/30)"
|
||||
sleep 5
|
||||
COUNT=$((COUNT+1))
|
||||
done
|
||||
echo "Application failed to start or login page is not accessible."
|
||||
docker logs astalange_app
|
||||
exit 1
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>as-talange-parent</artifactId>
|
||||
<groupId>com.astalange</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<version>1.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>as-talange-parent</artifactId>
|
||||
<groupId>com.astalange</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<version>1.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -41,6 +41,12 @@ public class Adherent {
|
||||
@Column(name = "representant_legal", length = 150)
|
||||
private String representantLegal;
|
||||
|
||||
@Column(name = "type_demande", length = 50)
|
||||
private String typeDemande;
|
||||
|
||||
@Column(name = "ancien_club", length = 150)
|
||||
private String ancienClub;
|
||||
|
||||
private boolean residentTalange = true;
|
||||
|
||||
@Column(nullable = false, length = 10)
|
||||
@@ -87,6 +93,12 @@ public class Adherent {
|
||||
public String getRepresentantLegal() { return representantLegal; }
|
||||
public void setRepresentantLegal(String representantLegal) { this.representantLegal = representantLegal; }
|
||||
|
||||
public String getTypeDemande() { return typeDemande; }
|
||||
public void setTypeDemande(String typeDemande) { this.typeDemande = typeDemande; }
|
||||
|
||||
public String getAncienClub() { return ancienClub; }
|
||||
public void setAncienClub(String ancienClub) { this.ancienClub = ancienClub; }
|
||||
|
||||
public boolean isResidentTalange() { return residentTalange; }
|
||||
public void setResidentTalange(boolean residentTalange) { this.residentTalange = residentTalange; }
|
||||
|
||||
|
||||
@@ -84,4 +84,33 @@ public class Categorie {
|
||||
.map(CategorieEquipement::getPrix)
|
||||
.orElse(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Transient
|
||||
public BigDecimal getEquipementPrixByNom(String nom) {
|
||||
if (categorieEquipements == null || nom == null) return BigDecimal.ZERO;
|
||||
return categorieEquipements.stream()
|
||||
.filter(ce -> ce.getEquipement() != null && nom.equalsIgnoreCase(ce.getEquipement().getNom()))
|
||||
.findFirst()
|
||||
.map(CategorieEquipement::getPrix)
|
||||
.map(p -> p == null ? BigDecimal.ZERO : p)
|
||||
.orElse(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
@Transient
|
||||
public boolean isU11OrBelow() {
|
||||
if (nom == null) return false;
|
||||
String nomCat = nom.trim().toUpperCase();
|
||||
if (nomCat.startsWith("U")) {
|
||||
java.util.regex.Matcher m = java.util.regex.Pattern.compile("^U(\\d+)").matcher(nomCat);
|
||||
if (m.find()) {
|
||||
try {
|
||||
int age = Integer.parseInt(m.group(1));
|
||||
return age <= 11;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ public class PreInscription {
|
||||
@Column(name = "consentement_rgpd", nullable = false)
|
||||
private Boolean consentementRgpd = false;
|
||||
|
||||
@Column(name = "type_demande", nullable = false, length = 50)
|
||||
private String typeDemande = "NOUVELLE";
|
||||
|
||||
@Column(name = "ancien_club", length = 150)
|
||||
private String ancienClub;
|
||||
|
||||
// Getters and Setters
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
@@ -94,4 +100,10 @@ public class PreInscription {
|
||||
|
||||
public Boolean getConsentementRgpd() { return consentementRgpd; }
|
||||
public void setConsentementRgpd(Boolean consentementRgpd) { this.consentementRgpd = consentementRgpd; }
|
||||
|
||||
public String getTypeDemande() { return typeDemande; }
|
||||
public void setTypeDemande(String typeDemande) { this.typeDemande = typeDemande; }
|
||||
|
||||
public String getAncienClub() { return ancienClub; }
|
||||
public void setAncienClub(String ancienClub) { this.ancienClub = ancienClub; }
|
||||
}
|
||||
|
||||
@@ -7,7 +7,13 @@ import org.springframework.stereotype.Repository;
|
||||
import com.astalange.core.entity.Saison;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
@Repository
|
||||
public interface CategorieRepository extends JpaRepository<Categorie, Long> {
|
||||
List<Categorie> findBySaison(Saison saison);
|
||||
|
||||
@Query("SELECT c FROM Categorie c WHERE c.saison = :saison AND :annee BETWEEN c.anneeMin AND c.anneeMax")
|
||||
List<Categorie> findBySaisonAndAnnee(@Param("saison") Saison saison, @Param("annee") Integer annee);
|
||||
}
|
||||
|
||||
@@ -117,8 +117,21 @@ public class CategorieService {
|
||||
Dotation newDotation = new Dotation();
|
||||
newDotation.setLicence(licence);
|
||||
newDotation.setEquipement(ce.getEquipement());
|
||||
newDotation.setChoisi(ce.getObligatoire()); // By default checked if obligatoire
|
||||
boolean isMaillot = "Maillot".equalsIgnoreCase(ce.getEquipement().getNom());
|
||||
boolean isNouvelleAndU11 = false;
|
||||
if (licence.getTypeDemande() != null &&
|
||||
(licence.getTypeDemande().equalsIgnoreCase("Nouvelle demande") || licence.getTypeDemande().equalsIgnoreCase("NOUVELLE")) &&
|
||||
licence.getCategorie() != null &&
|
||||
licence.getCategorie().isU11OrBelow()) {
|
||||
isNouvelleAndU11 = true;
|
||||
}
|
||||
newDotation.setChoisi(ce.getObligatoire() || (isMaillot && isNouvelleAndU11)); // Checked if obligatoire or if it's a new registration for U11 or below
|
||||
newDotation.setFourni(false);
|
||||
newDotation.setTaille("");
|
||||
newDotation.setNumero("");
|
||||
if (licence.getAdherent() != null) {
|
||||
newDotation.setFlocage(licence.getAdherent().getNom().toUpperCase() + " " + licence.getAdherent().getPrenom());
|
||||
}
|
||||
currentDotations.add(newDotation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ public class PreInscriptionService {
|
||||
adherent.setEmail(pre.getEmail());
|
||||
adherent.setTelephone(pre.getTelephone());
|
||||
adherent.setRepresentantLegal(pre.getRepresentantLegal());
|
||||
adherent.setTypeDemande(pre.getTypeDemande());
|
||||
adherent.setAncienClub(pre.getAncienClub());
|
||||
// Par défaut
|
||||
adherent.setSexe("MASCULIN");
|
||||
adherent.setTypeMaillot("JOUEUR");
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE pre_inscription
|
||||
ADD COLUMN type_demande VARCHAR(50) DEFAULT 'NOUVELLE' NOT NULL,
|
||||
ADD COLUMN ancien_club VARCHAR(150);
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE adherent
|
||||
ADD COLUMN type_demande VARCHAR(50),
|
||||
ADD COLUMN ancien_club VARCHAR(150);
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>as-talange-parent</artifactId>
|
||||
<groupId>com.astalange</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<version>1.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
package com.astalange.web.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
|
||||
@ControllerAdvice
|
||||
public class GlobalControllerAdvice {
|
||||
|
||||
@Value("${app.version}")
|
||||
private String appVersion;
|
||||
|
||||
@ModelAttribute("requestURI")
|
||||
public String requestURI(final HttpServletRequest request) {
|
||||
return request.getRequestURI();
|
||||
}
|
||||
|
||||
@ModelAttribute("appVersion")
|
||||
public String appVersion() {
|
||||
return appVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,21 +82,8 @@ public class LicenceController {
|
||||
|
||||
licence = licenceRepository.save(licence);
|
||||
|
||||
// Auto-initialize dotations for all configured equipments of the category
|
||||
List<CategorieEquipement> catEquipements = categorie.getCategorieEquipements();
|
||||
if (catEquipements != null) {
|
||||
for (CategorieEquipement ce : catEquipements) {
|
||||
Dotation dotation = new Dotation();
|
||||
dotation.setLicence(licence);
|
||||
dotation.setEquipement(ce.getEquipement());
|
||||
dotation.setTaille("");
|
||||
dotation.setFlocage(adherent.getNom().toUpperCase() + " " + adherent.getPrenom());
|
||||
dotation.setNumero("");
|
||||
dotation.setFourni(false);
|
||||
dotation.setChoisi(Boolean.TRUE.equals(ce.getObligatoire()));
|
||||
dotationRepository.save(dotation);
|
||||
}
|
||||
}
|
||||
// Auto-initialize dotations via CategorieService to ensure consistency
|
||||
categorieService.syncDotationsForLicence(licence);
|
||||
|
||||
return "redirect:/adherents/" + id + "/edit";
|
||||
}
|
||||
|
||||
+70
-2
@@ -6,6 +6,8 @@ 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.repository.CategorieRepository;
|
||||
import com.astalange.core.entity.Categorie;
|
||||
import com.astalange.core.service.CaptchaValidationService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@@ -14,7 +16,10 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/inscription-public")
|
||||
@@ -24,15 +29,18 @@ public class PublicInscriptionController {
|
||||
private final TokenPreInscriptionRepository tokenRepository;
|
||||
private final PreInscriptionRepository preInscriptionRepository;
|
||||
private final SaisonRepository saisonRepository;
|
||||
private final CategorieRepository categorieRepository;
|
||||
|
||||
public PublicInscriptionController(CaptchaValidationService captchaValidationService,
|
||||
TokenPreInscriptionRepository tokenRepository,
|
||||
PreInscriptionRepository preInscriptionRepository,
|
||||
SaisonRepository saisonRepository) {
|
||||
SaisonRepository saisonRepository,
|
||||
CategorieRepository categorieRepository) {
|
||||
this.captchaValidationService = captchaValidationService;
|
||||
this.tokenRepository = tokenRepository;
|
||||
this.preInscriptionRepository = preInscriptionRepository;
|
||||
this.saisonRepository = saisonRepository;
|
||||
this.categorieRepository = categorieRepository;
|
||||
}
|
||||
|
||||
@Value("${captcha.sitekey:1x00000000000000000000AA}")
|
||||
@@ -66,7 +74,7 @@ public class PublicInscriptionController {
|
||||
|
||||
TokenPreInscription token = new TokenPreInscription();
|
||||
token.setValeurUuid(UUID.randomUUID().toString());
|
||||
token.setDateExpiration(LocalDateTime.now().plusMinutes(5));
|
||||
token.setDateExpiration(LocalDateTime.now().plusMinutes(15));
|
||||
tokenRepository.save(token);
|
||||
|
||||
return "redirect:/inscription-public/formulaire?token=" + token.getValeurUuid();
|
||||
@@ -117,9 +125,69 @@ public class PublicInscriptionController {
|
||||
token.setEstUtilise(true);
|
||||
tokenRepository.save(token);
|
||||
|
||||
if ("NOUVELLE".equals(preInscription.getTypeDemande())) {
|
||||
String tarifStr = "210 €"; // Default
|
||||
if (preInscription.getDateNaissance() != null) {
|
||||
int annee = preInscription.getDateNaissance().getYear();
|
||||
List<Categorie> categories = categorieRepository.findBySaisonAndAnnee(activeSaison, annee);
|
||||
if (!categories.isEmpty()) {
|
||||
Categorie cat = categories.get(0);
|
||||
if (cat.getTarifBase() != null) {
|
||||
BigDecimal prixMaillot = BigDecimal.ZERO;
|
||||
if (cat.isU11OrBelow()) {
|
||||
prixMaillot = cat.getEquipementPrixByNom("Maillot");
|
||||
}
|
||||
BigDecimal tarifTotal = cat.getTarifBase().add(prixMaillot);
|
||||
tarifStr = tarifTotal.intValue() + " €";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redirectAttributes.addFlashAttribute("prenom", preInscription.getPrenom());
|
||||
redirectAttributes.addFlashAttribute("tarif", tarifStr);
|
||||
redirectAttributes.addFlashAttribute("documents", java.util.Arrays.asList(
|
||||
"Photo d’identité",
|
||||
"Carte d’identité ou passeport de l'adhérent",
|
||||
"Livret de famille (si pas de pièce d’identité)",
|
||||
"Adresse e-mail de l'adhérent (représentant légal en cas de mineur)",
|
||||
"Pour les adhérents nés à l'étranger : Passeport + justificatif de domicile (du représentant légal en cas de mineur)"
|
||||
));
|
||||
redirectAttributes.addFlashAttribute("isNouvelle", true);
|
||||
}
|
||||
|
||||
return "redirect:/inscription-public/succes";
|
||||
}
|
||||
|
||||
@GetMapping("/api/tarif")
|
||||
@ResponseBody
|
||||
public String getTarif(@RequestParam(value = "dateNaissance", required = false) String dateNaissanceStr) {
|
||||
if (dateNaissanceStr == null || dateNaissanceStr.isEmpty()) {
|
||||
return "210 €";
|
||||
}
|
||||
try {
|
||||
LocalDate dateNaissance = LocalDate.parse(dateNaissanceStr);
|
||||
int annee = dateNaissance.getYear();
|
||||
Saison activeSaison = saisonRepository.findByEstActiveTrue().orElse(null);
|
||||
if (activeSaison == null) return "210 €";
|
||||
|
||||
List<Categorie> categories = categorieRepository.findBySaisonAndAnnee(activeSaison, annee);
|
||||
if (categories.isEmpty()) return "210 €";
|
||||
|
||||
Categorie cat = categories.get(0);
|
||||
if (cat.getTarifBase() != null) {
|
||||
BigDecimal prixMaillot = BigDecimal.ZERO;
|
||||
if (cat.isU11OrBelow()) {
|
||||
prixMaillot = cat.getEquipementPrixByNom("Maillot");
|
||||
}
|
||||
BigDecimal tarifTotal = cat.getTarifBase().add(prixMaillot);
|
||||
return tarifTotal.intValue() + " €";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Return default on error
|
||||
}
|
||||
return "210 €";
|
||||
}
|
||||
|
||||
@GetMapping("/succes")
|
||||
public String showSucces() {
|
||||
return "public/succes";
|
||||
|
||||
@@ -20,3 +20,6 @@ spring:
|
||||
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
app:
|
||||
version: @project.version@
|
||||
|
||||
@@ -148,6 +148,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
|
||||
<!-- Type de demande (Read-only) -->
|
||||
<div>
|
||||
<label for="typeDemande" class="block text-sm font-medium text-gray-700 mb-1">Type de demande (Pré-inscription)</label>
|
||||
<input type="text" id="typeDemande" th:field="*{typeDemande}" readonly
|
||||
class="w-full border border-gray-200 bg-gray-50 rounded-lg px-4 py-2 text-sm text-gray-500 outline-none cursor-not-allowed">
|
||||
</div>
|
||||
|
||||
<!-- Ancien club -->
|
||||
<div>
|
||||
<label for="ancienClub" class="block text-sm font-medium text-gray-700 mb-1">Ancien club</label>
|
||||
<input type="text" id="ancienClub" th:field="*{ancienClub}"
|
||||
class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-colors"
|
||||
th:classappend="${#fields.hasErrors('ancienClub')} ? 'border-red-500' : ''">
|
||||
<p th:if="${#fields.hasErrors('ancienClub')}" th:errors="*{ancienClub}" class="mt-1 text-xs text-red-600"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Removed Adresse -->
|
||||
|
||||
<div class="pt-4 flex justify-end space-x-3 border-t border-gray-100">
|
||||
@@ -549,7 +567,18 @@
|
||||
form.action = '/adherents/' + adherentId + '/licences';
|
||||
document.getElementById('numeroLicenceInput').value = '';
|
||||
document.getElementById('etatSelect').value = 'Brouillon';
|
||||
document.getElementById('typeDemandeSelect').value = '';
|
||||
|
||||
// Pré-remplissage du type de demande
|
||||
const adherentTypeDemande = document.getElementById('typeDemande') ? document.getElementById('typeDemande').value : '';
|
||||
const typeDemandeSelect = document.getElementById('typeDemandeSelect');
|
||||
if (adherentTypeDemande === 'NOUVELLE') {
|
||||
typeDemandeSelect.value = 'Nouvelle demande';
|
||||
} else if (adherentTypeDemande === 'RENOUVELLEMENT') {
|
||||
typeDemandeSelect.value = 'Renouvellement';
|
||||
} else {
|
||||
typeDemandeSelect.value = '';
|
||||
}
|
||||
|
||||
document.getElementById('typeLicenceSelect').value = '';
|
||||
|
||||
// Pré-sélection de la catégorie selon l'année de naissance
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
<form th:action="@{/logout}" method="post">
|
||||
<button type="submit" class="text-sm text-red-600 hover:text-red-700 font-medium">Déconnexion</button>
|
||||
</form>
|
||||
<div class="mt-2 text-[10px] text-gray-400">v<span th:text="${appVersion}">1.0-SNAPSHOT</span></div>
|
||||
</div>
|
||||
</aside>
|
||||
</body>
|
||||
|
||||
@@ -6,7 +6,17 @@
|
||||
<title>AS Talange - Formulaire d'inscription</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style> body { font-family: 'Inter', sans-serif; } </style>
|
||||
<style>
|
||||
body { font-family: 'Inter', sans-serif; }
|
||||
.was-validated input:invalid, .was-validated select:invalid {
|
||||
border-color: #ef4444 !important;
|
||||
background-color: #fef2f2 !important;
|
||||
}
|
||||
.was-validated input[type="radio"]:invalid, .was-validated input[type="checkbox"]:invalid {
|
||||
outline: 2px solid #ef4444;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 flex items-center justify-center min-h-screen py-12">
|
||||
<div class="max-w-xl w-full bg-white rounded-xl shadow-md p-8 border border-gray-100">
|
||||
@@ -14,9 +24,50 @@
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-2 text-center">Formulaire de pré-inscription</h2>
|
||||
<p class="text-sm text-gray-500 mb-8 text-center">Veuillez remplir les informations concernant le futur licencié.</p>
|
||||
|
||||
<form th:action="@{/inscription-public/formulaire}" th:object="${preInscription}" method="post" class="space-y-6">
|
||||
<div class="mb-6 p-4 bg-blue-50 border-l-4 border-blue-500 text-blue-700 text-sm rounded">
|
||||
<h3 class="font-bold mb-1">Information sur le type de demande</h3>
|
||||
<ul class="list-disc pl-5 space-y-1">
|
||||
<li><strong>Nouvelle licence :</strong> Concerne les personnes qui n'étaient pas inscrites au club la saison précédente.</li>
|
||||
<li><strong>Renouvellement :</strong> Réservé aux personnes déjà inscrites au club la saison précédente.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<form th:action="@{/inscription-public/formulaire}" th:object="${preInscription}" method="post" class="space-y-6" novalidate>
|
||||
<input type="hidden" name="token" th:value="${tokenUuid}" />
|
||||
|
||||
<div class="space-y-3">
|
||||
<label class="block text-sm font-medium text-gray-700">Type de demande <span class="text-red-500">*</span></label>
|
||||
<div class="flex items-center space-x-6">
|
||||
<label class="flex items-center">
|
||||
<input type="radio" th:field="*{typeDemande}" value="NOUVELLE" required class="w-4 h-4 text-blue-600 border-gray-300 focus:ring-blue-500">
|
||||
<span class="ml-2 text-sm text-gray-700">Nouvelle licence</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input type="radio" th:field="*{typeDemande}" value="RENOUVELLEMENT" required class="w-4 h-4 text-blue-600 border-gray-300 focus:ring-blue-500">
|
||||
<span class="ml-2 text-sm text-gray-700">Renouvellement</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="nouvelleLicenceBlock" class="hidden space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Ancien club</label>
|
||||
<input type="text" th:field="*{ancienClub}" class="w-full border border-gray-300 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none" placeholder="Nom du club (laisser vide si aucun)">
|
||||
</div>
|
||||
|
||||
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 rounded-lg">
|
||||
<h4 class="font-bold text-yellow-800 mb-2">Documents et informations à fournir</h4>
|
||||
<p class="text-sm text-yellow-700 mb-2">Veuillez préparer les éléments suivants pour finaliser la licence :</p>
|
||||
<ul class="list-disc pl-5 text-sm text-yellow-700 space-y-1">
|
||||
<li>Photo d’identité</li>
|
||||
<li>Carte d’identité ou passeport de l'adhérent</li>
|
||||
<li>Livret de famille (si pas de pièce d’identité)</li>
|
||||
<li>Adresse e-mail de l'adhérent (représentant légal en cas de mineur)</li>
|
||||
<li>Pour les adhérents nés à l'étranger : Passeport + justificatif de domicile (du représentant légal en cas de mineur)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Nom <span class="text-red-500">*</span></label>
|
||||
@@ -84,10 +135,55 @@
|
||||
<p class="text-[10px] text-gray-400 text-center mt-4 leading-tight">
|
||||
Les données sont stockées de manière sécurisée, accessibles uniquement au bureau du club. Vous pouvez demander leur suppression à tout moment.
|
||||
</p>
|
||||
<p class="text-[10px] text-gray-400 text-center mt-2">
|
||||
v<span th:text="${appVersion}">1.0-SNAPSHOT</span>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const form = document.querySelector('form');
|
||||
form.addEventListener('submit', function(event) {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault();
|
||||
|
||||
let errorMsg = document.getElementById('form-error-msg');
|
||||
if (!errorMsg) {
|
||||
errorMsg = document.createElement('div');
|
||||
errorMsg.id = 'form-error-msg';
|
||||
errorMsg.className = 'mb-6 p-4 bg-red-50 border-l-4 border-red-500 text-red-700 text-sm rounded transition-all duration-300';
|
||||
errorMsg.innerHTML = '<h3 class="font-bold">Erreur de validation</h3><p>Veuillez renseigner tous les champs obligatoires (encadrés en rouge).</p>';
|
||||
form.insertBefore(errorMsg, form.firstChild);
|
||||
}
|
||||
}
|
||||
form.classList.add('was-validated');
|
||||
|
||||
// Scroll to the first invalid element for better mobile UX
|
||||
const firstInvalid = form.querySelector(':invalid');
|
||||
if (firstInvalid) {
|
||||
firstInvalid.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
});
|
||||
|
||||
const typeDemandeRadios = document.querySelectorAll('input[name="typeDemande"]');
|
||||
const nouvelleLicenceBlock = document.getElementById('nouvelleLicenceBlock');
|
||||
|
||||
typeDemandeRadios.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
if (this.value === 'NOUVELLE') {
|
||||
nouvelleLicenceBlock.classList.remove('hidden');
|
||||
} else {
|
||||
nouvelleLicenceBlock.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Trigger on load if already checked (e.g. going back or validation error)
|
||||
const checkedRadio = document.querySelector('input[name="typeDemande"]:checked');
|
||||
if (checkedRadio && checkedRadio.value === 'NOUVELLE') {
|
||||
nouvelleLicenceBlock.classList.remove('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('dateNaissance').addEventListener('change', function() {
|
||||
const dateInput = this.value;
|
||||
const repBlock = document.getElementById('representantBlock');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.11"></script>
|
||||
<meta charset="UTF-8">
|
||||
<title>Inscription réussie</title>
|
||||
@@ -16,8 +17,56 @@
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">Pré-inscription envoyée !</h1>
|
||||
<p class="text-gray-600 mb-2">Votre dossier a bien été transmis au secrétariat de l'AS Talange.</p>
|
||||
<p class="text-gray-600 mb-2" th:text="'Votre dossier pour ' + ${prenom != null ? prenom : 'le futur licencié'} + ' a bien été transmis au secrétariat de l\'AS Talange.'">Votre dossier a bien été transmis au secrétariat de l'AS Talange.</p>
|
||||
<p class="text-sm text-gray-500">Un responsable va traiter votre demande prochainement. Vous pouvez fermer cette page.</p>
|
||||
|
||||
<div th:if="${isNouvelle}" class="mt-8 text-left">
|
||||
<div id="ticket-documents" class="bg-yellow-50 border-2 border-dashed border-yellow-300 p-6 rounded-lg relative overflow-hidden shadow-sm">
|
||||
<!-- Decorative elements for ticket look -->
|
||||
<div class="absolute -left-3 top-1/2 w-6 h-6 bg-white rounded-full border-r-2 border-yellow-300 transform -translate-y-1/2"></div>
|
||||
<div class="absolute -right-3 top-1/2 w-6 h-6 bg-white rounded-full border-l-2 border-yellow-300 transform -translate-y-1/2"></div>
|
||||
|
||||
<h2 class="text-lg font-bold text-yellow-800 mb-4 text-center border-b-2 border-yellow-200 pb-2 border-dashed uppercase tracking-wider">Mémo Inscription</h2>
|
||||
|
||||
<div class="mb-4">
|
||||
<p class="text-sm font-semibold text-yellow-900">Tarif Licence : <span class="text-lg" th:text="${tarif}">210 €</span></p>
|
||||
</div>
|
||||
|
||||
<h3 class="font-semibold text-yellow-900 mb-2 text-sm uppercase tracking-wide">Documents à fournir :</h3>
|
||||
<ul class="list-none space-y-2 mb-3">
|
||||
<li th:each="doc : ${documents}" class="flex items-start">
|
||||
<svg class="w-4 h-4 text-yellow-500 mr-2 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path></svg>
|
||||
<span class="text-sm text-yellow-800" th:text="${doc}">Document</span>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="text-xs text-yellow-800 italic text-center mb-2">Ces documents sont à fournir au secrétariat du club pour finaliser l'inscription.</p>
|
||||
<div class="mt-4 pt-2 border-t-2 border-yellow-200 border-dashed text-center">
|
||||
<p class="text-xs text-yellow-600 font-medium">As Talange</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<button type="button" onclick="captureTicket()" class="bg-gray-800 hover:bg-gray-700 text-white font-medium py-2 px-4 rounded-lg shadow transition-colors inline-flex items-center text-sm">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
||||
</svg>
|
||||
Enregistrer cette liste (Image)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script th:if="${isNouvelle}">
|
||||
function captureTicket() {
|
||||
const ticket = document.getElementById('ticket-documents');
|
||||
html2canvas(ticket, { scale: 2 }).then(canvas => {
|
||||
const imgData = canvas.toDataURL('image/png');
|
||||
const link = document.createElement('a');
|
||||
link.download = 'ASTalange-Documents.png';
|
||||
link.href = imgData;
|
||||
link.click();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user