@wix/headless-bookings 0.0.15 → 0.0.17

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 (39) hide show
  1. package/cjs/dist/api/time-slot-list/index.d.ts +21 -1
  2. package/cjs/dist/api/time-slot-list/index.js +46 -8
  3. package/cjs/dist/react/core/booking/Booking.d.ts +17 -9
  4. package/cjs/dist/react/core/booking/Booking.js +13 -7
  5. package/cjs/dist/react/core/time-slot-list/TimeSlot.d.ts +22 -18
  6. package/cjs/dist/react/core/time-slot-list/TimeSlot.js +41 -28
  7. package/cjs/dist/react/core/time-slot-list/TimeSlotList.d.ts +39 -7
  8. package/cjs/dist/react/core/time-slot-list/TimeSlotList.js +56 -11
  9. package/cjs/dist/react/time-slot-list/TimeSlot.js +3 -3
  10. package/cjs/dist/react/time-slot-list/TimeSlotList.d.ts +55 -12
  11. package/cjs/dist/react/time-slot-list/TimeSlotList.js +47 -17
  12. package/cjs/dist/services/booking/booking.d.ts +48 -30
  13. package/cjs/dist/services/booking/booking.js +74 -44
  14. package/cjs/dist/services/index.d.ts +3 -3
  15. package/cjs/dist/services/service/service.js +10 -7
  16. package/cjs/dist/services/time-slot-list/index.d.ts +1 -1
  17. package/cjs/dist/services/time-slot-list/time-slot-list.d.ts +3 -3
  18. package/cjs/dist/services/time-slot-list/time-slot-list.def.d.ts +23 -4
  19. package/cjs/dist/services/time-slot-list/time-slot-list.js +146 -65
  20. package/dist/api/time-slot-list/index.d.ts +21 -1
  21. package/dist/api/time-slot-list/index.js +46 -8
  22. package/dist/react/core/booking/Booking.d.ts +17 -9
  23. package/dist/react/core/booking/Booking.js +13 -7
  24. package/dist/react/core/time-slot-list/TimeSlot.d.ts +22 -18
  25. package/dist/react/core/time-slot-list/TimeSlot.js +41 -28
  26. package/dist/react/core/time-slot-list/TimeSlotList.d.ts +39 -7
  27. package/dist/react/core/time-slot-list/TimeSlotList.js +56 -11
  28. package/dist/react/time-slot-list/TimeSlot.js +3 -3
  29. package/dist/react/time-slot-list/TimeSlotList.d.ts +55 -12
  30. package/dist/react/time-slot-list/TimeSlotList.js +47 -17
  31. package/dist/services/booking/booking.d.ts +48 -30
  32. package/dist/services/booking/booking.js +74 -44
  33. package/dist/services/index.d.ts +3 -3
  34. package/dist/services/service/service.js +10 -7
  35. package/dist/services/time-slot-list/index.d.ts +1 -1
  36. package/dist/services/time-slot-list/time-slot-list.d.ts +3 -3
  37. package/dist/services/time-slot-list/time-slot-list.def.d.ts +23 -4
  38. package/dist/services/time-slot-list/time-slot-list.js +146 -65
  39. package/package.json +3 -2
@@ -3,7 +3,7 @@
3
3
  * Defines interfaces and service contract for time slot management
4
4
  */
5
5
  import type { Signal } from '@wix/services-definitions/core-services/signals';
6
- import type { TimeSlot as ApiTimeSlot } from '@wix/auto_sdk_bookings_availability-time-slots';
6
+ import type { TimeSlot as ApiTimeSlot, Location } from '@wix/auto_sdk_bookings_availability-time-slots';
7
7
  import type { Service } from '@wix/auto_sdk_bookings_services';
