@wix/headless-bookings 0.0.33 → 0.0.35

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,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__";
@@ -22,7 +22,7 @@ export interface ServiceSelectionTimeSlot {
22
22
  /** Resources available in this time slot (populated when slot is selected) */
23
23
  resources?: Resource[];
24
24
  /** Location of this time slot (populated when slot is selected) */
25
- location?: Location;
25
+ location?: TimeSlotLocationType;
26
26
  }
27
27
  /**
28
28
  * ServiceSelection contains the service AND its related selections
@@ -39,10 +39,6 @@ export interface ServiceSelection {
39
39
  /** Number of participants for this service selection */
40
40
  totalParticipants?: number;
41
41
  }
42
- export interface Location {
43
- id: string;
44
- name?: string;
45
- }
46
42
  /**
47
43
  * BookingService actions - setters only, no API calls
48
44
  */
@@ -61,7 +57,7 @@ export interface BookingServiceActions {
61
57
  setResourceById(resourceId: string, resource: Resource, instanceId?: string): void;
62
58
  /** Gets a resource by ID from a service selection. If no instanceId provided, uses the first item. */
63
59
  getResourceById(resourceId: string, instanceId?: string): Resource | undefined;
64
- setLocation(location: Location | null): void;
60
+ setLocation(location: ServiceLocationType | null): void;
65
61
  clearLocation(): void;
66
62
  setTimezone(timezone: string): void;
67
63
  setFormSubmission(values: FormValues | null): void;
@@ -77,7 +73,7 @@ export interface BookingService {
77
73
  /** Signal containing the service selections (services with their related data) */
78
74
  serviceSelections: Signal<ServiceSelection[]>;
79
75
  /** Signal containing the selected location */
80
- location: Signal<Location | null>;
76
+ location: Signal<ServiceLocationType | null>;
81
77
  /** Signal containing the selected timezone */
82
78
  timezone: Signal<string | undefined>;
83
79
  /** Signal containing the form submission */
@@ -101,7 +97,7 @@ export interface BookingServiceConfig {
101
97
  /** Optional initial service selections to pre-populate */
102
98
  serviceSelections?: ServiceSelection[];
103
99
  /** Optional initial location */
104
- location?: Location | null;
100
+ location?: ServiceLocationType | null;
105
101
  /** Optional initial timezone */
106
102
  timezone?: string;
107
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 {
@@ -90,27 +123,19 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
90
123
  _id: r._id,
91
124
  name: r.name,
92
125
  }));
93
- const slotLocation = {
94
- id: slot.location?._id,
95
- name: slot.location?.name ?? undefined,
96
- };
126
+ const resources = staffMembersFromSlot.length === 1 ? staffMembersFromSlot : [];
97
127
  const timeSlot = {
98
128
  startDate: slot.localStartDate,
99
129
  endDate: slot.localEndDate,
100
130
  totalCapacity: slot.totalCapacity ?? undefined,
101
131
  remainingCapacity: slot.remainingCapacity ?? undefined,
102
132
  scheduleId: slot.scheduleId ?? undefined,
103
- eventInfo: slot.eventInfo ?? undefined,
104
- resources: staffMembersFromSlot,
105
- location: slotLocation,
133
+ eventInfo: slot.eventInfo,
134
+ location: slot.location,
135
+ resources,
106
136
  };
107
- // Keep setting top-level location (for pre-slot selection use cases)
108
- bookingService.actions.setLocation(slotLocation);
109
- // Auto-select resources only when single staff
110
- const resources = staffMembersFromSlot.length === 1 ? staffMembersFromSlot : [];
111
137
  bookingService.actions.updateItem({
112
138
  timeSlot,
113
- resources,
114
139
  });
115
140
  };
116
141
  /**
@@ -118,82 +143,46 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
118
143
  * Updates the booking service with the selected resource
119
144
  */
