@wix/headless-bookings 0.0.9 → 0.0.11

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 (69) hide show
  1. package/cjs/dist/api/index.d.ts +10 -1
  2. package/cjs/dist/api/index.js +10 -1
  3. package/cjs/dist/api/query-services/index.d.ts +78 -0
  4. package/cjs/dist/api/query-services/index.js +137 -0
  5. package/cjs/dist/react/core/booking/Booking.d.ts +4 -4
  6. package/cjs/dist/react/core/booking/Booking.js +3 -3
  7. package/cjs/dist/react/core/location/Location.d.ts +44 -0
  8. package/cjs/dist/react/core/location/Location.js +78 -0
  9. package/cjs/dist/react/core/service/Service.d.ts +160 -17
  10. package/cjs/dist/react/core/service/Service.js +211 -13
  11. package/cjs/dist/react/core/service-list/ServiceList.d.ts +75 -0
  12. package/cjs/dist/react/core/service-list/ServiceList.js +58 -0
  13. package/cjs/dist/react/index.d.ts +2 -0
  14. package/cjs/dist/react/index.js +2 -0
  15. package/cjs/dist/react/location/Location.d.ts +77 -0
  16. package/cjs/dist/react/location/Location.js +89 -0
  17. package/cjs/dist/react/service/Service.d.ts +1036 -31
  18. package/cjs/dist/react/service/Service.js +1060 -47
  19. package/cjs/dist/react/service-list/ServiceList.d.ts +185 -0
  20. package/cjs/dist/react/service-list/ServiceList.js +184 -0
  21. package/cjs/dist/services/booking/booking.d.ts +50 -33
  22. package/cjs/dist/services/booking/booking.js +72 -38
  23. package/cjs/dist/services/index.d.ts +3 -2
  24. package/cjs/dist/services/index.js +3 -2
  25. package/cjs/dist/services/service/service.d.ts +71 -18
  26. package/cjs/dist/services/service/service.js +187 -52
  27. package/cjs/dist/services/service-list/service-list.d.ts +157 -0
  28. package/cjs/dist/services/service-list/service-list.js +274 -0
  29. package/cjs/dist/utils/dateAndTime.d.ts +2 -0
  30. package/cjs/dist/utils/dateAndTime.js +18 -0
  31. package/cjs/dist/utils/money.d.ts +21 -0
  32. package/cjs/dist/utils/money.js +34 -0
  33. package/dist/api/index.d.ts +10 -1
  34. package/dist/api/index.js +10 -1
  35. package/dist/api/query-services/index.d.ts +78 -0
  36. package/dist/api/query-services/index.js +137 -0
  37. package/dist/react/core/booking/Booking.d.ts +4 -4
  38. package/dist/react/core/booking/Booking.js +3 -3
  39. package/dist/react/core/location/Location.d.ts +44 -0
  40. package/dist/react/core/location/Location.js +78 -0
  41. package/dist/react/core/service/Service.d.ts +160 -17
  42. package/dist/react/core/service/Service.js +211 -13
  43. package/dist/react/core/service-list/ServiceList.d.ts +75 -0
  44. package/dist/react/core/service-list/ServiceList.js +58 -0
  45. package/dist/react/index.d.ts +2 -0
  46. package/dist/react/index.js +2 -0
  47. package/dist/react/location/Location.d.ts +77 -0
  48. package/dist/react/location/Location.js +89 -0
  49. package/dist/react/service/Service.d.ts +1036 -31
  50. package/dist/react/service/Service.js +1060 -47
  51. package/dist/react/service-list/ServiceList.d.ts +185 -0
  52. package/dist/react/service-list/ServiceList.js +184 -0
  53. package/dist/services/booking/booking.d.ts +50 -33
  54. package/dist/services/booking/booking.js +72 -38
  55. package/dist/services/index.d.ts +3 -2
  56. package/dist/services/index.js +3 -2
  57. package/dist/services/service/service.d.ts +71 -18
  58. package/dist/services/service/service.js +187 -52
  59. package/dist/services/service-list/service-list.d.ts +157 -0
  60. package/dist/services/service-list/service-list.js +274 -0
  61. package/dist/utils/dateAndTime.d.ts +2 -0
  62. package/dist/utils/dateAndTime.js +18 -0
  63. package/dist/utils/money.d.ts +21 -0
  64. package/dist/utils/money.js +34 -0
  65. package/package.json +3 -2
  66. package/cjs/dist/api/query-service/index.d.ts +0 -23
  67. package/cjs/dist/api/query-service/index.js +0 -41
  68. package/dist/api/query-service/index.d.ts +0 -23
  69. package/dist/api/query-service/index.js +0 -41
