@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.
Files changed (35) hide show
  1. package/cjs/dist/__mocks__/services/services-mocks.d.ts +17 -1
  2. package/cjs/dist/__mocks__/services/services-mocks.js +35 -1
  3. package/cjs/dist/api/index.d.ts +1 -1
  4. package/cjs/dist/api/index.js +2 -1
  5. package/cjs/dist/api/query-services/index.d.ts +60 -15
  6. package/cjs/dist/api/query-services/index.js +86 -10
  7. package/cjs/dist/react/core/service-list/ServiceList.d.ts +2 -3
  8. package/cjs/dist/react/service-list/ServiceList.d.ts +3 -3
  9. package/cjs/dist/react/service-list/ServiceList.js +1 -1
  10. package/cjs/dist/react/service-list/ServiceList.stories.d.ts +29 -0
  11. package/cjs/dist/react/service-list/ServiceList.stories.js +125 -0
  12. package/cjs/dist/services/constants.d.ts +2 -0
  13. package/cjs/dist/services/constants.js +3 -1
  14. package/cjs/dist/services/index.d.ts +2 -2
  15. package/cjs/dist/services/index.js +2 -1
  16. package/cjs/dist/services/service-list/service-list.d.ts +45 -36
  17. package/cjs/dist/services/service-list/service-list.js +90 -112
  18. package/dist/__mocks__/services/services-mocks.d.ts +17 -1
  19. package/dist/__mocks__/services/services-mocks.js +34 -0
  20. package/dist/api/index.d.ts +1 -1
  21. package/dist/api/index.js +1 -1
  22. package/dist/api/query-services/index.d.ts +60 -15
  23. package/dist/api/query-services/index.js +89 -10
  24. package/dist/react/core/service-list/ServiceList.d.ts +2 -3
  25. package/dist/react/service-list/ServiceList.d.ts +3 -3
  26. package/dist/react/service-list/ServiceList.js +1 -1
  27. package/dist/react/service-list/ServiceList.stories.d.ts +29 -0
  28. package/dist/react/service-list/ServiceList.stories.js +89 -0
  29. package/dist/services/constants.d.ts +2 -0
  30. package/dist/services/constants.js +2 -0
  31. package/dist/services/index.d.ts +2 -2
  32. package/dist/services/index.js +1 -1
  33. package/dist/services/service-list/service-list.d.ts +45 -36
  34. package/dist/services/service-list/service-list.js +90 -112
  35. package/package.json +17 -2
@@ -1,8 +1,16 @@
1
1
  /**
2
2
  * Query Services API
3
- * Fetches single or multiple services with pagination and sorting
3
+ * Fetches single or multiple services with pagination and sorting.
4
+ *
5
+ * `queryServices` is the single entry point. It decides which underlying endpoint
6
+ * to call based on the request: when an `availabilityFilter` is present it queries
7
+ * the catalog-search `QueryServicesByAvailability` endpoint (cursor paging, each
8
+ * service enriched with an `available` flag); otherwise it uses the services
9
+ * `queryServices` endpoint (offset paging). Callers always get the same
10
+ * `{ services, pagingMetadata }` shape.
4
11
  */
5
12
  import { type Service, type Paging, type Sorting } from '@wix/auto_sdk_bookings_services';
13
+ import { LocationType as AvailabilityLocationType, type AvailabilityFilter, type Attribute, type Location as AvailabilityLocation } from '@wix/auto_sdk_bookings_catalog-search';
6
14
  import type { FilterValue as Filter } from '@wix/headless-components/react';
