@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,299 @@
1
+ /**
2
+ * Delivery Zone Styles
3
+ */
4
+
5
+ /* Header Zone Display */
6
+ .header-delivery-zone {
7
+ display: flex;
8
+ align-items: center;
9
+ gap: var(--spacing-small);
10
+ }
11
+
12
+ .zone-selector {
13
+ display: flex;
14
+ align-items: center;
15
+ gap: var(--spacing-small);
16
+ padding: var(--spacing-small) var(--spacing-small);
17
+ background: none;
18
+ border: none;
19
+ border-bottom: 2px solid var(--color-error);
20
+ cursor: pointer;
21
+ transition: all 0.2s ease;
22
+ text-decoration: none;
23
+ color: inherit;
24
+ font-family: inherit;
25
+ font-size: inherit;
26
+ }
27
+
28
+ .zone-selector:hover {
29
+ opacity: 0.8;
30
+ border-bottom-color: var(--color-error);
31
+ }
32
+
33
+ .zone-selector svg {
34
+ width: var(--spacing-element);
35
+ height: var(--spacing-element);
36
+ color: var(--color-error);
37
+ }
38
+
39
+ .zone-name,
40
+ .zone-zipcode {
41
+ font-weight: var(--font-weight-bold);
42
+ color: var(--color-text);
43
+ }
44
+
45
+ .zone-zipcode {
46
+ opacity: 0.8;
47
+ }
48
+
49
+ /* Modal Styles (already in snippet, keeping for reference) */
50
+ .delivery-zone-modal {
51
+ position: fixed;
52
+ inset: 0;
53
+ z-index: 9999;
54
+ display: none;
55
+ }
56
+
57
+ .delivery-zone-modal.active {
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ }
62
+
63
+ .delivery-zone-overlay {
64
+ position: absolute;
65
+ inset: 0;
66
+ background: rgba(0, 0, 0, 0.5);
67
+ backdrop-filter: blur(4px);
68
+ }
69
+
70
+ .delivery-zone-panel {
71
+ position: relative;
72
+ background: var(--color-background);
73
+ border-radius: var(--border-radius-large);
74
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
75
+ width: 90%;
76
+ max-width: 500px;
77
+ max-height: 90vh;
78
+ overflow: hidden;
79
+ display: flex;
80
+ flex-direction: column;
81
+ }
82
+
83
+ .delivery-zone-header {
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: space-between;
87
+ padding: var(--spacing-component);
88
+ border-bottom: 1px solid var(--color-border);
89
+ }
90
+
91
+ .delivery-zone-title {
92
+ margin: 0;
93
+ font-size: 20px;
94
+ font-weight: var(--font-weight-bold);
95
+ color: var(--color-text);
96
+ }
97
+
98
+ .delivery-zone-close {
99
+ background: none;
100
+ border: none;
101
+ cursor: pointer;
102
+ color: #6b7280;
103
+ padding: 4px;
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ transition: color 0.2s ease;
108
+ }
109
+
110
+ .delivery-zone-close:hover {
111
+ color: var(--color-text);
112
+ }
113
+
114
+ .delivery-zone-content {
115
+ padding: var(--spacing-component);
116
+ overflow-y: auto;
117
+ }
118
+
119
+ .delivery-zone-form {
120
+ display: flex;
121
+ flex-direction: column;
122
+ gap: 20px;
123
+ }
124
+
125
+ .delivery-zone-input-group {
126
+ position: relative;
127
+ display: flex;
128
+ align-items: center;
129
+ }
130
+
131
+ .delivery-zone-input {
132
+ width: 100%;
133
+ padding: 14px 48px 14px 20px;
134
+ border: 2px solid #a78bfa;
135
+ border-radius: 12px;
136
+ font-size: 16px;
137
+ transition: all 0.2s ease;
138
+ background: var(--color-background);
139
+ }
140
+
141
+ .delivery-zone-input:focus {
142
+ outline: none;
143
+ border-color: var(--color-accent-dark);
144
+ box-shadow: 0 0 0 3px rgba(from var(--color-accent) r g b / 0.1);
145
+ }
146
+
147
+ .delivery-zone-search-icon {
148
+ position: absolute;
149
+ right: 16px;
150
+ color: var(--color-accent-light);
151
+ pointer-events: none;
152
+ }
153
+
154
+ .delivery-zone-autodetect {
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: 16px;
158
+ }
159
+
160
+ .delivery-zone-detect-btn {
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ gap: 8px;
165
+ width: 100%;
166
+ padding: 14px;
167
+ font-size: 16px;
168
+ font-weight: 600;
169
+ }
170
+
171
+ .delivery-zone-or-divider {
172
+ display: flex;
173
+ align-items: center;
174
+ gap: 12px;
175
+ text-transform: uppercase;
176
+ font-size: 12px;
177
+ font-weight: 600;
178
+ color: #6b7280;
179
+ text-align: center;
180
+ }
181
+
182
+ .delivery-zone-or-divider::before,
183
+ .delivery-zone-or-divider::after {
184
+ content: '';
185
+ flex: 1;
186
+ height: 1px;
187
+ background: #e5e7eb;
188
+ }
189
+
190
+ .delivery-zone-current {
191
+ padding: 16px;
192
+ background: #f3f4f6;
193
+ border-radius: 8px;
194
+ display: flex;
195
+ flex-direction: column;
196
+ gap: 4px;
197
+ }
198
+
199
+ .delivery-zone-current-label {
200
+ margin: 0;
201
+ font-size: 12px;
202
+ color: #6b7280;
203
+ text-transform: uppercase;
204
+ font-weight: 600;
205
+ }
206
+
207
+ .delivery-zone-current-value {
208
+ margin: 0;
209
+ font-size: 14px;
210
+ color: var(--color-text);
211
+ font-weight: 600;
212
+ display: flex;
213
+ align-items: center;
214
+ gap: 8px;
215
+ }
216
+
217
+ .delivery-zone-error {
218
+ padding: 12px 16px;
219
+ background: #fef2f2;
220
+ border: 1px solid #fecaca;
221
+ border-radius: 8px;
222
+ color: #991b1b;
223
+ font-size: 14px;
224
+ }
225
+
226
+ .delivery-zone-actions {
227
+ display: flex;
228
+ gap: 12px;
229
+ }
230
+
231
+ .delivery-zone-submit {
232
+ flex: 1;
233
+ padding: 14px;
234
+ font-size: 16px;
235
+ font-weight: 600;
236
+ }
237
+
238
+ .delivery-zone-loading {
239
+ position: absolute;
240
+ inset: 0;
241
+ background: rgba(255, 255, 255, 0.95);
242
+ display: flex;
243
+ flex-direction: column;
244
+ align-items: center;
245
+ justify-content: center;
246
+ gap: 16px;
247
+ }
248
+
249
+ .spinner {
250
+ width: 40px;
251
+ height: 40px;
252
+ border: 4px solid #e5e7eb;
253
+ border-top-color: #a78bfa;
254
+ border-radius: 50%;
255
+ animation: spin 0.8s linear infinite;
256
+ }
257
+
258
+ @keyframes spin {
259
+ to { transform: rotate(360deg); }
260
+ }
261
+
262
+ /* Mobile responsive */
263
+ @media (max-width: 640px) {
264
+ .delivery-zone-panel {
265
+ width: 100%;
266
+ max-width: 100%;
267
+ border-radius: 0;
268
+ height: 100%;
269
+ max-height: 100%;
270
+ }
271
+
272
+ .delivery-zone-header {
273
+ padding: 16px;
274
+ }
275
+
276
+ .delivery-zone-content {
277
+ padding: 16px;
278
+ }
279
+
280
+ .delivery-zone-title {
281
+ font-size: 18px;
282
+ }
283
+
284
+ .zone-selector {
285
+ padding: 6px 8px;
286
+ gap: 6px;
287
+ }
288
+
289
+ .zone-name,
290
+ .zone-zipcode {
291
+ font-size: 12px;
292
+ }
293
+
294
+ .zone-selector svg {
295
+ width: 14px;
296
+ height: 14px;
297
+ }
298
+ }
299
+
@@ -0,0 +1,396 @@
1
+ /**
2
+ * Delivery Zone Selection JavaScript
3
+ * Handles modal interactions, API calls, and zone selection
4
+ */
5
+
6
+ (function() {
7
+ 'use strict';
8
+
9
+ // DOM elements - will be set in init()
10
+ let modal;
11
+ let overlay;
12
+ let closeBtn;
13
+ let form;
14
+ let zipcodeInput;
15
+ let citySelect;
16
+ let searchResults;
17
+ let detectBtn;
18
+ let errorDiv;
19
+ let loadingDiv;
20
+ let loadingCitiesDiv;
21
+
22
+ // State
23
+ let searchTimeout;
24
+ let currentMode = 0; // 0=Zipcode, 1=City, 2=AutoDetect
25
+
26
+ // Initialize
27
+ function init() {
28
+ // Get DOM elements now that they should be available
29
+ modal = document.getElementById('delivery-zone-modal');
30
+ overlay = modal?.querySelector('[data-zone-overlay]');
31
+ closeBtn = modal?.querySelector('[data-zone-close]');
32
+ form = document.getElementById('delivery-zone-form');
33
+ zipcodeInput = document.getElementById('delivery-zone-zipcode');
34
+ citySelect = document.getElementById('delivery-zone-city');
35
+ searchResults = document.getElementById('delivery-zone-search-results');
36
+ detectBtn = document.getElementById('detect-location-btn');
37
+ errorDiv = document.getElementById('delivery-zone-error');
38
+ loadingDiv = document.getElementById('delivery-zone-loading');
39
+ loadingCitiesDiv = document.getElementById('delivery-zone-loading-cities');
40
+
41
+ if (!modal) {
42
+ return;
43
+ }
44
+
45
+ // Show modal on first visit if configured
46
+ const showModalValue = modal.dataset.showModal;
47
+ const showModal = showModalValue === 'true' || showModalValue === true;
48
+
49
+ if (showModal) {
50
+ setTimeout(() => {
51
+ openModal();
52
+ }, 500);
53
+ }
54
+
55
+ // Event listeners
56
+ if (closeBtn) closeBtn.addEventListener('click', closeModal);
57
+ if (overlay) overlay.addEventListener('click', closeModal);
58
+
59
+ // Form submission
60
+ if (form) {
61
+ form.addEventListener('submit', handleSubmit);
62
+ }
63
+
64
+ // Zipcode search with autocomplete
65
+ if (zipcodeInput) {
66
+ zipcodeInput.addEventListener('input', handleZipcodeSearch);
67
+ zipcodeInput.addEventListener('focus', handleZipcodeFocus);
68
+ }
69
+
70
+ // Geolocation for AutoDetect mode
71
+ if (detectBtn) {
72
+ detectBtn.addEventListener('click', handleGeolocation);
73
+ }
74
+
75
+ // City selector initialization
76
+ if (citySelect) {
77
+ loadCities();
78
+ }
79
+
80
+ // Header zone selector (if exists)
81
+ const headerZoneBtn = document.querySelector('[data-zone-toggle]');
82
+ if (headerZoneBtn) {
83
+ headerZoneBtn.addEventListener('click', openModal);
84
+ }
85
+
86
+ // Detect mode from DOM
87
+ if (zipcodeInput && searchResults) {
88
+ currentMode = 0; // Zipcode mode
89
+ } else if (citySelect) {
90
+ currentMode = 1; // City mode
91
+ } else if (detectBtn) {
92
+ currentMode = 2; // AutoDetect mode
93
+ }
94
+ }
95
+
96
+ // Modal functions
97
+ function openModal() {
98
+ if (modal) {
99
+ modal.classList.add('active');
100
+ document.body.style.overflow = 'hidden';
101
+
102
+ // Focus first input
103
+ setTimeout(() => {
104
+ if (zipcodeInput) {
105
+ zipcodeInput.focus();
106
+ } else if (citySelect) {
107
+ citySelect.focus();
108
+ }
109
+ }, 100);
110
+ }
111
+ }
112
+
113
+ function closeModal() {
114
+ if (modal) {
115
+ modal.classList.remove('active');
116
+ document.body.style.overflow = '';
117
+
118
+ // Clear form state
119
+ if (searchResults) {
120
+ searchResults.style.display = 'none';
121
+ }
122
+ if (errorDiv) {
123
+ errorDiv.style.display = 'none';
124
+ }
125
+ }
126
+ }
127
+
128
+ // Handle form submission
129
+ async function handleSubmit(e) {
130
+ e.preventDefault();
131
+ showLoading(true);
132
+ hideError();
133
+
134
+ let zoneId = null;
135
+ let zipcode = null;
136
+
137
+ try {
138
+ if (currentMode === 0) {
139
+ // Zipcode mode
140
+ zipcode = zipcodeInput.value.trim();
141
+ if (!zipcode) {
142
+ throw new Error('Please enter a valid zipcode');
143
+ }
144
+
145
+ // Get zone by zipcode
146
+ const response = await fetch(`/webstoreapi/delivery-zone/by-zipcode/${zipcode}`);
147
+ const data = await response.json();
148
+
149
+ if (!data.success || !data.data || data.data.length === 0) {
150
+ throw new Error('No delivery zones found for this zipcode');
151
+ }
152
+
153
+ zoneId = data.data[0].zoneId;
154
+ } else if (currentMode === 1) {
155
+ // City mode
156
+ zoneId = citySelect.value;
157
+ if (!zoneId) {
158
+ throw new Error('Please select a city');
159
+ }
160
+ } else if (currentMode === 2) {
161
+ // AutoDetect mode - should have been handled by geolocation
162
+ zipcode = zipcodeInput.value.trim();
163
+ if (!zipcode) {
164
+ throw new Error('Please detect location or enter a zipcode');
165
+ }
166
+
167
+ const response = await fetch(`/webstoreapi/delivery-zone/by-zipcode/${zipcode}`);
168
+ const data = await response.json();
169
+
170
+ if (!data.success || !data.data || data.data.length === 0) {
171
+ throw new Error('No delivery zones found for this zipcode');
172
+ }
173
+
174
+ zoneId = data.data[0].zoneId;
175
+ }
176
+
177
+ // Set zone cookie
178
+ const setResponse = await fetch('/webstoreapi/delivery-zone/select', {
179
+ method: 'POST',
180
+ headers: {
181
+ 'Content-Type': 'application/json'
182
+ },
183
+ body: JSON.stringify({ zoneId, zipcode })
184
+ });
185
+
186
+ const setData = await setResponse.json();
187
+
188
+ if (!setData.success) {
189
+ throw new Error(setData.error || 'Failed to set delivery zone');
190
+ }
191
+
192
+ // Success - reload page to show filtered products
193
+ window.location.reload();
194
+ } catch (error) {
195
+ console.error('Error setting delivery zone:', error);
196
+ showError(error.message);
197
+ showLoading(false);
198
+ }
199
+ }
200
+
201
+ // Handle zipcode search with autocomplete
202
+ function handleZipcodeSearch(e) {
203
+ const query = e.target.value.trim();
204
+
205
+ if (searchTimeout) {
206
+ clearTimeout(searchTimeout);
207
+ }
208
+
209
+ if (query.length < 2) {
210
+ if (searchResults) {
211
+ searchResults.style.display = 'none';
212
+ }
213
+ return;
214
+ }
215
+
216
+ // Debounce search
217
+ searchTimeout = setTimeout(() => {
218
+ searchZipcodes(query);
219
+ }, 300);
220
+ }
221
+
222
+ function handleZipcodeFocus() {
223
+ // Show recent results if input has value
224
+ if (zipcodeInput && zipcodeInput.value.trim().length >= 2) {
225
+ searchZipcodes(zipcodeInput.value.trim());
226
+ }
227
+ }
228
+
229
+ async function searchZipcodes(query) {
230
+ try {
231
+ const response = await fetch(`/webstoreapi/delivery-zone/search-zipcodes?q=${encodeURIComponent(query)}`);
232
+ const data = await response.json();
233
+
234
+ if (!searchResults) return;
235
+
236
+ if (!data.success || !data.data || data.data.length === 0) {
237
+ searchResults.innerHTML = `
238
+ <div class="delivery-zone-search-empty">
239
+ No locations found
240
+ </div>
241
+ `;
242
+ searchResults.style.display = 'block';
243
+ return;
244
+ }
245
+
246
+ // Render results
247
+ searchResults.innerHTML = data.data.map(result => `
248
+ <div class="delivery-zone-search-result" data-zipcode="${result.zipcode}">
249
+ <div class="delivery-zone-result-zipcode">${result.zipcode}</div>
250
+ ${result.displayName ? `<div class="delivery-zone-result-details">${result.displayName}</div>` : ''}
251
+ </div>
252
+ `).join('');
253
+
254
+ // Add click handlers
255
+ const resultItems = searchResults.querySelectorAll('.delivery-zone-search-result');
256
+ resultItems.forEach(item => {
257
+ item.addEventListener('click', () => {
258
+ const zipcode = item.dataset.zipcode;
259
+ zipcodeInput.value = zipcode;
260
+ searchResults.style.display = 'none';
261
+ form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
262
+ });
263
+ });
264
+
265
+ searchResults.style.display = 'block';
266
+ } catch (error) {
267
+ console.error('Error searching zipcodes:', error);
268
+ if (searchResults) {
269
+ searchResults.style.display = 'none';
270
+ }
271
+ }
272
+ }
273
+
274
+ // Handle geolocation
275
+ function handleGeolocation(e) {
276
+ e.preventDefault();
277
+
278
+ if (!navigator.geolocation) {
279
+ showError('Geolocation is not supported by your browser');
280
+ return;
281
+ }
282
+
283
+ detectBtn.disabled = true;
284
+ detectBtn.innerHTML = `
285
+ <div class="spinner-small"></div>
286
+ Detecting...
287
+ `;
288
+
289
+ navigator.geolocation.getCurrentPosition(
290
+ async (position) => {
291
+ try {
292
+ const { latitude, longitude } = position.coords;
293
+
294
+ // Reverse geocode to zipcode
295
+ // Note: You may need to implement or use a service for this
296
+ // For now, show error asking user to enter manually
297
+ showError('Please enter your zipcode manually. Geolocation to zipcode conversion coming soon.');
298
+ detectBtn.disabled = false;
299
+ detectBtn.innerHTML = `
300
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
301
+ <path d="M12 2v20M2 12h20"/>
302
+ </svg>
303
+ Detect My Location
304
+ `;
305
+ } catch (error) {
306
+ console.error('Error reverse geocoding:', error);
307
+ showError('Could not determine your location. Please enter zipcode manually.');
308
+ detectBtn.disabled = false;
309
+ detectBtn.innerHTML = `
310
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
311
+ <path d="M12 2v20M2 12h20"/>
312
+ </svg>
313
+ Detect My Location
314
+ `;
315
+ }
316
+ },
317
+ (error) => {
318
+ console.error('Geolocation error:', error);
319
+ showError('Could not detect your location. Please enter zipcode manually.');
320
+ detectBtn.disabled = false;
321
+ detectBtn.innerHTML = `
322
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
323
+ <path d="M12 2v20M2 12h20"/>
324
+ </svg>
325
+ Detect My Location
326
+ `;
327
+ }
328
+ );
329
+ }
330
+
331
+ // Load cities
332
+ async function loadCities() {
333
+ if (!citySelect || !loadingCitiesDiv) return;
334
+
335
+ try {
336
+ loadingCitiesDiv.classList.add('active');
337
+ const response = await fetch('/webstoreapi/delivery-zone/cities');
338
+ const data = await response.json();
339
+
340
+ if (!data.success || !data.data || data.data.length === 0) {
341
+ throw new Error('No cities available');
342
+ }
343
+
344
+ // Populate city select
345
+ citySelect.innerHTML = '<option value="">Select a city</option>' +
346
+ data.data.map(city => `
347
+ <option value="${city.zoneId}">
348
+ ${city.zoneName}
349
+ </option>
350
+ `).join('');
351
+
352
+ loadingCitiesDiv.classList.remove('active');
353
+ } catch (error) {
354
+ console.error('Error loading cities:', error);
355
+ if (citySelect) {
356
+ citySelect.innerHTML = '<option value="">Error loading cities</option>';
357
+ }
358
+ loadingCitiesDiv.classList.remove('active');
359
+ }
360
+ }
361
+
362
+ // Utility functions
363
+ function showLoading(show) {
364
+ if (loadingDiv) {
365
+ loadingDiv.style.display = show ? 'flex' : 'none';
366
+ }
367
+ }
368
+
369
+ function showError(message) {
370
+ if (errorDiv) {
371
+ errorDiv.textContent = message;
372
+ errorDiv.style.display = 'block';
373
+ }
374
+ }
375
+
376
+ function hideError() {
377
+ if (errorDiv) {
378
+ errorDiv.style.display = 'none';
379
+ }
380
+ }
381
+
382
+ // Close modal on ESC key
383
+ document.addEventListener('keydown', (e) => {
384
+ if (e.key === 'Escape' && modal?.classList.contains('active')) {
385
+ closeModal();
386
+ }
387
+ });
388
+
389
+ // Initialize on DOM ready
390
+ if (document.readyState === 'loading') {
391
+ document.addEventListener('DOMContentLoaded', init);
392
+ } else {
393
+ init();
394
+ }
395
+ })();
396
+
Binary file