@@ -2,8 +2,43 @@
2
2
  * Service Service - Reactive service data management
3
3
  * Handles loading and managing a single service's data, loading state, and errors.
4
4
  */
5
- import { type Signal } from '@wix/services-definitions/core-services/signals';
5
+ import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
6
6
  import { services } from '@wix/bookings';
7
+ import type { MoneyData } from '@wix/headless-components/react';
8
+ /**
9
+ * Actions object for service selection
10
+ */
11
+ export interface ServiceActions {
12
+ /** Replace entire selection with this service (with current modifiers) */
13
+ select: () => void;
14
+ /** Add this service to selection (with current modifiers) */
15
+ add: () => void;
16
+ /** Remove this service from selection */
17
+ remove: () => void;
18
+ }
19
+ /**
20
+ * Payment data derived from service payment information
21
+ *
22
+ * @interface ServicePaymentData
23
+ */
24
+ export interface ServicePaymentData {
25
+ /** The service payment information from the service */
26
+ servicePayment: services.Payment | undefined;
27
+ /** The rate type of the service (FIXED, VARIED, CUSTOM, NO_FEE, etc.) */
28
+ rateType: services.Payment['rateType'] | undefined;
29
+ /** Primary price (used for FIXED or VARIED default) */
30
+ money: MoneyData | null;
31
+ /** Minimum price (used for VARIED) */
32
+ minPrice: MoneyData | null;
33
+ /** Maximum price (used for VARIED) */
34
+ maxPrice: MoneyData | null;
35
+ /** Price description text (used for CUSTOM) */
36
+ priceDescription: string | null;
37
+ /** Whether the service has add-ons available */
38
+ hasAddOns: boolean;
39
+ /** Whether the service has pricing plans available */
40
+ hasPricingPlans: boolean;
41
+ }
7
42
  /**
8
43
  * API interface for the Service service, providing reactive service data management.
9
44
  *
@@ -11,19 +46,23 @@ import { services } from '@wix/bookings';
11
46
  */
12
47
  export interface ServiceServiceAPI {
13
48
  /** Reactive signal containing the current service data */
14
- service: Signal<services.Service>;
49
+ serviceSignal: Signal<services.Service>;
15
50
  /** Reactive signal indicating if a service is currently being loaded */
16
- isLoading: Signal<boolean>;
51
+ isLoadingSignal: Signal<boolean>;
17
52
  /** Reactive signal containing any error message, or null if no error */
18
- error: Signal<string | null>;
53
+ errorSignal: Signal<string | null>;
19
54
  /** Function to load a service by its id */
20
- loadService: (id: string) => Promise<void>;
21
- /** Function to select this service (replaces entire selection) */
22
- select: () => void;
23
- /** Function to add this service to the selection */
24
- add: () => void;
25
- /** Function to remove this service from the selection */
26
- remove: () => void;
55
+ loadServiceById: (id: string) => Promise<void>;
56
+ /** Indicates if BookingService is available (false in showcase mode/lead nurturing) */
57
+ bookableSignal: ReadOnlySignal<boolean>;
58
+ /** Indicates if the current service is selected in BookingService */
59
+ selectedSignal: ReadOnlySignal<boolean>;
60
+ /** Indicates if the service requires availability selection (true for APPOINTMENT and CLASS, false for COURSE) */
61
+ requiresAvailabilitySignal: ReadOnlySignal<boolean>;
62
+ /** Reactive signal containing computed payment data derived from service */
63
+ paymentDataSignal: ReadOnlySignal<ServicePaymentData>;
64
+ /** Actions object containing service selection actions (only work when bookable is true) */
65
+ actions: ServiceActions;
27
66
  }
