@teddy-dev/frontend 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/algolia/algolia-loader.js +56 -0
  2. package/algolia/algolia-search.js +222 -0
  3. package/auth/teddy-auth-login.js +29 -0
  4. package/auth/teddy-auth-profile.js +58 -0
  5. package/auth/teddy-auth-register.js +52 -0
  6. package/auth/teddy-auth-reset.js +39 -0
  7. package/auth/teddy-auth.js +92 -0
  8. package/bantoa/bantoa-product.js +64 -0
  9. package/bantoa/bantoa-search.js +46 -0
  10. package/bantoa/bantoa-stylemix.js +43 -0
  11. package/bantoa/bantoa.js +21 -0
  12. package/cart/teddy-cart-controller.js +163 -0
  13. package/cart/teddy-cart-customer.js +86 -0
  14. package/cart/teddy-cart-discounts.js +55 -0
  15. package/cart/teddy-cart-promo-apply.js +24 -0
  16. package/cart/teddy-cart-promo-error.js +38 -0
  17. package/cart/teddy-cart-promo-loading.js +20 -0
  18. package/cart/teddy-cart-promo.js +96 -0
  19. package/components/mc-btn.js +38 -0
  20. package/components/mc-card-render.js +33 -0
  21. package/components/mc-cart-counter.js +14 -0
  22. package/components/mc-cart-error.js +35 -0
  23. package/components/mc-cart-quantity.js +43 -0
  24. package/components/mc-cart-remove.js +32 -0
  25. package/components/mc-copy.js +33 -0
  26. package/components/mc-element.js +84 -0
  27. package/components/mc-form-errors.js +35 -0
  28. package/components/mc-form-success.js +31 -0
  29. package/components/mc-form.js +223 -0
  30. package/components/mc-input-area.js +13 -0
  31. package/components/mc-input-checkbox-group.js +52 -0
  32. package/components/mc-input-checkbox.js +12 -0
  33. package/components/mc-input-password.js +39 -0
  34. package/components/mc-input-places.js +43 -0
  35. package/components/mc-input-quantity.js +43 -0
  36. package/components/mc-input-select.js +14 -0
  37. package/components/mc-input-text.js +12 -0
  38. package/components/mc-input.js +137 -0
  39. package/components/mc-loader.js +21 -0
  40. package/components/mc-modal.js +69 -0
  41. package/components/mc-password-eye.js +24 -0
  42. package/components/mc-password-tips.js +60 -0
  43. package/components/mc-qrcode.js +41 -0
  44. package/components/mc-range.js +108 -0
  45. package/components/mc-read-more.js +56 -0
  46. package/components/mc-recommended.js +50 -0
  47. package/components/mc-referrer.js +20 -0
  48. package/components/mc-splash-controller.js +32 -0
  49. package/components/mc-splash-link.js +65 -0
  50. package/components/mc-stoq.js +30 -0
  51. package/components/mc-swiper.js +333 -0
  52. package/components/mc-video.js +61 -0
  53. package/css/bootstrap-lg.css +2762 -0
  54. package/css/bootstrap-lg.min.css +1 -0
  55. package/css/bootstrap-md.css +2762 -0
  56. package/css/bootstrap-md.min.css +1 -0
  57. package/css/bootstrap-sm.css +2762 -0
  58. package/css/bootstrap-sm.min.css +1 -0
  59. package/css/bootstrap-xl.css +2790 -0
  60. package/css/bootstrap-xl.min.css +1 -0
  61. package/css/bootstrap-xxl.css +2762 -0
  62. package/css/bootstrap-xxl.min.css +1 -0
  63. package/css/bootstrap.css +9824 -0
  64. package/css/bootstrap.min.css +5 -0
  65. package/css/mc-form-errors.css +3 -0
  66. package/css/mc-loader.css +6 -0
  67. package/css/mc-password-eye.css +6 -0
  68. package/css/mc-password-tips.css +6 -0
  69. package/css/mc-read-more.css +14 -0
  70. package/css/mc-video.css +4 -0
  71. package/css/mc-wishlist-hero-btn.css +7 -0
  72. package/loader.js +110 -0
  73. package/loyalty/teddy-loyalty-create.js +40 -0
  74. package/loyalty/teddy-loyalty-redeem.js +38 -0
  75. package/loyalty/teddy-loyalty.js +214 -0
  76. package/package.json +20 -0
  77. package/res/mc-events.js +43 -0
  78. package/res/mc-shopify.js +439 -0
  79. package/res/mc-utils.js +119 -0
  80. package/res/mc-vue.js +9350 -0
  81. package/returns/teddy-return-iban.js +115 -0
  82. package/returns/teddy-return-list.js +165 -0
  83. package/returns/teddy-return-request.js +353 -0
  84. package/scripts/publish-store.js +62 -0
  85. package/test/auth-login-error.json +4 -0
  86. package/test/auth-login-success.json +13 -0
  87. package/test/auth-password-change-error.json +4 -0
  88. package/test/auth-password-change-success.json +3 -0
  89. package/test/auth-register-error.json +4 -0
  90. package/test/auth-register-success.json +12 -0
  91. package/test/auth-reset-error.json +4 -0
  92. package/test/auth-reset-success.json +3 -0
  93. package/test/loyalty-error.json +4 -0
  94. package/test/loyalty-noinfo.json +18 -0
  95. package/test/loyalty-redeem-error.json +4 -0
  96. package/test/loyalty-redeem-success.json +11 -0
  97. package/test/loyalty-success.json +143 -0
  98. package/test/returns-create-success.json +1 -0
  99. package/test/returns-get-success.json +35 -0
  100. package/test/returns-get-tracking.json +30 -0
  101. package/test/returns-list-empty.json +4 -0
  102. package/test/returns-list-success.json +59 -0
  103. package/test/returns-search-manual.json +67 -0
  104. package/test/returns-search-success.json +76 -0
  105. package/test/wishlist-error.json +4 -0
  106. package/test/wishlist-success.json +3 -0
  107. package/wishlist/teddy-wishlist-btn.js +55 -0
  108. package/wishlist/teddy-wishlist-clear.js +18 -0
  109. package/wishlist/teddy-wishlist-counter.js +35 -0
  110. package/wishlist/teddy-wishlist-item.js +46 -0
  111. package/wishlist/teddy-wishlist-page.js +59 -0
  112. package/wishlist/teddy-wishlist.js +131 -0
