Jeder Artikel im Warenkorb hat jetzt eine Checkbox "Jetzt kaufen". Nur angehakte Artikel zählen zur Zwischensumme, zum Versand, zum Gutschein und zum Checkout — abgewählte Artikel bleiben sichtbar (leicht abgedunkelt, mit "Bleibt im Warenkorb, wird noch nicht gekauft"-Hinweis) im Warenkorb liegen, ohne mitgekauft zu werden. Die Auswahl wird pro Artikel gespeichert (localStorage) und bleibt bis zur nächsten Änderung bestehen. Der Checkout übernimmt automatisch nur die ausgewählten Artikel. Verifiziert per Puppeteer: Abwählen eines Artikels reduziert Summe/ Rechnungsliste sofort korrekt, Checkout zeigt nur den ausgewählten Artikel, keine Konsolenfehler.
197 lines
10 KiB
Plaintext
197 lines
10 KiB
Plaintext
---
|
||
import Layout from "../../layouts/Layout.astro";
|
||
import type { Locale } from "../../i18n/config";
|
||
import { useTranslations } from "../../i18n/ui";
|
||
|
||
const lang: Locale = "de";
|
||
const t = useTranslations(lang);
|
||
---
|
||
<Layout title="Warenkorb" description="Dein Warenkorb bei Van's DIY & Bastelbedarf." lang={lang} path="/warenkorb/">
|
||
<section class="section-tight">
|
||
<div class="container">
|
||
<span class="eyebrow">{t.cart.eyebrow}</span>
|
||
<h1>{t.cart.title}</h1>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section-tight">
|
||
<div class="container">
|
||
<div id="cart-empty" class="card" style="display:none;">
|
||
<p>{t.cart.empty}</p>
|
||
<a class="btn btn-primary" href="/shop/">{t.common.browseShop}</a>
|
||
</div>
|
||
|
||
<div id="cart-content" class="cart-layout" style="display:none;">
|
||
<div class="card cart-items" id="cart-items"></div>
|
||
<aside class="card cart-summary">
|
||
<h3>🧾 {t.cart.subtotal}</h3>
|
||
<div id="summary-items" class="checkout-summary"></div>
|
||
<div class="summary-row"><span>{t.cart.subtotal}</span><span id="sum-subtotal">0,00 €</span></div>
|
||
<div class="summary-row discount-row" id="discount-row" style="display:none;"><span>{t.cart.discount}</span><span id="sum-discount">-0,00 €</span></div>
|
||
<div class="coupon-row">
|
||
<label for="coupon-input">{t.cart.couponLabel}</label>
|
||
<div class="coupon-input-group">
|
||
<input type="text" id="coupon-input" placeholder={t.cart.couponPlaceholder} />
|
||
<button type="button" id="coupon-apply" class="btn btn-outline btn-sm">{t.cart.couponApply}</button>
|
||
</div>
|
||
<p class="small" id="coupon-status"></p>
|
||
</div>
|
||
<div class="ship-country-row">
|
||
<label for="cart-land">📦 {t.checkout.country}</label>
|
||
<select id="cart-land">
|
||
<option value="de">Deutschland</option>
|
||
<option value="at">Österreich</option>
|
||
<option value="ch">Schweiz</option>
|
||
<option value="lu">Luxemburg</option>
|
||
</select>
|
||
</div>
|
||
<div class="summary-row"><span>{t.cart.shipping}</span><span id="sum-shipping">{t.cart.shippingCalculated}</span></div>
|
||
<p class="free-shipping-badge" id="shipping-hint"></p>
|
||
<hr class="divider" />
|
||
<div class="summary-row total"><span>{t.cart.total}</span><span id="sum-total">0,00 €</span></div>
|
||
<p class="small">{t.common.vatNote}</p>
|
||
<a class="btn btn-primary btn-block" href="/checkout/">{t.cart.toCheckout}</a>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</Layout>
|
||
|
||
<script define:vars={{ freeShipFrom: 75, removeLabel: t.cart.remove, perItemLabel: t.cart.perItem, remainingTemplate: t.cart.remaining("__V__"), freeShippingText: t.cart.freeShipping, freeLabel: t.checkout.free, cartLang: lang, mengenrabattTemplate: t.cart.mengenrabatt("__P__"), couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove, buyNowLabel: t.cart.buyNow, keptInCartLabel: t.cart.keptInCart }}>
|
||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
||
window.__cartVars = { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel };
|
||
</script>
|
||
<script type="module">
|
||
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, istAusgewaehlt, setAusgewaehlt } from "../../scripts/cart";
|
||
import { formatPrice } from "../../i18n/format";
|
||
import { getProduct, products } from "../../data/products";
|
||
import { berechneVersandkosten } from "../../data/versand";
|
||
import { effektiverZeilenpreis, mengenrabattProzent } from "../../data/rabatt";
|
||
|
||
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel } = window.__cartVars;
|
||
const landSelect = document.getElementById("cart-land");
|
||
|
||
function render() {
|
||
const cart = getCart();
|
||
const empty = document.getElementById("cart-empty");
|
||
const content = document.getElementById("cart-content");
|
||
const itemsEl = document.getElementById("cart-items");
|
||
|
||
if (cart.length === 0) {
|
||
empty.style.display = "block";
|
||
content.style.display = "none";
|
||
return;
|
||
}
|
||
empty.style.display = "none";
|
||
content.style.display = "grid";
|
||
|
||
const zeilen = cart.map((i) => {
|
||
const produkt = getProduct(i.slug);
|
||
const zeilenpreis = produkt ? effektiverZeilenpreis(produkt, i.menge) : i.menge * i.preis;
|
||
const mengenProzent = produkt ? mengenrabattProzent(produkt, i.menge) : 0;
|
||
return { item: i, produkt, zeilenpreis, mengenProzent };
|
||
});
|
||
|
||
itemsEl.innerHTML = zeilen.map(({ item: i, produkt, zeilenpreis, mengenProzent }) => {
|
||
const foto = produkt?.bilder?.[0];
|
||
const thumb = foto
|
||
? `<img class="thumb product-photo" src="${foto}" alt="${i.name}" loading="lazy" />`
|
||
: `<div class="img-placeholder thumb" role="img" aria-label="${i.name}"></div>`;
|
||
const mengenBadge = mengenProzent > 0 ? `<div class="small mengenrabatt-badge">${mengenrabattTemplate.replace("__P__", mengenProzent)}</div>` : "";
|
||
const ausgewaehlt = istAusgewaehlt(i);
|
||
const keptBadge = !ausgewaehlt ? `<div class="small kept-in-cart-badge">🕓 ${keptInCartLabel}</div>` : "";
|
||
return `
|
||
<div class="cart-item ${ausgewaehlt ? "" : "deselected"}">
|
||
<input type="checkbox" class="buy-toggle" data-slug="${i.slug}" ${ausgewaehlt ? "checked" : ""} aria-label="${buyNowLabel}" title="${buyNowLabel}" />
|
||
${thumb}
|
||
<div class="info">
|
||
<strong>${i.name}</strong>
|
||
<div class="small">${formatPrice(zeilenpreis / i.menge, cartLang)} ${perItemLabel}</div>
|
||
${mengenBadge}
|
||
${keptBadge}
|
||
</div>
|
||
<div class="qty-controls">
|
||
<button data-action="dec" data-slug="${i.slug}" aria-label="−">−</button>
|
||
<span>${i.menge}</span>
|
||
<button data-action="inc" data-slug="${i.slug}" aria-label="+">+</button>
|
||
</div>
|
||
<div class="line-total">${formatPrice(zeilenpreis, cartLang)}</div>
|
||
<a href="#" class="remove" data-action="remove" data-slug="${i.slug}">✕ ${removeLabel}</a>
|
||
</div>
|
||
`;
|
||
}).join("");
|
||
|
||
// Nur ausgewählte Artikel fließen in Rechnung/Zwischensumme/Versand/Checkout ein — abgewählte
|
||
// bleiben sichtbar im Warenkorb liegen (leicht abgedunkelt), ohne mitgekauft zu werden.
|
||
const ausgewaehlteZeilen = zeilen.filter(({ item: i }) => istAusgewaehlt(i));
|
||
|
||
// Rechnungs-Übersicht in der Zusammenfassung — dieselbe Positionsliste wie im Warenkorb
|
||
// links, nur kompakt als "Beleg" (Menge × Name → Zeilenpreis), passend zum Checkout.
|
||
document.getElementById("summary-items").innerHTML = ausgewaehlteZeilen.map(({ item: i, zeilenpreis }) =>
|
||
`<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(zeilenpreis, cartLang)}</span></div>`
|
||
).join("");
|
||
|
||
const subtotal = cartTotal();
|
||
const remaining = Math.max(0, freeShipFrom - subtotal);
|
||
const shipping = berechneVersandkosten(landSelect.value, ausgewaehlteZeilen.map(({ item: i }) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
||
const coupon = appliedCoupon();
|
||
const discount = couponDiscount(subtotal);
|
||
const total = Math.max(0, subtotal - discount) + shipping;
|
||
document.getElementById("sum-subtotal").textContent = formatPrice(subtotal, cartLang);
|
||
document.getElementById("sum-shipping").textContent = shipping === 0 ? freeLabel : formatPrice(shipping, cartLang);
|
||
document.getElementById("sum-total").textContent = formatPrice(total, cartLang);
|
||
document.getElementById("shipping-hint").textContent =
|
||
remaining > 0 ? remainingTemplate.replace("__V__", formatPrice(remaining, cartLang)) : `🩵 ${freeShippingText}`;
|
||
|
||
const discountRow = document.getElementById("discount-row");
|
||
const couponStatus = document.getElementById("coupon-status");
|
||
const couponInput = document.getElementById("coupon-input");
|
||
if (coupon && discount > 0) {
|
||
discountRow.style.display = "flex";
|
||
document.getElementById("sum-discount").textContent = "-" + formatPrice(discount, cartLang);
|
||
couponStatus.innerHTML = `✅ ${couponAppliedTemplate.replace("__C__", coupon.code)} <a href="#" id="coupon-clear">✕ ${couponRemoveLabel}</a>`;
|
||
couponInput.value = coupon.code;
|
||
const clearLink = document.getElementById("coupon-clear");
|
||
if (clearLink) clearLink.addEventListener("click", (e) => { e.preventDefault(); clearCoupon(); render(); });
|
||
} else {
|
||
discountRow.style.display = "none";
|
||
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
||
}
|
||
|
||
itemsEl.querySelectorAll("button, a.remove").forEach((el) => {
|
||
el.addEventListener("click", (e) => {
|
||
e.preventDefault();
|
||
const target = e.currentTarget;
|
||
const slug = target.dataset.slug;
|
||
const action = target.dataset.action;
|
||
const item = getCart().find((i) => i.slug === slug);
|
||
if (!item) return;
|
||
if (action === "inc") updateQuantity(slug, item.menge + 1);
|
||
if (action === "dec") updateQuantity(slug, item.menge - 1);
|
||
if (action === "remove") removeFromCart(slug);
|
||
render();
|
||
});
|
||
});
|
||
|
||
itemsEl.querySelectorAll(".buy-toggle").forEach((el) => {
|
||
el.addEventListener("change", (e) => {
|
||
setAusgewaehlt(e.currentTarget.dataset.slug, e.currentTarget.checked);
|
||
render();
|
||
});
|
||
});
|
||
}
|
||
|
||
document.getElementById("coupon-apply").addEventListener("click", () => {
|
||
const input = document.getElementById("coupon-input");
|
||
const code = input.value.trim();
|
||
if (!code) { clearCoupon(); render(); return; }
|
||
setCouponCode(code);
|
||
input.dataset.invalid = appliedCoupon() ? "0" : "1";
|
||
render();
|
||
});
|
||
|
||
render();
|
||
window.addEventListener("cart:changed", render);
|
||
landSelect.addEventListener("change", render);
|
||
</script>
|