@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.
Files changed (116) hide show
  1. package/README.md +425 -0
  2. package/assets/Logo_o2vend.png +0 -0
  3. package/assets/favicon.png +0 -0
  4. package/assets/logo-white.png +0 -0
  5. package/bin/o2vend +42 -0
  6. package/config/widget-map.json +50 -0
  7. package/lib/commands/check.js +201 -0
  8. package/lib/commands/generate.js +33 -0
  9. package/lib/commands/init.js +214 -0
  10. package/lib/commands/optimize.js +216 -0
  11. package/lib/commands/package.js +208 -0
  12. package/lib/commands/serve.js +105 -0
  13. package/lib/commands/validate.js +191 -0
  14. package/lib/lib/api-client.js +357 -0
  15. package/lib/lib/dev-server.js +2618 -0
  16. package/lib/lib/file-watcher.js +80 -0
  17. package/lib/lib/hot-reload.js +106 -0
  18. package/lib/lib/liquid-engine.js +822 -0
  19. package/lib/lib/liquid-filters.js +671 -0
  20. package/lib/lib/mock-api-server.js +989 -0
  21. package/lib/lib/mock-data.js +1468 -0
  22. package/lib/lib/widget-service.js +321 -0
  23. package/package.json +70 -0
  24. package/test-theme/README.md +27 -0
  25. package/test-theme/assets/async-sections.js +446 -0
  26. package/test-theme/assets/cart-drawer.js +463 -0
  27. package/test-theme/assets/cart-manager.js +223 -0
  28. package/test-theme/assets/checkout-price-handler.js +368 -0
  29. package/test-theme/assets/components.css +4629 -0
  30. package/test-theme/assets/delivery-zone.css +299 -0
  31. package/test-theme/assets/delivery-zone.js +396 -0
  32. package/test-theme/assets/logo.png +0 -0
  33. package/test-theme/assets/sections.css +48 -0
  34. package/test-theme/assets/theme.css +3500 -0
  35. package/test-theme/assets/theme.js +3745 -0
  36. package/test-theme/config/settings_data.json +292 -0
  37. package/test-theme/config/settings_schema.json +1050 -0
  38. package/test-theme/layout/theme.liquid +195 -0
  39. package/test-theme/locales/en.default.json +260 -0
  40. package/test-theme/sections/content-fallback.liquid +53 -0
  41. package/test-theme/sections/content.liquid +57 -0
  42. package/test-theme/sections/footer-fallback.liquid +328 -0
  43. package/test-theme/sections/footer.liquid +278 -0
  44. package/test-theme/sections/header-fallback.liquid +1805 -0
  45. package/test-theme/sections/header.liquid +1145 -0
  46. package/test-theme/sections/hero-fallback.liquid +212 -0
  47. package/test-theme/sections/hero.liquid +136 -0
  48. package/test-theme/snippets/account-sidebar.liquid +200 -0
  49. package/test-theme/snippets/add-to-cart-modal.liquid +484 -0
  50. package/test-theme/snippets/breadcrumbs.liquid +134 -0
  51. package/test-theme/snippets/cart-drawer.liquid +467 -0
  52. package/test-theme/snippets/delivery-zone-city-selector.liquid +79 -0
  53. package/test-theme/snippets/delivery-zone-modal.liquid +337 -0
  54. package/test-theme/snippets/delivery-zone-search.liquid +78 -0
  55. package/test-theme/snippets/icon.liquid +105 -0
  56. package/test-theme/snippets/login-modal.liquid +346 -0
  57. package/test-theme/snippets/mega-menu.liquid +812 -0
  58. package/test-theme/snippets/news-thumbnail.liquid +187 -0
  59. package/test-theme/snippets/pagination.liquid +120 -0
  60. package/test-theme/snippets/price.liquid +92 -0
  61. package/test-theme/snippets/product-card-related.liquid +78 -0
  62. package/test-theme/snippets/product-card-simple.liquid +41 -0
  63. package/test-theme/snippets/product-card.liquid +697 -0
  64. package/test-theme/snippets/rating.liquid +85 -0
  65. package/test-theme/snippets/skeleton-collection-grid.liquid +114 -0
  66. package/test-theme/snippets/skeleton-product-card.liquid +124 -0
  67. package/test-theme/snippets/skeleton-product-grid.liquid +34 -0
  68. package/test-theme/snippets/social-sharing.liquid +185 -0
  69. package/test-theme/templates/account/dashboard.liquid +401 -0
  70. package/test-theme/templates/account/loyalty-redemption.liquid +405 -0
  71. package/test-theme/templates/account/loyalty.liquid +588 -0
  72. package/test-theme/templates/account/order-detail.liquid +230 -0
  73. package/test-theme/templates/account/orders.liquid +349 -0
  74. package/test-theme/templates/account/profile.liquid +758 -0
  75. package/test-theme/templates/account/register.liquid +232 -0
  76. package/test-theme/templates/account/return-orders.liquid +348 -0
  77. package/test-theme/templates/account/store-credit.liquid +464 -0
  78. package/test-theme/templates/account/subscriptions.liquid +601 -0
  79. package/test-theme/templates/account/wishlist.liquid +419 -0
  80. package/test-theme/templates/address-book.liquid +1092 -0
  81. package/test-theme/templates/categories.liquid +452 -0
  82. package/test-theme/templates/checkout.liquid +4511 -0
  83. package/test-theme/templates/error.liquid +384 -0
  84. package/test-theme/templates/index.liquid +11 -0
  85. package/test-theme/templates/login.liquid +185 -0
  86. package/test-theme/templates/order-confirmation.liquid +720 -0
  87. package/test-theme/templates/page.liquid +297 -0
  88. package/test-theme/templates/product-detail.liquid +4363 -0
  89. package/test-theme/templates/products.liquid +518 -0
  90. package/test-theme/templates/search.liquid +922 -0
  91. package/test-theme/theme.json.example +19 -0
  92. package/test-theme/widgets/brand-carousel.liquid +676 -0
  93. package/test-theme/widgets/brand.liquid +245 -0
  94. package/test-theme/widgets/carousel.liquid +843 -0
  95. package/test-theme/widgets/category-list-carousel.liquid +656 -0
  96. package/test-theme/widgets/category-list.liquid +340 -0
  97. package/test-theme/widgets/category.liquid +475 -0
  98. package/test-theme/widgets/discount-time.liquid +176 -0
  99. package/test-theme/widgets/footer-menu.liquid +695 -0
  100. package/test-theme/widgets/footer.liquid +179 -0
  101. package/test-theme/widgets/gallery.liquid +271 -0
  102. package/test-theme/widgets/header-menu.liquid +932 -0
  103. package/test-theme/widgets/header.liquid +159 -0
  104. package/test-theme/widgets/html.liquid +214 -0
  105. package/test-theme/widgets/news.liquid +217 -0
  106. package/test-theme/widgets/product-canvas.liquid +235 -0
  107. package/test-theme/widgets/product-carousel.liquid +502 -0
  108. package/test-theme/widgets/product.liquid +45 -0
  109. package/test-theme/widgets/recently-viewed.liquid +26 -0
  110. package/test-theme/widgets/shared/product-grid.liquid +339 -0
  111. package/test-theme/widgets/simple-product.liquid +42 -0
  112. package/test-theme/widgets/single-product.liquid +610 -0
  113. package/test-theme/widgets/spacebar-carousel.liquid +663 -0
  114. package/test-theme/widgets/spacebar.liquid +279 -0
  115. package/test-theme/widgets/splash.liquid +378 -0
  116. 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
+