Treuebonus-Karte deutlich detaillierter: Meilensteine, Kennzahlen, Regeln

Die Treuebonus-Karte im Konto zeigte bisher nur einen nackten Fortschrittsbalken
mit einem Satz Text. Jetzt: (1) zehn Meilenstein-Striche direkt im Balken plus
Geschenk-Symbol am Ziel, (2) vier Kennzahl-Kacheln im gleichen Stil wie beim Abo-
Status (Gesammelt, Noch nötig, Bonus bei Ziel, Bisher eingelöst), (3) ein neues
Datenfeld "wie oft insgesamt schon eingelöst + wann zuletzt" (account.ts), das in
einer eigenen, gold→grün abgewandelten "Bonus bereit"-Karte mit Glow-Effekt landet
statt eines einfachen Links, (4) eine kurze Icon-Liste mit den Sammelregeln statt
einem einzelnen Erklärsatz. Alles in 4 Sprachen, mit echten Vorschau-Zuständen
(Fortschritt/bereit/gesperrt) per Screenshot verifiziert.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
qcigano
2026-08-03 20:14:29 +02:00
co-authored by Claude Sonnet 5
parent 5c1ebfac60
commit 9170b9bb22
7 changed files with 417 additions and 52 deletions
+75 -13
View File
@@ -6,6 +6,7 @@ import type { Locale } from "../../../i18n/config";
import { useTranslations } from "../../../i18n/ui";
import { formatPrice } from "../../../i18n/format";
import { ABO_STUFEN, aboStufenKonfig, aboStufeName, aboStufeVorteil, type AboStufe } from "../../../data/abo";
import { TREUE_ZIEL, TREUE_RABATT_PROZENT } from "../../../scripts/account";
const lang: Locale = "en";
const t = useTranslations(lang);
@@ -333,11 +334,58 @@ const angekommeneBestellungen = beispielBestellungen.filter((b) => b.status ===
<button type="button" class="btn btn-primary btn-sm" id="loyalty-locked-abo-btn" style="align-self:flex-start;">{t.accountDash.loyaltyLockedBtn}</button>
</div>
<div class="loyalty-inner" id="loyalty-unlocked-content">
<div class="loyalty-progress-bar"><div class="loyalty-progress-fill" id="loyalty-bar-fill"></div></div>
<div class="loyalty-progress-track">
<div class="loyalty-progress-bar">
<div class="loyalty-progress-fill" id="loyalty-bar-fill"></div>
<div class="loyalty-progress-dots" aria-hidden="true">
{Array.from({ length: TREUE_ZIEL - 1 }).map((_, i) => (
<span class="loyalty-progress-dot" style={`left:${((i + 1) / TREUE_ZIEL) * 100}%`}></span>
))}
</div>
</div>
<span class="loyalty-progress-goal-icon" aria-hidden="true">🎁</span>
</div>
<p class="loyalty-progress-text" id="loyalty-progress-text"></p>
<div class="abo-stat-grid loyalty-stat-grid">
<div class="abo-stat-tile">
<span class="abo-stat-icon" aria-hidden="true">📦</span>
<span class="abo-stat-label">{t.accountDash.loyaltyStatCollected}</span>
<span class="abo-stat-value" id="loyalty-stat-collected"></span>
</div>
<div class="abo-stat-tile">
<span class="abo-stat-icon" aria-hidden="true">⏳</span>
<span class="abo-stat-label">{t.accountDash.loyaltyStatMissing}</span>
<span class="abo-stat-value" id="loyalty-stat-missing"></span>
</div>
<div class="abo-stat-tile loyalty-stat-tile-highlight">
<span class="abo-stat-icon" aria-hidden="true">💰</span>
<span class="abo-stat-label">{t.accountDash.loyaltyStatBonus}</span>
<span class="abo-stat-value">{TREUE_RABATT_PROZENT}%</span>
</div>
<div class="abo-stat-tile">
<span class="abo-stat-icon" aria-hidden="true">🏅</span>
<span class="abo-stat-label">{t.accountDash.loyaltyStatRedeemed}</span>
<span class="abo-stat-value" id="loyalty-stat-redeemed"></span>
</div>
</div>
<p class="small loyalty-last-redeemed" id="loyalty-last-redeemed" style="display:none;"></p>
<div class="loyalty-ready-block" id="loyalty-ready-block" style="display:none;">
<span class="loyalty-ready-icon" aria-hidden="true">🎁</span>
<div class="loyalty-ready-body">
<strong>{t.accountDash.loyaltyUnlockedTitle}</strong>
<p class="small">{t.accountDash.loyaltyUnlockedText}</p>
<a class="btn btn-primary btn-sm" id="loyalty-cart-link" href="/warenkorb/">{t.accountDash.loyaltyGoToCart}</a>
</div>
</div>
<p id="loyalty-status-text"></p>
<a class="btn btn-primary btn-sm" id="loyalty-cart-link" href="/warenkorb/" style="display:none; align-self:flex-start;">{t.accountDash.loyaltyGoToCart}</a>
<p class="small loyalty-explainer">{t.accountDash.loyaltyExplainer}</p>
<ul class="loyalty-rules">
<li><span aria-hidden="true">✅</span> {t.accountDash.loyaltyRuleValid}</li>
<li><span aria-hidden="true">❌</span> {t.accountDash.loyaltyRuleInvalid}</li>
<li><span aria-hidden="true">🔁</span> {t.accountDash.loyaltyRuleReset}</li>
</ul>
</div>
</div>
</div>
@@ -480,8 +528,8 @@ const angekommeneBestellungen = beispielBestellungen.filter((b) => b.status ===
</section>
</Layout>
<script define:vars={{ loginPath: "/en/konto/", fallbackName: "Customer", greetingTemplate: t.accountDash.greeting("{name}"), reviewedBadgeText: t.accountDash.reviewedBadge , invoiceLang: lang, invoiceLabels: { title: t.accountDash.invoiceTitle, numberLabel: t.accountDash.invoiceNumberLabel, dateLabel: t.accountDash.invoiceDateLabel, sellerLabel: t.accountDash.invoiceSellerLabel, billToLabel: t.accountDash.invoiceBillToLabel, itemLabel: t.accountDash.invoiceItemLabel, qtyLabel: t.accountDash.invoiceQtyLabel, unitPriceLabel: t.accountDash.invoiceUnitPriceLabel, sumLabel: t.accountDash.invoiceSumLabel, totalLabel: t.accountDash.orderTotal, vatNote: t.common.vatNote }, loyaltyProgressTemplate: t.accountDash.loyaltyProgressTemplate, loyaltyRemainingOne: t.accountDash.loyaltyRemainingOne, loyaltyRemainingOther: t.accountDash.loyaltyRemainingOther, loyaltyUnlockedTitle: t.accountDash.loyaltyUnlockedTitle, loyaltyUnlockedText: t.accountDash.loyaltyUnlockedText, aboLang: lang, aboLabels: { nameKlein: t.accountDash.aboNameKlein, nameMittel: t.accountDash.aboNameMittel, nameGross: t.accountDash.aboNameGross, preisSuffix: t.accountDash.aboPreisSuffix, monateSuffix: t.accountDash.aboMonateSuffix, fortschrittKleinTemplate: t.accountDash.aboFortschrittKleinTemplate, fortschrittGrossTemplate: t.accountDash.aboFortschrittGrossTemplate, praemienErhaltenTemplate: t.accountDash.aboPraemienErhaltenTemplate, aktivBadge: t.accountDash.aboAktivesLabel, nichtAktivBadge: t.accountDash.aboNichtAktivBadge, gekuendigtHinweisTemplate: t.accountDash.aboGekuendigtHinweisTemplate, kuendigenConfirm: t.accountDash.aboKuendigenConfirm, abschliessenBtn: t.accountDash.aboAbschliessenBtn, wechselnBtn: t.accountDash.aboWechselnBtn, nichtBuchbar: t.accountDash.aboNichtBuchbar, keineBenachrichtigungen: t.accountDash.aboKeineBenachrichtigungen, notifKeyLabels: { abo_abgeschlossen: t.accountDash.notifAboAbgeschlossen, abo_gewechselt: t.accountDash.notifAboGewechselt, abo_zahlung_erfolgreich: t.accountDash.notifAboZahlungErfolgreich, abo_teddy_klein_frei: t.accountDash.notifAboTeddyKleinFrei, abo_teddy_gross_frei: t.accountDash.notifAboTeddyGrossFrei, abo_bald_praemie: t.accountDash.notifAboBaldPraemie, abo_gekuendigt: t.accountDash.notifAboGekuendigt, abo_teddy_klein_eingeloest: t.accountDash.notifAboTeddyKleinEingeloest, abo_teddy_gross_eingeloest: t.accountDash.notifAboTeddyGrossEingeloest } } }}>
window.__accountDashVars = { loginPath, fallbackName, greetingTemplate, reviewedBadgeText, invoiceLang, invoiceLabels, loyaltyProgressTemplate, loyaltyRemainingOne, loyaltyRemainingOther, loyaltyUnlockedTitle, loyaltyUnlockedText, aboLang, aboLabels };
<script define:vars={{ loginPath: "/en/konto/", fallbackName: "Customer", greetingTemplate: t.accountDash.greeting("{name}"), reviewedBadgeText: t.accountDash.reviewedBadge , invoiceLang: lang, invoiceLabels: { title: t.accountDash.invoiceTitle, numberLabel: t.accountDash.invoiceNumberLabel, dateLabel: t.accountDash.invoiceDateLabel, sellerLabel: t.accountDash.invoiceSellerLabel, billToLabel: t.accountDash.invoiceBillToLabel, itemLabel: t.accountDash.invoiceItemLabel, qtyLabel: t.accountDash.invoiceQtyLabel, unitPriceLabel: t.accountDash.invoiceUnitPriceLabel, sumLabel: t.accountDash.invoiceSumLabel, totalLabel: t.accountDash.orderTotal, vatNote: t.common.vatNote }, loyaltyProgressTemplate: t.accountDash.loyaltyProgressTemplate, loyaltyRemainingOne: t.accountDash.loyaltyRemainingOne, loyaltyRemainingOther: t.accountDash.loyaltyRemainingOther, loyaltyUnlockedTitle: t.accountDash.loyaltyUnlockedTitle, loyaltyUnlockedText: t.accountDash.loyaltyUnlockedText, loyaltyStatRedeemedNever: t.accountDash.loyaltyStatRedeemedNever, loyaltyStatRedeemedSuffix: t.accountDash.loyaltyStatRedeemedSuffix, loyaltyLastRedeemed: t.accountDash.loyaltyLastRedeemed, aboLang: lang, aboLabels: { nameKlein: t.accountDash.aboNameKlein, nameMittel: t.accountDash.aboNameMittel, nameGross: t.accountDash.aboNameGross, preisSuffix: t.accountDash.aboPreisSuffix, monateSuffix: t.accountDash.aboMonateSuffix, fortschrittKleinTemplate: t.accountDash.aboFortschrittKleinTemplate, fortschrittGrossTemplate: t.accountDash.aboFortschrittGrossTemplate, praemienErhaltenTemplate: t.accountDash.aboPraemienErhaltenTemplate, aktivBadge: t.accountDash.aboAktivesLabel, nichtAktivBadge: t.accountDash.aboNichtAktivBadge, gekuendigtHinweisTemplate: t.accountDash.aboGekuendigtHinweisTemplate, kuendigenConfirm: t.accountDash.aboKuendigenConfirm, abschliessenBtn: t.accountDash.aboAbschliessenBtn, wechselnBtn: t.accountDash.aboWechselnBtn, nichtBuchbar: t.accountDash.aboNichtBuchbar, keineBenachrichtigungen: t.accountDash.aboKeineBenachrichtigungen, notifKeyLabels: { abo_abgeschlossen: t.accountDash.notifAboAbgeschlossen, abo_gewechselt: t.accountDash.notifAboGewechselt, abo_zahlung_erfolgreich: t.accountDash.notifAboZahlungErfolgreich, abo_teddy_klein_frei: t.accountDash.notifAboTeddyKleinFrei, abo_teddy_gross_frei: t.accountDash.notifAboTeddyGrossFrei, abo_bald_praemie: t.accountDash.notifAboBaldPraemie, abo_gekuendigt: t.accountDash.notifAboGekuendigt, abo_teddy_klein_eingeloest: t.accountDash.notifAboTeddyKleinEingeloest, abo_teddy_gross_eingeloest: t.accountDash.notifAboTeddyGrossEingeloest } } }}>
window.__accountDashVars = { loginPath, fallbackName, greetingTemplate, reviewedBadgeText, invoiceLang, invoiceLabels, loyaltyProgressTemplate, loyaltyRemainingOne, loyaltyRemainingOther, loyaltyUnlockedTitle, loyaltyUnlockedText, loyaltyStatRedeemedNever, loyaltyStatRedeemedSuffix, loyaltyLastRedeemed, aboLang, aboLabels };
</script>
<script>
// Schnellnavigation als echte Filter-Tabs: Klick zeigt NUR den passenden Bereich, blendet den
@@ -573,7 +621,7 @@ const angekommeneBestellungen = beispielBestellungen.filter((b) => b.status ===
import { fuegeTeddyGratisHinzu } from "../../../scripts/cart";
import { formatPrice } from "../../../i18n/format";
import { aboStufenKonfig } from "../../../data/abo";
const { loginPath, fallbackName, greetingTemplate, reviewedBadgeText, invoiceLang, invoiceLabels, loyaltyProgressTemplate, loyaltyRemainingOne, loyaltyRemainingOther, loyaltyUnlockedTitle, loyaltyUnlockedText, aboLang, aboLabels } = (window as any).__accountDashVars;
const { loginPath, fallbackName, greetingTemplate, reviewedBadgeText, invoiceLang, invoiceLabels, loyaltyProgressTemplate, loyaltyRemainingOne, loyaltyRemainingOther, loyaltyUnlockedTitle, loyaltyUnlockedText, loyaltyStatRedeemedNever, loyaltyStatRedeemedSuffix, loyaltyLastRedeemed, aboLang, aboLabels } = (window as any).__accountDashVars;
// Zugriffsschutz: diese Seite ist nur für angemeldete Kund:innen gedacht. Es gibt noch kein
// echtes Backend, das den Zugriff serverseitig verweigern könnte (Phase 2) — deshalb hier per
@@ -617,6 +665,7 @@ const angekommeneBestellungen = beispielBestellungen.filter((b) => b.status ===
// Treuebonus: Fortschritt/Status live aus dem Konto rendern (Nav-Kachel + Inhaltsbereich).
function renderLoyalty() {
const setText = (id: string, text: string) => { const el = document.getElementById(id); if (el) el.textContent = text; };
const f = treueFortschritt(getAccount());
const navValue = document.getElementById("loyalty-nav-value");
if (navValue) navValue.textContent = f.gesperrt ? "🔒" : `${f.aktuelle}/${f.ziel}`;
@@ -638,15 +687,28 @@ const angekommeneBestellungen = beispielBestellungen.filter((b) => b.status ===
.replace("{ziel}", String(f.ziel));
}
const statusText = document.getElementById("loyalty-status-text");
const cartLink = document.getElementById("loyalty-cart-link") as HTMLElement | null;
if (statusText) {
if (f.aktiv) {
statusText.innerHTML = `<strong>${loyaltyUnlockedTitle}</strong><br />${loyaltyUnlockedText}`;
if (cartLink) cartLink.style.display = "inline-flex";
setText("loyalty-stat-collected", `${f.aktuelle}/${f.ziel}`);
setText("loyalty-stat-missing", String(f.fehlende));
setText("loyalty-stat-redeemed", f.eingeloestGesamt > 0 ? `${f.eingeloestGesamt}${loyaltyStatRedeemedSuffix}` : loyaltyStatRedeemedNever);
const lastRedeemedEl = document.getElementById("loyalty-last-redeemed");
if (lastRedeemedEl) {
if (f.letzteEinloesungDatum) {
lastRedeemedEl.textContent = loyaltyLastRedeemed.replace("{date}", f.letzteEinloesungDatum);
lastRedeemedEl.style.display = "";
} else {
lastRedeemedEl.style.display = "none";
}
}
const readyBlock = document.getElementById("loyalty-ready-block");
const statusText = document.getElementById("loyalty-status-text");
if (f.aktiv) {
if (readyBlock) readyBlock.style.display = "flex";
if (statusText) statusText.textContent = "";
} else {
if (readyBlock) readyBlock.style.display = "none";
if (statusText) {
statusText.textContent = f.fehlende === 1 ? loyaltyRemainingOne : loyaltyRemainingOther.replace("{n}", String(f.fehlende));
if (cartLink) cartLink.style.display = "none";
}
}
}