@o2vend/theme-cli 1.0.32
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 +425 -0
- package/assets/Logo_o2vend.png +0 -0
- package/assets/favicon.png +0 -0
- package/assets/logo-white.png +0 -0
- package/bin/o2vend +42 -0
- package/config/widget-map.json +50 -0
- package/lib/commands/check.js +201 -0
- package/lib/commands/generate.js +33 -0
- package/lib/commands/init.js +214 -0
- package/lib/commands/optimize.js +216 -0
- package/lib/commands/package.js +208 -0
- package/lib/commands/serve.js +105 -0
- package/lib/commands/validate.js +191 -0
- package/lib/lib/api-client.js +357 -0
- package/lib/lib/dev-server.js +2618 -0
- package/lib/lib/file-watcher.js +80 -0
- package/lib/lib/hot-reload.js +106 -0
- package/lib/lib/liquid-engine.js +822 -0
- package/lib/lib/liquid-filters.js +671 -0
- package/lib/lib/mock-api-server.js +989 -0
- package/lib/lib/mock-data.js +1468 -0
- package/lib/lib/widget-service.js +321 -0
- package/package.json +70 -0
- package/test-theme/README.md +27 -0
- package/test-theme/assets/async-sections.js +446 -0
- package/test-theme/assets/cart-drawer.js +463 -0
- package/test-theme/assets/cart-manager.js +223 -0
- package/test-theme/assets/checkout-price-handler.js +368 -0
- package/test-theme/assets/components.css +4629 -0
- package/test-theme/assets/delivery-zone.css +299 -0
- package/test-theme/assets/delivery-zone.js +396 -0
- package/test-theme/assets/logo.png +0 -0
- package/test-theme/assets/sections.css +48 -0
- package/test-theme/assets/theme.css +3500 -0
- package/test-theme/assets/theme.js +3745 -0
- package/test-theme/config/settings_data.json +292 -0
- package/test-theme/config/settings_schema.json +1050 -0
- package/test-theme/layout/theme.liquid +195 -0
- package/test-theme/locales/en.default.json +260 -0
- package/test-theme/sections/content-fallback.liquid +53 -0
- package/test-theme/sections/content.liquid +57 -0
- package/test-theme/sections/footer-fallback.liquid +328 -0
- package/test-theme/sections/footer.liquid +278 -0
- package/test-theme/sections/header-fallback.liquid +1805 -0
- package/test-theme/sections/header.liquid +1145 -0
- package/test-theme/sections/hero-fallback.liquid +212 -0
- package/test-theme/sections/hero.liquid +136 -0
- package/test-theme/snippets/account-sidebar.liquid +200 -0
- package/test-theme/snippets/add-to-cart-modal.liquid +484 -0
- package/test-theme/snippets/breadcrumbs.liquid +134 -0
- package/test-theme/snippets/cart-drawer.liquid +467 -0
- package/test-theme/snippets/delivery-zone-city-selector.liquid +79 -0
- package/test-theme/snippets/delivery-zone-modal.liquid +337 -0
- package/test-theme/snippets/delivery-zone-search.liquid +78 -0
- package/test-theme/snippets/icon.liquid +105 -0
- package/test-theme/snippets/login-modal.liquid +346 -0
- package/test-theme/snippets/mega-menu.liquid +812 -0
- package/test-theme/snippets/news-thumbnail.liquid +187 -0
- package/test-theme/snippets/pagination.liquid +120 -0
- package/test-theme/snippets/price.liquid +92 -0
- package/test-theme/snippets/product-card-related.liquid +78 -0
- package/test-theme/snippets/product-card-simple.liquid +41 -0
- package/test-theme/snippets/product-card.liquid +697 -0
- package/test-theme/snippets/rating.liquid +85 -0
- package/test-theme/snippets/skeleton-collection-grid.liquid +114 -0
- package/test-theme/snippets/skeleton-product-card.liquid +124 -0
- package/test-theme/snippets/skeleton-product-grid.liquid +34 -0
- package/test-theme/snippets/social-sharing.liquid +185 -0
- package/test-theme/templates/account/dashboard.liquid +401 -0
- package/test-theme/templates/account/loyalty-redemption.liquid +405 -0
- package/test-theme/templates/account/loyalty.liquid +588 -0
- package/test-theme/templates/account/order-detail.liquid +230 -0
- package/test-theme/templates/account/orders.liquid +349 -0
- package/test-theme/templates/account/profile.liquid +758 -0
- package/test-theme/templates/account/register.liquid +232 -0
- package/test-theme/templates/account/return-orders.liquid +348 -0
- package/test-theme/templates/account/store-credit.liquid +464 -0
- package/test-theme/templates/account/subscriptions.liquid +601 -0
- package/test-theme/templates/account/wishlist.liquid +419 -0
- package/test-theme/templates/address-book.liquid +1092 -0
- package/test-theme/templates/categories.liquid +452 -0
- package/test-theme/templates/checkout.liquid +4511 -0
- package/test-theme/templates/error.liquid +384 -0
- package/test-theme/templates/index.liquid +11 -0
- package/test-theme/templates/login.liquid +185 -0
- package/test-theme/templates/order-confirmation.liquid +720 -0
- package/test-theme/templates/page.liquid +297 -0
- package/test-theme/templates/product-detail.liquid +4363 -0
- package/test-theme/templates/products.liquid +518 -0
- package/test-theme/templates/search.liquid +922 -0
- package/test-theme/theme.json.example +19 -0
- package/test-theme/widgets/brand-carousel.liquid +676 -0
- package/test-theme/widgets/brand.liquid +245 -0
- package/test-theme/widgets/carousel.liquid +843 -0
- package/test-theme/widgets/category-list-carousel.liquid +656 -0
- package/test-theme/widgets/category-list.liquid +340 -0
- package/test-theme/widgets/category.liquid +475 -0
- package/test-theme/widgets/discount-time.liquid +176 -0
- package/test-theme/widgets/footer-menu.liquid +695 -0
- package/test-theme/widgets/footer.liquid +179 -0
- package/test-theme/widgets/gallery.liquid +271 -0
- package/test-theme/widgets/header-menu.liquid +932 -0
- package/test-theme/widgets/header.liquid +159 -0
- package/test-theme/widgets/html.liquid +214 -0
- package/test-theme/widgets/news.liquid +217 -0
- package/test-theme/widgets/product-canvas.liquid +235 -0
- package/test-theme/widgets/product-carousel.liquid +502 -0
- package/test-theme/widgets/product.liquid +45 -0
- package/test-theme/widgets/recently-viewed.liquid +26 -0
- package/test-theme/widgets/shared/product-grid.liquid +339 -0
- package/test-theme/widgets/simple-product.liquid +42 -0
- package/test-theme/widgets/single-product.liquid +610 -0
- package/test-theme/widgets/spacebar-carousel.liquid +663 -0
- package/test-theme/widgets/spacebar.liquid +279 -0
- package/test-theme/widgets/splash.liquid +378 -0
- package/test-theme/widgets/testimonial-carousel.liquid +709 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkout Price Handler
|
|
3
|
+
* Handles price change detection, polling, and user notifications on the checkout page
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
(function() {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// Configuration
|
|
10
|
+
const POLL_INTERVAL = 45000; // Poll every 45 seconds
|
|
11
|
+
const PRICE_CHANGE_THRESHOLD = 0.01; // 1 cent threshold for price changes
|
|
12
|
+
const MAX_POLL_ATTEMPTS = 20; // Stop polling after 20 attempts (15 minutes)
|
|
13
|
+
|
|
14
|
+
let pollInterval = null;
|
|
15
|
+
let pollAttempts = 0;
|
|
16
|
+
let lastCheckoutData = null;
|
|
17
|
+
let priceChangeAcknowledged = false;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get checkout token from various sources
|
|
21
|
+
*/
|
|
22
|
+
function getCheckoutToken() {
|
|
23
|
+
// Try from global variable first
|
|
24
|
+
if (typeof CHECKOUT_TOKEN !== 'undefined' && CHECKOUT_TOKEN) {
|
|
25
|
+
return CHECKOUT_TOKEN;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Try from URL
|
|
29
|
+
const urlMatch = window.location.pathname.match(/\/checkout\/([^\/]+)/);
|
|
30
|
+
if (urlMatch) {
|
|
31
|
+
return urlMatch[1];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Try from cookie
|
|
35
|
+
const cookies = document.cookie.split(';');
|
|
36
|
+
for (let cookie of cookies) {
|
|
37
|
+
const [name, value] = cookie.trim().split('=');
|
|
38
|
+
if (name === 'checkout_token' || name === 'checkoutToken') {
|
|
39
|
+
return decodeURIComponent(value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Fetch latest checkout data
|
|
48
|
+
*/
|
|
49
|
+
async function fetchCheckout() {
|
|
50
|
+
const checkoutToken = getCheckoutToken();
|
|
51
|
+
if (!checkoutToken) {
|
|
52
|
+
console.warn('[PRICE HANDLER] No checkout token found');
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(`/webstoreapi/checkout`, {
|
|
58
|
+
method: 'GET',
|
|
59
|
+
headers: {
|
|
60
|
+
'Content-Type': 'application/json'
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const data = await response.json();
|
|
69
|
+
return data.success ? data.data : data;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('[PRICE HANDLER] Error fetching checkout:', error);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Show price change notification
|
|
78
|
+
*/
|
|
79
|
+
function showPriceChangeNotification(checkoutData) {
|
|
80
|
+
const banner = document.getElementById('price-change-banner');
|
|
81
|
+
if (!banner) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Update banner content if needed
|
|
86
|
+
const metadata = checkoutData.priceChangeMetadata || {};
|
|
87
|
+
if (metadata.detected) {
|
|
88
|
+
banner.style.display = 'block';
|
|
89
|
+
|
|
90
|
+
// Add appropriate class based on change type
|
|
91
|
+
banner.classList.remove('price-decrease', 'price-critical');
|
|
92
|
+
if (metadata.totalChange < 0) {
|
|
93
|
+
banner.classList.add('price-decrease');
|
|
94
|
+
} else if (metadata.hasCriticalIssues) {
|
|
95
|
+
banner.classList.add('price-critical');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Update price change details display
|
|
102
|
+
*/
|
|
103
|
+
function updatePriceChangeDetails(checkoutData) {
|
|
104
|
+
const detailsContainer = document.getElementById('price-change-details');
|
|
105
|
+
if (!detailsContainer) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const metadata = checkoutData.priceChangeMetadata || {};
|
|
110
|
+
if (!metadata.detected || metadata.itemsChanged === 0) {
|
|
111
|
+
detailsContainer.style.display = 'none';
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
detailsContainer.style.display = 'block';
|
|
116
|
+
|
|
117
|
+
// Update line items if needed
|
|
118
|
+
const itemsList = detailsContainer.querySelector('.price-change-items-list');
|
|
119
|
+
if (itemsList && checkoutData.lineItems) {
|
|
120
|
+
// Items are already rendered server-side, just ensure visibility
|
|
121
|
+
checkoutData.lineItems.forEach((item, index) => {
|
|
122
|
+
const itemElement = itemsList.querySelector(`[data-item-id="${item.id || item.variantId || index}"]`);
|
|
123
|
+
if (itemElement) {
|
|
124
|
+
itemElement.style.display = 'flex';
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Refresh checkout prices
|
|
132
|
+
*/
|
|
133
|
+
async function refreshCheckoutPrices() {
|
|
134
|
+
const refreshBtn = document.getElementById('refresh-checkout-prices');
|
|
135
|
+
if (refreshBtn) {
|
|
136
|
+
refreshBtn.disabled = true;
|
|
137
|
+
refreshBtn.textContent = 'Refreshing...';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const checkoutData = await fetchCheckout();
|
|
142
|
+
if (checkoutData) {
|
|
143
|
+
// Reload page to show updated prices
|
|
144
|
+
window.location.reload();
|
|
145
|
+
} else {
|
|
146
|
+
alert('Unable to refresh prices. Please try again.');
|
|
147
|
+
if (refreshBtn) {
|
|
148
|
+
refreshBtn.disabled = false;
|
|
149
|
+
refreshBtn.textContent = 'Refresh Prices';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error('[PRICE HANDLER] Error refreshing prices:', error);
|
|
154
|
+
alert('Error refreshing prices. Please refresh the page.');
|
|
155
|
+
if (refreshBtn) {
|
|
156
|
+
refreshBtn.disabled = false;
|
|
157
|
+
refreshBtn.textContent = 'Refresh Prices';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Dismiss price change banner
|
|
164
|
+
*/
|
|
165
|
+
function dismissPriceChangeBanner() {
|
|
166
|
+
const banner = document.getElementById('price-change-banner');
|
|
167
|
+
if (banner) {
|
|
168
|
+
banner.style.display = 'none';
|
|
169
|
+
// Store dismissal in sessionStorage
|
|
170
|
+
sessionStorage.setItem('priceChangeBannerDismissed', 'true');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Check for price changes
|
|
176
|
+
*/
|
|
177
|
+
async function checkForPriceChanges() {
|
|
178
|
+
pollAttempts++;
|
|
179
|
+
|
|
180
|
+
// Stop polling after max attempts
|
|
181
|
+
if (pollAttempts > MAX_POLL_ATTEMPTS) {
|
|
182
|
+
stopPolling();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const checkoutData = await fetchCheckout();
|
|
187
|
+
if (!checkoutData) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const metadata = checkoutData.priceChangeMetadata || {};
|
|
192
|
+
|
|
193
|
+
// Check if prices changed
|
|
194
|
+
if (metadata.detected) {
|
|
195
|
+
// Compare with last known data
|
|
196
|
+
if (lastCheckoutData) {
|
|
197
|
+
const lastMetadata = lastCheckoutData.priceChangeMetadata || {};
|
|
198
|
+
if (lastMetadata.detected !== metadata.detected ||
|
|
199
|
+
lastMetadata.itemsChanged !== metadata.itemsChanged) {
|
|
200
|
+
// Prices changed, show notification
|
|
201
|
+
showPriceChangeNotification(checkoutData);
|
|
202
|
+
updatePriceChangeDetails(checkoutData);
|
|
203
|
+
|
|
204
|
+
// Show browser notification if supported
|
|
205
|
+
if ('Notification' in window && Notification.permission === 'granted') {
|
|
206
|
+
new Notification('Prices Updated', {
|
|
207
|
+
body: 'Some prices in your checkout have changed. Please review your order.',
|
|
208
|
+
icon: '/favicon.ico'
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
// First check, just update display
|
|
214
|
+
showPriceChangeNotification(checkoutData);
|
|
215
|
+
updatePriceChangeDetails(checkoutData);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
lastCheckoutData = checkoutData;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Start polling for price changes
|
|
224
|
+
*/
|
|
225
|
+
function startPolling() {
|
|
226
|
+
// Don't start if already polling
|
|
227
|
+
if (pollInterval) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Don't poll if page is hidden
|
|
232
|
+
if (document.hidden) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Initial check
|
|
237
|
+
checkForPriceChanges();
|
|
238
|
+
|
|
239
|
+
// Set up interval
|
|
240
|
+
pollInterval = setInterval(() => {
|
|
241
|
+
// Only poll if page is visible
|
|
242
|
+
if (!document.hidden) {
|
|
243
|
+
checkForPriceChanges();
|
|
244
|
+
}
|
|
245
|
+
}, POLL_INTERVAL);
|
|
246
|
+
|
|
247
|
+
console.log('[PRICE HANDLER] Started polling for price changes');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Stop polling for price changes
|
|
252
|
+
*/
|
|
253
|
+
function stopPolling() {
|
|
254
|
+
if (pollInterval) {
|
|
255
|
+
clearInterval(pollInterval);
|
|
256
|
+
pollInterval = null;
|
|
257
|
+
console.log('[PRICE HANDLER] Stopped polling for price changes');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Handle form submission with price change validation
|
|
263
|
+
*/
|
|
264
|
+
function handleFormSubmission(e) {
|
|
265
|
+
const form = e.target;
|
|
266
|
+
const acknowledgeCheckbox = document.getElementById('acknowledge-price-changes');
|
|
267
|
+
|
|
268
|
+
// Check if price changes require acknowledgment
|
|
269
|
+
if (acknowledgeCheckbox && !acknowledgeCheckbox.checked) {
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
alert('Please acknowledge the price changes before completing your order.');
|
|
272
|
+
acknowledgeCheckbox.focus();
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Add acknowledgment to form data if checkbox is checked
|
|
277
|
+
if (acknowledgeCheckbox && acknowledgeCheckbox.checked) {
|
|
278
|
+
const hiddenInput = document.createElement('input');
|
|
279
|
+
hiddenInput.type = 'hidden';
|
|
280
|
+
hiddenInput.name = 'acknowledgePriceChanges';
|
|
281
|
+
hiddenInput.value = 'true';
|
|
282
|
+
form.appendChild(hiddenInput);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Stop polling when form is submitted
|
|
286
|
+
stopPolling();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Request notification permission
|
|
291
|
+
*/
|
|
292
|
+
function requestNotificationPermission() {
|
|
293
|
+
if ('Notification' in window && Notification.permission === 'default') {
|
|
294
|
+
Notification.requestPermission().then(permission => {
|
|
295
|
+
console.log('[PRICE HANDLER] Notification permission:', permission);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Initialize price change handler
|
|
302
|
+
*/
|
|
303
|
+
function init() {
|
|
304
|
+
// Only run on checkout page
|
|
305
|
+
if (!window.location.pathname.includes('/checkout')) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Check if banner was dismissed
|
|
310
|
+
const bannerDismissed = sessionStorage.getItem('priceChangeBannerDismissed');
|
|
311
|
+
if (bannerDismissed === 'true') {
|
|
312
|
+
const banner = document.getElementById('price-change-banner');
|
|
313
|
+
if (banner) {
|
|
314
|
+
banner.style.display = 'none';
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Set up event listeners
|
|
319
|
+
const refreshBtn = document.getElementById('refresh-checkout-prices');
|
|
320
|
+
if (refreshBtn) {
|
|
321
|
+
refreshBtn.addEventListener('click', refreshCheckoutPrices);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const dismissBtn = document.getElementById('dismiss-price-change-banner');
|
|
325
|
+
if (dismissBtn) {
|
|
326
|
+
dismissBtn.addEventListener('click', dismissPriceChangeBanner);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const checkoutForm = document.getElementById('checkout-form');
|
|
330
|
+
if (checkoutForm) {
|
|
331
|
+
checkoutForm.addEventListener('submit', handleFormSubmission);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Request notification permission
|
|
335
|
+
requestNotificationPermission();
|
|
336
|
+
|
|
337
|
+
// Start polling when page becomes visible
|
|
338
|
+
if (!document.hidden) {
|
|
339
|
+
startPolling();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Handle visibility change
|
|
343
|
+
document.addEventListener('visibilitychange', () => {
|
|
344
|
+
if (document.hidden) {
|
|
345
|
+
stopPolling();
|
|
346
|
+
} else {
|
|
347
|
+
startPolling();
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Stop polling when page is about to unload
|
|
352
|
+
window.addEventListener('beforeunload', stopPolling);
|
|
353
|
+
|
|
354
|
+
console.log('[PRICE HANDLER] Initialized');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Initialize when DOM is ready
|
|
358
|
+
if (document.readyState === 'loading') {
|
|
359
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
360
|
+
} else {
|
|
361
|
+
init();
|
|
362
|
+
}
|
|
363
|
+
})();
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
|