Compare commits
7 Commits
24126eb834
...
d6184b6c84
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6184b6c84 | ||
|
|
4cde466f9a | ||
|
|
ee43996a77 | ||
|
|
f0d32bf3f1 | ||
|
|
20f926cd7a | ||
|
|
d1ef962f74 | ||
|
|
e84d9aad5b |
1
Gemfile
1
Gemfile
@@ -75,6 +75,7 @@ group :test do
|
||||
gem "rails-controller-testing"
|
||||
# For mocking and stubbing
|
||||
gem "mocha"
|
||||
gem "timecop"
|
||||
end
|
||||
|
||||
gem "devise", "~> 4.9"
|
||||
|
||||
@@ -381,6 +381,7 @@ GEM
|
||||
thruster (0.1.15-aarch64-linux)
|
||||
thruster (0.1.15-x86_64-darwin)
|
||||
thruster (0.1.15-x86_64-linux)
|
||||
timecop (0.9.10)
|
||||
timeout (0.4.3)
|
||||
ttfunk (1.8.0)
|
||||
bigdecimal (~> 3.1)
|
||||
@@ -452,6 +453,7 @@ DEPENDENCIES
|
||||
stimulus-rails
|
||||
stripe (~> 15.5)
|
||||
thruster
|
||||
timecop
|
||||
turbo-rails
|
||||
tzinfo-data
|
||||
web-console
|
||||
|
||||
25
app/controllers/api/v1/carts_controller.rb
Normal file
25
app/controllers/api/v1/carts_controller.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
module Api
|
||||
module V1
|
||||
class CartsController < ApiController
|
||||
# Skip API key authentication for store_cart action (used by frontend forms)
|
||||
skip_before_action :authenticate_api_key, only: [ :store ]
|
||||
|
||||
def store
|
||||
event_id = params[:event_id]
|
||||
@event = Event.find(event_id)
|
||||
|
||||
cart_data = params[:cart] || {}
|
||||
session[:pending_cart] = cart_data
|
||||
session[:event_id] = @event.id
|
||||
|
||||
render json: { status: "success", message: "Cart stored successfully" }
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { status: "error", message: "Event not found" }, status: :not_found
|
||||
rescue => e
|
||||
error_message = e.message.present? ? e.message : "Unknown error"
|
||||
Rails.logger.error "Error storing cart: #{error_message}"
|
||||
render json: { status: "error", message: "Failed to store cart" }, status: 500
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
# Contrôleur API pour la gestion des ressources d'événements
|
||||
# Fournit des points de terminaison RESTful pour les opérations CRUD sur le modèle Event
|
||||
# API Controller for managing event resources
|
||||
# Provides RESTful endpoints for CRUD operations on the Event model
|
||||
|
||||
module Api
|
||||
module V1
|
||||
@@ -7,27 +7,27 @@ module Api
|
||||
# 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
|
||||
# Loads the event before certain actions to reduce duplications
|
||||
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)
|
||||
# Retrieves all events sorted by creation date (most recent first)
|
||||
def index
|
||||
@events = Event.all.order(created_at: :desc)
|
||||
render json: @events, status: :ok
|
||||
end
|
||||
|
||||
# GET /api/v1/events/:id
|
||||
# Récupère un seul événement par son ID
|
||||
# Retourne 404 si l'événement n'est pas trouvé
|
||||
# Retrieves a single event by its ID
|
||||
# Returns 404 if the event is not found
|
||||
def show
|
||||
render json: @event, status: :ok
|
||||
end
|
||||
|
||||
# POST /api/v1/events
|
||||
# Crée un nouvel événement avec les attributs fournis
|
||||
# Retourne 201 Created en cas de succès avec les données de l'événement
|
||||
# Retourne 422 Unprocessable Entity avec les messages d'erreur en cas d'échec
|
||||
# Creates a new event with the provided attributes
|
||||
# Returns 201 Created on success with the event data
|
||||
# Returns 422 Unprocessable Entity with error messages on failure
|
||||
def create
|
||||
@event = Event.new(event_params)
|
||||
if @event.save
|
||||
@@ -38,9 +38,9 @@ module Api
|
||||
end
|
||||
|
||||
# PATCH/PUT /api/v1/events/:id
|
||||
# Met à jour un événement existant avec les attributs fournis
|
||||
# Retourne 200 OK avec les données mises à jour en cas de succès
|
||||
# Retourne 422 Unprocessable Entity avec les messages d'erreur en cas d'échec
|
||||
# Updates an existing event with the provided attributes
|
||||
# Returns 200 OK with updated data on success
|
||||
# Returns 422 Unprocessable Entity with error messages on failure
|
||||
def update
|
||||
if @event.update(event_params)
|
||||
render json: @event, status: :ok
|
||||
@@ -50,8 +50,8 @@ module Api
|
||||
end
|
||||
|
||||
# DELETE /api/v1/events/:id
|
||||
# Supprime définitivement un événement
|
||||
# Retourne 204 No Content en cas de succès
|
||||
# Permanently deletes an event
|
||||
# Returns 204 No Content on success
|
||||
def destroy
|
||||
@event.destroy
|
||||
head :no_content
|
||||
@@ -66,33 +66,37 @@ module Api
|
||||
|
||||
render json: { status: "success", message: "Cart stored successfully" }
|
||||
rescue => e
|
||||
error_message = e.message.present? ? e.message : "Erreur inconnue"
|
||||
error_message = e.message.present? ? e.message : "Unknown error"
|
||||
Rails.logger.error "Error storing cart: #{error_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
|
||||
# Utilisé comme before_action pour les actions show, update et destroy
|
||||
# Finds an event by its ID or returns 404 Not Found
|
||||
# Used as before_action for the show, update, and destroy actions
|
||||
def set_event
|
||||
@event = Event.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { error: "Événement non trouvé" }, status: :not_found
|
||||
render json: { error: "Event not found" }, status: :not_found
|
||||
end
|
||||
|
||||
# Paramètres forts pour la création et la mise à jour des événements
|
||||
# Liste blanche des attributs autorisés pour éviter les vulnérabilités de mass assignment
|
||||
# Strong parameters for creating and updating events
|
||||
# Whitelist of allowed attributes to avoid mass assignment vulnerabilities
|
||||
def event_params
|
||||
params.require(:event).permit(
|
||||
:name,
|
||||
:slug,
|
||||
:description,
|
||||
:state,
|
||||
:venue_name,
|
||||
:venue_address,
|
||||
:start_time,
|
||||
:end_time,
|
||||
:latitude,
|
||||
:longitude,
|
||||
:featured
|
||||
:featured,
|
||||
:user_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
module Api
|
||||
module V1
|
||||
class OrdersController < ApiController
|
||||
# Skip API key authentication for store_cart action (used by frontend forms)
|
||||
skip_before_action :authenticate_api_key, only: [ :store_cart ]
|
||||
|
||||
before_action :set_order, only: [ :show, :checkout, :retry_payment, :increment_payment_attempt ]
|
||||
before_action :set_event, only: [ :new, :create ]
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Provides authentication and common functionality for API controllers
|
||||
class ApiController < ApplicationController
|
||||
# Disable CSRF protection for API requests (token-based authentication instead)
|
||||
protect_from_forgery with: :null_session
|
||||
protect_from_forgery prepend: true
|
||||
|
||||
# Authenticate all API requests using API key
|
||||
# Must be called before any API action
|
||||
|
||||
@@ -38,8 +38,6 @@ class ApplicationController < ActionController::Base
|
||||
# Skip for API endpoints
|
||||
controller_name.start_with?("api/") ||
|
||||
# Skip for health checks
|
||||
controller_name == "rails/health" ||
|
||||
# Skip for home page (when not signed in)
|
||||
(controller_name == "pages" && action_name == "home")
|
||||
controller_name == "rails/health"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ export default class extends Controller {
|
||||
"checkoutButton",
|
||||
"form",
|
||||
];
|
||||
static values = { eventSlug: String, eventId: String };
|
||||
static values = { eventSlug: String, eventId: String, orderNewUrl: String, storeCartUrl: String };
|
||||
|
||||
// Initialize the controller and update the cart summary
|
||||
connect() {
|
||||
@@ -117,9 +117,9 @@ export default class extends Controller {
|
||||
// Store cart data in session
|
||||
await this.storeCartInSession(cartData);
|
||||
|
||||
// Redirect to event-scoped orders/new page
|
||||
const OrderNewUrl = `/orders/new/events/${this.eventSlugValue}.${this.eventIdValue}`;
|
||||
window.location.href = OrderNewUrl;
|
||||
// Redirect to event-scoped orders/new page
|
||||
const orderNewUrl = this.orderNewUrlValue;
|
||||
window.location.href = orderNewUrl;
|
||||
} catch (error) {
|
||||
console.error("Error storing cart:", error);
|
||||
alert("Une erreur est survenue. Veuillez réessayer.");
|
||||
@@ -145,7 +145,7 @@ export default class extends Controller {
|
||||
|
||||
// Store cart data in session via AJAX
|
||||
async storeCartInSession(cartData) {
|
||||
const storeCartUrl = `/api/v1/events/${this.eventIdValue}/store_cart`;
|
||||
const storeCartUrl = this.storeCartUrlValue;
|
||||
|
||||
const response = await fetch(storeCartUrl, {
|
||||
method: "POST",
|
||||
@@ -155,7 +155,7 @@ export default class extends Controller {
|
||||
.querySelector('meta[name="csrf-token"]')
|
||||
.getAttribute("content"),
|
||||
},
|
||||
body: JSON.stringify({ cart: cartData }),
|
||||
body: JSON.stringify({ cart: cartData, event_id: this.eventIdValue }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -96,6 +96,12 @@ class Event < ApplicationRecord
|
||||
Time.current >= end_time
|
||||
end
|
||||
|
||||
# Check if booking is allowed during the event
|
||||
# This is a simple attribute reader that defaults to false if nil
|
||||
def allow_booking_during_event?
|
||||
!!allow_booking_during_event
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Determine if we should perform server-side geocoding
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class TicketType < ApplicationRecord
|
||||
# Associations
|
||||
belongs_to :event
|
||||
has_many :tickets, dependent: :destroy
|
||||
has_many :tickets, dependent: :destroy # Cannot delete ticket types if already tickets sold
|
||||
|
||||
# Validations
|
||||
validates :name, presence: true, length: { minimum: 3, maximum: 50 }
|
||||
@@ -80,6 +80,10 @@ class TicketType < ApplicationRecord
|
||||
|
||||
def sale_times_within_event_period
|
||||
return unless event&.start_time && sale_end_at
|
||||
errors.add(:sale_end_at, "cannot be after the event starts") if sale_end_at > event.start_time
|
||||
|
||||
# Only enforce this restriction if booking during event is not allowed
|
||||
unless event.allow_booking_during_event?
|
||||
errors.add(:sale_end_at, "cannot be after the event starts") if sale_end_at > event.start_time
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,26 +6,41 @@
|
||||
<%# ] %>
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<nav class="inline-flex items-center gap-2 bg-white px-4 py-3 rounded-xl shadow-sm border border-gray-100 mb-8" aria-label="Breadcrumb">
|
||||
<% crumbs.each_with_index do |crumb, index| %>
|
||||
<% if crumb[:path].present? %>
|
||||
<%# Crumb with link %>
|
||||
<%= link_to crumb[:path], class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-primary-600 transition-colors duration-200" do %>
|
||||
<% if index == 0 %>
|
||||
<i data-lucide="home" class="w-4 h-4 mr-2"></i>
|
||||
<nav class="w-full bg-white px-3 sm:px-4 py-3 rounded-xl shadow-sm border border-gray-100 mb-6 sm:mb-8 overflow-hidden" aria-label="Breadcrumb">
|
||||
<div class="flex items-center gap-1 sm:gap-2 min-w-0">
|
||||
<% crumbs.each_with_index do |crumb, index| %>
|
||||
<% if crumb[:path].present? %>
|
||||
<%# Crumb with link %>
|
||||
<%= link_to crumb[:path], class: "inline-flex items-center text-xs sm:text-sm font-medium text-gray-700 hover:text-primary-600 transition-colors duration-200 flex-shrink-0" do %>
|
||||
<% if index == 0 %>
|
||||
<i data-lucide="home" class="w-3 h-3 sm:w-4 sm:h-4 mr-1 sm:mr-2 flex-shrink-0"></i>
|
||||
<% end %>
|
||||
<span class="<%= 'hidden sm:inline' if index > 0 && index < crumbs.length - 2 %>">
|
||||
<%= crumb[:name] %>
|
||||
</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%# Current page (no link) %>
|
||||
<span class="text-xs sm:text-sm font-medium text-primary-600 truncate min-w-0 flex-1" aria-current="page">
|
||||
<%= crumb[:name] %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<%# Separator (except for the last item) %>
|
||||
<% if index < crumbs.length - 1 %>
|
||||
<% if index == 0 || index >= crumbs.length - 2 %>
|
||||
<i data-lucide="chevron-right" class="w-3 h-3 sm:w-4 sm:h-4 text-gray-400 flex-shrink-0"></i>
|
||||
<% else %>
|
||||
<span class="hidden sm:inline">
|
||||
<i data-lucide="chevron-right" class="w-4 h-4 text-gray-400 flex-shrink-0"></i>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= crumb[:name] %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%# Current page (no link) %>
|
||||
<span class="text-sm font-medium text-primary-600 truncate max-w-xs" aria-current="page">
|
||||
<%= crumb[:name] %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<%# Separator (except for the last item) %>
|
||||
<% if index < crumbs.length - 1 %>
|
||||
<i data-lucide="chevron-right" class="w-4 h-4 text-gray-400"></i>
|
||||
<%# Show ellipsis on mobile when there are more than 3 items %>
|
||||
<% if crumbs.length > 3 %>
|
||||
<span class="text-gray-400 text-xs font-medium sm:hidden flex-shrink-0">...</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -107,10 +107,7 @@
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-4">Aucun événement disponible</h3>
|
||||
<p class="text-gray-600 mb-8 max-w-md mx-auto">Il n'y a aucun événement à venir pour le moment. Revenez bientôt pour découvrir de nouvelles sorties!</p>
|
||||
<%= link_to "Retour à l'accueil", root_path, class: "inline-flex items-center bg-purple-600 text-white px-6 py-3 rounded-full font-semibold hover:bg-purple-700 transition-all duration-200 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5" do %>
|
||||
<i data-lucide="home" class="w-4 h-4 mr-2"></i>
|
||||
Retour à l'accueil
|
||||
<% end %>
|
||||
<%= link_to "<i data-lucide=\"home\" class=\"w-4 h-4 mr-2\"></i> Retour à l'accueil".html_safe, root_path, class: "inline-flex items-center bg-purple-600 text-white px-6 py-3 rounded-full font-semibold hover:bg-purple-700 transition-all duration-200 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -135,8 +135,12 @@
|
||||
controller: "ticket-selection",
|
||||
ticket_selection_target: "form",
|
||||
ticket_selection_event_slug_value: @event.slug,
|
||||
ticket_selection_event_id_value: @event.id
|
||||
} do |form| %>
|
||||
ticket_selection_event_id_value: @event.id,
|
||||
ticket_selection_order_new_url_value: event_order_new_path(@event.slug, @event.id),
|
||||
ticket_selection_store_cart_url_value: api_v1_store_cart_path,
|
||||
ticket_selection_order_new_url_value: event_order_new_path(@event.slug, @event.id),
|
||||
ticket_selection_store_cart_url_value: api_v1_store_cart_path
|
||||
} do |form| %>
|
||||
|
||||
<div class="bg-gradient-to-br from-purple-50 to-indigo-50 rounded-2xl border border-purple-100 p-6 shadow-sm">
|
||||
<div class="flex justify-center sm:justify-start mb-6">
|
||||
|
||||
@@ -139,10 +139,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="checkout-button"
|
||||
class="w-full btn btn-primary py-4 px-6 rounded-xl transition-all duration-200 transform hover:scale-105 active:scale-95 shadow-lg hover:shadow-xl"
|
||||
>
|
||||
<button
|
||||
id="checkout-button"
|
||||
data-order-id="<%= @order.id %>"
|
||||
data-increment-url="/api/v1/orders/<%= @order.id %>/increment_payment_attempt"
|
||||
data-session-id="<%= @checkout_session.id if @checkout_session.present? %>"
|
||||
class="w-full btn btn-primary py-4 px-6 rounded-xl transition-all duration-200 transform hover:scale-105 active:scale-95 shadow-lg hover:shadow-xl"
|
||||
>
|
||||
<div class="flex items-center justify-center">
|
||||
<i data-lucide="credit-card" class="w-5 h-5 mr-2"></i>
|
||||
Payer <%= @order.total_amount_euros %>€
|
||||
@@ -199,13 +202,16 @@
|
||||
|
||||
try {
|
||||
// Increment payment attempt counter
|
||||
console.log('Incrementing payment attempt for order:', '<%= @order.id %>');
|
||||
const response = await fetch('/api/v1/orders/<%= @order.id %>/increment_payment_attempt', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const orderId = checkoutButton.dataset.orderId;
|
||||
const incrementUrl = checkoutButton.dataset.incrementUrl;
|
||||
console.log('Incrementing payment attempt for order:', orderId);
|
||||
const response = await fetch(incrementUrl, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('[name=csrf-token]').content
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Payment attempt increment failed:', response.status, response.statusText);
|
||||
@@ -226,10 +232,11 @@
|
||||
`;
|
||||
|
||||
// Redirect to Stripe
|
||||
console.log('Redirecting to Stripe with session ID:', '<%= @checkout_session&.id %>');
|
||||
const stripeResult = await stripe.redirectToCheckout({
|
||||
sessionId: '<%= @checkout_session.id %>'
|
||||
});
|
||||
const sessionId = checkoutButton.dataset.sessionId;
|
||||
console.log('Redirecting to Stripe with session ID:', sessionId);
|
||||
const stripeResult = await stripe.redirectToCheckout({
|
||||
sessionId: sessionId
|
||||
});
|
||||
|
||||
if (stripeResult.error) {
|
||||
throw new Error(stripeResult.error.message);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<%= render 'components/breadcrumb', crumbs: [
|
||||
{ name: 'Accueil', path: root_path },
|
||||
{ name: 'Événements', path: events_path },
|
||||
|
||||
@@ -9,19 +9,22 @@
|
||||
{ name: 'Mes événements' }
|
||||
] %>
|
||||
|
||||
<div class="mb-8 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">Mes événements</h1>
|
||||
<p class="text-gray-600">Gérez tous vos événements depuis cette interface</p>
|
||||
<div class="mb-8">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2">Mes événements</h1>
|
||||
<p class="text-gray-600">Gérez tous vos événements depuis cette interface</p>
|
||||
</div>
|
||||
<%= link_to new_promoter_event_path, class: "inline-flex items-center justify-center px-6 py-3 bg-gray-900 text-white font-medium rounded-lg hover:bg-gray-800 transition-colors duration-200 w-full sm:w-auto" do %>
|
||||
<i data-lucide="plus" class="w-4 h-4 mr-2"></i>
|
||||
Créer un événement
|
||||
<% end %>
|
||||
</div>
|
||||
<%= link_to new_promoter_event_path, class: "inline-flex items-center px-6 py-3 bg-gray-900 text-white font-medium rounded-lg hover:bg-gray-800 transition-colors duration-200" do %>
|
||||
<i data-lucide="plus" class="w-4 h-4 mr-2"></i>
|
||||
Créer un événement
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if @events.any? %>
|
||||
<div class="bg-white rounded-2xl shadow-xl border border-gray-200 overflow-hidden">
|
||||
<!-- Desktop Table View -->
|
||||
<div class="hidden lg:block bg-white rounded-2xl shadow-xl border border-gray-200 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50 border-b border-gray-200">
|
||||
@@ -30,7 +33,7 @@
|
||||
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Statut</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Lieu</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-48 lg:w-auto">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@@ -94,27 +97,32 @@
|
||||
<div><%= event.venue_name %></div>
|
||||
<div class="text-xs text-gray-400 truncate max-w-xs"><%= event.venue_address %></div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<%= link_to promoter_event_path(event), class: "text-gray-400 hover:text-gray-600 transition-colors", title: "Voir" do %>
|
||||
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||
<td class="px-6 py-4 w-48 lg:w-auto">
|
||||
<div class="flex flex-col lg:flex-row items-stretch lg:items-center space-y-2 lg:space-y-0 lg:space-x-2 min-w-0">
|
||||
<%= link_to promoter_event_path(event), class: "flex-1 lg:flex-initial inline-flex items-center justify-center px-3 py-2 bg-gray-100 text-gray-700 text-sm font-medium rounded-lg hover:bg-gray-200 transition-colors lg:bg-transparent lg:text-gray-400 lg:hover:text-gray-600 lg:p-0 lg:rounded-none whitespace-nowrap", title: "Voir" do %>
|
||||
<i data-lucide="eye" class="w-4 h-4 lg:mr-0 mr-2"></i>
|
||||
<span class="lg:hidden">Voir</span>
|
||||
<% end %>
|
||||
<%= link_to edit_promoter_event_path(event), class: "text-gray-400 hover:text-blue-600 transition-colors", title: "Modifier" do %>
|
||||
<i data-lucide="edit" class="w-4 h-4"></i>
|
||||
<%= link_to edit_promoter_event_path(event), class: "flex-1 lg:flex-initial inline-flex items-center justify-center px-3 py-2 bg-blue-100 text-blue-700 text-sm font-medium rounded-lg hover:bg-blue-200 transition-colors lg:bg-transparent lg:text-gray-400 lg:hover:text-blue-600 lg:p-0 lg:rounded-none whitespace-nowrap", title: "Modifier" do %>
|
||||
<i data-lucide="edit" class="w-4 h-4 lg:mr-0 mr-2"></i>
|
||||
<span class="lg:hidden">Modifier</span>
|
||||
<% end %>
|
||||
<% if event.draft? %>
|
||||
<%= button_to publish_promoter_event_path(event), method: :patch, class: "text-gray-400 hover:text-green-600 transition-colors", title: "Publier" do %>
|
||||
<i data-lucide="upload" class="w-4 h-4"></i>
|
||||
<%= button_to publish_promoter_event_path(event), method: :patch, class: "flex-1 lg:flex-initial inline-flex items-center justify-center px-3 py-2 bg-green-100 text-green-700 text-sm font-medium rounded-lg hover:bg-green-200 transition-colors lg:bg-transparent lg:text-gray-400 lg:hover:text-green-600 lg:p-0 lg:rounded-none whitespace-nowrap", title: "Publier" do %>
|
||||
<i data-lucide="upload" class="w-4 h-4 lg:mr-0 mr-2"></i>
|
||||
<span class="lg:hidden">Publier</span>
|
||||
<% end %>
|
||||
<% elsif event.published? %>
|
||||
<%= button_to unpublish_promoter_event_path(event), method: :patch, class: "text-gray-400 hover:text-yellow-600 transition-colors", title: "Dépublier" do %>
|
||||
<i data-lucide="download" class="w-4 h-4"></i>
|
||||
<%= button_to unpublish_promoter_event_path(event), method: :patch, class: "flex-1 lg:flex-initial inline-flex items-center justify-center px-3 py-2 bg-yellow-100 text-yellow-700 text-sm font-medium rounded-lg hover:bg-yellow-200 transition-colors lg:bg-transparent lg:text-gray-400 lg:hover:text-yellow-600 lg:p-0 lg:rounded-none whitespace-nowrap", title: "Dépublier" do %>
|
||||
<i data-lucide="download" class="w-4 h-4 lg:mr-0 mr-2"></i>
|
||||
<span class="lg:hidden">Dépublier</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= button_to promoter_event_path(event), method: :delete,
|
||||
data: { confirm: "Êtes-vous sûr de vouloir supprimer cet événement ?" },
|
||||
class: "text-gray-400 hover:text-red-600 transition-colors", title: "Supprimer" do %>
|
||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
class: "flex-1 lg:flex-initial inline-flex items-center justify-center px-3 py-2 bg-red-100 text-red-700 text-sm font-medium rounded-lg hover:bg-red-200 transition-colors lg:bg-transparent lg:text-gray-400 lg:hover:text-red-600 lg:p-0 lg:rounded-none whitespace-nowrap", title: "Supprimer" do %>
|
||||
<i data-lucide="trash-2" class="w-4 h-4 lg:mr-0 mr-2"></i>
|
||||
<span class="lg:hidden">Supprimer</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
@@ -125,17 +133,108 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Card View -->
|
||||
<div class="lg:hidden space-y-4">
|
||||
<% @events.each do |event| %>
|
||||
<div class="bg-white rounded-2xl shadow-lg border border-gray-200 overflow-hidden">
|
||||
<div class="p-4">
|
||||
<!-- Event Header -->
|
||||
<div class="flex items-start space-x-4 mb-4">
|
||||
<div class="h-12 w-12 rounded-lg bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center flex-shrink-0">
|
||||
<i data-lucide="calendar" class="w-6 h-6 text-white"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-1">
|
||||
<%= link_to event.name, promoter_event_path(event), class: "hover:text-purple-600 transition-colors" %>
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 line-clamp-2">
|
||||
<%= event.description.truncate(100) %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="flex flex-wrap items-center gap-2 mb-4">
|
||||
<% case event.state %>
|
||||
<% when "draft" %>
|
||||
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-gray-100 text-gray-800">
|
||||
<i data-lucide="edit-3" class="w-3 h-3 mr-1"></i>
|
||||
Brouillon
|
||||
</span>
|
||||
<% when "published" %>
|
||||
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
|
||||
<i data-lucide="eye" class="w-3 h-3 mr-1"></i>
|
||||
Publié
|
||||
</span>
|
||||
<% when "canceled" %>
|
||||
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800">
|
||||
<i data-lucide="x-circle" class="w-3 h-3 mr-1"></i>
|
||||
Annulé
|
||||
</span>
|
||||
<% when "sold_out" %>
|
||||
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||
<i data-lucide="users" class="w-3 h-3 mr-1"></i>
|
||||
Complet
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<% if event.featured? %>
|
||||
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full bg-yellow-100 text-yellow-800">
|
||||
<i data-lucide="star" class="w-3 h-3 mr-1"></i>
|
||||
À la une
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<!-- Details Grid -->
|
||||
<div class="grid grid-cols-2 gap-4 mb-4 text-sm">
|
||||
<div>
|
||||
<dt class="font-medium text-gray-500 mb-1">Date</dt>
|
||||
<dd class="text-gray-900">
|
||||
<% if event.start_time %>
|
||||
<div><%= event.start_time.strftime("%d/%m/%Y") %></div>
|
||||
<div class="text-xs text-gray-500"><%= event.start_time.strftime("%H:%M") %></div>
|
||||
<% else %>
|
||||
<span class="text-gray-400">Non définie</span>
|
||||
<% end %>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="font-medium text-gray-500 mb-1">Lieu</dt>
|
||||
<dd class="text-gray-900">
|
||||
<div class="truncate"><%= event.venue_name %></div>
|
||||
<div class="text-xs text-gray-500 truncate"><%= event.venue_address %></div>
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="space-y-2 pt-4 border-t border-gray-100">
|
||||
<%= link_to promoter_event_path(event), class: "w-full inline-flex items-center justify-center px-3 py-2 bg-gray-100 text-gray-700 text-sm font-medium rounded-lg hover:bg-gray-200 transition-colors" do %>
|
||||
<i data-lucide="eye" class="w-4 h-4 mr-2"></i>
|
||||
Voir
|
||||
<% end %>
|
||||
<%= link_to edit_promoter_event_path(event), class: "w-full inline-flex items-center justify-center px-3 py-2 bg-blue-100 text-blue-700 text-sm font-medium rounded-lg hover:bg-blue-200 transition-colors" do %>
|
||||
<i data-lucide="edit" class="w-4 h-4 mr-2"></i>
|
||||
Modifier
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<%= paginate @events if respond_to?(:paginate) %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="bg-white rounded-2xl border-2 border-dashed border-gray-300 p-12 text-center">
|
||||
<div class="mx-auto h-24 w-24 rounded-full bg-gray-100 flex items-center justify-center mb-6">
|
||||
<i data-lucide="calendar-plus" class="w-12 h-12 text-gray-400"></i>
|
||||
<div class="bg-white rounded-2xl border-2 border-dashed border-gray-300 p-6 sm:p-12 text-center">
|
||||
<div class="mx-auto h-20 w-20 sm:h-24 sm:w-24 rounded-full bg-gray-100 flex items-center justify-center mb-6">
|
||||
<i data-lucide="calendar-plus" class="w-10 h-10 sm:w-12 sm:h-12 text-gray-400"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">Aucun événement</h3>
|
||||
<p class="text-gray-500 mb-6">Vous n'avez pas encore créé d'événement. Commencez dès maintenant !</p>
|
||||
<%= link_to new_promoter_event_path, class: "inline-flex items-center px-6 py-3 bg-gray-900 text-white font-medium rounded-lg hover:bg-gray-800 transition-colors duration-200" do %>
|
||||
<h3 class="text-lg sm:text-xl font-semibold text-gray-900 mb-2">Aucun événement</h3>
|
||||
<p class="text-gray-500 mb-6 px-4">Vous n'avez pas encore créé d'événement. Commencez dès maintenant !</p>
|
||||
<%= link_to new_promoter_event_path, class: "inline-flex items-center justify-center px-6 py-3 bg-gray-900 text-white font-medium rounded-lg hover:bg-gray-800 transition-colors duration-200 w-full sm:w-auto" do %>
|
||||
<i data-lucide="plus" class="w-4 h-4 mr-2"></i>
|
||||
Créer mon premier événement
|
||||
<% end %>
|
||||
|
||||
@@ -12,57 +12,58 @@
|
||||
|
||||
<!-- Header with actions -->
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<%= link_to promoter_events_path, class: "text-gray-400 hover:text-gray-600 transition-colors" do %>
|
||||
<i data-lucide="arrow-left" class="w-5 h-5"></i>
|
||||
<% end %>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2"><%= @event.name %></h1>
|
||||
<div class="flex items-center space-x-4 text-sm text-gray-500">
|
||||
<span class="flex items-center">
|
||||
<i data-lucide="calendar" class="w-4 h-4 mr-1"></i>
|
||||
<%= @event.start_time&.strftime("%d/%m/%Y à %H:%M") || "Date non définie" %>
|
||||
</span>
|
||||
<span class="flex items-center">
|
||||
<i data-lucide="map-pin" class="w-4 h-4 mr-1"></i>
|
||||
<%= @event.venue_name %>
|
||||
</span>
|
||||
</div>
|
||||
<!-- Back button and title -->
|
||||
<div class="flex items-center space-x-4 mb-6">
|
||||
<%= link_to promoter_events_path, class: "text-gray-400 hover:text-gray-600 transition-colors flex-shrink-0" do %>
|
||||
<i data-lucide="arrow-left" class="w-5 h-5"></i>
|
||||
<% end %>
|
||||
<div class="min-w-0 flex-1">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2 truncate"><%= @event.name %></h1>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 text-sm text-gray-500">
|
||||
<span class="flex items-center">
|
||||
<i data-lucide="calendar" class="w-4 h-4 mr-1 flex-shrink-0"></i>
|
||||
<span class="truncate"><%= @event.start_time&.strftime("%d/%m/%Y à %H:%M") || "Date non définie" %></span>
|
||||
</span>
|
||||
<span class="flex items-center">
|
||||
<i data-lucide="map-pin" class="w-4 h-4 mr-1 flex-shrink-0"></i>
|
||||
<span class="truncate"><%= @event.venue_name %></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<%= link_to edit_promoter_event_path(@event), class: "inline-flex items-center px-4 py-2 bg-white border border-gray-300 text-gray-700 font-medium rounded-lg hover:bg-gray-50 transition-colors duration-200" do %>
|
||||
<i data-lucide="edit" class="w-4 h-4 mr-2"></i>
|
||||
Modifier
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if @event.draft? %>
|
||||
<% if @event.ticket_types.blank? %>
|
||||
<%= button_to publish_promoter_event_path(@event), method: :patch, disabled: true, class: "inline-flex items-center px-4 py-2 bg-gray-400 text-white font-medium rounded-lg cursor-not-allowed transition-colors duration-200", title: "Vous devez créer au moins un type de billet avant de publier" do %>
|
||||
<i data-lucide="upload" class="w-4 h-4 mr-2"></i>
|
||||
Publier
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button_to publish_promoter_event_path(@event), method: :patch, class: "inline-flex items-center px-4 py-2 bg-green-600 text-white font-medium rounded-lg hover:bg-green-700 transition-colors duration-200" do %>
|
||||
<i data-lucide="upload" class="w-4 h-4 mr-2"></i>
|
||||
Publier
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% elsif @event.published? %>
|
||||
<%= button_to unpublish_promoter_event_path(@event), method: :patch, class: "inline-flex items-center px-4 py-2 bg-yellow-600 text-white font-medium rounded-lg hover:bg-yellow-700 transition-colors duration-200" do %>
|
||||
<i data-lucide="download" class="w-4 h-4 mr-2"></i>
|
||||
Dépublier
|
||||
<% end %>
|
||||
<% end %>
|
||||
<!-- Action buttons -->
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<%= link_to edit_promoter_event_path(@event), class: "w-full sm:w-auto inline-flex items-center justify-center px-4 py-2 bg-white border border-gray-300 text-gray-700 font-medium rounded-lg hover:bg-gray-50 transition-colors duration-200" do %>
|
||||
<i data-lucide="edit" class="w-4 h-4 mr-2"></i>
|
||||
Modifier
|
||||
<% end %>
|
||||
|
||||
<% if @event.published? %>
|
||||
<%= button_to cancel_promoter_event_path(@event), method: :patch, class: "inline-flex items-center px-4 py-2 bg-red-600 text-white font-medium rounded-lg hover:bg-red-700 transition-colors duration-200", data: { confirm: "Êtes-vous sûr de vouloir annuler cet événement ?" } do %>
|
||||
<i data-lucide="x-circle" class="w-4 h-4 mr-2"></i>
|
||||
Annuler
|
||||
<% if @event.draft? %>
|
||||
<% if @event.ticket_types.blank? %>
|
||||
<%= button_to publish_promoter_event_path(@event), method: :patch, disabled: true, class: "w-full sm:w-auto inline-flex items-center justify-center px-4 py-2 bg-gray-400 text-white font-medium rounded-lg cursor-not-allowed transition-colors duration-200", title: "Vous devez créer au moins un type de billet avant de publier" do %>
|
||||
<i data-lucide="upload" class="w-4 h-4 mr-2"></i>
|
||||
Publier
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button_to publish_promoter_event_path(@event), method: :patch, class: "w-full sm:w-auto inline-flex items-center justify-center px-4 py-2 bg-green-600 text-white font-medium rounded-lg hover:bg-green-700 transition-colors duration-200" do %>
|
||||
<i data-lucide="upload" class="w-4 h-4 mr-2"></i>
|
||||
Publier
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% elsif @event.published? %>
|
||||
<%= button_to unpublish_promoter_event_path(@event), method: :patch, class: "w-full sm:w-auto inline-flex items-center justify-center px-4 py-2 bg-yellow-600 text-white font-medium rounded-lg hover:bg-yellow-700 transition-colors duration-200" do %>
|
||||
<i data-lucide="download" class="w-4 h-4 mr-2"></i>
|
||||
Dépublier
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if @event.published? %>
|
||||
<%= button_to cancel_promoter_event_path(@event), method: :patch, class: "w-full sm:w-auto inline-flex items-center justify-center px-4 py-2 bg-red-600 text-white font-medium rounded-lg hover:bg-red-700 transition-colors duration-200", data: { confirm: "Êtes-vous sûr de vouloir annuler cet événement ?" } do %>
|
||||
<i data-lucide="x-circle" class="w-4 h-4 mr-2"></i>
|
||||
Annuler
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -82,14 +83,14 @@
|
||||
|
||||
<% if @event.ticket_types.blank? %>
|
||||
<div class="bg-amber-50 border border-amber-200 rounded-2xl p-4 mt-4">
|
||||
<div class="flex items-center">
|
||||
<i data-lucide="alert-triangle" class="w-5 h-5 text-amber-400 mr-3"></i>
|
||||
<div>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-3">
|
||||
<i data-lucide="alert-triangle" class="w-5 h-5 text-amber-400 flex-shrink-0"></i>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-sm font-medium text-amber-900">Aucun type de billet configuré</h3>
|
||||
<p class="text-sm text-amber-700">Vous devez créer au moins un type de billet avant de pouvoir publier cet événement.</p>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<%= link_to promoter_event_ticket_types_path(@event), class: "text-amber-600 hover:text-amber-800 font-medium text-sm" do %>
|
||||
<div class="flex-shrink-0">
|
||||
<%= link_to promoter_event_ticket_types_path(@event), class: "text-amber-600 hover:text-amber-800 font-medium text-sm whitespace-nowrap" do %>
|
||||
Configurer les billets <i data-lucide="external-link" class="w-4 h-4 inline ml-1"></i>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -98,14 +99,14 @@
|
||||
<% end %>
|
||||
<% when "published" %>
|
||||
<div class="bg-green-50 border border-green-200 rounded-2xl p-4">
|
||||
<div class="flex items-center">
|
||||
<i data-lucide="eye" class="w-5 h-5 text-green-400 mr-3"></i>
|
||||
<div>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-3">
|
||||
<i data-lucide="eye" class="w-5 h-5 text-green-400 flex-shrink-0"></i>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-sm font-medium text-green-900">Événement publié</h3>
|
||||
<p class="text-sm text-green-700">Cet événement est visible publiquement et les utilisateurs peuvent acheter des billets.</p>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<%= link_to event_path(@event.slug, @event), target: "_blank", class: "text-green-600 hover:text-green-800 font-medium text-sm" do %>
|
||||
<div class="flex-shrink-0">
|
||||
<%= link_to event_path(@event.slug, @event), target: "_blank", class: "text-green-600 hover:text-green-800 font-medium text-sm whitespace-nowrap" do %>
|
||||
Voir publiquement <i data-lucide="external-link" class="w-4 h-4 inline ml-1"></i>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -159,9 +160,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Event details -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 lg:gap-8">
|
||||
<!-- Main content -->
|
||||
<div class="lg:col-span-2 space-y-8">
|
||||
<div class="lg:col-span-2 space-y-6 lg:space-y-8">
|
||||
<!-- Event image -->
|
||||
<% if @event.image.present? %>
|
||||
<div class="aspect-video bg-gray-100 rounded-2xl overflow-hidden">
|
||||
@@ -170,27 +171,27 @@
|
||||
<% end %>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-6">
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-4 sm:p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Description</h3>
|
||||
<div class="prose prose-gray max-w-none">
|
||||
<div class="prose prose-gray prose-sm sm:prose-base max-w-none">
|
||||
<%= simple_format(@event.description) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location details -->
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-6">
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-4 sm:p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Lieu</h3>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-start space-x-3">
|
||||
<i data-lucide="building" class="w-5 h-5 text-gray-400 mt-0.5"></i>
|
||||
<div>
|
||||
<p class="font-medium text-gray-900"><%= @event.venue_name %></p>
|
||||
<p class="text-gray-500"><%= @event.venue_address %></p>
|
||||
<i data-lucide="building" class="w-5 h-5 text-gray-400 mt-0.5 flex-shrink-0"></i>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="font-medium text-gray-900 break-words"><%= @event.venue_name %></p>
|
||||
<p class="text-gray-500 break-words"><%= @event.venue_address %></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3 text-sm text-gray-500">
|
||||
<i data-lucide="map-pin" class="w-4 h-4"></i>
|
||||
<span><%= @event.latitude %>, <%= @event.longitude %></span>
|
||||
<i data-lucide="map-pin" class="w-4 h-4 flex-shrink-0"></i>
|
||||
<span class="break-all"><%= @event.latitude %>, <%= @event.longitude %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -199,20 +200,20 @@
|
||||
<!-- Sidebar -->
|
||||
<div class="space-y-6">
|
||||
<!-- Event stats -->
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-6">
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-4 sm:p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Statistiques</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-500">Types de billets</span>
|
||||
<span class="text-gray-500 text-sm sm:text-base">Types de billets</span>
|
||||
<span class="font-medium"><%= @event.ticket_types.count %></span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-500">Billets vendus</span>
|
||||
<span class="text-gray-500 text-sm sm:text-base">Billets vendus</span>
|
||||
<span class="font-medium"><%= @event.tickets.count %></span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-500">Revenus</span>
|
||||
<span class="font-medium">
|
||||
<span class="text-gray-500 text-sm sm:text-base">Revenus</span>
|
||||
<span class="font-medium text-sm sm:text-base">
|
||||
<%= number_to_currency(@event.tickets.sum(:price_cents) / 100.0, unit: "€") %>
|
||||
</span>
|
||||
</div>
|
||||
@@ -220,25 +221,25 @@
|
||||
</div>
|
||||
|
||||
<!-- Event info -->
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-6">
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-4 sm:p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Informations</h3>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<span class="text-sm text-gray-500">Créé le</span>
|
||||
<p class="text-sm"><%= @event.created_at.strftime("%d/%m/%Y à %H:%M") %></p>
|
||||
<p class="text-sm break-words"><%= @event.created_at.strftime("%d/%m/%Y à %H:%M") %></p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-gray-500">Modifié le</span>
|
||||
<p class="text-sm"><%= @event.updated_at.strftime("%d/%m/%Y à %H:%M") %></p>
|
||||
<p class="text-sm break-words"><%= @event.updated_at.strftime("%d/%m/%Y à %H:%M") %></p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-gray-500">Réservation pendant l'événement</span>
|
||||
<p class="text-sm flex items-center">
|
||||
<% if @event.allow_booking_during_event? %>
|
||||
<i data-lucide="check-circle" class="w-4 h-4 text-green-500 mr-1"></i>
|
||||
<i data-lucide="check-circle" class="w-4 h-4 text-green-500 mr-1 flex-shrink-0"></i>
|
||||
Autorisée
|
||||
<% else %>
|
||||
<i data-lucide="x-circle" class="w-4 h-4 text-red-500 mr-1"></i>
|
||||
<i data-lucide="x-circle" class="w-4 h-4 text-red-500 mr-1 flex-shrink-0"></i>
|
||||
Interdite
|
||||
<% end %>
|
||||
</p>
|
||||
@@ -246,34 +247,34 @@
|
||||
<% if @event.start_time %>
|
||||
<div>
|
||||
<span class="text-sm text-gray-500">Début</span>
|
||||
<p class="text-sm"><%= @event.start_time.strftime("%d/%m/%Y à %H:%M") %></p>
|
||||
<p class="text-sm break-words"><%= @event.start_time.strftime("%d/%m/%Y à %H:%M") %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @event.end_time %>
|
||||
<div>
|
||||
<span class="text-sm text-gray-500">Fin</span>
|
||||
<p class="text-sm"><%= @event.end_time.strftime("%d/%m/%Y à %H:%M") %></p>
|
||||
<p class="text-sm break-words"><%= @event.end_time.strftime("%d/%m/%Y à %H:%M") %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick actions -->
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-6">
|
||||
<div class="bg-white rounded-2xl border border-gray-200 p-4 sm:p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Actions rapides</h3>
|
||||
<div class="space-y-3">
|
||||
<%= link_to promoter_event_ticket_types_path(@event), class: "w-full inline-flex items-center px-4 py-2 bg-purple-600 text-white font-medium rounded-lg hover:bg-purple-700 transition-colors duration-200" do %>
|
||||
<%= link_to promoter_event_ticket_types_path(@event), class: "w-full inline-flex items-center justify-center px-4 py-3 bg-purple-600 text-white font-medium text-sm rounded-lg hover:bg-purple-700 transition-colors duration-200" do %>
|
||||
<i data-lucide="ticket" class="w-4 h-4 mr-2"></i>
|
||||
Gérer les types de billets
|
||||
<% end %>
|
||||
<%= button_to mark_sold_out_promoter_event_path(@event), method: :patch, class: "w-full inline-flex items-center px-4 py-2 bg-gray-50 text-gray-700 font-medium rounded-lg hover:bg-gray-100 transition-colors duration-200", disabled: !@event.published? do %>
|
||||
<%= button_to mark_sold_out_promoter_event_path(@event), method: :patch, class: "w-full inline-flex items-center justify-center px-4 py-3 bg-gray-50 text-gray-700 font-medium text-sm rounded-lg hover:bg-gray-100 transition-colors duration-200", disabled: !@event.published? do %>
|
||||
<i data-lucide="users" class="w-4 h-4 mr-2"></i>
|
||||
Marquer comme complet
|
||||
<% end %>
|
||||
<hr class="border-gray-200">
|
||||
<%= button_to promoter_event_path(@event), method: :delete,
|
||||
data: { confirm: "Êtes-vous sûr de vouloir supprimer cet événement ? Cette action est irréversible." },
|
||||
class: "w-full inline-flex items-center px-4 py-2 text-red-600 font-medium rounded-lg hover:bg-red-50 transition-colors duration-200" do %>
|
||||
class: "w-full inline-flex items-center justify-center px-4 py-3 text-red-600 font-medium text-sm rounded-lg hover:bg-red-50 transition-colors duration-200" do %>
|
||||
<i data-lucide="trash-2" class="w-4 h-4 mr-2"></i>
|
||||
Supprimer l'événement
|
||||
<% end %>
|
||||
|
||||
@@ -3,25 +3,25 @@
|
||||
<div class="container py-8">
|
||||
<!-- Header with actions -->
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<%= link_to promoter_event_ticket_types_path(@event), class: "text-gray-400 hover:text-gray-600 transition-colors" do %>
|
||||
<i data-lucide="arrow-left" class="w-5 h-5"></i>
|
||||
<% end %>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-2"><%= @ticket_type.name %></h1>
|
||||
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2"><%= @ticket_type.name %></h1>
|
||||
<p class="text-gray-600">
|
||||
<%= link_to @event.name, promoter_event_path(@event), class: "text-purple-600 hover:text-purple-800" %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<%= link_to edit_promoter_event_ticket_type_path(@event, @ticket_type), class: "inline-flex items-center px-4 py-2 bg-white border border-gray-300 text-gray-700 font-medium rounded-lg hover:bg-gray-50 transition-colors duration-200" do %>
|
||||
<div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
|
||||
<%= link_to edit_promoter_event_ticket_type_path(@event, @ticket_type), class: "w-full inline-flex items-center justify-center px-4 py-2 bg-white border border-gray-300 text-gray-700 font-medium rounded-lg hover:bg-gray-50 transition-colors duration-200" do %>
|
||||
<i data-lucide="edit" class="w-4 h-4 mr-2"></i>
|
||||
Modifier
|
||||
<% end %>
|
||||
|
||||
<%= button_to duplicate_promoter_event_ticket_type_path(@event, @ticket_type), method: :post, class: "inline-flex items-center px-4 py-2 bg-green-600 text-white font-medium rounded-lg hover:bg-green-700 transition-colors duration-200" do %>
|
||||
<%= button_to duplicate_promoter_event_ticket_type_path(@event, @ticket_type), method: :post, class: "w-full inline-flex items-center justify-center px-4 py-2 bg-green-600 text-white font-medium rounded-lg hover:bg-green-700 transition-colors duration-200" do %>
|
||||
<i data-lucide="copy" class="w-4 h-4 mr-2"></i>
|
||||
Dupliquer
|
||||
<% end %>
|
||||
@@ -209,7 +209,7 @@
|
||||
data: { confirm: "Êtes-vous sûr de vouloir supprimer ce type de billet ? Cette action est irréversible." },
|
||||
class: "w-full inline-flex items-center px-4 py-2 text-red-600 font-medium rounded-lg hover:bg-red-50 transition-colors duration-200" do %>
|
||||
<i data-lucide="trash-2" class="w-4 h-4 mr-2"></i>
|
||||
Supprimer le type
|
||||
Supprimer le type de billet
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="w-full inline-flex items-center px-4 py-2 text-gray-400 font-medium rounded-lg cursor-not-allowed">
|
||||
|
||||
@@ -28,6 +28,7 @@ development:
|
||||
test:
|
||||
<<: *default
|
||||
database: aperonight_test
|
||||
isolation_level: READ UNCOMMITTED
|
||||
# adapter: sqlite3
|
||||
# pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
# database: data/test.sqlite3
|
||||
|
||||
@@ -96,11 +96,8 @@ Rails.application.routes.draw do
|
||||
namespace :api do
|
||||
namespace :v1 do
|
||||
# RESTful routes for event management
|
||||
resources :events, only: [ :index, :show, :create, :update, :destroy ] do
|
||||
member do
|
||||
post :store_cart
|
||||
end
|
||||
end
|
||||
resources :events, only: [ :index, :show, :create, :update, :destroy ]
|
||||
post "carts/store", to: "carts#store", as: "store_cart"
|
||||
|
||||
# RESTful routes for order management
|
||||
resources :orders, only: [] do
|
||||
|
||||
@@ -19,6 +19,9 @@ class CreateEvents < ActiveRecord::Migration[8.0]
|
||||
t.boolean :featured, default: false, null: false
|
||||
t.references :user, null: false, foreign_key: false
|
||||
|
||||
# Allow ticket sell during the event
|
||||
t.boolean :allow_booking_during_event, default: false, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
class AddAllowBookingDuringEventToEvents < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_column :events, :allow_booking_during_event, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
||||
58
test/controllers/api/v1/events_controller_test.rb
Normal file
58
test/controllers/api/v1/events_controller_test.rb
Normal file
@@ -0,0 +1,58 @@
|
||||
require "test_helper"
|
||||
|
||||
class Api::V1::EventsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
ENV["API_KEY"] = "test_key"
|
||||
@user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
@event = Event.create!(name: "Test Event", slug: "test-event", description: "A description that is long enough for validation", latitude: 48.8566, longitude: 2.3522, venue_name: "Venue", venue_address: "Address", user: @user, start_time: 1.week.from_now, end_time: 1.week.from_now + 3.hours, state: :published)
|
||||
end
|
||||
|
||||
test "should get index" do
|
||||
get api_v1_events_url, headers: headers_api_key
|
||||
assert_response :success
|
||||
assert_kind_of Array, json_response
|
||||
end
|
||||
|
||||
test "should show event" do
|
||||
get api_v1_event_url(@event.id), headers: headers_api_key
|
||||
assert_response :success
|
||||
assert_equal @event.id, json_response["id"]
|
||||
end
|
||||
|
||||
test "should create event" do
|
||||
assert_difference("Event.count") do
|
||||
post api_v1_events_url, params: { event: { name: "New Event", slug: "new-event", description: "New description that is long enough", latitude: 48.8566, longitude: 2.3522, venue_name: "New Venue", venue_address: "New Address", user_id: @user.id, start_time: "2024-01-01 10:00:00", end_time: "2024-01-01 13:00:00", state: "published" } }, as: :json, headers: headers_api_key
|
||||
end
|
||||
assert_response :created
|
||||
end
|
||||
|
||||
test "should update event" do
|
||||
patch api_v1_event_url(@event.id), params: { event: { name: "Updated Event" } }, as: :json, headers: headers_api_key
|
||||
assert_response :ok
|
||||
@event.reload
|
||||
assert_equal "Updated Event", @event.name
|
||||
end
|
||||
|
||||
test "should destroy event" do
|
||||
assert_difference("Event.count", -1) do
|
||||
delete api_v1_event_url(@event.id), headers: headers_api_key
|
||||
end
|
||||
assert_response :no_content
|
||||
end
|
||||
|
||||
test "should store cart" do
|
||||
post api_v1_store_cart_path, params: { cart: { ticket_type_id: 1, quantity: 2 }, event_id: @event.id }, as: :json, headers: headers_api_key
|
||||
assert_response :success
|
||||
assert_equal @event.id, session[:event_id]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def json_response
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
def headers_api_key
|
||||
{ "X-API-Key" => "test_key" }
|
||||
end
|
||||
end
|
||||
@@ -41,7 +41,7 @@ class ApplicationControllerOnboardingTest < ActionDispatch::IntegrationTest
|
||||
test "should redirect signed in incomplete users from home to onboarding" do
|
||||
sign_in @user_without_onboarding
|
||||
get root_path
|
||||
assert_redirected_to dashboard_path # Home redirects to dashboard for signed in users
|
||||
assert_redirected_to onboarding_path
|
||||
end
|
||||
|
||||
test "should not interfere with devise controllers" do
|
||||
|
||||
33
test/controllers/concerns/stripe_concern_test.rb
Normal file
33
test/controllers/concerns/stripe_concern_test.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
require "test_helper"
|
||||
|
||||
class StripeConcernTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
Rails.application.config.stripe = { secret_key: nil }
|
||||
end
|
||||
|
||||
test "stripe_configured? returns false when no secret key" do
|
||||
controller = ApplicationController.new
|
||||
controller.extend StripeConcern
|
||||
assert_not controller.stripe_configured?
|
||||
end
|
||||
|
||||
test "stripe_configured? returns true when secret key present" do
|
||||
Rails.application.config.stripe = { secret_key: "sk_test_key" }
|
||||
controller = ApplicationController.new
|
||||
controller.extend StripeConcern
|
||||
assert controller.stripe_configured?
|
||||
end
|
||||
|
||||
test "initialize_stripe returns false when not configured" do
|
||||
controller = ApplicationController.new
|
||||
controller.extend StripeConcern
|
||||
assert_not controller.initialize_stripe
|
||||
end
|
||||
|
||||
test "initialize_stripe returns true when configured" do
|
||||
Rails.application.config.stripe = { secret_key: "sk_test_key" }
|
||||
controller = ApplicationController.new
|
||||
controller.extend StripeConcern
|
||||
assert controller.initialize_stripe
|
||||
end
|
||||
end
|
||||
30
test/controllers/promoter/events_controller_test.rb
Normal file
30
test/controllers/promoter/events_controller_test.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
require "test_helper"
|
||||
|
||||
class Promoter::EventsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@promoter = User.create!(email: "promoter@example.com", password: "password123", password_confirmation: "password123", is_professionnal: true, onboarding_completed: true)
|
||||
@event = Event.create!(name: "Test Event", slug: "test-event", description: "A valid description for the test event that is long enough to meet the minimum character requirement", latitude: 48.8566, longitude: 2.3522, venue_name: "Venue", venue_address: "Address", user: @promoter, start_time: 1.week.from_now, end_time: 1.week.from_now + 3.hours, state: :draft)
|
||||
end
|
||||
|
||||
test "should require authentication for index" do
|
||||
get promoter_events_path
|
||||
assert_redirected_to new_user_session_path
|
||||
end
|
||||
|
||||
test "should get index for authenticated promoter" do
|
||||
sign_in @promoter
|
||||
get promoter_events_path
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "should show promoter's events only" do
|
||||
sign_in @promoter
|
||||
other_event = Event.create!(name: "Other Event", slug: "other", description: "Valid description for the event", latitude: 48.0, longitude: 2.0, venue_name: "V", venue_address: "A", user_id: users(:one).id, start_time: 1.day.from_now, end_time: 2.days.from_now, state: :draft)
|
||||
get promoter_events_path
|
||||
assert_response :success
|
||||
assert_includes assigns(:events), @event
|
||||
assert_not_includes assigns(:events), other_event
|
||||
end
|
||||
|
||||
# Add tests for new, create, etc. as needed
|
||||
end
|
||||
22
test/controllers/promoter/ticket_types_controller_test.rb
Normal file
22
test/controllers/promoter/ticket_types_controller_test.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
require "test_helper"
|
||||
|
||||
class Promoter::TicketTypesControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@promoter = User.create!(email: "promoter@example.com", password: "password123", password_confirmation: "password123", is_professionnal: true, onboarding_completed: true)
|
||||
@event = Event.create!(name: "Test Event", slug: "test-event", description: "A valid description for the test event that is long enough to meet the minimum character requirement", latitude: 48.8566, longitude: 2.3522, venue_name: "Venue", venue_address: "Address", user: @promoter, start_time: 1.week.from_now, end_time: 1.week.from_now + 3.hours, state: :draft)
|
||||
@ticket_type = TicketType.create!(name: "General", description: "General admission", price_cents: 2500, quantity: 100, sale_start_at: Time.current, sale_end_at: @event.start_time, event: @event)
|
||||
end
|
||||
|
||||
test "should require authentication for index" do
|
||||
get promoter_event_ticket_types_path(@event)
|
||||
assert_redirected_to new_user_session_path
|
||||
end
|
||||
|
||||
test "should get index for promoter's event" do
|
||||
sign_in @promoter
|
||||
get promoter_event_ticket_types_path(@event)
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
# Add more tests for create, update, destroy
|
||||
end
|
||||
@@ -1,4 +1,5 @@
|
||||
require "test_helper"
|
||||
require "timecop"
|
||||
|
||||
class EventTest < ActiveSupport::TestCase
|
||||
# Test that Event model exists
|
||||
@@ -160,4 +161,114 @@ class EventTest < ActiveSupport::TestCase
|
||||
test "should respond to search_by_name scope" do
|
||||
assert_respond_to Event, :search_by_name
|
||||
end
|
||||
|
||||
test "upcoming scope should return only published future events" do
|
||||
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
future_published = Event.create!(name: "Future", slug: "future", description: "Valid description for the event", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.day.from_now, state: :published)
|
||||
past_published = Event.create!(name: "Past", slug: "past", description: "Valid description for the event", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.day.ago, state: :published)
|
||||
future_draft = Event.create!(name: "Draft", slug: "draft", description: "Valid description for the event", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.day.from_now, state: :draft)
|
||||
|
||||
upcoming = Event.upcoming
|
||||
assert_includes upcoming, future_published
|
||||
assert_not_includes upcoming, past_published
|
||||
assert_not_includes upcoming, future_draft
|
||||
end
|
||||
|
||||
test "geocoding_successful? should return true for valid coordinates" do
|
||||
event = Event.new(latitude: 48.8566, longitude: 2.3522, name: "Test", slug: "test", description: "A description that is sufficiently long", venue_name: "v", venue_address: "a")
|
||||
assert event.geocoding_successful?
|
||||
end
|
||||
|
||||
test "geocoding_successful? should return false for fallback coordinates" do
|
||||
event = Event.new(latitude: 46.603354, longitude: 1.888334, name: "Test", slug: "test", description: "Valid description for the event", venue_name: "v", venue_address: "a")
|
||||
assert_not event.geocoding_successful?
|
||||
end
|
||||
|
||||
test "geocoding_status_message should return message when not successful" do
|
||||
event = Event.new(latitude: 46.603354, longitude: 1.888334, name: "Test", slug: "test", description: "Valid description for the event", venue_name: "v", venue_address: "a")
|
||||
assert_match(/coordonnées/, event.geocoding_status_message)
|
||||
end
|
||||
|
||||
test "geocoding_status_message should return nil when successful" do
|
||||
event = Event.new(latitude: 48.8566, longitude: 2.3522, name: "Test", slug: "test", description: "Valid description for the event", venue_name: "v", venue_address: "a")
|
||||
assert_nil event.geocoding_status_message
|
||||
end
|
||||
|
||||
test "booking_allowed? should be true for published future event" do
|
||||
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
event = Event.create!(name: "Test", slug: "test", description: "A description that is sufficiently long", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.day.from_now, state: :published)
|
||||
assert event.booking_allowed?
|
||||
end
|
||||
|
||||
test "booking_allowed? should be false for draft event" do
|
||||
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
event = Event.create!(name: "Test", slug: "test", description: "Valid description for the event", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.day.from_now, state: :draft)
|
||||
assert_not event.booking_allowed?
|
||||
end
|
||||
|
||||
test "booking_allowed? should be false for canceled event" do
|
||||
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
event = Event.create!(name: "Test", slug: "test", description: "Valid description for the event", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.day.from_now, state: :canceled)
|
||||
assert_not event.booking_allowed?
|
||||
end
|
||||
|
||||
test "booking_allowed? should be false for sold_out event" do
|
||||
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
event = Event.create!(name: "Test", slug: "test", description: "A description that is sufficiently long", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.day.from_now, state: :sold_out)
|
||||
assert_not event.booking_allowed?
|
||||
end
|
||||
|
||||
test "booking_allowed? should be false during event without allow_booking_during_event" do
|
||||
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
event = Event.create!(name: "Test", slug: "test", description: "Valid description for the event", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.hour.ago, end_time: 2.hours.from_now, state: :published, allow_booking_during_event: false)
|
||||
assert_not event.booking_allowed?
|
||||
end
|
||||
|
||||
test "booking_allowed? should be true during event with allow_booking_during_event" do
|
||||
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
event = Event.create!(name: "Test", slug: "test", description: "Valid description for the event", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.hour.ago, end_time: 2.hours.from_now, state: :published, allow_booking_during_event: true)
|
||||
assert event.booking_allowed?
|
||||
end
|
||||
|
||||
test "event_started? should be true after start_time" do
|
||||
Timecop.freeze(1.hour.from_now) do
|
||||
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
event = Event.create!(name: "Test", slug: "test", description: "A description that is sufficiently long", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.hour.ago)
|
||||
assert event.event_started?
|
||||
end
|
||||
end
|
||||
|
||||
test "event_started? should be false before start_time" do
|
||||
Timecop.freeze(1.hour.ago) do
|
||||
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
event = Event.create!(name: "Test", slug: "test", description: "Valid description for the event", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.hour.from_now)
|
||||
assert_not event.event_started?
|
||||
end
|
||||
end
|
||||
|
||||
test "event_ended? should be true after end_time" do
|
||||
Timecop.freeze(1.hour.from_now) do
|
||||
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
event = Event.create!(name: "Test", slug: "test", description: "Valid description for the event", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.hour.ago, end_time: 30.minutes.ago)
|
||||
assert event.event_ended?
|
||||
end
|
||||
end
|
||||
|
||||
test "event_ended? should be false before end_time" do
|
||||
Timecop.freeze(1.hour.ago) do
|
||||
user = User.create!(email: "test@example.com", password: "password123", password_confirmation: "password123")
|
||||
event = Event.create!(name: "Test", slug: "test", description: "A description that is sufficiently long", venue_name: "v", venue_address: "a", user: user, latitude: 48.0, longitude: 2.0, start_time: 1.hour.ago, end_time: 1.hour.from_now)
|
||||
assert_not event.event_ended?
|
||||
end
|
||||
end
|
||||
|
||||
test "allow_booking_during_event? should return true when set to true" do
|
||||
event = Event.new(allow_booking_during_event: true)
|
||||
assert event.allow_booking_during_event?
|
||||
end
|
||||
|
||||
test "allow_booking_during_event? should return false when nil" do
|
||||
event = Event.new
|
||||
assert_not event.allow_booking_during_event?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,6 +12,7 @@ module ActiveSupport
|
||||
class TestCase
|
||||
# Run tests in parallel with specified workers
|
||||
parallelize(workers: :number_of_processors)
|
||||
use_transactional_fixtures = true
|
||||
|
||||
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
|
||||
fixtures :all
|
||||
|
||||
Reference in New Issue
Block a user