PageSourceSearch

https://app.replicants.ai/js/money-format.js?v=20260929m24

js replicants.ai collected 2026-10-05 12:29:49 UTC 7,655 bytes, 165 lines download raw bytes

1/**
2 * money-format.js — THE ONE MONEY FORMATTER for the coach's screens, and the
3 * one list of currencies a business can sell in. Shared verbatim by the
4 * browser and the server (the same dual-mode export as date-format.js).
5 *
6 * Alex, 2026-09-29: *"there is no GBP sign. I just want to make sure that the
7 * currency that the accounts are in is similar to how we determine it so that
8 * we default to the country… put a setting in Maya Brain about what currency
9 * is the main currency for each customer so they can change it if they want
10 * to. For example I want to always charge in USD although I'm UK-based."*
11 *
12 * ── What lives here ─────────────────────────────────────────────────────
13 *   CURRENCIES          the currencies a business can choose as its main one
14 *                       and price in: all two-decimal Stripe currencies, the
15 *                       common ones first. Zero-decimal ones (JPY, HUF…) are
16 *                       left out on purpose: every price box on the platform
17 *                       works in hundredths.
18 *   currencyForCountry  an account's country → its default main currency.
19 *   format(minor, cur)  "£49.00", "$1,497.00", "A$49.00", "CHF 49.00". It is
20 *                       Intl.NumberFormat('en', { style: 'currency' }), the
21 *                       call the order screens already made, in one place.
22 *   symbol(cur)         "£", "$", "€", "A$", "CHF".
23 *
24 * ── The business's main currency (browser only) ─────────────────────────
25 * `App.Money.main()` is the signed-in business profile's main currency
26 * (Maya's settings → Main currency; /auth/me `main_currency`, resolved on the
27 * server by services/business-currency.js). It only chooses the DEFAULT for a
28 * new price and what is shown where nothing else names a currency. A price
29 * that exists keeps its own currency, always.
30 *
31 * ⚠️ The buyer-facing runtimes (checkout, receipt, course access) are inlined
32 * into published pages and carry their own character-identical formatter
33 * (html-sanitiser `_deriveMainPriceLabel`); they are NOT this file.
34 */
35(function (root) {
36  'use strict';
37
38  /* Common first, in the order Alex named them, then the rest by name. */
39  var CURRENCIES = [
40    { code: 'gbp', name: 'British pound' },
41    { code: 'usd', name: 'US dollar' },
42    { code: 'eur', name: 'Euro' },
43    { code: 'aud', name: 'Australian dollar' },
44    { code: 'cad', name: 'Canadian dollar' },
45    { code: 'nzd', name: 'New Zealand dollar' },
46    { code: 'czk', name: 'Czech koruna' },
47    { code: 'dkk', name: 'Danish krone' },
48    { code: 'hkd', name: 'Hong Kong dollar' },
49    { code: 'mxn', name: 'Mexican peso' },
50    { code: 'nok', name: 'Norwegian krone' },
51    { code: 'pln', name: 'Polish zloty' },
52    { code: 'sgd', name: 'Singapore dollar' },
53    { code: 'zar', name: 'South African rand' },
54    { code: 'sek', name: 'Swedish krona' },
55    { code: 'chf', name: 'Swiss franc' },
56    { code: 'aed', name: 'UAE dirham' },
57  ];
58  var CODES = CURRENCIES.map(function (c) { return c.code; });
59  var DEFAULT_CURRENCY = 'usd';
60
61  /* The eurozone, plus the places that use the euro without being in it. */
62  var EURO = ['at', 'be', 'hr', 'cy', 'ee', 'fi', 'fr', 'de', 'gr', 'ie', 'it', 'lv', 'lt',
63    'lu', 'mt', 'nl', 'pt', 'sk', 'si', 'es', 'ad', 'mc', 'sm', 'va', 'me', 'xk'];
64  var BY_COUNTRY = {
65    gb: 'gbp', uk: 'gbp', je: 'gbp', gg: 'gbp', im: 'gbp',
66    us: 'usd', pr: 'usd',
67    au: 'aud', ca: 'cad', nz: 'nzd',
68    ch: 'chf', li: 'chf', dk: 'dkk', no: 'nok', se: 'sek', pl: 'pln', cz: 'czk',
69    hk: 'hkd', sg: 'sgd', za: 'zar', ae: 'aed', mx: 'mxn',
70  };
71  EURO.forEach(function (c) { BY_COUNTRY[c] = 'eur'; });
72
73  /** 'GBP' / ' gbp ' → 'gbp'; anything not in CURRENCIES → null. */
74  function normalise(code) {
75    var c = String(code == null ? '' : code).trim().toLowerCase();
76    return CODES.indexOf(c) === -1 ? null : c;
77  }
78
79  function isSupported(code) { return normalise(code) !== null; }
80
81  /** An ISO 3166 alpha-2 country ('gb', 'US') → its currency; unknown → 'usd'. */
82  function currencyForCountry(cc) {
83    var c = String(cc == null ? '' : cc).trim().toLowerCase();
84    return BY_COUNTRY[c] || DEFAULT_CURRENCY;
85  }
86
87  function _nf(code, whole) {
88    var opts = { style: 'currency', currency: String(code).toUpperCase() };
89    if (whole) { opts.minimumFractionDigits = 0; opts.maximumFractionDigits = 0; }
90    return new Intl.NumberFormat('en', opts);
91  }
92
93  /**
94   * Minor units → money with its sign: format(4900, 'gbp') → "£49.00".
95   * `{ whole: true }` drops ".00" from a whole amount ("£49", but "£49.50").
96   * A currency Intl does not know still says what it is ("49.00 XYZ"), never
97   * a bare number and never a wrong sign.
98   */
99  function format(minor, currency, opts) {
100    var n = (Number(minor) || 0) / 100;
101    var code = String(currency || '').trim();
102    var whole = !!(opts && opts.whole) && Math.round(n * 100) % 100 === 0;
103    if (!code) return whole ? String(n) : n.toFixed(2);
104    try { return _nf(code, whole).format(n); } catch (_) {
105      return (whole ? String(n) : n.toFixed(2)) + ' ' + code.toUpperCase();
106    }
107  }
108
109  /** The same, from major units (a figure a coach typed): 49 → "£49.00". */
110  function formatMajor(major, currency, opts) {
111    return format(Math.round((Number(major) || 0) * 100), currency, opts);
112  }
113
114  /** 'gbp' → '£', 'aud' → 'A$', 'chf' → 'CHF'. Unknown → the code in capitals. */
115  function symbol(currency) {
116    var code = String(currency || '').trim();
117    if (!code) return '';
118    try {
119      var parts = _nf(code, false).formatToParts(1);
120      for (var i = 0; i < parts.length; i++) if (parts[i].type === 'currency') return parts[i].value;
121    } catch (_) { /* below */ }
122    return code.toUpperCase();
123  }
124
125  /** '<option>' rows for a currency select, the given one selected. */
126  function optionsHtml(selected) {
127    var sel = normalise(selected);
128    return CURRENCIES.map(function (c) {
129      return '<option value="' + c.code + '"' + (c.code === sel ? ' selected' : '') + '>' +
130        c.code.toUpperCase() + ' ' + symbol(c.code).replace(/&/g, '&amp;').replace(/</g, '&lt;') +
131        ' · ' + c.name + '</option>';
132    }).join('');
133  }
134
135  var API = {
136    CURRENCIES: CURRENCIES, CODES: CODES, DEFAULT_CURRENCY: DEFAULT_CURRENCY,
137    normalise: normalise, isSupported: isSupported, currencyForCountry: currencyForCountry,
138    format: format, formatMajor: formatMajor, symbol: symbol, optionsHtml: optionsHtml,
139  };
140
141  if (typeof module !== 'undefined' && module.exports) module.exports = API;
142  if (!root || typeof root !== 'object') return;
143  root.CBMoney = API;
144
145  /* ── Browser: the signed-in business's main currency ─────────────────── */
146  var _main = null;
147  function main() {
148    if (_main) return _main;
149    var u = null;
150    try { u = (typeof currentUser !== 'undefined' && currentUser) || root.currentUser || null; } catch (_) { u = root.currentUser || null; }
151    return normalise(u && u.main_currency) || currencyForCountry(u && u.home_country);
152  }
153  function setMain(code) {
154    var c = normalise(code);
155    if (!c) return;
156    _main = c;
157    try { if (typeof currentUser !== 'undefined' && currentUser) currentUser.main_currency = c; } catch (_) {}
158  }
159  var Money = {};
160  for (var k in API) Money[k] = API[k];
161  Money.main = main;
162  Money.setMain = setMain;
163  root.App = root.App || {};
164  root.App.Money = Money;
165})(typeof window !== 'undefined' ? window : this);

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.