@wix/headless-bookings 0.0.32 → 0.0.34

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.
@@ -3,15 +3,7 @@
3
3
  */
4
4
  import type { Resource } from '@wix/auto_sdk_bookings_availability-time-slots';
5
5
  import type { FormValues } from '@wix/form-public';
6
- import type { ServiceSelection, ServiceSelectionTimeSlot, Location, BookingServiceConfig } from '../services/booking/booking.js';
7
- /**
8
- * Mock time slot data
9
- */
10
- export declare const mockTimeSlot: ServiceSelectionTimeSlot;
11
- /**
12
- * Mock time slot for afternoon
13
- */
14
- export declare const mockTimeSlotAfternoon: ServiceSelectionTimeSlot;
6
+ import type { ServiceSelection, ServiceSelectionTimeSlot, BookingServiceConfig } from '../services/booking/booking.js';
15
7
  /**
16
8
  * Mock resource (staff member)
17
9
  */
@@ -24,6 +16,28 @@ export declare const mockResourceTwo: Resource;
24
16
  * Mock resource (room)
25
17
  */
26
18
  export declare const mockResourceRoom: Resource;
19
+ /**
20
+ * Mock location
21
+ */
22
+ export declare const mockLocation: {
23
+ _id: string;
24
+ name: string;
25
+ };
26
+ /**
27
+ * Mock location (second)
28
+ */
29
+ export declare const mockLocationTwo: {
30
+ _id: string;
31
+ name: string;
32
+ };
33
+ /**
34
+ * Mock time slot data
35
+ */
36
+ export declare const mockTimeSlot: ServiceSelectionTimeSlot;
37
+ /**
38
+ * Mock time slot for afternoon
39
+ */
40
+ export declare const mockTimeSlotAfternoon: ServiceSelectionTimeSlot;
27
41
  /**
28
42
  * Mock service selection with all data
29
43
  */
@@ -36,14 +50,6 @@ export declare const mockServiceSelectionWithoutTimeSlot: ServiceSelection;
36
50
  * Mock service selection for class
37
51
  */
38
52
  export declare const mockServiceSelectionClass: ServiceSelection;
39
- /**
40
- * Mock location
41
- */
42
- export declare const mockLocation: Location;
43
- /**
44
- * Mock location (second)
45
- */
46
- export declare const mockLocationTwo: Location;
47
53
  /**
48
54
  * Mock form submission (contact info)
49
55
  */
@@ -2,26 +2,6 @@
2
2
  * Shared mock data for booking service tests
3
3
  */
4
4
  import { mockService, mockServiceClass } from './services.js';
5
- /**
6
- * Mock time slot data
7
- */
8
- export const mockTimeSlot = {
9
- startDate: '2024-03-15T10:00:00Z',
10
- endDate: '2024-03-15T11:00:00Z',
11
- totalCapacity: 10,
12
- remainingCapacity: 5,
13
- scheduleId: 'schedule-123',
14
- };
15
- /**
16
- * Mock time slot for afternoon
17
- */
18
- export const mockTimeSlotAfternoon = {
19
- startDate: '2024-03-15T14:00:00Z',
20
- endDate: '2024-03-15T15:00:00Z',
21
- totalCapacity: 10,
22
- remainingCapacity: 8,
23
- scheduleId: 'schedule-456',
24
- };
25
5
  /**
26
6
  * Mock resource (staff member)
27
7
  */
@@ -48,6 +28,44 @@ export const mockResourceRoom = {
48
28
  name: 'Conference Room A',
49
29
  tag: 'RESOURCE',
50
30
  };
31
+ /**
32
+ * Mock location
33
+ */
34
+ export const mockLocation = {
35
+ _id: 'location-1',
36
+ name: 'Main Office',
37
+ };
38
+ /**
39
+ * Mock location (second)
40
+ */
41
+ export const mockLocationTwo = {
42
+ _id: 'location-2',
43
+ name: 'Downtown Branch',
44
+ };
45
+ /**
46
+ * Mock time slot data
47
+ */
48
+ export const mockTimeSlot = {
49
+ startDate: '2024-03-15T10:00:00Z',
50
+ endDate: '2024-03-15T11:00:00Z',
51
+ totalCapacity: 10,
52
+ remainingCapacity: 5,
53
+ scheduleId: 'schedule-123',
54
+ resources: [mockResource],
55
+ location: mockLocation,
56
+ };
57
+ /**
58
+ * Mock time slot for afternoon
59
+ */
60
+ export const mockTimeSlotAfternoon = {
61
+ startDate: '2024-03-15T14:00:00Z',
62
+ endDate: '2024-03-15T15:00:00Z',
63
+ totalCapacity: 10,
64
+ remainingCapacity: 8,
65
+ scheduleId: 'schedule-456',
66
+ resources: [mockResourceTwo],
67
+ location: mockLocationTwo,
68
+ };
51
69
  /**
52
70
  * Mock service selection with all data
53
71
  */
@@ -77,20 +95,6 @@ export const mockServiceSelectionClass = {
77
95
  timeSlot: mockTimeSlotAfternoon,
78
96
  totalParticipants: 3,
79
97
  };
80
- /**
81
- * Mock location
82
- */
83
- export const mockLocation = {
84
- id: 'location-1',
85
- name: 'Main Office',
86
- };
87
- /**
88
- * Mock location (second)
89
- */
90
- export const mockLocationTwo = {
91
- id: 'location-2',
92
- name: 'Downtown Branch',
93
- };
94
98
  /**
95
99
  * Mock form submission (contact info)
96
100
  */
@@ -3,8 +3,9 @@
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 ServiceSelection, type Location, type BookingServiceActions } from '../../../services/booking/booking.js';
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
9
  /**
9
10
  * Props for Booking.Root component
10
11
  */
@@ -34,7 +35,7 @@ export declare function Root(props: RootProps): React.ReactNode;
34
35
  */
