Abosystem mit dauerhaften Rabatten und Teddy-Prämien
Drei Mitgliedschaftsstufen (Klein 4,99€/5%, Mittel 9,99€/10%, Groß 19,99€/20%) mit automatischem Bestellrabatt und Teddy-Prämien nach Meilensteinen (12-Monats- Rhythmus bzw. 6+12 bei Groß). Preise/Rabatte und alle Admin-Regeln (Kombinier- barkeit mit anderen Rabatten, Fortsetzung bei Wiederanmeldung, Monate über Stufen zusammenzählen, kostenloser Versand bei Prämie) sind über Decap CMS einstellbar (src/content/abo-einstellungen.json). Neuer "Mein Abo"-Bereich im Kundenkonto (6. Nav-Kachel) mit Plan-Vergleich, Live-Status (Laufzeit, nächste Zahlung, Rabatt, Fortschritt bis zur nächsten Prämie), Einlösen-Buttons für freigeschaltete Teddys, Kündigen/Wechseln sowie einer übersetzten Benachrichtigungsliste. Dazu ein extra hervorgehobener "Mein Abo verwalten"-Button neben Abmelden in der Kopfkarte. Warenkorb/Checkout rechnen den Abo-Rabatt korrekt in die Summe ein und stellen eingelöste Teddys als 0€-Gratisposition dar. Alles in 4 Sprachen (DE/EN/CH/FR). Unterwegs zwei echte Bugs gefunden und behoben: addMonateISO() vermischte lokale Zeit mit toISOString() (UTC) und verlor dadurch bei jeder Monats- verlängerung reproduzierbar einen Tag; die Checkout-Positionsliste zeigte für Gratis-Prämien fälschlich den vollen Preis statt 0€. Wie beim Treuebonus gilt: Speicherung aktuell im localStorage des Geräts (Phase 1) statt einer echten geräteübergreifenden Datenbank — folgt mit der Zahlungsanbindung in Phase 2. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
0cb7d3ca65
commit
208d995941
@@ -40,6 +40,7 @@ const t = useTranslations(lang);
|
||||
<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="summary-row discount-row" id="partner-discount-row" style="display:none;"><span>{t.cart.partnerDiscount}</span><span id="sum-partner-discount">-0,00 €</span></div>
|
||||
<div class="summary-row discount-row" id="loyalty-discount-row" style="display:none;"><span>{t.cart.loyaltyDiscount}</span><span id="sum-loyalty-discount">-0,00 €</span></div>
|
||||
<div class="summary-row discount-row" id="abo-discount-row" style="display:none;"><span>{t.cart.aboDiscount}</span><span id="sum-abo-discount">-0,00 €</span></div>
|
||||
<div class="coupon-row">
|
||||
<label for="coupon-input">{t.cart.couponLabel}</label>
|
||||
<div class="coupon-input-group">
|
||||
@@ -77,19 +78,19 @@ 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, 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, partnerCodeInvalid: t.cart.partnerCodeInvalid, partnerCodeAppliedTemplate: t.cart.partnerCodeApplied("__C__"), partnerCodeRemoveLabel: t.cart.partnerCodeRemove, loyaltySelectLabel: t.cart.loyaltySelectLabel, loyaltySelectedLabel: t.cart.loyaltySelectedLabel }}>
|
||||
<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, partnerCodeInvalid: t.cart.partnerCodeInvalid, partnerCodeAppliedTemplate: t.cart.partnerCodeApplied("__C__"), partnerCodeRemoveLabel: t.cart.partnerCodeRemove, loyaltySelectLabel: t.cart.loyaltySelectLabel, loyaltySelectedLabel: t.cart.loyaltySelectedLabel, gratisLabel: t.cart.gratisPraemie }}>
|
||||
// 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, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel, loyaltySelectLabel, loyaltySelectedLabel };
|
||||
window.__cartVars = { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel, loyaltySelectLabel, loyaltySelectedLabel, gratisLabel };
|
||||
</script>
|
||||
<script type="module">
|
||||
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, treuebonusDiscount, istAusgewaehlt, setAusgewaehlt } from "../../../scripts/cart";
|
||||
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, treuebonusDiscount, aboDiscount, istAusgewaehlt, setAusgewaehlt, zeilenpreisFuer } 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";
|
||||
import { mengenrabattProzent } from "../../../data/rabatt";
|
||||
import { getAccount, treueFortschritt, setTreuebonusArtikel } from "../../../scripts/account";
|
||||
|
||||
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel, loyaltySelectLabel, loyaltySelectedLabel } = window.__cartVars;
|
||||
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel, loyaltySelectLabel, loyaltySelectedLabel, gratisLabel } = window.__cartVars;
|
||||
const landSelect = document.getElementById("cart-land");
|
||||
|
||||
function render() {
|
||||
@@ -108,7 +109,7 @@ import { getAccount, treueFortschritt, setTreuebonusArtikel } from "../../../scr
|
||||
|
||||
const zeilen = cart.map((i) => {
|
||||
const produkt = getProduct(i.slug);
|
||||
const zeilenpreis = produkt ? effektiverZeilenpreis(produkt, i.menge) : i.menge * i.preis;
|
||||
const zeilenpreis = zeilenpreisFuer(i);
|
||||
const mengenProzent = produkt ? mengenrabattProzent(produkt, i.menge) : 0;
|
||||
return { item: i, produkt, zeilenpreis, mengenProzent };
|
||||
});
|
||||
@@ -133,15 +134,17 @@ import { getAccount, treueFortschritt, setTreuebonusArtikel } from "../../../scr
|
||||
const loyaltyControl = !treueAktiv ? "" : istTreueartikel
|
||||
? `<span class="loyalty-selected-badge">${loyaltySelectedLabel}</span>`
|
||||
: `<label class="loyalty-select"><input type="radio" name="loyalty-artikel" data-loyalty-slug="${i.slug}" /> ${loyaltySelectLabel}</label>`;
|
||||
const gratisBadge = i.gratis ? `<div class="small loyalty-selected-badge">🧸 ${gratisLabel}</div>` : "";
|
||||
return `
|
||||
<div class="cart-item ${ausgewaehlt ? "" : "deselected"} ${istTreueartikel ? "loyalty-selected" : ""}">
|
||||
<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>
|
||||
${!i.gratis ? `<div class="small">${formatPrice(zeilenpreis / i.menge, cartLang)} ${perItemLabel}</div>` : ""}
|
||||
${mengenBadge}
|
||||
${keptBadge}
|
||||
${gratisBadge}
|
||||
${loyaltyControl}
|
||||
</div>
|
||||
<div class="qty-controls">
|
||||
@@ -227,6 +230,17 @@ import { getAccount, treueFortschritt, setTreuebonusArtikel } from "../../../scr
|
||||
}
|
||||
}
|
||||
|
||||
const aboDiscountRow = document.getElementById("abo-discount-row");
|
||||
const aboDiscountBetrag = aboDiscount(subtotal);
|
||||
if (aboDiscountRow) {
|
||||
if (aboDiscountBetrag > 0) {
|
||||
aboDiscountRow.style.display = "flex";
|
||||
document.getElementById("sum-abo-discount").textContent = "-" + formatPrice(aboDiscountBetrag, cartLang);
|
||||
} else {
|
||||
aboDiscountRow.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
itemsEl.querySelectorAll("button, a.remove").forEach((el) => {
|
||||
el.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user