@wix/headless-bookings 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Booking Service - Global booking store
3
- * Manages booking flow state (selected services, time slot, location, form values)
3
+ * Manages booking flow state (selected services, time slot, location, form submission)
4
4
  * Persists across page navigation in SPA until page refresh
5
5
  */
6
6
  import { defineService, implementService } from '@wix/services-definitions';
@@ -9,7 +9,7 @@ import { SignalsServiceDefinition, } from '@wix/services-definitions/core-servic
9
9
  // Types
10
10
  // ============================================================================
11
11
  /**
12
- * Generates a unique instance ID for booking items
12
+ * Generates a unique instance ID for service selections
13
13
  */
14
14
  function generateInstanceId() {
15
15
  return crypto.randomUUID();
@@ -39,8 +39,8 @@ export const BookingServiceDefinition = defineService('booking');
39
39
  *
40
40
  * // In any page - access booking data
41
41
  * <Booking.Data>
42
- * {({ bookingItems, location, formValues, actions }) => (
43
- * <div>{bookingItems.map(item => item.service.name).join(', ')}</div>
42
+ * {({ serviceSelections, location, formSubmission, actions }) => (
43
+ * <div>{serviceSelections.map(item => item.service.name).join(', ')}</div>
44
44
  * )}
45
45
  * </Booking.Data>
46
46
  * ```
@@ -48,15 +48,14 @@ export const BookingServiceDefinition = defineService('booking');
48
48
  export const BookingService = implementService.withConfig()(BookingServiceDefinition, ({ getService, config }) => {
49
49
  const signalsService = getService(SignalsServiceDefinition);
50
50
  // Initialize signals with config values
51
- // Ensure all initial booking items have instanceIds
52
- const initialItemsWithIds = (config.bookingItems || []).map((item) => ({
51
+ // Ensure all initial service selections have instanceIds
52
+ const initialItemsWithIds = (config.serviceSelections || []).map((item) => ({
53
53
  ...item,
54
54
  instanceId: item.instanceId || generateInstanceId(),
55
55
  }));
56
- const bookingItemsSignal = signalsService.signal(initialItemsWithIds);
56
+ const serviceSelectionsSignal = signalsService.signal(initialItemsWithIds);
57
57
  const locationSignal = signalsService.signal(config.location || null);
58
- const formValuesSignal = signalsService.signal(config.formValues || null);
59
- const numberOfParticipantsSignal = signalsService.signal(config.numberOfParticipants ?? 1);
58
+ const formSubmissionSignal = signalsService.signal(config.formSubmission || null);
60
59
  // ========================================================================
61
60
  // Actions Implementation
62
61
  // ========================================================================
@@ -68,43 +67,43 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
68
67
  ...item,
69
68
  instanceId,
70
69
  };
71
- bookingItemsSignal.set([newItem]);
70
+ serviceSelectionsSignal.set([newItem]);
72
71
  return instanceId;
73
72
  },
74
73
  addItem(item) {
75
74
  const instanceId = generateInstanceId();
76
- const current = bookingItemsSignal.get();
75
+ const current = serviceSelectionsSignal.get();
77
76
  const newItem = {
78
77
  ...item,
79
78
  instanceId,
80
79
  };
81
- bookingItemsSignal.set([...current, newItem]);
80
+ serviceSelectionsSignal.set([...current, newItem]);
82
81
  return instanceId;
83
82
  },
84
83
  updateItem(updates, instanceId) {
85
- const current = bookingItemsSignal.get();
84
+ const current = serviceSelectionsSignal.get();
86
85
  // If no instanceId provided, update the first item (index 0)
87
86
  const targetId = instanceId ?? current[0]?.instanceId;
88
87
  const updated = current.map((item) => item.instanceId === targetId ? { ...item, ...updates } : item);
89
- bookingItemsSignal.set(updated);
88
+ serviceSelectionsSignal.set(updated);
90
89
  },
91
90
  removeItem(instanceId) {
92
- const current = bookingItemsSignal.get();
91
+ const current = serviceSelectionsSignal.get();
93
92
  if (!instanceId) {
94
93
  // Remove first item if no instanceId provided
95
- bookingItemsSignal.set(current.slice(1));
94
+ serviceSelectionsSignal.set(current.slice(1));
96
95
  }
97
96
  else {
98
97
  const filtered = current.filter((item) => item.instanceId !== instanceId);
99
- bookingItemsSignal.set(filtered);
98
+ serviceSelectionsSignal.set(filtered);
100
99
  }
101
100
  },
102
101
  clearItems() {
103
- bookingItemsSignal.set([]);
102
+ serviceSelectionsSignal.set([]);
104
103
  },
105
104
  // Resource helpers
106
105
  setResourceById(resourceId, resource, instanceId) {
107
- const current = bookingItemsSignal.get();
106
+ const current = serviceSelectionsSignal.get();
108
107
  // If no instanceId provided, use the first item
109
108
  const targetId = instanceId ?? current[0]?.instanceId;
110
109
  if (!targetId)
@@ -123,10 +122,10 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
123
122
  }
124
123
  return { ...item, resources: newResources };
125
124
  });
126
- bookingItemsSignal.set(updated);
125
+ serviceSelectionsSignal.set(updated);
127
126
  },
128
127
  getResourceById(resourceId, instanceId) {
129
- const current = bookingItemsSignal.get();
128
+ const current = serviceSelectionsSignal.get();
130
129
  // If no instanceId provided, use the first item
131
130
  const targetId = instanceId ?? current[0]?.instanceId;
132
131
  if (!targetId)
@@ -143,30 +142,24 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
143
142
  clearLocation() {
144
143
  locationSignal.set(null);
145
144
  },
146
- // Form Values actions
147
- setFormValues(values) {
148
- formValuesSignal.set(values);
145
+ // Form Submission actions
146
+ setFormSubmission(values) {
147
+ formSubmissionSignal.set(values);
149
148
  },
150
- clearFormValues() {
151
- formValuesSignal.set(null);
152
- },
153
- // Number of Participants actions
154
- setNumberOfParticipants(count) {
155
- numberOfParticipantsSignal.set(count);
149
+ clearFormSubmission() {
150
+ formSubmissionSignal.set(null);
156
151
  },
157
152
  // Clear all
158
153
  clearAll() {
159
- bookingItemsSignal.set([]);
154
+ serviceSelectionsSignal.set([]);
160
155
  locationSignal.set(null);
161
- formValuesSignal.set(null);
162
- numberOfParticipantsSignal.set(1);
156
+ formSubmissionSignal.set(null);
163
157
  },
164
158
  };
165
159
  return {
166
- bookingItems: bookingItemsSignal,
160
+ serviceSelections: serviceSelectionsSignal,
167
161
  location: locationSignal,
168
- formValues: formValuesSignal,
169
- numberOfParticipants: numberOfParticipantsSignal,
162
+ formSubmission: formSubmissionSignal,
170
163
  actions,
171
164
  };
172
165
  });
@@ -3,6 +3,6 @@
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, } from './booking/booking.js';
6
+ export { BookingService, BookingServiceDefinition, type BookingServiceConfig, type Location, type BookingServiceActions, type ServiceSelection, type ServiceSelectionTimeSlot, } from './booking/booking.js';
7
7
  export { TimeSlotListService, TimeSlotListServiceDefinition, ViewMode, loadTimeSlotListConfig, type TimeSlotListServiceAPI, type TimeSlotListServiceActions, type TimeSlotListServiceConfig, type SlotSelection, type TimeSlot, 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';
@@ -81,8 +81,8 @@ export const ServiceService = implementService.withConfig()(ServiceServiceDefini
81
81
  const currentService = serviceSignal.get();
82
82
  if (!currentService._id)
83
83
  return false;
84
- const bookingItems = bookingService.bookingItems.get();
85
- return bookingItems.some(({ service }) => service._id === currentService._id);
84
+ const serviceSelections = bookingService.serviceSelections.get();
85
+ return serviceSelections.some(({ service }) => service._id === currentService._id);
86
86
  });
87
87
  // requiresAvailability signal - true for APPOINTMENT and CLASS types
88
88
  const requiresAvailabilitySignal = signalsService.computed(() => {
@@ -201,8 +201,8 @@ export const ServiceService = implementService.withConfig()(ServiceServiceDefini
201
201
  return;
202
202
  const currentService = serviceSignal.get();
203
203
  // Remove all instances of this service (by serviceId)
204
- const bookingItems = bookingService.bookingItems.get();
205
- const instancesToRemove = bookingItems
204
+ const serviceSelections = bookingService.serviceSelections.get();
205
+ const instancesToRemove = serviceSelections
206
206
  .filter((s) => s.service._id === currentService._id)
207
207
  .map((s) => s.instanceId);
208
208
  instancesToRemove.forEach((instanceId) => {
@@ -51,8 +51,6 @@ export interface TimeSlotsFilters {
51
51
  locations?: Location[];
52
52
  /** Filter by specific staff member IDs */
53
53
  staffMemberIds?: string[];
54
- /** Maximum slots to return per day (useful for calendar views) */
55
- slotsPerDay?: number;
56
54
  /** Filter by bookable status. If undefined, returns both bookable and non-bookable */
57
55
  bookable?: boolean;
58
56
  /** Filter by booking policy violations */
@@ -61,6 +59,17 @@ export interface TimeSlotsFilters {
61
59
  tooLateToBook?: boolean;
62
60
  bookOnlineDisabled?: boolean;
63
61
  };
62
+ /**
63
+ * Number of time slots to load per request.
64
+ * Controls how many slots are fetched initially and on each "Load More" action.
65
+ * Default: 1000. Max: 1000.
66
+ * @example
67
+ * ```ts
68
+ * // Load 6 slots at a time
69
+ * filters: { slotsPerPage: 6 }
70
+ * ```
71
+ */
72
+ slotsPerPage?: number;
64
73
  }
65
74
  export interface TimeSlotListServiceConfig {
66
75
  /** Services to fetch time slots for */
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { implementService } from '@wix/services-definitions';
6
6
  import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
7
+ import { format } from 'date-fns';
7
8
  import { TimeSlotListServiceDefinition, ViewMode, } from './time-slot-list.def.js';
8
9
  import { fetchAvailability } from '../../api/time-slot-list/index.js';
9
10
  import { BookingServiceDefinition, } from '../booking/booking.js';
@@ -22,13 +23,8 @@ function getClientTimezone() {
22
23
  * Formats a Date to local ISO string (YYYY-MM-DDThh:mm:ss)
23
24
  */
24
25
  function toLocalDateString(date) {
25
- const year = date.getFullYear();
26
- const month = String(date.getMonth() + 1).padStart(2, '0');
27
- const day = String(date.getDate()).padStart(2, '0');
28
- const hours = String(date.getHours()).padStart(2, '0');
29
- const minutes = String(date.getMinutes()).padStart(2, '0');
30
- const seconds = String(date.getSeconds()).padStart(2, '0');
31
- return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
26
+ const pattern = "yyyy-MM-dd'T'HH:mm:ss";
27
+ return format(date, pattern);
32
28
  }
33
29
  /**
34
30
  * Merges new grouped slots with existing ones
@@ -80,7 +76,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
80
76
  const getServices = () => {
81
77
  if (config.services?.length)
82
78
  return config.services;
83
- return bookingService.bookingItems.get().map((s) => s.service);
79
+ return bookingService.serviceSelections.get().map((s) => s.service);
84
80
  };
85
81
  /**
86
82
  * Helper: Get filters from config or BookingService
@@ -88,7 +84,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
88
84
  const getFilters = () => {
89
85
  // Get BookingService selections (these take priority)
90
86
  const location = bookingService.location.get();
91
- const selections = bookingService.bookingItems.get();
87
+ const selections = bookingService.serviceSelections.get();
92
88
  const selectedStaffMemberIds = selections
93
89
  .map(({ resources }) => resources.map((r) => r._id))
94
90
  .flat();
@@ -114,6 +110,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
114
110
  isLoadingSignal.set(true);
115
111
  errorSignal.set(null);
116
112
  cursorSignal.set(undefined);
113
+ timeSlotsSignal.set([]);
117
114
  try {
118
115
  const response = await fetchAvailability({
119
116
  services: selectedServices,
@@ -121,6 +118,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
121
118
  toLocalDate: toLocalDateString(endDateSignal.get()),
122
119
  timeZone: timezoneSignal.get(),
123
120
  viewMode: viewModeSignal.get(),
121
+ cursor: cursorSignal.get(),
124
122
  ...filters,
125
123
  });
126
124
  timeSlotsSignal.set(response.timeSlots);
@@ -312,7 +310,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
312
310
  fromLocalDate: toLocalDateString(startDateSignal.get()),
313
311
  toLocalDate: toLocalDateString(endDateSignal.get()),
314
312
  timeZone: timezoneSignal.get(),
315
- cursorPaging: { cursor },
313
+ cursor,
316
314
  viewMode: viewModeSignal.get(),
317
315
  ...filters,
318
316
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-bookings",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -39,7 +39,8 @@
39
39
  "@wix/headless-media": "0.0.18",
40
40
  "@wix/headless-utils": "0.0.8",
41
41
  "@wix/services-definitions": "^0.1.4",
42
- "@wix/services-manager-react": "^0.1.26"
42
+ "@wix/services-manager-react": "^0.1.26",
43
+ "date-fns": "^4.1.0"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@testing-library/dom": "^10.4.0",
@@ -69,5 +70,5 @@
69
70
  "groupId": "com.wixpress.headless-components"
70
71
  }
71
72
  },
72
- "falconPackageHash": "e46d03203e690ac494ff428495021e6c913ba752169e356628f8b4b2"
73
+ "falconPackageHash": "25ba2b137a6e9ae4a9d267c7eeede1474665fe843f2999d663f593e4"
73
74
  }