forked from Aaron/Kino-Website
delete useless files started component building
This commit is contained in:
@@ -11,12 +11,13 @@ export default defineConfig({
|
|||||||
include: ['**/react/*']
|
include: ['**/react/*']
|
||||||
})],
|
})],
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()],
|
// @ts-ignore
|
||||||
|
plugins: [tailwindcss({optimize:false})]
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
schema: {
|
schema: {
|
||||||
TMDB_API_TOKEN: envField.string({ context: 'client', access: 'public', default: 'https://api.example.com' }),
|
TMDB_API_TOKEN: envField.string({ context: 'server', access: 'secret'}),
|
||||||
SETTINGS_TOKEN: envField.string({ context: 'server', access: 'secret' }),
|
TMDB_API_KEY: envField.string({context: "server", access: "secret"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
959
package-lock.json
generated
959
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -16,11 +16,12 @@
|
|||||||
"@tailwindcss/vite": "^4.2.4",
|
"@tailwindcss/vite": "^4.2.4",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"astro": "^6.1.10",
|
"astro": "^6.2.0",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.5",
|
||||||
"react-dom": "^19.2.5",
|
"react-dom": "^19.2.5",
|
||||||
"tailwindcss": "^4.2.4"
|
"tailwindcss": "^4.2.4",
|
||||||
|
"vite": "^6.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.6.0"
|
"@types/node": "^25.6.0"
|
||||||
|
|||||||
469
src/account.ts
469
src/account.ts
@@ -1,469 +0,0 @@
|
|||||||
import type { User } from "./interfaces.js";
|
|
||||||
|
|
||||||
function readStorageJson(key: string, fallbackValue: any) {
|
|
||||||
const raw = localStorage.getItem(key);
|
|
||||||
|
|
||||||
if (!raw || raw === "undefined" || raw === "null") {
|
|
||||||
return fallbackValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(raw);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`Konnte LocalStorage-Wert fuer ${key} nicht lesen.`, error);
|
|
||||||
return fallbackValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeUser(user: User): User {
|
|
||||||
return {
|
|
||||||
firstName: user.firstName || "",
|
|
||||||
lastName: user.lastName || "",
|
|
||||||
email: user.email || "",
|
|
||||||
hashedPassword: user.hashedPassword || "",
|
|
||||||
orders: Array.isArray(user.orders) ? user.orders : [],
|
|
||||||
paymentMethods: Array.isArray(user.paymentMethods) ? user.paymentMethods : []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(value: string) {
|
|
||||||
return String(value || "")
|
|
||||||
.replaceAll("&", "&")
|
|
||||||
.replaceAll("<", "<")
|
|
||||||
.replaceAll(">", ">")
|
|
||||||
.replaceAll('"', """)
|
|
||||||
.replaceAll("'", "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatEuro(value: string) {
|
|
||||||
return `${Number(value || 0).toFixed(2).replace(".", ",")} EUR`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function persistUsers() {
|
|
||||||
localStorage.setItem("eagleUsers", JSON.stringify(users));
|
|
||||||
}
|
|
||||||
|
|
||||||
function persistCurrentUser() {
|
|
||||||
if (currentUser) {
|
|
||||||
localStorage.setItem("currentUser", JSON.stringify(currentUser));
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem("currentUser");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export let users = readStorageJson("eagleUsers", []);
|
|
||||||
if (!Array.isArray(users)) {
|
|
||||||
users = [];
|
|
||||||
}
|
|
||||||
users = users.map(normalizeUser).filter(Boolean);
|
|
||||||
|
|
||||||
const rawCurrentUser = readStorageJson("currentUser", null);
|
|
||||||
|
|
||||||
export var currentUser: User | null = rawCurrentUser ? normalizeUser(rawCurrentUser) : null;
|
|
||||||
|
|
||||||
if (currentUser && currentUser.email) {
|
|
||||||
const currentEmail = currentUser.email;
|
|
||||||
const storedMatch = users.find((user: { email: string; }) => {
|
|
||||||
return user.email === currentEmail;
|
|
||||||
});
|
|
||||||
if (storedMatch) {
|
|
||||||
currentUser = storedMatch;
|
|
||||||
} else {
|
|
||||||
users.push(currentUser);
|
|
||||||
persistUsers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function hashMessage(message: string) {
|
|
||||||
const msgBuffer = new TextEncoder().encode(message); // Encode as UTF-8
|
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); // Hash
|
|
||||||
const hashArray = Array.from(new Uint8Array(hashBuffer)); // Convert to bytes
|
|
||||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // Hex string
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInputValue(id: string): string {
|
|
||||||
const el = document.getElementById(id) as HTMLInputElement | null;
|
|
||||||
return el?.value.trim() ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function registerUser() {
|
|
||||||
const firstName = getInputValue("reg-firstname");
|
|
||||||
const lastName = getInputValue("reg-lastname");
|
|
||||||
const email = getInputValue("reg-email").toLowerCase();
|
|
||||||
const password = document.querySelector<HTMLInputElement>("#reg-password")?.value ?? "";
|
|
||||||
|
|
||||||
if (!firstName || !lastName || !email || !password) {
|
|
||||||
alert("Bitte fuelle alle Felder aus.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!email.includes("@")) {
|
|
||||||
alert("Bitte gib eine gueltige E-Mail-Adresse ein.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingUser = users.find((user: User) => user.email.toLowerCase() === email);
|
|
||||||
if (existingUser) {
|
|
||||||
alert("E-Mail bereits registriert");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hashedPassword = await hashMessage(password);
|
|
||||||
|
|
||||||
const newUser = {
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
email,
|
|
||||||
hashedPassword,
|
|
||||||
orders: [],
|
|
||||||
paymentMethods: []
|
|
||||||
};
|
|
||||||
|
|
||||||
users.push(newUser);
|
|
||||||
currentUser = newUser;
|
|
||||||
|
|
||||||
persistUsers();
|
|
||||||
persistCurrentUser();
|
|
||||||
|
|
||||||
alert("Registrierung erfolgreich");
|
|
||||||
document.getElementById("register-modal")?.classList.add("hidden");
|
|
||||||
|
|
||||||
openAccountDashboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loginUser() {
|
|
||||||
const email = (document.querySelector<HTMLInputElement>("#login-email")?.value.trim() || "").toLowerCase();
|
|
||||||
const password = document.querySelector<HTMLInputElement>("#login-password")?.value || "";
|
|
||||||
const hashedPassword = await hashMessage(password);
|
|
||||||
|
|
||||||
const user = users.find(
|
|
||||||
(entry: User) => entry.email.toLowerCase() === email && entry.hashedPassword === hashedPassword
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
document.getElementById("login-error")?.classList.remove("hidden");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentUser = user;
|
|
||||||
persistCurrentUser();
|
|
||||||
openAccountDashboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function openAccountDashboard() {
|
|
||||||
const accountView = document.getElementById("account-view");
|
|
||||||
if (!accountView) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentUser) {
|
|
||||||
accountView.innerHTML = "<div class='account-login-box'><h2>Mein Konto</h2><p>Bitte melde dich an oder registriere dich.</p></div>";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
accountView.innerHTML = /*html*/`
|
|
||||||
<div class="account-panel">
|
|
||||||
<div class="account-panel-header">
|
|
||||||
<h2>Mein Konto</h2>
|
|
||||||
<button class="account-logout-btn" onclick="logoutUser()">Abmelden</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="account-tabs">
|
|
||||||
<button class="account-tab-btn" onclick="renderPersonalInfo()">Persönliche Daten</button>
|
|
||||||
<button class="account-tab-btn" onclick="renderOrders()">Meine Bestellungen</button>
|
|
||||||
<button class="account-tab-btn" onclick="renderPayments()">Zahlungsmethoden</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="account-tab-content"></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
renderPersonalInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPersonalInfo() {
|
|
||||||
const target = document.getElementById("account-tab-content");
|
|
||||||
if (!target || !currentUser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
target.innerHTML = `
|
|
||||||
<div class="account-card">
|
|
||||||
<p><strong>Vorname:</strong> ${currentUser.firstName || "-"}</p>
|
|
||||||
<p><strong>Nachname:</strong> ${currentUser.lastName || "-"}</p>
|
|
||||||
<p><strong>E-Mail:</strong> ${currentUser.email || "-"}</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderOrders() {
|
|
||||||
const target = document.getElementById("account-tab-content");
|
|
||||||
if (!target || !currentUser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const orders = Array.isArray(currentUser.orders) ? currentUser.orders : [];
|
|
||||||
|
|
||||||
if (!orders.length) {
|
|
||||||
target.innerHTML = `
|
|
||||||
<div class="account-card">
|
|
||||||
<h3>Meine Bestellungen</h3>
|
|
||||||
<p>Noch keine Bestellungen vorhanden.</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderHtml = orders
|
|
||||||
.map((order, index) => {
|
|
||||||
const movieItems = Array.isArray(order.items)
|
|
||||||
? order.items.filter((item: any) => item.category === "movie")
|
|
||||||
: [];
|
|
||||||
const previewItem = movieItems[0] || (Array.isArray(order.items) ? order.items[0] : null);
|
|
||||||
const previewTitle = previewItem?.title || "Bestellung";
|
|
||||||
const ticketsCount = movieItems.length || (Array.isArray(order.items) ? order.items.length : 0);
|
|
||||||
|
|
||||||
return `
|
|
||||||
<button type="button" class="order-box order-item-btn" data-order-index="${index}">
|
|
||||||
<div class="order-item-head">
|
|
||||||
<h4>${escapeHtml(previewTitle)}</h4>
|
|
||||||
<span>${formatEuro(order.total || 0)}</span>
|
|
||||||
</div>
|
|
||||||
<p><strong>Datum:</strong> ${escapeHtml(order.date || "-")}</p>
|
|
||||||
<p><strong>Anzahl:</strong> ${ticketsCount}x</p>
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
target.innerHTML = `
|
|
||||||
<div class="account-orders-shell">
|
|
||||||
<h3>Meine Bestellungen</h3>
|
|
||||||
<p class="account-payments-note">Klicke auf eine Bestellung, um dein Ticket-Detail zu sehen.</p>
|
|
||||||
<div class="account-orders-grid">${orderHtml}</div>
|
|
||||||
<div id="order-ticket-details" class="order-ticket-details hidden"></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const detailTarget = document.getElementById("order-ticket-details");
|
|
||||||
const orderButtons = Array.from(target.querySelectorAll<HTMLButtonElement>(".order-item-btn"));
|
|
||||||
|
|
||||||
const renderOrderTicket = (orderIndex: number) => {
|
|
||||||
const order = orders[orderIndex];
|
|
||||||
if (!order || !detailTarget) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const movieItems = Array.isArray(order.items)
|
|
||||||
? order.items.filter((item: any) => item.category === "movie")
|
|
||||||
: [];
|
|
||||||
const primaryMovie = movieItems[0] || (Array.isArray(order.items) ? order.items[0] : null);
|
|
||||||
const poster = primaryMovie?.img || "";
|
|
||||||
const seats = movieItems.map((item: any) => item.seatId).filter(Boolean).join(", ") || "-";
|
|
||||||
const ticketCount = movieItems.length || (Array.isArray(order.items) ? order.items.length : 0);
|
|
||||||
const hall = primaryMovie?.hall || "-";
|
|
||||||
const time = primaryMovie?.time ? `${primaryMovie.time} Uhr` : "-";
|
|
||||||
|
|
||||||
detailTarget.innerHTML = `
|
|
||||||
<article class="order-ticket-card">
|
|
||||||
<div class="order-ticket-poster">
|
|
||||||
${poster
|
|
||||||
? `<img src="${escapeHtml(poster)}" alt="${escapeHtml(primaryMovie?.title || "Film")}">`
|
|
||||||
: `<div class="order-ticket-poster-fallback">Kein Poster</div>`}
|
|
||||||
</div>
|
|
||||||
<div class="order-ticket-content">
|
|
||||||
<div class="order-ticket-brand">EAGLE'S IMAX | Bestell-Details</div>
|
|
||||||
<h4>${escapeHtml(primaryMovie?.title || "Bestellung")}</h4>
|
|
||||||
<div class="order-ticket-grid">
|
|
||||||
<p><span>Datum</span><strong>${escapeHtml(order.date || "-")}</strong></p>
|
|
||||||
<p><span>Saal</span><strong>${escapeHtml(hall)}</strong></p>
|
|
||||||
<p><span>Uhrzeit</span><strong>${escapeHtml(time)}</strong></p>
|
|
||||||
<p><span>Tickets</span><strong>${ticketCount}x</strong></p>
|
|
||||||
<p><span>Sitze</span><strong>${escapeHtml(seats)}</strong></p>
|
|
||||||
<p><span>Gesamt</span><strong>${formatEuro(order.total || 0)}</strong></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
`;
|
|
||||||
|
|
||||||
detailTarget.classList.remove("hidden");
|
|
||||||
orderButtons.forEach((button) => {
|
|
||||||
button.classList.toggle("active", Number(button.dataset.orderIndex) === orderIndex);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
orderButtons.forEach((button) => {
|
|
||||||
button.addEventListener("click", () => {
|
|
||||||
const orderIndex = Number(button.dataset.orderIndex || -1);
|
|
||||||
if (orderIndex >= 0) {
|
|
||||||
renderOrderTicket(orderIndex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPayments() {
|
|
||||||
const target = document.getElementById("account-tab-content");
|
|
||||||
if (!target || !currentUser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
target.innerHTML = /*html*/`
|
|
||||||
<div class="account-card">
|
|
||||||
<h3>Zahlungsmethoden</h3>
|
|
||||||
<p class="account-payments-note">Platzhalter zum Hinterlegen deiner Logos oder Anbieter-Informationen.</p>
|
|
||||||
<div class="account-payment-grid">
|
|
||||||
<button type="button" class="account-payment-card account-pay-trigger" data-pay-modal="pay-modal-card">
|
|
||||||
<div class="payment-logo-slot">
|
|
||||||
<img src="img/mastercard.png" alt="Mastercard">
|
|
||||||
</div>
|
|
||||||
<h4>Visa / Mastercard</h4>
|
|
||||||
<p>Karteninformationen hinterlegen</p>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="account-payment-card account-pay-trigger" data-pay-modal="pay-modal-paypal">
|
|
||||||
<div class="payment-logo-slot">
|
|
||||||
<img src="img/paypal.png" alt="PayPal">
|
|
||||||
</div>
|
|
||||||
<h4>PayPal</h4>
|
|
||||||
<p>Konto verbinden</p>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="account-payment-card account-pay-trigger" data-pay-modal="pay-modal-apple">
|
|
||||||
<div class="payment-logo-slot">
|
|
||||||
<img src="img/applepay.png" alt="Apple Pay">
|
|
||||||
</div>
|
|
||||||
<h4>Apple Pay</h4>
|
|
||||||
<p>Geraet freischalten</p>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="account-payment-card account-pay-trigger" data-pay-modal="pay-modal-google">
|
|
||||||
<div class="payment-logo-slot">
|
|
||||||
<img src="img/googlepay.png" alt="Google Pay">
|
|
||||||
</div>
|
|
||||||
<h4>Google Pay</h4>
|
|
||||||
<p>Wallet verknuepfen</p>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="pay-modal-card" class="pay-modal-overlay hidden">
|
|
||||||
<div class="pay-modal-panel pay-modal-card-style">
|
|
||||||
<button type="button" class="pay-close-btn" data-pay-close>×</button>
|
|
||||||
<div class="pay-modal-head">
|
|
||||||
<img src="img/mastercard.png" alt="Kreditkarte">
|
|
||||||
<h4>Kreditkarte hinterlegen</h4>
|
|
||||||
</div>
|
|
||||||
<div class="pay-form-grid">
|
|
||||||
<label>Kartennummer
|
|
||||||
<input type="text" placeholder="1234 5678 9012 3456">
|
|
||||||
</label>
|
|
||||||
<div class="pay-form-row">
|
|
||||||
<label>Exp. Datum
|
|
||||||
<input type="text" placeholder="MM/JJ">
|
|
||||||
</label>
|
|
||||||
<label>CVV
|
|
||||||
<input type="text" placeholder="123">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<label>Name auf Karte
|
|
||||||
<input type="text" placeholder="Max Mustermann">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="pay-submit-btn">Karte speichern</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="pay-modal-paypal" class="pay-modal-overlay hidden">
|
|
||||||
<div class="pay-modal-panel pay-modal-paypal-style">
|
|
||||||
<button type="button" class="pay-close-btn" data-pay-close>×</button>
|
|
||||||
<div class="pay-modal-head">
|
|
||||||
<img src="img/paypal.png" alt="PayPal">
|
|
||||||
<h4>PayPal verbinden</h4>
|
|
||||||
</div>
|
|
||||||
<p>Einloggen und dein PayPal-Konto mit deinem Kino-Account verbinden.</p>
|
|
||||||
<label>E-Mail
|
|
||||||
<input type="email" placeholder="name@beispiel.de">
|
|
||||||
</label>
|
|
||||||
<label>Passwort
|
|
||||||
<input type="password" placeholder="Passwort">
|
|
||||||
</label>
|
|
||||||
<button type="button" class="pay-submit-btn paypal-btn">Mit PayPal fortfahren</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="pay-modal-apple" class="pay-modal-overlay hidden">
|
|
||||||
<div class="pay-modal-panel pay-modal-apple-style">
|
|
||||||
<button type="button" class="pay-close-btn" data-pay-close>×</button>
|
|
||||||
<div class="pay-modal-head">
|
|
||||||
<img src="img/applepay.png" alt="Apple Pay">
|
|
||||||
<h4>Apple Pay einrichten</h4>
|
|
||||||
</div>
|
|
||||||
<p>Apple Pay wirkt schlicht, klar und fokussiert. Hinterlege hier die bevorzugte Karte für schnelle Zahlungen.</p>
|
|
||||||
<label>Apple-ID E-Mail
|
|
||||||
<input type="email" placeholder="appleid@beispiel.de">
|
|
||||||
</label>
|
|
||||||
<label>Bevorzugte Karte
|
|
||||||
<input type="text" placeholder="Visa, Mastercard, ...">
|
|
||||||
</label>
|
|
||||||
<button type="button" class="pay-submit-btn apple-btn">Zu Wallet hinzufügen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="pay-modal-google" class="pay-modal-overlay hidden">
|
|
||||||
<div class="pay-modal-panel pay-modal-google-style">
|
|
||||||
<button type="button" class="pay-close-btn" data-pay-close>×</button>
|
|
||||||
<div class="pay-modal-head">
|
|
||||||
<img src="img/googlepay.png" alt="Google Pay">
|
|
||||||
<h4>Google Pay einrichten</h4>
|
|
||||||
</div>
|
|
||||||
<p>Verbinde deine Wallet, damit zukünftige Bestellungen in wenigen Klicks abgeschlossen werden.</p>
|
|
||||||
<label>Google-Konto
|
|
||||||
<input type="email" placeholder="konto@gmail.com">
|
|
||||||
</label>
|
|
||||||
<label>Standard-Zahlungsquelle
|
|
||||||
<input type="text" placeholder="z. B. Visa 1234">
|
|
||||||
</label>
|
|
||||||
<button type="button" class="pay-submit-btn google-btn">Wallet verbinden</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const modals = Array.from(target.querySelectorAll(".pay-modal-overlay"));
|
|
||||||
const triggers = Array.from(target.querySelectorAll(".account-pay-trigger"));
|
|
||||||
const closeButtons = Array.from(target.querySelectorAll("[data-pay-close]"));
|
|
||||||
|
|
||||||
const closeAllPaymentModals = () => {
|
|
||||||
modals.forEach((modal) => modal.classList.add("hidden"));
|
|
||||||
document.body.style.overflow = "auto";
|
|
||||||
};
|
|
||||||
|
|
||||||
triggers.forEach((trigger) => {
|
|
||||||
trigger.addEventListener("click", () => {
|
|
||||||
closeAllPaymentModals();
|
|
||||||
const targetId = trigger.getAttribute("data-pay-modal");
|
|
||||||
const modal = targetId ? target.querySelector(`#${targetId}`) : null;
|
|
||||||
|
|
||||||
if (modal) {
|
|
||||||
modal.classList.remove("hidden");
|
|
||||||
document.body.style.overflow = "hidden";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
closeButtons.forEach((button) => {
|
|
||||||
button.addEventListener("click", closeAllPaymentModals);
|
|
||||||
});
|
|
||||||
|
|
||||||
modals.forEach((modal) => {
|
|
||||||
modal.addEventListener("click", (event) => {
|
|
||||||
if (event.target === modal) {
|
|
||||||
closeAllPaymentModals();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function logoutUser() {
|
|
||||||
persistCurrentUser();
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
358
src/booking.ts
358
src/booking.ts
@@ -1,358 +0,0 @@
|
|||||||
import { seatLayouts, occupiedSeatsData, prices, cart } from "./main.js"
|
|
||||||
import { renderCart, saveCart } from "./cart.js";
|
|
||||||
import { renderCheckout } from "./checkout.js";
|
|
||||||
|
|
||||||
let currentBookingContext: any = null;
|
|
||||||
let currentHallLayout: any = null;
|
|
||||||
|
|
||||||
export function openBooking(movie: string, hall: string, time: any) {
|
|
||||||
const titleEl = document.getElementById("modal-movie-title");
|
|
||||||
const infoEl = document.getElementById("modal-info-text");
|
|
||||||
|
|
||||||
if (titleEl) {
|
|
||||||
titleEl.innerText = movie;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (infoEl) {
|
|
||||||
infoEl.innerText = `${hall} • ${time} Uhr`;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentBookingContext = { movie, hall, time };
|
|
||||||
|
|
||||||
createSeats(hall, time);
|
|
||||||
renderBookingLegend();
|
|
||||||
updateBookingSummary();
|
|
||||||
|
|
||||||
document.getElementById("booking-modal")?.classList.remove("hidden");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRowLabel(rowIndex: number) {
|
|
||||||
return String(rowIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildHallLayout(hallName: string, baseConfig:any) {
|
|
||||||
const rows = Number(baseConfig.rows || 0);
|
|
||||||
const totalCols = Number(baseConfig.left || 0) + Number(baseConfig.right || 0);
|
|
||||||
const isDeluxe = /deluxe/i.test(hallName);
|
|
||||||
|
|
||||||
const left = isDeluxe
|
|
||||||
? Math.max(3, Number(baseConfig.left || 0) - 1)
|
|
||||||
: Number(baseConfig.left || 0);
|
|
||||||
const right = Math.max(0, totalCols - left);
|
|
||||||
|
|
||||||
const vipRows = rows > 0 ? [rows] : [];
|
|
||||||
|
|
||||||
const dboxMap = new Set();
|
|
||||||
const markDboxRange = (rowNumber: number, startCol: number, width: number) => {
|
|
||||||
if (!rowNumber || width <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxCol = Math.min(totalCols, startCol + width - 1);
|
|
||||||
for (let col = startCol; col <= maxCol; col++) {
|
|
||||||
dboxMap.add(`${rowNumber}-${col}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isDeluxe) {
|
|
||||||
const configuredDboxSeats = Array.isArray(baseConfig.dbox)
|
|
||||||
? baseConfig.dbox.reduce((sum: number, section: any) => sum + Number(section.w || 0), 0)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
const totalDboxSeats = Math.max(4, configuredDboxSeats || 0);
|
|
||||||
|
|
||||||
const firstRow = Math.max(1, rows - 2);
|
|
||||||
const secondRow = Math.max(1, rows - 1);
|
|
||||||
const targetRows = [firstRow, secondRow]
|
|
||||||
.filter((rowNumber, index, arr) => arr.indexOf(rowNumber) === index)
|
|
||||||
.filter((rowNumber) => !vipRows.includes(rowNumber));
|
|
||||||
|
|
||||||
const rowCount = Math.max(1, targetRows.length);
|
|
||||||
const seatsPerFirstRows = Math.ceil(totalDboxSeats / rowCount);
|
|
||||||
let remaining = totalDboxSeats;
|
|
||||||
|
|
||||||
targetRows.forEach((rowNumber, index) => {
|
|
||||||
const seatsForRow = index === targetRows.length - 1
|
|
||||||
? remaining
|
|
||||||
: Math.min(seatsPerFirstRows, remaining);
|
|
||||||
remaining -= seatsForRow;
|
|
||||||
|
|
||||||
const startCol = left + Math.max(1, Math.floor((right - seatsForRow) / 2) + 1);
|
|
||||||
markDboxRange(rowNumber, startCol, seatsForRow);
|
|
||||||
});
|
|
||||||
} else if (Array.isArray(baseConfig.dbox)) {
|
|
||||||
baseConfig.dbox.forEach((section: any) => {
|
|
||||||
const rowNumber = Number(section.r || 0);
|
|
||||||
const width = Number(section.w || 0);
|
|
||||||
const startCol = Number(section.c || 0);
|
|
||||||
markDboxRange(rowNumber, startCol, width);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
rows,
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
totalCols,
|
|
||||||
vipRows,
|
|
||||||
dboxMap,
|
|
||||||
isImax: Boolean(baseConfig.isImax)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSeatType(layout: any, rowNumber: number, colNumber: number) {
|
|
||||||
if (layout.dboxMap.has(`${rowNumber}-${colNumber}`)) {
|
|
||||||
return "dbox";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout.vipRows.includes(rowNumber)) {
|
|
||||||
return "vip";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout.isImax) {
|
|
||||||
return "imax";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "normal";
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSeatElement({seatId, seatType, occupiedSeats }:any) {
|
|
||||||
const seat = document.createElement("button");
|
|
||||||
seat.type = "button";
|
|
||||||
seat.classList.add("seat", seatType);
|
|
||||||
seat.dataset.seatId = seatId;
|
|
||||||
seat.dataset.type = seatType;
|
|
||||||
seat.title = `${seatId} (${seatType.toUpperCase()})`;
|
|
||||||
|
|
||||||
if (occupiedSeats.has(seatId)) {
|
|
||||||
seat.classList.add("occupied");
|
|
||||||
seat.disabled = true;
|
|
||||||
seat.setAttribute("aria-label", `${seatId} belegt`);
|
|
||||||
return seat;
|
|
||||||
}
|
|
||||||
|
|
||||||
seat.setAttribute("aria-label", `${seatId} frei`);
|
|
||||||
seat.addEventListener("click", () => {
|
|
||||||
seat.classList.toggle("selected");
|
|
||||||
updateBookingSummary();
|
|
||||||
});
|
|
||||||
|
|
||||||
return seat;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSeats(hallName: string, time: any) {
|
|
||||||
const seatGrid = document.getElementById("seat-grid");
|
|
||||||
if (!seatGrid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
seatGrid.innerHTML = "";
|
|
||||||
|
|
||||||
const arrIndex = hallName as keyof typeof seatLayouts;
|
|
||||||
const baseConfig: any = seatLayouts[arrIndex];
|
|
||||||
if (!baseConfig) {
|
|
||||||
currentHallLayout = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentHallLayout = buildHallLayout(hallName, baseConfig);
|
|
||||||
|
|
||||||
const occupiedKey = `${hallName}-${time}`;
|
|
||||||
const occupiedSeats = new Set(Array.isArray(occupiedSeatsData?.[occupiedKey]) ? occupiedSeatsData[occupiedKey] : []);
|
|
||||||
|
|
||||||
for (let rowIndex = 0; rowIndex < currentHallLayout.rows; rowIndex++) {
|
|
||||||
const rowNumber = rowIndex + 1;
|
|
||||||
const rowLabel = getRowLabel(rowIndex);
|
|
||||||
|
|
||||||
const perspectiveFactor = (currentHallLayout.rows - rowNumber) / Math.max(currentHallLayout.rows - 1, 1);
|
|
||||||
const rowIndent = Math.round(18 * perspectiveFactor);
|
|
||||||
|
|
||||||
const row = document.createElement("div");
|
|
||||||
row.className = "seat-row cinema-row";
|
|
||||||
row.style.setProperty("--row-indent", `${rowIndent}px`);
|
|
||||||
|
|
||||||
const leftLabel = document.createElement("div");
|
|
||||||
leftLabel.className = "row-label";
|
|
||||||
leftLabel.textContent = rowLabel;
|
|
||||||
|
|
||||||
const rightLabel = document.createElement("div");
|
|
||||||
rightLabel.className = "row-label row-label-right";
|
|
||||||
rightLabel.textContent = rowLabel;
|
|
||||||
|
|
||||||
const leftBlock = document.createElement("div");
|
|
||||||
leftBlock.className = "row-seat-block left-block";
|
|
||||||
|
|
||||||
const rightBlock = document.createElement("div");
|
|
||||||
rightBlock.className = "row-seat-block right-block";
|
|
||||||
|
|
||||||
for (let col = 1; col <= currentHallLayout.totalCols; col++) {
|
|
||||||
const seatId = `R${rowNumber}-P${col}`;
|
|
||||||
const seatType = getSeatType(currentHallLayout, rowNumber, col);
|
|
||||||
const seat = createSeatElement({ seatId, seatType, occupiedSeats });
|
|
||||||
|
|
||||||
if (col <= currentHallLayout.left) {
|
|
||||||
leftBlock.appendChild(seat);
|
|
||||||
} else {
|
|
||||||
rightBlock.appendChild(seat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const aisle = document.createElement("div");
|
|
||||||
aisle.className = "aisle-gap";
|
|
||||||
|
|
||||||
row.append(leftLabel, leftBlock, aisle, rightBlock, rightLabel);
|
|
||||||
seatGrid.appendChild(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderBookingLegend() {
|
|
||||||
const legend = document.getElementById("dynamic-legend");
|
|
||||||
if (!legend || !currentHallLayout) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const legendItems = [
|
|
||||||
{ type: "normal", label: "Standard" },
|
|
||||||
{ type: "selected", label: "Ausgewählt" },
|
|
||||||
{ type: "occupied", label: "Belegt" }
|
|
||||||
];
|
|
||||||
|
|
||||||
if (currentHallLayout.isImax) {
|
|
||||||
legendItems.unshift({ type: "imax", label: "IMAX" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentHallLayout.vipRows.length > 0) {
|
|
||||||
legendItems.unshift({ type: "vip", label: "VIP" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentHallLayout.dboxMap.size > 0) {
|
|
||||||
legendItems.unshift({ type: "dbox", label: "D-BOX" });
|
|
||||||
}
|
|
||||||
|
|
||||||
legend.innerHTML = legendItems
|
|
||||||
.map((item) => `
|
|
||||||
<div class="item">
|
|
||||||
<span class="seat ${item.type}"></span>
|
|
||||||
<span>${item.label}</span>
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateBookingSummary() {
|
|
||||||
const selectedSeats = Array.from(document.querySelectorAll("#seat-grid .seat.selected")) as HTMLElement[];;
|
|
||||||
const summaryPanel = document.getElementById("booking-summary");
|
|
||||||
const summaryItems = document.getElementById("summary-items");
|
|
||||||
const totalEl = document.getElementById("total-price");
|
|
||||||
|
|
||||||
let total = 0;
|
|
||||||
|
|
||||||
if (summaryItems) {
|
|
||||||
summaryItems.innerHTML = selectedSeats
|
|
||||||
.map((seat) => {
|
|
||||||
const type = (seat.dataset.type || "normal") as keyof typeof prices;
|
|
||||||
const seatPrice = Number(prices?.[type] ?? prices?.normal ?? 11);
|
|
||||||
total += seatPrice;
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="summary-row">
|
|
||||||
<span>${seat.dataset.seatId}</span>
|
|
||||||
<span>${seatPrice.toFixed(2).replace(".", ",")} EUR</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
} else {
|
|
||||||
selectedSeats.forEach((seat) => {
|
|
||||||
const type = seat.dataset.type || "normal";
|
|
||||||
const seatPrice = Number(prices?.[type] ?? prices?.normal ?? 11);
|
|
||||||
total += seatPrice;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalEl) {
|
|
||||||
totalEl.innerText = `${total.toFixed(2).replace(".", ",")} EUR`;
|
|
||||||
}
|
|
||||||
|
|
||||||
summaryPanel?.classList.toggle("hidden", selectedSeats.length === 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function findMoviePoster(movieTitle: string) {
|
|
||||||
const cards = Array.from(document.querySelectorAll(".movie-card, .detailed-card"));
|
|
||||||
const normalizedTarget = String(movieTitle || "").trim().toLowerCase();
|
|
||||||
|
|
||||||
for (const card of cards) {
|
|
||||||
const currentCard = card.querySelector("h2, h3") as HTMLElement;
|
|
||||||
const title = currentCard.innerText?.trim().toLowerCase();
|
|
||||||
if (title === normalizedTarget) {
|
|
||||||
const imageSrc = card.querySelector("img")?.src;
|
|
||||||
if (imageSrc) {
|
|
||||||
return imageSrc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmSelectedSeats() {
|
|
||||||
const selectedSeats = Array.from(document.querySelectorAll("#seat-grid .seat.selected")) as HTMLElement[];
|
|
||||||
|
|
||||||
if (!currentBookingContext || selectedSeats.length === 0) {
|
|
||||||
alert("Bitte waehle mindestens einen Platz aus.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moviePoster = findMoviePoster(currentBookingContext.movie);
|
|
||||||
const addedSeats = [];
|
|
||||||
|
|
||||||
selectedSeats.forEach((seat) => {
|
|
||||||
const seatId = seat.dataset.seatId;
|
|
||||||
const seatType = seat.dataset.type || "normal";
|
|
||||||
|
|
||||||
const alreadyInCart = cart.some((item: any) =>
|
|
||||||
item.category === "movie" &&
|
|
||||||
item.title === currentBookingContext.movie &&
|
|
||||||
item.hall === currentBookingContext.hall &&
|
|
||||||
item.time === currentBookingContext.time &&
|
|
||||||
item.seatId === seatId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (alreadyInCart) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cart.push({
|
|
||||||
id: Date.now() + Math.random(),
|
|
||||||
category: "movie",
|
|
||||||
title: currentBookingContext.movie,
|
|
||||||
hall: currentBookingContext.hall,
|
|
||||||
time: currentBookingContext.time,
|
|
||||||
seatId,
|
|
||||||
type: seatType.toUpperCase(),
|
|
||||||
price: Number(prices?.[seatType] ?? prices?.normal ?? 11),
|
|
||||||
img: moviePoster
|
|
||||||
});
|
|
||||||
|
|
||||||
addedSeats.push(seatId);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!addedSeats.length) {
|
|
||||||
alert("Diese Plaetze sind bereits im Warenkorb.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveCart?.();
|
|
||||||
renderCart?.();
|
|
||||||
renderCheckout?.();
|
|
||||||
|
|
||||||
document.getElementById("booking-modal")?.classList.add("hidden");
|
|
||||||
|
|
||||||
const snackOverlay = document.getElementById("snack-prompt-overlay");
|
|
||||||
snackOverlay?.classList.remove("hidden");
|
|
||||||
document.body.style.overflow = "hidden";
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
document.getElementById("btn-confirm-seats")?.addEventListener("click", confirmSelectedSeats);
|
|
||||||
});
|
|
||||||
203
src/cart.ts
203
src/cart.ts
@@ -1,203 +0,0 @@
|
|||||||
import { cart } from "./main.js";
|
|
||||||
|
|
||||||
function formatEuro(value: number) {
|
|
||||||
return `${Number(value || 0).toFixed(2).replace(".", ",")} EUR`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(value: any) {
|
|
||||||
return String(value || "")
|
|
||||||
.replaceAll("&", "&")
|
|
||||||
.replaceAll("<", "<")
|
|
||||||
.replaceAll(">", ">")
|
|
||||||
.replaceAll('"', """)
|
|
||||||
.replaceAll("'", "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildCartKey(item: { category: string; seatId: any; hall: any; time: any; title: any; }) {
|
|
||||||
const infoText = item.category === "movie"
|
|
||||||
? `Sitz: ${item.seatId} (${item.hall})`
|
|
||||||
: item.time;
|
|
||||||
return `${item.title}-${item.hall}-${infoText}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDrinkItem(item: { category: string; title: any; hall: any; }) {
|
|
||||||
if (item.category !== "snack") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = String(item.title || "").toLowerCase();
|
|
||||||
const size = String(item.hall || "").toLowerCase();
|
|
||||||
const drinkKeywords = [
|
|
||||||
"cola",
|
|
||||||
"sprite",
|
|
||||||
"fanta",
|
|
||||||
"mezzo",
|
|
||||||
"fuze",
|
|
||||||
"wasser",
|
|
||||||
"getraenk",
|
|
||||||
"drink"
|
|
||||||
];
|
|
||||||
|
|
||||||
return drinkKeywords.some((word) => title.includes(word)) || size.includes("l");
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildItemInfo(item: { category: any; seatId?: any; hall: any; time?: any; title: any; }) {
|
|
||||||
if (item.category === "movie") {
|
|
||||||
return `
|
|
||||||
<div>Sitzplatz: ${escapeHtml(item.seatId || "-")}</div>
|
|
||||||
<div>Saal: ${escapeHtml(item.hall || "-")}</div>
|
|
||||||
<div>Uhrzeit: ${escapeHtml(item.time || "-")} Uhr</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDrinkItem(item)) {
|
|
||||||
return `
|
|
||||||
<div>Variante: ${escapeHtml(item.time || "-")}</div>
|
|
||||||
<div>Groesse: ${escapeHtml(item.hall || "-")}</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div>Kategorie: Snack</div>
|
|
||||||
<div>Variante: ${escapeHtml(item.time || "-")}</div>
|
|
||||||
<div>Groesse: ${escapeHtml(item.hall || "-")}</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function groupCartItems() {
|
|
||||||
const groups = new Map();
|
|
||||||
|
|
||||||
cart.forEach((item: { price?: any; category: string; seatId: any; hall: any; time: any; title: any; }) => {
|
|
||||||
const key = buildCartKey(item);
|
|
||||||
|
|
||||||
if (!groups.has(key)) {
|
|
||||||
groups.set(key, {
|
|
||||||
key,
|
|
||||||
quantity: 0,
|
|
||||||
total: 0,
|
|
||||||
item
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const group = groups.get(key);
|
|
||||||
group.quantity += 1;
|
|
||||||
group.total += Number(item.price || 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Array.from(groups.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveCart() {
|
|
||||||
localStorage.setItem("eagleCart", JSON.stringify(cart));
|
|
||||||
updateCartBadge();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateCartBadge() {
|
|
||||||
const cartBadge = document.getElementById("cart-badge");
|
|
||||||
|
|
||||||
if (!cartBadge) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cartBadge.innerText = cart.length;
|
|
||||||
cartBadge.classList.toggle("hidden", cart.length === 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderCart() {
|
|
||||||
const cartList = document.getElementById("cart-items-list");
|
|
||||||
const totalEl = document.getElementById("cart-total-right");
|
|
||||||
const vatEl = document.getElementById("cart-vat-right");
|
|
||||||
|
|
||||||
if (!cartList || !totalEl || !vatEl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(cart) || cart.length === 0) {
|
|
||||||
cartList.innerHTML = '<p>Dein Warenkorb ist leer.</p>';
|
|
||||||
totalEl.innerText = formatEuro(0);
|
|
||||||
vatEl.innerText = `inkl. 19% MwSt: ${formatEuro(0)}`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const groupedItems = groupCartItems();
|
|
||||||
|
|
||||||
const header = /*html*/`
|
|
||||||
<div class="cart-header-row">
|
|
||||||
<div class="col-amount">MENGE</div>
|
|
||||||
<div class="col-img">VORSCHAU</div>
|
|
||||||
<div class="col-product">NAME</div>
|
|
||||||
<div class="col-details">INFO</div>
|
|
||||||
<div class="col-price">PREIS</div>
|
|
||||||
<div class="col-action">AKTION</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const rows = groupedItems
|
|
||||||
.map((group) => {
|
|
||||||
const imageHtml = group.item.img
|
|
||||||
? /*html*/`<img class="cart-img-small" src="${escapeHtml(group.item.img)}" alt="${escapeHtml(group.item.title)}">`
|
|
||||||
: /*html*/`<div class="cart-img-fallback">Kein Bild</div>`;
|
|
||||||
const quantityHtml = group.item.category === "movie"
|
|
||||||
? /*html*/`<div class="qty-static" aria-label="Feste Ticketanzahl">${group.quantity}x</div>`
|
|
||||||
: /*html*/`
|
|
||||||
<div class="qty-stepper">
|
|
||||||
<button class="btn-qty" data-action="minus" data-key="${escapeHtml(group.key)}">-</button>
|
|
||||||
<span>${group.quantity}</span>
|
|
||||||
<button class="btn-qty" data-action="plus" data-key="${escapeHtml(group.key)}">+</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return /*html*/`
|
|
||||||
<div class="cart-item-row">
|
|
||||||
<div class="col-amount">
|
|
||||||
${quantityHtml}
|
|
||||||
</div>
|
|
||||||
<div class="col-img">${imageHtml}</div>
|
|
||||||
<div class="col-product">${escapeHtml(group.item.title)}</div>
|
|
||||||
<div class="col-details cart-item-info">${buildItemInfo(group.item)}</div>
|
|
||||||
<div class="col-price">${formatEuro(group.total)}</div>
|
|
||||||
<div class="col-action">
|
|
||||||
<button class="btn-delete-item" data-key="${escapeHtml(group.key)}" aria-label="Eintrag entfernen"><span aria-hidden="true">🗑️</span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
cartList.innerHTML = header + rows;
|
|
||||||
|
|
||||||
const total = cart.reduce((sum, item) => sum + Number(item.price || 0), 0);
|
|
||||||
const vat = total - total / 1.19;
|
|
||||||
|
|
||||||
totalEl.innerText = formatEuro(total);
|
|
||||||
vatEl.innerText = `inkl. 19% MwSt: ${formatEuro(vat)}`;
|
|
||||||
|
|
||||||
saveCart();
|
|
||||||
}
|
|
||||||
//@ts-ignore
|
|
||||||
window.removeItem = function removeItem(id: any) {
|
|
||||||
var localCart = cart.filter((item: { id: any; }) => item.id !== id);
|
|
||||||
saveCart();
|
|
||||||
renderCart();
|
|
||||||
};
|
|
||||||
|
|
||||||
//@ts-ignore
|
|
||||||
window.changeQty = function changeQty(title, delta): void {
|
|
||||||
if (delta > 0) {
|
|
||||||
const item = cart.find((entry: { title: any; }) => entry.title === title);
|
|
||||||
if (item) {
|
|
||||||
cart.push({ ...item, id: Date.now() + Math.random() });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const index = cart
|
|
||||||
.map((entry: { title: any; }) => entry.title)
|
|
||||||
.lastIndexOf(title);
|
|
||||||
if (index !== -1) {
|
|
||||||
cart.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveCart();
|
|
||||||
renderCart();
|
|
||||||
};
|
|
||||||
|
|
||||||
238
src/checkout.ts
238
src/checkout.ts
@@ -1,238 +0,0 @@
|
|||||||
import { currentUser, users } from "./account.js";
|
|
||||||
import { renderCart, saveCart } from "./cart.js";
|
|
||||||
import { cart, emptyCart, occupiedSeatsData } from "./main.js";
|
|
||||||
|
|
||||||
function formatCheckoutEuro(value: number) {
|
|
||||||
return `${Number(value || 0).toFixed(2).replace(".", ",")} EUR`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let selectedPaymentMethod = "";
|
|
||||||
let checkoutEventsBound = false;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
export 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 safeCart = Array.isArray(cart) ? cart : [];
|
|
||||||
const total = safeCart.reduce((sum, item) => sum + Number(item.price || 0), 0);
|
|
||||||
const vat = total - total / 1.19;
|
|
||||||
|
|
||||||
safeCart.forEach((item) => {
|
|
||||||
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.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 = (Array.isArray(cart) ? cart : []).filter((item) => item.category === "movie");
|
|
||||||
if (!moviesInCart.length) {
|
|
||||||
ticketContainer.innerHTML = "<p>Danke fuer deinen Einkauf!</p>";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainMovie = moviesInCart[0];
|
|
||||||
const matchingMovieSeats = moviesInCart
|
|
||||||
.filter((item) => item.title === mainMovie.title && item.time === mainMovie.time)
|
|
||||||
.map((item) => 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 = /*html*/`
|
|
||||||
<div class="luxury-ticket">
|
|
||||||
<div class="ticket-left">
|
|
||||||
<img src="${mainMovie.img}" 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 saveOrderForCurrentUser(orderItems: any[], orderTotal: any) {
|
|
||||||
if (typeof currentUser === "undefined" || !currentUser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof users === "undefined" || !Array.isArray(users)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const order = {
|
|
||||||
date: new Date().toLocaleString("de-DE"),
|
|
||||||
items: orderItems,
|
|
||||||
total: orderTotal,
|
|
||||||
paymentMethod: selectedPaymentMethod || "-"
|
|
||||||
};
|
|
||||||
|
|
||||||
//@ts-ignore
|
|
||||||
const userIndex = users.findIndex((entry) => entry.email === currentUser.email);
|
|
||||||
if (userIndex === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(users[userIndex].orders)) {
|
|
||||||
users[userIndex].orders = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
users[userIndex].orders.push(order);
|
|
||||||
localStorage.setItem("eagleUsers", JSON.stringify(users));
|
|
||||||
}
|
|
||||||
|
|
||||||
function reserveSeatsAfterPayment(orderItems: any[]) {
|
|
||||||
const movieItems = orderItems.filter((item) => item.category === "movie");
|
|
||||||
|
|
||||||
movieItems.forEach((item) => {
|
|
||||||
const key = `${item.hall}-${item.time}`;
|
|
||||||
if (!occupiedSeatsData[key]) {
|
|
||||||
occupiedSeatsData[key] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
occupiedSeatsData[key].push(item.seatId);
|
|
||||||
});
|
|
||||||
|
|
||||||
localStorage.setItem("eagleOccupied", JSON.stringify(occupiedSeatsData));
|
|
||||||
}
|
|
||||||
|
|
||||||
function completeCheckout() {
|
|
||||||
const orderItems = Array.isArray(cart) ? [...cart] : [];
|
|
||||||
const orderTotal = orderItems.reduce((sum, item) => sum + Number(item.price || 0), 0);
|
|
||||||
|
|
||||||
saveOrderForCurrentUser(orderItems, orderTotal);
|
|
||||||
reserveSeatsAfterPayment(orderItems);
|
|
||||||
|
|
||||||
emptyCart?.()
|
|
||||||
saveCart?.();
|
|
||||||
renderCart?.();
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindCheckoutEvents() {
|
|
||||||
if (checkoutEventsBound) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkoutEventsBound = true;
|
|
||||||
|
|
||||||
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");
|
|
||||||
//@ts-ignore
|
|
||||||
selectedPaymentMethod = method.dataset.method || "";
|
|
||||||
nextButton?.classList.remove("hidden");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
nextButton?.addEventListener("click", () => {
|
|
||||||
if (!selectedPaymentMethod) {
|
|
||||||
alert("Bitte waehle zuerst eine Zahlungsmethode aus.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCheckoutStep(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
backButton?.addEventListener("click", () => {
|
|
||||||
setCheckoutStep(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
payNowButton?.addEventListener("click", () => {
|
|
||||||
if (!Array.isArray(cart) || !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.addEventListener("DOMContentLoaded", bindCheckoutEvents);
|
|
||||||
14
src/components/Movie.astro
Normal file
14
src/components/Movie.astro
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
import { getTopMovies } from "../scripts/fetchMovies";
|
||||||
|
import type { MovieCatalog } from "../scripts/interfaces";
|
||||||
|
|
||||||
|
const movieProgram = await getTopMovies();
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
movieProgram.map((movie: MovieCatalog, programIndex: any) => {
|
||||||
|
{<div>{movie.genre}</div>}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
---
|
---
|
||||||
|
import { getTopMovies } from "../scripts/fetchMovies"
|
||||||
const api_token = import.meta.env.TMDB_API_TOKEN;
|
import Movie from "./Movie.astro"
|
||||||
|
console.log(await getTopMovies())
|
||||||
var api = await fetch("https://api.themoviedb.org/3/movie/11")
|
|
||||||
var request = await api.json
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<section id="movie-list-view" class="hidden">
|
<section id="movie-list-view" class="hidden">
|
||||||
<div class="container movie-list-shell">
|
<div class="container movie-list-shell">
|
||||||
<h1 class="list-title">Aktuelle Filme & Spielzeiten</h1>
|
<h1 class="list-title">Aktuelle Filme & Spielzeiten</h1>
|
||||||
<p class="list-subtitle">Alle Filme mit 7 Tagen Spielplan. Erste Vorstellung täglich ab 13:00 Uhr.</p>
|
<p class="list-subtitle">Alle Filme mit 7 Tagen Spielplan. Erste Vorstellung täglich ab 13:00 Uhr.</p>
|
||||||
<div>{ api_token }</div>
|
|
||||||
|
|
||||||
<!-- Movie List. -->
|
<!-- Movie List. -->
|
||||||
<div id="movie-program-list" class="movie-program-list"></div>
|
<!-- <div id="movie-program-list" class="movie-program-list"></div> -->
|
||||||
|
<Movie />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
export interface User {
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
email: string;
|
|
||||||
hashedPassword: string;
|
|
||||||
orders: any[]; // TODO: figure out proper array type of orders. Probably smartest do create an Order interface which this would be an array of
|
|
||||||
paymentMethods: any[]; // TODO: figure out proper array type of paymentMethods. create paymentMethod interface and make this an array of it
|
|
||||||
}
|
|
||||||
1112
src/main.ts
1112
src/main.ts
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
|||||||
import type { MovieCatalog } from "./interfaces";
|
|
||||||
// script will be removed since we switch to apis
|
|
||||||
|
|
||||||
|
|
||||||
export const movieCatalog:MovieCatalog[] = [
|
|
||||||
{
|
|
||||||
title: "Zoomania 2",
|
|
||||||
genre: "Animation",
|
|
||||||
duration: 148,
|
|
||||||
fsk: "6",
|
|
||||||
description: "In Walt Disney Animation Studios’ \"Zoomania 2\" geraten die tierischen Detektive Judy Hopps und Nick Wilde auf die rätselhafte Spur eines geheimnisvollen Reptils, das in Zoomania auftaucht und die Metropole völlig auf den Kopf stellt: Gary De’Snake! ",
|
|
||||||
poster: "img/Zoomania-2.jpg",
|
|
||||||
backdrop: "img/Zoomania-2.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Shelter",
|
|
||||||
genre: "Action, Abenteuer",
|
|
||||||
duration: 147,
|
|
||||||
fsk: "16",
|
|
||||||
description: "Michael Mason, ein untergetauchter Elite-Agent, lebt auf einer abgelegenen Insel in Schottland. Als er in einem schweren Sturm ein Mädchen vor dem Ertrinken rettet, setzt er damit eine Kette von Ereignissen in Gang, die sein Versteck enttarnen und ihn zurück in die Welt zwingen. Dort muss er nicht nur um das Überleben der Teenagerin kämpfen, sondern sich auch den Dämonen seiner Vergangenheit stellen…",
|
|
||||||
poster: "img/shelter.jpg",
|
|
||||||
backdrop: "img/shelter.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Mutiny",
|
|
||||||
genre: "Action, Abenteuer",
|
|
||||||
duration: 0,
|
|
||||||
fsk: "?",
|
|
||||||
description: "Als sein milliardenschwerer Boss vor seinen Augen einem Mordkomplott zum Opfer fällt, wird Cole Reed (Jason Statham) für das Verbrechen verantwortlich gemacht. Im Bestreben dessen Tod zu rächen und die Täter zu überführen, gelangt Reed an Bord eines Frachters und stößt dabei auf eine internationale Verschwörung. Im Alleingang startet Cole eine gnadenlose Racheaktion auf hoher See…",
|
|
||||||
poster: "img/mutiny.jpg",
|
|
||||||
backdrop: "img/mutiny.jpg"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
22
src/scripts/fetchMovies.ts
Normal file
22
src/scripts/fetchMovies.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type { MovieCatalog, ITMDBResponse } from "./interfaces";
|
||||||
|
|
||||||
|
export async function getTopMovies(): Promise<MovieCatalog[]> {
|
||||||
|
const API_KEY = import.meta.env.TMDB_API_KEY;
|
||||||
|
const IMAGE_BASE_URL = 'https://image.tmdb.org/t/p/w500'
|
||||||
|
|
||||||
|
const response = await fetch(`https://api.themoviedb.org/3/discover/movie?api_key=${API_KEY}`);
|
||||||
|
const data: ITMDBResponse = await response.json();
|
||||||
|
|
||||||
|
return data.results.slice(0, 5).map((movie) => ({
|
||||||
|
id: movie.id,
|
||||||
|
title: movie.title,
|
||||||
|
poster: `${IMAGE_BASE_URL}${movie.poster_path}`,
|
||||||
|
rating: movie.vote_average,
|
||||||
|
year: movie.release_date.split('-')[0], // Extracts just the year
|
||||||
|
genre: movie.genre,
|
||||||
|
duration: movie.duration,
|
||||||
|
fsk: movie.age_reccomendation,
|
||||||
|
description: movie.overview,
|
||||||
|
backdrop: movie.backdrop_path,
|
||||||
|
}));
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ export interface User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MovieCatalog {
|
export interface MovieCatalog {
|
||||||
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
genre: string;
|
genre: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
@@ -15,4 +16,29 @@ export interface MovieCatalog {
|
|||||||
description: string;
|
description: string;
|
||||||
poster: string;
|
poster: string;
|
||||||
backdrop: string;
|
backdrop: string;
|
||||||
|
rating: number;
|
||||||
|
year: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The shape of a single movie object from TMDb
|
||||||
|
interface ITMDBMovie {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
poster_path: string;
|
||||||
|
release_date: string;
|
||||||
|
vote_average: number;
|
||||||
|
overview: string;
|
||||||
|
genre: string;
|
||||||
|
duration: number;
|
||||||
|
age_reccomendation: string;
|
||||||
|
backdrop_path: string;
|
||||||
|
// ... add other fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// The shape of the API response
|
||||||
|
export interface ITMDBResponse {
|
||||||
|
page: number;
|
||||||
|
results: ITMDBMovie[];
|
||||||
|
total_pages: number;
|
||||||
|
total_results: number;
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,22 @@
|
|||||||
import { openBooking } from "./booking.js";
|
import { openBooking } from "./booking.js";
|
||||||
import { renderCart, saveCart, updateCartBadge } from "./cart.js";
|
import { renderCart, saveCart, updateCartBadge } from "./cart.js";
|
||||||
import { renderCheckout } from "./checkout.js";
|
import { renderCheckout } from "./checkout.js";
|
||||||
import { movieCatalog } from "./bigConsts.js";
|
import type { MovieCatalog as MovieCatalogInterface } from "./interfaces.js";
|
||||||
|
|
||||||
|
export const movieCatalog: MovieCatalogInterface[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "oh hell nah",
|
||||||
|
genre: "n",
|
||||||
|
duration: 3,
|
||||||
|
fsk: "jfd",
|
||||||
|
description: "jsss",
|
||||||
|
poster: "g",
|
||||||
|
backdrop: "f",
|
||||||
|
rating: 1,
|
||||||
|
year: "d",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
// Shared app state for legacy script files (account.js, booking.js, cart.js, checkout.js)
|
// Shared app state for legacy script files (account.js, booking.js, cart.js, checkout.js)
|
||||||
export const prices: Record<string, number> = { normal: 11.0, imax: 15.0, vip: 12.0, dbox: 16.0 };
|
export const prices: Record<string, number> = { normal: 11.0, imax: 15.0, vip: 12.0, dbox: 16.0 };
|
||||||
@@ -12,7 +27,7 @@ export var seatLayouts = {
|
|||||||
"Deluxe 1": { rows: 10, left: 7, right: 8, vipRows: [9], dbox: [{ r: 4, c: 5, w: 4 }] },
|
"Deluxe 1": { rows: 10, left: 7, right: 8, vipRows: [9], dbox: [{ r: 4, c: 5, w: 4 }] },
|
||||||
IMAX: { rows: 15, left: 10, right: 10, vipRows: [], dbox: [], isImax: true }
|
IMAX: { rows: 15, left: 10, right: 10, vipRows: [], dbox: [], isImax: true }
|
||||||
};
|
};
|
||||||
export var cart:any[] = JSON.parse(localStorage.getItem("eagleCart") || '[]');
|
export var cart: any[] = JSON.parse(localStorage.getItem("eagleCart") || '[]');
|
||||||
export var occupiedSeatsData = JSON.parse(localStorage.getItem("eagleOccupied") || '{}');
|
export var occupiedSeatsData = JSON.parse(localStorage.getItem("eagleOccupied") || '{}');
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
@@ -72,7 +87,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
let movieProgram: any = []; // TODO: Find type
|
let movieProgram: any = []; // TODO: Find type
|
||||||
let heroItems: any = []; // TODO: find Type
|
let heroItems: any = []; // TODO: find Type
|
||||||
let heroIndex = 0;
|
let heroIndex = 0;
|
||||||
let heroTimer:any = null; // TODO: find type
|
let heroTimer: any = null; // TODO: find type
|
||||||
|
|
||||||
const weekdayShort = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
|
const weekdayShort = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
|
||||||
|
|
||||||
@@ -91,7 +106,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const showMovieList = (programIndexToFocus:number = NaN) => {
|
const showMovieList = (programIndexToFocus: number = NaN) => {
|
||||||
hideAllViews();
|
hideAllViews();
|
||||||
views.list?.classList.remove("hidden");
|
views.list?.classList.remove("hidden");
|
||||||
|
|
||||||
@@ -205,7 +220,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const buildMovieProgram = () => {
|
const buildMovieProgram = () => {
|
||||||
//@ts-ignore
|
|
||||||
movieProgram = movieCatalog.map((movie: any, movieIndex: number) => ({
|
movieProgram = movieCatalog.map((movie: any, movieIndex: number) => ({
|
||||||
...movie,
|
...movie,
|
||||||
schedule: buildScheduleForMovie(movieIndex)
|
schedule: buildScheduleForMovie(movieIndex)
|
||||||
@@ -247,7 +261,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
`).join("");
|
`).join("");
|
||||||
|
|
||||||
if (ui.heroDots) {
|
if (ui.heroDots) {
|
||||||
ui.heroDots.innerHTML = heroItems.map((_:any, index: number) => `
|
ui.heroDots.innerHTML = heroItems.map((_: any, index: number) => `
|
||||||
<button type="button" class="hero-dot ${index === 0 ? "active" : ""}" data-hero-index="${index}"></button>
|
<button type="button" class="hero-dot ${index === 0 ? "active" : ""}" data-hero-index="${index}"></button>
|
||||||
`).join("");
|
`).join("");
|
||||||
|
|
||||||
@@ -406,14 +420,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
ui.linkSnacks?.addEventListener("click", (event) => {
|
ui.linkSnacks?.addEventListener("click", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (views.snacks) {
|
if (views.snacks) {
|
||||||
showStaticView(views.snacks);
|
showStaticView(views.snacks);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.linkAbout?.addEventListener("click", (event) => {
|
ui.linkAbout?.addEventListener("click", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (views.about) {
|
if (views.about) {
|
||||||
showStaticView(views.about);
|
showStaticView(views.about);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -444,7 +458,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const bindProgramActions = () => {
|
const bindProgramActions = () => {
|
||||||
views.moviesGrid?.addEventListener("click", (event:any) => {
|
views.moviesGrid?.addEventListener("click", (event: any) => {
|
||||||
const trigger = event.target.closest(".open-program-btn");
|
const trigger = event.target.closest(".open-program-btn");
|
||||||
if (!trigger) {
|
if (!trigger) {
|
||||||
return;
|
return;
|
||||||
@@ -454,7 +468,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
showMovieList(programIndex);
|
showMovieList(programIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.movieProgramList?.addEventListener("click", (event:any) => {
|
ui.movieProgramList?.addEventListener("click", (event: any) => {
|
||||||
const dayButton = event.target.closest(".program-day-tab");
|
const dayButton = event.target.closest(".program-day-tab");
|
||||||
if (!dayButton) {
|
if (!dayButton) {
|
||||||
return;
|
return;
|
||||||
@@ -736,7 +750,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const bindSnacksActions = () => {
|
const bindSnacksActions = () => {
|
||||||
ui.snacksView?.addEventListener("click", (event:any) => {
|
ui.snacksView?.addEventListener("click", (event: any) => {
|
||||||
const sizeChip = event.target.closest(".size-chip");
|
const sizeChip = event.target.closest(".size-chip");
|
||||||
if (!sizeChip) {
|
if (!sizeChip) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user