PageSourceSearch

https://www.alphadi.org/js/cart-sync.js?v=9IsathfVPUw6nubjgrQKNjXkyoTVZ-DBaj0qJNKNn8w

js alphadi.org collected 2026-10-09 22:41:39 UTC 6,228 bytes, 143 lines download raw bytes

1/* Alphadi — Cart sync helper.
2 * The cart lives in localStorage and can go stale: prices change, sales start,
3 * items get unpublished/sold out between the moment they were added and the
4 * moment the visitor opens the cart / checkout. This re-resolves every line
5 * against the server (authoritative price + availability) and patches the
6 * passed-in state object in place, so drawer, cart page and checkout all agree
7 * with what will actually be charged in SubmitOrder. It also reports what
8 * changed so the UI can show a notice ("X was removed", "price of Y changed").
9 */
10(function () {
11  'use strict';
12
13  // "product-12__Hardcover" / "apt-5__p1699..." -> base id "product-12" / "apt-5"
14  function baseId(key) {
15    var raw = key || '';
16    var us = raw.indexOf('__');
17    return us >= 0 ? raw.slice(0, us) : raw;
18  }
19
20  // Keep requests on the current language route (matches the other /cart/* calls).
21  function langPrefix() {
22    var seg = (window.location.pathname.split('/').filter(Boolean)[0] || '');
23    return /^[a-z]{2}$/i.test(seg) ? '/' + seg : '';
24  }
25
26  function esc(s) {
27    return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
28      return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
29    });
30  }
31
32  /**
33   * Mutates `state` in place: updates price/name/image, drops unavailable lines,
34   * and persists back to localStorage. Resolves to a result object:
35   *   { changed: bool, removed: [name...], repriced: [{ name, price }...] }
36   */
37  function applyTo(state) {
38    var result = { changed: false, removed: [], repriced: [] };
39    if (!state || typeof state !== 'object') { return Promise.resolve(result); }
40
41    var keys = Object.keys(state).filter(function (k) { return state[k] && state[k].qty > 0; });
42    if (!keys.length) { return Promise.resolve(result); }
43
44    var payload = keys.map(function (k) {
45      return { key: k, id: baseId(k), type: state[k].type || '' };
46    });
47
48    return fetch(langPrefix() + '/cart/refresh', {
49      method: 'POST',
50      headers: { 'Content-Type': 'application/json' },
51      body: JSON.stringify(payload)
52    })
53      .then(function (r) { return r.ok ? r.json() : null; })
54      .then(function (data) {
55        if (!data || !data.items) { return result; }
56
57        var map = {};
58        data.items.forEach(function (it) { map[it.key] = it; });
59
60        keys.forEach(function (k) {
61          var fresh = map[k];
62          var item = state[k];
63
64          if (!fresh || !fresh.available) {
65            result.removed.push(item.name || k);
66            delete state[k];
67            result.changed = true;
68            return;
69          }
70
71          var isBooking = item.type === 'training' || item.type === 'event';
72          // Products with selectable options carry option surcharges baked into their
73          // stored price, so we can't safely overwrite it from the base price — the
74          // server flags those via `hasOptions`. Simple products and bookings are safe.
75          if (!fresh.hasOptions && typeof fresh.price === 'number' && fresh.price !== item.price) {
76            result.repriced.push({ name: fresh.name || item.name || k, price: fresh.price });
77            item.price = fresh.price;
78            result.changed = true;
79          }
80          if (fresh.name && fresh.name !== item.name) { item.name = fresh.name; result.changed = true; }
81          if (!isBooking && typeof fresh.image === 'string' && fresh.image !== item.image) {
82            item.image = fresh.image; result.changed = true;
83          }
84        });
85
86        if (result.changed) {
87          localStorage.setItem('alphadi-cart', JSON.stringify(state));
88        }
89        return result;
90      })
91      .catch(function () { return result; });
92  }
93
94  /**
95   * Renders (or clears) a dismissible notice inside `container` describing what
96   * the last sync changed. Message templates are read from the container's
97   * data-msg-* attributes (translated server-side) with English fallbacks.
98   */
99  function showNotice(result, container) {
100    if (!container) { return; }
101    var removed = (result && result.removed) || [];
102    var repriced = (result && result.repriced) || [];
103
104    if (!removed.length && !repriced.length) {
105      container.style.display = 'none';
106      container.innerHTML = '';
107      return;
108    }
109
110    var d = container.dataset || {};
111    var tTitle = d.msgTitle || 'Your cart was updated';
112    var tRemoved = d.msgRemoved || '{name} is no longer available and was removed.';
113    var tRepriced = d.msgRepriced || 'Price of {name} changed to {price}.';
114
115    var lines = [];
116    removed.forEach(function (name) {
117      lines.push('<li>' + tRemoved.replace('{name}', '<strong>' + esc(name) + '</strong>') + '</li>');
118    });
119    repriced.forEach(function (it) {
120      lines.push('<li>' + tRepriced
121        .replace('{name}', '<strong>' + esc(it.name) + '</strong>')
122        .replace('{price}', (window.formatPrice ? window.formatPrice(it.price) : '€' + (Number(it.price) || 0).toFixed(2))) + '</li>');
123    });
124
125    container.innerHTML =
126      '<div style="background:#fff8e6;border:1px solid #f0d38a;border-radius:8px;padding:.7rem .9rem;position:relative;font-family:var(--f-ui,sans-serif);">' +
127        '<div style="font-weight:700;font-size:.82rem;color:#8a6d1a;margin-bottom:.3rem;padding-right:1.2rem;">' + esc(tTitle) + '</div>' +
128        '<ul style="margin:0;padding-left:1.1rem;font-size:.78rem;color:#6b5514;line-height:1.5;">' + lines.join('') + '</ul>' +
129        '<button type="button" class="cart-notice__close" aria-label="close" style="position:absolute;top:.35rem;right:.5rem;background:none;border:none;font-size:1.15rem;line-height:1;color:#8a6d1a;cursor:pointer;">&times;</button>' +
130      '</div>';
131    container.style.display = '';
132
133    var closeBtn = container.querySelector('.cart-notice__close');
134    if (closeBtn) {
135      closeBtn.addEventListener('click', function () {
136        container.style.display = 'none';
137        container.innerHTML = '';
138      });
139    }
140  }
141
142  window.AlphadiCartSync = { applyTo: applyTo, showNotice: showNotice };
143})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.