@wix/headless-bookings 0.0.49 → 0.0.51

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,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,42 @@ export function Root(props) {
39
44
  * </Booking.Data>
40
45
  * ```
41
46
  */
47
+ /**
48
+ * Maps a TimeSlotLocationType to ServiceLocationType.
49
+ * These types are structurally similar but have subtle differences
50
+ * (e.g., _id being string | null | undefined vs string | undefined).
51
+ */
52
+ function mapTimeSlotLocationToServiceLocation(timeSlotLocation) {
53
+ return {
54
+ ...timeSlotLocation,
55
+ _id: timeSlotLocation._id ?? undefined,
56
+ };
57
+ }
58
+ /**
59
+ * Resolves the location with fallback logic:
60
+ * 1. First, use the explicitly set booking location
61
+ * 2. If not set, fall back to the first service selection's slot location
62
+ */
63
+ function resolveLocation(bookingLocation, serviceSelections) {
64
+ if (bookingLocation) {
65
+ return bookingLocation;
66
+ }
67
+ // Fallback to first slot's location
68
+ const firstSlotLocation = serviceSelections[0]?.timeSlot?.location;
69
+ if (firstSlotLocation) {
70
+ return mapTimeSlotLocationToServiceLocation(firstSlotLocation);
71
+ }
72
+ return null;
73
+ }
42
74
  export function Data(props) {
43
75
  const { children } = props;
44
76
  const bookingService = useService(BookingServiceDefinition);
45
77
  const serviceSelections = bookingService.serviceSelections.get();
46
- const location = bookingService.location.get();
78
+ const bookingLocation = bookingService.location.get();
47
79
  const timezone = bookingService.timezone.get();
48
80
  const formSubmission = bookingService.formSubmission.get();
49
81
  const { actions } = bookingService;
82
+ const location = resolveLocation(bookingLocation, serviceSelections);
50
83
  return children({
51
84
  serviceSelections,
52
85
  location,
@@ -55,3 +88,177 @@ export function Data(props) {
55
88
  actions,
56
89
  });
57
90
  }
91
+ /**
92
+ * Context for the current BookingItem
93
+ */
94
+ const BookingItemContext = React.createContext(null);
95
+ /**
96
+ * Hook to access the current BookingItem from context.
97
+ * Must be used within BookingItemProvider.
98
+ *
99
+ * @throws Error if used outside of BookingItemProvider context
100
+ */
101
+ export function useBookingItemContext() {
102
+ const context = React.useContext(BookingItemContext);
103
+ if (!context) {
104
+ throw new Error('BookingItem components must be used within BookingItemProvider');
105
+ }
106
+ return context;
107
+ }
108
+ /**
109
+ * Provider component used by repeaters to provide BookingItem context.
110
+ * Can be used by core consumers to create their own repeater.
111
+ *
112
+ * @component
113
+ * @example
114
+ * ```tsx
115
+ * {items.map((item) => (
116
+ * <BookingItemProvider key={item.instanceId} item={item}>
117
+ * {children}
118
+ * </BookingItemProvider>
119
+ * ))}
120
+ * ```
121
+ */
122
+ export function BookingItemProvider(props) {
123
+ const { children, item } = props;
124
+ return (_jsx(BookingItemContext.Provider, { value: item, children: children }));
125
+ }
126
+ /**
127
+ * Core component that provides access to booking item info via render props.
128
+ * Must be used within BookingItemProvider.
129
+ *
130
+ * @component
131
+ * @example
132
+ * ```tsx
133
+ * <BookingItemInfo>
134
+ * {({ service, timeSlot, staffName }) => (
135
+ * <div>
136
+ * <span>{service.name}</span>
137
+ * <span>{staffName}</span>
138
+ * </div>
139
+ * )}
140
+ * </BookingItemInfo>
141
+ * ```
142
+ */
143
+ export function BookingItemInfo(props) {
144
+ const item = useBookingItemContext();
145
+ const staffName = item.staff?.[0]?.name || '';
146
+ return props.children({
147
+ service: item.service,
148
+ timeSlot: item.timeSlot,
149
+ staffName,
150
+ instanceId: item.instanceId,
151
+ index: item.index,
152
+ item,
153
+ });
154
+ }
155
+ // ============================================================================
156
+ // Helper: Convert ServiceSelectionTimeSlot to SDK TimeSlot
157
+ // ============================================================================
158
+ /**
159
+ * Converts a ServiceSelectionTimeSlot to SDK TimeSlot format
160
+ * for compatibility with TimeSlot.Root component.
161
+ */
162
+ function mapSelectionTimeSlotToTimeSlot(serviceSelectionTimeSlot) {
163
+ if (!serviceSelectionTimeSlot) {
164
+ return null;
165
+ }
166
+ const availableResources = serviceSelectionTimeSlot.resources?.length
167
+ ? [{ resources: serviceSelectionTimeSlot.resources }]
168
+ : undefined;
169
+ return {
170
+ localStartDate: serviceSelectionTimeSlot.startDate,
171
+ localEndDate: serviceSelectionTimeSlot.endDate,
172
+ bookable: true,
173
+ totalCapacity: serviceSelectionTimeSlot.totalCapacity,
174
+ remainingCapacity: serviceSelectionTimeSlot.remainingCapacity,
175
+ scheduleId: serviceSelectionTimeSlot.scheduleId,
176
+ eventInfo: serviceSelectionTimeSlot.eventInfo,
177
+ location: serviceSelectionTimeSlot.location,
178
+ availableResources,
179
+ };
180
+ }
181
+ // ============================================================================
182
+ // Helper: Resolve Staff with Priority Logic
183
+ // ============================================================================
184
+ /**
185
+ * Gets selected staff resources with priority:
186
+ * 1. From selected time slot (if resources exist)
187
+ * 2. From service selection resources
188
+ * Returns null if no staff is chosen
189
+ */
190
+ function getSelectedStaff(serviceSelection) {
191
+ // Priority 1: From selected time slot
192
+ if (serviceSelection.timeSlot?.resources?.length) {
193
+ return serviceSelection.timeSlot.resources;
194
+ }
195
+ // Priority 2: From service selection
196
+ if (serviceSelection.resources?.length) {
197
+ return serviceSelection.resources;
198
+ }
199
+ // No staff chosen
200
+ return null;
201
+ }
202
+ /**
203
+ * Creates a BookingItem from a ServiceSelection
204
+ */
205
+ function createBookingItem(serviceSelection, index) {
206
+ return {
207
+ service: serviceSelection.service,
208
+ timeSlot: mapSelectionTimeSlotToTimeSlot(serviceSelection.timeSlot),
209
+ staff: getSelectedStaff(serviceSelection),
210
+ instanceId: serviceSelection.instanceId,
211
+ index,
212
+ };
213
+ }
214
+ /**
215
+ * Core component that provides access to booking items array via render props.
216
+ * Transforms serviceSelections into BookingItem objects.
217
+ *
218
+ * @component
219
+ * @example
220
+ * ```tsx
221
+ * <Booking.ItemsData>
222
+ * {({ items }) => (
223
+ * items.length > 0 ? (
224
+ * <div>{items.length} bookings</div>
225
+ * ) : (
226
+ * <div>No bookings</div>
227
+ * )
228
+ * )}
229
+ * </Booking.ItemsData>
230
+ * ```
231
+ */
232
+ export function ItemsData(props) {
233
+ const { children } = props;
234
+ const bookingService = useService(BookingServiceDefinition);
235
+ const serviceSelections = bookingService.serviceSelections.get();
236
+ const items = serviceSelections.map((selection, index) => createBookingItem(selection, index));
237
+ return children({ items });
238
+ }
239
+ /**
240
+ * Core component that provides payment data via render props.
241
+ * Computes slotServices from serviceSelections.
242
+ *
243
+ * @component
244
+ * @example
245
+ * ```tsx
246
+ * <Booking.PaymentData>
247
+ * {({ slotServices }) => (
248
+ * <Payment.Root slotServices={slotServices}>
249
+ * <Payment.TotalPrice />
250
+ * </Payment.Root>
251
+ * )}
252
+ * </Booking.PaymentData>
253
+ * ```
254
+ */
255
+ export function PaymentData(props) {
256
+ const { children } = props;
257
+ const bookingService = useService(BookingServiceDefinition);
258
+ const serviceSelections = bookingService.serviceSelections.get();
259
+ const slotServices = serviceSelections.map((selection) => ({
260
+ service: selection.service,
261
+ lineItemId: selection.instanceId,
262
+ }));
263
+ return children({ slotServices });
264
+ }
@@ -11,7 +11,7 @@ import type { Service } from '@wix/auto_sdk_bookings_services';
11
11
  * - Offline payment: payNow = 0
12
12
  *
13
13
  * Force checkout when:
14
- * - Service has cancellation fee
14
+ * - Service has cancellation fee policy
15
15
  *
16
16
  * @param checkout - The checkout response from createCheckout
17
17
  * @param service - The service being booked
@@ -9,15 +9,15 @@
9
9
  * - Offline payment: payNow = 0
10
10
  *
11
11
  * Force checkout when:
12
- * - Service has cancellation fee
12
+ * - Service has cancellation fee policy
13
13
  *
14
14
  * @param checkout - The checkout response from createCheckout
15
15
  * @param service - The service being booked
16
16
  * @returns true if checkout page is required, false if can skip to order
17
17
  */
18
18
  export function isCheckoutRequired(checkout, service) {
19
- // Force checkout for cancellation fee
20
- const hasCancellationFee = service.bookingPolicy?.cancellationPolicy?.enabled;
19
+ // Force checkout for cancellation fee policy
20
+ const hasCancellationFee = service.bookingPolicy?.cancellationFeePolicy?.enabled;
21
21
  if (hasCancellationFee) {
22
22
  return true;
23
23
  }
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Booking - High-level component for managing bookings
3
- * Provides root component for booking context
3
+ * Provides root component for booking context and booking item display components
4
4
  */
5
5
  import React from 'react';
6
+ import * as CoreBooking from '../core/booking/Booking.js';
6
7
  import type { BookingServiceConfig } from '../../services/booking/booking.js';
7
- export type { BookingData, DataProps } from '../core/booking/Booking.js';
8
+ import { type AsChildChildren } from '@wix/headless-utils/react';
9
+ export type { BookingData, DataProps, BookingItem, } from '../core/booking/Booking.js';
8
10
  /**
9
11
  * Props for Booking.Root component
10
12
  */
@@ -52,3 +54,212 @@ export declare const Root: {
52
54
  export declare const Actions: {
53
55
  Book: React.ForwardRefExoticComponent<import("./Book.js").BookProps & React.RefAttributes<HTMLButtonElement>>;
54
56
  };
57
+ /**
58
+ * Hook to access the current BookingItem from context.
59
+ * Must be used within Booking.ItemRepeater.
60
+ *
61
+ * @throws Error if used outside of ItemRepeater context
62
+ */
63
+ export declare const useBookingItem: typeof CoreBooking.useBookingItemContext;
64
+ /**
65
+ * Props for Booking.ItemsRoot component (Container Level)
66
+ */
67
+ export interface ItemsRootProps {
68
+ children: React.ReactNode;
69
+ className?: string;
70
+ }
71
+ /**
72
+ * Container component that provides GenericList.Root context for booking items.
73
+ * Use Booking.Items inside for list container with emptyState support.
74
+ *
75
+ * @component
76
+ * @example
77
+ * ```tsx
78
+ * <Booking.ItemsRoot>
79
+ * <Booking.Items emptyState={<div>No bookings selected</div>}>
80
+ * <Booking.ItemRepeater>
81
+ * <div className="booking-card">
82
+ * <Booking.Service>
83
+ * <Service.Name />
84
+ * </Booking.Service>
85
+ * </div>
86
+ * </Booking.ItemRepeater>
87
+ * </Booking.Items>
88
+ * </Booking.ItemsRoot>
89
+ * ```
90
+ */
91
+ export declare const ItemsRoot: React.ForwardRefExoticComponent<ItemsRootProps & React.RefAttributes<HTMLDivElement>>;
92
+ /**
93
+ * Props for Booking.Items component (List Container Level)
94
+ */
95
+ export interface ItemsProps {
96
+ children: React.ReactNode;
97
+ /** Content to render when there are no booking items */
98
+ emptyState?: React.ReactNode;
99
+ className?: string;
100
+ }
101
+ /**
102
+ * List container component with emptyState support.
103
+ * Wraps GenericList.Items. Must be used within Booking.ItemsRoot.
104
+ *
105
+ * @component
106
+ * @example
107
+ * ```tsx
108
+ * <Booking.ItemsRoot>
109
+ * <Booking.Items emptyState={<div>No bookings selected</div>}>
110
+ * <Booking.ItemRepeater>
111
+ * <Booking.Service>
112
+ * <Service.Name />
113
+ * </Booking.Service>
114
+ * </Booking.ItemRepeater>
115
+ * </Booking.Items>
116
+ * </Booking.ItemsRoot>
117
+ * ```
118
+ */
119
+ export declare const Items: React.ForwardRefExoticComponent<ItemsProps & React.RefAttributes<HTMLElement>>;
120
+ /**
121
+ * Props for Booking.ItemRepeater component
122
+ */
123
+ export interface ItemRepeaterProps {
124
+ children: React.ReactNode;
125
+ }
126
+ /**
127
+ * Repeater component that maps over booking items and provides context per item.
128
+ * Uses GenericList.Repeater internally with itemWrapper that provides BookingItemContext.
129
+ * Children have access to Booking.Service, Booking.TimeSlot, etc.
130
+ *
131
+ * @component
132
+ * @example
133
+ * ```tsx
134
+ * <Booking.ItemRepeater>
135
+ * <div className="booking-card">
136
+ * <Booking.Service>
137
+ * <Service.Name />
138
+ * <Service.Price />
139
+ * </Booking.Service>
140
+ * <Booking.TimeSlot>
141
+ * <TimeSlot.StartDate />
142
+ * </Booking.TimeSlot>
143
+ * <Booking.StaffName />
144
+ * </div>
145
+ * </Booking.ItemRepeater>
146
+ * ```
147
+ */
148
+ export declare const ItemRepeater: React.ForwardRefExoticComponent<ItemRepeaterProps & React.RefAttributes<HTMLElement>>;
149
+ /**
150
+ * Props for Booking.Service component
151
+ */
152
+ export interface BookingServiceProps {
153
+ children: React.ReactNode;
154
+ }
155
+ /**
156
+ * Wraps Service.Root with the service from the current booking item context.
157
+ * Children can use all Service.* components (Service.Name, Service.Price, etc.).
158
+ *
159
+ * @component
160
+ * @example
161
+ * ```tsx
162
+ * <Booking.Service>
163
+ * <Service.Name className="text-xl font-bold" />
164
+ * <Service.Description />
165
+ * <Service.Price />
166
+ * </Booking.Service>
167
+ * ```
168
+ */
169
+ export declare function Service(props: BookingServiceProps): React.ReactNode;
170
+ /**
171
+ * Props for Booking.TimeSlot component
172
+ */
173
+ export interface BookingTimeSlotProps {
174
+ children: React.ReactNode;
175
+ }
176
+ /**
177
+ * Wraps TimeSlot.Root with the timeSlot from the current booking item context.
178
+ * Children can use all TimeSlot.* components (TimeSlot.StartDate, TimeSlot.Duration, etc.).
179
+ * Always renders with data-has-time-slot attribute for CSS targeting.
180
+ *
181
+ * @component
182
+ * @example
183
+ * ```tsx
184
+ * <Booking.TimeSlot>
185
+ * <TimeSlot.StartDate />
186
+ * <TimeSlot.Duration />
187
+ * </Booking.TimeSlot>
188
+ * ```
189
+ */
190
+ export declare function TimeSlot(props: BookingTimeSlotProps): React.ReactNode;
191
+ /**
192
+ * Props for Booking.Payment component
193
+ */
194
+ export interface BookingPaymentProps {
195
+ children: React.ReactNode;
196
+ }
197
+ /**
198
+ * Wraps Payment.Root with all services from all booking items.
199
+ * Should be used outside of ItemRepeater - it calculates payment for all items.
200
+ * Children can use all Payment.* components (Payment.Total, Payment.Subtotal, etc.).
201
+ * Always renders with data-has-payment attribute for CSS targeting.
202
+ *
203
+ * @component
204
+ * @example
205
+ * ```tsx
206
+ * <Booking.Root>
207
+ * <Booking.ItemsRoot>
208
+ * <Booking.Items>
209
+ * <Booking.ItemRepeater>
210
+ * <Booking.Service><Service.Name /></Booking.Service>
211
+ * </Booking.ItemRepeater>
212
+ * </Booking.Items>
213
+ * </Booking.ItemsRoot>
214
+ *
215
+ * <Booking.Payment>
216
+ * <Payment.Total className="text-xl font-bold" />
217
+ * <Payment.Subtotal />
218
+ * </Booking.Payment>
219
+ * </Booking.Root>
220
+ * ```
221
+ */
222
+ export declare function Payment(props: BookingPaymentProps): React.ReactNode;
223
+ /**
224
+ * Props for Booking.Location component
225
+ */
226
+ export interface BookingLocationProps {
227
+ children: React.ReactNode;
228
+ }
229
+ /**
230
+ * Wraps Location.Root with the location from the booking context.
231
+ * Children can use all Location.* components (Location.Name, Location.Address, etc.).
232
+ * Always renders with data-has-location attribute for CSS targeting.
233
+ *
234
+ * @component
235
+ * @example
236
+ * ```tsx
237
+ * <Booking.Location>
238
+ * <Location.Name className="font-bold" />
239
+ * <Location.Address />
240
+ * <Location.Phone />
241
+ * </Booking.Location>
242
+ * ```
243
+ */
244
+ export declare function Location(props: BookingLocationProps): React.ReactNode;
245
+ /**
246
+ * Props for Booking.StaffName component
247
+ */
248
+ export interface StaffNameProps {
249
+ asChild?: boolean;
250
+ children?: AsChildChildren<{
251
+ name: string;
252
+ }>;
253
+ className?: string;
254
+ }
255
+ /**
256
+ * Displays the first staff member's name from the current booking item.
257
+ * Always renders with data-has-staff-name attribute for CSS targeting.
258
+ *
259
+ * @component
260
+ * @example
261
+ * ```tsx
262
+ * <Booking.StaffName className="text-foreground" />
263
+ * ```
264
+ */
265
+ export declare const StaffName: React.ForwardRefExoticComponent<StaffNameProps & React.RefAttributes<HTMLElement>>;