diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bae1930..0cbd1a8 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -12,5 +12,6 @@ class ApplicationController < ActionController::Base # - Badge API for notifications # - Import maps for JavaScript modules # - CSS nesting and :has() pseudo-class - allow_browser versions: :modern + # allow_browser versions: :modern + # allow_browser versions: { safari: 16.4, firefox: 121, ie: false } end diff --git a/app/controllers/parties_controller.rb b/app/controllers/parties_controller.rb index 0625990..e4cbfd1 100644 --- a/app/controllers/parties_controller.rb +++ b/app/controllers/parties_controller.rb @@ -9,4 +9,56 @@ class PartiesController < ApplicationController def show @party = Party.find(params[:id]) end + + # Handle checkout process + def checkout + @party = Party.find(params[:id]) + cart_data = JSON.parse(params[:cart] || "{}") + + if cart_data.empty? + redirect_to party_path(@party), alert: "Please select at least one ticket" + return + end + + # Create order items from cart + order_items = [] + total_amount = 0 + + cart_data.each do |ticket_type_id, item| + ticket_type = @party.ticket_types.find_by(id: ticket_type_id) + next unless ticket_type + + quantity = item["quantity"].to_i + next if quantity <= 0 + + # Check availability + available = ticket_type.quantity - ticket_type.tickets.count + if quantity > available + redirect_to party_path(@party), alert: "Not enough tickets available for #{ticket_type.name}" + return + end + + order_items << { + ticket_type: ticket_type, + quantity: quantity, + price_cents: ticket_type.price_cents + } + + total_amount += ticket_type.price_cents * quantity + end + + if order_items.empty? + redirect_to party_path(@party), alert: "Invalid order" + return + end + + # Here you would typically: + # 1. Create an Order record + # 2. Create Ticket records for each item + # 3. Redirect to payment processing + + # For now, we'll just redirect with a success message + # In a real app, you'd redirect to a payment page + redirect_to party_path(@party), notice: "Order created successfully! Proceeding to payment..." + end end diff --git a/app/javascript/controllers/ticket_cart_controller.js b/app/javascript/controllers/ticket_cart_controller.js new file mode 100644 index 0000000..2e8f13d --- /dev/null +++ b/app/javascript/controllers/ticket_cart_controller.js @@ -0,0 +1,107 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["quantity", "cartCount", "cartTotal", "checkoutButton"] + static values = { partyId: String } + + connect() { + this.cart = {} + this.updateCartDisplay() + } + + increaseQuantity(event) { + const ticketTypeId = event.currentTarget.dataset.ticketTypeId + const max = parseInt(event.currentTarget.dataset.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.currentTarget.dataset.ticketTypeId + const input = this.quantityTargetFor(ticketTypeId) + + const current = parseInt(input.value) || 0 + if (current > 0) { + input.value = current - 1 + 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 + } + + this.cartCountTarget.textContent = totalTickets + this.cartTotalTarget.textContent = totalPrice.toFixed(2) + + const checkoutBtn = this.checkoutButtonTarget + if (totalTickets > 0) { + checkoutBtn.disabled = false + } else { + checkoutBtn.disabled = true + } + } + + proceedToCheckout() { + if (Object.keys(this.cart).length === 0) { + alert('Please select at least one ticket') + return + } + + const form = document.createElement('form') + form.method = 'POST' + form.action = `/parties/${this.partyIdValue}/checkout` + form.style.display = 'none' + + // 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() + } + + // Helper method to find quantity input by ticket type ID + quantityTargetFor(ticketTypeId) { + return document.querySelector(`#quantity_${ticketTypeId}`) + } +} diff --git a/app/views/components/_header.html.erb b/app/views/components/_header.html.erb index a1b3fec..7856db2 100644 --- a/app/views/components/_header.html.erb +++ b/app/views/components/_header.html.erb @@ -1,6 +1,6 @@ -
-
-