PageSourceSearch

https://www.ndlalaarena.co.za/assets/config.js

js ndlalaarena.co.za collected 2026-10-08 20:00:46 UTC 6,403 bytes, 121 lines download raw bytes

1/* ============================================================
2   Ndlala Arena — data-driven display
3   Pulls prices + package details from /api/public/config and updates:
4   - window.NDLALA.TIERS prices (seat picker totals/legend)
5   - [data-price="<tier>"] elements (tier price)
6   - [data-price-from] elements (lowest price)
7   - [data-includes="<tier>"] lists (package "what's included")
8   Static HTML holds fallbacks, so the site works if this fails.
9   Exposes window.NDLALA_CONFIG_READY (a promise) for the seat picker to await.
10   Load in <head>, after site.js.
11   ============================================================ */
12(function () {
13  function rands(cents) { var n = Math.round((cents || 0) / 100); return 'R' + String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ' '); }
14
15  // What a seat COSTS TODAY. The server publishes both the list price and the
16  // early bird price per tier and decides the discount on its own clock, so the
17  // page never works one out for itself. A browser with a wrong clock would
18  // otherwise advertise a price /api/reserve then refuses to honour.
19  function nowCents(t) {
20    return (t && t.early_price_cents != null) ? t.early_price_cents : (t ? t.price_cents : 0);
21  }
22
23  function updateTiers(cfg) {
24    if (!cfg || !cfg.tiers) return;
25    if (window.NDLALA && window.NDLALA.TIERS) {
26      // The seat picker, the cart and the checkout all total from this one
27      // number, so it has to be the price that will actually be charged. Leaving
28      // it at list would show a total that shrinks at the reserve step.
29      cfg.tiers.forEach(function (t) { if (window.NDLALA.TIERS[t.key]) window.NDLALA.TIERS[t.key].price = Math.round(nowCents(t) / 100); });
30    }
31    window.NDLALA_EARLY = (cfg && cfg.event && cfg.event.earlyPct > 0)
32      ? { pct: cfg.event.earlyPct, until: cfg.event.earlyUntil } : null;
33  }
34  function applyDom(cfg) {
35    if (!cfg || !cfg.tiers) return;
36    var byKey = {}; cfg.tiers.forEach(function (t) { byKey[t.key] = t; });
37    document.querySelectorAll('[data-price]').forEach(function (el) {
38      var t = byKey[el.getAttribute('data-price')];
39      if (!t) return;
40      // Discounted: show what it costs now, with the list price struck through
41      // beside it, so the saving is visible rather than just a smaller number.
42      if (t.early_price_cents != null && t.early_price_cents < t.price_cents) {
43        el.innerHTML = '<span class="was">' + rands(t.price_cents) + '</span> ' + rands(t.early_price_cents);
44      } else {
45        el.textContent = rands(t.price_cents);
46      }
47    });
48    var prices = cfg.tiers.map(nowCents);
49    if (prices.length) { var min = Math.min.apply(null, prices); document.querySelectorAll('[data-price-from]').forEach(function (el) { el.textContent = rands(min); }); }
50    document.querySelectorAll('[data-includes]').forEach(function (el) {
51      var t = byKey[el.getAttribute('data-includes')];
52      var items = t && t.includes ? t.includes.split('\n').map(function (x) { return x.trim(); }).filter(Boolean) : [];
53      var wrap = el.closest('[data-includes-wrap]');
54      if (items.length) { el.innerHTML = items.map(function (li) { return '<li>' + li.replace(/&/g, '&amp;').replace(/</g, '&lt;') + '</li>'; }).join(''); if (wrap) wrap.style.display = ''; }
55      else if (wrap) { wrap.style.display = 'none'; }
56    });
57  }
58  // window.NDLALA.EVENT is a hardcoded night-one fallback in site.js. Once the API
59  // has told us which night this page is actually selling, overwrite it, or the
60  // seat picker prints night one's date and day above night two's seats.
61  function updateEvent(cfg) {
62    var e = cfg && cfg.event, E = window.NDLALA && window.NDLALA.EVENT;
63    if (!e || !E) return;
64    if (e.slug)       E.id = e.slug;
65    if (e.name)       E.name = e.name;
66    if (e.tagline)    E.tagline = e.tagline;
67    if (e.dayLabel)   E.dayLabel = e.dayLabel;
68    if (e.doorsLabel) E.doors = e.doorsLabel;
69    if (e.venue)      E.venue = e.venue;
70    if (e.address)    E.address = e.address;
71    if (e.date) {
72      E.dateISO = String(e.date).slice(0, 10);
73      // The night is a plain calendar date — format it with no time zone at all,
74      // so it reads the same everywhere (see NDLALA_EV.plainDate).
75      if (window.NDLALA_EV && window.NDLALA_EV.plainDate) {
76        E.dateLabel = window.NDLALA_EV.plainDate(e.date, { day: 'numeric', month: 'long', year: 'numeric' });
77      }
78    }
79  }
80
81  function whenReady(fn) { if (document.readyState !== 'loading') fn(); else document.addEventListener('DOMContentLoaded', fn); }
82
83  // Prices are per event now. Resolve which night this page is about first, so a
84  // night-two page never shows night-one pricing.
85  function cfgUrl() {
86    try {
87      var e = new URLSearchParams(location.search).get('e') || new URLSearchParams(location.search).get('event');
88      if (e) return '/api/public/config?event=' + encodeURIComponent(e);
89    } catch (x) {}
90    try {
91      var c = JSON.parse(sessionStorage.getItem('ndlala_cart') || '{}');
92      if (c && c.event) return '/api/public/config?event=' + encodeURIComponent(c.event);
93    } catch (x) {}
94    return null;
95  }
96
97  // No night in the link (the homepage Book Now, every What's On link) used to
98  // ask the API for its default, which is night one of the August run: a past
99  // event with no tiers, so the page kept the stale prices in its HTML. Thami saw
100  // R13 000 for Diamond on 6 Oct that way. Ask events.js which night is actually
101  // on sale instead, the same night event-page.js renders.
102  function resolveUrl() {
103    var u = cfgUrl();
104    if (u) return Promise.resolve(u);
105    var EV = window.NDLALA_EV;
106    if (!EV || !EV.current) return Promise.resolve('/api/public/config');
107    return EV.current().then(function (slug) {
108      return slug ? '/api/public/config?event=' + encodeURIComponent(slug) : '/api/public/config';
109    }, function () { return '/api/public/config'; });
110  }
111
112  window.NDLALA_CONFIG_READY = resolveUrl()
113    .then(function (u) { return fetch(u, { cache: 'no-store' }); })
114    .then(function (r) { return r.ok ? r.json() : null; })
115    .then(function (cfg) {
116      // Buyer-facing hold copy reads this instead of hardcoding a duration.
117      if (cfg && cfg.holdMinutes) window.NDLALA_HOLD_MINUTES = cfg.holdMinutes;
118      updateTiers(cfg); updateEvent(cfg); whenReady(function () { applyDom(cfg); }); return cfg;
119    })
120    .catch(function () {});
121})();

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.