@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.
- package/algolia/algolia-loader.js +56 -0
- package/algolia/algolia-search.js +222 -0
- package/auth/teddy-auth-login.js +29 -0
- package/auth/teddy-auth-profile.js +58 -0
- package/auth/teddy-auth-register.js +52 -0
- package/auth/teddy-auth-reset.js +39 -0
- package/auth/teddy-auth.js +92 -0
- package/bantoa/bantoa-product.js +64 -0
- package/bantoa/bantoa-search.js +46 -0
- package/bantoa/bantoa-stylemix.js +43 -0
- package/bantoa/bantoa.js +21 -0
- package/cart/teddy-cart-controller.js +163 -0
- package/cart/teddy-cart-customer.js +86 -0
- package/cart/teddy-cart-discounts.js +55 -0
- package/cart/teddy-cart-promo-apply.js +24 -0
- package/cart/teddy-cart-promo-error.js +38 -0
- package/cart/teddy-cart-promo-loading.js +20 -0
- package/cart/teddy-cart-promo.js +96 -0
- package/components/mc-btn.js +38 -0
- package/components/mc-card-render.js +33 -0
- package/components/mc-cart-counter.js +14 -0
- package/components/mc-cart-error.js +35 -0
- package/components/mc-cart-quantity.js +43 -0
- package/components/mc-cart-remove.js +32 -0
- package/components/mc-copy.js +33 -0
- package/components/mc-element.js +84 -0
- package/components/mc-form-errors.js +35 -0
- package/components/mc-form-success.js +31 -0
- package/components/mc-form.js +223 -0
- package/components/mc-input-area.js +13 -0
- package/components/mc-input-checkbox-group.js +52 -0
- package/components/mc-input-checkbox.js +12 -0
- package/components/mc-input-password.js +39 -0
- package/components/mc-input-places.js +43 -0
- package/components/mc-input-quantity.js +43 -0
- package/components/mc-input-select.js +14 -0
- package/components/mc-input-text.js +12 -0
- package/components/mc-input.js +137 -0
- package/components/mc-loader.js +21 -0
- package/components/mc-modal.js +69 -0
- package/components/mc-password-eye.js +24 -0
- package/components/mc-password-tips.js +60 -0
- package/components/mc-qrcode.js +41 -0
- package/components/mc-range.js +108 -0
- package/components/mc-read-more.js +56 -0
- package/components/mc-recommended.js +50 -0
- package/components/mc-referrer.js +20 -0
- package/components/mc-splash-controller.js +32 -0
- package/components/mc-splash-link.js +65 -0
- package/components/mc-stoq.js +30 -0
- package/components/mc-swiper.js +333 -0
- package/components/mc-video.js +61 -0
- package/css/bootstrap-lg.css +2762 -0
- package/css/bootstrap-lg.min.css +1 -0
- package/css/bootstrap-md.css +2762 -0
- package/css/bootstrap-md.min.css +1 -0
- package/css/bootstrap-sm.css +2762 -0
- package/css/bootstrap-sm.min.css +1 -0
- package/css/bootstrap-xl.css +2790 -0
- package/css/bootstrap-xl.min.css +1 -0
- package/css/bootstrap-xxl.css +2762 -0
- package/css/bootstrap-xxl.min.css +1 -0
- package/css/bootstrap.css +9824 -0
- package/css/bootstrap.min.css +5 -0
- package/css/mc-form-errors.css +3 -0
- package/css/mc-loader.css +6 -0
- package/css/mc-password-eye.css +6 -0
- package/css/mc-password-tips.css +6 -0
- package/css/mc-read-more.css +14 -0
- package/css/mc-video.css +4 -0
- package/css/mc-wishlist-hero-btn.css +7 -0
- package/loader.js +110 -0
- package/loyalty/teddy-loyalty-create.js +40 -0
- package/loyalty/teddy-loyalty-redeem.js +38 -0
- package/loyalty/teddy-loyalty.js +214 -0
- package/package.json +20 -0
- package/res/mc-events.js +43 -0
- package/res/mc-shopify.js +439 -0
- package/res/mc-utils.js +119 -0
- package/res/mc-vue.js +9350 -0
- package/returns/teddy-return-iban.js +115 -0
- package/returns/teddy-return-list.js +165 -0
- package/returns/teddy-return-request.js +353 -0
- package/scripts/publish-store.js +62 -0
- package/test/auth-login-error.json +4 -0
- package/test/auth-login-success.json +13 -0
- package/test/auth-password-change-error.json +4 -0
- package/test/auth-password-change-success.json +3 -0
- package/test/auth-register-error.json +4 -0
- package/test/auth-register-success.json +12 -0
- package/test/auth-reset-error.json +4 -0
- package/test/auth-reset-success.json +3 -0
- package/test/loyalty-error.json +4 -0
- package/test/loyalty-noinfo.json +18 -0
- package/test/loyalty-redeem-error.json +4 -0
- package/test/loyalty-redeem-success.json +11 -0
- package/test/loyalty-success.json +143 -0
- package/test/returns-create-success.json +1 -0
- package/test/returns-get-success.json +35 -0
- package/test/returns-get-tracking.json +30 -0
- package/test/returns-list-empty.json +4 -0
- package/test/returns-list-success.json +59 -0
- package/test/returns-search-manual.json +67 -0
- package/test/returns-search-success.json +76 -0
- package/test/wishlist-error.json +4 -0
- package/test/wishlist-success.json +3 -0
- package/wishlist/teddy-wishlist-btn.js +55 -0
- package/wishlist/teddy-wishlist-clear.js +18 -0
- package/wishlist/teddy-wishlist-counter.js +35 -0
- package/wishlist/teddy-wishlist-item.js +46 -0
- package/wishlist/teddy-wishlist-page.js +59 -0
- 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;
|
package/res/mc-utils.js
ADDED
|
@@ -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;
|