@wix/headless-bookings 0.0.50 → 0.0.52

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.
@@ -1,6 +1,31 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Booking - High-level component for managing bookings
4
+ * Provides root component for booking context and booking item display components
5
+ */
6
+ import React from 'react';
2
7
  import * as CoreBooking from '../core/booking/Booking.js';
3
8
  import { Book } from './Book.js';
9
+ import { AsChildSlot } from '@wix/headless-utils/react';
10
+ import * as ServiceComponent from '../service/Service.js';
11
+ import * as TimeSlotComponent from '../time-slot-list/TimeSlot.js';
12
+ import * as PaymentComponent from '../payment/Payment.js';
13
+ import * as CoreLocation from '../core/location/Location.js';
14
+ import { GenericList } from '@wix/headless-components/react';
15
+ // ============================================================================
16
+ // TestIds
17
+ // ============================================================================
18
+ var TestIds;
19
+ (function (TestIds) {
20
+ TestIds["bookingItemsRoot"] = "booking-items-root";
21
+ TestIds["bookingItems"] = "booking-items";
22
+ TestIds["bookingItem"] = "booking-item";
23
+ TestIds["bookingItemService"] = "booking-item-service";
24
+ TestIds["bookingItemTimeSlot"] = "booking-item-time-slot";
25
+ TestIds["bookingItemPayment"] = "booking-item-payment";
26
+ TestIds["bookingLocation"] = "booking-location";
27
+ TestIds["bookingStaffName"] = "booking-staff-name";
28
+ })(TestIds || (TestIds = {}));
4
29
  /**
5
30
  * Root component that provides booking context to the entire app.
6
31
  *
@@ -24,6 +49,9 @@ export const Root = (props) => {
24
49
  return (_jsx(CoreBooking.Root, { bookingServiceConfig: bookingServiceConfig, children: children }));
25
50
  };
26
51
  Root.displayName = 'Booking.Root';
52
+ // ============================================================================
53
+ // Actions Namespace
54
+ // ============================================================================
27
55
  /**
28
56
  * Actions namespace containing action components for booking
29
57
  *
@@ -42,3 +70,201 @@ Root.displayName = 'Booking.Root';
42
70
  export const Actions = {
43
71
  Book,
44
72
  };
73
+ // ============================================================================
74
+ // BookingItem Context (re-export from core)
75
+ // ============================================================================
76
+ /**
77
+ * Hook to access the current BookingItem from context.
78
+ * Must be used within Booking.ItemRepeater.
79
+ *
80
+ * @throws Error if used outside of ItemRepeater context
81
+ */
82
+ export const useBookingItem = CoreBooking.useBookingItemContext;
83
+ /**
84
+ * Container component that provides GenericList.Root context for booking items.
85
+ * Use Booking.Items inside for list container with emptyState support.
86
+ *
87
+ * @component
88
+ * @example
89
+ * ```tsx
90
+ * <Booking.ItemsRoot>
91
+ * <Booking.Items emptyState={<div>No bookings selected</div>}>
92
+ * <Booking.ItemRepeater>
93
+ * <div className="booking-card">
94
+ * <Booking.Service>
95
+ * <Service.Name />
96
+ * </Booking.Service>
97
+ * </div>
98
+ * </Booking.ItemRepeater>
99
+ * </Booking.Items>
100
+ * </Booking.ItemsRoot>
101
+ * ```
102
+ */
103
+ export const ItemsRoot = React.forwardRef((props, ref) => {
104
+ const { children, ...otherProps } = props;
105
+ return (_jsx(CoreBooking.ItemsData, { children: ({ items }) => (_jsx(GenericList.Root, { ref: ref, items: items.map((item) => ({
106
+ ...item,
107
+ id: item.instanceId,
108
+ })), hasMore: false, isLoading: false, "data-testid": TestIds.bookingItemsRoot, ...otherProps, children: children })) }));
109
+ });
110
+ ItemsRoot.displayName = 'Booking.ItemsRoot';
111
+ /**
112
+ * List container component with emptyState support.
113
+ * Wraps GenericList.Items. Must be used within Booking.ItemsRoot.
114
+ *
115
+ * @component
116
+ * @example
117
+ * ```tsx
118
+ * <Booking.ItemsRoot>
119
+ * <Booking.Items emptyState={<div>No bookings selected</div>}>
120
+ * <Booking.ItemRepeater>
121
+ * <Booking.Service>
122
+ * <Service.Name />
123
+ * </Booking.Service>
124
+ * </Booking.ItemRepeater>
125
+ * </Booking.Items>
126
+ * </Booking.ItemsRoot>
127
+ * ```
128
+ */
129
+ export const Items = React.forwardRef((props, ref) => {
130
+ const { children, ...otherProps } = props;
131
+ return (_jsx(GenericList.Items, { ref: ref, "data-testid": TestIds.bookingItems, ...otherProps, children: children }));
132
+ });
133
+ Items.displayName = 'Booking.Items';
134
+ /**
135
+ * Internal component that wraps each booking item with context provider.
136
+ */
137
+ const BookingItemWrapper = ({ item, children, }) => {
138
+ return (_jsx(CoreBooking.BookingItemProvider, { item: item, children: children }));
139
+ };
140
+ /**
141
+ * Repeater component that maps over booking items and provides context per item.
142
+ * Uses GenericList.Repeater internally with itemWrapper that provides BookingItemContext.
143
+ * Children have access to Booking.Service, Booking.TimeSlot, etc.
144
+ *
145
+ * @component
146
+ * @example
147
+ * ```tsx
148
+ * <Booking.ItemRepeater>
149
+ * <div className="booking-card">
150
+ * <Booking.Service>
151
+ * <Service.Name />
152
+ * <Service.Price />
153
+ * </Booking.Service>
154
+ * <Booking.TimeSlot>
155
+ * <TimeSlot.StartDate />
156
+ * </Booking.TimeSlot>
157
+ * <Booking.StaffName />
158
+ * </div>
159
+ * </Booking.ItemRepeater>
160
+ * ```
161
+ */
162
+ export const ItemRepeater = React.forwardRef((props, ref) => {
163
+ const { children } = props;
164
+ return (_jsx(GenericList.Repeater, { ref: ref, itemWrapper: ({ item }) => (_jsx(BookingItemWrapper, { item: item, children: children }, item.instanceId)), children: undefined }));
165
+ });
166
+ ItemRepeater.displayName = 'Booking.ItemRepeater';
167
+ /**
168
+ * Wraps Service.Root with the service from the current booking item context.
169
+ * Children can use all Service.* components (Service.Name, Service.Price, etc.).
170
+ *
171
+ * @component
172
+ * @example
173
+ * ```tsx
174
+ * <Booking.Service>
175
+ * <Service.Name className="text-xl font-bold" />
176
+ * <Service.Description />
177
+ * <Service.Price />
178
+ * </Booking.Service>
179
+ * ```
180
+ */
181
+ export function Service(props) {
182
+ const { children } = props;
183
+ return (_jsx(CoreBooking.BookingItemInfo, { children: ({ service }) => (_jsx(ServiceComponent.Root, { service: service, children: children })) }));
184
+ }
185
+ /**
186
+ * Wraps TimeSlot.Root with the timeSlot from the current booking item context.
187
+ * Children can use all TimeSlot.* components (TimeSlot.StartDate, TimeSlot.Duration, etc.).
188
+ * Always renders with data-has-time-slot attribute for CSS targeting.
189
+ *
190
+ * @component
191
+ * @example
192
+ * ```tsx
193
+ * <Booking.TimeSlot>
194
+ * <TimeSlot.StartDate />
195
+ * <TimeSlot.Duration />
196
+ * </Booking.TimeSlot>
197
+ * ```
198
+ */
199
+ export function TimeSlot(props) {
200
+ const { children } = props;
201
+ return (_jsx(CoreBooking.BookingItemInfo, { children: ({ timeSlot }) => timeSlot ? (_jsx(TimeSlotComponent.Root, { timeSlot: timeSlot, children: children })) : null }));
202
+ }
203
+ /**
204
+ * Wraps Payment.Root with all services from all booking items.
205
+ * Should be used outside of ItemRepeater - it calculates payment for all items.
206
+ * Children can use all Payment.* components (Payment.Total, Payment.Subtotal, etc.).
207
+ * Always renders with data-has-payment attribute for CSS targeting.
208
+ *
209
+ * @component
210
+ * @example
211
+ * ```tsx
212
+ * <Booking.Root>
213
+ * <Booking.ItemsRoot>
214
+ * <Booking.Items>
215
+ * <Booking.ItemRepeater>
216
+ * <Booking.Service><Service.Name /></Booking.Service>
217
+ * </Booking.ItemRepeater>
218
+ * </Booking.Items>
219
+ * </Booking.ItemsRoot>
220
+ *
221
+ * <Booking.Payment>
222
+ * <Payment.Total className="text-xl font-bold" />
223
+ * <Payment.Subtotal />
224
+ * </Booking.Payment>
225
+ * </Booking.Root>
226
+ * ```
227
+ */
228
+ export function Payment(props) {
229
+ const { children } = props;
230
+ return (_jsx(CoreBooking.PaymentData, { children: ({ slotServices }) => (_jsx(PaymentComponent.Root, { slotServices: slotServices, children: children })) }));
231
+ }
232
+ /**
233
+ * Wraps Location context with the location from the booking context.
234
+ * Children can use all Location.* components (Location.Name, Location.Address, etc.).
235
+ * Returns null if no location is set.
236
+ *
237
+ * @component
238
+ * @example
239
+ * ```tsx
240
+ * <Booking.Location>
241
+ * <Location.Name className="font-bold" />
242
+ * <Location.Address />
243
+ * <Location.Phone />
244
+ * </Booking.Location>
245
+ * ```
246
+ */
247
+ export function Location(props) {
248
+ const { children } = props;
249
+ return (_jsx(CoreBooking.Data, { children: ({ location }) => location ? (_jsx(CoreLocation.Root, { location: location, children: children })) : null }));
250
+ }
251
+ /**
252
+ * Displays the first staff member's name from the current booking item.
253
+ * Returns null when staff name is empty.
254
+ *
255
+ * @component
256
+ * @example
257
+ * ```tsx
258
+ * <Booking.StaffName className="text-foreground" />
259
+ * ```
260
+ */
261
+ export const StaffName = React.forwardRef((props, ref) => {
262
+ const { asChild, children, className } = props;
263
+ return (_jsx(CoreBooking.BookingItemInfo, { children: ({ staffName }) => {
264
+ if (!staffName) {
265
+ return null;
266
+ }
267
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.bookingStaffName, customElement: children, customElementProps: { name: staffName }, children: _jsx("span", { children: staffName }) }));
268
+ } }));
269
+ });
270
+ StaffName.displayName = 'Booking.StaffName';
@@ -5,7 +5,9 @@
5
5
  import React from 'react';
