Compare commits
3 Commits
feat/seo
...
73eefdd7bd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73eefdd7bd | ||
|
|
29f1d75969 | ||
|
|
340f655102 |
61
BACKLOG.md
61
BACKLOG.md
@@ -2,43 +2,44 @@
|
||||
|
||||
## 📋 Todo
|
||||
|
||||
- [ ] Set up project infrastructure
|
||||
- [ ] Design user interface mockups
|
||||
- [ ] Create user dashboard
|
||||
- [ ] Implement data persistence
|
||||
- [ ] Add responsive design
|
||||
- [ ] Write unit tests
|
||||
- [ ] Set up CI/CD pipeline
|
||||
- [ ] Add error handling
|
||||
- [ ] Implement search functionality
|
||||
- [ ] Add user profile management
|
||||
- [ ] Create admin panel
|
||||
- [ ] Optimize performance
|
||||
- [ ] Add documentation
|
||||
- [ ] Security audit
|
||||
- [ ] Deploy to production
|
||||
### High Priority
|
||||
- [ ] feat: Check-in system with QR code scanning
|
||||
|
||||
### Medium Priority
|
||||
- [ ] feat: Promoter system with event creation, ticket types creation and metrics display
|
||||
- [ ] feat: Multiple ticket types (early bird, VIP, general admission)
|
||||
- [ ] feat: Email notifications (purchase confirmations, event reminders)
|
||||
- [ ] feat: Refund management system
|
||||
- [ ] feat: Real-time sales analytics dashboard
|
||||
- [ ] feat: Guest checkout without account creation
|
||||
- [ ] feat: Seat selection with interactive venue maps
|
||||
- [ ] feat: Dynamic pricing based on demand
|
||||
|
||||
### Low Priority
|
||||
- [ ] feat: SMS integration for ticket delivery and updates
|
||||
- [ ] feat: Mobile wallet integration
|
||||
- [ ] feat: Multi-currency support
|
||||
- [ ] feat: Event updates communication system
|
||||
- [ ] feat: Bulk operations for group bookings
|
||||
- [ ] feat: Fraud prevention and bot protection
|
||||
- [ ] feat: Social login options
|
||||
- [ ] feat: Event recommendations system
|
||||
|
||||
### Design & Infrastructure
|
||||
- [ ] style: Rewrite design system
|
||||
- [ ] refactor: Rewrite design mockup
|
||||
|
||||
## 🚧 Doing
|
||||
|
||||
- [ ] refactor: Moving checkout to OrdersController
|
||||
|
||||
## ✅ Done
|
||||
|
||||
- [x] Initialize git repository
|
||||
- [x] Set up development environment
|
||||
- [x] Create project structure
|
||||
- [x] Install dependencies
|
||||
- [x] Configure build tools
|
||||
- [x] Set up linting rules
|
||||
- [x] Create initial README
|
||||
- [x] Set up version control
|
||||
- [x] Configure development server
|
||||
- [x] Establish coding standards
|
||||
- [x] Set up package.json
|
||||
- [x] Create .gitignore file
|
||||
- [x] Initialize npm project
|
||||
- [x] Set up basic folder structure
|
||||
- [x] Configure environment variables
|
||||
- [x] Create authentication system
|
||||
- [x] Implement user registration
|
||||
- [x] Add login functionality
|
||||
- [x] refactor: Moving checkout to OrdersController
|
||||
- [x] feat: Payment gateway integration (Stripe) - PayPal not implemented
|
||||
- [x] feat: Digital tickets with QR codes
|
||||
- [x] feat: Ticket inventory management and capacity limits
|
||||
- [x] feat: Event discovery with search and filtering
|
||||
|
||||
3
Gemfile
3
Gemfile
@@ -87,7 +87,8 @@ gem "kaminari-tailwind", "~> 0.1.0"
|
||||
gem "stripe", "~> 15.5"
|
||||
|
||||
# PDF generation for tickets
|
||||
gem "grover"
|
||||
gem "prawn", "~> 2.5"
|
||||
gem "prawn-qrcode", "~> 0.5"
|
||||
|
||||
# QR code generation
|
||||
gem "rqrcode", "~> 3.1"
|
||||
|
||||
15
Gemfile.lock
15
Gemfile.lock
@@ -127,8 +127,6 @@ GEM
|
||||
raabro (~> 1.4)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
grover (1.2.3)
|
||||
nokogiri (~> 1)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
io-console (0.8.1)
|
||||
@@ -223,8 +221,16 @@ GEM
|
||||
parser (3.3.9.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pdf-core (0.10.0)
|
||||
pp (0.6.2)
|
||||
prettyprint
|
||||
prawn (2.5.0)
|
||||
matrix (~> 0.4)
|
||||
pdf-core (~> 0.10.0)
|
||||
ttfunk (~> 1.8)
|
||||
prawn-qrcode (0.5.2)
|
||||
prawn (>= 1)
|
||||
rqrcode (>= 1.0.0)
|
||||
prettyprint (0.2.0)
|
||||
prism (1.4.0)
|
||||
propshaft (1.2.1)
|
||||
@@ -372,6 +378,8 @@ GEM
|
||||
thruster (0.1.15-aarch64-linux)
|
||||
thruster (0.1.15-x86_64-linux)
|
||||
timeout (0.4.3)
|
||||
ttfunk (1.8.0)
|
||||
bigdecimal (~> 3.1)
|
||||
turbo-rails (2.0.16)
|
||||
actionpack (>= 7.1.0)
|
||||
railties (>= 7.1.0)
|
||||
@@ -415,7 +423,6 @@ DEPENDENCIES
|
||||
debug
|
||||
devise (~> 4.9)
|
||||
dotenv-rails
|
||||
grover
|
||||
jbuilder
|
||||
jsbundling-rails
|
||||
kamal
|
||||
@@ -424,6 +431,8 @@ DEPENDENCIES
|
||||
minitest-reporters (~> 1.7)
|
||||
mocha
|
||||
mysql2 (~> 0.5)
|
||||
prawn (~> 2.5)
|
||||
prawn-qrcode (~> 0.5)
|
||||
propshaft
|
||||
puma (>= 5.0)
|
||||
rails (~> 8.0.2, >= 8.0.2.1)
|
||||
|
||||
@@ -13,16 +13,3 @@
|
||||
|
||||
/* Import pages */
|
||||
@import "pages/home";
|
||||
|
||||
/* QR Code Styles */
|
||||
.qr-code-container {
|
||||
@apply flex items-center justify-center;
|
||||
}
|
||||
|
||||
.qr-code-container svg {
|
||||
max-width: 100% !important;
|
||||
max-height: 100% !important;
|
||||
width: 208px !important;
|
||||
height: 208px !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
/* PDF Styles for Ticket Generation */
|
||||
|
||||
body {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
color: #000000;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.ticket-container {
|
||||
max-width: 350px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #2D1B69;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Event name */
|
||||
.event-name {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.event-name h2 {
|
||||
color: #000000;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Ticket info box */
|
||||
.ticket-info-box {
|
||||
background-color: #F9FAFB;
|
||||
border: 1px solid #E5E7EB;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
display: inline-block;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* Venue information */
|
||||
.venue-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.venue-info h3 {
|
||||
color: #374151;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.venue-details {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.venue-name {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.venue-address {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* QR Code */
|
||||
.qr-code-section {
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.qr-code-section h3 {
|
||||
color: #000000;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.qr-code-container {
|
||||
text-align: center;
|
||||
margin: 0 auto 10px auto;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.qr-code-text {
|
||||
font-size: 8px;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
border-top: 1px solid #E5E7EB;
|
||||
padding-top: 15px;
|
||||
text-align: center;
|
||||
font-size: 8px;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.generated-date {
|
||||
margin-top: 5px;
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/* PDF Styles for Ticket Generation */
|
||||
|
||||
body {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
color: #000000;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.ticket-container {
|
||||
max-width: 350px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #2D1B69;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Event name */
|
||||
.event-name {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.event-name h2 {
|
||||
color: #000000;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Ticket info box */
|
||||
.ticket-info-box {
|
||||
background-color: #F9FAFB;
|
||||
border: 1px solid #E5E7EB;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
display: inline-block;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* Venue information */
|
||||
.venue-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.venue-info h3 {
|
||||
color: #374151;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.venue-details {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.venue-name {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.venue-address {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* QR Code */
|
||||
.qr-code-section {
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.qr-code-section h3 {
|
||||
color: #000000;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.qr-code-container {
|
||||
text-align: center;
|
||||
margin: 0 auto 10px auto;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.qr-code-text {
|
||||
font-size: 8px;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
border-top: 1px solid #E5E7EB;
|
||||
padding-top: 15px;
|
||||
text-align: center;
|
||||
font-size: 8px;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.generated-date {
|
||||
margin-top: 5px;
|
||||
}
|
||||
@@ -14,48 +14,4 @@ class ApplicationController < ActionController::Base
|
||||
# - CSS nesting and :has() pseudo-class
|
||||
# allow_browser versions: :modern
|
||||
# allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
|
||||
|
||||
protected
|
||||
|
||||
# Generate SEO-friendly path for an event
|
||||
def seo_event_path(event)
|
||||
year = event.start_time.year
|
||||
month = format("%02d", event.start_time.month)
|
||||
event_path(year: year, month: month, slug: event.slug)
|
||||
end
|
||||
helper_method :seo_event_path
|
||||
|
||||
# Generate SEO-friendly booking URL for an event
|
||||
def seo_book_tickets_path(event)
|
||||
year = event.start_time.year
|
||||
month = format("%02d", event.start_time.month)
|
||||
book_event_tickets_path(year: year, month: month, slug: event.slug)
|
||||
end
|
||||
helper_method :seo_book_tickets_path
|
||||
|
||||
# Generate SEO-friendly checkout URL for an event
|
||||
def seo_checkout_path(event)
|
||||
year = event.start_time.year
|
||||
month = format("%02d", event.start_time.month)
|
||||
event_checkout_path(year: year, month: month, slug: event.slug)
|
||||
end
|
||||
helper_method :seo_checkout_path
|
||||
|
||||
# Generate SEO-friendly ticket URL
|
||||
def seo_ticket_path(ticket)
|
||||
ticket_path(event_slug: ticket.event.slug, ticket_id: ticket.id)
|
||||
end
|
||||
helper_method :seo_ticket_path
|
||||
|
||||
# Generate SEO-friendly ticket view URL
|
||||
def seo_ticket_view_path(ticket)
|
||||
view_ticket_path(event_slug: ticket.event.slug, ticket_id: ticket.id)
|
||||
end
|
||||
helper_method :seo_ticket_view_path
|
||||
|
||||
# Generate SEO-friendly ticket download URL
|
||||
def seo_ticket_download_path(ticket)
|
||||
download_ticket_path(event_slug: ticket.event.slug, ticket_id: ticket.id)
|
||||
end
|
||||
helper_method :seo_ticket_download_path
|
||||
end
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
# Handle payment callbacks for booking workflow
|
||||
class Booking::PaymentsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
# Handle successful payment callback
|
||||
def success
|
||||
session_id = params[:session_id]
|
||||
|
||||
# Check if Stripe is properly configured
|
||||
stripe_configured = Rails.application.config.stripe[:secret_key].present?
|
||||
Rails.logger.debug "Payment success - Stripe configured: #{stripe_configured}"
|
||||
|
||||
unless stripe_configured
|
||||
redirect_to root_path, alert: "Le système de paiement n'est pas correctement configuré. Veuillez contacter l'administrateur."
|
||||
return
|
||||
end
|
||||
|
||||
begin
|
||||
stripe_session = Stripe::Checkout::Session.retrieve(session_id)
|
||||
|
||||
if stripe_session.payment_status == "paid"
|
||||
# Get order_id from session metadata
|
||||
order_id = stripe_session.metadata["order_id"]
|
||||
|
||||
unless order_id.present?
|
||||
redirect_to dashboard_path, alert: "Informations de commande manquantes"
|
||||
return
|
||||
end
|
||||
|
||||
# Find and update the order
|
||||
@order = current_user.orders.includes(tickets: :ticket_type).find(order_id)
|
||||
@order.mark_as_paid!
|
||||
|
||||
# Schedule Stripe invoice generation in background
|
||||
begin
|
||||
StripeInvoiceGenerationJob.perform_later(@order.id)
|
||||
Rails.logger.info "Scheduled Stripe invoice generation for order #{@order.id}"
|
||||
rescue => e
|
||||
Rails.logger.error "Failed to schedule invoice generation for order #{@order.id}: #{e.message}"
|
||||
end
|
||||
|
||||
# Send confirmation emails
|
||||
@order.tickets.each do |ticket|
|
||||
begin
|
||||
TicketMailer.purchase_confirmation(ticket).deliver_now
|
||||
rescue => e
|
||||
Rails.logger.error "Failed to send confirmation email for ticket #{ticket.id}: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Clear session data
|
||||
session.delete(:pending_cart)
|
||||
session.delete(:ticket_names)
|
||||
session.delete(:draft_order_id)
|
||||
|
||||
render "payment_success"
|
||||
else
|
||||
redirect_to dashboard_path, alert: "Le paiement n'a pas été complété avec succès"
|
||||
end
|
||||
rescue Stripe::StripeError => e
|
||||
error_message = e.message.present? ? e.message : "Erreur Stripe inconnue"
|
||||
redirect_to dashboard_path, alert: "Erreur lors du traitement de votre confirmation de paiement : #{error_message}"
|
||||
rescue => e
|
||||
error_message = e.message.present? ? e.message : "Erreur inconnue"
|
||||
Rails.logger.error "Payment success error: #{e.class} - #{error_message}"
|
||||
redirect_to dashboard_path, alert: "Une erreur inattendue s'est produite : #{error_message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Handle payment cancellation callback
|
||||
def cancel
|
||||
order_id = params[:order_id] || session[:draft_order_id]
|
||||
|
||||
if order_id.present?
|
||||
order = current_user.orders.find_by(id: order_id, status: "draft")
|
||||
|
||||
if order&.can_retry_payment?
|
||||
# Extract year and month from event start_time for SEO URL
|
||||
year = order.event.start_time.year
|
||||
month = format("%02d", order.event.start_time.month)
|
||||
|
||||
redirect_to event_checkout_path(year: year, month: month, slug: order.event.slug),
|
||||
alert: "Le paiement a été annulé. Vous pouvez réessayer."
|
||||
else
|
||||
session.delete(:draft_order_id)
|
||||
redirect_to root_path, alert: "Le paiement a été annulé et votre commande a expiré."
|
||||
end
|
||||
else
|
||||
redirect_to root_path, alert: "Le paiement a été annulé"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -27,33 +27,10 @@ class EventsController < ApplicationController
|
||||
private
|
||||
|
||||
# Find and set the current event with eager-loaded associations
|
||||
# Supports both old slug-only format and new SEO-friendly year/month/slug format
|
||||
#
|
||||
# Loads event with ticket types to avoid N+1 queries
|
||||
# Raises ActiveRecord::RecordNotFound if event doesn't exist
|
||||
def set_event
|
||||
if params[:year] && params[:month]
|
||||
# New SEO-friendly format: /events/2024/07/summer-party
|
||||
year = params[:year].to_i
|
||||
month = params[:month].to_i
|
||||
start_of_month = Date.new(year, month, 1).beginning_of_month
|
||||
end_of_month = start_of_month.end_of_month
|
||||
|
||||
@event = Event.includes(:ticket_types)
|
||||
.where(slug: params[:slug])
|
||||
.where(start_time: start_of_month..end_of_month)
|
||||
.first!
|
||||
else
|
||||
# Legacy format: /events/summer-party (for backward compatibility)
|
||||
@event = Event.includes(:ticket_types).find_by!(slug: params[:slug])
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to events_path, alert: "Événement non trouvé"
|
||||
@event = Event.includes(:ticket_types).find(params[:id])
|
||||
end
|
||||
|
||||
# Generate SEO-friendly path for an event
|
||||
def seo_event_path(event)
|
||||
year = event.start_time.year
|
||||
month = format("%02d", event.start_time.month)
|
||||
event_path(year: year, month: month, slug: event.slug)
|
||||
end
|
||||
helper_method :seo_event_path
|
||||
end
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# Handle legacy URL redirects to new SEO-friendly URLs
|
||||
class LegacyRedirectsController < ApplicationController
|
||||
# Redirect old event URLs to new SEO-friendly format
|
||||
# OLD: /events/summer-party-2024
|
||||
# NEW: /events/2024/07/summer-party-2024
|
||||
def event_redirect
|
||||
event = Event.find_by(slug: params[:slug])
|
||||
|
||||
if event
|
||||
year = event.start_time.year
|
||||
month = format("%02d", event.start_time.month)
|
||||
redirect_to event_path(year: year, month: month, slug: event.slug), status: :moved_permanently
|
||||
else
|
||||
redirect_to events_path, alert: "Événement non trouvé"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,11 +1,11 @@
|
||||
# Handle order management and checkout process with SEO-friendly URLs
|
||||
# Handle order management and checkout process
|
||||
#
|
||||
# This controller manages the order lifecycle from checkout to payment completion
|
||||
# Orders group multiple tickets together for better transaction management
|
||||
class OrdersController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_event_from_seo_params, only: [:new, :create, :checkout]
|
||||
before_action :set_order_from_id, only: [:show, :retry_payment, :increment_payment_attempt]
|
||||
before_action :set_order, only: [ :show, :checkout, :retry_payment, :increment_payment_attempt ]
|
||||
before_action :set_event, only: [ :new, :create ]
|
||||
|
||||
# Display new order form with name collection
|
||||
#
|
||||
@@ -15,7 +15,7 @@ class OrdersController < ApplicationController
|
||||
@cart_data = params[:cart_data] || session[:pending_cart] || {}
|
||||
|
||||
if @cart_data.empty?
|
||||
redirect_to seo_event_path(@event), alert: "Veuillez d'abord sélectionner vos billets sur la page de l'événement"
|
||||
redirect_to event_path(@event.slug, @event), alert: "Veuillez d'abord sélectionner vos billets sur la page de l'événement"
|
||||
return
|
||||
end
|
||||
|
||||
@@ -47,7 +47,7 @@ class OrdersController < ApplicationController
|
||||
@cart_data = params[:cart_data] || session[:pending_cart] || {}
|
||||
|
||||
if @cart_data.empty?
|
||||
redirect_to seo_event_path(@event), alert: "Aucun billet sélectionné"
|
||||
redirect_to event_path(@event.slug, @event), alert: "Aucun billet sélectionné"
|
||||
return
|
||||
end
|
||||
|
||||
@@ -87,44 +87,32 @@ class OrdersController < ApplicationController
|
||||
if success
|
||||
session[:draft_order_id] = @order.id
|
||||
session.delete(:pending_cart)
|
||||
year = @event.start_time.year
|
||||
month = format("%02d", @event.start_time.month)
|
||||
redirect_to event_checkout_path(year: year, month: month, slug: @event.slug)
|
||||
redirect_to checkout_order_path(@order)
|
||||
else
|
||||
year = @event.start_time.year
|
||||
month = format("%02d", @event.start_time.month)
|
||||
redirect_to book_event_tickets_path(year: year, month: month, slug: @event.slug)
|
||||
redirect_to event_order_new_path(@event.slug, @event.id)
|
||||
end
|
||||
rescue => e
|
||||
error_message = e.message.present? ? e.message : "Erreur inconnue"
|
||||
flash[:alert] = "Une erreur est survenue: #{error_message}"
|
||||
year = @event.start_time.year
|
||||
month = format("%02d", @event.start_time.month)
|
||||
redirect_to book_event_tickets_path(year: year, month: month, slug: @event.slug)
|
||||
redirect_to event_order_new_path(@event.slug, @event.id)
|
||||
end
|
||||
|
||||
# Display order summary
|
||||
#
|
||||
#
|
||||
def show
|
||||
@tickets = @order.tickets.includes(:ticket_type)
|
||||
end
|
||||
|
||||
# Display payment page for an order (SEO-friendly checkout URL)
|
||||
# Display payment page for an order
|
||||
#
|
||||
# Display a summary of all tickets in the order and permit user
|
||||
# to proceed to payment via Stripe
|
||||
def checkout
|
||||
# Find order from session or create one
|
||||
@order = current_user.orders.find_by(id: session[:draft_order_id], event: @event, status: "draft")
|
||||
|
||||
unless @order
|
||||
redirect_to seo_event_path(@event), alert: "Aucune commande en attente trouvée"
|
||||
return
|
||||
end
|
||||
|
||||
# Handle expired orders
|
||||
if @order.expired?
|
||||
@order.expire_if_overdue!
|
||||
return redirect_to seo_event_path(@event),
|
||||
return redirect_to event_path(@order.event.slug, @order.event),
|
||||
alert: "Votre commande a expiré. Veuillez recommencer."
|
||||
end
|
||||
|
||||
@@ -153,41 +141,117 @@ class OrdersController < ApplicationController
|
||||
# Allow users to retry payment for failed/cancelled payments
|
||||
def retry_payment
|
||||
unless @order.can_retry_payment?
|
||||
redirect_to seo_event_path(@order.event),
|
||||
redirect_to event_path(@order.event.slug, @order.event),
|
||||
alert: "Cette commande ne peut plus être payée"
|
||||
return
|
||||
end
|
||||
|
||||
year = @order.event.start_time.year
|
||||
month = format("%02d", @order.event.start_time.month)
|
||||
redirect_to event_checkout_path(year: year, month: month, slug: @order.event.slug)
|
||||
redirect_to checkout_order_path(@order)
|
||||
end
|
||||
|
||||
# Handle successful payment
|
||||
def payment_success
|
||||
session_id = params[:session_id]
|
||||
|
||||
# Check if Stripe is properly configured
|
||||
stripe_configured = Rails.application.config.stripe[:secret_key].present?
|
||||
Rails.logger.debug "Payment success - Stripe configured: #{stripe_configured}"
|
||||
|
||||
unless stripe_configured
|
||||
redirect_to root_path, alert: "Le système de paiement n'est pas correctement configuré. Veuillez contacter l'administrateur."
|
||||
return
|
||||
end
|
||||
|
||||
begin
|
||||
stripe_session = Stripe::Checkout::Session.retrieve(session_id)
|
||||
|
||||
if stripe_session.payment_status == "paid"
|
||||
# Get order_id from session metadata
|
||||
order_id = stripe_session.metadata["order_id"]
|
||||
|
||||
unless order_id.present?
|
||||
redirect_to dashboard_path, alert: "Informations de commande manquantes"
|
||||
return
|
||||
end
|
||||
|
||||
# Find and update the order
|
||||
@order = current_user.orders.includes(tickets: :ticket_type).find(order_id)
|
||||
@order.mark_as_paid!
|
||||
|
||||
# Schedule Stripe invoice generation in background
|
||||
# This creates accounting records without blocking the payment success flow
|
||||
begin
|
||||
StripeInvoiceGenerationJob.perform_later(@order.id)
|
||||
Rails.logger.info "Scheduled Stripe invoice generation for order #{@order.id}"
|
||||
rescue => e
|
||||
Rails.logger.error "Failed to schedule invoice generation for order #{@order.id}: #{e.message}"
|
||||
# Don't fail the payment process due to job scheduling issues
|
||||
end
|
||||
|
||||
# Send confirmation emails
|
||||
@order.tickets.each do |ticket|
|
||||
begin
|
||||
TicketMailer.purchase_confirmation(ticket).deliver_now
|
||||
rescue => e
|
||||
Rails.logger.error "Failed to send confirmation email for ticket #{ticket.id}: #{e.message}"
|
||||
# Don't fail the entire payment process due to email/PDF generation issues
|
||||
end
|
||||
end
|
||||
|
||||
# Clear session data
|
||||
session.delete(:pending_cart)
|
||||
session.delete(:ticket_names)
|
||||
session.delete(:draft_order_id)
|
||||
|
||||
render "payment_success"
|
||||
else
|
||||
redirect_to dashboard_path, alert: "Le paiement n'a pas été complété avec succès"
|
||||
end
|
||||
rescue Stripe::StripeError => e
|
||||
error_message = e.message.present? ? e.message : "Erreur Stripe inconnue"
|
||||
redirect_to dashboard_path, alert: "Erreur lors du traitement de votre confirmation de paiement : #{error_message}"
|
||||
rescue => e
|
||||
error_message = e.message.present? ? e.message : "Erreur inconnue"
|
||||
Rails.logger.error "Payment success error: #{e.class} - #{error_message}"
|
||||
redirect_to dashboard_path, alert: "Une erreur inattendue s'est produite : #{error_message}"
|
||||
end
|
||||
end
|
||||
|
||||
# Handle payment failure/cancellation
|
||||
def payment_cancel
|
||||
order_id = params[:order_id] || session[:draft_order_id]
|
||||
|
||||
if order_id.present?
|
||||
order = current_user.orders.find_by(id: order_id, status: "draft")
|
||||
|
||||
if order&.can_retry_payment?
|
||||
redirect_to checkout_order_path(order),
|
||||
alert: "Le paiement a été annulé. Vous pouvez réessayer."
|
||||
else
|
||||
session.delete(:draft_order_id)
|
||||
redirect_to root_path, alert: "Le paiement a été annulé et votre commande a expiré."
|
||||
end
|
||||
else
|
||||
redirect_to root_path, alert: "Le paiement a été annulé"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_event_from_seo_params
|
||||
year = params[:year].to_i
|
||||
month = params[:month].to_i
|
||||
start_of_month = Date.new(year, month, 1).beginning_of_month
|
||||
end_of_month = start_of_month.end_of_month
|
||||
|
||||
@event = Event.includes(:ticket_types)
|
||||
.where(slug: params[:slug])
|
||||
.where(start_time: start_of_month..end_of_month)
|
||||
.first
|
||||
|
||||
return redirect_to events_path, alert: "Événement non trouvé" unless @event
|
||||
end
|
||||
|
||||
def set_order_from_id
|
||||
@order = current_user.orders.includes(:tickets, :event).find(params[:order_id])
|
||||
@event = @order.event
|
||||
def set_order
|
||||
@order = current_user.orders.includes(:tickets, :event).find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to root_path, alert: "Commande non trouvée"
|
||||
end
|
||||
|
||||
def set_event
|
||||
@event = Event.includes(:ticket_types).find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to events_path, alert: "Événement non trouvé"
|
||||
end
|
||||
|
||||
def order_params
|
||||
params.permit(tickets_attributes: [:ticket_type_id, :first_name, :last_name])
|
||||
params.permit(tickets_attributes: [ :ticket_type_id, :first_name, :last_name ])
|
||||
end
|
||||
|
||||
def create_stripe_session
|
||||
@@ -206,23 +270,15 @@ class OrdersController < ApplicationController
|
||||
end
|
||||
|
||||
Stripe::Checkout::Session.create(
|
||||
payment_method_types: ["card"],
|
||||
payment_method_types: [ "card" ],
|
||||
line_items: line_items,
|
||||
mode: "payment",
|
||||
success_url: booking_payment_success_url + "?session_id={CHECKOUT_SESSION_ID}",
|
||||
cancel_url: booking_payment_cancelled_url + "?order_id=#{@order.id}",
|
||||
success_url: order_payment_success_url + "?session_id={CHECKOUT_SESSION_ID}",
|
||||
cancel_url: order_payment_cancel_url,
|
||||
metadata: {
|
||||
order_id: @order.id,
|
||||
user_id: current_user.id
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
# Generate SEO-friendly path for an event
|
||||
def seo_event_path(event)
|
||||
year = event.start_time.year
|
||||
month = format("%02d", event.start_time.month)
|
||||
event_path(year: year, month: month, slug: event.slug)
|
||||
end
|
||||
helper_method :seo_event_path
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,127 +1,76 @@
|
||||
# Tickets controller - handles ticket viewing and downloads with SEO-friendly URLs
|
||||
# Legacy tickets controller - redirects to new order system
|
||||
#
|
||||
# This controller manages individual ticket display and downloads
|
||||
# Uses event-slug-ticket-id format for SEO-friendly URLs
|
||||
# This controller now primarily handles legacy redirects and backward compatibility
|
||||
# Most ticket creation functionality has been moved to OrdersController
|
||||
class TicketsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_ticket_from_seo_params, only: [:show, :view, :download, :retry_payment]
|
||||
before_action :authenticate_user!, only: [ :payment_success, :payment_cancel ]
|
||||
before_action :set_event, only: [ :checkout, :retry_payment ]
|
||||
|
||||
# Display ticket details
|
||||
def show
|
||||
@event = @ticket.event
|
||||
end
|
||||
|
||||
# Display ticket in PDF-like format
|
||||
def view
|
||||
@event = @ticket.event
|
||||
end
|
||||
|
||||
# Download PDF ticket - only accessible by ticket owner
|
||||
# User must be authenticated to download ticket
|
||||
def download
|
||||
# Generate PDF using Grover
|
||||
begin
|
||||
Rails.logger.info "Starting PDF generation for ticket ID: #{@ticket.id}"
|
||||
|
||||
# Render the HTML template
|
||||
html = render_to_string(
|
||||
partial: "tickets/pdf_ticket",
|
||||
layout: false,
|
||||
locals: { ticket: @ticket }
|
||||
)
|
||||
|
||||
Rails.logger.info "HTML template rendered successfully, length: #{html.length}"
|
||||
|
||||
# Configure Grover options for PDF generation
|
||||
pdf_options = {
|
||||
format: 'A4',
|
||||
margin: {
|
||||
top: '0.5in',
|
||||
bottom: '0.5in',
|
||||
left: '0.5in',
|
||||
right: '0.5in'
|
||||
},
|
||||
print_background: true,
|
||||
display_header_footer: false,
|
||||
prefer_css_page_size: true,
|
||||
launch_args: ["--no-sandbox", "--disable-setuid-sandbox"] # For better compatibility
|
||||
}
|
||||
|
||||
# Generate PDF
|
||||
pdf = Grover.new(html, pdf_options).to_pdf
|
||||
|
||||
Rails.logger.info "PDF generation completed for ticket ID: #{@ticket.id}"
|
||||
|
||||
# Send PDF as download with SEO-friendly filename
|
||||
send_data pdf,
|
||||
filename: "billet-#{@ticket.event.slug}-#{@ticket.id}.pdf",
|
||||
type: 'application/pdf',
|
||||
disposition: 'attachment'
|
||||
rescue => e
|
||||
Rails.logger.error "PDF generation failed for ticket ID: #{@ticket.id} - Error: #{e.message}"
|
||||
Rails.logger.error e.backtrace.join("\n")
|
||||
|
||||
redirect_to view_ticket_path(event_slug: @ticket.event.slug, ticket_id: @ticket.id),
|
||||
alert: "Erreur lors de la génération du PDF. Veuillez réessayer."
|
||||
# Redirect to order-based checkout
|
||||
def checkout
|
||||
# Check for draft order
|
||||
if session[:draft_order_id].present?
|
||||
order = current_user.orders.find_by(id: session[:draft_order_id], status: "draft")
|
||||
if order.present?
|
||||
redirect_to order_checkout_path(order)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# No order found
|
||||
@event = Event.includes(:ticket_types).find(params[:id])
|
||||
redirect_to event_path(@event.slug, @event), alert: "Aucun billet en attente de paiement"
|
||||
end
|
||||
|
||||
# Redirect to order-based payment success
|
||||
def payment_success
|
||||
redirect_to order_payment_success_path(session_id: params[:session_id])
|
||||
end
|
||||
|
||||
# Redirect to order-based payment cancel
|
||||
def payment_cancel
|
||||
redirect_to order_payment_cancel_path
|
||||
end
|
||||
|
||||
# Redirect retry payment to order system
|
||||
def retry_payment
|
||||
# Look for draft order for this ticket's event
|
||||
order = current_user.orders.find_by(event: @ticket.event, status: "draft")
|
||||
@event = Event.includes(:ticket_types).find(params[:id])
|
||||
|
||||
# Look for draft order for this event
|
||||
order = current_user.orders.find_by(event: @event, status: "draft")
|
||||
|
||||
if order&.can_retry_payment?
|
||||
year = order.event.start_time.year
|
||||
month = format("%02d", order.event.start_time.month)
|
||||
redirect_to event_checkout_path(year: year, month: month, slug: order.event.slug)
|
||||
redirect_to retry_payment_order_path(order)
|
||||
else
|
||||
redirect_to seo_event_path(@ticket.event),
|
||||
redirect_to event_path(@event.slug, @event),
|
||||
alert: "Aucune commande disponible pour un nouveau paiement"
|
||||
end
|
||||
end
|
||||
|
||||
# Legacy redirects for backward compatibility
|
||||
def payment_success
|
||||
redirect_to booking_payment_success_path(session_id: params[:session_id])
|
||||
def show
|
||||
@ticket = current_user.orders.joins(:tickets).find(params[:ticket_id])
|
||||
@event = @ticket.event
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to dashboard_path, alert: "Billet non trouvé"
|
||||
end
|
||||
|
||||
def payment_cancel
|
||||
redirect_to booking_payment_cancelled_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_ticket_from_seo_params
|
||||
# Parse event_slug and ticket_id from the SEO-friendly format: event-slug-123
|
||||
slug_and_id = params[:event_slug_ticket_id] || "#{params[:event_slug]}-#{params[:ticket_id]}"
|
||||
|
||||
# Split by last dash to separate event slug from ticket ID
|
||||
parts = slug_and_id.split('-')
|
||||
ticket_id = parts.pop
|
||||
event_slug = parts.join('-')
|
||||
def set_event
|
||||
event_id = params[:id] || session[:event_id]
|
||||
|
||||
# Find ticket and ensure it belongs to current user
|
||||
@ticket = Ticket.joins(order: :user)
|
||||
.includes(:event, :ticket_type, order: :user)
|
||||
.joins(:event)
|
||||
.where(
|
||||
tickets: { id: ticket_id },
|
||||
orders: { user_id: current_user.id },
|
||||
events: { slug: event_slug }
|
||||
)
|
||||
.first
|
||||
Rails.logger.debug "TicketsController#set_event - params[:id]: #{params[:id].inspect}, session[:event_id]: #{session[:event_id].inspect}"
|
||||
|
||||
unless @ticket
|
||||
redirect_to dashboard_path, alert: "Billet non trouvé ou vous n'avez pas l'autorisation d'accéder à ce billet"
|
||||
unless event_id
|
||||
Rails.logger.error "TicketsController#set_event - No event ID found"
|
||||
redirect_to events_path, alert: "Aucun événement spécifié"
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# Generate SEO-friendly path for an event
|
||||
def seo_event_path(event)
|
||||
year = event.start_time.year
|
||||
month = format("%02d", event.start_time.month)
|
||||
event_path(year: year, month: month, slug: event.slug)
|
||||
@event = Event.includes(:ticket_types).find(event_id)
|
||||
Rails.logger.debug "TicketsController#set_event - Found event: #{@event.id} - #{@event.name}"
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
Rails.logger.error "TicketsController#set_event - Event not found with ID: #{event_id}"
|
||||
redirect_to events_path, alert: "Événement non trouvé"
|
||||
end
|
||||
helper_method :seo_event_path
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
module PdfHelper
|
||||
require "rqrcode"
|
||||
|
||||
# Generate SVG QR code for tickets
|
||||
def qr_code_tag(data)
|
||||
qrcode = RQRCode::QRCode.new(data)
|
||||
|
||||
# Render as SVG
|
||||
raw qrcode.as_svg(
|
||||
offset: 0,
|
||||
color: "000",
|
||||
shape_rendering: "crispEdges",
|
||||
module_size: 4,
|
||||
standalone: true
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -118,7 +118,7 @@ export default class extends Controller {
|
||||
await this.storeCartInSession(cartData);
|
||||
|
||||
// Redirect to event-scoped orders/new page
|
||||
const OrderNewUrl = `/events/${this.eventSlugValue}/orders/new`;
|
||||
const OrderNewUrl = `/events/${this.eventSlugValue}.${this.eventIdValue}/orders/new`;
|
||||
window.location.href = OrderNewUrl;
|
||||
} catch (error) {
|
||||
console.error("Error storing cart:", error);
|
||||
|
||||
@@ -27,29 +27,6 @@ class Ticket < ApplicationRecord
|
||||
TicketPdfGenerator.new(self).generate
|
||||
end
|
||||
|
||||
# Generate QR code data for ticket validation
|
||||
def to_qr_data
|
||||
{
|
||||
ticket_id: id,
|
||||
qr_code: qr_code,
|
||||
event_id: event&.id,
|
||||
user_id: user&.id
|
||||
}.compact.to_json
|
||||
end
|
||||
|
||||
# Generate QR code as SVG
|
||||
def generate_qr_svg
|
||||
require "rqrcode"
|
||||
qrcode = RQRCode::QRCode.new(to_qr_data)
|
||||
qrcode.as_svg(
|
||||
offset: 0,
|
||||
color: "000",
|
||||
shape_rendering: "crispEdges",
|
||||
module_size: 4,
|
||||
standalone: true
|
||||
)
|
||||
end
|
||||
|
||||
# Price in euros (formatted)
|
||||
def price_euros
|
||||
price_cents / 100.0
|
||||
|
||||
113
app/services/ticket_pdf_generator.rb
Executable file
113
app/services/ticket_pdf_generator.rb
Executable file
@@ -0,0 +1,113 @@
|
||||
require "prawn"
|
||||
require "prawn/qrcode"
|
||||
require "rqrcode"
|
||||
|
||||
# PDF ticket generator service using Prawn
|
||||
#
|
||||
# Generates PDF tickets with QR codes for event entry validation
|
||||
# Includes event details, venue information, and unique QR code for each ticket
|
||||
class TicketPdfGenerator
|
||||
# Suppress Prawn's internationalization warning for built-in fonts
|
||||
Prawn::Fonts::AFM.hide_m17n_warning = true
|
||||
attr_reader :ticket
|
||||
|
||||
def initialize(ticket)
|
||||
@ticket = ticket
|
||||
end
|
||||
|
||||
def generate
|
||||
Prawn::Document.new(page_size: [ 350, 600 ], margin: 20) do |pdf|
|
||||
# Header
|
||||
pdf.fill_color "2D1B69"
|
||||
pdf.font "Helvetica", style: :bold, size: 24
|
||||
pdf.text "ApéroNight", align: :center
|
||||
pdf.move_down 10
|
||||
|
||||
# Event name
|
||||
pdf.fill_color "000000"
|
||||
pdf.font "Helvetica", style: :bold, size: 18
|
||||
pdf.text ticket.event.name, align: :center
|
||||
pdf.move_down 20
|
||||
|
||||
# Ticket info box
|
||||
pdf.stroke_color "E5E7EB"
|
||||
pdf.fill_color "F9FAFB"
|
||||
pdf.rounded_rectangle [ 0, pdf.cursor ], 310, 120, 10
|
||||
pdf.fill_and_stroke
|
||||
|
||||
pdf.move_down 10
|
||||
pdf.fill_color "000000"
|
||||
pdf.font "Helvetica", size: 12
|
||||
|
||||
# Ticket details
|
||||
pdf.text "Ticket Type:", style: :bold
|
||||
pdf.text ticket.ticket_type.name
|
||||
pdf.move_down 8
|
||||
|
||||
pdf.text "Price:", style: :bold
|
||||
pdf.text "€#{ticket.price_euros}"
|
||||
pdf.move_down 8
|
||||
|
||||
pdf.text "Date & Time:", style: :bold
|
||||
pdf.text ticket.event.start_time.strftime("%B %d, %Y at %I:%M %p")
|
||||
pdf.move_down 20
|
||||
|
||||
# Venue information
|
||||
pdf.fill_color "374151"
|
||||
pdf.font "Helvetica", style: :bold, size: 14
|
||||
pdf.text "Venue Information"
|
||||
pdf.move_down 8
|
||||
|
||||
pdf.font "Helvetica", size: 11
|
||||
pdf.text ticket.event.venue_name, style: :bold
|
||||
pdf.text ticket.event.venue_address
|
||||
pdf.move_down 20
|
||||
|
||||
# QR Code
|
||||
pdf.fill_color "000000"
|
||||
pdf.font "Helvetica", style: :bold, size: 14
|
||||
pdf.text "Ticket QR Code", align: :center
|
||||
pdf.move_down 10
|
||||
|
||||
# Ensure all required data is present before generating QR code
|
||||
if ticket.qr_code.blank?
|
||||
raise "Ticket QR code is missing"
|
||||
end
|
||||
|
||||
qr_code_data = {
|
||||
ticket_id: ticket.id,
|
||||
qr_code: ticket.qr_code,
|
||||
event_id: ticket.event&.id,
|
||||
user_id: ticket.user&.id
|
||||
}.compact.to_json
|
||||
|
||||
# Validate QR code data before creating QR code
|
||||
if qr_code_data.blank? || qr_code_data == "{}"
|
||||
raise "QR code data is empty or invalid"
|
||||
end
|
||||
|
||||
qrcode = RQRCode::QRCode.new(qr_code_data)
|
||||
pdf.print_qr_code(qrcode, extent: 120, align: :center)
|
||||
|
||||
pdf.move_down 15
|
||||
|
||||
# QR code text
|
||||
pdf.font "Helvetica", size: 8
|
||||
pdf.fill_color "6B7280"
|
||||
pdf.text "QR Code: #{ticket.qr_code[0..7]}...", align: :center
|
||||
|
||||
# Footer
|
||||
pdf.move_down 30
|
||||
pdf.stroke_color "E5E7EB"
|
||||
pdf.horizontal_line 0, 310
|
||||
pdf.move_down 10
|
||||
|
||||
pdf.font "Helvetica", size: 8
|
||||
pdf.fill_color "6B7280"
|
||||
pdf.text "This ticket is valid for one entry only.", align: :center
|
||||
pdf.text "Present this ticket at the venue entrance.", align: :center
|
||||
pdf.move_down 5
|
||||
pdf.text "Generated on #{Time.current.strftime('%B %d, %Y at %I:%M %p')}", align: :center
|
||||
end.render
|
||||
end
|
||||
end
|
||||
@@ -1,61 +0,0 @@
|
||||
<div class="min-h-screen bg-gradient-to-br from-green-50 to-green-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-md w-full bg-white rounded-2xl shadow-xl p-8">
|
||||
<!-- Success Icon -->
|
||||
<div class="flex justify-center mb-6">
|
||||
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">Paiement réussi !</h1>
|
||||
<p class="text-gray-600">Votre commande a été confirmée et vos billets ont été envoyés par email.</p>
|
||||
</div>
|
||||
|
||||
<!-- Order Details -->
|
||||
<% if @order&.present? %>
|
||||
<div class="border-t border-gray-200 pt-6 mb-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<span class="text-sm font-medium text-gray-900">Commande #<%= @order.id %></span>
|
||||
<span class="text-sm text-gray-500"><%= @order.created_at.strftime("%d/%m/%Y à %H:%M") %></span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 mb-4">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-sm text-gray-600">Événement:</span>
|
||||
<span class="text-sm font-medium text-gray-900"><%= @order.event.name %></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-sm text-gray-600">Nombre de billets:</span>
|
||||
<span class="text-sm font-medium text-gray-900"><%= @order.tickets.count %></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-sm text-gray-600">Total:</span>
|
||||
<span class="text-sm font-bold text-green-600"><%= @order.total_amount_euros %>€</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="space-y-3">
|
||||
<%= link_to dashboard_path, class: "w-full bg-green-600 hover:bg-green-700 text-white font-medium py-3 px-4 rounded-lg transition-colors text-center block" do %>
|
||||
Voir mes billets
|
||||
<% end %>
|
||||
|
||||
<%= link_to events_path, class: "w-full bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium py-3 px-4 rounded-lg transition-colors text-center block" do %>
|
||||
Découvrir d'autres événements
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-xs text-gray-500">
|
||||
Un email de confirmation a été envoyé à votre adresse email avec vos billets en pièce jointe.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,4 +1,4 @@
|
||||
<%= link_to seo_event_path(event), class: "group block p-4 rounded-lg border border-slate-200 dark:border-slate-700 hover:border-purple-300 dark:hover:border-purple-600 hover:shadow-md transition-all duration-200" do %>
|
||||
<%= link_to event_path(event.slug, event), class: "group block p-4 rounded-lg border border-slate-200 dark:border-slate-700 hover:border-purple-300 dark:hover:border-purple-600 hover:shadow-md transition-all duration-200" do %>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="w-16 h-16 bg-slate-200 dark:bg-slate-700 rounded-lg overflow-hidden flex-shrink-0">
|
||||
<%= image_tag event.image, alt: event.name, class: "w-full h-full object-cover" if event.image.present? %>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
>
|
||||
<% if event.image.present? %>
|
||||
<div class="h-48 overflow-hidden">
|
||||
<%= link_to event_path(event) do %>
|
||||
<%= link_to event_path(event.slug, event) do %>
|
||||
<img
|
||||
src="<%= event.image %>"
|
||||
alt="<%= event.name %>"
|
||||
@@ -130,7 +130,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= link_to event_path(event), class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-lg shadow-sm text-white bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200" do %>
|
||||
<%= link_to event_path(event.slug, event), class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-lg shadow-sm text-white bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 transition-all duration-200" do %>
|
||||
Détails
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
|
||||
@@ -1,62 +1,4 @@
|
||||
<!-- SEO Meta Tags for Event -->
|
||||
<% content_for :title, "#{@event.name} - #{@event.start_time.strftime('%d/%m/%Y')} | Aperonight" %>
|
||||
<% content_for :description, @event.description.truncate(160) %>
|
||||
<% content_for :keywords, "#{@event.name}, événement, soirée, #{@event.venue_name}, billets, réservation" %>
|
||||
<% content_for :canonical_url, seo_event_path(@event) %>
|
||||
<% content_for :og_image, @event.image if @event.image.present? %>
|
||||
|
||||
<!-- Structured Data for Event -->
|
||||
<% content_for :head do %>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Event",
|
||||
"name": "<%= @event.name %>",
|
||||
"description": "<%= strip_tags(@event.description) %>",
|
||||
"startDate": "<%= @event.start_time.iso8601 %>",
|
||||
"endDate": "<%= @event.end_time&.iso8601 || (@event.start_time + 4.hours).iso8601 %>",
|
||||
"eventStatus": "https://schema.org/EventScheduled",
|
||||
"eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode",
|
||||
"location": {
|
||||
"@type": "Place",
|
||||
"name": "<%= @event.venue_name %>",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"streetAddress": "<%= @event.venue_address %>"
|
||||
},
|
||||
"geo": {
|
||||
"@type": "GeoCoordinates",
|
||||
"latitude": <%= @event.latitude %>,
|
||||
"longitude": <%= @event.longitude %>
|
||||
}
|
||||
},
|
||||
"organizer": {
|
||||
"@type": "Organization",
|
||||
"name": "<%= @event.user.company_name.present? ? @event.user.company_name : "#{@event.user.first_name} #{@event.user.last_name}" %>",
|
||||
"email": "<%= @event.user.email %>"
|
||||
},
|
||||
<% if @event.image.present? %>
|
||||
"image": [
|
||||
"<%= @event.image %>"
|
||||
],
|
||||
<% end %>
|
||||
"offers": [
|
||||
<% @event.ticket_types.each_with_index do |ticket_type, index| %>
|
||||
{
|
||||
"@type": "Offer",
|
||||
"name": "<%= ticket_type.name %>",
|
||||
"price": "<%= ticket_type.price_cents / 100.0 %>",
|
||||
"priceCurrency": "EUR",
|
||||
"availability": "https://schema.org/InStock",
|
||||
"url": "<%= seo_book_tickets_path(@event) %>"
|
||||
}<%= ',' if index < @event.ticket_types.count - 1 %>
|
||||
<% end %>
|
||||
]
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
|
||||
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100">
|
||||
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<!-- Breadcrumb -->
|
||||
<nav class="mb-6" aria-label="Breadcrumb">
|
||||
@@ -255,7 +197,7 @@
|
||||
|
||||
<!-- Right Column: Ticket Selection -->
|
||||
<div class="lg:col-span-1">
|
||||
<%= form_with url: book_event_tickets_path(year: @event.start_time.year, month: format("%02d", @event.start_time.month), slug: @event.slug), method: :get, id: "checkout_form", local: true, data: {
|
||||
<%= form_with url: event_order_new_path(@event.slug, @event.id), method: :get, id: "checkout_form", local: true, data: {
|
||||
controller: "ticket-selection",
|
||||
ticket_selection_target: "form",
|
||||
ticket_selection_event_slug_value: @event.slug,
|
||||
|
||||
@@ -1,31 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title><%= content_for(:title) || "Aperonight - Événements et Soirées" %></title>
|
||||
<title><%= content_for(:title) || "Aperonight" %></title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
|
||||
<!-- SEO Meta Tags -->
|
||||
<meta name="description" content="<%= content_for(:description) || "Découvrez et réservez vos billets pour les meilleurs événements et soirées. Aperonight vous connecte aux événements incontournables près de chez vous." %>">
|
||||
<meta name="keywords" content="<%= content_for(:keywords) || "événements, soirées, billets, réservation, nightlife, fêtes" %>">
|
||||
<meta name="robots" content="index, follow">
|
||||
<link rel="canonical" href="<%= content_for(:canonical_url) || request.original_url %>">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="<%= request.original_url %>">
|
||||
<meta property="og:title" content="<%= content_for(:title) || "Aperonight - Événements et Soirées" %>">
|
||||
<meta property="og:description" content="<%= content_for(:description) || "Découvrez et réservez vos billets pour les meilleurs événements et soirées." %>">
|
||||
<meta property="og:image" content="<%= content_for(:og_image) || asset_url('aperonight-og-image.jpg') %>">
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image">
|
||||
<meta property="twitter:url" content="<%= request.original_url %>">
|
||||
<meta property="twitter:title" content="<%= content_for(:title) || "Aperonight - Événements et Soirées" %>">
|
||||
<meta property="twitter:description" content="<%= content_for(:description) || "Découvrez et réservez vos billets pour les meilleurs événements et soirées." %>">
|
||||
<meta property="twitter:image" content="<%= content_for(:og_image) || asset_url('aperonight-og-image.jpg') %>">
|
||||
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
<%= yield :head %>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><%= yield :title %></title>
|
||||
<%= stylesheet_link_tag "pdf" %>
|
||||
</head>
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
||||
@@ -18,7 +18,7 @@
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
<%= link_to event_path(@order.event), class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
<%= link_to event_path(@order.event.slug, @order.event), class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
<%= @order.event.name %>
|
||||
<% end %>
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -289,7 +289,7 @@
|
||||
<!-- Order Actions -->
|
||||
<div class="border-t border-gray-200 pt-6 mt-6">
|
||||
<div class="space-y-3">
|
||||
<%= link_to event_path(@order.event), class: "block w-full text-center py-3 px-4 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition-colors" do %>
|
||||
<%= link_to event_path(@order.event.slug, @order.event), class: "block w-full text-center py-3 px-4 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition-colors" do %>
|
||||
<div class="flex items-center justify-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
<%= link_to event_path(@event), class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
<%= link_to event_path(@event.slug, @event), class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
<%= @event.name %>
|
||||
<% end %>
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -89,7 +89,7 @@
|
||||
<p class="text-gray-600 max-w-md mx-auto">Veuillez fournir les prénoms et noms des personnes qui utiliseront les billets.</p>
|
||||
</div>
|
||||
|
||||
<%= form_with url: event_orders_path(@event), method: :post, local: true, class: "space-y-8" do |form| %>
|
||||
<%= form_with url: event_order_create_path(@event.slug, @event.id), method: :post, local: true, class: "space-y-8" do |form| %>
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-center mb-2">
|
||||
<div class="bg-purple-600 rounded-full p-2 mr-3">
|
||||
@@ -129,7 +129,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-4 pt-6">
|
||||
<%= link_to "Retour", event_path(@event), class: "px-6 py-3 border border-gray-300 text-gray-700 rounded-xl hover:bg-gray-50 text-center font-medium transition-colors duration-200" %>
|
||||
<%= link_to "Retour", event_path(@event.slug, @event), class: "px-6 py-3 border border-gray-300 text-gray-700 rounded-xl hover:bg-gray-50 text-center font-medium transition-colors duration-200" %>
|
||||
<%= form.submit "Procéder au paiement", class: "flex-1 bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white font-medium py-3 px-6 rounded-xl shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transform hover:-translate-y-0.5" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
<%= link_to seo_event_path(@order.event), class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
<%= link_to event_path(@order.event.slug, @order.event), class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
<%= @order.event.name %>
|
||||
<% end %>
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -79,7 +79,7 @@
|
||||
<!-- Actions -->
|
||||
<div class="border-t border-gray-200 pt-6 mt-6">
|
||||
<div class="flex space-x-4">
|
||||
<%= link_to seo_event_path(@order.event), class: "bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium py-2 px-4 rounded-lg transition-colors" do %>
|
||||
<%= link_to event_path(@order.event.slug, @order.event), class: "bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium py-2 px-4 rounded-lg transition-colors" do %>
|
||||
<div class="flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
||||
@@ -88,7 +88,7 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @order.can_retry_payment? %>
|
||||
<%= link_to booking_summary_path(@order), class: "bg-purple-600 hover:bg-purple-700 text-white font-medium py-2 px-4 rounded-lg transition-colors" do %>
|
||||
<%= link_to checkout_order_path(@order), class: "bg-purple-600 hover:bg-purple-700 text-white font-medium py-2 px-4 rounded-lg transition-colors" do %>
|
||||
<div class="flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"/>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="featured-events-grid" data-controller="featured-event">
|
||||
<% @featured_events.each do |event| %>
|
||||
<div class="featured-event-card" data-featured-event-target="card">
|
||||
<%= link_to event_path(event) do %>
|
||||
<%= link_to event_path(event.slug, event) do %>
|
||||
<img src="<%= event.image %>" alt="<%= event.name %>" class="featured-event-image" data-featured-event-target="animated">
|
||||
<% end %>
|
||||
<div class="featured-event-content">
|
||||
@@ -58,7 +58,7 @@
|
||||
<p class="featured-event-description"><%= event.description %></p>
|
||||
<div class="featured-event-footer">
|
||||
<span class="featured-event-price">€<%= event.ticket_types.minimum(:price_cents).to_f / 100 %></span>
|
||||
<%= link_to "Réserver une place", event_path(event), class: "btn btn-sm btn-primary" %>
|
||||
<%= link_to "Réserver une place", event_path(event.slug, event), class: "btn btn-sm btn-primary" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
<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), target: "_blank", class: "text-green-600 hover:text-green-800 font-medium text-sm" do %>
|
||||
<%= link_to event_path(@event.slug, @event), target: "_blank", class: "text-green-600 hover:text-green-800 font-medium text-sm" do %>
|
||||
Voir publiquement <i data-lucide="external-link" class="w-4 h-4 inline ml-1"></i>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Ticket #<%= ticket.id %></title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
color: #000000;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.ticket-container {
|
||||
max-width: 350px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #2D1B69;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.event-name {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.event-name h2 {
|
||||
color: #000000;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ticket-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.qr-code-section {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.qr-code-container svg {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="ticket-container">
|
||||
<div class="header">
|
||||
<h1>ApéroNight</h1>
|
||||
</div>
|
||||
|
||||
<div class="event-name">
|
||||
<h2><%= ticket.event.name %></h2>
|
||||
</div>
|
||||
|
||||
<div class="ticket-info">
|
||||
<div class="info-row">
|
||||
<strong>Ticket Holder:</strong> <%= ticket.first_name %> <%= ticket.last_name %>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<strong>Ticket Type:</strong> <%= ticket.ticket_type.name %>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<strong>Price:</strong> €<%= ticket.price_euros %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qr-code-section">
|
||||
<div class="qr-code-container">
|
||||
<%= raw ticket.generate_qr_svg %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -18,7 +18,7 @@
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
<%= link_to event_path(@event), class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
<%= link_to event_path(@event.slug, @event), class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
<%= @event.name %>
|
||||
<% end %>
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
<%= link_to event_path(@event), class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
<%= link_to event_path(@event.slug, @event), class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
<%= @event.name %>
|
||||
<% end %>
|
||||
<svg
|
||||
@@ -180,7 +180,7 @@
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-4 pt-6">
|
||||
<%= link_to "Retour",
|
||||
event_path(@event),
|
||||
event_path(@event.slug, @event),
|
||||
class:
|
||||
"px-6 py-3 border border-gray-300 text-gray-700 rounded-xl hover:bg-gray-50 text-center font-medium transition-colors duration-200" %>
|
||||
<%= form.submit "Procéder au paiement",
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
<div class="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 py-8">
|
||||
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 py-8">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<!-- Breadcrumb -->
|
||||
<nav class="mb-8" aria-label="Breadcrumb">
|
||||
<ol class="flex items-center space-x-2 text-sm">
|
||||
<%= link_to root_path, class: "text-slate-500 hover:text-purple-600 transition-colors duration-200" do %>
|
||||
<svg class="w-4 h-4 inline-block mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/>
|
||||
<%= link_to root_path, class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
<svg class="w-4 h-4 inline-block mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/>
|
||||
</svg>
|
||||
Accueil
|
||||
<% end %>
|
||||
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
<%= link_to dashboard_path, class: "text-slate-500 hover:text-purple-600 transition-colors duration-200" do %>
|
||||
<%= link_to dashboard_path, class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
Tableau de bord
|
||||
<% end %>
|
||||
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
<li class="font-medium text-slate-900" aria-current="page">Billet #<%= @ticket.id %></li>
|
||||
<li class="font-medium text-gray-900" aria-current="page">Billet #<%= @ticket.id %></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-xl overflow-hidden border border-slate-200">
|
||||
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
|
||||
<!-- Ticket Header -->
|
||||
<div class="bg-gradient-to-r from-purple-600 to-violet-600 px-8 py-6">
|
||||
<div class="bg-gradient-to-r from-purple-600 to-indigo-600 px-8 py-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl md:text-3xl font-bold text-white mb-2">Billet Électronique</h1>
|
||||
<p class="text-purple-100">ID: #<%= @ticket.id %></p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium <%=
|
||||
<div class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium <%=
|
||||
case @ticket.status
|
||||
when 'active' then 'bg-emerald-100 text-emerald-800'
|
||||
when 'draft' then 'bg-amber-100 text-amber-800'
|
||||
when 'used' then 'bg-slate-100 text-slate-800'
|
||||
when 'active' then 'bg-green-100 text-green-800'
|
||||
when 'draft' then 'bg-yellow-100 text-yellow-800'
|
||||
when 'used' then 'bg-gray-100 text-gray-800'
|
||||
when 'expired' then 'bg-red-100 text-red-800'
|
||||
when 'refunded' then 'bg-sky-100 text-sky-800'
|
||||
else 'bg-slate-100 text-slate-800'
|
||||
when 'refunded' then 'bg-blue-100 text-blue-800'
|
||||
else 'bg-gray-100 text-gray-800'
|
||||
end %>">
|
||||
<%=
|
||||
<%=
|
||||
case @ticket.status
|
||||
when 'active' then 'Valide'
|
||||
when 'draft' then 'En attente'
|
||||
@@ -58,49 +58,47 @@
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<!-- Event Details -->
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900 mb-6">Détails de l'événement</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-6">Détails de l'événement</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-500 mb-2">Événement</label>
|
||||
<p class="text-lg font-semibold text-slate-900"><%= @event.name %></p>
|
||||
<label class="block text-sm font-medium text-gray-500 mb-1">Événement</label>
|
||||
<p class="text-lg font-semibold text-gray-900"><%= @event.name %></p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-500 mb-2">Date et heure</label>
|
||||
<div class="flex items-start text-slate-900">
|
||||
<svg class="w-4 h-4 mr-2 mt-0.5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
||||
<label class="block text-sm font-medium text-gray-500 mb-1">Date et heure</label>
|
||||
<div class="flex items-center text-gray-900">
|
||||
<svg class="w-4 h-4 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="font-medium"><%= @event.start_time.strftime("%d %B %Y") %></div>
|
||||
<div class="text-sm text-slate-600"><%= @event.start_time.strftime("%H:%M") %></div>
|
||||
</div>
|
||||
<%= @event.start_time.strftime("%d %B %Y") %><br>
|
||||
<small class="text-gray-600"><%= @event.start_time.strftime("%H:%M") %></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-500 mb-2">Lieu</label>
|
||||
<div class="flex items-center text-slate-900">
|
||||
<svg class="w-4 h-4 mr-2 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
<label class="block text-sm font-medium text-gray-500 mb-1">Lieu</label>
|
||||
<div class="flex items-center text-gray-900">
|
||||
<svg class="w-4 h-4 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
<span class="font-medium"><%= @event.venue_name %></span>
|
||||
<%= @event.venue_name %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-500 mb-2">Type de billet</label>
|
||||
<p class="text-slate-900 font-medium mb-1"><%= @ticket.ticket_type.name %></p>
|
||||
<p class="text-sm text-slate-600"><%= @ticket.ticket_type.description %></p>
|
||||
<label class="block text-sm font-medium text-gray-500 mb-1">Type de billet</label>
|
||||
<p class="text-gray-900 font-medium"><%= @ticket.ticket_type.name %></p>
|
||||
<p class="text-sm text-gray-600"><%= @ticket.ticket_type.description %></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-500 mb-2">Prix</label>
|
||||
<p class="text-2xl font-bold text-slate-900">
|
||||
<label class="block text-sm font-medium text-gray-500 mb-1">Prix</label>
|
||||
<p class="text-xl font-bold text-gray-900">
|
||||
<%= number_to_currency(@ticket.price_euros, unit: "€", separator: ",", delimiter: " ", format: "%n %u") %>
|
||||
</p>
|
||||
</div>
|
||||
@@ -109,36 +107,38 @@
|
||||
|
||||
<!-- Ticket Details -->
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900 mb-6">Informations du billet</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900 mb-6">Informations du billet</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-500 mb-2">Prénom</label>
|
||||
<p class="text-slate-900 font-medium"><%= @ticket.first_name %></p>
|
||||
<label class="block text-sm font-medium text-gray-500 mb-1">Prénom</label>
|
||||
<p class="text-gray-900 font-medium"><%= @ticket.first_name %></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-500 mb-2">Nom</label>
|
||||
<p class="text-slate-900 font-medium"><%= @ticket.last_name %></p>
|
||||
<label class="block text-sm font-medium text-gray-500 mb-1">Nom</label>
|
||||
<p class="text-gray-900 font-medium"><%= @ticket.last_name %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-500 mb-2">Date d'achat</label>
|
||||
<p class="text-slate-900 font-medium"><%= @ticket.created_at.strftime("%d %B %Y à %H:%M") %></p>
|
||||
<label class="block text-sm font-medium text-gray-500 mb-1">Date d'achat</label>
|
||||
<p class="text-gray-900"><%= @ticket.created_at.strftime("%d %B %Y à %H:%M") %></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-500 mb-2">QR Code</label>
|
||||
<div class="bg-slate-50 rounded-xl p-6 text-center border border-slate-200">
|
||||
<div class="inline-block bg-white p-4 rounded-xl shadow-sm border border-slate-200">
|
||||
<div class="w-64 h-64 flex items-center justify-center">
|
||||
<%= raw @ticket.generate_qr_svg %>
|
||||
<label class="block text-sm font-medium text-gray-500 mb-1">Code QR</label>
|
||||
<div class="bg-gray-50 rounded-lg p-4 text-center">
|
||||
<div class="inline-block bg-white p-4 rounded-lg shadow-sm">
|
||||
<!-- QR Code would be generated here -->
|
||||
<div class="w-32 h-32 bg-gray-200 rounded flex items-center justify-center">
|
||||
<svg class="w-12 h-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-slate-500 mt-3 font-mono tracking-wider"><%= @ticket.qr_code[0..7]... %></p>
|
||||
<p class="text-xs text-slate-400 mt-1">Scannez ce code à l'entrée</p>
|
||||
<p class="text-xs text-gray-500 mt-2 font-mono"><%= @ticket.qr_code %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,21 +146,21 @@
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="mt-8 pt-6 border-t border-slate-200">
|
||||
<div class="mt-8 pt-6 border-t border-gray-200">
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<%= link_to dashboard_path,
|
||||
class: "flex items-center justify-center px-6 py-3 border border-slate-300 text-slate-700 rounded-xl hover:bg-slate-50 hover:border-slate-400 font-medium transition-all duration-200" do %>
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7 16l-4-4m0 0l4-4m-4 4h18"/>
|
||||
<%= link_to dashboard_path,
|
||||
class: "px-6 py-3 border border-gray-300 text-gray-700 rounded-xl hover:bg-gray-50 text-center font-medium transition-colors duration-200" do %>
|
||||
<svg class="w-4 h-4 inline-block mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16l-4-4m0 0l4-4m-4 4h18"/>
|
||||
</svg>
|
||||
Retour au tableau de bord
|
||||
<% end %>
|
||||
|
||||
<% if @ticket.status == 'active' %>
|
||||
<%= link_to download_ticket_path(@ticket.id),
|
||||
class: "flex-1 flex items-center justify-center bg-gradient-to-r from-purple-600 to-violet-600 hover:from-purple-700 hover:to-violet-700 text-white font-medium py-3 px-6 rounded-xl shadow-sm hover:shadow-md transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transform hover:-translate-y-0.5" do %>
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
<%= link_to "#",
|
||||
class: "flex-1 bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white font-medium py-3 px-6 rounded-xl shadow-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transform hover:-translate-y-0.5 text-center" do %>
|
||||
<svg class="w-4 h-4 inline-block mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
Télécharger le PDF
|
||||
<% end %>
|
||||
@@ -169,26 +169,17 @@
|
||||
</div>
|
||||
|
||||
<!-- Important Notice -->
|
||||
<div class="mt-6 bg-sky-50 border border-sky-200 rounded-xl p-6">
|
||||
<div class="mt-6 bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<svg class="w-5 h-5 text-sky-600 mr-3 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
<svg class="w-5 h-5 text-blue-600 mr-2 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-sky-800 font-semibold mb-2">Informations importantes</h3>
|
||||
<ul class="text-sky-700 text-sm space-y-2">
|
||||
<li class="flex items-start">
|
||||
<span class="w-1.5 h-1.5 bg-sky-600 rounded-full mt-2 mr-3 flex-shrink-0"></span>
|
||||
Présentez ce billet (ou son code QR) à l'entrée de l'événement
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span class="w-1.5 h-1.5 bg-sky-600 rounded-full mt-2 mr-3 flex-shrink-0"></span>
|
||||
Arrivez en avance pour éviter les files d'attente
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span class="w-1.5 h-1.5 bg-sky-600 rounded-full mt-2 mr-3 flex-shrink-0"></span>
|
||||
En cas de problème, contactez l'organisateur
|
||||
</li>
|
||||
<h3 class="text-blue-800 font-medium mb-1">Informations importantes</h3>
|
||||
<ul class="text-blue-700 text-sm space-y-1">
|
||||
<li>• Présentez ce billet (ou son code QR) à l'entrée de l'événement</li>
|
||||
<li>• Arrivez en avance pour éviter les files d'attente</li>
|
||||
<li>• En cas de problème, contactez l'organisateur</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<% content_for :title, "Ticket ##{ticket.id}" %>
|
||||
|
||||
<div style="font-family: Arial, sans-serif; max-width: 350px; margin: 20px auto; padding: 20px; border: 1px solid #ccc;">
|
||||
<div style="text-align: center;">
|
||||
<h1 style="color: #2D1B69;">ApéroNight</h1>
|
||||
</div>
|
||||
<h2><%= ticket.event.name %></h2>
|
||||
<p>Ticket Holder: <%= ticket.first_name %> <%= ticket.last_name %></p>
|
||||
<p>Ticket Type: <%= ticket.ticket_type.name %></p>
|
||||
<p>Price: €<%= ticket.price_euros %></p>
|
||||
<div style="text-align: center; margin-top: 20px;">
|
||||
<%= raw ticket.generate_qr_svg %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,118 +0,0 @@
|
||||
<% content_for :title, "Billet ##{@ticket.id} - #{@ticket.event.name}" %>
|
||||
|
||||
<div class="min-h-screen bg-slate-100 py-8">
|
||||
<div class="max-w-md mx-auto px-4">
|
||||
<!-- Ticket Card -->
|
||||
<div class="max-w-md bg-white rounded-xl shadow-2xl overflow-hidden mx-auto border border-slate-200">
|
||||
<!-- Header -->
|
||||
<div class="bg-gradient-to-r from-purple-700 to-violet-600 text-center py-6 px-6">
|
||||
<h1 class="text-2xl font-bold text-white mb-2">ApéroNight</h1>
|
||||
<div class="w-16 h-0.5 bg-purple-200 mx-auto rounded-full"></div>
|
||||
</div>
|
||||
|
||||
<!-- Event Name -->
|
||||
<div class="text-center py-4 px-6 bg-purple-50 border-b border-purple-100">
|
||||
<h2 class="text-xl font-bold text-slate-900 leading-tight"><%= @ticket.event.name %></h2>
|
||||
</div>
|
||||
|
||||
<!-- Ticket Information -->
|
||||
<div class="p-6 space-y-4">
|
||||
<!-- Ticket Holder -->
|
||||
<div class="flex justify-between items-center py-2 border-b border-slate-100">
|
||||
<span class="text-sm font-medium text-slate-600">Porteur du billet:</span>
|
||||
<span class="text-sm font-semibold text-slate-900 text-right"><%= @ticket.first_name %> <%= @ticket.last_name %></span>
|
||||
</div>
|
||||
|
||||
<!-- Ticket Type -->
|
||||
<div class="flex justify-between items-center py-2 border-b border-slate-100">
|
||||
<span class="text-sm font-medium text-slate-600">Type de billet:</span>
|
||||
<span class="text-sm font-semibold text-slate-900"><%= @ticket.ticket_type.name %></span>
|
||||
</div>
|
||||
|
||||
<!-- Price -->
|
||||
<div class="flex justify-between items-center py-2 border-b border-slate-100">
|
||||
<span class="text-sm font-medium text-slate-600">Prix:</span>
|
||||
<span class="text-sm font-semibold text-slate-900">
|
||||
<%= number_to_currency(@ticket.price_euros, unit: "€", separator: ",", delimiter: " ", format: "%n %u") %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Date & Time -->
|
||||
<div class="flex justify-between items-center py-2 border-b border-slate-100">
|
||||
<span class="text-sm font-medium text-slate-600">Date & Heure:</span>
|
||||
<div class="text-right">
|
||||
<div class="text-sm font-semibold text-slate-900"><%= @ticket.event.start_time.strftime("%d %B %Y") %></div>
|
||||
<div class="text-xs text-slate-600"><%= @ticket.event.start_time.strftime("%H:%M") %></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Venue -->
|
||||
<div class="py-2 border-b border-slate-100">
|
||||
<span class="text-sm font-medium text-slate-600 block mb-1">Lieu :</span>
|
||||
<div class="text-sm font-semibold text-slate-900"><%= @ticket.event.venue_name %></div>
|
||||
<% if @ticket.event.venue_address.present? %>
|
||||
<div class="text-xs text-slate-600 mt-1"><%= @ticket.event.venue_address %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- QR Code Section -->
|
||||
<div class="bg-slate-50 p-6 text-center border-t border-slate-200">
|
||||
<h3 class="text-sm font-semibold text-slate-900 mb-4">Code QR du billet</h3>
|
||||
<div class="inline-block bg-white p-6 rounded-xl shadow-sm border border-slate-200">
|
||||
<div class="w-52 h-52 flex items-center justify-center qr-code-container">
|
||||
<%= raw @ticket.generate_qr_svg %>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-slate-500 mt-3 font-mono tracking-wider">QR: <%= @ticket.qr_code[0..7] %>...</p>
|
||||
</div>
|
||||
|
||||
<!-- Footer Notice -->
|
||||
<div class="bg-slate-100 px-6 py-4 text-center border-t border-slate-200">
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs text-slate-600">Ce billet est valide pour une seule entrée.</p>
|
||||
<p class="text-xs text-slate-600">Présentez ce billet à l'entrée du lieu.</p>
|
||||
<div class="pt-2 border-t border-slate-200">
|
||||
<p class="text-xs text-slate-500">
|
||||
Généré le <%= Time.current.strftime('%d %B %Y à %H:%M') %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="p-4 bg-white border-t border-slate-200">
|
||||
<div class="flex space-x-2">
|
||||
<%= link_to ticket_path(@ticket),
|
||||
class: "flex-1 flex items-center justify-center bg-slate-100 hover:bg-slate-200 text-slate-700 py-2.5 px-3 rounded-lg text-sm font-medium transition-colors duration-200" do %>
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||||
</svg>
|
||||
Vue détaillée
|
||||
<% end %>
|
||||
|
||||
<% if @ticket.status == 'active' %>
|
||||
<%= link_to download_ticket_path(@ticket.id),
|
||||
class: "flex-1 flex items-center justify-center bg-purple-600 hover:bg-purple-700 text-white py-2.5 px-3 rounded-lg text-sm font-medium transition-colors duration-200 shadow-sm hover:shadow-md" do %>
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
PDF
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="text-center mt-6">
|
||||
<%= link_to dashboard_path, class: "inline-flex items-center text-purple-600 hover:text-purple-800 text-sm font-medium transition-colors duration-200" do %>
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7 16l-4-4m0 0l4-4m-4 4h18"/>
|
||||
</svg>
|
||||
Retour au tableau de bord
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,6 +25,6 @@ module Aperonight
|
||||
# config.eager_load_paths << Rails.root.join("extras")
|
||||
|
||||
config.i18n.load_path += Dir[Rails.root.join("my", "locales", "*.{rb,yml}")]
|
||||
# config.i18n.default_locale = :fr
|
||||
config.i18n.default_locale = :fr
|
||||
end
|
||||
end
|
||||
|
||||
126
config/routes.rb
126
config/routes.rb
@@ -1,72 +1,79 @@
|
||||
Rails.application.routes.draw do
|
||||
# Health check
|
||||
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
|
||||
|
||||
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
|
||||
# Can be used by load balancers and uptime monitors to verify that the app is live.
|
||||
get "up" => "rails/health#show", as: :rails_health_check
|
||||
|
||||
# Root
|
||||
|
||||
# Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb)
|
||||
# get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
|
||||
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
|
||||
|
||||
# Defines the root path route ("/")
|
||||
root "pages#home"
|
||||
|
||||
# === Authentication ===
|
||||
# === Devise ===
|
||||
# Routes for devise authentication Gem
|
||||
# Bind devise to user
|
||||
devise_for :users, path: "auth", path_names: {
|
||||
sign_in: "sign_in",
|
||||
sign_out: "sign_out",
|
||||
password: "reset-password",
|
||||
confirmation: "verification",
|
||||
unlock: "unblock",
|
||||
sign_up: "signup"
|
||||
}, controllers: {
|
||||
sessions: "auth/sessions",
|
||||
registrations: "auth/registrations",
|
||||
passwords: "auth/passwords",
|
||||
confirmation: "auth/confirmations"
|
||||
sign_in: "sign_in", # Route for user login
|
||||
sign_out: "sign_out", # Route for user logout
|
||||
password: "reset-password", # Route for changing password
|
||||
confirmation: "verification", # Route for account confirmation
|
||||
unlock: "unblock", # Route for account unlock
|
||||
# registration: "account", # Route for user account
|
||||
sign_up: "signup" # Route for user registration
|
||||
},
|
||||
controllers: {
|
||||
sessions: "auth/sessions", # Custom controller for sessions
|
||||
registrations: "auth/registrations", # Custom controller for registrations
|
||||
passwords: "auth/passwords", # Custom controller for passwords
|
||||
confirmation: "auth/confirmations" # Custom controller for confirmations
|
||||
}
|
||||
|
||||
# === Main App - SEO Friendly URLs ===
|
||||
get "dashboard", to: "pages#dashboard"
|
||||
|
||||
# Events with date-based SEO structure
|
||||
get "events", to: "events#index", as: "events"
|
||||
get "events/:year/:month/:slug", to: "events#show", as: "event",
|
||||
constraints: { year: /\d{4}/, month: /\d{2}/ }
|
||||
|
||||
# Booking workflow with semantic URLs
|
||||
get "events/:year/:month/:slug/book-tickets", to: "orders#new", as: "book_event_tickets",
|
||||
constraints: { year: /\d{4}/, month: /\d{2}/ }
|
||||
post "events/:year/:month/:slug/book-tickets", to: "orders#create", as: "create_booking",
|
||||
constraints: { year: /\d{4}/, month: /\d{2}/ }
|
||||
|
||||
# Checkout process with semantic URLs
|
||||
get "events/:year/:month/:slug/checkout", to: "orders#checkout", as: "event_checkout",
|
||||
constraints: { year: /\d{4}/, month: /\d{2}/ }
|
||||
get "booking/:order_id/summary", to: "orders#show", as: "booking_summary"
|
||||
post "booking/:order_id/retry-payment", to: "orders#retry_payment", as: "retry_booking_payment"
|
||||
post "booking/:order_id/increment-attempts", to: "orders#increment_payment_attempt", as: "increment_booking_attempts"
|
||||
|
||||
# Individual tickets with descriptive URLs
|
||||
get "tickets/:event_slug-:ticket_id", to: "tickets#show", as: "ticket"
|
||||
get "tickets/:event_slug-:ticket_id/view", to: "tickets#view", as: "view_ticket"
|
||||
get "tickets/:event_slug-:ticket_id/download", to: "tickets#download", as: "download_ticket"
|
||||
post "tickets/:event_slug-:ticket_id/retry-payment", to: "tickets#retry_payment", as: "retry_ticket_payment"
|
||||
# === Pages ===
|
||||
get "dashboard", to: "pages#dashboard", as: "dashboard"
|
||||
|
||||
# Payment callbacks with descriptive paths
|
||||
namespace :booking do
|
||||
get "payment-success", to: "payments#success", as: "payment_success"
|
||||
get "payment-cancelled", to: "payments#cancel", as: "payment_cancelled"
|
||||
# === Events ===
|
||||
get "events", to: "events#index", as: "events"
|
||||
get "events/:slug.:id", to: "events#show", as: "event"
|
||||
|
||||
# === Orders (scoped to events) ===
|
||||
get "events/:slug.:id/orders/new", to: "orders#new", as: "event_order_new"
|
||||
post "events/:slug.:id/orders", to: "orders#create", as: "event_order_create"
|
||||
|
||||
resources :orders, only: [ :show ] do
|
||||
member do
|
||||
get :checkout
|
||||
post :retry_payment
|
||||
post :increment_payment_attempt
|
||||
end
|
||||
end
|
||||
|
||||
# Legacy redirects for backward compatibility
|
||||
get "events/:slug", to: "legacy_redirects#event_redirect"
|
||||
|
||||
# Legacy payment routes
|
||||
get "payments/success", to: redirect("/booking/payment-success")
|
||||
get "payments/cancel", to: redirect("/booking/payment-cancelled")
|
||||
get "orders/payments/success", to: "orders#payment_success", as: "order_payment_success"
|
||||
get "orders/payments/cancel", to: "orders#payment_cancel", as: "order_payment_cancel"
|
||||
|
||||
# === Promoter Dashboard ===
|
||||
# Legacy ticket routes - redirect to order system
|
||||
get "events/:slug.:id/tickets/checkout", to: "tickets#checkout", as: "ticket_checkout"
|
||||
post "events/:slug.:id/tickets/retry", to: "tickets#retry_payment", as: "ticket_retry_payment"
|
||||
get "payments/success", to: "tickets#payment_success", as: "payment_success"
|
||||
get "payments/cancel", to: "tickets#payment_cancel", as: "payment_cancel"
|
||||
|
||||
# === Tickets ===
|
||||
get "tickets/:ticket_id/download", to: "events#download_ticket", as: "download_ticket"
|
||||
|
||||
# === Promoter Routes ===
|
||||
namespace :promoter do
|
||||
resources :events, path: "my-events" do
|
||||
resources :events do
|
||||
member do
|
||||
patch :publish, :unpublish, :cancel, :mark_sold_out
|
||||
patch :publish
|
||||
patch :unpublish
|
||||
patch :cancel
|
||||
patch :mark_sold_out
|
||||
end
|
||||
resources :ticket_types, path: "ticket-options" do
|
||||
|
||||
# Nested ticket types routes
|
||||
resources :ticket_types do
|
||||
member do
|
||||
post :duplicate
|
||||
end
|
||||
@@ -74,14 +81,17 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
# === API ===
|
||||
|
||||
# API routes versioning
|
||||
namespace :api do
|
||||
namespace :v1 do
|
||||
resources :events, except: [:new, :edit] do
|
||||
# RESTful routes for event management
|
||||
resources :events, only: [ :index, :show, :create, :update, :destroy ] do
|
||||
member do
|
||||
post :store_cart
|
||||
end
|
||||
end
|
||||
# resources :ticket_types, only: [ :index, :show, :create, :update, :destroy ]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
1092
docs/checkin-system-implementation.md
Normal file
1092
docs/checkin-system-implementation.md
Normal file
File diff suppressed because it is too large
Load Diff
609
package-lock.json
generated
609
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -11,7 +11,6 @@
|
||||
"@hotwired/turbo-rails": "^8.0.13",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"lucide": "^0.542.0",
|
||||
"puppeteer": "^24.19.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
@@ -32,21 +31,5 @@
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "ecosystem.config.js",
|
||||
"directories": {
|
||||
"doc": "docs",
|
||||
"lib": "lib",
|
||||
"test": "test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "ssh://git@gitea.cyanet.fr:2222/kbe/aperonight.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs"
|
||||
}
|
||||
}
|
||||
|
||||
283
test/services/ticket_pdf_generator_test.rb
Normal file
283
test/services/ticket_pdf_generator_test.rb
Normal file
@@ -0,0 +1,283 @@
|
||||
require "test_helper"
|
||||
|
||||
class TicketPdfGeneratorTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
# Stub QR code generation to avoid dependency issues
|
||||
mock_qrcode = mock("qrcode")
|
||||
mock_qrcode.stubs(:modules).returns([])
|
||||
RQRCode::QRCode.stubs(:new).returns(mock_qrcode)
|
||||
|
||||
@user = User.create!(
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
password_confirmation: "password123"
|
||||
)
|
||||
|
||||
@event = Event.create!(
|
||||
name: "Test Event",
|
||||
slug: "test-event",
|
||||
description: "A valid description for the test event that is long enough",
|
||||
latitude: 48.8566,
|
||||
longitude: 2.3522,
|
||||
venue_name: "Test Venue",
|
||||
venue_address: "123 Test Street",
|
||||
user: @user,
|
||||
start_time: 1.week.from_now,
|
||||
end_time: 1.week.from_now + 3.hours,
|
||||
state: :published
|
||||
)
|
||||
|
||||
@ticket_type = TicketType.create!(
|
||||
name: "General Admission",
|
||||
description: "General admission tickets with full access to the event",
|
||||
price_cents: 2500,
|
||||
quantity: 100,
|
||||
sale_start_at: Time.current,
|
||||
sale_end_at: @event.start_time - 1.hour,
|
||||
requires_id: false,
|
||||
event: @event
|
||||
)
|
||||
|
||||
@order = Order.create!(
|
||||
user: @user,
|
||||
event: @event,
|
||||
status: "paid",
|
||||
total_amount_cents: 2500
|
||||
)
|
||||
|
||||
@ticket = Ticket.create!(
|
||||
order: @order,
|
||||
ticket_type: @ticket_type,
|
||||
status: "active",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
qr_code: "test-qr-code-123"
|
||||
)
|
||||
end
|
||||
|
||||
# === Initialization Tests ===
|
||||
|
||||
test "should initialize with ticket" do
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
assert_equal @ticket, generator.ticket
|
||||
end
|
||||
|
||||
# === PDF Generation Tests ===
|
||||
|
||||
test "should generate PDF for valid ticket" do
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
pdf_string = generator.generate
|
||||
|
||||
assert_not_nil pdf_string
|
||||
assert_kind_of String, pdf_string
|
||||
assert pdf_string.length > 0
|
||||
|
||||
# Check if it starts with PDF header
|
||||
assert pdf_string.start_with?("%PDF")
|
||||
end
|
||||
|
||||
test "should include event name in PDF" do
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
|
||||
# Test that PDF generates successfully
|
||||
pdf_string = generator.generate
|
||||
assert_not_nil pdf_string
|
||||
assert pdf_string.start_with?("%PDF")
|
||||
assert pdf_string.length > 1000, "PDF should be substantial in size"
|
||||
end
|
||||
|
||||
test "should include ticket type information in PDF" do
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
pdf_string = generator.generate
|
||||
|
||||
# Basic check that PDF was generated - actual content validation
|
||||
# would require parsing the PDF which is complex
|
||||
assert_not_nil pdf_string
|
||||
assert pdf_string.length > 0
|
||||
end
|
||||
|
||||
test "should include price information in PDF" do
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
pdf_string = generator.generate
|
||||
|
||||
assert_not_nil pdf_string
|
||||
assert pdf_string.length > 0
|
||||
end
|
||||
|
||||
test "should include venue information in PDF" do
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
pdf_string = generator.generate
|
||||
|
||||
assert_not_nil pdf_string
|
||||
assert pdf_string.length > 0
|
||||
end
|
||||
|
||||
test "should include QR code in PDF" do
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
|
||||
# Just test that PDF generates successfully
|
||||
pdf_string = generator.generate
|
||||
assert_not_nil pdf_string
|
||||
assert pdf_string.length > 0
|
||||
assert pdf_string.start_with?("%PDF")
|
||||
end
|
||||
|
||||
# === Error Handling Tests ===
|
||||
|
||||
test "should raise error when QR code is blank" do
|
||||
# Create ticket with blank QR code (skip validations)
|
||||
ticket_with_blank_qr = Ticket.new(
|
||||
order: @order,
|
||||
ticket_type: @ticket_type,
|
||||
status: "active",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
price_cents: 2500,
|
||||
qr_code: ""
|
||||
)
|
||||
ticket_with_blank_qr.save(validate: false)
|
||||
|
||||
generator = TicketPdfGenerator.new(ticket_with_blank_qr)
|
||||
|
||||
error = assert_raises(RuntimeError) do
|
||||
generator.generate
|
||||
end
|
||||
|
||||
assert_equal "Ticket QR code is missing", error.message
|
||||
end
|
||||
|
||||
test "should raise error when QR code is nil" do
|
||||
# Create ticket with nil QR code (skip validations)
|
||||
ticket_with_nil_qr = Ticket.new(
|
||||
order: @order,
|
||||
ticket_type: @ticket_type,
|
||||
status: "active",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
price_cents: 2500,
|
||||
qr_code: nil
|
||||
)
|
||||
ticket_with_nil_qr.save(validate: false)
|
||||
|
||||
generator = TicketPdfGenerator.new(ticket_with_nil_qr)
|
||||
|
||||
error = assert_raises(RuntimeError) do
|
||||
generator.generate
|
||||
end
|
||||
|
||||
assert_equal "Ticket QR code is missing", error.message
|
||||
end
|
||||
|
||||
test "should handle missing event gracefully in QR data" do
|
||||
# Create ticket with minimal data but valid QR code
|
||||
orphaned_ticket = Ticket.new(
|
||||
order: @order,
|
||||
ticket_type: @ticket_type,
|
||||
status: "active",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
price_cents: 2500,
|
||||
qr_code: "test-qr-code-orphaned"
|
||||
)
|
||||
orphaned_ticket.save(validate: false)
|
||||
|
||||
generator = TicketPdfGenerator.new(orphaned_ticket)
|
||||
|
||||
# Should still generate PDF
|
||||
pdf_string = generator.generate
|
||||
assert_not_nil pdf_string
|
||||
assert pdf_string.length > 0
|
||||
assert pdf_string.start_with?("%PDF")
|
||||
end
|
||||
|
||||
# === QR Code Data Tests ===
|
||||
|
||||
test "should generate correct QR code data" do
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
|
||||
# Just test that PDF generates successfully with QR data
|
||||
pdf_string = generator.generate
|
||||
assert_not_nil pdf_string
|
||||
assert pdf_string.start_with?("%PDF")
|
||||
end
|
||||
|
||||
test "should compact QR code data removing nils" do
|
||||
# Test with a ticket that has unique QR code
|
||||
ticket_with_minimal_data = Ticket.new(
|
||||
order: @order,
|
||||
ticket_type: @ticket_type,
|
||||
status: "active",
|
||||
first_name: "Jane",
|
||||
last_name: "Smith",
|
||||
price_cents: 2500,
|
||||
qr_code: "test-qr-minimal-data"
|
||||
)
|
||||
ticket_with_minimal_data.save(validate: false)
|
||||
|
||||
generator = TicketPdfGenerator.new(ticket_with_minimal_data)
|
||||
|
||||
# Should generate PDF successfully
|
||||
pdf_string = generator.generate
|
||||
assert_not_nil pdf_string
|
||||
assert pdf_string.start_with?("%PDF")
|
||||
end
|
||||
|
||||
# === Price Display Tests ===
|
||||
|
||||
test "should format price correctly in euros" do
|
||||
# Test different price formats
|
||||
@ticket.update!(price_cents: 1050) # €10.50
|
||||
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
pdf_string = generator.generate
|
||||
|
||||
assert_not_nil pdf_string
|
||||
assert_equal 10.5, @ticket.price_euros
|
||||
end
|
||||
|
||||
test "should handle low price" do
|
||||
@ticket_type.update!(price_cents: 1)
|
||||
@ticket.update!(price_cents: 1)
|
||||
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
pdf_string = generator.generate
|
||||
|
||||
assert_not_nil pdf_string
|
||||
assert_equal 0.01, @ticket.price_euros
|
||||
end
|
||||
|
||||
# === Date Formatting Tests ===
|
||||
|
||||
test "should format event date correctly" do
|
||||
specific_time = Time.parse("2024-12-25 19:30:00")
|
||||
@event.update!(start_time: specific_time)
|
||||
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
pdf_string = generator.generate
|
||||
|
||||
# Just verify PDF generates - date formatting is handled by strftime
|
||||
assert_not_nil pdf_string
|
||||
assert pdf_string.length > 0
|
||||
end
|
||||
|
||||
# === Integration Tests ===
|
||||
|
||||
test "should generate valid PDF with all required elements" do
|
||||
generator = TicketPdfGenerator.new(@ticket)
|
||||
pdf_string = generator.generate
|
||||
|
||||
# Basic PDF structure validation
|
||||
assert_not_nil pdf_string
|
||||
assert pdf_string.start_with?("%PDF")
|
||||
assert pdf_string.end_with?("%%EOF\n")
|
||||
assert pdf_string.length > 1000, "PDF should be substantial in size"
|
||||
end
|
||||
|
||||
test "should be callable from ticket model" do
|
||||
# Test the integration with the Ticket model's to_pdf method
|
||||
pdf_string = @ticket.to_pdf
|
||||
|
||||
assert_not_nil pdf_string
|
||||
assert pdf_string.start_with?("%PDF")
|
||||
end
|
||||
end
|
||||
606
yarn.lock
606
yarn.lock
@@ -7,20 +7,6 @@
|
||||
resolved "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz"
|
||||
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
|
||||
|
||||
"@babel/code-frame@^7.0.0":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz"
|
||||
integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.27.1"
|
||||
js-tokens "^4.0.0"
|
||||
picocolors "^1.1.1"
|
||||
|
||||
"@babel/helper-validator-identifier@^7.27.1":
|
||||
version "7.27.1"
|
||||
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz"
|
||||
integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
|
||||
|
||||
"@csstools/selector-resolve-nested@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz"
|
||||
@@ -31,11 +17,158 @@
|
||||
resolved "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz"
|
||||
integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==
|
||||
|
||||
"@emnapi/core@^1.4.3", "@emnapi/core@^1.4.5":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.5.0.tgz#85cd84537ec989cebb2343606a1ee663ce4edaf0"
|
||||
integrity sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==
|
||||
dependencies:
|
||||
"@emnapi/wasi-threads" "1.1.0"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/runtime@^1.4.3", "@emnapi/runtime@^1.4.5":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.5.0.tgz#9aebfcb9b17195dce3ab53c86787a6b7d058db73"
|
||||
integrity sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/wasi-threads@1.1.0", "@emnapi/wasi-threads@^1.0.4":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf"
|
||||
integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@esbuild/aix-ppc64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz#bef96351f16520055c947aba28802eede3c9e9a9"
|
||||
integrity sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==
|
||||
|
||||
"@esbuild/android-arm64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz#d2e70be7d51a529425422091e0dcb90374c1546c"
|
||||
integrity sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==
|
||||
|
||||
"@esbuild/android-arm@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.9.tgz#d2a753fe2a4c73b79437d0ba1480e2d760097419"
|
||||
integrity sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==
|
||||
|
||||
"@esbuild/android-x64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.9.tgz#5278836e3c7ae75761626962f902a0d55352e683"
|
||||
integrity sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==
|
||||
|
||||
"@esbuild/darwin-arm64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz#f1513eaf9ec8fa15dcaf4c341b0f005d3e8b47ae"
|
||||
integrity sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==
|
||||
|
||||
"@esbuild/darwin-x64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz#e27dbc3b507b3a1cea3b9280a04b8b6b725f82be"
|
||||
integrity sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==
|
||||
|
||||
"@esbuild/freebsd-arm64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz#364e3e5b7a1fd45d92be08c6cc5d890ca75908ca"
|
||||
integrity sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==
|
||||
|
||||
"@esbuild/freebsd-x64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz#7c869b45faeb3df668e19ace07335a0711ec56ab"
|
||||
integrity sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==
|
||||
|
||||
"@esbuild/linux-arm64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz#48d42861758c940b61abea43ba9a29b186d6cb8b"
|
||||
integrity sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==
|
||||
|
||||
"@esbuild/linux-arm@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz#6ce4b9cabf148274101701d112b89dc67cc52f37"
|
||||
integrity sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==
|
||||
|
||||
"@esbuild/linux-ia32@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz#207e54899b79cac9c26c323fc1caa32e3143f1c4"
|
||||
integrity sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==
|
||||
|
||||
"@esbuild/linux-loong64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz#0ba48a127159a8f6abb5827f21198b999ffd1fc0"
|
||||
integrity sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==
|
||||
|
||||
"@esbuild/linux-mips64el@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz#a4d4cc693d185f66a6afde94f772b38ce5d64eb5"
|
||||
integrity sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==
|
||||
|
||||
"@esbuild/linux-ppc64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz#0f5805c1c6d6435a1dafdc043cb07a19050357db"
|
||||
integrity sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==
|
||||
|
||||
"@esbuild/linux-riscv64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz#6776edece0f8fca79f3386398b5183ff2a827547"
|
||||
integrity sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==
|
||||
|
||||
"@esbuild/linux-s390x@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz#3f6f29ef036938447c2218d309dc875225861830"
|
||||
integrity sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==
|
||||
|
||||
"@esbuild/linux-x64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz"
|
||||
integrity sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==
|
||||
|
||||
"@esbuild/netbsd-arm64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz#06f99d7eebe035fbbe43de01c9d7e98d2a0aa548"
|
||||
integrity sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==
|
||||
|
||||
"@esbuild/netbsd-x64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz#db99858e6bed6e73911f92a88e4edd3a8c429a52"
|
||||
integrity sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==
|
||||
|
||||
"@esbuild/openbsd-arm64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz#afb886c867e36f9d86bb21e878e1185f5d5a0935"
|
||||
integrity sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==
|
||||
|
||||
"@esbuild/openbsd-x64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz#30855c9f8381fac6a0ef5b5f31ac6e7108a66ecf"
|
||||
integrity sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==
|
||||
|
||||
"@esbuild/openharmony-arm64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz#2f2144af31e67adc2a8e3705c20c2bd97bd88314"
|
||||
integrity sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==
|
||||
|
||||
"@esbuild/sunos-x64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz#69b99a9b5bd226c9eb9c6a73f990fddd497d732e"
|
||||
integrity sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==
|
||||
|
||||
"@esbuild/win32-arm64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz#d789330a712af916c88325f4ffe465f885719c6b"
|
||||
integrity sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==
|
||||
|
||||
"@esbuild/win32-ia32@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz#52fc735406bd49688253e74e4e837ac2ba0789e3"
|
||||
integrity sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==
|
||||
|
||||
"@esbuild/win32-x64@0.25.9":
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz#585624dc829cfb6e7c0aa6c3ca7d7e6daa87e34f"
|
||||
integrity sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==
|
||||
|
||||
"@hotwired/stimulus@^3.2.2":
|
||||
version "3.2.2"
|
||||
resolved "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.2.2.tgz"
|
||||
@@ -95,6 +228,15 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@napi-rs/wasm-runtime@^0.2.12":
|
||||
version "0.2.12"
|
||||
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2"
|
||||
integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==
|
||||
dependencies:
|
||||
"@emnapi/core" "^1.4.3"
|
||||
"@emnapi/runtime" "^1.4.3"
|
||||
"@tybys/wasm-util" "^0.10.0"
|
||||
|
||||
"@pm2/agent@~2.1.1":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/@pm2/agent/-/agent-2.1.1.tgz"
|
||||
@@ -145,19 +287,6 @@
|
||||
dependencies:
|
||||
debug "^4.3.1"
|
||||
|
||||
"@puppeteer/browsers@2.10.8":
|
||||
version "2.10.8"
|
||||
resolved "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.8.tgz"
|
||||
integrity sha512-f02QYEnBDE0p8cteNoPYHHjbDuwyfbe4cCIVlNi8/MRicIxFW4w4CfgU0LNgWEID6s06P+hRJ1qjpBLMhPRCiQ==
|
||||
dependencies:
|
||||
debug "^4.4.1"
|
||||
extract-zip "^2.0.1"
|
||||
progress "^2.0.3"
|
||||
proxy-agent "^6.5.0"
|
||||
semver "^7.7.2"
|
||||
tar-fs "^3.1.0"
|
||||
yargs "^17.7.2"
|
||||
|
||||
"@radix-ui/react-compose-refs@1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz"
|
||||
@@ -292,19 +421,12 @@
|
||||
resolved "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz"
|
||||
integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==
|
||||
|
||||
"@types/node@*":
|
||||
version "24.3.1"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz"
|
||||
integrity sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==
|
||||
"@tybys/wasm-util@^0.10.0":
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.0.tgz#2fd3cd754b94b378734ce17058d0507c45c88369"
|
||||
integrity sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==
|
||||
dependencies:
|
||||
undici-types "~7.10.0"
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.10.3"
|
||||
resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz"
|
||||
integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
tslib "^2.4.0"
|
||||
|
||||
agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.2:
|
||||
version "7.1.4"
|
||||
@@ -318,7 +440,7 @@ amp-message@~0.1.1:
|
||||
dependencies:
|
||||
amp "0.3.1"
|
||||
|
||||
amp@~0.3.1, amp@0.3.1:
|
||||
amp@0.3.1, amp@~0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz"
|
||||
integrity sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==
|
||||
@@ -365,7 +487,7 @@ ast-types@^0.13.4:
|
||||
dependencies:
|
||||
tslib "^2.0.1"
|
||||
|
||||
async@^2.6.3:
|
||||
async@^2.6.3, async@~2.6.1:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.npmjs.org/async/-/async-2.6.4.tgz"
|
||||
integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
|
||||
@@ -377,13 +499,6 @@ async@^3.2.0, async@~3.2.0, async@~3.2.6:
|
||||
resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz"
|
||||
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
|
||||
|
||||
async@~2.6.1:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.npmjs.org/async/-/async-2.6.4.tgz"
|
||||
integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
|
||||
dependencies:
|
||||
lodash "^4.17.14"
|
||||
|
||||
autoprefixer@^10.4.21:
|
||||
version "10.4.21"
|
||||
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz"
|
||||
@@ -396,44 +511,6 @@ autoprefixer@^10.4.21:
|
||||
picocolors "^1.1.1"
|
||||
postcss-value-parser "^4.2.0"
|
||||
|
||||
b4a@^1.6.4:
|
||||
version "1.6.7"
|
||||
resolved "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz"
|
||||
integrity sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==
|
||||
|
||||
bare-events@*, bare-events@^2.2.0, bare-events@^2.5.4:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.npmjs.org/bare-events/-/bare-events-2.6.1.tgz"
|
||||
integrity sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==
|
||||
|
||||
bare-fs@^4.0.1:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/bare-fs/-/bare-fs-4.2.3.tgz"
|
||||
integrity sha512-1aGs5pRVLToMQ79elP+7cc0u0s/wXAzfBv/7hDloT7WFggLqECCas5qqPky7WHCFdsBH5WDq6sD4fAoz5sJbtA==
|
||||
dependencies:
|
||||
bare-events "^2.5.4"
|
||||
bare-path "^3.0.0"
|
||||
bare-stream "^2.6.4"
|
||||
|
||||
bare-os@^3.0.1:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz"
|
||||
integrity sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==
|
||||
|
||||
bare-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz"
|
||||
integrity sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==
|
||||
dependencies:
|
||||
bare-os "^3.0.1"
|
||||
|
||||
bare-stream@^2.6.4:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz"
|
||||
integrity sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==
|
||||
dependencies:
|
||||
streamx "^2.21.0"
|
||||
|
||||
basic-ftp@^5.0.2:
|
||||
version "5.0.5"
|
||||
resolved "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz"
|
||||
@@ -466,7 +543,7 @@ braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
browserslist@^4.0.0, browserslist@^4.24.4, browserslist@^4.25.1, "browserslist@>= 4.21.0":
|
||||
browserslist@^4.0.0, browserslist@^4.24.4, browserslist@^4.25.1:
|
||||
version "4.25.2"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz"
|
||||
integrity sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==
|
||||
@@ -476,21 +553,11 @@ browserslist@^4.0.0, browserslist@^4.24.4, browserslist@^4.25.1, "browserslist@>
|
||||
node-releases "^2.0.19"
|
||||
update-browserslist-db "^1.1.3"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz"
|
||||
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
|
||||
callsites@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
caniuse-api@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz"
|
||||
@@ -506,7 +573,7 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001733:
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz"
|
||||
integrity sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==
|
||||
|
||||
chalk@~3.0.0, chalk@3.0.0:
|
||||
chalk@3.0.0, chalk@~3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz"
|
||||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||
@@ -539,14 +606,6 @@ chownr@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz"
|
||||
integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==
|
||||
|
||||
chromium-bidi@8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-8.0.0.tgz"
|
||||
integrity sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==
|
||||
dependencies:
|
||||
mitt "^3.0.1"
|
||||
zod "^3.24.1"
|
||||
|
||||
class-variance-authority@^0.7.1:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz"
|
||||
@@ -592,25 +651,15 @@ colord@^2.9.3:
|
||||
resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz"
|
||||
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
|
||||
|
||||
commander@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz"
|
||||
integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==
|
||||
|
||||
commander@2.15.1:
|
||||
version "2.15.1"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz"
|
||||
integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
|
||||
|
||||
cosmiconfig@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz"
|
||||
integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==
|
||||
dependencies:
|
||||
env-paths "^2.2.1"
|
||||
import-fresh "^3.3.0"
|
||||
js-yaml "^4.1.0"
|
||||
parse-json "^5.2.0"
|
||||
commander@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz"
|
||||
integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==
|
||||
|
||||
croner@~4.1.92:
|
||||
version "4.1.97"
|
||||
@@ -735,6 +784,13 @@ dayjs@~1.8.24:
|
||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz"
|
||||
integrity sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==
|
||||
|
||||
debug@4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.7:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz"
|
||||
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
debug@^3.2.6:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
|
||||
@@ -742,13 +798,6 @@ debug@^3.2.6:
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.7, debug@^4.4.1, debug@4:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz"
|
||||
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
debug@~4.3.1:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz"
|
||||
@@ -775,11 +824,6 @@ detect-libc@^2.0.3, detect-libc@^2.0.4:
|
||||
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz"
|
||||
integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==
|
||||
|
||||
devtools-protocol@*, devtools-protocol@0.0.1495869:
|
||||
version "0.0.1495869"
|
||||
resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1495869.tgz"
|
||||
integrity sha512-i+bkd9UYFis40RcnkW7XrOprCujXRAHg62IVh/Ah3G8MmNXpCGt1m0dTFhSdx/AVs8XEMbdOGRwdkR1Bcta8AA==
|
||||
|
||||
dom-serializer@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz"
|
||||
@@ -820,13 +864,6 @@ emoji-regex@^8.0.0:
|
||||
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
end-of-stream@^1.1.0:
|
||||
version "1.4.5"
|
||||
resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz"
|
||||
integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
enhanced-resolve@^5.18.3:
|
||||
version "5.18.3"
|
||||
resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz"
|
||||
@@ -847,18 +884,6 @@ entities@^4.2.0:
|
||||
resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz"
|
||||
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
|
||||
|
||||
env-paths@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz"
|
||||
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
|
||||
|
||||
error-ex@^1.3.1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz"
|
||||
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
esbuild@^0.25.4:
|
||||
version "0.25.9"
|
||||
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz"
|
||||
@@ -927,26 +952,15 @@ esutils@^2.0.2:
|
||||
resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
|
||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||
|
||||
eventemitter2@^6.3.1:
|
||||
version "6.4.9"
|
||||
resolved "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz"
|
||||
integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==
|
||||
|
||||
eventemitter2@~5.0.1, eventemitter2@5.0.1:
|
||||
eventemitter2@5.0.1, eventemitter2@~5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz"
|
||||
integrity sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==
|
||||
|
||||
extract-zip@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz"
|
||||
integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
get-stream "^5.1.0"
|
||||
yauzl "^2.10.0"
|
||||
optionalDependencies:
|
||||
"@types/yauzl" "^2.9.1"
|
||||
eventemitter2@^6.3.1:
|
||||
version "6.4.9"
|
||||
resolved "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz"
|
||||
integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==
|
||||
|
||||
extrareqp2@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -955,28 +969,16 @@ extrareqp2@^1.0.0:
|
||||
dependencies:
|
||||
follow-redirects "^1.14.0"
|
||||
|
||||
fast-fifo@^1.2.0, fast-fifo@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz"
|
||||
integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==
|
||||
|
||||
fast-json-patch@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz"
|
||||
integrity sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==
|
||||
|
||||
fclone@~1.0.11, fclone@1.0.11:
|
||||
fclone@1.0.11, fclone@~1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz"
|
||||
integrity sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz"
|
||||
integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
fdir@^6.4.4:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz"
|
||||
@@ -1008,6 +1010,11 @@ fs-extra@^11.0.0:
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^2.0.0"
|
||||
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
function-bind@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
||||
@@ -1018,13 +1025,6 @@ get-caller-file@^2.0.5:
|
||||
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-stream@^5.1.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz"
|
||||
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-uri@^6.0.1:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz"
|
||||
@@ -1091,14 +1091,6 @@ iconv-lite@^0.4.4:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
import-fresh@^3.3.0:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz"
|
||||
integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
|
||||
dependencies:
|
||||
parent-module "^1.0.0"
|
||||
resolve-from "^4.0.0"
|
||||
|
||||
ini@^1.3.5:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
|
||||
@@ -1109,11 +1101,6 @@ ip-address@^10.0.1:
|
||||
resolved "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz"
|
||||
integrity sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==
|
||||
|
||||
is-arrayish@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
|
||||
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
|
||||
|
||||
is-binary-path@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz"
|
||||
@@ -1150,7 +1137,7 @@ is-number@^7.0.0:
|
||||
resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
|
||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||
|
||||
jiti@^2.5.1, jiti@>=1.21.0:
|
||||
jiti@^2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz"
|
||||
integrity sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==
|
||||
@@ -1165,23 +1152,18 @@ js-git@^0.7.8:
|
||||
git-sha1 "^0.1.2"
|
||||
pako "^0.2.5"
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
"js-tokens@^3.0.0 || ^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
|
||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||
|
||||
js-yaml@^4.1.0, js-yaml@~4.1.0:
|
||||
js-yaml@~4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz"
|
||||
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
json-parse-even-better-errors@^2.3.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz"
|
||||
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
|
||||
|
||||
json-stringify-safe@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
|
||||
@@ -1269,11 +1251,6 @@ lilconfig@^3.1.1, lilconfig@^3.1.3:
|
||||
resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz"
|
||||
integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==
|
||||
|
||||
lines-and-columns@^1.1.6:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz"
|
||||
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
|
||||
|
||||
lodash.memoize@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz"
|
||||
@@ -1342,21 +1319,16 @@ minizlib@^3.0.1:
|
||||
dependencies:
|
||||
minipass "^7.1.2"
|
||||
|
||||
mitt@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz"
|
||||
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
|
||||
mkdirp@1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
mkdirp@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz"
|
||||
integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
|
||||
|
||||
mkdirp@1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
module-details-from-path@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz"
|
||||
@@ -1413,14 +1385,7 @@ nth-check@^2.0.1:
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
|
||||
once@^1.3.1, once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
|
||||
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
pac-proxy-agent@^7.0.1, pac-proxy-agent@^7.1.0:
|
||||
pac-proxy-agent@^7.0.1:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz"
|
||||
integrity sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==
|
||||
@@ -1447,33 +1412,11 @@ pako@^0.2.5:
|
||||
resolved "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz"
|
||||
integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
|
||||
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
parse-json@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz"
|
||||
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
error-ex "^1.3.1"
|
||||
json-parse-even-better-errors "^2.3.0"
|
||||
lines-and-columns "^1.1.6"
|
||||
|
||||
path-parse@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
picocolors@^1.0.0, picocolors@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
|
||||
@@ -1484,7 +1427,7 @@ picomatch@^2.0.4, picomatch@^2.2.1:
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
"picomatch@^3 || ^4", picomatch@^4.0.2:
|
||||
picomatch@^4.0.2:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz"
|
||||
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
|
||||
@@ -1863,7 +1806,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
|
||||
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@^8.0.0, postcss@^8.0.9, postcss@^8.1.0, postcss@^8.1.4, postcss@^8.2.14, postcss@^8.4, postcss@^8.4.32, postcss@^8.4.38, postcss@^8.4.41, postcss@^8.5.3, postcss@>=8.0.9:
|
||||
postcss@^8.4.41, postcss@^8.5.3:
|
||||
version "8.5.6"
|
||||
resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz"
|
||||
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
|
||||
@@ -1877,11 +1820,6 @@ pretty-hrtime@^1.0.3:
|
||||
resolved "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz"
|
||||
integrity sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==
|
||||
|
||||
progress@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz"
|
||||
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
||||
|
||||
promptly@^2:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz"
|
||||
@@ -1889,20 +1827,6 @@ promptly@^2:
|
||||
dependencies:
|
||||
read "^1.0.4"
|
||||
|
||||
proxy-agent@^6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz"
|
||||
integrity sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==
|
||||
dependencies:
|
||||
agent-base "^7.1.2"
|
||||
debug "^4.3.4"
|
||||
http-proxy-agent "^7.0.1"
|
||||
https-proxy-agent "^7.0.6"
|
||||
lru-cache "^7.14.1"
|
||||
pac-proxy-agent "^7.1.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
socks-proxy-agent "^8.0.5"
|
||||
|
||||
proxy-agent@~6.4.0:
|
||||
version "6.4.0"
|
||||
resolved "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz"
|
||||
@@ -1922,38 +1846,6 @@ proxy-from-env@^1.1.0:
|
||||
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
pump@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz"
|
||||
integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==
|
||||
dependencies:
|
||||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
puppeteer-core@24.19.0:
|
||||
version "24.19.0"
|
||||
resolved "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.19.0.tgz"
|
||||
integrity sha512-qsEys4OIb2VGC2tNWKAs4U0mnjkIAxueMOOzk2nEFM9g4Y8QuvYkEMtmwsEdvzNGsUFd7DprOQfABmlN7WBOlg==
|
||||
dependencies:
|
||||
"@puppeteer/browsers" "2.10.8"
|
||||
chromium-bidi "8.0.0"
|
||||
debug "^4.4.1"
|
||||
devtools-protocol "0.0.1495869"
|
||||
typed-query-selector "^2.12.0"
|
||||
ws "^8.18.3"
|
||||
|
||||
puppeteer@^24.19.0:
|
||||
version "24.19.0"
|
||||
resolved "https://registry.npmjs.org/puppeteer/-/puppeteer-24.19.0.tgz"
|
||||
integrity sha512-gUWgHX36m9K6yUbvNBEA7CXElIL92yXMoAVFrO8OpZkItqrruLVqYA8ikmfgwcw/cNfYgkt0n2+yP9jd9RSETA==
|
||||
dependencies:
|
||||
"@puppeteer/browsers" "2.10.8"
|
||||
chromium-bidi "8.0.0"
|
||||
cosmiconfig "^9.0.0"
|
||||
devtools-protocol "0.0.1495869"
|
||||
puppeteer-core "24.19.0"
|
||||
typed-query-selector "^2.12.0"
|
||||
|
||||
react-dom@^18.3.1:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
|
||||
@@ -1962,7 +1854,7 @@ react-dom@^18.3.1:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.2"
|
||||
|
||||
"react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", react@^18.3.1:
|
||||
react@^18.3.1:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
|
||||
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
|
||||
@@ -2004,11 +1896,6 @@ require-in-the-middle@^5.0.0:
|
||||
module-details-from-path "^1.0.3"
|
||||
resolve "^1.22.1"
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
|
||||
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
|
||||
|
||||
resolve@^1.1.7, resolve@^1.22.1:
|
||||
version "1.22.10"
|
||||
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz"
|
||||
@@ -2045,19 +1932,12 @@ scheduler@^0.23.2:
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
semver@^7.6.2, semver@^7.7.2:
|
||||
semver@^7.6.2:
|
||||
version "7.7.2"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz"
|
||||
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
|
||||
|
||||
semver@~7.5.0:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@~7.5.4:
|
||||
semver@~7.5.0, semver@~7.5.4:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
@@ -2124,16 +2004,6 @@ sprintf-js@1.1.2:
|
||||
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz"
|
||||
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
|
||||
|
||||
streamx@^2.15.0, streamx@^2.21.0:
|
||||
version "2.22.1"
|
||||
resolved "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz"
|
||||
integrity sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==
|
||||
dependencies:
|
||||
fast-fifo "^1.3.2"
|
||||
text-decoder "^1.1.0"
|
||||
optionalDependencies:
|
||||
bare-events "^2.2.0"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
@@ -2198,7 +2068,7 @@ tailwindcss-animate@^1.0.7:
|
||||
resolved "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz"
|
||||
integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==
|
||||
|
||||
tailwindcss@^4.1.4, "tailwindcss@>=3.0.0 || insiders", tailwindcss@4.1.12:
|
||||
tailwindcss@4.1.12, tailwindcss@^4.1.4:
|
||||
version "4.1.12"
|
||||
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz"
|
||||
integrity sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==
|
||||
@@ -2208,26 +2078,6 @@ tapable@^2.2.0:
|
||||
resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz"
|
||||
integrity sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==
|
||||
|
||||
tar-fs@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz"
|
||||
integrity sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
tar-stream "^3.1.5"
|
||||
optionalDependencies:
|
||||
bare-fs "^4.0.1"
|
||||
bare-path "^3.0.0"
|
||||
|
||||
tar-stream@^3.1.5:
|
||||
version "3.1.7"
|
||||
resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz"
|
||||
integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==
|
||||
dependencies:
|
||||
b4a "^1.6.4"
|
||||
fast-fifo "^1.2.0"
|
||||
streamx "^2.15.0"
|
||||
|
||||
tar@^7.4.3:
|
||||
version "7.4.3"
|
||||
resolved "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz"
|
||||
@@ -2240,13 +2090,6 @@ tar@^7.4.3:
|
||||
mkdirp "^3.0.1"
|
||||
yallist "^5.0.0"
|
||||
|
||||
text-decoder@^1.1.0:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz"
|
||||
integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==
|
||||
dependencies:
|
||||
b4a "^1.6.4"
|
||||
|
||||
thenby@^1.3.4:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz"
|
||||
@@ -2267,16 +2110,16 @@ to-regex-range@^5.0.1:
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
tslib@^2.0.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
tslib@^2.8.0, tslib@1.9.3:
|
||||
tslib@1.9.3:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz"
|
||||
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
||||
|
||||
tslib@^2.0.1, tslib@^2.4.0, tslib@^2.8.0:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
tv4@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz"
|
||||
@@ -2289,16 +2132,6 @@ tx2@~1.0.4:
|
||||
dependencies:
|
||||
json-stringify-safe "^5.0.1"
|
||||
|
||||
typed-query-selector@^2.12.0:
|
||||
version "2.12.0"
|
||||
resolved "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz"
|
||||
integrity sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==
|
||||
|
||||
undici-types@~7.10.0:
|
||||
version "7.10.0"
|
||||
resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz"
|
||||
integrity sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==
|
||||
|
||||
universalify@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz"
|
||||
@@ -2336,21 +2169,11 @@ wrap-ansi@^7.0.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
|
||||
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||
|
||||
ws@^7.0.0, ws@~7.5.10:
|
||||
version "7.5.10"
|
||||
resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz"
|
||||
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
|
||||
|
||||
ws@^8.18.3:
|
||||
version "8.18.3"
|
||||
resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz"
|
||||
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
|
||||
|
||||
y18n@^5.0.5:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
|
||||
@@ -2376,7 +2199,7 @@ yargs-parser@^21.1.1:
|
||||
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
|
||||
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
|
||||
|
||||
yargs@^17.0.0, yargs@^17.7.2:
|
||||
yargs@^17.0.0:
|
||||
version "17.7.2"
|
||||
resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz"
|
||||
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
|
||||
@@ -2388,16 +2211,3 @@ yargs@^17.0.0, yargs@^17.7.2:
|
||||
string-width "^4.2.3"
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^21.1.1"
|
||||
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz"
|
||||
integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
zod@^3.24.1:
|
||||
version "3.25.76"
|
||||
resolved "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz"
|
||||
integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==
|
||||
|
||||
Reference in New Issue
Block a user