@wix/headless-bookings 0.0.10 → 0.0.12
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/api/index.d.ts +10 -1
- package/cjs/dist/api/index.js +10 -1
- package/cjs/dist/api/query-services/index.d.ts +78 -0
- package/cjs/dist/api/query-services/index.js +137 -0
- package/cjs/dist/react/core/booking/Booking.d.ts +5 -5
- package/cjs/dist/react/core/booking/Booking.js +5 -5
- package/cjs/dist/react/core/location/Location.d.ts +44 -0
- package/cjs/dist/react/core/location/Location.js +78 -0
- package/cjs/dist/react/core/service/Service.d.ts +160 -17
- package/cjs/dist/react/core/service/Service.js +211 -13
- package/cjs/dist/react/core/service-list/ServiceList.d.ts +75 -0
- package/cjs/dist/react/core/service-list/ServiceList.js +58 -0
- package/cjs/dist/react/index.d.ts +2 -0
- package/cjs/dist/react/index.js +2 -0
- package/cjs/dist/react/location/Location.d.ts +77 -0
- package/cjs/dist/react/location/Location.js +89 -0
- package/cjs/dist/react/service/Service.d.ts +1036 -31
- package/cjs/dist/react/service/Service.js +1060 -47
- package/cjs/dist/react/service-list/ServiceList.d.ts +185 -0
- package/cjs/dist/react/service-list/ServiceList.js +184 -0
- package/cjs/dist/services/booking/booking.d.ts +19 -11
- package/cjs/dist/services/booking/booking.js +48 -16
- package/cjs/dist/services/index.d.ts +3 -2
- package/cjs/dist/services/index.js +2 -1
- package/cjs/dist/services/service/service.d.ts +71 -18
- package/cjs/dist/services/service/service.js +186 -51
- package/cjs/dist/services/service-list/service-list.d.ts +157 -0
- package/cjs/dist/services/service-list/service-list.js +274 -0
- package/cjs/dist/utils/dateAndTime.d.ts +2 -0
- package/cjs/dist/utils/dateAndTime.js +18 -0
- package/cjs/dist/utils/money.d.ts +21 -0
- package/cjs/dist/utils/money.js +34 -0
- package/dist/api/index.d.ts +10 -1
- package/dist/api/index.js +10 -1
- package/dist/api/query-services/index.d.ts +78 -0
- package/dist/api/query-services/index.js +137 -0
- package/dist/react/core/booking/Booking.d.ts +5 -5
- package/dist/react/core/booking/Booking.js +5 -5
- package/dist/react/core/location/Location.d.ts +44 -0
- package/dist/react/core/location/Location.js +78 -0
- package/dist/react/core/service/Service.d.ts +160 -17
- package/dist/react/core/service/Service.js +211 -13
- package/dist/react/core/service-list/ServiceList.d.ts +75 -0
- package/dist/react/core/service-list/ServiceList.js +58 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +2 -0
- package/dist/react/location/Location.d.ts +77 -0
- package/dist/react/location/Location.js +89 -0
- package/dist/react/service/Service.d.ts +1036 -31
- package/dist/react/service/Service.js +1060 -47
- package/dist/react/service-list/ServiceList.d.ts +185 -0
- package/dist/react/service-list/ServiceList.js +184 -0
- package/dist/services/booking/booking.d.ts +19 -11
- package/dist/services/booking/booking.js +48 -16
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +2 -1
- package/dist/services/service/service.d.ts +71 -18
- package/dist/services/service/service.js +186 -51
- package/dist/services/service-list/service-list.d.ts +157 -0
- package/dist/services/service-list/service-list.js +274 -0
- package/dist/utils/dateAndTime.d.ts +2 -0
- package/dist/utils/dateAndTime.js +18 -0
- package/dist/utils/money.d.ts +21 -0
- package/dist/utils/money.js +34 -0
- package/package.json +2 -2
- package/cjs/dist/api/query-service/index.d.ts +0 -23
- package/cjs/dist/api/query-service/index.js +0 -41
- package/dist/api/query-service/index.d.ts +0 -23
- 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
|
-
|
|
49
|
+
serviceSignal: Signal<services.Service>;
|
|
15
50
|
/** Reactive signal indicating if a service is currently being loaded */
|
|
16
|
-
|
|
51
|
+
isLoadingSignal: Signal<boolean>;
|
|
17
52
|
/** Reactive signal containing any error message, or null if no error */
|
|
18
|
-
|
|
53
|
+
errorSignal: Signal<string | null>;
|
|
19
54
|
/** Function to load a service by its id */
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
|
|
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.
|
|
74
|
-
* const isLoading = serviceService.
|
|
75
|
-
* const error = serviceService.
|
|
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
|
|
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 {
|
|
153
|
+
* import { loadServiceServiceInitialData } from '@wix/headless-bookings/services';
|
|
154
|
+
*
|
|
155
|
+
* // Load by slug
|
|
156
|
+
* const serviceResult = await loadServiceServiceInitialData({ slug: 'group-trainings' });
|
|
111
157
|
*
|
|
112
|
-
*
|
|
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
|
|
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 {
|
|
8
|
-
import {
|
|
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.
|
|
39
|
-
* const isLoading = serviceService.
|
|
40
|
-
* const error = serviceService.
|
|
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,54 +54,169 @@ export const ServiceServiceDefinition = defineService('service');
|
|
|
48
54
|
*/
|
|
49
55
|
export const ServiceService = implementService.withConfig()(ServiceServiceDefinition, ({ getService, config }) => {
|
|
50
56
|
const signalsService = getService(SignalsServiceDefinition);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 selections = bookingService.bookingSelections.get();
|
|
85
|
+
return selections.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
|
-
|
|
61
|
-
|
|
149
|
+
errorSignal.set('Service not found');
|
|
150
|
+
isLoadingSignal.set(false);
|
|
62
151
|
}
|
|
63
152
|
else {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
153
|
+
serviceSignal.set(serviceResponse.service);
|
|
154
|
+
errorSignal.set(null);
|
|
155
|
+
isLoadingSignal.set(false);
|
|
67
156
|
}
|
|
68
157
|
};
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const selection = { service: currentService };
|
|
83
|
-
bookingService.services.set([...selections, selection]);
|
|
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
|
+
}
|
|
84
171
|
};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
+
bookingService.actions.setService({ service: currentService });
|
|
189
|
+
},
|
|
190
|
+
add: () => {
|
|
191
|
+
if (!bookableSignal.get() || !bookingService)
|
|
192
|
+
return;
|
|
193
|
+
const currentService = serviceSignal.get();
|
|
194
|
+
bookingService.actions.addService({ service: currentService });
|
|
195
|
+
},
|
|
196
|
+
remove: () => {
|
|
197
|
+
if (!bookableSignal.get() || !bookingService)
|
|
198
|
+
return;
|
|
199
|
+
const currentService = serviceSignal.get();
|
|
200
|
+
// Remove all instances of this service (by serviceId)
|
|
201
|
+
const selections = bookingService.bookingSelections.get();
|
|
202
|
+
const instancesToRemove = selections
|
|
203
|
+
.filter((s) => s.service._id === currentService._id)
|
|
204
|
+
.map((s) => s.instanceId);
|
|
205
|
+
instancesToRemove.forEach((instanceId) => {
|
|
206
|
+
bookingService.actions.removeServiceByInstanceId(instanceId);
|
|
207
|
+
});
|
|
208
|
+
},
|
|
90
209
|
};
|
|
91
210
|
return {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
211
|
+
serviceSignal,
|
|
212
|
+
isLoadingSignal,
|
|
213
|
+
errorSignal,
|
|
214
|
+
loadServiceById,
|
|
215
|
+
bookableSignal,
|
|
216
|
+
selectedSignal,
|
|
217
|
+
requiresAvailabilitySignal,
|
|
218
|
+
paymentDataSignal,
|
|
219
|
+
actions,
|
|
99
220
|
};
|
|
100
221
|
});
|
|
101
222
|
/**
|
|
@@ -103,15 +224,19 @@ export const ServiceService = implementService.withConfig()(ServiceServiceDefini
|
|
|
103
224
|
* This function is designed to be called during the build or server-side rendering phase
|
|
104
225
|
* to pre-fetch service data before the React components are hydrated on the client.
|
|
105
226
|
*
|
|
106
|
-
* @param
|
|
227
|
+
* @param params - Either `{ slug: string, appId?: string }` or `{ serviceId: string, appId?: string }`
|
|
107
228
|
* @returns A promise that resolves to either a success result with the config or a notFound result
|
|
108
229
|
*
|
|
109
230
|
* @example
|
|
110
231
|
* ```ts
|
|
111
232
|
* // In an Astro page or API route
|
|
112
|
-
* import {
|
|
233
|
+
* import { loadServiceServiceInitialData } from '@wix/headless-bookings/services';
|
|
234
|
+
*
|
|
235
|
+
* // Load by slug
|
|
236
|
+
* const serviceResult = await loadServiceServiceInitialData({ slug: 'group-trainings' });
|
|
113
237
|
*
|
|
114
|
-
*
|
|
238
|
+
* // Load by serviceId
|
|
239
|
+
* const serviceResult = await loadServiceServiceInitialData({ serviceId: 'service-id-123' });
|
|
115
240
|
*
|
|
116
241
|
* if (serviceResult.type === 'notFound') {
|
|
117
242
|
* return Astro.redirect('/404');
|
|
@@ -120,9 +245,17 @@ export const ServiceService = implementService.withConfig()(ServiceServiceDefini
|
|
|
120
245
|
* const serviceConfig = serviceResult.config;
|
|
121
246
|
* ```
|
|
122
247
|
*/
|
|
123
|
-
export async function
|
|
248
|
+
export async function loadServiceServiceInitialData(params) {
|
|
124
249
|
try {
|
|
125
|
-
|
|
250
|
+
// Default appId to BOOKING_APP_ID if not provided
|
|
251
|
+
const finalAppId = params.appId ?? BOOKING_APP_ID;
|
|
252
|
+
let serviceResponse;
|
|
253
|
+
if ('slug' in params) {
|
|
254
|
+
serviceResponse = await getServiceBySlug(params.slug, finalAppId);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
serviceResponse = await getServiceById(params.serviceId, finalAppId);
|
|
258
|
+
}
|
|
126
259
|
if (!serviceResponse.service) {
|
|
127
260
|
return { type: 'notFound' };
|
|
128
261
|
}
|
|
@@ -130,11 +263,13 @@ export async function loadServiceServiceConfig(slug) {
|
|
|
130
263
|
type: 'success',
|
|
131
264
|
config: {
|
|
132
265
|
service: serviceResponse.service,
|
|
266
|
+
appId: finalAppId,
|
|
133
267
|
},
|
|
134
268
|
};
|
|
135
269
|
}
|
|
136
270
|
catch (error) {
|
|
137
|
-
|
|
271
|
+
const identifier = 'slug' in params ? `slug "${params.slug}"` : `id "${params.serviceId}"`;
|
|
272
|
+
console.error(`Failed to load service for ${identifier}:`, error);
|
|
138
273
|
return { type: 'notFound' };
|
|
139
274
|
}
|
|
140
275
|
}
|
|
@@ -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>;
|