Rabatt-System, größerer Mengen-Stepper, Zahlungsart zuerst, echte HasiDog-Inhalte
- Neues Rabatt-System: Prozent-/Betrag-Rabatt mit optionalem Ablaufdatum, Mengenrabatt-Staffel pro Produkt, sowie site-weite Gutscheincodes. Wird live im Warenkorb/Checkout berechnet (src/data/rabatt.ts, src/data/gutscheine.ts, cart.ts) und ist im Adminbereich direkt unter dem Preis bearbeitbar (config.yml: "Rabatte" + neue Collection "Gutscheincodes"). Effektiver Preis + Rabatt-Badge auf Produktkarten und Produktseite sichtbar. - Mengen-Stepper auf der Produktseite vergrößert (bereits vorbereitet, jetzt mit Build/Test verifiziert). - Zahlungsart im Checkout auf Platz 1, mit markenfarbigen, fertig gestalteten Buttons (PayPal/Klarna/Banküberweisung). - Unterkategorie-Seiten zeigen jetzt VanVans eigene Produktbeschreibung und das echte Fotos des Produkts statt Platzhaltertext, mit kleinem Link zur HasiDog-Universum-Seite statt großem Button. Verifiziert per Puppeteer-Funktionstest gegen den echten Production-Build (Rabatt-Berechnung, Mengenrabatt, Gutschein-Einlösung, Checkout-Summary, keine Konsolenfehler).
This commit is contained in:
@@ -26,6 +26,15 @@ const t = useTranslations(lang);
|
||||
<aside class="card cart-summary">
|
||||
<h3>🧾 {t.cart.subtotal}</h3>
|
||||
<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">
|
||||
@@ -47,17 +56,18 @@ const t = useTranslations(lang);
|
||||
</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 }}>
|
||||
<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 }}>
|
||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
||||
window.__cartVars = { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang };
|
||||
window.__cartVars = { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel };
|
||||
</script>
|
||||
<script type="module">
|
||||
import { getCart, updateQuantity, removeFromCart, cartTotal } from "../../scripts/cart";
|
||||
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon } 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 } = window.__cartVars;
|
||||
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel } = window.__cartVars;
|
||||
const landSelect = document.getElementById("cart-land");
|
||||
|
||||
function render() {
|
||||
@@ -80,19 +90,23 @@ const t = useTranslations(lang);
|
||||
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 zeilenpreis = produkt ? effektiverZeilenpreis(produkt, i.menge) : i.menge * i.preis;
|
||||
const mengenProzent = produkt ? mengenrabattProzent(produkt, i.menge) : 0;
|
||||
const mengenBadge = mengenProzent > 0 ? `<div class="small mengenrabatt-badge">${mengenrabattTemplate.replace("__P__", mengenProzent)}</div>` : "";
|
||||
return `
|
||||
<div class="cart-item">
|
||||
${thumb}
|
||||
<div class="info">
|
||||
<strong>${i.name}</strong>
|
||||
<div class="small">${formatPrice(i.preis, cartLang)} ${perItemLabel}</div>
|
||||
<div class="small">${formatPrice(zeilenpreis / i.menge, cartLang)} ${perItemLabel}</div>
|
||||
${mengenBadge}
|
||||
</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(i.preis * i.menge, cartLang)}</div>
|
||||
<div class="line-total">${formatPrice(zeilenpreis, cartLang)}</div>
|
||||
<a href="#" class="remove" data-action="remove" data-slug="${i.slug}">✕ ${removeLabel}</a>
|
||||
</div>
|
||||
`;
|
||||
@@ -101,12 +115,30 @@ const t = useTranslations(lang);
|
||||
const subtotal = cartTotal();
|
||||
const remaining = Math.max(0, freeShipFrom - subtotal);
|
||||
const shipping = berechneVersandkosten(landSelect.value, cart.map((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(subtotal + 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();
|
||||
@@ -123,6 +155,15 @@ const t = useTranslations(lang);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user