arky-sdk 0.9.13 → 0.9.17
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 +16 -12
- package/dist/admin-6xpHuyKc.d.cts +1544 -0
- package/dist/admin-DVFAgnHm.d.ts +1544 -0
- package/dist/admin.cjs +982 -425
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +3 -3
- package/dist/admin.d.ts +3 -3
- package/dist/admin.js +982 -425
- package/dist/admin.js.map +1 -1
- package/dist/{api-DvsFdOaF.d.cts → api-DJrUdQ1C.d.cts} +1425 -657
- package/dist/{api-DvsFdOaF.d.ts → api-DJrUdQ1C.d.ts} +1425 -657
- package/dist/index.cjs +1317 -539
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1317 -539
- package/dist/index.js.map +1 -1
- package/dist/{index-CQd9b_7n.d.ts → inventory-DOwNF3D-.d.cts} +6 -4
- package/dist/{index-BC06yiuv.d.cts → inventory-GpWTZ2oe.d.ts} +6 -4
- package/dist/storefront.cjs +1210 -658
- package/dist/storefront.cjs.map +1 -1
- package/dist/storefront.d.cts +88 -285
- package/dist/storefront.d.ts +88 -285
- package/dist/storefront.js +1207 -659
- package/dist/storefront.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.cjs +198 -16
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +18 -2
- package/dist/utils.d.ts +18 -2
- package/dist/utils.js +191 -17
- package/dist/utils.js.map +1 -1
- package/package.json +4 -5
- package/dist/admin-Q9MBFwCb.d.cts +0 -1496
- package/dist/admin-ZLXD4_en.d.ts +0 -1496
- package/scripts/contract-admin.mjs +0 -120
- package/scripts/contract-storefront.mjs +0 -296
package/dist/storefront.cjs
CHANGED
|
@@ -3,21 +3,25 @@
|
|
|
3
3
|
var nanostores = require('nanostores');
|
|
4
4
|
|
|
5
5
|
// src/utils/orderItems.ts
|
|
6
|
-
function
|
|
6
|
+
function sanitizePublicCheckoutItems(items) {
|
|
7
7
|
return items.map((item) => {
|
|
8
|
-
if (
|
|
9
|
-
return
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
if (item.type === "product") {
|
|
9
|
+
return {
|
|
10
|
+
type: "product",
|
|
11
|
+
...item.id ? { id: item.id } : {},
|
|
12
|
+
product_id: item.product_id,
|
|
13
|
+
variant_id: item.variant_id,
|
|
14
|
+
quantity: item.quantity
|
|
15
|
+
};
|
|
13
16
|
}
|
|
14
|
-
return {
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
return {
|
|
18
|
+
type: "service",
|
|
19
|
+
...item.id ? { id: item.id } : {},
|
|
20
|
+
service_id: item.service_id,
|
|
21
|
+
provider_id: item.provider_id,
|
|
22
|
+
slots: item.slots,
|
|
23
|
+
...item.forms ? { forms: item.forms } : {}
|
|
24
|
+
};
|
|
21
25
|
});
|
|
22
26
|
}
|
|
23
27
|
|
|
@@ -42,10 +46,9 @@ var createActionApi = (apiConfig) => ({
|
|
|
42
46
|
COMMON_ACTION_KEYS,
|
|
43
47
|
async track(params) {
|
|
44
48
|
try {
|
|
45
|
-
const key = "key" in params && params.key ? params.key : params.type;
|
|
46
49
|
await apiConfig.httpClient.post(
|
|
47
50
|
`/v1/storefront/${apiConfig.storeId}/actions/track`,
|
|
48
|
-
{ key, payload: params.payload }
|
|
51
|
+
{ key: params.key, payload: params.payload }
|
|
49
52
|
);
|
|
50
53
|
} catch {
|
|
51
54
|
}
|
|
@@ -53,6 +56,47 @@ var createActionApi = (apiConfig) => ({
|
|
|
53
56
|
});
|
|
54
57
|
var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
55
58
|
const base = (storeId = apiConfig.storeId) => `/v1/storefront/${storeId}`;
|
|
59
|
+
const pendingVerifications = /* @__PURE__ */ new Map();
|
|
60
|
+
function persistIdentification(result) {
|
|
61
|
+
const issued = result.token;
|
|
62
|
+
if (issued?.token) {
|
|
63
|
+
updateContactSession(() => ({
|
|
64
|
+
access_token: issued.token,
|
|
65
|
+
contact: result.contact,
|
|
66
|
+
store: result.store,
|
|
67
|
+
market: result.market
|
|
68
|
+
}));
|
|
69
|
+
} else {
|
|
70
|
+
if (result.verification_challenge) {
|
|
71
|
+
pendingVerifications.set(result.verification_challenge.challenge_id, {
|
|
72
|
+
store: result.store,
|
|
73
|
+
market: result.market
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
updateContactSession(
|
|
77
|
+
(current) => current ? {
|
|
78
|
+
...current,
|
|
79
|
+
contact: result.contact,
|
|
80
|
+
store: result.store,
|
|
81
|
+
market: result.market
|
|
82
|
+
} : null
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
async function submitIdentification(path, params, options) {
|
|
88
|
+
const store_id = apiConfig.storeId;
|
|
89
|
+
const result = await apiConfig.httpClient.post(
|
|
90
|
+
`${base(store_id)}/account/${path}`,
|
|
91
|
+
{
|
|
92
|
+
store_id,
|
|
93
|
+
market: params?.market || apiConfig.market || null,
|
|
94
|
+
email: params?.email
|
|
95
|
+
},
|
|
96
|
+
options
|
|
97
|
+
);
|
|
98
|
+
return persistIdentification(result);
|
|
99
|
+
}
|
|
56
100
|
return {
|
|
57
101
|
store: {
|
|
58
102
|
getStore(options) {
|
|
@@ -60,7 +104,10 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
60
104
|
},
|
|
61
105
|
location: {
|
|
62
106
|
getCountries(options) {
|
|
63
|
-
return apiConfig.httpClient.get(
|
|
107
|
+
return apiConfig.httpClient.get(
|
|
108
|
+
`/v1/platform/countries`,
|
|
109
|
+
options
|
|
110
|
+
);
|
|
64
111
|
},
|
|
65
112
|
getCountry(countryCode, options) {
|
|
66
113
|
return apiConfig.httpClient.get(
|
|
@@ -69,18 +116,30 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
69
116
|
);
|
|
70
117
|
},
|
|
71
118
|
list(options) {
|
|
72
|
-
return apiConfig.httpClient.get(
|
|
119
|
+
return apiConfig.httpClient.get(
|
|
120
|
+
`${base()}/locations`,
|
|
121
|
+
options
|
|
122
|
+
);
|
|
73
123
|
},
|
|
74
124
|
get(id, options) {
|
|
75
|
-
return apiConfig.httpClient.get(
|
|
125
|
+
return apiConfig.httpClient.get(
|
|
126
|
+
`${base()}/locations/${id}`,
|
|
127
|
+
options
|
|
128
|
+
);
|
|
76
129
|
}
|
|
77
130
|
},
|
|
78
131
|
market: {
|
|
79
132
|
list(options) {
|
|
80
|
-
return apiConfig.httpClient.get(
|
|
133
|
+
return apiConfig.httpClient.get(
|
|
134
|
+
`${base()}/markets`,
|
|
135
|
+
options
|
|
136
|
+
);
|
|
81
137
|
},
|
|
82
138
|
get(id, options) {
|
|
83
|
-
return apiConfig.httpClient.get(
|
|
139
|
+
return apiConfig.httpClient.get(
|
|
140
|
+
`${base()}/markets/${id}`,
|
|
141
|
+
options
|
|
142
|
+
);
|
|
84
143
|
}
|
|
85
144
|
}
|
|
86
145
|
},
|
|
@@ -88,11 +147,9 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
88
147
|
collection: {
|
|
89
148
|
get(params, options) {
|
|
90
149
|
const store_id = params.store_id || apiConfig.storeId;
|
|
91
|
-
|
|
92
|
-
throw new Error("GetCollectionParams requires id");
|
|
93
|
-
}
|
|
150
|
+
const identifier = params.id !== void 0 ? params.id : `${store_id}:${params.key}`;
|
|
94
151
|
return apiConfig.httpClient.get(
|
|
95
|
-
`${base(store_id)}/collections/${
|
|
152
|
+
`${base(store_id)}/collections/${identifier}`,
|
|
96
153
|
options
|
|
97
154
|
);
|
|
98
155
|
}
|
|
@@ -110,10 +167,13 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
110
167
|
},
|
|
111
168
|
find(params, options) {
|
|
112
169
|
const { store_id, ...queryParams } = params;
|
|
113
|
-
return apiConfig.httpClient.get(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
170
|
+
return apiConfig.httpClient.get(
|
|
171
|
+
`${base(store_id)}/entries`,
|
|
172
|
+
{
|
|
173
|
+
...options,
|
|
174
|
+
params: queryParams
|
|
175
|
+
}
|
|
176
|
+
);
|
|
117
177
|
}
|
|
118
178
|
},
|
|
119
179
|
form: {
|
|
@@ -189,10 +249,13 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
189
249
|
},
|
|
190
250
|
find(params, options) {
|
|
191
251
|
const { store_id, ...queryParams } = params;
|
|
192
|
-
return apiConfig.httpClient.get(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
252
|
+
return apiConfig.httpClient.get(
|
|
253
|
+
`${base(store_id)}/products`,
|
|
254
|
+
{
|
|
255
|
+
...options,
|
|
256
|
+
params: queryParams
|
|
257
|
+
}
|
|
258
|
+
);
|
|
196
259
|
}
|
|
197
260
|
},
|
|
198
261
|
cart: {
|
|
@@ -201,16 +264,23 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
201
264
|
const { store_id: _store_id, ...payload } = params;
|
|
202
265
|
return apiConfig.httpClient.post(
|
|
203
266
|
`${base(store_id)}/carts/current`,
|
|
204
|
-
{
|
|
267
|
+
{
|
|
268
|
+
...payload,
|
|
269
|
+
store_id,
|
|
270
|
+
market: payload.market || apiConfig.market
|
|
271
|
+
},
|
|
205
272
|
options
|
|
206
273
|
);
|
|
207
274
|
},
|
|
208
275
|
get(params, options) {
|
|
209
276
|
const store_id = params.store_id || apiConfig.storeId;
|
|
210
|
-
return apiConfig.httpClient.get(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
277
|
+
return apiConfig.httpClient.get(
|
|
278
|
+
`${base(store_id)}/carts/${params.id}`,
|
|
279
|
+
{
|
|
280
|
+
...options,
|
|
281
|
+
params: params.token ? { token: params.token } : options?.params
|
|
282
|
+
}
|
|
283
|
+
);
|
|
214
284
|
},
|
|
215
285
|
update(params, options) {
|
|
216
286
|
const { store_id, items, ...payload } = params;
|
|
@@ -220,7 +290,7 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
220
290
|
{
|
|
221
291
|
...payload,
|
|
222
292
|
store_id: target,
|
|
223
|
-
...items ? { items:
|
|
293
|
+
...items ? { items: sanitizePublicCheckoutItems(items) } : {}
|
|
224
294
|
},
|
|
225
295
|
options
|
|
226
296
|
);
|
|
@@ -233,7 +303,7 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
233
303
|
{
|
|
234
304
|
...payload,
|
|
235
305
|
store_id: target,
|
|
236
|
-
item:
|
|
306
|
+
item: sanitizePublicCheckoutItems([item])[0]
|
|
237
307
|
},
|
|
238
308
|
options
|
|
239
309
|
);
|
|
@@ -288,18 +358,35 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
288
358
|
},
|
|
289
359
|
find(params, options) {
|
|
290
360
|
const { store_id, ...queryParams } = params;
|
|
291
|
-
return apiConfig.httpClient.get(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
361
|
+
return apiConfig.httpClient.get(
|
|
362
|
+
`${base(store_id)}/orders`,
|
|
363
|
+
{
|
|
364
|
+
...options,
|
|
365
|
+
params: queryParams
|
|
366
|
+
}
|
|
367
|
+
);
|
|
295
368
|
},
|
|
296
369
|
downloadDigitalAccess(params, options) {
|
|
297
370
|
const store_id = params.store_id || apiConfig.storeId;
|
|
298
371
|
return apiConfig.httpClient.post(
|
|
299
|
-
`${base(store_id)}/orders/${params.
|
|
372
|
+
`${base(store_id)}/orders/${params.order_id}/digital-access/${params.grant_id}/download`,
|
|
300
373
|
{},
|
|
301
374
|
options
|
|
302
375
|
);
|
|
376
|
+
},
|
|
377
|
+
findDigitalAccess(params, options) {
|
|
378
|
+
const { order_id, store_id, ...queryParams } = params;
|
|
379
|
+
return apiConfig.httpClient.get(
|
|
380
|
+
`${base(store_id)}/orders/${order_id}/digital-access`,
|
|
381
|
+
{ ...options, params: queryParams }
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
getDigitalAccess(params, options) {
|
|
385
|
+
const store_id = params.store_id || apiConfig.storeId;
|
|
386
|
+
return apiConfig.httpClient.get(
|
|
387
|
+
`${base(store_id)}/orders/${params.order_id}/digital-access/${params.grant_id}`,
|
|
388
|
+
options
|
|
389
|
+
);
|
|
303
390
|
}
|
|
304
391
|
},
|
|
305
392
|
service: {
|
|
@@ -320,17 +407,23 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
320
407
|
},
|
|
321
408
|
find(params, options) {
|
|
322
409
|
const { store_id, ...queryParams } = params;
|
|
323
|
-
return apiConfig.httpClient.get(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
410
|
+
return apiConfig.httpClient.get(
|
|
411
|
+
`${base(store_id)}/services`,
|
|
412
|
+
{
|
|
413
|
+
...options,
|
|
414
|
+
params: queryParams
|
|
415
|
+
}
|
|
416
|
+
);
|
|
327
417
|
},
|
|
328
418
|
findProviders(params, options) {
|
|
329
419
|
const { store_id, ...queryParams } = params;
|
|
330
|
-
return apiConfig.httpClient.get(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
420
|
+
return apiConfig.httpClient.get(
|
|
421
|
+
`${base(store_id)}/service-providers`,
|
|
422
|
+
{
|
|
423
|
+
...options,
|
|
424
|
+
params: queryParams
|
|
425
|
+
}
|
|
426
|
+
);
|
|
334
427
|
},
|
|
335
428
|
getAvailability(params, options) {
|
|
336
429
|
const { store_id, ...queryParams } = params;
|
|
@@ -359,48 +452,47 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
359
452
|
},
|
|
360
453
|
find(params, options) {
|
|
361
454
|
const { store_id, ...queryParams } = params;
|
|
362
|
-
return apiConfig.httpClient.get(
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
455
|
+
return apiConfig.httpClient.get(
|
|
456
|
+
`${base(store_id)}/providers`,
|
|
457
|
+
{
|
|
458
|
+
...options,
|
|
459
|
+
params: queryParams
|
|
460
|
+
}
|
|
461
|
+
);
|
|
366
462
|
}
|
|
367
463
|
}
|
|
368
464
|
},
|
|
369
465
|
crm: {
|
|
370
466
|
contact: {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
store_id,
|
|
377
|
-
market: params?.market || apiConfig.market || null,
|
|
378
|
-
email: params?.email,
|
|
379
|
-
verify: params?.verify ?? false
|
|
380
|
-
},
|
|
381
|
-
options
|
|
382
|
-
);
|
|
383
|
-
if (result?.token?.token) {
|
|
384
|
-
updateContactSession(() => ({
|
|
385
|
-
access_token: result.token.token,
|
|
386
|
-
contact: result.contact,
|
|
387
|
-
store: result.store,
|
|
388
|
-
market: result.market
|
|
389
|
-
}));
|
|
390
|
-
}
|
|
391
|
-
return result;
|
|
467
|
+
identify(params, options) {
|
|
468
|
+
return submitIdentification("identify", params, options);
|
|
469
|
+
},
|
|
470
|
+
requestCode(params, options) {
|
|
471
|
+
return submitIdentification("code", params, options);
|
|
392
472
|
},
|
|
393
473
|
async verify(params, options) {
|
|
394
474
|
const store_id = apiConfig.storeId;
|
|
395
475
|
const result = await apiConfig.httpClient.post(
|
|
396
476
|
`${base(store_id)}/account/verify`,
|
|
397
|
-
{ store_id, code: params.code },
|
|
477
|
+
{ store_id, challenge_id: params.challenge_id, code: params.code },
|
|
398
478
|
options
|
|
399
479
|
);
|
|
400
|
-
if (result?.token) {
|
|
480
|
+
if (result?.token?.token) {
|
|
481
|
+
const pending = pendingVerifications.get(params.challenge_id);
|
|
482
|
+
const identifiedStore = pending?.store || await apiConfig.httpClient.get(base(store_id), options);
|
|
401
483
|
updateContactSession(
|
|
402
|
-
(prev) => prev ? {
|
|
484
|
+
(prev) => prev ? {
|
|
485
|
+
...prev,
|
|
486
|
+
access_token: result.token.token,
|
|
487
|
+
contact: result.contact
|
|
488
|
+
} : {
|
|
489
|
+
access_token: result.token.token,
|
|
490
|
+
contact: result.contact,
|
|
491
|
+
store: identifiedStore,
|
|
492
|
+
market: pending?.market || null
|
|
493
|
+
}
|
|
403
494
|
);
|
|
495
|
+
pendingVerifications.delete(params.challenge_id);
|
|
404
496
|
}
|
|
405
497
|
return result;
|
|
406
498
|
},
|
|
@@ -417,7 +509,10 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
417
509
|
}
|
|
418
510
|
},
|
|
419
511
|
getMe(options) {
|
|
420
|
-
return apiConfig.httpClient.get(
|
|
512
|
+
return apiConfig.httpClient.get(
|
|
513
|
+
`${base()}/account/me`,
|
|
514
|
+
options
|
|
515
|
+
);
|
|
421
516
|
}
|
|
422
517
|
},
|
|
423
518
|
contactList: {
|
|
@@ -430,10 +525,31 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
430
525
|
},
|
|
431
526
|
find(params, options) {
|
|
432
527
|
const { store_id, ...queryParams } = params || {};
|
|
433
|
-
return apiConfig.httpClient.get(
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
528
|
+
return apiConfig.httpClient.get(
|
|
529
|
+
`${base(store_id)}/contact-lists`,
|
|
530
|
+
{
|
|
531
|
+
...options,
|
|
532
|
+
params: queryParams
|
|
533
|
+
}
|
|
534
|
+
);
|
|
535
|
+
},
|
|
536
|
+
plans: {
|
|
537
|
+
find(params, options) {
|
|
538
|
+
const { store_id, contact_list_id, ...queryParams } = params;
|
|
539
|
+
return apiConfig.httpClient.get(
|
|
540
|
+
`${base(store_id)}/contact-lists/${contact_list_id}/plans`,
|
|
541
|
+
{ ...options, params: queryParams }
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
memberships: {
|
|
546
|
+
find(params, options) {
|
|
547
|
+
const { store_id, ...queryParams } = params || {};
|
|
548
|
+
return apiConfig.httpClient.get(`${base(store_id)}/contact-lists/memberships`, {
|
|
549
|
+
...options,
|
|
550
|
+
params: queryParams
|
|
551
|
+
});
|
|
552
|
+
}
|
|
437
553
|
},
|
|
438
554
|
subscribe(params, options) {
|
|
439
555
|
const { store_id, id, ...payload } = params;
|
|
@@ -458,13 +574,31 @@ var createStorefrontApi = (apiConfig, updateContactSession) => {
|
|
|
458
574
|
options
|
|
459
575
|
);
|
|
460
576
|
},
|
|
461
|
-
|
|
577
|
+
manage(token, options) {
|
|
462
578
|
return apiConfig.httpClient.post(
|
|
463
|
-
`${base()}/contact-lists/
|
|
579
|
+
`${base()}/contact-lists/manage`,
|
|
464
580
|
{ token },
|
|
465
581
|
options
|
|
466
582
|
);
|
|
467
583
|
},
|
|
584
|
+
unsubscribe(token, options) {
|
|
585
|
+
const headers = { ...options?.headers };
|
|
586
|
+
for (const name of Object.keys(headers)) {
|
|
587
|
+
if (name.toLowerCase() === "content-type") delete headers[name];
|
|
588
|
+
}
|
|
589
|
+
return apiConfig.httpClient.post(
|
|
590
|
+
`${base()}/contact-lists/unsubscribe`,
|
|
591
|
+
new URLSearchParams({ "List-Unsubscribe": "One-Click" }),
|
|
592
|
+
{
|
|
593
|
+
...options,
|
|
594
|
+
params: { ...options?.params || {}, token },
|
|
595
|
+
headers: {
|
|
596
|
+
...headers,
|
|
597
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
);
|
|
601
|
+
},
|
|
468
602
|
confirm(token, options) {
|
|
469
603
|
return apiConfig.httpClient.post(
|
|
470
604
|
`${base()}/contact-lists/confirm`,
|
|
@@ -629,27 +763,47 @@ var convertServerErrorToRequestError = (serverError, renameRules) => {
|
|
|
629
763
|
|
|
630
764
|
// src/utils/queryParams.ts
|
|
631
765
|
function buildQueryString(params) {
|
|
632
|
-
const queryParts =
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const jsonString = JSON.stringify(value);
|
|
646
|
-
queryParts.push(`${key}=${encodeURIComponent(jsonString)}`);
|
|
766
|
+
const queryParts = Object.entries(params).flatMap(
|
|
767
|
+
([key, value]) => {
|
|
768
|
+
if (value === null || value === void 0) return [];
|
|
769
|
+
if (typeof value === "string") {
|
|
770
|
+
return [`${key}=${encodeURIComponent(value)}`];
|
|
771
|
+
}
|
|
772
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
773
|
+
return [`${key}=${value}`];
|
|
774
|
+
}
|
|
775
|
+
if (Array.isArray(value) || typeof value === "object") {
|
|
776
|
+
return [`${key}=${encodeURIComponent(JSON.stringify(value))}`];
|
|
777
|
+
}
|
|
778
|
+
return [];
|
|
647
779
|
}
|
|
648
|
-
|
|
780
|
+
);
|
|
649
781
|
return queryParts.length > 0 ? `?${queryParts.join("&")}` : "";
|
|
650
782
|
}
|
|
651
783
|
|
|
652
784
|
// src/services/createHttpClient.ts
|
|
785
|
+
function requestError(name, message, details = {}) {
|
|
786
|
+
return Object.assign(new Error(message), { name }, details);
|
|
787
|
+
}
|
|
788
|
+
function isRecord(value) {
|
|
789
|
+
return typeof value === "object" && value !== null;
|
|
790
|
+
}
|
|
791
|
+
function isTokenSet(value) {
|
|
792
|
+
return isRecord(value) && typeof value.access_token === "string" && (value.refresh_token === void 0 || typeof value.refresh_token === "string") && (value.access_expires_at === void 0 || typeof value.access_expires_at === "number");
|
|
793
|
+
}
|
|
794
|
+
function isValidationError(value) {
|
|
795
|
+
return isRecord(value) && typeof value.field === "string" && typeof value.error === "string";
|
|
796
|
+
}
|
|
797
|
+
function toServerError(value, statusCode) {
|
|
798
|
+
const payload = isRecord(value) ? value : {};
|
|
799
|
+
const validationErrors = Array.isArray(payload.validationErrors) ? payload.validationErrors.filter(isValidationError) : [];
|
|
800
|
+
return {
|
|
801
|
+
message: typeof payload.message === "string" ? payload.message : "Request failed",
|
|
802
|
+
error: typeof payload.error === "string" ? payload.error : "REQUEST_FAILED",
|
|
803
|
+
statusCode: typeof payload.statusCode === "number" ? payload.statusCode : statusCode,
|
|
804
|
+
validationErrors
|
|
805
|
+
};
|
|
806
|
+
}
|
|
653
807
|
function createHttpClient(cfg) {
|
|
654
808
|
const { authStorage } = cfg;
|
|
655
809
|
let refreshPromise = null;
|
|
@@ -666,32 +820,41 @@ function createHttpClient(cfg) {
|
|
|
666
820
|
const refresh_token = tokens?.refresh_token;
|
|
667
821
|
if (!refresh_token) {
|
|
668
822
|
authStorage.onForcedLogout();
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
throw err;
|
|
823
|
+
throw requestError("ApiError", "No refresh token available", {
|
|
824
|
+
statusCode: 401
|
|
825
|
+
});
|
|
673
826
|
}
|
|
674
827
|
const refRes = await fetch(getRefreshEndpoint(), {
|
|
675
828
|
method: "POST",
|
|
676
|
-
headers: {
|
|
829
|
+
headers: {
|
|
830
|
+
Accept: "application/json",
|
|
831
|
+
"Content-Type": "application/json"
|
|
832
|
+
},
|
|
677
833
|
body: JSON.stringify({ refresh_token })
|
|
678
834
|
});
|
|
679
835
|
if (!refRes.ok) {
|
|
680
836
|
authStorage.onForcedLogout();
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
throw err;
|
|
837
|
+
throw requestError("ApiError", "Token refresh failed", {
|
|
838
|
+
statusCode: 401
|
|
839
|
+
});
|
|
685
840
|
}
|
|
686
841
|
const data = await refRes.json();
|
|
842
|
+
if (!isTokenSet(data)) {
|
|
843
|
+
authStorage.onForcedLogout();
|
|
844
|
+
throw requestError(
|
|
845
|
+
"ParseError",
|
|
846
|
+
"Token refresh returned an invalid response",
|
|
847
|
+
{ statusCode: refRes.status }
|
|
848
|
+
);
|
|
849
|
+
}
|
|
687
850
|
authStorage.onTokensRefreshed(data);
|
|
688
851
|
})().finally(() => {
|
|
689
852
|
refreshPromise = null;
|
|
690
853
|
});
|
|
691
854
|
return refreshPromise;
|
|
692
855
|
}
|
|
693
|
-
async function request(method, path, body, options) {
|
|
694
|
-
if (options?.transformRequest) {
|
|
856
|
+
async function request(method, path, body, options, retried = false) {
|
|
857
|
+
if (!retried && options?.transformRequest) {
|
|
695
858
|
body = options.transformRequest(body);
|
|
696
859
|
}
|
|
697
860
|
const headers = {
|
|
@@ -709,23 +872,27 @@ function createHttpClient(cfg) {
|
|
|
709
872
|
headers["Authorization"] = `Bearer ${tokens.access_token}`;
|
|
710
873
|
}
|
|
711
874
|
const finalPath = options?.params ? path + buildQueryString(options.params) : path;
|
|
712
|
-
const
|
|
875
|
+
const fetchOptions = {
|
|
876
|
+
method,
|
|
877
|
+
headers,
|
|
878
|
+
signal: options?.signal
|
|
879
|
+
};
|
|
713
880
|
if (!["GET", "DELETE"].includes(method) && body !== void 0) {
|
|
714
|
-
|
|
881
|
+
fetchOptions.body = body instanceof URLSearchParams ? body.toString() : JSON.stringify(body);
|
|
715
882
|
}
|
|
716
883
|
const fullUrl = `${cfg.baseUrl}${finalPath}`;
|
|
717
884
|
let res;
|
|
718
885
|
let data;
|
|
719
886
|
const startedAt = Date.now();
|
|
720
887
|
try {
|
|
721
|
-
res = await fetch(fullUrl,
|
|
888
|
+
res = await fetch(fullUrl, fetchOptions);
|
|
722
889
|
} catch (error) {
|
|
723
890
|
const aborted = options?.signal?.aborted || error instanceof Error && error.name === "AbortError";
|
|
724
|
-
const err =
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
891
|
+
const err = requestError(
|
|
892
|
+
aborted ? "AbortError" : "NetworkError",
|
|
893
|
+
error instanceof Error ? error.message : "Network request failed",
|
|
894
|
+
{ method, url: fullUrl, aborted }
|
|
895
|
+
);
|
|
729
896
|
if (options?.onError && method !== "GET") {
|
|
730
897
|
Promise.resolve(
|
|
731
898
|
options.onError({ error: err, method, url: fullUrl, aborted })
|
|
@@ -734,18 +901,9 @@ function createHttpClient(cfg) {
|
|
|
734
901
|
}
|
|
735
902
|
throw err;
|
|
736
903
|
}
|
|
737
|
-
if (res.status === 401 && !
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
const refreshed = authStorage.getTokens();
|
|
741
|
-
if (refreshed?.access_token) {
|
|
742
|
-
headers["Authorization"] = `Bearer ${refreshed.access_token}`;
|
|
743
|
-
fetchOpts.headers = headers;
|
|
744
|
-
}
|
|
745
|
-
return request(method, path, body, { ...options, _retried: true });
|
|
746
|
-
} catch (refreshError) {
|
|
747
|
-
throw refreshError;
|
|
748
|
-
}
|
|
904
|
+
if (res.status === 401 && !retried) {
|
|
905
|
+
await ensureFreshToken();
|
|
906
|
+
return request(method, path, body, options, true);
|
|
749
907
|
}
|
|
750
908
|
try {
|
|
751
909
|
const contentLength = res.headers.get("content-length");
|
|
@@ -756,30 +914,35 @@ function createHttpClient(cfg) {
|
|
|
756
914
|
data = await res.json();
|
|
757
915
|
}
|
|
758
916
|
} catch (error) {
|
|
759
|
-
const err =
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
917
|
+
const err = requestError("ParseError", "Failed to parse response", {
|
|
918
|
+
method,
|
|
919
|
+
url: fullUrl,
|
|
920
|
+
statusCode: res.status
|
|
921
|
+
});
|
|
764
922
|
if (options?.onError && method !== "GET") {
|
|
765
923
|
Promise.resolve(
|
|
766
|
-
options.onError({
|
|
924
|
+
options.onError({
|
|
925
|
+
error: err,
|
|
926
|
+
method,
|
|
927
|
+
url: fullUrl,
|
|
928
|
+
status: res.status
|
|
929
|
+
})
|
|
767
930
|
).catch(() => {
|
|
768
931
|
});
|
|
769
932
|
}
|
|
770
933
|
throw err;
|
|
771
934
|
}
|
|
772
935
|
if (!res.ok) {
|
|
773
|
-
const serverErr = data;
|
|
936
|
+
const serverErr = toServerError(data, res.status);
|
|
774
937
|
const reqErr = convertServerErrorToRequestError(serverErr);
|
|
775
|
-
const err = new Error(serverErr.message || "Request failed");
|
|
776
|
-
err.name = "ApiError";
|
|
777
|
-
err.statusCode = serverErr.statusCode || res.status;
|
|
778
|
-
err.validationErrors = reqErr.validationErrors;
|
|
779
|
-
err.method = method;
|
|
780
|
-
err.url = fullUrl;
|
|
781
938
|
const requestId = res.headers.get("x-request-id") || res.headers.get("request-id");
|
|
782
|
-
|
|
939
|
+
const err = requestError("ApiError", serverErr.message, {
|
|
940
|
+
statusCode: serverErr.statusCode,
|
|
941
|
+
validationErrors: reqErr.validationErrors,
|
|
942
|
+
method,
|
|
943
|
+
url: fullUrl,
|
|
944
|
+
requestId: requestId || void 0
|
|
945
|
+
});
|
|
783
946
|
if (options?.onError && method !== "GET") {
|
|
784
947
|
Promise.resolve(
|
|
785
948
|
options.onError({
|
|
@@ -829,183 +992,279 @@ function supportConversationQuery(params) {
|
|
|
829
992
|
if (params.after_id) qs.set("after_id", params.after_id);
|
|
830
993
|
return qs.toString();
|
|
831
994
|
}
|
|
995
|
+
function storefrontSupportOptions(supportToken, options) {
|
|
996
|
+
if (!/^[0-9a-f]{64}$/.test(supportToken)) {
|
|
997
|
+
throw new Error("support_token must be a 64-character lowercase hexadecimal token");
|
|
998
|
+
}
|
|
999
|
+
const headers = { ...options?.headers };
|
|
1000
|
+
for (const name of Object.keys(headers)) {
|
|
1001
|
+
if (name.toLowerCase() === "x-arky-support-token") delete headers[name];
|
|
1002
|
+
}
|
|
1003
|
+
return {
|
|
1004
|
+
...options,
|
|
1005
|
+
headers: {
|
|
1006
|
+
...headers,
|
|
1007
|
+
"X-Arky-Support-Token": supportToken
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
832
1011
|
function createStorefrontSupportApi(config) {
|
|
833
|
-
const { httpClient
|
|
1012
|
+
const { httpClient } = config;
|
|
834
1013
|
return {
|
|
835
1014
|
async startConversation(params = {}, opts) {
|
|
1015
|
+
const storeId = config.storeId;
|
|
836
1016
|
return httpClient.post(
|
|
837
1017
|
`/v1/storefront/${storeId}/support/conversations`,
|
|
838
|
-
{ store_id: storeId
|
|
1018
|
+
{ ...params, store_id: storeId },
|
|
839
1019
|
opts
|
|
840
1020
|
);
|
|
841
1021
|
},
|
|
842
1022
|
async sendMessage(params, opts) {
|
|
1023
|
+
const { support_token, ...request } = params;
|
|
1024
|
+
const storeId = config.storeId;
|
|
843
1025
|
return httpClient.post(
|
|
844
|
-
`/v1/storefront/${storeId}/support/conversations/${
|
|
845
|
-
{ store_id: storeId
|
|
846
|
-
opts
|
|
1026
|
+
`/v1/storefront/${storeId}/support/conversations/${request.conversation_id}/messages`,
|
|
1027
|
+
{ ...request, store_id: storeId },
|
|
1028
|
+
storefrontSupportOptions(support_token, opts)
|
|
847
1029
|
);
|
|
848
1030
|
},
|
|
849
1031
|
async getConversation(params, opts) {
|
|
850
|
-
const
|
|
1032
|
+
const { support_token, ...request } = params;
|
|
1033
|
+
const storeId = config.storeId;
|
|
1034
|
+
const qs = supportConversationQuery({ ...request, store_id: storeId });
|
|
851
1035
|
return httpClient.get(
|
|
852
|
-
`/v1/storefront/${storeId}/support/conversations/${
|
|
853
|
-
opts
|
|
1036
|
+
`/v1/storefront/${storeId}/support/conversations/${request.conversation_id}?${qs}`,
|
|
1037
|
+
storefrontSupportOptions(support_token, opts)
|
|
854
1038
|
);
|
|
855
1039
|
}
|
|
856
1040
|
};
|
|
857
1041
|
}
|
|
858
1042
|
|
|
859
1043
|
// src/utils/blocks.ts
|
|
1044
|
+
function isRecord2(value) {
|
|
1045
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1046
|
+
}
|
|
1047
|
+
function isBlock(value) {
|
|
1048
|
+
return isRecord2(value) && typeof value.key === "string" && typeof value.type === "string";
|
|
1049
|
+
}
|
|
1050
|
+
function recordFromBlocks(values, locale) {
|
|
1051
|
+
const result = {};
|
|
1052
|
+
for (const value of values) {
|
|
1053
|
+
if (!isBlock(value)) continue;
|
|
1054
|
+
result[value.key] = unwrapBlock(value, locale);
|
|
1055
|
+
}
|
|
1056
|
+
return result;
|
|
1057
|
+
}
|
|
1058
|
+
function localizedValue(value, locale) {
|
|
1059
|
+
if (typeof value === "string") return value;
|
|
1060
|
+
if (!isRecord2(value)) return "";
|
|
1061
|
+
const selected = value[locale] ?? value.en;
|
|
1062
|
+
return typeof selected === "string" ? selected : "";
|
|
1063
|
+
}
|
|
1064
|
+
function unwrapBlock(value, locale) {
|
|
1065
|
+
if (!isBlock(value)) return value;
|
|
1066
|
+
if (value.type === "localized_text" || value.type === "markdown") {
|
|
1067
|
+
return localizedValue(value.value, locale);
|
|
1068
|
+
}
|
|
1069
|
+
if (value.type === "array") {
|
|
1070
|
+
if (!Array.isArray(value.value)) return [];
|
|
1071
|
+
return value.value.map((item) => {
|
|
1072
|
+
if (isBlock(item)) return unwrapBlock(item, locale);
|
|
1073
|
+
if (isRecord2(item) && Array.isArray(item.value)) {
|
|
1074
|
+
return recordFromBlocks(item.value, locale);
|
|
1075
|
+
}
|
|
1076
|
+
if (!isRecord2(item)) return item;
|
|
1077
|
+
return Object.fromEntries(
|
|
1078
|
+
Object.entries(item).map(([key, nested]) => [key, unwrapBlock(nested, locale)])
|
|
1079
|
+
);
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
if (value.type === "object") {
|
|
1083
|
+
if (Array.isArray(value.value)) return recordFromBlocks(value.value, locale);
|
|
1084
|
+
if (!isRecord2(value.value)) return {};
|
|
1085
|
+
return Object.fromEntries(
|
|
1086
|
+
Object.entries(value.value).map(([key, nested]) => [key, unwrapBlock(nested, locale)])
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
return value.value;
|
|
1090
|
+
}
|
|
1091
|
+
function blockContentArray(values, locale) {
|
|
1092
|
+
const blocks = values.filter(isBlock);
|
|
1093
|
+
if (blocks.length === 0) return [];
|
|
1094
|
+
if (blocks.every((block) => block.key === blocks[0].key)) {
|
|
1095
|
+
return blocks.map((block) => blockContentValue(block, locale));
|
|
1096
|
+
}
|
|
1097
|
+
return Object.fromEntries(
|
|
1098
|
+
blocks.map((block) => [block.key, blockContentValue(block, locale)])
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
function blockContentValue(block, locale) {
|
|
1102
|
+
if (block.type === "localized_text" || block.type === "markdown") {
|
|
1103
|
+
return localizedValue(block.value, locale);
|
|
1104
|
+
}
|
|
1105
|
+
if (block.type === "media") return block.value ?? null;
|
|
1106
|
+
if (block.type === "array") {
|
|
1107
|
+
return Array.isArray(block.value) ? blockContentArray(block.value, locale) : [];
|
|
1108
|
+
}
|
|
1109
|
+
if (block.type === "object") {
|
|
1110
|
+
if (Array.isArray(block.value)) return blockContentArray(block.value, locale);
|
|
1111
|
+
if (!isRecord2(block.value)) return {};
|
|
1112
|
+
return Object.fromEntries(
|
|
1113
|
+
Object.entries(block.value).map(([key, value]) => [
|
|
1114
|
+
key,
|
|
1115
|
+
isBlock(value) ? blockContentValue(value, locale) : value
|
|
1116
|
+
])
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
return block.value;
|
|
1120
|
+
}
|
|
1121
|
+
function findBlock(entry, key) {
|
|
1122
|
+
return entry?.blocks?.find((block) => block.key === key);
|
|
1123
|
+
}
|
|
860
1124
|
function getBlockLabel(block) {
|
|
861
|
-
|
|
862
|
-
return block.key?.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()) ?? "";
|
|
1125
|
+
return block?.key.replace(/_/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()) ?? "";
|
|
863
1126
|
}
|
|
864
1127
|
function formatBlockValue(block) {
|
|
865
|
-
if (
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
return new Date(value).toLocaleDateString();
|
|
873
|
-
}
|
|
874
|
-
return String(value);
|
|
875
|
-
case "media":
|
|
876
|
-
if (value && typeof value === "object") {
|
|
877
|
-
return value.mime_type ? value.name || value.id : value.title || value.name || value.id;
|
|
878
|
-
}
|
|
879
|
-
return String(value);
|
|
880
|
-
default:
|
|
881
|
-
return String(value);
|
|
1128
|
+
if (block?.value === null || block?.value === void 0) return "";
|
|
1129
|
+
if (block.type === "boolean") return block.value ? "Yes" : "No";
|
|
1130
|
+
if (block.type === "number") {
|
|
1131
|
+
const properties = isRecord2(block.properties) ? block.properties : {};
|
|
1132
|
+
if (properties.variant === "DATE" || properties.variant === "DATE_TIME") {
|
|
1133
|
+
return new Date(Number(block.value)).toLocaleDateString();
|
|
1134
|
+
}
|
|
882
1135
|
}
|
|
1136
|
+
if (block.type === "media" && isRecord2(block.value)) {
|
|
1137
|
+
const label = block.value.name ?? block.value.title ?? block.value.id;
|
|
1138
|
+
return typeof label === "string" ? label : "";
|
|
1139
|
+
}
|
|
1140
|
+
return String(block.value);
|
|
883
1141
|
}
|
|
884
|
-
function prepareBlocksForSubmission(formData, blockTypes) {
|
|
885
|
-
return Object.
|
|
1142
|
+
function prepareBlocksForSubmission(formData, blockTypes = {}) {
|
|
1143
|
+
return Object.entries(formData).filter(([, value]) => value !== null && value !== void 0).map(([key, value]) => ({
|
|
886
1144
|
key,
|
|
887
|
-
value:
|
|
1145
|
+
value: blockTypes[key] === "array" && !Array.isArray(value) ? [value] : value
|
|
888
1146
|
}));
|
|
889
1147
|
}
|
|
890
1148
|
function extractBlockValues(blocks) {
|
|
891
|
-
|
|
892
|
-
blocks.forEach((block) => {
|
|
893
|
-
values[block.key] = block.value ?? null;
|
|
894
|
-
});
|
|
895
|
-
return values;
|
|
1149
|
+
return Object.fromEntries(blocks.map((block) => [block.key, block.value ?? null]));
|
|
896
1150
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
var getBlockTextValue = (block, locale = "en") => {
|
|
1151
|
+
function getBlockValue(entry, key) {
|
|
1152
|
+
return findBlock(entry, key)?.value ?? null;
|
|
1153
|
+
}
|
|
1154
|
+
function getBlockTextValue(block, locale = "en") {
|
|
902
1155
|
if (!block || block.value === null || block.value === void 0) return "";
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
if (typeof block.value === "object" && block.value !== null) {
|
|
906
|
-
return block.value[locale] ?? block.value["en"] ?? "";
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
if (typeof block.value === "string") return block.value;
|
|
910
|
-
return String(block.value ?? "");
|
|
911
|
-
};
|
|
912
|
-
var getBlockValues = (entry, blockKey) => {
|
|
913
|
-
const block = entry?.blocks?.find((f) => f.key === blockKey);
|
|
914
|
-
if (!block) return [];
|
|
915
|
-
if (block.type === "array" && Array.isArray(block.value)) {
|
|
916
|
-
return block.value;
|
|
917
|
-
}
|
|
918
|
-
return [];
|
|
919
|
-
};
|
|
920
|
-
function unwrapBlock(block, locale) {
|
|
921
|
-
if (!block?.type || block.value === void 0) return block;
|
|
922
|
-
const blockType = block.type;
|
|
923
|
-
if (blockType === "array") {
|
|
924
|
-
return block.value.map((obj) => {
|
|
925
|
-
const parsed = {};
|
|
926
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
927
|
-
parsed[k] = unwrapBlock(v, locale);
|
|
928
|
-
}
|
|
929
|
-
return parsed;
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
|
-
if (blockType === "object") {
|
|
933
|
-
const parsed = {};
|
|
934
|
-
for (const [k, v] of Object.entries(block.value || {})) {
|
|
935
|
-
parsed[k] = unwrapBlock(v, locale);
|
|
936
|
-
}
|
|
937
|
-
return parsed;
|
|
1156
|
+
if (block.type === "localized_text" || block.type === "markdown") {
|
|
1157
|
+
return localizedValue(block.value, locale);
|
|
938
1158
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1159
|
+
return typeof block.value === "string" ? block.value : String(block.value);
|
|
1160
|
+
}
|
|
1161
|
+
function getBlockContentValue(entry, key, locale = "en") {
|
|
1162
|
+
const block = findBlock(entry, key);
|
|
1163
|
+
return block ? blockContentValue(block, locale) : null;
|
|
943
1164
|
}
|
|
944
|
-
|
|
945
|
-
const block = entry
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
return
|
|
952
|
-
}
|
|
1165
|
+
function getBlockValues(entry, key) {
|
|
1166
|
+
const block = findBlock(entry, key);
|
|
1167
|
+
return block?.type === "array" && Array.isArray(block.value) ? block.value : [];
|
|
1168
|
+
}
|
|
1169
|
+
function getBlockObjectValues(entry, key, locale = "en") {
|
|
1170
|
+
return getBlockValues(entry, key).map((value) => {
|
|
1171
|
+
if (isRecord2(value) && Array.isArray(value.value)) {
|
|
1172
|
+
return recordFromBlocks(value.value, locale);
|
|
1173
|
+
}
|
|
1174
|
+
return isRecord2(value) ? Object.fromEntries(
|
|
1175
|
+
Object.entries(value).map(([field, nested]) => [field, unwrapBlock(nested, locale)])
|
|
1176
|
+
) : {};
|
|
953
1177
|
});
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
const block = entry
|
|
1178
|
+
}
|
|
1179
|
+
function getBlockFromArray(entry, key, locale = "en") {
|
|
1180
|
+
const block = findBlock(entry, key);
|
|
957
1181
|
if (!block) return {};
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
const mediaValue = imageBlock.value;
|
|
977
|
-
return mediaValue?.resolutions?.original?.url || mediaValue?.url || null;
|
|
978
|
-
}
|
|
979
|
-
if (isBlock) {
|
|
980
|
-
if (typeof imageBlock === "string") return imageBlock;
|
|
981
|
-
if (imageBlock.url) return imageBlock.url;
|
|
982
|
-
}
|
|
983
|
-
return imageBlock.resolutions?.original?.url || null;
|
|
984
|
-
};
|
|
1182
|
+
const value = unwrapBlock(block, locale);
|
|
1183
|
+
if (isRecord2(value)) return value;
|
|
1184
|
+
if (Array.isArray(block.value)) return recordFromBlocks(block.value, locale);
|
|
1185
|
+
return { [block.key]: value };
|
|
1186
|
+
}
|
|
1187
|
+
function nestedUrl(value) {
|
|
1188
|
+
const resolutions = isRecord2(value.resolutions) ? value.resolutions : null;
|
|
1189
|
+
const original = resolutions && isRecord2(resolutions.original) ? resolutions.original : null;
|
|
1190
|
+
if (typeof original?.url === "string") return original.url;
|
|
1191
|
+
return typeof value.url === "string" ? value.url : null;
|
|
1192
|
+
}
|
|
1193
|
+
function getImageUrl(value, isBlock2 = true) {
|
|
1194
|
+
if (typeof value === "string") return value;
|
|
1195
|
+
if (!isRecord2(value)) return null;
|
|
1196
|
+
if (value.type === "media" && isRecord2(value.value)) return nestedUrl(value.value);
|
|
1197
|
+
if (isBlock2 && typeof value.url === "string") return value.url;
|
|
1198
|
+
return nestedUrl(value);
|
|
1199
|
+
}
|
|
985
1200
|
|
|
986
1201
|
// src/utils/price.ts
|
|
1202
|
+
var SUPPORTED_STORE_CURRENCIES = Object.freeze([
|
|
1203
|
+
"USD",
|
|
1204
|
+
"EUR",
|
|
1205
|
+
"GBP",
|
|
1206
|
+
"JPY",
|
|
1207
|
+
"CNY",
|
|
1208
|
+
"CHF",
|
|
1209
|
+
"AUD",
|
|
1210
|
+
"CAD",
|
|
1211
|
+
"HKD",
|
|
1212
|
+
"SGD",
|
|
1213
|
+
"NZD",
|
|
1214
|
+
"KRW",
|
|
1215
|
+
"SEK",
|
|
1216
|
+
"NOK",
|
|
1217
|
+
"DKK",
|
|
1218
|
+
"INR",
|
|
1219
|
+
"MXN",
|
|
1220
|
+
"BRL",
|
|
1221
|
+
"ZAR",
|
|
1222
|
+
"RUB",
|
|
1223
|
+
"TRY",
|
|
1224
|
+
"PLN",
|
|
1225
|
+
"THB",
|
|
1226
|
+
"IDR",
|
|
1227
|
+
"MYR",
|
|
1228
|
+
"PHP",
|
|
1229
|
+
"CZK",
|
|
1230
|
+
"ILS",
|
|
1231
|
+
"AED",
|
|
1232
|
+
"SAR",
|
|
1233
|
+
"HUF",
|
|
1234
|
+
"RON",
|
|
1235
|
+
"BGN",
|
|
1236
|
+
"HRK",
|
|
1237
|
+
"BAM",
|
|
1238
|
+
"RSD",
|
|
1239
|
+
"MKD",
|
|
1240
|
+
"ALL"
|
|
1241
|
+
]);
|
|
1242
|
+
var SUPPORTED_STORE_CURRENCY_SET = Object.freeze(
|
|
1243
|
+
new Set(SUPPORTED_STORE_CURRENCIES)
|
|
1244
|
+
);
|
|
1245
|
+
var ZERO_MINOR_UNIT_STORE_CURRENCIES = Object.freeze(
|
|
1246
|
+
/* @__PURE__ */ new Set(["JPY", "KRW"])
|
|
1247
|
+
);
|
|
987
1248
|
function formatCurrency(amount, currencyCode, locale = "en") {
|
|
988
|
-
|
|
1249
|
+
const normalized = currencyCode.trim().toUpperCase();
|
|
1250
|
+
if (!normalized) return "";
|
|
1251
|
+
const minorUnits = getCurrencyMinorUnits(normalized);
|
|
989
1252
|
return new Intl.NumberFormat(locale, {
|
|
990
1253
|
style: "currency",
|
|
991
|
-
currency:
|
|
1254
|
+
currency: normalized,
|
|
1255
|
+
minimumFractionDigits: minorUnits,
|
|
1256
|
+
maximumFractionDigits: minorUnits
|
|
992
1257
|
}).format(amount);
|
|
993
1258
|
}
|
|
994
|
-
function
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
currency: currency.toUpperCase()
|
|
999
|
-
});
|
|
1000
|
-
const parts = formatter.formatToParts(1.11);
|
|
1001
|
-
const fractionPart = parts.find((p) => p.type === "fraction");
|
|
1002
|
-
return fractionPart?.value.length ?? 2;
|
|
1003
|
-
} catch {
|
|
1004
|
-
return 2;
|
|
1259
|
+
function getCurrencyMinorUnits(currency) {
|
|
1260
|
+
const normalized = currency.trim().toUpperCase();
|
|
1261
|
+
if (!SUPPORTED_STORE_CURRENCY_SET.has(normalized)) {
|
|
1262
|
+
throw new RangeError(`Unsupported currency '${currency}'`);
|
|
1005
1263
|
}
|
|
1264
|
+
return ZERO_MINOR_UNIT_STORE_CURRENCIES.has(normalized) ? 0 : 2;
|
|
1006
1265
|
}
|
|
1007
1266
|
function convertToMajor(minorAmount, currency) {
|
|
1008
|
-
const units =
|
|
1267
|
+
const units = getCurrencyMinorUnits(currency);
|
|
1009
1268
|
return minorAmount / Math.pow(10, units);
|
|
1010
1269
|
}
|
|
1011
1270
|
function getCurrencySymbol(currency) {
|
|
@@ -1027,22 +1286,24 @@ function getCurrencyName(currency) {
|
|
|
1027
1286
|
}
|
|
1028
1287
|
}
|
|
1029
1288
|
function formatMinor(amountMinor, currency) {
|
|
1030
|
-
if (!
|
|
1289
|
+
if (!Number.isSafeInteger(amountMinor)) {
|
|
1290
|
+
throw new RangeError("Minor-unit amount must be a safe integer");
|
|
1291
|
+
}
|
|
1031
1292
|
return formatCurrency(convertToMajor(amountMinor, currency), currency);
|
|
1032
1293
|
}
|
|
1033
1294
|
function formatPayment(payment) {
|
|
1034
1295
|
return formatMinor(payment.total, payment.currency);
|
|
1035
1296
|
}
|
|
1036
1297
|
function formatPrice(prices, marketId) {
|
|
1037
|
-
if (!prices || prices.length === 0) return "";
|
|
1038
|
-
const price =
|
|
1039
|
-
if (!price) return "";
|
|
1298
|
+
if (!prices || prices.length === 0 || !marketId) return "";
|
|
1299
|
+
const price = prices.find((p) => p.market === marketId);
|
|
1300
|
+
if (!price || !Number.isSafeInteger(price.amount) || price.amount < 0 || !price.currency) return "";
|
|
1040
1301
|
return formatMinor(price.amount, price.currency);
|
|
1041
1302
|
}
|
|
1042
1303
|
function getPriceAmount(prices, marketId) {
|
|
1043
|
-
if (!prices || prices.length === 0) return
|
|
1044
|
-
const price = prices.find((p) => p.market === marketId)
|
|
1045
|
-
return price
|
|
1304
|
+
if (!prices || prices.length === 0 || !marketId) return null;
|
|
1305
|
+
const price = prices.find((p) => p.market === marketId);
|
|
1306
|
+
return price && Number.isSafeInteger(price.amount) && price.amount >= 0 ? price.amount : null;
|
|
1046
1307
|
}
|
|
1047
1308
|
|
|
1048
1309
|
// src/utils/validation.ts
|
|
@@ -1143,6 +1404,7 @@ function formatDate(date, locale) {
|
|
|
1143
1404
|
async function fetchSvgContent(mediaObject) {
|
|
1144
1405
|
if (!mediaObject) return null;
|
|
1145
1406
|
const svgUrl = getImageUrl(mediaObject, false);
|
|
1407
|
+
if (!svgUrl) return null;
|
|
1146
1408
|
try {
|
|
1147
1409
|
const response = await fetch(svgUrl);
|
|
1148
1410
|
if (!response.ok) {
|
|
@@ -1235,9 +1497,10 @@ function getFirstAvailableFCId(variant, quantity = 1) {
|
|
|
1235
1497
|
// src/index.ts
|
|
1236
1498
|
function createUtilitySurface(apiConfig) {
|
|
1237
1499
|
return {
|
|
1238
|
-
getImageUrl: (imageBlock,
|
|
1500
|
+
getImageUrl: (imageBlock, isBlock2 = true) => getImageUrl(imageBlock, isBlock2),
|
|
1239
1501
|
getBlockValue,
|
|
1240
1502
|
getBlockTextValue,
|
|
1503
|
+
getBlockContentValue,
|
|
1241
1504
|
getBlockValues,
|
|
1242
1505
|
getBlockLabel,
|
|
1243
1506
|
getBlockObjectValues,
|
|
@@ -1272,29 +1535,50 @@ function createUtilitySurface(apiConfig) {
|
|
|
1272
1535
|
getFirstAvailableFCId
|
|
1273
1536
|
};
|
|
1274
1537
|
}
|
|
1275
|
-
|
|
1276
|
-
function readContactSession() {
|
|
1538
|
+
function defaultStorefrontSessionStorage() {
|
|
1277
1539
|
if (typeof window === "undefined") return null;
|
|
1278
1540
|
try {
|
|
1279
|
-
|
|
1280
|
-
return raw ? JSON.parse(raw) : null;
|
|
1541
|
+
return window.localStorage;
|
|
1281
1542
|
} catch {
|
|
1282
1543
|
return null;
|
|
1283
1544
|
}
|
|
1284
1545
|
}
|
|
1285
|
-
function
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
localStorage.setItem(CONTACT_STORAGE_KEY, JSON.stringify(s));
|
|
1289
|
-
} else {
|
|
1290
|
-
localStorage.removeItem(CONTACT_STORAGE_KEY);
|
|
1291
|
-
}
|
|
1546
|
+
function storefrontSessionStorageKey(baseUrl, storeId) {
|
|
1547
|
+
const normalizedBaseUrl = baseUrl.trim().replace(/\/+$/, "").toLowerCase();
|
|
1548
|
+
return `arky_contact_session:${encodeURIComponent(normalizedBaseUrl)}:${encodeURIComponent(storeId)}`;
|
|
1292
1549
|
}
|
|
1293
|
-
function
|
|
1550
|
+
function createStorefrontClient(config) {
|
|
1294
1551
|
const locale = config.locale || "en";
|
|
1295
1552
|
const initialMarket = config.market || "";
|
|
1296
1553
|
const listeners = /* @__PURE__ */ new Set();
|
|
1297
1554
|
let bareIdentifyPromise = null;
|
|
1555
|
+
let identityTail = Promise.resolve();
|
|
1556
|
+
const sessionStorage = config.sessionStorage || defaultStorefrontSessionStorage();
|
|
1557
|
+
let memorySession = null;
|
|
1558
|
+
function readContactSession() {
|
|
1559
|
+
if (!sessionStorage) return memorySession;
|
|
1560
|
+
try {
|
|
1561
|
+
const raw = sessionStorage.getItem(
|
|
1562
|
+
storefrontSessionStorageKey(config.baseUrl, config.storeId)
|
|
1563
|
+
);
|
|
1564
|
+
return raw ? JSON.parse(raw) : null;
|
|
1565
|
+
} catch {
|
|
1566
|
+
return memorySession;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
function writeContactSession(session) {
|
|
1570
|
+
memorySession = session;
|
|
1571
|
+
if (!sessionStorage) return;
|
|
1572
|
+
const key = storefrontSessionStorageKey(config.baseUrl, config.storeId);
|
|
1573
|
+
try {
|
|
1574
|
+
if (session) {
|
|
1575
|
+
sessionStorage.setItem(key, JSON.stringify(session));
|
|
1576
|
+
} else {
|
|
1577
|
+
sessionStorage.removeItem(key);
|
|
1578
|
+
}
|
|
1579
|
+
} catch {
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1298
1582
|
function toPublic(s) {
|
|
1299
1583
|
return s ? { contact: s.contact, store: s.store, market: s.market } : null;
|
|
1300
1584
|
}
|
|
@@ -1350,19 +1634,23 @@ function createStorefront(config) {
|
|
|
1350
1634
|
const contactApi = storefrontApi.crm.contact;
|
|
1351
1635
|
function identify(params) {
|
|
1352
1636
|
if (params?.market !== void 0) apiConfig.market = params.market;
|
|
1637
|
+
const market = apiConfig.market;
|
|
1353
1638
|
const isBareCall = !params?.email && !params?.verify;
|
|
1354
1639
|
if (isBareCall && bareIdentifyPromise) return bareIdentifyPromise;
|
|
1355
|
-
const
|
|
1640
|
+
const run = async () => {
|
|
1356
1641
|
try {
|
|
1357
|
-
const result = await contactApi.
|
|
1358
|
-
market
|
|
1359
|
-
email: params
|
|
1360
|
-
|
|
1361
|
-
|
|
1642
|
+
const result = await (params?.verify ? contactApi.requestCode({
|
|
1643
|
+
market,
|
|
1644
|
+
email: params.email
|
|
1645
|
+
}) : contactApi.identify({
|
|
1646
|
+
market,
|
|
1647
|
+
email: params?.email
|
|
1648
|
+
}));
|
|
1362
1649
|
return {
|
|
1363
1650
|
contact: result.contact,
|
|
1364
1651
|
store: result.store,
|
|
1365
|
-
market: result.market
|
|
1652
|
+
market: result.market,
|
|
1653
|
+
verification_challenge: result.verification_challenge
|
|
1366
1654
|
};
|
|
1367
1655
|
} catch (err) {
|
|
1368
1656
|
const e = err;
|
|
@@ -1370,21 +1658,34 @@ function createStorefront(config) {
|
|
|
1370
1658
|
if (isBareCall && status === 401) {
|
|
1371
1659
|
updateSession(() => null);
|
|
1372
1660
|
const result = await contactApi.identify({
|
|
1373
|
-
market
|
|
1661
|
+
market
|
|
1374
1662
|
});
|
|
1375
1663
|
return {
|
|
1376
1664
|
contact: result.contact,
|
|
1377
1665
|
store: result.store,
|
|
1378
|
-
market: result.market
|
|
1666
|
+
market: result.market,
|
|
1667
|
+
verification_challenge: result.verification_challenge
|
|
1379
1668
|
};
|
|
1380
1669
|
}
|
|
1381
1670
|
throw err;
|
|
1382
1671
|
}
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1672
|
+
};
|
|
1673
|
+
const promise = identityTail.then(run);
|
|
1674
|
+
identityTail = promise.then(
|
|
1675
|
+
() => void 0,
|
|
1676
|
+
() => void 0
|
|
1677
|
+
);
|
|
1678
|
+
if (isBareCall) {
|
|
1679
|
+
bareIdentifyPromise = promise;
|
|
1680
|
+
void promise.then(
|
|
1681
|
+
() => {
|
|
1682
|
+
if (bareIdentifyPromise === promise) bareIdentifyPromise = null;
|
|
1683
|
+
},
|
|
1684
|
+
() => {
|
|
1685
|
+
if (bareIdentifyPromise === promise) bareIdentifyPromise = null;
|
|
1686
|
+
}
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1388
1689
|
return promise;
|
|
1389
1690
|
}
|
|
1390
1691
|
async function verify(params) {
|
|
@@ -1427,17 +1728,12 @@ function createStorefront(config) {
|
|
|
1427
1728
|
};
|
|
1428
1729
|
},
|
|
1429
1730
|
store: storefrontApi.store,
|
|
1430
|
-
cart: createCartController(storefrontApi.eshop.cart),
|
|
1431
1731
|
cms: storefrontApi.cms,
|
|
1432
1732
|
eshop: storefrontApi.eshop,
|
|
1433
1733
|
crm: storefrontApi.crm,
|
|
1434
1734
|
action: storefrontApi.action,
|
|
1435
1735
|
experiments: storefrontApi.experiments,
|
|
1436
1736
|
support: createStorefrontSupportApi(apiConfig),
|
|
1437
|
-
setStoreId: (storeId) => {
|
|
1438
|
-
apiConfig.storeId = storeId;
|
|
1439
|
-
bareIdentifyPromise = null;
|
|
1440
|
-
},
|
|
1441
1737
|
getStoreId: () => apiConfig.storeId,
|
|
1442
1738
|
setMarket: (key) => {
|
|
1443
1739
|
apiConfig.market = key;
|
|
@@ -1451,14 +1747,40 @@ function createStorefront(config) {
|
|
|
1451
1747
|
utils: createUtilitySurface(apiConfig)
|
|
1452
1748
|
};
|
|
1453
1749
|
}
|
|
1750
|
+
function createStorefront(config) {
|
|
1751
|
+
const sessionStorage = config.sessionStorage || defaultStorefrontSessionStorage() || void 0;
|
|
1752
|
+
const scopedConfig = {
|
|
1753
|
+
...config,
|
|
1754
|
+
sessionStorage
|
|
1755
|
+
};
|
|
1756
|
+
const client = createStorefrontClient(scopedConfig);
|
|
1757
|
+
Object.defineProperty(client, "forStore", {
|
|
1758
|
+
enumerable: true,
|
|
1759
|
+
configurable: false,
|
|
1760
|
+
writable: false,
|
|
1761
|
+
value: (storeId) => createStorefront({
|
|
1762
|
+
...scopedConfig,
|
|
1763
|
+
storeId,
|
|
1764
|
+
market: client.getMarket(),
|
|
1765
|
+
locale: client.getLocale()
|
|
1766
|
+
})
|
|
1767
|
+
});
|
|
1768
|
+
return client;
|
|
1769
|
+
}
|
|
1454
1770
|
|
|
1455
1771
|
// src/payments/stripe.ts
|
|
1772
|
+
async function defaultStripeLoader(publishableKey, options) {
|
|
1773
|
+
const { loadStripe } = await import('@stripe/stripe-js');
|
|
1774
|
+
return loadStripe(publishableKey, options);
|
|
1775
|
+
}
|
|
1456
1776
|
function normalizeCurrency(currency) {
|
|
1457
1777
|
return currency.trim().toLowerCase();
|
|
1458
1778
|
}
|
|
1459
|
-
async function createStripeConfirmationTokenController(config) {
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1779
|
+
async function createStripeConfirmationTokenController(config, stripeLoader = defaultStripeLoader) {
|
|
1780
|
+
const stripe = await stripeLoader(
|
|
1781
|
+
config.publishableKey,
|
|
1782
|
+
config.connectedAccountId ? { stripeAccount: config.connectedAccountId } : void 0
|
|
1783
|
+
);
|
|
1462
1784
|
if (!stripe) throw new Error("Stripe failed to initialize");
|
|
1463
1785
|
let elements = createElements(stripe, config);
|
|
1464
1786
|
let paymentElement = elements.create("payment", {
|
|
@@ -1547,11 +1869,11 @@ function firstLocalized(value, locale) {
|
|
|
1547
1869
|
}
|
|
1548
1870
|
return "";
|
|
1549
1871
|
}
|
|
1550
|
-
function
|
|
1872
|
+
function findBlock2(blocks, keys) {
|
|
1551
1873
|
return (blocks || []).find((block) => keys.includes(block.key)) || null;
|
|
1552
1874
|
}
|
|
1553
1875
|
function blockText(blocks, keys, locale) {
|
|
1554
|
-
const block =
|
|
1876
|
+
const block = findBlock2(blocks, keys);
|
|
1555
1877
|
if (!block) return "";
|
|
1556
1878
|
return firstLocalized(block.value, locale);
|
|
1557
1879
|
}
|
|
@@ -1567,15 +1889,32 @@ function providerName(provider, locale) {
|
|
|
1567
1889
|
function entitySlug(entity, locale) {
|
|
1568
1890
|
return entity.slug?.[locale] || entity.slug?.en || Object.values(entity.slug || {})[0] || entity.id;
|
|
1569
1891
|
}
|
|
1570
|
-
function priceForMarket(prices, market,
|
|
1571
|
-
const
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
}
|
|
1892
|
+
function priceForMarket(prices, market, marketCurrency) {
|
|
1893
|
+
const marketKey = market.trim();
|
|
1894
|
+
if (!marketKey) throw new Error("A market is required to select a product price");
|
|
1895
|
+
const currency = marketCurrency?.trim().toUpperCase();
|
|
1896
|
+
if (!currency) throw new Error(`Market ${marketKey} does not have an authoritative currency`);
|
|
1897
|
+
const marketPrices = prices.filter((candidate) => candidate.market === marketKey);
|
|
1898
|
+
if (marketPrices.length === 0) {
|
|
1899
|
+
throw new Error(`Product is not priced for market ${marketKey}`);
|
|
1900
|
+
}
|
|
1901
|
+
if (marketPrices.some(
|
|
1902
|
+
(candidate) => !Number.isSafeInteger(candidate.amount) || candidate.amount < 0 || candidate.currency.trim().toUpperCase() !== currency
|
|
1903
|
+
)) {
|
|
1904
|
+
throw new Error(`Product has an invalid price for market ${marketKey}`);
|
|
1905
|
+
}
|
|
1906
|
+
const authorizedPrices = marketPrices.filter((candidate) => candidate.contact_list_id);
|
|
1907
|
+
if (authorizedPrices.length > 0) {
|
|
1908
|
+
return authorizedPrices.reduce(
|
|
1909
|
+
(lowest, candidate) => candidate.amount < lowest.amount ? candidate : lowest
|
|
1910
|
+
);
|
|
1911
|
+
}
|
|
1912
|
+
const basePrices = marketPrices.filter((candidate) => !candidate.contact_list_id);
|
|
1913
|
+
if (basePrices.length !== 1) {
|
|
1914
|
+
throw new Error(`Product does not have one base price for market ${marketKey}`);
|
|
1915
|
+
}
|
|
1916
|
+
const price = basePrices[0];
|
|
1917
|
+
return price;
|
|
1579
1918
|
}
|
|
1580
1919
|
function availableStock(client, variant) {
|
|
1581
1920
|
const fromUtility = client.utils.getAvailableStock(variant);
|
|
@@ -1594,8 +1933,9 @@ function locationToAddress(location) {
|
|
|
1594
1933
|
street2: null
|
|
1595
1934
|
};
|
|
1596
1935
|
}
|
|
1597
|
-
function
|
|
1598
|
-
|
|
1936
|
+
function createFormEntry(formId, fields) {
|
|
1937
|
+
if (!formId.trim()) throw new Error("formId is required");
|
|
1938
|
+
return { form_id: formId, fields };
|
|
1599
1939
|
}
|
|
1600
1940
|
function toProductCheckoutItems(items) {
|
|
1601
1941
|
return items.map((item) => ({
|
|
@@ -1607,36 +1947,97 @@ function toProductCheckoutItems(items) {
|
|
|
1607
1947
|
}));
|
|
1608
1948
|
}
|
|
1609
1949
|
function toServiceCheckoutItems(items) {
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1950
|
+
return items.map((item) => ({
|
|
1951
|
+
type: "service",
|
|
1952
|
+
id: item.id,
|
|
1953
|
+
service_id: item.service_id,
|
|
1954
|
+
provider_id: item.provider_id,
|
|
1955
|
+
slots: [...item.slots].sort((a, b) => a.from - b.from),
|
|
1956
|
+
forms: item.forms
|
|
1957
|
+
}));
|
|
1958
|
+
}
|
|
1959
|
+
function formValueError(field, message) {
|
|
1960
|
+
return new Error(`Invalid value for form field '${field.key}': ${message}`);
|
|
1961
|
+
}
|
|
1962
|
+
function isValidGeoLocation(value) {
|
|
1963
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1964
|
+
const location = value;
|
|
1965
|
+
if (location.label !== void 0 && location.label !== null && typeof location.label !== "string") {
|
|
1966
|
+
return false;
|
|
1967
|
+
}
|
|
1968
|
+
const coordinates = location.coordinates;
|
|
1969
|
+
if (!coordinates || typeof coordinates !== "object") return false;
|
|
1970
|
+
return Number.isFinite(coordinates.lat) && Number.isFinite(coordinates.lon) && coordinates.lat >= -90 && coordinates.lat <= 90 && coordinates.lon >= -180 && coordinates.lon <= 180;
|
|
1971
|
+
}
|
|
1972
|
+
function buildFormField(field, value) {
|
|
1973
|
+
const common = { id: field.id, key: field.key };
|
|
1974
|
+
switch (field.type) {
|
|
1975
|
+
case "text":
|
|
1976
|
+
if (typeof value !== "string") throw formValueError(field, "expected text");
|
|
1977
|
+
if (field.required && value.trim().length === 0) throw formValueError(field, "required text is blank");
|
|
1978
|
+
return { ...common, type: "text", value };
|
|
1979
|
+
case "number":
|
|
1980
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1981
|
+
throw formValueError(field, "expected a finite number");
|
|
1982
|
+
}
|
|
1983
|
+
if (field.min !== null && field.min !== void 0 && value < field.min) {
|
|
1984
|
+
throw formValueError(field, `must be at least ${field.min}`);
|
|
1985
|
+
}
|
|
1986
|
+
if (field.max !== null && field.max !== void 0 && value > field.max) {
|
|
1987
|
+
throw formValueError(field, `must be at most ${field.max}`);
|
|
1988
|
+
}
|
|
1989
|
+
return { ...common, type: "number", value };
|
|
1990
|
+
case "boolean":
|
|
1991
|
+
if (typeof value !== "boolean") throw formValueError(field, "expected a boolean");
|
|
1992
|
+
return { ...common, type: "boolean", value };
|
|
1993
|
+
case "date":
|
|
1994
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value)) {
|
|
1995
|
+
throw formValueError(field, "expected an integer timestamp");
|
|
1996
|
+
}
|
|
1997
|
+
return { ...common, type: "date", value };
|
|
1998
|
+
case "geo_location":
|
|
1999
|
+
if (!isValidGeoLocation(value)) throw formValueError(field, "expected valid coordinates");
|
|
2000
|
+
return { ...common, type: "geo_location", value };
|
|
2001
|
+
case "select": {
|
|
2002
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
2003
|
+
throw formValueError(field, "expected a list of options");
|
|
2004
|
+
}
|
|
2005
|
+
const selected = value;
|
|
2006
|
+
if (field.required && selected.length === 0) throw formValueError(field, "at least one option is required");
|
|
2007
|
+
if (new Set(selected).size !== selected.length || selected.some((item) => !field.options.includes(item))) {
|
|
2008
|
+
throw formValueError(field, "contains an unknown or duplicate option");
|
|
2009
|
+
}
|
|
2010
|
+
return { ...common, type: "select", value: selected };
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
function isEmptyOptionalValue(field, value) {
|
|
2015
|
+
if (field.required) return false;
|
|
2016
|
+
if (field.type === "text") return value === "";
|
|
2017
|
+
if (field.type === "select") return Array.isArray(value) && value.length === 0;
|
|
2018
|
+
if (field.type === "geo_location") {
|
|
2019
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0);
|
|
2020
|
+
}
|
|
2021
|
+
return false;
|
|
2022
|
+
}
|
|
2023
|
+
function buildFormFields(schema, values) {
|
|
2024
|
+
const knownKeys = new Set(schema.map((field) => field.key));
|
|
2025
|
+
const unknownKey = Object.keys(values).find((key) => !knownKeys.has(key));
|
|
2026
|
+
if (unknownKey) throw new Error(`Form field '${unknownKey}' is not defined by the form schema`);
|
|
2027
|
+
const fields = [];
|
|
2028
|
+
for (const field of schema) {
|
|
2029
|
+
const value = values[field.key];
|
|
2030
|
+
if (value === void 0) {
|
|
2031
|
+
if (field.required) throw formValueError(field, "required value is missing");
|
|
1617
2032
|
continue;
|
|
1618
2033
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
id: item.id,
|
|
1622
|
-
service_id: item.service_id,
|
|
1623
|
-
provider_id: item.provider_id,
|
|
1624
|
-
slots: [slot],
|
|
1625
|
-
forms: item.forms || []
|
|
1626
|
-
});
|
|
2034
|
+
if (isEmptyOptionalValue(field, value)) continue;
|
|
2035
|
+
fields.push(buildFormField(field, value));
|
|
1627
2036
|
}
|
|
1628
|
-
return
|
|
1629
|
-
...item,
|
|
1630
|
-
slots: [...item.slots].sort((a, b) => a.from - b.from)
|
|
1631
|
-
}));
|
|
2037
|
+
return fields;
|
|
1632
2038
|
}
|
|
1633
|
-
function
|
|
1634
|
-
return
|
|
1635
|
-
id: block.id,
|
|
1636
|
-
key: block.key,
|
|
1637
|
-
type: block.type,
|
|
1638
|
-
value: block.value
|
|
1639
|
-
}));
|
|
2039
|
+
function createFormEntryFromValues(form, values) {
|
|
2040
|
+
return createFormEntry(form.id, buildFormFields(form.schema, values));
|
|
1640
2041
|
}
|
|
1641
2042
|
function getFormBlockType(field) {
|
|
1642
2043
|
if (field.key === "email") return "email";
|
|
@@ -1646,11 +2047,15 @@ function getFormBlockType(field) {
|
|
|
1646
2047
|
}
|
|
1647
2048
|
function getFormBlockValue(field) {
|
|
1648
2049
|
if (field.type === "boolean") return false;
|
|
1649
|
-
if (field.type === "
|
|
2050
|
+
if (field.type === "select") return [];
|
|
1650
2051
|
if (field.type === "geo_location") return {};
|
|
2052
|
+
if (field.type === "number" || field.type === "date") return void 0;
|
|
1651
2053
|
return "";
|
|
1652
2054
|
}
|
|
1653
2055
|
function formSchemaToBlock(field) {
|
|
2056
|
+
const min = field.type === "number" ? field.min : void 0;
|
|
2057
|
+
const max = field.type === "number" ? field.max : void 0;
|
|
2058
|
+
const options = field.type === "select" ? field.options : void 0;
|
|
1654
2059
|
return {
|
|
1655
2060
|
id: field.id,
|
|
1656
2061
|
key: field.key,
|
|
@@ -1658,9 +2063,9 @@ function formSchemaToBlock(field) {
|
|
|
1658
2063
|
properties: {
|
|
1659
2064
|
isRequired: field.required,
|
|
1660
2065
|
minValues: field.required ? 1 : 0,
|
|
1661
|
-
min
|
|
1662
|
-
max
|
|
1663
|
-
options
|
|
2066
|
+
min,
|
|
2067
|
+
max,
|
|
2068
|
+
options,
|
|
1664
2069
|
pattern: field.key === "email" ? "^.+@.+\\..+$" : field.key === "phone" ? "^.{6,20}$" : void 0
|
|
1665
2070
|
},
|
|
1666
2071
|
value: getFormBlockValue(field)
|
|
@@ -1703,15 +2108,13 @@ function createServiceInitialState() {
|
|
|
1703
2108
|
service: null,
|
|
1704
2109
|
availability: null,
|
|
1705
2110
|
providers: [],
|
|
2111
|
+
serviceProviders: [],
|
|
1706
2112
|
selectedProviderId: null,
|
|
1707
2113
|
currentMonth: new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1),
|
|
1708
2114
|
calendar: [],
|
|
1709
2115
|
selectedDate: null,
|
|
1710
|
-
startDate: null,
|
|
1711
|
-
endDate: null,
|
|
1712
2116
|
slots: [],
|
|
1713
2117
|
selectedSlot: null,
|
|
1714
|
-
cart: [],
|
|
1715
2118
|
timezone: typeof window !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : "UTC",
|
|
1716
2119
|
tzGroups: {},
|
|
1717
2120
|
loading: false,
|
|
@@ -1721,7 +2124,6 @@ function createServiceInitialState() {
|
|
|
1721
2124
|
quoteError: null,
|
|
1722
2125
|
currency: null,
|
|
1723
2126
|
dateTimeConfirmed: false,
|
|
1724
|
-
isMultiDay: false,
|
|
1725
2127
|
availablePaymentMethods: [],
|
|
1726
2128
|
cartId: null,
|
|
1727
2129
|
promoCode: null
|
|
@@ -1742,8 +2144,9 @@ function normalizeTimezoneGroups(groups) {
|
|
|
1742
2144
|
function firstFiniteNumber(...values) {
|
|
1743
2145
|
return values.find((value) => typeof value === "number" && Number.isFinite(value));
|
|
1744
2146
|
}
|
|
1745
|
-
function
|
|
2147
|
+
function initializeStore(config) {
|
|
1746
2148
|
const client = createStorefront(config);
|
|
2149
|
+
const formClients = /* @__PURE__ */ new Map([[client.getStoreId(), client]]);
|
|
1747
2150
|
const session = nanostores.atom(client.session);
|
|
1748
2151
|
const locale = nanostores.atom(config.locale || client.getLocale());
|
|
1749
2152
|
const market_key = nanostores.atom(config.market || client.getMarket());
|
|
@@ -1754,7 +2157,10 @@ function initialize(config) {
|
|
|
1754
2157
|
const store = value?.store;
|
|
1755
2158
|
const methods = value?.market?.payment_methods || [];
|
|
1756
2159
|
const hasCreditCard = methods.some((method) => method.type === "credit_card");
|
|
1757
|
-
return {
|
|
2160
|
+
return {
|
|
2161
|
+
provider: store?.payment || null,
|
|
2162
|
+
enabled: hasCreditCard && !!store?.payment
|
|
2163
|
+
};
|
|
1758
2164
|
});
|
|
1759
2165
|
const cart = nanostores.atom(null);
|
|
1760
2166
|
const product_items = nanostores.atom([]);
|
|
@@ -1795,7 +2201,10 @@ function initialize(config) {
|
|
|
1795
2201
|
);
|
|
1796
2202
|
const service_item_count = nanostores.computed(
|
|
1797
2203
|
[cart, service_items],
|
|
1798
|
-
(cartValue, items) => Math.max(
|
|
2204
|
+
(cartValue, items) => Math.max(
|
|
2205
|
+
rawServiceItemCount(cartValue),
|
|
2206
|
+
items.reduce((total, item) => total + Math.max(1, item.slots.length), 0)
|
|
2207
|
+
)
|
|
1799
2208
|
);
|
|
1800
2209
|
const item_count = nanostores.computed(
|
|
1801
2210
|
[cart, product_item_count, service_item_count],
|
|
@@ -1835,8 +2244,15 @@ function initialize(config) {
|
|
|
1835
2244
|
error: null
|
|
1836
2245
|
});
|
|
1837
2246
|
const service_state = nanostores.map(createServiceInitialState());
|
|
1838
|
-
const
|
|
1839
|
-
const
|
|
2247
|
+
const service_form_definitions = /* @__PURE__ */ new Map();
|
|
2248
|
+
const service_form_state = nanostores.map({
|
|
2249
|
+
provider_id: null,
|
|
2250
|
+
groups: [],
|
|
2251
|
+
loading: false,
|
|
2252
|
+
error: null
|
|
2253
|
+
});
|
|
2254
|
+
const service_form_groups = nanostores.computed(service_form_state, (state) => state.groups);
|
|
2255
|
+
const service_form_blocks = nanostores.computed(service_form_groups, (groups) => groups.flatMap((group) => group.blocks));
|
|
1840
2256
|
client.onAuthStateChanged((value) => session.set(value));
|
|
1841
2257
|
currency.subscribe((value) => service_state.setKey("currency", value));
|
|
1842
2258
|
session.subscribe((value) => {
|
|
@@ -1851,25 +2267,30 @@ function initialize(config) {
|
|
|
1851
2267
|
function currentLocale() {
|
|
1852
2268
|
return locale.get() || client.getLocale() || "en";
|
|
1853
2269
|
}
|
|
1854
|
-
function
|
|
1855
|
-
|
|
2270
|
+
function clientForStore(storeId) {
|
|
2271
|
+
const existing = formClients.get(storeId);
|
|
2272
|
+
if (existing) return existing;
|
|
2273
|
+
const scoped = client.forStore(storeId);
|
|
2274
|
+
formClients.set(storeId, scoped);
|
|
2275
|
+
return scoped;
|
|
1856
2276
|
}
|
|
1857
2277
|
function currentStripePublishableKey() {
|
|
1858
2278
|
const provider = payment_config.get()?.provider;
|
|
1859
|
-
return provider?.publishable_key ||
|
|
2279
|
+
return provider?.publishable_key || null;
|
|
2280
|
+
}
|
|
2281
|
+
function currentStripeConnectedAccountId() {
|
|
2282
|
+
const provider = payment_config.get()?.provider;
|
|
2283
|
+
return provider?.connected_account_id || null;
|
|
1860
2284
|
}
|
|
1861
2285
|
function currentPaymentAmount() {
|
|
1862
2286
|
return Math.max(
|
|
1863
2287
|
0,
|
|
1864
|
-
firstFiniteNumber(
|
|
1865
|
-
quote.get()?.charge_amount,
|
|
1866
|
-
quote.get()?.payment?.total,
|
|
1867
|
-
cart.get()?.quote_snapshot?.charge_amount,
|
|
1868
|
-
cart.get()?.quote_snapshot?.payment?.total,
|
|
1869
|
-
cart.get()?.quote_snapshot?.total
|
|
1870
|
-
) ?? 0
|
|
2288
|
+
firstFiniteNumber(quote.get()?.charge_amount, cart.get()?.quote_snapshot?.charge_amount, cart.get()?.quote_snapshot?.total) ?? 0
|
|
1871
2289
|
);
|
|
1872
2290
|
}
|
|
2291
|
+
function currentPaymentCurrency() {
|
|
2292
|
+
return quote.get()?.money?.currency?.trim() || cart.get()?.quote_snapshot?.money?.currency?.trim() || null;
|
|
2293
|
+
}
|
|
1873
2294
|
function setPaymentController(controller) {
|
|
1874
2295
|
const current = payment_controller.get();
|
|
1875
2296
|
if (current && current !== controller) {
|
|
@@ -1886,10 +2307,24 @@ function initialize(config) {
|
|
|
1886
2307
|
if (!publishableKey) {
|
|
1887
2308
|
throw new Error("Stripe publishable key is required to mount card payment");
|
|
1888
2309
|
}
|
|
2310
|
+
const hasExplicitAmount = options.amount !== void 0;
|
|
2311
|
+
const hasExplicitCurrency = Boolean(options.currency?.trim());
|
|
2312
|
+
if (hasExplicitAmount !== hasExplicitCurrency) {
|
|
2313
|
+
throw new Error("Stripe amount and currency must be supplied together");
|
|
2314
|
+
}
|
|
2315
|
+
const amount = hasExplicitAmount ? options.amount : currentPaymentAmount();
|
|
2316
|
+
if (!Number.isSafeInteger(amount) || amount <= 0) {
|
|
2317
|
+
throw new Error("A positive minor-unit payment amount is required to mount card payment");
|
|
2318
|
+
}
|
|
2319
|
+
const paymentCurrency = hasExplicitCurrency ? options.currency.trim() : currentPaymentCurrency();
|
|
2320
|
+
if (!paymentCurrency || !/^[a-z]{3}$/i.test(paymentCurrency)) {
|
|
2321
|
+
throw new Error("An explicit three-letter payment currency is required to mount card payment");
|
|
2322
|
+
}
|
|
1889
2323
|
const controller = await createStripeConfirmationTokenController({
|
|
1890
2324
|
publishableKey,
|
|
1891
|
-
|
|
1892
|
-
|
|
2325
|
+
connectedAccountId: options.connectedAccountId || currentStripeConnectedAccountId() || void 0,
|
|
2326
|
+
amount,
|
|
2327
|
+
currency: paymentCurrency,
|
|
1893
2328
|
...options.appearance ? { appearance: options.appearance } : {}
|
|
1894
2329
|
});
|
|
1895
2330
|
controller.mount(target);
|
|
@@ -1915,10 +2350,24 @@ function initialize(config) {
|
|
|
1915
2350
|
}
|
|
1916
2351
|
async function identify(params = {}) {
|
|
1917
2352
|
if (params.market) setMarket(params.market);
|
|
1918
|
-
const result = await client.identify({
|
|
1919
|
-
|
|
2353
|
+
const result = await client.identify({
|
|
2354
|
+
...params,
|
|
2355
|
+
market: params.market || currentMarketKey()
|
|
2356
|
+
});
|
|
2357
|
+
session.set({
|
|
2358
|
+
contact: result.contact,
|
|
2359
|
+
store: result.store,
|
|
2360
|
+
market: result.market
|
|
2361
|
+
});
|
|
1920
2362
|
return result;
|
|
1921
2363
|
}
|
|
2364
|
+
async function identifyContactEmailIfMissing(email) {
|
|
2365
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
2366
|
+
if (!normalizedEmail) throw new Error("Contact email is required");
|
|
2367
|
+
const current = session.get();
|
|
2368
|
+
if (current?.contact.email === normalizedEmail) return current;
|
|
2369
|
+
return identify({ email: normalizedEmail });
|
|
2370
|
+
}
|
|
1922
2371
|
function setMarket(key) {
|
|
1923
2372
|
market_key.set(key);
|
|
1924
2373
|
client.setMarket(key);
|
|
@@ -1943,7 +2392,9 @@ function initialize(config) {
|
|
|
1943
2392
|
const refreshRevision = cartWriteRevision;
|
|
1944
2393
|
cartRequest = (async () => {
|
|
1945
2394
|
await ensureSession();
|
|
1946
|
-
const response = await client.cart.
|
|
2395
|
+
const response = await client.eshop.cart.current({
|
|
2396
|
+
market: currentMarketKey()
|
|
2397
|
+
});
|
|
1947
2398
|
await applyCartResponse(response, { ifRevision: refreshRevision });
|
|
1948
2399
|
return response;
|
|
1949
2400
|
})();
|
|
@@ -1957,9 +2408,9 @@ function initialize(config) {
|
|
|
1957
2408
|
cart_status.setKey("loading", false);
|
|
1958
2409
|
}
|
|
1959
2410
|
}
|
|
1960
|
-
async function buildProductCartItem(item, source) {
|
|
2411
|
+
async function buildProductCartItem(item, source, productHint) {
|
|
1961
2412
|
try {
|
|
1962
|
-
const product = await client.eshop.product.get({ id: item.product_id });
|
|
2413
|
+
const product = productHint?.id === item.product_id ? productHint : await client.eshop.product.get({ id: item.product_id });
|
|
1963
2414
|
const variant = product.variants.find((candidate) => candidate.id === item.variant_id);
|
|
1964
2415
|
if (!variant) return null;
|
|
1965
2416
|
return {
|
|
@@ -1970,7 +2421,7 @@ function initialize(config) {
|
|
|
1970
2421
|
product_slug: entitySlug(product, currentLocale()),
|
|
1971
2422
|
variant_attributes: variant.attributes,
|
|
1972
2423
|
requires_shipping: variant.requires_shipping !== false,
|
|
1973
|
-
price: priceForMarket(variant.prices, currentMarketKey(),
|
|
2424
|
+
price: priceForMarket(variant.prices, currentMarketKey(), market.get()?.currency),
|
|
1974
2425
|
quantity: item.quantity,
|
|
1975
2426
|
added_at: source.created_at ? source.created_at * 1e3 : Date.now(),
|
|
1976
2427
|
max_stock: availableStock(client, variant)
|
|
@@ -1992,18 +2443,15 @@ function initialize(config) {
|
|
|
1992
2443
|
provider = await client.eshop.provider.get({ id: item.provider_id });
|
|
1993
2444
|
} catch {
|
|
1994
2445
|
}
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
provider_name: provider ? providerName(provider, currentLocale()) : item.provider_id
|
|
2005
|
-
});
|
|
2006
|
-
}
|
|
2446
|
+
rows.push({
|
|
2447
|
+
id: item.id || createId("service"),
|
|
2448
|
+
service_id: item.service_id,
|
|
2449
|
+
provider_id: item.provider_id,
|
|
2450
|
+
slots: item.slots,
|
|
2451
|
+
forms: item.forms || [],
|
|
2452
|
+
service_name: service ? serviceName(service, currentLocale()) : item.service_id,
|
|
2453
|
+
provider_name: provider ? providerName(provider, currentLocale()) : item.provider_id
|
|
2454
|
+
});
|
|
2007
2455
|
}
|
|
2008
2456
|
return rows;
|
|
2009
2457
|
}
|
|
@@ -2018,11 +2466,9 @@ function initialize(config) {
|
|
|
2018
2466
|
quote.set(response.quote_snapshot || null);
|
|
2019
2467
|
const items = response.items || [];
|
|
2020
2468
|
const products = await Promise.all(
|
|
2021
|
-
items.filter((item) => item.type === "product").map((item) => buildProductCartItem(item, response))
|
|
2022
|
-
);
|
|
2023
|
-
const services = await buildServiceCartItems(
|
|
2024
|
-
items.filter((item) => item.type === "service")
|
|
2469
|
+
items.filter((item) => item.type === "product").map((item) => buildProductCartItem(item, response, options.productHint))
|
|
2025
2470
|
);
|
|
2471
|
+
const services = await buildServiceCartItems(items.filter((item) => item.type === "service"));
|
|
2026
2472
|
product_items.set(products.filter((item) => item !== null));
|
|
2027
2473
|
service_items.set(services);
|
|
2028
2474
|
return response;
|
|
@@ -2038,21 +2484,17 @@ function initialize(config) {
|
|
|
2038
2484
|
cart_status.setKey("error", null);
|
|
2039
2485
|
try {
|
|
2040
2486
|
const current = cart.get() || await ensureCart();
|
|
2041
|
-
const response = await client.cart.update({
|
|
2487
|
+
const response = await client.eshop.cart.update({
|
|
2042
2488
|
id: current.id,
|
|
2043
2489
|
market: currentMarketKey(),
|
|
2044
2490
|
items: checkoutItems(input),
|
|
2045
|
-
shipping_address: input.shipping_address
|
|
2046
|
-
billing_address: input.billing_address
|
|
2047
|
-
forms:
|
|
2048
|
-
promo_code: input.promo_code === void 0 ? promo_code.get() || void 0 : input.promo_code
|
|
2049
|
-
payment_method_key: input.payment_method_key
|
|
2050
|
-
shipping_method_id: input.shipping_method_id
|
|
2491
|
+
shipping_address: input.shipping_address,
|
|
2492
|
+
billing_address: input.billing_address,
|
|
2493
|
+
forms: input.forms,
|
|
2494
|
+
promo_code: input.promo_code === null ? "" : input.promo_code === void 0 ? promo_code.get() || void 0 : input.promo_code,
|
|
2495
|
+
payment_method_key: input.payment_method_key === null ? "" : input.payment_method_key,
|
|
2496
|
+
shipping_method_id: input.shipping_method_id === null ? "" : input.shipping_method_id === void 0 ? cart_status.get().selected_shipping_method_id || void 0 : input.shipping_method_id
|
|
2051
2497
|
});
|
|
2052
|
-
if (input.promo_code !== void 0) promo_code.set(input.promo_code);
|
|
2053
|
-
if (input.shipping_method_id !== void 0) {
|
|
2054
|
-
cart_status.setKey("selected_shipping_method_id", input.shipping_method_id);
|
|
2055
|
-
}
|
|
2056
2498
|
await applyCartResponse(response, { ifRevision: writeRevision });
|
|
2057
2499
|
return response;
|
|
2058
2500
|
} catch (error) {
|
|
@@ -2067,7 +2509,7 @@ function initialize(config) {
|
|
|
2067
2509
|
const writeRevision = nextCartWriteRevision();
|
|
2068
2510
|
try {
|
|
2069
2511
|
const current = cart.get() || await ensureCart();
|
|
2070
|
-
const response = await client.cart.addItem({
|
|
2512
|
+
const response = await client.eshop.cart.addItem({
|
|
2071
2513
|
id: current.id,
|
|
2072
2514
|
item: {
|
|
2073
2515
|
type: "product",
|
|
@@ -2076,7 +2518,7 @@ function initialize(config) {
|
|
|
2076
2518
|
quantity
|
|
2077
2519
|
}
|
|
2078
2520
|
});
|
|
2079
|
-
await applyCartResponse(response, { ifRevision: writeRevision });
|
|
2521
|
+
await applyCartResponse(response, { ifRevision: writeRevision, productHint: product });
|
|
2080
2522
|
return response;
|
|
2081
2523
|
} catch (error) {
|
|
2082
2524
|
cart_status.setKey("error", readErrorMessage(error, "Failed to add product to cart."));
|
|
@@ -2099,11 +2541,9 @@ function initialize(config) {
|
|
|
2099
2541
|
product_items.set(product_items.get().filter((candidate) => candidate.id !== itemId));
|
|
2100
2542
|
const current = cart.get();
|
|
2101
2543
|
if (!current || !item) return null;
|
|
2102
|
-
const response = await client.cart.removeItem({
|
|
2544
|
+
const response = await client.eshop.cart.removeItem({
|
|
2103
2545
|
id: current.id,
|
|
2104
|
-
item_id: item.id
|
|
2105
|
-
product_id: item.product_id,
|
|
2106
|
-
variant_id: item.variant_id
|
|
2546
|
+
item_id: item.id
|
|
2107
2547
|
});
|
|
2108
2548
|
await applyCartResponse(response, { ifRevision: writeRevision });
|
|
2109
2549
|
return response;
|
|
@@ -2125,7 +2565,7 @@ function initialize(config) {
|
|
|
2125
2565
|
const current = cart.get();
|
|
2126
2566
|
clearLocalCart();
|
|
2127
2567
|
if (!current) return null;
|
|
2128
|
-
const response = await client.cart.clear({ id: current.id });
|
|
2568
|
+
const response = await client.eshop.cart.clear({ id: current.id });
|
|
2129
2569
|
await applyCartResponse(response, { ifRevision: writeRevision });
|
|
2130
2570
|
return response;
|
|
2131
2571
|
}
|
|
@@ -2146,7 +2586,7 @@ function initialize(config) {
|
|
|
2146
2586
|
cart_status.setKey("quote_error", null);
|
|
2147
2587
|
try {
|
|
2148
2588
|
const current = await syncCart(input);
|
|
2149
|
-
const response = await client.cart.quote({ id: current.id });
|
|
2589
|
+
const response = await client.eshop.cart.quote({ id: current.id });
|
|
2150
2590
|
quote.set(response);
|
|
2151
2591
|
return response;
|
|
2152
2592
|
} catch (error) {
|
|
@@ -2164,23 +2604,17 @@ function initialize(config) {
|
|
|
2164
2604
|
try {
|
|
2165
2605
|
const current = await syncCart(input);
|
|
2166
2606
|
const quoteValue = quote.get();
|
|
2167
|
-
const paymentMethodKey = input.payment_method_key || current.payment_method_key || quoteValue?.
|
|
2168
|
-
let chargeAmount = firstFiniteNumber(
|
|
2169
|
-
quoteValue?.charge_amount,
|
|
2170
|
-
quoteValue?.payment?.total,
|
|
2171
|
-
current.quote_snapshot?.charge_amount,
|
|
2172
|
-
current.quote_snapshot?.payment?.total
|
|
2173
|
-
);
|
|
2607
|
+
const paymentMethodKey = input.payment_method_key || current.payment_method_key || quoteValue?.money?.payment_method_key || void 0;
|
|
2608
|
+
let chargeAmount = firstFiniteNumber(quoteValue?.charge_amount, current.quote_snapshot?.charge_amount);
|
|
2174
2609
|
if (paymentMethodKey === "credit_card" && chargeAmount === void 0) {
|
|
2175
|
-
const latestQuote = await client.cart.quote({ id: current.id });
|
|
2610
|
+
const latestQuote = await client.eshop.cart.quote({ id: current.id });
|
|
2176
2611
|
quote.set(latestQuote);
|
|
2177
|
-
chargeAmount = firstFiniteNumber(
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
);
|
|
2612
|
+
chargeAmount = firstFiniteNumber(latestQuote.charge_amount, latestQuote.total);
|
|
2613
|
+
}
|
|
2614
|
+
if (paymentMethodKey === "credit_card" && (typeof chargeAmount !== "number" || !Number.isSafeInteger(chargeAmount) || chargeAmount < 0)) {
|
|
2615
|
+
throw new Error("Card checkout requires a non-negative integer charge amount in minor units");
|
|
2182
2616
|
}
|
|
2183
|
-
const needsConfirmationToken = paymentMethodKey === "credit_card" &&
|
|
2617
|
+
const needsConfirmationToken = paymentMethodKey === "credit_card" && typeof chargeAmount === "number" && chargeAmount > 0;
|
|
2184
2618
|
let confirmationTokenId;
|
|
2185
2619
|
let returnUrl = input.return_url;
|
|
2186
2620
|
const paymentController = input.payment ?? payment_controller.get();
|
|
@@ -2194,7 +2628,7 @@ function initialize(config) {
|
|
|
2194
2628
|
confirmationTokenId = token.confirmation_token_id;
|
|
2195
2629
|
returnUrl = token.return_url || returnUrl;
|
|
2196
2630
|
}
|
|
2197
|
-
const response = await client.cart.checkout({
|
|
2631
|
+
const response = await client.eshop.cart.checkout({
|
|
2198
2632
|
id: current.id,
|
|
2199
2633
|
payment_method_key: paymentMethodKey,
|
|
2200
2634
|
confirmation_token_id: confirmationTokenId,
|
|
@@ -2213,8 +2647,8 @@ function initialize(config) {
|
|
|
2213
2647
|
service_items: input.service_items || service_items.get(),
|
|
2214
2648
|
shipping_address: input.shipping_address || null,
|
|
2215
2649
|
billing_address: input.billing_address || null,
|
|
2216
|
-
total:
|
|
2217
|
-
currency:
|
|
2650
|
+
total: response.payment.amount,
|
|
2651
|
+
currency: response.payment.currency,
|
|
2218
2652
|
payment_method_key: paymentMethodKey || null,
|
|
2219
2653
|
created_at: Date.now()
|
|
2220
2654
|
};
|
|
@@ -2232,7 +2666,7 @@ function initialize(config) {
|
|
|
2232
2666
|
}
|
|
2233
2667
|
function serviceCalendar() {
|
|
2234
2668
|
const state = service_state.get();
|
|
2235
|
-
const { currentMonth, selectedDate,
|
|
2669
|
+
const { currentMonth, selectedDate, availability, selectedProviderId } = state;
|
|
2236
2670
|
const year = currentMonth.getFullYear();
|
|
2237
2671
|
const monthIndex = currentMonth.getMonth();
|
|
2238
2672
|
const first = new Date(year, monthIndex, 1);
|
|
@@ -2255,18 +2689,12 @@ function initialize(config) {
|
|
|
2255
2689
|
for (let day = 1; day <= last.getDate(); day++) {
|
|
2256
2690
|
const date = new Date(year, monthIndex, day);
|
|
2257
2691
|
const iso = `${year}-${String(monthIndex + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
2258
|
-
const isSelected = iso === selectedDate || iso === startDate || iso === endDate;
|
|
2259
|
-
let isInRange = false;
|
|
2260
|
-
if (startDate && endDate) {
|
|
2261
|
-
const time = date.getTime();
|
|
2262
|
-
isInRange = time > new Date(startDate).getTime() && time < new Date(endDate).getTime();
|
|
2263
|
-
}
|
|
2264
2692
|
cells.push({
|
|
2265
2693
|
date,
|
|
2266
2694
|
iso,
|
|
2267
2695
|
available: hasAvailableSlotsForDate(availability, iso, selectedProviderId),
|
|
2268
|
-
isSelected,
|
|
2269
|
-
isInRange,
|
|
2696
|
+
isSelected: iso === selectedDate,
|
|
2697
|
+
isInRange: false,
|
|
2270
2698
|
isToday: date.getTime() === today.getTime(),
|
|
2271
2699
|
blank: false
|
|
2272
2700
|
});
|
|
@@ -2303,46 +2731,38 @@ function initialize(config) {
|
|
|
2303
2731
|
})
|
|
2304
2732
|
}));
|
|
2305
2733
|
}
|
|
2306
|
-
function toServiceCartItem(
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2734
|
+
function toServiceCartItem(slots, forms = []) {
|
|
2735
|
+
const orderedSlots = [...slots].sort((left, right) => left.from - right.from || left.to - right.to);
|
|
2736
|
+
const [first] = orderedSlots;
|
|
2737
|
+
if (!first) throw new Error("At least one service slot is required");
|
|
2738
|
+
if (orderedSlots.some((slot) => slot.serviceId !== first.serviceId || slot.providerId !== first.providerId)) {
|
|
2739
|
+
throw new Error("A booking can only contain slots for one service and provider");
|
|
2740
|
+
}
|
|
2741
|
+
if (orderedSlots.some((slot) => !Number.isSafeInteger(slot.from) || !Number.isSafeInteger(slot.to) || slot.from >= slot.to)) {
|
|
2742
|
+
throw new Error("A booking contains an invalid service slot");
|
|
2743
|
+
}
|
|
2744
|
+
for (let index = 1; index < orderedSlots.length; index += 1) {
|
|
2745
|
+
if (orderedSlots[index - 1].to !== orderedSlots[index].from) {
|
|
2746
|
+
throw new Error("A multi-slot booking must contain adjacent slots");
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2321
2749
|
return {
|
|
2322
|
-
id:
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
isMultiDay: item.is_multi_day
|
|
2750
|
+
id: createId("booking"),
|
|
2751
|
+
service_id: first.serviceId,
|
|
2752
|
+
provider_id: first.providerId,
|
|
2753
|
+
slots: orderedSlots.map(({ from, to }) => ({ from, to })),
|
|
2754
|
+
forms,
|
|
2755
|
+
service_name: first.serviceName,
|
|
2756
|
+
date_text: first.dateText,
|
|
2757
|
+
time_text: first.timeText,
|
|
2758
|
+
is_multi_day: orderedSlots.length > 1
|
|
2332
2759
|
};
|
|
2333
2760
|
}
|
|
2334
|
-
function
|
|
2335
|
-
const next = items.map(fromServiceCartItem);
|
|
2336
|
-
const current = service_state.get().cart;
|
|
2337
|
-
if (JSON.stringify(current) !== JSON.stringify(next)) {
|
|
2338
|
-
service_state.setKey("cart", next);
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2341
|
-
async function syncServiceCart(slots) {
|
|
2761
|
+
async function syncServiceCart(items) {
|
|
2342
2762
|
try {
|
|
2343
2763
|
return await syncCart({
|
|
2344
2764
|
product_items: product_items.get(),
|
|
2345
|
-
service_items:
|
|
2765
|
+
service_items: items
|
|
2346
2766
|
});
|
|
2347
2767
|
} catch (error) {
|
|
2348
2768
|
service_state.setKey("quoteError", readErrorMessage(error, "Failed to sync service cart."));
|
|
@@ -2359,18 +2779,22 @@ function initialize(config) {
|
|
|
2359
2779
|
const service_can_proceed = nanostores.computed(service_state, (state) => {
|
|
2360
2780
|
const step = serviceCurrentStepName();
|
|
2361
2781
|
if (step === "datetime") {
|
|
2362
|
-
return
|
|
2782
|
+
return !!(state.selectedDate && state.selectedSlot);
|
|
2363
2783
|
}
|
|
2364
2784
|
if (step === "review") return true;
|
|
2365
2785
|
return false;
|
|
2366
2786
|
});
|
|
2367
2787
|
const service_month_year = nanostores.computed(
|
|
2368
2788
|
service_state,
|
|
2369
|
-
(state) => state.currentMonth.toLocaleString(void 0, {
|
|
2789
|
+
(state) => state.currentMonth.toLocaleString(void 0, {
|
|
2790
|
+
month: "long",
|
|
2791
|
+
year: "numeric"
|
|
2792
|
+
})
|
|
2370
2793
|
);
|
|
2371
|
-
const service_chain_start = nanostores.computed(
|
|
2372
|
-
|
|
2373
|
-
|
|
2794
|
+
const service_chain_start = nanostores.computed(service_items, (items) => {
|
|
2795
|
+
const slots = items.flatMap((item) => item.slots);
|
|
2796
|
+
if (!slots.length) return null;
|
|
2797
|
+
return Math.max(...slots.map((slot) => slot.to));
|
|
2374
2798
|
});
|
|
2375
2799
|
const service_total_steps = nanostores.computed(service_state, (state) => state.service ? 2 : 0);
|
|
2376
2800
|
const service_steps = nanostores.computed(service_state, () => ({
|
|
@@ -2385,40 +2809,101 @@ function initialize(config) {
|
|
|
2385
2809
|
});
|
|
2386
2810
|
function formatServiceDateDisplay(value) {
|
|
2387
2811
|
if (!value) return "";
|
|
2388
|
-
return new Date(value).toLocaleDateString(void 0, {
|
|
2812
|
+
return new Date(value).toLocaleDateString(void 0, {
|
|
2813
|
+
month: "short",
|
|
2814
|
+
day: "numeric"
|
|
2815
|
+
});
|
|
2389
2816
|
}
|
|
2390
|
-
function
|
|
2391
|
-
|
|
2817
|
+
function configuredServiceFormIds(relationship) {
|
|
2818
|
+
const formIds = relationship.forms.map((entry) => entry.form_id.trim());
|
|
2819
|
+
if (formIds.some((formId) => !formId) || new Set(formIds).size !== formIds.length) {
|
|
2820
|
+
throw new Error(`Service provider ${relationship.provider_id} has blank or duplicate configured form IDs`);
|
|
2821
|
+
}
|
|
2822
|
+
return formIds;
|
|
2823
|
+
}
|
|
2824
|
+
function resolveServiceProvider(state, providerId) {
|
|
2825
|
+
const serviceId = state.service?.id;
|
|
2826
|
+
if (!serviceId) return null;
|
|
2827
|
+
const relationships = state.serviceProviders.filter((relationship) => relationship.service_id === serviceId);
|
|
2828
|
+
const targetProviderId = providerId ?? state.selectedSlot?.providerId ?? state.selectedProviderId;
|
|
2829
|
+
if (targetProviderId) {
|
|
2830
|
+
return relationships.find((relationship) => relationship.provider_id === targetProviderId) || null;
|
|
2831
|
+
}
|
|
2832
|
+
return relationships.length === 1 ? relationships[0] : null;
|
|
2833
|
+
}
|
|
2834
|
+
function clearServiceFormState(error = null, loading = false) {
|
|
2835
|
+
service_form_state.set({
|
|
2836
|
+
provider_id: null,
|
|
2837
|
+
groups: [],
|
|
2838
|
+
loading,
|
|
2839
|
+
error
|
|
2840
|
+
});
|
|
2392
2841
|
}
|
|
2393
|
-
function
|
|
2394
|
-
const
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2842
|
+
function activateServiceProviderForms(providerId, reset = false) {
|
|
2843
|
+
const relationship = resolveServiceProvider(service_state.get(), providerId);
|
|
2844
|
+
if (!relationship) {
|
|
2845
|
+
clearServiceFormState();
|
|
2846
|
+
return null;
|
|
2847
|
+
}
|
|
2848
|
+
const formIds = configuredServiceFormIds(relationship);
|
|
2849
|
+
const current = service_form_state.get();
|
|
2850
|
+
const currentFormIds = current.groups.map((group) => group.form.id);
|
|
2851
|
+
if (!reset && current.provider_id === relationship.provider_id && currentFormIds.length === formIds.length && currentFormIds.every((formId, index) => formId === formIds[index])) {
|
|
2852
|
+
if (current.error) service_form_state.setKey("error", null);
|
|
2853
|
+
return relationship;
|
|
2400
2854
|
}
|
|
2401
|
-
|
|
2855
|
+
const groups = formIds.map((formId) => {
|
|
2856
|
+
const form = service_form_definitions.get(formId);
|
|
2857
|
+
if (!form) throw new Error(`Configured booking form '${formId}' was not loaded`);
|
|
2858
|
+
return {
|
|
2859
|
+
form,
|
|
2860
|
+
blocks: form.schema.map(formSchemaToBlock)
|
|
2861
|
+
};
|
|
2862
|
+
});
|
|
2863
|
+
service_form_state.set({
|
|
2864
|
+
provider_id: relationship.provider_id,
|
|
2865
|
+
groups,
|
|
2866
|
+
loading: false,
|
|
2867
|
+
error: null
|
|
2868
|
+
});
|
|
2869
|
+
return relationship;
|
|
2402
2870
|
}
|
|
2403
|
-
async function
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2871
|
+
async function loadServiceFormDefinitions(relationships) {
|
|
2872
|
+
const formIds = [
|
|
2873
|
+
...new Set(relationships.flatMap((relationship) => configuredServiceFormIds(relationship)))
|
|
2874
|
+
];
|
|
2875
|
+
if (formIds.length === 0) return;
|
|
2876
|
+
const forms = await Promise.all(formIds.map((formId) => loadForm({ id: formId })));
|
|
2877
|
+
for (let index = 0; index < forms.length; index += 1) {
|
|
2878
|
+
const form = forms[index];
|
|
2879
|
+
const formId = formIds[index];
|
|
2880
|
+
if (form.id !== formId) {
|
|
2881
|
+
throw new Error(`Configured booking form '${formId}' resolved to '${form.id}'`);
|
|
2882
|
+
}
|
|
2412
2883
|
}
|
|
2884
|
+
for (const form of forms) service_form_definitions.set(form.id, form);
|
|
2885
|
+
}
|
|
2886
|
+
function configuredServiceFormEntries(relationship) {
|
|
2887
|
+
const formIds = configuredServiceFormIds(relationship);
|
|
2888
|
+
if (formIds.length === 0) return [];
|
|
2889
|
+
activateServiceProviderForms(relationship.provider_id);
|
|
2890
|
+
const state = service_form_state.get();
|
|
2891
|
+
if (state.provider_id !== relationship.provider_id || state.groups.length !== formIds.length || state.groups.some((group, index) => group.form.id !== formIds[index])) {
|
|
2892
|
+
throw new Error(`Booking forms are not ready for provider ${relationship.provider_id}`);
|
|
2893
|
+
}
|
|
2894
|
+
return state.groups.map(
|
|
2895
|
+
(group) => createFormEntryFromValues(
|
|
2896
|
+
group.form,
|
|
2897
|
+
Object.fromEntries(group.blocks.map((block) => [block.key, block.value]))
|
|
2898
|
+
)
|
|
2899
|
+
);
|
|
2413
2900
|
}
|
|
2414
2901
|
const service_controller = {
|
|
2415
2902
|
async initialize() {
|
|
2416
2903
|
service_state.setKey("tzGroups", normalizeTimezoneGroups(client.utils.tzGroups));
|
|
2417
2904
|
await ensureCart();
|
|
2418
|
-
setServiceCartFromServiceItems(service_items.get());
|
|
2419
2905
|
const methods = session.get()?.market?.payment_methods || [];
|
|
2420
2906
|
if (methods.length) service_state.setKey("availablePaymentMethods", methods);
|
|
2421
|
-
await loadServiceForm();
|
|
2422
2907
|
},
|
|
2423
2908
|
setTimezone(tz) {
|
|
2424
2909
|
service_state.setKey("timezone", tz);
|
|
@@ -2427,39 +2912,62 @@ function initialize(config) {
|
|
|
2427
2912
|
if (state.selectedDate) {
|
|
2428
2913
|
service_state.setKey("slots", computeServiceSlots(state.selectedDate));
|
|
2429
2914
|
service_state.setKey("selectedSlot", null);
|
|
2915
|
+
service_state.setKey("quote", null);
|
|
2916
|
+
service_state.setKey("quoteError", null);
|
|
2917
|
+
activateServiceProviderForms();
|
|
2430
2918
|
}
|
|
2431
2919
|
},
|
|
2432
2920
|
async select(service) {
|
|
2433
|
-
|
|
2921
|
+
service_form_definitions.clear();
|
|
2922
|
+
clearServiceFormState(null, true);
|
|
2923
|
+
service_state.set({
|
|
2924
|
+
...service_state.get(),
|
|
2925
|
+
service: null,
|
|
2926
|
+
serviceProviders: [],
|
|
2927
|
+
providers: [],
|
|
2928
|
+
selectedProviderId: null,
|
|
2929
|
+
availability: null,
|
|
2930
|
+
selectedDate: null,
|
|
2931
|
+
slots: [],
|
|
2932
|
+
selectedSlot: null,
|
|
2933
|
+
dateTimeConfirmed: false,
|
|
2934
|
+
quote: null,
|
|
2935
|
+
quoteError: null,
|
|
2936
|
+
loading: true
|
|
2937
|
+
});
|
|
2434
2938
|
try {
|
|
2435
|
-
const isMultiDayBlock = service.blocks?.find((block) => block.key === "isMultiDay");
|
|
2436
|
-
const blockValue = isMultiDayBlock?.value;
|
|
2437
|
-
const isMultiDay = Array.isArray(blockValue) ? blockValue[0] === true : blockValue === true;
|
|
2438
2939
|
const [fullService, serviceProviders] = await Promise.all([
|
|
2439
2940
|
client.eshop.service.get({ id: service.id }),
|
|
2440
|
-
client.eshop.service.findProviders({
|
|
2941
|
+
client.eshop.service.findProviders({
|
|
2942
|
+
service_id: service.id
|
|
2943
|
+
})
|
|
2944
|
+
]);
|
|
2945
|
+
const providerIds = [...new Set(serviceProviders.map((relationship) => relationship.provider_id))];
|
|
2946
|
+
const [providerResults] = await Promise.all([
|
|
2947
|
+
Promise.all(providerIds.map((id) => client.eshop.provider.get({ id }).catch(() => null))),
|
|
2948
|
+
loadServiceFormDefinitions(serviceProviders)
|
|
2441
2949
|
]);
|
|
2442
|
-
const providerIds = [...new Set(serviceProviders.map(serviceProviderId))];
|
|
2443
|
-
const providerResults = await Promise.all(
|
|
2444
|
-
providerIds.map((id) => client.eshop.provider.get({ id }).catch(() => null))
|
|
2445
|
-
);
|
|
2446
2950
|
service_state.set({
|
|
2447
2951
|
...service_state.get(),
|
|
2448
2952
|
service: fullService,
|
|
2953
|
+
serviceProviders,
|
|
2449
2954
|
providers: providerResults.filter((provider) => provider !== null),
|
|
2450
2955
|
selectedProviderId: null,
|
|
2451
2956
|
availability: null,
|
|
2452
2957
|
selectedDate: null,
|
|
2453
|
-
startDate: null,
|
|
2454
|
-
endDate: null,
|
|
2455
2958
|
slots: [],
|
|
2456
2959
|
selectedSlot: null,
|
|
2457
2960
|
currentMonth: new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1),
|
|
2458
2961
|
loading: false,
|
|
2459
|
-
|
|
2962
|
+
dateTimeConfirmed: false,
|
|
2963
|
+
quote: null,
|
|
2964
|
+
quoteError: null
|
|
2460
2965
|
});
|
|
2966
|
+
activateServiceProviderForms();
|
|
2461
2967
|
await service_controller.loadMonth();
|
|
2462
2968
|
} catch (error) {
|
|
2969
|
+
service_form_definitions.clear();
|
|
2970
|
+
clearServiceFormState(readErrorMessage(error, "Failed to load booking forms."));
|
|
2463
2971
|
service_state.setKey("loading", false);
|
|
2464
2972
|
throw error;
|
|
2465
2973
|
}
|
|
@@ -2502,90 +3010,71 @@ function initialize(config) {
|
|
|
2502
3010
|
void service_controller.loadMonth();
|
|
2503
3011
|
},
|
|
2504
3012
|
selectProvider(providerId) {
|
|
3013
|
+
const state = service_state.get();
|
|
3014
|
+
if (providerId && !resolveServiceProvider(state, providerId)) {
|
|
3015
|
+
throw new Error(`Provider ${providerId} is not configured for the selected service`);
|
|
3016
|
+
}
|
|
2505
3017
|
service_state.set({
|
|
2506
|
-
...
|
|
3018
|
+
...state,
|
|
2507
3019
|
selectedProviderId: providerId,
|
|
2508
3020
|
selectedDate: null,
|
|
2509
|
-
startDate: null,
|
|
2510
|
-
endDate: null,
|
|
2511
3021
|
slots: [],
|
|
2512
|
-
selectedSlot: null
|
|
3022
|
+
selectedSlot: null,
|
|
3023
|
+
dateTimeConfirmed: false,
|
|
3024
|
+
quote: null,
|
|
3025
|
+
quoteError: null
|
|
2513
3026
|
});
|
|
3027
|
+
activateServiceProviderForms(providerId);
|
|
2514
3028
|
void service_controller.loadMonth();
|
|
2515
3029
|
},
|
|
2516
3030
|
selectDate(cell) {
|
|
2517
3031
|
if (cell.blank || !cell.available) return;
|
|
2518
|
-
service_state.setKey("dateTimeConfirmed", false);
|
|
2519
3032
|
const state = service_state.get();
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
service_state.setKey("endDate", cell.iso);
|
|
2532
|
-
}
|
|
2533
|
-
service_controller.createMultiDaySlots();
|
|
2534
|
-
} else {
|
|
2535
|
-
service_state.setKey("startDate", cell.iso);
|
|
2536
|
-
service_state.setKey("selectedDate", cell.iso);
|
|
2537
|
-
service_state.setKey("endDate", null);
|
|
2538
|
-
service_state.setKey("selectedSlot", null);
|
|
2539
|
-
}
|
|
2540
|
-
service_controller.updateCalendar();
|
|
2541
|
-
} else {
|
|
2542
|
-
service_state.set({
|
|
2543
|
-
...state,
|
|
2544
|
-
selectedDate: cell.iso,
|
|
2545
|
-
slots: computeServiceSlots(cell.iso),
|
|
2546
|
-
selectedSlot: null
|
|
2547
|
-
});
|
|
2548
|
-
service_state.setKey("calendar", serviceCalendar());
|
|
2549
|
-
}
|
|
3033
|
+
service_state.set({
|
|
3034
|
+
...state,
|
|
3035
|
+
selectedDate: cell.iso,
|
|
3036
|
+
slots: computeServiceSlots(cell.iso),
|
|
3037
|
+
selectedSlot: null,
|
|
3038
|
+
dateTimeConfirmed: false,
|
|
3039
|
+
quote: null,
|
|
3040
|
+
quoteError: null
|
|
3041
|
+
});
|
|
3042
|
+
activateServiceProviderForms();
|
|
3043
|
+
service_state.setKey("calendar", serviceCalendar());
|
|
2550
3044
|
},
|
|
2551
|
-
|
|
3045
|
+
selectTimeSlot(slot) {
|
|
2552
3046
|
const state = service_state.get();
|
|
2553
|
-
if (
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
from: slot.from,
|
|
2563
|
-
to: slot.to,
|
|
2564
|
-
timeText: formatServiceSlotTime(slot.from, slot.to, state.timezone),
|
|
2565
|
-
dateText: new Date(slot.from * 1e3).toLocaleDateString([], {
|
|
2566
|
-
weekday: "short",
|
|
2567
|
-
month: "short",
|
|
2568
|
-
day: "numeric",
|
|
2569
|
-
timeZone: state.timezone
|
|
2570
|
-
}),
|
|
2571
|
-
isMultiDay: true
|
|
2572
|
-
});
|
|
3047
|
+
if (slot) {
|
|
3048
|
+
if (!state.service || slot.serviceId !== state.service.id) {
|
|
3049
|
+
throw new Error("The selected slot does not belong to the selected service");
|
|
3050
|
+
}
|
|
3051
|
+
if (state.selectedProviderId && slot.providerId !== state.selectedProviderId) {
|
|
3052
|
+
throw new Error("The selected slot does not belong to the selected provider");
|
|
3053
|
+
}
|
|
3054
|
+
if (!resolveServiceProvider(state, slot.providerId)) {
|
|
3055
|
+
throw new Error(`Provider ${slot.providerId} is not configured for the selected service`);
|
|
2573
3056
|
}
|
|
2574
3057
|
}
|
|
2575
|
-
service_state.
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
3058
|
+
service_state.set({
|
|
3059
|
+
...state,
|
|
3060
|
+
selectedSlot: slot,
|
|
3061
|
+
dateTimeConfirmed: false,
|
|
3062
|
+
quote: null,
|
|
3063
|
+
quoteError: null
|
|
3064
|
+
});
|
|
3065
|
+
activateServiceProviderForms(slot?.providerId);
|
|
2581
3066
|
},
|
|
2582
3067
|
resetDateSelection() {
|
|
2583
|
-
service_state.
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
3068
|
+
service_state.set({
|
|
3069
|
+
...service_state.get(),
|
|
3070
|
+
selectedDate: null,
|
|
3071
|
+
slots: [],
|
|
3072
|
+
selectedSlot: null,
|
|
3073
|
+
dateTimeConfirmed: false,
|
|
3074
|
+
quote: null,
|
|
3075
|
+
quoteError: null
|
|
3076
|
+
});
|
|
3077
|
+
activateServiceProviderForms();
|
|
2589
3078
|
},
|
|
2590
3079
|
updateCalendar() {
|
|
2591
3080
|
service_state.setKey("calendar", serviceCalendar());
|
|
@@ -2598,70 +3087,79 @@ function initialize(config) {
|
|
|
2598
3087
|
}
|
|
2599
3088
|
}
|
|
2600
3089
|
},
|
|
2601
|
-
async addToCart() {
|
|
3090
|
+
async addToCart(explicitSlots) {
|
|
2602
3091
|
const state = service_state.get();
|
|
2603
|
-
const
|
|
2604
|
-
|
|
3092
|
+
const slots = explicitSlots || (state.selectedSlot ? [state.selectedSlot] : []);
|
|
3093
|
+
if (slots.length === 0) return;
|
|
3094
|
+
const first = slots[0];
|
|
3095
|
+
if (!state.service || first.serviceId !== state.service.id) {
|
|
3096
|
+
throw new Error("The booking slots do not belong to the selected service");
|
|
3097
|
+
}
|
|
3098
|
+
const relationship = resolveServiceProvider(state, first.providerId);
|
|
3099
|
+
if (!relationship) {
|
|
3100
|
+
throw new Error(`Provider ${first.providerId} is not configured for the selected service`);
|
|
3101
|
+
}
|
|
3102
|
+
let forms;
|
|
3103
|
+
try {
|
|
3104
|
+
forms = configuredServiceFormEntries(relationship);
|
|
3105
|
+
} catch (error) {
|
|
3106
|
+
service_form_state.setKey("error", readErrorMessage(error, "Booking forms are invalid."));
|
|
3107
|
+
throw error;
|
|
3108
|
+
}
|
|
3109
|
+
const displayName = serviceName(state.service, currentLocale());
|
|
3110
|
+
const enriched = slots.map((slot) => ({
|
|
2605
3111
|
...slot,
|
|
2606
|
-
serviceName:
|
|
2607
|
-
date: slot.dateText
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
if (!selected.length) return;
|
|
2612
|
-
const nextCart = [...state.cart, ...selected];
|
|
3112
|
+
serviceName: displayName,
|
|
3113
|
+
date: slot.dateText
|
|
3114
|
+
}));
|
|
3115
|
+
const nextItems = [...service_items.get(), toServiceCartItem(enriched, forms)];
|
|
3116
|
+
await syncServiceCart(nextItems);
|
|
2613
3117
|
service_state.set({
|
|
2614
|
-
...
|
|
2615
|
-
cart: nextCart,
|
|
3118
|
+
...service_state.get(),
|
|
2616
3119
|
selectedDate: null,
|
|
2617
|
-
startDate: null,
|
|
2618
|
-
endDate: null,
|
|
2619
3120
|
slots: [],
|
|
2620
|
-
selectedSlot: null
|
|
3121
|
+
selectedSlot: null,
|
|
3122
|
+
dateTimeConfirmed: false,
|
|
3123
|
+
quote: null,
|
|
3124
|
+
quoteError: null
|
|
2621
3125
|
});
|
|
2622
|
-
|
|
3126
|
+
activateServiceProviderForms(service_state.get().selectedProviderId, true);
|
|
2623
3127
|
service_state.setKey("calendar", serviceCalendar());
|
|
2624
3128
|
},
|
|
2625
|
-
async removeFromCart(
|
|
2626
|
-
|
|
2627
|
-
service_state.setKey("cart", nextCart);
|
|
2628
|
-
await syncServiceCart(nextCart);
|
|
3129
|
+
async removeFromCart(bookingId) {
|
|
3130
|
+
await syncServiceCart(service_items.get().filter((item) => item.id !== bookingId));
|
|
2629
3131
|
},
|
|
2630
3132
|
async clearCart() {
|
|
2631
|
-
service_state.setKey("cart", []);
|
|
2632
3133
|
await syncServiceCart([]);
|
|
2633
3134
|
},
|
|
2634
3135
|
async checkout(paymentMethodId, forms = []) {
|
|
2635
3136
|
const state = service_state.get();
|
|
2636
|
-
|
|
3137
|
+
const items = service_items.get();
|
|
3138
|
+
if (!items.length) throw new Error("Cart is empty");
|
|
2637
3139
|
service_state.setKey("loading", true);
|
|
2638
3140
|
try {
|
|
2639
3141
|
const result = await checkout({
|
|
2640
|
-
service_items:
|
|
2641
|
-
...toServiceCartItem(slot),
|
|
2642
|
-
forms: []
|
|
2643
|
-
})),
|
|
3142
|
+
service_items: items,
|
|
2644
3143
|
payment_method_key: paymentMethodId,
|
|
2645
3144
|
promo_code: state.promoCode || void 0,
|
|
2646
3145
|
forms
|
|
2647
3146
|
});
|
|
2648
3147
|
service_state.setKey("cartId", cart.get()?.id || null);
|
|
2649
|
-
return
|
|
2650
|
-
} catch (error) {
|
|
2651
|
-
return { success: false, error: readErrorMessage(error, "Checkout failed.") };
|
|
3148
|
+
return result;
|
|
2652
3149
|
} finally {
|
|
2653
3150
|
service_state.setKey("loading", false);
|
|
2654
3151
|
}
|
|
2655
3152
|
},
|
|
2656
3153
|
async fetchQuote(paymentMethodId, promoCode) {
|
|
2657
|
-
|
|
2658
|
-
|
|
3154
|
+
service_state.get();
|
|
3155
|
+
const items = service_items.get();
|
|
3156
|
+
if (!items.length) return null;
|
|
2659
3157
|
service_state.setKey("fetchingQuote", true);
|
|
2660
3158
|
service_state.setKey("quoteError", null);
|
|
2661
3159
|
try {
|
|
2662
3160
|
service_state.setKey("promoCode", promoCode || null);
|
|
2663
3161
|
const response = await fetchQuote({
|
|
2664
|
-
service_items:
|
|
3162
|
+
service_items: items,
|
|
2665
3163
|
payment_method_key: paymentMethodId,
|
|
2666
3164
|
promo_code: promoCode || void 0
|
|
2667
3165
|
});
|
|
@@ -2689,6 +3187,9 @@ function initialize(config) {
|
|
|
2689
3187
|
if (current === "datetime") {
|
|
2690
3188
|
service_state.setKey("selectedSlot", null);
|
|
2691
3189
|
service_state.setKey("dateTimeConfirmed", false);
|
|
3190
|
+
service_state.setKey("quote", null);
|
|
3191
|
+
service_state.setKey("quoteError", null);
|
|
3192
|
+
activateServiceProviderForms();
|
|
2692
3193
|
}
|
|
2693
3194
|
},
|
|
2694
3195
|
nextStep() {
|
|
@@ -2698,17 +3199,20 @@ function initialize(config) {
|
|
|
2698
3199
|
},
|
|
2699
3200
|
getServicePrice() {
|
|
2700
3201
|
const state = service_state.get();
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
3202
|
+
const relationship = resolveServiceProvider(state);
|
|
3203
|
+
if (!relationship) return "";
|
|
3204
|
+
try {
|
|
3205
|
+
const price = priceForMarket(relationship.prices, currentMarketKey(), market.get()?.currency);
|
|
3206
|
+
return client.utils.formatPrice([price]);
|
|
3207
|
+
} catch {
|
|
3208
|
+
return "";
|
|
3209
|
+
}
|
|
2705
3210
|
},
|
|
2706
3211
|
formatDateDisplay: formatServiceDateDisplay,
|
|
2707
|
-
serviceItemsFromSlots(slots) {
|
|
2708
|
-
return slots.
|
|
3212
|
+
serviceItemsFromSlots(slots, forms = []) {
|
|
3213
|
+
return slots.length ? [toServiceCartItem(slots, forms)] : [];
|
|
2709
3214
|
}
|
|
2710
3215
|
};
|
|
2711
|
-
service_items.subscribe((items) => setServiceCartFromServiceItems(items));
|
|
2712
3216
|
async function loadEntry(params, options) {
|
|
2713
3217
|
cms_state.setKey("loading", true);
|
|
2714
3218
|
cms_state.setKey("error", null);
|
|
@@ -2718,7 +3222,10 @@ function initialize(config) {
|
|
|
2718
3222
|
if (entryParams.id) {
|
|
2719
3223
|
const entry2 = await client.cms.entry.get(entryParams, options);
|
|
2720
3224
|
const cacheKey = entryParams.key || entryParams.id || entry2.id;
|
|
2721
|
-
cms_state.setKey("entries", {
|
|
3225
|
+
cms_state.setKey("entries", {
|
|
3226
|
+
...cms_state.get().entries,
|
|
3227
|
+
[cacheKey]: entry2
|
|
3228
|
+
});
|
|
2722
3229
|
return entry2;
|
|
2723
3230
|
}
|
|
2724
3231
|
if (!entryParams.collection_id || !entryParams.key) {
|
|
@@ -2737,7 +3244,10 @@ function initialize(config) {
|
|
|
2737
3244
|
if (!entry) {
|
|
2738
3245
|
throw new Error("CMS entry not found");
|
|
2739
3246
|
}
|
|
2740
|
-
cms_state.setKey("entries", {
|
|
3247
|
+
cms_state.setKey("entries", {
|
|
3248
|
+
...cms_state.get().entries,
|
|
3249
|
+
[entryParams.key]: entry
|
|
3250
|
+
});
|
|
2741
3251
|
return entry;
|
|
2742
3252
|
} catch (error) {
|
|
2743
3253
|
cms_state.setKey("error", readErrorMessage(error, "Failed to load CMS entry."));
|
|
@@ -2746,13 +3256,22 @@ function initialize(config) {
|
|
|
2746
3256
|
cms_state.setKey("loading", false);
|
|
2747
3257
|
}
|
|
2748
3258
|
}
|
|
3259
|
+
function formCacheKey(params) {
|
|
3260
|
+
const storeId = params.store_id || client.getStoreId();
|
|
3261
|
+
const identifier = params.id ? `id:${params.id}` : params.key ? `key:${params.key}` : "missing";
|
|
3262
|
+
return `${storeId}:${identifier}`;
|
|
3263
|
+
}
|
|
2749
3264
|
async function loadForm(params, options) {
|
|
2750
3265
|
cms_state.setKey("loading", true);
|
|
2751
3266
|
cms_state.setKey("error", null);
|
|
2752
3267
|
try {
|
|
2753
|
-
const
|
|
2754
|
-
const
|
|
2755
|
-
|
|
3268
|
+
const storeId = params.store_id || client.getStoreId();
|
|
3269
|
+
const formClient = clientForStore(storeId);
|
|
3270
|
+
const form = await formClient.cms.form.get({ ...params, store_id: storeId }, options);
|
|
3271
|
+
const forms = { ...cms_state.get().forms };
|
|
3272
|
+
forms[formCacheKey({ id: form.id, store_id: storeId })] = form;
|
|
3273
|
+
forms[formCacheKey({ key: form.key, store_id: storeId })] = form;
|
|
3274
|
+
cms_state.setKey("forms", forms);
|
|
2756
3275
|
return form;
|
|
2757
3276
|
} catch (error) {
|
|
2758
3277
|
cms_state.setKey("error", readErrorMessage(error, "Failed to load CMS form."));
|
|
@@ -2761,12 +3280,26 @@ function initialize(config) {
|
|
|
2761
3280
|
cms_state.setKey("loading", false);
|
|
2762
3281
|
}
|
|
2763
3282
|
}
|
|
2764
|
-
async function
|
|
2765
|
-
const
|
|
2766
|
-
const
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
3283
|
+
async function ensureFormClient(storeId) {
|
|
3284
|
+
const formClient = clientForStore(storeId);
|
|
3285
|
+
const marketKey = currentMarketKey();
|
|
3286
|
+
if (formClient === client) {
|
|
3287
|
+
await ensureSession();
|
|
3288
|
+
} else if (!formClient.session || marketKey && formClient.session.market?.key !== marketKey) {
|
|
3289
|
+
await formClient.identify({ market: marketKey });
|
|
3290
|
+
}
|
|
3291
|
+
return formClient;
|
|
3292
|
+
}
|
|
3293
|
+
async function submitForm(params, options) {
|
|
3294
|
+
const storeId = params.store_id || client.getStoreId();
|
|
3295
|
+
const formClient = await ensureFormClient(storeId);
|
|
3296
|
+
return formClient.cms.form.submit({ ...params, store_id: storeId }, options);
|
|
3297
|
+
}
|
|
3298
|
+
async function submitFormByKey(params, options) {
|
|
3299
|
+
const storeId = params.store_id || client.getStoreId();
|
|
3300
|
+
const form = await loadForm({ key: params.key, store_id: storeId }, options);
|
|
3301
|
+
const entry = createFormEntryFromValues(form, params.values);
|
|
3302
|
+
return submitForm({ store_id: storeId, form_id: form.id, fields: entry.fields }, options);
|
|
2770
3303
|
}
|
|
2771
3304
|
async function loadProducts(params = {}, options) {
|
|
2772
3305
|
eshop_state.setKey("loading_products", true);
|
|
@@ -2869,38 +3402,32 @@ function initialize(config) {
|
|
|
2869
3402
|
destroy: destroyPaymentController
|
|
2870
3403
|
},
|
|
2871
3404
|
applyPromoCode(code, input = {}) {
|
|
2872
|
-
promo_code.set(code);
|
|
2873
3405
|
return fetchQuote({ ...input, promo_code: code });
|
|
2874
3406
|
},
|
|
2875
3407
|
removePromoCode(input = {}) {
|
|
2876
|
-
promo_code.set(null);
|
|
2877
3408
|
return fetchQuote({ ...input, promo_code: null });
|
|
2878
3409
|
},
|
|
2879
3410
|
selectShippingMethod(id) {
|
|
2880
3411
|
cart_status.setKey("selected_shipping_method_id", id);
|
|
2881
3412
|
},
|
|
2882
3413
|
locationToAddress,
|
|
3414
|
+
createFormEntry,
|
|
2883
3415
|
buildItems: checkoutItems,
|
|
2884
3416
|
buildProductItems: toProductCheckoutItems,
|
|
2885
3417
|
buildServiceItems: toServiceCheckoutItems
|
|
2886
3418
|
};
|
|
2887
3419
|
const product_store = {
|
|
2888
3420
|
get: (params, options) => client.eshop.product.get(params, options),
|
|
2889
|
-
|
|
2890
|
-
list: loadProducts,
|
|
2891
|
-
loadListing: loadProducts,
|
|
2892
|
-
loadDetail: (params, options) => client.eshop.product.get(params, options)
|
|
3421
|
+
list: loadProducts
|
|
2893
3422
|
};
|
|
2894
3423
|
const service_store = {
|
|
2895
3424
|
get: (params, options) => client.eshop.service.get(params, options),
|
|
2896
|
-
find: loadServices,
|
|
2897
3425
|
list: loadServices,
|
|
2898
|
-
loadListing: loadServices,
|
|
2899
|
-
loadDetail: (params, options) => client.eshop.service.get(params, options),
|
|
2900
3426
|
listProviders: (params, options) => client.eshop.service.findProviders(params, options),
|
|
2901
|
-
findProviders: (params, options) => client.eshop.service.findProviders(params, options),
|
|
2902
3427
|
getAvailability: loadAvailability,
|
|
2903
3428
|
state: service_state,
|
|
3429
|
+
form_state: service_form_state,
|
|
3430
|
+
form_groups: service_form_groups,
|
|
2904
3431
|
form_blocks: service_form_blocks,
|
|
2905
3432
|
current_step_name: service_current_step_name,
|
|
2906
3433
|
can_proceed: service_can_proceed,
|
|
@@ -2917,7 +3444,6 @@ function initialize(config) {
|
|
|
2917
3444
|
nextMonth: service_controller.nextMonth,
|
|
2918
3445
|
selectProvider: service_controller.selectProvider,
|
|
2919
3446
|
selectDate: service_controller.selectDate,
|
|
2920
|
-
createMultiDaySlots: service_controller.createMultiDaySlots,
|
|
2921
3447
|
selectTimeSlot: service_controller.selectTimeSlot,
|
|
2922
3448
|
resetDateSelection: service_controller.resetDateSelection,
|
|
2923
3449
|
updateCalendar: service_controller.updateCalendar,
|
|
@@ -2941,8 +3467,8 @@ function initialize(config) {
|
|
|
2941
3467
|
currency,
|
|
2942
3468
|
allowed_payment_methods,
|
|
2943
3469
|
payment_config,
|
|
2944
|
-
cart: cart_store,
|
|
2945
3470
|
identify,
|
|
3471
|
+
identifyContactEmailIfMissing,
|
|
2946
3472
|
verify: client.verify,
|
|
2947
3473
|
me: client.me,
|
|
2948
3474
|
logout: client.logout,
|
|
@@ -2967,7 +3493,7 @@ function initialize(config) {
|
|
|
2967
3493
|
},
|
|
2968
3494
|
form: {
|
|
2969
3495
|
get: loadForm,
|
|
2970
|
-
submit:
|
|
3496
|
+
submit: submitForm,
|
|
2971
3497
|
submitByKey: submitFormByKey
|
|
2972
3498
|
},
|
|
2973
3499
|
taxonomy: client.cms.taxonomy
|
|
@@ -2978,7 +3504,7 @@ function initialize(config) {
|
|
|
2978
3504
|
service: service_store,
|
|
2979
3505
|
provider: {
|
|
2980
3506
|
get: (params, options) => client.eshop.provider.get(params, options),
|
|
2981
|
-
|
|
3507
|
+
list: loadProviders
|
|
2982
3508
|
},
|
|
2983
3509
|
order: client.eshop.order,
|
|
2984
3510
|
cart: cart_store
|
|
@@ -3001,11 +3527,37 @@ function initialize(config) {
|
|
|
3001
3527
|
utils: client.utils
|
|
3002
3528
|
};
|
|
3003
3529
|
}
|
|
3530
|
+
function initialize(config) {
|
|
3531
|
+
const stores = /* @__PURE__ */ new Map();
|
|
3532
|
+
const createScope = (scopeConfig) => {
|
|
3533
|
+
const existing = stores.get(scopeConfig.storeId);
|
|
3534
|
+
if (existing) return existing;
|
|
3535
|
+
const store = initializeStore(scopeConfig);
|
|
3536
|
+
stores.set(scopeConfig.storeId, store);
|
|
3537
|
+
Object.defineProperty(store, "forStore", {
|
|
3538
|
+
enumerable: true,
|
|
3539
|
+
configurable: false,
|
|
3540
|
+
writable: false,
|
|
3541
|
+
value: (storeId) => createScope({
|
|
3542
|
+
...config,
|
|
3543
|
+
storeId,
|
|
3544
|
+
market: store.getMarket(),
|
|
3545
|
+
locale: store.getLocale()
|
|
3546
|
+
})
|
|
3547
|
+
});
|
|
3548
|
+
return store;
|
|
3549
|
+
};
|
|
3550
|
+
return createScope(config);
|
|
3551
|
+
}
|
|
3004
3552
|
|
|
3005
3553
|
exports.COMMON_ACTION_KEYS = COMMON_ACTION_KEYS;
|
|
3554
|
+
exports.buildFormFields = buildFormFields;
|
|
3006
3555
|
exports.createCartController = createCartController;
|
|
3556
|
+
exports.createFormEntry = createFormEntry;
|
|
3557
|
+
exports.createFormEntryFromValues = createFormEntryFromValues;
|
|
3007
3558
|
exports.createStorefront = createStorefront;
|
|
3008
3559
|
exports.createStripeConfirmationTokenController = createStripeConfirmationTokenController;
|
|
3560
|
+
exports.getBlockContentValue = getBlockContentValue;
|
|
3009
3561
|
exports.initialize = initialize;
|
|
3010
3562
|
//# sourceMappingURL=storefront.cjs.map
|
|
3011
3563
|
//# sourceMappingURL=storefront.cjs.map
|