28
67
  /**
29
68
  * Service definition for the Service service.
@@ -47,6 +86,10 @@ export interface ServiceServiceConfig {
47
86
  service?: services.Service;
48
87
  /** The service ID to load if no initial service is provided */
49
88
  serviceId?: string;
89
+ /** The service slug to load if no initial service is provided */
90
+ serviceSlug?: string;
91
+ /** The app ID to filter services by app and determine bookable */
92
+ appId?: string;
50
93
  }
51
94
  /**
52
95
  * Implementation of the Service service that manages reactive service data.
@@ -70,9 +113,9 @@ export interface ServiceServiceConfig {
70
113
  *
71
114
  * function ServiceDisplay() {
72
115
  * const serviceService = useService(ServiceServiceDefinition);
73
- * const service = serviceService.service.get();
74
- * const isLoading = serviceService.isLoading.get();
75
- * const error = serviceService.error.get();
116
+ * const service = serviceService.serviceSignal.get();
117
+ * const isLoading = serviceService.isLoadingSignal.get();
118
+ * const error = serviceService.errorSignal.get();
76
119
  *
77
120
  * if (isLoading) return <div>Loading...</div>;
78
121
  * if (error) return <div>Error: {error}</div>;
@@ -101,15 +144,19 @@ export type NotFoundServiceServiceConfigResult = {
101
144
  * This function is designed to be called during the build or server-side rendering phase
102
145
  * to pre-fetch service data before the React components are hydrated on the client.
103
146
  *
104
- * @param slug - The slug of the service to load (e.g., "group-trainings", "yoga-class")
147
+ * @param params - Either `{ slug: string, appId?: string }` or `{ serviceId: string, appId?: string }`
105
148
  * @returns A promise that resolves to either a success result with the config or a notFound result
106
149
  *
107
150
  * @example
108
151
  * ```ts
109
152
  * // In an Astro page or API route
110
- * import { loadServiceServiceConfig } from '@wix/headless-bookings/services';
153
+ * import { loadServiceServiceInitialData } from '@wix/headless-bookings/services';
154
+ *
155
+ * // Load by slug
156
+ * const serviceResult = await loadServiceServiceInitialData({ slug: 'group-trainings' });
111
157
  *
112
- * const serviceResult = await loadServiceServiceConfig('group-trainings');
158
+ * // Load by serviceId
159
+ * const serviceResult = await loadServiceServiceInitialData({ serviceId: 'service-id-123' });
113
160
  *
114
161
  * if (serviceResult.type === 'notFound') {
115
162
  * return Astro.redirect('/404');
@@ -118,4 +165,10 @@ export type NotFoundServiceServiceConfigResult = {
118
165
  * const serviceConfig = serviceResult.config;
119
166
  * ```
120
167
  */
121
- export declare function loadServiceServiceConfig(slug: string): Promise<SuccessServiceServiceConfigResult | NotFoundServiceServiceConfigResult>;
168
+ export declare function loadServiceServiceInitialData(params: {
169
+ slug: string;
170
+ appId?: string;
171
+ } | {
172
+ serviceId: string;
173
+ appId?: string;
174
+ }): Promise<SuccessServiceServiceConfigResult | NotFoundServiceServiceConfigResult>;
@@ -4,8 +4,14 @@
4
4
  */
5
5
  import { defineService, implementService } from '@wix/services-definitions';
6
6
  import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
