@wix/headless-bookings 1.0.9 → 1.0.10
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/cjs/dist/__mocks__/services/services-mocks.d.ts +17 -1
- package/cjs/dist/__mocks__/services/services-mocks.js +35 -1
- package/cjs/dist/api/index.d.ts +1 -1
- package/cjs/dist/api/index.js +2 -1
- package/cjs/dist/api/query-services/index.d.ts +60 -15
- package/cjs/dist/api/query-services/index.js +86 -10
- package/cjs/dist/react/core/service-list/ServiceList.d.ts +2 -3
- package/cjs/dist/react/service-list/ServiceList.d.ts +3 -3
- package/cjs/dist/react/service-list/ServiceList.js +1 -1
- package/cjs/dist/react/service-list/ServiceList.stories.d.ts +29 -0
- package/cjs/dist/react/service-list/ServiceList.stories.js +125 -0
- package/cjs/dist/services/constants.d.ts +2 -0
- package/cjs/dist/services/constants.js +3 -1
- package/cjs/dist/services/index.d.ts +2 -2
- package/cjs/dist/services/index.js +2 -1
- package/cjs/dist/services/service-list/service-list.d.ts +45 -36
- package/cjs/dist/services/service-list/service-list.js +90 -112
- package/dist/__mocks__/services/services-mocks.d.ts +17 -1
- package/dist/__mocks__/services/services-mocks.js +34 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/query-services/index.d.ts +60 -15
- package/dist/api/query-services/index.js +89 -10
- package/dist/react/core/service-list/ServiceList.d.ts +2 -3
- package/dist/react/service-list/ServiceList.d.ts +3 -3
- package/dist/react/service-list/ServiceList.js +1 -1
- package/dist/react/service-list/ServiceList.stories.d.ts +29 -0
- package/dist/react/service-list/ServiceList.stories.js +89 -0
- package/dist/services/constants.d.ts +2 -0
- package/dist/services/constants.js +2 -0
- package/dist/services/index.d.ts +2 -2
- package/dist/services/index.js +1 -1
- package/dist/services/service-list/service-list.d.ts +45 -36
- package/dist/services/service-list/service-list.js +90 -112
- package/package.json +17 -2
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Main export file for all service definitions and implementations
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.staffMemberListServiceBinding = exports.loadStaffMemberListServiceInitialData = exports.StaffMemberListServiceDefinition = exports.StaffMemberListService = exports.getLocationById = exports.locationListServiceBinding = exports.loadLocationListServiceInitialData = exports.LocationType = exports.LocationListServiceDefinition = exports.LocationListService = exports.loadPaymentConfig = exports.PaymentServiceDefinition = exports.PaymentService = exports.BookingFormServiceDefinition = exports.BookingFormService = exports.getDayRangeBounds = exports.isDayRangeService = exports.createBookingsForCart = exports.buildDayRangeBookingRequest = exports.buildCartRequest = exports.buildBookingRequest = exports.canBook = exports.executeBookAction = exports.SYNTHETIC_CUSTOMER_ID = exports.SYNTHETIC_CUSTOM_ID = exports.loadServiceListServiceInitialData = exports.ServiceListServiceDefinition = exports.ServiceListService = exports.loadTimeSlotListConfig = exports.ViewMode = exports.TimeSlotServiceDefinition = exports.TimeSlotService = exports.TimeSlotListServiceDefinition = exports.TimeSlotListService = exports.BookingServiceDefinition = exports.BookingService = exports.loadServiceServiceInitialData = exports.ServiceServiceDefinition = exports.ServiceService = void 0;
|
|
7
|
+
exports.staffMemberListServiceBinding = exports.loadStaffMemberListServiceInitialData = exports.StaffMemberListServiceDefinition = exports.StaffMemberListService = exports.getLocationById = exports.locationListServiceBinding = exports.loadLocationListServiceInitialData = exports.LocationType = exports.LocationListServiceDefinition = exports.LocationListService = exports.loadPaymentConfig = exports.PaymentServiceDefinition = exports.PaymentService = exports.BookingFormServiceDefinition = exports.BookingFormService = exports.getDayRangeBounds = exports.isDayRangeService = exports.createBookingsForCart = exports.buildDayRangeBookingRequest = exports.buildCartRequest = exports.buildBookingRequest = exports.canBook = exports.executeBookAction = exports.AvailabilityLocationType = exports.SYNTHETIC_CUSTOMER_ID = exports.SYNTHETIC_CUSTOM_ID = exports.loadServiceListServiceInitialData = exports.ServiceListServiceDefinition = exports.ServiceListService = exports.loadTimeSlotListConfig = exports.ViewMode = exports.TimeSlotServiceDefinition = exports.TimeSlotService = exports.TimeSlotListServiceDefinition = exports.TimeSlotListService = exports.BookingServiceDefinition = exports.BookingService = exports.loadServiceServiceInitialData = exports.ServiceServiceDefinition = exports.ServiceService = void 0;
|
|
8
8
|
var service_js_1 = require("./service/service.js");
|
|
9
9
|
Object.defineProperty(exports, "ServiceService", { enumerable: true, get: function () { return service_js_1.ServiceService; } });
|
|
10
10
|
Object.defineProperty(exports, "ServiceServiceDefinition", { enumerable: true, get: function () { return service_js_1.ServiceServiceDefinition; } });
|
|
@@ -26,6 +26,7 @@ Object.defineProperty(exports, "loadServiceListServiceInitialData", { enumerable
|
|
|
26
26
|
var index_js_2 = require("../api/query-services/index.js");
|
|
27
27
|
Object.defineProperty(exports, "SYNTHETIC_CUSTOM_ID", { enumerable: true, get: function () { return index_js_2.SYNTHETIC_CUSTOM_ID; } });
|
|
28
28
|
Object.defineProperty(exports, "SYNTHETIC_CUSTOMER_ID", { enumerable: true, get: function () { return index_js_2.SYNTHETIC_CUSTOMER_ID; } });
|
|
29
|
+
Object.defineProperty(exports, "AvailabilityLocationType", { enumerable: true, get: function () { return index_js_2.AvailabilityLocationType; } });
|
|
29
30
|
var index_js_3 = require("./booking/book-action/index.js");
|
|
30
31
|
Object.defineProperty(exports, "executeBookAction", { enumerable: true, get: function () { return index_js_3.executeBookAction; } });
|
|
31
32
|
Object.defineProperty(exports, "canBook", { enumerable: true, get: function () { return index_js_3.canBook; } });
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ServiceList Service - Reactive service list data management
|
|
3
|
-
* Handles querying and pagination of services list (NO selection management)
|
|
3
|
+
* Handles querying and pagination of services list (NO selection management).
|
|
4
|
+
*
|
|
5
|
+
* Availability is just part of the query: when the query options carry an
|
|
6
|
+
* `availabilityFilter`, the API layer queries services by real bookable
|
|
7
|
+
* availability (and each service gets an `available` flag); otherwise it runs the
|
|
8
|
+
* plain offset query. The service does not know or care which endpoint is used —
|
|
9
|
+
* that decision lives in {@link queryServices}.
|
|
4
10
|
*/
|
|
5
11
|
import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
|
|
6
|
-
import type {
|
|
7
|
-
import type { QueryServicesRequest, PagingMetadata } from '../../api/query-services/index.js';
|
|
12
|
+
import type { Sorting } from '@wix/auto_sdk_bookings_services';
|
|
13
|
+
import type { QueryServicesRequest, PagingMetadata, ServiceWithAvailabilityItem, AvailabilityFilter } from '../../api/query-services/index.js';
|
|
8
14
|
import { type Category } from '../../api/query-categories/index.js';
|
|
9
15
|
import type { FilterOption, FilterValue as Filter } from '@wix/headless-components/react';
|
|
10
16
|
/**
|
|
11
|
-
* Query options without paging metadata (user-controlled request params)
|
|
12
|
-
*
|
|
17
|
+
* Query options without paging metadata (user-controlled request params).
|
|
18
|
+
* Includes `filter` (category/location) and `availabilityFilter`; the effect
|
|
19
|
+
* subscribes to changes in this signal to trigger re-fetches.
|
|
13
20
|
*/
|
|
14
21
|
export type QueryOptions = Omit<QueryServicesRequest, 'pagingMetadata'>;
|
|
15
22
|
/**
|
|
16
23
|
* Configuration for the ServiceList service
|
|
17
24
|
*/
|
|
18
25
|
export interface ServiceListServiceConfig {
|
|
19
|
-
services?:
|
|
26
|
+
services?: ServiceWithAvailabilityItem[];
|
|
20
27
|
categories?: Category[];
|
|
21
28
|
options?: QueryServicesRequest;
|
|
22
29
|
}
|
|
@@ -35,10 +42,10 @@ export interface ServiceListActions {
|
|
|
35
42
|
*/
|
|
36
43
|
export interface ServiceListServiceAPI {
|
|
37
44
|
/** Reactive signal containing the list of services */
|
|
38
|
-
servicesSignal: Signal<
|
|
39
|
-
/** Reactive signal containing current query options (appId, filter, sort) - changes trigger re-fetch */
|
|
45
|
+
servicesSignal: Signal<ServiceWithAvailabilityItem[]>;
|
|
46
|
+
/** Reactive signal containing current query options (appId, filter, availabilityFilter, sort) - changes trigger re-fetch */
|
|
40
47
|
queryOptionsSignal: Signal<QueryOptions>;
|
|
41
|
-
/** Reactive signal containing pagination metadata (limit, offset, count, hasNext) - updated after fetches */
|
|
48
|
+
/** Reactive signal containing pagination metadata (limit, offset/cursor, count, hasNext) - updated after fetches */
|
|
42
49
|
pagingMetadataSignal: Signal<PagingMetadata>;
|
|
43
50
|
/** Reactive signal indicating if services are currently being loaded */
|
|
44
51
|
isLoadingSignal: Signal<boolean>;
|
|
@@ -50,6 +57,8 @@ export interface ServiceListServiceAPI {
|
|
|
50
57
|
filterOptionsSignal: ReadOnlySignal<FilterOption[]>;
|
|
51
58
|
/** Computed signal indicating if any filters are active (from queryOptionsSignal.filter) */
|
|
52
59
|
isFilteredSignal: ReadOnlySignal<boolean>;
|
|
60
|
+
/** Computed signal indicating whether an availability filter is currently applied */
|
|
61
|
+
hasAvailabilityFilterSignal: ReadOnlySignal<boolean>;
|
|
53
62
|
/** Computed signal containing effective filter (queryOptions.filter + location from BookingService) */
|
|
54
63
|
effectiveFilterSignal: ReadOnlySignal<Filter | undefined>;
|
|
55
64
|
/** Actions object containing query actions */
|
|
@@ -91,7 +100,6 @@ export declare const ServiceListServiceDefinition: string & {
|
|
|
91
100
|
* const services = serviceListService.servicesSignal.get();
|
|
92
101
|
* const isLoading = serviceListService.isLoadingSignal.get();
|
|
93
102
|
* const error = serviceListService.errorSignal.get();
|
|
94
|
-
* const pagingMetadata = serviceListService.pagingMetadataSignal.get();
|
|
95
103
|
* const hasMore = serviceListService.hasMoreServicesSignal.get();
|
|
96
104
|
*
|
|
97
105
|
* if (isLoading) return <div>Loading services...</div>;
|
|
@@ -110,9 +118,6 @@ export declare const ServiceListServiceDefinition: string & {
|
|
|
110
118
|
* Load More
|
|
111
119
|
* </button>
|
|
112
120
|
* )}
|
|
113
|
-
* {pagingMetadata.hasNext && (
|
|
114
|
-
* <span>More services available</span>
|
|
115
|
-
* )}
|
|
116
121
|
* </div>
|
|
117
122
|
* );
|
|
118
123
|
* }
|
|
@@ -135,36 +140,40 @@ export type ErrorServiceListServiceConfigResult = {
|
|
|
135
140
|
error: Error;
|
|
136
141
|
};
|
|
137
142
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
* Options for {@link loadServiceListServiceInitialData}.
|
|
144
|
+
* Pass an `availabilityFilter` to load availability-aware services.
|
|
145
|
+
*/
|
|
146
|
+
export interface LoadServiceListServiceOptions {
|
|
147
|
+
appId?: string;
|
|
148
|
+
filter?: Filter;
|
|
149
|
+
pagingMetadata?: PagingMetadata;
|
|
150
|
+
sort?: Sorting[];
|
|
151
|
+
availabilityFilter?: AvailabilityFilter;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Loads service list configuration for SSR, pre-fetching services and categories.
|
|
155
|
+
* Pass an `availabilityFilter` to query availability-aware services.
|
|
142
156
|
*
|
|
143
|
-
* @param options - Query options
|
|
144
|
-
* @returns A
|
|
157
|
+
* @param options - Query options.
|
|
158
|
+
* @returns A success result with the config, or an error result.
|
|
145
159
|
*
|
|
146
160
|
* @example
|
|
147
161
|
* ```ts
|
|
148
|
-
* //
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* // Load with query options
|
|
152
|
-
* const serviceListResult = await loadServiceListServiceInitialData({
|
|
162
|
+
* // Plain offset query
|
|
163
|
+
* const result = await loadServiceListServiceInitialData({
|
|
153
164
|
* appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
|
|
154
165
|
* pagingMetadata: { limit: 20, offset: 0 },
|
|
155
166
|
* });
|
|
156
167
|
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* // More services can be loaded
|
|
167
|
-
* }
|
|
168
|
+
* // Availability-aware query (cursor paging + `available` flag)
|
|
169
|
+
* const availabilityResult = await loadServiceListServiceInitialData({
|
|
170
|
+
* availabilityFilter: {
|
|
171
|
+
* localStartDate: '2026-06-01T09:00:00',
|
|
172
|
+
* localEndDate: '2026-06-07T17:00:00',
|
|
173
|
+
* timeZone: 'America/New_York',
|
|
174
|
+
* },
|
|
175
|
+
* pagingMetadata: { limit: 20 },
|
|
176
|
+
* });
|
|
168
177
|
* ```
|
|
169
178
|
*/
|
|
170
|
-
export declare function loadServiceListServiceInitialData(options:
|
|
179
|
+
export declare function loadServiceListServiceInitialData(options: LoadServiceListServiceOptions): Promise<SuccessServiceListServiceConfigResult | ErrorServiceListServiceConfigResult>;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* ServiceList Service - Reactive service list data management
|
|
4
|
-
* Handles querying and pagination of services list (NO selection management)
|
|
4
|
+
* Handles querying and pagination of services list (NO selection management).
|
|
5
|
+
*
|
|
6
|
+
* Availability is just part of the query: when the query options carry an
|
|
7
|
+
* `availabilityFilter`, the API layer queries services by real bookable
|
|
8
|
+
* availability (and each service gets an `available` flag); otherwise it runs the
|
|
9
|
+
* plain offset query. The service does not know or care which endpoint is used —
|
|
10
|
+
* that decision lives in {@link queryServices}.
|
|
5
11
|
*/
|
|
6
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
13
|
exports.ServiceListService = exports.ServiceListServiceDefinition = void 0;
|
|
@@ -12,10 +18,6 @@ const index_js_1 = require("../../api/query-services/index.js");
|
|
|
12
18
|
const index_js_2 = require("../../api/query-categories/index.js");
|
|
13
19
|
const booking_js_1 = require("../booking/booking.js");
|
|
14
20
|
const constants_js_1 = require("../constants.js");
|
|
15
|
-
/**
|
|
16
|
-
* Default query limit
|
|
17
|
-
*/
|
|
18
|
-
const DEFAULT_QUERY_LIMIT = 100;
|
|
19
21
|
/**
|
|
20
22
|
* Service definition for the ServiceList service
|
|
21
23
|
*/
|
|
@@ -46,7 +48,6 @@ exports.ServiceListServiceDefinition = (0, services_definitions_1.defineService)
|
|
|
46
48
|
* const services = serviceListService.servicesSignal.get();
|
|
47
49
|
* const isLoading = serviceListService.isLoadingSignal.get();
|
|
48
50
|
* const error = serviceListService.errorSignal.get();
|
|
49
|
-
* const pagingMetadata = serviceListService.pagingMetadataSignal.get();
|
|
50
51
|
* const hasMore = serviceListService.hasMoreServicesSignal.get();
|
|
51
52
|
*
|
|
52
53
|
* if (isLoading) return <div>Loading services...</div>;
|
|
@@ -65,9 +66,6 @@ exports.ServiceListServiceDefinition = (0, services_definitions_1.defineService)
|
|
|
65
66
|
* Load More
|
|
66
67
|
* </button>
|
|
67
68
|
* )}
|
|
68
|
-
* {pagingMetadata.hasNext && (
|
|
69
|
-
* <span>More services available</span>
|
|
70
|
-
* )}
|
|
71
69
|
* </div>
|
|
72
70
|
* );
|
|
73
71
|
* }
|
|
@@ -85,20 +83,22 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
|
|
|
85
83
|
// Initialize services signal - use empty array if not provided
|
|
86
84
|
const initialServices = config.services || [];
|
|
87
85
|
const servicesSignal = signalsService.signal(initialServices);
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
86
|
+
// Build query options from config. The filter/availabilityFilter/sort must
|
|
87
|
+
// be preserved even when services are prefetched (e.g. SSR), so loadMore
|
|
88
|
+
// keeps querying the same way and stays in availability mode. A pure
|
|
89
|
+
// showcase config (services without options) naturally yields just
|
|
90
|
+
// { appId } since these fields are absent.
|
|
91
|
+
const initialQueryOptions = {
|
|
92
|
+
appId,
|
|
93
|
+
filter: config.options?.filter,
|
|
94
|
+
availabilityFilter: config.options?.availabilityFilter,
|
|
95
|
+
sort: config.options?.sort,
|
|
96
|
+
};
|
|
97
|
+
// Initialize paging metadata separately (response data - doesn't trigger effect).
|
|
98
|
+
// Carries either offset (plain mode) or cursor (availability mode).
|
|
99
99
|
const initialPagingMetadata = config.options
|
|
100
100
|
?.pagingMetadata || {
|
|
101
|
-
limit: DEFAULT_QUERY_LIMIT,
|
|
101
|
+
limit: constants_js_1.DEFAULT_QUERY_LIMIT,
|
|
102
102
|
offset: 0,
|
|
103
103
|
count: initialServices.length,
|
|
104
104
|
hasNext: false,
|
|
@@ -159,6 +159,10 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
|
|
|
159
159
|
return true;
|
|
160
160
|
});
|
|
161
161
|
});
|
|
162
|
+
// Computed signal indicating whether an availability filter is currently
|
|
163
|
+
// applied. Reuses the same predicate as the API dispatch so the two can't
|
|
164
|
+
// drift. Exposed for the availability filter UI (follow-up PR).
|
|
165
|
+
const hasAvailabilityFilterSignal = signalsService.computed(() => (0, index_js_1.hasActiveAvailabilityFilter)(queryOptionsSignal.get().availabilityFilter));
|
|
162
166
|
// Computed signal for effective filter - combines queryOptions.filter with location from BookingService
|
|
163
167
|
const effectiveFilterSignal = signalsService.computed(() => {
|
|
164
168
|
const filter = queryOptionsSignal.get().filter;
|
|
@@ -175,23 +179,21 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
|
|
|
175
179
|
: {}),
|
|
176
180
|
};
|
|
177
181
|
});
|
|
178
|
-
// Initial fetch - runs once on client when no prefetched data
|
|
179
|
-
// Fetches both services AND categories in parallel
|
|
182
|
+
// Initial fetch - runs once on client when no prefetched data.
|
|
183
|
+
// Fetches both services AND categories in parallel.
|
|
180
184
|
if (typeof window !== 'undefined' && !hasInitialServices) {
|
|
181
|
-
const
|
|
182
|
-
const currentPaging = pagingMetadataSignal.peek();
|
|
183
|
-
// Use effectiveFilterSignal.peek() to get the computed filter value
|
|
184
|
-
const effectiveFilter = effectiveFilterSignal.peek();
|
|
185
|
+
const options = queryOptionsSignal.peek();
|
|
185
186
|
isLoadingSignal.set(true);
|
|
186
187
|
Promise.allSettled([
|
|
187
188
|
(0, index_js_1.queryServices)({
|
|
188
|
-
appId:
|
|
189
|
-
filter:
|
|
189
|
+
appId: options.appId,
|
|
190
|
+
filter: effectiveFilterSignal.peek(),
|
|
191
|
+
availabilityFilter: options.availabilityFilter,
|
|
190
192
|
pagingMetadata: {
|
|
191
|
-
limit:
|
|
193
|
+
limit: pagingMetadataSignal.peek().limit,
|
|
192
194
|
offset: 0,
|
|
193
195
|
},
|
|
194
|
-
sort:
|
|
196
|
+
sort: options.sort,
|
|
195
197
|
}),
|
|
196
198
|
!hasInitialCategories ? (0, index_js_2.queryCategories)() : Promise.resolve(null),
|
|
197
199
|
])
|
|
@@ -215,37 +217,34 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
|
|
|
215
217
|
isLoadingSignal.set(false);
|
|
216
218
|
});
|
|
217
219
|
}
|
|
218
|
-
//
|
|
219
|
-
// IMPORTANT: Subscribes to effectiveFilterSignal which includes queryOptions.filter + location
|
|
220
|
+
// Re-fetch when query options or the effective filter (filter + location) change.
|
|
220
221
|
if (typeof window !== 'undefined') {
|
|
221
222
|
let isFirstEffectRun = true;
|
|
222
223
|
signalsService.effect(async () => {
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
const queryOptions = queryOptionsSignal.get();
|
|
224
|
+
// Read both signals to establish dependencies.
|
|
225
|
+
const options = queryOptionsSignal.get();
|
|
226
226
|
const effectiveFilter = effectiveFilterSignal.get();
|
|
227
|
-
// Skip first run - initial fetch is handled separately above
|
|
227
|
+
// Skip first run - initial fetch is handled separately above.
|
|
228
228
|
if (isFirstEffectRun) {
|
|
229
229
|
isFirstEffectRun = false;
|
|
230
230
|
return;
|
|
231
231
|
}
|
|
232
|
-
// Read paging metadata outside of dependency tracking for the fetch
|
|
233
|
-
const currentPaging = pagingMetadataSignal.peek();
|
|
234
232
|
try {
|
|
235
233
|
isLoadingSignal.set(true);
|
|
236
234
|
servicesSignal.set([]);
|
|
237
235
|
errorSignal.set(null);
|
|
236
|
+
// Reset to the first page when query options change.
|
|
238
237
|
const result = await (0, index_js_1.queryServices)({
|
|
239
|
-
appId:
|
|
238
|
+
appId: options.appId,
|
|
240
239
|
filter: effectiveFilter,
|
|
240
|
+
availabilityFilter: options.availabilityFilter,
|
|
241
241
|
pagingMetadata: {
|
|
242
|
-
limit:
|
|
243
|
-
offset: 0,
|
|
242
|
+
limit: pagingMetadataSignal.peek().limit,
|
|
243
|
+
offset: 0,
|
|
244
244
|
},
|
|
245
|
-
sort:
|
|
245
|
+
sort: options.sort,
|
|
246
246
|
});
|
|
247
247
|
servicesSignal.set(result.services);
|
|
248
|
-
// Update ONLY pagingMetadataSignal (not queryOptionsSignal) to avoid re-triggering effect
|
|
249
248
|
pagingMetadataSignal.set(result.pagingMetadata);
|
|
250
249
|
}
|
|
251
250
|
catch (error) {
|
|
@@ -256,29 +255,25 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
|
|
|
256
255
|
}
|
|
257
256
|
});
|
|
258
257
|
}
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
const currentServicesCount = servicesSignal.get().length;
|
|
263
|
-
const nextOffset = currentServicesCount;
|
|
264
|
-
const nextPaging = {
|
|
265
|
-
limit: count || DEFAULT_QUERY_LIMIT,
|
|
266
|
-
offset: nextOffset,
|
|
267
|
-
};
|
|
268
|
-
// Get effective filter from computed signal (includes location from BookingService)
|
|
269
|
-
const effectiveFilter = effectiveFilterSignal.get();
|
|
258
|
+
const loadMore = async (count) => {
|
|
259
|
+
const options = queryOptionsSignal.peek();
|
|
260
|
+
const currentPaging = pagingMetadataSignal.peek();
|
|
270
261
|
try {
|
|
271
262
|
isLoadingSignal.set(true);
|
|
272
263
|
errorSignal.set(null);
|
|
273
264
|
const result = await (0, index_js_1.queryServices)({
|
|
274
|
-
appId:
|
|
275
|
-
filter:
|
|
276
|
-
|
|
277
|
-
|
|
265
|
+
appId: options.appId,
|
|
266
|
+
filter: effectiveFilterSignal.peek(),
|
|
267
|
+
availabilityFilter: options.availabilityFilter,
|
|
268
|
+
pagingMetadata: {
|
|
269
|
+
limit: count ?? currentPaging.limit ?? constants_js_1.DEFAULT_QUERY_LIMIT,
|
|
270
|
+
// Offset (plain mode) and cursor (availability mode) — queryServices uses what it needs.
|
|
271
|
+
offset: servicesSignal.peek().length,
|
|
272
|
+
cursor: currentPaging.cursor,
|
|
273
|
+
},
|
|
274
|
+
sort: options.sort,
|
|
278
275
|
});
|
|
279
|
-
// Append new services to existing ones
|
|
280
276
|
servicesSignal.set([...servicesSignal.get(), ...result.services]);
|
|
281
|
-
// Update ONLY pagingMetadataSignal (not queryOptionsSignal)
|
|
282
277
|
pagingMetadataSignal.set(result.pagingMetadata);
|
|
283
278
|
}
|
|
284
279
|
catch (error) {
|
|
@@ -297,101 +292,81 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
|
|
|
297
292
|
categoriesSignal,
|
|
298
293
|
filterOptionsSignal,
|
|
299
294
|
isFilteredSignal,
|
|
295
|
+
hasAvailabilityFilterSignal,
|
|
300
296
|
effectiveFilterSignal,
|
|
301
297
|
actions: {
|
|
302
|
-
loadMore
|
|
303
|
-
await loadMoreCursor(count);
|
|
304
|
-
},
|
|
298
|
+
loadMore,
|
|
305
299
|
setQueryOptions: (options) => {
|
|
306
|
-
// Reset services and paging when query options change
|
|
300
|
+
// Reset services and paging when query options change.
|
|
307
301
|
servicesSignal.set([]);
|
|
308
302
|
pagingMetadataSignal.set({
|
|
309
|
-
limit: pagingMetadataSignal.get().limit || DEFAULT_QUERY_LIMIT,
|
|
303
|
+
limit: pagingMetadataSignal.get().limit || constants_js_1.DEFAULT_QUERY_LIMIT,
|
|
310
304
|
offset: 0,
|
|
311
305
|
count: 0,
|
|
312
306
|
hasNext: false,
|
|
307
|
+
cursor: undefined,
|
|
313
308
|
});
|
|
314
309
|
queryOptionsSignal.set(options);
|
|
315
310
|
},
|
|
316
311
|
setFilter: (filter) => {
|
|
317
|
-
|
|
318
|
-
const currentOptions = queryOptionsSignal.get();
|
|
319
|
-
queryOptionsSignal.set({
|
|
320
|
-
...currentOptions,
|
|
321
|
-
filter,
|
|
322
|
-
});
|
|
312
|
+
queryOptionsSignal.set({ ...queryOptionsSignal.get(), filter });
|
|
323
313
|
},
|
|
324
314
|
resetFilter: () => {
|
|
325
|
-
// Clear filter from query options (triggers re-fetch)
|
|
326
|
-
const currentOptions = queryOptionsSignal.get();
|
|
327
315
|
queryOptionsSignal.set({
|
|
328
|
-
...
|
|
316
|
+
...queryOptionsSignal.get(),
|
|
329
317
|
filter: undefined,
|
|
330
318
|
});
|
|
331
319
|
},
|
|
332
320
|
resetFilterField: (fieldName) => {
|
|
333
|
-
|
|
334
|
-
const currentFilter = queryOptionsSignal.get().filter || {};
|
|
335
|
-
const newFilter = { ...currentFilter };
|
|
336
|
-
// Remove the specific field
|
|
321
|
+
const newFilter = { ...(queryOptionsSignal.get().filter || {}) };
|
|
337
322
|
delete newFilter[fieldName];
|
|
338
|
-
// Update query options with new filter (triggers re-fetch)
|
|
339
|
-
const currentOptions = queryOptionsSignal.get();
|
|
340
323
|
queryOptionsSignal.set({
|
|
341
|
-
...
|
|
324
|
+
...queryOptionsSignal.get(),
|
|
342
325
|
filter: Object.keys(newFilter).length > 0 ? newFilter : undefined,
|
|
343
326
|
});
|
|
344
327
|
},
|
|
345
328
|
},
|
|
346
|
-
hasMoreServicesSignal: signalsService.computed(() =>
|
|
347
|
-
const paging = pagingMetadataSignal.get();
|
|
348
|
-
// Use hasNext flag from pagingMetadata to determine if more services are available
|
|
349
|
-
return paging.hasNext ?? false;
|
|
350
|
-
}),
|
|
329
|
+
hasMoreServicesSignal: signalsService.computed(() => pagingMetadataSignal.get().hasNext ?? false),
|
|
351
330
|
};
|
|
352
331
|
});
|
|
353
332
|
/**
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* to pre-fetch services data before the React components are hydrated on the client.
|
|
357
|
-
* The returned config includes paging metadata with hasNext flag indicating if more services are available.
|
|
333
|
+
* Loads service list configuration for SSR, pre-fetching services and categories.
|
|
334
|
+
* Pass an `availabilityFilter` to query availability-aware services.
|
|
358
335
|
*
|
|
359
|
-
* @param options - Query options
|
|
360
|
-
* @returns A
|
|
336
|
+
* @param options - Query options.
|
|
337
|
+
* @returns A success result with the config, or an error result.
|
|
361
338
|
*
|
|
362
339
|
* @example
|
|
363
340
|
* ```ts
|
|
364
|
-
* //
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
* // Load with query options
|
|
368
|
-
* const serviceListResult = await loadServiceListServiceInitialData({
|
|
341
|
+
* // Plain offset query
|
|
342
|
+
* const result = await loadServiceListServiceInitialData({
|
|
369
343
|
* appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
|
|
370
344
|
* pagingMetadata: { limit: 20, offset: 0 },
|
|
371
345
|
* });
|
|
372
346
|
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
* // More services can be loaded
|
|
383
|
-
* }
|
|
347
|
+
* // Availability-aware query (cursor paging + `available` flag)
|
|
348
|
+
* const availabilityResult = await loadServiceListServiceInitialData({
|
|
349
|
+
* availabilityFilter: {
|
|
350
|
+
* localStartDate: '2026-06-01T09:00:00',
|
|
351
|
+
* localEndDate: '2026-06-07T17:00:00',
|
|
352
|
+
* timeZone: 'America/New_York',
|
|
353
|
+
* },
|
|
354
|
+
* pagingMetadata: { limit: 20 },
|
|
355
|
+
* });
|
|
384
356
|
* ```
|
|
385
357
|
*/
|
|
386
358
|
async function loadServiceListServiceInitialData(options) {
|
|
387
359
|
try {
|
|
388
|
-
|
|
360
|
+
const appId = options.appId ?? constants_js_1.BOOKING_APP_ID;
|
|
361
|
+
// Fetch services and categories in parallel; categories failure is non-fatal.
|
|
362
|
+
// queryServices picks the endpoint by the presence of availabilityFilter.
|
|
389
363
|
const [servicesResult, categoriesResult] = await Promise.all([
|
|
390
364
|
(0, index_js_1.queryServices)({
|
|
391
|
-
appId
|
|
365
|
+
appId,
|
|
392
366
|
filter: options.filter,
|
|
393
367
|
pagingMetadata: options.pagingMetadata,
|
|
394
368
|
sort: options.sort,
|
|
369
|
+
availabilityFilter: options.availabilityFilter,
|
|
395
370
|
}),
|
|
396
371
|
(0, index_js_2.queryCategories)().catch(() => null),
|
|
397
372
|
]);
|
|
@@ -401,7 +376,10 @@ async function loadServiceListServiceInitialData(options) {
|
|
|
401
376
|
services: servicesResult.services,
|
|
402
377
|
categories: categoriesResult?.categories ?? [],
|
|
403
378
|
options: {
|
|
404
|
-
|
|
379
|
+
appId,
|
|
380
|
+
filter: options.filter,
|
|
381
|
+
sort: options.sort,
|
|
382
|
+
availabilityFilter: options.availabilityFilter,
|
|
405
383
|
pagingMetadata: servicesResult.pagingMetadata,
|
|
406
384
|
},
|
|
407
385
|
},
|
|
@@ -5,7 +5,7 @@ import type { Service, StaffMember } from '@wix/auto_sdk_bookings_services';
|
|
|
5
5
|
import type { ServiceServiceConfig } from '../../services/service/service.js';
|
|
6
6
|
import type { ServiceListServiceConfig } from '../../services/service-list/service-list.js';
|
|
7
7
|
import type { StaffMemberListServiceConfig } from '../../services/staff-member-list/staff-member-list.js';
|
|
8
|
-
import type { PagingMetadata } from '../../api/query-services/index.js';
|
|
8
|
+
import type { PagingMetadata, AvailabilityFilter, ServiceWithAvailabilityItem } from '../../api/query-services/index.js';
|
|
9
9
|
import type { Category } from '../../api/query-categories/index.js';
|
|
10
10
|
/**
|
|
11
11
|
* Base mock service with all fields populated (APPOINTMENT type, FIXED pricing)
|
|
@@ -102,6 +102,22 @@ export declare const mockCategories: Category[];
|
|
|
102
102
|
* Service list config with categories for filter testing
|
|
103
103
|
*/
|
|
104
104
|
export declare const mockServiceListConfigWithCategories: ServiceListServiceConfig;
|
|
105
|
+
/**
|
|
106
|
+
* Mock services enriched with an `available` flag (catalog-search results).
|
|
107
|
+
*/
|
|
108
|
+
export declare const mockServicesWithAvailability: ServiceWithAvailabilityItem[];
|
|
109
|
+
/**
|
|
110
|
+
* Cursor-based paging metadata with a next cursor available.
|
|
111
|
+
*/
|
|
112
|
+
export declare const mockAvailabilityPagingMetadata: PagingMetadata;
|
|
113
|
+
/**
|
|
114
|
+
* Cursor-based paging metadata for the last page (no next cursor).
|
|
115
|
+
*/
|
|
116
|
+
export declare const mockAvailabilityPagingMetadataLast: PagingMetadata;
|
|
117
|
+
/**
|
|
118
|
+
* Mock availability filter (date window + timezone).
|
|
119
|
+
*/
|
|
120
|
+
export declare const mockAvailabilityFilter: AvailabilityFilter;
|
|
105
121
|
/**
|
|
106
122
|
* Base mock staff member with all fields
|
|
107
123
|
*/
|
|
@@ -396,6 +396,40 @@ export const mockServiceListConfigWithCategories = {
|
|
|
396
396
|
},
|
|
397
397
|
};
|
|
398
398
|
// ============================================================================
|
|
399
|
+
// Availability (catalog-search) Mocks
|
|
400
|
+
// ============================================================================
|
|
401
|
+
/**
|
|
402
|
+
* Mock services enriched with an `available` flag (catalog-search results).
|
|
403
|
+
*/
|
|
404
|
+
export const mockServicesWithAvailability = mockServices.map((service, index) => ({
|
|
405
|
+
...service,
|
|
406
|
+
available: index !== 2, // third service is unavailable
|
|
407
|
+
}));
|
|
408
|
+
/**
|
|
409
|
+
* Cursor-based paging metadata with a next cursor available.
|
|
410
|
+
*/
|
|
411
|
+
export const mockAvailabilityPagingMetadata = {
|
|
412
|
+
count: 3,
|
|
413
|
+
hasNext: true,
|
|
414
|
+
cursor: 'next-cursor-token',
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* Cursor-based paging metadata for the last page (no next cursor).
|
|
418
|
+
*/
|
|
419
|
+
export const mockAvailabilityPagingMetadataLast = {
|
|
420
|
+
count: 3,
|
|
421
|
+
hasNext: false,
|
|
422
|
+
cursor: undefined,
|
|
423
|
+
};
|
|
424
|
+
/**
|
|
425
|
+
* Mock availability filter (date window + timezone).
|
|
426
|
+
*/
|
|
427
|
+
export const mockAvailabilityFilter = {
|
|
428
|
+
localStartDate: '2026-06-01T09:00:00',
|
|
429
|
+
localEndDate: '2026-06-01T17:00:00',
|
|
430
|
+
timeZone: 'America/New_York',
|
|
431
|
+
};
|
|
432
|
+
// ============================================================================
|
|
399
433
|
// Staff Member Mocks
|
|
400
434
|
// ============================================================================
|
|
401
435
|
/**
|
package/dist/api/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @module API
|
|
6
6
|
*/
|
|
7
|
-
export { getServiceById, getServiceBySlug, queryServices, convertPlatformFilterToApiFilter, type PagingMetadata, type QueryServicesRequest, SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from './query-services/index.js';
|
|
7
|
+
export { getServiceById, getServiceBySlug, queryServices, convertPlatformFilterToApiFilter, type PagingMetadata, type QueryServicesRequest, type QueryServicesResult, type ServiceWithAvailabilityItem, AvailabilityLocationType, type AvailabilityFilter, type Attribute, type AvailabilityLocation, SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from './query-services/index.js';
|
|
8
8
|
export { fetchAvailability } from './fetch-availability/index.js';
|
|
9
9
|
export * from '@wix/auto_sdk_bookings_bookings';
|
|
10
10
|
export { createBooking, type CreateBookingOptions, } from './create-booking/index.js';
|
package/dist/api/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @module API
|
|
6
6
|
*/
|
|
7
|
-
export { getServiceById, getServiceBySlug, queryServices, convertPlatformFilterToApiFilter, SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from './query-services/index.js';
|
|
7
|
+
export { getServiceById, getServiceBySlug, queryServices, convertPlatformFilterToApiFilter, AvailabilityLocationType, SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from './query-services/index.js';
|
|
8
8
|
export { fetchAvailability } from './fetch-availability/index.js';
|
|
9
9
|
export * from '@wix/auto_sdk_bookings_bookings';
|
|
10
10
|
export { createBooking, } from './create-booking/index.js';
|