7
15
  /**
8
16
  * Synthetic location ID constants for non-business locations
@@ -10,24 +18,48 @@ import type { FilterValue as Filter } from '@wix/headless-components/react';
10
18
  export declare const SYNTHETIC_CUSTOM_ID = "custom";
11
19
  export declare const SYNTHETIC_CUSTOMER_ID = "customer";
12
20
  /**
13
- * Paging metadata from query services response.
14
- * Extends the SDK's Paging type with hasNext flag to indicate if more services are available.
21
+ * Re-exported availability filter types so consumers can build availability UIs
22
+ * without importing the ambassador package directly.
23
+ */
24
+ export { AvailabilityLocationType, type AvailabilityFilter, type Attribute, type AvailabilityLocation, };
25
+ /**
26
+ * A service, optionally enriched with a bookable-availability flag.
27
+ * `available` is only populated when the query runs in availability mode.
28
+ */
29
+ export type ServiceWithAvailabilityItem = Service & {
30
+ /** Whether the service has bookable slots in the requested window. */
31
+ available?: boolean;
32
+ };
33
+ /**
34
+ * Paging metadata from a query response. Extends the SDK's Paging type with a
35
+ * `hasNext` flag and an opaque `cursor` (used in availability/cursor mode).
15
36
  */
16
37
  export type PagingMetadata = Paging & {
17
38
  /** Number of items returned in the current page */
18
39
  count?: number;
19
40
  /** Whether there are more services available to load */
20
41
  hasNext?: boolean;
42
+ /** Opaque cursor for the next page (availability/cursor mode only). Undefined in offset mode. */
43
+ cursor?: string;
21
44
  };
22
45
  /**
23
- * Query services request options
24
- * Used internally by ServiceListService to track query state
46
+ * Query services request options.
47
+ * When `availabilityFilter` is present, services are queried by real bookable
48
+ * availability; `pagingMetadata.cursor` then drives pagination instead of `offset`.
25
49
  */
26
50
  export interface QueryServicesRequest {
27
51
  appId: string;
28
52
  filter?: Filter;
29
53
  pagingMetadata?: PagingMetadata;
30
54
  sort?: Sorting[];
55
+ availabilityFilter?: AvailabilityFilter;
56
+ }
57
+ /**
58
+ * Result shape shared by both query modes.
59
+ */
60
+ export interface QueryServicesResult {
61
+ services: ServiceWithAvailabilityItem[];
62
+ pagingMetadata: PagingMetadata;
31
63
  }
32
64
  /**
33
65
  * Fetches a service by its ID
@@ -72,28 +104,41 @@ export declare const getServiceBySlug: (slug: string, appId?: string) => Promise
72
104
  * ```
73
105
  */
74
106
  export declare function convertPlatformFilterToApiFilter(platformFilter: Filter): Record<string, any>;
107
+ /**
108
+ * Whether an availability filter carries a real bookable constraint that warrants
109
+ * routing to the availability endpoint. Modifiers that don't define a query on their
110
+ * own (`timeZone`, `exactMatch`, `includeUnavailable`) are ignored: only a complete
111
+ * date window (both `localStartDate` and `localEndDate`) or a resource constraint
112
+ * (`locations`/`attributes`/`resourceTypes`) counts.
113
+ *
114
+ * This is the single source of truth shared by the API dispatch and the service's
115
+ * `hasAvailabilityFilterSignal`.
116
+ */
117
+ export declare function hasActiveAvailabilityFilter(availabilityFilter?: AvailabilityFilter): boolean;
75
118
  /**
76
119
  * Query services with pagination and sorting.
77
- * Returns services array and paging metadata including hasNext flag.
120
+ * Picks the underlying endpoint by request: availability filter present → catalog-search
121
+ * (cursor paging, `available` flag); otherwise the offset services query.
78
122
  *
79
- * @param params - Query parameters including appId, filter (platform Filter type), paging metadata, and sort options
123
+ * @param request - Query parameters including appId, filter (platform Filter type), paging
124
+ * metadata, sort options, and an optional availability filter
80
125
  * @returns Promise that resolves to services array and paging metadata with hasNext flag
81
126
  *
82
127
  * @example
83
128
  * ```ts
129
+ * // Offset query
84
130
  * const { services, pagingMetadata } = await queryServices({
85
131
  * appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
86
132
  * filter: { '_id': { $in: ['service1', 'service2'] } },
87
133
  * pagingMetadata: { limit: 20, offset: 0 },
88
- * sort: [{ fieldName: 'name', order: 'ASC' }]
89
134
  * });
90
135
  *
91
- * if (pagingMetadata.hasNext) {
92
- * // Load more services
93
- * }
136
+ * // Availability-aware query (cursor paging + `available` flag)
137
+ * const availability = await queryServices({
138
+ * appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
139
+ * availabilityFilter: { localStartDate: '...', localEndDate: '...', timeZone: '...' },
140
+ * pagingMetadata: { limit: 20 },
141
+ * });
94
142
  * ```
95
143
  */