6
6
  import { type BookingServiceConfig, type ServiceSelection, type BookingServiceActions } from '../../../services/booking/booking.js';
7
7
  import type { FormValues } from '@wix/form-public';
8
- import type { Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
8
+ import type { TimeSlot, Resource } from '@wix/auto_sdk_bookings_availability-time-slots';
9
+ import type { Service, Location as ServiceLocationType } from '@wix/auto_sdk_bookings_services';
10
+ import type { SlotService } from '../../../services/payment/payment.js';
9
11
  /**
10
12
  * Props for Booking.Root component
11
13
  */
@@ -35,7 +37,7 @@ export declare function Root(props: RootProps): React.ReactNode;
35
37
  */
36
38
  export interface BookingData {
37
39
  serviceSelections: ServiceSelection[];
38
- location: TimeSlotLocationType | null;
40
+ location: ServiceLocationType | null;
39
41
  timezone: string | undefined;
40
42
  formSubmission: FormValues | null;
41
43
  actions: BookingServiceActions;
@@ -46,22 +48,148 @@ export interface BookingData {
46
48
  export interface DataProps {
47
49
  children: (data: BookingData) => React.ReactNode;
48
50
  }
51
+ export declare function Data(props: DataProps): React.ReactNode;
52
+ /**
53
+ * Unified data object for a single booking item.
54
+ * Contains resolved data ready for use - no raw serviceSelection duplication.
55
+ */
56
+ export interface BookingItem {
57
+ /** SDK Service object */
58
+ service: Service;
59
+ /** Adapted SDK TimeSlot (or null if no time slot selected) */
60
+ timeSlot: TimeSlot | null;
61
+ /** Resolved staff: timeSlot.resources ?? selection.resources, or null if none */
62
+ staff: Resource[] | null;
63
+ /** Unique identifier for this booking instance */
64
+ instanceId: string;
65
+ /** Position in the serviceSelections array */
66
+ index: number;
67
+ }
68
+ /**
69
+ * Hook to access the current BookingItem from context.
70
+ * Must be used within BookingItemProvider.
71
+ *
72
+ * @throws Error if used outside of BookingItemProvider context
73
+ */
74
+ export declare function useBookingItemContext(): BookingItem;
75
+ /**
76
+ * Props for BookingItemProvider
77
+ */
78
+ export interface BookingItemProviderProps {
79
+ children: React.ReactNode;
80
+ item: BookingItem;
81
+ }
82
+ /**
83
+ * Provider component used by repeaters to provide BookingItem context.
84
+ * Can be used by core consumers to create their own repeater.
85
+ *
86
+ * @component
87
+ * @example
88
+ * ```tsx
89
+ * {items.map((item) => (
90
+ * <BookingItemProvider key={item.instanceId} item={item}>
91
+ * {children}
92
+ * </BookingItemProvider>
93
+ * ))}
94
+ * ```
95
+ */
96
+ export declare function BookingItemProvider(props: BookingItemProviderProps): React.ReactNode;
97
+ /**
98
+ * Props for BookingItemInfo render prop component
99
+ */
100
+ export interface BookingItemInfoProps {
101
+ children: (data: {
102
+ /** SDK Service object */
103
+ service: Service;
104
+ /** Adapted SDK TimeSlot (or null if no time slot selected) */
105
+ timeSlot: TimeSlot | null;
106
+ /** First staff member's name, empty string if none */
107
+ staffName: string;
108
+ /** Unique identifier for this booking instance */
109
+ instanceId: string;
110
+ /** Position in the serviceSelections array */
111
+ index: number;
112
+ /** The raw BookingItem object */
113
+ item: BookingItem;
114
+ }) => React.ReactNode;
115
+ }
116
+ /**
117
+ * Core component that provides access to booking item info via render props.
118
+ * Must be used within BookingItemProvider.
119
+ *
120
+ * @component
121
+ * @example
122
+ * ```tsx
123
+ * <BookingItemInfo>
124
+ * {({ service, timeSlot, staffName }) => (
125
+ * <div>
126
+ * <span>{service.name}</span>
127
+ * <span>{staffName}</span>
128
+ * </div>
129
+ * )}
130
+ * </BookingItemInfo>
131
+ * ```
132
+ */
133
+ export declare function BookingItemInfo(props: BookingItemInfoProps): React.ReactNode;
134
+ /**
135
+ * Data exposed by ItemsData render prop
136
+ */
137
+ export interface ItemsDataRenderProps {
138
+ /** Array of BookingItem objects */
139
+ items: BookingItem[];
140
+ }
49
141
  /**
50
- * Core component that provides access to booking data via render props.
51
- * Used internally by higher-level Booking components.
142
+ * Props for ItemsData render prop component
143
+ */
144
+ export interface ItemsDataProps {
145
+ children: (data: ItemsDataRenderProps) => React.ReactNode;
146
+ }
147
+ /**
148
+ * Core component that provides access to booking items array via render props.
149
+ * Transforms serviceSelections into BookingItem objects.
52
150
  *
53
151
  * @component
54
152
  * @example
55
153
  * ```tsx
56
- * <Booking.Data>
57
- * {({ serviceSelections, location, timezone, formSubmission, actions }) => (
58
- * serviceSelections.length > 0 ? (
59
- * <div>{serviceSelections.map(item => item.service.name).join(', ')}</div>
154
+ * <Booking.ItemsData>
155
+ * {({ items }) => (
156
+ * items.length > 0 ? (
157
+ * <div>{items.length} bookings</div>
60
158
  * ) : (
61
- * <div>No services selected</div>
159
+ * <div>No bookings</div>
62
160
  * )
63
161
  * )}
64
- * </Booking.Data>
162
+ * </Booking.ItemsData>
65
163
  * ```
66
164
  */
67
- export declare function Data(props: DataProps): React.ReactNode;
165
+ export declare function ItemsData(props: ItemsDataProps): React.ReactNode;
166
+ /**
167
+ * Data exposed by PaymentData render prop
168
+ */
169
+ export interface PaymentDataRenderProps {
170
+ /** SlotServices array for Payment.Root */
171
+ slotServices: SlotService[];
172
+ }
173
+ /**
174
+ * Props for PaymentData render prop component
175
+ */
176
+ export interface PaymentDataProps {
177
+ children: (data: PaymentDataRenderProps) => React.ReactNode;
178
+ }
179
+ /**
180
+ * Core component that provides payment data via render props.
181
+ * Computes slotServices from serviceSelections.
182
+ *
183
+ * @component
184
+ * @example
185
+ * ```tsx
186
+ * <Booking.PaymentData>
187
+ * {({ slotServices }) => (
188
+ * <Payment.Root slotServices={slotServices}>
189
+ * <Payment.TotalPrice />
190
+ * </Payment.Root>
191
+ * )}
192
+ * </Booking.PaymentData>
193
+ * ```
194
+ */
195
+ export declare function PaymentData(props: PaymentDataProps): React.ReactNode;
@@ -1,4 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Core Booking Components
4
+ * Provides low-level access to BookingService via render props and service setup
5
+ */
6
+ import React from 'react';
2
7
  import { WixServices, useService } from '@wix/services-manager-react';
3
8
  import { createServicesMap } from '@wix/services-manager';
4
9
  import { BookingServiceDefinition, BookingService, } from '../../../services/booking/booking.js';
@@ -39,14 +44,53 @@ export function Root(props) {
39
44
  * </Booking.Data>
40
45
  * ```
41
46
  */
47
+ /**
48
+ * Maps a TimeSlotLocationType to ServiceLocationType.
49
+ * TimeSlot location has flat structure: { name, formattedAddress, locationType }
50
+ * Service location has nested structure: { business: { name, address }, type }
51
+ */
52
+ function mapTimeSlotLocationToServiceLocation(timeSlotLocation) {
53
+ // TimeSlot location has flat name/formattedAddress at top level
54
+ // Convert to nested business structure that CoreLocation expects
55
+ return {
56
+ _id: timeSlotLocation._id ?? undefined,
57
+ type: timeSlotLocation.locationType,
58
+ business: {
59
+ _id: timeSlotLocation._id ?? undefined,
60
+ name: timeSlotLocation.name ?? undefined,
61
+ address: timeSlotLocation.formattedAddress
62
+ ? {
63
+ addressLine: timeSlotLocation.formattedAddress,
64
+ }
65
+ : undefined,
66
+ },
67
+ };
68
+ }
69
+ /**
70
+ * Resolves the location with fallback logic:
71
+ * 1. First, use the explicitly set booking location
72
+ * 2. If not set, fall back to the first service selection's slot location
73
+ */
74
+ function resolveLocation(bookingLocation, serviceSelections) {
75
+ if (bookingLocation) {
76
+ return bookingLocation;
77
+ }
78
+ // Fallback to first slot's location
79
+ const firstSlotLocation = serviceSelections[0]?.timeSlot?.location;
80
+ if (firstSlotLocation) {
81
+ return mapTimeSlotLocationToServiceLocation(firstSlotLocation);
82
+ }
83
+ return null;
84
+ }
42
85
  export function Data(props) {
43
86
  const { children } = props;
44
87
  const bookingService = useService(BookingServiceDefinition);
45
88
  const serviceSelections = bookingService.serviceSelections.get();
46
- const location = bookingService.location.get();
89
+ const bookingLocation = bookingService.location.get();
47
90
  const timezone = bookingService.timezone.get();
48
91
  const formSubmission = bookingService.formSubmission.get();
49
92
  const { actions } = bookingService;
93
+ const location = resolveLocation(bookingLocation, serviceSelections);
50
94
  return children({
51
95
  serviceSelections,
52
96
  location,
@@ -55,3 +99,177 @@ export function Data(props) {
55
99
  actions,
56
100
  });
57
101
  }
102
+ /**
103
+ * Context for the current BookingItem
104
+ */
105
+ const BookingItemContext = React.createContext(null);
106
+ /**
107
+ * Hook to access the current BookingItem from context.
108
+ * Must be used within BookingItemProvider.
109
+ *
110
+ * @throws Error if used outside of BookingItemProvider context
111
+ */
112
+ export function useBookingItemContext() {
113
+ const context = React.useContext(BookingItemContext);
114
+ if (!context) {
115
+ throw new Error('BookingItem components must be used within BookingItemProvider');
116
+ }
117
+ return context;
118
+ }
119
+ /**
120
+ * Provider component used by repeaters to provide BookingItem context.
121
+ * Can be used by core consumers to create their own repeater.
122
+ *
123
+ * @component
124
+ * @example
125
+ * ```tsx
126
+ * {items.map((item) => (
127
+ * <BookingItemProvider key={item.instanceId} item={item}>
128
+ * {children}
129
+ * </BookingItemProvider>
130
+ * ))}
131
+ * ```
132
+ */
133
+ export function BookingItemProvider(props) {
134
+ const { children, item } = props;
135
+ return (_jsx(BookingItemContext.Provider, { value: item, children: children }));
136
+ }
137
+ /**
138
+ * Core component that provides access to booking item info via render props.
139
+ * Must be used within BookingItemProvider.
140
+ *
141
+ * @component
142
+ * @example
143
+ * ```tsx
144
+ * <BookingItemInfo>
145
+ * {({ service, timeSlot, staffName }) => (
146
+ * <div>
147
+ * <span>{service.name}</span>
148
+ * <span>{staffName}</span>
149
+ * </div>
150
+ * )}
151
+ * </BookingItemInfo>
152
+ * ```
153
+ */
154
+ export function BookingItemInfo(props) {
155
+ const item = useBookingItemContext();
156
+ const staffName = item.staff?.[0]?.name || '';
157
+ return props.children({
158
+ service: item.service,
159
+ timeSlot: item.timeSlot,
160
+ staffName,
161
+ instanceId: item.instanceId,
162
+ index: item.index,
163
+ item,
164
+ });
165
+ }
166
+ // ============================================================================
167
+ // Helper: Convert ServiceSelectionTimeSlot to SDK TimeSlot
168
+ // ============================================================================
169
+ /**
170
+ * Converts a ServiceSelectionTimeSlot to SDK TimeSlot format
171
+ * for compatibility with TimeSlot.Root component.
172
+ */
173
+ function mapSelectionTimeSlotToTimeSlot(serviceSelectionTimeSlot) {
174
+ if (!serviceSelectionTimeSlot) {
175
+ return null;
176
+ }
177
+ const availableResources = serviceSelectionTimeSlot.resources?.length
178
+ ? [{ resources: serviceSelectionTimeSlot.resources }]
179
+ : undefined;
180
+ return {
181
+ localStartDate: serviceSelectionTimeSlot.startDate,
182
+ localEndDate: serviceSelectionTimeSlot.endDate,
183
+ bookable: true,
184
+ totalCapacity: serviceSelectionTimeSlot.totalCapacity,
185
+ remainingCapacity: serviceSelectionTimeSlot.remainingCapacity,
186
+ scheduleId: serviceSelectionTimeSlot.scheduleId,
187
+ eventInfo: serviceSelectionTimeSlot.eventInfo,
188
+ location: serviceSelectionTimeSlot.location,
189
+ availableResources,
190
+ };
191
+ }
192
+ // ============================================================================
193
+ // Helper: Resolve Staff with Priority Logic
194
+ // ============================================================================
195
+ /**
196
+ * Gets selected staff resources with priority:
197
+ * 1. From selected time slot (if resources exist)
198
+ * 2. From service selection resources
199
+ * Returns null if no staff is chosen
200
+ */
201
+ function getSelectedStaff(serviceSelection) {
202
+ // Priority 1: From selected time slot
203
+ if (serviceSelection.timeSlot?.resources?.length) {
204
+ return serviceSelection.timeSlot.resources;
205
+ }
206
+ // Priority 2: From service selection
207
+ if (serviceSelection.resources?.length) {
208
+ return serviceSelection.resources;
209
+ }
210
+ // No staff chosen
211
+ return null;
212
+ }
213
+ /**
214
+ * Creates a BookingItem from a ServiceSelection
215
+ */
216
+ function createBookingItem(serviceSelection, index) {
217
+ return {
218
+ service: serviceSelection.service,
219
+ timeSlot: mapSelectionTimeSlotToTimeSlot(serviceSelection.timeSlot),
220
+ staff: getSelectedStaff(serviceSelection),
221
+ instanceId: serviceSelection.instanceId,
222
+ index,
223
+ };
224
+ }
225
+ /**
226
+ * Core component that provides access to booking items array via render props.
227
+ * Transforms serviceSelections into BookingItem objects.
228
+ *
229
+ * @component
230
+ * @example
231
+ * ```tsx
232
+ * <Booking.ItemsData>
233
+ * {({ items }) => (
234
+ * items.length > 0 ? (
235
+ * <div>{items.length} bookings</div>
236
+ * ) : (
237
+ * <div>No bookings</div>
238
+ * )
239
+ * )}
240
+ * </Booking.ItemsData>
241
+ * ```
242
+ */
243
+ export function ItemsData(props) {
244
+ const { children } = props;
245
+ const bookingService = useService(BookingServiceDefinition);
246
+ const serviceSelections = bookingService.serviceSelections.get();
247
+ const items = serviceSelections.map((selection, index) => createBookingItem(selection, index));
248
+ return children({ items });
249
+ }
250
+ /**
251
+ * Core component that provides payment data via render props.
252
+ * Computes slotServices from serviceSelections.
253
+ *
254
+ * @component
255
+ * @example
256
+ * ```tsx
257
+ * <Booking.PaymentData>
258
+ * {({ slotServices }) => (
259
+ * <Payment.Root slotServices={slotServices}>
260
+ * <Payment.TotalPrice />
261
+ * </Payment.Root>
262
+ * )}
263
+ * </Booking.PaymentData>
264
+ * ```
265
+ */
266
+ export function PaymentData(props) {
267
+ const { children } = props;
268
+ const bookingService = useService(BookingServiceDefinition);
269
+ const serviceSelections = bookingService.serviceSelections.get();
270
+ const slotServices = serviceSelections.map((selection) => ({
271
+ service: selection.service,
272
+ lineItemId: selection.instanceId,
273
+ }));
274
+ return children({ slotServices });
275
+ }
@@ -3,6 +3,7 @@ export * as BookingForm from './booking-form/BookingForm.js';
3
3
  export * as Service from './service/Service.js';
4
4
  export * as ServiceMedia from './service/ServiceMedia.js';
5
5
  export * as TimeSlotList from './time-slot-list/TimeSlotList.js';
6
+ export * as TimeSlot from './time-slot-list/TimeSlot.js';
6
7
  export * as ServiceList from './service-list/ServiceList.js';
7
8
  export * as Location from './location/Location.js';
8
9
  export * as LocationList from './location/LocationList.js';
@@ -3,6 +3,7 @@ export * as BookingForm from './booking-form/BookingForm.js';
3
3
  export * as Service from './service/Service.js';
4
4
  export * as ServiceMedia from './service/ServiceMedia.js';
5
5
  export * as TimeSlotList from './time-slot-list/TimeSlotList.js';
6
+ export * as TimeSlot from './time-slot-list/TimeSlot.js';
6
7
  export * as ServiceList from './service-list/ServiceList.js';
7
8
  export * as Location from './location/Location.js';
8
9
  export * as LocationList from './location/LocationList.js';
@@ -89,7 +89,7 @@ Root.displayName = 'Location.Root';
89
89
  export const Name = React.forwardRef((props, ref) => {
90
90
  const { asChild, children, className, ...attrs } = props;
91
91
  const { name } = useLocationContext();
92
- return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.locationName, customElement: children, customElementProps: { name: name ?? '' }, ...attrs, children: _jsx("span", { children: name }) }));
92
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.locationName, customElement: children, customElementProps: { name: name ?? '' }, ...attrs, children: name ? _jsx("span", { children: name }) : null }));
93
93
  });
94
94
  Name.displayName = 'Location.Name';
95
95
  /**
@@ -117,7 +117,7 @@ export const Type = React.forwardRef((props, ref) => {
117
117
  locationType,
118
118
  isCustomerLocation,
119
119
  isCustomLocation,
120
- }, ...attrs, children: _jsx("span", { children: locationType }) }));
120
+ }, ...attrs, children: locationType ? _jsx("span", { children: locationType }) : null }));
121
121
  });
122
122
  Type.displayName = 'Location.Type';
123
123
  /**