@wix/headless-bookings 0.0.36 → 0.0.38

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,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';
@@ -15,31 +15,6 @@ function getClientTimezone() {
15
15
  ? Intl.DateTimeFormat().resolvedOptions().timeZone
16
16
  : undefined;
17
17
  }
18
- /** Compare two string arrays for equality (order-independent) */
19
- function arraysEqual(a, b) {
20
- if (a.length !== b.length)
21
- return false;
22
- const sortedA = [...a].sort();
23
- const sortedB = [...b].sort();
24
- return sortedA.every((val, i) => val === sortedB[i]);
25
- }
26
- /** Create a memoized value that only updates when IDs change */
27
- function createMemoizedByIds({ fallback, getValue, extractId, }) {
28
- let cachedIds = [];
29
- let cachedValue = fallback;
30
- return () => {
31
- const value = getValue();
32
- const items = value ?? [];
33
- const newIds = items.length && typeof items[0] === 'string'
34
- ? items
35
- : items.map(extractId);
36
- if (!arraysEqual(newIds, cachedIds)) {
37
- cachedIds = newIds;
38
- cachedValue = items.length ? value : fallback;
39
- }
40
- return cachedValue;
41
- };
42
- }
43
18
  // ============================================================================
44
19
  // Service Implementation
45
20
  // ============================================================================
@@ -59,30 +34,24 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
59
34
  end: config.endDate,
60
35
  });
61
36
  const cursorSignal = signalsService.signal(undefined);