96
- export declare function queryServices({ appId, filter, pagingMetadata, sort, }: QueryServicesRequest): Promise<{
97
- services: Service[];
98
- pagingMetadata: PagingMetadata;
99
- }>;
144
+ export declare function queryServices(request: QueryServicesRequest): Promise<QueryServicesResult>;
@@ -1,14 +1,27 @@
1
1
  /**
2
2
  * Query Services API
3
- * Fetches single or multiple services with pagination and sorting
3
+ * Fetches single or multiple services with pagination and sorting.
4
+ *
5
+ * `queryServices` is the single entry point. It decides which underlying endpoint
6
+ * to call based on the request: when an `availabilityFilter` is present it queries
7
+ * the catalog-search `QueryServicesByAvailability` endpoint (cursor paging, each
8
+ * service enriched with an `available` flag); otherwise it uses the services
9
+ * `queryServices` endpoint (offset paging). Callers always get the same
10
+ * `{ services, pagingMetadata }` shape.
4
11
  */
5
12
  import { queryServices as queryServicesApi, LocationTypeEnumLocationType as LocationType, } from '@wix/auto_sdk_bookings_services';
6
- import { BOOKING_APP_ID } from '../../services/constants.js';
13
+ import { queryServicesByAvailability as queryServicesByAvailabilitySdk, LocationType as AvailabilityLocationType, } from '@wix/auto_sdk_bookings_catalog-search';
14
+ import { BOOKING_APP_ID, DEFAULT_QUERY_LIMIT, } from '../../services/constants.js';
7
15
  /**
8
16
  * Synthetic location ID constants for non-business locations
9
17
  */
10
18
  export const SYNTHETIC_CUSTOM_ID = 'custom';
11
19
  export const SYNTHETIC_CUSTOMER_ID = 'customer';
