feat(show): wip to checkout
This commit is contained in:
@@ -1,22 +1,56 @@
|
||||
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100" data-controller="ticket-cart" data-ticket-cart-event-id-value="<%= params[:id] %>">
|
||||
<div
|
||||
class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100"
|
||||
x-data="ticketCart(<%= @event.id %>)"
|
||||
x-init="init()"
|
||||
>
|
||||
<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">
|
||||
<ol class="flex items-center space-x-2 text-sm">
|
||||
<%= 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
|
||||
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-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
|
||||
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 events_path, class: "text-gray-500 hover:text-purple-600 transition-colors" do %>
|
||||
Événements
|
||||
<% end %>
|
||||
<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
|
||||
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-gray-900 truncate max-w-xs" aria-current="page">
|
||||
<%= @event.name %>
|
||||
@@ -29,7 +63,11 @@
|
||||
<% if @event.image.present? %>
|
||||
<div class="relative h-96">
|
||||
<%= image_tag @event.image, class: "w-full h-full object-cover" %>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/70 to-transparent"></div>
|
||||
<div
|
||||
class="
|
||||
absolute inset-0 bg-gradient-to-t from-black via-black/70 to-transparent
|
||||
"
|
||||
></div>
|
||||
<div class="absolute bottom-0 left-0 right-0 p-6 md:p-8">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-3xl md:text-4xl font-bold text-white mb-2"><%= @event.name %></h1>
|
||||
@@ -41,22 +79,46 @@
|
||||
<h1 class="text-3xl md:text-4xl font-bold text-white mb-4"><%= @event.name %></h1>
|
||||
<div class="flex flex-wrap items-center gap-4 text-white/90">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-200" 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>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
<svg
|
||||
class="w-5 h-5 mr-2 text-purple-200"
|
||||
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>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<span><%= @event.venue_name %></span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
<svg
|
||||
class="w-5 h-5 mr-2 text-purple-200"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
<span><%= @event.start_time.strftime("%d %B %Y à %H:%M") %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Event Content -->
|
||||
<div class="p-6 md:p-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
@@ -72,9 +134,24 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||
<div class="bg-gray-50 rounded-xl p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-600" 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>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
<svg
|
||||
class="w-5 h-5 mr-2 text-purple-600"
|
||||
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>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
></path>
|
||||
</svg>
|
||||
Lieu
|
||||
</h3>
|
||||
@@ -84,27 +161,44 @@
|
||||
|
||||
<div class="bg-gray-50 rounded-xl p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
<svg
|
||||
class="w-5 h-5 mr-2 text-purple-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
></path>
|
||||
</svg>
|
||||
Date & Heure
|
||||
</h3>
|
||||
<p class="text-gray-700 font-medium"><%= @event.start_time.strftime("%A %d %B %Y") %></p>
|
||||
<p class="text-gray-600 mt-1">À <%= @event.start_time.strftime("%H:%M") %></p>
|
||||
<p class="text-gray-600 mt-1">À
|
||||
<%= @event.start_time.strftime("%H:%M") %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-8 bg-gray-50 rounded-xl p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">Organisateur</h3>
|
||||
<div class="flex items-center">
|
||||
<div class="w-12 h-12 rounded-full bg-gradient-to-r from-purple-500 to-indigo-600 flex items-center justify-center text-white font-bold">
|
||||
<div
|
||||
class="
|
||||
w-12 h-12 rounded-full bg-gradient-to-r from-purple-500 to-indigo-600 flex
|
||||
items-center justify-center text-white font-bold
|
||||
"
|
||||
>
|
||||
<%= @event.user.email.first.upcase %>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<% if @event.user.first_name.present? && @event.user.last_name.present? %>
|
||||
<p class="font-medium text-gray-900"><%= @event.user.first_name %> <%= @event.user.last_name %></p>
|
||||
<p class="font-medium text-gray-900"><%= @event.user.first_name %>
|
||||
<%= @event.user.last_name %></p>
|
||||
<% else %>
|
||||
<p class="font-medium text-gray-900"><%= @event.user.email.split('@').first %></p>
|
||||
<p class="font-medium text-gray-900"><%= @event.user.email.split("@").first %></p>
|
||||
<% end %>
|
||||
<% if @event.user.company_name.present? %>
|
||||
<p class="text-sm text-gray-500"><%= @event.user.company_name %></p>
|
||||
@@ -113,11 +207,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Ticket Selection -->
|
||||
<div class="lg:col-span-1">
|
||||
<div class="sticky top-6">
|
||||
<div class="bg-gradient-to-br from-purple-50 to-indigo-50 rounded-2xl border border-purple-100 p-6 shadow-sm">
|
||||
<div
|
||||
class="
|
||||
bg-gradient-to-br from-purple-50 to-indigo-50 rounded-2xl border
|
||||
border-purple-100 p-6 shadow-sm
|
||||
"
|
||||
>
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">Billets disponibles</h2>
|
||||
|
||||
<% if @event.ticket_types.any? %>
|
||||
@@ -126,44 +224,66 @@
|
||||
<% sold_out = ticket_type.quantity <= ticket_type.tickets.count %>
|
||||
<% remaining = ticket_type.quantity - ticket_type.tickets.count %>
|
||||
|
||||
<%= render 'components/ticket_card', {
|
||||
<%= render "components/ticket_card",
|
||||
{
|
||||
id: ticket_type.id,
|
||||
name: ticket_type.name,
|
||||
description: ticket_type.description,
|
||||
price_cents: ticket_type.price_cents,
|
||||
quantity: ticket_type.quantity,
|
||||
sold_out: sold_out,
|
||||
remaining: remaining
|
||||
remaining: remaining,
|
||||
} %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="text-center py-8">
|
||||
<svg class="w-12 h-12 mx-auto text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z" />
|
||||
<svg
|
||||
class="w-12 h-12 mx-auto text-gray-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="mt-4 text-lg font-medium text-gray-900">Aucun billet disponible</h3>
|
||||
<p class="mt-2 text-gray-500">Les billets pour cet événement ne sont pas encore disponibles ou sont épuisés.</p>
|
||||
<p class="mt-2 text-gray-500">Les billets pour cet événement ne sont pas encore
|
||||
disponibles ou sont épuisés.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Cart Summary -->
|
||||
<div class="border-t border-gray-200 pt-6 mt-6">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-gray-600">Total billets:</span>
|
||||
<span data-ticket-cart-target="cartCount" class="font-medium">0</span>
|
||||
<span class="text-gray-600">Quantité :</span>
|
||||
<span x-text="totalTickets" class="font-medium">0</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<span class="text-gray-600">Montant total:</span>
|
||||
<span data-ticket-cart-target="cartTotal" class="text-xl font-bold text-purple-700">€0.00</span>
|
||||
<span x-text="formattedTotalPrice" class="text-xl font-bold text-purple-700">€0.00</span>
|
||||
</div>
|
||||
<button
|
||||
data-ticket-cart-target="checkoutButton"
|
||||
class="w-full 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-4 rounded-xl shadow-sm transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 flex items-center justify-center"
|
||||
disabled
|
||||
data-action="click->ticket-cart#proceedToCheckout">
|
||||
@click="proceedToCheckout"
|
||||
:disabled="totalTickets === 0"
|
||||
:class="totalTickets === 0 ? "opacity-50 cursor-not-allowed" : "hover:from-purple-700 hover:to-indigo-700""
|
||||
class="
|
||||
w-full bg-gradient-to-r from-purple-600 to-indigo-600 text-white font-medium
|
||||
py-3 px-4 rounded-xl shadow-sm transition-all duration-200 focus:outline-none
|
||||
focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 flex items-center
|
||||
justify-center
|
||||
"
|
||||
>
|
||||
<svg class="w-5 h-5 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" />
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
Procéder au paiement
|
||||
</button>
|
||||
@@ -176,3 +296,252 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("ticketCart", (eventId) => ({
|
||||
cart: {},
|
||||
eventId: eventId,
|
||||
|
||||
init() {
|
||||
// Listen for cart item updates from ticket cards
|
||||
this.$nextTick(() => {
|
||||
window.addEventListener("update-cart-item", (event) => {
|
||||
const { ticketTypeId, name, price, quantity, max } = event.detail;
|
||||
this.updateCartItem(ticketTypeId, name, price, quantity, max);
|
||||
});
|
||||
});
|
||||
|
||||
// Check for pending cart in session storage (after login)
|
||||
this.checkForPendingCart();
|
||||
},
|
||||
|
||||
get totalTickets() {
|
||||
return Object.values(this.cart).reduce((total, item) => total + item.quantity, 0);
|
||||
},
|
||||
|
||||
get totalPrice() {
|
||||
return Object.values(this.cart).reduce((total, item) => total + (item.price * item.quantity), 0);
|
||||
},
|
||||
|
||||
get formattedTotalPrice() {
|
||||
return `€${(this.totalPrice / 100).toFixed(2)}`;
|
||||
},
|
||||
|
||||
updateCartItem(ticketTypeId, name, price, quantity, max) {
|
||||
// Validate quantity
|
||||
if (quantity < 0) quantity = 0;
|
||||
if (quantity > max) quantity = max;
|
||||
|
||||
if (quantity > 0) {
|
||||
this.cart[ticketTypeId] = {
|
||||
name: name,
|
||||
price: price,
|
||||
quantity: quantity
|
||||
};
|
||||
} else {
|
||||
delete this.cart[ticketTypeId];
|
||||
}
|
||||
},
|
||||
|
||||
validateCartAvailability() {
|
||||
// This would need to be implemented with a server check in a real app
|
||||
// For now, we"ll assume availability is correct
|
||||
return true;
|
||||
},
|
||||
|
||||
proceedToCheckout() {
|
||||
if (this.totalTickets === 0) {
|
||||
this.showNotification("Veuillez sélectionner au moins un billet", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate cart contents
|
||||
if (!this.validateCartAvailability()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user is authenticated
|
||||
const isAuthenticated = document.body.dataset.userAuthenticated === "true";
|
||||
|
||||
if (!isAuthenticated) {
|
||||
this.showLoginModal();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
this.setCheckoutLoading(true);
|
||||
|
||||
// Create form and submit to checkout
|
||||
const form = document.createElement("form");
|
||||
form.method = "POST";
|
||||
form.action = "/events/" + document.body.dataset.eventSlug + "." + this.eventId + "/checkout";
|
||||
|
||||
// Add CSRF token
|
||||
const csrfToken = document.querySelector("meta[name="csrf-token"]").content;
|
||||
const csrfInput = document.createElement("input");
|
||||
csrfInput.type = "hidden";
|
||||
csrfInput.name = "authenticity_token";
|
||||
csrfInput.value = csrfToken;
|
||||
form.appendChild(csrfInput);
|
||||
|
||||
// Add cart data
|
||||
const cartInput = document.createElement("input");
|
||||
cartInput.type = "hidden";
|
||||
cartInput.name = "cart";
|
||||
cartInput.value = JSON.stringify(this.cart);
|
||||
form.appendChild(cartInput);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
},
|
||||
|
||||
showLoginModal() {
|
||||
// Create and show modern login modal
|
||||
const modal = document.createElement("div");
|
||||
modal.className = "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50";
|
||||
modal.innerHTML = `
|
||||
<div class="bg-white rounded-2xl p-8 max-w-md mx-4 shadow-2xl">
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="
|
||||
mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-purple-100
|
||||
mb-4
|
||||
"
|
||||
>
|
||||
<svg
|
||||
class="h-6 w-6 text-purple-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Connexion requise</h3>
|
||||
<p class="text-sm text-gray-500 mb-6">Vous devez être connecté pour acheter des billets. Votre panier sera
|
||||
conservé.</p>
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<button
|
||||
id="login-btn"
|
||||
class="
|
||||
flex-1 bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg
|
||||
font-medium transition-colors
|
||||
"
|
||||
>
|
||||
Se connecter
|
||||
</button>
|
||||
<button
|
||||
id="cancel-login"
|
||||
class="
|
||||
flex-1 bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded-lg
|
||||
font-medium transition-colors
|
||||
"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Handle login button
|
||||
modal.querySelector("#login-btn").addEventListener("click", () => {
|
||||
// Store cart in session storage
|
||||
sessionStorage.setItem("pending_cart", JSON.stringify({
|
||||
eventId: this.eventId,
|
||||
cart: this.cart
|
||||
}));
|
||||
window.location.href = "/auth/sign_in";
|
||||
});
|
||||
|
||||
// Handle cancel button
|
||||
modal.querySelector("#cancel-login").addEventListener("click", () => {
|
||||
document.body.removeChild(modal);
|
||||
});
|
||||
|
||||
// Handle backdrop click
|
||||
modal.addEventListener("click", (e) => {
|
||||
if (e.target === modal) {
|
||||
document.body.removeChild(modal);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setCheckoutLoading(loading) {
|
||||
// This would update the checkout button UI
|
||||
// Implementation depends on how you want to show loading state
|
||||
},
|
||||
|
||||
showNotification(message, type = "info") {
|
||||
// Create toast notification
|
||||
const toast = document.createElement("div");
|
||||
const colors = {
|
||||
success: "bg-green-50 text-green-800 border-green-200",
|
||||
error: "bg-red-50 text-red-800 border-red-200",
|
||||
warning: "bg-yellow-50 text-yellow-800 border-yellow-200",
|
||||
info: "bg-blue-50 text-blue-800 border-blue-200"
|
||||
};
|
||||
|
||||
toast.className = `fixed top-4 right-4 z-50 max-w-sm p-4 border rounded-lg shadow-lg ${colors[type]} transform transition-all duration-300 translate-x-full`;
|
||||
toast.innerHTML = `
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-medium">${message}</p>
|
||||
</div>
|
||||
<button
|
||||
class="ml-3 text-sm font-medium opacity-70 hover:opacity-100"
|
||||
onclick="this.parentElement.parentElement.remove()"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// Animate in
|
||||
setTimeout(() => {
|
||||
toast.classList.remove("translate-x-full");
|
||||
}, 10);
|
||||
|
||||
// Auto remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(toast)) {
|
||||
toast.classList.add("translate-x-full");
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(toast)) {
|
||||
document.body.removeChild(toast);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
checkForPendingCart() {
|
||||
const pendingCart = sessionStorage.getItem("pending_cart");
|
||||
if (pendingCart) {
|
||||
try {
|
||||
const cartData = JSON.parse(pendingCart);
|
||||
if (cartData.eventId == this.eventId) {
|
||||
this.cart = cartData.cart;
|
||||
// Dispatch event to update quantities in ticket cards
|
||||
this.$dispatch("restore-cart", { cart: this.cart });
|
||||
}
|
||||
sessionStorage.removeItem("pending_cart");
|
||||
} catch (e) {
|
||||
console.error("Error restoring pending cart:", e);
|
||||
sessionStorage.removeItem("pending_cart");
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user