templates/default/formulaire.html.twig line 1

Open in your IDE?
  1. {% extends 'base.html.twig' %}
  2. {% block title %}Casting Kalina 2026 - Saison 4 - Formulaire {% endblock %}
  3. {% block body %}
  4. <div class="kalina-header">
  5.   <img src="{{ asset('img/logo-kalina.webp') }}" alt="Logo Kalina">
  6. </div>
  7. <div class="container mt-5">
  8.   <h2 class="text-center mb-4">đź“‹ Formulaire de Candidature Kalina</h2>
  9. <form method="POST" id="kalinaForm" action="{{ path('form.submit') }}" enctype="multipart/form-data">
  10.      {#  <div class="wizard-nav">
  11.       <button type="button" id="step1Btn">Étape 1</button>
  12.       <button type="button" id="step2Btn">Étape 2</button>
  13.       <button type="button" id="step3Btn">Étape 3</button>
  14.     </div>
  15.     <div class="progress mb-4">
  16.   <div id="progressBar" class="progress-bar bg-warning" role="progressbar" style="width: 33%;" aria-valuenow="33" aria-valuemin="0" aria-valuemax="100"></div>
  17. </div> #} 
  18.       {% for message in app.flashes('email_error') %}
  19.       <div class="form-error" style="color: #e44118; font-size: 13px;">{{ message }}</div>
  20.     {% endfor %}
  21.   {{ include('default/step/step1.html.twig') }}
  22.   {{ include('default/step/step2.html.twig') }}
  23.   {{ include('default/step/step3.html.twig') }}
  24. </form>
  25. </div>
  26. <!-- Footer -->
  27. <footer>
  28.   Site créé par <a href="https://basicom.fr" target="_blank">Basicom</a>
  29. </footer>
  30. <div id="formLoader" style="display: none; position: fixed; top:0; left:0; width:100%; height:100%; background-color: rgba(23, 33, 40, 0.9); z-index: 9999; justify-content: center; align-items: center;">
  31.   <div class="spinner-border text-light" style="width: 4rem; height: 4rem;" role="status">
  32.     <span class="visually-hidden">Chargement...</span>
  33.   </div>
  34.   <p class="text-white mt-3">Envoi en cours...</p>
  35. </div>
  36. {% endblock %}
  37. {% block javascripts %}
  38. <script>
  39. document.addEventListener("DOMContentLoaded", function () {
  40.   function showStep(step) {
  41.     document.querySelectorAll(".wizard-step").forEach(el => el.classList.remove("active"));
  42.     const currentStep = document.getElementById(`step${step}`);
  43.     if (currentStep) currentStep.classList.add("active");
  44.     const progress = document.getElementById("progressBar");
  45.     if (progress) {
  46.       progress.style.width = step === 1 ? "33%" : step === 2 ? "66%" : "100%";
  47.     }
  48.     document.getElementById("kalinaForm").scrollIntoView({ behavior: "smooth" });
  49.   }
  50.   function validateStep(step) {
  51.     let valid = true;
  52.     const currentStep = document.getElementById(`step${step}`);
  53.     if (!currentStep) return false;
  54.     const requiredFields = currentStep.querySelectorAll("input[required], select[required], textarea[required]");
  55.     requiredFields.forEach(field => {
  56.       const error = field.parentNode.querySelector(".form-error");
  57.       field.classList.remove("is-invalid");
  58.       if (error) error.remove();
  59.       if (!field.value.trim()) {
  60.         valid = false;
  61.         field.classList.add("is-invalid");
  62.         const errorMsg = document.createElement("div");
  63.         errorMsg.classList.add("form-error");
  64.         errorMsg.style.color = "#e44118";
  65.         errorMsg.style.fontSize = "13px";
  66.         errorMsg.textContent = "Champ non renseignĂ©";
  67.         field.parentNode.appendChild(errorMsg);
  68.       }
  69.     });
  70.     return valid;
  71.   }
  72.   function validateEmailsMatch(live = false) {
  73.     const email = document.querySelector("input[name='email']");
  74.     const confirmEmail = document.querySelector("input[name='confirm_email']");
  75.     if (!email || !confirmEmail) return true;
  76.     [email, confirmEmail].forEach(field => {
  77.       field.classList.remove("is-invalid", "is-valid");
  78.       const error = field.parentNode.querySelector(".form-feedback");
  79.       if (error) error.remove();
  80.     });
  81.     const feedback = document.createElement("div");
  82.     feedback.classList.add("form-feedback");
  83.     feedback.style.fontSize = "13px";
  84.     if (email.value.trim() !== confirmEmail.value.trim()) {
  85.       if (!live) {
  86.         confirmEmail.classList.add("is-invalid");
  87.         feedback.style.color = "#e44118";
  88.         feedback.textContent = "Adresse mail diffĂ©rente";
  89.         confirmEmail.parentNode.appendChild(feedback);
  90.         return false;
  91.       }
  92.     } else {
  93.       if (confirmEmail.value.trim() !== "") {
  94.         confirmEmail.classList.add("is-valid");
  95.         feedback.style.color = "green";
  96.         feedback.textContent = "✔️ Adresse confirmĂ©e";
  97.         confirmEmail.parentNode.appendChild(feedback);
  98.       }
  99.     }
  100.     return true;
  101.   }
  102.   function validateFile(input, maxMo, allowedExtensions = []) {
  103.     const file = input.files[0];
  104.     if (!file) return true;
  105.     const maxBytes = maxMo * 1024 * 1024;
  106.     const ext = file.name.split('.').pop().toLowerCase();
  107.     const existingError = input.parentNode.querySelector(".file-error");
  108.     if (existingError) existingError.remove();
  109.     input.classList.remove("is-invalid");
  110.     if (file.size > maxBytes) {
  111.       input.classList.add("is-invalid");
  112.       const error = document.createElement("div");
  113.       error.classList.add("file-error");
  114.       error.style.color = "#e44118";
  115.       error.style.fontSize = "13px";
  116.       error.textContent = `Fichier trop volumineux (max ${maxMo} Mo)`;
  117.       input.parentNode.appendChild(error);
  118.       return false;
  119.     }
  120.     if (allowedExtensions.length && !allowedExtensions.includes(ext)) {
  121.       input.classList.add("is-invalid");
  122.       const error = document.createElement("div");
  123.       error.classList.add("file-error");
  124.       error.style.color = "#e44118";
  125.       error.style.fontSize = "13px";
  126.       error.textContent = `Format non autorisĂ© (${allowedExtensions.join(', ')})`;
  127.       input.parentNode.appendChild(error);
  128.       return false;
  129.     }
  130.     return true;
  131.   }
  132.   // Navigation Ă‰tapes
  133.   document.querySelectorAll(".btn-next").forEach(btn => {
  134.     btn.addEventListener("click", function () {
  135.       const next = parseInt(this.dataset.next);
  136.       const current = next - 1;
  137.       if (validateStep(current)) showStep(next);
  138.     });
  139.   });
  140.   document.querySelectorAll(".btn-prev").forEach(btn => {
  141.     btn.addEventListener("click", function () {
  142.       const prev = parseInt(this.dataset.prev);
  143.       showStep(prev);
  144.     });
  145.   });
  146.   // Live email check
  147.   const confirmEmail = document.querySelector("input[name='confirm_email']");
  148.   if (confirmEmail) {
  149.     confirmEmail.addEventListener("input", () => validateEmailsMatch(true));
  150.   }
  151.   // Reset d'erreur sur fichier quand modifiĂ©
  152.   document.querySelectorAll('input[type="file"]').forEach(input => {
  153.     input.addEventListener("change", () => {
  154.       const isVideo = input.id.includes("video");
  155.       validateFile(input, isVideo ? 100 : 3, isVideo ? ["mp4", "mov", "avi", "mkv"] : ["jpg", "jpeg", "png"]);
  156.     });
  157.   });
  158.   // Soumission finale
  159.   const form = document.getElementById("kalinaForm");
  160.   if (form) {
  161.     form.addEventListener("submit", function (e) {
  162.       e.preventDefault();
  163.       if (!validateStep(3)) return;
  164.       if (!validateEmailsMatch()) return;
  165.       const isPhoto1OK = validateFile(document.querySelector("#photo1"), 8, ["jpg", "jpeg", "png"]);
  166.       const isPhoto2OK = validateFile(document.querySelector("#photo2"), 8, ["jpg", "jpeg", "png"]);
  167.       const isPhoto3OK = validateFile(document.querySelector("#photo3"), 8, ["jpg", "jpeg", "png"]);
  168.      // const isVideoOK  = validateFile(document.querySelector("#video_motivation"), 100, ["mp4", "mov", "avi", "mkv"]);
  169.       if (!isPhoto1OK || !isPhoto2OK || !isPhoto3OK /*|| !isVideoOK*/) return;
  170.       // Supprimer l'attribut "required" des champs non visibles pour Ă©viter les erreurs de focus
  171.       form.querySelectorAll("input[required], select[required], textarea[required]").forEach(field => {
  172.        if (!field.offsetParent) {
  173.       field.removeAttribute("required");
  174.       }
  175.       });
  176.       // Affiche le loader
  177.       document.getElementById("formLoader").style.display = "flex";
  178.       setTimeout(() => {
  179.         form.submit();
  180.       }, 500); // Un petit dĂ©lai pour UX, sinon instantanĂ©
  181.     });
  182.   }
  183.   showStep(1);
  184. });
  185. </script>
  186. {% endblock %}