@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,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
|