@@ -0,0 +1,439 @@
1
+ import '../res/mc-events.js';
2
+ await loadComponent('./res/mc-utils.js', false);
3
+
4
+ class McShopify {
5
+
6
+ // elenco di storage keys usati da McShopify
7
+ static STORAGE = {
8
+ COOKIE_CONSENT: 'cookie_consent', // Key usata per salvare le preferenze dei cookie
9
+ };
10
+
11
+ // Carica le customerPrivacy di Shopify. Da chiamare subito sempre, quando c'è un cookie banner
12
+ static loadCustomerPrivacy = (callback) => {
13
+ window.Shopify.loadFeatures(
14
+ [
15
+ {
16
+ name: 'consent-tracking-api',
17
+ version: '0.1',
18
+ },
19
+ ],
20
+ error => {
21
+ if (error) {
22
+ throw new Error(error);
23
+ }
24
+ if (typeof callback === 'function') callback();
25
+ }
26
+ );
27
+ }
28
+
29
+ // Da chiamare nel cookie banner di iubenda per settare i consensi. Pubblica l'evento COOKIE_CONSENT
30
+ static iubendaIntegration = (preferences) => {
31
+ const isPrefEmpty = !preferences || Object.keys(preferences).length === 0;
32
+ const consent = isPrefEmpty ? _iub.cs.api.getPreferences() : preferences;
33
+ const shopifyPurposes = {
34
+ "analytics": [4, 's'],
35
+ "marketing": [5, 'adv'],
36
+ "preferences": [2, 3],
37
+ "sale_of_data": ['s', 'sh'],
38
+ }
39
+ const expressedConsent = {};
40
+ Object.keys(shopifyPurposes).forEach(function(purposeItem) {
41
+ var purposeExpressed = null
42
+ shopifyPurposes[purposeItem].forEach(item => {
43
+ if (consent.purposes && typeof consent.purposes[item] === 'boolean') {
44
+ purposeExpressed = consent.purposes[item];
45
+ }
46
+ if (consent.uspr && typeof consent.uspr[item] === 'boolean' && purposeExpressed !== false) {
47
+ purposeExpressed = consent.uspr[item];
48
+ }
49
+ })
50
+
51
+ if (typeof purposeExpressed === 'boolean') {
52
+ expressedConsent[purposeItem] = purposeExpressed;
53
+ }
54
+ });
55
+ localStorage.setItem(McShopify.STORAGE.COOKIE_CONSENT, JSON.stringify(expressedConsent));
56
+ window.Shopify.customerPrivacy.setTrackingConsent(expressedConsent);
57
+ McShopify.customEvent(MCEVENTS.COOKIE_CONSENT, expressedConsent);
58
+ }
59
+
60
+ // Pubblica un evento su window e per le analitycs di Shopify
61
+ static customEvent = (eventName, data, element) => {
62
+ if(!eventName || typeof eventName !== 'string') {
63
+ console.error('McShopify.customEvent: Il primo parametro deve essere una stringa valida per il nome dell\'evento');
64
+ return;
65
+ }
66
+ if(data && typeof data !== 'object') {
67
+ console.error('McShopify.customEvent: Il secondo parametro deve essere un oggetto o undefined');
68
+ return;
69
+ }
70
+ if(element && !(element instanceof HTMLElement)) {
71
+ console.error('McShopify.customEvent: Il terzo parametro, se fornito, deve essere un elemento HTML valido');
72
+ return;
73
+ }
74
+ const e = new CustomEvent(eventName, {
75
+ detail: data
76
+ });
77
+ if(element){
78
+ element.dispatchEvent(e);
79
+ } else {
80
+ window.dispatchEvent(e);
81
+ }
82
+ if(data != undefined && McUtils.isJSONObject(data)){
83
+ Shopify.analytics.publish(eventName, data);
84
+ }
85
+ }
86
+
87
+ // Controlla se i cookie di preferenza sono stati accettati
88
+ static isPreferencesAllowed = () => {
89
+ if(!Shopify.customerPrivacy) {
90
+ McShopify.loadCustomerPrivacy();
91
+ return;
92
+ }
93
+ const consent = JSON.parse(localStorage.getItem(McShopify.STORAGE.COOKIE_CONSENT));
94
+ return (consent && consent.preferences) || Shopify.customerPrivacy.preferencesProcessingAllowed();
95
+ }
96
+
97
+ // Controlla se i cookie di marketing sono stati accettati
98
+ static isMarketingAllowed = () => {
99
+ if(!Shopify.customerPrivacy) {
100
+ McShopify.loadCustomerPrivacy();
101
+ return;
102
+ }
103
+ const consent = JSON.parse(localStorage.getItem(McShopify.STORAGE.COOKIE_CONSENT));
104
+ return (consent && consent.marketing) || Shopify.customerPrivacy.marketingAllowed();
105
+ }
106
+
107
+ // Controlla se i cookie di analisi sono stati accettati
108
+ static isAnalyticsAllowed = () => {
109
+ if(!Shopify.customerPrivacy) {
110
+ McShopify.loadCustomerPrivacy();
111
+ return;
112
+ }
113
+ const consent = JSON.parse(localStorage.getItem(McShopify.STORAGE.COOKIE_CONSENT));
114
+ return (consent && consent.analytics) || Shopify.customerPrivacy.analyticsProcessingAllowed();
115
+ }
116
+
117
+ // Aggiunge uno o più prodotti al carrello e pubblica l'evento CART_ADDED. Tutti i parametri sono facoltativi ma ne va passato almeno uno
118
+ static addToCart = ({
119
+ form, // Form html da cui prendere i dati
120
+ formData, // Oggetto FormData da inviare
121
+ items, // Array di oggetti da inviare
122
+ onSuccess, // Funzione da chiamare in caso di successo
123
+ onError, // Funzione da chiamare in caso di errore
124
+ onComplete // Funzione da chiamare al termine della richiesta
125
+ }) => {
126
+ if(!form && !formData && !items) { console.error('McShopify.addToCart: Devi passare almeno uno dei parametri form, formData o items'); return; }
127
+ if(form && !(form instanceof HTMLFormElement)) { console.error('McShopify.addToCart: Il parametro form deve essere un elemento HTMLFormElement valido'); return; }
128
+ if(formData && !(formData instanceof FormData)) { console.error('McShopify.addToCart: Il parametro formData deve essere un oggetto FormData valido'); return; }
129
+ if(items && !Array.isArray(items)) { console.error('McShopify.addToCart: Il parametro items deve essere un array di oggetti'); return; }
130
+ if(items && items.length === 0) { console.error('McShopify.addToCart: L\'array items non può essere vuoto'); return; }
131
+
132
+ const config = {
133
+ url: `${Shopify.routes.root}cart/add.js`,
134
+ method: 'post'
135
+ };
136
+
137
+ if(form)
138
+ config.data = new FormData(form);
139
+ else if(formData)
140
+ config.data = formData;
141
+ else if(items) {
142
+ config.data = { items: items };
143
+ config.headers = {
144
+ 'content-type': 'application/json'
145
+ };
146
+ }
147
+
148
+ if((form || formData) && !config.data.get('id')) { console.error('McShopify.addToCart: Devi specificare almeno un prodotto da aggiungere al carrello, usando il campo id contenente l\'id della variante'); return;}
149
+ if((form || formData) && !config.data.get('quantity')) { console.error('McShopify.addToCart: Devi specificare almeno un prodotto da aggiungere al carrello, usando il campo quantity contenente la quantità del prodotto'); return;}
150
+
151
+ axios(config).then(r => {
152
+ McShopify.customEvent(MCEVENTS.CART_UPDATED, r.data);
153
+ McShopify.customEvent(MCEVENTS.CART_ADDED, r.data);
154
+ if(typeof onSuccess === 'function') { onSuccess(r.data); }
155
+ }).catch(e => {
156
+ McShopify.customEvent(MCEVENTS.CART_ERROR, e);
157
+ if(typeof onError === 'function') { onError(e); }
158
+ }).finally(() => {
159
+ if(typeof onComplete === 'function') { onComplete(); }
160
+ });
161
+ }
162
+
163
+ // Modifica una linea del carrello e pubblica l'evento CART_UPDATED. Se un prodotto è stato aumentato di quantità pubblica anche CART_ADDED, se invece è stato diminuito o rimosso pubblica CART_REMOVED
164
+ static updateCartLine = ({
165
+ id, // ID della linea del carrello da modificare
166
+ quantity, // Nuova quantità del prodotto
167
+ line, // Numero della linea del carrello da modificare (opzionale, se non specificato usa l'id)
168
+ properties, // Oggetto con le proprietà del prodotto da modificare (opzionale)
169
+ selling_plan,
170
+ onSuccess, // Funzione da chiamare in caso di successo
171
+ onError, // Funzione da chiamare in caso di errore
172
+ onComplete // Funzione da chiamare al termine della richiesta
173
+ }) => {
174
+ if(!id && !line) { console.error('McShopify.updateCartLine: Devi passare almeno uno dei parametri id o line'); return; }
175
+ if(quantity === undefined || quantity === null) { console.error('McShopify.updateCartLine: Devi passare il parametro quantity'); return; }
176
+ if(properties && typeof properties !== 'object') { console.error('McShopify.updateCartLine: Il parametro properties deve essere un oggetto'); return; }
177
+
178
+ const qty = Number(quantity);
179
+ if(isNaN(qty) || qty < 0) { console.error('McShopify.updateCartLine: Il parametro quantity deve essere un numero maggiore o uguale a 0'); return; }
180
+
181
+ const data = {id, quantity, line, properties, selling_plan};
182
+
183
+ const config = {
184
+ url: `${Shopify.routes.root}cart/change.js`,
185
+ method: 'post',
186
+ data: data,
187
+ headers: {
188
+ 'content-type': 'application/json'
189
+ }
190
+ };
191
+
192
+ axios(config).then(r => {
193
+ McShopify.customEvent(MCEVENTS.CART_UPDATED, r.data);
194
+ if(r.data.items_removed.length > 0){ //se ci sono elementi rimossi
195
+ McShopify.customEvent(MCEVENTS.CART_REMOVED, r.data);
196
+ }
197
+ if(r.data.items_added.length > 0){ //se ci sono elementi aggiunti
198
+ McShopify.customEvent(MCEVENTS.CART_ADDED, r.data);
199
+ }
200
+ if(typeof onSuccess === 'function') { onSuccess(r.data); }
201
+ }).catch(e => {
202
+ McShopify.customEvent(MCEVENTS.CART_ERROR, e);
203
+ if(typeof onError === 'function') { onError(e); }
204
+ }).finally(() => {
205
+ if(typeof onComplete === 'function') { onComplete(); }
206
+ });
207
+ }
208
+
209
+ // Modifica il carrello, utile per note e attributi. Pubblica l'evento CART_UPDATED. Se un prodotto è stato aumentato di quantità pubblica anche CART_ADDED, se invece è stato diminuito o rimosso pubblica CART_REMOVED. Solo uno dei parametri è necessario.
210
+ static updateCart = ({
211
+ form, // Form html da cui prendere i dati
212
+ formData, // Oggetto FormData da inviare
213
+ data, // Oggetto con i dati da inviare (opzionale, se non specificato usa form o formData)
214
+ onSuccess, // Funzione da chiamare in caso di successo
215
+ onError, // Funzione da chiamare in caso di errore
216
+ onComplete // Funzione da chiamare al termine della richiesta
217
+ }) => {
218
+ if(!form && !formData && !data) { console.error('McShopify.updateCart: Devi passare almeno uno dei parametri form, formData o data'); return; }
219
+ if(form && !(form instanceof HTMLFormElement)) { console.error('McShopify.updateCart: Il parametro form deve essere un elemento HTMLFormElement valido'); return; }
220
+ if(formData && !(formData instanceof FormData)) { console.error('McShopify.updateCart: Il parametro formData deve essere un oggetto FormData valido'); return; }
221
+ if(data && typeof data !== 'object') { console.error('McShopify.updateCart: Il parametro data deve essere un oggetto'); return; }
222
+
223
+ const config = {
224
+ url: `${Shopify.routes.root}cart/update.js`,
225
+ method: 'post'
226
+ };
227
+
228
+ if(form)
229
+ config.data = new FormData(form);
230
+ else if(formData)
231
+ config.data = formData;
232
+ else{
233
+ config.data = data;
234
+ config.headers = {
235
+ 'content-type': 'application/json'
236
+ };
237
+ }
238
+
239
+ axios(config).then(r => {
240
+ McShopify.customEvent(MCEVENTS.CART_UPDATED, r.data);
241
+ if(r.data.items_changelog?.added?.length > 0){ //se ci sono elementi rimossi
242
+ McShopify.customEvent(MCEVENTS.CART_REMOVED, r.data);
243
+ }
244
+ if(r.data.items_changelog?.removed?.length > 0){ //se ci sono elementi aggiunti
245
+ McShopify.customEvent(MCEVENTS.CART_ADDED, r.data);
246
+ }
247
+ if(typeof onSuccess === 'function') { onSuccess(r.data); }
248
+ }).catch(e => {
249
+ McShopify.customEvent(MCEVENTS.CART_ERROR, e);
250
+ if(typeof onError === 'function') { onError(e); }
251
+ }).finally(() => {
252
+ if(typeof onComplete === 'function') { onComplete(); }
253
+ });
254
+ }
255
+
256
+ // Ottiene il carrello ed esegue il callback al success
257
+ static getCart = ({
258
+ onSuccess, // Funzione da chiamare in caso di successo
259
+ onError, // Funzione da chiamare in caso di errore
260
+ onComplete // Funzione da chiamare al termine della richiesta
261
+ }) => {
262
+ axios({
263
+ url: `${Shopify.routes.root}cart.js`,
264
+ method: 'get'
265
+ }).then(r => {
266
+ if(typeof onSuccess === 'function') { onSuccess(r.data); }
267
+ }).catch(e => {
268
+ if(typeof onError === 'function') { onError(e); }
269
+ }).finally(() => {
270
+ if(typeof onComplete === 'function') { onComplete(); }
271
+ });
272
+ }
273
+
274
+ // Svuota il carrello ed esegue il callback. Pubblica l'evento CART_UPDATED
275
+ static clearCart = ({
276
+ onSuccess, // Funzione da chiamare in caso di successo
277
+ onError, // Funzione da chiamare in caso di errore
278
+ onComplete // Funzione da chiamare al termine della richiesta
279
+ }) => {
280
+ axios({
281
+ url: `${Shopify.routes.root}cart/clear.js`,
282
+ method: 'get'
283
+ }).then(r => {
284
+ if(typeof onSuccess === 'function') { onSuccess(r.data); }
285
+ }).catch(e => {
286
+ if(typeof onError === 'function') { onError(e); }
287
+ }).finally(() => {
288
+ if(typeof onComplete === 'function') { onComplete(); }
289
+ });
290
+ }
291
+
292
+ // Ottiene i dati di un prodotto tramite il suo handle ed esegue callback
293
+ static getProduct = ({
294
+ handle, // Handle del prodotto da cercare
295
+ onSuccess, // Funzione da chiamare in caso di successo
296
+ onError, // Funzione da chiamare in caso di errore
297
+ onComplete // Funzione da chiamare al termine della richiesta
298
+ }) => {
299
+ if(!handle || typeof handle !== 'string') { console.error('McShopify.getProduct: Devi passare un handle valido del prodotto'); return; }
300
+ if(handle.includes('/')) { console.error('McShopify.getProduct: L\'handle non può contenere il carattere /'); return; }
301
+
302
+ axios({
303
+ url: `${Shopify.routes.root}products/${handle}.js`,
304
+ method: 'get'
305
+ }).then(r => {
306
+ if(typeof onSuccess === 'function') { onSuccess(r.data); }
307
+ }).catch(e => {
308
+ if(typeof onError === 'function') { onError(e); }
309
+ }).finally(() => {
310
+ if(typeof onComplete === 'function') { onComplete(); }
311
+ });
312
+ }
313
+
314
+ // Ottiene i prodotti raccomandati per un prodotto in base ai settaggi dell'app search and discovery ed esegue il callback
315
+ static getRecommendations = ({
316
+ product_id, // ID del prodotto per cui ottenere le raccomandazioni
317
+ section_id, // ID della sezione per cui ottenere le raccomandazioni (opzionale)
318
+ limit=10, // Numero massimo di prodotti da restituire (max 10)
319
+ intent='related', // Intento della raccomandazione (related | complementary)
320
+ onSuccess, onError, onComplete
321
+ }) => {
322
+ if(!product_id) { console.error('McShopify.getRecommendations: Devi passare un ID prodotto valido'); return; }
323
+ if(section_id && typeof section_id !== 'string') { console.error('McShopify.getRecommendations: Il parametro section_id deve essere una stringa'); return; }
324
+ if(intent && !['related', 'complementary'].includes(intent)) { console.error('McShopify.getRecommendations: Il parametro intent deve essere "related" o "complementary"'); return; }
325
+
326
+ const l = parseInt(limit);
327
+ if(isNaN(l) || l < 1 || l > 10) { console.error('McShopify.getRecommendations: Il parametro limit deve essere un numero compreso tra 1 e 10'); return; }
328
+
329
+ const data = {product_id, limit: l, intent};
330
+ const filename = section_id ? 'products' : 'products.json';
331
+ if(section_id){
332
+ data.section_id = section_id;
333
+ }
334
+
335
+ const queryString = new URLSearchParams(data).toString();
336
+
337
+ axios({
338
+ url: `${Shopify.routes.root}recommendations/${filename}?${queryString}`,
339
+ method: 'get',
340
+ headers: {
341
+ 'content-type': 'application/json'
342
+ }
343
+ }).then(r => {
344
+ if(typeof onSuccess === 'function') { onSuccess(r.data); }
345
+ }).catch(e => {
346
+ if(typeof onError === 'function') { onError(e); }
347
+ }).finally(() => {
348
+ if(typeof onComplete === 'function') { onComplete(); }
349
+ });
350
+ }
351
+
352
+ // Avvia una ricerca predittiva ed esegue il callback
353
+ static search = ({
354
+ q, // Termini di ricerca
355
+ type='product,article,page,collection,query', // Tipo di risorsa da cercare (opzionale, può essere 'product', 'article', 'page', 'collection' o 'query')
356
+ limit=10, // Numero massimo di risultati da restituire (da 1 a 10)
357
+ limit_scope='each', // Ambito del limite (each | all)
358
+ unavailable_products='last', // show: mostra normalmente i prodotti non disponibili, hide: nasconde i prodotti non disponibili, last: mostra i prodotti non disponibili alla fine della lista
359
+ fields, // Campi nei quali cercare (opzionale, può essere 'author', 'body', 'product_type', 'tag', 'title', 'variants.barcode', 'variants.sku', 'variants.title', 'vendor')
360
+ section_id, // ID della sezione opzionale
361
+ onSuccess, // Funzione da chiamare in caso di successo
362
+ onError, // Funzione da chiamare in caso di errore
363
+ onComplete // Funzione da chiamare al termine della richiesta
364
+ }) => {
365
+ if(!q || typeof q !== 'string') { console.error('McShopify.search: Devi passare una stringa valida per il parametro q'); return; }
366
+ if(type && type.split(',').some(t => !['product', 'article', 'page', 'collection', 'query'].includes(t))) { console.error('McShopify.search: Il parametro type deve essere uno tra "product", "article", "page", "collection" o "query"'); return; }
367
+ if(limit_scope && !['each', 'all'].includes(limit_scope)) { console.error('McShopify.search: Il parametro limit_scope deve essere "each" o "all"'); return; }
368
+ if(unavailable_products && !['show', 'hide', 'last'].includes(unavailable_products)) { console.error('McShopify.search: Il parametro unavailable_products deve essere "show", "hide" o "last"'); return; }
369
+ if(fields && fields.split(',').some(f => !['author', 'body', 'product_type', 'tag', 'title', 'variants.barcode', 'variants.sku', 'variants.title', 'vendor'].includes(f))) { console.error('McShopify.search: Il parametro fields deve essere uno tra "author", "body", "product_type", "tag", "title", "variants.barcode", "variants.sku", "variants.title" o "vendor"'); return; }
370
+
371
+ const l = parseInt(limit);
372
+ if(isNaN(l) || l < 1 || l > 10) { console.error('McShopify.search: Il parametro limit deve essere un numero compreso tra 1 e 10'); return; }
373
+
374
+ const params = new URLSearchParams();
375
+ params.append('q', q);
376
+ if(type) params.append('resources[type]', type);
377
+ if(limit) params.append('resources[limit]', l);
378
+ if(limit_scope) params.append('resources[limit_scope]', limit_scope);
379
+ if(unavailable_products) params.append('resources[options][unavailable_products]', unavailable_products);
380
+ if(fields) params.append('resources[options][fields]', fields);
381
+
382
+ const filename = section_id ? 'suggest' : 'suggest.json';
383
+
384
+ axios({
385
+ url: `${Shopify.routes.root}search/${filename}?${params.toString()}`,
386
+ method: 'get',
387
+ headers: {
388
+ 'content-type': 'application/json'
389
+ }
390
+ }).then(r => {
391
+ McShopify.customEvent(MCEVENTS.SEARCH, r.data);
392
+ if(typeof onSuccess === 'function') { onSuccess(r.data); }
393
+ }).catch(e => {
394
+ McShopify.customEvent(MCEVENTS.SEARCH_ERROR, e);
395
+ if(typeof onError === 'function') { onError(e); }
396
+ }).finally(() => {
397
+ if(typeof onComplete === 'function') { onComplete(); }
398
+ });
399
+ }
400
+
401
+ static section = ({
402
+ path, // URL della sezione da caricare
403
+ section_id, // ID della sezione da caricare (opzionale, se non specificato usa l'url)
404
+ sections, // Array di sezioni da caricare (opzionale, se non specificato usa l'url)
405
+ onSuccess, // Funzione da chiamare in caso di successo
406
+ onError, // Funzione da chiamare in caso di errore
407
+ onComplete // Funzione da chiamare al termine della richiesta
408
+ }) => {
409
+ if(!path) { console.error('McShopify.section: Devi passare path'); return; }
410
+ if(!section_id && !sections) { console.error('McShopify.section: Devi passare almeno uno dei parametri section_id o sections'); return; }
411
+ if(path && typeof path !== 'string') { console.error('McShopify.section: Il parametro path deve essere una stringa'); return; }
412
+ if(section_id && typeof section_id !== 'string') { console.error('McShopify.section: Il parametro section_id deve essere una stringa'); return; }
413
+ if(sections && !Array.isArray(sections)) { console.error('McShopify.section: Il parametro sections deve essere un array di sezioni'); return; }
414
+
415
+ const params = new URLSearchParams();
416
+ if(sections?.length > 0)
417
+ params.append('sections', sections.join(','));
418
+ else
419
+ params.append('section_id', section_id);
420
+
421
+ const config = {
422
+ url: `${path}?${params.toString()}`,
423
+ method: 'get',
424
+ headers: {
425
+ 'content-type': 'application/json'
426
+ },
427
+ };
428
+
429
+ axios(config).then(r => {
430
+ if(typeof onSuccess === 'function') { onSuccess(r.data); }
431
+ }).catch(e => {
432
+ if(typeof onError === 'function') { onError(e); }
433
+ }).finally(() => {
434
+ if(typeof onComplete === 'function') { onComplete(); }
435
+ });
436
+ }
437
+ }
438
+ window.McShopify = McShopify;
439
+ export default McShopify;
@@ -0,0 +1,119 @@
1
+ // Questa classe `McUtils` raccoglie utility generiche riutilizzabili per la gestione di formattazione valuta, parsing HTML, logging avanzato e aggiornamento dinamico di elementi HTML.
2
+ // Non richiede data attributes o elementi HTML specifici: tutte le funzioni sono statiche e possono essere richiamate direttamente tramite `McUtils`.
3
+
4
+ class McUtils {
5
+
6
+ static VERSION = '2.0.4';
7
+
8
+ // Formatta un importo numerico in formato valuta secondo il formato definito in window.theme.moneyFormat
9
+ // Esempio: 123456 -> "1 234,56 €" (a seconda del formato configurato)
10
+ static money = (amount) => {
11
+ const n = Number(amount);
12
+ if(isNaN(n)) { console.error('McUtils.money: Il valore passato non è un numero valido:', amount); return ''; }
13
+
14
+ // Divide per 100, fissa a 2 decimali, usa la virgola come separatore decimale e lo spazio per le migliaia
15
+ const a = parseFloat(n/100).toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, " ");
16
+ if(window.theme?.moneyFormat === undefined) { console.warn('McUtils.money: La formattazione della valuta non è definita in window.theme.moneyFormat'); return a; }
17
+
18
+ return window.theme.moneyFormat.replace('{{amount}}', a).replace('{{amount_with_comma_separator}}', a);
19
+ }
20
+
21
+ // Ottiene un oggetto Document a partire da una stringa HTML
22
+ static getDocument = (html) => {
23
+ if(typeof html !== 'string') { console.error('McUtils.getDocument: il primo parametro deve essere una stringa HTML'); return; }
24
+ const parser = new DOMParser();
25
+ return parser.parseFromString(html, 'text/html');
26
+ }
27
+
28
+ // Converte una stringa in uno slug "handle" (come la funzione handleize di Liquid)
29
+ // Esempio: "Ciao Mondo!" -> "ciao-mondo"
30
+ static handleize = (str) => {
31
+ return str.toString().toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]+/g, '');
32
+ }
33
+
34
+ // Log avanzato: mostra il chiamante e i parametri solo in ambiente di sviluppo o debug
35
+ static log = (...args) => {
36
+ if(Shopify.theme.role == 'development' || window.theme.debug) {
37
+ try {
38
+ const err = new Error();
39
+ const stack = err.stack || '';
40
+ const stackLines = stack.split('\n');
41
+ const callerLine = stackLines[2] || '';
42
+ let caller = 'unknown';
43
+ // Estrae il nome della funzione chiamante dallo stack trace
44
+ const matchFunction = callerLine.match(/at\s+([\w.$<>]+)\s*\(/);
45
+ if (matchFunction && matchFunction[1]) {
46
+ caller = matchFunction[1];
47
+ } else {
48
+ const matchFile = callerLine.match(/at\s+(.+:\d+:\d+)/);
49
+ if (matchFile && matchFile[1]) {
50
+ caller = matchFile[1];
51
+ }
52
+ }
53
+ // Log con chiamante + argomenti
54
+ console.log('🌝', `[${caller}]`, ...args);
55
+ } catch (e) {
56
+ // Fallback in caso di errore estrazione del chiamante
57
+ console.log('🌝', ...args);
58
+ }
59
+ }
60
+ }
61
+
62
+ // Aggiorna dinamicamente il contenuto di un elemento HTML con quello di un nuovo elemento estratto da una stringa HTML
63
+ // html: stringa HTML sorgente
64
+ // elementToReload: elemento da aggiornare
65
+ // selectorOfNewElement: selettore CSS dell'elemento da cui prendere il nuovo contenuto
66
+ static reloadElement = (html, elementToReload, selectorOfNewElement) => {
67
+ if(!html || !elementToReload || !selectorOfNewElement) { console.error('reloadElement: parametri mancanti o non validi'); return; }
68
+ if(typeof html !== 'string') { console.error('reloadElement: il primo parametro deve essere una stringa HTML'); return; }
69
+ if(!(elementToReload instanceof HTMLElement)) { console.error('reloadElement: il secondo parametro deve essere un elemento HTML'); return; }
70
+ if(typeof selectorOfNewElement !== 'string') { console.error('reloadElement: il terzo parametro deve essere una stringa CSS selector'); return; }
71
+
72
+ const newDocument = McUtils.getDocument(html);
73
+ const newElement = newDocument.querySelector(selectorOfNewElement);
74
+ if(!newElement) { console.error('reloadElement: Elemento non trovato nella risposta:', selectorOfNewElement); return; }
75
+
76
+ elementToReload.innerHTML = newElement.innerHTML;
77
+ }
78
+
79
+ static addCss = (url) => {
80
+ if (!url || typeof url !== 'string') { console.error('McElement.addCss: Il parametro "url" deve essere una stringa valida.'); return; }
81
+ const element = document.querySelector(`link[href="${url}"]`);
82
+ if (element) { console.warn(`McElement.addCss: Il CSS "${url}" è già stato aggiunto.`, element); return; }
83
+ const link = document.createElement('link');
84
+ link.rel = 'stylesheet';
85
+ link.href = url;
86
+ document.head.appendChild(link);
87
+ }
88
+
89
+ static isJSONObject = (value) =>{
90
+ function isJSONValue(v){
91
+ if (
92
+ typeof v === 'string' ||
93
+ typeof v === 'number' ||
94
+ typeof v === 'boolean' ||
95
+ v === null
96
+ ) return true;
97
+
98
+ if (Array.isArray(v)) return v.every(isJSONValue);
99
+
100
+ if (typeof v === 'object') {
101
+ // Solo plain object, no Date/Map/DOM ecc.
102
+ if (Object.getPrototypeOf(v) !== Object.prototype) return false;
103
+ return Object.values(v).every(isJSONValue);
104
+ }
105
+ return false;
106
+ }
107
+ return (
108
+ typeof value === 'object' &&
109
+ value !== null &&
110
+ !Array.isArray(value) &&
111
+ Object.getPrototypeOf(value) === Object.prototype &&
112
+ Object.values(value).every(isJSONValue)
113
+ );
114
+ }
115
+ }
116
+
117
+ // Esporta la classe McUtils come globale e come modulo
118
+ window.McUtils = McUtils;
119
+ export default McUtils;