Files
Kino-Website-Framework/src/components/CheckoutView.astro
Jannis Heydemann 06606131ef add TypeScript interfaces for cart and cart items
Introduces BaseCartItem, MovieCartItem, SnackCartItem, and the CartItem
discriminated union in bigConstants.ts. Replaces all any-typed cart
references across CartView, CheckoutView, SnacksView, and BookingModal
with the new typed interfaces. Also types users/currentUser with the
existing User interface and removes the unused JSONType import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 08:25:16 +02:00

240 lines
11 KiB
Plaintext

<section id="checkout-view" style="padding: 40px 20px;">
<div class="checkout-container">
<div class="progress-bar">
<div class="step active" id="step-1-indicator">1</div>
<div class="line" id="line-1"></div>
<div class="step" id="step-2-indicator">2</div>
<div class="line" id="line-2"></div>
<div class="step" id="step-3-indicator">3</div>
</div>
<div id="checkout-step-1" class="checkout-step">
<h2 style="text-align: center; margin-bottom: 20px;">Zahlungsmethode wählen</h2>
<div class="payment-grid">
<div class="payment-method" data-method="Apple Pay">
<img src="/img/applepay.png" alt="Apple Pay" style="height: 20px;">
<span>Apple Pay</span>
</div>
<div class="payment-method" data-method="PayPal">
<img src="/img/paypal.png" alt="PayPal" style="height: 20px;">
<span>PayPal</span>
</div>
<div class="payment-method" data-method="Google Pay">
<img src="/img/googlepay.png" alt="Google Pay" style="height: 20px;">
<span>Google Pay</span>
</div>
<div class="payment-method" data-method="Visa">
<img src="/img/visa.png" alt="Visa" style="height: 20px;">
<span>Visa</span>
</div>
</div>
<button id="btn-next-step-2" class="hidden" style="margin-top: 25px; width: 100%; padding: 15px; background: #0071e3; color: white; border: none; border-radius: 8px; font-weight: bold; cursor: pointer;">Weiter zur Übersicht</button>
</div>
<div id="checkout-step-2" class="checkout-step hidden">
<div style="position: relative;">
<button id="btn-back-to-step1" style="position: absolute; top: -15px; left: 0; background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer; opacity: 0.6; transition: 0.3s;">&larr;</button>
<h2 style="text-align: center; margin-bottom: 25px;">Persönliche Daten</h2>
</div>
<h2 style="text-align: center; margin-bottom: 20px;">Bestellübersicht</h2>
<div id="checkout-summary-list" style="background: #222; padding: 15px; border-radius: 8px;"></div>
<h3 id="checkout-total-display" style="text-align: right; margin-top: 15px;"></h3>
<div id="checkout-vat-display" style="text-align: right; color: #86868b; font-size: 0.8rem;"></div>
<button id="btn-pay-now" style="margin-top: 25px; width: 100%; padding: 15px; background: #4caf50; color: white; border: none; border-radius: 8px; font-weight: bold; cursor: pointer;">Jetzt Bezahlen</button>
</div>
<div id="checkout-step-3" class="checkout-step hidden">
<h2 style="color: #4caf50; text-align: center;">Kauf erfolgreich!</h2>
<div id="ticket-container" style="margin-top: 30px;"></div>
<button id="btn-back-home" style="margin-top: 30px; width: 100%; padding: 15px; background: #333; color: white; border: none; border-radius: 8px; cursor: pointer;">Zurück zur Startseite</button>
</div>
</div>
</section>
<script>
import { currentUser, users, cart, emptyCart, occupiedSeatsData, updateCart, updateOccupiedSeats } from "../scripts/bigConstants";
import type { CartItem, MovieCartItem } from "../scripts/bigConstants";
function formatCheckoutEuro(value: number) {
return `${Number(value || 0).toFixed(2).replace(".", ",")} EUR`;
}
let selectedPaymentMethod = "";
function setCheckoutStep(step: number) {
const step1 = document.getElementById("checkout-step-1");
const step2 = document.getElementById("checkout-step-2");
const step3 = document.getElementById("checkout-step-3");
step1?.classList.toggle("hidden", step !== 1);
step2?.classList.toggle("hidden", step !== 2);
step3?.classList.toggle("hidden", step !== 3);
const line1 = document.getElementById("line-1");
const line2 = document.getElementById("line-2");
const indicator1 = document.getElementById("step-1-indicator");
const indicator2 = document.getElementById("step-2-indicator");
const indicator3 = document.getElementById("step-3-indicator");
indicator1?.classList.add("active");
indicator2?.classList.toggle("active", step >= 2);
indicator3?.classList.toggle("active", step >= 3);
line1?.classList.toggle("active", step >= 2);
line2?.classList.toggle("active", step >= 3);
}
function renderCheckout() {
const summaryList = document.getElementById("checkout-summary-list");
const totalDisplay = document.getElementById("checkout-total-display");
const vatDisplay = document.getElementById("checkout-vat-display");
const nextButton = document.getElementById("btn-next-step-2");
if (!summaryList) return;
summaryList.innerHTML = "";
const total = cart.reduce((sum: number, item: CartItem) => sum + item.price, 0);
const vat = total - total / 1.19;
cart.forEach((item: CartItem) => {
const row = document.createElement("div");
row.style.cssText = "display:flex; justify-content:space-between; gap:12px; margin-bottom:10px; font-size:0.95rem;";
const infoText = item.category === "movie"
? `Sitz ${(item as MovieCartItem).seatId} | ${item.hall} | ${item.time} Uhr`
: `${item.time || "Standard"} | ${item.hall}`;
row.innerHTML = `<span>${item.title} (${infoText})</span><span>${formatCheckoutEuro(item.price)}</span>`;
summaryList.appendChild(row);
});
if (totalDisplay) totalDisplay.innerText = `Gesamtbetrag: ${formatCheckoutEuro(total)}`;
if (vatDisplay) vatDisplay.innerText = `inkl. 19% MwSt: ${formatCheckoutEuro(vat)}`;
selectedPaymentMethod = "";
document.querySelectorAll(".payment-method").forEach((method) => method.classList.remove("selected"));
nextButton?.classList.add("hidden");
setCheckoutStep(1);
}
function generateTicket() {
const ticketContainer = document.getElementById("ticket-container");
if (!ticketContainer) return;
const moviesInCart = cart.filter((item: CartItem): item is MovieCartItem => item.category === "movie");
if (!moviesInCart.length) {
ticketContainer.innerHTML = "<p>Danke für deinen Einkauf!</p>";
return;
}
const mainMovie: MovieCartItem = moviesInCart[0];
const matchingMovieSeats = moviesInCart
.filter((item: MovieCartItem) => item.title === mainMovie.title && item.time === mainMovie.time)
.map((item: MovieCartItem) => item.seatId)
.join(", ");
const qrData = encodeURIComponent(`EAGLE-IMAX|${mainMovie.title}|${mainMovie.hall}|${matchingMovieSeats}`);
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${qrData}&bgcolor=ffffff`;
ticketContainer.innerHTML = `
<div class="luxury-ticket">
<div class="ticket-left">
<img src="${mainMovie.img || mainMovie.poster}" class="ticket-poster" alt="${mainMovie.title}">
</div>
<div class="ticket-right">
<div class="ticket-brand">EAGLE'S IMAX PREMIUM</div>
<h2 class="ticket-title">${mainMovie.title}</h2>
<div class="ticket-details">
<p><span>SAAL</span> <strong>${mainMovie.hall}</strong></p>
<p><span>ZEIT</span> <strong>${mainMovie.time} Uhr</strong></p>
<p><span>SITZE</span> <strong>${matchingMovieSeats || "-"}</strong></p>
</div>
<div class="ticket-footer">
<img src="${qrUrl}" class="ticket-qr" alt="QR Code">
<div class="ticket-code">#${Math.floor(Math.random() * 90000) + 10000}</div>
</div>
</div>
</div>
`;
}
function completeCheckout() {
const orderItems: CartItem[] = [...cart];
const orderTotal = orderItems.reduce((sum: number, item: CartItem) => sum + item.price, 0);
if (currentUser && Array.isArray(users)) {
const userIndex = users.findIndex((entry: any) => entry.email === (currentUser as any).email);
if (userIndex !== -1) {
if (!Array.isArray(users[userIndex].orders)) users[userIndex].orders = [];
users[userIndex].orders.push({
date: new Date().toLocaleString("de-DE"),
items: orderItems,
total: orderTotal,
paymentMethod: selectedPaymentMethod || "-"
});
localStorage.setItem("eagleUsers", JSON.stringify(users));
}
}
orderItems
.filter((item: CartItem): item is MovieCartItem => item.category === "movie")
.forEach((item: MovieCartItem) => {
const key = `${item.hall}-${item.time}`;
if (!occupiedSeatsData[key]) occupiedSeatsData[key] = [];
occupiedSeatsData[key].push(item.seatId);
});
updateOccupiedSeats(occupiedSeatsData);
emptyCart();
window.dispatchEvent(new CustomEvent("cart-updated"));
}
function bindCheckoutEvents() {
const nextButton = document.getElementById("btn-next-step-2");
const backButton = document.getElementById("btn-back-to-step1");
const payNowButton = document.getElementById("btn-pay-now") as HTMLButtonElement;
document.querySelectorAll(".payment-method").forEach((method) => {
method.addEventListener("click", () => {
document.querySelectorAll(".payment-method").forEach((entry) => entry.classList.remove("selected"));
method.classList.add("selected");
selectedPaymentMethod = (method as HTMLElement).dataset.method || "";
nextButton?.classList.remove("hidden");
});
});
nextButton?.addEventListener("click", () => {
if (!selectedPaymentMethod) {
alert("Bitte wähle zuerst eine Zahlungsmethode aus.");
return;
}
setCheckoutStep(2);
});
backButton?.addEventListener("click", () => setCheckoutStep(1));
payNowButton?.addEventListener("click", () => {
if (!cart.length) {
alert("Dein Warenkorb ist leer.");
return;
}
payNowButton.disabled = true;
payNowButton.innerText = "Verarbeite...";
payNowButton.style.opacity = "0.7";
setTimeout(() => {
setCheckoutStep(3);
generateTicket();
completeCheckout();
payNowButton.disabled = false;
payNowButton.innerText = "Jetzt Bezahlen";
payNowButton.style.opacity = "1";
}, 1200);
});
document.getElementById("btn-back-home")?.addEventListener("click", () => {
window.location.href = "/";
});
}
if (document.getElementById("checkout-view")) {
renderCheckout();
bindCheckoutEvents();
}
</script>