@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,1092 @@
|
|
|
1
|
+
{% layout 'layout/theme' %}
|
|
2
|
+
|
|
3
|
+
<section class="account-page">
|
|
4
|
+
<div class="container">
|
|
5
|
+
<div class="account-header">
|
|
6
|
+
<h1 class="account-title">My Addresses</h1>
|
|
7
|
+
<p class="account-subtitle">
|
|
8
|
+
Manage your saved shipping and billing addresses
|
|
9
|
+
</p>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="account-layout">
|
|
13
|
+
|
|
14
|
+
<!-- Sidebar -->
|
|
15
|
+
<aside class="account-sidebar">
|
|
16
|
+
{% render 'snippets/account-sidebar' %}
|
|
17
|
+
</aside>
|
|
18
|
+
|
|
19
|
+
<!-- Content -->
|
|
20
|
+
<div class="account-content">
|
|
21
|
+
|
|
22
|
+
<div class="account-section-header">
|
|
23
|
+
<h2 class="account-section-title">Saved Addresses</h2>
|
|
24
|
+
|
|
25
|
+
<button class="btn btn-primary btn-sm" id="add-address-btn">
|
|
26
|
+
+ Add Address
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="addresses-list">
|
|
31
|
+
{% if addresses and addresses.size > 0 %}
|
|
32
|
+
{% for address in addresses %}
|
|
33
|
+
<div class="address-card">
|
|
34
|
+
|
|
35
|
+
<div class="address-card-header">
|
|
36
|
+
<div>
|
|
37
|
+
<h4 class="address-name">{{ address.contactName }}</h4>
|
|
38
|
+
<span class="address-type-badge">
|
|
39
|
+
{{ address.addressType }}
|
|
40
|
+
</span>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
{% if address.isDefaultShipping or address.isDefaultBilling %}
|
|
44
|
+
<span class="default-badge">
|
|
45
|
+
{% if address.isDefaultShipping %}Default Shipping{% endif %}
|
|
46
|
+
{% if address.isDefaultBilling %}Default Billing{% endif %}
|
|
47
|
+
</span>
|
|
48
|
+
{% endif %}
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="address-card-body">
|
|
52
|
+
<p>{{ address.addressLine1 }}</p>
|
|
53
|
+
|
|
54
|
+
{% if address.addressLine2 %}
|
|
55
|
+
<p>{{ address.addressLine2 }}</p>
|
|
56
|
+
{% endif %}
|
|
57
|
+
|
|
58
|
+
<p>
|
|
59
|
+
{{ address.city }}
|
|
60
|
+
{% if address.districtName %}, {{ address.districtName }}{% endif %}
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
<p>{{ address.stateOrProvinceName }} – {{ address.zipCode }}</p>
|
|
64
|
+
<p>{{ address.countryName }}</p>
|
|
65
|
+
|
|
66
|
+
{% if address.phone %}
|
|
67
|
+
<p class="address-phone">📞 {{ address.phone }}</p>
|
|
68
|
+
{% endif %}
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="address-card-actions">
|
|
72
|
+
<button class="btn btn-outline btn-sm edit-address" data-address-id="{{ address.id }}">Edit</button>
|
|
73
|
+
<button class="btn btn-outline-danger btn-sm delete-address" data-address-id="{{ address.id }}">
|
|
74
|
+
<span class="btn-text">Delete</span>
|
|
75
|
+
<span class="btn-loading" style="display: none;">
|
|
76
|
+
<span class="btn-spinner"></span>
|
|
77
|
+
<span>Deleting...</span>
|
|
78
|
+
</span>
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
</div>
|
|
83
|
+
{% endfor %}
|
|
84
|
+
{% else %}
|
|
85
|
+
<div class="empty-state">
|
|
86
|
+
<div class="empty-icon">📍</div>
|
|
87
|
+
<h3>No Saved Addresses</h3>
|
|
88
|
+
<p>Add an address to make checkout faster.</p>
|
|
89
|
+
<button class="btn btn-primary" id="add-first-address-btn"> + Add Address</button>
|
|
90
|
+
</div>
|
|
91
|
+
{% endif %}
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</section>
|
|
98
|
+
|
|
99
|
+
<style>
|
|
100
|
+
:root {
|
|
101
|
+
--color-primary: #2563eb;
|
|
102
|
+
--color-primary-hover: #1d4ed8;
|
|
103
|
+
--color-primary-light: #dbeafe;
|
|
104
|
+
--color-success: #10b981;
|
|
105
|
+
--color-success-light: #d1fae5;
|
|
106
|
+
--color-danger: #ef4444;
|
|
107
|
+
--color-danger-hover: #dc2626;
|
|
108
|
+
--color-danger-light: #fee2e2;
|
|
109
|
+
--color-text: #1f2937;
|
|
110
|
+
--color-text-light: #6b7280;
|
|
111
|
+
--color-background: #f9fafb;
|
|
112
|
+
--color-card-bg: #ffffff;
|
|
113
|
+
--color-border: #e5e7eb;
|
|
114
|
+
--color-border-light: #f3f4f6;
|
|
115
|
+
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
116
|
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
117
|
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
118
|
+
--radius-md: 0.5rem;
|
|
119
|
+
--radius-lg: 0.75rem;
|
|
120
|
+
--spacing-xs: 0.5rem;
|
|
121
|
+
--spacing-sm: 1rem;
|
|
122
|
+
--spacing-md: 1.5rem;
|
|
123
|
+
--spacing-lg: 2rem;
|
|
124
|
+
--spacing-xl: 3rem;
|
|
125
|
+
}
|
|
126
|
+
/* Page */
|
|
127
|
+
.account-page {
|
|
128
|
+
padding: 3rem 1rem;
|
|
129
|
+
background: #f9fafb;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.container {
|
|
133
|
+
max-width: 1200px;
|
|
134
|
+
margin: 0 auto;
|
|
135
|
+
padding: 0 var(--spacing-sm);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Header */
|
|
139
|
+
.account-header {
|
|
140
|
+
text-align: center;
|
|
141
|
+
margin-bottom: 3rem;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.account-title {
|
|
145
|
+
font-size: 2.5rem;
|
|
146
|
+
font-weight: 700;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.account-subtitle {
|
|
150
|
+
color: #6b7280;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Layout */
|
|
154
|
+
.account-layout {
|
|
155
|
+
display: grid;
|
|
156
|
+
grid-template-columns: 280px 1fr;
|
|
157
|
+
gap: 2rem;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.account-sidebar,
|
|
161
|
+
.account-content {
|
|
162
|
+
background: #fff;
|
|
163
|
+
border-radius: 14px;
|
|
164
|
+
padding: 1.75rem;
|
|
165
|
+
border: 1px solid #e5e7eb;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Section header */
|
|
169
|
+
.account-section-header {
|
|
170
|
+
display: flex;
|
|
171
|
+
justify-content: space-between;
|
|
172
|
+
align-items: center;
|
|
173
|
+
margin-bottom: 2rem;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.account-section-title {
|
|
177
|
+
font-size: 1.6rem;
|
|
178
|
+
font-weight: 600;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Address Grid */
|
|
182
|
+
.addresses-list {
|
|
183
|
+
display: grid;
|
|
184
|
+
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
185
|
+
gap: 1.5rem;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Address Card */
|
|
189
|
+
.address-card {
|
|
190
|
+
background: #f9fafb;
|
|
191
|
+
border: 1px solid #e5e7eb;
|
|
192
|
+
border-radius: 14px;
|
|
193
|
+
padding: 1.5rem;
|
|
194
|
+
display: flex;
|
|
195
|
+
flex-direction: column;
|
|
196
|
+
transition: all .25s ease;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.address-card:hover {
|
|
200
|
+
background: #fff;
|
|
201
|
+
box-shadow: 0 10px 25px rgba(0,0,0,.06);
|
|
202
|
+
transform: translateY(-3px);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* Card Header */
|
|
206
|
+
.address-card-header {
|
|
207
|
+
display: flex;
|
|
208
|
+
justify-content: space-between;
|
|
209
|
+
margin-bottom: .75rem;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.address-name {
|
|
213
|
+
font-size: 1.5rem;
|
|
214
|
+
font-weight: 600;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Badges */
|
|
218
|
+
.address-type-badge {
|
|
219
|
+
font-size: 1.1rem;
|
|
220
|
+
padding: 5px;
|
|
221
|
+
border-radius: 5px;
|
|
222
|
+
background:black;
|
|
223
|
+
color:white;
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.default-badge {
|
|
228
|
+
background: #dcfce7;
|
|
229
|
+
color: #166534;
|
|
230
|
+
font-size: .7rem;
|
|
231
|
+
padding: .3rem .6rem;
|
|
232
|
+
border-radius: 999px;
|
|
233
|
+
font-weight: 600;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* Body */
|
|
237
|
+
.address-card-body p {
|
|
238
|
+
margin: .25rem 0;
|
|
239
|
+
color: #374151;
|
|
240
|
+
font-size: 1.5rem;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.address-phone {
|
|
244
|
+
margin-top: .5rem;
|
|
245
|
+
font-weight: 500;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* Actions */
|
|
249
|
+
.address-card-actions {
|
|
250
|
+
display: flex;
|
|
251
|
+
gap: 1.75rem;
|
|
252
|
+
justify-content: flex-end;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Buttons */
|
|
256
|
+
.btn {
|
|
257
|
+
padding: 10px;
|
|
258
|
+
border-radius: 5px;
|
|
259
|
+
font-size: 1.5rem;;
|
|
260
|
+
cursor: pointer;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.btn-primary {
|
|
264
|
+
|
|
265
|
+
border: none;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.btn-outline {
|
|
269
|
+
background: transparent;
|
|
270
|
+
border: 1px solid #d1d5db;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.btn-outline-danger {
|
|
274
|
+
background: transparent;
|
|
275
|
+
border: 1px solid #fecaca;
|
|
276
|
+
color: #dc2626;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* Empty State */
|
|
280
|
+
.empty-state {
|
|
281
|
+
grid-column: 1 / -1;
|
|
282
|
+
text-align: center;
|
|
283
|
+
padding: 4rem 2rem;
|
|
284
|
+
border: 2px dashed #e5e7eb;
|
|
285
|
+
border-radius: 14px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.empty-icon {
|
|
289
|
+
font-size: 3rem;
|
|
290
|
+
margin-bottom: 1rem;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* Mobile */
|
|
294
|
+
@media (max-width: 768px) {
|
|
295
|
+
.account-layout {
|
|
296
|
+
grid-template-columns: 1fr;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.account-section-header {
|
|
300
|
+
flex-direction: column;
|
|
301
|
+
align-items: flex-start;
|
|
302
|
+
gap: 1rem;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.addresses-list {
|
|
306
|
+
grid-template-columns: 1fr;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* Address Modal Styles */
|
|
311
|
+
.address-modal {
|
|
312
|
+
position: fixed;
|
|
313
|
+
top: 0;
|
|
314
|
+
left: 0;
|
|
315
|
+
width: 100%;
|
|
316
|
+
height: 100%;
|
|
317
|
+
z-index: 1050;
|
|
318
|
+
display: none;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.address-modal.show {
|
|
322
|
+
display: block;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.address-modal-overlay {
|
|
326
|
+
position: absolute;
|
|
327
|
+
top: 0;
|
|
328
|
+
left: 0;
|
|
329
|
+
width: 100%;
|
|
330
|
+
height: 100%;
|
|
331
|
+
background: rgba(0, 0, 0, 0.5);
|
|
332
|
+
cursor: pointer;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.address-modal-content {
|
|
336
|
+
position: absolute;
|
|
337
|
+
top: 50%;
|
|
338
|
+
left: 50%;
|
|
339
|
+
transform: translate(-50%, -50%);
|
|
340
|
+
background: white;
|
|
341
|
+
border-radius: 14px;
|
|
342
|
+
padding: 2rem;
|
|
343
|
+
max-width: 600px;
|
|
344
|
+
width: 90%;
|
|
345
|
+
max-height: 90vh;
|
|
346
|
+
overflow-y: auto;
|
|
347
|
+
box-shadow: 0 20px 25px rgba(0, 0, 0, 0.15);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.address-modal-header {
|
|
351
|
+
display: flex;
|
|
352
|
+
justify-content: space-between;
|
|
353
|
+
align-items: center;
|
|
354
|
+
margin-bottom: 1.5rem;
|
|
355
|
+
border-bottom: 1px solid #d1d5db;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.address-modal-close {
|
|
359
|
+
background: none;
|
|
360
|
+
border: none;
|
|
361
|
+
font-size: 2rem;
|
|
362
|
+
cursor: pointer;
|
|
363
|
+
color: #6b7280;
|
|
364
|
+
line-height: 1;
|
|
365
|
+
padding: 0;
|
|
366
|
+
width: 32px;
|
|
367
|
+
height: 32px;
|
|
368
|
+
display: flex;
|
|
369
|
+
align-items: center;
|
|
370
|
+
justify-content: center;
|
|
371
|
+
border-radius: 6px;
|
|
372
|
+
transition: background 0.2s;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.address-modal-close:hover {
|
|
376
|
+
background: #f3f4f6;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.address-form {
|
|
380
|
+
display: flex;
|
|
381
|
+
flex-direction: column;
|
|
382
|
+
gap: 1rem;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.form-group {
|
|
386
|
+
display: flex;
|
|
387
|
+
flex-direction: column;
|
|
388
|
+
gap: 0.5rem;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.form-label {
|
|
392
|
+
font-size: 1.7rem;
|
|
393
|
+
font-weight: 500;
|
|
394
|
+
color: #374151;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.form-input {
|
|
398
|
+
padding: 0.75rem;
|
|
399
|
+
border: 1px solid #d1d5db;
|
|
400
|
+
border-radius: 8px;
|
|
401
|
+
font-size: 1.5rem;
|
|
402
|
+
transition: border-color 0.2s;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* intl-tel-input styling for address book */
|
|
406
|
+
#address-phone {
|
|
407
|
+
padding-left: 3.5rem;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.iti {
|
|
411
|
+
width: 100%;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.iti__flag-container {
|
|
415
|
+
z-index: 1;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.iti__selected-flag {
|
|
419
|
+
padding: 0 0.75rem 0 0.5rem;
|
|
420
|
+
border-right: 1px solid #d1d5db;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.iti__selected-flag:hover {
|
|
424
|
+
background-color: #f9fafb;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.iti__country-list {
|
|
428
|
+
z-index: 1051;
|
|
429
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
430
|
+
border: 1px solid #d1d5db;
|
|
431
|
+
border-radius: 8px;
|
|
432
|
+
max-height: 200px;
|
|
433
|
+
overflow-y: auto;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.iti__country {
|
|
437
|
+
padding: 0.5rem 0.75rem;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.iti__country:hover,
|
|
441
|
+
.iti__country.iti__highlight {
|
|
442
|
+
background-color: #dbeafe;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.form-input:focus {
|
|
446
|
+
outline: none;
|
|
447
|
+
border-color: #2563eb;
|
|
448
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.form-row {
|
|
452
|
+
display: grid;
|
|
453
|
+
grid-template-columns: 1fr 1fr;
|
|
454
|
+
gap: 1rem;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.form-checkbox {
|
|
458
|
+
display: flex;
|
|
459
|
+
align-items: center;
|
|
460
|
+
gap: 0.5rem;
|
|
461
|
+
cursor: pointer;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.form-checkbox input[type="checkbox"] {
|
|
465
|
+
width: 18px;
|
|
466
|
+
height: 18px;
|
|
467
|
+
cursor: pointer;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.form-actions {
|
|
471
|
+
display: flex;
|
|
472
|
+
gap: 0.75rem;
|
|
473
|
+
justify-content: flex-end;
|
|
474
|
+
margin-top: 1rem;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* Button loading states */
|
|
478
|
+
.btn.loading {
|
|
479
|
+
opacity: 0.7;
|
|
480
|
+
cursor: not-allowed;
|
|
481
|
+
pointer-events: none;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.btn-loading {
|
|
485
|
+
display: flex;
|
|
486
|
+
align-items: center;
|
|
487
|
+
gap: 0.5rem;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.btn-spinner {
|
|
491
|
+
width: 14px;
|
|
492
|
+
height: 14px;
|
|
493
|
+
border: 2px solid currentColor;
|
|
494
|
+
border-top-color: transparent;
|
|
495
|
+
border-radius: 50%;
|
|
496
|
+
animation: spin 0.6s linear infinite;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
@keyframes spin {
|
|
500
|
+
to {
|
|
501
|
+
transform: rotate(360deg);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.btn-sm .btn-spinner {
|
|
506
|
+
width: 12px;
|
|
507
|
+
height: 12px;
|
|
508
|
+
border-width: 1.5px;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
@media (max-width: 768px) {
|
|
512
|
+
.form-row {
|
|
513
|
+
grid-template-columns: 1fr;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.address-modal-content {
|
|
517
|
+
width: 95%;
|
|
518
|
+
padding: 1.5rem;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
</style>
|
|
523
|
+
|
|
524
|
+
<!-- Address Form Modal -->
|
|
525
|
+
<div class="address-modal" id="address-modal">
|
|
526
|
+
<div class="address-modal-overlay" data-address-modal-close></div>
|
|
527
|
+
<div class="address-modal-content">
|
|
528
|
+
<div class="address-modal-header">
|
|
529
|
+
<h3 id="address-modal-title">Add New Address</h3>
|
|
530
|
+
<button class="address-modal-close" data-address-modal-close aria-label="Close">×</button>
|
|
531
|
+
</div>
|
|
532
|
+
<form class="address-form" id="address-form" action="/webstoreapi/addresses" method="post">
|
|
533
|
+
<input type="hidden" name="addressId" id="address-id">
|
|
534
|
+
|
|
535
|
+
<div class="form-group">
|
|
536
|
+
<label for="address-first-name" class="form-label">First Name</label>
|
|
537
|
+
<input type="text" id="address-first-name" name="firstName" class="form-input" required>
|
|
538
|
+
</div>
|
|
539
|
+
|
|
540
|
+
<div class="form-group">
|
|
541
|
+
<label for="address-last-name" class="form-label">Last Name</label>
|
|
542
|
+
<input type="text" id="address-last-name" name="lastName" class="form-input">
|
|
543
|
+
</div>
|
|
544
|
+
|
|
545
|
+
<div class="form-group">
|
|
546
|
+
<label for="address-address" class="form-label">Address Line1</label>
|
|
547
|
+
<input type="text" id="address-address" name="address" class="form-input" required>
|
|
548
|
+
</div>
|
|
549
|
+
|
|
550
|
+
<div class="form-group">
|
|
551
|
+
<label for="address-address2" class="form-label">Address Line 2</label>
|
|
552
|
+
<input type="text" id="address-address2" name="address2" class="form-input">
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<div class="form-group">
|
|
556
|
+
<label for="address-city" class="form-label">City</label>
|
|
557
|
+
<input type="text" id="address-city" name="city" class="form-input" required>
|
|
558
|
+
</div>
|
|
559
|
+
|
|
560
|
+
<div class="form-row">
|
|
561
|
+
<div class="form-group">
|
|
562
|
+
<label for="address-state" class="form-label">State/Province</label>
|
|
563
|
+
<select id="address-state" name="state" class="form-input" required>
|
|
564
|
+
<option value="">Select a state</option>
|
|
565
|
+
</select>
|
|
566
|
+
</div>
|
|
567
|
+
|
|
568
|
+
<div class="form-group">
|
|
569
|
+
<label for="address-zip" class="form-label">ZIP/Postal Code</label>
|
|
570
|
+
<input type="text" id="address-zip" name="zip" class="form-input" required>
|
|
571
|
+
</div>
|
|
572
|
+
</div>
|
|
573
|
+
|
|
574
|
+
<div class="form-group">
|
|
575
|
+
<label for="address-country" class="form-label">Country</label>
|
|
576
|
+
<select id="address-country" name="country" class="form-input" required>
|
|
577
|
+
<option value="">Select a country</option>
|
|
578
|
+
{% if countries and countries.size > 0 %}
|
|
579
|
+
{% for country in countries %}
|
|
580
|
+
<option value="{{ country.id }}" data-country-code2="{{ country.code2 | default: country.code }}">{{ country.name }}</option>
|
|
581
|
+
{% endfor %}
|
|
582
|
+
{% else %}
|
|
583
|
+
<option value="US">United States</option>
|
|
584
|
+
<option value="CA">Canada</option>
|
|
585
|
+
<option value="GB">United Kingdom</option>
|
|
586
|
+
<option value="AU">Australia</option>
|
|
587
|
+
<option value="IN">India</option>
|
|
588
|
+
{% endif %}
|
|
589
|
+
</select>
|
|
590
|
+
</div>
|
|
591
|
+
|
|
592
|
+
<div class="form-group">
|
|
593
|
+
<label for="address-phone" class="form-label">Phone</label>
|
|
594
|
+
<input type="tel" id="address-phone" name="phone" class="form-input">
|
|
595
|
+
</div>
|
|
596
|
+
|
|
597
|
+
<div class="form-group">
|
|
598
|
+
<label class="form-checkbox">
|
|
599
|
+
<input type="checkbox" name="isDefault" id="address-is-default">
|
|
600
|
+
<span>Set as default address</span>
|
|
601
|
+
</label>
|
|
602
|
+
</div>
|
|
603
|
+
|
|
604
|
+
<div class="form-actions">
|
|
605
|
+
<button type="button" class="btn btn-outline" data-address-modal-close>Cancel</button>
|
|
606
|
+
<button type="submit" class="btn btn-primary" id="save-address-btn">
|
|
607
|
+
<span class="btn-text">Save Address</span>
|
|
608
|
+
<span class="btn-loading" style="display: none;">
|
|
609
|
+
<span class="btn-spinner"></span>
|
|
610
|
+
<span>Saving...</span>
|
|
611
|
+
</span>
|
|
612
|
+
</button>
|
|
613
|
+
</div>
|
|
614
|
+
</form>
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
|
|
618
|
+
<script>
|
|
619
|
+
// Button loading utility function
|
|
620
|
+
function setButtonLoading(button, loading, loadingText = null) {
|
|
621
|
+
if (!button) return;
|
|
622
|
+
|
|
623
|
+
const btnText = button.querySelector('.btn-text');
|
|
624
|
+
const btnLoading = button.querySelector('.btn-loading');
|
|
625
|
+
|
|
626
|
+
if (loading) {
|
|
627
|
+
button.disabled = true;
|
|
628
|
+
button.classList.add('loading');
|
|
629
|
+
if (btnText) btnText.style.display = 'none';
|
|
630
|
+
if (btnLoading) {
|
|
631
|
+
btnLoading.style.display = 'flex';
|
|
632
|
+
if (loadingText && btnLoading.querySelector('span:last-child')) {
|
|
633
|
+
btnLoading.querySelector('span:last-child').textContent = loadingText;
|
|
634
|
+
}
|
|
635
|
+
} else if (loadingText) {
|
|
636
|
+
button.textContent = loadingText;
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
button.disabled = false;
|
|
640
|
+
button.classList.remove('loading');
|
|
641
|
+
if (btnText) btnText.style.display = 'inline';
|
|
642
|
+
if (btnLoading) btnLoading.style.display = 'none';
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
647
|
+
const modal = document.getElementById('address-modal');
|
|
648
|
+
const openButtons = document.querySelectorAll('#add-address-btn, #add-first-address-btn, .edit-address');
|
|
649
|
+
const closeButtons = document.querySelectorAll('[data-address-modal-close]');
|
|
650
|
+
const form = document.getElementById('address-form');
|
|
651
|
+
const addresses = {% if addresses %}{{ addresses | json }}{% else %}[]{% endif %};
|
|
652
|
+
const COUNTRIES_DATA = {% if countries %}{{ countries | json }}{% else %}[]{% endif %};
|
|
653
|
+
let addressPhoneIti = null;
|
|
654
|
+
|
|
655
|
+
// Helper function to get states for a country
|
|
656
|
+
function getStatesForCountry(countryIdentifier) {
|
|
657
|
+
if (!countryIdentifier) return null;
|
|
658
|
+
|
|
659
|
+
// Try to parse as number (country ID)
|
|
660
|
+
const countryId = parseInt(countryIdentifier, 10);
|
|
661
|
+
const isNumericId = !isNaN(countryId);
|
|
662
|
+
|
|
663
|
+
if (!Array.isArray(COUNTRIES_DATA) || COUNTRIES_DATA.length === 0) {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Find country by ID or code
|
|
668
|
+
const country = COUNTRIES_DATA.find(c => {
|
|
669
|
+
if (isNumericId) {
|
|
670
|
+
const cId = c.id || c.countryId;
|
|
671
|
+
if (cId === countryId || String(cId) === String(countryId)) {
|
|
672
|
+
return true;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const cCode2 = c.code2 || '';
|
|
677
|
+
const cCode = c.code || '';
|
|
678
|
+
const cCountryCode = c.countryCode || '';
|
|
679
|
+
|
|
680
|
+
return cCode2 === countryIdentifier ||
|
|
681
|
+
cCode === countryIdentifier ||
|
|
682
|
+
cCountryCode === countryIdentifier ||
|
|
683
|
+
String(c.id) === String(countryIdentifier);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
if (country) {
|
|
687
|
+
if (country.statesOrProvinces && Array.isArray(country.statesOrProvinces)) {
|
|
688
|
+
return country.statesOrProvinces;
|
|
689
|
+
}
|
|
690
|
+
if (country.states && Array.isArray(country.states)) {
|
|
691
|
+
return country.states;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Populate states dropdown based on selected country
|
|
699
|
+
function populateStates(countrySelect, stateSelect, selectedStateId = null, selectedStateName = null) {
|
|
700
|
+
if (!stateSelect || !countrySelect) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const countryIdentifier = countrySelect.value;
|
|
705
|
+
|
|
706
|
+
// Clear existing options
|
|
707
|
+
stateSelect.innerHTML = '<option value="">Select a state</option>';
|
|
708
|
+
|
|
709
|
+
if (!countryIdentifier) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Get states for the selected country
|
|
714
|
+
const countryStates = getStatesForCountry(countryIdentifier);
|
|
715
|
+
|
|
716
|
+
if (!countryStates || !Array.isArray(countryStates) || countryStates.length === 0) {
|
|
717
|
+
// If no states found, convert to text input
|
|
718
|
+
const formGroup = stateSelect.closest('.form-group');
|
|
719
|
+
if (formGroup) {
|
|
720
|
+
let textInput = document.getElementById('address-state-text');
|
|
721
|
+
if (!textInput) {
|
|
722
|
+
textInput = document.createElement('input');
|
|
723
|
+
textInput.type = 'text';
|
|
724
|
+
textInput.id = 'address-state-text';
|
|
725
|
+
textInput.name = 'state';
|
|
726
|
+
textInput.className = 'form-input';
|
|
727
|
+
textInput.required = true;
|
|
728
|
+
textInput.placeholder = 'Enter state/province';
|
|
729
|
+
formGroup.appendChild(textInput);
|
|
730
|
+
}
|
|
731
|
+
stateSelect.style.display = 'none';
|
|
732
|
+
stateSelect.removeAttribute('required');
|
|
733
|
+
textInput.style.display = 'block';
|
|
734
|
+
textInput.required = true;
|
|
735
|
+
if (selectedStateName) {
|
|
736
|
+
textInput.value = selectedStateName;
|
|
737
|
+
} else if (selectedStateId) {
|
|
738
|
+
textInput.value = selectedStateId;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Show select and hide text input if it exists
|
|
745
|
+
const textInput = document.getElementById('address-state-text');
|
|
746
|
+
if (textInput) {
|
|
747
|
+
textInput.style.display = 'none';
|
|
748
|
+
textInput.removeAttribute('required');
|
|
749
|
+
}
|
|
750
|
+
stateSelect.style.display = 'block';
|
|
751
|
+
stateSelect.required = true;
|
|
752
|
+
|
|
753
|
+
// Populate the dropdown
|
|
754
|
+
countryStates.forEach(state => {
|
|
755
|
+
const option = document.createElement('option');
|
|
756
|
+
const stateId = state.id || state.stateOrProvinceId || state.stateId;
|
|
757
|
+
const stateName = state.name || state.label || '';
|
|
758
|
+
|
|
759
|
+
// Use stateId as the value (required by API)
|
|
760
|
+
option.value = stateId ? String(stateId) : '';
|
|
761
|
+
option.textContent = stateName;
|
|
762
|
+
if (stateId) {
|
|
763
|
+
option.dataset.stateId = String(stateId);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Check if this should be selected
|
|
767
|
+
if (selectedStateId) {
|
|
768
|
+
const selectedStateStr = String(selectedStateId).toLowerCase();
|
|
769
|
+
const stateIdStr = stateId ? String(stateId).toLowerCase() : '';
|
|
770
|
+
|
|
771
|
+
if (stateIdStr && stateIdStr === selectedStateStr) {
|
|
772
|
+
option.selected = true;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Also check by name if provided
|
|
777
|
+
if (!option.selected && selectedStateName) {
|
|
778
|
+
const selectedStateNameStr = String(selectedStateName).toLowerCase();
|
|
779
|
+
const stateNameStr = String(stateName).toLowerCase();
|
|
780
|
+
|
|
781
|
+
if (stateNameStr === selectedStateNameStr || stateNameStr.includes(selectedStateNameStr)) {
|
|
782
|
+
option.selected = true;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
stateSelect.appendChild(option);
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Initialize intl-tel-input for address phone
|
|
791
|
+
function initializeAddressPhoneInput() {
|
|
792
|
+
const phoneInput = document.getElementById('address-phone');
|
|
793
|
+
if (phoneInput && typeof intlTelInput !== 'undefined') {
|
|
794
|
+
// Destroy existing instance if any
|
|
795
|
+
if (addressPhoneIti) {
|
|
796
|
+
addressPhoneIti.destroy();
|
|
797
|
+
addressPhoneIti = null;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Get country code from country select
|
|
801
|
+
const countrySelect = document.getElementById('address-country');
|
|
802
|
+
let initialCountry = 'auto';
|
|
803
|
+
|
|
804
|
+
if (countrySelect && countrySelect.value) {
|
|
805
|
+
const countryCode = countrySelect.options[countrySelect.selectedIndex]?.getAttribute('data-country-code2') ||
|
|
806
|
+
countrySelect.value.toLowerCase();
|
|
807
|
+
initialCountry = countryCode.toLowerCase();
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Initialize intl-tel-input
|
|
811
|
+
addressPhoneIti = intlTelInput(phoneInput, {
|
|
812
|
+
utilsScript: 'https://cdn.jsdelivr.net/npm/intl-tel-input@23.0.0/build/js/utils.js',
|
|
813
|
+
initialCountry: initialCountry,
|
|
814
|
+
preferredCountries: ['us', 'gb', 'ca', 'au', 'in'],
|
|
815
|
+
separateDialCode: true,
|
|
816
|
+
nationalMode: false
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
// Update country when address country changes
|
|
820
|
+
if (countrySelect) {
|
|
821
|
+
countrySelect.addEventListener('change', function() {
|
|
822
|
+
const countryCode = this.options[this.selectedIndex]?.getAttribute('data-country-code2') ||
|
|
823
|
+
this.value.toLowerCase();
|
|
824
|
+
if (addressPhoneIti && countryCode) {
|
|
825
|
+
addressPhoneIti.setCountry(countryCode.toLowerCase());
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Populate states when country changes
|
|
829
|
+
const stateSelect = document.getElementById('address-state');
|
|
830
|
+
if (stateSelect) {
|
|
831
|
+
populateStates(countrySelect, stateSelect);
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function openModal(addressId = null) {
|
|
839
|
+
const countrySelect = document.getElementById('address-country');
|
|
840
|
+
const stateSelect = document.getElementById('address-state');
|
|
841
|
+
|
|
842
|
+
if (addressId) {
|
|
843
|
+
// Load address data for editing
|
|
844
|
+
const address = addresses.find(a => a.id == addressId);
|
|
845
|
+
if (address) {
|
|
846
|
+
document.getElementById('address-modal-title').textContent = 'Edit Address';
|
|
847
|
+
document.getElementById('address-id').value = addressId;
|
|
848
|
+
document.getElementById('address-first-name').value = address.contactName || '';
|
|
849
|
+
document.getElementById('address-last-name').value = address.lastName || '';
|
|
850
|
+
document.getElementById('address-address').value = address.addressLine1 || '';
|
|
851
|
+
document.getElementById('address-address2').value = address.addressLine2 || '';
|
|
852
|
+
document.getElementById('address-city').value = address.city || '';
|
|
853
|
+
document.getElementById('address-zip').value = address.zipCode || '';
|
|
854
|
+
|
|
855
|
+
// Set country - try to find matching country by ID or code
|
|
856
|
+
let selectedCountryId = address.countryId || '';
|
|
857
|
+
if (!selectedCountryId && address.countryCode && Array.isArray(COUNTRIES_DATA)) {
|
|
858
|
+
// Find country by code if we don't have countryId
|
|
859
|
+
const country = COUNTRIES_DATA.find(c =>
|
|
860
|
+
c.code2 === address.countryCode ||
|
|
861
|
+
c.code === address.countryCode ||
|
|
862
|
+
c.countryCode === address.countryCode
|
|
863
|
+
);
|
|
864
|
+
if (country) {
|
|
865
|
+
selectedCountryId = country.id;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (countrySelect && selectedCountryId) {
|
|
870
|
+
countrySelect.value = String(selectedCountryId);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
document.getElementById('address-phone').value = address.phone || '';
|
|
874
|
+
document.getElementById('address-is-default').checked = address.isDefaultShipping || address.isDefaultBilling || false;
|
|
875
|
+
|
|
876
|
+
// Update form action for PUT request
|
|
877
|
+
form.action = `/webstoreapi/addresses/${addressId}`;
|
|
878
|
+
form.setAttribute('data-method', 'put');
|
|
879
|
+
|
|
880
|
+
// Populate states after country is set (with a small delay to ensure DOM is updated)
|
|
881
|
+
if (countrySelect && stateSelect && selectedCountryId) {
|
|
882
|
+
const stateOrProvinceId = address.stateOrProvinceId || null;
|
|
883
|
+
const stateOrProvinceName = address.stateOrProvinceName || null;
|
|
884
|
+
setTimeout(() => {
|
|
885
|
+
populateStates(countrySelect, stateSelect, stateOrProvinceId, stateOrProvinceName);
|
|
886
|
+
}, 50);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
} else {
|
|
890
|
+
document.getElementById('address-modal-title').textContent = 'Add New Address';
|
|
891
|
+
document.getElementById('address-id').value = '';
|
|
892
|
+
form.reset();
|
|
893
|
+
form.action = '/webstoreapi/addresses';
|
|
894
|
+
form.removeAttribute('data-method');
|
|
895
|
+
|
|
896
|
+
// Reset state dropdown
|
|
897
|
+
if (stateSelect) {
|
|
898
|
+
stateSelect.innerHTML = '<option value="">Select a state</option>';
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
modal.classList.add('show');
|
|
902
|
+
document.body.style.overflow = 'hidden';
|
|
903
|
+
|
|
904
|
+
// Initialize phone input after modal is shown
|
|
905
|
+
setTimeout(function() {
|
|
906
|
+
initializeAddressPhoneInput();
|
|
907
|
+
// Set phone number if editing (after initialization completes)
|
|
908
|
+
if (addressId) {
|
|
909
|
+
setTimeout(function() {
|
|
910
|
+
const address = addresses.find(a => a.id == addressId);
|
|
911
|
+
if (address && address.phone && addressPhoneIti) {
|
|
912
|
+
addressPhoneIti.setNumber(address.phone);
|
|
913
|
+
}
|
|
914
|
+
}, 150);
|
|
915
|
+
}
|
|
916
|
+
}, 100);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function closeModal() {
|
|
920
|
+
modal.classList.remove('show');
|
|
921
|
+
document.body.style.overflow = '';
|
|
922
|
+
form.reset();
|
|
923
|
+
// Destroy phone input instance when modal closes
|
|
924
|
+
if (addressPhoneIti) {
|
|
925
|
+
addressPhoneIti.destroy();
|
|
926
|
+
addressPhoneIti = null;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
openButtons.forEach(btn => {
|
|
931
|
+
btn.addEventListener('click', function() {
|
|
932
|
+
const addressId = this.dataset.addressId || null;
|
|
933
|
+
openModal(addressId);
|
|
934
|
+
});
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
closeButtons.forEach(btn => {
|
|
938
|
+
btn.addEventListener('click', closeModal);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
// Handle form submission
|
|
942
|
+
form.addEventListener('submit', async function(e) {
|
|
943
|
+
e.preventDefault();
|
|
944
|
+
|
|
945
|
+
const submitBtn = document.getElementById('save-address-btn');
|
|
946
|
+
setButtonLoading(submitBtn, true, 'Saving...');
|
|
947
|
+
|
|
948
|
+
const formData = new FormData(form);
|
|
949
|
+
const addressId = formData.get('addressId');
|
|
950
|
+
const method = form.getAttribute('data-method')?.toUpperCase() || 'POST';
|
|
951
|
+
const url = method === 'PUT' ? `/webstoreapi/addresses/${addressId}` : '/webstoreapi/addresses';
|
|
952
|
+
|
|
953
|
+
// Get phone number from intl-tel-input if available
|
|
954
|
+
let phoneNumber = formData.get('phone');
|
|
955
|
+
if (addressPhoneIti) {
|
|
956
|
+
const fullPhoneNumber = addressPhoneIti.getNumber();
|
|
957
|
+
if (fullPhoneNumber) {
|
|
958
|
+
// Remove leading + sign
|
|
959
|
+
phoneNumber = fullPhoneNumber.replace(/^\+/, '');
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// Derive country/state identifiers and names for API
|
|
964
|
+
const countrySelectEl = document.getElementById('address-country');
|
|
965
|
+
const stateSelectEl = document.getElementById('address-state');
|
|
966
|
+
const stateTextInput = document.getElementById('address-state-text');
|
|
967
|
+
|
|
968
|
+
let countryCode = formData.get('country') || '';
|
|
969
|
+
let countryId = null;
|
|
970
|
+
let stateOrProvinceId = null;
|
|
971
|
+
let stateOrProvinceName = null;
|
|
972
|
+
|
|
973
|
+
if (countrySelectEl && countrySelectEl.value) {
|
|
974
|
+
const selectedOption = countrySelectEl.options[countrySelectEl.selectedIndex];
|
|
975
|
+
const parsedCountryId = parseInt(countrySelectEl.value, 10);
|
|
976
|
+
if (!Number.isNaN(parsedCountryId)) {
|
|
977
|
+
countryId = parsedCountryId;
|
|
978
|
+
}
|
|
979
|
+
const optionCode = selectedOption?.getAttribute('data-country-code2');
|
|
980
|
+
if (optionCode) {
|
|
981
|
+
countryCode = optionCode;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
if (stateSelectEl && stateSelectEl.style.display !== 'none') {
|
|
986
|
+
const stateValue = stateSelectEl.value;
|
|
987
|
+
if (stateValue) {
|
|
988
|
+
const parsedStateId = parseInt(stateValue, 10);
|
|
989
|
+
if (!Number.isNaN(parsedStateId)) {
|
|
990
|
+
stateOrProvinceId = parsedStateId;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
const selectedStateOption = stateSelectEl.options[stateSelectEl.selectedIndex];
|
|
994
|
+
if (selectedStateOption) {
|
|
995
|
+
stateOrProvinceName = selectedStateOption.textContent.trim();
|
|
996
|
+
}
|
|
997
|
+
} else if (stateTextInput && stateTextInput.style.display !== 'none') {
|
|
998
|
+
stateOrProvinceName = stateTextInput.value;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const data = {
|
|
1002
|
+
firstName: formData.get('firstName'),
|
|
1003
|
+
lastName: formData.get('lastName'),
|
|
1004
|
+
addressLine1: formData.get('address'),
|
|
1005
|
+
addressLine2: formData.get('address2'),
|
|
1006
|
+
city: formData.get('city'),
|
|
1007
|
+
stateOrProvinceId: stateOrProvinceId,
|
|
1008
|
+
stateOrProvinceName: stateOrProvinceName,
|
|
1009
|
+
zipCode: formData.get('zip'),
|
|
1010
|
+
countryId: countryId,
|
|
1011
|
+
countryCode: countryCode,
|
|
1012
|
+
phone: phoneNumber,
|
|
1013
|
+
isDefaultShipping: formData.get('isDefault') === 'on',
|
|
1014
|
+
isDefaultBilling: formData.get('isDefault') === 'on'
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
try {
|
|
1018
|
+
const response = await fetch(url, {
|
|
1019
|
+
method: method,
|
|
1020
|
+
headers: {
|
|
1021
|
+
'Content-Type': 'application/json',
|
|
1022
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
1023
|
+
},
|
|
1024
|
+
body: JSON.stringify(data)
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
if (response.ok) {
|
|
1028
|
+
const responseData = await response.json();
|
|
1029
|
+
const savedAddressId = responseData.data?.id || addressId;
|
|
1030
|
+
|
|
1031
|
+
// Handle setting default address if checkbox is checked
|
|
1032
|
+
const isDefault = formData.get('isDefault') === 'on';
|
|
1033
|
+
if (isDefault && savedAddressId) {
|
|
1034
|
+
try {
|
|
1035
|
+
// Set as default shipping address
|
|
1036
|
+
await fetch(`/webstoreapi/addresses/${savedAddressId}/set-default?addressType=Shipping`, {
|
|
1037
|
+
method: 'PUT',
|
|
1038
|
+
headers: {
|
|
1039
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
} catch (defaultError) {
|
|
1043
|
+
console.warn('Failed to set default address:', defaultError);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// Reload page to show updated addresses
|
|
1048
|
+
window.location.reload();
|
|
1049
|
+
} else {
|
|
1050
|
+
const error = await response.json();
|
|
1051
|
+
setButtonLoading(submitBtn, false);
|
|
1052
|
+
alert('Error saving address: ' + (error.error || error.message || 'Unknown error'));
|
|
1053
|
+
}
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
console.error('Error saving address:', error);
|
|
1056
|
+
setButtonLoading(submitBtn, false);
|
|
1057
|
+
alert('Error saving address. Please try again.');
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
// Handle delete
|
|
1062
|
+
document.querySelectorAll('.delete-address').forEach(btn => {
|
|
1063
|
+
btn.addEventListener('click', async function() {
|
|
1064
|
+
if (confirm('Are you sure you want to delete this address?')) {
|
|
1065
|
+
const addressId = this.dataset.addressId;
|
|
1066
|
+
setButtonLoading(this, true, 'Deleting...');
|
|
1067
|
+
|
|
1068
|
+
try {
|
|
1069
|
+
const response = await fetch(`/webstoreapi/addresses/${addressId}`, {
|
|
1070
|
+
method: 'DELETE',
|
|
1071
|
+
headers: {
|
|
1072
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
if (response.ok) {
|
|
1077
|
+
window.location.reload();
|
|
1078
|
+
} else {
|
|
1079
|
+
const error = await response.json();
|
|
1080
|
+
setButtonLoading(this, false);
|
|
1081
|
+
alert('Error deleting address: ' + (error.error || error.message || 'Unknown error'));
|
|
1082
|
+
}
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
console.error('Error deleting address:', error);
|
|
1085
|
+
setButtonLoading(this, false);
|
|
1086
|
+
alert('Error deleting address. Please try again.');
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
});
|
|
1091
|
+
});
|
|
1092
|
+
</script>
|