develop #3

Merged
kbe merged 227 commits from develop into main 2025-09-16 14:35:23 +00:00
5 changed files with 56 additions and 48 deletions
Showing only changes of commit b493027c86 - Show all commits

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()
} }
} }

View File

@@ -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 ]