chore: No more javascript in show

This commit is contained in:
kbe
2025-08-30 14:02:41 +02:00
parent 394190ace8
commit 58dbcf3a6a
2 changed files with 19 additions and 306 deletions

View File

@@ -1,41 +1,4 @@
<div class="bg-white rounded-xl border <%= sold_out ? "border-gray-200 opacity-75" : "border-purple-200" %> shadow-sm overflow-hidden"
x-data="{
quantity: 0,
max: <%= remaining %>,
ticketTypeId: <%= id %>,
name: '<%= name %>',
price: <%= price_cents %>
}"
x-init="
$watch('quantity', (value) => {
$dispatch('update-cart-item', {
ticketTypeId: ticketTypeId,
name: name,
price: price,
quantity: value,
max: max
});
});
$nextTick(() => {
$dispatch('cart-initialized', { ticketTypeId: ticketTypeId });
});
"
@update-cart-item.window="
if ($event.detail.ticketTypeId === ticketTypeId) {
quantity = $event.detail.quantity;
}
"
@cart-change.window="
if (!$event.detail.cart[ticketTypeId]) {
quantity = 0;
}
"
@restore-cart.window="
if ($event.detail.cart[ticketTypeId]) {
quantity = $event.detail.cart[ticketTypeId].quantity;
}
">
<div class="bg-white rounded-xl border <%= sold_out ? "border-gray-200 opacity-75" : "border-purple-200" %> shadow-sm overflow-hidden">
<div class="p-5">
<div class="flex justify-between items-start mb-3">
<div>
@@ -72,19 +35,18 @@
<div class="flex items-center space-x-2 order-1 sm:order-2">
<button type="button"
class="w-8 h-8 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center transition-colors duration-200"
@click="quantity = Math.max(0, quantity - 1)"
:disabled="quantity <= 0">
disabled>
<span class="text-gray-600 font-bold">-</span>
</button>
<input type="number"
min="0"
:max="max"
x-model="quantity"
class="w-12 text-center border border-gray-300 rounded-md px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-1">
max="<%= remaining %>"
value="0"
class="w-12 text-center border border-gray-300 rounded-md px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-1"
disabled>
<button type="button"
class="w-8 h-8 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center transition-colors duration-200"
@click="quantity = Math.min(max, quantity + 1)"
:disabled="quantity >= max">
disabled>
<span class="text-gray-600 font-bold">+</span>
</button>
</div>

View File

@@ -1,7 +1,5 @@
<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 -->
@@ -206,18 +204,22 @@
</div>
</div>
</div>
</div>
</div><!-- Left Column: Event Details -->
<!-- Right Column: Ticket Selection -->
<div class="lg:col-span-1">
<div class="sticky top-6">
<div class="">
<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>
<div class="flex justify-center sm:justify-start mb-6">
<h2 class="text-lg font-bold text-gray-900">Billets disponibles</h2>
</div>
<div class="space-y-4 mb-6">
<% if @event.ticket_types.any? %>
<div class="space-y-4 mb-6">
<% @event.ticket_types.each do |ticket_type| %>
@@ -260,21 +262,19 @@
<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">Quantité :</span>
<span x-text="totalTickets" class="font-medium">0</span>
<span class="font-medium">0</span>
</div>
<div class="flex justify-between items-center mb-4">
<span class="text-gray-600">Montant total:</span>
<span x-text="formattedTotalPrice" class="text-xl font-bold text-purple-700">€0.00</span>
<span class="text-xl font-bold text-purple-700">€0.00</span>
</div>
<button
@click="proceedToCheckout"
:disabled="totalTickets === 0"
:class="totalTickets === 0 ? "opacity-50 cursor-not-allowed" : "hover:from-purple-700 hover:to-indigo-700""
disabled
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
justify-center opacity-50 cursor-not-allowed
"
>
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -290,258 +290,9 @@
</div>
</div>
</div>
</div><!-- Right Column: Ticket Selection -->
</div>
</div>
</div>
</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>