Files
hd-commerce/public/wishlist.js
T
till 30c41c355e v2.3: Feature-Module live — Suche, Merkliste, Kundenkonten+Adressbuch, Bewertungen, Abandoned-Cart
- feature_search: Storefront-Header-Suche + /suche (SSR, SQLite LIKE, case-insensitiv; Name/Kurz/Desc/Material/Kategorie), Treffer als Karten, Leer-Zustand
- feature_wishlist: Herz-Button auf Karten/PDP (localStorage, public/wishlist.js) + /merkliste
- feature_accounts: getrennte Kunden-Session (Cookie hdc_customer, scrypt), /konto/registrieren|anmelden|abmelden, /konto (Bestellhistorie+Adressbuch), Tabelle customer_addresses, Checkout-Vorbefuellung + orders.customer_id-Zuordnung; Gast-Checkout bleibt
- feature_reviews: Tabelle reviews (1-5, Moderation), /api/review (approved=0), PDP-Anzeige Durchschnitt+Reviews + aggregateRating-JSON-LD, Admin /bewertungen (Freigeben/Verbergen/Loeschen) + Nav-Zaehler
- feature_abandoned_cart: Tabelle abandoned_carts, /api/cart-capture beim Checkout-Start, /api/cron/abandoned (CRON_TOKEN) sendet Erinnerungsmail (Mailer/Log) + reminded=1, recovered=1 bei Bestellung; Status in Einstellungen
- Gating: Flag aus => Storefront-Elemente weg, Routen 302/404, Admin-Nav-Punkt entfaellt; KEIN 'in Vorbereitung' mehr
- API/MCP: reviews CRUD + abandoned_carts (read) in admin-api + ai-admin.txt + MCP-Tools; Manifest v2.3
- README + .env.example (CRON_TOKEN, ABANDONED_AFTER_MINUTES); 16 neue Unit-Tests (Suche/Review-Avg/Kunden/Abandoned)
2026-06-18 07:27:34 +00:00

76 lines
4.0 KiB
JavaScript

/* hd-commerce — Merkliste (Wunschliste) clientseitig via localStorage. */
(function () {
var KEY = 'hdc_wishlist';
function read() { try { return JSON.parse(localStorage.getItem(KEY) || '[]'); } catch (e) { return []; } }
function write(c) { localStorage.setItem(KEY, JSON.stringify(c)); updateBadge(); paintButtons(); }
function has(slug) { return read().some(function (i) { return i.slug === slug; }); }
function toggle(item) {
var c = read();
var idx = c.findIndex(function (i) { return i.slug === item.slug; });
if (idx > -1) { c.splice(idx, 1); } else { c.push(item); }
write(c);
return idx === -1; // true wenn jetzt gemerkt
}
function remove(slug) { write(read().filter(function (i) { return i.slug !== slug; })); }
function updateBadge() {
var b = document.getElementById('wishBadge');
if (!b) return;
var n = read().length;
b.textContent = n;
b.classList.toggle('show', n > 0);
}
function paintButtons() {
document.querySelectorAll('[data-wish]').forEach(function (btn) {
var p; try { p = JSON.parse(btn.getAttribute('data-wish') || '{}'); } catch (e) { p = {}; }
btn.classList.toggle('active', has(p.slug));
btn.setAttribute('aria-pressed', has(p.slug) ? 'true' : 'false');
});
}
window.HDCWish = { read: read, has: has, toggle: toggle, remove: remove, count: function () { return read().length; } };
document.addEventListener('DOMContentLoaded', function () {
updateBadge();
paintButtons();
document.querySelectorAll('[data-wish]').forEach(function (btn) {
btn.addEventListener('click', function (e) {
e.preventDefault(); e.stopPropagation();
var p; try { p = JSON.parse(btn.getAttribute('data-wish') || '{}'); } catch (err) { p = {}; }
if (!p.slug) return;
var added = toggle(p);
if (window.HDC && window.HDC.track) { /* optional analytics */ }
if (typeof window.hdcWishToast === 'function') window.hdcWishToast(added ? 'Gemerkt' : 'Entfernt');
});
});
// Merkliste-Seite rendern, falls vorhanden
var root = document.getElementById('wishlistRoot');
if (root) renderList(root);
});
function fmt(c, cur) { try { return new Intl.NumberFormat('de-DE', { style: 'currency', currency: cur || 'EUR' }).format((c||0)/100); } catch(e){ return ((c||0)/100).toFixed(2)+' '+(cur||'EUR'); } }
function renderList(root) {
var cur = root.getAttribute('data-currency') || 'EUR';
function draw() {
var items = read();
if (!items.length) {
root.innerHTML = '<div class="empty-state"><h2>Deine Merkliste ist leer</h2><p>Tippe auf das Herz an einem Produkt, um es hier zu sammeln.</p><a class="btn btn-primary btn-lg" href="/shop" style="margin-top:16px">Zum Shop</a></div>';
return;
}
var cards = items.map(function (p) {
return '<div class="prod-card-wrap">' +
'<a class="prod-card" href="/produkt/' + p.slug + '">' +
'<div class="prod-media">' + (p.image ? '<img src="' + p.image + '" alt="" loading="lazy">' : '') + '</div>' +
'<div class="prod-info"><span class="prod-cat">' + (p.category || '') + '</span>' +
'<span class="prod-name">' + (p.name || '') + '</span>' +
'<span class="prod-price">' + fmt(p.priceCents, cur) + '</span>' +
'<span class="prod-tax">inkl. MwSt.</span></div></a>' +
'<button class="wish-btn active" data-rm="' + p.slug + '" aria-label="Entfernen" title="Entfernen">' +
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.6l-1-1a5.5 5.5 0 0 0-7.8 7.8l1 1L12 21l7.8-7.6 1-1a5.5 5.5 0 0 0 0-7.8Z"/></svg></button></div>';
}).join('');
root.innerHTML = '<div class="prod-grid">' + cards + '</div>';
root.querySelectorAll('[data-rm]').forEach(function (b) {
b.addEventListener('click', function (e) { e.preventDefault(); remove(b.getAttribute('data-rm')); draw(); });
});
}
draw();
}
})();