From b493027c86f3214f232ca37c82382fc54958ec1e Mon Sep 17 00:00:00 2001 From: kbe Date: Sat, 30 Aug 2025 20:12:50 +0200 Subject: [PATCH] feat: Refactor cart storage to use API architecture Move store_cart functionality from main EventsController to API namespace: - Add store_cart method to Api::V1::EventsController with API key bypass - Remove store_cart from main EventsController - Update routes to use RESTful API endpoint structure - Maintain session-based cart storage for frontend compatibility --- app/controllers/api/v1/events_controller.rb | 17 +++++- app/controllers/events_controller.rb | 12 +--- app/controllers/pages_controller.rb | 10 ++-- .../ticket_selection_controller.js | 58 +++++++++---------- config/routes.rb | 7 ++- 5 files changed, 56 insertions(+), 48 deletions(-) diff --git a/app/controllers/api/v1/events_controller.rb b/app/controllers/api/v1/events_controller.rb index 1469a1a..608b11f 100755 --- a/app/controllers/api/v1/events_controller.rb +++ b/app/controllers/api/v1/events_controller.rb @@ -4,8 +4,11 @@ module Api module V1 class EventsController < ApiController + # Skip API key authentication for store_cart action (used by frontend forms) + skip_before_action :authenticate_api_key, only: [:store_cart] + # Charge l'évén avant certaines actions pour réduire les duplications - before_action :set_event, only: [ :show, :update, :destroy ] + before_action :set_event, only: [ :show, :update, :destroy, :store_cart ] # GET /api/v1/events # Récupère tous les événements triés par date de création (du plus récent au plus ancien) @@ -54,6 +57,18 @@ module Api head :no_content end + # POST /api/v1/events/:id/store_cart + # Store cart data in session (AJAX endpoint) + def store_cart + cart_data = params[:cart] || {} + session[:pending_cart] = cart_data + + render json: { status: "success", message: "Cart stored successfully" } + rescue => e + Rails.logger.error "Error storing cart: #{e.message}" + render json: { status: "error", message: "Failed to store cart" }, status: 500 + end + private # Trouve un événement par son ID ou retourne 404 Introuvable diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index ebfa030..0005efe 100755 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -6,7 +6,7 @@ class EventsController < ApplicationController include StripeConcern before_action :authenticate_user!, only: [ :checkout, :process_names, :payment_success, :download_ticket ] - before_action :set_event, only: [ :show, :checkout, :process_names, :store_cart ] + before_action :set_event, only: [ :show, :checkout, :process_names ] # Display all events def index @@ -91,16 +91,6 @@ class EventsController < ApplicationController process_payment(cart_data) end - # Store cart data in session (AJAX endpoint) - def store_cart - cart_data = params[:cart] || {} - session[:pending_cart] = cart_data - - render json: { status: "success", message: "Cart stored successfully" } - rescue => e - Rails.logger.error "Error storing cart: #{e.message}" - render json: { status: "error", message: "Failed to store cart" }, status: 500 - end # Handle successful payment def payment_success diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 50bce80..1ced93f 100755 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -10,7 +10,7 @@ class PagesController < ApplicationController @events = Event.published.featured.limit(3) if user_signed_in? - return redirect_to(dashboard_path) + redirect_to(dashboard_path) end end @@ -25,14 +25,14 @@ class PagesController < ApplicationController # User's booked events @user_booked_events = Event.joins(ticket_types: :tickets) - .where(tickets: { user: current_user, status: 'active' }) - .distinct - .limit(5) + .where(tickets: { user: current_user, status: "active" }) + .distinct + .limit(5) # Events sections @today_events = Event.published.where("DATE(start_time) = ?", Date.current).order(start_time: :asc) @tomorrow_events = Event.published.where("DATE(start_time) = ?", Date.current + 1).order(start_time: :asc) - @other_events = Event.published.upcoming.where.not("DATE(start_time) IN (?)", [Date.current, Date.current + 1]).order(start_time: :asc).page(params[:page]) + @other_events = Event.published.upcoming.where.not("DATE(start_time) IN (?)", [ Date.current, Date.current + 1 ]).order(start_time: :asc).page(params[:page]) end # Events page showing all published events with pagination diff --git a/app/javascript/controllers/ticket_selection_controller.js b/app/javascript/controllers/ticket_selection_controller.js index 59a710d..b041a2a 100644 --- a/app/javascript/controllers/ticket_selection_controller.js +++ b/app/javascript/controllers/ticket_selection_controller.js @@ -5,77 +5,77 @@ import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["quantityInput", "totalQuantity", "totalAmount", "checkoutButton", "form"] static values = { eventSlug: String, eventId: String } - + // Initialize the controller and update the cart summary connect() { this.updateCartSummary() this.bindFormSubmission() } - + // Bind form submission to handle cart storage bindFormSubmission() { if (this.hasFormTarget) { this.formTarget.addEventListener('submit', this.submitCart.bind(this)) } } - + // Increment the quantity for a specific ticket type increment(event) { const ticketTypeId = event.currentTarget.dataset.target const input = this.quantityInputTargets.find(input => input.dataset.target === ticketTypeId) const value = parseInt(input.value) || 0 const max = parseInt(input.max) || 0 - + if (value < max) { input.value = value + 1 this.updateCartSummary() } } - + // Decrement the quantity for a specific ticket type decrement(event) { const ticketTypeId = event.currentTarget.dataset.target const input = this.quantityInputTargets.find(input => input.dataset.target === ticketTypeId) const value = parseInt(input.value) || 0 - + if (value > 0) { input.value = value - 1 this.updateCartSummary() } } - + // Update quantity when directly edited in the input field updateQuantity(event) { const input = event.currentTarget let value = parseInt(input.value) || 0 const max = parseInt(input.max) || 0 - + // Ensure value is within valid range (0 to max available) if (value < 0) value = 0 if (value > max) value = max - + input.value = value this.updateCartSummary() } - + // Calculate and update the cart summary (total quantity and amount) updateCartSummary() { let totalQuantity = 0 let totalAmount = 0 - + // Sum up quantities and calculate total amount this.quantityInputTargets.forEach(input => { const quantity = parseInt(input.value) || 0 const price = parseInt(input.dataset.price) || 0 - + totalQuantity += quantity totalAmount += quantity * price }) - + // Update the displayed total quantity and amount this.totalQuantityTarget.textContent = totalQuantity this.totalAmountTarget.textContent = `€${(totalAmount / 100).toFixed(2)}` - + // Enable/disable checkout button based on whether any tickets are selected if (totalQuantity > 0) { this.checkoutButtonTarget.classList.remove('opacity-50', 'cursor-not-allowed') @@ -85,36 +85,36 @@ export default class extends Controller { this.checkoutButtonTarget.disabled = true } } - + // Handle form submission - store cart in session before proceeding async submitCart(event) { event.preventDefault() - + const cartData = this.buildCartData() - + if (Object.keys(cartData).length === 0) { alert('Veuillez sélectionner au moins un billet') return } - + try { // Store cart data in session await this.storeCartInSession(cartData) - + // Redirect to tickets/new page const ticketNewUrl = `/events/${this.eventSlugValue}.${this.eventIdValue}/tickets/new` window.location.href = ticketNewUrl - + } catch (error) { console.error('Error storing cart:', error) alert('Une erreur est survenue. Veuillez réessayer.') } } - + // Build cart data from current form state buildCartData() { const cartData = {} - + this.quantityInputTargets.forEach(input => { const quantity = parseInt(input.value) || 0 if (quantity > 0) { @@ -124,14 +124,14 @@ export default class extends Controller { } } }) - + return cartData } - + // Store cart data in session via AJAX async storeCartInSession(cartData) { - const storeCartUrl = `/events/${this.eventSlugValue}.${this.eventIdValue}/store_cart` - + const storeCartUrl = `/api/v1/events/${this.eventIdValue}/store_cart` + const response = await fetch(storeCartUrl, { method: 'POST', headers: { @@ -140,11 +140,11 @@ export default class extends Controller { }, body: JSON.stringify({ cart: cartData }) }) - + if (!response.ok) { throw new Error(`Failed to store cart data: ${response.status} ${response.statusText}`) } - + return response.json() } -} \ No newline at end of file +} diff --git a/config/routes.rb b/config/routes.rb index ad0b919..e50260a 100755 --- a/config/routes.rb +++ b/config/routes.rb @@ -39,7 +39,6 @@ Rails.application.routes.draw do # === Events === get "events", to: "events#index", as: "events" get "events/:slug.:id", to: "events#show", as: "event" - post "events/:slug.:id/store_cart", to: "events#store_cart", as: "store_cart" # === Tickets === get "events/:slug.:id/tickets/new", to: "tickets#new", as: "ticket_new" @@ -64,7 +63,11 @@ Rails.application.routes.draw do namespace :api do namespace :v1 do # RESTful routes for event management - resources :events, only: [ :index, :show, :create, :update, :destroy ] + resources :events, only: [ :index, :show, :create, :update, :destroy ] do + member do + post :store_cart + end + end # resources :bundles, only: [ :index, :show, :create, :update, :destroy ]