20
+ /**
21
+ * Re-exported availability filter types so consumers can build availability UIs
22
+ * without importing the ambassador package directly.
23
+ */
24
+ export { AvailabilityLocationType, };
12
25
  /**
13
26
  * Fetches a service by its ID
14
27
  * @param id - The service ID
@@ -180,34 +193,100 @@ const buildServicesQueryRequest = ({ appId, filter: platformFilter, paging, sort
180
193
  const query = {
181
194
  filter: Object.keys(filter).length > 0 ? filter : undefined,
182
195
  paging: paging || {
183
- limit: 100, // Default limit
196
+ limit: DEFAULT_QUERY_LIMIT,
184
197
  },
185
198
  sort,
186
199
  };
187
200
  return { query };
188
201
  };
202
+ /**
203
+ * Whether an availability filter carries a real bookable constraint that warrants
204
+ * routing to the availability endpoint. Modifiers that don't define a query on their
205
+ * own (`timeZone`, `exactMatch`, `includeUnavailable`) are ignored: only a complete
206
+ * date window (both `localStartDate` and `localEndDate`) or a resource constraint
207
+ * (`locations`/`attributes`/`resourceTypes`) counts.
208
+ *
209
+ * This is the single source of truth shared by the API dispatch and the service's
210
+ * `hasAvailabilityFilterSignal`.
211
+ */
212
+ export function hasActiveAvailabilityFilter(availabilityFilter) {
213
+ if (!availabilityFilter)
214
+ return false;
215
+ const { localStartDate, localEndDate, locationIds, attributes, resourceTypes, } = availabilityFilter;
216
+ const hasDateWindow = Boolean(localStartDate && localEndDate);
217
+ const hasResourceConstraint = (locationIds?.length ?? 0) > 0 ||
218
+ (attributes?.length ?? 0) > 0 ||
219
+ (resourceTypes?.length ?? 0) > 0;
220
+ return hasDateWindow || hasResourceConstraint;
221
+ }
222
+ /**
223
+ * Queries services by real bookable availability (catalog-search). Cursor-based.
224
+ * The WQL filter reuses the same platform→API conversion as the offset query.
225
+ */
226
+ async function queryServicesByAvailability({ appId, filter, availabilityFilter, sort, pagingMetadata, }) {
227
+ const wqlFilter = {
228
+ appId: appId ?? BOOKING_APP_ID,
229
+ };
230
+ if (filter) {
231
+ Object.assign(wqlFilter, convertPlatformFilterToApiFilter(filter));
232
+ }
233
+ const limit = pagingMetadata?.limit ?? DEFAULT_QUERY_LIMIT;
234
+ const cursor = pagingMetadata?.cursor;
235
+ const availabilityQuery = {
236
+ filter: wqlFilter,
237
+ sort,
238
+ cursorPaging: { limit, ...(cursor ? { cursor } : {}) },
239
+ };
240
+ const response = await queryServicesByAvailabilitySdk({
241
+ query: availabilityQuery,
242
+ availabilityFilter,
243
+ });
244
+ const results = response.results ?? [];
245
+ return {
246
+ services: results.map((result) => ({
247
+ ...result.service,
248
+ available: result.available ?? true,
249
+ })),
250
+ pagingMetadata: {
251
+ limit,
252
+ count: response.pagingMetadata?.count ?? results.length,
253
+ hasNext: response.pagingMetadata?.hasNext ?? false,
254
+ cursor: response.pagingMetadata?.cursors?.next ?? undefined,
255
+ },
256
+ };
257
+ }
189
258
  /**
190
259
  * Query services with pagination and sorting.
191
- * Returns services array and paging metadata including hasNext flag.
260
+ * Picks the underlying endpoint by request: availability filter present → catalog-search
261
+ * (cursor paging, `available` flag); otherwise the offset services query.
192
262
  *
193
- * @param params - Query parameters including appId, filter (platform Filter type), paging metadata, and sort options
263
+ * @param request - Query parameters including appId, filter (platform Filter type), paging
264
+ * metadata, sort options, and an optional availability filter
194
265
  * @returns Promise that resolves to services array and paging metadata with hasNext flag
195
266
  *
196
267
  * @example
197
268
  * ```ts
269
+ * // Offset query
198
270
  * const { services, pagingMetadata } = await queryServices({
199
271
  * appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
200
272
  * filter: { '_id': { $in: ['service1', 'service2'] } },
201
273
  * pagingMetadata: { limit: 20, offset: 0 },
202
- * sort: [{ fieldName: 'name', order: 'ASC' }]
203
274
  * });
204
275
  *
205
- * if (pagingMetadata.hasNext) {
206
- * // Load more services
207
- * }
276
+ * // Availability-aware query (cursor paging + `available` flag)
277
+ * const availability = await queryServices({
278
+ * appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
279
+ * availabilityFilter: { localStartDate: '...', localEndDate: '...', timeZone: '...' },
280
+ * pagingMetadata: { limit: 20 },
281
+ * });
208
282
  * ```
209
283
  */
