- Enhanced events index page with improved visual design and better information display - Completely redesigned event show page with modern layout, ticket selection, and checkout functionality - Implemented Stripe payment processing for ticket purchases - Created ticket generation system with PDF tickets and QR codes - Added email confirmation system with ticket attachments - Updated database configuration to use SQLite for easier development setup - Fixed gem dependencies and resolved conflicts - Improved error handling throughout the checkout process - Enhanced Stimulus controller for ticket cart management - Added proper redirect handling for successful and cancelled payments
178 lines
5.0 KiB
JavaScript
Executable File
178 lines
5.0 KiB
JavaScript
Executable File
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) {
|
|
alert('Veuillez sélectionner au moins un billet')
|
|
return
|
|
}
|
|
|
|
// Check if user is authenticated
|
|
const isAuthenticated = document.body.dataset.userAuthenticated === "true"
|
|
|
|
if (!isAuthenticated) {
|
|
if (confirm('Vous devez être connecté pour acheter des billets. Souhaitez-vous vous connecter maintenant ?')) {
|
|
// Store cart in session storage
|
|
sessionStorage.setItem('pending_cart', JSON.stringify({
|
|
eventId: this.eventIdValue,
|
|
cart: this.cart
|
|
}))
|
|
window.location.href = '/auth/sign_in'
|
|
}
|
|
return
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|
|
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}`)
|
|
}
|
|
}
|