import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["quantity", "cartCount", "cartTotal", "checkoutButton"] static values = { eventId: String } connect() { this.cart = {} this.updateCartDisplay() // Check for pending cart in session storage (after login) this.checkForPendingCart() } increaseQuantity(event) { const ticketTypeId = event.params.ticketTypeId const max = parseInt(event.params.max) const input = this.quantityTargetFor(ticketTypeId) const current = parseInt(input.value) || 0 if (current < max) { input.value = current + 1 this.updateCartItem(ticketTypeId, input) } } decreaseQuantity(event) { const ticketTypeId = event.params.ticketTypeId const input = this.quantityTargetFor(ticketTypeId) const current = parseInt(input.value) || 0 if (current > 0) { input.value = current - 1 this.updateCartItem(ticketTypeId, input) } } updateQuantityFromInput(event) { const input = event.target const ticketTypeId = input.dataset.ticketTypeId const max = parseInt(input.max) const quantity = parseInt(input.value) || 0 // Validate input if (quantity < 0) { input.value = 0 } else if (quantity > max) { input.value = max } this.updateCartItem(ticketTypeId, input) } updateCartItem(ticketTypeId, input) { const name = input.dataset.name const price = parseInt(input.dataset.price) const quantity = parseInt(input.value) || 0 if (quantity > 0) { this.cart[ticketTypeId] = { name: name, price: price, quantity: quantity } } else { delete this.cart[ticketTypeId] } this.updateCartDisplay() } updateCartDisplay() { let totalTickets = 0 let totalPrice = 0 for (let ticketTypeId in this.cart) { totalTickets += this.cart[ticketTypeId].quantity totalPrice += (this.cart[ticketTypeId].price * this.cart[ticketTypeId].quantity) / 100 } // Update cart count and total if (this.hasCartCountTarget) { this.cartCountTarget.textContent = totalTickets } if (this.hasCartTotalTarget) { this.cartTotalTarget.textContent = totalPrice.toFixed(2) } // Update checkout button state if (this.hasCheckoutButtonTarget) { const checkoutBtn = this.checkoutButtonTarget if (totalTickets > 0) { checkoutBtn.disabled = false checkoutBtn.classList.remove('opacity-50', 'cursor-not-allowed') } else { checkoutBtn.disabled = true checkoutBtn.classList.add('opacity-50', 'cursor-not-allowed') } } } proceedToCheckout() { if (Object.keys(this.cart).length === 0) { this.showNotification('Veuillez sélectionner au moins un billet', 'warning') return } // Validate cart contents if (!this.validateCartAvailability()) { return } // Check if user is authenticated const isAuthenticated = document.body.dataset.userAuthenticated === "true" if (!isAuthenticated) { this.showLoginModal() return } // Show loading state this.setCheckoutLoading(true) // Create form and submit to checkout const form = document.createElement('form') form.method = 'POST' form.action = `/events/${document.body.dataset.eventSlug}.${this.eventIdValue}/checkout` // Add CSRF token const csrfToken = document.querySelector('meta[name="csrf-token"]').content const csrfInput = document.createElement('input') csrfInput.type = 'hidden' csrfInput.name = 'authenticity_token' csrfInput.value = csrfToken form.appendChild(csrfInput) // Add cart data const cartInput = document.createElement('input') cartInput.type = 'hidden' cartInput.name = 'cart' cartInput.value = JSON.stringify(this.cart) form.appendChild(cartInput) document.body.appendChild(form) form.submit() } validateCartAvailability() { // Check each ticket type availability before checkout for (let ticketTypeId in this.cart) { const input = this.quantityTargetFor(ticketTypeId) if (input) { const maxAvailable = parseInt(input.max) const requested = this.cart[ticketTypeId].quantity if (requested > maxAvailable) { this.showNotification(`Seulement ${maxAvailable} billets disponibles pour ${this.cart[ticketTypeId].name}`, 'error') // Adjust cart to maximum available input.value = maxAvailable this.updateCartItem(ticketTypeId, input) return false } } } return true } showLoginModal() { // Create and show modern login modal const modal = document.createElement('div') modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50' modal.innerHTML = `

Connexion requise

Vous devez être connecté pour acheter des billets. Votre panier sera conservé.

` document.body.appendChild(modal) // Handle login button modal.querySelector('#login-btn').addEventListener('click', () => { // Store cart in session storage sessionStorage.setItem('pending_cart', JSON.stringify({ eventId: this.eventIdValue, cart: this.cart })) window.location.href = '/auth/sign_in' }) // Handle cancel button modal.querySelector('#cancel-login').addEventListener('click', () => { document.body.removeChild(modal) }) // Handle backdrop click modal.addEventListener('click', (e) => { if (e.target === modal) { document.body.removeChild(modal) } }) } setCheckoutLoading(loading) { const checkoutBtn = this.checkoutButtonTarget if (loading) { checkoutBtn.disabled = true checkoutBtn.innerHTML = ` Redirection... ` } else { checkoutBtn.disabled = false checkoutBtn.innerHTML = ` Procéder au paiement ` } } showNotification(message, type = 'info') { // Create toast notification const toast = document.createElement('div') const colors = { success: 'bg-green-50 text-green-800 border-green-200', error: 'bg-red-50 text-red-800 border-red-200', warning: 'bg-yellow-50 text-yellow-800 border-yellow-200', info: 'bg-blue-50 text-blue-800 border-blue-200' } toast.className = `fixed top-4 right-4 z-50 max-w-sm p-4 border rounded-lg shadow-lg ${colors[type]} transform transition-all duration-300 translate-x-full` toast.innerHTML = `

${message}

` document.body.appendChild(toast) // Animate in setTimeout(() => { toast.classList.remove('translate-x-full') }, 10) // Auto remove after 5 seconds setTimeout(() => { if (document.body.contains(toast)) { toast.classList.add('translate-x-full') setTimeout(() => { if (document.body.contains(toast)) { document.body.removeChild(toast) } }, 300) } }, 5000) } checkForPendingCart() { const pendingCart = sessionStorage.getItem('pending_cart') if (pendingCart) { try { const cartData = JSON.parse(pendingCart) if (cartData.eventId == this.eventIdValue) { this.cart = cartData.cart this.updateCartDisplay() // Restore quantities in inputs for (let ticketTypeId in this.cart) { const input = this.quantityTargetFor(ticketTypeId) if (input) { input.value = this.cart[ticketTypeId].quantity } } } sessionStorage.removeItem('pending_cart') } catch (e) { console.error('Error restoring pending cart:', e) sessionStorage.removeItem('pending_cart') } } } // Helper method to find quantity input by ticket type ID quantityTargetFor(ticketTypeId) { return document.querySelector(`#quantity_${ticketTypeId}`) } }