210
- export async function queryServices({ appId, filter, pagingMetadata, sort, }) {
284
+ export async function queryServices(request) {
285
+ const { appId, filter, pagingMetadata, sort, availabilityFilter } = request;
286
+ // Decide the endpoint by the filters passed.
287
+ if (hasActiveAvailabilityFilter(availabilityFilter)) {
288
+ return queryServicesByAvailability(request);
289
+ }
211
290
  try {
212
291
  const queryRequest = buildServicesQueryRequest({
213
292
  appId,
@@ -4,8 +4,7 @@
4
4
  */
5
5
  import React from 'react';
6
6
  import { type ServiceListServiceConfig, type QueryOptions } from '../../../services/service-list/service-list.js';
7
- import type { Service } from '@wix/auto_sdk_bookings_services';
8
- import type { PagingMetadata } from '../../../api/query-services/index.js';
7
+ import type { PagingMetadata, ServiceWithAvailabilityItem } from '../../../api/query-services/index.js';
9
8
  /**
10
9
  * Props for ServiceList.Root component
11
10
  */
@@ -37,7 +36,7 @@ export interface ServicesProps {
37
36
  */
38
37
  export interface ServicesRenderProps {
39
38
  /** Array of services */
40
- services: Service[];
39
+ services: ServiceWithAvailabilityItem[];
41
40
  /** Whether services are currently loading */
42
41
  isLoading: boolean;
43
42
  /** Error message, or null if no error */
@@ -6,7 +6,7 @@ import React from 'react';
6
6
  import { ListVariant, GenericListLoadMoreRenderProps, type FilterRootProps } from '@wix/headless-components/react';
7
7
  import { AsChildChildren } from '@wix/headless-utils/react';
8
8
  import type { ServiceListServiceConfig } from '../../services/service-list/service-list.js';
9
- import type { Service as ServiceType } from '@wix/auto_sdk_bookings_services';
9
+ import type { ServiceWithAvailabilityItem } from '../../api/query-services/index.js';
10
10
  export declare const TestIds: {
11
11
  readonly serviceListRoot: "service-list-root";
12
12
  readonly serviceListServices: "service-list-services";
@@ -95,7 +95,7 @@ export type ServiceRootChildren = AsChildChildren<{
95
95
  * Supports both ReactNode and function children that receive { selected, bookable }
96
96
  */
97
97
  export interface ServiceItemWrapperProps {
98
- item: ServiceType;
98
+ item: ServiceWithAvailabilityItem;
99
99
  index?: number;
100
100
  /** Children can be ReactNode or a function that receives { selected, bookable } from Service.Root */
101
101
  children: React.ReactNode | ServiceRootChildren;
@@ -105,7 +105,7 @@ export interface ServiceItemWrapperProps {
105
105
  * Provides items array and itemWrapper function for custom rendering
106
106
  */
107
107
  export interface ServiceRepeaterRenderProps {
108
- items: ServiceType[];
108
+ items: ServiceWithAvailabilityItem[];
109
109
  variant?: ListVariant;
110
110
  /** Wraps each item in Service.Root - children can be ReactNode or function receiving { selected, bookable } */
111
111
  itemWrapper: (props: ServiceItemWrapperProps) => React.ReactNode;
@@ -121,7 +121,7 @@ export const ServiceRepeater = React.forwardRef((props, ref) => {
121
121
  // Custom itemWrapper that matches ItemWrapperProps signature
122
122
  const serviceItemWrapper = (wrapperProps) => {
123
123
  const { item: service, children: itemChildren } = wrapperProps;
124
- return (_jsx(Service.Root, { service: service, "data-testid": TestIds.serviceListService, "data-service-id": service._id, "data-item-id": service._id, children: isServiceRootFunction
124
+ return (_jsx(Service.Root, { service: service, "data-testid": TestIds.serviceListService, "data-service-id": service._id, "data-item-id": service._id, "data-available": service.available, children: isServiceRootFunction
125
125
  ? children
126
126
  : itemChildren }, service._id));
127
127
  };
@@ -0,0 +1,29 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import * as ServiceList from './ServiceList';
3
+ /**
4
+ * ServiceList component story.
5
+ *
6
+ * ServiceList renders a list of bookings services. When the configured services
7
+ * carry an `available` flag (availability mode), stories can style each item by
8
+ * its availability. It supports filtering by categories, availability dates, and
9
+ * custom attributes.
10
+ */
11
+ declare const meta: Meta<typeof ServiceList.Root>;
12
+ export default meta;
13
+ type Story = StoryObj<typeof meta>;
14
+ /**
15
+ * Basic list with three services, two available and one unavailable.
16
+ */
17
+ export declare const BasicList: Story;
18
+ /**
19
+ * List with empty state message displayed when no services are available.
20
+ */
21
+ export declare const EmptyState: Story;
22
+ /**
23
+ * List with detailed service information (name and price).
24
+ */
25
+ export declare const WithServiceDetails: Story;
26
+ /**
27
+ * List with custom styling for available/unavailable items.
28
+ */
29
+ export declare const WithAvailabilityStates: Story;
@@ -0,0 +1,89 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as ServiceList from './ServiceList';
3
+ import * as Booking from '../booking/Booking';
4
+ import * as Service from '../service/Service';
5
+ import { mockServicesWithAvailability, mockCategories, } from '../../__mocks__/services/services-mocks';
6
+ import { BOOKING_APP_ID } from '../../services/constants';
7
+ const config = {
8
+ services: mockServicesWithAvailability,
9
+ categories: mockCategories,
10
+ options: { appId: BOOKING_APP_ID },
11
+ };
12
+ const emptyConfig = {
13
+ services: [],
14
+ categories: [],
15
+ options: { appId: BOOKING_APP_ID },
16
+ };
17
+ /**
18
+ * ServiceList component story.
19
+ *
20
+ * ServiceList renders a list of bookings services. When the configured services
21
+ * carry an `available` flag (availability mode), stories can style each item by
22
+ * its availability. It supports filtering by categories, availability dates, and
23
+ * custom attributes.
24
+ */
25
+ const meta = {
26
+ title: 'Bookings/ServiceList',
27
+ component: ServiceList.Root,
28
+ tags: ['autodocs'],
29
+ };
30
+ export default meta;
31
+ /**
32
+ * Basic list with three services, two available and one unavailable.
33
+ */
34
+ export const BasicList = {
35
+ render: () => (_jsx(Booking.Root, { children: _jsx(ServiceList.Root, { serviceListConfig: config, children: _jsx(ServiceList.Services, { children: _jsx(ServiceList.ServiceRepeater, { children: _jsx(Service.Name, {}) }) }) }) })),
36
+ };
37
+ /**
38
+ * List with empty state message displayed when no services are available.
39
+ */
40
+ export const EmptyState = {
41
+ render: () => (_jsx(Booking.Root, { children: _jsx(ServiceList.Root, { serviceListConfig: emptyConfig, children: _jsx(ServiceList.Services, { emptyState: _jsx("div", { style: {
42
+ padding: '2rem',
43
+ textAlign: 'center',
44
+ color: 'var(--color-muted-foreground)',
45
+ }, children: "No services available" }), children: _jsx(ServiceList.ServiceRepeater, { children: _jsx(Service.Name, {}) }) }) }) })),
46
+ };
47
+ /**
48
+ * List with detailed service information (name and price).
49
+ */
50
+ export const WithServiceDetails = {
51
+ render: () => (_jsx(Booking.Root, { children: _jsx(ServiceList.Root, { serviceListConfig: config, children: _jsx(ServiceList.Services, { children: _jsx(ServiceList.ServiceRepeater, { asChild: true, children: ({ items, itemWrapper, }) => (_jsx("ul", { style: { listStyle: 'none', padding: 0 }, children: items.map((service, index) => (_jsx("li", { style: {
52
+ padding: '1rem',
53
+ borderBottom: '1px solid var(--color-border)',
54
+ opacity: service.available === false ? 0.6 : 1,
55
+ }, children: itemWrapper({
56
+ item: service,
57
+ index,
58
+ children: (_jsxs("div", { children: [_jsx(Service.Name, {}), _jsx(Service.Price, {})] })),
59
+ }) }, service._id))) })) }) }) }) })),
60
+ };
61
+ /**
62
+ * List with custom styling for available/unavailable items.
63
+ */
64
+ export const WithAvailabilityStates = {
65
+ render: () => (_jsx(Booking.Root, { children: _jsx(ServiceList.Root, { serviceListConfig: config, children: _jsx(ServiceList.Services, { children: _jsx(ServiceList.ServiceRepeater, { asChild: true, children: ({ items, itemWrapper, }) => (_jsx("ul", { style: { listStyle: 'none', padding: 0 }, children: items.map((service, index) => (_jsx("li", { style: {
66
+ padding: '1rem',
67
+ marginBottom: '0.5rem',
68
+ borderRadius: '4px',
69
+ backgroundColor: service.available
70
+ ? 'color-mix(in srgb, var(--color-primary) 8%, transparent)'
71
+ : 'var(--color-secondary)',
72
+ borderLeft: `4px solid ${service.available ? 'var(--color-primary)' : 'var(--color-border)'}`,
73
+ opacity: service.available ? 1 : 0.7,
74
+ }, children: itemWrapper({
75
+ item: service,
76
+ index,
77
+ children: (_jsxs("div", { style: {
78
+ display: 'flex',
79
+ justifyContent: 'space-between',
80
+ alignItems: 'center',
81
+ }, children: [_jsx(Service.Name, {}), _jsx("span", { style: {
82
+ fontSize: '0.875rem',
83
+ fontWeight: 500,
84
+ color: service.available
85
+ ? 'var(--color-primary)'
86
+ : 'var(--color-muted-foreground)',
87
+ }, children: service.available ? 'Available' : 'Not Available' })] })),
88
+ }) }, service._id))) })) }) }) }) })),
89
+ };
@@ -5,6 +5,8 @@
5
5
  */
6
6
  /** Booking app definition ID */
7
7
  export declare const BOOKING_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
8
+ /** Default page size for service queries (offset and cursor/availability modes) */
9
+ export declare const DEFAULT_QUERY_LIMIT = 100;
8
10
  /** Resource type ID for staff members */
9
11
  export declare const STAFF_MEMBER_RESOURCE_TYPE_ID = "1cd44cf8-756f-41c3-bd90-3e2ffcaf1155";
10
12
  export declare const ClientServiceIds: {
@@ -5,6 +5,8 @@
5
5
  */
6
6
  /** Booking app definition ID */
7
7
  export const BOOKING_APP_ID = '13d21c63-b5ec-5912-8397-c3a5ddb27a97';
8
+ /** Default page size for service queries (offset and cursor/availability modes) */
9
+ export const DEFAULT_QUERY_LIMIT = 100;
8
10
  /** Resource type ID for staff members */
9
11
  export const STAFF_MEMBER_RESOURCE_TYPE_ID = '1cd44cf8-756f-41c3-bd90-3e2ffcaf1155';
10
12
  export const ClientServiceIds = {
@@ -5,8 +5,8 @@
5
5
  export { ServiceService, ServiceServiceDefinition, type ServiceServiceAPI, type ServiceServiceConfig, type ServiceActions, type ServicePaymentData, loadServiceServiceInitialData, type SuccessServiceServiceConfigResult, type NotFoundServiceServiceConfigResult, } from './service/service.js';
6
6
  export { BookingService, BookingServiceDefinition, type BookingServiceConfig, type BookingServiceActions, type ServiceSelection, type ServiceSelectionTimeSlot, } from './booking/booking.js';
7
7
  export { TimeSlotListService, TimeSlotListServiceDefinition, TimeSlotService, TimeSlotServiceDefinition, ViewMode, loadTimeSlotListConfig, type TimeSlotListServiceAPI, type TimeSlotListServiceActions, type TimeSlotListServiceConfig, type TimeSlotServiceAPI, type TimeSlotServiceConfig, type LoadTimeSlotListConfigOptions, type LoadTimeSlotListConfigResult, } from './time-slot-list/index.js';
8
- export { ServiceListService, ServiceListServiceDefinition, type ServiceListServiceAPI, type ServiceListServiceConfig, type ServiceListActions, type QueryOptions, loadServiceListServiceInitialData, type SuccessServiceListServiceConfigResult, type ErrorServiceListServiceConfigResult, } from './service-list/service-list.js';
9
- export { SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from '../api/query-services/index.js';
8
+ export { ServiceListService, ServiceListServiceDefinition, type ServiceListServiceAPI, type ServiceListServiceConfig, type ServiceListActions, type QueryOptions, loadServiceListServiceInitialData, type LoadServiceListServiceOptions, type SuccessServiceListServiceConfigResult, type ErrorServiceListServiceConfigResult, } from './service-list/service-list.js';
9
+ export { SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, AvailabilityLocationType, type ServiceWithAvailabilityItem, type AvailabilityFilter, type Attribute, type AvailabilityLocation, } from '../api/query-services/index.js';
10
10
  export { executeBookAction, canBook, buildBookingRequest, buildCartRequest, buildDayRangeBookingRequest, createBookingsForCart, isDayRangeService, getDayRangeBounds, type CanBookParams, type BookResult, type BookingError, type BookActionParams, type BookChildProps, type BookProps, type BuildCartParams, type BuildDayRangeBookingRequest, type CreatedBookings, type DayRangeBounds, } from './booking/book-action/index.js';
11
11
  export { BookingFormService, BookingFormServiceDefinition, type BookingFormServiceAPI, type BookingFormServiceConfig, } from './booking-form/booking-form.js';
12
12
  export { PaymentService, PaymentServiceDefinition, type PaymentServiceAPI, type PaymentServiceConfig, type PaymentDetails, type PricingServiceSelection, type LineItemAdditionalInfo, type ServicePaymentOptions, loadPaymentConfig, type SuccessPaymentConfigResult, type ErrorPaymentConfigResult, } from './payment/payment.js';
@@ -6,7 +6,7 @@ export { ServiceService, ServiceServiceDefinition, loadServiceServiceInitialData
6
6
  export { BookingService, BookingServiceDefinition, } from './booking/booking.js';
7
7
  export { TimeSlotListService, TimeSlotListServiceDefinition, TimeSlotService, TimeSlotServiceDefinition, ViewMode, loadTimeSlotListConfig, } from './time-slot-list/index.js';
8
8
  export { ServiceListService, ServiceListServiceDefinition, loadServiceListServiceInitialData, } from './service-list/service-list.js';
9
- export { SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from '../api/query-services/index.js';
9
+ export { SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, AvailabilityLocationType, } from '../api/query-services/index.js';
10
10
  export { executeBookAction, canBook, buildBookingRequest, buildCartRequest, buildDayRangeBookingRequest, createBookingsForCart, isDayRangeService, getDayRangeBounds, } from './booking/book-action/index.js';
11
11
  export { BookingFormService, BookingFormServiceDefinition, } from './booking-form/booking-form.js';
12
12
  export { PaymentService, PaymentServiceDefinition, loadPaymentConfig, } from './payment/payment.js';
@@ -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 { Service } from '@wix/auto_sdk_bookings_services';
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
- * Effect subscribes to changes in this signal to trigger re-fetches
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?: Service[];
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<Service[]>;
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
- * Load service list configuration for Server-Side Rendering (SSR).
139
- * This function is designed to be called during the build or server-side rendering phase
140
- * to pre-fetch services data before the React components are hydrated on the client.
141
- * The returned config includes paging metadata with hasNext flag indicating if more services are available.
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 following QueryServicesRequest type, or a URL string (to be implemented)
144
- * @returns A promise that resolves to either a success result with the config or an error result
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
- * // In an Astro page or API route
149
- * import { loadServiceListServiceInitialData } from '@wix/bookings/services';
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
- * if (serviceListResult.type === 'error') {
158
- * console.error('Failed to load services:', serviceListResult.error);
159
- * return Astro.redirect('/500');
160
- * }
161
- *
162
- * const serviceListConfig = serviceListResult.config;
163
- *
164
- * // Check if more services are available
165
- * if (serviceListConfig.options?.pagingMetadata?.hasNext) {
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: QueryServicesRequest): Promise<SuccessServiceListServiceConfigResult | ErrorServiceListServiceConfigResult>;
179
+ export declare function loadServiceListServiceInitialData(options: LoadServiceListServiceOptions): Promise<SuccessServiceListServiceConfigResult | ErrorServiceListServiceConfigResult>;