7
- import { getServiceById, getServiceBySlug, } from '../../api/query-service/index.js';
8
- import { BookingServiceDefinition } from '../booking/booking.js';
7
+ import { services } from '@wix/bookings';
8
+ import { getServiceById, getServiceBySlug, } from '../../api/query-services/index.js';
9
+ import { BookingServiceDefinition, } from '../booking/booking.js';
10
+ import { convertToMoneyData } from '../../utils/money.js';
11
+ /**
12
+ * Booking app ID constant
13
+ */
14
+ const BOOKING_APP_ID = '13d21c63-b5ec-5912-8397-c3a5ddb27a97';
9
15
  /**
10
16
  * Service definition for the Service service.
11
17
  * This defines the contract that the ServiceService must implement.
@@ -35,9 +41,9 @@ export const ServiceServiceDefinition = defineService('service');
35
41
  *
36
42
  * function ServiceDisplay() {
37
43
  * const serviceService = useService(ServiceServiceDefinition);
38
- * const service = serviceService.service.get();
39
- * const isLoading = serviceService.isLoading.get();
40
- * const error = serviceService.error.get();
44
+ * const service = serviceService.serviceSignal.get();
45
+ * const isLoading = serviceService.isLoadingSignal.get();
46
+ * const error = serviceService.errorSignal.get();
41
47
  *
42
48
  * if (isLoading) return <div>Loading...</div>;
43
49
  * if (error) return <div>Error: {error}</div>;
@@ -48,55 +54,170 @@ export const ServiceServiceDefinition = defineService('service');
48
54
  */
