Neu: Partner-/Influencer-Rabattcodes (Warenkorb, Checkout, Admin)
Neben dem allgemeinen Gutscheincode gibt es jetzt einen zweiten, unabhängigen "Partner-Rabattcode"-Slot — für Codes, die VanVan gezielt mit einzelnen Streamer:innen/Content-Creator:innen aushandelt. Kund:innen lösen ihn genauso ein wie einen Gutschein (eigene, optisch identische Box direkt unter dem Gutschein im Warenkorb bzw. im Checkout), beide Codes können unabhängig voneinander gleichzeitig aktiv sein und ihre Rabatte werden addiert (auf die Zwischensumme gedeckelt, damit der Preis nie unter 0 € fällt). Admin (Sveltia CMS): neuer Bereich "🤝 Partner-Rabattcodes", getrennt von den allgemeinen Gutscheinen geführt. Pro Code lässt sich hinterlegen, zu welcher Partnerin/welchem Partner er gehört (nur für VanVans eigene Übersicht, wird Kund:innen nirgends gezeigt), Prozent- ODER Betrags- Rabatt, optionales Ablaufdatum, ein "Aktiv"-Schalter zum Pausieren ohne Löschen, sowie ein freies Notizfeld für die vereinbarten Konditionen (z.B. Provision) — echtes automatisches Auswerten, wer wie oft welchen Code einlöst, bräuchte ein eigenes Backend (Phase 2) und ist bewusst nicht Teil dieser rein statischen Umsetzung. Nebenbei einen bestehenden Bug behoben: die define:vars-Variablenliste im Checkout-Skript war bei EN/CH/FR unvollständig (discountLabel, couponInvalid, Klarna-/Bank-Texte fehlten) — dadurch wären dort Teile der Checkout-Seitenleiste als "undefined" gerendert worden. Jetzt an die vollständige Liste der DE-Version angeglichen. Neu erstellte Dateien lokal mit Test-Codes durchgespielt (Gutschein + Partnercode gleichzeitig eingelöst, Rabatte korrekt addiert, Admin- Formular mit allen Feldern geprüft) und wieder auf leere Listen zurückgesetzt vor dem Commit. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
45f64b95ee
commit
6fc64e0972
@@ -284,6 +284,36 @@ collections:
|
|||||||
- { label: "ODER: Rabatt als fester Betrag (€)", name: betrag, widget: number, value_type: float, step: 0.01, required: false }
|
- { label: "ODER: Rabatt als fester Betrag (€)", name: betrag, widget: number, value_type: float, step: 0.01, required: false }
|
||||||
- { label: "Gültig bis (leer = unbegrenzt)", name: bis, widget: datetime, format: "YYYY-MM-DD", date_format: "YYYY-MM-DD", time_format: false, required: false }
|
- { label: "Gültig bis (leer = unbegrenzt)", name: bis, widget: datetime, format: "YYYY-MM-DD", date_format: "YYYY-MM-DD", time_format: false, required: false }
|
||||||
|
|
||||||
|
- name: partnercodes
|
||||||
|
label: "🤝 Partner-Rabattcodes"
|
||||||
|
icon: handshake
|
||||||
|
files:
|
||||||
|
- name: codes
|
||||||
|
label: "Partner-Rabattcodes"
|
||||||
|
file: src/content/partnercodes.json
|
||||||
|
description: >
|
||||||
|
Eigene Rabattcodes für Kooperationen mit Streamer:innen, Content-Creator:innen o.ä.:
|
||||||
|
du handelst mit der Person einen Rabatt aus, den ihre Follower:innen mit diesem Code
|
||||||
|
beim Einkauf bekommen. Fachlich wie ein Gutscheincode, aber bewusst getrennt von den
|
||||||
|
allgemeinen Gutscheinen gelistet, damit du hier den Überblick behältst, welcher Code zu
|
||||||
|
welcher Partnerin/welchem Partner gehört. WICHTIG: Diese Seite kann noch nicht
|
||||||
|
automatisch mitzählen, wie oft ein Code eingelöst wurde (das bräuchte eine eigene
|
||||||
|
Datenbank, kommt evtl. in einer späteren Ausbaustufe) — nutze das Notizfeld unten, um
|
||||||
|
dir die vereinbarte Provision/Konditionen selbst zu merken.
|
||||||
|
fields:
|
||||||
|
- label: Partner-Rabattcodes
|
||||||
|
name: codes
|
||||||
|
widget: list
|
||||||
|
summary: "{{fields.code}} — {{fields.partner}}"
|
||||||
|
fields:
|
||||||
|
- { label: "Name der Partnerin/des Partners (z.B. Streamer-Name)", name: partner, widget: string, hint: "Nur für deine eigene Übersicht — wird Kund:innen nirgends angezeigt." }
|
||||||
|
- { label: "Code (z.B. MAXPLAYS15)", name: code, widget: string, hint: "Groß-/Kleinschreibung ist egal — wird automatisch angepasst." }
|
||||||
|
- { label: "Rabatt in Prozent (z.B. 15 für 15%)", name: prozent, widget: number, value_type: int, required: false }
|
||||||
|
- { label: "ODER: Rabatt als fester Betrag (€)", name: betrag, widget: number, value_type: float, step: 0.01, required: false }
|
||||||
|
- { label: "Gültig bis (leer = unbegrenzt)", name: bis, widget: datetime, format: "YYYY-MM-DD", date_format: "YYYY-MM-DD", time_format: false, required: false }
|
||||||
|
- { label: "Aktiv", name: aktiv, widget: boolean, default: true, hint: "Ausschalten, um den Code vorübergehend zu sperren, ohne ihn (und die Historie) zu löschen." }
|
||||||
|
- { label: "Notiz zu den Konditionen (z.B. vereinbarte Provision)", name: notiz, widget: text, required: false, hint: "Rein intern für dich — wirkt sich nicht auf die Website aus." }
|
||||||
|
|
||||||
- name: bankverbindung
|
- name: bankverbindung
|
||||||
label: "🏦 Bankverbindung"
|
label: "🏦 Bankverbindung"
|
||||||
icon: account_balance
|
icon: account_balance
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"codes": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Partner-/Influencer-Rabattcodes — eigene Codes, die VanVan z.B. mit Streamer:innen oder
|
||||||
|
// Content-Creator:innen aushandelt: Kund:innen bekommen damit Rabatt beim Einkauf, während VanVan
|
||||||
|
// im Adminbereich für jeden Code hinterlegt, zu welcher Partnerin/welchem Partner er gehört (für
|
||||||
|
// die eigene Übersicht, z.B. welche vereinbarte Provision dahintersteckt).
|
||||||
|
//
|
||||||
|
// Fachlich fast identisch zu einem normalen Gutschein (Rabatt auf den Warenwert), aber bewusst als
|
||||||
|
// eigene, separate Liste geführt — allgemeine Rabattaktionen (Gutscheine) und individuell mit
|
||||||
|
// einzelnen Partner:innen ausgehandelte Codes sollen im Admin nicht durcheinandergeraten.
|
||||||
|
//
|
||||||
|
// WICHTIG (Grenze von Phase 1 — reine statische Seite ohne eigenes Backend): Diese Codes können
|
||||||
|
// einen Rabatt gewähren, aber es gibt (noch) keine automatische Zähl-/Provisions-Auswertung, WER
|
||||||
|
// wie oft welchen Code eingelöst hat — das bräuchte einen echten Server (Phase 2, siehe
|
||||||
|
// Tech-Stack-Entscheidung im Projekt-Vault). Bis dahin: das `notiz`-Feld im Admin dient VanVan als
|
||||||
|
// Gedächtnisstütze für die vereinbarten Konditionen.
|
||||||
|
|
||||||
|
// @ts-ignore -- JSON-Import, zur Build-Zeit von Astro/Vite aufgelöst
|
||||||
|
import partnercodesRaw from "../content/partnercodes.json";
|
||||||
|
|
||||||
|
export interface Partnercode {
|
||||||
|
code: string;
|
||||||
|
/** Name der Partnerin/des Partners (Streamer:in, Creator:in, ...) — nur für VanVans eigene
|
||||||
|
* Übersicht im Admin, wird Kund:innen nirgends angezeigt. */
|
||||||
|
partner: string;
|
||||||
|
prozent?: number;
|
||||||
|
betrag?: number;
|
||||||
|
bis?: string; // ISO-Datum, optional
|
||||||
|
/** Auf false setzen, um einen Code zu pausieren, ohne ihn (und die Partner-Historie) zu löschen. */
|
||||||
|
aktiv?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const partnercodes = (partnercodesRaw as { codes: Partnercode[] }).codes ?? [];
|
||||||
|
|
||||||
|
function istGueltig(bis?: string): boolean {
|
||||||
|
if (!bis) return true;
|
||||||
|
return new Date() <= new Date(`${bis}T23:59:59`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sucht einen Partnercode anhand des eingegebenen Codes (Groß-/Kleinschreibung egal). Pausierte
|
||||||
|
* (aktiv: false) oder abgelaufene Codes gelten als nicht gefunden. */
|
||||||
|
export function findePartnercode(code: string): Partnercode | undefined {
|
||||||
|
const normalisiert = code.trim().toUpperCase();
|
||||||
|
return partnercodes.find(
|
||||||
|
(c) => c.code.toUpperCase() === normalisiert && c.aktiv !== false && istGueltig(c.bis)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Berechnet den Rabatt-Betrag in Euro für einen gegebenen Warenwert. */
|
||||||
|
export function partnercodeRabatt(code: Partnercode, warenwert: number): number {
|
||||||
|
if (typeof code.prozent === "number" && code.prozent > 0) {
|
||||||
|
return Math.min(warenwert, warenwert * (code.prozent / 100));
|
||||||
|
}
|
||||||
|
if (typeof code.betrag === "number" && code.betrag > 0) {
|
||||||
|
return Math.min(warenwert, code.betrag);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -155,6 +155,9 @@ export const ui = {
|
|||||||
couponLabel: "🎟️ Gutscheincode", couponPlaceholder: "Code eingeben", couponApply: "Einlösen", couponRemove: "Entfernen",
|
couponLabel: "🎟️ Gutscheincode", couponPlaceholder: "Code eingeben", couponApply: "Einlösen", couponRemove: "Entfernen",
|
||||||
buyNow: "Jetzt kaufen", keptInCart: "Bleibt im Warenkorb, wird noch nicht gekauft",
|
buyNow: "Jetzt kaufen", keptInCart: "Bleibt im Warenkorb, wird noch nicht gekauft",
|
||||||
couponInvalid: "Code ungültig oder abgelaufen.", couponApplied: (code: string) => `Gutschein „${code}" eingelöst`,
|
couponInvalid: "Code ungültig oder abgelaufen.", couponApplied: (code: string) => `Gutschein „${code}" eingelöst`,
|
||||||
|
partnerDiscount: "🤝 Partner-Rabatt",
|
||||||
|
partnerCodeLabel: "🤝 Partner-Rabattcode", partnerCodePlaceholder: "Code eingeben", partnerCodeApply: "Einlösen", partnerCodeRemove: "Entfernen",
|
||||||
|
partnerCodeInvalid: "Code ungültig oder abgelaufen.", partnerCodeApplied: (code: string) => `Partner-Code „${code}" eingelöst`,
|
||||||
},
|
},
|
||||||
checkout: {
|
checkout: {
|
||||||
eyebrow: "Checkout", title: "Bestellung abschließen",
|
eyebrow: "Checkout", title: "Bestellung abschließen",
|
||||||
@@ -338,6 +341,9 @@ export const ui = {
|
|||||||
couponLabel: "🎟️ Coupon code", couponPlaceholder: "Enter code", couponApply: "Apply", couponRemove: "Remove",
|
couponLabel: "🎟️ Coupon code", couponPlaceholder: "Enter code", couponApply: "Apply", couponRemove: "Remove",
|
||||||
buyNow: "Buy now", keptInCart: "Stays in cart, not purchased yet",
|
buyNow: "Buy now", keptInCart: "Stays in cart, not purchased yet",
|
||||||
couponInvalid: "Code invalid or expired.", couponApplied: (code: string) => `Coupon "${code}" applied`,
|
couponInvalid: "Code invalid or expired.", couponApplied: (code: string) => `Coupon "${code}" applied`,
|
||||||
|
partnerDiscount: "🤝 Partner discount",
|
||||||
|
partnerCodeLabel: "🤝 Partner discount code", partnerCodePlaceholder: "Enter code", partnerCodeApply: "Apply", partnerCodeRemove: "Remove",
|
||||||
|
partnerCodeInvalid: "Code invalid or expired.", partnerCodeApplied: (code: string) => `Partner code "${code}" applied`,
|
||||||
},
|
},
|
||||||
checkout: {
|
checkout: {
|
||||||
eyebrow: "Checkout", title: "Complete your order",
|
eyebrow: "Checkout", title: "Complete your order",
|
||||||
@@ -521,6 +527,9 @@ export const ui = {
|
|||||||
couponLabel: "🎟️ Gutschy-Code", couponPlaceholder: "Code igäh", couponApply: "Ilöse", couponRemove: "Ewägmache",
|
couponLabel: "🎟️ Gutschy-Code", couponPlaceholder: "Code igäh", couponApply: "Ilöse", couponRemove: "Ewägmache",
|
||||||
buyNow: "Jetzt chaufe", keptInCart: "Blibt im Warenchorb, wird no nid chauft",
|
buyNow: "Jetzt chaufe", keptInCart: "Blibt im Warenchorb, wird no nid chauft",
|
||||||
couponInvalid: "Code ungültig oder abgloffe.", couponApplied: (code: string) => `Gutschy „${code}" iglöst`,
|
couponInvalid: "Code ungültig oder abgloffe.", couponApplied: (code: string) => `Gutschy „${code}" iglöst`,
|
||||||
|
partnerDiscount: "🤝 Partner-Rabatt",
|
||||||
|
partnerCodeLabel: "🤝 Partner-Rabattcode", partnerCodePlaceholder: "Code igäh", partnerCodeApply: "Ilöse", partnerCodeRemove: "Ewägmache",
|
||||||
|
partnerCodeInvalid: "Code ungültig oder abgloffe.", partnerCodeApplied: (code: string) => `Partner-Code „${code}" iglöst`,
|
||||||
},
|
},
|
||||||
checkout: {
|
checkout: {
|
||||||
eyebrow: "Checkout", title: "Bschtellig abschliesse",
|
eyebrow: "Checkout", title: "Bschtellig abschliesse",
|
||||||
@@ -704,6 +713,9 @@ export const ui = {
|
|||||||
couponLabel: "🎟️ Code promo", couponPlaceholder: "Entrer le code", couponApply: "Valider", couponRemove: "Retirer",
|
couponLabel: "🎟️ Code promo", couponPlaceholder: "Entrer le code", couponApply: "Valider", couponRemove: "Retirer",
|
||||||
buyNow: "Acheter maintenant", keptInCart: "Reste dans le panier, pas encore acheté",
|
buyNow: "Acheter maintenant", keptInCart: "Reste dans le panier, pas encore acheté",
|
||||||
couponInvalid: "Code invalide ou expiré.", couponApplied: (code: string) => `Code promo « ${code} » appliqué`,
|
couponInvalid: "Code invalide ou expiré.", couponApplied: (code: string) => `Code promo « ${code} » appliqué`,
|
||||||
|
partnerDiscount: "🤝 Remise partenaire",
|
||||||
|
partnerCodeLabel: "🤝 Code partenaire", partnerCodePlaceholder: "Entrer le code", partnerCodeApply: "Valider", partnerCodeRemove: "Retirer",
|
||||||
|
partnerCodeInvalid: "Code invalide ou expiré.", partnerCodeApplied: (code: string) => `Code partenaire « ${code} » appliqué`,
|
||||||
},
|
},
|
||||||
checkout: {
|
checkout: {
|
||||||
eyebrow: "Paiement", title: "Finaliser la commande",
|
eyebrow: "Paiement", title: "Finaliser la commande",
|
||||||
|
|||||||
@@ -134,6 +134,15 @@ const t = useTranslations(lang);
|
|||||||
<p class="small" id="coupon-status"></p>
|
<p class="small" id="coupon-status"></p>
|
||||||
</div>
|
</div>
|
||||||
<hr class="divider stitched" />
|
<hr class="divider stitched" />
|
||||||
|
<div class="coupon-row">
|
||||||
|
<label for="partner-code-input">{t.cart.partnerCodeLabel}</label>
|
||||||
|
<div class="coupon-input-group">
|
||||||
|
<input type="text" id="partner-code-input" placeholder={t.cart.partnerCodePlaceholder} />
|
||||||
|
<button type="button" id="partner-code-apply" class="btn btn-outline btn-sm">{t.cart.partnerCodeApply}</button>
|
||||||
|
</div>
|
||||||
|
<p class="small" id="partner-code-status"></p>
|
||||||
|
</div>
|
||||||
|
<hr class="divider stitched" />
|
||||||
<h3>🚚 {t.checkout.shippingHintTitle}</h3>
|
<h3>🚚 {t.checkout.shippingHintTitle}</h3>
|
||||||
<p class="small">{t.checkout.shippingHint}</p>
|
<p class="small">{t.checkout.shippingHint}</p>
|
||||||
<a class="small" href="/ch/versand-zahlung/">{t.checkout.shippingLink}</a>
|
<a class="small" href="/ch/versand-zahlung/">{t.checkout.shippingLink}</a>
|
||||||
@@ -142,19 +151,19 @@ const t = useTranslations(lang);
|
|||||||
</section>
|
</section>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/ch/checkout/danke/", checkoutLang: lang }}>
|
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/ch/checkout/danke/", checkoutLang: lang, discountLabel: t.cart.discount, couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove, klarnaConnectLabel: t.checkout.klarnaConnect, klarnaConnectingLabel: t.checkout.klarnaConnecting, klarnaConnectedLabel: t.checkout.klarnaConnected, bankHolderLabel: t.checkout.bankHolder, bankIbanLabel: t.checkout.bankIban, bankBicLabel: t.checkout.bankBic, bankBankNameLabel: t.checkout.bankBankName, bankPlaceholderLabel: t.checkout.bankPlaceholder, partnerDiscountLabel: t.cart.partnerDiscount, partnerCodeInvalid: t.cart.partnerCodeInvalid, partnerCodeAppliedTemplate: t.cart.partnerCodeApplied("__C__"), partnerCodeRemoveLabel: t.cart.partnerCodeRemove }}>
|
||||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
||||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel };
|
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel, partnerDiscountLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel };
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, istAusgewaehlt } from "../../../scripts/cart";
|
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, istAusgewaehlt } from "../../../scripts/cart";
|
||||||
import { formatPrice } from "../../../i18n/format";
|
import { formatPrice } from "../../../i18n/format";
|
||||||
import { products } from "../../../data/products";
|
import { products } from "../../../data/products";
|
||||||
import { berechneVersandkosten } from "../../../data/versand";
|
import { berechneVersandkosten } from "../../../data/versand";
|
||||||
import { effektiverZeilenpreis } from "../../../data/rabatt";
|
import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||||
import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankverbindung";
|
import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankverbindung";
|
||||||
|
|
||||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel } = window.__checkoutVars;
|
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel, partnerDiscountLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel } = window.__checkoutVars;
|
||||||
|
|
||||||
const summary = document.getElementById("checkout-summary");
|
const summary = document.getElementById("checkout-summary");
|
||||||
const landSelect = document.getElementById("land");
|
const landSelect = document.getElementById("land");
|
||||||
@@ -169,7 +178,9 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
const shipping = cart.length === 0 ? 0 : berechneVersandkosten(land, cart.map((i) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
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 coupon = appliedCoupon();
|
||||||
const discount = couponDiscount(subtotal);
|
const discount = couponDiscount(subtotal);
|
||||||
const total = Math.max(0, subtotal - discount) + shipping;
|
const partnerCode = appliedPartnerCode();
|
||||||
|
const partnerDiscount = partnerCodeDiscount(subtotal);
|
||||||
|
const total = Math.max(0, subtotal - totalDiscount(subtotal)) + shipping;
|
||||||
currentTotal = total;
|
currentTotal = total;
|
||||||
|
|
||||||
summary.innerHTML = `
|
summary.innerHTML = `
|
||||||
@@ -179,6 +190,7 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
return `<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(zeilenpreis, checkoutLang)}</span></div>`;
|
return `<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(zeilenpreis, checkoutLang)}</span></div>`;
|
||||||
}).join("")}
|
}).join("")}
|
||||||
${coupon && discount > 0 ? `<div class="row"><span>${discountLabel}</span><span>-${formatPrice(discount, checkoutLang)}</span></div>` : ""}
|
${coupon && discount > 0 ? `<div class="row"><span>${discountLabel}</span><span>-${formatPrice(discount, checkoutLang)}</span></div>` : ""}
|
||||||
|
${partnerCode && partnerDiscount > 0 ? `<div class="row"><span>${partnerDiscountLabel}</span><span>-${formatPrice(partnerDiscount, checkoutLang)}</span></div>` : ""}
|
||||||
<div class="row"><span>${shippingLabel}</span><span>${shipping === 0 ? freeLabel : formatPrice(shipping, 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(total, checkoutLang)}</span></div>
|
<div class="row total"><span>${totalLabel}</span><span>${formatPrice(total, checkoutLang)}</span></div>
|
||||||
`;
|
`;
|
||||||
@@ -193,6 +205,17 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
} else {
|
} else {
|
||||||
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const partnerStatus = document.getElementById("partner-code-status");
|
||||||
|
const partnerInput = document.getElementById("partner-code-input");
|
||||||
|
if (partnerCode && partnerDiscount > 0) {
|
||||||
|
partnerStatus.innerHTML = `✅ ${partnerCodeAppliedTemplate.replace("__C__", partnerCode.code)} <a href="#" id="partner-code-clear">✕ ${partnerCodeRemoveLabel}</a>`;
|
||||||
|
partnerInput.value = partnerCode.code;
|
||||||
|
const partnerClearLink = document.getElementById("partner-code-clear");
|
||||||
|
if (partnerClearLink) partnerClearLink.addEventListener("click", (e) => { e.preventDefault(); clearPartnerCode(); renderSummary(); });
|
||||||
|
} else {
|
||||||
|
partnerStatus.textContent = partnerInput.dataset.invalid === "1" ? partnerCodeInvalid : "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSummary();
|
renderSummary();
|
||||||
@@ -274,6 +297,15 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
renderSummary();
|
renderSummary();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("partner-code-apply").addEventListener("click", () => {
|
||||||
|
const input = document.getElementById("partner-code-input");
|
||||||
|
const code = input.value.trim();
|
||||||
|
if (!code) { clearPartnerCode(); renderSummary(); return; }
|
||||||
|
setPartnerCode(code);
|
||||||
|
input.dataset.invalid = appliedPartnerCode() ? "0" : "1";
|
||||||
|
renderSummary();
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById("checkout-form").addEventListener("submit", (e) => {
|
document.getElementById("checkout-form").addEventListener("submit", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (cart.length === 0) {
|
if (cart.length === 0) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const t = useTranslations(lang);
|
|||||||
<div id="summary-items" class="checkout-summary"></div>
|
<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"><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="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="coupon-row">
|
<div class="coupon-row">
|
||||||
<label for="coupon-input">{t.cart.couponLabel}</label>
|
<label for="coupon-input">{t.cart.couponLabel}</label>
|
||||||
<div class="coupon-input-group">
|
<div class="coupon-input-group">
|
||||||
@@ -36,6 +37,14 @@ const t = useTranslations(lang);
|
|||||||
</div>
|
</div>
|
||||||
<p class="small" id="coupon-status"></p>
|
<p class="small" id="coupon-status"></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="coupon-row">
|
||||||
|
<label for="partner-code-input">{t.cart.partnerCodeLabel}</label>
|
||||||
|
<div class="coupon-input-group">
|
||||||
|
<input type="text" id="partner-code-input" placeholder={t.cart.partnerCodePlaceholder} />
|
||||||
|
<button type="button" id="partner-code-apply" class="btn btn-outline btn-sm">{t.cart.partnerCodeApply}</button>
|
||||||
|
</div>
|
||||||
|
<p class="small" id="partner-code-status"></p>
|
||||||
|
</div>
|
||||||
<div class="ship-country-row">
|
<div class="ship-country-row">
|
||||||
<label for="cart-land">📦 {t.checkout.country}</label>
|
<label for="cart-land">📦 {t.checkout.country}</label>
|
||||||
<select id="cart-land">
|
<select id="cart-land">
|
||||||
@@ -57,18 +66,18 @@ const t = useTranslations(lang);
|
|||||||
</section>
|
</section>
|
||||||
</Layout>
|
</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 }}>
|
<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 }}>
|
||||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
// 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 };
|
window.__cartVars = { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel };
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, istAusgewaehlt, setAusgewaehlt } from "../../../scripts/cart";
|
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, istAusgewaehlt, setAusgewaehlt } from "../../../scripts/cart";
|
||||||
import { formatPrice } from "../../../i18n/format";
|
import { formatPrice } from "../../../i18n/format";
|
||||||
import { getProduct, products } from "../../../data/products";
|
import { getProduct, products } from "../../../data/products";
|
||||||
import { berechneVersandkosten } from "../../../data/versand";
|
import { berechneVersandkosten } from "../../../data/versand";
|
||||||
import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt";
|
import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt";
|
||||||
|
|
||||||
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel } = window.__cartVars;
|
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel } = window.__cartVars;
|
||||||
const landSelect = document.getElementById("cart-land");
|
const landSelect = document.getElementById("cart-land");
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
@@ -136,7 +145,9 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt
|
|||||||
const shipping = berechneVersandkosten(landSelect.value, ausgewaehlteZeilen.map(({ item: i }) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
const shipping = berechneVersandkosten(landSelect.value, ausgewaehlteZeilen.map(({ item: i }) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
||||||
const coupon = appliedCoupon();
|
const coupon = appliedCoupon();
|
||||||
const discount = couponDiscount(subtotal);
|
const discount = couponDiscount(subtotal);
|
||||||
const total = Math.max(0, subtotal - discount) + shipping;
|
const partnerCode = appliedPartnerCode();
|
||||||
|
const partnerDiscount = partnerCodeDiscount(subtotal);
|
||||||
|
const total = Math.max(0, subtotal - totalDiscount(subtotal)) + shipping;
|
||||||
document.getElementById("sum-subtotal").textContent = formatPrice(subtotal, cartLang);
|
document.getElementById("sum-subtotal").textContent = formatPrice(subtotal, cartLang);
|
||||||
document.getElementById("sum-shipping").textContent = shipping === 0 ? freeLabel : formatPrice(shipping, cartLang);
|
document.getElementById("sum-shipping").textContent = shipping === 0 ? freeLabel : formatPrice(shipping, cartLang);
|
||||||
document.getElementById("sum-total").textContent = formatPrice(total, cartLang);
|
document.getElementById("sum-total").textContent = formatPrice(total, cartLang);
|
||||||
@@ -158,6 +169,21 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt
|
|||||||
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const partnerDiscountRow = document.getElementById("partner-discount-row");
|
||||||
|
const partnerStatus = document.getElementById("partner-code-status");
|
||||||
|
const partnerInput = document.getElementById("partner-code-input");
|
||||||
|
if (partnerCode && partnerDiscount > 0) {
|
||||||
|
partnerDiscountRow.style.display = "flex";
|
||||||
|
document.getElementById("sum-partner-discount").textContent = "-" + formatPrice(partnerDiscount, cartLang);
|
||||||
|
partnerStatus.innerHTML = `✅ ${partnerCodeAppliedTemplate.replace("__C__", partnerCode.code)} <a href="#" id="partner-code-clear">✕ ${partnerCodeRemoveLabel}</a>`;
|
||||||
|
partnerInput.value = partnerCode.code;
|
||||||
|
const partnerClearLink = document.getElementById("partner-code-clear");
|
||||||
|
if (partnerClearLink) partnerClearLink.addEventListener("click", (e) => { e.preventDefault(); clearPartnerCode(); render(); });
|
||||||
|
} else {
|
||||||
|
partnerDiscountRow.style.display = "none";
|
||||||
|
partnerStatus.textContent = partnerInput.dataset.invalid === "1" ? partnerCodeInvalid : "";
|
||||||
|
}
|
||||||
|
|
||||||
itemsEl.querySelectorAll("button, a.remove").forEach((el) => {
|
itemsEl.querySelectorAll("button, a.remove").forEach((el) => {
|
||||||
el.addEventListener("click", (e) => {
|
el.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -195,6 +221,15 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt
|
|||||||
render();
|
render();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("partner-code-apply").addEventListener("click", () => {
|
||||||
|
const input = document.getElementById("partner-code-input");
|
||||||
|
const code = input.value.trim();
|
||||||
|
if (!code) { clearPartnerCode(); render(); return; }
|
||||||
|
setPartnerCode(code);
|
||||||
|
input.dataset.invalid = appliedPartnerCode() ? "0" : "1";
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
render();
|
render();
|
||||||
window.addEventListener("cart:changed", render);
|
window.addEventListener("cart:changed", render);
|
||||||
landSelect.addEventListener("change", render);
|
landSelect.addEventListener("change", render);
|
||||||
|
|||||||
@@ -134,6 +134,15 @@ const t = useTranslations(lang);
|
|||||||
<p class="small" id="coupon-status"></p>
|
<p class="small" id="coupon-status"></p>
|
||||||
</div>
|
</div>
|
||||||
<hr class="divider stitched" />
|
<hr class="divider stitched" />
|
||||||
|
<div class="coupon-row">
|
||||||
|
<label for="partner-code-input">{t.cart.partnerCodeLabel}</label>
|
||||||
|
<div class="coupon-input-group">
|
||||||
|
<input type="text" id="partner-code-input" placeholder={t.cart.partnerCodePlaceholder} />
|
||||||
|
<button type="button" id="partner-code-apply" class="btn btn-outline btn-sm">{t.cart.partnerCodeApply}</button>
|
||||||
|
</div>
|
||||||
|
<p class="small" id="partner-code-status"></p>
|
||||||
|
</div>
|
||||||
|
<hr class="divider stitched" />
|
||||||
<h3>🚚 {t.checkout.shippingHintTitle}</h3>
|
<h3>🚚 {t.checkout.shippingHintTitle}</h3>
|
||||||
<p class="small">{t.checkout.shippingHint}</p>
|
<p class="small">{t.checkout.shippingHint}</p>
|
||||||
<a class="small" href="/versand-zahlung/">{t.checkout.shippingLink}</a>
|
<a class="small" href="/versand-zahlung/">{t.checkout.shippingLink}</a>
|
||||||
@@ -142,22 +151,22 @@ const t = useTranslations(lang);
|
|||||||
</section>
|
</section>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/checkout/danke/", checkoutLang: lang, discountLabel: t.cart.discount, couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove, klarnaConnectLabel: t.checkout.klarnaConnect, klarnaConnectingLabel: t.checkout.klarnaConnecting, klarnaConnectedLabel: t.checkout.klarnaConnected, bankHolderLabel: t.checkout.bankHolder, bankIbanLabel: t.checkout.bankIban, bankBicLabel: t.checkout.bankBic, bankBankNameLabel: t.checkout.bankBankName, bankPlaceholderLabel: t.checkout.bankPlaceholder }}>
|
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/checkout/danke/", checkoutLang: lang, discountLabel: t.cart.discount, couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove, klarnaConnectLabel: t.checkout.klarnaConnect, klarnaConnectingLabel: t.checkout.klarnaConnecting, klarnaConnectedLabel: t.checkout.klarnaConnected, bankHolderLabel: t.checkout.bankHolder, bankIbanLabel: t.checkout.bankIban, bankBicLabel: t.checkout.bankBic, bankBankNameLabel: t.checkout.bankBankName, bankPlaceholderLabel: t.checkout.bankPlaceholder, partnerDiscountLabel: t.cart.partnerDiscount, partnerCodeInvalid: t.cart.partnerCodeInvalid, partnerCodeAppliedTemplate: t.cart.partnerCodeApplied("__C__"), partnerCodeRemoveLabel: t.cart.partnerCodeRemove }}>
|
||||||
// WICHTIG: define:vars-Skripte werden von Astro in ein IIFE gepackt (kein ES-Modul) — echte
|
// WICHTIG: define:vars-Skripte werden von Astro in ein IIFE gepackt (kein ES-Modul) — echte
|
||||||
// `import`-Anweisungen würden hier zur Laufzeit mit "Cannot use import statement outside a
|
// `import`-Anweisungen würden hier zur Laufzeit mit "Cannot use import statement outside a
|
||||||
// module" fehlschlagen. Deshalb hier nur die vom Server gerenderten Werte auf window ablegen,
|
// module" fehlschlagen. Deshalb hier nur die vom Server gerenderten Werte auf window ablegen,
|
||||||
// die eigentliche Logik läuft im separaten <script type="module"> darunter.
|
// die eigentliche Logik läuft im separaten <script type="module"> darunter.
|
||||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel };
|
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel, partnerDiscountLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel };
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, istAusgewaehlt } from "../../scripts/cart";
|
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, istAusgewaehlt } from "../../scripts/cart";
|
||||||
import { formatPrice } from "../../i18n/format";
|
import { formatPrice } from "../../i18n/format";
|
||||||
import { products } from "../../data/products";
|
import { products } from "../../data/products";
|
||||||
import { berechneVersandkosten } from "../../data/versand";
|
import { berechneVersandkosten } from "../../data/versand";
|
||||||
import { effektiverZeilenpreis } from "../../data/rabatt";
|
import { effektiverZeilenpreis } from "../../data/rabatt";
|
||||||
import { bankverbindung, bankverbindungVollstaendig } from "../../data/bankverbindung";
|
import { bankverbindung, bankverbindungVollstaendig } from "../../data/bankverbindung";
|
||||||
|
|
||||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel } = window.__checkoutVars;
|
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel, partnerDiscountLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel } = window.__checkoutVars;
|
||||||
|
|
||||||
const summary = document.getElementById("checkout-summary");
|
const summary = document.getElementById("checkout-summary");
|
||||||
const landSelect = document.getElementById("land");
|
const landSelect = document.getElementById("land");
|
||||||
@@ -172,7 +181,9 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../data/bankverbi
|
|||||||
const shipping = cart.length === 0 ? 0 : berechneVersandkosten(land, cart.map((i) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
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 coupon = appliedCoupon();
|
||||||
const discount = couponDiscount(subtotal);
|
const discount = couponDiscount(subtotal);
|
||||||
const total = Math.max(0, subtotal - discount) + shipping;
|
const partnerCode = appliedPartnerCode();
|
||||||
|
const partnerDiscount = partnerCodeDiscount(subtotal);
|
||||||
|
const total = Math.max(0, subtotal - totalDiscount(subtotal)) + shipping;
|
||||||
currentTotal = total;
|
currentTotal = total;
|
||||||
|
|
||||||
summary.innerHTML = `
|
summary.innerHTML = `
|
||||||
@@ -182,6 +193,7 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../data/bankverbi
|
|||||||
return `<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(zeilenpreis, checkoutLang)}</span></div>`;
|
return `<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(zeilenpreis, checkoutLang)}</span></div>`;
|
||||||
}).join("")}
|
}).join("")}
|
||||||
${coupon && discount > 0 ? `<div class="row"><span>${discountLabel}</span><span>-${formatPrice(discount, checkoutLang)}</span></div>` : ""}
|
${coupon && discount > 0 ? `<div class="row"><span>${discountLabel}</span><span>-${formatPrice(discount, checkoutLang)}</span></div>` : ""}
|
||||||
|
${partnerCode && partnerDiscount > 0 ? `<div class="row"><span>${partnerDiscountLabel}</span><span>-${formatPrice(partnerDiscount, checkoutLang)}</span></div>` : ""}
|
||||||
<div class="row"><span>${shippingLabel}</span><span>${shipping === 0 ? freeLabel : formatPrice(shipping, 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(total, checkoutLang)}</span></div>
|
<div class="row total"><span>${totalLabel}</span><span>${formatPrice(total, checkoutLang)}</span></div>
|
||||||
`;
|
`;
|
||||||
@@ -196,6 +208,17 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../data/bankverbi
|
|||||||
} else {
|
} else {
|
||||||
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const partnerStatus = document.getElementById("partner-code-status");
|
||||||
|
const partnerInput = document.getElementById("partner-code-input");
|
||||||
|
if (partnerCode && partnerDiscount > 0) {
|
||||||
|
partnerStatus.innerHTML = `✅ ${partnerCodeAppliedTemplate.replace("__C__", partnerCode.code)} <a href="#" id="partner-code-clear">✕ ${partnerCodeRemoveLabel}</a>`;
|
||||||
|
partnerInput.value = partnerCode.code;
|
||||||
|
const partnerClearLink = document.getElementById("partner-code-clear");
|
||||||
|
if (partnerClearLink) partnerClearLink.addEventListener("click", (e) => { e.preventDefault(); clearPartnerCode(); renderSummary(); });
|
||||||
|
} else {
|
||||||
|
partnerStatus.textContent = partnerInput.dataset.invalid === "1" ? partnerCodeInvalid : "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSummary();
|
renderSummary();
|
||||||
@@ -277,6 +300,15 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../data/bankverbi
|
|||||||
renderSummary();
|
renderSummary();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("partner-code-apply").addEventListener("click", () => {
|
||||||
|
const input = document.getElementById("partner-code-input");
|
||||||
|
const code = input.value.trim();
|
||||||
|
if (!code) { clearPartnerCode(); renderSummary(); return; }
|
||||||
|
setPartnerCode(code);
|
||||||
|
input.dataset.invalid = appliedPartnerCode() ? "0" : "1";
|
||||||
|
renderSummary();
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById("checkout-form").addEventListener("submit", (e) => {
|
document.getElementById("checkout-form").addEventListener("submit", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (cart.length === 0) {
|
if (cart.length === 0) {
|
||||||
|
|||||||
@@ -134,6 +134,15 @@ const t = useTranslations(lang);
|
|||||||
<p class="small" id="coupon-status"></p>
|
<p class="small" id="coupon-status"></p>
|
||||||
</div>
|
</div>
|
||||||
<hr class="divider stitched" />
|
<hr class="divider stitched" />
|
||||||
|
<div class="coupon-row">
|
||||||
|
<label for="partner-code-input">{t.cart.partnerCodeLabel}</label>
|
||||||
|
<div class="coupon-input-group">
|
||||||
|
<input type="text" id="partner-code-input" placeholder={t.cart.partnerCodePlaceholder} />
|
||||||
|
<button type="button" id="partner-code-apply" class="btn btn-outline btn-sm">{t.cart.partnerCodeApply}</button>
|
||||||
|
</div>
|
||||||
|
<p class="small" id="partner-code-status"></p>
|
||||||
|
</div>
|
||||||
|
<hr class="divider stitched" />
|
||||||
<h3>🚚 {t.checkout.shippingHintTitle}</h3>
|
<h3>🚚 {t.checkout.shippingHintTitle}</h3>
|
||||||
<p class="small">{t.checkout.shippingHint}</p>
|
<p class="small">{t.checkout.shippingHint}</p>
|
||||||
<a class="small" href="/en/versand-zahlung/">{t.checkout.shippingLink}</a>
|
<a class="small" href="/en/versand-zahlung/">{t.checkout.shippingLink}</a>
|
||||||
@@ -142,19 +151,19 @@ const t = useTranslations(lang);
|
|||||||
</section>
|
</section>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<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 }}>
|
<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, discountLabel: t.cart.discount, couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove, klarnaConnectLabel: t.checkout.klarnaConnect, klarnaConnectingLabel: t.checkout.klarnaConnecting, klarnaConnectedLabel: t.checkout.klarnaConnected, bankHolderLabel: t.checkout.bankHolder, bankIbanLabel: t.checkout.bankIban, bankBicLabel: t.checkout.bankBic, bankBankNameLabel: t.checkout.bankBankName, bankPlaceholderLabel: t.checkout.bankPlaceholder, partnerDiscountLabel: t.cart.partnerDiscount, partnerCodeInvalid: t.cart.partnerCodeInvalid, partnerCodeAppliedTemplate: t.cart.partnerCodeApplied("__C__"), partnerCodeRemoveLabel: t.cart.partnerCodeRemove }}>
|
||||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
||||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel };
|
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel, partnerDiscountLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel };
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, istAusgewaehlt } from "../../../scripts/cart";
|
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, istAusgewaehlt } from "../../../scripts/cart";
|
||||||
import { formatPrice } from "../../../i18n/format";
|
import { formatPrice } from "../../../i18n/format";
|
||||||
import { products } from "../../../data/products";
|
import { products } from "../../../data/products";
|
||||||
import { berechneVersandkosten } from "../../../data/versand";
|
import { berechneVersandkosten } from "../../../data/versand";
|
||||||
import { effektiverZeilenpreis } from "../../../data/rabatt";
|
import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||||
import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankverbindung";
|
import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankverbindung";
|
||||||
|
|
||||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel } = window.__checkoutVars;
|
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel, partnerDiscountLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel } = window.__checkoutVars;
|
||||||
|
|
||||||
const summary = document.getElementById("checkout-summary");
|
const summary = document.getElementById("checkout-summary");
|
||||||
const landSelect = document.getElementById("land");
|
const landSelect = document.getElementById("land");
|
||||||
@@ -169,7 +178,9 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
const shipping = cart.length === 0 ? 0 : berechneVersandkosten(land, cart.map((i) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
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 coupon = appliedCoupon();
|
||||||
const discount = couponDiscount(subtotal);
|
const discount = couponDiscount(subtotal);
|
||||||
const total = Math.max(0, subtotal - discount) + shipping;
|
const partnerCode = appliedPartnerCode();
|
||||||
|
const partnerDiscount = partnerCodeDiscount(subtotal);
|
||||||
|
const total = Math.max(0, subtotal - totalDiscount(subtotal)) + shipping;
|
||||||
currentTotal = total;
|
currentTotal = total;
|
||||||
|
|
||||||
summary.innerHTML = `
|
summary.innerHTML = `
|
||||||
@@ -179,6 +190,7 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
return `<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(zeilenpreis, checkoutLang)}</span></div>`;
|
return `<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(zeilenpreis, checkoutLang)}</span></div>`;
|
||||||
}).join("")}
|
}).join("")}
|
||||||
${coupon && discount > 0 ? `<div class="row"><span>${discountLabel}</span><span>-${formatPrice(discount, checkoutLang)}</span></div>` : ""}
|
${coupon && discount > 0 ? `<div class="row"><span>${discountLabel}</span><span>-${formatPrice(discount, checkoutLang)}</span></div>` : ""}
|
||||||
|
${partnerCode && partnerDiscount > 0 ? `<div class="row"><span>${partnerDiscountLabel}</span><span>-${formatPrice(partnerDiscount, checkoutLang)}</span></div>` : ""}
|
||||||
<div class="row"><span>${shippingLabel}</span><span>${shipping === 0 ? freeLabel : formatPrice(shipping, 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(total, checkoutLang)}</span></div>
|
<div class="row total"><span>${totalLabel}</span><span>${formatPrice(total, checkoutLang)}</span></div>
|
||||||
`;
|
`;
|
||||||
@@ -193,6 +205,17 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
} else {
|
} else {
|
||||||
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const partnerStatus = document.getElementById("partner-code-status");
|
||||||
|
const partnerInput = document.getElementById("partner-code-input");
|
||||||
|
if (partnerCode && partnerDiscount > 0) {
|
||||||
|
partnerStatus.innerHTML = `✅ ${partnerCodeAppliedTemplate.replace("__C__", partnerCode.code)} <a href="#" id="partner-code-clear">✕ ${partnerCodeRemoveLabel}</a>`;
|
||||||
|
partnerInput.value = partnerCode.code;
|
||||||
|
const partnerClearLink = document.getElementById("partner-code-clear");
|
||||||
|
if (partnerClearLink) partnerClearLink.addEventListener("click", (e) => { e.preventDefault(); clearPartnerCode(); renderSummary(); });
|
||||||
|
} else {
|
||||||
|
partnerStatus.textContent = partnerInput.dataset.invalid === "1" ? partnerCodeInvalid : "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSummary();
|
renderSummary();
|
||||||
@@ -274,6 +297,15 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
renderSummary();
|
renderSummary();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("partner-code-apply").addEventListener("click", () => {
|
||||||
|
const input = document.getElementById("partner-code-input");
|
||||||
|
const code = input.value.trim();
|
||||||
|
if (!code) { clearPartnerCode(); renderSummary(); return; }
|
||||||
|
setPartnerCode(code);
|
||||||
|
input.dataset.invalid = appliedPartnerCode() ? "0" : "1";
|
||||||
|
renderSummary();
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById("checkout-form").addEventListener("submit", (e) => {
|
document.getElementById("checkout-form").addEventListener("submit", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (cart.length === 0) {
|
if (cart.length === 0) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const t = useTranslations(lang);
|
|||||||
<div id="summary-items" class="checkout-summary"></div>
|
<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"><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="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="coupon-row">
|
<div class="coupon-row">
|
||||||
<label for="coupon-input">{t.cart.couponLabel}</label>
|
<label for="coupon-input">{t.cart.couponLabel}</label>
|
||||||
<div class="coupon-input-group">
|
<div class="coupon-input-group">
|
||||||
@@ -36,6 +37,14 @@ const t = useTranslations(lang);
|
|||||||
</div>
|
</div>
|
||||||
<p class="small" id="coupon-status"></p>
|
<p class="small" id="coupon-status"></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="coupon-row">
|
||||||
|
<label for="partner-code-input">{t.cart.partnerCodeLabel}</label>
|
||||||
|
<div class="coupon-input-group">
|
||||||
|
<input type="text" id="partner-code-input" placeholder={t.cart.partnerCodePlaceholder} />
|
||||||
|
<button type="button" id="partner-code-apply" class="btn btn-outline btn-sm">{t.cart.partnerCodeApply}</button>
|
||||||
|
</div>
|
||||||
|
<p class="small" id="partner-code-status"></p>
|
||||||
|
</div>
|
||||||
<div class="ship-country-row">
|
<div class="ship-country-row">
|
||||||
<label for="cart-land">📦 {t.checkout.country}</label>
|
<label for="cart-land">📦 {t.checkout.country}</label>
|
||||||
<select id="cart-land">
|
<select id="cart-land">
|
||||||
@@ -57,18 +66,18 @@ const t = useTranslations(lang);
|
|||||||
</section>
|
</section>
|
||||||
</Layout>
|
</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 }}>
|
<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 }}>
|
||||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
// 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 };
|
window.__cartVars = { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel };
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, istAusgewaehlt, setAusgewaehlt } from "../../../scripts/cart";
|
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, istAusgewaehlt, setAusgewaehlt } from "../../../scripts/cart";
|
||||||
import { formatPrice } from "../../../i18n/format";
|
import { formatPrice } from "../../../i18n/format";
|
||||||
import { getProduct, products } from "../../../data/products";
|
import { getProduct, products } from "../../../data/products";
|
||||||
import { berechneVersandkosten } from "../../../data/versand";
|
import { berechneVersandkosten } from "../../../data/versand";
|
||||||
import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt";
|
import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt";
|
||||||
|
|
||||||
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel } = window.__cartVars;
|
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel } = window.__cartVars;
|
||||||
const landSelect = document.getElementById("cart-land");
|
const landSelect = document.getElementById("cart-land");
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
@@ -136,7 +145,9 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt
|
|||||||
const shipping = berechneVersandkosten(landSelect.value, ausgewaehlteZeilen.map(({ item: i }) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
const shipping = berechneVersandkosten(landSelect.value, ausgewaehlteZeilen.map(({ item: i }) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
||||||
const coupon = appliedCoupon();
|
const coupon = appliedCoupon();
|
||||||
const discount = couponDiscount(subtotal);
|
const discount = couponDiscount(subtotal);
|
||||||
const total = Math.max(0, subtotal - discount) + shipping;
|
const partnerCode = appliedPartnerCode();
|
||||||
|
const partnerDiscount = partnerCodeDiscount(subtotal);
|
||||||
|
const total = Math.max(0, subtotal - totalDiscount(subtotal)) + shipping;
|
||||||
document.getElementById("sum-subtotal").textContent = formatPrice(subtotal, cartLang);
|
document.getElementById("sum-subtotal").textContent = formatPrice(subtotal, cartLang);
|
||||||
document.getElementById("sum-shipping").textContent = shipping === 0 ? freeLabel : formatPrice(shipping, cartLang);
|
document.getElementById("sum-shipping").textContent = shipping === 0 ? freeLabel : formatPrice(shipping, cartLang);
|
||||||
document.getElementById("sum-total").textContent = formatPrice(total, cartLang);
|
document.getElementById("sum-total").textContent = formatPrice(total, cartLang);
|
||||||
@@ -158,6 +169,21 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt
|
|||||||
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const partnerDiscountRow = document.getElementById("partner-discount-row");
|
||||||
|
const partnerStatus = document.getElementById("partner-code-status");
|
||||||
|
const partnerInput = document.getElementById("partner-code-input");
|
||||||
|
if (partnerCode && partnerDiscount > 0) {
|
||||||
|
partnerDiscountRow.style.display = "flex";
|
||||||
|
document.getElementById("sum-partner-discount").textContent = "-" + formatPrice(partnerDiscount, cartLang);
|
||||||
|
partnerStatus.innerHTML = `✅ ${partnerCodeAppliedTemplate.replace("__C__", partnerCode.code)} <a href="#" id="partner-code-clear">✕ ${partnerCodeRemoveLabel}</a>`;
|
||||||
|
partnerInput.value = partnerCode.code;
|
||||||
|
const partnerClearLink = document.getElementById("partner-code-clear");
|
||||||
|
if (partnerClearLink) partnerClearLink.addEventListener("click", (e) => { e.preventDefault(); clearPartnerCode(); render(); });
|
||||||
|
} else {
|
||||||
|
partnerDiscountRow.style.display = "none";
|
||||||
|
partnerStatus.textContent = partnerInput.dataset.invalid === "1" ? partnerCodeInvalid : "";
|
||||||
|
}
|
||||||
|
|
||||||
itemsEl.querySelectorAll("button, a.remove").forEach((el) => {
|
itemsEl.querySelectorAll("button, a.remove").forEach((el) => {
|
||||||
el.addEventListener("click", (e) => {
|
el.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -195,6 +221,15 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt
|
|||||||
render();
|
render();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("partner-code-apply").addEventListener("click", () => {
|
||||||
|
const input = document.getElementById("partner-code-input");
|
||||||
|
const code = input.value.trim();
|
||||||
|
if (!code) { clearPartnerCode(); render(); return; }
|
||||||
|
setPartnerCode(code);
|
||||||
|
input.dataset.invalid = appliedPartnerCode() ? "0" : "1";
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
render();
|
render();
|
||||||
window.addEventListener("cart:changed", render);
|
window.addEventListener("cart:changed", render);
|
||||||
landSelect.addEventListener("change", render);
|
landSelect.addEventListener("change", render);
|
||||||
|
|||||||
@@ -134,6 +134,15 @@ const t = useTranslations(lang);
|
|||||||
<p class="small" id="coupon-status"></p>
|
<p class="small" id="coupon-status"></p>
|
||||||
</div>
|
</div>
|
||||||
<hr class="divider stitched" />
|
<hr class="divider stitched" />
|
||||||
|
<div class="coupon-row">
|
||||||
|
<label for="partner-code-input">{t.cart.partnerCodeLabel}</label>
|
||||||
|
<div class="coupon-input-group">
|
||||||
|
<input type="text" id="partner-code-input" placeholder={t.cart.partnerCodePlaceholder} />
|
||||||
|
<button type="button" id="partner-code-apply" class="btn btn-outline btn-sm">{t.cart.partnerCodeApply}</button>
|
||||||
|
</div>
|
||||||
|
<p class="small" id="partner-code-status"></p>
|
||||||
|
</div>
|
||||||
|
<hr class="divider stitched" />
|
||||||
<h3>🚚 {t.checkout.shippingHintTitle}</h3>
|
<h3>🚚 {t.checkout.shippingHintTitle}</h3>
|
||||||
<p class="small">{t.checkout.shippingHint}</p>
|
<p class="small">{t.checkout.shippingHint}</p>
|
||||||
<a class="small" href="/fr/versand-zahlung/">{t.checkout.shippingLink}</a>
|
<a class="small" href="/fr/versand-zahlung/">{t.checkout.shippingLink}</a>
|
||||||
@@ -142,19 +151,19 @@ const t = useTranslations(lang);
|
|||||||
</section>
|
</section>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/fr/checkout/danke/", checkoutLang: lang }}>
|
<script define:vars={{ freeLabel: t.checkout.free, totalLabel: t.checkout.total, shippingLabel: t.cart.shipping, emptyCartAlert: t.checkout.emptyCartAlert, thankYouPath: "/fr/checkout/danke/", checkoutLang: lang, discountLabel: t.cart.discount, couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove, klarnaConnectLabel: t.checkout.klarnaConnect, klarnaConnectingLabel: t.checkout.klarnaConnecting, klarnaConnectedLabel: t.checkout.klarnaConnected, bankHolderLabel: t.checkout.bankHolder, bankIbanLabel: t.checkout.bankIban, bankBicLabel: t.checkout.bankBic, bankBankNameLabel: t.checkout.bankBankName, bankPlaceholderLabel: t.checkout.bankPlaceholder, partnerDiscountLabel: t.cart.partnerDiscount, partnerCodeInvalid: t.cart.partnerCodeInvalid, partnerCodeAppliedTemplate: t.cart.partnerCodeApplied("__C__"), partnerCodeRemoveLabel: t.cart.partnerCodeRemove }}>
|
||||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
||||||
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel };
|
window.__checkoutVars = { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel, partnerDiscountLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel };
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, istAusgewaehlt } from "../../../scripts/cart";
|
import { getCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, istAusgewaehlt } from "../../../scripts/cart";
|
||||||
import { formatPrice } from "../../../i18n/format";
|
import { formatPrice } from "../../../i18n/format";
|
||||||
import { products } from "../../../data/products";
|
import { products } from "../../../data/products";
|
||||||
import { berechneVersandkosten } from "../../../data/versand";
|
import { berechneVersandkosten } from "../../../data/versand";
|
||||||
import { effektiverZeilenpreis } from "../../../data/rabatt";
|
import { effektiverZeilenpreis } from "../../../data/rabatt";
|
||||||
import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankverbindung";
|
import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankverbindung";
|
||||||
|
|
||||||
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel } = window.__checkoutVars;
|
const { freeLabel, totalLabel, shippingLabel, emptyCartAlert, thankYouPath, checkoutLang, discountLabel, couponInvalid, couponAppliedTemplate, couponRemoveLabel, klarnaConnectLabel, klarnaConnectingLabel, klarnaConnectedLabel, bankHolderLabel, bankIbanLabel, bankBicLabel, bankBankNameLabel, bankPlaceholderLabel, partnerDiscountLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel } = window.__checkoutVars;
|
||||||
|
|
||||||
const summary = document.getElementById("checkout-summary");
|
const summary = document.getElementById("checkout-summary");
|
||||||
const landSelect = document.getElementById("land");
|
const landSelect = document.getElementById("land");
|
||||||
@@ -169,7 +178,9 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
const shipping = cart.length === 0 ? 0 : berechneVersandkosten(land, cart.map((i) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
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 coupon = appliedCoupon();
|
||||||
const discount = couponDiscount(subtotal);
|
const discount = couponDiscount(subtotal);
|
||||||
const total = Math.max(0, subtotal - discount) + shipping;
|
const partnerCode = appliedPartnerCode();
|
||||||
|
const partnerDiscount = partnerCodeDiscount(subtotal);
|
||||||
|
const total = Math.max(0, subtotal - totalDiscount(subtotal)) + shipping;
|
||||||
currentTotal = total;
|
currentTotal = total;
|
||||||
|
|
||||||
summary.innerHTML = `
|
summary.innerHTML = `
|
||||||
@@ -179,6 +190,7 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
return `<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(zeilenpreis, checkoutLang)}</span></div>`;
|
return `<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(zeilenpreis, checkoutLang)}</span></div>`;
|
||||||
}).join("")}
|
}).join("")}
|
||||||
${coupon && discount > 0 ? `<div class="row"><span>${discountLabel}</span><span>-${formatPrice(discount, checkoutLang)}</span></div>` : ""}
|
${coupon && discount > 0 ? `<div class="row"><span>${discountLabel}</span><span>-${formatPrice(discount, checkoutLang)}</span></div>` : ""}
|
||||||
|
${partnerCode && partnerDiscount > 0 ? `<div class="row"><span>${partnerDiscountLabel}</span><span>-${formatPrice(partnerDiscount, checkoutLang)}</span></div>` : ""}
|
||||||
<div class="row"><span>${shippingLabel}</span><span>${shipping === 0 ? freeLabel : formatPrice(shipping, 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(total, checkoutLang)}</span></div>
|
<div class="row total"><span>${totalLabel}</span><span>${formatPrice(total, checkoutLang)}</span></div>
|
||||||
`;
|
`;
|
||||||
@@ -193,6 +205,17 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
} else {
|
} else {
|
||||||
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const partnerStatus = document.getElementById("partner-code-status");
|
||||||
|
const partnerInput = document.getElementById("partner-code-input");
|
||||||
|
if (partnerCode && partnerDiscount > 0) {
|
||||||
|
partnerStatus.innerHTML = `✅ ${partnerCodeAppliedTemplate.replace("__C__", partnerCode.code)} <a href="#" id="partner-code-clear">✕ ${partnerCodeRemoveLabel}</a>`;
|
||||||
|
partnerInput.value = partnerCode.code;
|
||||||
|
const partnerClearLink = document.getElementById("partner-code-clear");
|
||||||
|
if (partnerClearLink) partnerClearLink.addEventListener("click", (e) => { e.preventDefault(); clearPartnerCode(); renderSummary(); });
|
||||||
|
} else {
|
||||||
|
partnerStatus.textContent = partnerInput.dataset.invalid === "1" ? partnerCodeInvalid : "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSummary();
|
renderSummary();
|
||||||
@@ -274,6 +297,15 @@ import { bankverbindung, bankverbindungVollstaendig } from "../../../data/bankve
|
|||||||
renderSummary();
|
renderSummary();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("partner-code-apply").addEventListener("click", () => {
|
||||||
|
const input = document.getElementById("partner-code-input");
|
||||||
|
const code = input.value.trim();
|
||||||
|
if (!code) { clearPartnerCode(); renderSummary(); return; }
|
||||||
|
setPartnerCode(code);
|
||||||
|
input.dataset.invalid = appliedPartnerCode() ? "0" : "1";
|
||||||
|
renderSummary();
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById("checkout-form").addEventListener("submit", (e) => {
|
document.getElementById("checkout-form").addEventListener("submit", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (cart.length === 0) {
|
if (cart.length === 0) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const t = useTranslations(lang);
|
|||||||
<div id="summary-items" class="checkout-summary"></div>
|
<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"><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="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="coupon-row">
|
<div class="coupon-row">
|
||||||
<label for="coupon-input">{t.cart.couponLabel}</label>
|
<label for="coupon-input">{t.cart.couponLabel}</label>
|
||||||
<div class="coupon-input-group">
|
<div class="coupon-input-group">
|
||||||
@@ -36,6 +37,14 @@ const t = useTranslations(lang);
|
|||||||
</div>
|
</div>
|
||||||
<p class="small" id="coupon-status"></p>
|
<p class="small" id="coupon-status"></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="coupon-row">
|
||||||
|
<label for="partner-code-input">{t.cart.partnerCodeLabel}</label>
|
||||||
|
<div class="coupon-input-group">
|
||||||
|
<input type="text" id="partner-code-input" placeholder={t.cart.partnerCodePlaceholder} />
|
||||||
|
<button type="button" id="partner-code-apply" class="btn btn-outline btn-sm">{t.cart.partnerCodeApply}</button>
|
||||||
|
</div>
|
||||||
|
<p class="small" id="partner-code-status"></p>
|
||||||
|
</div>
|
||||||
<div class="ship-country-row">
|
<div class="ship-country-row">
|
||||||
<label for="cart-land">📦 {t.checkout.country}</label>
|
<label for="cart-land">📦 {t.checkout.country}</label>
|
||||||
<select id="cart-land">
|
<select id="cart-land">
|
||||||
@@ -57,18 +66,18 @@ const t = useTranslations(lang);
|
|||||||
</section>
|
</section>
|
||||||
</Layout>
|
</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 }}>
|
<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 }}>
|
||||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
// 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 };
|
window.__cartVars = { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel };
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, istAusgewaehlt, setAusgewaehlt } from "../../../scripts/cart";
|
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, istAusgewaehlt, setAusgewaehlt } from "../../../scripts/cart";
|
||||||
import { formatPrice } from "../../../i18n/format";
|
import { formatPrice } from "../../../i18n/format";
|
||||||
import { getProduct, products } from "../../../data/products";
|
import { getProduct, products } from "../../../data/products";
|
||||||
import { berechneVersandkosten } from "../../../data/versand";
|
import { berechneVersandkosten } from "../../../data/versand";
|
||||||
import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt";
|
import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt";
|
||||||
|
|
||||||
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel } = window.__cartVars;
|
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel } = window.__cartVars;
|
||||||
const landSelect = document.getElementById("cart-land");
|
const landSelect = document.getElementById("cart-land");
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
@@ -136,7 +145,9 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt
|
|||||||
const shipping = berechneVersandkosten(landSelect.value, ausgewaehlteZeilen.map(({ item: i }) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
const shipping = berechneVersandkosten(landSelect.value, ausgewaehlteZeilen.map(({ item: i }) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
||||||
const coupon = appliedCoupon();
|
const coupon = appliedCoupon();
|
||||||
const discount = couponDiscount(subtotal);
|
const discount = couponDiscount(subtotal);
|
||||||
const total = Math.max(0, subtotal - discount) + shipping;
|
const partnerCode = appliedPartnerCode();
|
||||||
|
const partnerDiscount = partnerCodeDiscount(subtotal);
|
||||||
|
const total = Math.max(0, subtotal - totalDiscount(subtotal)) + shipping;
|
||||||
document.getElementById("sum-subtotal").textContent = formatPrice(subtotal, cartLang);
|
document.getElementById("sum-subtotal").textContent = formatPrice(subtotal, cartLang);
|
||||||
document.getElementById("sum-shipping").textContent = shipping === 0 ? freeLabel : formatPrice(shipping, cartLang);
|
document.getElementById("sum-shipping").textContent = shipping === 0 ? freeLabel : formatPrice(shipping, cartLang);
|
||||||
document.getElementById("sum-total").textContent = formatPrice(total, cartLang);
|
document.getElementById("sum-total").textContent = formatPrice(total, cartLang);
|
||||||
@@ -158,6 +169,21 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt
|
|||||||
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const partnerDiscountRow = document.getElementById("partner-discount-row");
|
||||||
|
const partnerStatus = document.getElementById("partner-code-status");
|
||||||
|
const partnerInput = document.getElementById("partner-code-input");
|
||||||
|
if (partnerCode && partnerDiscount > 0) {
|
||||||
|
partnerDiscountRow.style.display = "flex";
|
||||||
|
document.getElementById("sum-partner-discount").textContent = "-" + formatPrice(partnerDiscount, cartLang);
|
||||||
|
partnerStatus.innerHTML = `✅ ${partnerCodeAppliedTemplate.replace("__C__", partnerCode.code)} <a href="#" id="partner-code-clear">✕ ${partnerCodeRemoveLabel}</a>`;
|
||||||
|
partnerInput.value = partnerCode.code;
|
||||||
|
const partnerClearLink = document.getElementById("partner-code-clear");
|
||||||
|
if (partnerClearLink) partnerClearLink.addEventListener("click", (e) => { e.preventDefault(); clearPartnerCode(); render(); });
|
||||||
|
} else {
|
||||||
|
partnerDiscountRow.style.display = "none";
|
||||||
|
partnerStatus.textContent = partnerInput.dataset.invalid === "1" ? partnerCodeInvalid : "";
|
||||||
|
}
|
||||||
|
|
||||||
itemsEl.querySelectorAll("button, a.remove").forEach((el) => {
|
itemsEl.querySelectorAll("button, a.remove").forEach((el) => {
|
||||||
el.addEventListener("click", (e) => {
|
el.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -195,6 +221,15 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../../data/rabatt
|
|||||||
render();
|
render();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("partner-code-apply").addEventListener("click", () => {
|
||||||
|
const input = document.getElementById("partner-code-input");
|
||||||
|
const code = input.value.trim();
|
||||||
|
if (!code) { clearPartnerCode(); render(); return; }
|
||||||
|
setPartnerCode(code);
|
||||||
|
input.dataset.invalid = appliedPartnerCode() ? "0" : "1";
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
render();
|
render();
|
||||||
window.addEventListener("cart:changed", render);
|
window.addEventListener("cart:changed", render);
|
||||||
landSelect.addEventListener("change", render);
|
landSelect.addEventListener("change", render);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const t = useTranslations(lang);
|
|||||||
<div id="summary-items" class="checkout-summary"></div>
|
<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"><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="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="coupon-row">
|
<div class="coupon-row">
|
||||||
<label for="coupon-input">{t.cart.couponLabel}</label>
|
<label for="coupon-input">{t.cart.couponLabel}</label>
|
||||||
<div class="coupon-input-group">
|
<div class="coupon-input-group">
|
||||||
@@ -36,6 +37,14 @@ const t = useTranslations(lang);
|
|||||||
</div>
|
</div>
|
||||||
<p class="small" id="coupon-status"></p>
|
<p class="small" id="coupon-status"></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="coupon-row">
|
||||||
|
<label for="partner-code-input">{t.cart.partnerCodeLabel}</label>
|
||||||
|
<div class="coupon-input-group">
|
||||||
|
<input type="text" id="partner-code-input" placeholder={t.cart.partnerCodePlaceholder} />
|
||||||
|
<button type="button" id="partner-code-apply" class="btn btn-outline btn-sm">{t.cart.partnerCodeApply}</button>
|
||||||
|
</div>
|
||||||
|
<p class="small" id="partner-code-status"></p>
|
||||||
|
</div>
|
||||||
<div class="ship-country-row">
|
<div class="ship-country-row">
|
||||||
<label for="cart-land">📦 {t.checkout.country}</label>
|
<label for="cart-land">📦 {t.checkout.country}</label>
|
||||||
<select id="cart-land">
|
<select id="cart-land">
|
||||||
@@ -57,18 +66,18 @@ const t = useTranslations(lang);
|
|||||||
</section>
|
</section>
|
||||||
</Layout>
|
</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 }}>
|
<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 }}>
|
||||||
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
|
// 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 };
|
window.__cartVars = { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel };
|
||||||
</script>
|
</script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, istAusgewaehlt, setAusgewaehlt } from "../../scripts/cart";
|
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, istAusgewaehlt, setAusgewaehlt } from "../../scripts/cart";
|
||||||
import { formatPrice } from "../../i18n/format";
|
import { formatPrice } from "../../i18n/format";
|
||||||
import { getProduct, products } from "../../data/products";
|
import { getProduct, products } from "../../data/products";
|
||||||
import { berechneVersandkosten } from "../../data/versand";
|
import { berechneVersandkosten } from "../../data/versand";
|
||||||
import { effektiverZeilenpreis, mengenrabattProzent } from "../../data/rabatt";
|
import { effektiverZeilenpreis, mengenrabattProzent } from "../../data/rabatt";
|
||||||
|
|
||||||
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel } = window.__cartVars;
|
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel } = window.__cartVars;
|
||||||
const landSelect = document.getElementById("cart-land");
|
const landSelect = document.getElementById("cart-land");
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
@@ -136,7 +145,9 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../data/rabatt";
|
|||||||
const shipping = berechneVersandkosten(landSelect.value, ausgewaehlteZeilen.map(({ item: i }) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
const shipping = berechneVersandkosten(landSelect.value, ausgewaehlteZeilen.map(({ item: i }) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
|
||||||
const coupon = appliedCoupon();
|
const coupon = appliedCoupon();
|
||||||
const discount = couponDiscount(subtotal);
|
const discount = couponDiscount(subtotal);
|
||||||
const total = Math.max(0, subtotal - discount) + shipping;
|
const partnerCode = appliedPartnerCode();
|
||||||
|
const partnerDiscount = partnerCodeDiscount(subtotal);
|
||||||
|
const total = Math.max(0, subtotal - totalDiscount(subtotal)) + shipping;
|
||||||
document.getElementById("sum-subtotal").textContent = formatPrice(subtotal, cartLang);
|
document.getElementById("sum-subtotal").textContent = formatPrice(subtotal, cartLang);
|
||||||
document.getElementById("sum-shipping").textContent = shipping === 0 ? freeLabel : formatPrice(shipping, cartLang);
|
document.getElementById("sum-shipping").textContent = shipping === 0 ? freeLabel : formatPrice(shipping, cartLang);
|
||||||
document.getElementById("sum-total").textContent = formatPrice(total, cartLang);
|
document.getElementById("sum-total").textContent = formatPrice(total, cartLang);
|
||||||
@@ -158,6 +169,21 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../data/rabatt";
|
|||||||
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const partnerDiscountRow = document.getElementById("partner-discount-row");
|
||||||
|
const partnerStatus = document.getElementById("partner-code-status");
|
||||||
|
const partnerInput = document.getElementById("partner-code-input");
|
||||||
|
if (partnerCode && partnerDiscount > 0) {
|
||||||
|
partnerDiscountRow.style.display = "flex";
|
||||||
|
document.getElementById("sum-partner-discount").textContent = "-" + formatPrice(partnerDiscount, cartLang);
|
||||||
|
partnerStatus.innerHTML = `✅ ${partnerCodeAppliedTemplate.replace("__C__", partnerCode.code)} <a href="#" id="partner-code-clear">✕ ${partnerCodeRemoveLabel}</a>`;
|
||||||
|
partnerInput.value = partnerCode.code;
|
||||||
|
const partnerClearLink = document.getElementById("partner-code-clear");
|
||||||
|
if (partnerClearLink) partnerClearLink.addEventListener("click", (e) => { e.preventDefault(); clearPartnerCode(); render(); });
|
||||||
|
} else {
|
||||||
|
partnerDiscountRow.style.display = "none";
|
||||||
|
partnerStatus.textContent = partnerInput.dataset.invalid === "1" ? partnerCodeInvalid : "";
|
||||||
|
}
|
||||||
|
|
||||||
itemsEl.querySelectorAll("button, a.remove").forEach((el) => {
|
itemsEl.querySelectorAll("button, a.remove").forEach((el) => {
|
||||||
el.addEventListener("click", (e) => {
|
el.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -195,6 +221,15 @@ import { effektiverZeilenpreis, mengenrabattProzent } from "../../data/rabatt";
|
|||||||
render();
|
render();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("partner-code-apply").addEventListener("click", () => {
|
||||||
|
const input = document.getElementById("partner-code-input");
|
||||||
|
const code = input.value.trim();
|
||||||
|
if (!code) { clearPartnerCode(); render(); return; }
|
||||||
|
setPartnerCode(code);
|
||||||
|
input.dataset.invalid = appliedPartnerCode() ? "0" : "1";
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
render();
|
render();
|
||||||
window.addEventListener("cart:changed", render);
|
window.addEventListener("cart:changed", render);
|
||||||
landSelect.addEventListener("change", render);
|
landSelect.addEventListener("change", render);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import { getProduct } from "../data/products";
|
import { getProduct } from "../data/products";
|
||||||
import { effektiverZeilenpreis } from "../data/rabatt";
|
import { effektiverZeilenpreis } from "../data/rabatt";
|
||||||
import { findeGutschein, gutscheinRabatt, type Gutschein } from "../data/gutscheine";
|
import { findeGutschein, gutscheinRabatt, type Gutschein } from "../data/gutscheine";
|
||||||
|
import { findePartnercode, partnercodeRabatt, type Partnercode } from "../data/partnercodes";
|
||||||
|
|
||||||
export interface CartItem {
|
export interface CartItem {
|
||||||
slug: string;
|
slug: string;
|
||||||
@@ -130,3 +131,43 @@ export function couponDiscount(subtotal: number): number {
|
|||||||
const g = appliedCoupon();
|
const g = appliedCoupon();
|
||||||
return g ? gutscheinRabatt(g, subtotal) : 0;
|
return g ? gutscheinRabatt(g, subtotal) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Partner-/Influencer-Rabattcodes — eigener, unabhängiger "Slot" neben dem allgemeinen
|
||||||
|
// Gutscheincode (siehe src/data/partnercodes.ts für den fachlichen Hintergrund). Eigener
|
||||||
|
// localStorage-Schlüssel, damit beide Codes unabhängig voneinander gesetzt/entfernt werden können.
|
||||||
|
const PARTNER_CODE_KEY = "vandiy_partnercode_v1";
|
||||||
|
|
||||||
|
export function getPartnerCode(): string {
|
||||||
|
if (typeof localStorage === "undefined") return "";
|
||||||
|
return localStorage.getItem(PARTNER_CODE_KEY) || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setPartnerCode(code: string) {
|
||||||
|
localStorage.setItem(PARTNER_CODE_KEY, code.trim());
|
||||||
|
window.dispatchEvent(new CustomEvent("cart:changed", { detail: getCart() }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearPartnerCode() {
|
||||||
|
localStorage.removeItem(PARTNER_CODE_KEY);
|
||||||
|
window.dispatchEvent(new CustomEvent("cart:changed", { detail: getCart() }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Der aktuell eingelöste Partnercode, falls der gespeicherte Code (noch) gültig ist. */
|
||||||
|
export function appliedPartnerCode(): Partnercode | undefined {
|
||||||
|
const code = getPartnerCode();
|
||||||
|
if (!code) return undefined;
|
||||||
|
return findePartnercode(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rabatt-Betrag in Euro, den der eingelöste Partnercode auf die gegebene Zwischensumme gibt. */
|
||||||
|
export function partnerCodeDiscount(subtotal: number): number {
|
||||||
|
const p = appliedPartnerCode();
|
||||||
|
return p ? partnercodeRabatt(p, subtotal) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gutschein und Partnercode können gleichzeitig eingelöst sein (zwei unabhängige Codes) — die
|
||||||
|
* Summe wird hier auf die Zwischensumme gedeckelt, damit der Gesamtpreis nie unter 0 € rutscht,
|
||||||
|
* egal wie großzügig die einzelnen Rabatte sind. */
|
||||||
|
export function totalDiscount(subtotal: number): number {
|
||||||
|
return Math.min(subtotal, couponDiscount(subtotal) + partnerCodeDiscount(subtotal));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user