120
145
  const selectStaffMember = (staffMemberId) => {
121
- const selectedBookingTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
122
- if (!selectedBookingTimeSlot)
146
+ const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
147
+ if (!selectedTimeSlot)
123
148
  return;
124
- const findStaffName = (id) => {
149
+ const getStaffName = (id) => {
150
+ if (id === ANY_STAFF_MEMBER_ID)
151
+ return 'Any staff member';
125
152
  const slots = timeSlotsSignal.get();
126
153
  return (slots
127
154
  .flatMap((slot) => slot.availableResources[0].resources)
128
155
  .find((r) => r._id === id)?.name ?? undefined);
129
156
  };
130
- if (staffMemberId === ANY_STAFF_MEMBER_ID) {
131
- bookingService.actions.updateItem({
132
- resources: [
133
- {
134
- _id: ANY_STAFF_MEMBER_ID,
135
- name: 'Any staff member',
136
- },
137
- ],
138
- });
139
- return;
140
- }
157
+ const resources = [
158
+ {
159
+ _id: staffMemberId,
160
+ name: getStaffName(staffMemberId),
161
+ },
162
+ ];
141
163
  bookingService.actions.updateItem({
142
- resources: [
143
- {
144
- _id: staffMemberId,
145
- name: findStaffName(staffMemberId),
146
- },
147
- ],
164
+ timeSlot: {
165
+ ...selectedTimeSlot,
166
+ resources,
167
+ },
148
168
  });
149
169
  };
150
170
  /**
151
171
  * Action: Clear staff selection while keeping the selected time slot
152
172
  */
153
173
  const clearStaffSelection = () => {
154
- const selectedBookingTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
155
- if (!selectedBookingTimeSlot)
174
+ const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
175
+ if (!selectedTimeSlot)
156
176
  return;
157
177
  bookingService.actions.updateItem({
158
- resources: [],
178
+ timeSlot: { ...selectedTimeSlot, resources: [] },
159
179
  });
160
180
  };
161
181
  /**
162
182
  * Action: Load more slots (pagination)
163
183
  */
164
184
  const loadMore = async () => {
165
- const cursor = cursorSignal.get();
166
- if (!cursor || isLoadingSignal.get())
167
- return;
168
- const selectedServices = getServices();
169
- isLoadingSignal.set(true);
170
- try {
171
- const response = await fetchAvailability({
172
- services: selectedServices,
173
- fromLocalDate: startDateSignal.get(),
174
- toLocalDate: endDateSignal.get(),
175
- timeZone: bookingService.timezone.get(),
176
- cursor,
177
- viewMode: viewModeSignal.get(),
178
- ...config.filters,
179
- });
180
- timeSlotsSignal.set([
181
- ...timeSlotsSignal.get(),
182
- ...response.timeSlots,
183
- ]);
184
- const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
185
- cursorSignal.set(nextCursor);
186
- hasMoreSignal.set(!!nextCursor);
187
- }
188
- catch (error) {
189
- const message = error instanceof Error
190
- ? error.message
191
- : 'Failed to load more time slots';
192
- errorSignal.set(message);
193
- }
194
- finally {
195
- isLoadingSignal.set(false);
196
- }
185
+ return fetchTimeSlots({ isLoadMore: true });
197
186
  };
198
187
  /**
199
188
  * Action: Update date range and refetch slots
@@ -207,22 +196,19 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
207
196
  }
208
197
  startDateSignal.set(newStartDate);
209
198
  endDateSignal.set(newEndDate);
210
- fetchTimeSlots();
211
199
  };
212
- // Auto-fetch on initialization if no pre-loaded data provided
213
- if (!config.timeSlots) {
214
- fetchTimeSlots();
215
- }
216
- // Subscribe to timezone changes from BookingService to auto-refetch
217
- // NOTE: This should depend ONLY on bookingService.timezone.
218
- let firstTimezoneEffectRun = true;
200
+ const resourcesSignal = signalsService.computed(() => {
201
+ return bookingService.serviceSelections.get()[0]?.resources;
202
+ });
203
+ let isFirstRun = true;
219
204
  signalsService.effect(() => {
220
205
  bookingService.timezone.get();
221
- if (firstTimezoneEffectRun) {
222
- firstTimezoneEffectRun = false;
223
- return;
206
+ bookingService.location.get();
207
+ resourcesSignal.get();
208
+ if (!isFirstRun || !config.timeSlots) {
209
+ fetchTimeSlots();
224
210
  }
225
- fetchTimeSlots();
211
+ isFirstRun = false;
226
212
  });
227
213
  // Return service API
228
214
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-bookings",
3
- "version": "0.0.33",
3
+ "version": "0.0.35",
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": "b5f66fe6afde6dde21cff958f2df9ecaa8bb6890eed0ee093057887e"
75
+ "falconPackageHash": "ad087858a42d4f0c4cf86b870ec3b6b93fac74806bacb2f95cf753a2"
76
76
  }