arky-sdk 0.2.0 → 0.3.0
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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -4
- package/dist/index.d.ts +14 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -10
- package/dist/config-B7Oy_bdX.d.cts +0 -13
- package/dist/config-B7Oy_bdX.d.ts +0 -13
- package/dist/stores.cjs +0 -2165
- package/dist/stores.cjs.map +0 -1
- package/dist/stores.d.cts +0 -214
- package/dist/stores.d.ts +0 -214
- package/dist/stores.js +0 -2128
- package/dist/stores.js.map +0 -1
package/dist/stores.js
DELETED
|
@@ -1,2128 +0,0 @@
|
|
|
1
|
-
import { deepMap, computed, atom } from 'nanostores';
|
|
2
|
-
import { persistentAtom } from '@nanostores/persistent';
|
|
3
|
-
|
|
4
|
-
// src/config.ts
|
|
5
|
-
var globalConfig = null;
|
|
6
|
-
function setGlobalConfig(config) {
|
|
7
|
-
globalConfig = config;
|
|
8
|
-
}
|
|
9
|
-
function getGlobalConfig() {
|
|
10
|
-
if (!globalConfig) {
|
|
11
|
-
throw new Error(
|
|
12
|
-
"Arky SDK not initialized. Call initArky() first."
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
return globalConfig;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// src/utils/queryParams.ts
|
|
19
|
-
function buildQueryString(params) {
|
|
20
|
-
const queryParts = [];
|
|
21
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
22
|
-
if (value === null || value === void 0) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if (Array.isArray(value)) {
|
|
26
|
-
const jsonString = JSON.stringify(value);
|
|
27
|
-
queryParts.push(`${key}=${encodeURIComponent(jsonString)}`);
|
|
28
|
-
} else if (typeof value === "string") {
|
|
29
|
-
queryParts.push(`${key}=${encodeURIComponent(value)}`);
|
|
30
|
-
} else if (typeof value === "number" || typeof value === "boolean") {
|
|
31
|
-
queryParts.push(`${key}=${value}`);
|
|
32
|
-
} else if (typeof value === "object") {
|
|
33
|
-
const jsonString = JSON.stringify(value);
|
|
34
|
-
queryParts.push(`${key}=${encodeURIComponent(jsonString)}`);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
return queryParts.length > 0 ? `?${queryParts.join("&")}` : "";
|
|
38
|
-
}
|
|
39
|
-
function appendQueryString(url, params) {
|
|
40
|
-
const queryString = buildQueryString(params);
|
|
41
|
-
return queryString ? `${url}${queryString}` : url;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// src/services/http.ts
|
|
45
|
-
async function get(url, options) {
|
|
46
|
-
try {
|
|
47
|
-
const finalUrl = options?.params ? appendQueryString(url, options.params) : url;
|
|
48
|
-
const response = await fetch(finalUrl);
|
|
49
|
-
if (!response.ok) {
|
|
50
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
51
|
-
}
|
|
52
|
-
const data = await response.json();
|
|
53
|
-
return {
|
|
54
|
-
value: data,
|
|
55
|
-
success: true
|
|
56
|
-
};
|
|
57
|
-
} catch (error) {
|
|
58
|
-
const errorMsg = error instanceof Error ? error.message : "Unknown error occurred";
|
|
59
|
-
return {
|
|
60
|
-
value: null,
|
|
61
|
-
success: false,
|
|
62
|
-
error: errorMsg
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
async function post(url, data, options) {
|
|
67
|
-
try {
|
|
68
|
-
const response = await fetch(url, {
|
|
69
|
-
method: "POST",
|
|
70
|
-
headers: {
|
|
71
|
-
"Content-Type": "application/json"
|
|
72
|
-
},
|
|
73
|
-
body: JSON.stringify(data)
|
|
74
|
-
});
|
|
75
|
-
if (!response.ok) {
|
|
76
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
77
|
-
}
|
|
78
|
-
const responseData = await response.json();
|
|
79
|
-
return {
|
|
80
|
-
value: responseData,
|
|
81
|
-
success: true
|
|
82
|
-
};
|
|
83
|
-
} catch (error) {
|
|
84
|
-
const errorMsg = error instanceof Error ? error.message : "Unknown error occurred";
|
|
85
|
-
return {
|
|
86
|
-
value: null,
|
|
87
|
-
success: false,
|
|
88
|
-
error: errorMsg
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
var httpClient = {
|
|
93
|
-
get,
|
|
94
|
-
post
|
|
95
|
-
};
|
|
96
|
-
var http_default = httpClient;
|
|
97
|
-
|
|
98
|
-
// src/api/reservation.ts
|
|
99
|
-
var reservationApi = {
|
|
100
|
-
// Get quote for reservation parts
|
|
101
|
-
async getQuote({
|
|
102
|
-
token,
|
|
103
|
-
businessId,
|
|
104
|
-
market,
|
|
105
|
-
currency: currency2,
|
|
106
|
-
userId,
|
|
107
|
-
parts,
|
|
108
|
-
paymentMethod = "CASH",
|
|
109
|
-
promoCode
|
|
110
|
-
}) {
|
|
111
|
-
try {
|
|
112
|
-
const config = getGlobalConfig();
|
|
113
|
-
const lines = parts.map((part) => ({
|
|
114
|
-
type: "SERVICE",
|
|
115
|
-
serviceId: part.serviceId,
|
|
116
|
-
quantity: 1
|
|
117
|
-
}));
|
|
118
|
-
const payload = {
|
|
119
|
-
businessId,
|
|
120
|
-
market,
|
|
121
|
-
currency: currency2,
|
|
122
|
-
userId,
|
|
123
|
-
paymentMethod,
|
|
124
|
-
lines,
|
|
125
|
-
promoCode: promoCode || void 0,
|
|
126
|
-
shippingMethodId: null
|
|
127
|
-
};
|
|
128
|
-
const res = await fetch(`${config.apiUrl}/v1/payments/quote`, {
|
|
129
|
-
method: "POST",
|
|
130
|
-
headers: {
|
|
131
|
-
"Content-Type": "application/json",
|
|
132
|
-
"Authorization": `Bearer ${token}`
|
|
133
|
-
},
|
|
134
|
-
body: JSON.stringify(payload)
|
|
135
|
-
});
|
|
136
|
-
const text = await res.text();
|
|
137
|
-
if (!res.ok) {
|
|
138
|
-
try {
|
|
139
|
-
const json = JSON.parse(text);
|
|
140
|
-
return { success: false, error: json.reason || json.error || "Failed to fetch quote", code: json.code };
|
|
141
|
-
} catch {
|
|
142
|
-
return { success: false, error: text || "Failed to fetch quote" };
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
const quote = text ? JSON.parse(text) : null;
|
|
146
|
-
return { success: true, data: quote };
|
|
147
|
-
} catch (e) {
|
|
148
|
-
return {
|
|
149
|
-
success: false,
|
|
150
|
-
error: e.message || "Failed to fetch quote"
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
},
|
|
154
|
-
// Get available slots for a service
|
|
155
|
-
async getAvailableSlots({
|
|
156
|
-
businessId,
|
|
157
|
-
serviceId,
|
|
158
|
-
from,
|
|
159
|
-
to,
|
|
160
|
-
limit = 1e3,
|
|
161
|
-
providerId = null
|
|
162
|
-
}) {
|
|
163
|
-
const config = getGlobalConfig();
|
|
164
|
-
const url = `${config.apiUrl}/v1/businesses/${businessId}/services/${serviceId}/available-slots`;
|
|
165
|
-
const response = await http_default.get(url, {
|
|
166
|
-
params: {
|
|
167
|
-
from,
|
|
168
|
-
to,
|
|
169
|
-
limit,
|
|
170
|
-
providerId
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
if (response.success) {
|
|
174
|
-
const json = response.value;
|
|
175
|
-
return {
|
|
176
|
-
success: true,
|
|
177
|
-
data: json.data?.items || json.items || []
|
|
178
|
-
};
|
|
179
|
-
} else {
|
|
180
|
-
console.error("Error fetching available slots:", response.error);
|
|
181
|
-
return {
|
|
182
|
-
success: false,
|
|
183
|
-
error: response.error,
|
|
184
|
-
data: []
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
// Get all providers for a service
|
|
189
|
-
async getProviders({ businessId, serviceId, limit = 50 }) {
|
|
190
|
-
const config = getGlobalConfig();
|
|
191
|
-
const url = `${config.apiUrl}/v1/businesses/${businessId}/providers`;
|
|
192
|
-
const response = await http_default.get(url, {
|
|
193
|
-
params: {
|
|
194
|
-
serviceId,
|
|
195
|
-
limit
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
if (response.success) {
|
|
199
|
-
const json = response.value;
|
|
200
|
-
return {
|
|
201
|
-
success: true,
|
|
202
|
-
data: json.items || []
|
|
203
|
-
};
|
|
204
|
-
} else {
|
|
205
|
-
console.error("Error loading providers:", response.error);
|
|
206
|
-
return {
|
|
207
|
-
success: false,
|
|
208
|
-
error: response.error,
|
|
209
|
-
data: []
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
// Get guest token or create a new one
|
|
214
|
-
async getGuestToken() {
|
|
215
|
-
try {
|
|
216
|
-
const config = getGlobalConfig();
|
|
217
|
-
const res = await fetch(`${config.apiUrl}/v1/users/login`, {
|
|
218
|
-
method: "POST",
|
|
219
|
-
headers: { "Content-Type": "application/json" },
|
|
220
|
-
body: JSON.stringify({ provider: "GUEST" })
|
|
221
|
-
});
|
|
222
|
-
if (!res.ok) throw new Error("Guest login failed");
|
|
223
|
-
const json = await res.json();
|
|
224
|
-
return {
|
|
225
|
-
success: true,
|
|
226
|
-
data: { token: json.accessToken }
|
|
227
|
-
};
|
|
228
|
-
} catch (e) {
|
|
229
|
-
return {
|
|
230
|
-
success: false,
|
|
231
|
-
error: e.message
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
// Update user's phone number
|
|
236
|
-
async updateProfilePhone({ token, phoneNumber }) {
|
|
237
|
-
try {
|
|
238
|
-
const config = getGlobalConfig();
|
|
239
|
-
const res = await fetch(`${config.apiUrl}/v1/users/update`, {
|
|
240
|
-
method: "PUT",
|
|
241
|
-
headers: {
|
|
242
|
-
"Content-Type": "application/json",
|
|
243
|
-
Authorization: `Bearer ${token}`
|
|
244
|
-
},
|
|
245
|
-
body: JSON.stringify({
|
|
246
|
-
phoneNumber,
|
|
247
|
-
phoneNumbers: [],
|
|
248
|
-
addresses: []
|
|
249
|
-
})
|
|
250
|
-
});
|
|
251
|
-
if (!res.ok) {
|
|
252
|
-
const error = await res.text() || res.statusText;
|
|
253
|
-
return {
|
|
254
|
-
success: false,
|
|
255
|
-
error
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
return {
|
|
259
|
-
success: true
|
|
260
|
-
};
|
|
261
|
-
} catch (e) {
|
|
262
|
-
return {
|
|
263
|
-
success: false,
|
|
264
|
-
error: e.message
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
},
|
|
268
|
-
// Verify phone number with code
|
|
269
|
-
async verifyPhoneCode({ token, phoneNumber, code }) {
|
|
270
|
-
try {
|
|
271
|
-
const config = getGlobalConfig();
|
|
272
|
-
const res = await fetch(`${config.apiUrl}/v1/users/confirm/phone-number`, {
|
|
273
|
-
method: "PUT",
|
|
274
|
-
headers: {
|
|
275
|
-
"Content-Type": "application/json",
|
|
276
|
-
Authorization: `Bearer ${token}`
|
|
277
|
-
},
|
|
278
|
-
body: JSON.stringify({
|
|
279
|
-
phoneNumber,
|
|
280
|
-
code
|
|
281
|
-
})
|
|
282
|
-
});
|
|
283
|
-
if (!res.ok) {
|
|
284
|
-
const error = await res.text() || res.statusText;
|
|
285
|
-
return {
|
|
286
|
-
success: false,
|
|
287
|
-
error
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
return {
|
|
291
|
-
success: true
|
|
292
|
-
};
|
|
293
|
-
} catch (e) {
|
|
294
|
-
return {
|
|
295
|
-
success: false,
|
|
296
|
-
error: e.message
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
},
|
|
300
|
-
// Complete reservation checkout - Backend calculates currency from market
|
|
301
|
-
async checkout({
|
|
302
|
-
token,
|
|
303
|
-
businessId,
|
|
304
|
-
parts,
|
|
305
|
-
paymentMethod = "CASH",
|
|
306
|
-
blocks = [],
|
|
307
|
-
market = "US",
|
|
308
|
-
promoCode
|
|
309
|
-
}) {
|
|
310
|
-
try {
|
|
311
|
-
const config = getGlobalConfig();
|
|
312
|
-
const payload = {
|
|
313
|
-
businessId,
|
|
314
|
-
blocks,
|
|
315
|
-
market,
|
|
316
|
-
parts: parts.map((p) => ({
|
|
317
|
-
serviceId: p.serviceId,
|
|
318
|
-
from: p.from,
|
|
319
|
-
to: p.to,
|
|
320
|
-
blocks: p.blocks,
|
|
321
|
-
reservationMethod: p.reservationMethod,
|
|
322
|
-
providerId: p.providerId
|
|
323
|
-
}))
|
|
324
|
-
};
|
|
325
|
-
if (paymentMethod !== void 0) {
|
|
326
|
-
payload.paymentMethod = paymentMethod;
|
|
327
|
-
}
|
|
328
|
-
if (promoCode) {
|
|
329
|
-
payload.promoCode = promoCode;
|
|
330
|
-
}
|
|
331
|
-
const res = await fetch(`${config.apiUrl}/v1/reservations/checkout`, {
|
|
332
|
-
method: "POST",
|
|
333
|
-
headers: {
|
|
334
|
-
"Content-Type": "application/json",
|
|
335
|
-
Authorization: `Bearer ${token}`
|
|
336
|
-
},
|
|
337
|
-
body: JSON.stringify(payload)
|
|
338
|
-
});
|
|
339
|
-
if (!res.ok) {
|
|
340
|
-
const error = await res.text() || res.statusText;
|
|
341
|
-
throw new Error(error);
|
|
342
|
-
}
|
|
343
|
-
const json = await res.json();
|
|
344
|
-
return {
|
|
345
|
-
success: true,
|
|
346
|
-
data: json
|
|
347
|
-
// Should include reservationId and clientSecret for payments
|
|
348
|
-
};
|
|
349
|
-
} catch (e) {
|
|
350
|
-
return {
|
|
351
|
-
success: false,
|
|
352
|
-
error: e.message
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
// src/services/auth.ts
|
|
359
|
-
async function getGuestToken(currentToken = null) {
|
|
360
|
-
if (currentToken) return currentToken;
|
|
361
|
-
const response = await reservationApi.getGuestToken();
|
|
362
|
-
if (response.success && response.data) {
|
|
363
|
-
return response.data.token;
|
|
364
|
-
}
|
|
365
|
-
throw new Error("Failed to get guest token");
|
|
366
|
-
}
|
|
367
|
-
async function updateProfilePhone(token, phoneNumber) {
|
|
368
|
-
if (!phoneNumber) {
|
|
369
|
-
throw new Error("Phone number is required");
|
|
370
|
-
}
|
|
371
|
-
const response = await reservationApi.updateProfilePhone({ token, phoneNumber });
|
|
372
|
-
if (response.success) {
|
|
373
|
-
return { success: true };
|
|
374
|
-
} else {
|
|
375
|
-
throw new Error(response.error || "Failed to send verification code");
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
async function verifyPhoneCode(token, phoneNumber, code) {
|
|
379
|
-
if (!code) {
|
|
380
|
-
throw new Error("Verification code is required");
|
|
381
|
-
}
|
|
382
|
-
const response = await reservationApi.verifyPhoneCode({ token, phoneNumber, code });
|
|
383
|
-
if (response.success) {
|
|
384
|
-
return { success: true };
|
|
385
|
-
} else {
|
|
386
|
-
throw new Error(response.error || "Invalid verification code");
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
async function getBusinessConfig(businessId) {
|
|
390
|
-
try {
|
|
391
|
-
const config = getGlobalConfig();
|
|
392
|
-
const response = await fetch(`${config.apiUrl}/v1/businesses/${businessId}`, {
|
|
393
|
-
method: "GET",
|
|
394
|
-
headers: {
|
|
395
|
-
"Content-Type": "application/json"
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
if (!response.ok) {
|
|
399
|
-
throw new Error(`Failed to fetch business config: ${response.status}`);
|
|
400
|
-
}
|
|
401
|
-
const business = await response.json();
|
|
402
|
-
return {
|
|
403
|
-
success: true,
|
|
404
|
-
data: business
|
|
405
|
-
};
|
|
406
|
-
} catch (error) {
|
|
407
|
-
return {
|
|
408
|
-
success: false,
|
|
409
|
-
error: error.message
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// src/utils/currency.ts
|
|
415
|
-
function getCurrencySymbol(currency2) {
|
|
416
|
-
const currencySymbols = {
|
|
417
|
-
USD: "$",
|
|
418
|
-
EUR: "\u20AC",
|
|
419
|
-
GBP: "\xA3",
|
|
420
|
-
CAD: "C$",
|
|
421
|
-
AUD: "A$",
|
|
422
|
-
JPY: "\xA5",
|
|
423
|
-
CHF: "CHF",
|
|
424
|
-
SEK: "kr",
|
|
425
|
-
NOK: "kr",
|
|
426
|
-
DKK: "kr",
|
|
427
|
-
PLN: "z\u0142",
|
|
428
|
-
CZK: "K\u010D",
|
|
429
|
-
HUF: "Ft",
|
|
430
|
-
RON: "lei",
|
|
431
|
-
BGN: "\u043B\u0432",
|
|
432
|
-
HRK: "kn",
|
|
433
|
-
RSD: "\u0434\u0438\u043D",
|
|
434
|
-
BAM: "KM",
|
|
435
|
-
MKD: "\u0434\u0435\u043D",
|
|
436
|
-
ALL: "L",
|
|
437
|
-
TRY: "\u20BA",
|
|
438
|
-
RUB: "\u20BD",
|
|
439
|
-
UAH: "\u20B4",
|
|
440
|
-
BYN: "Br",
|
|
441
|
-
CNY: "\xA5",
|
|
442
|
-
INR: "\u20B9",
|
|
443
|
-
KRW: "\u20A9",
|
|
444
|
-
THB: "\u0E3F",
|
|
445
|
-
VND: "\u20AB",
|
|
446
|
-
SGD: "S$",
|
|
447
|
-
MYR: "RM",
|
|
448
|
-
IDR: "Rp",
|
|
449
|
-
PHP: "\u20B1",
|
|
450
|
-
BRL: "R$",
|
|
451
|
-
ARS: "$",
|
|
452
|
-
CLP: "$",
|
|
453
|
-
COP: "$",
|
|
454
|
-
PEN: "S/",
|
|
455
|
-
MXN: "$",
|
|
456
|
-
ZAR: "R",
|
|
457
|
-
EGP: "E\xA3",
|
|
458
|
-
NGN: "\u20A6",
|
|
459
|
-
KES: "KSh",
|
|
460
|
-
GHS: "\u20B5",
|
|
461
|
-
MAD: "DH",
|
|
462
|
-
TND: "\u062F.\u062A",
|
|
463
|
-
DZD: "\u062F.\u062C",
|
|
464
|
-
LYD: "\u0644.\u062F",
|
|
465
|
-
AED: "\u062F.\u0625",
|
|
466
|
-
SAR: "\u0631.\u0633",
|
|
467
|
-
QAR: "\u0631.\u0642",
|
|
468
|
-
KWD: "\u062F.\u0643",
|
|
469
|
-
BHD: "\u0628.\u062F",
|
|
470
|
-
OMR: "\u0631.\u0639",
|
|
471
|
-
JOD: "\u062F.\u0623",
|
|
472
|
-
LBP: "\u0644.\u0644",
|
|
473
|
-
SYP: "\u0644.\u0633",
|
|
474
|
-
IQD: "\u0639.\u062F",
|
|
475
|
-
IRR: "\uFDFC",
|
|
476
|
-
AFN: "\u060B",
|
|
477
|
-
PKR: "\u20A8",
|
|
478
|
-
LKR: "\u20A8",
|
|
479
|
-
NPR: "\u20A8",
|
|
480
|
-
BDT: "\u09F3",
|
|
481
|
-
MMK: "K",
|
|
482
|
-
LAK: "\u20AD",
|
|
483
|
-
KHR: "\u17DB",
|
|
484
|
-
MNT: "\u20AE",
|
|
485
|
-
KZT: "\u20B8",
|
|
486
|
-
UZS: "\u043B\u0432",
|
|
487
|
-
KGS: "\u043B\u0432",
|
|
488
|
-
TJS: "SM",
|
|
489
|
-
TMT: "T",
|
|
490
|
-
AZN: "\u20BC",
|
|
491
|
-
GEL: "\u20BE",
|
|
492
|
-
AMD: "\u058F",
|
|
493
|
-
BYR: "p.",
|
|
494
|
-
MDL: "L"
|
|
495
|
-
};
|
|
496
|
-
return currencySymbols[currency2.toUpperCase()] || currency2;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// src/stores/business.ts
|
|
500
|
-
var businessStore = deepMap({
|
|
501
|
-
data: null,
|
|
502
|
-
loading: false,
|
|
503
|
-
error: null,
|
|
504
|
-
initialized: false
|
|
505
|
-
});
|
|
506
|
-
var selectedMarket = computed(businessStore, (state) => {
|
|
507
|
-
if (!state.data?.configs?.markets) return null;
|
|
508
|
-
const markets2 = state.data.configs.markets;
|
|
509
|
-
return markets2.find((m) => m.id === "us") || markets2[0] || null;
|
|
510
|
-
});
|
|
511
|
-
var currency = computed(selectedMarket, (market) => {
|
|
512
|
-
return market?.currency || "USD";
|
|
513
|
-
});
|
|
514
|
-
var currencySymbol = computed(selectedMarket, (market) => {
|
|
515
|
-
return getCurrencySymbol(market?.currency || "USD");
|
|
516
|
-
});
|
|
517
|
-
var markets = computed(businessStore, (state) => {
|
|
518
|
-
if (!state.data?.configs?.markets) return [];
|
|
519
|
-
return state.data.configs.markets;
|
|
520
|
-
});
|
|
521
|
-
var zones = computed(businessStore, (state) => {
|
|
522
|
-
if (!state.data?.configs?.zones) return [];
|
|
523
|
-
return state.data.configs.zones;
|
|
524
|
-
});
|
|
525
|
-
var getZoneByCountry = (countryCode) => {
|
|
526
|
-
const allZones = zones.get();
|
|
527
|
-
return allZones.find(
|
|
528
|
-
(zone) => zone.countries.length === 0 || // Empty = all countries
|
|
529
|
-
zone.countries.includes(countryCode.toUpperCase())
|
|
530
|
-
) || null;
|
|
531
|
-
};
|
|
532
|
-
var getShippingMethodsForCountry = (countryCode) => {
|
|
533
|
-
const zone = getZoneByCountry(countryCode);
|
|
534
|
-
return zone?.shippingMethods || [];
|
|
535
|
-
};
|
|
536
|
-
var paymentMethods = computed(selectedMarket, (market) => {
|
|
537
|
-
if (!market) return ["CASH"];
|
|
538
|
-
const methods = market.paymentMethods || [];
|
|
539
|
-
return methods.map((pm) => pm.method || pm);
|
|
540
|
-
});
|
|
541
|
-
var paymentConfig = computed(businessStore, (state) => {
|
|
542
|
-
if (!state.data?.configs) return { provider: null, enabled: false };
|
|
543
|
-
const provider = state.data.configs.paymentProvider || null;
|
|
544
|
-
const hasCreditCard = paymentMethods.get().includes("CREDIT_CARD");
|
|
545
|
-
return {
|
|
546
|
-
provider,
|
|
547
|
-
enabled: hasCreditCard && !!provider
|
|
548
|
-
};
|
|
549
|
-
});
|
|
550
|
-
var orderBlocks = computed(businessStore, (state) => {
|
|
551
|
-
return state.data?.configs?.orderBlocks || [];
|
|
552
|
-
});
|
|
553
|
-
var reservationBlocks = computed(businessStore, (state) => {
|
|
554
|
-
return state.data?.configs?.reservationBlocks || [];
|
|
555
|
-
});
|
|
556
|
-
var businessActions = {
|
|
557
|
-
// Initialize business data - SINGLE API CALL for entire app
|
|
558
|
-
async init() {
|
|
559
|
-
const state = businessStore.get();
|
|
560
|
-
if (state.initialized && state.data) {
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
try {
|
|
564
|
-
businessStore.setKey("loading", true);
|
|
565
|
-
businessStore.setKey("error", null);
|
|
566
|
-
const config = getGlobalConfig();
|
|
567
|
-
const result = await getBusinessConfig(config.businessId);
|
|
568
|
-
if (result.success) {
|
|
569
|
-
businessStore.setKey("data", result.data);
|
|
570
|
-
businessStore.setKey("initialized", true);
|
|
571
|
-
} else {
|
|
572
|
-
throw new Error(result.error || "Failed to load business configuration");
|
|
573
|
-
}
|
|
574
|
-
} catch (error) {
|
|
575
|
-
businessStore.setKey("error", error.message);
|
|
576
|
-
console.error("Business store initialization failed:", error);
|
|
577
|
-
} finally {
|
|
578
|
-
businessStore.setKey("loading", false);
|
|
579
|
-
}
|
|
580
|
-
},
|
|
581
|
-
// Reset store (useful for testing)
|
|
582
|
-
reset() {
|
|
583
|
-
businessStore.setKey("data", null);
|
|
584
|
-
businessStore.setKey("loading", false);
|
|
585
|
-
businessStore.setKey("error", null);
|
|
586
|
-
businessStore.setKey("initialized", false);
|
|
587
|
-
},
|
|
588
|
-
// Get business data (with auto-init)
|
|
589
|
-
async getBusiness() {
|
|
590
|
-
const state = businessStore.get();
|
|
591
|
-
if (!state.initialized || !state.data) {
|
|
592
|
-
await this.init();
|
|
593
|
-
}
|
|
594
|
-
return businessStore.get().data;
|
|
595
|
-
}
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
// src/api/eshop.ts
|
|
599
|
-
var eshopApi = {
|
|
600
|
-
// Get products
|
|
601
|
-
async getProducts({
|
|
602
|
-
businessId,
|
|
603
|
-
categoryIds = null,
|
|
604
|
-
status = "ACTIVE",
|
|
605
|
-
limit = 20,
|
|
606
|
-
cursor = null
|
|
607
|
-
}) {
|
|
608
|
-
const config = getGlobalConfig();
|
|
609
|
-
const url = `${config.apiUrl}/v1/businesses/${encodeURIComponent(businessId)}/products`;
|
|
610
|
-
const response = await http_default.get(url, {
|
|
611
|
-
params: {
|
|
612
|
-
categoryIds: categoryIds && categoryIds.length > 0 ? categoryIds : void 0,
|
|
613
|
-
status,
|
|
614
|
-
limit,
|
|
615
|
-
cursor
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
if (response.success) {
|
|
619
|
-
const json = response.value;
|
|
620
|
-
return {
|
|
621
|
-
success: true,
|
|
622
|
-
data: json.items || [],
|
|
623
|
-
cursor: json.cursor,
|
|
624
|
-
total: json.total || 0
|
|
625
|
-
};
|
|
626
|
-
} else {
|
|
627
|
-
console.error("Error fetching products:", response.error);
|
|
628
|
-
return {
|
|
629
|
-
success: false,
|
|
630
|
-
error: response.error,
|
|
631
|
-
data: []
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
},
|
|
635
|
-
// Get product by slug
|
|
636
|
-
async getProductBySlug({ businessId, slug }) {
|
|
637
|
-
try {
|
|
638
|
-
const config = getGlobalConfig();
|
|
639
|
-
const url = `${config.apiUrl}/v1/businesses/${encodeURIComponent(businessId)}/products/slug/${encodeURIComponent(businessId)}/${encodeURIComponent(slug)}`;
|
|
640
|
-
const res = await fetch(url);
|
|
641
|
-
if (!res.ok) throw new Error("Product not found");
|
|
642
|
-
const json = await res.json();
|
|
643
|
-
return {
|
|
644
|
-
success: true,
|
|
645
|
-
data: json
|
|
646
|
-
};
|
|
647
|
-
} catch (e) {
|
|
648
|
-
console.error("Error fetching product:", e);
|
|
649
|
-
return {
|
|
650
|
-
success: false,
|
|
651
|
-
error: e.message,
|
|
652
|
-
data: null
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
},
|
|
656
|
-
// Get quote for cart (pricing with promo codes, shipping, taxes)
|
|
657
|
-
async getQuote({
|
|
658
|
-
token,
|
|
659
|
-
businessId,
|
|
660
|
-
items,
|
|
661
|
-
market,
|
|
662
|
-
currency: currency2,
|
|
663
|
-
userId,
|
|
664
|
-
paymentMethod,
|
|
665
|
-
shippingMethodId,
|
|
666
|
-
promoCode
|
|
667
|
-
}) {
|
|
668
|
-
try {
|
|
669
|
-
const config = getGlobalConfig();
|
|
670
|
-
const lines = items.map((item) => ({
|
|
671
|
-
type: "PRODUCT_VARIANT",
|
|
672
|
-
productId: item.productId,
|
|
673
|
-
variantId: item.variantId,
|
|
674
|
-
quantity: item.quantity
|
|
675
|
-
}));
|
|
676
|
-
const payload = {
|
|
677
|
-
businessId,
|
|
678
|
-
market,
|
|
679
|
-
currency: currency2,
|
|
680
|
-
userId,
|
|
681
|
-
paymentMethod,
|
|
682
|
-
lines,
|
|
683
|
-
...shippingMethodId && { shippingMethodId },
|
|
684
|
-
...promoCode && { promoCode }
|
|
685
|
-
};
|
|
686
|
-
const res = await fetch(`${config.apiUrl}/v1/payments/quote`, {
|
|
687
|
-
method: "POST",
|
|
688
|
-
headers: {
|
|
689
|
-
"Content-Type": "application/json",
|
|
690
|
-
Authorization: `Bearer ${token}`
|
|
691
|
-
},
|
|
692
|
-
body: JSON.stringify(payload)
|
|
693
|
-
});
|
|
694
|
-
const text = await res.text();
|
|
695
|
-
if (!res.ok) {
|
|
696
|
-
try {
|
|
697
|
-
const json = JSON.parse(text);
|
|
698
|
-
return { success: false, error: json.reason || json.error || res.statusText, code: json.code };
|
|
699
|
-
} catch {
|
|
700
|
-
return { success: false, error: text || res.statusText };
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
const quote = text ? JSON.parse(text) : null;
|
|
704
|
-
return { success: true, data: quote };
|
|
705
|
-
} catch (e) {
|
|
706
|
-
console.error("Quote fetch failed:", e);
|
|
707
|
-
return {
|
|
708
|
-
success: false,
|
|
709
|
-
error: e.message
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
},
|
|
713
|
-
// Checkout - Backend calculates currency from market
|
|
714
|
-
async checkout({
|
|
715
|
-
token,
|
|
716
|
-
businessId,
|
|
717
|
-
items,
|
|
718
|
-
paymentMethod,
|
|
719
|
-
blocks,
|
|
720
|
-
market = "US",
|
|
721
|
-
shippingMethodId,
|
|
722
|
-
promoCode,
|
|
723
|
-
paymentIntentId = null
|
|
724
|
-
}) {
|
|
725
|
-
try {
|
|
726
|
-
const config = getGlobalConfig();
|
|
727
|
-
const payload = {
|
|
728
|
-
businessId,
|
|
729
|
-
items,
|
|
730
|
-
paymentMethod,
|
|
731
|
-
blocks,
|
|
732
|
-
market,
|
|
733
|
-
...shippingMethodId && { shippingMethodId },
|
|
734
|
-
...promoCode && { promoCode },
|
|
735
|
-
...paymentIntentId && { paymentIntentId }
|
|
736
|
-
};
|
|
737
|
-
const res = await fetch(`${config.apiUrl}/v1/businesses/${encodeURIComponent(businessId)}/orders/checkout`, {
|
|
738
|
-
method: "POST",
|
|
739
|
-
headers: {
|
|
740
|
-
"Content-Type": "application/json",
|
|
741
|
-
Authorization: `Bearer ${token}`
|
|
742
|
-
},
|
|
743
|
-
body: JSON.stringify(payload)
|
|
744
|
-
});
|
|
745
|
-
const text = await res.text();
|
|
746
|
-
if (!res.ok) {
|
|
747
|
-
try {
|
|
748
|
-
const json2 = JSON.parse(text);
|
|
749
|
-
return { success: false, error: json2.reason || json2.error || res.statusText, code: json2.code };
|
|
750
|
-
} catch {
|
|
751
|
-
return { success: false, error: text || res.statusText };
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
const json = text ? JSON.parse(text) : null;
|
|
755
|
-
return { success: true, data: json };
|
|
756
|
-
} catch (e) {
|
|
757
|
-
return {
|
|
758
|
-
success: false,
|
|
759
|
-
error: e.message
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
},
|
|
763
|
-
// Create payment intent for Stripe
|
|
764
|
-
async createPaymentIntent({ amount, currency: currency2, businessId }) {
|
|
765
|
-
try {
|
|
766
|
-
const config = getGlobalConfig();
|
|
767
|
-
const tokenResponse = await reservationApi.getGuestToken();
|
|
768
|
-
if (!tokenResponse.success || !tokenResponse.data) {
|
|
769
|
-
throw new Error("Failed to get guest token");
|
|
770
|
-
}
|
|
771
|
-
const token = tokenResponse.data.token;
|
|
772
|
-
const res = await fetch(`${config.apiUrl}/v1/businesses/${encodeURIComponent(businessId)}/payment/create-intent`, {
|
|
773
|
-
method: "POST",
|
|
774
|
-
headers: {
|
|
775
|
-
"Content-Type": "application/json",
|
|
776
|
-
Authorization: `Bearer ${token}`
|
|
777
|
-
},
|
|
778
|
-
body: JSON.stringify({
|
|
779
|
-
amount,
|
|
780
|
-
currency: currency2,
|
|
781
|
-
businessId
|
|
782
|
-
})
|
|
783
|
-
});
|
|
784
|
-
if (!res.ok) {
|
|
785
|
-
const error = await res.text() || res.statusText;
|
|
786
|
-
throw new Error(error);
|
|
787
|
-
}
|
|
788
|
-
const json = await res.json();
|
|
789
|
-
return {
|
|
790
|
-
success: true,
|
|
791
|
-
data: json
|
|
792
|
-
};
|
|
793
|
-
} catch (e) {
|
|
794
|
-
console.error("Payment intent creation failed:", e);
|
|
795
|
-
return {
|
|
796
|
-
success: false,
|
|
797
|
-
error: e.message
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
|
|
803
|
-
// src/utils/price.ts
|
|
804
|
-
var CURRENCY_SYMBOLS = {
|
|
805
|
-
"USD": "$",
|
|
806
|
-
"EUR": "\u20AC",
|
|
807
|
-
"GBP": "\xA3",
|
|
808
|
-
"CAD": "C$",
|
|
809
|
-
"AUD": "A$"
|
|
810
|
-
};
|
|
811
|
-
var MARKET_CURRENCIES = {
|
|
812
|
-
"US": "USD",
|
|
813
|
-
"EU": "EUR",
|
|
814
|
-
"UK": "GBP",
|
|
815
|
-
"CA": "CAD",
|
|
816
|
-
"AU": "AUD"
|
|
817
|
-
};
|
|
818
|
-
function convertToMajor(minorAmount) {
|
|
819
|
-
return (minorAmount ?? 0) / 100;
|
|
820
|
-
}
|
|
821
|
-
function getSymbol(currency2) {
|
|
822
|
-
return CURRENCY_SYMBOLS[currency2] || "$";
|
|
823
|
-
}
|
|
824
|
-
function getCurrencyFromMarket(marketId) {
|
|
825
|
-
return MARKET_CURRENCIES[marketId] || "USD";
|
|
826
|
-
}
|
|
827
|
-
function formatCurrencyAmount(amount, currency2, options = {}) {
|
|
828
|
-
const { showSymbols = true, decimalPlaces = 2, customSymbol } = options;
|
|
829
|
-
const roundedAmount = amount.toFixed(decimalPlaces);
|
|
830
|
-
if (!showSymbols) {
|
|
831
|
-
return `${roundedAmount} ${currency2}`;
|
|
832
|
-
}
|
|
833
|
-
const symbol = customSymbol || getSymbol(currency2);
|
|
834
|
-
return `${symbol}${roundedAmount}`;
|
|
835
|
-
}
|
|
836
|
-
function formatMinor(amountMinor, currency2, options = {}) {
|
|
837
|
-
const major = convertToMajor(amountMinor);
|
|
838
|
-
return formatCurrencyAmount(major, currency2, options);
|
|
839
|
-
}
|
|
840
|
-
function formatPayment(payment, options = {}) {
|
|
841
|
-
if (!payment) return "";
|
|
842
|
-
const { showSymbols = true, decimalPlaces = 2, showBreakdown = false } = options;
|
|
843
|
-
if (showBreakdown) {
|
|
844
|
-
const subtotal = formatMinor(payment.subtotal, payment.currency, { showSymbols, decimalPlaces });
|
|
845
|
-
const discount = (payment.discount ?? 0) > 0 ? formatMinor(payment.discount, payment.currency, { showSymbols, decimalPlaces }) : null;
|
|
846
|
-
const tax = (payment.tax ?? 0) > 0 ? formatMinor(payment.tax, payment.currency, { showSymbols, decimalPlaces }) : null;
|
|
847
|
-
const total = formatMinor(payment.total, payment.currency, { showSymbols, decimalPlaces });
|
|
848
|
-
let result = `Subtotal: ${subtotal}`;
|
|
849
|
-
if (discount) result += `, Discount: -${discount}`;
|
|
850
|
-
if (tax) result += `, Tax: ${tax}`;
|
|
851
|
-
result += `, Total: ${total}`;
|
|
852
|
-
return result;
|
|
853
|
-
}
|
|
854
|
-
return formatMinor(payment.total, payment.currency, { showSymbols, decimalPlaces });
|
|
855
|
-
}
|
|
856
|
-
function getMarketPrice(prices, marketId, businessMarkets, options = {}) {
|
|
857
|
-
if (!prices || prices.length === 0) return "";
|
|
858
|
-
const {
|
|
859
|
-
showSymbols = true,
|
|
860
|
-
decimalPlaces = 2,
|
|
861
|
-
showCompareAt = true,
|
|
862
|
-
fallbackMarket = "US"
|
|
863
|
-
} = options;
|
|
864
|
-
let price = prices.find((p) => p.market === marketId);
|
|
865
|
-
if (!price) {
|
|
866
|
-
price = prices.find((p) => p.market === fallbackMarket) || prices[0];
|
|
867
|
-
}
|
|
868
|
-
if (!price) return "";
|
|
869
|
-
let currency2;
|
|
870
|
-
let symbol;
|
|
871
|
-
{
|
|
872
|
-
currency2 = getCurrencyFromMarket(price.market);
|
|
873
|
-
symbol = getSymbol(currency2);
|
|
874
|
-
}
|
|
875
|
-
const formattedPrice = formatMinor(price.amount ?? 0, currency2, {
|
|
876
|
-
showSymbols,
|
|
877
|
-
decimalPlaces,
|
|
878
|
-
customSymbol: symbol
|
|
879
|
-
});
|
|
880
|
-
if (showCompareAt && price.compareAt && price.compareAt > (price.amount ?? 0)) {
|
|
881
|
-
const formattedCompareAt = formatMinor(price.compareAt, currency2, {
|
|
882
|
-
showSymbols,
|
|
883
|
-
decimalPlaces,
|
|
884
|
-
customSymbol: symbol
|
|
885
|
-
});
|
|
886
|
-
return `${formattedPrice} was ${formattedCompareAt}`;
|
|
887
|
-
}
|
|
888
|
-
return formattedPrice;
|
|
889
|
-
}
|
|
890
|
-
function getPriceAmount(prices, marketId, fallbackMarket = "US") {
|
|
891
|
-
if (!prices || prices.length === 0) return 0;
|
|
892
|
-
const price = prices.find((p) => p.market === marketId) || prices.find((p) => p.market === fallbackMarket) || prices[0];
|
|
893
|
-
return price?.amount || 0;
|
|
894
|
-
}
|
|
895
|
-
function createPaymentForCheckout(subtotalMinor, marketId, currency2, paymentMethod, options = {}) {
|
|
896
|
-
const { discount = 0, tax = 0, promoCodeId } = options;
|
|
897
|
-
const total = subtotalMinor - discount + tax;
|
|
898
|
-
return {
|
|
899
|
-
currency: currency2,
|
|
900
|
-
market: marketId,
|
|
901
|
-
subtotal: subtotalMinor,
|
|
902
|
-
shipping: 0,
|
|
903
|
-
discount,
|
|
904
|
-
tax,
|
|
905
|
-
total,
|
|
906
|
-
promoCodeId,
|
|
907
|
-
method: paymentMethod
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// src/stores/eshop.ts
|
|
912
|
-
var cartItems = persistentAtom("eshopCart", [], {
|
|
913
|
-
encode: JSON.stringify,
|
|
914
|
-
decode: JSON.parse
|
|
915
|
-
});
|
|
916
|
-
var promoCodeAtom = atom(null);
|
|
917
|
-
var quoteAtom = atom(null);
|
|
918
|
-
var store = deepMap({
|
|
919
|
-
selectedShippingMethodId: null,
|
|
920
|
-
// Selected shipping method ID
|
|
921
|
-
shippingLocation: null,
|
|
922
|
-
// Deprecated; kept for backward compat
|
|
923
|
-
userToken: null,
|
|
924
|
-
processingCheckout: false,
|
|
925
|
-
loading: false,
|
|
926
|
-
error: null,
|
|
927
|
-
// Phone verification
|
|
928
|
-
phoneNumber: "",
|
|
929
|
-
phoneError: null,
|
|
930
|
-
verificationCode: "",
|
|
931
|
-
verifyError: null,
|
|
932
|
-
// Quote fetching
|
|
933
|
-
fetchingQuote: false,
|
|
934
|
-
quoteError: null
|
|
935
|
-
});
|
|
936
|
-
var cartTotal = computed([cartItems, selectedMarket, currency], (items, market, curr) => {
|
|
937
|
-
const subtotalMinor = (items || []).reduce((sum, item) => {
|
|
938
|
-
let amountMinor = 0;
|
|
939
|
-
if ("amount" in item.price) {
|
|
940
|
-
amountMinor = item.price.amount || 0;
|
|
941
|
-
}
|
|
942
|
-
return sum + amountMinor * item.quantity;
|
|
943
|
-
}, 0);
|
|
944
|
-
const marketId = market?.id || "us";
|
|
945
|
-
const currencyCode = curr || "USD";
|
|
946
|
-
return createPaymentForCheckout(subtotalMinor, marketId, currencyCode, "CASH" /* Cash */);
|
|
947
|
-
});
|
|
948
|
-
var cartItemCount = computed(cartItems, (items) => {
|
|
949
|
-
return items.reduce((sum, item) => sum + item.quantity, 0);
|
|
950
|
-
});
|
|
951
|
-
var allowedPaymentMethods = paymentMethods;
|
|
952
|
-
var actions = {
|
|
953
|
-
// Add item to cart
|
|
954
|
-
addItem(product, variant, quantity = 1) {
|
|
955
|
-
const items = cartItems.get();
|
|
956
|
-
const market = selectedMarket.get();
|
|
957
|
-
const existingItemIndex = items.findIndex(
|
|
958
|
-
(item) => item.productId === product.id && item.variantId === variant.id
|
|
959
|
-
);
|
|
960
|
-
if (existingItemIndex !== -1) {
|
|
961
|
-
const updatedItems = [...items];
|
|
962
|
-
updatedItems[existingItemIndex].quantity += quantity;
|
|
963
|
-
cartItems.set(updatedItems);
|
|
964
|
-
} else {
|
|
965
|
-
let cartPrice;
|
|
966
|
-
if (variant.prices && Array.isArray(variant.prices)) {
|
|
967
|
-
const marketCode = market?.id || "us";
|
|
968
|
-
const marketAmount = getPriceAmount(variant.prices, marketCode);
|
|
969
|
-
cartPrice = {
|
|
970
|
-
amount: marketAmount ?? 0,
|
|
971
|
-
market: marketCode
|
|
972
|
-
};
|
|
973
|
-
} else {
|
|
974
|
-
cartPrice = { amount: 0, market: market?.id || "us" };
|
|
975
|
-
}
|
|
976
|
-
const newItem = {
|
|
977
|
-
id: crypto.randomUUID(),
|
|
978
|
-
productId: product.id,
|
|
979
|
-
variantId: variant.id,
|
|
980
|
-
productName: product.name,
|
|
981
|
-
productSlug: product.slug,
|
|
982
|
-
variantAttributes: variant.attributes || {},
|
|
983
|
-
price: cartPrice,
|
|
984
|
-
quantity,
|
|
985
|
-
addedAt: Date.now()
|
|
986
|
-
};
|
|
987
|
-
cartItems.set([...items, newItem]);
|
|
988
|
-
}
|
|
989
|
-
},
|
|
990
|
-
// Update item quantity
|
|
991
|
-
updateQuantity(itemId, newQuantity) {
|
|
992
|
-
const items = cartItems.get();
|
|
993
|
-
const updatedItems = items.map(
|
|
994
|
-
(item) => item.id === itemId ? { ...item, quantity: Math.max(1, newQuantity) } : item
|
|
995
|
-
);
|
|
996
|
-
cartItems.set(updatedItems);
|
|
997
|
-
},
|
|
998
|
-
// Remove item from cart
|
|
999
|
-
removeItem(itemId) {
|
|
1000
|
-
const items = cartItems.get();
|
|
1001
|
-
const updatedItems = items.filter((item) => item.id !== itemId);
|
|
1002
|
-
cartItems.set(updatedItems);
|
|
1003
|
-
},
|
|
1004
|
-
// Clear entire cart
|
|
1005
|
-
clearCart() {
|
|
1006
|
-
cartItems.set([]);
|
|
1007
|
-
},
|
|
1008
|
-
// Get guest token
|
|
1009
|
-
async getGuestToken() {
|
|
1010
|
-
const state = store.get();
|
|
1011
|
-
const token = await getGuestToken(state.userToken);
|
|
1012
|
-
if (token !== state.userToken) {
|
|
1013
|
-
store.setKey("userToken", token);
|
|
1014
|
-
}
|
|
1015
|
-
return token;
|
|
1016
|
-
},
|
|
1017
|
-
// Prepare order items for checkout API
|
|
1018
|
-
prepareOrderItems() {
|
|
1019
|
-
const items = cartItems.get();
|
|
1020
|
-
return items.map((item) => ({
|
|
1021
|
-
productId: item.productId,
|
|
1022
|
-
variantId: item.variantId,
|
|
1023
|
-
quantity: item.quantity
|
|
1024
|
-
}));
|
|
1025
|
-
},
|
|
1026
|
-
// Get order info blocks (they already have values from DynamicForm)
|
|
1027
|
-
getOrderInfoBlocks() {
|
|
1028
|
-
return orderBlocks.get() || [];
|
|
1029
|
-
},
|
|
1030
|
-
// Process checkout - Updated to use Payment structure
|
|
1031
|
-
async checkout(paymentMethod = "CASH" /* Cash */, orderInfoBlocks, promoCode) {
|
|
1032
|
-
const items = cartItems.get();
|
|
1033
|
-
if (!items.length) {
|
|
1034
|
-
return { success: false, error: "Cart is empty" };
|
|
1035
|
-
}
|
|
1036
|
-
try {
|
|
1037
|
-
store.setKey("processingCheckout", true);
|
|
1038
|
-
store.setKey("error", null);
|
|
1039
|
-
const token = await this.getGuestToken();
|
|
1040
|
-
const orderItems = this.prepareOrderItems();
|
|
1041
|
-
const blocks = orderInfoBlocks || this.getOrderInfoBlocks();
|
|
1042
|
-
const state = store.get();
|
|
1043
|
-
const market = selectedMarket.get();
|
|
1044
|
-
if (!market) {
|
|
1045
|
-
throw new Error("No market selected");
|
|
1046
|
-
}
|
|
1047
|
-
const locationBlock = blocks.find((b) => b.key === "location" && b.type === "GEO_LOCATION");
|
|
1048
|
-
const countryCode = locationBlock?.value?.[0]?.countryCode;
|
|
1049
|
-
if (!countryCode) {
|
|
1050
|
-
throw new Error("Country is required for checkout");
|
|
1051
|
-
}
|
|
1052
|
-
const availableShippingMethods = getShippingMethodsForCountry(countryCode) || [];
|
|
1053
|
-
if (!availableShippingMethods || availableShippingMethods.length === 0) {
|
|
1054
|
-
throw new Error(`No shipping methods available for country: ${countryCode}`);
|
|
1055
|
-
}
|
|
1056
|
-
const shippingMethodId = state.selectedShippingMethodId;
|
|
1057
|
-
const shippingMethod = availableShippingMethods.find((sm) => sm.id === shippingMethodId) || availableShippingMethods[0];
|
|
1058
|
-
if (!shippingMethod) {
|
|
1059
|
-
throw new Error("No shipping method available");
|
|
1060
|
-
}
|
|
1061
|
-
const config = getGlobalConfig();
|
|
1062
|
-
const promo = promoCode !== void 0 ? promoCode : promoCodeAtom.get();
|
|
1063
|
-
const response = await eshopApi.checkout({
|
|
1064
|
-
token,
|
|
1065
|
-
businessId: config.businessId,
|
|
1066
|
-
items: orderItems,
|
|
1067
|
-
paymentMethod,
|
|
1068
|
-
blocks,
|
|
1069
|
-
market: market.id,
|
|
1070
|
-
shippingMethodId: shippingMethod.id,
|
|
1071
|
-
promoCode: promo || void 0
|
|
1072
|
-
});
|
|
1073
|
-
if (response.success) {
|
|
1074
|
-
return {
|
|
1075
|
-
success: true,
|
|
1076
|
-
data: {
|
|
1077
|
-
orderId: response.data.orderId,
|
|
1078
|
-
orderNumber: response.data.orderNumber,
|
|
1079
|
-
clientSecret: response.data.clientSecret
|
|
1080
|
-
}
|
|
1081
|
-
};
|
|
1082
|
-
} else {
|
|
1083
|
-
throw new Error(response.error || "Failed to place order");
|
|
1084
|
-
}
|
|
1085
|
-
} catch (err) {
|
|
1086
|
-
const errorMessage = `Checkout failed: ${err.message}`;
|
|
1087
|
-
store.setKey("error", errorMessage);
|
|
1088
|
-
console.error("Checkout error:", err);
|
|
1089
|
-
return { success: false, error: errorMessage };
|
|
1090
|
-
} finally {
|
|
1091
|
-
store.setKey("processingCheckout", false);
|
|
1092
|
-
}
|
|
1093
|
-
},
|
|
1094
|
-
// Phone verification for eshop
|
|
1095
|
-
async updateProfilePhone() {
|
|
1096
|
-
try {
|
|
1097
|
-
const token = await this.getGuestToken();
|
|
1098
|
-
const phoneNumber = store.get().phoneNumber;
|
|
1099
|
-
await updateProfilePhone(token, phoneNumber);
|
|
1100
|
-
store.setKey("phoneError", null);
|
|
1101
|
-
return true;
|
|
1102
|
-
} catch (error) {
|
|
1103
|
-
console.error("Phone update error:", error);
|
|
1104
|
-
store.setKey("phoneError", error.message);
|
|
1105
|
-
return false;
|
|
1106
|
-
}
|
|
1107
|
-
},
|
|
1108
|
-
async verifyPhoneCode() {
|
|
1109
|
-
try {
|
|
1110
|
-
const token = await this.getGuestToken();
|
|
1111
|
-
const phoneNumber = store.get().phoneNumber;
|
|
1112
|
-
const verificationCode = store.get().verificationCode;
|
|
1113
|
-
await verifyPhoneCode(token, phoneNumber, verificationCode);
|
|
1114
|
-
store.setKey("verifyError", null);
|
|
1115
|
-
return true;
|
|
1116
|
-
} catch (error) {
|
|
1117
|
-
console.error("Phone verification error:", error);
|
|
1118
|
-
store.setKey("verifyError", error.message);
|
|
1119
|
-
return false;
|
|
1120
|
-
}
|
|
1121
|
-
},
|
|
1122
|
-
formatPrice(priceOrPayment) {
|
|
1123
|
-
const currencyCode = currency.get();
|
|
1124
|
-
if ("total" in priceOrPayment) {
|
|
1125
|
-
return formatPayment(priceOrPayment, { showSymbols: true, decimalPlaces: 2 });
|
|
1126
|
-
}
|
|
1127
|
-
return formatMinor(priceOrPayment.amount || 0, currencyCode);
|
|
1128
|
-
},
|
|
1129
|
-
getCartPayment() {
|
|
1130
|
-
const items = cartItems.get();
|
|
1131
|
-
const market = selectedMarket.get();
|
|
1132
|
-
const currencyCode = currency.get();
|
|
1133
|
-
const marketId = market?.id || "us";
|
|
1134
|
-
if (!items || items.length === 0) {
|
|
1135
|
-
return createPaymentForCheckout(0, marketId, currencyCode, "CASH" /* Cash */);
|
|
1136
|
-
}
|
|
1137
|
-
const subtotalMinor = items.reduce((sum, item) => {
|
|
1138
|
-
let amountMinor = 0;
|
|
1139
|
-
if ("amount" in item.price) {
|
|
1140
|
-
amountMinor = item.price.amount || 0;
|
|
1141
|
-
}
|
|
1142
|
-
return sum + amountMinor * item.quantity;
|
|
1143
|
-
}, 0);
|
|
1144
|
-
return createPaymentForCheckout(subtotalMinor, marketId, currencyCode, "CASH" /* Cash */);
|
|
1145
|
-
},
|
|
1146
|
-
// Get available payment methods for selected market
|
|
1147
|
-
getAvailablePaymentMethods() {
|
|
1148
|
-
return paymentMethods.get() || ["CASH" /* Cash */];
|
|
1149
|
-
},
|
|
1150
|
-
// Get shipping methods for a country code
|
|
1151
|
-
getShippingMethodsForCountry(countryCode) {
|
|
1152
|
-
return getShippingMethodsForCountry(countryCode);
|
|
1153
|
-
},
|
|
1154
|
-
// Fetch quote from backend
|
|
1155
|
-
async fetchQuote(promoCode) {
|
|
1156
|
-
const items = cartItems.get();
|
|
1157
|
-
const market = selectedMarket.get();
|
|
1158
|
-
const currencyCode = currency.get();
|
|
1159
|
-
const state = store.get();
|
|
1160
|
-
const promo = promoCode !== void 0 ? promoCode : promoCodeAtom.get();
|
|
1161
|
-
if (!items || items.length === 0) {
|
|
1162
|
-
quoteAtom.set(null);
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
if (!market) {
|
|
1166
|
-
console.error("No market selected for quote");
|
|
1167
|
-
return;
|
|
1168
|
-
}
|
|
1169
|
-
try {
|
|
1170
|
-
store.setKey("fetchingQuote", true);
|
|
1171
|
-
store.setKey("quoteError", null);
|
|
1172
|
-
const config = getGlobalConfig();
|
|
1173
|
-
const token = await this.getGuestToken();
|
|
1174
|
-
const shippingMethodId = state.selectedShippingMethodId || void 0;
|
|
1175
|
-
const response = await eshopApi.getQuote({
|
|
1176
|
-
token,
|
|
1177
|
-
businessId: config.businessId,
|
|
1178
|
-
items: items.map((item) => ({
|
|
1179
|
-
productId: item.productId,
|
|
1180
|
-
variantId: item.variantId,
|
|
1181
|
-
quantity: item.quantity
|
|
1182
|
-
})),
|
|
1183
|
-
market: market.id,
|
|
1184
|
-
currency: currencyCode,
|
|
1185
|
-
userId: token,
|
|
1186
|
-
paymentMethod: "CASH" /* Cash */,
|
|
1187
|
-
shippingMethodId,
|
|
1188
|
-
promoCode: promo || void 0
|
|
1189
|
-
});
|
|
1190
|
-
if (response.success && response.data) {
|
|
1191
|
-
quoteAtom.set(response.data);
|
|
1192
|
-
} else {
|
|
1193
|
-
const friendly = mapQuoteError(response.code, response.error);
|
|
1194
|
-
store.setKey("quoteError", friendly);
|
|
1195
|
-
quoteAtom.set(null);
|
|
1196
|
-
}
|
|
1197
|
-
} catch (error) {
|
|
1198
|
-
console.error("Quote fetch error:", error);
|
|
1199
|
-
store.setKey("quoteError", error.message);
|
|
1200
|
-
quoteAtom.set(null);
|
|
1201
|
-
} finally {
|
|
1202
|
-
store.setKey("fetchingQuote", false);
|
|
1203
|
-
}
|
|
1204
|
-
},
|
|
1205
|
-
// Apply promo code
|
|
1206
|
-
async applyPromoCode(code) {
|
|
1207
|
-
promoCodeAtom.set(code);
|
|
1208
|
-
await this.fetchQuote();
|
|
1209
|
-
},
|
|
1210
|
-
// Remove promo code
|
|
1211
|
-
async removePromoCode() {
|
|
1212
|
-
promoCodeAtom.set(null);
|
|
1213
|
-
await this.fetchQuote();
|
|
1214
|
-
}
|
|
1215
|
-
};
|
|
1216
|
-
function mapQuoteError(code, fallback) {
|
|
1217
|
-
switch (code) {
|
|
1218
|
-
case "PROMO.MIN_ORDER":
|
|
1219
|
-
return fallback || "Promo requires a higher minimum order.";
|
|
1220
|
-
case "PROMO.NOT_ACTIVE":
|
|
1221
|
-
return "Promo code is not active.";
|
|
1222
|
-
case "PROMO.NOT_YET_VALID":
|
|
1223
|
-
return "Promo code is not yet valid.";
|
|
1224
|
-
case "PROMO.EXPIRED":
|
|
1225
|
-
return "Promo code has expired.";
|
|
1226
|
-
case "PROMO.MAX_USES":
|
|
1227
|
-
return "Promo code usage limit exceeded.";
|
|
1228
|
-
case "PROMO.MAX_USES_PER_USER":
|
|
1229
|
-
return "You have already used this promo code.";
|
|
1230
|
-
case "PROMO.NOT_FOUND":
|
|
1231
|
-
return "Promo code not found.";
|
|
1232
|
-
default:
|
|
1233
|
-
return fallback || "Failed to fetch quote.";
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
function initEshopStore() {
|
|
1237
|
-
businessActions.init();
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
// src/utils/i18n.ts
|
|
1241
|
-
var defaultLocale = "en";
|
|
1242
|
-
function getLocale() {
|
|
1243
|
-
if (typeof window !== "undefined" && window.navigator) {
|
|
1244
|
-
return window.navigator.language.split("-")[0] || defaultLocale;
|
|
1245
|
-
}
|
|
1246
|
-
return defaultLocale;
|
|
1247
|
-
}
|
|
1248
|
-
function getLocalizedString(value, locale) {
|
|
1249
|
-
if (!value) return "";
|
|
1250
|
-
if (typeof value === "string") return value;
|
|
1251
|
-
if (typeof value === "object") {
|
|
1252
|
-
const targetLocale = locale || getLocale();
|
|
1253
|
-
return value[targetLocale] || value["en"] || value[Object.keys(value)[0]] || "";
|
|
1254
|
-
}
|
|
1255
|
-
return String(value);
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
// src/utils/validation.ts
|
|
1259
|
-
function validatePhoneNumber(phone) {
|
|
1260
|
-
if (!phone) {
|
|
1261
|
-
return { isValid: false, error: "Phone number is required" };
|
|
1262
|
-
}
|
|
1263
|
-
const cleaned = phone.replace(/\D/g, "");
|
|
1264
|
-
if (cleaned.length < 8) {
|
|
1265
|
-
return { isValid: false, error: "Phone number is too short" };
|
|
1266
|
-
}
|
|
1267
|
-
if (cleaned.length > 15) {
|
|
1268
|
-
return { isValid: false, error: "Phone number is too long" };
|
|
1269
|
-
}
|
|
1270
|
-
return { isValid: true };
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
// src/utils/timezone.ts
|
|
1274
|
-
var tzGroups = [
|
|
1275
|
-
{
|
|
1276
|
-
label: "US",
|
|
1277
|
-
zones: [
|
|
1278
|
-
{ label: "Eastern Time", value: "America/New_York" },
|
|
1279
|
-
{ label: "Central Time", value: "America/Chicago" },
|
|
1280
|
-
{ label: "Mountain Time", value: "America/Denver" },
|
|
1281
|
-
{ label: "Pacific Time", value: "America/Los_Angeles" }
|
|
1282
|
-
]
|
|
1283
|
-
},
|
|
1284
|
-
{
|
|
1285
|
-
label: "Europe",
|
|
1286
|
-
zones: [
|
|
1287
|
-
{ label: "London", value: "Europe/London" },
|
|
1288
|
-
{ label: "Paris", value: "Europe/Paris" },
|
|
1289
|
-
{ label: "Berlin", value: "Europe/Berlin" },
|
|
1290
|
-
{ label: "Rome", value: "Europe/Rome" }
|
|
1291
|
-
]
|
|
1292
|
-
},
|
|
1293
|
-
{
|
|
1294
|
-
label: "Asia",
|
|
1295
|
-
zones: [
|
|
1296
|
-
{ label: "Tokyo", value: "Asia/Tokyo" },
|
|
1297
|
-
{ label: "Shanghai", value: "Asia/Shanghai" },
|
|
1298
|
-
{ label: "Mumbai", value: "Asia/Kolkata" },
|
|
1299
|
-
{ label: "Dubai", value: "Asia/Dubai" }
|
|
1300
|
-
]
|
|
1301
|
-
}
|
|
1302
|
-
];
|
|
1303
|
-
function findTimeZone(groups) {
|
|
1304
|
-
try {
|
|
1305
|
-
const detected = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1306
|
-
for (const group of groups) {
|
|
1307
|
-
for (const zone of group.zones) {
|
|
1308
|
-
if (zone.value === detected) {
|
|
1309
|
-
return detected;
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
return "UTC";
|
|
1314
|
-
} catch (e) {
|
|
1315
|
-
return "UTC";
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
// src/stores/reservation.ts
|
|
1320
|
-
var cartParts = persistentAtom("reservationCart", [], {
|
|
1321
|
-
encode: JSON.stringify,
|
|
1322
|
-
decode: JSON.parse
|
|
1323
|
-
});
|
|
1324
|
-
var store2 = deepMap({
|
|
1325
|
-
currentStep: 1,
|
|
1326
|
-
totalSteps: 4,
|
|
1327
|
-
steps: {
|
|
1328
|
-
1: { name: "method", labelKey: "method" },
|
|
1329
|
-
2: { name: "provider", labelKey: "provider" },
|
|
1330
|
-
3: { name: "datetime", labelKey: "datetime" },
|
|
1331
|
-
4: { name: "review", labelKey: "review" }
|
|
1332
|
-
},
|
|
1333
|
-
// Calendar data
|
|
1334
|
-
weekdays: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
|
1335
|
-
monthYear: "",
|
|
1336
|
-
days: [],
|
|
1337
|
-
current: /* @__PURE__ */ new Date(),
|
|
1338
|
-
// Selection state
|
|
1339
|
-
selectedDate: null,
|
|
1340
|
-
slots: [],
|
|
1341
|
-
selectedSlot: null,
|
|
1342
|
-
selectedMethod: null,
|
|
1343
|
-
selectedProvider: null,
|
|
1344
|
-
providers: [],
|
|
1345
|
-
// Status flags
|
|
1346
|
-
loading: false,
|
|
1347
|
-
startDate: null,
|
|
1348
|
-
endDate: null,
|
|
1349
|
-
isMultiDay: false,
|
|
1350
|
-
// Phone verification
|
|
1351
|
-
phoneNumber: "",
|
|
1352
|
-
phoneError: null,
|
|
1353
|
-
phoneSuccess: null,
|
|
1354
|
-
verificationCode: "",
|
|
1355
|
-
verifyError: null,
|
|
1356
|
-
isPhoneVerified: false,
|
|
1357
|
-
isSendingCode: false,
|
|
1358
|
-
isVerifying: false,
|
|
1359
|
-
codeSentAt: null,
|
|
1360
|
-
canResendAt: null,
|
|
1361
|
-
// Quote state
|
|
1362
|
-
fetchingQuote: false,
|
|
1363
|
-
quote: null,
|
|
1364
|
-
quoteError: null,
|
|
1365
|
-
// Service & config
|
|
1366
|
-
guestToken: null,
|
|
1367
|
-
service: null,
|
|
1368
|
-
timezone: findTimeZone(tzGroups),
|
|
1369
|
-
tzGroups,
|
|
1370
|
-
parts: []
|
|
1371
|
-
});
|
|
1372
|
-
var currentStepName = computed(store2, (state) => {
|
|
1373
|
-
return state?.steps?.[state?.currentStep]?.name || "";
|
|
1374
|
-
});
|
|
1375
|
-
var canProceed = computed(store2, (state) => {
|
|
1376
|
-
const stepName = state?.steps?.[state?.currentStep]?.name;
|
|
1377
|
-
switch (stepName) {
|
|
1378
|
-
case "method":
|
|
1379
|
-
return !!state.selectedMethod;
|
|
1380
|
-
case "provider":
|
|
1381
|
-
return !!state.selectedProvider;
|
|
1382
|
-
case "datetime":
|
|
1383
|
-
return state.isMultiDay ? !!(state.startDate && state.endDate && state.selectedSlot) : !!(state.selectedDate && state.selectedSlot);
|
|
1384
|
-
case "review":
|
|
1385
|
-
return true;
|
|
1386
|
-
default:
|
|
1387
|
-
return false;
|
|
1388
|
-
}
|
|
1389
|
-
});
|
|
1390
|
-
var createCalendarGrid = (date) => {
|
|
1391
|
-
const first = new Date(date.getFullYear(), date.getMonth(), 1);
|
|
1392
|
-
const last = new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
|
1393
|
-
const cells = [];
|
|
1394
|
-
const pad = (first.getDay() + 6) % 7;
|
|
1395
|
-
for (let i = 0; i < pad; i++) cells.push({ key: `b-${i}`, blank: true });
|
|
1396
|
-
for (let d = 1; d <= last.getDate(); d++) {
|
|
1397
|
-
cells.push({
|
|
1398
|
-
key: `d-${d}`,
|
|
1399
|
-
blank: false,
|
|
1400
|
-
date: new Date(date.getFullYear(), date.getMonth(), d),
|
|
1401
|
-
available: false
|
|
1402
|
-
});
|
|
1403
|
-
}
|
|
1404
|
-
const suffix = (7 - cells.length % 7) % 7;
|
|
1405
|
-
for (let i = 0; i < suffix; i++) cells.push({ key: `b2-${i}`, blank: true });
|
|
1406
|
-
return cells;
|
|
1407
|
-
};
|
|
1408
|
-
var formatTimeSlot = (from, to, timezone) => {
|
|
1409
|
-
const opts = { hour: "2-digit", minute: "2-digit", timeZone: timezone };
|
|
1410
|
-
return `${new Date(from * 1e3).toLocaleTimeString([], opts)} \u2013 ${new Date(to * 1e3).toLocaleTimeString([], opts)}`;
|
|
1411
|
-
};
|
|
1412
|
-
var actions2 = {
|
|
1413
|
-
// Calendar management
|
|
1414
|
-
updateCalendarGrid() {
|
|
1415
|
-
const state = store2.get();
|
|
1416
|
-
const cur = state.current || new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1);
|
|
1417
|
-
const days = createCalendarGrid(cur);
|
|
1418
|
-
store2.setKey("current", cur);
|
|
1419
|
-
store2.setKey("monthYear", cur.toLocaleString(void 0, { month: "long", year: "numeric" }));
|
|
1420
|
-
store2.setKey("days", days);
|
|
1421
|
-
},
|
|
1422
|
-
updateCalendar() {
|
|
1423
|
-
this.updateCalendarGrid();
|
|
1424
|
-
const state = store2.get();
|
|
1425
|
-
if (state.service) this.fetchAvailability("month");
|
|
1426
|
-
},
|
|
1427
|
-
prevMonth() {
|
|
1428
|
-
const { current } = store2.get();
|
|
1429
|
-
store2.setKey("current", new Date(current.getFullYear(), current.getMonth() - 1, 1));
|
|
1430
|
-
this.updateCalendar();
|
|
1431
|
-
},
|
|
1432
|
-
nextMonth() {
|
|
1433
|
-
const { current } = store2.get();
|
|
1434
|
-
store2.setKey("current", new Date(current.getFullYear(), current.getMonth() + 1, 1));
|
|
1435
|
-
this.updateCalendar();
|
|
1436
|
-
},
|
|
1437
|
-
// Service initialization
|
|
1438
|
-
setService(service) {
|
|
1439
|
-
store2.setKey("service", service);
|
|
1440
|
-
store2.setKey("selectedMethod", null);
|
|
1441
|
-
store2.setKey("selectedProvider", null);
|
|
1442
|
-
store2.setKey("providers", []);
|
|
1443
|
-
store2.setKey("selectedDate", null);
|
|
1444
|
-
store2.setKey("startDate", null);
|
|
1445
|
-
store2.setKey("endDate", null);
|
|
1446
|
-
store2.setKey("slots", []);
|
|
1447
|
-
store2.setKey("selectedSlot", null);
|
|
1448
|
-
store2.setKey("currentStep", 1);
|
|
1449
|
-
store2.setKey("isMultiDay", !!service?.reservationConfigs?.isMultiDay);
|
|
1450
|
-
const now = /* @__PURE__ */ new Date();
|
|
1451
|
-
store2.setKey("current", new Date(now.getFullYear(), now.getMonth(), 1));
|
|
1452
|
-
this.updateCalendarGrid();
|
|
1453
|
-
if (service.reservationMethods?.length === 1) {
|
|
1454
|
-
const method = service.reservationMethods[0];
|
|
1455
|
-
store2.setKey("selectedMethod", method);
|
|
1456
|
-
this.determineTotalSteps();
|
|
1457
|
-
this.handleMethodSelection(method, false);
|
|
1458
|
-
} else {
|
|
1459
|
-
this.determineTotalSteps();
|
|
1460
|
-
}
|
|
1461
|
-
this.fetchAvailability("month");
|
|
1462
|
-
},
|
|
1463
|
-
// Step management
|
|
1464
|
-
determineTotalSteps() {
|
|
1465
|
-
const state = store2.get();
|
|
1466
|
-
if (!state.service) {
|
|
1467
|
-
store2.setKey("totalSteps", 1);
|
|
1468
|
-
return 1;
|
|
1469
|
-
}
|
|
1470
|
-
const active = [];
|
|
1471
|
-
if (state.service.reservationMethods?.length > 1) {
|
|
1472
|
-
active.push({ name: "method", label: "Choose Reservation Type" });
|
|
1473
|
-
}
|
|
1474
|
-
if (state.selectedMethod?.includes("SPECIFIC")) {
|
|
1475
|
-
active.push({ name: "provider", label: "Choose Provider" });
|
|
1476
|
-
}
|
|
1477
|
-
if (state.selectedMethod && state.selectedMethod !== "ORDER") {
|
|
1478
|
-
active.push({
|
|
1479
|
-
name: "datetime",
|
|
1480
|
-
label: state.isMultiDay ? "Choose Date Range" : "Choose Date & Time"
|
|
1481
|
-
});
|
|
1482
|
-
}
|
|
1483
|
-
active.push({ name: "review", label: "Review & Confirm" });
|
|
1484
|
-
const stepObj = {};
|
|
1485
|
-
active.forEach((st, idx) => {
|
|
1486
|
-
stepObj[idx + 1] = st;
|
|
1487
|
-
});
|
|
1488
|
-
store2.setKey("steps", stepObj);
|
|
1489
|
-
store2.setKey("totalSteps", active.length);
|
|
1490
|
-
if (state.currentStep > active.length) {
|
|
1491
|
-
store2.setKey("currentStep", active.length);
|
|
1492
|
-
}
|
|
1493
|
-
return active.length;
|
|
1494
|
-
},
|
|
1495
|
-
async getGuestToken() {
|
|
1496
|
-
const state = store2.get();
|
|
1497
|
-
const token = await getGuestToken(state.guestToken);
|
|
1498
|
-
if (token !== state.guestToken) {
|
|
1499
|
-
store2.setKey("guestToken", token);
|
|
1500
|
-
}
|
|
1501
|
-
return token;
|
|
1502
|
-
},
|
|
1503
|
-
getStepNumberByName(name) {
|
|
1504
|
-
const { steps } = store2.get();
|
|
1505
|
-
for (const [k, v] of Object.entries(steps)) {
|
|
1506
|
-
if (v.name === name) return Number(k);
|
|
1507
|
-
}
|
|
1508
|
-
return null;
|
|
1509
|
-
},
|
|
1510
|
-
nextStep() {
|
|
1511
|
-
const state = store2.get();
|
|
1512
|
-
if (state.currentStep >= state.totalSteps || !canProceed.get()) return;
|
|
1513
|
-
const next = state.currentStep + 1;
|
|
1514
|
-
const name = state.steps[next]?.name;
|
|
1515
|
-
store2.setKey("currentStep", next);
|
|
1516
|
-
if (name === "datetime") {
|
|
1517
|
-
this.fetchAvailability("month");
|
|
1518
|
-
if (!state.selectedDate && !state.startDate) {
|
|
1519
|
-
this.findFirstAvailable();
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
},
|
|
1523
|
-
prevStep() {
|
|
1524
|
-
const state = store2.get();
|
|
1525
|
-
if (state.currentStep <= 1) return;
|
|
1526
|
-
this.clearCurrentStepState();
|
|
1527
|
-
store2.setKey("currentStep", state.currentStep - 1);
|
|
1528
|
-
},
|
|
1529
|
-
clearCurrentStepState() {
|
|
1530
|
-
const name = currentStepName.get();
|
|
1531
|
-
if (name === "method") {
|
|
1532
|
-
store2.setKey("selectedMethod", null);
|
|
1533
|
-
} else if (name === "provider") {
|
|
1534
|
-
store2.setKey("selectedProvider", null);
|
|
1535
|
-
store2.setKey("providers", []);
|
|
1536
|
-
} else if (name === "datetime") {
|
|
1537
|
-
store2.setKey("selectedDate", null);
|
|
1538
|
-
store2.setKey("startDate", null);
|
|
1539
|
-
store2.setKey("endDate", null);
|
|
1540
|
-
store2.setKey("slots", []);
|
|
1541
|
-
store2.setKey("selectedSlot", null);
|
|
1542
|
-
}
|
|
1543
|
-
},
|
|
1544
|
-
goToStep(step) {
|
|
1545
|
-
const state = store2.get();
|
|
1546
|
-
if (step < 1 || step > state.totalSteps) return;
|
|
1547
|
-
if (step < state.currentStep) {
|
|
1548
|
-
for (let i = state.currentStep; i > step; i--) {
|
|
1549
|
-
const n = state.steps[i]?.name;
|
|
1550
|
-
if (n === "datetime") {
|
|
1551
|
-
store2.setKey("selectedDate", null);
|
|
1552
|
-
store2.setKey("startDate", null);
|
|
1553
|
-
store2.setKey("endDate", null);
|
|
1554
|
-
store2.setKey("slots", []);
|
|
1555
|
-
store2.setKey("selectedSlot", null);
|
|
1556
|
-
} else if (n === "provider") {
|
|
1557
|
-
store2.setKey("selectedProvider", null);
|
|
1558
|
-
store2.setKey("providers", []);
|
|
1559
|
-
} else if (n === "method") {
|
|
1560
|
-
store2.setKey("selectedMethod", null);
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
store2.setKey("currentStep", step);
|
|
1565
|
-
if (state.steps[step]?.name === "datetime") {
|
|
1566
|
-
this.fetchAvailability("month");
|
|
1567
|
-
if (!state.selectedDate && !state.startDate) {
|
|
1568
|
-
this.findFirstAvailable();
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
},
|
|
1572
|
-
// Method selection
|
|
1573
|
-
async handleMethodSelection(method, advance = true) {
|
|
1574
|
-
store2.setKey("selectedDate", null);
|
|
1575
|
-
store2.setKey("startDate", null);
|
|
1576
|
-
store2.setKey("endDate", null);
|
|
1577
|
-
store2.setKey("slots", []);
|
|
1578
|
-
store2.setKey("selectedSlot", null);
|
|
1579
|
-
store2.setKey("selectedMethod", method);
|
|
1580
|
-
this.determineTotalSteps();
|
|
1581
|
-
if (method === "ORDER") {
|
|
1582
|
-
this.handleOrderMethod();
|
|
1583
|
-
if (advance) {
|
|
1584
|
-
const reviewStep = this.getStepNumberByName("review");
|
|
1585
|
-
if (reviewStep) this.goToStep(reviewStep);
|
|
1586
|
-
return;
|
|
1587
|
-
}
|
|
1588
|
-
} else if (method.includes("SPECIFIC")) {
|
|
1589
|
-
await this.loadProviders();
|
|
1590
|
-
const state = store2.get();
|
|
1591
|
-
if (advance && state.providers.length === 1) {
|
|
1592
|
-
this.selectProvider(state.providers[0]);
|
|
1593
|
-
const datetimeStep = this.getStepNumberByName("datetime");
|
|
1594
|
-
if (datetimeStep) this.goToStep(datetimeStep);
|
|
1595
|
-
return;
|
|
1596
|
-
}
|
|
1597
|
-
} else if (method === "STANDARD" && advance) {
|
|
1598
|
-
const datetimeStep = this.getStepNumberByName("datetime");
|
|
1599
|
-
if (datetimeStep) this.goToStep(datetimeStep);
|
|
1600
|
-
return;
|
|
1601
|
-
}
|
|
1602
|
-
if (advance && store2.get().currentStep < store2.get().totalSteps) {
|
|
1603
|
-
this.nextStep();
|
|
1604
|
-
}
|
|
1605
|
-
},
|
|
1606
|
-
handleOrderMethod() {
|
|
1607
|
-
const state = store2.get();
|
|
1608
|
-
const now = /* @__PURE__ */ new Date();
|
|
1609
|
-
const dur = state.service.durations?.reduce((a, c) => a + c.duration, 0) || 3600;
|
|
1610
|
-
const from = Math.floor(now.getTime() / 1e3);
|
|
1611
|
-
const to = from + dur;
|
|
1612
|
-
store2.setKey("selectedSlot", {
|
|
1613
|
-
from,
|
|
1614
|
-
to,
|
|
1615
|
-
timeText: formatTimeSlot(from, to, state.timezone)
|
|
1616
|
-
});
|
|
1617
|
-
},
|
|
1618
|
-
// Provider management
|
|
1619
|
-
async loadProviders() {
|
|
1620
|
-
store2.setKey("loading", true);
|
|
1621
|
-
store2.setKey("providers", []);
|
|
1622
|
-
try {
|
|
1623
|
-
const config = getGlobalConfig();
|
|
1624
|
-
const { service } = store2.get();
|
|
1625
|
-
const res = await reservationApi.getProviders({ businessId: config.businessId, serviceId: service.id });
|
|
1626
|
-
store2.setKey("providers", res.success ? res.data : []);
|
|
1627
|
-
} catch (e) {
|
|
1628
|
-
console.error("Error loading providers:", e);
|
|
1629
|
-
} finally {
|
|
1630
|
-
store2.setKey("loading", false);
|
|
1631
|
-
}
|
|
1632
|
-
},
|
|
1633
|
-
selectProvider(provider) {
|
|
1634
|
-
store2.setKey("selectedProvider", provider);
|
|
1635
|
-
store2.setKey("selectedDate", null);
|
|
1636
|
-
store2.setKey("startDate", null);
|
|
1637
|
-
store2.setKey("endDate", null);
|
|
1638
|
-
store2.setKey("slots", []);
|
|
1639
|
-
store2.setKey("selectedSlot", null);
|
|
1640
|
-
if (currentStepName.get() === "datetime") {
|
|
1641
|
-
this.fetchAvailability("month");
|
|
1642
|
-
this.findFirstAvailable();
|
|
1643
|
-
}
|
|
1644
|
-
},
|
|
1645
|
-
// Availability and date management
|
|
1646
|
-
async fetchAvailability(type, date = null) {
|
|
1647
|
-
const state = store2.get();
|
|
1648
|
-
if (!state.service || currentStepName.get() !== "datetime") return;
|
|
1649
|
-
store2.setKey("loading", true);
|
|
1650
|
-
try {
|
|
1651
|
-
let from, to, limit;
|
|
1652
|
-
if (type === "month") {
|
|
1653
|
-
from = Math.floor(
|
|
1654
|
-
new Date(state.current.getFullYear(), state.current.getMonth(), 1).getTime() / 1e3
|
|
1655
|
-
);
|
|
1656
|
-
to = Math.floor(
|
|
1657
|
-
new Date(state.current.getFullYear(), state.current.getMonth() + 1, 0).getTime() / 1e3
|
|
1658
|
-
);
|
|
1659
|
-
limit = 100;
|
|
1660
|
-
} else if (type === "day" && date) {
|
|
1661
|
-
const dObj = typeof date === "string" ? new Date(date) : date;
|
|
1662
|
-
from = Math.floor(dObj.getTime() / 1e3);
|
|
1663
|
-
to = from + 24 * 3600;
|
|
1664
|
-
limit = 100;
|
|
1665
|
-
} else if (type === "first") {
|
|
1666
|
-
const now = /* @__PURE__ */ new Date();
|
|
1667
|
-
from = Math.floor(now.setHours(0, 0, 0, 0) / 1e3);
|
|
1668
|
-
to = Math.floor(new Date(now.getFullYear(), now.getMonth() + 3, 0).getTime() / 1e3);
|
|
1669
|
-
limit = 1;
|
|
1670
|
-
} else {
|
|
1671
|
-
store2.setKey("loading", false);
|
|
1672
|
-
return;
|
|
1673
|
-
}
|
|
1674
|
-
const config = getGlobalConfig();
|
|
1675
|
-
const params = { businessId: config.businessId, serviceId: state.service.id, from, to, limit };
|
|
1676
|
-
if (state.selectedProvider) params.providerId = state.selectedProvider.id;
|
|
1677
|
-
const result = await reservationApi.getAvailableSlots(params);
|
|
1678
|
-
if (!result.success) {
|
|
1679
|
-
console.error(`Error fetching availability (${type}):`, result.error);
|
|
1680
|
-
return;
|
|
1681
|
-
}
|
|
1682
|
-
if (type === "month") {
|
|
1683
|
-
const avail = new Set(
|
|
1684
|
-
result.data.map((i) => {
|
|
1685
|
-
const date2 = new Date(i.from * 1e3);
|
|
1686
|
-
return date2.toISOString().slice(0, 10);
|
|
1687
|
-
})
|
|
1688
|
-
);
|
|
1689
|
-
store2.setKey(
|
|
1690
|
-
"days",
|
|
1691
|
-
state.days.map((c) => {
|
|
1692
|
-
if (!c.blank && c.date) {
|
|
1693
|
-
const iso = c.date.toISOString().slice(0, 10);
|
|
1694
|
-
return { ...c, available: avail.has(iso) };
|
|
1695
|
-
}
|
|
1696
|
-
return c;
|
|
1697
|
-
})
|
|
1698
|
-
);
|
|
1699
|
-
} else if (type === "day") {
|
|
1700
|
-
const slots = result.data.map((i, idx) => ({
|
|
1701
|
-
...i,
|
|
1702
|
-
id: `slot-${i.from}-${idx}`,
|
|
1703
|
-
day: new Date(i.from * 1e3).toISOString().slice(0, 10),
|
|
1704
|
-
timeText: formatTimeSlot(i.from, i.to, state.timezone)
|
|
1705
|
-
}));
|
|
1706
|
-
store2.setKey("slots", slots);
|
|
1707
|
-
if (slots.length && !state.selectedSlot) {
|
|
1708
|
-
store2.setKey("selectedSlot", slots[0]);
|
|
1709
|
-
}
|
|
1710
|
-
} else if (type === "first" && result.data.length) {
|
|
1711
|
-
const first = new Date(result.data[0].from * 1e3);
|
|
1712
|
-
const iso = first.toISOString().slice(0, 10);
|
|
1713
|
-
store2.setKey("current", new Date(first.getFullYear(), first.getMonth(), 1));
|
|
1714
|
-
this.updateCalendarGrid();
|
|
1715
|
-
await this.fetchAvailability("month");
|
|
1716
|
-
if (state.isMultiDay) {
|
|
1717
|
-
store2.setKey("startDate", iso);
|
|
1718
|
-
store2.setKey("selectedDate", iso);
|
|
1719
|
-
} else {
|
|
1720
|
-
store2.setKey("selectedDate", iso);
|
|
1721
|
-
await this.fetchAvailability("day", iso);
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
} catch (err) {
|
|
1725
|
-
console.error(`Error in fetchAvailability (${type}):`, err);
|
|
1726
|
-
} finally {
|
|
1727
|
-
store2.setKey("loading", false);
|
|
1728
|
-
}
|
|
1729
|
-
},
|
|
1730
|
-
findFirstAvailable() {
|
|
1731
|
-
if (currentStepName.get() === "datetime") this.fetchAvailability("first");
|
|
1732
|
-
},
|
|
1733
|
-
// Date selection
|
|
1734
|
-
selectDate(cell) {
|
|
1735
|
-
if (!cell.date || !cell.available) return;
|
|
1736
|
-
const dateInfo = {
|
|
1737
|
-
year: cell.date.getFullYear(),
|
|
1738
|
-
month: cell.date.getMonth() + 1,
|
|
1739
|
-
day: cell.date.getDate(),
|
|
1740
|
-
iso: `${cell.date.getFullYear()}-${String(cell.date.getMonth() + 1).padStart(2, "0")}-${String(cell.date.getDate()).padStart(2, "0")}`
|
|
1741
|
-
};
|
|
1742
|
-
const state = store2.get();
|
|
1743
|
-
if (state.isMultiDay) {
|
|
1744
|
-
if (!state.startDate) {
|
|
1745
|
-
store2.setKey("startDate", dateInfo.iso);
|
|
1746
|
-
store2.setKey("selectedSlot", null);
|
|
1747
|
-
store2.setKey("selectedDate", dateInfo.iso);
|
|
1748
|
-
store2.setKey("endDate", null);
|
|
1749
|
-
} else if (!state.endDate) {
|
|
1750
|
-
const start = new Date(state.startDate).getTime();
|
|
1751
|
-
const cellT = cell.date.getTime();
|
|
1752
|
-
if (cellT < start) {
|
|
1753
|
-
store2.setKey("endDate", state.startDate);
|
|
1754
|
-
store2.setKey("startDate", dateInfo.iso);
|
|
1755
|
-
} else {
|
|
1756
|
-
store2.setKey("endDate", dateInfo.iso);
|
|
1757
|
-
}
|
|
1758
|
-
} else {
|
|
1759
|
-
store2.setKey("startDate", dateInfo.iso);
|
|
1760
|
-
store2.setKey("selectedDate", dateInfo.iso);
|
|
1761
|
-
store2.setKey("endDate", null);
|
|
1762
|
-
store2.setKey("selectedSlot", null);
|
|
1763
|
-
}
|
|
1764
|
-
} else {
|
|
1765
|
-
store2.setKey("selectedSlot", null);
|
|
1766
|
-
store2.setKey("selectedDate", dateInfo.iso);
|
|
1767
|
-
this.fetchAvailability("day", dateInfo.iso);
|
|
1768
|
-
}
|
|
1769
|
-
},
|
|
1770
|
-
createMultiDaySlot() {
|
|
1771
|
-
const state = store2.get();
|
|
1772
|
-
if (!state.startDate || !state.endDate) return;
|
|
1773
|
-
const startDT = new Date(state.startDate);
|
|
1774
|
-
startDT.setHours(9, 0, 0, 0);
|
|
1775
|
-
const endDT = new Date(state.endDate);
|
|
1776
|
-
endDT.setHours(17, 0, 0, 0);
|
|
1777
|
-
const from = Math.floor(startDT.getTime() / 1e3);
|
|
1778
|
-
const to = Math.floor(endDT.getTime() / 1e3);
|
|
1779
|
-
const rangeSlot = {
|
|
1780
|
-
id: `multi-day-slot-${from}-${to}`,
|
|
1781
|
-
from,
|
|
1782
|
-
to,
|
|
1783
|
-
isMultiDay: true,
|
|
1784
|
-
timeText: `9:00 AM - 5:00 PM daily`,
|
|
1785
|
-
dateRange: `${this.formatDateDisplay(state.startDate)} to ${this.formatDateDisplay(state.endDate)}`,
|
|
1786
|
-
day: state.startDate
|
|
1787
|
-
};
|
|
1788
|
-
store2.setKey("slots", [rangeSlot]);
|
|
1789
|
-
store2.setKey("selectedSlot", rangeSlot);
|
|
1790
|
-
},
|
|
1791
|
-
resetDateSelection() {
|
|
1792
|
-
store2.setKey("startDate", null);
|
|
1793
|
-
store2.setKey("endDate", null);
|
|
1794
|
-
store2.setKey("selectedDate", null);
|
|
1795
|
-
store2.setKey("slots", []);
|
|
1796
|
-
store2.setKey("selectedSlot", null);
|
|
1797
|
-
},
|
|
1798
|
-
selectTimeSlot(slot) {
|
|
1799
|
-
store2.setKey("selectedSlot", slot);
|
|
1800
|
-
},
|
|
1801
|
-
setSelectedTimeZone(zone) {
|
|
1802
|
-
const state = store2.get();
|
|
1803
|
-
if (zone === state.timezone) return;
|
|
1804
|
-
store2.setKey("timezone", zone);
|
|
1805
|
-
if (currentStepName.get() === "datetime") {
|
|
1806
|
-
if (state.selectedDate) {
|
|
1807
|
-
this.fetchAvailability("day", state.selectedDate);
|
|
1808
|
-
} else if (!state.selectedDate && !state.startDate) {
|
|
1809
|
-
this.findFirstAvailable();
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
},
|
|
1813
|
-
// Calendar helpers
|
|
1814
|
-
isAvailable(cell) {
|
|
1815
|
-
return cell.date && cell.available;
|
|
1816
|
-
},
|
|
1817
|
-
isSelectedDay(cell) {
|
|
1818
|
-
if (cell.blank || !cell.date) return false;
|
|
1819
|
-
const iso = `${cell.date.getFullYear()}-${String(cell.date.getMonth() + 1).padStart(2, "0")}-${String(cell.date.getDate()).padStart(2, "0")}`;
|
|
1820
|
-
const state = store2.get();
|
|
1821
|
-
return iso === state.startDate || iso === state.endDate || iso === state.selectedDate;
|
|
1822
|
-
},
|
|
1823
|
-
isInSelectedRange(cell) {
|
|
1824
|
-
const state = store2.get();
|
|
1825
|
-
if (cell.blank || !cell.date || !state.startDate || !state.endDate) return false;
|
|
1826
|
-
const t = cell.date.getTime();
|
|
1827
|
-
const a = new Date(state.startDate).getTime();
|
|
1828
|
-
const b = new Date(state.endDate).getTime();
|
|
1829
|
-
return t >= a && t <= b;
|
|
1830
|
-
},
|
|
1831
|
-
formatDateDisplay(ds) {
|
|
1832
|
-
if (!ds) return "";
|
|
1833
|
-
const d = new Date(ds);
|
|
1834
|
-
return d.toLocaleDateString(getLocale(), { month: "short", day: "numeric" });
|
|
1835
|
-
},
|
|
1836
|
-
// Cart operations
|
|
1837
|
-
addToCart(slot) {
|
|
1838
|
-
const state = store2.get();
|
|
1839
|
-
const id = crypto.randomUUID();
|
|
1840
|
-
let dateDisplay, timeText;
|
|
1841
|
-
if (state.isMultiDay && slot.isMultiDay) {
|
|
1842
|
-
const a = new Date(slot.from * 1e3), b = new Date(slot.to * 1e3);
|
|
1843
|
-
dateDisplay = `${a.toLocaleDateString(getLocale(), { month: "short", day: "numeric" })} - ${b.toLocaleDateString(getLocale(), { month: "short", day: "numeric", year: "numeric" })}`;
|
|
1844
|
-
timeText = slot.timeText;
|
|
1845
|
-
} else {
|
|
1846
|
-
const date = state.selectedDate ? new Date(state.selectedDate) : new Date(slot.from * 1e3);
|
|
1847
|
-
dateDisplay = date.toLocaleDateString(getLocale(), {
|
|
1848
|
-
weekday: "short",
|
|
1849
|
-
year: "numeric",
|
|
1850
|
-
month: "short",
|
|
1851
|
-
day: "numeric"
|
|
1852
|
-
});
|
|
1853
|
-
timeText = slot.timeText;
|
|
1854
|
-
}
|
|
1855
|
-
const blocks = (state.service?.reservationBlocks || []).map((f) => ({
|
|
1856
|
-
...f,
|
|
1857
|
-
value: Array.isArray(f.value) ? f.value : [f.value]
|
|
1858
|
-
}));
|
|
1859
|
-
const newPart = {
|
|
1860
|
-
id,
|
|
1861
|
-
serviceId: state.service.id,
|
|
1862
|
-
serviceName: getLocalizedString(state.service.name, getLocale()),
|
|
1863
|
-
date: dateDisplay,
|
|
1864
|
-
from: slot.from,
|
|
1865
|
-
to: slot.to,
|
|
1866
|
-
timeText,
|
|
1867
|
-
isMultiDay: state.isMultiDay && (!!state.endDate || slot.isMultiDay),
|
|
1868
|
-
reservationMethod: state.selectedMethod || "",
|
|
1869
|
-
providerId: state.selectedProvider?.id,
|
|
1870
|
-
blocks
|
|
1871
|
-
};
|
|
1872
|
-
const newParts = [...state.parts, newPart];
|
|
1873
|
-
store2.setKey("parts", newParts);
|
|
1874
|
-
cartParts.set(newParts);
|
|
1875
|
-
this.resetDateSelection();
|
|
1876
|
-
store2.setKey("currentStep", 1);
|
|
1877
|
-
if (state.service.reservationMethods?.length > 1) {
|
|
1878
|
-
store2.setKey("selectedMethod", null);
|
|
1879
|
-
}
|
|
1880
|
-
},
|
|
1881
|
-
removePart(id) {
|
|
1882
|
-
const filteredParts = store2.get().parts.filter((p) => p.id !== id);
|
|
1883
|
-
store2.setKey("parts", filteredParts);
|
|
1884
|
-
cartParts.set(filteredParts);
|
|
1885
|
-
},
|
|
1886
|
-
// Phone validation helper (using shared utility)
|
|
1887
|
-
validatePhoneNumber(phone) {
|
|
1888
|
-
const result = validatePhoneNumber(phone);
|
|
1889
|
-
return result.isValid;
|
|
1890
|
-
},
|
|
1891
|
-
// Phone verification
|
|
1892
|
-
async updateProfilePhone() {
|
|
1893
|
-
store2.setKey("phoneError", null);
|
|
1894
|
-
store2.setKey("phoneSuccess", null);
|
|
1895
|
-
store2.setKey("isSendingCode", true);
|
|
1896
|
-
try {
|
|
1897
|
-
const phoneNumber = store2.get().phoneNumber;
|
|
1898
|
-
if (!this.validatePhoneNumber(phoneNumber)) {
|
|
1899
|
-
store2.setKey("phoneError", "Please enter a valid phone number");
|
|
1900
|
-
return false;
|
|
1901
|
-
}
|
|
1902
|
-
const token = await this.getGuestToken();
|
|
1903
|
-
await updateProfilePhone(token, phoneNumber);
|
|
1904
|
-
store2.setKey("phoneSuccess", "Verification code sent successfully!");
|
|
1905
|
-
store2.setKey("codeSentAt", Date.now());
|
|
1906
|
-
return true;
|
|
1907
|
-
} catch (e) {
|
|
1908
|
-
store2.setKey("phoneError", e.message);
|
|
1909
|
-
return false;
|
|
1910
|
-
} finally {
|
|
1911
|
-
store2.setKey("isSendingCode", false);
|
|
1912
|
-
}
|
|
1913
|
-
},
|
|
1914
|
-
async verifyPhoneCode() {
|
|
1915
|
-
store2.setKey("verifyError", null);
|
|
1916
|
-
store2.setKey("isVerifying", true);
|
|
1917
|
-
try {
|
|
1918
|
-
const { phoneNumber, verificationCode } = store2.get();
|
|
1919
|
-
if (!verificationCode || verificationCode.length !== 4) {
|
|
1920
|
-
store2.setKey("verifyError", "Please enter a 4-digit verification code");
|
|
1921
|
-
return false;
|
|
1922
|
-
}
|
|
1923
|
-
const token = await this.getGuestToken();
|
|
1924
|
-
await verifyPhoneCode(token, phoneNumber, verificationCode);
|
|
1925
|
-
store2.setKey("isPhoneVerified", true);
|
|
1926
|
-
store2.setKey("phoneSuccess", null);
|
|
1927
|
-
store2.setKey("verificationCode", "");
|
|
1928
|
-
return true;
|
|
1929
|
-
} catch (e) {
|
|
1930
|
-
let errorMessage = "Invalid verification code";
|
|
1931
|
-
if (e.message?.includes("expired")) {
|
|
1932
|
-
errorMessage = "Verification code has expired. Please request a new one.";
|
|
1933
|
-
} else if (e.message?.includes("incorrect") || e.message?.includes("invalid")) {
|
|
1934
|
-
errorMessage = "Incorrect verification code. Please try again.";
|
|
1935
|
-
}
|
|
1936
|
-
store2.setKey("verifyError", errorMessage);
|
|
1937
|
-
return false;
|
|
1938
|
-
} finally {
|
|
1939
|
-
store2.setKey("isVerifying", false);
|
|
1940
|
-
}
|
|
1941
|
-
},
|
|
1942
|
-
async checkout(paymentMethod = "CASH" /* Cash */, reservationBlocks3, promoCode) {
|
|
1943
|
-
const state = store2.get();
|
|
1944
|
-
if (state.loading || !state.parts.length) return { success: false, error: "No parts in cart" };
|
|
1945
|
-
store2.setKey("loading", true);
|
|
1946
|
-
try {
|
|
1947
|
-
const token = await this.getGuestToken();
|
|
1948
|
-
const config = getGlobalConfig();
|
|
1949
|
-
const result = await reservationApi.checkout({
|
|
1950
|
-
token,
|
|
1951
|
-
businessId: config.businessId,
|
|
1952
|
-
blocks: reservationBlocks3 || [],
|
|
1953
|
-
parts: state.parts,
|
|
1954
|
-
paymentMethod,
|
|
1955
|
-
market: "us",
|
|
1956
|
-
promoCode
|
|
1957
|
-
});
|
|
1958
|
-
if (result.success) {
|
|
1959
|
-
return {
|
|
1960
|
-
success: true,
|
|
1961
|
-
data: {
|
|
1962
|
-
reservationId: result.data?.reservationId,
|
|
1963
|
-
clientSecret: result.data?.clientSecret
|
|
1964
|
-
}
|
|
1965
|
-
};
|
|
1966
|
-
} else {
|
|
1967
|
-
throw new Error(result.error);
|
|
1968
|
-
}
|
|
1969
|
-
} catch (e) {
|
|
1970
|
-
console.error("Reservation checkout error:", e);
|
|
1971
|
-
return { success: false, error: e.message };
|
|
1972
|
-
} finally {
|
|
1973
|
-
store2.setKey("loading", false);
|
|
1974
|
-
}
|
|
1975
|
-
},
|
|
1976
|
-
async fetchQuote(paymentMethod = "CASH" /* Cash */, promoCode) {
|
|
1977
|
-
const state = store2.get();
|
|
1978
|
-
console.log("fetchQuote called with promoCode:", promoCode);
|
|
1979
|
-
if (!state.parts.length) {
|
|
1980
|
-
store2.setKey("quote", null);
|
|
1981
|
-
store2.setKey("quoteError", null);
|
|
1982
|
-
return;
|
|
1983
|
-
}
|
|
1984
|
-
store2.setKey("fetchingQuote", true);
|
|
1985
|
-
store2.setKey("quoteError", null);
|
|
1986
|
-
try {
|
|
1987
|
-
const token = await this.getGuestToken();
|
|
1988
|
-
const marketObj = selectedMarket.get();
|
|
1989
|
-
const market = marketObj?.id || "us";
|
|
1990
|
-
const curr = currency.get() || "USD";
|
|
1991
|
-
console.log("Calling reservationApi.getQuote with:", { market, currency: curr, promoCode });
|
|
1992
|
-
const config = getGlobalConfig();
|
|
1993
|
-
const result = await reservationApi.getQuote({
|
|
1994
|
-
token,
|
|
1995
|
-
businessId: config.businessId,
|
|
1996
|
-
market,
|
|
1997
|
-
currency: curr,
|
|
1998
|
-
userId: token,
|
|
1999
|
-
// Use token as userId for guests
|
|
2000
|
-
parts: state.parts,
|
|
2001
|
-
paymentMethod,
|
|
2002
|
-
promoCode
|
|
2003
|
-
});
|
|
2004
|
-
if (result.success && result.data) {
|
|
2005
|
-
console.log("Quote received:", result.data);
|
|
2006
|
-
store2.setKey("quote", result.data);
|
|
2007
|
-
store2.setKey("quoteError", null);
|
|
2008
|
-
} else {
|
|
2009
|
-
console.error("Quote error:", result.error);
|
|
2010
|
-
store2.setKey("quote", null);
|
|
2011
|
-
store2.setKey("quoteError", mapQuoteError2(result.code, result.error));
|
|
2012
|
-
}
|
|
2013
|
-
} catch (e) {
|
|
2014
|
-
console.error("Fetch quote error:", e);
|
|
2015
|
-
store2.setKey("quote", null);
|
|
2016
|
-
store2.setKey("quoteError", e.message || "Failed to get quote");
|
|
2017
|
-
} finally {
|
|
2018
|
-
store2.setKey("fetchingQuote", false);
|
|
2019
|
-
}
|
|
2020
|
-
},
|
|
2021
|
-
// Helpers
|
|
2022
|
-
getLabel(block, locale = getLocale()) {
|
|
2023
|
-
if (!block) return "";
|
|
2024
|
-
if (block.properties?.label) {
|
|
2025
|
-
if (typeof block.properties.label === "object") {
|
|
2026
|
-
return block.properties.label[locale] || block.properties.label.en || Object.values(block.properties.label)[0] || "";
|
|
2027
|
-
}
|
|
2028
|
-
if (typeof block.properties.label === "string") {
|
|
2029
|
-
return block.properties.label;
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
return block.key || "";
|
|
2033
|
-
},
|
|
2034
|
-
getServicePrice() {
|
|
2035
|
-
const state = store2.get();
|
|
2036
|
-
if (state.service?.prices && Array.isArray(state.service.prices)) {
|
|
2037
|
-
return getMarketPrice(state.service.prices, "us");
|
|
2038
|
-
}
|
|
2039
|
-
return "";
|
|
2040
|
-
},
|
|
2041
|
-
// NEW: Get reservation total as Payment structure
|
|
2042
|
-
getReservationPayment() {
|
|
2043
|
-
const state = store2.get();
|
|
2044
|
-
const subtotalMinor = state.parts.reduce((sum, part) => {
|
|
2045
|
-
const servicePrices = state.service?.prices || [];
|
|
2046
|
-
const amountMinor = servicePrices.length > 0 ? getPriceAmount(servicePrices, "US") : 0;
|
|
2047
|
-
return sum + amountMinor;
|
|
2048
|
-
}, 0);
|
|
2049
|
-
const currencyCode = currency.get();
|
|
2050
|
-
return createPaymentForCheckout(
|
|
2051
|
-
subtotalMinor,
|
|
2052
|
-
"US",
|
|
2053
|
-
currencyCode,
|
|
2054
|
-
"CASH" /* Cash */
|
|
2055
|
-
);
|
|
2056
|
-
}
|
|
2057
|
-
};
|
|
2058
|
-
function initReservationStore() {
|
|
2059
|
-
actions2.updateCalendarGrid();
|
|
2060
|
-
businessActions.init();
|
|
2061
|
-
const savedParts = cartParts.get();
|
|
2062
|
-
if (savedParts && savedParts.length > 0) {
|
|
2063
|
-
store2.setKey("parts", savedParts);
|
|
2064
|
-
}
|
|
2065
|
-
store2.listen((state) => {
|
|
2066
|
-
if (state.isMultiDay && state.startDate && state.endDate && currentStepName.get() === "datetime" && (!state.slots.length || !state.slots[0].isMultiDay)) {
|
|
2067
|
-
actions2.createMultiDaySlot();
|
|
2068
|
-
}
|
|
2069
|
-
if (JSON.stringify(state.parts) !== JSON.stringify(cartParts.get())) {
|
|
2070
|
-
cartParts.set(state.parts);
|
|
2071
|
-
}
|
|
2072
|
-
});
|
|
2073
|
-
cartParts.listen((parts) => {
|
|
2074
|
-
const currentParts = store2.get().parts;
|
|
2075
|
-
if (JSON.stringify(parts) !== JSON.stringify(currentParts)) {
|
|
2076
|
-
store2.setKey("parts", [...parts]);
|
|
2077
|
-
}
|
|
2078
|
-
});
|
|
2079
|
-
}
|
|
2080
|
-
function mapQuoteError2(code, fallback) {
|
|
2081
|
-
switch (code) {
|
|
2082
|
-
case "PROMO.MIN_ORDER":
|
|
2083
|
-
return fallback || "Promo requires a higher minimum order.";
|
|
2084
|
-
case "PROMO.NOT_ACTIVE":
|
|
2085
|
-
return "Promo code is not active.";
|
|
2086
|
-
case "PROMO.NOT_YET_VALID":
|
|
2087
|
-
return "Promo code is not yet valid.";
|
|
2088
|
-
case "PROMO.EXPIRED":
|
|
2089
|
-
return "Promo code has expired.";
|
|
2090
|
-
case "PROMO.MAX_USES":
|
|
2091
|
-
return "Promo code usage limit exceeded.";
|
|
2092
|
-
case "PROMO.MAX_USES_PER_USER":
|
|
2093
|
-
return "You have already used this promo code.";
|
|
2094
|
-
case "PROMO.NOT_FOUND":
|
|
2095
|
-
return "Promo code not found.";
|
|
2096
|
-
default:
|
|
2097
|
-
return fallback || "Failed to get quote.";
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
var totalCartItems = computed([cartItems, cartParts], (eshop, reservation) => {
|
|
2101
|
-
const eshopCount = eshop?.reduce((sum, item) => sum + item.quantity, 0) || 0;
|
|
2102
|
-
const reservationCount = reservation?.length || 0;
|
|
2103
|
-
return eshopCount + reservationCount;
|
|
2104
|
-
});
|
|
2105
|
-
var hasEshopItems = computed(cartItems, (items) => items?.length > 0);
|
|
2106
|
-
var hasReservationItems = computed(cartParts, (items) => items?.length > 0);
|
|
2107
|
-
var isCartEmpty = computed([cartItems, cartParts], (eshop, reservation) => {
|
|
2108
|
-
return (!eshop || eshop.length === 0) && (!reservation || reservation.length === 0);
|
|
2109
|
-
});
|
|
2110
|
-
var showEshopSection = computed([hasEshopItems, isCartEmpty], (hasEshop, isEmpty) => hasEshop || isEmpty);
|
|
2111
|
-
var showReservationSection = computed([hasReservationItems, isCartEmpty], (hasReservation, isEmpty) => hasReservation || isEmpty);
|
|
2112
|
-
var showBothSections = computed([hasEshopItems, hasReservationItems], (hasEshop, hasReservation) => hasEshop && hasReservation);
|
|
2113
|
-
|
|
2114
|
-
// src/stores/index.ts
|
|
2115
|
-
function initArky(config) {
|
|
2116
|
-
if (!config.apiUrl) {
|
|
2117
|
-
throw new Error("apiUrl is required");
|
|
2118
|
-
}
|
|
2119
|
-
if (!config.businessId) {
|
|
2120
|
-
throw new Error("businessId is required");
|
|
2121
|
-
}
|
|
2122
|
-
setGlobalConfig(config);
|
|
2123
|
-
return config;
|
|
2124
|
-
}
|
|
2125
|
-
|
|
2126
|
-
export { allowedPaymentMethods, businessActions, businessStore, canProceed, cartItemCount, cartItems, cartParts, cartTotal, currency, currencySymbol, currentStepName, actions as eshopActions, store as eshopStore, getShippingMethodsForCountry, getZoneByCountry, hasEshopItems, hasReservationItems, initArky, initEshopStore, initReservationStore, isCartEmpty, markets, orderBlocks, paymentConfig, paymentMethods, promoCodeAtom, quoteAtom, actions2 as reservationActions, reservationBlocks, store2 as reservationStore, selectedMarket, showBothSections, showEshopSection, showReservationSection, totalCartItems, zones };
|
|
2127
|
-
//# sourceMappingURL=stores.js.map
|
|
2128
|
-
//# sourceMappingURL=stores.js.map
|