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
This commit is contained in:
@@ -4,8 +4,11 @@
|
|||||||
module Api
|
module Api
|
||||||
module V1
|
module V1
|
||||||
class EventsController < ApiController
|
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
|
# 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
|
# 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)
|
# 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
|
head :no_content
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
# Trouve un événement par son ID ou retourne 404 Introuvable
|
# Trouve un événement par son ID ou retourne 404 Introuvable
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ class EventsController < ApplicationController
|
|||||||
include StripeConcern
|
include StripeConcern
|
||||||
|
|
||||||
before_action :authenticate_user!, only: [ :checkout, :process_names, :payment_success, :download_ticket ]
|
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
|
# Display all events
|
||||||
def index
|
def index
|
||||||
@@ -91,16 +91,6 @@ class EventsController < ApplicationController
|
|||||||
process_payment(cart_data)
|
process_payment(cart_data)
|
||||||
end
|
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
|
# Handle successful payment
|
||||||
def payment_success
|
def payment_success
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class PagesController < ApplicationController
|
|||||||
@events = Event.published.featured.limit(3)
|
@events = Event.published.featured.limit(3)
|
||||||
|
|
||||||
if user_signed_in?
|
if user_signed_in?
|
||||||
return redirect_to(dashboard_path)
|
redirect_to(dashboard_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -25,14 +25,14 @@ class PagesController < ApplicationController
|
|||||||
|
|
||||||
# User's booked events
|
# User's booked events
|
||||||
@user_booked_events = Event.joins(ticket_types: :tickets)
|
@user_booked_events = Event.joins(ticket_types: :tickets)
|
||||||
.where(tickets: { user: current_user, status: 'active' })
|
.where(tickets: { user: current_user, status: "active" })
|
||||||
.distinct
|
.distinct
|
||||||
.limit(5)
|
.limit(5)
|
||||||
|
|
||||||
# Events sections
|
# Events sections
|
||||||
@today_events = Event.published.where("DATE(start_time) = ?", Date.current).order(start_time: :asc)
|
@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)
|
@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
|
end
|
||||||
|
|
||||||
# Events page showing all published events with pagination
|
# Events page showing all published events with pagination
|
||||||
|
|||||||
@@ -5,77 +5,77 @@ import { Controller } from "@hotwired/stimulus"
|
|||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static targets = ["quantityInput", "totalQuantity", "totalAmount", "checkoutButton", "form"]
|
static targets = ["quantityInput", "totalQuantity", "totalAmount", "checkoutButton", "form"]
|
||||||
static values = { eventSlug: String, eventId: String }
|
static values = { eventSlug: String, eventId: String }
|
||||||
|
|
||||||
// Initialize the controller and update the cart summary
|
// Initialize the controller and update the cart summary
|
||||||
connect() {
|
connect() {
|
||||||
this.updateCartSummary()
|
this.updateCartSummary()
|
||||||
this.bindFormSubmission()
|
this.bindFormSubmission()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind form submission to handle cart storage
|
// Bind form submission to handle cart storage
|
||||||
bindFormSubmission() {
|
bindFormSubmission() {
|
||||||
if (this.hasFormTarget) {
|
if (this.hasFormTarget) {
|
||||||
this.formTarget.addEventListener('submit', this.submitCart.bind(this))
|
this.formTarget.addEventListener('submit', this.submitCart.bind(this))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment the quantity for a specific ticket type
|
// Increment the quantity for a specific ticket type
|
||||||
increment(event) {
|
increment(event) {
|
||||||
const ticketTypeId = event.currentTarget.dataset.target
|
const ticketTypeId = event.currentTarget.dataset.target
|
||||||
const input = this.quantityInputTargets.find(input => input.dataset.target === ticketTypeId)
|
const input = this.quantityInputTargets.find(input => input.dataset.target === ticketTypeId)
|
||||||
const value = parseInt(input.value) || 0
|
const value = parseInt(input.value) || 0
|
||||||
const max = parseInt(input.max) || 0
|
const max = parseInt(input.max) || 0
|
||||||
|
|
||||||
if (value < max) {
|
if (value < max) {
|
||||||
input.value = value + 1
|
input.value = value + 1
|
||||||
this.updateCartSummary()
|
this.updateCartSummary()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrement the quantity for a specific ticket type
|
// Decrement the quantity for a specific ticket type
|
||||||
decrement(event) {
|
decrement(event) {
|
||||||
const ticketTypeId = event.currentTarget.dataset.target
|
const ticketTypeId = event.currentTarget.dataset.target
|
||||||
const input = this.quantityInputTargets.find(input => input.dataset.target === ticketTypeId)
|
const input = this.quantityInputTargets.find(input => input.dataset.target === ticketTypeId)
|
||||||
const value = parseInt(input.value) || 0
|
const value = parseInt(input.value) || 0
|
||||||
|
|
||||||
if (value > 0) {
|
if (value > 0) {
|
||||||
input.value = value - 1
|
input.value = value - 1
|
||||||
this.updateCartSummary()
|
this.updateCartSummary()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update quantity when directly edited in the input field
|
// Update quantity when directly edited in the input field
|
||||||
updateQuantity(event) {
|
updateQuantity(event) {
|
||||||
const input = event.currentTarget
|
const input = event.currentTarget
|
||||||
let value = parseInt(input.value) || 0
|
let value = parseInt(input.value) || 0
|
||||||
const max = parseInt(input.max) || 0
|
const max = parseInt(input.max) || 0
|
||||||
|
|
||||||
// Ensure value is within valid range (0 to max available)
|
// Ensure value is within valid range (0 to max available)
|
||||||
if (value < 0) value = 0
|
if (value < 0) value = 0
|
||||||
if (value > max) value = max
|
if (value > max) value = max
|
||||||
|
|
||||||
input.value = value
|
input.value = value
|
||||||
this.updateCartSummary()
|
this.updateCartSummary()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate and update the cart summary (total quantity and amount)
|
// Calculate and update the cart summary (total quantity and amount)
|
||||||
updateCartSummary() {
|
updateCartSummary() {
|
||||||
let totalQuantity = 0
|
let totalQuantity = 0
|
||||||
let totalAmount = 0
|
let totalAmount = 0
|
||||||
|
|
||||||
// Sum up quantities and calculate total amount
|
// Sum up quantities and calculate total amount
|
||||||
this.quantityInputTargets.forEach(input => {
|
this.quantityInputTargets.forEach(input => {
|
||||||
const quantity = parseInt(input.value) || 0
|
const quantity = parseInt(input.value) || 0
|
||||||
const price = parseInt(input.dataset.price) || 0
|
const price = parseInt(input.dataset.price) || 0
|
||||||
|
|
||||||
totalQuantity += quantity
|
totalQuantity += quantity
|
||||||
totalAmount += quantity * price
|
totalAmount += quantity * price
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update the displayed total quantity and amount
|
// Update the displayed total quantity and amount
|
||||||
this.totalQuantityTarget.textContent = totalQuantity
|
this.totalQuantityTarget.textContent = totalQuantity
|
||||||
this.totalAmountTarget.textContent = `€${(totalAmount / 100).toFixed(2)}`
|
this.totalAmountTarget.textContent = `€${(totalAmount / 100).toFixed(2)}`
|
||||||
|
|
||||||
// Enable/disable checkout button based on whether any tickets are selected
|
// Enable/disable checkout button based on whether any tickets are selected
|
||||||
if (totalQuantity > 0) {
|
if (totalQuantity > 0) {
|
||||||
this.checkoutButtonTarget.classList.remove('opacity-50', 'cursor-not-allowed')
|
this.checkoutButtonTarget.classList.remove('opacity-50', 'cursor-not-allowed')
|
||||||
@@ -85,36 +85,36 @@ export default class extends Controller {
|
|||||||
this.checkoutButtonTarget.disabled = true
|
this.checkoutButtonTarget.disabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle form submission - store cart in session before proceeding
|
// Handle form submission - store cart in session before proceeding
|
||||||
async submitCart(event) {
|
async submitCart(event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
const cartData = this.buildCartData()
|
const cartData = this.buildCartData()
|
||||||
|
|
||||||
if (Object.keys(cartData).length === 0) {
|
if (Object.keys(cartData).length === 0) {
|
||||||
alert('Veuillez sélectionner au moins un billet')
|
alert('Veuillez sélectionner au moins un billet')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Store cart data in session
|
// Store cart data in session
|
||||||
await this.storeCartInSession(cartData)
|
await this.storeCartInSession(cartData)
|
||||||
|
|
||||||
// Redirect to tickets/new page
|
// Redirect to tickets/new page
|
||||||
const ticketNewUrl = `/events/${this.eventSlugValue}.${this.eventIdValue}/tickets/new`
|
const ticketNewUrl = `/events/${this.eventSlugValue}.${this.eventIdValue}/tickets/new`
|
||||||
window.location.href = ticketNewUrl
|
window.location.href = ticketNewUrl
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error storing cart:', error)
|
console.error('Error storing cart:', error)
|
||||||
alert('Une erreur est survenue. Veuillez réessayer.')
|
alert('Une erreur est survenue. Veuillez réessayer.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build cart data from current form state
|
// Build cart data from current form state
|
||||||
buildCartData() {
|
buildCartData() {
|
||||||
const cartData = {}
|
const cartData = {}
|
||||||
|
|
||||||
this.quantityInputTargets.forEach(input => {
|
this.quantityInputTargets.forEach(input => {
|
||||||
const quantity = parseInt(input.value) || 0
|
const quantity = parseInt(input.value) || 0
|
||||||
if (quantity > 0) {
|
if (quantity > 0) {
|
||||||
@@ -124,14 +124,14 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return cartData
|
return cartData
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store cart data in session via AJAX
|
// Store cart data in session via AJAX
|
||||||
async storeCartInSession(cartData) {
|
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, {
|
const response = await fetch(storeCartUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -140,11 +140,11 @@ export default class extends Controller {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ cart: cartData })
|
body: JSON.stringify({ cart: cartData })
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to store cart data: ${response.status} ${response.statusText}`)
|
throw new Error(`Failed to store cart data: ${response.status} ${response.statusText}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ Rails.application.routes.draw do
|
|||||||
# === Events ===
|
# === Events ===
|
||||||
get "events", to: "events#index", as: "events"
|
get "events", to: "events#index", as: "events"
|
||||||
get "events/:slug.:id", to: "events#show", as: "event"
|
get "events/:slug.:id", to: "events#show", as: "event"
|
||||||
post "events/:slug.:id/store_cart", to: "events#store_cart", as: "store_cart"
|
|
||||||
|
|
||||||
# === Tickets ===
|
# === Tickets ===
|
||||||
get "events/:slug.:id/tickets/new", to: "tickets#new", as: "ticket_new"
|
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 :api do
|
||||||
namespace :v1 do
|
namespace :v1 do
|
||||||
# RESTful routes for event management
|
# 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 ]
|
# resources :bundles, only: [ :index, :show, :create, :update, :destroy ]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user