arky-sdk 0.1.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/api/cms.d.ts +19 -0
- package/dist/api/cms.d.ts.map +1 -0
- package/dist/api/cms.js +41 -0
- package/dist/api/eshop.d.ts +89 -0
- package/dist/api/eshop.d.ts.map +1 -0
- package/dist/api/eshop.js +183 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +5 -0
- package/dist/api/newsletter.d.ts +32 -0
- package/dist/api/newsletter.d.ts.map +1 -0
- package/dist/api/newsletter.js +70 -0
- package/dist/api/reservation.d.ts +84 -0
- package/dist/api/reservation.d.ts.map +1 -0
- package/dist/api/reservation.js +239 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +20 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/services/auth.d.ts +17 -0
- package/dist/services/auth.d.ts.map +1 -0
- package/dist/services/auth.js +62 -0
- package/dist/services/http.d.ts +20 -0
- package/dist/services/http.d.ts.map +1 -0
- package/dist/services/http.js +73 -0
- package/dist/stores/business.d.ts +28 -0
- package/dist/stores/business.d.ts.map +1 -0
- package/dist/stores/business.js +122 -0
- package/dist/stores/cart.d.ts +8 -0
- package/dist/stores/cart.d.ts.map +1 -0
- package/dist/stores/cart.js +20 -0
- package/dist/stores/eshop.d.ts +121 -0
- package/dist/stores/eshop.d.ts.map +1 -0
- package/dist/stores/eshop.js +377 -0
- package/dist/stores/index.d.ts +7 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +19 -0
- package/dist/stores/reservation.d.ts +237 -0
- package/dist/stores/reservation.d.ts.map +1 -0
- package/dist/stores/reservation.js +853 -0
- package/dist/types/index.d.ts +244 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/utils/blocks.d.ts +30 -0
- package/dist/utils/blocks.d.ts.map +1 -0
- package/dist/utils/blocks.js +237 -0
- package/dist/utils/currency.d.ts +9 -0
- package/dist/utils/currency.d.ts.map +1 -0
- package/dist/utils/currency.js +99 -0
- package/dist/utils/errors.d.ts +121 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +114 -0
- package/dist/utils/i18n.d.ts +5 -0
- package/dist/utils/i18n.d.ts.map +1 -0
- package/dist/utils/i18n.js +37 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/price.d.ts +33 -0
- package/dist/utils/price.d.ts.map +1 -0
- package/dist/utils/price.js +141 -0
- package/dist/utils/queryParams.d.ts +21 -0
- package/dist/utils/queryParams.d.ts.map +1 -0
- package/dist/utils/queryParams.js +47 -0
- package/dist/utils/svg.d.ts +17 -0
- package/dist/utils/svg.d.ts.map +1 -0
- package/dist/utils/svg.js +62 -0
- package/dist/utils/text.d.ts +26 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +64 -0
- package/dist/utils/timezone.d.ts +9 -0
- package/dist/utils/timezone.d.ts.map +1 -0
- package/dist/utils/timezone.js +49 -0
- package/dist/utils/validation.d.ts +9 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +44 -0
- package/package.json +58 -0
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
// Reservation store with TypeScript - Simplified with Business Store
|
|
2
|
+
import { computed, deepMap } from "nanostores";
|
|
3
|
+
import { persistentAtom } from "@nanostores/persistent";
|
|
4
|
+
import { getLocalizedString, getLocale } from "../utils/i18n";
|
|
5
|
+
import { API_URL, BUSINESS_ID, STORAGE_URL } from "../config";
|
|
6
|
+
import { reservationApi } from "../api/reservation";
|
|
7
|
+
import { getMarketPrice, getPriceAmount, createPaymentForCheckout } from "../utils/price";
|
|
8
|
+
import * as authService from "../services/auth";
|
|
9
|
+
import { validatePhoneNumber } from "../utils/validation";
|
|
10
|
+
import { tzGroups, findTimeZone } from "../utils/timezone";
|
|
11
|
+
import { selectedMarket, currency, businessActions } from "./business";
|
|
12
|
+
import { PaymentMethod } from "../types";
|
|
13
|
+
export const cartParts = persistentAtom("reservationCart", [], {
|
|
14
|
+
encode: JSON.stringify,
|
|
15
|
+
decode: JSON.parse,
|
|
16
|
+
});
|
|
17
|
+
export const store = deepMap({
|
|
18
|
+
currentStep: 1,
|
|
19
|
+
totalSteps: 4,
|
|
20
|
+
steps: {
|
|
21
|
+
1: { name: "method", labelKey: "method" },
|
|
22
|
+
2: { name: "provider", labelKey: "provider" },
|
|
23
|
+
3: { name: "datetime", labelKey: "datetime" },
|
|
24
|
+
4: { name: "review", labelKey: "review" },
|
|
25
|
+
},
|
|
26
|
+
// Calendar data
|
|
27
|
+
weekdays: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
|
28
|
+
monthYear: "",
|
|
29
|
+
days: [],
|
|
30
|
+
current: new Date(),
|
|
31
|
+
// Selection state
|
|
32
|
+
selectedDate: null,
|
|
33
|
+
slots: [],
|
|
34
|
+
selectedSlot: null,
|
|
35
|
+
selectedMethod: null,
|
|
36
|
+
selectedProvider: null,
|
|
37
|
+
providers: [],
|
|
38
|
+
// Status flags
|
|
39
|
+
loading: false,
|
|
40
|
+
startDate: null,
|
|
41
|
+
endDate: null,
|
|
42
|
+
isMultiDay: false,
|
|
43
|
+
// Phone verification
|
|
44
|
+
phoneNumber: "",
|
|
45
|
+
phoneError: null,
|
|
46
|
+
phoneSuccess: null,
|
|
47
|
+
verificationCode: "",
|
|
48
|
+
verifyError: null,
|
|
49
|
+
isPhoneVerified: false,
|
|
50
|
+
isSendingCode: false,
|
|
51
|
+
isVerifying: false,
|
|
52
|
+
codeSentAt: null,
|
|
53
|
+
canResendAt: null,
|
|
54
|
+
// Quote state
|
|
55
|
+
fetchingQuote: false,
|
|
56
|
+
quote: null,
|
|
57
|
+
quoteError: null,
|
|
58
|
+
// Service & config
|
|
59
|
+
guestToken: null,
|
|
60
|
+
service: null,
|
|
61
|
+
apiUrl: API_URL,
|
|
62
|
+
businessId: BUSINESS_ID,
|
|
63
|
+
storageUrl: STORAGE_URL,
|
|
64
|
+
timezone: findTimeZone(tzGroups),
|
|
65
|
+
tzGroups,
|
|
66
|
+
parts: [],
|
|
67
|
+
});
|
|
68
|
+
export const currentStepName = computed(store, (state) => {
|
|
69
|
+
return state?.steps?.[state?.currentStep]?.name || "";
|
|
70
|
+
});
|
|
71
|
+
export const canProceed = computed(store, (state) => {
|
|
72
|
+
const stepName = state?.steps?.[state?.currentStep]?.name;
|
|
73
|
+
switch (stepName) {
|
|
74
|
+
case "method":
|
|
75
|
+
return !!state.selectedMethod;
|
|
76
|
+
case "provider":
|
|
77
|
+
return !!state.selectedProvider;
|
|
78
|
+
case "datetime":
|
|
79
|
+
return state.isMultiDay
|
|
80
|
+
? !!(state.startDate && state.endDate && state.selectedSlot)
|
|
81
|
+
: !!(state.selectedDate && state.selectedSlot);
|
|
82
|
+
case "review":
|
|
83
|
+
return true;
|
|
84
|
+
default:
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
const createCalendarGrid = (date) => {
|
|
89
|
+
const first = new Date(date.getFullYear(), date.getMonth(), 1);
|
|
90
|
+
const last = new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
|
91
|
+
const cells = [];
|
|
92
|
+
// Leading blanks
|
|
93
|
+
const pad = (first.getDay() + 6) % 7;
|
|
94
|
+
for (let i = 0; i < pad; i++)
|
|
95
|
+
cells.push({ key: `b-${i}`, blank: true });
|
|
96
|
+
// Date cells
|
|
97
|
+
for (let d = 1; d <= last.getDate(); d++) {
|
|
98
|
+
cells.push({
|
|
99
|
+
key: `d-${d}`,
|
|
100
|
+
blank: false,
|
|
101
|
+
date: new Date(date.getFullYear(), date.getMonth(), d),
|
|
102
|
+
available: false,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// Trailing blanks
|
|
106
|
+
const suffix = (7 - (cells.length % 7)) % 7;
|
|
107
|
+
for (let i = 0; i < suffix; i++)
|
|
108
|
+
cells.push({ key: `b2-${i}`, blank: true });
|
|
109
|
+
return cells;
|
|
110
|
+
};
|
|
111
|
+
const formatTimeSlot = (from, to, timezone) => {
|
|
112
|
+
const opts = { hour: "2-digit", minute: "2-digit", timeZone: timezone };
|
|
113
|
+
return `${new Date(from * 1000).toLocaleTimeString([], opts)} – ${new Date(to * 1000).toLocaleTimeString([], opts)}`;
|
|
114
|
+
};
|
|
115
|
+
// Actions
|
|
116
|
+
export const actions = {
|
|
117
|
+
// Calendar management
|
|
118
|
+
updateCalendarGrid() {
|
|
119
|
+
const state = store.get();
|
|
120
|
+
const cur = state.current || new Date(new Date().getFullYear(), new Date().getMonth(), 1);
|
|
121
|
+
const days = createCalendarGrid(cur);
|
|
122
|
+
store.setKey("current", cur);
|
|
123
|
+
store.setKey("monthYear", cur.toLocaleString(undefined, { month: "long", year: "numeric" }));
|
|
124
|
+
store.setKey("days", days);
|
|
125
|
+
},
|
|
126
|
+
updateCalendar() {
|
|
127
|
+
this.updateCalendarGrid();
|
|
128
|
+
const state = store.get();
|
|
129
|
+
if (state.service)
|
|
130
|
+
this.fetchAvailability("month");
|
|
131
|
+
},
|
|
132
|
+
prevMonth() {
|
|
133
|
+
const { current } = store.get();
|
|
134
|
+
store.setKey("current", new Date(current.getFullYear(), current.getMonth() - 1, 1));
|
|
135
|
+
this.updateCalendar();
|
|
136
|
+
},
|
|
137
|
+
nextMonth() {
|
|
138
|
+
const { current } = store.get();
|
|
139
|
+
store.setKey("current", new Date(current.getFullYear(), current.getMonth() + 1, 1));
|
|
140
|
+
this.updateCalendar();
|
|
141
|
+
},
|
|
142
|
+
// Service initialization
|
|
143
|
+
setService(service) {
|
|
144
|
+
store.setKey("service", service);
|
|
145
|
+
store.setKey("selectedMethod", null);
|
|
146
|
+
store.setKey("selectedProvider", null);
|
|
147
|
+
store.setKey("providers", []);
|
|
148
|
+
store.setKey("selectedDate", null);
|
|
149
|
+
store.setKey("startDate", null);
|
|
150
|
+
store.setKey("endDate", null);
|
|
151
|
+
store.setKey("slots", []);
|
|
152
|
+
store.setKey("selectedSlot", null);
|
|
153
|
+
store.setKey("currentStep", 1);
|
|
154
|
+
store.setKey("isMultiDay", !!service?.reservationConfigs?.isMultiDay);
|
|
155
|
+
const now = new Date();
|
|
156
|
+
store.setKey("current", new Date(now.getFullYear(), now.getMonth(), 1));
|
|
157
|
+
this.updateCalendarGrid();
|
|
158
|
+
// Auto-select if only one method available
|
|
159
|
+
if (service.reservationMethods?.length === 1) {
|
|
160
|
+
const method = service.reservationMethods[0];
|
|
161
|
+
store.setKey("selectedMethod", method);
|
|
162
|
+
this.determineTotalSteps();
|
|
163
|
+
this.handleMethodSelection(method, false);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
this.determineTotalSteps();
|
|
167
|
+
}
|
|
168
|
+
this.fetchAvailability("month");
|
|
169
|
+
},
|
|
170
|
+
// Step management
|
|
171
|
+
determineTotalSteps() {
|
|
172
|
+
const state = store.get();
|
|
173
|
+
if (!state.service) {
|
|
174
|
+
store.setKey("totalSteps", 1);
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
177
|
+
const active = [];
|
|
178
|
+
if (state.service.reservationMethods?.length > 1) {
|
|
179
|
+
active.push({ name: "method", label: "Choose Reservation Type" });
|
|
180
|
+
}
|
|
181
|
+
if (state.selectedMethod?.includes("SPECIFIC")) {
|
|
182
|
+
active.push({ name: "provider", label: "Choose Provider" });
|
|
183
|
+
}
|
|
184
|
+
if (state.selectedMethod && state.selectedMethod !== "ORDER") {
|
|
185
|
+
active.push({
|
|
186
|
+
name: "datetime",
|
|
187
|
+
label: state.isMultiDay ? "Choose Date Range" : "Choose Date & Time",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
active.push({ name: "review", label: "Review & Confirm" });
|
|
191
|
+
const stepObj = {};
|
|
192
|
+
active.forEach((st, idx) => {
|
|
193
|
+
stepObj[idx + 1] = st;
|
|
194
|
+
});
|
|
195
|
+
store.setKey("steps", stepObj);
|
|
196
|
+
store.setKey("totalSteps", active.length);
|
|
197
|
+
if (state.currentStep > active.length) {
|
|
198
|
+
store.setKey("currentStep", active.length);
|
|
199
|
+
}
|
|
200
|
+
return active.length;
|
|
201
|
+
},
|
|
202
|
+
async getGuestToken() {
|
|
203
|
+
const state = store.get();
|
|
204
|
+
const token = await authService.getGuestToken(state.guestToken);
|
|
205
|
+
if (token !== state.guestToken) {
|
|
206
|
+
store.setKey("guestToken", token);
|
|
207
|
+
}
|
|
208
|
+
return token;
|
|
209
|
+
},
|
|
210
|
+
getStepNumberByName(name) {
|
|
211
|
+
const { steps } = store.get();
|
|
212
|
+
for (const [k, v] of Object.entries(steps)) {
|
|
213
|
+
if (v.name === name)
|
|
214
|
+
return Number(k);
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
},
|
|
218
|
+
nextStep() {
|
|
219
|
+
const state = store.get();
|
|
220
|
+
if (state.currentStep >= state.totalSteps || !canProceed.get())
|
|
221
|
+
return;
|
|
222
|
+
const next = state.currentStep + 1;
|
|
223
|
+
const name = state.steps[next]?.name;
|
|
224
|
+
store.setKey("currentStep", next);
|
|
225
|
+
if (name === "datetime") {
|
|
226
|
+
this.fetchAvailability("month");
|
|
227
|
+
if (!state.selectedDate && !state.startDate) {
|
|
228
|
+
this.findFirstAvailable();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
prevStep() {
|
|
233
|
+
const state = store.get();
|
|
234
|
+
if (state.currentStep <= 1)
|
|
235
|
+
return;
|
|
236
|
+
this.clearCurrentStepState();
|
|
237
|
+
store.setKey("currentStep", state.currentStep - 1);
|
|
238
|
+
},
|
|
239
|
+
clearCurrentStepState() {
|
|
240
|
+
const name = currentStepName.get();
|
|
241
|
+
if (name === "method") {
|
|
242
|
+
store.setKey("selectedMethod", null);
|
|
243
|
+
}
|
|
244
|
+
else if (name === "provider") {
|
|
245
|
+
store.setKey("selectedProvider", null);
|
|
246
|
+
store.setKey("providers", []);
|
|
247
|
+
}
|
|
248
|
+
else if (name === "datetime") {
|
|
249
|
+
store.setKey("selectedDate", null);
|
|
250
|
+
store.setKey("startDate", null);
|
|
251
|
+
store.setKey("endDate", null);
|
|
252
|
+
store.setKey("slots", []);
|
|
253
|
+
store.setKey("selectedSlot", null);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
goToStep(step) {
|
|
257
|
+
const state = store.get();
|
|
258
|
+
if (step < 1 || step > state.totalSteps)
|
|
259
|
+
return;
|
|
260
|
+
if (step < state.currentStep) {
|
|
261
|
+
for (let i = state.currentStep; i > step; i--) {
|
|
262
|
+
const n = state.steps[i]?.name;
|
|
263
|
+
if (n === "datetime") {
|
|
264
|
+
store.setKey("selectedDate", null);
|
|
265
|
+
store.setKey("startDate", null);
|
|
266
|
+
store.setKey("endDate", null);
|
|
267
|
+
store.setKey("slots", []);
|
|
268
|
+
store.setKey("selectedSlot", null);
|
|
269
|
+
}
|
|
270
|
+
else if (n === "provider") {
|
|
271
|
+
store.setKey("selectedProvider", null);
|
|
272
|
+
store.setKey("providers", []);
|
|
273
|
+
}
|
|
274
|
+
else if (n === "method") {
|
|
275
|
+
store.setKey("selectedMethod", null);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
store.setKey("currentStep", step);
|
|
280
|
+
if (state.steps[step]?.name === "datetime") {
|
|
281
|
+
this.fetchAvailability("month");
|
|
282
|
+
if (!state.selectedDate && !state.startDate) {
|
|
283
|
+
this.findFirstAvailable();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
// Method selection
|
|
288
|
+
async handleMethodSelection(method, advance = true) {
|
|
289
|
+
store.setKey("selectedDate", null);
|
|
290
|
+
store.setKey("startDate", null);
|
|
291
|
+
store.setKey("endDate", null);
|
|
292
|
+
store.setKey("slots", []);
|
|
293
|
+
store.setKey("selectedSlot", null);
|
|
294
|
+
store.setKey("selectedMethod", method);
|
|
295
|
+
this.determineTotalSteps();
|
|
296
|
+
if (method === "ORDER") {
|
|
297
|
+
this.handleOrderMethod();
|
|
298
|
+
if (advance) {
|
|
299
|
+
const reviewStep = this.getStepNumberByName("review");
|
|
300
|
+
if (reviewStep)
|
|
301
|
+
this.goToStep(reviewStep);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
else if (method.includes("SPECIFIC")) {
|
|
306
|
+
await this.loadProviders();
|
|
307
|
+
const state = store.get();
|
|
308
|
+
if (advance && state.providers.length === 1) {
|
|
309
|
+
this.selectProvider(state.providers[0]);
|
|
310
|
+
const datetimeStep = this.getStepNumberByName("datetime");
|
|
311
|
+
if (datetimeStep)
|
|
312
|
+
this.goToStep(datetimeStep);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else if (method === "STANDARD" && advance) {
|
|
317
|
+
const datetimeStep = this.getStepNumberByName("datetime");
|
|
318
|
+
if (datetimeStep)
|
|
319
|
+
this.goToStep(datetimeStep);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (advance && store.get().currentStep < store.get().totalSteps) {
|
|
323
|
+
this.nextStep();
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
handleOrderMethod() {
|
|
327
|
+
const state = store.get();
|
|
328
|
+
const now = new Date();
|
|
329
|
+
const dur = state.service.durations?.reduce((a, c) => a + c.duration, 0) || 3600;
|
|
330
|
+
const from = Math.floor(now.getTime() / 1000);
|
|
331
|
+
const to = from + dur;
|
|
332
|
+
store.setKey("selectedSlot", {
|
|
333
|
+
from,
|
|
334
|
+
to,
|
|
335
|
+
timeText: formatTimeSlot(from, to, state.timezone),
|
|
336
|
+
});
|
|
337
|
+
},
|
|
338
|
+
// Provider management
|
|
339
|
+
async loadProviders() {
|
|
340
|
+
store.setKey("loading", true);
|
|
341
|
+
store.setKey("providers", []);
|
|
342
|
+
try {
|
|
343
|
+
const { businessId, service } = store.get();
|
|
344
|
+
const res = await reservationApi.getProviders({ businessId, serviceId: service.id });
|
|
345
|
+
store.setKey("providers", res.success ? res.data : []);
|
|
346
|
+
}
|
|
347
|
+
catch (e) {
|
|
348
|
+
console.error("Error loading providers:", e);
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
store.setKey("loading", false);
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
selectProvider(provider) {
|
|
355
|
+
store.setKey("selectedProvider", provider);
|
|
356
|
+
store.setKey("selectedDate", null);
|
|
357
|
+
store.setKey("startDate", null);
|
|
358
|
+
store.setKey("endDate", null);
|
|
359
|
+
store.setKey("slots", []);
|
|
360
|
+
store.setKey("selectedSlot", null);
|
|
361
|
+
if (currentStepName.get() === "datetime") {
|
|
362
|
+
this.fetchAvailability("month");
|
|
363
|
+
this.findFirstAvailable();
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
// Availability and date management
|
|
367
|
+
async fetchAvailability(type, date = null) {
|
|
368
|
+
const state = store.get();
|
|
369
|
+
if (!state.service || currentStepName.get() !== "datetime")
|
|
370
|
+
return;
|
|
371
|
+
store.setKey("loading", true);
|
|
372
|
+
try {
|
|
373
|
+
let from, to, limit;
|
|
374
|
+
if (type === "month") {
|
|
375
|
+
from = Math.floor(new Date(state.current.getFullYear(), state.current.getMonth(), 1).getTime() / 1000);
|
|
376
|
+
to = Math.floor(new Date(state.current.getFullYear(), state.current.getMonth() + 1, 0).getTime() / 1000);
|
|
377
|
+
limit = 100;
|
|
378
|
+
}
|
|
379
|
+
else if (type === "day" && date) {
|
|
380
|
+
const dObj = typeof date === "string" ? new Date(date) : date;
|
|
381
|
+
from = Math.floor(dObj.getTime() / 1000);
|
|
382
|
+
to = from + 24 * 3600;
|
|
383
|
+
limit = 100;
|
|
384
|
+
}
|
|
385
|
+
else if (type === "first") {
|
|
386
|
+
const now = new Date();
|
|
387
|
+
from = Math.floor(now.setHours(0, 0, 0, 0) / 1000);
|
|
388
|
+
to = Math.floor(new Date(now.getFullYear(), now.getMonth() + 3, 0).getTime() / 1000);
|
|
389
|
+
limit = 1;
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
store.setKey("loading", false);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const params = { businessId: state.businessId, serviceId: state.service.id, from, to, limit };
|
|
396
|
+
if (state.selectedProvider)
|
|
397
|
+
params.providerId = state.selectedProvider.id;
|
|
398
|
+
const result = await reservationApi.getAvailableSlots(params);
|
|
399
|
+
if (!result.success) {
|
|
400
|
+
console.error(`Error fetching availability (${type}):`, result.error);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (type === "month") {
|
|
404
|
+
const avail = new Set(result.data.map((i) => {
|
|
405
|
+
const date = new Date(i.from * 1000);
|
|
406
|
+
return date.toISOString().slice(0, 10);
|
|
407
|
+
}));
|
|
408
|
+
store.setKey("days", state.days.map((c) => {
|
|
409
|
+
if (!c.blank && c.date) {
|
|
410
|
+
const iso = c.date.toISOString().slice(0, 10);
|
|
411
|
+
return { ...c, available: avail.has(iso) };
|
|
412
|
+
}
|
|
413
|
+
return c;
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
else if (type === "day") {
|
|
417
|
+
const slots = result.data.map((i, idx) => ({
|
|
418
|
+
...i,
|
|
419
|
+
id: `slot-${i.from}-${idx}`,
|
|
420
|
+
day: new Date(i.from * 1000).toISOString().slice(0, 10),
|
|
421
|
+
timeText: formatTimeSlot(i.from, i.to, state.timezone),
|
|
422
|
+
}));
|
|
423
|
+
store.setKey("slots", slots);
|
|
424
|
+
if (slots.length && !state.selectedSlot) {
|
|
425
|
+
store.setKey("selectedSlot", slots[0]);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else if (type === "first" && result.data.length) {
|
|
429
|
+
const first = new Date(result.data[0].from * 1000);
|
|
430
|
+
const iso = first.toISOString().slice(0, 10);
|
|
431
|
+
store.setKey("current", new Date(first.getFullYear(), first.getMonth(), 1));
|
|
432
|
+
this.updateCalendarGrid();
|
|
433
|
+
await this.fetchAvailability("month");
|
|
434
|
+
if (state.isMultiDay) {
|
|
435
|
+
store.setKey("startDate", iso);
|
|
436
|
+
store.setKey("selectedDate", iso);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
store.setKey("selectedDate", iso);
|
|
440
|
+
await this.fetchAvailability("day", iso);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
console.error(`Error in fetchAvailability (${type}):`, err);
|
|
446
|
+
}
|
|
447
|
+
finally {
|
|
448
|
+
store.setKey("loading", false);
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
findFirstAvailable() {
|
|
452
|
+
if (currentStepName.get() === "datetime")
|
|
453
|
+
this.fetchAvailability("first");
|
|
454
|
+
},
|
|
455
|
+
// Date selection
|
|
456
|
+
selectDate(cell) {
|
|
457
|
+
if (!cell.date || !cell.available)
|
|
458
|
+
return;
|
|
459
|
+
// Store date components directly to avoid timezone issues
|
|
460
|
+
const dateInfo = {
|
|
461
|
+
year: cell.date.getFullYear(),
|
|
462
|
+
month: cell.date.getMonth() + 1,
|
|
463
|
+
day: cell.date.getDate(),
|
|
464
|
+
iso: `${cell.date.getFullYear()}-${String(cell.date.getMonth() + 1).padStart(2, '0')}-${String(cell.date.getDate()).padStart(2, '0')}`
|
|
465
|
+
};
|
|
466
|
+
const state = store.get();
|
|
467
|
+
if (state.isMultiDay) {
|
|
468
|
+
if (!state.startDate) {
|
|
469
|
+
store.setKey("startDate", dateInfo.iso);
|
|
470
|
+
store.setKey("selectedSlot", null);
|
|
471
|
+
store.setKey("selectedDate", dateInfo.iso);
|
|
472
|
+
store.setKey("endDate", null);
|
|
473
|
+
}
|
|
474
|
+
else if (!state.endDate) {
|
|
475
|
+
const start = new Date(state.startDate).getTime();
|
|
476
|
+
const cellT = cell.date.getTime();
|
|
477
|
+
if (cellT < start) {
|
|
478
|
+
store.setKey("endDate", state.startDate);
|
|
479
|
+
store.setKey("startDate", dateInfo.iso);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
store.setKey("endDate", dateInfo.iso);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
store.setKey("startDate", dateInfo.iso);
|
|
487
|
+
store.setKey("selectedDate", dateInfo.iso);
|
|
488
|
+
store.setKey("endDate", null);
|
|
489
|
+
store.setKey("selectedSlot", null);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
store.setKey("selectedSlot", null);
|
|
494
|
+
store.setKey("selectedDate", dateInfo.iso);
|
|
495
|
+
this.fetchAvailability("day", dateInfo.iso);
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
createMultiDaySlot() {
|
|
499
|
+
const state = store.get();
|
|
500
|
+
if (!state.startDate || !state.endDate)
|
|
501
|
+
return;
|
|
502
|
+
const startDT = new Date(state.startDate);
|
|
503
|
+
startDT.setHours(9, 0, 0, 0);
|
|
504
|
+
const endDT = new Date(state.endDate);
|
|
505
|
+
endDT.setHours(17, 0, 0, 0);
|
|
506
|
+
const from = Math.floor(startDT.getTime() / 1000);
|
|
507
|
+
const to = Math.floor(endDT.getTime() / 1000);
|
|
508
|
+
const rangeSlot = {
|
|
509
|
+
id: `multi-day-slot-${from}-${to}`,
|
|
510
|
+
from,
|
|
511
|
+
to,
|
|
512
|
+
isMultiDay: true,
|
|
513
|
+
timeText: `9:00 AM - 5:00 PM daily`,
|
|
514
|
+
dateRange: `${this.formatDateDisplay(state.startDate)} to ${this.formatDateDisplay(state.endDate)}`,
|
|
515
|
+
day: state.startDate,
|
|
516
|
+
};
|
|
517
|
+
store.setKey("slots", [rangeSlot]);
|
|
518
|
+
store.setKey("selectedSlot", rangeSlot);
|
|
519
|
+
},
|
|
520
|
+
resetDateSelection() {
|
|
521
|
+
store.setKey("startDate", null);
|
|
522
|
+
store.setKey("endDate", null);
|
|
523
|
+
store.setKey("selectedDate", null);
|
|
524
|
+
store.setKey("slots", []);
|
|
525
|
+
store.setKey("selectedSlot", null);
|
|
526
|
+
},
|
|
527
|
+
selectTimeSlot(slot) {
|
|
528
|
+
store.setKey("selectedSlot", slot);
|
|
529
|
+
},
|
|
530
|
+
setSelectedTimeZone(zone) {
|
|
531
|
+
const state = store.get();
|
|
532
|
+
if (zone === state.timezone)
|
|
533
|
+
return;
|
|
534
|
+
store.setKey("timezone", zone);
|
|
535
|
+
if (currentStepName.get() === "datetime") {
|
|
536
|
+
if (state.selectedDate) {
|
|
537
|
+
this.fetchAvailability("day", state.selectedDate);
|
|
538
|
+
}
|
|
539
|
+
else if (!state.selectedDate && !state.startDate) {
|
|
540
|
+
this.findFirstAvailable();
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
// Calendar helpers
|
|
545
|
+
isAvailable(cell) {
|
|
546
|
+
return cell.date && cell.available;
|
|
547
|
+
},
|
|
548
|
+
isSelectedDay(cell) {
|
|
549
|
+
if (cell.blank || !cell.date)
|
|
550
|
+
return false;
|
|
551
|
+
const iso = `${cell.date.getFullYear()}-${String(cell.date.getMonth() + 1).padStart(2, '0')}-${String(cell.date.getDate()).padStart(2, '0')}`;
|
|
552
|
+
const state = store.get();
|
|
553
|
+
return iso === state.startDate || iso === state.endDate || iso === state.selectedDate;
|
|
554
|
+
},
|
|
555
|
+
isInSelectedRange(cell) {
|
|
556
|
+
const state = store.get();
|
|
557
|
+
if (cell.blank || !cell.date || !state.startDate || !state.endDate)
|
|
558
|
+
return false;
|
|
559
|
+
const t = cell.date.getTime();
|
|
560
|
+
const a = new Date(state.startDate).getTime();
|
|
561
|
+
const b = new Date(state.endDate).getTime();
|
|
562
|
+
return t >= a && t <= b;
|
|
563
|
+
},
|
|
564
|
+
formatDateDisplay(ds) {
|
|
565
|
+
if (!ds)
|
|
566
|
+
return "";
|
|
567
|
+
const d = new Date(ds);
|
|
568
|
+
return d.toLocaleDateString(getLocale(), { month: "short", day: "numeric" });
|
|
569
|
+
},
|
|
570
|
+
// Cart operations
|
|
571
|
+
addToCart(slot) {
|
|
572
|
+
const state = store.get();
|
|
573
|
+
const id = crypto.randomUUID();
|
|
574
|
+
let dateDisplay, timeText;
|
|
575
|
+
if (state.isMultiDay && slot.isMultiDay) {
|
|
576
|
+
const a = new Date(slot.from * 1000), b = new Date(slot.to * 1000);
|
|
577
|
+
dateDisplay = `${a.toLocaleDateString(getLocale(), { month: "short", day: "numeric" })} - ${b.toLocaleDateString(getLocale(), { month: "short", day: "numeric", year: "numeric" })}`;
|
|
578
|
+
timeText = slot.timeText;
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
const date = state.selectedDate ? new Date(state.selectedDate) : new Date(slot.from * 1000);
|
|
582
|
+
dateDisplay = date.toLocaleDateString(getLocale(), {
|
|
583
|
+
weekday: "short",
|
|
584
|
+
year: "numeric",
|
|
585
|
+
month: "short",
|
|
586
|
+
day: "numeric",
|
|
587
|
+
});
|
|
588
|
+
timeText = slot.timeText;
|
|
589
|
+
}
|
|
590
|
+
const blocks = (state.service?.reservationBlocks || []).map((f) => ({
|
|
591
|
+
...f,
|
|
592
|
+
value: Array.isArray(f.value) ? f.value : [f.value],
|
|
593
|
+
}));
|
|
594
|
+
const newPart = {
|
|
595
|
+
id,
|
|
596
|
+
serviceId: state.service.id,
|
|
597
|
+
serviceName: getLocalizedString(state.service.name, getLocale()),
|
|
598
|
+
date: dateDisplay,
|
|
599
|
+
from: slot.from,
|
|
600
|
+
to: slot.to,
|
|
601
|
+
timeText,
|
|
602
|
+
isMultiDay: state.isMultiDay && (!!state.endDate || slot.isMultiDay),
|
|
603
|
+
reservationMethod: state.selectedMethod || '',
|
|
604
|
+
providerId: state.selectedProvider?.id,
|
|
605
|
+
blocks,
|
|
606
|
+
};
|
|
607
|
+
const newParts = [...state.parts, newPart];
|
|
608
|
+
store.setKey("parts", newParts);
|
|
609
|
+
cartParts.set(newParts);
|
|
610
|
+
this.resetDateSelection();
|
|
611
|
+
store.setKey("currentStep", 1);
|
|
612
|
+
if (state.service.reservationMethods?.length > 1) {
|
|
613
|
+
store.setKey("selectedMethod", null);
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
removePart(id) {
|
|
617
|
+
const filteredParts = store.get().parts.filter((p) => p.id !== id);
|
|
618
|
+
store.setKey("parts", filteredParts);
|
|
619
|
+
cartParts.set(filteredParts);
|
|
620
|
+
},
|
|
621
|
+
// Phone validation helper (using shared utility)
|
|
622
|
+
validatePhoneNumber(phone) {
|
|
623
|
+
const result = validatePhoneNumber(phone);
|
|
624
|
+
return result.isValid;
|
|
625
|
+
},
|
|
626
|
+
// Phone verification
|
|
627
|
+
async updateProfilePhone() {
|
|
628
|
+
store.setKey("phoneError", null);
|
|
629
|
+
store.setKey("phoneSuccess", null);
|
|
630
|
+
store.setKey("isSendingCode", true);
|
|
631
|
+
try {
|
|
632
|
+
const phoneNumber = store.get().phoneNumber;
|
|
633
|
+
// Validate phone number format
|
|
634
|
+
if (!this.validatePhoneNumber(phoneNumber)) {
|
|
635
|
+
store.setKey("phoneError", "Please enter a valid phone number");
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
const token = await this.getGuestToken();
|
|
639
|
+
await authService.updateProfilePhone(token, phoneNumber);
|
|
640
|
+
store.setKey("phoneSuccess", "Verification code sent successfully!");
|
|
641
|
+
store.setKey("codeSentAt", Date.now());
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
catch (e) {
|
|
645
|
+
store.setKey("phoneError", e.message);
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
finally {
|
|
649
|
+
store.setKey("isSendingCode", false);
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
async verifyPhoneCode() {
|
|
653
|
+
store.setKey("verifyError", null);
|
|
654
|
+
store.setKey("isVerifying", true);
|
|
655
|
+
try {
|
|
656
|
+
const { phoneNumber, verificationCode } = store.get();
|
|
657
|
+
// Validate code format
|
|
658
|
+
if (!verificationCode || verificationCode.length !== 4) {
|
|
659
|
+
store.setKey("verifyError", "Please enter a 4-digit verification code");
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
const token = await this.getGuestToken();
|
|
663
|
+
await authService.verifyPhoneCode(token, phoneNumber, verificationCode);
|
|
664
|
+
store.setKey("isPhoneVerified", true);
|
|
665
|
+
store.setKey("phoneSuccess", null);
|
|
666
|
+
store.setKey("verificationCode", "");
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
catch (e) {
|
|
670
|
+
// Provide user-friendly error messages
|
|
671
|
+
let errorMessage = "Invalid verification code";
|
|
672
|
+
if (e.message?.includes("expired")) {
|
|
673
|
+
errorMessage = "Verification code has expired. Please request a new one.";
|
|
674
|
+
}
|
|
675
|
+
else if (e.message?.includes("incorrect") || e.message?.includes("invalid")) {
|
|
676
|
+
errorMessage = "Incorrect verification code. Please try again.";
|
|
677
|
+
}
|
|
678
|
+
store.setKey("verifyError", errorMessage);
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
finally {
|
|
682
|
+
store.setKey("isVerifying", false);
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
async checkout(paymentMethod = PaymentMethod.Cash, reservationBlocks, promoCode) {
|
|
686
|
+
const state = store.get();
|
|
687
|
+
if (state.loading || !state.parts.length)
|
|
688
|
+
return { success: false, error: "No parts in cart" };
|
|
689
|
+
store.setKey("loading", true);
|
|
690
|
+
try {
|
|
691
|
+
const token = await this.getGuestToken();
|
|
692
|
+
const result = await reservationApi.checkout({
|
|
693
|
+
token,
|
|
694
|
+
businessId: state.businessId,
|
|
695
|
+
blocks: reservationBlocks || [],
|
|
696
|
+
parts: state.parts,
|
|
697
|
+
paymentMethod,
|
|
698
|
+
market: 'us',
|
|
699
|
+
promoCode,
|
|
700
|
+
});
|
|
701
|
+
if (result.success) {
|
|
702
|
+
return {
|
|
703
|
+
success: true,
|
|
704
|
+
data: {
|
|
705
|
+
reservationId: result.data?.reservationId,
|
|
706
|
+
clientSecret: result.data?.clientSecret,
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
throw new Error(result.error);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
catch (e) {
|
|
715
|
+
console.error("Reservation checkout error:", e);
|
|
716
|
+
return { success: false, error: e.message };
|
|
717
|
+
}
|
|
718
|
+
finally {
|
|
719
|
+
store.setKey("loading", false);
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
async fetchQuote(paymentMethod = PaymentMethod.Cash, promoCode) {
|
|
723
|
+
const state = store.get();
|
|
724
|
+
console.log('fetchQuote called with promoCode:', promoCode);
|
|
725
|
+
if (!state.parts.length) {
|
|
726
|
+
store.setKey("quote", null);
|
|
727
|
+
store.setKey("quoteError", null);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
store.setKey("fetchingQuote", true);
|
|
731
|
+
store.setKey("quoteError", null);
|
|
732
|
+
try {
|
|
733
|
+
const token = await this.getGuestToken();
|
|
734
|
+
const marketObj = selectedMarket.get();
|
|
735
|
+
const market = marketObj?.id || 'us';
|
|
736
|
+
const curr = currency.get() || 'USD';
|
|
737
|
+
console.log('Calling reservationApi.getQuote with:', { market, currency: curr, promoCode });
|
|
738
|
+
const result = await reservationApi.getQuote({
|
|
739
|
+
token,
|
|
740
|
+
businessId: state.businessId,
|
|
741
|
+
market,
|
|
742
|
+
currency: curr,
|
|
743
|
+
userId: token, // Use token as userId for guests
|
|
744
|
+
parts: state.parts,
|
|
745
|
+
paymentMethod,
|
|
746
|
+
promoCode,
|
|
747
|
+
});
|
|
748
|
+
if (result.success && result.data) {
|
|
749
|
+
console.log('Quote received:', result.data);
|
|
750
|
+
store.setKey("quote", result.data);
|
|
751
|
+
store.setKey("quoteError", null);
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
console.error('Quote error:', result.error);
|
|
755
|
+
store.setKey("quote", null);
|
|
756
|
+
store.setKey("quoteError", mapQuoteError(result.code, result.error));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
catch (e) {
|
|
760
|
+
console.error("Fetch quote error:", e);
|
|
761
|
+
store.setKey("quote", null);
|
|
762
|
+
store.setKey("quoteError", e.message || "Failed to get quote");
|
|
763
|
+
}
|
|
764
|
+
finally {
|
|
765
|
+
store.setKey("fetchingQuote", false);
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
// Helpers
|
|
769
|
+
getLabel(block, locale = getLocale()) {
|
|
770
|
+
if (!block)
|
|
771
|
+
return "";
|
|
772
|
+
if (block.properties?.label) {
|
|
773
|
+
if (typeof block.properties.label === "object") {
|
|
774
|
+
return (block.properties.label[locale] ||
|
|
775
|
+
block.properties.label.en ||
|
|
776
|
+
Object.values(block.properties.label)[0] ||
|
|
777
|
+
"");
|
|
778
|
+
}
|
|
779
|
+
if (typeof block.properties.label === "string") {
|
|
780
|
+
return block.properties.label;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return block.key || "";
|
|
784
|
+
},
|
|
785
|
+
getServicePrice() {
|
|
786
|
+
const state = store.get();
|
|
787
|
+
if (state.service?.prices && Array.isArray(state.service.prices)) {
|
|
788
|
+
// Market-based pricing (amounts are minor units)
|
|
789
|
+
// TODO: Get market from business config instead of hardcoded 'us'
|
|
790
|
+
return getMarketPrice(state.service.prices, 'us');
|
|
791
|
+
}
|
|
792
|
+
return '';
|
|
793
|
+
},
|
|
794
|
+
// NEW: Get reservation total as Payment structure
|
|
795
|
+
getReservationPayment() {
|
|
796
|
+
const state = store.get();
|
|
797
|
+
const subtotalMinor = state.parts.reduce((sum, part) => {
|
|
798
|
+
const servicePrices = state.service?.prices || [];
|
|
799
|
+
// amounts are in minor units
|
|
800
|
+
const amountMinor = servicePrices.length > 0 ? getPriceAmount(servicePrices, 'US') : 0;
|
|
801
|
+
return sum + amountMinor;
|
|
802
|
+
}, 0);
|
|
803
|
+
const currencyCode = currency.get();
|
|
804
|
+
return createPaymentForCheckout(subtotalMinor, 'US', currencyCode, PaymentMethod.Cash);
|
|
805
|
+
},
|
|
806
|
+
};
|
|
807
|
+
export function initReservationStore() {
|
|
808
|
+
actions.updateCalendarGrid();
|
|
809
|
+
businessActions.init(); // Use unified business store
|
|
810
|
+
const savedParts = cartParts.get();
|
|
811
|
+
if (savedParts && savedParts.length > 0) {
|
|
812
|
+
store.setKey("parts", savedParts);
|
|
813
|
+
}
|
|
814
|
+
store.listen((state) => {
|
|
815
|
+
if (state.isMultiDay &&
|
|
816
|
+
state.startDate &&
|
|
817
|
+
state.endDate &&
|
|
818
|
+
currentStepName.get() === "datetime" &&
|
|
819
|
+
(!state.slots.length || !state.slots[0].isMultiDay)) {
|
|
820
|
+
actions.createMultiDaySlot();
|
|
821
|
+
}
|
|
822
|
+
if (JSON.stringify(state.parts) !== JSON.stringify(cartParts.get())) {
|
|
823
|
+
cartParts.set(state.parts);
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
cartParts.listen((parts) => {
|
|
827
|
+
const currentParts = store.get().parts;
|
|
828
|
+
if (JSON.stringify(parts) !== JSON.stringify(currentParts)) {
|
|
829
|
+
store.setKey("parts", [...parts]);
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
function mapQuoteError(code, fallback) {
|
|
834
|
+
switch (code) {
|
|
835
|
+
case 'PROMO.MIN_ORDER':
|
|
836
|
+
return fallback || 'Promo requires a higher minimum order.';
|
|
837
|
+
case 'PROMO.NOT_ACTIVE':
|
|
838
|
+
return 'Promo code is not active.';
|
|
839
|
+
case 'PROMO.NOT_YET_VALID':
|
|
840
|
+
return 'Promo code is not yet valid.';
|
|
841
|
+
case 'PROMO.EXPIRED':
|
|
842
|
+
return 'Promo code has expired.';
|
|
843
|
+
case 'PROMO.MAX_USES':
|
|
844
|
+
return 'Promo code usage limit exceeded.';
|
|
845
|
+
case 'PROMO.MAX_USES_PER_USER':
|
|
846
|
+
return 'You have already used this promo code.';
|
|
847
|
+
case 'PROMO.NOT_FOUND':
|
|
848
|
+
return 'Promo code not found.';
|
|
849
|
+
default:
|
|
850
|
+
return fallback || 'Failed to get quote.';
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
export default { store, actions, initReservationStore };
|