35
36
  export interface BookingData {
36
37
  serviceSelections: ServiceSelection[];
37
- location: Location | null;
38
+ location: TimeSlotLocationType | null;
38
39
  timezone: string | undefined;
39
40
  formSubmission: FormValues | null;
40
41
  actions: BookingServiceActions;
@@ -31,9 +31,8 @@ export function TimeSlotProvider(props) {
31
31
  const service = useService(TimeSlotListServiceDefinition);
32
32
  const bookingService = useService(BookingServiceDefinition);
33
33
  const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
34
- const selectedLocationId = bookingService.location.get()?.id;
35
34
  const isSelected = selectedTimeSlot?.startDate === timeSlot.localStartDate &&
36
- selectedLocationId === timeSlot.location?._id;
35
+ selectedTimeSlot?.location?._id === timeSlot.location?._id;
37
36
  const selectTimeSlot = () => {
38
37
  service.actions.selectSlot(timeSlot);
39
38
  };
@@ -132,7 +131,7 @@ export function Actions(props) {
132
131
  const service = useService(TimeSlotListServiceDefinition);
133
132
  const bookingService = useService(BookingServiceDefinition);
134
133
  const bookable = timeSlot.bookable ?? false;
135
- const selectedStaffMemberId = bookingService.serviceSelections.get()[0]?.resources?.[0]?._id;
134
+ const selectedStaffMemberId = bookingService.serviceSelections.get()[0]?.timeSlot?.resources?.[0]?._id;
136
135
  const staffFromSlot = timeSlot.availableResources[0].resources.map((r) => r._id);
137
136
  const shouldShowClearStaffSelection = !!selectedStaffMemberId && staffFromSlot.length > 1;
138
137
  const clearStaffSelection = shouldShowClearStaffSelection
@@ -169,7 +168,7 @@ export function StaffMembers(props) {
169
168
  const { timeSlot } = useTimeSlotContext();
170
169
  const service = useService(TimeSlotListServiceDefinition);
171
170
  const bookingService = useService(BookingServiceDefinition);
172
- const selectedStaffMemberId = bookingService.serviceSelections.get()[0]?.resources?.[0]?._id;
171
+ const selectedStaffMemberId = bookingService.serviceSelections.get()[0]?.timeSlot?.resources?.[0]?._id;
173
172
  const staffMembers = timeSlot.availableResources[0].resources;
174
173
  // Return null if only 1 staff member - nothing to choose from
175
174
  if (staffMembers.length <= 1) {
@@ -43,8 +43,9 @@ export async function executeBookAction(params) {
43
43
  throw new Error('Failed to create booking');
44
44
  }
45
45
  // Step 2: Create checkout
46
- // businessLocationId comes from location.id for multi-location support
47
- const businessLocationId = params.location?.id ?? undefined;
46
+ // businessLocationId: prefer timeSlot.location, fallback to global location
47
+ const firstSelection = params.serviceSelections[0];
48
+ const businessLocationId = firstSelection?.timeSlot?.location?._id ?? undefined;
48
49
  const checkoutRequest = buildCheckoutRequest({
49
50
  bookingId,
50
51
  contactDetails,
@@ -46,8 +46,10 @@ export function buildBookingRequest(params) {
46
46
  if (!item.timeSlot) {
47
47
  throw new Error('No time slot selected for service selection');
48
48
  }
49
- // Get resource from service selection
50
- const resource = item.resources?.[0];
49
+ // Get resource: prefer timeSlot.resources, fallback to item.resources
50
+ const resource = item.timeSlot.resources?.[0] ?? item.resources?.[0];
51
+ // Get location: prefer timeSlot.location, fallback to params.location
52
+ const slotLocation = item.timeSlot.location ?? location;
51
53
  return {
52
54
  booking: {
53
55
  // Payment option derived from service payment settings
@@ -66,8 +68,8 @@ export function buildBookingRequest(params) {
66
68
  resource: resource
67
69
  ? { _id: resource._id, name: resource.name }
68
70
  : undefined,
69
- location: location
70
- ? { _id: location.id, name: location.name }
71
+ location: slotLocation
72
+ ? { _id: slotLocation._id, name: slotLocation.name }
71
73
  : undefined,
72
74
  },
73
75
  },
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Types for the Book action
3
3
  */
4
- import type { ServiceSelection, Location } from '../booking.js';
4
+ import type { ServiceSelection } from '../booking.js';
5
5
  import type { FormValues } from '@wix/form-public';
6
+ import type { Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
6
7
  /**
7
8
  * Result returned from the book action
8
9
  */
@@ -21,7 +22,7 @@ export interface BookingError {
21
22
  */
22
23
  export interface BookActionParams {
23
24
  serviceSelections: ServiceSelection[];
24
- location: Location | null;
25
+ location: TimeSlotLocationType | null;
25
26
  formSubmission: FormValues | null;
26
27
  timezone?: string;
27
28
  }
@@ -4,8 +4,8 @@
4
4
  * Persists across page navigation in SPA until page refresh
5
5
  */
6
6
  import { type Signal } from '@wix/services-definitions/core-services/signals';
7
- import type { Service } from '@wix/auto_sdk_bookings_services';
8
- import type { Resource, EventInfo } from '@wix/auto_sdk_bookings_availability-time-slots';
7
+ import type { Service, Location as ServiceLocationType } from '@wix/auto_sdk_bookings_services';
8
+ import type { Resource, EventInfo, Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
9
9
  import type { FormValues } from '@wix/form-public';
10
10
  import type { BookResult } from './book-action/types.js';
11
11
  export declare const ANY_STAFF_MEMBER_ID = "__any_staff_member__";
@@ -19,6 +19,10 @@ export interface ServiceSelectionTimeSlot {
19
19
  remainingCapacity?: number;
20
20
  scheduleId?: string;
21
21
  eventInfo?: EventInfo;
22
+ /** Resources available in this time slot (populated when slot is selected) */
23
+ resources?: Resource[];
24
+ /** Location of this time slot (populated when slot is selected) */
25
+ location?: TimeSlotLocationType;
22
26
  }
23
27
  /**
24
28
  * ServiceSelection contains the service AND its related selections
@@ -35,10 +39,6 @@ export interface ServiceSelection {
35
39
  /** Number of participants for this service selection */
36
40
  totalParticipants?: number;
37
41
  }
38
- export interface Location {
39
- id: string;
40
- name?: string;
41
- }
42
42
  /**
43
43
  * BookingService actions - setters only, no API calls
44
44
  */
@@ -57,7 +57,7 @@ export interface BookingServiceActions {
57
57
  setResourceById(resourceId: string, resource: Resource, instanceId?: string): void;
58
58
  /** Gets a resource by ID from a service selection. If no instanceId provided, uses the first item. */
59
59
  getResourceById(resourceId: string, instanceId?: string): Resource | undefined;
60
- setLocation(location: Location | null): void;
60
+ setLocation(location: ServiceLocationType | null): void;
61
61
  clearLocation(): void;
62
62
  setTimezone(timezone: string): void;
63
63
  setFormSubmission(values: FormValues | null): void;
@@ -73,7 +73,7 @@ export interface BookingService {
73
73
  /** Signal containing the service selections (services with their related data) */
74
74
  serviceSelections: Signal<ServiceSelection[]>;
75
75
  /** Signal containing the selected location */
76
- location: Signal<Location | null>;
76
+ location: Signal<ServiceLocationType | null>;
77
77
  /** Signal containing the selected timezone */
78
78
  timezone: Signal<string | undefined>;
79
79
  /** Signal containing the form submission */
@@ -97,7 +97,7 @@ export interface BookingServiceConfig {
97
97
  /** Optional initial service selections to pre-populate */
98
98
  serviceSelections?: ServiceSelection[];
99
99
  /** Optional initial location */
100
- location?: Location | null;
100
+ location?: ServiceLocationType | null;
101
101
  /** Optional initial timezone */
102
102
  timezone?: string;
103
103
  /** Optional initial form submission */
@@ -3,7 +3,7 @@
3
3
  * Main export file for all service definitions and implementations
4
4
  */
5
5
  export { ServiceService, ServiceServiceDefinition, type ServiceServiceAPI, type ServiceServiceConfig, type ServiceActions, type ServicePaymentData, loadServiceServiceInitialData, type SuccessServiceServiceConfigResult, type NotFoundServiceServiceConfigResult, } from './service/service.js';
6
- export { BookingService, BookingServiceDefinition, type BookingServiceConfig, type Location, type BookingServiceActions, type ServiceSelection, type ServiceSelectionTimeSlot, } from './booking/booking.js';
6
+ export { BookingService, BookingServiceDefinition, type BookingServiceConfig, type BookingServiceActions, type ServiceSelection, type ServiceSelectionTimeSlot, } from './booking/booking.js';
7
7
  export { TimeSlotListService, TimeSlotListServiceDefinition, ViewMode, loadTimeSlotListConfig, type TimeSlotListServiceAPI, type TimeSlotListServiceActions, type TimeSlotListServiceConfig, type LoadTimeSlotListConfigOptions, type LoadTimeSlotListConfigResult, } from './time-slot-list/index.js';
8
8
  export { ServiceListService, ServiceListServiceDefinition, type ServiceListServiceAPI, type ServiceListServiceConfig, type ServiceListFilters, type ServiceListActions, type QueryOptions, loadServiceListServiceInitialData, type SuccessServiceListServiceConfigResult, type ErrorServiceListServiceConfigResult, } from './service-list/service-list.js';
9
9
  export { executeBookAction, canBook, buildBookingRequest, buildCheckoutRequest, type CanBookParams, type BookResult, type BookingError, type BookActionParams, type BookChildProps, type BookProps, type BuildCheckoutParams, } from './booking/book-action/index.js';
@@ -3,7 +3,7 @@
3
3
  * Manages time slot fetching, selection, and state
4
4
  */
5
5
  import { implementService } from '@wix/services-definitions';
6
- import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
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/fetch-availability/index.js';
9
9
  import { BookingServiceDefinition, ANY_STAFF_MEMBER_ID, } from '../booking/booking.js';
@@ -44,27 +44,58 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
44
44
  }
45
45
  return config.services ?? [];
46
46
  };
47
+ /**
48
+ * Helper: Get filters combining config with booking overrides
49
+ * Uses location and resources from booking if they exist (not from the slot)
50
+ */
51
+ const getFilters = () => {
52
+ const baseFilters = config.filters ?? {};
53
+ const selectedStaffMembersIds = bookingService.serviceSelections
54
+ .get()[0]
55
+ ?.resources?.map((resource) => resource._id);
56
+ const staffMemberIds = selectedStaffMembersIds?.length
57
+ ? selectedStaffMembersIds
58
+ : baseFilters.staffMemberIds;
59
+ const selectedLocation = bookingService.location.get();
60
+ const location = selectedLocation
61
+ ? {
62
+ id: selectedLocation._id,
63
+ locationType: selectedLocation.type,
64
+ }
65
+ : baseFilters.location;
66
+ return {
67
+ ...baseFilters,
68
+ location,
69
+ staffMemberIds,
70
+ };
71
+ };
47
72
  /**
48
73
  * Fetch time slots from API
49
- * Called on initialization or when filters change
74
+ * If cursor is provided, appends to existing slots (pagination)
75
+ * If no cursor, resets state and fetches fresh slots
50
76
  */
51
- const fetchTimeSlots = async () => {
52
- const selectedServices = getServices();
77
+ const fetchTimeSlots = async ({ isLoadMore } = { isLoadMore: false }) => {
78
+ if (isLoadingSignal.get() || (isLoadMore && !cursorSignal.get())) {
79
+ return;
80
+ }
81
+ const cursor = isLoadMore ? cursorSignal.get() : undefined;
53
82
  isLoadingSignal.set(true);
54
83
  errorSignal.set(null);
55
- cursorSignal.set(undefined);
56
- timeSlotsSignal.set([]);
84
+ if (!isLoadMore) {
85
+ timeSlotsSignal.set([]);
86
+ }
57
87
  try {
58
88
  const response = await fetchAvailability({
59
- services: selectedServices,
89
+ services: getServices(),
60
90
  fromLocalDate: startDateSignal.get(),
61
91
  toLocalDate: endDateSignal.get(),
62
92
  timeZone: bookingService.timezone.get(),
63
93
  viewMode: viewModeSignal.get(),
64
- cursor: cursorSignal.get(),
65
- ...config.filters,
94
+ cursor,
95
+ ...getFilters(),
66
96
  });
67
- timeSlotsSignal.set(response.timeSlots);
97
+ const newSlots = response.timeSlots;
98
+ timeSlotsSignal.set(isLoadMore ? [...timeSlotsSignal.get(), ...newSlots] : newSlots);
68
99
  const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
69
100
  cursorSignal.set(nextCursor);
70
101
  hasMoreSignal.set(!!nextCursor);
@@ -72,7 +103,9 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
72
103
  catch (error) {
73
104
  const message = error instanceof Error
74
105
  ? error.message
75
- : 'Failed to load time slots';
106
+ : isLoadMore
107
+ ? 'Failed to load more time slots'
108
+ : 'Failed to load time slots';
76
109
  errorSignal.set(message);
77
110
  }
78
111
  finally {
@@ -86,26 +119,23 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
86
119
  * - Auto-selects staff only when there is exactly one staff option in this slot
87
120
  */
88
121
  const selectSlot = (slot) => {
122
+ const staffMembersFromSlot = slot.availableResources[0].resources.map((r) => ({
123
+ _id: r._id,
124
+ name: r.name,
125
+ }));
126
+ const resources = staffMembersFromSlot.length === 1 ? staffMembersFromSlot : [];
89
127
  const timeSlot = {
90
128
  startDate: slot.localStartDate,
91
129
  endDate: slot.localEndDate,
92
130
  totalCapacity: slot.totalCapacity ?? undefined,
93
131
  remainingCapacity: slot.remainingCapacity ?? undefined,
94
132
  scheduleId: slot.scheduleId ?? undefined,
95
- eventInfo: slot.eventInfo ?? undefined,
133
+ eventInfo: slot.eventInfo,
134
+ location: slot.location,
135
+ resources,
96
136
  };
97
- bookingService.actions.setLocation({
98
- id: slot.location?._id,
99
- name: slot.location?.name ?? undefined,
100
- });
101
- const staffMembersFromSlot = slot.availableResources[0].resources.map((r) => ({
102
- id: r._id,
103
- name: r.name,
104
- }));
105
- const resources = staffMembersFromSlot.length === 1 ? staffMembersFromSlot : [];
106
137
  bookingService.actions.updateItem({
107
138
  timeSlot,
108
- resources,
109
139
  });
110
140
  };
111
141
  /**
@@ -113,82 +143,46 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
113
143
  * Updates the booking service with the selected resource
114
144
  */
115
145
  const selectStaffMember = (staffMemberId) => {
116
- const selectedBookingTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
117
- if (!selectedBookingTimeSlot)
146
+ const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
147
+ if (!selectedTimeSlot)
118
148
  return;
119
- const findStaffName = (id) => {
149
+ const getStaffName = (id) => {
150
+ if (id === ANY_STAFF_MEMBER_ID)
151
+ return 'Any staff member';
120
152
  const slots = timeSlotsSignal.get();
121
153
  return (slots
122
154
  .flatMap((slot) => slot.availableResources[0].resources)
123
155
  .find((r) => r._id === id)?.name ?? undefined);
124
156
  };
125
- if (staffMemberId === ANY_STAFF_MEMBER_ID) {
126
- bookingService.actions.updateItem({
127
- resources: [
128
- {
129
- _id: ANY_STAFF_MEMBER_ID,
130
- name: 'Any staff member',
131
- },
132
- ],
133
- });
134
- return;
135
- }
157
+ const resources = [
158
+ {
159
+ _id: staffMemberId,
160
+ name: getStaffName(staffMemberId),
161
+ },
162
+ ];
136
163
  bookingService.actions.updateItem({
137
- resources: [
138
- {
139
- _id: staffMemberId,
140
- name: findStaffName(staffMemberId),
141
- },
142
- ],
164
+ timeSlot: {
165
+ ...selectedTimeSlot,
166
+ resources,
167
+ },
143
168
  });
144
169
  };
145
170
  /**
146
171
  * Action: Clear staff selection while keeping the selected time slot
147
172
  */
148
173
  const clearStaffSelection = () => {
149
- const selectedBookingTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
150
- if (!selectedBookingTimeSlot)
174
+ const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
175
+ if (!selectedTimeSlot)
151
176
  return;
152
177
  bookingService.actions.updateItem({
153
- resources: [],
178
+ timeSlot: { ...selectedTimeSlot, resources: [] },
154
179
  });
155
180
  };
156
181
  /**
157
182
  * Action: Load more slots (pagination)
158
183
  */
159
184
  const loadMore = async () => {
160
- const cursor = cursorSignal.get();
161
- if (!cursor || isLoadingSignal.get())
162
- return;
163
- const selectedServices = getServices();
164
- isLoadingSignal.set(true);
165
- try {
166
- const response = await fetchAvailability({
167
- services: selectedServices,
168
- fromLocalDate: startDateSignal.get(),
169
- toLocalDate: endDateSignal.get(),
170
- timeZone: bookingService.timezone.get(),
171
- cursor,
172
- viewMode: viewModeSignal.get(),
173
- ...config.filters,
174
- });
175
- timeSlotsSignal.set([
176
- ...timeSlotsSignal.get(),
177
- ...response.timeSlots,
178
- ]);
179
- const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
180
- cursorSignal.set(nextCursor);
181
- hasMoreSignal.set(!!nextCursor);
182
- }
183
- catch (error) {
184
- const message = error instanceof Error
185
- ? error.message
186
- : 'Failed to load more time slots';
187
- errorSignal.set(message);
188
- }
189
- finally {
190
- isLoadingSignal.set(false);
191
- }
185
+ return fetchTimeSlots({ isLoadMore: true });
192
186
  };
193
187
  /**
194
188
  * Action: Update date range and refetch slots
@@ -202,22 +196,19 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
202
196
  }
203
197
  startDateSignal.set(newStartDate);
204
198
  endDateSignal.set(newEndDate);
205
- fetchTimeSlots();
206
199
  };
207
- // Auto-fetch on initialization if no pre-loaded data provided
208
- if (!config.timeSlots) {
209
- fetchTimeSlots();
210
- }
211
- // Subscribe to timezone changes from BookingService to auto-refetch
212
- // NOTE: This should depend ONLY on bookingService.timezone.
213
- let firstTimezoneEffectRun = true;
200
+ const resourcesSignal = signalsService.computed(() => {
201
+ return bookingService.serviceSelections.get()[0]?.resources;
202
+ });
203
+ let isFirstRun = true;
214
204
  signalsService.effect(() => {
215
205
  bookingService.timezone.get();
216
- if (firstTimezoneEffectRun) {
217
- firstTimezoneEffectRun = false;
218
- return;
206
+ bookingService.location.get();
207
+ resourcesSignal.get();
208
+ if (!isFirstRun || !config.timeSlots) {
209
+ fetchTimeSlots();
219
210
  }
220
- fetchTimeSlots();
211
+ isFirstRun = false;
221
212
  });
222
213
  // Return service API
223
214
  return {
@@ -3,15 +3,7 @@
3
3
  */
4
4
  import type { Resource } from '@wix/auto_sdk_bookings_availability-time-slots';
5
5
  import type { FormValues } from '@wix/form-public';
6
- import type { ServiceSelection, ServiceSelectionTimeSlot, Location, BookingServiceConfig } from '../services/booking/booking.js';
7
- /**
8
- * Mock time slot data
9
- */
10
- export declare const mockTimeSlot: ServiceSelectionTimeSlot;
11
- /**
12
- * Mock time slot for afternoon
13
- */
14
- export declare const mockTimeSlotAfternoon: ServiceSelectionTimeSlot;
6
+ import type { ServiceSelection, ServiceSelectionTimeSlot, BookingServiceConfig } from '../services/booking/booking.js';
15
7
  /**
16
8
  * Mock resource (staff member)
17
9
  */
@@ -24,6 +16,28 @@ export declare const mockResourceTwo: Resource;
24
16
  * Mock resource (room)
25
17
  */
26
18
  export declare const mockResourceRoom: Resource;
19
+ /**
20
+ * Mock location
21
+ */
22
+ export declare const mockLocation: {
23
+ _id: string;
24
+ name: string;
25
+ };
26
+ /**
27
+ * Mock location (second)
28
+ */
29
+ export declare const mockLocationTwo: {
30
+ _id: string;
31
+ name: string;
32
+ };
33
+ /**
34
+ * Mock time slot data
35
+ */
36
+ export declare const mockTimeSlot: ServiceSelectionTimeSlot;
37
+ /**
38
+ * Mock time slot for afternoon
39
+ */
40
+ export declare const mockTimeSlotAfternoon: ServiceSelectionTimeSlot;
27
41
  /**
28
42
  * Mock service selection with all data
29
43
  */
@@ -36,14 +50,6 @@ export declare const mockServiceSelectionWithoutTimeSlot: ServiceSelection;
36
50
  * Mock service selection for class
37
51
  */
38
52
  export declare const mockServiceSelectionClass: ServiceSelection;
39
- /**
40
- * Mock location
41
- */
42
- export declare const mockLocation: Location;
43
- /**
44
- * Mock location (second)
45
- */
46
- export declare const mockLocationTwo: Location;
47
53
  /**
48
54
  * Mock form submission (contact info)
49
55
  */
@@ -2,26 +2,6 @@
2
2
  * Shared mock data for booking service tests
3
3
  */
4
4
  import { mockService, mockServiceClass } from './services.js';
5
- /**
6
- * Mock time slot data
7
- */
8
- export const mockTimeSlot = {
9
- startDate: '2024-03-15T10:00:00Z',
10
- endDate: '2024-03-15T11:00:00Z',
11
- totalCapacity: 10,
12
- remainingCapacity: 5,
13
- scheduleId: 'schedule-123',
14
- };
15
- /**
16
- * Mock time slot for afternoon
17
- */
18
- export const mockTimeSlotAfternoon = {
19
- startDate: '2024-03-15T14:00:00Z',
20
- endDate: '2024-03-15T15:00:00Z',
21
- totalCapacity: 10,
22
- remainingCapacity: 8,
23
- scheduleId: 'schedule-456',
24
- };
25
5
  /**
26
6
  * Mock resource (staff member)
27
7
  */
@@ -48,6 +28,44 @@ export const mockResourceRoom = {
48
28
  name: 'Conference Room A',
49
29
  tag: 'RESOURCE',
50
30
  };
31
+ /**
32
+ * Mock location
33
+ */
34
+ export const mockLocation = {
35
+ _id: 'location-1',
36
+ name: 'Main Office',
37
+ };
38
+ /**
39
+ * Mock location (second)
40
+ */
41
+ export const mockLocationTwo = {
42
+ _id: 'location-2',
43
+ name: 'Downtown Branch',
44
+ };
45
+ /**
46
+ * Mock time slot data
47
+ */
48
+ export const mockTimeSlot = {
49
+ startDate: '2024-03-15T10:00:00Z',
50
+ endDate: '2024-03-15T11:00:00Z',
51
+ totalCapacity: 10,
52
+ remainingCapacity: 5,
53
+ scheduleId: 'schedule-123',
54
+ resources: [mockResource],
55
+ location: mockLocation,
56
+ };
57
+ /**
58
+ * Mock time slot for afternoon
59
+ */
60
+ export const mockTimeSlotAfternoon = {
61
+ startDate: '2024-03-15T14:00:00Z',
62
+ endDate: '2024-03-15T15:00:00Z',
63
+ totalCapacity: 10,
64
+ remainingCapacity: 8,
65
+ scheduleId: 'schedule-456',
66
+ resources: [mockResourceTwo],
67
+ location: mockLocationTwo,
68
+ };
51
69
  /**
52
70
  * Mock service selection with all data
53
71
  */
@@ -77,20 +95,6 @@ export const mockServiceSelectionClass = {
77
95
  timeSlot: mockTimeSlotAfternoon,
78
96
  totalParticipants: 3,
79
97
  };
80
- /**
81
- * Mock location
82
- */
83
- export const mockLocation = {
84
- id: 'location-1',
85
- name: 'Main Office',
86
- };
87
- /**
88
- * Mock location (second)
89
- */
90
- export const mockLocationTwo = {
91
- id: 'location-2',
92
- name: 'Downtown Branch',
93
- };
94
98
  /**
95
99
  * Mock form submission (contact info)
96
100
  */
@@ -3,8 +3,9 @@
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 ServiceSelection, type Location, type BookingServiceActions } from '../../../services/booking/booking.js';
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
9
  /**
9
10
  * Props for Booking.Root component
10
11
  */
@@ -34,7 +35,7 @@ export declare function Root(props: RootProps): React.ReactNode;
34
35
  */
35
36
  export interface BookingData {
36
37
  serviceSelections: ServiceSelection[];
37
- location: Location | null;
38
+ location: TimeSlotLocationType | null;
38
39
  timezone: string | undefined;
39
40
  formSubmission: FormValues | null;
40
41
  actions: BookingServiceActions;
@@ -31,9 +31,8 @@ export function TimeSlotProvider(props) {
31
31
  const service = useService(TimeSlotListServiceDefinition);
32
32
  const bookingService = useService(BookingServiceDefinition);
33
33
  const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
34
- const selectedLocationId = bookingService.location.get()?.id;
35
34
  const isSelected = selectedTimeSlot?.startDate === timeSlot.localStartDate &&
36
- selectedLocationId === timeSlot.location?._id;
35
+ selectedTimeSlot?.location?._id === timeSlot.location?._id;
37
36
  const selectTimeSlot = () => {
38
37
  service.actions.selectSlot(timeSlot);
39
38
  };
@@ -132,7 +131,7 @@ export function Actions(props) {
132
131
  const service = useService(TimeSlotListServiceDefinition);
133
132
  const bookingService = useService(BookingServiceDefinition);
134
133
  const bookable = timeSlot.bookable ?? false;
135
- const selectedStaffMemberId = bookingService.serviceSelections.get()[0]?.resources?.[0]?._id;
134
+ const selectedStaffMemberId = bookingService.serviceSelections.get()[0]?.timeSlot?.resources?.[0]?._id;
136
135
  const staffFromSlot = timeSlot.availableResources[0].resources.map((r) => r._id);
137
136
  const shouldShowClearStaffSelection = !!selectedStaffMemberId && staffFromSlot.length > 1;
138
137
  const clearStaffSelection = shouldShowClearStaffSelection
@@ -169,7 +168,7 @@ export function StaffMembers(props) {
169
168
  const { timeSlot } = useTimeSlotContext();
170
169
  const service = useService(TimeSlotListServiceDefinition);
171
170
  const bookingService = useService(BookingServiceDefinition);
172
- const selectedStaffMemberId = bookingService.serviceSelections.get()[0]?.resources?.[0]?._id;
171
+ const selectedStaffMemberId = bookingService.serviceSelections.get()[0]?.timeSlot?.resources?.[0]?._id;
173
172
  const staffMembers = timeSlot.availableResources[0].resources;
174
173
  // Return null if only 1 staff member - nothing to choose from
175
174
  if (staffMembers.length <= 1) {
@@ -43,8 +43,9 @@ export async function executeBookAction(params) {
43
43
  throw new Error('Failed to create booking');
44
44
  }
45
45
  // Step 2: Create checkout
46
- // businessLocationId comes from location.id for multi-location support
47
- const businessLocationId = params.location?.id ?? undefined;
46
+ // businessLocationId: prefer timeSlot.location, fallback to global location
47
+ const firstSelection = params.serviceSelections[0];
48
+ const businessLocationId = firstSelection?.timeSlot?.location?._id ?? undefined;
48
49
  const checkoutRequest = buildCheckoutRequest({
49
50
  bookingId,
50
51
  contactDetails,
@@ -46,8 +46,10 @@ export function buildBookingRequest(params) {
46
46
  if (!item.timeSlot) {
47
47
  throw new Error('No time slot selected for service selection');
48
48
  }
49
- // Get resource from service selection
50
- const resource = item.resources?.[0];
49
+ // Get resource: prefer timeSlot.resources, fallback to item.resources
50
+ const resource = item.timeSlot.resources?.[0] ?? item.resources?.[0];
51
+ // Get location: prefer timeSlot.location, fallback to params.location
52
+ const slotLocation = item.timeSlot.location ?? location;
51
53
  return {
52
54
  booking: {
53
55
  // Payment option derived from service payment settings
@@ -66,8 +68,8 @@ export function buildBookingRequest(params) {
66
68
  resource: resource
67
69
  ? { _id: resource._id, name: resource.name }
68
70
  : undefined,
69
- location: location
70
- ? { _id: location.id, name: location.name }
71
+ location: slotLocation
72
+ ? { _id: slotLocation._id, name: slotLocation.name }
71
73
  : undefined,
72
74
  },
73
75
  },
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Types for the Book action
3
3
  */
4
- import type { ServiceSelection, Location } from '../booking.js';
4
+ import type { ServiceSelection } from '../booking.js';
5
5
  import type { FormValues } from '@wix/form-public';
6
+ import type { Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
6
7
  /**
7
8
  * Result returned from the book action
8
9
  */
@@ -21,7 +22,7 @@ export interface BookingError {
21
22
  */
22
23
  export interface BookActionParams {
23
24
  serviceSelections: ServiceSelection[];
24
- location: Location | null;
25
+ location: TimeSlotLocationType | null;
25
26
  formSubmission: FormValues | null;
26
27
  timezone?: string;
27
28
  }
@@ -4,8 +4,8 @@
4
4
  * Persists across page navigation in SPA until page refresh
5
5
  */
6
6
  import { type Signal } from '@wix/services-definitions/core-services/signals';
7
- import type { Service } from '@wix/auto_sdk_bookings_services';
8
- import type { Resource, EventInfo } from '@wix/auto_sdk_bookings_availability-time-slots';
7
+ import type { Service, Location as ServiceLocationType } from '@wix/auto_sdk_bookings_services';
8
+ import type { Resource, EventInfo, Location as TimeSlotLocationType } from '@wix/auto_sdk_bookings_availability-time-slots';
9
9
  import type { FormValues } from '@wix/form-public';
10
10
  import type { BookResult } from './book-action/types.js';
11
11
  export declare const ANY_STAFF_MEMBER_ID = "__any_staff_member__";
@@ -19,6 +19,10 @@ export interface ServiceSelectionTimeSlot {
19
19
  remainingCapacity?: number;
20
20
  scheduleId?: string;
21
21
  eventInfo?: EventInfo;
22
+ /** Resources available in this time slot (populated when slot is selected) */
23
+ resources?: Resource[];
24
+ /** Location of this time slot (populated when slot is selected) */
25
+ location?: TimeSlotLocationType;
22
26
  }
23
27
  /**
24
28
  * ServiceSelection contains the service AND its related selections
@@ -35,10 +39,6 @@ export interface ServiceSelection {
35
39
  /** Number of participants for this service selection */
36
40
  totalParticipants?: number;
37
41
  }
38
- export interface Location {
39
- id: string;
40
- name?: string;
41
- }
42
42
  /**
43
43
  * BookingService actions - setters only, no API calls
44
44
  */
@@ -57,7 +57,7 @@ export interface BookingServiceActions {
57
57
  setResourceById(resourceId: string, resource: Resource, instanceId?: string): void;
58
58
  /** Gets a resource by ID from a service selection. If no instanceId provided, uses the first item. */
59
59
  getResourceById(resourceId: string, instanceId?: string): Resource | undefined;
60
- setLocation(location: Location | null): void;
60
+ setLocation(location: ServiceLocationType | null): void;
61
61
  clearLocation(): void;
62
62
  setTimezone(timezone: string): void;
63
63
  setFormSubmission(values: FormValues | null): void;
@@ -73,7 +73,7 @@ export interface BookingService {
73
73
  /** Signal containing the service selections (services with their related data) */
74
74
  serviceSelections: Signal<ServiceSelection[]>;
75
75
  /** Signal containing the selected location */
76
- location: Signal<Location | null>;
76
+ location: Signal<ServiceLocationType | null>;
77
77
  /** Signal containing the selected timezone */
78
78
  timezone: Signal<string | undefined>;
79
79
  /** Signal containing the form submission */
@@ -97,7 +97,7 @@ export interface BookingServiceConfig {
97
97
  /** Optional initial service selections to pre-populate */
98
98
  serviceSelections?: ServiceSelection[];
99
99
  /** Optional initial location */
100
- location?: Location | null;
100
+ location?: ServiceLocationType | null;
101
101
  /** Optional initial timezone */
102
102
  timezone?: string;
103
103
  /** Optional initial form submission */
@@ -3,7 +3,7 @@
3
3
  * Main export file for all service definitions and implementations
4
4
  */
5
5
  export { ServiceService, ServiceServiceDefinition, type ServiceServiceAPI, type ServiceServiceConfig, type ServiceActions, type ServicePaymentData, loadServiceServiceInitialData, type SuccessServiceServiceConfigResult, type NotFoundServiceServiceConfigResult, } from './service/service.js';
6
- export { BookingService, BookingServiceDefinition, type BookingServiceConfig, type Location, type BookingServiceActions, type ServiceSelection, type ServiceSelectionTimeSlot, } from './booking/booking.js';
6
+ export { BookingService, BookingServiceDefinition, type BookingServiceConfig, type BookingServiceActions, type ServiceSelection, type ServiceSelectionTimeSlot, } from './booking/booking.js';
7
7
  export { TimeSlotListService, TimeSlotListServiceDefinition, ViewMode, loadTimeSlotListConfig, type TimeSlotListServiceAPI, type TimeSlotListServiceActions, type TimeSlotListServiceConfig, type LoadTimeSlotListConfigOptions, type LoadTimeSlotListConfigResult, } from './time-slot-list/index.js';
8
8
  export { ServiceListService, ServiceListServiceDefinition, type ServiceListServiceAPI, type ServiceListServiceConfig, type ServiceListFilters, type ServiceListActions, type QueryOptions, loadServiceListServiceInitialData, type SuccessServiceListServiceConfigResult, type ErrorServiceListServiceConfigResult, } from './service-list/service-list.js';
9
9
  export { executeBookAction, canBook, buildBookingRequest, buildCheckoutRequest, type CanBookParams, type BookResult, type BookingError, type BookActionParams, type BookChildProps, type BookProps, type BuildCheckoutParams, } from './booking/book-action/index.js';
@@ -3,7 +3,7 @@
3
3
  * Manages time slot fetching, selection, and state
4
4
  */
5
5
  import { implementService } from '@wix/services-definitions';
6
- import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
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/fetch-availability/index.js';
9
9
  import { BookingServiceDefinition, ANY_STAFF_MEMBER_ID, } from '../booking/booking.js';
@@ -44,27 +44,58 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
44
44
  }
45
45
  return config.services ?? [];
46
46
  };
47
+ /**
48
+ * Helper: Get filters combining config with booking overrides
49
+ * Uses location and resources from booking if they exist (not from the slot)
50
+ */
51
+ const getFilters = () => {
52
+ const baseFilters = config.filters ?? {};
53
+ const selectedStaffMembersIds = bookingService.serviceSelections
54
+ .get()[0]
55
+ ?.resources?.map((resource) => resource._id);
56
+ const staffMemberIds = selectedStaffMembersIds?.length
57
+ ? selectedStaffMembersIds
58
+ : baseFilters.staffMemberIds;
59
+ const selectedLocation = bookingService.location.get();
60
+ const location = selectedLocation
61
+ ? {
62
+ id: selectedLocation._id,
63
+ locationType: selectedLocation.type,
64
+ }
65
+ : baseFilters.location;
66
+ return {
67
+ ...baseFilters,
68
+ location,
69
+ staffMemberIds,
70
+ };
71
+ };
47
72
  /**
48
73
  * Fetch time slots from API
49
- * Called on initialization or when filters change
74
+ * If cursor is provided, appends to existing slots (pagination)
75
+ * If no cursor, resets state and fetches fresh slots
50
76
  */
51
- const fetchTimeSlots = async () => {
52
- const selectedServices = getServices();
77
+ const fetchTimeSlots = async ({ isLoadMore } = { isLoadMore: false }) => {
78
+ if (isLoadingSignal.get() || (isLoadMore && !cursorSignal.get())) {
79
+ return;
80
+ }
81
+ const cursor = isLoadMore ? cursorSignal.get() : undefined;
53
82
  isLoadingSignal.set(true);
54
83
  errorSignal.set(null);
55
- cursorSignal.set(undefined);
56
- timeSlotsSignal.set([]);
84
+ if (!isLoadMore) {
85
+ timeSlotsSignal.set([]);
86
+ }
57
87
  try {
58
88
  const response = await fetchAvailability({
59
- services: selectedServices,
89
+ services: getServices(),
60
90
  fromLocalDate: startDateSignal.get(),
61
91
  toLocalDate: endDateSignal.get(),
62
92
  timeZone: bookingService.timezone.get(),
63
93
  viewMode: viewModeSignal.get(),
64
- cursor: cursorSignal.get(),
65
- ...config.filters,
94
+ cursor,
95
+ ...getFilters(),
66
96
  });
67
- timeSlotsSignal.set(response.timeSlots);
97
+ const newSlots = response.timeSlots;
98
+ timeSlotsSignal.set(isLoadMore ? [...timeSlotsSignal.get(), ...newSlots] : newSlots);
68
99
  const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
69
100
  cursorSignal.set(nextCursor);
70
101
  hasMoreSignal.set(!!nextCursor);
@@ -72,7 +103,9 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
72
103
  catch (error) {
73
104
  const message = error instanceof Error
74
105
  ? error.message
75
- : 'Failed to load time slots';
106
+ : isLoadMore
107
+ ? 'Failed to load more time slots'
108
+ : 'Failed to load time slots';
76
109
  errorSignal.set(message);
77
110
  }
78
111
  finally {
@@ -86,26 +119,23 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
86
119
  * - Auto-selects staff only when there is exactly one staff option in this slot
87
120
  */
88
121
  const selectSlot = (slot) => {
122
+ const staffMembersFromSlot = slot.availableResources[0].resources.map((r) => ({
123
+ _id: r._id,
124
+ name: r.name,
125
+ }));
126
+ const resources = staffMembersFromSlot.length === 1 ? staffMembersFromSlot : [];
89
127
  const timeSlot = {
90
128
  startDate: slot.localStartDate,
91
129
  endDate: slot.localEndDate,
92
130
  totalCapacity: slot.totalCapacity ?? undefined,
93
131
  remainingCapacity: slot.remainingCapacity ?? undefined,
94
132
  scheduleId: slot.scheduleId ?? undefined,
95
- eventInfo: slot.eventInfo ?? undefined,
133
+ eventInfo: slot.eventInfo,
134
+ location: slot.location,
135
+ resources,
96
136
  };
97
- bookingService.actions.setLocation({
98
- id: slot.location?._id,
99
- name: slot.location?.name ?? undefined,
100
- });
101
- const staffMembersFromSlot = slot.availableResources[0].resources.map((r) => ({
102
- id: r._id,
103
- name: r.name,
104
- }));
105
- const resources = staffMembersFromSlot.length === 1 ? staffMembersFromSlot : [];
106
137
  bookingService.actions.updateItem({
107
138
  timeSlot,
108
- resources,
109
139
  });
110
140
  };
111
141
  /**
@@ -113,82 +143,46 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
113
143
  * Updates the booking service with the selected resource
114
144
  */
115
145
  const selectStaffMember = (staffMemberId) => {
116
- const selectedBookingTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
117
- if (!selectedBookingTimeSlot)
146
+ const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
147
+ if (!selectedTimeSlot)
118
148
  return;
119
- const findStaffName = (id) => {
149
+ const getStaffName = (id) => {
150
+ if (id === ANY_STAFF_MEMBER_ID)
151
+ return 'Any staff member';
120
152
  const slots = timeSlotsSignal.get();
121
153
  return (slots
122
154
  .flatMap((slot) => slot.availableResources[0].resources)
123
155
  .find((r) => r._id === id)?.name ?? undefined);
124
156
  };
125
- if (staffMemberId === ANY_STAFF_MEMBER_ID) {
126
- bookingService.actions.updateItem({
127
- resources: [
128
- {
129
- _id: ANY_STAFF_MEMBER_ID,
130
- name: 'Any staff member',
131
- },
132
- ],
133
- });
134
- return;
135
- }
157
+ const resources = [
158
+ {
159
+ _id: staffMemberId,
160
+ name: getStaffName(staffMemberId),
161
+ },
162
+ ];
136
163
  bookingService.actions.updateItem({
137
- resources: [
138
- {
139
- _id: staffMemberId,
140
- name: findStaffName(staffMemberId),
141
- },
142
- ],
164
+ timeSlot: {
165
+ ...selectedTimeSlot,
166
+ resources,
167
+ },
143
168
  });
144
169
  };
145
170
  /**
146
171
  * Action: Clear staff selection while keeping the selected time slot
147
172
  */
148
173
  const clearStaffSelection = () => {
149
- const selectedBookingTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
150
- if (!selectedBookingTimeSlot)
174
+ const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
175
+ if (!selectedTimeSlot)
151
176
  return;
152
177
  bookingService.actions.updateItem({
153
- resources: [],
178
+ timeSlot: { ...selectedTimeSlot, resources: [] },
154
179
  });
155
180
  };
156
181
  /**
157
182
  * Action: Load more slots (pagination)
158
183
  */
159
184
  const loadMore = async () => {
160
- const cursor = cursorSignal.get();
161
- if (!cursor || isLoadingSignal.get())
162
- return;
163
- const selectedServices = getServices();
164
- isLoadingSignal.set(true);
165
- try {
166
- const response = await fetchAvailability({
167
- services: selectedServices,
168
- fromLocalDate: startDateSignal.get(),
169
- toLocalDate: endDateSignal.get(),
170
- timeZone: bookingService.timezone.get(),
171
- cursor,
172
- viewMode: viewModeSignal.get(),
173
- ...config.filters,
174
- });
175
- timeSlotsSignal.set([
176
- ...timeSlotsSignal.get(),
177
- ...response.timeSlots,
178
- ]);
179
- const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
180
- cursorSignal.set(nextCursor);
181
- hasMoreSignal.set(!!nextCursor);
182
- }
183
- catch (error) {
184
- const message = error instanceof Error
185
- ? error.message
186
- : 'Failed to load more time slots';
187
- errorSignal.set(message);
188
- }
189
- finally {
190
- isLoadingSignal.set(false);
191
- }
185
+ return fetchTimeSlots({ isLoadMore: true });
192
186
  };
193
187
  /**
194
188
  * Action: Update date range and refetch slots
@@ -202,22 +196,19 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
202
196
  }
203
197
  startDateSignal.set(newStartDate);
204
198
  endDateSignal.set(newEndDate);
205
- fetchTimeSlots();
206
199
  };
207
- // Auto-fetch on initialization if no pre-loaded data provided
208
- if (!config.timeSlots) {
209
- fetchTimeSlots();
210
- }
211
- // Subscribe to timezone changes from BookingService to auto-refetch
212
- // NOTE: This should depend ONLY on bookingService.timezone.
213
- let firstTimezoneEffectRun = true;
200
+ const resourcesSignal = signalsService.computed(() => {
201
+ return bookingService.serviceSelections.get()[0]?.resources;
202
+ });
203
+ let isFirstRun = true;
214
204
  signalsService.effect(() => {
215
205
  bookingService.timezone.get();
216
- if (firstTimezoneEffectRun) {
217
- firstTimezoneEffectRun = false;
218
- return;
206
+ bookingService.location.get();
207
+ resourcesSignal.get();
208
+ if (!isFirstRun || !config.timeSlots) {
209
+ fetchTimeSlots();
219
210
  }
220
- fetchTimeSlots();
211
+ isFirstRun = false;
221
212
  });
222
213
  // Return service API
223
214
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-bookings",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
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": "b165685e9ccb4d9fe0e8d310393e59018c8d44c874e2c7cd0fc9906a"
75
+ "falconPackageHash": "b195003cbc18c0477da02f42dd5e5409c483f2de627832cbdb859424"
76
76
  }