62
- const servicesSignal = signalsService.computed(createMemoizedByIds({
63
- fallback: config.services ?? [],
64
- getValue: () => bookingService.serviceSelections.get().map((s) => s.service),
65
- extractId: (service) => service._id,
66
- }));
67
- const preSelectedStaffSignal = signalsService.computed(createMemoizedByIds({
68
- fallback: config.filters?.staffMemberIds,
69
- getValue: () => (bookingService.serviceSelections.get()[0]?.resources ?? []).map((resource) => resource._id),
70
- }));
71
- const filtersSignal = signalsService.computed(() => {
37
+ const getServices = () => {
38
+ const serviceSelections = bookingService.serviceSelections.peek();
39
+ return serviceSelections.length
40
+ ? serviceSelections.map((s) => s.service)
41
+ : (config.services ?? []);
42
+ };
43
+ const getFilters = () => {
72
44
  const baseFilters = config.filters ?? {};
73
- const selectedLocation = bookingService.location.get();
45
+ const selectedLocation = bookingService.location.peek();
74
46
  const location = selectedLocation
75
- ? {
76
- id: selectedLocation._id,
77
- locationType: selectedLocation.type,
78
- }
47
+ ? { id: selectedLocation._id, locationType: selectedLocation.type }
79
48
  : baseFilters.location;
80
- return {
81
- ...baseFilters,
82
- location,
83
- staffMemberIds: preSelectedStaffSignal.get(),
84
- };
85
- });
49
+ const selectedResources = bookingService.serviceSelections.peek()[0]?.resources ?? [];
50
+ const staffMemberIds = selectedResources.length
51
+ ? selectedResources.map((r) => r._id)
52
+ : baseFilters.staffMemberIds;
53
+ return { ...baseFilters, location, staffMemberIds };
54
+ };
86
55
  const fetchTimeSlots = async () => {
87
56
  isLoadingSignal.set(true);
88
57
  errorSignal.set(null);
@@ -90,12 +59,12 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
90
59
  timeSlotsSignal.set([]);
91
60
  try {
92
61
  const response = await fetchAvailability({
93
- services: servicesSignal.get(),
94
- timeZone: bookingService.timezone.get(),
95
- viewMode: viewModeSignal.get(),
96
- fromLocalDate: dateRangeSignal.get().start,
97
- toLocalDate: dateRangeSignal.get().end,
98
- ...filtersSignal.get(),
62
+ services: getServices(),
63
+ timeZone: bookingService.timezone.peek(),
64
+ viewMode: viewModeSignal.peek(),
65
+ fromLocalDate: dateRangeSignal.peek().start,
66
+ toLocalDate: dateRangeSignal.peek().end,
67
+ ...getFilters(),
99
68
  });
100
69
  timeSlotsSignal.set(response.timeSlots);
101
70
  const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
@@ -143,13 +112,13 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
143
112
  * Updates the booking service with the selected resource
144
113
  */
145
114
  const selectStaffMember = (staffMemberId) => {
146
- const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
115
+ const selectedTimeSlot = bookingService.serviceSelections.peek()[0]?.timeSlot;
147
116
  if (!selectedTimeSlot)
148
117
  return;
149
118
  const getStaffName = (id) => {
150
119
  if (id === ANY_STAFF_MEMBER_ID)
151
120
  return 'Any staff member';
152
- const slots = timeSlotsSignal.get();
121
+ const slots = timeSlotsSignal.peek();
153
122
  return (slots
154
123
  .flatMap((slot) => slot.availableResources[0].resources)
155
124
  .find((r) => r._id === id)?.name ?? undefined);
@@ -171,7 +140,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
171
140
  * Action: Clear staff selection while keeping the selected time slot
172
141
  */
173
142
  const clearStaffSelection = () => {
174
- const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
143
+ const selectedTimeSlot = bookingService.serviceSelections.peek()[0]?.timeSlot;
175
144
  if (!selectedTimeSlot)
176
145
  return;
177
146
  bookingService.actions.updateItem({
@@ -182,7 +151,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
182
151
  * Action: Load more slots (pagination)
183
152
  */
184
153
  const loadMore = async () => {
185
- const cursor = cursorSignal.get();
154
+ const cursor = cursorSignal.peek();
186
155
  if (!cursor) {
187
156
  return;
188
157
  }
@@ -190,16 +159,16 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
190
159
  errorSignal.set(null);
191
160
  try {
192
161
  const response = await fetchAvailability({
193
- services: servicesSignal.get(),
194
- fromLocalDate: dateRangeSignal.get().start,
195
- toLocalDate: dateRangeSignal.get().end,
196
- timeZone: bookingService.timezone.get(),
197
- viewMode: viewModeSignal.get(),
162
+ services: getServices(),
163
+ fromLocalDate: dateRangeSignal.peek().start,
164
+ toLocalDate: dateRangeSignal.peek().end,
165
+ timeZone: bookingService.timezone.peek(),
166
+ viewMode: viewModeSignal.peek(),
198
167
  cursor,
199
- ...filtersSignal.get(),
168
+ ...getFilters(),
200
169
  });
201
170
  const newSlots = response.timeSlots;
202
- timeSlotsSignal.set([...timeSlotsSignal.get(), ...newSlots]);
171
+ timeSlotsSignal.set([...timeSlotsSignal.peek(), ...newSlots]);
203
172
  const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
204
173
  cursorSignal.set(nextCursor);
205
174
  hasMoreSignal.set(!!nextCursor);
@@ -218,18 +187,26 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
218
187
  * Action: Update date range (effect will handle refetch)
219
188
  */
220
189
  const setDateRange = (newStartDate, newEndDate) => {
221
- const { start: currentStartDate, end: currentEndDate } = dateRangeSignal.get();
190
+ const { start: currentStartDate, end: currentEndDate } = dateRangeSignal.peek();
222
191
  if (currentStartDate.getTime() === newStartDate.getTime() &&
223
192
  currentEndDate.getTime() === newEndDate.getTime()) {
224
193
  return;
225
194
  }
226
195
  dateRangeSignal.set({ start: newStartDate, end: newEndDate });
227
196
  };
197
+ const preSelectedStaffMemberId = signalsService.computed(() => {
198
+ const serviceSelections = bookingService.serviceSelections.peek();
199
+ return serviceSelections.length
200
+ ? (serviceSelections[0]?.resources?.[0]?._id ?? undefined)
201
+ : undefined;
202
+ });
228
203
  let isFirstRun = true;
229
204
  signalsService.effect(() => {
230
- filtersSignal.get();
205
+ // Dependencies - trigger refetch when these change
231
206
  dateRangeSignal.get();
232
207
  bookingService.timezone.get();
208
+ bookingService.location.get();
209
+ preSelectedStaffMemberId.get();
233
210
  if (isFirstRun) {
234
211
  isFirstRun = false;
235
212
  if (config.timeSlots) {
@@ -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';
@@ -15,31 +15,6 @@ function getClientTimezone() {
15
15
  ? Intl.DateTimeFormat().resolvedOptions().timeZone
16
16
  : undefined;
17
17
  }
18
- /** Compare two string arrays for equality (order-independent) */
19
- function arraysEqual(a, b) {
20
- if (a.length !== b.length)
21
- return false;
22
- const sortedA = [...a].sort();
23
- const sortedB = [...b].sort();
24
- return sortedA.every((val, i) => val === sortedB[i]);
25
- }
26
- /** Create a memoized value that only updates when IDs change */
27
- function createMemoizedByIds({ fallback, getValue, extractId, }) {
28
- let cachedIds = [];
29
- let cachedValue = fallback;
30
- return () => {
31
- const value = getValue();
32
- const items = value ?? [];
33
- const newIds = items.length && typeof items[0] === 'string'
34
- ? items
35
- : items.map(extractId);
36
- if (!arraysEqual(newIds, cachedIds)) {
37
- cachedIds = newIds;
38
- cachedValue = items.length ? value : fallback;
39
- }
40
- return cachedValue;
41
- };
42
- }
43
18
  // ============================================================================
44
19
  // Service Implementation
45
20
  // ============================================================================
@@ -59,30 +34,24 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
59
34
  end: config.endDate,
60
35
  });
61
36
  const cursorSignal = signalsService.signal(undefined);
62
- const servicesSignal = signalsService.computed(createMemoizedByIds({
63
- fallback: config.services ?? [],
64
- getValue: () => bookingService.serviceSelections.get().map((s) => s.service),
65
- extractId: (service) => service._id,
66
- }));
67
- const preSelectedStaffSignal = signalsService.computed(createMemoizedByIds({
68
- fallback: config.filters?.staffMemberIds,
69
- getValue: () => (bookingService.serviceSelections.get()[0]?.resources ?? []).map((resource) => resource._id),
70
- }));
71
- const filtersSignal = signalsService.computed(() => {
37
+ const getServices = () => {
38
+ const serviceSelections = bookingService.serviceSelections.peek();
39
+ return serviceSelections.length
40
+ ? serviceSelections.map((s) => s.service)
41
+ : (config.services ?? []);
42
+ };
43
+ const getFilters = () => {
72
44
  const baseFilters = config.filters ?? {};
73
- const selectedLocation = bookingService.location.get();
45
+ const selectedLocation = bookingService.location.peek();
74
46
  const location = selectedLocation
75
- ? {
76
- id: selectedLocation._id,
77
- locationType: selectedLocation.type,
78
- }
47
+ ? { id: selectedLocation._id, locationType: selectedLocation.type }
79
48
  : baseFilters.location;
80
- return {
81
- ...baseFilters,
82
- location,
83
- staffMemberIds: preSelectedStaffSignal.get(),
84
- };
85
- });
49
+ const selectedResources = bookingService.serviceSelections.peek()[0]?.resources ?? [];
50
+ const staffMemberIds = selectedResources.length
51
+ ? selectedResources.map((r) => r._id)
52
+ : baseFilters.staffMemberIds;
53
+ return { ...baseFilters, location, staffMemberIds };
54
+ };
86
55
  const fetchTimeSlots = async () => {
87
56
  isLoadingSignal.set(true);
88
57
  errorSignal.set(null);
@@ -90,12 +59,12 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
90
59
  timeSlotsSignal.set([]);
91
60
  try {
92
61
  const response = await fetchAvailability({
93
- services: servicesSignal.get(),
94
- timeZone: bookingService.timezone.get(),
95
- viewMode: viewModeSignal.get(),
96
- fromLocalDate: dateRangeSignal.get().start,
97
- toLocalDate: dateRangeSignal.get().end,
98
- ...filtersSignal.get(),
62
+ services: getServices(),
63
+ timeZone: bookingService.timezone.peek(),
64
+ viewMode: viewModeSignal.peek(),
65
+ fromLocalDate: dateRangeSignal.peek().start,
66
+ toLocalDate: dateRangeSignal.peek().end,
67
+ ...getFilters(),
99
68
  });
100
69
  timeSlotsSignal.set(response.timeSlots);
101
70
  const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
@@ -143,13 +112,13 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
143
112
  * Updates the booking service with the selected resource
144
113
  */
145
114
  const selectStaffMember = (staffMemberId) => {
146
- const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
115
+ const selectedTimeSlot = bookingService.serviceSelections.peek()[0]?.timeSlot;
147
116
  if (!selectedTimeSlot)
148
117
  return;
149
118
  const getStaffName = (id) => {
150
119
  if (id === ANY_STAFF_MEMBER_ID)
151
120
  return 'Any staff member';
152
- const slots = timeSlotsSignal.get();
121
+ const slots = timeSlotsSignal.peek();
153
122
  return (slots
154
123
  .flatMap((slot) => slot.availableResources[0].resources)
155
124
  .find((r) => r._id === id)?.name ?? undefined);
@@ -171,7 +140,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
171
140
  * Action: Clear staff selection while keeping the selected time slot
172
141
  */
173
142
  const clearStaffSelection = () => {
174
- const selectedTimeSlot = bookingService.serviceSelections.get()[0]?.timeSlot;
143
+ const selectedTimeSlot = bookingService.serviceSelections.peek()[0]?.timeSlot;
175
144
  if (!selectedTimeSlot)
176
145
  return;
177
146
  bookingService.actions.updateItem({
@@ -182,7 +151,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
182
151
  * Action: Load more slots (pagination)
183
152
  */
184
153
  const loadMore = async () => {
185
- const cursor = cursorSignal.get();
154
+ const cursor = cursorSignal.peek();
186
155
  if (!cursor) {
187
156
  return;
188
157
  }
@@ -190,16 +159,16 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
190
159
  errorSignal.set(null);
191
160
  try {
192
161
  const response = await fetchAvailability({
193
- services: servicesSignal.get(),
194
- fromLocalDate: dateRangeSignal.get().start,
195
- toLocalDate: dateRangeSignal.get().end,
196
- timeZone: bookingService.timezone.get(),
197
- viewMode: viewModeSignal.get(),
162
+ services: getServices(),
163
+ fromLocalDate: dateRangeSignal.peek().start,
164
+ toLocalDate: dateRangeSignal.peek().end,
165
+ timeZone: bookingService.timezone.peek(),
166
+ viewMode: viewModeSignal.peek(),
198
167
  cursor,
199
- ...filtersSignal.get(),
168
+ ...getFilters(),
200
169
  });
201
170
  const newSlots = response.timeSlots;
202
- timeSlotsSignal.set([...timeSlotsSignal.get(), ...newSlots]);
171
+ timeSlotsSignal.set([...timeSlotsSignal.peek(), ...newSlots]);
203
172
  const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
204
173
  cursorSignal.set(nextCursor);
205
174
  hasMoreSignal.set(!!nextCursor);
@@ -218,18 +187,26 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
218
187
  * Action: Update date range (effect will handle refetch)
219
188
  */
220
189
  const setDateRange = (newStartDate, newEndDate) => {
221
- const { start: currentStartDate, end: currentEndDate } = dateRangeSignal.get();
190
+ const { start: currentStartDate, end: currentEndDate } = dateRangeSignal.peek();
222
191
  if (currentStartDate.getTime() === newStartDate.getTime() &&
223
192
  currentEndDate.getTime() === newEndDate.getTime()) {
224
193
  return;
225
194
  }
226
195
  dateRangeSignal.set({ start: newStartDate, end: newEndDate });
227
196
  };
197
+ const preSelectedStaffMemberId = signalsService.computed(() => {
198
+ const serviceSelections = bookingService.serviceSelections.peek();
199
+ return serviceSelections.length
200
+ ? (serviceSelections[0]?.resources?.[0]?._id ?? undefined)
201
+ : undefined;
202
+ });
228
203
  let isFirstRun = true;
229
204
  signalsService.effect(() => {
230
- filtersSignal.get();
205
+ // Dependencies - trigger refetch when these change
231
206
  dateRangeSignal.get();
232
207
  bookingService.timezone.get();
208
+ bookingService.location.get();
209
+ preSelectedStaffMemberId.get();
233
210
  if (isFirstRun) {
234
211
  isFirstRun = false;
235
212
  if (config.timeSlots) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-bookings",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
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": "ecae9bfd11fe7e50bf6e5eeb5ad0b3e9763b64d5cfa1aa1477176483"
75
+ "falconPackageHash": "9f684dcab2e31f9a11e4fa26eb6c63aef1f039fd24342eb4e056f05d"
76
76
  }