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:
@@ -18,6 +18,26 @@ const t = useTranslations(lang);
|
||||
<section class="section-tight">
|
||||
<div class="container checkout-layout">
|
||||
<form class="frm card" id="checkout-form">
|
||||
<h3>{t.checkout.step2}</h3>
|
||||
<div class="payment-options">
|
||||
<label class="pay-option" style="--brand-color:#009cde; --brand-bg:#003087;">
|
||||
<span class="pay-icon" style="background:#003087;">P</span>
|
||||
<span>PayPal</span>
|
||||
<input type="radio" name="pay" value="paypal" checked />
|
||||
</label>
|
||||
<label class="pay-option" style="--brand-color:#ffb3c7; --brand-bg:#17120f;">
|
||||
<span class="pay-icon" style="background:#17120f; color:#ffb3c7;">K</span>
|
||||
<span>Klarna</span>
|
||||
<input type="radio" name="pay" value="klarna" />
|
||||
</label>
|
||||
<label class="pay-option" style="--brand-color:#7fa8c9; --brand-bg:#3b5a76;">
|
||||
<span class="pay-icon" style="background:#3b5a76;">🏦</span>
|
||||
<span>{t.checkout.bankTransfer}</span>
|
||||
<input type="radio" name="pay" value="ueberweisung" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<hr class="divider" />
|
||||
<h3>{t.checkout.step1}</h3>
|
||||
<div><label for="email">{t.checkout.email}</label><input id="email" type="email" required /></div>
|
||||
<div class="grid grid-2">
|
||||
@@ -39,26 +59,6 @@ const t = useTranslations(lang);
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr class="divider" />
|
||||
<h3>{t.checkout.step2}</h3>
|
||||
<div class="payment-options">
|
||||
<label class="pay-option">
|
||||
<span class="pay-icon" aria-hidden="true">🅿️</span>
|
||||
<span>PayPal</span>
|
||||
<input type="radio" name="pay" value="paypal" checked />
|
||||
</label>
|
||||
<label class="pay-option">
|
||||
<span class="pay-icon" aria-hidden="true">💳</span>
|
||||
<span>Klarna</span>
|
||||
<input type="radio" name="pay" value="klarna" />
|
||||
</label>
|
||||
<label class="pay-option">
|
||||
<span class="pay-icon" aria-hidden="true">🏦</span>
|
||||
<span>{t.checkout.bankTransfer}</span>
|
||||
<input type="radio" name="pay" value="ueberweisung" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<hr class="divider" />
|
||||
<h3>{t.checkout.step3}</h3>
|
||||
<div id="checkout-summary" class="checkout-summary"></div>
|
||||
@@ -77,6 +77,15 @@ const t = useTranslations(lang);
|
||||
</form>
|
||||
|
||||
<aside class="card checkout-aside">
|
||||
<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>
|
||||
<hr class="divider" />
|
||||
<h3>{t.checkout.shippingHintTitle}</h3>
|
||||
<p class="small">{t.checkout.shippingHint}</p>
|
||||
<a class="small" href="/en/versand-zahlung/">{t.checkout.shippingLink}</a>
|
||||
@@ -87,15 +96,16 @@ const t = useTranslations(lang);
|
||||
|
||||
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/en/checkout/danke/", checkoutLang: lang }}>
|
||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang };
|
||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel };
|
||||
</script>
|
||||
<script type="module">
|
||||
import { getCart, cartTotal } from "../../../scripts/cart";
|
||||
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon } from "../../../scripts/cart";
|
||||
import { formatPrice } from "../../../i18n/format";
|
||||
import { products } from "../../../data/products";
|
||||
import { berechneVersandkosten } from "../../../data/versand";
|
||||
import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||
|
||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang } = window.__checkoutVars;
|
||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel } = window.__checkoutVars;
|
||||
|
||||
const summary = document.getElementById("checkout-summary");
|
||||
const landSelect = document.getElementById("land");
|
||||
@@ -105,17 +115,45 @@ const t = useTranslations(lang);
|
||||
function renderSummary() {
|
||||
const land = landSelect.value;
|
||||
const shipping = cart.length === 0 ? 0 : berechneVersandkosten(land, 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;
|
||||
|
||||
summary.innerHTML = `
|
||||
${cart.map((i) => `<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(i.preis * i.menge, checkoutLang)}</span></div>`).join("")}
|
||||
${cart.map((i) => {
|
||||
const produkt = products.find((p) => p.slug === i.slug);
|
||||
const zeilenpreis = produkt ? effektiverZeilenpreis(produkt, i.menge) : i.menge * i.preis;
|
||||
return `<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(zeilenpreis, checkoutLang)}</span></div>`;
|
||||
}).join("")}
|
||||
${coupon && discount > 0 ? `<div class="row"><span>${discountLabel}</span><span>-${formatPrice(discount, checkoutLang)}</span></div>` : ""}
|
||||
<div class="row"><span>${shippingLabel}</span><span>${shipping === 0 ? freeLabel : formatPrice(shipping, checkoutLang)}</span></div>
|
||||
<div class="row total"><span>${totalLabel}</span><span>${formatPrice(subtotal + shipping, checkoutLang)}</span></div>
|
||||
<div class="row total"><span>${totalLabel}</span><span>${formatPrice(total, checkoutLang)}</span></div>
|
||||
`;
|
||||
|
||||
const couponStatus = document.getElementById("coupon-status");
|
||||
const couponInput = document.getElementById("coupon-input");
|
||||
if (coupon && discount > 0) {
|
||||
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(); renderSummary(); });
|
||||
} else {
|
||||
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
||||
}
|
||||
}
|
||||
|
||||
renderSummary();
|
||||
landSelect.addEventListener("change", renderSummary);
|
||||
|
||||
document.getElementById("coupon-apply").addEventListener("click", () => {
|
||||
const input = document.getElementById("coupon-input");
|
||||
const code = input.value.trim();
|
||||
if (!code) { clearCoupon(); renderSummary(); return; }
|
||||
setCouponCode(code);
|
||||
input.dataset.invalid = appliedCoupon() ? "0" : "1";
|
||||
renderSummary();
|
||||
});
|
||||
|
||||
document.getElementById("checkout-form").addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
if (cart.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user