PageSourceSearch

https://site.fr/_static/ad2521ddc1b5acb4/scripts/price-context.js

js site.fr collected 2026-10-02 04:04:33 UTC 6,619 bytes, 241 lines download raw bytes

1import '../shared/price-core.js';
2import { getCurrentUser } from './client-utils.js';
3
4const SELECTED_VAT_STORAGE_KEY = 'selectedVat';
5
6const normalizeSelectedVat = (value) => {
7  if (value === null || value === undefined) {
8    return null;
9  }
10
11  const raw = String(value).trim().replace('%', '').trim();
12  if (!raw) {
13    return null;
14  }
15
16  const numeric = Number(raw.replace(',', '.'));
17  if (!Number.isFinite(numeric) || numeric < 0 || numeric > 100) {
18    return null;
19  }
20
21  return numeric
22    .toFixed(2)
23    .replace(/\.00$/, '')
24    .replace(/(\.\d)0$/, '$1');
25};
26
27const extractVatValue = (entry) => {
28  if (entry === null || entry === undefined) {
29    return null;
30  }
31  if (typeof entry === 'number' || typeof entry === 'string') {
32    return entry;
33  }
34  if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
35    return null;
36  }
37
38  return (
39    entry.percentage ??
40    entry.vat ??
41    entry.vat_percentage ??
42    entry.vatPercentage ??
43    entry.rate ??
44    entry.percent ??
45    entry.value ??
46    entry.amount ??
47    entry.tax ??
48    entry.vatRate ??
49    null
50  );
51};
52
53const buildVatOptions = (vatList) => {
54  if (!Array.isArray(vatList)) {
55    return [];
56  }
57
58  const options = [];
59  const seen = new Set();
60  vatList.forEach((entry) => {
61    const value = normalizeSelectedVat(extractVatValue(entry));
62    if (value === null || seen.has(value)) {
63      return;
64    }
65    seen.add(value);
66    options.push(Object.freeze({ value, label: `${value}%` }));
67  });
68  return options;
69};
70
71const getDefaultStorage = () => {
72  try {
73    return globalThis.localStorage || null;
74  } catch {
75    return null;
76  }
77};
78
79const readStoredVat = (storage) => {
80  try {
81    return normalizeSelectedVat(storage?.getItem?.(SELECTED_VAT_STORAGE_KEY));
82  } catch {
83    return null;
84  }
85};
86
87const persistVat = (storage, value) => {
88  try {
89    storage?.setItem?.(SELECTED_VAT_STORAGE_KEY, value);
90  } catch {}
91};
92
93const normalizeBulkDiscountLevel = (value) => {
94  const normalize = globalThis.PRICE_CORE?.normalizeBulkDiscountLevel;
95  if (typeof normalize === 'function') {
96    return normalize(value);
97  }
98  const numeric = Number(value);
99  return Number.isFinite(numeric) ? Math.max(0, Math.min(10, Math.floor(numeric))) : 0;
100};
101
102const createPriceContext = ({ getUser = getCurrentUser, storage = undefined } = {}) => {
103  const listeners = new Set();
104  const resolvedStorage = storage === undefined ? getDefaultStorage() : storage;
105  const state = {
106    selectedVat: null,
107    vatOptions: [],
108    bulkDiscountLevel: 0,
109    bulkDiscountStatus: 'idle',
110  };
111  let bulkDiscountPending = null;
112  let bulkDiscountRequestToken = 0;
113
114  const getSnapshot = () =>
115    Object.freeze({
116      selectedVat: state.selectedVat,
117      vatOptions: Object.freeze([...state.vatOptions]),
118      bulkDiscountLevel: state.bulkDiscountLevel,
119      bulkDiscountStatus: state.bulkDiscountStatus,
120    });
121
122  const notify = (reason) => {
123    const snapshot = getSnapshot();
124    listeners.forEach((listener) => {
125      try {
126        listener(snapshot, { reason });
127      } catch (error) {
128        console.error('Price context subscriber failed:', error);
129      }
130    });
131    return snapshot;
132  };
133
134  const initializeVatSelection = (vatList) => {
135    const vatOptions = buildVatOptions(vatList);
136    const storedVat = readStoredVat(resolvedStorage);
137    const selectedVat = vatOptions.some((option) => option.value === storedVat)
138      ? storedVat
139      : vatOptions[0]?.value || null;
140    const optionsChanged =
141      vatOptions.length !== state.vatOptions.length ||
142      vatOptions.some((option, index) => option.value !== state.vatOptions[index]?.value);
143    const selectionChanged = state.selectedVat !== selectedVat;
144
145    state.vatOptions = vatOptions;
146    state.selectedVat = selectedVat;
147    return optionsChanged || selectionChanged ? notify('vat-initialized') : getSnapshot();
148  };
149
150  const setSelectedVat = (value, { persist = true } = {}) => {
151    const normalized = normalizeSelectedVat(value);
152    if (normalized === null) {
153      return getSnapshot();
154    }
155    if (state.vatOptions.length && !state.vatOptions.some((option) => option.value === normalized)) {
156      return getSnapshot();
157    }
158    if (state.selectedVat === normalized) {
159      return getSnapshot();
160    }
161
162    state.selectedVat = normalized;
163    if (persist) {
164      persistVat(resolvedStorage, normalized);
165    }
166    return notify('vat-changed');
167  };
168
169  const loadBulkDiscountLevel = ({ force = false } = {}) => {
170    if (!force && state.bulkDiscountStatus === 'success') {
171      return Promise.resolve(state.bulkDiscountLevel);
172    }
173    if (!force && bulkDiscountPending) {
174      return bulkDiscountPending;
175    }
176
177    const requestToken = bulkDiscountRequestToken + 1;
178    bulkDiscountRequestToken = requestToken;
179    state.bulkDiscountStatus = 'loading';
180
181    const pending = Promise.resolve()
182      .then(() => getUser({ force }))
183      .then((user) => normalizeBulkDiscountLevel(user?.bulk_discount_level))
184      .catch(() => 0)
185      .then((bulkDiscountLevel) => {
186        if (requestToken === bulkDiscountRequestToken) {
187          state.bulkDiscountLevel = bulkDiscountLevel;
188          state.bulkDiscountStatus = 'success';
189          notify('bulk-discount-loaded');
190        }
191        return bulkDiscountLevel;
192      })
193      .finally(() => {
194        if (bulkDiscountPending === pending) {
195          bulkDiscountPending = null;
196        }
197      });
198
199    bulkDiscountPending = pending;
200    return pending;
201  };
202
203  const subscribe = (listener, { emitCurrent = false } = {}) => {
204    if (typeof listener !== 'function') {
205      return () => {};
206    }
207    listeners.add(listener);
208    if (emitCurrent) {
209      listener(getSnapshot(), { reason: 'current' });
210    }
211    return () => listeners.delete(listener);
212  };
213
214  return Object.freeze({
215    getSnapshot,
216    initializeVatSelection,
217    setSelectedVat,
218    loadBulkDiscountLevel,
219    subscribe,
220  });
221};
222
223const priceContext = createPriceContext();
224
225const getPriceContextSnapshot = () => priceContext.getSnapshot();
226const initializeVatSelection = (vatList) => priceContext.initializeVatSelection(vatList);
227const setSelectedVat = (value, options) => priceContext.setSelectedVat(value, options);
228const loadBulkDiscountLevel = (options) => priceContext.loadBulkDiscountLevel(options);
229const subscribePriceContext = (listener, options) => priceContext.subscribe(listener, options);
230
231export {
232  SELECTED_VAT_STORAGE_KEY,
233  buildVatOptions,
234  createPriceContext,
235  getPriceContextSnapshot,
236  initializeVatSelection,
237  loadBulkDiscountLevel,
238  normalizeSelectedVat,
239  setSelectedVat,
240  subscribePriceContext,
241};

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.