@wix/headless-bookings 0.0.24 → 0.0.26
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.
- package/cjs/dist/__mocks__/services.d.ts +75 -0
- package/cjs/dist/__mocks__/services.js +308 -0
- package/cjs/dist/api/fetch-availability/index.d.ts +5 -5
- package/cjs/dist/api/fetch-availability/index.js +9 -6
- package/cjs/dist/react/booking/Book.d.ts +49 -0
- package/cjs/dist/react/booking/Book.js +46 -0
- package/cjs/dist/react/booking/Booking.d.ts +18 -0
- package/cjs/dist/react/booking/Booking.js +19 -0
- package/cjs/dist/react/core/booking/Book.d.ts +41 -0
- package/cjs/dist/react/core/booking/Book.js +59 -0
- package/cjs/dist/react/core/time-slot-list/TimeSlot.d.ts +4 -4
- package/cjs/dist/react/core/time-slot-list/TimeSlot.js +3 -10
- package/cjs/dist/react/time-slot-list/TimeSlot.d.ts +3 -3
- package/cjs/dist/react/time-slot-list/TimeSlot.js +3 -3
- package/cjs/dist/react/time-slot-list/TimeSlotList.d.ts +5 -3
- package/cjs/dist/services/booking/book-action/bookAction.d.ts +21 -0
- package/cjs/dist/services/booking/book-action/bookAction.js +59 -0
- package/cjs/dist/services/booking/book-action/buildBookingRequest.d.ts +22 -0
- package/cjs/dist/services/booking/book-action/buildBookingRequest.js +81 -0
- package/cjs/dist/services/booking/book-action/buildCheckoutRequest.d.ts +26 -0
- package/cjs/dist/services/booking/book-action/buildCheckoutRequest.js +62 -0
- package/cjs/dist/services/booking/book-action/canBook.d.ts +23 -0
- package/cjs/dist/services/booking/book-action/canBook.js +19 -0
- package/cjs/dist/services/booking/book-action/index.d.ts +8 -0
- package/cjs/dist/services/booking/book-action/index.js +7 -0
- package/cjs/dist/services/booking/book-action/types.d.ts +50 -0
- package/cjs/dist/services/booking/book-action/types.js +4 -0
- package/cjs/dist/services/booking/booking.d.ts +2 -0
- package/cjs/dist/services/booking/booking.js +16 -0
- package/cjs/dist/services/index.d.ts +1 -0
- package/cjs/dist/services/index.js +1 -0
- package/cjs/dist/services/time-slot-list/time-slot-list.def.d.ts +10 -2
- package/cjs/dist/services/time-slot-list/time-slot-list.js +9 -29
- package/dist/__mocks__/services.d.ts +75 -0
- package/dist/__mocks__/services.js +308 -0
- package/dist/api/fetch-availability/index.d.ts +5 -5
- package/dist/api/fetch-availability/index.js +9 -6
- package/dist/react/booking/Book.d.ts +49 -0
- package/dist/react/booking/Book.js +46 -0
- package/dist/react/booking/Booking.d.ts +18 -0
- package/dist/react/booking/Booking.js +19 -0
- package/dist/react/core/booking/Book.d.ts +41 -0
- package/dist/react/core/booking/Book.js +59 -0
- package/dist/react/core/time-slot-list/TimeSlot.d.ts +4 -4
- package/dist/react/core/time-slot-list/TimeSlot.js +3 -10
- package/dist/react/time-slot-list/TimeSlot.d.ts +3 -3
- package/dist/react/time-slot-list/TimeSlot.js +3 -3
- package/dist/react/time-slot-list/TimeSlotList.d.ts +5 -3
- package/dist/services/booking/book-action/bookAction.d.ts +21 -0
- package/dist/services/booking/book-action/bookAction.js +59 -0
- package/dist/services/booking/book-action/buildBookingRequest.d.ts +22 -0
- package/dist/services/booking/book-action/buildBookingRequest.js +81 -0
- package/dist/services/booking/book-action/buildCheckoutRequest.d.ts +26 -0
- package/dist/services/booking/book-action/buildCheckoutRequest.js +62 -0
- package/dist/services/booking/book-action/canBook.d.ts +23 -0
- package/dist/services/booking/book-action/canBook.js +19 -0
- package/dist/services/booking/book-action/index.d.ts +8 -0
- package/dist/services/booking/book-action/index.js +7 -0
- package/dist/services/booking/book-action/types.d.ts +50 -0
- package/dist/services/booking/book-action/types.js +4 -0
- package/dist/services/booking/booking.d.ts +2 -0
- package/dist/services/booking/booking.js +16 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/time-slot-list/time-slot-list.def.d.ts +10 -2
- package/dist/services/time-slot-list/time-slot-list.js +9 -29
- package/package.json +4 -3
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
* Defines interfaces and service contract for time slot management
|
|
4
4
|
*/
|
|
5
5
|
import type { Signal } from '@wix/services-definitions/core-services/signals';
|
|
6
|
-
import type { TimeSlot as ApiTimeSlot,
|
|
6
|
+
import type { TimeSlot as ApiTimeSlot, LocationTypeWithLiterals } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
7
7
|
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
8
|
+
/**
|
|
9
|
+
* Location filter input for availability calls.
|
|
10
|
+
* Require a stable `id` + `locationType` pair (no optional fields).
|
|
11
|
+
*/
|
|
12
|
+
export interface Location {
|
|
13
|
+
id: string;
|
|
14
|
+
locationType: LocationTypeWithLiterals;
|
|
15
|
+
}
|
|
8
16
|
export declare enum ViewMode {
|
|
9
17
|
CLASSIC = "classic",
|
|
10
18
|
AGENDA = "agenda"
|
|
@@ -48,7 +56,7 @@ export interface SlotSelection {
|
|
|
48
56
|
*/
|
|
49
57
|
export interface TimeSlotsFilters {
|
|
50
58
|
/** Filter by specific locations */
|
|
51
|
-
|
|
59
|
+
location?: Location;
|
|
52
60
|
/** Filter by specific staff member IDs */
|
|
53
61
|
staffMemberIds?: string[];
|
|
54
62
|
/** Filter by bookable status. If undefined, returns both bookable and non-bookable */
|
|
@@ -65,31 +65,13 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
65
65
|
* Helper: Get services from config or BookingService
|
|
66
66
|
*/
|
|
67
67
|
const getServices = () => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const getFilters = () => {
|
|
76
|
-
// Get BookingService selections (these take priority)
|
|
77
|
-
const location = bookingService.location.get();
|
|
78
|
-
const serviceSelections = bookingService.serviceSelections.get();
|
|
79
|
-
const selectedStaffMemberIds = serviceSelections
|
|
80
|
-
.map((serviceSelection) => serviceSelection.resources.map((r) => r._id))
|
|
81
|
-
.flat();
|
|
82
|
-
const selectedLocation = location ? { _id: location.id } : null;
|
|
83
|
-
// Merge config filters with BookingService selections (selections win)
|
|
84
|
-
return {
|
|
85
|
-
...config.filters,
|
|
86
|
-
locations: selectedLocation
|
|
87
|
-
? [selectedLocation]
|
|
88
|
-
: config.filters?.locations,
|
|
89
|
-
staffMemberIds: selectedStaffMemberIds.length
|
|
90
|
-
? selectedStaffMemberIds
|
|
91
|
-
: config.filters?.staffMemberIds,
|
|
92
|
-
};
|
|
68
|
+
const selectedServices = bookingService.serviceSelections
|
|
69
|
+
.get()
|
|
70
|
+
.map((s) => s.service);
|
|
71
|
+
if (selectedServices.length) {
|
|
72
|
+
return selectedServices;
|
|
73
|
+
}
|
|
74
|
+
return config.services ?? [];
|
|
93
75
|
};
|
|
94
76
|
/**
|
|
95
77
|
* Fetch time slots from API
|
|
@@ -97,7 +79,6 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
97
79
|
*/
|
|
98
80
|
const fetchTimeSlots = async () => {
|
|
99
81
|
const selectedServices = getServices();
|
|
100
|
-
const filters = getFilters();
|
|
101
82
|
isLoadingSignal.set(true);
|
|
102
83
|
errorSignal.set(null);
|
|
103
84
|
cursorSignal.set(undefined);
|
|
@@ -110,7 +91,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
110
91
|
timeZone: bookingService.timezone.get(),
|
|
111
92
|
viewMode: viewModeSignal.get(),
|
|
112
93
|
cursor: cursorSignal.get(),
|
|
113
|
-
...filters,
|
|
94
|
+
...config.filters,
|
|
114
95
|
});
|
|
115
96
|
timeSlotsSignal.set(response.timeSlots);
|
|
116
97
|
const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
|
|
@@ -303,7 +284,6 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
303
284
|
if (!cursor || isLoadingSignal.get())
|
|
304
285
|
return;
|
|
305
286
|
const selectedServices = getServices();
|
|
306
|
-
const filters = getFilters();
|
|
307
287
|
isLoadingSignal.set(true);
|
|
308
288
|
try {
|
|
309
289
|
const response = await fetchAvailability({
|
|
@@ -313,7 +293,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
313
293
|
timeZone: bookingService.timezone.get(),
|
|
314
294
|
cursor,
|
|
315
295
|
viewMode: viewModeSignal.get(),
|
|
316
|
-
...filters,
|
|
296
|
+
...config.filters,
|
|
317
297
|
});
|
|
318
298
|
// API returns grouped slots, merge with existing
|
|
319
299
|
const existing = timeSlotsSignal.get();
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared mock data for booking service tests
|
|
3
|
+
*/
|
|
4
|
+
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
5
|
+
import type { ServiceServiceConfig } from '../services/service/service.js';
|
|
6
|
+
import type { ServiceListServiceConfig } from '../services/service-list/service-list.js';
|
|
7
|
+
import type { PagingMetadata } from '../api/query-services/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Booking app ID constant
|
|
10
|
+
*/
|
|
11
|
+
export declare const BOOKING_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
12
|
+
/**
|
|
13
|
+
* Base mock service with all fields populated (APPOINTMENT type, FIXED pricing)
|
|
14
|
+
*/
|
|
15
|
+
export declare const mockService: Service;
|
|
16
|
+
/**
|
|
17
|
+
* Mock service with VARIED pricing
|
|
18
|
+
*/
|
|
19
|
+
export declare const mockServiceVariedPrice: Service;
|
|
20
|
+
/**
|
|
21
|
+
* Mock service with CUSTOM pricing
|
|
22
|
+
*/
|
|
23
|
+
export declare const mockServiceCustomPrice: Service;
|
|
24
|
+
/**
|
|
25
|
+
* Mock service with NO_FEE pricing (free)
|
|
26
|
+
*/
|
|
27
|
+
export declare const mockServiceNoFee: Service;
|
|
28
|
+
/**
|
|
29
|
+
* Mock CLASS type service
|
|
30
|
+
*/
|
|
31
|
+
export declare const mockServiceClass: Service;
|
|
32
|
+
/**
|
|
33
|
+
* Mock COURSE type service
|
|
34
|
+
*/
|
|
35
|
+
export declare const mockServiceCourse: Service;
|
|
36
|
+
/**
|
|
37
|
+
* Mock service without optional fields (minimal)
|
|
38
|
+
*/
|
|
39
|
+
export declare const mockServiceMinimal: Service;
|
|
40
|
+
/**
|
|
41
|
+
* Array of mock services for list tests
|
|
42
|
+
*/
|
|
43
|
+
export declare const mockServices: Service[];
|
|
44
|
+
/**
|
|
45
|
+
* Default paging metadata
|
|
46
|
+
*/
|
|
47
|
+
export declare const mockPagingMetadata: PagingMetadata;
|
|
48
|
+
/**
|
|
49
|
+
* Paging metadata with more items available
|
|
50
|
+
*/
|
|
51
|
+
export declare const mockPagingMetadataWithMore: PagingMetadata;
|
|
52
|
+
/**
|
|
53
|
+
* Empty paging metadata
|
|
54
|
+
*/
|
|
55
|
+
export declare const mockPagingMetadataEmpty: PagingMetadata;
|
|
56
|
+
/**
|
|
57
|
+
* Service list config with services
|
|
58
|
+
*/
|
|
59
|
+
export declare const mockServiceListConfig: ServiceListServiceConfig;
|
|
60
|
+
/**
|
|
61
|
+
* Service list config with hasNext for pagination testing
|
|
62
|
+
*/
|
|
63
|
+
export declare const mockServiceListConfigWithMore: ServiceListServiceConfig;
|
|
64
|
+
/**
|
|
65
|
+
* Empty service list config
|
|
66
|
+
*/
|
|
67
|
+
export declare const mockEmptyServiceListConfig: ServiceListServiceConfig;
|
|
68
|
+
/**
|
|
69
|
+
* Service config for single service
|
|
70
|
+
*/
|
|
71
|
+
export declare const mockServiceConfig: ServiceServiceConfig;
|
|
72
|
+
/**
|
|
73
|
+
* Helper to create a large list of services for pagination tests
|
|
74
|
+
*/
|
|
75
|
+
export declare function createLargeServiceList(count: number): Service[];
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared mock data for booking service tests
|
|
3
|
+
*/
|
|
4
|
+
import { RateType, ServiceType } from '@wix/auto_sdk_bookings_services';
|
|
5
|
+
/**
|
|
6
|
+
* Booking app ID constant
|
|
7
|
+
*/
|
|
8
|
+
export const BOOKING_APP_ID = '13d21c63-b5ec-5912-8397-c3a5ddb27a97';
|
|
9
|
+
/**
|
|
10
|
+
* Base mock service with all fields populated (APPOINTMENT type, FIXED pricing)
|
|
11
|
+
*/
|
|
12
|
+
export const mockService = {
|
|
13
|
+
_id: 'test-service-id',
|
|
14
|
+
name: 'Test Service Name',
|
|
15
|
+
description: 'Test service description',
|
|
16
|
+
tagLine: 'Test tagline',
|
|
17
|
+
type: ServiceType.APPOINTMENT,
|
|
18
|
+
category: {
|
|
19
|
+
_id: 'category-1',
|
|
20
|
+
name: 'Test Category',
|
|
21
|
+
},
|
|
22
|
+
payment: {
|
|
23
|
+
rateType: RateType.FIXED,
|
|
24
|
+
fixed: {
|
|
25
|
+
price: {
|
|
26
|
+
value: '50.00',
|
|
27
|
+
currency: 'USD',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
schedule: {
|
|
32
|
+
availabilityConstraints: {
|
|
33
|
+
sessionDurations: [60],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
locations: [
|
|
37
|
+
{
|
|
38
|
+
_id: 'location-1',
|
|
39
|
+
type: 'BUSINESS',
|
|
40
|
+
business: {
|
|
41
|
+
_id: 'business-loc-1',
|
|
42
|
+
name: 'Main Office',
|
|
43
|
+
address: {
|
|
44
|
+
formatted: '123 Main St, City, ST 12345',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
media: {
|
|
50
|
+
mainMedia: {
|
|
51
|
+
image: 'wix:image://v1/test-image.jpg',
|
|
52
|
+
},
|
|
53
|
+
coverMedia: {
|
|
54
|
+
image: 'wix:image://v1/test-cover.jpg',
|
|
55
|
+
},
|
|
56
|
+
items: [
|
|
57
|
+
{ image: 'wix:image://v1/image1.jpg' },
|
|
58
|
+
{ image: 'wix:image://v1/image2.jpg' },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
bookingPolicy: {
|
|
62
|
+
cancellationPolicy: {
|
|
63
|
+
enabled: true,
|
|
64
|
+
limitLatestCancellation: true,
|
|
65
|
+
latestCancellationInMinutes: 60,
|
|
66
|
+
},
|
|
67
|
+
limitLateBookingPolicy: {
|
|
68
|
+
enabled: true,
|
|
69
|
+
latestBookingInMinutes: 30,
|
|
70
|
+
},
|
|
71
|
+
limitEarlyBookingPolicy: {
|
|
72
|
+
enabled: true,
|
|
73
|
+
earliestBookingInMinutes: 10080, // 7 days
|
|
74
|
+
},
|
|
75
|
+
reschedulePolicy: {
|
|
76
|
+
enabled: true,
|
|
77
|
+
limitLatestReschedule: true,
|
|
78
|
+
latestRescheduleInMinutes: 120,
|
|
79
|
+
},
|
|
80
|
+
waitlistPolicy: {
|
|
81
|
+
enabled: true,
|
|
82
|
+
capacity: 10,
|
|
83
|
+
},
|
|
84
|
+
bookAfterStartPolicy: {
|
|
85
|
+
enabled: true,
|
|
86
|
+
},
|
|
87
|
+
participantsPolicy: {
|
|
88
|
+
maxParticipantsPerBooking: 5,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
defaultCapacity: 20,
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Mock service with VARIED pricing
|
|
95
|
+
*/
|
|
96
|
+
export const mockServiceVariedPrice = {
|
|
97
|
+
...mockService,
|
|
98
|
+
_id: 'test-service-varied',
|
|
99
|
+
name: 'Varied Price Service',
|
|
100
|
+
payment: {
|
|
101
|
+
rateType: RateType.VARIED,
|
|
102
|
+
varied: {
|
|
103
|
+
defaultPrice: {
|
|
104
|
+
value: '50.00',
|
|
105
|
+
currency: 'USD',
|
|
106
|
+
},
|
|
107
|
+
minPrice: {
|
|
108
|
+
value: '30.00',
|
|
109
|
+
currency: 'USD',
|
|
110
|
+
},
|
|
111
|
+
maxPrice: {
|
|
112
|
+
value: '100.00',
|
|
113
|
+
currency: 'USD',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Mock service with CUSTOM pricing
|
|
120
|
+
*/
|
|
121
|
+
export const mockServiceCustomPrice = {
|
|
122
|
+
...mockService,
|
|
123
|
+
_id: 'test-service-custom',
|
|
124
|
+
name: 'Custom Price Service',
|
|
125
|
+
payment: {
|
|
126
|
+
rateType: RateType.CUSTOM,
|
|
127
|
+
custom: {
|
|
128
|
+
description: 'Contact for pricing',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Mock service with NO_FEE pricing (free)
|
|
134
|
+
*/
|
|
135
|
+
export const mockServiceNoFee = {
|
|
136
|
+
...mockService,
|
|
137
|
+
_id: 'test-service-no-fee',
|
|
138
|
+
name: 'Free Service',
|
|
139
|
+
payment: {
|
|
140
|
+
rateType: RateType.NO_FEE,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Mock CLASS type service
|
|
145
|
+
*/
|
|
146
|
+
export const mockServiceClass = {
|
|
147
|
+
...mockService,
|
|
148
|
+
_id: 'test-service-class',
|
|
149
|
+
name: 'Class Service',
|
|
150
|
+
type: ServiceType.CLASS,
|
|
151
|
+
defaultCapacity: 15,
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Mock COURSE type service
|
|
155
|
+
*/
|
|
156
|
+
export const mockServiceCourse = {
|
|
157
|
+
...mockService,
|
|
158
|
+
_id: 'test-service-course',
|
|
159
|
+
name: 'Course Service',
|
|
160
|
+
type: ServiceType.COURSE,
|
|
161
|
+
defaultCapacity: 25,
|
|
162
|
+
schedule: {
|
|
163
|
+
...mockService.schedule,
|
|
164
|
+
firstSessionStart: new Date('2024-01-15T09:00:00Z'),
|
|
165
|
+
lastSessionEnd: new Date('2024-03-15T17:00:00Z'),
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Mock service without optional fields (minimal)
|
|
170
|
+
*/
|
|
171
|
+
export const mockServiceMinimal = {
|
|
172
|
+
_id: 'test-service-minimal',
|
|
173
|
+
name: 'Minimal Service',
|
|
174
|
+
description: '',
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Array of mock services for list tests
|
|
178
|
+
*/
|
|
179
|
+
export const mockServices = [
|
|
180
|
+
{
|
|
181
|
+
_id: 'service-1',
|
|
182
|
+
name: 'Service One',
|
|
183
|
+
description: 'Description for service one',
|
|
184
|
+
type: ServiceType.APPOINTMENT,
|
|
185
|
+
payment: {
|
|
186
|
+
rateType: RateType.FIXED,
|
|
187
|
+
fixed: {
|
|
188
|
+
price: {
|
|
189
|
+
value: '50.00',
|
|
190
|
+
currency: 'USD',
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
_id: 'service-2',
|
|
197
|
+
name: 'Service Two',
|
|
198
|
+
description: 'Description for service two',
|
|
199
|
+
type: ServiceType.CLASS,
|
|
200
|
+
payment: {
|
|
201
|
+
rateType: RateType.FIXED,
|
|
202
|
+
fixed: {
|
|
203
|
+
price: {
|
|
204
|
+
value: '75.00',
|
|
205
|
+
currency: 'USD',
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
defaultCapacity: 15,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
_id: 'service-3',
|
|
213
|
+
name: 'Service Three',
|
|
214
|
+
description: 'Description for service three',
|
|
215
|
+
type: ServiceType.COURSE,
|
|
216
|
+
payment: {
|
|
217
|
+
rateType: RateType.CUSTOM,
|
|
218
|
+
custom: {
|
|
219
|
+
description: 'Contact for pricing',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
defaultCapacity: 25,
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
/**
|
|
226
|
+
* Default paging metadata
|
|
227
|
+
*/
|
|
228
|
+
export const mockPagingMetadata = {
|
|
229
|
+
limit: 10,
|
|
230
|
+
offset: 0,
|
|
231
|
+
count: 3,
|
|
232
|
+
hasNext: false,
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* Paging metadata with more items available
|
|
236
|
+
*/
|
|
237
|
+
export const mockPagingMetadataWithMore = {
|
|
238
|
+
limit: 10,
|
|
239
|
+
offset: 0,
|
|
240
|
+
count: 10,
|
|
241
|
+
hasNext: true,
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* Empty paging metadata
|
|
245
|
+
*/
|
|
246
|
+
export const mockPagingMetadataEmpty = {
|
|
247
|
+
limit: 10,
|
|
248
|
+
offset: 0,
|
|
249
|
+
count: 0,
|
|
250
|
+
hasNext: false,
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Service list config with services
|
|
254
|
+
*/
|
|
255
|
+
export const mockServiceListConfig = {
|
|
256
|
+
services: mockServices,
|
|
257
|
+
options: {
|
|
258
|
+
appId: BOOKING_APP_ID,
|
|
259
|
+
pagingMetadata: mockPagingMetadata,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* Service list config with hasNext for pagination testing
|
|
264
|
+
*/
|
|
265
|
+
export const mockServiceListConfigWithMore = {
|
|
266
|
+
services: mockServices,
|
|
267
|
+
options: {
|
|
268
|
+
appId: BOOKING_APP_ID,
|
|
269
|
+
pagingMetadata: mockPagingMetadataWithMore,
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* Empty service list config
|
|
274
|
+
*/
|
|
275
|
+
export const mockEmptyServiceListConfig = {
|
|
276
|
+
services: [],
|
|
277
|
+
options: {
|
|
278
|
+
appId: BOOKING_APP_ID,
|
|
279
|
+
pagingMetadata: mockPagingMetadataEmpty,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Service config for single service
|
|
284
|
+
*/
|
|
285
|
+
export const mockServiceConfig = {
|
|
286
|
+
service: mockService,
|
|
287
|
+
appId: BOOKING_APP_ID,
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* Helper to create a large list of services for pagination tests
|
|
291
|
+
*/
|
|
292
|
+
export function createLargeServiceList(count) {
|
|
293
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
294
|
+
_id: `service-${i}`,
|
|
295
|
+
name: `Service ${i}`,
|
|
296
|
+
description: `Description for service ${i}`,
|
|
297
|
+
type: ServiceType.APPOINTMENT,
|
|
298
|
+
payment: {
|
|
299
|
+
rateType: RateType.FIXED,
|
|
300
|
+
fixed: {
|
|
301
|
+
price: {
|
|
302
|
+
value: '50.00',
|
|
303
|
+
currency: 'USD',
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* Time Slot List API
|
|
3
3
|
* Fetches available time slots for booking services from Wix Bookings
|
|
4
4
|
*/
|
|
5
|
-
import { type
|
|
5
|
+
import { type CursorPaging, type CursorPagingMetadata } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
6
6
|
import { type Service } from '@wix/auto_sdk_bookings_services';
|
|
7
|
-
import { ViewMode, type TimeSlot } from '../../services/time-slot-list/time-slot-list.def.js';
|
|
7
|
+
import { ViewMode, type TimeSlot, type Location } from '../../services/time-slot-list/time-slot-list.def.js';
|
|
8
8
|
export interface FetchAvailabilityOptions {
|
|
9
9
|
services: Service[];
|
|
10
10
|
fromLocalDate?: string | null;
|
|
11
11
|
toLocalDate?: string | null;
|
|
12
12
|
timeZone?: string | null;
|
|
13
|
-
|
|
13
|
+
location?: Location;
|
|
14
14
|
/** Filter by specific staff member IDs (passed as resourceIds to API) */
|
|
15
15
|
staffMemberIds?: string[];
|
|
16
16
|
cursorPaging?: CursorPaging;
|
|
@@ -52,12 +52,12 @@ export interface FetchAvailabilityResponse {
|
|
|
52
52
|
* // response.timeSlots are grouped by time
|
|
53
53
|
* ```
|
|
54
54
|
*/
|
|
55
|
-
export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone,
|
|
55
|
+
export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone, location, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, timeSlotsPerDay, }: {
|
|
56
56
|
services: Service[];
|
|
57
57
|
fromLocalDate: Date;
|
|
58
58
|
toLocalDate: Date;
|
|
59
59
|
timeZone?: string;
|
|
60
|
-
|
|
60
|
+
location?: Location;
|
|
61
61
|
staffMemberIds?: string[];
|
|
62
62
|
cursor?: string;
|
|
63
63
|
viewMode?: ViewMode;
|
|
@@ -115,7 +115,7 @@ function detectFlow(bookingServices, viewMode) {
|
|
|
115
115
|
* // response.timeSlots are grouped by time
|
|
116
116
|
* ```
|
|
117
117
|
*/
|
|
118
|
-
export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone,
|
|
118
|
+
export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone, location, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, timeSlotsPerDay, }) {
|
|
119
119
|
const flow = detectFlow(selectedServices, viewMode);
|
|
120
120
|
const serviceIds = selectedServices.map((s) => s._id);
|
|
121
121
|
// Convert Date objects to local ISO strings
|
|
@@ -138,6 +138,12 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
|
|
|
138
138
|
},
|
|
139
139
|
]
|
|
140
140
|
: undefined;
|
|
141
|
+
const includeResourceTypeIds = isAnyStaffMemberSelected || isFetchAvailabileDays
|
|
142
|
+
? undefined
|
|
143
|
+
: [STAFF_RESOURCE_TYPE_ID];
|
|
144
|
+
const locations = location
|
|
145
|
+
? [{ _id: location.id, locationType: location.locationType }]
|
|
146
|
+
: undefined;
|
|
141
147
|
const response = await listAvailabilityTimeSlots({
|
|
142
148
|
serviceId: serviceIds[0],
|
|
143
149
|
fromLocalDate: fromLocalDateStr,
|
|
@@ -148,9 +154,7 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
|
|
|
148
154
|
resourceTypes,
|
|
149
155
|
bookable,
|
|
150
156
|
bookingPolicyViolations,
|
|
151
|
-
includeResourceTypeIds
|
|
152
|
-
? undefined
|
|
153
|
-
: [STAFF_RESOURCE_TYPE_ID],
|
|
157
|
+
includeResourceTypeIds,
|
|
154
158
|
timeSlotsPerDay,
|
|
155
159
|
});
|
|
156
160
|
return {
|
|
@@ -164,7 +168,7 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
|
|
|
164
168
|
}
|
|
165
169
|
if (flow === FlowType.CLASS) {
|
|
166
170
|
// Build eventFilter for location and staff filtering
|
|
167
|
-
const locationIds =
|
|
171
|
+
const locationIds = location ? [location.id] : undefined;
|
|
168
172
|
const eventFilter = {};
|
|
169
173
|
if (locationIds?.length) {
|
|
170
174
|
eventFilter['location.id'] = locationIds;
|
|
@@ -185,7 +189,6 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
|
|
|
185
189
|
});
|
|
186
190
|
return {
|
|
187
191
|
timeSlots: groupTimeSlotsByTime(response.timeSlots || []),
|
|
188
|
-
// Note: listEventTimeSlots uses pagingMetadata instead of cursorPagingMetadata
|
|
189
192
|
cursorPagingMetadata: response.pagingMetadata,
|
|
190
193
|
};
|
|
191
194
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Book - High-level headless component for the book action button
|
|
3
|
+
* Wraps core Book with AsChildSlot pattern for customization
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import type { BookChildProps, BookResult, BookingError } from '../../services/booking/book-action/types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Props for the high-level Book component
|
|
9
|
+
*/
|
|
10
|
+
export interface BookProps {
|
|
11
|
+
asChild?: boolean;
|
|
12
|
+
children?: React.ReactNode | ((props: BookChildProps) => React.ReactNode);
|
|
13
|
+
className?: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
loadingState?: string;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
onCheckoutRequired?: (result: BookResult) => void;
|
|
18
|
+
onError?: (error: BookingError) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Book action button component with AsChildSlot pattern
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* // Simple usage
|
|
26
|
+
* <Booking.Actions.Book />
|
|
27
|
+
*
|
|
28
|
+
* // With callbacks
|
|
29
|
+
* <Booking.Actions.Book
|
|
30
|
+
* onCheckoutRequired={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
31
|
+
* onError={(error) => console.error(error.message)}
|
|
32
|
+
* />
|
|
33
|
+
*
|
|
34
|
+
* // With render function
|
|
35
|
+
* <Booking.Actions.Book onCheckoutRequired={handleCheckout}>
|
|
36
|
+
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
37
|
+
* <button onClick={onClick} disabled={disabled}>
|
|
38
|
+
* {isLoading ? <Spinner /> : 'Book Now'}
|
|
39
|
+
* </button>
|
|
40
|
+
* )}
|
|
41
|
+
* </Booking.Actions.Book>
|
|
42
|
+
*
|
|
43
|
+
* // With asChild
|
|
44
|
+
* <Booking.Actions.Book asChild onCheckoutRequired={handleCheckout}>
|
|
45
|
+
* <CustomBookButton />
|
|
46
|
+
* </Booking.Actions.Book>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare const Book: React.ForwardRefExoticComponent<BookProps & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Book - High-level headless component for the book action button
|
|
4
|
+
* Wraps core Book with AsChildSlot pattern for customization
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { AsChildSlot } from '@wix/headless-utils/react';
|
|
8
|
+
import * as CoreBook from '../core/booking/Book.js';
|
|
9
|
+
var TestIds;
|
|
10
|
+
(function (TestIds) {
|
|
11
|
+
TestIds["bookingActionBook"] = "booking-action-book";
|
|
12
|
+
})(TestIds || (TestIds = {}));
|
|
13
|
+
/**
|
|
14
|
+
* Book action button component with AsChildSlot pattern
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* // Simple usage
|
|
19
|
+
* <Booking.Actions.Book />
|
|
20
|
+
*
|
|
21
|
+
* // With callbacks
|
|
22
|
+
* <Booking.Actions.Book
|
|
23
|
+
* onCheckoutRequired={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
24
|
+
* onError={(error) => console.error(error.message)}
|
|
25
|
+
* />
|
|
26
|
+
*
|
|
27
|
+
* // With render function
|
|
28
|
+
* <Booking.Actions.Book onCheckoutRequired={handleCheckout}>
|
|
29
|
+
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
30
|
+
* <button onClick={onClick} disabled={disabled}>
|
|
31
|
+
* {isLoading ? <Spinner /> : 'Book Now'}
|
|
32
|
+
* </button>
|
|
33
|
+
* )}
|
|
34
|
+
* </Booking.Actions.Book>
|
|
35
|
+
*
|
|
36
|
+
* // With asChild
|
|
37
|
+
* <Booking.Actions.Book asChild onCheckoutRequired={handleCheckout}>
|
|
38
|
+
* <CustomBookButton />
|
|
39
|
+
* </Booking.Actions.Book>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const Book = React.forwardRef((props, ref) => {
|
|
43
|
+
const { asChild, children, className, label = 'Book Now', loadingState = 'Booking...', disabled, onCheckoutRequired, onError, } = props;
|
|
44
|
+
return (_jsx(CoreBook.Book, { onCheckoutRequired: onCheckoutRequired, onError: onError, disabled: disabled, children: (childProps) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.bookingActionBook, "data-in-progress": childProps.isLoading, customElement: children, customElementProps: childProps, children: _jsx("button", { onClick: childProps.onClick, disabled: childProps.disabled, children: childProps.isLoading ? loadingState : label }) })) }));
|
|
45
|
+
});
|
|
46
|
+
Book.displayName = 'Booking.Actions.Book';
|
|
@@ -34,3 +34,21 @@ export declare const Root: {
|
|
|
34
34
|
(props: BookingRootProps): React.ReactNode;
|
|
35
35
|
displayName: string;
|
|
36
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Actions namespace containing action components for booking
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* // Simple usage
|
|
43
|
+
* <Booking.Actions.Book />
|
|
44
|
+
*
|
|
45
|
+
* // With callbacks
|
|
46
|
+
* <Booking.Actions.Book
|
|
47
|
+
* onCheckoutRequired={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
48
|
+
* onError={(error) => toast.error(error)}
|
|
49
|
+
* />
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare const Actions: {
|
|
53
|
+
Book: React.ForwardRefExoticComponent<import("./Book.js").BookProps & React.RefAttributes<HTMLButtonElement>>;
|
|
54
|
+
};
|