@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
@@ -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,2 @@
1
+ export declare const formatDate: (date: Date) => string;
2
+ export declare const formatDuration: (minutes: number) => string;
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-bookings",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -34,6 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@wix/bookings": "^1.0.122",
37
+ "@wix/form-public": "^0.64.0",
37
38
  "@wix/headless-media": "0.0.18",
38
39
  "@wix/headless-utils": "0.0.8",
39
40
  "@wix/services-definitions": "^0.1.4",
@@ -67,5 +68,5 @@
67
68
  "groupId": "com.wixpress.headless-components"
68
69
  }
69
70
  },
70
- "falconPackageHash": "629444840e2e541358d66f4112b18146dc2bb0598a3df541aa1a9daf"
71
+ "falconPackageHash": "2996fdf77f39e0cde0bf4bb18bd8bd0dd2630b3aa839f47d67059ca2"
71
72
  }
@@ -1,23 +0,0 @@
1
- /**
2
- * Query Service API
3
- * Fetches a single service by ID or slug from Wix Bookings
4
- */
5
- import { services } from '@wix/bookings';
6
- /**
7
- * Fetches a service by its ID
8
- * @param id - The service ID
9
- * @returns Object containing the service or null if not found
10
- */
11
- export declare const getServiceById: (id: string) => Promise<{
12
- service: services.Service;
13
- } | {
14
- service: null;
15
- }>;
16
- /**
17
- * Fetches a service by its slug
18
- * @param slug - The service slug
19
- * @returns Object containing the service or null if not found
20
- */
21
- export declare const getServiceBySlug: (slug: string) => Promise<{
22
- service: services.Service | null;
23
- }>;
@@ -1,41 +0,0 @@
1
- /**
2
- * Query Service API
3
- * Fetches a single service by ID or slug from Wix Bookings
4
- */
5
- import { services } from '@wix/bookings';
6
- /**
7
- * Fetches a service by its ID
8
- * @param id - The service ID
9
- * @returns Object containing the service or null if not found
10
- */
11
- export const getServiceById = async (id) => {
12
- try {
13
- const service = await services.getService(id);
14
- return { service };
15
- }
16
- catch (error) {
17
- console.error(`Failed to load service for id "${id}":`, error);
18
- return { service: null };
19
- }
20
- };
21
- /**
22
- * Fetches a service by its slug
23
- * @param slug - The service slug
24
- * @returns Object containing the service or null if not found
25
- */
26
- export const getServiceBySlug = async (slug) => {
27
- try {
28
- // Query services where mainSlug.name equals the provided slug
29
- const result = await services
30
- .queryServices()
31
- .eq('mainSlug.name', slug)
32
- .limit(1)
33
- .find();
34
- const service = result.items?.[0] || null;
35
- return { service };
36
- }
37
- catch (error) {
38
- console.error(`Failed to load service for slug "${slug}":`, error);
39
- return { service: null };
40
- }
41
- };
@@ -1,23 +0,0 @@
1
- /**
2
- * Query Service API
3
- * Fetches a single service by ID or slug from Wix Bookings
4
- */
5
- import { services } from '@wix/bookings';
6
- /**
7
- * Fetches a service by its ID
8
- * @param id - The service ID
9
- * @returns Object containing the service or null if not found
10
- */
11
- export declare const getServiceById: (id: string) => Promise<{
12
- service: services.Service;
13
- } | {
14
- service: null;
15
- }>;
16
- /**
17
- * Fetches a service by its slug
18
- * @param slug - The service slug
19
- * @returns Object containing the service or null if not found
20
- */
21
- export declare const getServiceBySlug: (slug: string) => Promise<{
22
- service: services.Service | null;
23
- }>;
@@ -1,41 +0,0 @@
1
- /**
2
- * Query Service API
3
- * Fetches a single service by ID or slug from Wix Bookings
4
- */
5
- import { services } from '@wix/bookings';
6
- /**
7
- * Fetches a service by its ID
8
- * @param id - The service ID
9
- * @returns Object containing the service or null if not found
10
- */
11
- export const getServiceById = async (id) => {
12
- try {
13
- const service = await services.getService(id);
14
- return { service };
15
- }
16
- catch (error) {
17
- console.error(`Failed to load service for id "${id}":`, error);
18
- return { service: null };
19
- }
20
- };
21
- /**
22
- * Fetches a service by its slug
23
- * @param slug - The service slug
24
- * @returns Object containing the service or null if not found
25
- */
26
- export const getServiceBySlug = async (slug) => {
27
- try {
28
- // Query services where mainSlug.name equals the provided slug
29
- const result = await services
30
- .queryServices()
31
- .eq('mainSlug.name', slug)
32
- .limit(1)
33
- .find();
34
- const service = result.items?.[0] || null;
35
- return { service };
36
- }
37
- catch (error) {
38
- console.error(`Failed to load service for slug "${slug}":`, error);
39
- return { service: null };
40
- }
41
- };