diff --git a/assets/controllers/form_controller.js b/assets/controllers/form_controller.js index 5c81119..88485c3 100644 --- a/assets/controllers/form_controller.js +++ b/assets/controllers/form_controller.js @@ -2,7 +2,7 @@ import { Controller } from "@hotwired/stimulus"; import { loadStripe } from "@stripe/stripe-js"; export default class extends Controller { - static targets = ["key", "submit", "firstname", "lastname", "email", "phone"]; + static targets = ["key", "submit", "firstname", "lastname", "email", "phone", "ticketType", "foodType", "note"]; stripe; @@ -13,39 +13,64 @@ export default class extends Controller { addEntry(event) { event.preventDefault(); - const formClone = this.element.querySelector("form").cloneNode(true); - - formClone.querySelectorAll("select").forEach((input) => { - input.value = "1"; + const template = this.element.querySelector(".forms").firstElementChild.cloneNode(true); + + template.querySelectorAll('select, input').forEach(element => { + element.value = ''; + if (element.id) { + element.id = `${element.id}_${Date.now()}`; + } }); - formClone.querySelector("input").value = ""; - this.element.querySelector(".forms").appendChild(formClone); + template.querySelectorAll('label').forEach(label => { + const forAttribute = label.getAttribute('for'); + if (forAttribute) { + label.setAttribute('for', `${forAttribute}_${Date.now()}`); + } + }); + + template.classList.add('opacity-0'); + this.element.querySelector(".forms").appendChild(template); + + requestAnimationFrame(() => { + template.classList.remove('opacity-0'); + template.classList.add('transition-all', 'duration-300'); + }); } removeEntry(event) { event.preventDefault(); - if (document.querySelector(".forms").childElementCount === 1) { + const forms = this.element.querySelector(".forms"); + if (forms.childElementCount <= 1) { return; } - event.target.closest("form").remove(); + const ticketElement = event.currentTarget.closest(".bg-white\\/80"); + if (!ticketElement) return; + + ticketElement.classList.add('opacity-0', 'transition-all', 'duration-300'); + setTimeout(() => { + ticketElement.remove(); + }, 300); } submit(event) { event.preventDefault(); + if (!this.validateForm()) { + return; + } this.submitTarget.querySelector("span").classList.add("hidden"); this.submitTarget.querySelector("svg").classList.remove("hidden"); - const forms = document.querySelectorAll("form"); + const tickets = this.element.querySelectorAll(".forms > div"); const personalData = this.getPersonalData(); - const ticketData = this.getTicketData(forms); + const ticketData = this.getTicketData(tickets); const formData = { personal: personalData, - tickets: ticketData, + tickets: ticketData }; fetch("/ticket/submit", { @@ -58,7 +83,7 @@ export default class extends Controller { if (!response.ok) { this.submitTarget.querySelector("svg").classList.add("hidden"); this.submitTarget.querySelector("span").classList.remove("hidden"); - alert("An error occurred"); + this.showError("Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut."); } else { response.json().then((data) => { this.stripe.redirectToCheckout({ @@ -69,29 +94,80 @@ export default class extends Controller { }); } - getTicketData(forms) { - const formData = []; - forms.forEach((form) => { - formData.push(this.extractFormData(form)); + validateForm() { + let isValid = true; + + ['firstname', 'lastname', 'email'].forEach(field => { + const target = this[`${field}Target`]; + if (!target.value.trim()) { + this.showFieldError(target); + isValid = false; + } }); - return formData; + this.element.querySelectorAll(".forms > div").forEach(ticket => { + const selects = ticket.querySelectorAll('select[required]'); + selects.forEach(select => { + if (!select.value) { + this.showFieldError(select); + isValid = false; + } + }); + }); + + return isValid; + } + + showFieldError(element) { + element.classList.add('border-red-500', 'focus:border-red-500', 'focus:ring-red-200'); + element.addEventListener('input', () => { + element.classList.remove('border-red-500', 'focus:border-red-500', 'focus:ring-red-200'); + }, { once: true }); + } + + showError(message) { + const errorDiv = document.createElement('div'); + errorDiv.className = 'fixed top-4 right-4 bg-red-50 border-l-4 border-red-500 p-4 rounded shadow-lg transform transition-all duration-500 opacity-0 translate-x-4'; + errorDiv.innerHTML = ` +
${message}
+