@wix/headless-bookings 0.0.46 → 0.0.48
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/react/core/service/Service.js +6 -0
- package/cjs/dist/react/service/Service.d.ts +32 -3
- package/cjs/dist/react/service/Service.js +25 -2
- package/cjs/dist/react/service-list/ServiceList.d.ts +11 -2
- package/cjs/dist/react/service-list/ServiceList.js +10 -6
- package/cjs/dist/services/booking/book-action/buildBookingRequest.js +21 -1
- package/cjs/dist/services/service/service.js +23 -3
- package/dist/react/core/service/Service.js +6 -0
- package/dist/react/service/Service.d.ts +32 -3
- package/dist/react/service/Service.js +25 -2
- package/dist/react/service-list/ServiceList.d.ts +11 -2
- package/dist/react/service-list/ServiceList.js +10 -6
- package/dist/services/booking/book-action/buildBookingRequest.js +21 -1
- package/dist/services/service/service.js +23 -3
- package/package.json +2 -2
|
@@ -15,8 +15,14 @@ import { ServiceServiceDefinition, ServiceService, } from '../../../services/ser
|
|
|
15
15
|
*/
|
|
16
16
|
function RootContent({ children, }) {
|
|
17
17
|
const serviceService = useService(ServiceServiceDefinition);
|
|
18
|
+
const service = serviceService.serviceSignal.get();
|
|
18
19
|
const selected = serviceService.selectedSignal.get();
|
|
19
20
|
const bookable = serviceService.bookableSignal.get();
|
|
21
|
+
// In "from booking signal" mode, render null when no service is selected
|
|
22
|
+
// The signal will trigger a re-render when a service is selected
|
|
23
|
+
if (!service || !service._id) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
20
26
|
if (typeof children === 'function') {
|
|
21
27
|
return children({ selected, bookable });
|
|
22
28
|
}
|
|
@@ -43,14 +43,32 @@ type ServiceRootWithSlug = ServiceRootBaseProps & {
|
|
|
43
43
|
serviceId?: never;
|
|
44
44
|
serviceSlug: string;
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Props for Service.Root that reads from BookingService's selected service.
|
|
48
|
+
* When used without service config, the component reads from the booking signal.
|
|
49
|
+
* Requires BookingService to be available in the context.
|
|
50
|
+
*/
|
|
51
|
+
type ServiceRootFromBooking = ServiceRootBaseProps & {
|
|
52
|
+
service?: never;
|
|
53
|
+
serviceId?: never;
|
|
54
|
+
serviceSlug?: never;
|
|
55
|
+
};
|
|
46
56
|
/**
|
|
47
57
|
* Props for Service.Root component.
|
|
48
|
-
*
|
|
58
|
+
* Accepts one of:
|
|
59
|
+
* - service: Pre-loaded service object (SSR pattern)
|
|
60
|
+
* - serviceId: Service ID for dynamic loading
|
|
61
|
+
* - serviceSlug: Service slug for dynamic loading
|
|
62
|
+
* - No service config: Reads from BookingService's selected service
|
|
49
63
|
*/
|
|
50
|
-
export type ServiceRootProps = ServiceRootWithService | ServiceRootWithId | ServiceRootWithSlug;
|
|
64
|
+
export type ServiceRootProps = ServiceRootWithService | ServiceRootWithId | ServiceRootWithSlug | ServiceRootFromBooking;
|
|
51
65
|
/**
|
|
52
66
|
* Root component that provides service context to the entire app.
|
|
53
|
-
*
|
|
67
|
+
* Can be used with:
|
|
68
|
+
* - service: Pre-loaded service object (SSR pattern)
|
|
69
|
+
* - serviceId: Service ID for dynamic loading
|
|
70
|
+
* - serviceSlug: Service slug for dynamic loading
|
|
71
|
+
* - No config: Reads from BookingService's selected service (requires BookingService)
|
|
54
72
|
*
|
|
55
73
|
* @order 1
|
|
56
74
|
* @component
|
|
@@ -87,6 +105,17 @@ export type ServiceRootProps = ServiceRootWithService | ServiceRootWithId | Serv
|
|
|
87
105
|
* </Service.Root>
|
|
88
106
|
* );
|
|
89
107
|
* }
|
|
108
|
+
*
|
|
109
|
+
* // Reading from BookingService's selected service
|
|
110
|
+
* // (requires Booking.Root parent context)
|
|
111
|
+
* function SelectedServiceDisplay() {
|
|
112
|
+
* return (
|
|
113
|
+
* <Service.Root>
|
|
114
|
+
* <Service.Name />
|
|
115
|
+
* <Service.Price />
|
|
116
|
+
* </Service.Root>
|
|
117
|
+
* );
|
|
118
|
+
* }
|
|
90
119
|
* ```
|
|
91
120
|
*/
|
|
92
121
|
export declare const Root: React.ForwardRefExoticComponent<ServiceRootProps & React.RefAttributes<HTMLElement>>;
|
|
@@ -49,7 +49,11 @@ var TestIds;
|
|
|
49
49
|
})(TestIds || (TestIds = {}));
|
|
50
50
|
/**
|
|
51
51
|
* Root component that provides service context to the entire app.
|
|
52
|
-
*
|
|
52
|
+
* Can be used with:
|
|
53
|
+
* - service: Pre-loaded service object (SSR pattern)
|
|
54
|
+
* - serviceId: Service ID for dynamic loading
|
|
55
|
+
* - serviceSlug: Service slug for dynamic loading
|
|
56
|
+
* - No config: Reads from BookingService's selected service (requires BookingService)
|
|
53
57
|
*
|
|
54
58
|
* @order 1
|
|
55
59
|
* @component
|
|
@@ -86,11 +90,30 @@ var TestIds;
|
|
|
86
90
|
* </Service.Root>
|
|
87
91
|
* );
|
|
88
92
|
* }
|
|
93
|
+
*
|
|
94
|
+
* // Reading from BookingService's selected service
|
|
95
|
+
* // (requires Booking.Root parent context)
|
|
96
|
+
* function SelectedServiceDisplay() {
|
|
97
|
+
* return (
|
|
98
|
+
* <Service.Root>
|
|
99
|
+
* <Service.Name />
|
|
100
|
+
* <Service.Price />
|
|
101
|
+
* </Service.Root>
|
|
102
|
+
* );
|
|
103
|
+
* }
|
|
89
104
|
* ```
|
|
90
105
|
*/
|
|
91
106
|
export const Root = React.forwardRef((props, ref) => {
|
|
92
107
|
const { children, service, serviceId, serviceSlug, appId, asChild, className, ...attrs } = props;
|
|
93
|
-
|
|
108
|
+
// Build config - when no service config is provided, pass empty object
|
|
109
|
+
// to trigger "from booking signal" mode in ServiceService
|
|
110
|
+
const serviceServiceConfig = {
|
|
111
|
+
service,
|
|
112
|
+
serviceId,
|
|
113
|
+
serviceSlug,
|
|
114
|
+
appId,
|
|
115
|
+
};
|
|
116
|
+
return (_jsx(CoreService.Root, { serviceServiceConfig: serviceServiceConfig, children: ({ selected, bookable }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.serviceRoot, "data-selected": selected, "data-bookable": bookable, customElement: children, customElementProps: { selected, bookable }, ...attrs, children: children })) }));
|
|
94
117
|
});
|
|
95
118
|
Root.displayName = 'Service.Root';
|
|
96
119
|
/**
|
|
@@ -47,19 +47,28 @@ export declare const Root: React.ForwardRefExoticComponent<ServiceListRootProps
|
|
|
47
47
|
*/
|
|
48
48
|
export interface ServicesProps {
|
|
49
49
|
children: React.ReactNode;
|
|
50
|
+
/** Content to display when no services are available */
|
|
50
51
|
emptyState?: React.ReactNode;
|
|
52
|
+
/** Content to display when loading and no services are available */
|
|
53
|
+
loadingState?: React.ReactNode;
|
|
54
|
+
/** Content to display when an error occurs and no services are available */
|
|
55
|
+
errorState?: React.ReactNode;
|
|
51
56
|
infiniteScroll?: boolean;
|
|
52
57
|
pageSize?: number;
|
|
53
58
|
className?: string;
|
|
54
59
|
}
|
|
55
60
|
/**
|
|
56
|
-
* Container for the services list with empty state support.
|
|
61
|
+
* Container for the services list with empty, loading, and error state support.
|
|
57
62
|
* Wraps GenericList.Items. Follows List Container Level pattern.
|
|
58
63
|
*
|
|
59
64
|
* @component
|
|
60
65
|
* @example
|
|
61
66
|
* ```tsx
|
|
62
|
-
* <ServiceList.Services
|
|
67
|
+
* <ServiceList.Services
|
|
68
|
+
* emptyState={<div>No services found</div>}
|
|
69
|
+
* loadingState={<div>Loading services...</div>}
|
|
70
|
+
* errorState={<div>Failed to load services</div>}
|
|
71
|
+
* >
|
|
63
72
|
* <ServiceList.ServiceRepeater>
|
|
64
73
|
* <Service.Name />
|
|
65
74
|
* <Service.Price />
|
|
@@ -73,22 +73,26 @@ const DEFAULT_PAGE_SIZE = 100;
|
|
|
73
73
|
*/
|
|
74
74
|
const RootContent = React.forwardRef((props, ref) => {
|
|
75
75
|
const { children, className, variant, pageSize = DEFAULT_PAGE_SIZE } = props;
|
|
76
|
-
return (_jsx(CoreServiceList.Services, { children: ({ services: servicesList, isLoading, hasMore, loadMore }) => {
|
|
76
|
+
return (_jsx(CoreServiceList.Services, { children: ({ services: servicesList, isLoading, hasMore, loadMore, error }) => {
|
|
77
77
|
const items = servicesList.map((service) => ({
|
|
78
78
|
...service,
|
|
79
79
|
id: service._id,
|
|
80
80
|
}));
|
|
81
|
-
return (_jsx(GenericList.Root, { items: items, loadMore: () => loadMore(pageSize), hasMore: hasMore, isLoading: isLoading, className: className, ref: ref, "data-testid": TestIds.serviceListRoot, variant: variant, children: children }));
|
|
81
|
+
return (_jsx(GenericList.Root, { items: items, loadMore: () => loadMore(pageSize), hasMore: hasMore, isLoading: isLoading, error: error, className: className, ref: ref, "data-testid": TestIds.serviceListRoot, variant: variant, children: children }));
|
|
82
82
|
} }));
|
|
83
83
|
});
|
|
84
84
|
/**
|
|
85
|
-
* Container for the services list with empty state support.
|
|
85
|
+
* Container for the services list with empty, loading, and error state support.
|
|
86
86
|
* Wraps GenericList.Items. Follows List Container Level pattern.
|
|
87
87
|
*
|
|
88
88
|
* @component
|
|
89
89
|
* @example
|
|
90
90
|
* ```tsx
|
|
91
|
-
* <ServiceList.Services
|
|
91
|
+
* <ServiceList.Services
|
|
92
|
+
* emptyState={<div>No services found</div>}
|
|
93
|
+
* loadingState={<div>Loading services...</div>}
|
|
94
|
+
* errorState={<div>Failed to load services</div>}
|
|
95
|
+
* >
|
|
92
96
|
* <ServiceList.ServiceRepeater>
|
|
93
97
|
* <Service.Name />
|
|
94
98
|
* <Service.Price />
|
|
@@ -97,8 +101,8 @@ const RootContent = React.forwardRef((props, ref) => {
|
|
|
97
101
|
* ```
|
|
98
102
|
*/
|
|
99
103
|
export const Services = React.forwardRef((props, ref) => {
|
|
100
|
-
const { children, ...otherProps } = props;
|
|
101
|
-
return (_jsx(GenericList.Items, { ref: ref, "data-testid": TestIds.serviceListServices, ...otherProps, children: children }));
|
|
104
|
+
const { children, emptyState, loadingState, errorState, ...otherProps } = props;
|
|
105
|
+
return (_jsx(GenericList.Items, { ref: ref, "data-testid": TestIds.serviceListServices, emptyState: emptyState, loadingState: loadingState, errorState: errorState, ...otherProps, children: children }));
|
|
102
106
|
});
|
|
103
107
|
Services.displayName = 'ServiceList.Services';
|
|
104
108
|
/**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Builds the createBooking request from BookActionParams
|
|
3
3
|
*/
|
|
4
|
+
import { LocationType as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
5
|
+
import { LocationType as BookingLocationType } from '@wix/auto_sdk_bookings_bookings';
|
|
4
6
|
/**
|
|
5
7
|
* Derives the selected payment option from service payment settings.
|
|
6
8
|
* Maps service.payment.options to booking API's selectedPaymentOption.
|
|
@@ -69,7 +71,11 @@ export function buildBookingRequest(params) {
|
|
|
69
71
|
? { _id: resource._id, name: resource.name }
|
|
70
72
|
: undefined,
|
|
71
73
|
location: slotLocation
|
|
72
|
-
? {
|
|
74
|
+
? {
|
|
75
|
+
_id: slotLocation._id,
|
|
76
|
+
name: slotLocation.name,
|
|
77
|
+
locationType: mapTimeSlotLocationTypeToBookingLocationType(slotLocation.locationType),
|
|
78
|
+
}
|
|
73
79
|
: undefined,
|
|
74
80
|
},
|
|
75
81
|
},
|
|
@@ -81,3 +87,17 @@ export function buildBookingRequest(params) {
|
|
|
81
87
|
formSubmission: formSubmission ?? undefined,
|
|
82
88
|
};
|
|
83
89
|
}
|
|
90
|
+
function mapTimeSlotLocationTypeToBookingLocationType(slotLocationType) {
|
|
91
|
+
switch (slotLocationType) {
|
|
92
|
+
case TimeSlotLocationType.BUSINESS:
|
|
93
|
+
return BookingLocationType.OWNER_BUSINESS;
|
|
94
|
+
case TimeSlotLocationType.CUSTOMER:
|
|
95
|
+
return BookingLocationType.CUSTOM;
|
|
96
|
+
case TimeSlotLocationType.CUSTOM:
|
|
97
|
+
return BookingLocationType.OWNER_CUSTOM;
|
|
98
|
+
case TimeSlotLocationType.UNKNOWN_LOCATION_TYPE:
|
|
99
|
+
return BookingLocationType.UNDEFINED;
|
|
100
|
+
default:
|
|
101
|
+
return BookingLocationType.OWNER_BUSINESS;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -54,6 +54,8 @@ export const ServiceServiceDefinition = defineService('service');
|
|
|
54
54
|
*/
|
|
55
55
|
export const ServiceService = implementService.withConfig()(ServiceServiceDefinition, ({ getService, config }) => {
|
|
56
56
|
const signalsService = getService(SignalsServiceDefinition);
|
|
57
|
+
// Determine if we're in "from booking signal" mode (no service config provided)
|
|
58
|
+
const noExplicitService = !config.service && !config.serviceId && !config.serviceSlug;
|
|
57
59
|
// Try to get BookingService (optional dependency for showcase mode)
|
|
58
60
|
let bookingService = null;
|
|
59
61
|
try {
|
|
@@ -63,9 +65,27 @@ export const ServiceService = implementService.withConfig()(ServiceServiceDefini
|
|
|
63
65
|
// BookingService not available - showcase mode
|
|
64
66
|
bookingService = null;
|
|
65
67
|
}
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
// In "from booking signal" mode, BookingService must be available
|
|
69
|
+
if (noExplicitService && !bookingService) {
|
|
70
|
+
throw new Error('Service.Root requires either a service config (service, serviceId, or serviceSlug) or BookingService to be available');
|
|
71
|
+
}
|
|
72
|
+
// Initialize service signal
|
|
73
|
+
// In "from booking signal" mode, use a computed signal that reads from BookingService
|
|
74
|
+
let serviceSignal;
|
|
75
|
+
if (noExplicitService && bookingService) {
|
|
76
|
+
// Create a computed signal that reads from BookingService's first selected service
|
|
77
|
+
const computedServiceSignal = signalsService.computed(() => {
|
|
78
|
+
const selections = bookingService.serviceSelections.get();
|
|
79
|
+
return selections[0]?.service || null;
|
|
80
|
+
});
|
|
81
|
+
// Wrap computed signal as a Signal (read-only in practice for this mode)
|
|
82
|
+
serviceSignal = computedServiceSignal;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Standard mode: use provided service or empty object
|
|
86
|
+
const initialService = config.service || {};
|
|
87
|
+
serviceSignal = signalsService.signal(initialService);
|
|
88
|
+
}
|
|
69
89
|
const isLoadingSignal = signalsService.signal(!!config.serviceId);
|
|
70
90
|
const errorSignal = signalsService.signal(null);
|
|
71
91
|
// Default appId to BOOKING_APP_ID if not provided
|
|
@@ -15,8 +15,14 @@ import { ServiceServiceDefinition, ServiceService, } from '../../../services/ser
|
|
|
15
15
|
*/
|
|
16
16
|
function RootContent({ children, }) {
|
|
17
17
|
const serviceService = useService(ServiceServiceDefinition);
|
|
18
|
+
const service = serviceService.serviceSignal.get();
|
|
18
19
|
const selected = serviceService.selectedSignal.get();
|
|
19
20
|
const bookable = serviceService.bookableSignal.get();
|
|
21
|
+
// In "from booking signal" mode, render null when no service is selected
|
|
22
|
+
// The signal will trigger a re-render when a service is selected
|
|
23
|
+
if (!service || !service._id) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
20
26
|
if (typeof children === 'function') {
|
|
21
27
|
return children({ selected, bookable });
|
|
22
28
|
}
|
|
@@ -43,14 +43,32 @@ type ServiceRootWithSlug = ServiceRootBaseProps & {
|
|
|
43
43
|
serviceId?: never;
|
|
44
44
|
serviceSlug: string;
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Props for Service.Root that reads from BookingService's selected service.
|
|
48
|
+
* When used without service config, the component reads from the booking signal.
|
|
49
|
+
* Requires BookingService to be available in the context.
|
|
50
|
+
*/
|
|
51
|
+
type ServiceRootFromBooking = ServiceRootBaseProps & {
|
|
52
|
+
service?: never;
|
|
53
|
+
serviceId?: never;
|
|
54
|
+
serviceSlug?: never;
|
|
55
|
+
};
|
|
46
56
|
/**
|
|
47
57
|
* Props for Service.Root component.
|
|
48
|
-
*
|
|
58
|
+
* Accepts one of:
|
|
59
|
+
* - service: Pre-loaded service object (SSR pattern)
|
|
60
|
+
* - serviceId: Service ID for dynamic loading
|
|
61
|
+
* - serviceSlug: Service slug for dynamic loading
|
|
62
|
+
* - No service config: Reads from BookingService's selected service
|
|
49
63
|
*/
|
|
50
|
-
export type ServiceRootProps = ServiceRootWithService | ServiceRootWithId | ServiceRootWithSlug;
|
|
64
|
+
export type ServiceRootProps = ServiceRootWithService | ServiceRootWithId | ServiceRootWithSlug | ServiceRootFromBooking;
|
|
51
65
|
/**
|
|
52
66
|
* Root component that provides service context to the entire app.
|
|
53
|
-
*
|
|
67
|
+
* Can be used with:
|
|
68
|
+
* - service: Pre-loaded service object (SSR pattern)
|
|
69
|
+
* - serviceId: Service ID for dynamic loading
|
|
70
|
+
* - serviceSlug: Service slug for dynamic loading
|
|
71
|
+
* - No config: Reads from BookingService's selected service (requires BookingService)
|
|
54
72
|
*
|
|
55
73
|
* @order 1
|
|
56
74
|
* @component
|
|
@@ -87,6 +105,17 @@ export type ServiceRootProps = ServiceRootWithService | ServiceRootWithId | Serv
|
|
|
87
105
|
* </Service.Root>
|
|
88
106
|
* );
|
|
89
107
|
* }
|
|
108
|
+
*
|
|
109
|
+
* // Reading from BookingService's selected service
|
|
110
|
+
* // (requires Booking.Root parent context)
|
|
111
|
+
* function SelectedServiceDisplay() {
|
|
112
|
+
* return (
|
|
113
|
+
* <Service.Root>
|
|
114
|
+
* <Service.Name />
|
|
115
|
+
* <Service.Price />
|
|
116
|
+
* </Service.Root>
|
|
117
|
+
* );
|
|
118
|
+
* }
|
|
90
119
|
* ```
|
|
91
120
|
*/
|
|
92
121
|
export declare const Root: React.ForwardRefExoticComponent<ServiceRootProps & React.RefAttributes<HTMLElement>>;
|
|
@@ -49,7 +49,11 @@ var TestIds;
|
|
|
49
49
|
})(TestIds || (TestIds = {}));
|
|
50
50
|
/**
|
|
51
51
|
* Root component that provides service context to the entire app.
|
|
52
|
-
*
|
|
52
|
+
* Can be used with:
|
|
53
|
+
* - service: Pre-loaded service object (SSR pattern)
|
|
54
|
+
* - serviceId: Service ID for dynamic loading
|
|
55
|
+
* - serviceSlug: Service slug for dynamic loading
|
|
56
|
+
* - No config: Reads from BookingService's selected service (requires BookingService)
|
|
53
57
|
*
|
|
54
58
|
* @order 1
|
|
55
59
|
* @component
|
|
@@ -86,11 +90,30 @@ var TestIds;
|
|
|
86
90
|
* </Service.Root>
|
|
87
91
|
* );
|
|
88
92
|
* }
|
|
93
|
+
*
|
|
94
|
+
* // Reading from BookingService's selected service
|
|
95
|
+
* // (requires Booking.Root parent context)
|
|
96
|
+
* function SelectedServiceDisplay() {
|
|
97
|
+
* return (
|
|
98
|
+
* <Service.Root>
|
|
99
|
+
* <Service.Name />
|
|
100
|
+
* <Service.Price />
|
|
101
|
+
* </Service.Root>
|
|
102
|
+
* );
|
|
103
|
+
* }
|
|
89
104
|
* ```
|
|
90
105
|
*/
|
|
91
106
|
export const Root = React.forwardRef((props, ref) => {
|
|
92
107
|
const { children, service, serviceId, serviceSlug, appId, asChild, className, ...attrs } = props;
|
|
93
|
-
|
|
108
|
+
// Build config - when no service config is provided, pass empty object
|
|
109
|
+
// to trigger "from booking signal" mode in ServiceService
|
|
110
|
+
const serviceServiceConfig = {
|
|
111
|
+
service,
|
|
112
|
+
serviceId,
|
|
113
|
+
serviceSlug,
|
|
114
|
+
appId,
|
|
115
|
+
};
|
|
116
|
+
return (_jsx(CoreService.Root, { serviceServiceConfig: serviceServiceConfig, children: ({ selected, bookable }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.serviceRoot, "data-selected": selected, "data-bookable": bookable, customElement: children, customElementProps: { selected, bookable }, ...attrs, children: children })) }));
|
|
94
117
|
});
|
|
95
118
|
Root.displayName = 'Service.Root';
|
|
96
119
|
/**
|
|
@@ -47,19 +47,28 @@ export declare const Root: React.ForwardRefExoticComponent<ServiceListRootProps
|
|
|
47
47
|
*/
|
|
48
48
|
export interface ServicesProps {
|
|
49
49
|
children: React.ReactNode;
|
|
50
|
+
/** Content to display when no services are available */
|
|
50
51
|
emptyState?: React.ReactNode;
|
|
52
|
+
/** Content to display when loading and no services are available */
|
|
53
|
+
loadingState?: React.ReactNode;
|
|
54
|
+
/** Content to display when an error occurs and no services are available */
|
|
55
|
+
errorState?: React.ReactNode;
|
|
51
56
|
infiniteScroll?: boolean;
|
|
52
57
|
pageSize?: number;
|
|
53
58
|
className?: string;
|
|
54
59
|
}
|
|
55
60
|
/**
|
|
56
|
-
* Container for the services list with empty state support.
|
|
61
|
+
* Container for the services list with empty, loading, and error state support.
|
|
57
62
|
* Wraps GenericList.Items. Follows List Container Level pattern.
|
|
58
63
|
*
|
|
59
64
|
* @component
|
|
60
65
|
* @example
|
|
61
66
|
* ```tsx
|
|
62
|
-
* <ServiceList.Services
|
|
67
|
+
* <ServiceList.Services
|
|
68
|
+
* emptyState={<div>No services found</div>}
|
|
69
|
+
* loadingState={<div>Loading services...</div>}
|
|
70
|
+
* errorState={<div>Failed to load services</div>}
|
|
71
|
+
* >
|
|
63
72
|
* <ServiceList.ServiceRepeater>
|
|
64
73
|
* <Service.Name />
|
|
65
74
|
* <Service.Price />
|
|
@@ -73,22 +73,26 @@ const DEFAULT_PAGE_SIZE = 100;
|
|
|
73
73
|
*/
|
|
74
74
|
const RootContent = React.forwardRef((props, ref) => {
|
|
75
75
|
const { children, className, variant, pageSize = DEFAULT_PAGE_SIZE } = props;
|
|
76
|
-
return (_jsx(CoreServiceList.Services, { children: ({ services: servicesList, isLoading, hasMore, loadMore }) => {
|
|
76
|
+
return (_jsx(CoreServiceList.Services, { children: ({ services: servicesList, isLoading, hasMore, loadMore, error }) => {
|
|
77
77
|
const items = servicesList.map((service) => ({
|
|
78
78
|
...service,
|
|
79
79
|
id: service._id,
|
|
80
80
|
}));
|
|
81
|
-
return (_jsx(GenericList.Root, { items: items, loadMore: () => loadMore(pageSize), hasMore: hasMore, isLoading: isLoading, className: className, ref: ref, "data-testid": TestIds.serviceListRoot, variant: variant, children: children }));
|
|
81
|
+
return (_jsx(GenericList.Root, { items: items, loadMore: () => loadMore(pageSize), hasMore: hasMore, isLoading: isLoading, error: error, className: className, ref: ref, "data-testid": TestIds.serviceListRoot, variant: variant, children: children }));
|
|
82
82
|
} }));
|
|
83
83
|
});
|
|
84
84
|
/**
|
|
85
|
-
* Container for the services list with empty state support.
|
|
85
|
+
* Container for the services list with empty, loading, and error state support.
|
|
86
86
|
* Wraps GenericList.Items. Follows List Container Level pattern.
|
|
87
87
|
*
|
|
88
88
|
* @component
|
|
89
89
|
* @example
|
|
90
90
|
* ```tsx
|
|
91
|
-
* <ServiceList.Services
|
|
91
|
+
* <ServiceList.Services
|
|
92
|
+
* emptyState={<div>No services found</div>}
|
|
93
|
+
* loadingState={<div>Loading services...</div>}
|
|
94
|
+
* errorState={<div>Failed to load services</div>}
|
|
95
|
+
* >
|
|
92
96
|
* <ServiceList.ServiceRepeater>
|
|
93
97
|
* <Service.Name />
|
|
94
98
|
* <Service.Price />
|
|
@@ -97,8 +101,8 @@ const RootContent = React.forwardRef((props, ref) => {
|
|
|
97
101
|
* ```
|
|
98
102
|
*/
|
|
99
103
|
export const Services = React.forwardRef((props, ref) => {
|
|
100
|
-
const { children, ...otherProps } = props;
|
|
101
|
-
return (_jsx(GenericList.Items, { ref: ref, "data-testid": TestIds.serviceListServices, ...otherProps, children: children }));
|
|
104
|
+
const { children, emptyState, loadingState, errorState, ...otherProps } = props;
|
|
105
|
+
return (_jsx(GenericList.Items, { ref: ref, "data-testid": TestIds.serviceListServices, emptyState: emptyState, loadingState: loadingState, errorState: errorState, ...otherProps, children: children }));
|
|
102
106
|
});
|
|
103
107
|
Services.displayName = 'ServiceList.Services';
|
|
104
108
|
/**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Builds the createBooking request from BookActionParams
|
|
3
3
|
*/
|
|
4
|
+
import { LocationType as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
5
|
+
import { LocationType as BookingLocationType } from '@wix/auto_sdk_bookings_bookings';
|
|
4
6
|
/**
|
|
5
7
|
* Derives the selected payment option from service payment settings.
|
|
6
8
|
* Maps service.payment.options to booking API's selectedPaymentOption.
|
|
@@ -69,7 +71,11 @@ export function buildBookingRequest(params) {
|
|
|
69
71
|
? { _id: resource._id, name: resource.name }
|
|
70
72
|
: undefined,
|
|
71
73
|
location: slotLocation
|
|
72
|
-
? {
|
|
74
|
+
? {
|
|
75
|
+
_id: slotLocation._id,
|
|
76
|
+
name: slotLocation.name,
|
|
77
|
+
locationType: mapTimeSlotLocationTypeToBookingLocationType(slotLocation.locationType),
|
|
78
|
+
}
|
|
73
79
|
: undefined,
|
|
74
80
|
},
|
|
75
81
|
},
|
|
@@ -81,3 +87,17 @@ export function buildBookingRequest(params) {
|
|
|
81
87
|
formSubmission: formSubmission ?? undefined,
|
|
82
88
|
};
|
|
83
89
|
}
|
|
90
|
+
function mapTimeSlotLocationTypeToBookingLocationType(slotLocationType) {
|
|
91
|
+
switch (slotLocationType) {
|
|
92
|
+
case TimeSlotLocationType.BUSINESS:
|
|
93
|
+
return BookingLocationType.OWNER_BUSINESS;
|
|
94
|
+
case TimeSlotLocationType.CUSTOMER:
|
|
95
|
+
return BookingLocationType.CUSTOM;
|
|
96
|
+
case TimeSlotLocationType.CUSTOM:
|
|
97
|
+
return BookingLocationType.OWNER_CUSTOM;
|
|
98
|
+
case TimeSlotLocationType.UNKNOWN_LOCATION_TYPE:
|
|
99
|
+
return BookingLocationType.UNDEFINED;
|
|
100
|
+
default:
|
|
101
|
+
return BookingLocationType.OWNER_BUSINESS;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -54,6 +54,8 @@ export const ServiceServiceDefinition = defineService('service');
|
|
|
54
54
|
*/
|
|
55
55
|
export const ServiceService = implementService.withConfig()(ServiceServiceDefinition, ({ getService, config }) => {
|
|
56
56
|
const signalsService = getService(SignalsServiceDefinition);
|
|
57
|
+
// Determine if we're in "from booking signal" mode (no service config provided)
|
|
58
|
+
const noExplicitService = !config.service && !config.serviceId && !config.serviceSlug;
|
|
57
59
|
// Try to get BookingService (optional dependency for showcase mode)
|
|
58
60
|
let bookingService = null;
|
|
59
61
|
try {
|
|
@@ -63,9 +65,27 @@ export const ServiceService = implementService.withConfig()(ServiceServiceDefini
|
|
|
63
65
|
// BookingService not available - showcase mode
|
|
64
66
|
bookingService = null;
|
|
65
67
|
}
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
// In "from booking signal" mode, BookingService must be available
|
|
69
|
+
if (noExplicitService && !bookingService) {
|
|
70
|
+
throw new Error('Service.Root requires either a service config (service, serviceId, or serviceSlug) or BookingService to be available');
|
|
71
|
+
}
|
|
72
|
+
// Initialize service signal
|
|
73
|
+
// In "from booking signal" mode, use a computed signal that reads from BookingService
|
|
74
|
+
let serviceSignal;
|
|
75
|
+
if (noExplicitService && bookingService) {
|
|
76
|
+
// Create a computed signal that reads from BookingService's first selected service
|
|
77
|
+
const computedServiceSignal = signalsService.computed(() => {
|
|
78
|
+
const selections = bookingService.serviceSelections.get();
|
|
79
|
+
return selections[0]?.service || null;
|
|
80
|
+
});
|
|
81
|
+
// Wrap computed signal as a Signal (read-only in practice for this mode)
|
|
82
|
+
serviceSignal = computedServiceSignal;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Standard mode: use provided service or empty object
|
|
86
|
+
const initialService = config.service || {};
|
|
87
|
+
serviceSignal = signalsService.signal(initialService);
|
|
88
|
+
}
|
|
69
89
|
const isLoadingSignal = signalsService.signal(!!config.serviceId);
|
|
70
90
|
const errorSignal = signalsService.signal(null);
|
|
71
91
|
// Default appId to BOOKING_APP_ID if not provided
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-bookings",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.48",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
"groupId": "com.wixpress.headless-components"
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
|
-
"falconPackageHash": "
|
|
75
|
+
"falconPackageHash": "d03db7a127ec41055bfdd5c0e6e6b7aa2c7446d3a1c8f69a4ff8aa57"
|
|
76
76
|
}
|