@wix/headless-bookings 0.0.10 → 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.
- 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/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/index.d.ts +2 -1
- 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 -50
- 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/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/index.d.ts +2 -1
- package/dist/services/index.js +2 -1
- package/dist/services/service/service.d.ts +71 -18
- package/dist/services/service/service.js +186 -50
- 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
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ServiceList Service - Reactive service list data management
|
|
3
|
+
* Handles querying and pagination of services list (NO selection management)
|
|
4
|
+
*/
|
|
5
|
+
import { defineService, implementService } from '@wix/services-definitions';
|
|
6
|
+
import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
|
|
7
|
+
import { queryServices } from '../../api/query-services/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Booking app ID constant
|
|
10
|
+
*/
|
|
11
|
+
const BOOKING_APP_ID = '13d21c63-b5ec-5912-8397-c3a5ddb27a97';
|
|
12
|
+
/**
|
|
13
|
+
* Default query limit
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_QUERY_LIMIT = 100;
|
|
16
|
+
/**
|
|
17
|
+
* Service definition for the ServiceList service
|
|
18
|
+
*/
|
|
19
|
+
export const ServiceListServiceDefinition = defineService('service-list');
|
|
20
|
+
/**
|
|
21
|
+
* Implementation of the ServiceList service that manages reactive services list data.
|
|
22
|
+
* This service provides signals for services data, query options, pagination metadata
|
|
23
|
+
* (including hasNext flag), loading state, and error handling.
|
|
24
|
+
* It automatically re-fetches services when query options change.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* import { ServiceListService, ServiceListServiceDefinition } from '@wix/bookings/services';
|
|
29
|
+
* import { useService } from '@wix/services-manager-react';
|
|
30
|
+
*
|
|
31
|
+
* function ServicesComponent({ serviceListConfig }) {
|
|
32
|
+
* return (
|
|
33
|
+
* <ServiceProvider services={createServicesMap([
|
|
34
|
+
* [ServiceListServiceDefinition, ServiceListService.withConfig(serviceListConfig)]
|
|
35
|
+
* ])}>
|
|
36
|
+
* <ServicesDisplay />
|
|
37
|
+
* </ServiceProvider>
|
|
38
|
+
* );
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* function ServicesDisplay() {
|
|
42
|
+
* const serviceListService = useService(ServiceListServiceDefinition);
|
|
43
|
+
* const services = serviceListService.servicesSignal.get();
|
|
44
|
+
* const isLoading = serviceListService.isLoadingSignal.get();
|
|
45
|
+
* const error = serviceListService.errorSignal.get();
|
|
46
|
+
* const pagingMetadata = serviceListService.pagingMetadataSignal.get();
|
|
47
|
+
* const hasMore = serviceListService.hasMoreServicesSignal.get();
|
|
48
|
+
*
|
|
49
|
+
* if (isLoading) return <div>Loading services...</div>;
|
|
50
|
+
* if (error) return <div>Error: {error}</div>;
|
|
51
|
+
*
|
|
52
|
+
* return (
|
|
53
|
+
* <div>
|
|
54
|
+
* {services.map(service => (
|
|
55
|
+
* <div key={service._id}>
|
|
56
|
+
* <h3>{service.name}</h3>
|
|
57
|
+
* <p>{service.description}</p>
|
|
58
|
+
* </div>
|
|
59
|
+
* ))}
|
|
60
|
+
* {hasMore && (
|
|
61
|
+
* <button onClick={() => serviceListService.actions.loadMore(10)}>
|
|
62
|
+
* Load More
|
|
63
|
+
* </button>
|
|
64
|
+
* )}
|
|
65
|
+
* {pagingMetadata.hasNext && (
|
|
66
|
+
* <span>More services available</span>
|
|
67
|
+
* )}
|
|
68
|
+
* </div>
|
|
69
|
+
* );
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export const ServiceListService = implementService.withConfig()(ServiceListServiceDefinition, ({ getService, config }) => {
|
|
74
|
+
let firstRun = true;
|
|
75
|
+
const signalsService = getService(SignalsServiceDefinition);
|
|
76
|
+
// Default appId to BOOKING_APP_ID if not provided
|
|
77
|
+
const appId = config.options?.appId ?? BOOKING_APP_ID;
|
|
78
|
+
// Check if services are provided (showcase mode vs query mode)
|
|
79
|
+
const hasInitialServices = config.services && config.services.length > 0;
|
|
80
|
+
// Initialize services signal - use empty array if not provided
|
|
81
|
+
const initialServices = config.services || [];
|
|
82
|
+
const servicesSignal = signalsService.signal(initialServices);
|
|
83
|
+
// Initialize query options based on mode:
|
|
84
|
+
// - If services provided: reset to defaults (appId only) - showcase mode
|
|
85
|
+
// - If no services: use full queryOptions from config - query mode
|
|
86
|
+
const initialQueryOptions = hasInitialServices
|
|
87
|
+
? { appId } // Reset to defaults when services are provided
|
|
88
|
+
: {
|
|
89
|
+
appId,
|
|
90
|
+
filters: config.options?.filters,
|
|
91
|
+
sort: config.options?.sort,
|
|
92
|
+
};
|
|
93
|
+
// Initialize paging metadata separately (response data - doesn't trigger effect)
|
|
94
|
+
const initialPagingMetadata = config.options
|
|
95
|
+
?.pagingMetadata || {
|
|
96
|
+
limit: DEFAULT_QUERY_LIMIT,
|
|
97
|
+
offset: 0,
|
|
98
|
+
count: initialServices.length,
|
|
99
|
+
hasNext: false,
|
|
100
|
+
};
|
|
101
|
+
// Ensure count and hasNext are set
|
|
102
|
+
if (!initialPagingMetadata.count) {
|
|
103
|
+
initialPagingMetadata.count = initialServices.length;
|
|
104
|
+
}
|
|
105
|
+
if (initialPagingMetadata.hasNext === undefined) {
|
|
106
|
+
initialPagingMetadata.hasNext = false;
|
|
107
|
+
}
|
|
108
|
+
// Query options signal - effect subscribes to this, changes trigger re-fetch
|
|
109
|
+
const queryOptionsSignal = signalsService.signal(initialQueryOptions);
|
|
110
|
+
// Paging metadata signal - updated after fetches, does NOT trigger effect
|
|
111
|
+
const pagingMetadataSignal = signalsService.signal(initialPagingMetadata);
|
|
112
|
+
const isLoadingSignal = signalsService.signal(false);
|
|
113
|
+
const errorSignal = signalsService.signal(null);
|
|
114
|
+
// Auto-query on queryOptions changes (client-side only)
|
|
115
|
+
// IMPORTANT: Only subscribes to queryOptionsSignal, NOT pagingMetadataSignal
|
|
116
|
+
if (typeof window !== 'undefined') {
|
|
117
|
+
signalsService.effect(async () => {
|
|
118
|
+
// CRITICAL: Read queryOptionsSignal to establish dependency
|
|
119
|
+
// Do NOT read pagingMetadataSignal here to avoid infinite loop
|
|
120
|
+
const queryOptions = queryOptionsSignal.get();
|
|
121
|
+
if (firstRun) {
|
|
122
|
+
firstRun = false;
|
|
123
|
+
// If we have initial services (showcase mode), don't query on first run
|
|
124
|
+
if (hasInitialServices) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Read paging metadata outside of dependency tracking for the fetch
|
|
129
|
+
const currentPaging = pagingMetadataSignal.get();
|
|
130
|
+
try {
|
|
131
|
+
isLoadingSignal.set(true);
|
|
132
|
+
errorSignal.set(null);
|
|
133
|
+
const result = await queryServices({
|
|
134
|
+
appId: queryOptions.appId,
|
|
135
|
+
filters: queryOptions.filters,
|
|
136
|
+
pagingMetadata: {
|
|
137
|
+
limit: currentPaging.limit,
|
|
138
|
+
offset: 0, // Reset to first page when query options change
|
|
139
|
+
},
|
|
140
|
+
sort: queryOptions.sort,
|
|
141
|
+
});
|
|
142
|
+
servicesSignal.set(result.services);
|
|
143
|
+
// Update ONLY pagingMetadataSignal (not queryOptionsSignal) to avoid re-triggering effect
|
|
144
|
+
pagingMetadataSignal.set(result.pagingMetadata);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
errorSignal.set(error instanceof Error ? error.message : 'Unknown error');
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
isLoadingSignal.set(false);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
firstRun = false;
|
|
155
|
+
const loadMoreCursor = async (count) => {
|
|
156
|
+
const currentQueryOptions = queryOptionsSignal.get();
|
|
157
|
+
// Calculate next page offset based on current services count
|
|
158
|
+
const currentServicesCount = servicesSignal.get().length;
|
|
159
|
+
const nextOffset = currentServicesCount;
|
|
160
|
+
const nextPaging = {
|
|
161
|
+
limit: count || DEFAULT_QUERY_LIMIT,
|
|
162
|
+
offset: nextOffset,
|
|
163
|
+
hasNext: false, // Will be updated after query
|
|
164
|
+
};
|
|
165
|
+
try {
|
|
166
|
+
isLoadingSignal.set(true);
|
|
167
|
+
errorSignal.set(null);
|
|
168
|
+
const result = await queryServices({
|
|
169
|
+
appId: currentQueryOptions.appId,
|
|
170
|
+
filters: currentQueryOptions.filters,
|
|
171
|
+
pagingMetadata: nextPaging,
|
|
172
|
+
sort: currentQueryOptions.sort,
|
|
173
|
+
});
|
|
174
|
+
// Append new services to existing ones
|
|
175
|
+
servicesSignal.set([...servicesSignal.get(), ...result.services]);
|
|
176
|
+
// Update ONLY pagingMetadataSignal (not queryOptionsSignal)
|
|
177
|
+
pagingMetadataSignal.set(result.pagingMetadata);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
errorSignal.set(error instanceof Error ? error.message : 'Unknown error');
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
isLoadingSignal.set(false);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
servicesSignal,
|
|
188
|
+
queryOptionsSignal,
|
|
189
|
+
pagingMetadataSignal,
|
|
190
|
+
isLoadingSignal,
|
|
191
|
+
errorSignal,
|
|
192
|
+
actions: {
|
|
193
|
+
loadMore: async (count) => {
|
|
194
|
+
await loadMoreCursor(count);
|
|
195
|
+
},
|
|
196
|
+
setQueryOptions: (options) => {
|
|
197
|
+
// Reset services and paging when query options change
|
|
198
|
+
servicesSignal.set([]);
|
|
199
|
+
pagingMetadataSignal.set({
|
|
200
|
+
limit: pagingMetadataSignal.get().limit || DEFAULT_QUERY_LIMIT,
|
|
201
|
+
offset: 0,
|
|
202
|
+
count: 0,
|
|
203
|
+
hasNext: false,
|
|
204
|
+
});
|
|
205
|
+
queryOptionsSignal.set(options);
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
hasMoreServicesSignal: signalsService.computed(() => {
|
|
209
|
+
const paging = pagingMetadataSignal.get();
|
|
210
|
+
// Use hasNext flag from pagingMetadata to determine if more services are available
|
|
211
|
+
return paging.hasNext ?? false;
|
|
212
|
+
}),
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
/**
|
|
216
|
+
* Load service list configuration for Server-Side Rendering (SSR).
|
|
217
|
+
* This function is designed to be called during the build or server-side rendering phase
|
|
218
|
+
* to pre-fetch services data before the React components are hydrated on the client.
|
|
219
|
+
* The returned config includes paging metadata with hasNext flag indicating if more services are available.
|
|
220
|
+
*
|
|
221
|
+
* @param options - Query options following QueryServicesRequest type, or a URL string (to be implemented)
|
|
222
|
+
* @returns A promise that resolves to either a success result with the config or an error result
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* // In an Astro page or API route
|
|
227
|
+
* import { loadServiceListServiceInitialData } from '@wix/bookings/services';
|
|
228
|
+
*
|
|
229
|
+
* // Load with query options
|
|
230
|
+
* const serviceListResult = await loadServiceListServiceInitialData({
|
|
231
|
+
* appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
|
|
232
|
+
* pagingMetadata: { limit: 20, offset: 0 },
|
|
233
|
+
* });
|
|
234
|
+
*
|
|
235
|
+
* if (serviceListResult.type === 'error') {
|
|
236
|
+
* console.error('Failed to load services:', serviceListResult.error);
|
|
237
|
+
* return Astro.redirect('/500');
|
|
238
|
+
* }
|
|
239
|
+
*
|
|
240
|
+
* const serviceListConfig = serviceListResult.config;
|
|
241
|
+
*
|
|
242
|
+
* // Check if more services are available
|
|
243
|
+
* if (serviceListConfig.options?.pagingMetadata?.hasNext) {
|
|
244
|
+
* // More services can be loaded
|
|
245
|
+
* }
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
export async function loadServiceListServiceInitialData(options) {
|
|
249
|
+
try {
|
|
250
|
+
const result = await queryServices({
|
|
251
|
+
appId: options.appId,
|
|
252
|
+
filters: options.filters,
|
|
253
|
+
pagingMetadata: options.pagingMetadata,
|
|
254
|
+
sort: options.sort,
|
|
255
|
+
});
|
|
256
|
+
return {
|
|
257
|
+
type: 'success',
|
|
258
|
+
config: {
|
|
259
|
+
services: result.services,
|
|
260
|
+
options: {
|
|
261
|
+
...options,
|
|
262
|
+
pagingMetadata: result.pagingMetadata,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error('Failed to load service list config:', error);
|
|
269
|
+
return {
|
|
270
|
+
type: 'error',
|
|
271
|
+
error: error instanceof Error ? error : new Error('Unknown error'),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const formatDate = (date) => {
|
|
2
|
+
return date.toLocaleDateString(undefined, {
|
|
3
|
+
year: 'numeric',
|
|
4
|
+
month: 'short',
|
|
5
|
+
day: 'numeric',
|
|
6
|
+
});
|
|
7
|
+
};
|
|
8
|
+
export const formatDuration = (minutes) => {
|
|
9
|
+
if (minutes < 60) {
|
|
10
|
+
return `${minutes} min`;
|
|
11
|
+
}
|
|
12
|
+
const hours = Math.floor(minutes / 60);
|
|
13
|
+
const mins = minutes % 60;
|
|
14
|
+
if (mins === 0) {
|
|
15
|
+
return `${hours} hour${hours > 1 ? 's' : ''}`;
|
|
16
|
+
}
|
|
17
|
+
return `${hours}h ${mins}m`;
|
|
18
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Money utility functions for converting between SDK Money types and MoneyData
|
|
3
|
+
*/
|
|
4
|
+
import type { MoneyData } from '@wix/headless-components/react';
|
|
5
|
+
/**
|
|
6
|
+
* Converts SDK Money format to MoneyData format
|
|
7
|
+
*
|
|
8
|
+
* @param sdkMoney - The SDK money object with value, currency, and optional formattedValue
|
|
9
|
+
* @returns MoneyData object with amount and currency, or null if conversion fails
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const money = convertToMoneyData({ value: "25.50", currency: "USD" });
|
|
14
|
+
* // Returns: { amount: 25.50, currency: "USD" }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function convertToMoneyData(sdkMoney: {
|
|
18
|
+
value?: string;
|
|
19
|
+
currency?: string;
|
|
20
|
+
formattedValue?: string | null;
|
|
21
|
+
} | undefined): MoneyData | null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Money utility functions for converting between SDK Money types and MoneyData
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Converts SDK Money format to MoneyData format
|
|
6
|
+
*
|
|
7
|
+
* @param sdkMoney - The SDK money object with value, currency, and optional formattedValue
|
|
8
|
+
* @returns MoneyData object with amount and currency, or null if conversion fails
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const money = convertToMoneyData({ value: "25.50", currency: "USD" });
|
|
13
|
+
* // Returns: { amount: 25.50, currency: "USD" }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function convertToMoneyData(sdkMoney) {
|
|
17
|
+
if (!sdkMoney)
|
|
18
|
+
return null;
|
|
19
|
+
const priceValue = sdkMoney.value;
|
|
20
|
+
const currency = sdkMoney.currency || 'USD';
|
|
21
|
+
// Convert price value to number
|
|
22
|
+
let amount = undefined;
|
|
23
|
+
if (typeof priceValue === 'string') {
|
|
24
|
+
const parsed = parseFloat(priceValue);
|
|
25
|
+
amount = !isNaN(parsed) ? parsed : undefined;
|
|
26
|
+
}
|
|
27
|
+
else if (typeof priceValue === 'number') {
|
|
28
|
+
amount = priceValue;
|
|
29
|
+
}
|
|
30
|
+
if (amount !== undefined && !isNaN(amount) && amount >= 0) {
|
|
31
|
+
return { amount, currency };
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
package/dist/api/index.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* API Layer
|
|
3
3
|
* Exports all Wix Bookings API functions
|
|
4
|
+
*
|
|
5
|
+
* @module API
|
|
6
|
+
*/
|
|
7
|
+
export { getServiceById, getServiceBySlug, queryServices, type PagingMetadata, type ServiceListFilters, type QueryServicesRequest, } from './query-services/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* PagingMetadata includes:
|
|
10
|
+
* - limit: Number of items per page
|
|
11
|
+
* - offset: Number of items to skip
|
|
12
|
+
* - count: Number of items returned in the current page
|
|
13
|
+
* - hasNext: Whether there are more services available to load
|
|
4
14
|
*/
|
|
5
|
-
export { getServiceById, getServiceBySlug } from './query-service/index.js';
|
package/dist/api/index.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* API Layer
|
|
3
3
|
* Exports all Wix Bookings API functions
|
|
4
|
+
*
|
|
5
|
+
* @module API
|
|
6
|
+
*/
|
|
7
|
+
export { getServiceById, getServiceBySlug, queryServices, } from './query-services/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* PagingMetadata includes:
|
|
10
|
+
* - limit: Number of items per page
|
|
11
|
+
* - offset: Number of items to skip
|
|
12
|
+
* - count: Number of items returned in the current page
|
|
13
|
+
* - hasNext: Whether there are more services available to load
|
|
4
14
|
*/
|
|
5
|
-
export { getServiceById, getServiceBySlug } from './query-service/index.js';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Services API
|
|
3
|
+
* Fetches single or multiple services with pagination and sorting
|
|
4
|
+
*/
|
|
5
|
+
import { services } from '@wix/bookings';
|
|
6
|
+
import type { services as servicesTypes } from '@wix/bookings';
|
|
7
|
+
/**
|
|
8
|
+
* Paging metadata from query services response.
|
|
9
|
+
* Extends the SDK's Paging type with hasNext flag to indicate if more services are available.
|
|
10
|
+
*/
|
|
11
|
+
export type PagingMetadata = services.Paging & {
|
|
12
|
+
/** Number of items returned in the current page */
|
|
13
|
+
count?: number;
|
|
14
|
+
/** Whether there are more services available to load */
|
|
15
|
+
hasNext?: boolean;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Service list filters
|
|
19
|
+
*/
|
|
20
|
+
export interface ServiceListFilters {
|
|
21
|
+
serviceIds?: string[];
|
|
22
|
+
categoryIds?: string[];
|
|
23
|
+
locationIds?: string[];
|
|
24
|
+
staffMemberIds?: string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Query services request options
|
|
28
|
+
* Used internally by ServiceListService to track query state
|
|
29
|
+
*/
|
|
30
|
+
export interface QueryServicesRequest {
|
|
31
|
+
appId: string;
|
|
32
|
+
filters?: ServiceListFilters;
|
|
33
|
+
pagingMetadata?: PagingMetadata;
|
|
34
|
+
sort?: services.Sorting[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Fetches a service by its ID
|
|
38
|
+
* @param id - The service ID
|
|
39
|
+
* @param appId - Optional app ID, defaults to booking app ID
|
|
40
|
+
* @returns Object containing the service or null if not found
|
|
41
|
+
*/
|
|
42
|
+
export declare const getServiceById: (id: string, appId?: string) => Promise<{
|
|
43
|
+
service: servicesTypes.Service | null;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Fetches a service by its slug
|
|
47
|
+
* @param slug - The service slug
|
|
48
|
+
* @param appId - Optional app ID, defaults to booking app ID
|
|
49
|
+
* @returns Object containing the service or null if not found
|
|
50
|
+
*/
|
|
51
|
+
export declare const getServiceBySlug: (slug: string, appId?: string) => Promise<{
|
|
52
|
+
service: servicesTypes.Service | null;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Query services with pagination and sorting.
|
|
56
|
+
* Returns services array and paging metadata including hasNext flag.
|
|
57
|
+
*
|
|
58
|
+
* @param params - Query parameters including appId, filters, paging metadata, and sort options
|
|
59
|
+
* @returns Promise that resolves to services array and paging metadata with hasNext flag
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const { services, pagingMetadata } = await queryServices({
|
|
64
|
+
* appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
|
|
65
|
+
* filters: { serviceIds: ['service1', 'service2'] },
|
|
66
|
+
* pagingMetadata: { limit: 20, offset: 0 },
|
|
67
|
+
* sort: [{ fieldName: 'name', order: 'ASC' }]
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* if (pagingMetadata.hasNext) {
|
|
71
|
+
* // Load more services
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function queryServices({ appId, filters, pagingMetadata, sort, }: QueryServicesRequest): Promise<{
|
|
76
|
+
services: servicesTypes.Service[];
|
|
77
|
+
pagingMetadata: PagingMetadata;
|
|
78
|
+
}>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Services API
|
|
3
|
+
* Fetches single or multiple services with pagination and sorting
|
|
4
|
+
*/
|
|
5
|
+
import { services } from '@wix/bookings';
|
|
6
|
+
/**
|
|
7
|
+
* Booking app ID constant
|
|
8
|
+
*/
|
|
9
|
+
const BOOKING_APP_ID = '13d21c63-b5ec-5912-8397-c3a5ddb27a97';
|
|
10
|
+
/**
|
|
11
|
+
* Fetches a service by its ID
|
|
12
|
+
* @param id - The service ID
|
|
13
|
+
* @param appId - Optional app ID, defaults to booking app ID
|
|
14
|
+
* @returns Object containing the service or null if not found
|
|
15
|
+
*/
|
|
16
|
+
export const getServiceById = async (id, appId) => {
|
|
17
|
+
try {
|
|
18
|
+
const finalAppId = appId ?? BOOKING_APP_ID;
|
|
19
|
+
// Query services where _id equals the provided id and appId matches
|
|
20
|
+
const query = services.queryServices({
|
|
21
|
+
conditionalFields: ['STAFF_MEMBER_DETAILS'],
|
|
22
|
+
});
|
|
23
|
+
const result = await query
|
|
24
|
+
.eq('_id', id)
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
.eq('appId', finalAppId)
|
|
27
|
+
.limit(1)
|
|
28
|
+
.find();
|
|
29
|
+
const service = result.items?.[0] || null;
|
|
30
|
+
return { service };
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error(`Failed to load service for id "${id}":`, error);
|
|
34
|
+
return { service: null };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Fetches a service by its slug
|
|
39
|
+
* @param slug - The service slug
|
|
40
|
+
* @param appId - Optional app ID, defaults to booking app ID
|
|
41
|
+
* @returns Object containing the service or null if not found
|
|
42
|
+
*/
|
|
43
|
+
export const getServiceBySlug = async (slug, appId) => {
|
|
44
|
+
try {
|
|
45
|
+
const finalAppId = appId ?? BOOKING_APP_ID;
|
|
46
|
+
// Query services where mainSlug.name equals the provided slug and appId matches
|
|
47
|
+
const query = services.queryServices({
|
|
48
|
+
conditionalFields: ['STAFF_MEMBER_DETAILS'],
|
|
49
|
+
});
|
|
50
|
+
const result = await query
|
|
51
|
+
.eq('mainSlug.name', slug)
|
|
52
|
+
// @ts-ignore
|
|
53
|
+
.eq('appId', finalAppId)
|
|
54
|
+
.limit(1)
|
|
55
|
+
.find();
|
|
56
|
+
const service = result.items?.[0] || null;
|
|
57
|
+
return { service };
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error(`Failed to load service for slug "${slug}":`, error);
|
|
61
|
+
return { service: null };
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Builds a QueryServicesRequest from appId, filters, paging, and sort
|
|
66
|
+
* @param params - Parameters including appId, filters, paging, and sort
|
|
67
|
+
* @returns QueryServicesRequest object
|
|
68
|
+
*/
|
|
69
|
+
const buildServicesQueryRequest = ({ appId, filters, paging, sort, }) => {
|
|
70
|
+
const filter = {
|
|
71
|
+
appId,
|
|
72
|
+
};
|
|
73
|
+
// Add serviceIds filter if provided
|
|
74
|
+
if (filters?.serviceIds && filters.serviceIds.length > 0) {
|
|
75
|
+
filter['_id'] = { $in: filters.serviceIds };
|
|
76
|
+
}
|
|
77
|
+
// Add other filters as they are implemented in future phases
|
|
78
|
+
// categoryIds, locationIds, staffMemberIds to be added later
|
|
79
|
+
const query = {
|
|
80
|
+
filter: Object.keys(filter).length > 0 ? filter : undefined,
|
|
81
|
+
paging: paging || {
|
|
82
|
+
limit: 100, // Default limit
|
|
83
|
+
},
|
|
84
|
+
sort,
|
|
85
|
+
};
|
|
86
|
+
return { query };
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Query services with pagination and sorting.
|
|
90
|
+
* Returns services array and paging metadata including hasNext flag.
|
|
91
|
+
*
|
|
92
|
+
* @param params - Query parameters including appId, filters, paging metadata, and sort options
|
|
93
|
+
* @returns Promise that resolves to services array and paging metadata with hasNext flag
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const { services, pagingMetadata } = await queryServices({
|
|
98
|
+
* appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
|
|
99
|
+
* filters: { serviceIds: ['service1', 'service2'] },
|
|
100
|
+
* pagingMetadata: { limit: 20, offset: 0 },
|
|
101
|
+
* sort: [{ fieldName: 'name', order: 'ASC' }]
|
|
102
|
+
* });
|
|
103
|
+
*
|
|
104
|
+
* if (pagingMetadata.hasNext) {
|
|
105
|
+
* // Load more services
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export async function queryServices({ appId, filters, pagingMetadata, sort, }) {
|
|
110
|
+
try {
|
|
111
|
+
const queryRequest = buildServicesQueryRequest({
|
|
112
|
+
appId,
|
|
113
|
+
filters,
|
|
114
|
+
paging: pagingMetadata,
|
|
115
|
+
sort,
|
|
116
|
+
});
|
|
117
|
+
// Call queryServices with QueryServicesRequest and execute the query
|
|
118
|
+
const result = await services.queryServices(queryRequest).find();
|
|
119
|
+
// Construct PagingMetadata from result properties
|
|
120
|
+
// Includes count (number of items returned) and hasNext (whether more items are available)
|
|
121
|
+
const resultPagingMetadata = {
|
|
122
|
+
limit: pagingMetadata?.limit,
|
|
123
|
+
offset: pagingMetadata?.offset,
|
|
124
|
+
count: result.items.length,
|
|
125
|
+
hasNext: (result?.totalCount || 0) >
|
|
126
|
+
result.items.length + (pagingMetadata?.offset || 0),
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
services: result.items || [],
|
|
130
|
+
pagingMetadata: resultPagingMetadata,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error('Failed to query services:', error);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Location Component - Parses service location data
|
|
3
|
+
* Handles business, custom, and customer location types
|
|
4
|
+
* Converts location data to Address component format
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { services } from '@wix/bookings';
|
|
8
|
+
import type { AddressData as AddressDataType } from '@wix/headless-components/react';
|
|
9
|
+
/**
|
|
10
|
+
* Re-export AddressData type from Address component for convenience
|
|
11
|
+
*/
|
|
12
|
+
export type AddressData = AddressDataType;
|
|
13
|
+
/**
|
|
14
|
+
* Location render props passed to children
|
|
15
|
+
*/
|
|
16
|
+
export interface LocationRenderProps {
|
|
17
|
+
locationType: services.LocationType;
|
|
18
|
+
address: AddressData | null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Props for the core Location component
|
|
22
|
+
*/
|
|
23
|
+
export interface LocationProps {
|
|
24
|
+
location: NonNullable<services.Service['locations']>[number];
|
|
25
|
+
children: (props: LocationRenderProps) => React.ReactNode;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Core Location component that parses service location data
|
|
29
|
+
* and provides location type and address data to children
|
|
30
|
+
*
|
|
31
|
+
* @component
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* <CoreLocation location={serviceLocation}>
|
|
35
|
+
* {({ locationType, address }) => (
|
|
36
|
+
* <div>
|
|
37
|
+
* <span>Type: {locationType}</span>
|
|
38
|
+
* {address && <Address.Root address={address}><Address.Formatted /></Address.Root>}
|
|
39
|
+
* </div>
|
|
40
|
+
* )}
|
|
41
|
+
* </CoreLocation>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function Location(props: LocationProps): React.ReactNode;
|