49
55
  export const ServiceService = implementService.withConfig()(ServiceServiceDefinition, ({ getService, config }) => {
50
56
  const signalsService = getService(SignalsServiceDefinition);
51
- const bookingService = getService(BookingServiceDefinition);
52
- const service = signalsService.signal(config.service);
53
- const isLoading = signalsService.signal(!!config.serviceId);
54
- const error = signalsService.signal(null);
55
- const loadService = async (id) => {
56
- isLoading.set(true);
57
- error.set(null);
58
- const serviceResponse = await getServiceById(id);
57
+ // Try to get BookingService (optional dependency for showcase mode)
58
+ let bookingService = null;
59
+ try {
60
+ bookingService = getService(BookingServiceDefinition);
61
+ }
62
+ catch {
63
+ // BookingService not available - showcase mode
64
+ bookingService = null;
65
+ }
66
+ // Initialize service signal - use empty service object if not provided
67
+ const initialService = config.service || {};
68
+ const serviceSignal = signalsService.signal(initialService);
69
+ const isLoadingSignal = signalsService.signal(!!config.serviceId);
70
+ const errorSignal = signalsService.signal(null);
71
+ // Default appId to BOOKING_APP_ID if not provided
72
+ const appId = config.appId ?? BOOKING_APP_ID;
73
+ // bookable signal - true if appId equals booking app ID
74
+ const bookableSignal = signalsService.computed(() => {
75
+ return appId === BOOKING_APP_ID;
76
+ });
77
+ // selected signal - true if current service is in BookingService preferences
78
+ const selectedSignal = signalsService.computed(() => {
79
+ if (!bookingService)
80
+ return false;
81
+ const currentService = serviceSignal.get();
82
+ if (!currentService._id)
83
+ return false;
84
+ const services = bookingService.services.get();
85
+ return services.some((selection) => selection.service._id === currentService._id);
86
+ });
87
+ // requiresAvailability signal - true for APPOINTMENT and CLASS types
88
+ const requiresAvailabilitySignal = signalsService.computed(() => {
89
+ const currentService = serviceSignal.get();
90
+ const serviceType = currentService.type;
91
+ return (serviceType === services.ServiceType.APPOINTMENT ||
92
+ serviceType === services.ServiceType.CLASS);
93
+ });
94
+ // paymentData signal - computed payment data from service
95
+ const paymentDataSignal = signalsService.computed(() => {
96
+ const service = serviceSignal.get();
97
+ const payment = service.payment;
98
+ const rateType = payment?.rateType;
99
+ // Initialize all price-related values
100
+ let money = null;
101
+ let minPrice = null;
102
+ let maxPrice = null;
103
+ let priceDescription = null;
104
+ // Extract pricing based on rate type
105
+ if (payment) {
106
+ switch (rateType) {
107
+ case services.RateType.FIXED:
108
+ // FIXED: Use payment.fixed.price
109
+ money = convertToMoneyData(payment.fixed?.price);
110
+ break;
111
+ case services.RateType.VARIED:
112
+ // VARIED: Use defaultPrice as money, plus min/max for range
113
+ money = convertToMoneyData(payment.varied?.defaultPrice);
114
+ minPrice = convertToMoneyData(payment.varied?.minPrice);
115
+ maxPrice = convertToMoneyData(payment.varied?.maxPrice);
116
+ break;
117
+ case services.RateType.CUSTOM:
118
+ // CUSTOM: Use description text (no numeric price)
119
+ priceDescription = payment.custom?.description || null;
120
+ break;
121
+ case services.RateType.NO_FEE:
122
+ // NO_FEE: Free service (amount = 0)
123
+ money = { amount: 0, currency: 'USD' };
124
+ break;
125
+ default:
126
+ // UNKNOWN_RATE_TYPE or undefined - leave all null
127
+ break;
128
+ }
129
+ }
130
+ // Derive service capabilities
131
+ const hasAddOns = (service.addOnGroups?.length ?? 0) > 0;
132
+ const hasPricingPlans = (payment?.pricingPlanIds?.length ?? 0) > 0;
133
+ return {
134
+ servicePayment: payment,
135
+ rateType,
136
+ money,
137
+ minPrice,
138
+ maxPrice,
139
+ priceDescription,
140
+ hasAddOns,
141
+ hasPricingPlans,
142
+ };
143
+ });
144
+ const loadServiceById = async (id) => {
145
+ isLoadingSignal.set(true);
146
+ errorSignal.set(null);
147
+ const serviceResponse = await getServiceById(id, appId);
59
148
  if (!serviceResponse.service) {
60
- error.set('Service not found');
61
- isLoading.set(false);
149
+ errorSignal.set('Service not found');
150
+ isLoadingSignal.set(false);
62
151
  }
63
152
  else {
64
- service.set(serviceResponse.service);
65
- error.set(null);
66
- isLoading.set(false);
153
+ serviceSignal.set(serviceResponse.service);
154
+ errorSignal.set(null);
155
+ isLoadingSignal.set(false);
67
156
  }
68
157
  };
69
- // Auto-load if serviceId is provided but no service data
70
- if (config.serviceId && !config.service) {
71
- loadService(config.serviceId);
72
- }
73
- // Action methods for booking
74
- const select = () => {
75
- const currentService = service.get();
76
- bookingService.selectedServices.set([currentService]);
77
- };
78
- const add = () => {
79
- const currentService = service.get();
80
- const selectedServices = bookingService.selectedServices.get();
81
- bookingService.selectedServices.set([
82
- ...selectedServices,
83
- currentService,
84
- ]);
158
+ const loadServiceBySlug = async (slug) => {
159
+ isLoadingSignal.set(true);
160
+ errorSignal.set(null);
161
+ const serviceResponse = await getServiceBySlug(slug, appId);
162
+ if (!serviceResponse.service) {
163
+ errorSignal.set('Service not found');
164
+ isLoadingSignal.set(false);
165
+ }
166
+ else {
167
+ serviceSignal.set(serviceResponse.service);
168
+ errorSignal.set(null);
169
+ isLoadingSignal.set(false);
170
+ }
85
171
  };
86
- const remove = () => {
87
- const currentService = service.get();
88
- const selectedServices = bookingService.selectedServices.get();
89
- const filtered = selectedServices.filter((s) => s._id !== currentService._id);
90
- bookingService.selectedServices.set(filtered);
172
+ // Auto-load if serviceId or serviceSlug is provided but no service data
173
+ // Priority: service > serviceSlug > serviceId
174
+ if (!config.service) {
175
+ if (config.serviceSlug) {
176
+ loadServiceBySlug(config.serviceSlug);
177
+ }
178
+ else if (config.serviceId) {
179
+ loadServiceById(config.serviceId);
180
+ }
181
+ }
182
+ // Action methods for booking - only work when bookable is true
183
+ const actions = {
184
+ select: () => {
185
+ if (!bookableSignal.get() || !bookingService)
186
+ return;
187
+ const currentService = serviceSignal.get();
188
+ const selection = {
189
+ service: currentService,
190
+ };
191
+ bookingService.actions.setServices([selection]);
192
+ },
193
+ add: () => {
194
+ if (!bookableSignal.get() || !bookingService)
195
+ return;
196
+ const currentService = serviceSignal.get();
197
+ const selection = {
198
+ service: currentService,
199
+ };
200
+ bookingService.actions.addService(selection);
201
+ },
202
+ remove: () => {
203
+ if (!bookableSignal.get() || !bookingService)
204
+ return;
205
+ const currentService = serviceSignal.get();
206
+ if (currentService._id) {
207
+ bookingService.actions.removeService(currentService._id);
208
+ }
209
+ },
91
210
  };
92
211
  return {
93
- service,
94
- isLoading,
95
- error,
96
- loadService,
97
- select,
98
- add,
99
- remove,
212
+ serviceSignal,
213
+ isLoadingSignal,
214
+ errorSignal,
215
+ loadServiceById,
216
+ bookableSignal,
217
+ selectedSignal,
218
+ requiresAvailabilitySignal,
219
+ paymentDataSignal,
220
+ actions,
100
221
  };
101
222
  });
102
223
  /**
@@ -104,15 +225,19 @@ export const ServiceService = implementService.withConfig()(ServiceServiceDefini
104
225
  * This function is designed to be called during the build or server-side rendering phase
105
226
  * to pre-fetch service data before the React components are hydrated on the client.
106
227
  *
107
- * @param slug - The slug of the service to load (e.g., "group-trainings", "yoga-class")
228
+ * @param params - Either `{ slug: string, appId?: string }` or `{ serviceId: string, appId?: string }`
108
229
  * @returns A promise that resolves to either a success result with the config or a notFound result
109
230
  *
110
231
  * @example
111
232
  * ```ts
112
233
  * // In an Astro page or API route
113
- * import { loadServiceServiceConfig } from '@wix/headless-bookings/services';
234
+ * import { loadServiceServiceInitialData } from '@wix/headless-bookings/services';
235
+ *
236
+ * // Load by slug
237
+ * const serviceResult = await loadServiceServiceInitialData({ slug: 'group-trainings' });
114
238
  *
115
- * const serviceResult = await loadServiceServiceConfig('group-trainings');
239
+ * // Load by serviceId
240
+ * const serviceResult = await loadServiceServiceInitialData({ serviceId: 'service-id-123' });
116
241
  *
117
242
  * if (serviceResult.type === 'notFound') {
118
243
  * return Astro.redirect('/404');
@@ -121,9 +246,17 @@ export const ServiceService = implementService.withConfig()(ServiceServiceDefini
121
246
  * const serviceConfig = serviceResult.config;
122
247
  * ```
123
248
  */
124
- export async function loadServiceServiceConfig(slug) {
249
+ export async function loadServiceServiceInitialData(params) {
125
250
  try {
126
- const serviceResponse = await getServiceBySlug(slug);
251
+ // Default appId to BOOKING_APP_ID if not provided
252
+ const finalAppId = params.appId ?? BOOKING_APP_ID;
253
+ let serviceResponse;
254
+ if ('slug' in params) {
255
+ serviceResponse = await getServiceBySlug(params.slug, finalAppId);
256
+ }
257
+ else {
258
+ serviceResponse = await getServiceById(params.serviceId, finalAppId);
259
+ }
127
260
  if (!serviceResponse.service) {
128
261
  return { type: 'notFound' };
129
262
  }
@@ -131,11 +264,13 @@ export async function loadServiceServiceConfig(slug) {
131
264
  type: 'success',
132
265
  config: {
133
266
  service: serviceResponse.service,
267
+ appId: finalAppId,
134
268
  },
135
269
  };
136
270
  }
137
271
  catch (error) {
138
- console.error(`Failed to load service for slug "${slug}":`, error);
272
+ const identifier = 'slug' in params ? `slug "${params.slug}"` : `id "${params.serviceId}"`;
273
+ console.error(`Failed to load service for ${identifier}:`, error);
139
274
  return { type: 'notFound' };
140
275
  }
141
276
  }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * ServiceList Service - Reactive service list data management
3
+ * Handles querying and pagination of services list (NO selection management)
4
+ */
5
+ import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
6
+ import { services } from '@wix/bookings';
7
+ import type { QueryServicesRequest, PagingMetadata } from '../../api/query-services/index.js';
8
+ export type { ServiceListFilters } from '../../api/query-services/index.js';
9
+ /**
10
+ * Query options without paging metadata (user-controlled request params)
11
+ * Effect subscribes to changes in this signal to trigger re-fetches
12
+ */
13
+ export type QueryOptions = Omit<QueryServicesRequest, 'pagingMetadata'>;
14
+ /**
15
+ * Configuration for the ServiceList service
16
+ */
17
+ export interface ServiceListServiceConfig {
18
+ services?: services.Service[];
19
+ options?: QueryServicesRequest;
20
+ }
21
+ /**
22
+ * Actions object for service list operations
23
+ */
24
+ export interface ServiceListActions {
25
+ loadMore: (count: number) => Promise<void>;
26
+ setQueryOptions: (options: QueryOptions) => void;
27
+ }
28
+ /**
29
+ * API interface for the ServiceList service
30
+ */
31
+ export interface ServiceListServiceAPI {
32
+ /** Reactive signal containing the list of services */
33
+ servicesSignal: Signal<services.Service[]>;
34
+ /** Reactive signal containing current query options (appId, filters, sort) - changes trigger re-fetch */
35
+ queryOptionsSignal: Signal<QueryOptions>;
36
+ /** Reactive signal containing pagination metadata (limit, offset, count, hasNext) - updated after fetches */
37
+ pagingMetadataSignal: Signal<PagingMetadata>;
38
+ /** Reactive signal indicating if services are currently being loaded */
39
+ isLoadingSignal: Signal<boolean>;
40
+ /** Reactive signal containing any error message, or null if no error */
41
+ errorSignal: Signal<string | null>;
42
+ /** Actions object containing query actions */
43
+ actions: ServiceListActions;
44
+ /** Reactive signal indicating if there are more services to load */
45
+ hasMoreServicesSignal: ReadOnlySignal<boolean>;
46
+ }
47
+ /**
48
+ * Service definition for the ServiceList service
49
+ */
50
+ export declare const ServiceListServiceDefinition: string & {
51
+ __api: ServiceListServiceAPI;
52
+ __config: ServiceListServiceConfig;
53
+ isServiceDefinition?: boolean;
54
+ } & ServiceListServiceAPI;
55
+ /**
56
+ * Implementation of the ServiceList service that manages reactive services list data.
57
+ * This service provides signals for services data, query options, pagination metadata
58
+ * (including hasNext flag), loading state, and error handling.
59
+ * It automatically re-fetches services when query options change.
60
+ *
61
+ * @example
62
+ * ```tsx
63
+ * import { ServiceListService, ServiceListServiceDefinition } from '@wix/bookings/services';
64
+ * import { useService } from '@wix/services-manager-react';
65
+ *
66
+ * function ServicesComponent({ serviceListConfig }) {
67
+ * return (
68
+ * <ServiceProvider services={createServicesMap([
69
+ * [ServiceListServiceDefinition, ServiceListService.withConfig(serviceListConfig)]
70
+ * ])}>
71
+ * <ServicesDisplay />
72
+ * </ServiceProvider>
73
+ * );
74
+ * }
75
+ *
76
+ * function ServicesDisplay() {
77
+ * const serviceListService = useService(ServiceListServiceDefinition);
78
+ * const services = serviceListService.servicesSignal.get();
79
+ * const isLoading = serviceListService.isLoadingSignal.get();
80
+ * const error = serviceListService.errorSignal.get();
81
+ * const pagingMetadata = serviceListService.pagingMetadataSignal.get();
82
+ * const hasMore = serviceListService.hasMoreServicesSignal.get();
83
+ *
84
+ * if (isLoading) return <div>Loading services...</div>;
85
+ * if (error) return <div>Error: {error}</div>;
86
+ *
87
+ * return (
88
+ * <div>
89
+ * {services.map(service => (
90
+ * <div key={service._id}>
91
+ * <h3>{service.name}</h3>
92
+ * <p>{service.description}</p>
93
+ * </div>
94
+ * ))}
95
+ * {hasMore && (
96
+ * <button onClick={() => serviceListService.actions.loadMore(10)}>
97
+ * Load More
98
+ * </button>
99
+ * )}
100
+ * {pagingMetadata.hasNext && (
101
+ * <span>More services available</span>
102
+ * )}
103
+ * </div>
104
+ * );
105
+ * }
106
+ * ```
107
+ */
108
+ export declare const ServiceListService: import("@wix/services-definitions").ServiceFactory<string & {
109
+ __api: ServiceListServiceAPI;
110
+ __config: ServiceListServiceConfig;
111
+ isServiceDefinition?: boolean;
112
+ } & ServiceListServiceAPI, ServiceListServiceConfig>;
113
+ /**
114
+ * Result types for service list loading operations
115
+ */
116
+ export type SuccessServiceListServiceConfigResult = {
117
+ type: 'success';
118
+ config: ServiceListServiceConfig;
119
+ };
120
+ export type ErrorServiceListServiceConfigResult = {
121
+ type: 'error';
122
+ error: Error;
123
+ };
124
+ /**
125
+ * Load service list configuration for Server-Side Rendering (SSR).
126
+ * This function is designed to be called during the build or server-side rendering phase
127
+ * to pre-fetch services data before the React components are hydrated on the client.
128
+ * The returned config includes paging metadata with hasNext flag indicating if more services are available.
129
+ *
130
+ * @param options - Query options following QueryServicesRequest type, or a URL string (to be implemented)
131
+ * @returns A promise that resolves to either a success result with the config or an error result
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * // In an Astro page or API route
136
+ * import { loadServiceListServiceInitialData } from '@wix/bookings/services';
137
+ *
138
+ * // Load with query options
139
+ * const serviceListResult = await loadServiceListServiceInitialData({
140
+ * appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
141
+ * pagingMetadata: { limit: 20, offset: 0 },
142
+ * });
143
+ *
144
+ * if (serviceListResult.type === 'error') {
145
+ * console.error('Failed to load services:', serviceListResult.error);
146
+ * return Astro.redirect('/500');
147
+ * }
148
+ *
149
+ * const serviceListConfig = serviceListResult.config;
150
+ *
151
+ * // Check if more services are available
152
+ * if (serviceListConfig.options?.pagingMetadata?.hasNext) {
153
+ * // More services can be loaded
154
+ * }
155
+ * ```
156
+ */
157
+ export declare function loadServiceListServiceInitialData(options: QueryServicesRequest): Promise<SuccessServiceListServiceConfigResult | ErrorServiceListServiceConfigResult>;