8
8
  export declare enum ViewMode {
9
9
  CLASSIC = "classic",
@@ -42,6 +42,26 @@ export interface SlotSelection {
42
42
  locationId: string | null;
43
43
  staffMemberId: string | null;
44
44
  }
45
+ /**
46
+ * Filters for narrowing down available time slots
47
+ * Matches Wix Bookings API filter parameters
48
+ */
49
+ export interface TimeSlotsFilters {
50
+ /** Filter by specific locations */
51
+ locations?: Location[];
52
+ /** Filter by specific staff member IDs */
53
+ staffMemberIds?: string[];
54
+ /** Maximum slots to return per day (useful for calendar views) */
55
+ slotsPerDay?: number;
56
+ /** Filter by bookable status. If undefined, returns both bookable and non-bookable */
57
+ bookable?: boolean;
58
+ /** Filter by booking policy violations */
59
+ bookingPolicyViolations?: {
60
+ tooEarlyToBook?: boolean;
61
+ tooLateToBook?: boolean;
62
+ bookOnlineDisabled?: boolean;
63
+ };
64
+ }
45
65
  export interface TimeSlotListServiceConfig {
46
66
  /** Services to fetch time slots for */
47
67
  services?: Service[];
@@ -53,8 +73,8 @@ export interface TimeSlotListServiceConfig {
53
73
  endDate?: Date;
54
74
  /** Timezone for formatting */
55
75
  timezone?: string;
56
- /** View mode */
57
- viewMode?: ViewMode;
76
+ /** Filters for narrowing down time slots */
77
+ filters?: TimeSlotsFilters;
58
78
  }
59
79
  export interface TimeSlotListServiceActions {
60
80
  selectSlot: (slot: TimeSlot) => void;
@@ -71,7 +91,6 @@ export interface TimeSlotListServiceAPI {
71
91
  isLoading: Signal<boolean>;
72
92
  error: Signal<string | null>;
73
93
  hasMore: Signal<boolean>;
74
- viewMode: Signal<ViewMode>;
75
94
  startDate: Signal<Date>;
76
95
  endDate: Signal<Date>;
77
96
  timezone: Signal<string | undefined>;
@@ -6,7 +6,7 @@ import { implementService } from '@wix/services-definitions';
6
6
  import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
7
7
  import { TimeSlotListServiceDefinition, ViewMode, } from './time-slot-list.def.js';
8
8
  import { fetchAvailability } from '../../api/time-slot-list/index.js';
9
- import { BookingServiceDefinition } from '../booking/booking.js';
9
+ import { BookingServiceDefinition, } from '../booking/booking.js';
10
10
  // ============================================================================
11
11
  // Helpers
12
12
  // ============================================================================
@@ -68,7 +68,8 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
68
68
  const isLoadingSignal = signalsService.signal(false);
69
69
  const errorSignal = signalsService.signal(null);
70
70
  const hasMoreSignal = signalsService.signal(false);
71
- const viewModeSignal = signalsService.signal(config.viewMode || ViewMode.CLASSIC);
71
+ // Internal only - viewMode is auto-detected based on service types
72
+ const viewModeSignal = signalsService.signal(ViewMode.CLASSIC);
72
73
  const startDateSignal = signalsService.signal(config.startDate || currentDate);
73
74
  const endDateSignal = signalsService.signal(config.endDate || getDefaultEndDate());
74
75
  const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
@@ -79,7 +80,29 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
79
80
  const getServices = () => {
80
81
  if (config.services?.length)
81
82
  return config.services;
82
- return bookingService.bookingSelections.get().map((s) => s.service);
83
+ return bookingService.bookingItems.get().map((s) => s.service);
84
+ };
85
+ /**
86
+ * Helper: Get filters from config or BookingService
87
+ */
88
+ const getFilters = () => {
89
+ // Get BookingService selections (these take priority)
90
+ const location = bookingService.location.get();
91
+ const selections = bookingService.bookingItems.get();
92
+ const selectedStaffMemberIds = selections
93
+ .map(({ resources }) => resources.map((r) => r._id))
94
+ .flat();
95
+ const selectedLocation = location ? { _id: location.id } : null;
96
+ // Merge config filters with BookingService selections (selections win)
97
+ return {
98
+ ...config.filters,
99
+ locations: selectedLocation
100
+ ? [selectedLocation]
101
+ : config.filters?.locations,
102
+ staffMemberIds: selectedStaffMemberIds.length
103
+ ? selectedStaffMemberIds
104
+ : config.filters?.staffMemberIds,
105
+ };
83
106
  };
84
107
  /**
85
108
  * Fetch time slots from API
@@ -87,6 +110,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
87
110
  */
88
111
  const fetchTimeSlots = async () => {
89
112
  const selectedServices = getServices();
113
+ const filters = getFilters();
90
114
  isLoadingSignal.set(true);
91
115
  errorSignal.set(null);
92
116
  cursorSignal.set(undefined);
@@ -97,6 +121,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
97
121
  toLocalDate: toLocalDateString(endDateSignal.get()),
98
122
  timeZone: timezoneSignal.get(),
99
123
  viewMode: viewModeSignal.get(),
124
+ ...filters,
100
125
  });
101
126
  timeSlotsSignal.set(response.timeSlots);
102
127
  const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
@@ -115,82 +140,139 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
115
140
  };
116
141
  /**
117
142
  * Action: Select a time slot
118
- * Only updates booking service if there's exactly one location and one resource
119
- * Otherwise, user must select location/resource before booking
143
+ * Auto-selects location/staff based on availability:
144
+ * - One location + one staff → auto-select both
145
+ * - One location + many staff → auto-select location only
146
+ * - Many locations + one staff → auto-select staff only
147
+ * - Many locations + many staff → don't auto-select anything
120
148
  */
121
149
  const selectSlot = (slot) => {
122
150
  const currentSelection = selectionSignal.get();
123
151
  if (currentSelection?.slot?.id === slot.id)
124
152
  return;
153
+ const timeSlot = {
154
+ startDate: slot.localStartDate,
155
+ endDate: slot.localEndDate,
156
+ totalCapacity: slot.totalCapacity ?? undefined,
157
+ remainingCapacity: slot.remainingCapacity ?? undefined,
158
+ scheduleId: slot.scheduleId ?? undefined,
159
+ eventInfo: slot.eventInfo ?? undefined,
160
+ };
125
161
  const hasOneLocation = slot.staffByLocation.length === 1;
126
- const hasOneResource = hasOneLocation && slot.staffByLocation[0].staffMembers.length === 1;
127
- if (hasOneLocation && hasOneResource) {
128
- const originalSlot = slot.originalSlots[0];
129
- const { location, staffMembers } = slot.staffByLocation[0];
130
- const staffMemberId = staffMembers[0].id;
131
- selectionSignal.set({
132
- slot,
133
- locationId: location.id,
134
- staffMemberId,
135
- });
136
- bookingService.actions.setTimeSlot(originalSlot);
137
- bookingService.actions.setLocation({
138
- id: location.id,
139
- name: location.name,
140
- });
141
- bookingService.actions.updateBookingSelection({
142
- staff: {
143
- _id: staffMemberId,
144
- name: staffMembers[0].name,
145
- },
162
+ // Collect all unique staff members across all locations
163
+ const allStaffMembers = slot.staffByLocation.flatMap((loc) => loc.staffMembers);
164
+ const uniqueStaffIds = new Set(allStaffMembers.map((s) => s.id));
165
+ const hasOneStaff = uniqueStaffIds.size === 1;
166
+ // Determine what to auto-select
167
+ let locationId = null;
168
+ let staffMemberId = null;
169
+ if (hasOneLocation) {
170
+ const { location } = slot.staffByLocation[0];
171
+ locationId = location.id;
172
+ bookingService.actions.setLocation(location);
173
+ }
174
+ else {
175
+ bookingService.actions.clearLocation();
176
+ }
177
+ if (hasOneStaff) {
178
+ const staffMember = allStaffMembers[0];
179
+ staffMemberId = staffMember.id;
180
+ bookingService.actions.updateItem({
181
+ timeSlot,
182
+ resources: [
183
+ {
184
+ _id: staffMemberId,
185
+ name: staffMember.name,
186
+ },
187
+ ],
146
188
  });
147
189
  }
148
190
  else {
149
- selectionSignal.set({
150
- slot,
151
- locationId: null,
152
- staffMemberId: null,
191
+ bookingService.actions.updateItem({
192
+ timeSlot,
193
+ resources: [],
153
194
  });
154
195
  }
196
+ selectionSignal.set({
197
+ slot,
198
+ locationId,
199
+ staffMemberId,
200
+ });
155
201
  };
156
202
  /**
157
203
  * Action: Select a location for the current slot
158
- * Updates the booking service with the correct original slot for this location
204
+ * Updates the booking service with the location
205
+ * If the location has only one staff member, auto-selects that staff member too
159
206
  */
160
207
  const selectLocation = (locationId) => {
161
208
  const currentSelections = selectionSignal.get();
162
209
  if (!currentSelections?.slot)
163
210
  return;
164
- selectionSignal.set({ ...currentSelections, locationId });
165
- const location = currentSelections.slot.originalSlots.find((s) => s.location?._id === locationId)?.location;
166
- if (location) {
167
- bookingService.actions.setLocation({
168
- id: location._id,
169
- name: location.name,
211
+ const slot = currentSelections.slot;
212
+ const locationEntry = slot.staffByLocation.find((loc) => loc.location.id === locationId);
213
+ if (!locationEntry)
214
+ return;
215
+ const { location, staffMembers } = locationEntry;
216
+ bookingService.actions.setLocation(location);
217
+ // If this location has only one staff member, auto-select them
218
+ const hasOneStaff = staffMembers.length === 1;
219
+ if (hasOneStaff) {
220
+ const staffMember = staffMembers[0];
221
+ selectionSignal.set({
222
+ ...currentSelections,
223
+ locationId,
224
+ staffMemberId: staffMember.id,
225
+ });
226
+ bookingService.actions.updateItem({
227
+ resources: [
228
+ {
229
+ _id: staffMember.id,
230
+ name: staffMember.name,
231
+ },
232
+ ],
170
233
  });
171
234
  }
235
+ else {
236
+ selectionSignal.set({ ...currentSelections, locationId });
237
+ }
172
238
  };
173
239
  /**
174
240
  * Action: Select a staff member for the current slot
175
- * Updates the booking service with the selected resource for the slot's service
241
+ * Updates the booking service with the selected resource
242
+ * If the staff member is only available at one location, auto-selects that location too
176
243
  */
177
244
  const selectStaffMember = (staffMemberId) => {
178
245
  const currentSelections = selectionSignal.get();
179
246
  if (!currentSelections?.slot)
180
247
  return;
181
- selectionSignal.set({ ...currentSelections, staffMemberId });
182
248
  const slot = currentSelections.slot;
183
- // Find the resource info from original slots
184
- for (const originalSlot of slot.originalSlots) {
185
- for (const resourceGroup of originalSlot.availableResources || []) {
186
- const staff = resourceGroup.resources?.find((r) => r._id === staffMemberId);
187
- if (staff) {
188
- bookingService.actions.updateBookingSelection({
189
- staff,
190
- });
191
- return;
192
- }
193
- }
249
+ // Find all locations that have this staff member
250
+ const locationsWithThisStaff = slot.staffByLocation.filter((loc) => loc.staffMembers.some((s) => s.id === staffMemberId));
251
+ // Find the staff member info
252
+ const staffMember = locationsWithThisStaff[0]?.staffMembers.find((s) => s.id === staffMemberId);
253
+ if (!staffMember)
254
+ return;
255
+ bookingService.actions.updateItem({
256
+ resources: [
257
+ {
258
+ _id: staffMember.id,
259
+ name: staffMember.name,
260
+ },
261
+ ],
262
+ });
263
+ // If this staff member is only at one location, auto-select that location
264
+ const hasOneLocation = locationsWithThisStaff.length === 1;
265
+ if (hasOneLocation) {
266
+ const location = locationsWithThisStaff[0].location;
267
+ selectionSignal.set({
268
+ ...currentSelections,
269
+ staffMemberId,
270
+ locationId: location.id,
271
+ });
272
+ bookingService.actions.setLocation(location);
273
+ }
274
+ else {
275
+ selectionSignal.set({ ...currentSelections, staffMemberId });
194
276
  }
195
277
  };
196
278
  /**
@@ -206,15 +288,11 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
206
288
  locationId: null,
207
289
  staffMemberId: null,
208
290
  });
209
- // Clear location in booking service
291
+ // Clear selections in booking service
210
292
  bookingService.actions.clearLocation();
211
- // Clear resources (staff) in booking service
212
- const serviceId = currentSelection.slot.serviceId;
213
- if (serviceId) {
214
- bookingService.actions.updateBookingSelection({
215
- staff: undefined,
216
- });
217
- }
293
+ bookingService.actions.updateItem({
294
+ resources: [],
295
+ });
218
296
  };
219
297
  /**
220
298
  * Action: Load more slots (pagination)
@@ -226,6 +304,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
226
304
  const selectedServices = getServices();
227
305
  if (selectedServices.length === 0)
228
306
  return;
307
+ const filters = getFilters();
229
308
  isLoadingSignal.set(true);
230
309
  try {
231
310
  const response = await fetchAvailability({
@@ -235,6 +314,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
235
314
  timeZone: timezoneSignal.get(),
236
315
  cursorPaging: { cursor },
237
316
  viewMode: viewModeSignal.get(),
317
+ ...filters,
238
318
  });
239
319
  // API returns grouped slots, merge with existing
240
320
  const existing = timeSlotsSignal.get();
@@ -257,6 +337,12 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
257
337
  * Action: Update date range and refetch slots
258
338
  */
259
339
  const setDateRange = (newStartDate, newEndDate) => {
340
+ const currentStartDate = startDateSignal.get();
341
+ const currentEndDate = endDateSignal.get();
342
+ if (currentStartDate.getTime() === newStartDate.getTime() &&
343
+ currentEndDate.getTime() === newEndDate.getTime()) {
344
+ return;
345
+ }
260
346
  startDateSignal.set(newStartDate);
261
347
  endDateSignal.set(newEndDate);
262
348
  fetchTimeSlots();
@@ -280,8 +366,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
280
366
  isLoading: isLoadingSignal,
281
367
  error: errorSignal,
282
368
  hasMore: hasMoreSignal,
283
- // Configuration Signals
284
- viewMode: viewModeSignal,
369
+ // Configuration Signals (viewMode is internal only)
285
370
  startDate: startDateSignal,
286
371
  endDate: endDateSignal,
287
372
  timezone: timezoneSignal,
@@ -322,23 +407,19 @@ export async function loadTimeSlotListConfig(options) {
322
407
  const now = new Date();
323
408
  const startDate = options.startDate || now;
324
409
  const endDate = options.endDate || getDefaultEndDate();
325
- const viewMode = options.viewMode || ViewMode.CLASSIC;
326
- // check for location and staff in config
327
410
  try {
328
411
  const response = await fetchAvailability({
329
412
  services: options.services || [],
330
413
  fromLocalDate: toLocalDateString(startDate),
331
414
  toLocalDate: toLocalDateString(endDate),
332
415
  timeZone: options.timezone ?? getClientTimezone(),
333
- viewMode,
416
+ ...options.filters,
334
417
  });
335
418
  const config = {
336
- services: options.services,
419
+ ...options,
337
420
  timeSlots: response.timeSlots || [],
338
421
  startDate,
339
422
  endDate,
340
- timezone: options.timezone,
341
- viewMode,
342
423
  };
343
424
  return { type: 'success', config };
344
425
  }
@@ -11,8 +11,20 @@ export interface FetchAvailabilityOptions {
11
11
  toLocalDate?: string | null;
12
12
  timeZone?: string | null;
13
13
  locations?: Location[];
14
+ /** Filter by specific staff member IDs (passed as resourceIds to API) */
15
+ staffMemberIds?: string[];
14
16
  cursorPaging?: CursorPaging;
15
17
  viewMode?: ViewMode;
18
+ /** Maximum slots to return per day (useful for calendar views) */
19
+ slotsPerDay?: number;
20
+ /** Filter by bookable status. If undefined, returns both bookable and non-bookable */
21
+ bookable?: boolean;
22
+ /** Filter by booking policy violations */
23
+ bookingPolicyViolations?: {
24
+ tooEarlyToBook?: boolean;
25
+ tooLateToBook?: boolean;
26
+ bookOnlineDisabled?: boolean;
27
+ };
16
28
  }
17
29
  /**
18
30
  * Response from fetchAvailability with grouped time slots
@@ -42,12 +54,20 @@ export interface FetchAvailabilityResponse {
42
54
  * // response.timeSlots are grouped by time
43
55
  * ```
44
56
  */
45
- export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone, locations, cursorPaging, }: {
57
+ export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursorPaging, slotsPerDay, bookable, bookingPolicyViolations, }: {
46
58
  services: Service[];
47
59
  fromLocalDate: string;
48
60
  toLocalDate: string;
49
61
  timeZone?: string;
50
62
  locations?: Location[];
63
+ staffMemberIds?: string[];
51
64
  cursorPaging?: CursorPaging;
52
65
  viewMode?: ViewMode;
66
+ slotsPerDay?: number;
67
+ bookable?: boolean;
68
+ bookingPolicyViolations?: {
69
+ tooEarlyToBook?: boolean;
70
+ tooLateToBook?: boolean;
71
+ bookOnlineDisabled?: boolean;
72
+ };
53
73
  }): Promise<FetchAvailabilityResponse>;
@@ -3,6 +3,7 @@
3
3
  * Fetches available time slots for booking services from Wix Bookings
4
4
  */
5
5
  import { listAvailabilityTimeSlots, } from '@wix/auto_sdk_bookings_availability-time-slots';
6
+ import { listEventTimeSlots } from '@wix/auto_sdk_bookings_event-time-slots';
6
7
  import { ServiceType } from '@wix/auto_sdk_bookings_services';
7
8
  import { ViewMode, } from '../../services/time-slot-list/time-slot-list.def.js';
8
9
  // ============================================================================
@@ -101,20 +102,31 @@ function detectFlow(bookingServices, viewMode) {
101
102
  * // response.timeSlots are grouped by time
102
103
  * ```
103
104
  */
104
- export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone, locations, cursorPaging, }) {
105
+ export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursorPaging, slotsPerDay, bookable, bookingPolicyViolations, }) {
105
106
  const flow = detectFlow(selectedServices, viewMode);
107
+ const serviceIds = selectedServices.map((s) => s._id);
106
108
  if (flow === FlowType.SINGLE_SERVICE) {
107
- const serviceId = selectedServices[0]?._id;
108
- if (!serviceId) {
109
- throw new Error('Service ID is required');
110
- }
109
+ // Build resourceTypes filter for staff members
110
+ // Staff member resource type ID is 1cd44cf8-756f-41c3-bd90-3e2ffcaf1155
111
+ const resourceTypes = staffMemberIds?.length
112
+ ? [
113
+ {
114
+ resourceTypeId: '1cd44cf8-756f-41c3-bd90-3e2ffcaf1155',
115
+ resourceIds: staffMemberIds,
116
+ },
117
+ ]
118
+ : undefined;
111
119
  const response = await listAvailabilityTimeSlots({
112
- serviceId,
120
+ serviceId: serviceIds[0],
113
121
  fromLocalDate,
114
122
  toLocalDate,
115
123
  timeZone,
116
124
  locations,
117
125
  cursorPaging,
126
+ resourceTypes,
127
+ timeSlotsPerDay: slotsPerDay,
128
+ bookable,
129
+ bookingPolicyViolations,
118
130
  });
119
131
  return {
120
132
  timeSlots: groupTimeSlotsByTime(response.timeSlots || []),
@@ -126,8 +138,34 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
126
138
  throw new Error('Multi-service appointments not yet supported');
127
139
  }
128
140
  if (flow === FlowType.CLASS) {
129
- // Phase 2: Class support
130
- throw new Error('Class bookings not yet supported');
141
+ // Build eventFilter for location and staff filtering
142
+ const locationIds = locations?.map((l) => l._id);
143
+ const eventFilter = {};
144
+ if (locationIds?.length) {
145
+ eventFilter['location.id'] = locationIds;
146
+ }
147
+ if (staffMemberIds?.length) {
148
+ eventFilter['resources.id'] = { $hasSome: staffMemberIds };
149
+ }
150
+ const response = await listEventTimeSlots({
151
+ fromLocalDate,
152
+ toLocalDate,
153
+ timeZone,
154
+ serviceIds: serviceIds.length ? serviceIds : undefined,
155
+ cursorPaging,
156
+ eventFilter: Object.keys(eventFilter).length ? eventFilter : undefined,
157
+ maxSlotsPerDay: slotsPerDay,
158
+ // bookable=true means only bookable, so includeNonBookable=false
159
+ // bookable=false means only non-bookable, so includeNonBookable=true
160
+ // bookable=undefined means both, so includeNonBookable=true (default)
161
+ includeNonBookable: bookable === undefined ? undefined : !bookable,
162
+ bookingPolicyViolations,
163
+ });
164
+ return {
165
+ timeSlots: groupTimeSlotsByTime(response.timeSlots || []),
166
+ // Note: listEventTimeSlots uses pagingMetadata instead of cursorPagingMetadata
167
+ cursorPagingMetadata: response.pagingMetadata,
168
+ };
131
169
  }
132
170
  throw new Error(`Unknown flow type: ${flow}`);
133
171
  }
@@ -3,8 +3,8 @@
3
3
  * Provides low-level access to BookingService via render props and service setup
4
4
  */
5
5
  import React from 'react';
6
- import { type BookingServiceConfig, type BookingSelection } from '../../../services/booking/booking.js';
7
- import type { TimeSlot } from '@wix/auto_sdk_bookings_availability-time-slots';
6
+ import { type BookingServiceConfig, type BookingItem, type Location, type BookingServiceActions } from '../../../services/booking/booking.js';
7
+ import type { FormValues } from '@wix/form-public';
8
8
  /**
9
9
  * Props for Booking.Root component
10
10
  */
@@ -28,14 +28,22 @@ export interface RootProps {
28
28
  * ```
29
29
  */
30
30
  export declare function Root(props: RootProps): React.ReactNode;
31
+ /**
32
+ * Data exposed by Booking.Data component
33
+ * Mirrors BookingServiceConfig structure with actions
34
+ */
35
+ export interface BookingData {
36
+ bookingItems: BookingItem[];
37
+ location: Location | null;
38
+ formValues: FormValues | null;
39
+ numberOfParticipants: number;
40
+ actions: BookingServiceActions;
41
+ }
31
42
  /**
32
43
  * Props for Data render prop component
33
44
  */
34
45
  export interface DataProps {
35
- children: (data: {
36
- bookingSelections: BookingSelection[];
37
- selectedTimeSlot: TimeSlot | null;
38
- }) => React.ReactNode;
46
+ children: (data: BookingData) => React.ReactNode;
39
47
  }
40
48
  /**
41
49
  * Core component that provides access to booking data via render props.
@@ -45,9 +53,9 @@ export interface DataProps {
45
53
  * @example
46
54
  * ```tsx
47
55
  * <Booking.Data>
48
- * {({ bookingSelections }) => (
49
- * bookingSelections.length > 0 ? (
50
- * <div>{bookingSelections.map(s => s.service.name).join(', ')}</div>
56
+ * {({ bookingItems, location, formValues, numberOfParticipants, actions }) => (
57
+ * bookingItems.length > 0 ? (
58
+ * <div>{bookingItems.map(item => item.service.name).join(', ')}</div>
51
59
  * ) : (
52
60
  * <div>No services selected</div>
53
61
  * )
@@ -28,9 +28,9 @@ export function Root(props) {
28
28
  * @example
29
29
  * ```tsx
30
30
  * <Booking.Data>
31
- * {({ bookingSelections }) => (
32
- * bookingSelections.length > 0 ? (
33
- * <div>{bookingSelections.map(s => s.service.name).join(', ')}</div>
31
+ * {({ bookingItems, location, formValues, numberOfParticipants, actions }) => (
32
+ * bookingItems.length > 0 ? (
33
+ * <div>{bookingItems.map(item => item.service.name).join(', ')}</div>
34
34
  * ) : (
35
35
  * <div>No services selected</div>
36
36
  * )
@@ -41,10 +41,16 @@ export function Root(props) {
41
41
  export function Data(props) {
42
42
  const { children } = props;
43
43
  const bookingService = useService(BookingServiceDefinition);
44
- const bookingSelections = bookingService.bookingSelections.get();
45
- const selectedTimeSlot = bookingService.timeSlot.get();
44
+ const bookingItems = bookingService.bookingItems.get();
45
+ const location = bookingService.location.get();
46
+ const formValues = bookingService.formValues.get();
47
+ const numberOfParticipants = bookingService.numberOfParticipants.get();
48
+ const { actions } = bookingService;
46
49
  return children({
47
- bookingSelections,
48
- selectedTimeSlot,
50
+ bookingItems,
51
+ location,
52
+ formValues,
53
+ numberOfParticipants,
54
+ actions,
49
55
  });
50
56
  }