arky-sdk 0.7.124 → 0.7.126
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/storefront-store.cjs +602 -20
- package/dist/storefront-store.cjs.map +1 -1
- package/dist/storefront-store.d.cts +1 -1
- package/dist/storefront-store.d.ts +1 -1
- package/dist/storefront-store.js +602 -20
- package/dist/storefront-store.js.map +1 -1
- package/dist/storefront.cjs +602 -20
- package/dist/storefront.cjs.map +1 -1
- package/dist/storefront.d.cts +104 -3
- package/dist/storefront.d.ts +104 -3
- package/dist/storefront.js +602 -20
- package/dist/storefront.js.map +1 -1
- package/package.json +1 -1
|
@@ -1571,23 +1571,6 @@ function toServiceCheckoutItems(items) {
|
|
|
1571
1571
|
slots: [...item.slots].sort((a, b) => a.from - b.from)
|
|
1572
1572
|
}));
|
|
1573
1573
|
}
|
|
1574
|
-
function readStoredOrder(key) {
|
|
1575
|
-
if (!key || typeof window === "undefined") return null;
|
|
1576
|
-
try {
|
|
1577
|
-
const raw = window.localStorage.getItem(key);
|
|
1578
|
-
return raw ? JSON.parse(raw) : null;
|
|
1579
|
-
} catch {
|
|
1580
|
-
return null;
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
function writeStoredOrder(key, value) {
|
|
1584
|
-
if (!key || typeof window === "undefined") return;
|
|
1585
|
-
try {
|
|
1586
|
-
if (value) window.localStorage.setItem(key, JSON.stringify(value));
|
|
1587
|
-
else window.localStorage.removeItem(key);
|
|
1588
|
-
} catch {
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
1574
|
function formFieldsFromBlocks(blocks) {
|
|
1592
1575
|
return blocks.map((block) => ({
|
|
1593
1576
|
id: block.id,
|
|
@@ -1596,6 +1579,105 @@ function formFieldsFromBlocks(blocks) {
|
|
|
1596
1579
|
value: block.value
|
|
1597
1580
|
}));
|
|
1598
1581
|
}
|
|
1582
|
+
function getFormBlockType(field) {
|
|
1583
|
+
if (field.key === "email") return "email";
|
|
1584
|
+
if (field.key === "phone") return "phone";
|
|
1585
|
+
if (field.type === "geo_location") return "address";
|
|
1586
|
+
return field.type;
|
|
1587
|
+
}
|
|
1588
|
+
function getFormBlockValue(field) {
|
|
1589
|
+
if (field.type === "boolean") return false;
|
|
1590
|
+
if (field.type === "number") return field.min ?? 0;
|
|
1591
|
+
if (field.type === "geo_location") return {};
|
|
1592
|
+
return "";
|
|
1593
|
+
}
|
|
1594
|
+
function formSchemaToBlock(field) {
|
|
1595
|
+
return {
|
|
1596
|
+
id: field.id,
|
|
1597
|
+
key: field.key,
|
|
1598
|
+
type: getFormBlockType(field),
|
|
1599
|
+
properties: {
|
|
1600
|
+
isRequired: field.required,
|
|
1601
|
+
minValues: field.required ? 1 : 0,
|
|
1602
|
+
min: field.min,
|
|
1603
|
+
max: field.max,
|
|
1604
|
+
options: field.options,
|
|
1605
|
+
pattern: field.key === "email" ? "^.+@.+\\..+$" : field.key === "phone" ? "^.{6,20}$" : void 0
|
|
1606
|
+
},
|
|
1607
|
+
value: getFormBlockValue(field)
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
function formatBookingTime(ts, tz) {
|
|
1611
|
+
return new Date(ts * 1e3).toLocaleTimeString([], {
|
|
1612
|
+
hour: "2-digit",
|
|
1613
|
+
minute: "2-digit",
|
|
1614
|
+
timeZone: tz
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
function formatBookingSlotTime(from, to, tz) {
|
|
1618
|
+
return `${formatBookingTime(from, tz)} - ${formatBookingTime(to, tz)}`;
|
|
1619
|
+
}
|
|
1620
|
+
function getSlotsForDate(availability, dateStr, providerId) {
|
|
1621
|
+
if (!availability) return [];
|
|
1622
|
+
const slots = [];
|
|
1623
|
+
for (const provider of availability.providers) {
|
|
1624
|
+
if (providerId && provider.provider_id !== providerId) continue;
|
|
1625
|
+
const day = provider.days.find((candidate) => candidate.date === dateStr);
|
|
1626
|
+
if (!day) continue;
|
|
1627
|
+
for (const slot of day.slots) {
|
|
1628
|
+
if (slot.spots > 0) slots.push({ from: slot.from, to: slot.to, providerId: provider.provider_id });
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
return slots.sort((a, b) => a.from - b.from);
|
|
1632
|
+
}
|
|
1633
|
+
function hasAvailableSlotsForDate(availability, dateStr, providerId) {
|
|
1634
|
+
if (!availability) return false;
|
|
1635
|
+
return availability.providers.some((provider) => {
|
|
1636
|
+
if (providerId && provider.provider_id !== providerId) return false;
|
|
1637
|
+
const day = provider.days.find((candidate) => candidate.date === dateStr);
|
|
1638
|
+
return !!day?.slots.some((slot) => slot.spots > 0);
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
var BOOKING_WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
1642
|
+
function createBookingInitialState() {
|
|
1643
|
+
return {
|
|
1644
|
+
service: null,
|
|
1645
|
+
availability: null,
|
|
1646
|
+
providers: [],
|
|
1647
|
+
selectedProviderId: null,
|
|
1648
|
+
currentMonth: new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1),
|
|
1649
|
+
calendar: [],
|
|
1650
|
+
selectedDate: null,
|
|
1651
|
+
startDate: null,
|
|
1652
|
+
endDate: null,
|
|
1653
|
+
slots: [],
|
|
1654
|
+
selectedSlot: null,
|
|
1655
|
+
cart: [],
|
|
1656
|
+
timezone: typeof window !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : "UTC",
|
|
1657
|
+
tzGroups: {},
|
|
1658
|
+
loading: false,
|
|
1659
|
+
weekdays: BOOKING_WEEKDAYS,
|
|
1660
|
+
quote: null,
|
|
1661
|
+
fetchingQuote: false,
|
|
1662
|
+
quoteError: null,
|
|
1663
|
+
currency: null,
|
|
1664
|
+
dateTimeConfirmed: false,
|
|
1665
|
+
isMultiDay: false,
|
|
1666
|
+
availablePaymentMethods: [],
|
|
1667
|
+
cartId: null,
|
|
1668
|
+
promoCode: null
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
function normalizeTimezoneGroups(groups) {
|
|
1672
|
+
const normalized = {};
|
|
1673
|
+
for (const group of groups) {
|
|
1674
|
+
normalized[group.label] = group.zones.map((zone) => ({
|
|
1675
|
+
zone: zone.value,
|
|
1676
|
+
name: zone.label
|
|
1677
|
+
}));
|
|
1678
|
+
}
|
|
1679
|
+
return normalized;
|
|
1680
|
+
}
|
|
1599
1681
|
function createArkyStore(config) {
|
|
1600
1682
|
const client = createStorefront(config);
|
|
1601
1683
|
const session = nanostores.atom(client.session);
|
|
@@ -1615,7 +1697,7 @@ function createArkyStore(config) {
|
|
|
1615
1697
|
const service_items = nanostores.atom([]);
|
|
1616
1698
|
const quote = nanostores.atom(null);
|
|
1617
1699
|
const promo_code = nanostores.atom(null);
|
|
1618
|
-
const last_order = nanostores.atom(
|
|
1700
|
+
const last_order = nanostores.atom(null);
|
|
1619
1701
|
const cart_status = nanostores.map({
|
|
1620
1702
|
loading: false,
|
|
1621
1703
|
syncing: false,
|
|
@@ -1659,9 +1741,18 @@ function createArkyStore(config) {
|
|
|
1659
1741
|
loading_availability: false,
|
|
1660
1742
|
error: null
|
|
1661
1743
|
});
|
|
1744
|
+
const booking_state = nanostores.map(createBookingInitialState());
|
|
1745
|
+
const booking_form_node = nanostores.atom(null);
|
|
1746
|
+
const booking_form_blocks = nanostores.computed(booking_form_node, (node) => node?.blocks || []);
|
|
1662
1747
|
client.onAuthStateChanged((value) => session.set(value));
|
|
1663
1748
|
snapshot.subscribe((value) => config.onCartChange?.(value));
|
|
1664
|
-
|
|
1749
|
+
currency.subscribe((value) => booking_state.setKey("currency", value));
|
|
1750
|
+
session.subscribe((value) => {
|
|
1751
|
+
const methods = value?.market?.payment_methods || [];
|
|
1752
|
+
if (methods.length && booking_state.get().availablePaymentMethods.length === 0) {
|
|
1753
|
+
booking_state.setKey("availablePaymentMethods", methods);
|
|
1754
|
+
}
|
|
1755
|
+
});
|
|
1665
1756
|
function currentMarketKey() {
|
|
1666
1757
|
return market_key.get() || client.getMarket() || market.get()?.key || "";
|
|
1667
1758
|
}
|
|
@@ -1923,6 +2014,485 @@ function createArkyStore(config) {
|
|
|
1923
2014
|
cart_status.setKey("processing_checkout", false);
|
|
1924
2015
|
}
|
|
1925
2016
|
}
|
|
2017
|
+
function bookingCalendar() {
|
|
2018
|
+
const state = booking_state.get();
|
|
2019
|
+
const { currentMonth, selectedDate, startDate, endDate, availability, selectedProviderId } = state;
|
|
2020
|
+
const year = currentMonth.getFullYear();
|
|
2021
|
+
const monthIndex = currentMonth.getMonth();
|
|
2022
|
+
const first = new Date(year, monthIndex, 1);
|
|
2023
|
+
const last = new Date(year, monthIndex + 1, 0);
|
|
2024
|
+
const today = /* @__PURE__ */ new Date();
|
|
2025
|
+
today.setHours(0, 0, 0, 0);
|
|
2026
|
+
const cells = [];
|
|
2027
|
+
const pad = (first.getDay() + 6) % 7;
|
|
2028
|
+
for (let i = 0; i < pad; i++) {
|
|
2029
|
+
cells.push({
|
|
2030
|
+
date: /* @__PURE__ */ new Date(0),
|
|
2031
|
+
iso: "",
|
|
2032
|
+
available: false,
|
|
2033
|
+
isSelected: false,
|
|
2034
|
+
isInRange: false,
|
|
2035
|
+
isToday: false,
|
|
2036
|
+
blank: true
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
for (let day = 1; day <= last.getDate(); day++) {
|
|
2040
|
+
const date = new Date(year, monthIndex, day);
|
|
2041
|
+
const iso = `${year}-${String(monthIndex + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
2042
|
+
const isSelected = iso === selectedDate || iso === startDate || iso === endDate;
|
|
2043
|
+
let isInRange = false;
|
|
2044
|
+
if (startDate && endDate) {
|
|
2045
|
+
const time = date.getTime();
|
|
2046
|
+
isInRange = time > new Date(startDate).getTime() && time < new Date(endDate).getTime();
|
|
2047
|
+
}
|
|
2048
|
+
cells.push({
|
|
2049
|
+
date,
|
|
2050
|
+
iso,
|
|
2051
|
+
available: hasAvailableSlotsForDate(availability, iso, selectedProviderId),
|
|
2052
|
+
isSelected,
|
|
2053
|
+
isInRange,
|
|
2054
|
+
isToday: date.getTime() === today.getTime(),
|
|
2055
|
+
blank: false
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
const suffix = (7 - cells.length % 7) % 7;
|
|
2059
|
+
for (let i = 0; i < suffix; i++) {
|
|
2060
|
+
cells.push({
|
|
2061
|
+
date: /* @__PURE__ */ new Date(0),
|
|
2062
|
+
iso: "",
|
|
2063
|
+
available: false,
|
|
2064
|
+
isSelected: false,
|
|
2065
|
+
isInRange: false,
|
|
2066
|
+
isToday: false,
|
|
2067
|
+
blank: true
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
return cells;
|
|
2071
|
+
}
|
|
2072
|
+
function computeBookingSlots(dateStr) {
|
|
2073
|
+
const state = booking_state.get();
|
|
2074
|
+
const { availability, selectedProviderId, timezone, service } = state;
|
|
2075
|
+
return getSlotsForDate(availability, dateStr, selectedProviderId).map((slot, index) => ({
|
|
2076
|
+
id: `${service?.id || "service"}-${slot.from}-${index}`,
|
|
2077
|
+
serviceId: service?.id || "",
|
|
2078
|
+
providerId: slot.providerId,
|
|
2079
|
+
from: slot.from,
|
|
2080
|
+
to: slot.to,
|
|
2081
|
+
timeText: formatBookingSlotTime(slot.from, slot.to, timezone),
|
|
2082
|
+
dateText: new Date(slot.from * 1e3).toLocaleDateString([], {
|
|
2083
|
+
weekday: "short",
|
|
2084
|
+
month: "short",
|
|
2085
|
+
day: "numeric",
|
|
2086
|
+
timeZone: timezone
|
|
2087
|
+
})
|
|
2088
|
+
}));
|
|
2089
|
+
}
|
|
2090
|
+
function toServiceCartItem(slot) {
|
|
2091
|
+
return {
|
|
2092
|
+
id: slot.id,
|
|
2093
|
+
service_id: slot.serviceId,
|
|
2094
|
+
provider_id: slot.providerId,
|
|
2095
|
+
from: slot.from,
|
|
2096
|
+
to: slot.to,
|
|
2097
|
+
forms: [],
|
|
2098
|
+
service_name: slot.serviceName,
|
|
2099
|
+
date_text: slot.dateText,
|
|
2100
|
+
time_text: slot.timeText,
|
|
2101
|
+
is_multi_day: slot.isMultiDay
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
function fromServiceCartItem(item) {
|
|
2105
|
+
return {
|
|
2106
|
+
id: item.id,
|
|
2107
|
+
serviceId: item.service_id,
|
|
2108
|
+
providerId: item.provider_id,
|
|
2109
|
+
from: item.from,
|
|
2110
|
+
to: item.to,
|
|
2111
|
+
serviceName: item.service_name || "",
|
|
2112
|
+
date: item.date_text || "",
|
|
2113
|
+
dateText: item.date_text || "",
|
|
2114
|
+
timeText: item.time_text || formatBookingSlotTime(item.from, item.to, booking_state.get().timezone),
|
|
2115
|
+
isMultiDay: item.is_multi_day
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
function setBookingCartFromServiceItems(items) {
|
|
2119
|
+
const next = items.map(fromServiceCartItem);
|
|
2120
|
+
const current = booking_state.get().cart;
|
|
2121
|
+
if (JSON.stringify(current) !== JSON.stringify(next)) {
|
|
2122
|
+
booking_state.setKey("cart", next);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
async function syncBookingCart(slots) {
|
|
2126
|
+
try {
|
|
2127
|
+
return await syncCart({
|
|
2128
|
+
product_items: product_items.get(),
|
|
2129
|
+
service_items: slots.map(toServiceCartItem)
|
|
2130
|
+
});
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
booking_state.setKey("quoteError", readErrorMessage(error, "Failed to sync booking cart."));
|
|
2133
|
+
throw error;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
function bookingCurrentStepName() {
|
|
2137
|
+
const state = booking_state.get();
|
|
2138
|
+
if (!state.service) return "";
|
|
2139
|
+
if (!state.selectedSlot || !state.dateTimeConfirmed) return "datetime";
|
|
2140
|
+
return "review";
|
|
2141
|
+
}
|
|
2142
|
+
const booking_current_step_name = nanostores.computed(booking_state, bookingCurrentStepName);
|
|
2143
|
+
const booking_can_proceed = nanostores.computed(booking_state, (state) => {
|
|
2144
|
+
const step = bookingCurrentStepName();
|
|
2145
|
+
if (step === "datetime") {
|
|
2146
|
+
return state.isMultiDay ? !!(state.startDate && state.endDate && state.selectedSlot) : !!(state.selectedDate && state.selectedSlot);
|
|
2147
|
+
}
|
|
2148
|
+
if (step === "review") return true;
|
|
2149
|
+
return false;
|
|
2150
|
+
});
|
|
2151
|
+
const booking_month_year = nanostores.computed(
|
|
2152
|
+
booking_state,
|
|
2153
|
+
(state) => state.currentMonth.toLocaleString(void 0, { month: "long", year: "numeric" })
|
|
2154
|
+
);
|
|
2155
|
+
const booking_chain_start = nanostores.computed(booking_state, (state) => {
|
|
2156
|
+
if (!state.cart.length) return null;
|
|
2157
|
+
return Math.max(...state.cart.map((slot) => slot.to));
|
|
2158
|
+
});
|
|
2159
|
+
const booking_total_steps = nanostores.computed(booking_state, (state) => state.service ? 2 : 0);
|
|
2160
|
+
const booking_steps = nanostores.computed(booking_state, () => ({
|
|
2161
|
+
1: { name: "datetime" },
|
|
2162
|
+
2: { name: "review" }
|
|
2163
|
+
}));
|
|
2164
|
+
const booking_current_step = nanostores.computed([booking_current_step_name, booking_steps], (name, steps) => {
|
|
2165
|
+
for (const [idx, step] of Object.entries(steps)) {
|
|
2166
|
+
if (step.name === name) return Number(idx);
|
|
2167
|
+
}
|
|
2168
|
+
return 1;
|
|
2169
|
+
});
|
|
2170
|
+
function formatBookingDateDisplay(value) {
|
|
2171
|
+
if (!value) return "";
|
|
2172
|
+
return new Date(value).toLocaleDateString(void 0, { month: "short", day: "numeric" });
|
|
2173
|
+
}
|
|
2174
|
+
function serviceProviderId(provider) {
|
|
2175
|
+
return "provider_id" in provider ? provider.provider_id : provider.id;
|
|
2176
|
+
}
|
|
2177
|
+
function getFirstServiceProviderEntry(state) {
|
|
2178
|
+
const serviceWithProviders = state.service;
|
|
2179
|
+
const providers = serviceWithProviders?.providers;
|
|
2180
|
+
if (!providers?.length) return null;
|
|
2181
|
+
if (state.selectedProviderId) {
|
|
2182
|
+
const match = providers.find((provider) => provider.provider_id === state.selectedProviderId);
|
|
2183
|
+
if (match) return match;
|
|
2184
|
+
}
|
|
2185
|
+
return providers[0];
|
|
2186
|
+
}
|
|
2187
|
+
async function loadBookingForm() {
|
|
2188
|
+
try {
|
|
2189
|
+
const form = await loadForm({ key: config.serviceOrderFormKey || "order-form" });
|
|
2190
|
+
const blocks = (form.schema || []).map(formSchemaToBlock);
|
|
2191
|
+
booking_form_node.set({ blocks });
|
|
2192
|
+
return blocks;
|
|
2193
|
+
} catch {
|
|
2194
|
+
booking_form_node.set({ blocks: [] });
|
|
2195
|
+
return [];
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
const booking_actions = {
|
|
2199
|
+
async initialize() {
|
|
2200
|
+
booking_state.setKey("tzGroups", normalizeTimezoneGroups(client.utils.tzGroups));
|
|
2201
|
+
await ensureCart();
|
|
2202
|
+
setBookingCartFromServiceItems(service_items.get());
|
|
2203
|
+
const methods = session.get()?.market?.payment_methods || [];
|
|
2204
|
+
if (methods.length) booking_state.setKey("availablePaymentMethods", methods);
|
|
2205
|
+
await loadBookingForm();
|
|
2206
|
+
},
|
|
2207
|
+
setTimezone(tz) {
|
|
2208
|
+
booking_state.setKey("timezone", tz);
|
|
2209
|
+
booking_state.setKey("calendar", bookingCalendar());
|
|
2210
|
+
const state = booking_state.get();
|
|
2211
|
+
if (state.selectedDate) {
|
|
2212
|
+
booking_state.setKey("slots", computeBookingSlots(state.selectedDate));
|
|
2213
|
+
booking_state.setKey("selectedSlot", null);
|
|
2214
|
+
}
|
|
2215
|
+
},
|
|
2216
|
+
async setService(service) {
|
|
2217
|
+
booking_state.setKey("loading", true);
|
|
2218
|
+
try {
|
|
2219
|
+
const isMultiDayBlock = service.blocks?.find((block) => block.key === "isMultiDay");
|
|
2220
|
+
const blockValue = isMultiDayBlock?.value;
|
|
2221
|
+
const isMultiDay = Array.isArray(blockValue) ? blockValue[0] === true : blockValue === true;
|
|
2222
|
+
const [fullService, serviceProviders] = await Promise.all([
|
|
2223
|
+
client.eshop.service.get({ id: service.id }),
|
|
2224
|
+
client.eshop.service.findProviders({ service_id: service.id })
|
|
2225
|
+
]);
|
|
2226
|
+
const providerIds = [...new Set(serviceProviders.map(serviceProviderId))];
|
|
2227
|
+
const providerResults = await Promise.all(
|
|
2228
|
+
providerIds.map((id) => client.eshop.provider.get({ id }).catch(() => null))
|
|
2229
|
+
);
|
|
2230
|
+
booking_state.set({
|
|
2231
|
+
...booking_state.get(),
|
|
2232
|
+
service: fullService,
|
|
2233
|
+
providers: providerResults.filter((provider) => provider !== null),
|
|
2234
|
+
selectedProviderId: null,
|
|
2235
|
+
availability: null,
|
|
2236
|
+
selectedDate: null,
|
|
2237
|
+
startDate: null,
|
|
2238
|
+
endDate: null,
|
|
2239
|
+
slots: [],
|
|
2240
|
+
selectedSlot: null,
|
|
2241
|
+
currentMonth: new Date((/* @__PURE__ */ new Date()).getFullYear(), (/* @__PURE__ */ new Date()).getMonth(), 1),
|
|
2242
|
+
loading: false,
|
|
2243
|
+
isMultiDay
|
|
2244
|
+
});
|
|
2245
|
+
await booking_actions.loadMonth();
|
|
2246
|
+
} catch (error) {
|
|
2247
|
+
booking_state.setKey("loading", false);
|
|
2248
|
+
throw error;
|
|
2249
|
+
}
|
|
2250
|
+
},
|
|
2251
|
+
async loadMonth() {
|
|
2252
|
+
const state = booking_state.get();
|
|
2253
|
+
if (!state.service) return;
|
|
2254
|
+
booking_state.setKey("loading", true);
|
|
2255
|
+
try {
|
|
2256
|
+
const chainedStart = booking_chain_start.get();
|
|
2257
|
+
let from;
|
|
2258
|
+
let to;
|
|
2259
|
+
if (chainedStart) {
|
|
2260
|
+
from = chainedStart;
|
|
2261
|
+
to = chainedStart;
|
|
2262
|
+
} else {
|
|
2263
|
+
const month = state.currentMonth;
|
|
2264
|
+
from = Math.floor(Date.UTC(month.getFullYear(), month.getMonth(), 1) / 1e3);
|
|
2265
|
+
to = Math.floor(Date.UTC(month.getFullYear(), month.getMonth() + 1, 1) / 1e3);
|
|
2266
|
+
}
|
|
2267
|
+
const availability = await loadAvailability({
|
|
2268
|
+
service_id: state.service.id,
|
|
2269
|
+
from,
|
|
2270
|
+
to
|
|
2271
|
+
});
|
|
2272
|
+
booking_state.setKey("availability", availability);
|
|
2273
|
+
booking_state.setKey("calendar", bookingCalendar());
|
|
2274
|
+
} finally {
|
|
2275
|
+
booking_state.setKey("loading", false);
|
|
2276
|
+
}
|
|
2277
|
+
},
|
|
2278
|
+
prevMonth() {
|
|
2279
|
+
const { currentMonth } = booking_state.get();
|
|
2280
|
+
booking_state.setKey("currentMonth", new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1));
|
|
2281
|
+
void booking_actions.loadMonth();
|
|
2282
|
+
},
|
|
2283
|
+
nextMonth() {
|
|
2284
|
+
const { currentMonth } = booking_state.get();
|
|
2285
|
+
booking_state.setKey("currentMonth", new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1));
|
|
2286
|
+
void booking_actions.loadMonth();
|
|
2287
|
+
},
|
|
2288
|
+
selectProvider(providerId) {
|
|
2289
|
+
booking_state.set({
|
|
2290
|
+
...booking_state.get(),
|
|
2291
|
+
selectedProviderId: providerId,
|
|
2292
|
+
selectedDate: null,
|
|
2293
|
+
startDate: null,
|
|
2294
|
+
endDate: null,
|
|
2295
|
+
slots: [],
|
|
2296
|
+
selectedSlot: null
|
|
2297
|
+
});
|
|
2298
|
+
void booking_actions.loadMonth();
|
|
2299
|
+
},
|
|
2300
|
+
selectDate(cell) {
|
|
2301
|
+
if (cell.blank || !cell.available) return;
|
|
2302
|
+
booking_state.setKey("dateTimeConfirmed", false);
|
|
2303
|
+
const state = booking_state.get();
|
|
2304
|
+
if (state.isMultiDay) {
|
|
2305
|
+
if (!state.startDate) {
|
|
2306
|
+
booking_state.setKey("startDate", cell.iso);
|
|
2307
|
+
booking_state.setKey("selectedDate", cell.iso);
|
|
2308
|
+
booking_state.setKey("endDate", null);
|
|
2309
|
+
booking_state.setKey("selectedSlot", null);
|
|
2310
|
+
} else if (!state.endDate) {
|
|
2311
|
+
if (cell.date.getTime() < new Date(state.startDate).getTime()) {
|
|
2312
|
+
booking_state.setKey("startDate", cell.iso);
|
|
2313
|
+
booking_state.setKey("endDate", state.startDate);
|
|
2314
|
+
} else {
|
|
2315
|
+
booking_state.setKey("endDate", cell.iso);
|
|
2316
|
+
}
|
|
2317
|
+
booking_actions.createMultiDaySlots();
|
|
2318
|
+
} else {
|
|
2319
|
+
booking_state.setKey("startDate", cell.iso);
|
|
2320
|
+
booking_state.setKey("selectedDate", cell.iso);
|
|
2321
|
+
booking_state.setKey("endDate", null);
|
|
2322
|
+
booking_state.setKey("selectedSlot", null);
|
|
2323
|
+
}
|
|
2324
|
+
booking_actions.updateCalendar();
|
|
2325
|
+
} else {
|
|
2326
|
+
booking_state.set({
|
|
2327
|
+
...state,
|
|
2328
|
+
selectedDate: cell.iso,
|
|
2329
|
+
slots: computeBookingSlots(cell.iso),
|
|
2330
|
+
selectedSlot: null
|
|
2331
|
+
});
|
|
2332
|
+
booking_state.setKey("calendar", bookingCalendar());
|
|
2333
|
+
}
|
|
2334
|
+
},
|
|
2335
|
+
createMultiDaySlots() {
|
|
2336
|
+
const state = booking_state.get();
|
|
2337
|
+
if (!state.startDate || !state.endDate || !state.availability) return;
|
|
2338
|
+
const slots = [];
|
|
2339
|
+
for (let day = new Date(state.startDate); day <= new Date(state.endDate); day.setDate(day.getDate() + 1)) {
|
|
2340
|
+
const iso = day.toISOString().slice(0, 10);
|
|
2341
|
+
for (const slot of getSlotsForDate(state.availability, iso, state.selectedProviderId)) {
|
|
2342
|
+
slots.push({
|
|
2343
|
+
id: `${state.service?.id || "service"}-${slot.from}-${slots.length}`,
|
|
2344
|
+
serviceId: state.service?.id || "",
|
|
2345
|
+
providerId: slot.providerId,
|
|
2346
|
+
from: slot.from,
|
|
2347
|
+
to: slot.to,
|
|
2348
|
+
timeText: formatBookingSlotTime(slot.from, slot.to, state.timezone),
|
|
2349
|
+
dateText: new Date(slot.from * 1e3).toLocaleDateString([], {
|
|
2350
|
+
weekday: "short",
|
|
2351
|
+
month: "short",
|
|
2352
|
+
day: "numeric",
|
|
2353
|
+
timeZone: state.timezone
|
|
2354
|
+
}),
|
|
2355
|
+
isMultiDay: true
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
booking_state.setKey("slots", slots);
|
|
2360
|
+
booking_state.setKey("selectedSlot", slots.length === 1 ? slots[0] : null);
|
|
2361
|
+
},
|
|
2362
|
+
selectTimeSlot(slot) {
|
|
2363
|
+
booking_state.setKey("dateTimeConfirmed", false);
|
|
2364
|
+
booking_state.setKey("selectedSlot", slot);
|
|
2365
|
+
},
|
|
2366
|
+
resetDateSelection() {
|
|
2367
|
+
booking_state.setKey("selectedDate", null);
|
|
2368
|
+
booking_state.setKey("startDate", null);
|
|
2369
|
+
booking_state.setKey("endDate", null);
|
|
2370
|
+
booking_state.setKey("slots", []);
|
|
2371
|
+
booking_state.setKey("selectedSlot", null);
|
|
2372
|
+
booking_state.setKey("dateTimeConfirmed", false);
|
|
2373
|
+
},
|
|
2374
|
+
updateCalendar() {
|
|
2375
|
+
booking_state.setKey("calendar", bookingCalendar());
|
|
2376
|
+
},
|
|
2377
|
+
findFirstAvailable() {
|
|
2378
|
+
for (const day of booking_state.get().calendar) {
|
|
2379
|
+
if (!day.blank && day.available) {
|
|
2380
|
+
booking_actions.selectDate(day);
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
},
|
|
2385
|
+
async addToCart() {
|
|
2386
|
+
const state = booking_state.get();
|
|
2387
|
+
const serviceBlocks = state.service?.forms || [];
|
|
2388
|
+
const enrich = (slot) => ({
|
|
2389
|
+
...slot,
|
|
2390
|
+
serviceName: state.service ? serviceName(state.service, currentLocale()) : "",
|
|
2391
|
+
date: slot.dateText,
|
|
2392
|
+
serviceBlocks
|
|
2393
|
+
});
|
|
2394
|
+
const selected = state.isMultiDay && state.slots.length > 0 ? state.slots.map(enrich) : state.selectedSlot ? [enrich(state.selectedSlot)] : [];
|
|
2395
|
+
if (!selected.length) return;
|
|
2396
|
+
const nextCart = [...state.cart, ...selected];
|
|
2397
|
+
booking_state.set({
|
|
2398
|
+
...state,
|
|
2399
|
+
cart: nextCart,
|
|
2400
|
+
selectedDate: null,
|
|
2401
|
+
startDate: null,
|
|
2402
|
+
endDate: null,
|
|
2403
|
+
slots: [],
|
|
2404
|
+
selectedSlot: null
|
|
2405
|
+
});
|
|
2406
|
+
await syncBookingCart(nextCart);
|
|
2407
|
+
booking_state.setKey("calendar", bookingCalendar());
|
|
2408
|
+
},
|
|
2409
|
+
async removeFromCart(slotId) {
|
|
2410
|
+
const nextCart = booking_state.get().cart.filter((slot) => slot.id !== slotId);
|
|
2411
|
+
booking_state.setKey("cart", nextCart);
|
|
2412
|
+
await syncBookingCart(nextCart);
|
|
2413
|
+
},
|
|
2414
|
+
async clearCart() {
|
|
2415
|
+
booking_state.setKey("cart", []);
|
|
2416
|
+
await syncBookingCart([]);
|
|
2417
|
+
},
|
|
2418
|
+
async checkout(paymentMethodId, forms = []) {
|
|
2419
|
+
const state = booking_state.get();
|
|
2420
|
+
if (!state.cart.length) return { success: false, error: "Cart is empty" };
|
|
2421
|
+
booking_state.setKey("loading", true);
|
|
2422
|
+
try {
|
|
2423
|
+
const result = await checkout({
|
|
2424
|
+
service_items: state.cart.map((slot) => ({
|
|
2425
|
+
...toServiceCartItem(slot),
|
|
2426
|
+
forms: []
|
|
2427
|
+
})),
|
|
2428
|
+
payment_method_id: paymentMethodId,
|
|
2429
|
+
promo_code: state.promoCode || void 0,
|
|
2430
|
+
forms
|
|
2431
|
+
});
|
|
2432
|
+
booking_state.setKey("cartId", cart.get()?.id || null);
|
|
2433
|
+
return { success: true, data: result };
|
|
2434
|
+
} catch (error) {
|
|
2435
|
+
return { success: false, error: readErrorMessage(error, "Checkout failed.") };
|
|
2436
|
+
} finally {
|
|
2437
|
+
booking_state.setKey("loading", false);
|
|
2438
|
+
}
|
|
2439
|
+
},
|
|
2440
|
+
async fetchQuote(paymentMethodId, promoCode) {
|
|
2441
|
+
const state = booking_state.get();
|
|
2442
|
+
if (!state.cart.length) return null;
|
|
2443
|
+
booking_state.setKey("fetchingQuote", true);
|
|
2444
|
+
booking_state.setKey("quoteError", null);
|
|
2445
|
+
try {
|
|
2446
|
+
booking_state.setKey("promoCode", promoCode || null);
|
|
2447
|
+
const response = await fetchQuote({
|
|
2448
|
+
service_items: state.cart.map(toServiceCartItem),
|
|
2449
|
+
payment_method_id: paymentMethodId,
|
|
2450
|
+
promo_code: promoCode || void 0
|
|
2451
|
+
});
|
|
2452
|
+
booking_state.setKey("cartId", cart.get()?.id || null);
|
|
2453
|
+
booking_state.setKey("quote", response);
|
|
2454
|
+
const methods = response?.payment_methods || session.get()?.market?.payment_methods || [];
|
|
2455
|
+
if (methods.length) booking_state.setKey("availablePaymentMethods", methods);
|
|
2456
|
+
return response;
|
|
2457
|
+
} catch (error) {
|
|
2458
|
+
booking_state.setKey("quoteError", readErrorMessage(error, "Failed to fetch quote."));
|
|
2459
|
+
return null;
|
|
2460
|
+
} finally {
|
|
2461
|
+
booking_state.setKey("fetchingQuote", false);
|
|
2462
|
+
}
|
|
2463
|
+
},
|
|
2464
|
+
getProvidersList() {
|
|
2465
|
+
return booking_state.get().providers;
|
|
2466
|
+
},
|
|
2467
|
+
prevStep() {
|
|
2468
|
+
const current = bookingCurrentStepName();
|
|
2469
|
+
if (current === "review") {
|
|
2470
|
+
booking_state.setKey("dateTimeConfirmed", false);
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
if (current === "datetime") {
|
|
2474
|
+
booking_state.setKey("selectedSlot", null);
|
|
2475
|
+
booking_state.setKey("dateTimeConfirmed", false);
|
|
2476
|
+
}
|
|
2477
|
+
},
|
|
2478
|
+
nextStep() {
|
|
2479
|
+
if (bookingCurrentStepName() === "datetime" && booking_can_proceed.get()) {
|
|
2480
|
+
booking_state.setKey("dateTimeConfirmed", true);
|
|
2481
|
+
}
|
|
2482
|
+
},
|
|
2483
|
+
getServicePrice() {
|
|
2484
|
+
const state = booking_state.get();
|
|
2485
|
+
if (state.quote?.total !== void 0) return String(state.quote.total);
|
|
2486
|
+
const provider = getFirstServiceProviderEntry(state);
|
|
2487
|
+
if (!provider?.prices) return "";
|
|
2488
|
+
return client.utils.formatPrice(provider.prices) || "0";
|
|
2489
|
+
},
|
|
2490
|
+
formatDateDisplay: formatBookingDateDisplay,
|
|
2491
|
+
serviceItemsFromSlots(slots) {
|
|
2492
|
+
return slots.map(toServiceCartItem);
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
service_items.subscribe((items) => setBookingCartFromServiceItems(items));
|
|
1926
2496
|
async function loadNode(params, options) {
|
|
1927
2497
|
cms_state.setKey("loading", true);
|
|
1928
2498
|
cms_state.setKey("error", null);
|
|
@@ -1939,7 +2509,7 @@ function createArkyStore(config) {
|
|
|
1939
2509
|
}
|
|
1940
2510
|
}
|
|
1941
2511
|
async function loadWebsiteNode(options) {
|
|
1942
|
-
const node = await loadNode({ key:
|
|
2512
|
+
const node = await loadNode({ key: "website" }, options);
|
|
1943
2513
|
cms_state.setKey("website_node", node);
|
|
1944
2514
|
return node;
|
|
1945
2515
|
}
|
|
@@ -2126,6 +2696,18 @@ function createArkyStore(config) {
|
|
|
2126
2696
|
buildServiceItems: toServiceCheckoutItems
|
|
2127
2697
|
}
|
|
2128
2698
|
},
|
|
2699
|
+
booking: {
|
|
2700
|
+
state: booking_state,
|
|
2701
|
+
form_blocks: booking_form_blocks,
|
|
2702
|
+
current_step_name: booking_current_step_name,
|
|
2703
|
+
can_proceed: booking_can_proceed,
|
|
2704
|
+
month_year: booking_month_year,
|
|
2705
|
+
chain_start: booking_chain_start,
|
|
2706
|
+
total_steps: booking_total_steps,
|
|
2707
|
+
steps: booking_steps,
|
|
2708
|
+
current_step: booking_current_step,
|
|
2709
|
+
actions: booking_actions
|
|
2710
|
+
},
|
|
2129
2711
|
crm: client.crm,
|
|
2130
2712
|
activity: {
|
|
2131
2713
|
track(params) {
|