@o2vend/theme-cli 1.0.36 → 1.0.38
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/README.md +4 -0
- package/lib/lib/dev-server.js +344 -48
- package/lib/lib/liquid-engine.js +3 -1
- package/lib/lib/mock-data.js +473 -119
- package/lib/lib/widget-service.js +12 -4
- package/package.json +2 -2
- package/test-theme/assets/async-sections.js +32 -24
- package/test-theme/assets/cart-drawer.js +20 -22
- package/test-theme/assets/cart-manager.js +1 -15
- package/test-theme/assets/checkout-price-handler.js +12 -11
- package/test-theme/assets/checkout.css +1415 -0
- package/test-theme/assets/checkout.js +3174 -0
- package/test-theme/assets/components.css +178 -29
- package/test-theme/assets/delivery-zone.js +1 -1
- package/test-theme/assets/product-detail.css +1050 -0
- package/test-theme/assets/product-detail.js +2940 -0
- package/test-theme/assets/theme.css +95 -120
- package/test-theme/assets/theme.js +781 -186
- package/test-theme/layout/theme.liquid +91 -17
- package/test-theme/sections/content.liquid +64 -57
- package/test-theme/sections/footer-fallback.liquid +57 -7
- package/test-theme/sections/footer.liquid +63 -12
- package/test-theme/sections/header-fallback.liquid +41 -41
- package/test-theme/sections/header.liquid +41 -51
- package/test-theme/sections/hero-fallback.liquid +1 -1
- package/test-theme/sections/hero.liquid +159 -136
- package/test-theme/snippets/account-sidebar.liquid +121 -29
- package/test-theme/snippets/add-to-cart-modal.liquid +258 -206
- package/test-theme/snippets/breadcrumbs.liquid +98 -11
- package/test-theme/snippets/cart-drawer.liquid +93 -0
- package/test-theme/snippets/delivery-zone-city-selector.liquid +101 -15
- package/test-theme/snippets/delivery-zone-modal.liquid +529 -84
- package/test-theme/snippets/delivery-zone-search.liquid +104 -18
- package/test-theme/snippets/login-modal.liquid +269 -82
- package/test-theme/snippets/mega-menu.liquid +130 -43
- package/test-theme/snippets/news-thumbnail.liquid +120 -28
- package/test-theme/snippets/pagination.liquid +1 -1
- package/test-theme/snippets/price.liquid +100 -9
- package/test-theme/snippets/product-card-related.liquid +22 -4
- package/test-theme/snippets/product-card-simple.liquid +521 -25
- package/test-theme/snippets/product-card.liquid +145 -232
- package/test-theme/snippets/rating.liquid +100 -9
- package/test-theme/snippets/skeleton-collection-grid.liquid +94 -8
- package/test-theme/snippets/skeleton-product-card.liquid +102 -16
- package/test-theme/snippets/skeleton-product-grid.liquid +87 -1
- package/test-theme/snippets/social-sharing.liquid +133 -32
- package/test-theme/templates/account/dashboard.liquid +30 -0
- package/test-theme/templates/account/loyalty-redemption.liquid +29 -28
- package/test-theme/templates/account/loyalty.liquid +45 -43
- package/test-theme/templates/account/order-detail.liquid +15 -8
- package/test-theme/templates/account/orders.liquid +189 -35
- package/test-theme/templates/account/profile.liquid +509 -114
- package/test-theme/templates/account/register.liquid +18 -8
- package/test-theme/templates/account/return-orders.liquid +31 -30
- package/test-theme/templates/account/store-credit.liquid +27 -26
- package/test-theme/templates/account/subscriptions.liquid +22 -5
- package/test-theme/templates/account/wishlist.liquid +88 -19
- package/test-theme/templates/address-book.liquid +166 -69
- package/test-theme/templates/categories.liquid +90 -30
- package/test-theme/templates/checkout.liquid +137 -3834
- package/test-theme/templates/error.liquid +23 -21
- package/test-theme/templates/index.liquid +29 -0
- package/test-theme/templates/login.liquid +33 -6
- package/test-theme/templates/order-confirmation.liquid +67 -9
- package/test-theme/templates/page.liquid +418 -206
- package/test-theme/templates/product-detail.liquid +124 -3878
- package/test-theme/templates/products.liquid +155 -30
- package/test-theme/templates/search.liquid +739 -225
- package/test-theme/widgets/brand-carousel.liquid +102 -82
- package/test-theme/widgets/brand.liquid +78 -50
- package/test-theme/widgets/carousel.liquid +253 -121
- package/test-theme/widgets/category-list-carousel.liquid +32 -8
- package/test-theme/widgets/category-list.liquid +21 -6
- package/test-theme/widgets/category.liquid +104 -37
- package/test-theme/widgets/discount-time.liquid +326 -119
- package/test-theme/widgets/footer-menu.liquid +115 -23
- package/test-theme/widgets/footer.liquid +118 -5
- package/test-theme/widgets/gallery.liquid +29 -5
- package/test-theme/widgets/header-menu.liquid +25 -13
- package/test-theme/widgets/header.liquid +64 -26
- package/test-theme/widgets/html.liquid +29 -6
- package/test-theme/widgets/news.liquid +6 -0
- package/test-theme/widgets/product-canvas.liquid +20 -12
- package/test-theme/widgets/product-carousel.liquid +118 -56
- package/test-theme/widgets/shared/product-grid.liquid +12 -0
- package/test-theme/widgets/single-product.liquid +688 -250
- package/test-theme/widgets/spacebar-carousel.liquid +39 -10
- package/test-theme/widgets/spacebar.liquid +77 -6
- package/test-theme/widgets/splash.liquid +40 -30
- package/test-theme/widgets/testimonial-carousel.liquid +111 -67
|
@@ -147,9 +147,17 @@ class WidgetService {
|
|
|
147
147
|
widget.settings.subtitle = widget.subtitle;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
// Store content
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
// Store content: only overwrite when we have parsed content from widget.content; otherwise preserve existing data.content (e.g. mock data with Items)
|
|
151
|
+
const hasParsedContent = parsedContent && typeof parsedContent === 'object' && (Array.isArray(parsedContent) ? parsedContent.length > 0 : Object.keys(parsedContent).length > 0);
|
|
152
|
+
if (hasParsedContent) {
|
|
153
|
+
widget.data.content = parsedContent;
|
|
154
|
+
widget.content = parsedContent;
|
|
155
|
+
} else if (widget.data && typeof widget.data.content === 'undefined') {
|
|
156
|
+
widget.data.content = {};
|
|
157
|
+
}
|
|
158
|
+
if (widget.data.content && !widget.content) {
|
|
159
|
+
widget.content = widget.data.content; // Legacy support
|
|
160
|
+
}
|
|
153
161
|
|
|
154
162
|
// Handle HtmlContent
|
|
155
163
|
if (widget.htmlContent !== undefined && widget.htmlContent !== null && widget.htmlContent !== '') {
|
|
@@ -332,7 +340,7 @@ class WidgetService {
|
|
|
332
340
|
* @returns {Promise<Object>} Widgets organized by section { header:[], hero:[], content:[], footer:[] }
|
|
333
341
|
*/
|
|
334
342
|
async fetchPageWidgets(pageId) {
|
|
335
|
-
const validPageIds = ['home', 'product', 'category', 'products', 'categories', 'brand', 'checkout'];
|
|
343
|
+
const validPageIds = ['home', 'product', 'category', 'products', 'categories', 'brand', 'cart', 'checkout'];
|
|
336
344
|
if (!pageId || !validPageIds.includes(pageId)) {
|
|
337
345
|
console.warn(`[WidgetService] Invalid pageId: ${pageId}, returning empty widgets`);
|
|
338
346
|
return { header: [], hero: [], content: [], footer: [] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@o2vend/theme-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.38",
|
|
4
4
|
"description": "O2VEND Theme Development CLI - Standalone tool for local theme development",
|
|
5
5
|
"bin": {
|
|
6
6
|
"o2vend": "./bin/o2vend"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"table": "^6.8.3"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
|
-
"node": ">=
|
|
49
|
+
"node": ">=24.0.0"
|
|
50
50
|
},
|
|
51
51
|
"files": [
|
|
52
52
|
"bin/**/*",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Loads non-critical page sections asynchronously to improve initial page load time
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
(
|
|
6
|
+
(() => {
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
const AsyncSectionLoader = {
|
|
@@ -18,17 +18,13 @@
|
|
|
18
18
|
*/
|
|
19
19
|
init(sectionsConfig) {
|
|
20
20
|
this.config = sectionsConfig || {};
|
|
21
|
-
console.log('[AsyncSectionLoader] Initializing with config:', this.config);
|
|
22
21
|
|
|
23
22
|
// Find all async sections in the page
|
|
24
23
|
const asyncSections = document.querySelectorAll('[data-async-section]');
|
|
25
24
|
|
|
26
25
|
if (asyncSections.length === 0) {
|
|
27
|
-
console.log('[AsyncSectionLoader] No async sections found');
|
|
28
26
|
return;
|
|
29
27
|
}
|
|
30
|
-
|
|
31
|
-
console.log(`[AsyncSectionLoader] Found ${asyncSections.length} async sections`);
|
|
32
28
|
|
|
33
29
|
// Load each section
|
|
34
30
|
asyncSections.forEach(section => {
|
|
@@ -45,12 +41,9 @@
|
|
|
45
41
|
const sectionConfig = this.config[sectionName];
|
|
46
42
|
|
|
47
43
|
if (!sectionConfig) {
|
|
48
|
-
console.warn(`[AsyncSectionLoader] No config found for section: ${sectionName}`);
|
|
49
44
|
return;
|
|
50
45
|
}
|
|
51
46
|
|
|
52
|
-
console.log(`[AsyncSectionLoader] Loading section: ${sectionName}`);
|
|
53
|
-
|
|
54
47
|
try {
|
|
55
48
|
// Get data from API
|
|
56
49
|
const data = await this.fetchSectionData(
|
|
@@ -84,12 +77,10 @@
|
|
|
84
77
|
|
|
85
78
|
// Check cache first
|
|
86
79
|
if (this.cache.has(url)) {
|
|
87
|
-
console.log(`[AsyncSectionLoader] Using cached data for: ${url}`);
|
|
88
80
|
return this.cache.get(url);
|
|
89
81
|
}
|
|
90
82
|
|
|
91
83
|
try {
|
|
92
|
-
console.log(`[AsyncSectionLoader] Fetching: ${url} (attempt ${attempt})`);
|
|
93
84
|
|
|
94
85
|
const response = await fetch(url, {
|
|
95
86
|
method: 'GET',
|
|
@@ -113,7 +104,6 @@
|
|
|
113
104
|
} catch (error) {
|
|
114
105
|
// Retry logic
|
|
115
106
|
if (attempt < this.retryAttempts) {
|
|
116
|
-
console.warn(`[AsyncSectionLoader] Retry ${attempt}/${this.retryAttempts} for ${url}`);
|
|
117
107
|
await this.delay(this.retryDelay * attempt);
|
|
118
108
|
return this.fetchSectionData(endpoint, params, attempt + 1);
|
|
119
109
|
}
|
|
@@ -129,8 +119,6 @@
|
|
|
129
119
|
* @param {string} sectionName - Section name
|
|
130
120
|
*/
|
|
131
121
|
renderSection(sectionElement, data, sectionName) {
|
|
132
|
-
console.log(`[AsyncSectionLoader] Rendering section: ${sectionName}`, data);
|
|
133
|
-
|
|
134
122
|
// Different rendering strategies based on section type
|
|
135
123
|
switch (sectionName) {
|
|
136
124
|
case 'products':
|
|
@@ -145,7 +133,6 @@
|
|
|
145
133
|
this.renderRelatedProducts(sectionElement, data);
|
|
146
134
|
break;
|
|
147
135
|
default:
|
|
148
|
-
console.warn(`[AsyncSectionLoader] Unknown section type: ${sectionName}`);
|
|
149
136
|
this.renderGenericSection(sectionElement, data);
|
|
150
137
|
}
|
|
151
138
|
|
|
@@ -241,17 +228,42 @@
|
|
|
241
228
|
renderProductCard(product, variant = 'default') {
|
|
242
229
|
const imageUrl = product.images?.[0] || product.thumbnailImage || product.image || '/assets/default/placeholder-product.png';
|
|
243
230
|
const price = this.formatPrice(product.prices?.priceString || product.prices?.price || product.price);
|
|
231
|
+
const priceString = product.prices?.priceString || price;
|
|
232
|
+
const showCallForPricing = (
|
|
233
|
+
product.showCallForPricing === true || product.showCallForPricing === 'true' || product.showCallForPricing === 1 || product.showCallForPricing === '1' ||
|
|
234
|
+
product.ShowCallForPricing === true || product.ShowCallForPricing === 'true' || product.ShowCallForPricing === 1 || product.ShowCallForPricing === '1' ||
|
|
235
|
+
product.isCallForPricing === true || product.isCallForPricing === 'true' || product.isCallForPricing === 1 || product.isCallForPricing === '1' ||
|
|
236
|
+
product.IsCallForPricing === true || product.IsCallForPricing === 'true' || product.IsCallForPricing === 1 || product.IsCallForPricing === '1'
|
|
237
|
+
);
|
|
244
238
|
const comparePrice = product.prices?.mrp && product.prices.mrp > product.prices.price
|
|
245
239
|
? this.formatPrice(product.prices.mrpString || product.prices.mrp)
|
|
246
240
|
: null;
|
|
247
241
|
const slug = product.slug || product.handle || product.id;
|
|
242
|
+
const productUrl = product.url || product.productUrl || slug;
|
|
248
243
|
const available = product.stockQuantity > 0 || product.inStock || product.available !== false;
|
|
249
|
-
const
|
|
244
|
+
const productTypeRaw = product.productType ?? product.type ?? 0;
|
|
245
|
+
const productType = Number.isNaN(Number(productTypeRaw)) ? 0 : Number(productTypeRaw);
|
|
246
|
+
const variants = Array.isArray(product.variations)
|
|
247
|
+
? product.variations
|
|
248
|
+
: (Array.isArray(product.variants) ? product.variants : []);
|
|
249
|
+
const variantsCountRaw = product.variantsCount ?? product.variationCount ?? variants.length ?? 0;
|
|
250
|
+
const variantsCount = Number.isNaN(Number(variantsCountRaw)) ? 0 : Number(variantsCountRaw);
|
|
251
|
+
const baseProductId = product.baseProductId || product.productId || product.id;
|
|
252
|
+
const showSaleBadge = !showCallForPricing && product.prices?.mrp && product.prices.mrp > product.prices.price;
|
|
250
253
|
|
|
251
254
|
return `
|
|
252
255
|
<div class="product-card"
|
|
253
|
-
data-
|
|
256
|
+
data-product-id="${product.productId || product.id}"
|
|
257
|
+
data-base-product-id="${baseProductId}"
|
|
258
|
+
data-price="${product.prices?.price ?? product.price ?? 0}"
|
|
259
|
+
data-price-string="${this.escapeHtml(priceString)}"
|
|
254
260
|
data-name="${this.escapeHtml((product.name || product.title || '').toLowerCase())}"
|
|
261
|
+
data-title="${this.escapeHtml(product.name || product.title || '')}"
|
|
262
|
+
data-product-url="${this.escapeHtml(String(productUrl))}"
|
|
263
|
+
data-image="${this.escapeHtml(String(imageUrl))}"
|
|
264
|
+
data-show-call-for-pricing="${showCallForPricing ? 'true' : 'false'}"
|
|
265
|
+
data-product-type="${productType}"
|
|
266
|
+
data-variants-count="${variantsCount}"
|
|
255
267
|
data-availability="${available ? 'in-stock' : 'out-of-stock'}"
|
|
256
268
|
data-brand="${this.escapeHtml((product.brandName || product.vendor || '').toLowerCase())}">
|
|
257
269
|
|
|
@@ -292,8 +304,8 @@
|
|
|
292
304
|
</h3>
|
|
293
305
|
|
|
294
306
|
<div class="product-price">
|
|
295
|
-
<span class="product-price-current">${price}</span>
|
|
296
|
-
${comparePrice ? `<span class="product-price-original">${comparePrice}</span>` : ''}
|
|
307
|
+
<span class="product-price-current">${showCallForPricing ? 'Call for pricing' : price}</span>
|
|
308
|
+
${(!showCallForPricing && comparePrice) ? `<span class="product-price-original">${comparePrice}</span>` : ''}
|
|
297
309
|
</div>
|
|
298
310
|
|
|
299
311
|
${product.shortDescription ? `<p class="product-description">${this.truncate(product.shortDescription, 80)}</p>` : ''}
|
|
@@ -321,12 +333,12 @@
|
|
|
321
333
|
<div class="collection-image-container">
|
|
322
334
|
<img src="${imageUrl}" alt="${this.escapeHtml(collection.title || collection.name)}" loading="lazy">
|
|
323
335
|
<div class="collection-overlay">
|
|
324
|
-
<a href="/
|
|
336
|
+
<a href="/categories/${slug}" class="collection-link">View Collection</a>
|
|
325
337
|
</div>
|
|
326
338
|
</div>
|
|
327
339
|
<div class="collection-content">
|
|
328
340
|
<h3 class="collection-title">
|
|
329
|
-
<a href="/
|
|
341
|
+
<a href="/categories/${slug}">${this.escapeHtml(collection.title || collection.name)}</a>
|
|
330
342
|
</h3>
|
|
331
343
|
${collection.description ? `<p class="collection-description">${this.truncate(collection.description, 120)}</p>` : ''}
|
|
332
344
|
<div class="collection-meta">
|
|
@@ -341,7 +353,6 @@
|
|
|
341
353
|
* Render generic section (fallback)
|
|
342
354
|
*/
|
|
343
355
|
renderGenericSection(sectionElement, data) {
|
|
344
|
-
console.log('[AsyncSectionLoader] Using generic renderer for:', data);
|
|
345
356
|
sectionElement.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
|
|
346
357
|
},
|
|
347
358
|
|
|
@@ -394,7 +405,6 @@
|
|
|
394
405
|
bubbles: true
|
|
395
406
|
});
|
|
396
407
|
sectionElement.dispatchEvent(event);
|
|
397
|
-
console.log(`[AsyncSectionLoader] Triggered event for: ${sectionName}`);
|
|
398
408
|
},
|
|
399
409
|
|
|
400
410
|
/**
|
|
@@ -440,7 +450,5 @@
|
|
|
440
450
|
// Make available globally
|
|
441
451
|
window.AsyncSectionLoader = AsyncSectionLoader;
|
|
442
452
|
|
|
443
|
-
console.log('[AsyncSectionLoader] Module loaded');
|
|
444
|
-
|
|
445
453
|
})();
|
|
446
454
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
;(
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
function qs(sel, ctx){ return (ctx||document).querySelector(sel) }
|
|
5
|
-
function qsa(sel, ctx){ return Array.from((ctx||document).querySelectorAll(sel)) }
|
|
1
|
+
;(() => {
|
|
2
|
+
const qs = (sel, ctx) => (ctx||document).querySelector(sel);
|
|
3
|
+
const qsa = (sel, ctx) => Array.from((ctx||document).querySelectorAll(sel));
|
|
6
4
|
|
|
7
5
|
const bodyEl = () => document.body;
|
|
8
6
|
const shopCurrency = () => (bodyEl() && bodyEl().dataset.shopCurrency) || window.__SHOP_CURRENCY__ || 'USD';
|
|
@@ -10,7 +8,7 @@
|
|
|
10
8
|
const shopLocale = () => (bodyEl() && bodyEl().dataset.shopLocale) || window.__SHOP_LOCALE__ || 'en-US';
|
|
11
9
|
|
|
12
10
|
// Format money helper
|
|
13
|
-
|
|
11
|
+
const formatMoney = (amount, currency = shopCurrency()) => {
|
|
14
12
|
const locale = shopLocale();
|
|
15
13
|
const value = typeof amount === 'number' ? amount : Number(amount) || 0;
|
|
16
14
|
const currencySymbol = shopCurrencySymbol();
|
|
@@ -23,7 +21,7 @@
|
|
|
23
21
|
currency
|
|
24
22
|
}).format(value);
|
|
25
23
|
} catch (error) {
|
|
26
|
-
|
|
24
|
+
// Silently handle formatting errors
|
|
27
25
|
}
|
|
28
26
|
}
|
|
29
27
|
|
|
@@ -36,7 +34,7 @@
|
|
|
36
34
|
return currencySymbol ? `${currencySymbol}${formattedNumber}` : formattedNumber;
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
const resetCheckoutButton = () => {
|
|
40
38
|
const checkoutBtn = qs('#cart-drawer-checkout-btn')
|
|
41
39
|
if (checkoutBtn) {
|
|
42
40
|
checkoutBtn.classList.remove('loading')
|
|
@@ -59,7 +57,7 @@
|
|
|
59
57
|
loadCartData()
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
|
|
60
|
+
const closeDrawer = () => {
|
|
63
61
|
const drawer = qs('#cart-drawer')
|
|
64
62
|
if(!drawer) return
|
|
65
63
|
drawer.classList.remove('active')
|
|
@@ -139,7 +137,7 @@
|
|
|
139
137
|
}
|
|
140
138
|
}
|
|
141
139
|
|
|
142
|
-
async
|
|
140
|
+
const loadCartData = async () => {
|
|
143
141
|
const loading = qs('[data-cart-loading]')
|
|
144
142
|
const empty = qs('[data-cart-empty]')
|
|
145
143
|
const itemsList = qs('[data-cart-items-list]')
|
|
@@ -206,7 +204,7 @@
|
|
|
206
204
|
try {
|
|
207
205
|
variantImage = localStorage.getItem(imageKey);
|
|
208
206
|
} catch (e) {
|
|
209
|
-
|
|
207
|
+
// Silently handle localStorage read failures
|
|
210
208
|
}
|
|
211
209
|
|
|
212
210
|
// Use variant image if available, otherwise use API image
|
|
@@ -240,7 +238,7 @@
|
|
|
240
238
|
}).join('')
|
|
241
239
|
}
|
|
242
240
|
|
|
243
|
-
|
|
241
|
+
const updateCartFooter = (cart) => {
|
|
244
242
|
const total = qs('[data-cart-total]')
|
|
245
243
|
if (total) {
|
|
246
244
|
total.textContent = formatMoney(cart.total)
|
|
@@ -253,7 +251,7 @@
|
|
|
253
251
|
await syncQuantity(input)
|
|
254
252
|
}
|
|
255
253
|
|
|
256
|
-
async
|
|
254
|
+
const syncQuantity = async (input) => {
|
|
257
255
|
const productId = input.dataset.productId
|
|
258
256
|
const variantId = input.dataset.variantId
|
|
259
257
|
const quantity = parseInt(input.value, 10)
|
|
@@ -295,7 +293,7 @@
|
|
|
295
293
|
// Flag to prevent multiple simultaneous API calls
|
|
296
294
|
// Use CartManager instead of making direct API calls
|
|
297
295
|
// This prevents duplicate API calls since CartManager handles caching and deduplication
|
|
298
|
-
|
|
296
|
+
const initCartQuantity = () => {
|
|
299
297
|
// Wait for CartManager to be available
|
|
300
298
|
const checkCartManager = () => {
|
|
301
299
|
if (window.CartManager && typeof window.CartManager.getCartCount === 'function') {
|
|
@@ -342,15 +340,15 @@
|
|
|
342
340
|
runCartQuantityInit()
|
|
343
341
|
}
|
|
344
342
|
|
|
345
|
-
document.addEventListener('DOMContentLoaded',
|
|
343
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
346
344
|
const toggle = qsa('[data-cart-toggle]')
|
|
347
|
-
toggle.forEach(t => t.addEventListener('click',
|
|
345
|
+
toggle.forEach(t => t.addEventListener('click', (e) => {
|
|
348
346
|
e.preventDefault()
|
|
349
347
|
e.stopPropagation()
|
|
350
348
|
openDrawer()
|
|
351
349
|
}))
|
|
352
350
|
|
|
353
|
-
document.body.addEventListener('click',
|
|
351
|
+
document.body.addEventListener('click', (e) => {
|
|
354
352
|
const closeBtn = e.target.closest('[data-cart-close]')
|
|
355
353
|
const overlay = e.target.closest('[data-cart-overlay]')
|
|
356
354
|
if (closeBtn || overlay) {
|
|
@@ -359,7 +357,7 @@
|
|
|
359
357
|
}
|
|
360
358
|
})
|
|
361
359
|
|
|
362
|
-
document.body.addEventListener('click',
|
|
360
|
+
document.body.addEventListener('click', (e) => {
|
|
363
361
|
const dec = e.target.closest('.qty-btn[data-action="decrease"]')
|
|
364
362
|
const inc = e.target.closest('.qty-btn[data-action="increase"]')
|
|
365
363
|
const removeBtn = e.target.closest('[data-remove-item]')
|
|
@@ -377,7 +375,7 @@
|
|
|
377
375
|
}
|
|
378
376
|
})
|
|
379
377
|
|
|
380
|
-
document.body.addEventListener('change',
|
|
378
|
+
document.body.addEventListener('change', (e) => {
|
|
381
379
|
const input = e.target.closest('.qty-input')
|
|
382
380
|
if (input) syncQuantity(input)
|
|
383
381
|
})
|
|
@@ -390,7 +388,7 @@
|
|
|
390
388
|
checkoutBtn.dataset.originalText = checkoutBtn.textContent.trim() || 'Check out'
|
|
391
389
|
}
|
|
392
390
|
|
|
393
|
-
checkoutBtn.addEventListener('click',
|
|
391
|
+
checkoutBtn.addEventListener('click', (e) => {
|
|
394
392
|
// Prevent multiple clicks
|
|
395
393
|
if (checkoutBtn.classList.contains('loading') || checkoutBtn.disabled) {
|
|
396
394
|
e.preventDefault()
|
|
@@ -428,7 +426,7 @@
|
|
|
428
426
|
// Discount toggle
|
|
429
427
|
const discountToggle = qs('[data-discount-toggle]')
|
|
430
428
|
if (discountToggle) {
|
|
431
|
-
discountToggle.addEventListener('click',
|
|
429
|
+
discountToggle.addEventListener('click', () => {
|
|
432
430
|
const content = qs('[data-discount-content]')
|
|
433
431
|
const isExpanded = discountToggle.getAttribute('aria-expanded') === 'true'
|
|
434
432
|
discountToggle.setAttribute('aria-expanded', !isExpanded)
|
|
@@ -439,7 +437,7 @@
|
|
|
439
437
|
}
|
|
440
438
|
|
|
441
439
|
// Close on Escape key
|
|
442
|
-
document.addEventListener('keydown',
|
|
440
|
+
document.addEventListener('keydown', (e) => {
|
|
443
441
|
if (e.key === 'Escape') {
|
|
444
442
|
const drawer = qs('#cart-drawer')
|
|
445
443
|
if (drawer && drawer.classList.contains('active')) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Ensures cart badge updates across all components
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
(
|
|
7
|
+
(() => {
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
const CartManager = {
|
|
@@ -55,9 +55,6 @@
|
|
|
55
55
|
cartToggles.forEach(toggle => {
|
|
56
56
|
toggle.setAttribute('aria-label', `Shopping cart with ${numericCount} item${numericCount !== 1 ? 's' : ''}`);
|
|
57
57
|
});
|
|
58
|
-
|
|
59
|
-
// Debug logging (can be removed in production)
|
|
60
|
-
console.log('[CartManager] Updated cart badge:', numericCount, 'badges:', countElements.length);
|
|
61
58
|
},
|
|
62
59
|
|
|
63
60
|
/**
|
|
@@ -72,14 +69,12 @@
|
|
|
72
69
|
if (!forceRefresh && this._cartQuantityCache !== null && this._cacheTimestamp) {
|
|
73
70
|
const cacheAge = now - this._cacheTimestamp;
|
|
74
71
|
if (cacheAge < this._cacheTTL) {
|
|
75
|
-
console.log('[CartManager] Returning cached cart count:', this._cartQuantityCache);
|
|
76
72
|
return this._cartQuantityCache;
|
|
77
73
|
}
|
|
78
74
|
}
|
|
79
75
|
|
|
80
76
|
// If already loading, return the existing promise to prevent duplicate calls
|
|
81
77
|
if (this._cartQuantityLoading && this._cartQuantityPromise) {
|
|
82
|
-
console.log('[CartManager] Cart quantity already loading, returning existing promise');
|
|
83
78
|
return this._cartQuantityPromise;
|
|
84
79
|
}
|
|
85
80
|
|
|
@@ -105,7 +100,6 @@
|
|
|
105
100
|
// Update cache
|
|
106
101
|
this._cartQuantityCache = count;
|
|
107
102
|
this._cacheTimestamp = Date.now();
|
|
108
|
-
console.log('[CartManager] Fetched and cached cart count:', count);
|
|
109
103
|
return count;
|
|
110
104
|
}
|
|
111
105
|
|
|
@@ -139,8 +133,6 @@
|
|
|
139
133
|
0;
|
|
140
134
|
}
|
|
141
135
|
|
|
142
|
-
console.log('[CartManager] Dispatching cart:updated event with count:', count, 'cartData:', cartData);
|
|
143
|
-
|
|
144
136
|
const event = new CustomEvent('cart:updated', {
|
|
145
137
|
detail: {
|
|
146
138
|
count: count,
|
|
@@ -166,29 +158,24 @@
|
|
|
166
158
|
invalidateCache() {
|
|
167
159
|
this._cartQuantityCache = null;
|
|
168
160
|
this._cacheTimestamp = null;
|
|
169
|
-
console.log('[CartManager] Cart quantity cache invalidated');
|
|
170
161
|
},
|
|
171
162
|
|
|
172
163
|
/**
|
|
173
164
|
* Initialize cart manager and set up event listeners
|
|
174
165
|
*/
|
|
175
166
|
init() {
|
|
176
|
-
console.log('[CartManager] Initializing...');
|
|
177
|
-
|
|
178
167
|
// Listen for cart:updated events from external sources
|
|
179
168
|
// Note: dispatchCartUpdated() already calls updateCartBadge() directly,
|
|
180
169
|
// so this listener handles events from other components (like cart-drawer fallback)
|
|
181
170
|
// It's safe to call updateCartBadge() multiple times as it's idempotent
|
|
182
171
|
document.addEventListener('cart:updated', (event) => {
|
|
183
172
|
const count = event.detail.count || 0;
|
|
184
|
-
console.log('[CartManager] Received cart:updated event with count:', count);
|
|
185
173
|
// updateCartBadge is idempotent, so calling it multiple times is safe
|
|
186
174
|
this.updateCartBadge(count);
|
|
187
175
|
});
|
|
188
176
|
|
|
189
177
|
// Load initial cart count on page load
|
|
190
178
|
const initCart = () => {
|
|
191
|
-
console.log('[CartManager] DOM ready, loading initial cart count...');
|
|
192
179
|
this.loadInitialCartCount();
|
|
193
180
|
};
|
|
194
181
|
|
|
@@ -207,7 +194,6 @@
|
|
|
207
194
|
// Small delay to ensure DOM is ready
|
|
208
195
|
setTimeout(async () => {
|
|
209
196
|
const count = await this.getCartCount();
|
|
210
|
-
console.log('[CartManager] Loaded initial cart count:', count);
|
|
211
197
|
this.updateCartBadge(count);
|
|
212
198
|
}, 100);
|
|
213
199
|
}
|
|
@@ -3,9 +3,18 @@
|
|
|
3
3
|
* Handles price change detection, polling, and user notifications on the checkout page
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
(
|
|
6
|
+
(() => {
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
+
const percentDecode = (s) => {
|
|
10
|
+
if (typeof s !== 'string') return s || '';
|
|
11
|
+
try {
|
|
12
|
+
return s.replace(/\+/g, ' ').replace(/%([0-9A-Fa-f]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
|
|
13
|
+
} catch {
|
|
14
|
+
return s;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
9
18
|
// Configuration
|
|
10
19
|
const POLL_INTERVAL = 45000; // Poll every 45 seconds
|
|
11
20
|
const PRICE_CHANGE_THRESHOLD = 0.01; // 1 cent threshold for price changes
|
|
@@ -36,7 +45,7 @@
|
|
|
36
45
|
for (let cookie of cookies) {
|
|
37
46
|
const [name, value] = cookie.trim().split('=');
|
|
38
47
|
if (name === 'checkout_token' || name === 'checkoutToken') {
|
|
39
|
-
return
|
|
48
|
+
return percentDecode(value);
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
51
|
|
|
@@ -49,7 +58,6 @@
|
|
|
49
58
|
async function fetchCheckout() {
|
|
50
59
|
const checkoutToken = getCheckoutToken();
|
|
51
60
|
if (!checkoutToken) {
|
|
52
|
-
console.warn('[PRICE HANDLER] No checkout token found');
|
|
53
61
|
return null;
|
|
54
62
|
}
|
|
55
63
|
|
|
@@ -243,8 +251,6 @@
|
|
|
243
251
|
checkForPriceChanges();
|
|
244
252
|
}
|
|
245
253
|
}, POLL_INTERVAL);
|
|
246
|
-
|
|
247
|
-
console.log('[PRICE HANDLER] Started polling for price changes');
|
|
248
254
|
}
|
|
249
255
|
|
|
250
256
|
/**
|
|
@@ -254,7 +260,6 @@
|
|
|
254
260
|
if (pollInterval) {
|
|
255
261
|
clearInterval(pollInterval);
|
|
256
262
|
pollInterval = null;
|
|
257
|
-
console.log('[PRICE HANDLER] Stopped polling for price changes');
|
|
258
263
|
}
|
|
259
264
|
}
|
|
260
265
|
|
|
@@ -291,9 +296,7 @@
|
|
|
291
296
|
*/
|
|
292
297
|
function requestNotificationPermission() {
|
|
293
298
|
if ('Notification' in window && Notification.permission === 'default') {
|
|
294
|
-
Notification.requestPermission()
|
|
295
|
-
console.log('[PRICE HANDLER] Notification permission:', permission);
|
|
296
|
-
});
|
|
299
|
+
Notification.requestPermission();
|
|
297
300
|
}
|
|
298
301
|
}
|
|
299
302
|
|
|
@@ -350,8 +353,6 @@
|
|
|
350
353
|
|
|
351
354
|
// Stop polling when page is about to unload
|
|
352
355
|
window.addEventListener('beforeunload', stopPolling);
|
|
353
|
-
|
|
354
|
-
console.log('[PRICE HANDLER] Initialized');
|
|
355
356
|
}
|
|
356
357
|
|
|
357
358
|
// Initialize when DOM is ready
|