@wix/headless-bookings 0.0.25 → 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/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/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
|
@@ -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
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as CoreBooking from '../core/booking/Booking.js';
|
|
3
|
+
import { Book } from './Book.js';
|
|
3
4
|
/**
|
|
4
5
|
* Root component that provides booking context to the entire app.
|
|
5
6
|
*
|
|
@@ -23,3 +24,21 @@ export const Root = (props) => {
|
|
|
23
24
|
return (_jsx(CoreBooking.Root, { bookingServiceConfig: bookingServiceConfig, children: children }));
|
|
24
25
|
};
|
|
25
26
|
Root.displayName = 'Booking.Root';
|
|
27
|
+
/**
|
|
28
|
+
* Actions namespace containing action components for booking
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* // Simple usage
|
|
33
|
+
* <Booking.Actions.Book />
|
|
34
|
+
*
|
|
35
|
+
* // With callbacks
|
|
36
|
+
* <Booking.Actions.Book
|
|
37
|
+
* onCheckoutRequired={({ checkoutId }) => router.push(`/checkout?id=${checkoutId}`)}
|
|
38
|
+
* onError={(error) => toast.error(error)}
|
|
39
|
+
* />
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const Actions = {
|
|
43
|
+
Book,
|
|
44
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Book - Provides book action data via render props
|
|
3
|
+
* NO AsChildSlot, NO asChild - that belongs in high-level component
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import type { BookResult, BookingError } from '../../../services/booking/book-action/types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Data exposed by Book component via render props
|
|
9
|
+
*/
|
|
10
|
+
export interface BookData {
|
|
11
|
+
isLoading: boolean;
|
|
12
|
+
error: BookingError | null;
|
|
13
|
+
canBook: boolean;
|
|
14
|
+
onClick: () => Promise<void>;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Props for core Book component
|
|
19
|
+
*/
|
|
20
|
+
export interface BookProps {
|
|
21
|
+
children: (data: BookData) => React.ReactNode;
|
|
22
|
+
onCheckoutRequired?: (result: BookResult) => void;
|
|
23
|
+
onError?: (error: BookingError) => void;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Core Book component - provides book action data via render props.
|
|
28
|
+
* Used internally by higher-level Booking.Actions.Book component.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* <Book onCheckoutRequired={handleCheckout}>
|
|
33
|
+
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
34
|
+
* <button onClick={onClick} disabled={disabled}>
|
|
35
|
+
* {isLoading ? 'Booking...' : 'Book Now'}
|
|
36
|
+
* </button>
|
|
37
|
+
* )}
|
|
38
|
+
* </Book>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function Book(props: BookProps): React.ReactNode;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Book - Provides book action data via render props
|
|
3
|
+
* NO AsChildSlot, NO asChild - that belongs in high-level component
|
|
4
|
+
*/
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
import { useService } from '@wix/services-manager-react';
|
|
7
|
+
import { BookingServiceDefinition } from '../../../services/booking/booking.js';
|
|
8
|
+
import { canBook } from '../../../services/booking/book-action/canBook.js';
|
|
9
|
+
/**
|
|
10
|
+
* Core Book component - provides book action data via render props.
|
|
11
|
+
* Used internally by higher-level Booking.Actions.Book component.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <Book onCheckoutRequired={handleCheckout}>
|
|
16
|
+
* {({ isLoading, error, canBook, onClick, disabled }) => (
|
|
17
|
+
* <button onClick={onClick} disabled={disabled}>
|
|
18
|
+
* {isLoading ? 'Booking...' : 'Book Now'}
|
|
19
|
+
* </button>
|
|
20
|
+
* )}
|
|
21
|
+
* </Book>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function Book(props) {
|
|
25
|
+
const { children, onCheckoutRequired, onError, disabled } = props;
|
|
26
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
27
|
+
const [error, setError] = useState(null);
|
|
28
|
+
const bookingService = useService(BookingServiceDefinition);
|
|
29
|
+
const serviceSelections = bookingService.serviceSelections.get();
|
|
30
|
+
const formSubmission = bookingService.formSubmission.get();
|
|
31
|
+
const canBookValue = canBook({ serviceSelections, formSubmission });
|
|
32
|
+
const handleClick = async () => {
|
|
33
|
+
setIsLoading(true);
|
|
34
|
+
setError(null);
|
|
35
|
+
try {
|
|
36
|
+
const result = await bookingService.actions.book();
|
|
37
|
+
onCheckoutRequired?.(result);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
const bookingError = {
|
|
41
|
+
message: e instanceof Error ? e.message : 'Booking failed',
|
|
42
|
+
errorObject: e,
|
|
43
|
+
};
|
|
44
|
+
setError(bookingError);
|
|
45
|
+
onError?.(bookingError);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
setIsLoading(false);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const isDisabled = isLoading || disabled || !canBookValue;
|
|
52
|
+
return children({
|
|
53
|
+
isLoading,
|
|
54
|
+
error,
|
|
55
|
+
canBook: canBookValue,
|
|
56
|
+
onClick: handleClick,
|
|
57
|
+
disabled: isDisabled,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -89,11 +89,11 @@ export declare function StaffMemberProvider(props: StaffMemberProviderProps): Re
|
|
|
89
89
|
export interface InfoProps {
|
|
90
90
|
children: (data: {
|
|
91
91
|
/** Parsed localStartDate as Date (constructed from the raw localStartDate string) */
|
|
92
|
-
startDate: Date
|
|
92
|
+
startDate: Date;
|
|
93
93
|
/** Parsed localEndDate as Date (constructed from the raw localEndDate string) */
|
|
94
|
-
endDate: Date
|
|
95
|
-
/** Calculated duration in minutes
|
|
96
|
-
durationInMinutes: number
|
|
94
|
+
endDate: Date;
|
|
95
|
+
/** Calculated duration in minutes */
|
|
96
|
+
durationInMinutes: number;
|
|
97
97
|
/** Whether the timeSlot is bookable */
|
|
98
98
|
bookable: boolean;
|
|
99
99
|
/** Whether this timeSlot is currently selected */
|
|
@@ -116,18 +116,11 @@ export function StaffMemberProvider(props) {
|
|
|
116
116
|
*/
|
|
117
117
|
export function Info(props) {
|
|
118
118
|
const { timeSlot, isSelected } = useTimeSlotContext();
|
|
119
|
-
const startDate = timeSlot.localStartDate
|
|
120
|
-
|
|
121
|
-
: undefined;
|
|
122
|
-
const endDate = timeSlot.localEndDate
|
|
123
|
-
? new Date(timeSlot.localEndDate)
|
|
124
|
-
: undefined;
|
|
119
|
+
const startDate = new Date(timeSlot.localStartDate);
|
|
120
|
+
const endDate = new Date(timeSlot.localEndDate);
|
|
125
121
|
const bookable = timeSlot.bookable ?? false;
|
|
126
122
|
// Calculate duration from start and end dates
|
|
127
|
-
|
|
128
|
-
if (startDate && endDate) {
|
|
129
|
-
durationInMinutes = Math.round((endDate.getTime() - startDate.getTime()) / (1000 * 60));
|
|
130
|
-
}
|
|
123
|
+
const durationInMinutes = Math.round((endDate.getTime() - startDate.getTime()) / (1000 * 60));
|
|
131
124
|
return props.children({
|
|
132
125
|
startDate,
|
|
133
126
|
endDate,
|
|
@@ -16,7 +16,7 @@ export type { LocationData, StaffMemberData, } from '../core/time-slot-list/Time
|
|
|
16
16
|
export interface StartDateProps {
|
|
17
17
|
asChild?: boolean;
|
|
18
18
|
children?: AsChildChildren<{
|
|
19
|
-
startDate: Date
|
|
19
|
+
startDate: Date;
|
|
20
20
|
}>;
|
|
21
21
|
className?: string;
|
|
22
22
|
}
|
|
@@ -45,7 +45,7 @@ export declare const StartDate: React.ForwardRefExoticComponent<StartDateProps &
|
|
|
45
45
|
export interface EndDateProps {
|
|
46
46
|
asChild?: boolean;
|
|
47
47
|
children?: AsChildChildren<{
|
|
48
|
-
endDate: Date
|
|
48
|
+
endDate: Date;
|
|
49
49
|
}>;
|
|
50
50
|
className?: string;
|
|
51
51
|
}
|
|
@@ -74,7 +74,7 @@ export declare const EndDate: React.ForwardRefExoticComponent<EndDateProps & Rea
|
|
|
74
74
|
export interface DurationProps {
|
|
75
75
|
asChild?: boolean;
|
|
76
76
|
children?: AsChildChildren<{
|
|
77
|
-
durationInMinutes: number
|
|
77
|
+
durationInMinutes: number;
|
|
78
78
|
}>;
|
|
79
79
|
className?: string;
|
|
80
80
|
}
|
|
@@ -53,7 +53,7 @@ var TestIds;
|
|
|
53
53
|
*/
|
|
54
54
|
export const StartDate = React.forwardRef((props, ref) => {
|
|
55
55
|
const { asChild, children, className } = props;
|
|
56
|
-
return (_jsx(CoreTimeSlot.Info, { children: ({ startDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotStartDate, customElement: children, customElementProps: { startDate }, children: _jsx("span", { children: startDate
|
|
56
|
+
return (_jsx(CoreTimeSlot.Info, { children: ({ startDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotStartDate, customElement: children, customElementProps: { startDate }, children: _jsx("span", { children: startDate.toString() }) })) }));
|
|
57
57
|
});
|
|
58
58
|
StartDate.displayName = 'TimeSlot.StartDate';
|
|
59
59
|
/**
|
|
@@ -76,7 +76,7 @@ StartDate.displayName = 'TimeSlot.StartDate';
|
|
|
76
76
|
*/
|
|
77
77
|
export const EndDate = React.forwardRef((props, ref) => {
|
|
78
78
|
const { asChild, children, className } = props;
|
|
79
|
-
return (_jsx(CoreTimeSlot.Info, { children: ({ endDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotEndDate, customElement: children, customElementProps: { endDate }, children: _jsx("span", { children: endDate
|
|
79
|
+
return (_jsx(CoreTimeSlot.Info, { children: ({ endDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotEndDate, customElement: children, customElementProps: { endDate }, children: _jsx("span", { children: endDate.toString() }) })) }));
|
|
80
80
|
});
|
|
81
81
|
EndDate.displayName = 'TimeSlot.EndDate';
|
|
82
82
|
/**
|
|
@@ -99,7 +99,7 @@ EndDate.displayName = 'TimeSlot.EndDate';
|
|
|
99
99
|
*/
|
|
100
100
|
export const Duration = React.forwardRef((props, ref) => {
|
|
101
101
|
const { asChild, children, className } = props;
|
|
102
|
-
return (_jsx(CoreTimeSlot.Info, { children: ({ durationInMinutes }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotDuration, customElement: children, customElementProps: { durationInMinutes }, children:
|
|
102
|
+
return (_jsx(CoreTimeSlot.Info, { children: ({ durationInMinutes }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotDuration, customElement: children, customElementProps: { durationInMinutes }, children: _jsx("span", { children: durationInMinutes }) })) }));
|
|
103
103
|
});
|
|
104
104
|
Duration.displayName = 'TimeSlot.Duration';
|
|
105
105
|
/**
|
|
@@ -85,6 +85,8 @@ export interface TimeSlotsProps {
|
|
|
85
85
|
emptyState?: React.ReactNode;
|
|
86
86
|
/** Content to display while time slots are loading */
|
|
87
87
|
loadingState?: React.ReactNode;
|
|
88
|
+
/** Content to display when an error occurs while loading time slots */
|
|
89
|
+
errorState?: React.ReactNode;
|
|
88
90
|
className?: string;
|
|
89
91
|
}
|
|
90
92
|
/**
|
|
@@ -114,11 +116,11 @@ export interface TimeSlotRepeaterChildProps {
|
|
|
114
116
|
/** Whether the slot can be booked */
|
|
115
117
|
bookable: boolean;
|
|
116
118
|
/** Start date/time as Date (constructed from the raw localStartDate string) */
|
|
117
|
-
startDate: Date
|
|
119
|
+
startDate: Date;
|
|
118
120
|
/** End date/time as Date (constructed from the raw localEndDate string) */
|
|
119
|
-
endDate: Date
|
|
121
|
+
endDate: Date;
|
|
120
122
|
/** Duration in minutes */
|
|
121
|
-
durationInMinutes: number
|
|
123
|
+
durationInMinutes: number;
|
|
122
124
|
/** The raw time slot data */
|
|
123
125
|
timeSlot: TimeSlotType;
|
|
124
126
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Book Action - Orchestrates the booking flow
|
|
3
|
+
*
|
|
4
|
+
* 1. Validates booking data with canBook()
|
|
5
|
+
* 2. Creates a booking via createBooking() from @wix/auto_sdk_bookings_bookings
|
|
6
|
+
* 3. Creates a checkout via createCheckout() from @wix/auto_sdk_ecom_checkout
|
|
7
|
+
* 4. Returns the checkoutId for navigation
|
|
8
|
+
*/
|
|
9
|
+
import type { BookActionParams, BookResult } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Executes the complete booking flow:
|
|
12
|
+
* 1. Validates that all required data is present
|
|
13
|
+
* 2. Creates a booking with the selected service, time slot, and form data
|
|
14
|
+
* 3. Creates a checkout session for payment (includes contact details from booking)
|
|
15
|
+
* 4. Returns the checkout ID for navigation to the checkout page
|
|
16
|
+
*
|
|
17
|
+
* @param params - The booking action parameters
|
|
18
|
+
* @returns BookResult with the checkoutId
|
|
19
|
+
* @throws Error if validation fails, or booking/checkout creation fails
|
|
20
|
+
*/
|
|
21
|
+
export declare function executeBookAction(params: BookActionParams): Promise<BookResult>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Book Action - Orchestrates the booking flow
|
|
3
|
+
*
|
|
4
|
+
* 1. Validates booking data with canBook()
|
|
5
|
+
* 2. Creates a booking via createBooking() from @wix/auto_sdk_bookings_bookings
|
|
6
|
+
* 3. Creates a checkout via createCheckout() from @wix/auto_sdk_ecom_checkout
|
|
7
|
+
* 4. Returns the checkoutId for navigation
|
|
8
|
+
*/
|
|
9
|
+
import { createBooking } from '@wix/auto_sdk_bookings_bookings';
|
|
10
|
+
import { createCheckout } from '@wix/auto_sdk_ecom_checkout';
|
|
11
|
+
import { buildBookingRequest } from './buildBookingRequest.js';
|
|
12
|
+
import { buildCheckoutRequest } from './buildCheckoutRequest.js';
|
|
13
|
+
import { canBook } from './canBook.js';
|
|
14
|
+
/**
|
|
15
|
+
* Executes the complete booking flow:
|
|
16
|
+
* 1. Validates that all required data is present
|
|
17
|
+
* 2. Creates a booking with the selected service, time slot, and form data
|
|
18
|
+
* 3. Creates a checkout session for payment (includes contact details from booking)
|
|
19
|
+
* 4. Returns the checkout ID for navigation to the checkout page
|
|
20
|
+
*
|
|
21
|
+
* @param params - The booking action parameters
|
|
22
|
+
* @returns BookResult with the checkoutId
|
|
23
|
+
* @throws Error if validation fails, or booking/checkout creation fails
|
|
24
|
+
*/
|
|
25
|
+
export async function executeBookAction(params) {
|
|
26
|
+
// Step 0: Validate
|
|
27
|
+
if (!canBook({
|
|
28
|
+
serviceSelections: params.serviceSelections,
|
|
29
|
+
formSubmission: params.formSubmission,
|
|
30
|
+
})) {
|
|
31
|
+
throw new Error('Cannot book: missing required data');
|
|
32
|
+
}
|
|
33
|
+
// Step 1: Create booking
|
|
34
|
+
const createBookingRequest = buildBookingRequest(params);
|
|
35
|
+
const bookingResponse = await createBooking(createBookingRequest.booking, {
|
|
36
|
+
participantNotification: createBookingRequest.participantNotification,
|
|
37
|
+
sendSmsReminder: createBookingRequest.sendSmsReminder,
|
|
38
|
+
formSubmission: createBookingRequest.formSubmission,
|
|
39
|
+
});
|
|
40
|
+
const bookingId = bookingResponse.booking?._id;
|
|
41
|
+
const contactDetails = bookingResponse.booking?.contactDetails;
|
|
42
|
+
if (!bookingId) {
|
|
43
|
+
throw new Error('Failed to create booking');
|
|
44
|
+
}
|
|
45
|
+
// Step 2: Create checkout
|
|
46
|
+
// businessLocationId comes from location.id for multi-location support
|
|
47
|
+
const businessLocationId = params.location?.id ?? undefined;
|
|
48
|
+
const checkoutRequest = buildCheckoutRequest({
|
|
49
|
+
bookingId,
|
|
50
|
+
contactDetails,
|
|
51
|
+
businessLocationId,
|
|
52
|
+
});
|
|
53
|
+
const checkoutResponse = await createCheckout(checkoutRequest);
|
|
54
|
+
const checkoutId = checkoutResponse._id;
|
|
55
|
+
if (!checkoutId) {
|
|
56
|
+
throw new Error('Failed to create checkout');
|
|
57
|
+
}
|
|
58
|
+
return { checkoutId };
|
|
59
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the createBooking request from BookActionParams
|
|
3
|
+
*/
|
|
4
|
+
import type { CreateBookingRequest } from '@wix/auto_sdk_bookings_bookings';
|
|
5
|
+
import type { BookActionParams } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Builds a CreateBookingRequest from the booking action parameters
|
|
8
|
+
*
|
|
9
|
+
* Maps ServiceSelection data to the Booking API's slot format:
|
|
10
|
+
* - serviceId: from serviceSelection.service._id
|
|
11
|
+
* - scheduleId: from serviceSelection.timeSlot.scheduleId
|
|
12
|
+
* - startDate: from serviceSelection.timeSlot.startDate
|
|
13
|
+
* - endDate: from serviceSelection.timeSlot.endDate
|
|
14
|
+
* - timezone: from serviceSelection.timezone
|
|
15
|
+
* - eventId: from serviceSelection.timeSlot.eventInfo.eventId (for classes)
|
|
16
|
+
* - location: from location param
|
|
17
|
+
* - resource: from serviceSelection.resources[0]
|
|
18
|
+
*
|
|
19
|
+
* @param params - The booking action parameters
|
|
20
|
+
* @returns CreateBookingRequest ready to be sent to the API
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildBookingRequest(params: BookActionParams): CreateBookingRequest;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the createBooking request from BookActionParams
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Derives the selected payment option from service payment settings.
|
|
6
|
+
* Maps service.payment.options to booking API's selectedPaymentOption.
|
|
7
|
+
*
|
|
8
|
+
* @param service - The service containing payment options
|
|
9
|
+
* @returns 'ONLINE' or 'OFFLINE' based on available payment methods
|
|
10
|
+
*/
|
|
11
|
+
function deriveSelectedPaymentOption(service) {
|
|
12
|
+
const options = service.payment?.options;
|
|
13
|
+
// Only online payment available
|
|
14
|
+
if (options?.online && !options?.inPerson) {
|
|
15
|
+
return 'ONLINE';
|
|
16
|
+
}
|
|
17
|
+
// Only offline payment available
|
|
18
|
+
if (!options?.online && options?.inPerson) {
|
|
19
|
+
return 'OFFLINE';
|
|
20
|
+
}
|
|
21
|
+
// Both available or neither specified - default to ONLINE
|
|
22
|
+
return 'ONLINE';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Builds a CreateBookingRequest from the booking action parameters
|
|
26
|
+
*
|
|
27
|
+
* Maps ServiceSelection data to the Booking API's slot format:
|
|
28
|
+
* - serviceId: from serviceSelection.service._id
|
|
29
|
+
* - scheduleId: from serviceSelection.timeSlot.scheduleId
|
|
30
|
+
* - startDate: from serviceSelection.timeSlot.startDate
|
|
31
|
+
* - endDate: from serviceSelection.timeSlot.endDate
|
|
32
|
+
* - timezone: from serviceSelection.timezone
|
|
33
|
+
* - eventId: from serviceSelection.timeSlot.eventInfo.eventId (for classes)
|
|
34
|
+
* - location: from location param
|
|
35
|
+
* - resource: from serviceSelection.resources[0]
|
|
36
|
+
*
|
|
37
|
+
* @param params - The booking action parameters
|
|
38
|
+
* @returns CreateBookingRequest ready to be sent to the API
|
|
39
|
+
*/
|
|
40
|
+
export function buildBookingRequest(params) {
|
|
41
|
+
const { serviceSelections, location, formSubmission } = params;
|
|
42
|
+
const item = serviceSelections[0]; // Single booking focus
|
|
43
|
+
if (!item) {
|
|
44
|
+
throw new Error('No service selection provided');
|
|
45
|
+
}
|
|
46
|
+
if (!item.timeSlot) {
|
|
47
|
+
throw new Error('No time slot selected for service selection');
|
|
48
|
+
}
|
|
49
|
+
// Get resource from service selection
|
|
50
|
+
const resource = item.resources?.[0];
|
|
51
|
+
return {
|
|
52
|
+
booking: {
|
|
53
|
+
// Payment option derived from service payment settings
|
|
54
|
+
selectedPaymentOption: deriveSelectedPaymentOption(item.service),
|
|
55
|
+
// Participants
|
|
56
|
+
totalParticipants: item.totalParticipants || 1,
|
|
57
|
+
// Booked entity slot
|
|
58
|
+
bookedEntity: {
|
|
59
|
+
slot: {
|
|
60
|
+
serviceId: item.service._id ?? undefined,
|
|
61
|
+
scheduleId: item.timeSlot.scheduleId ?? undefined,
|
|
62
|
+
startDate: item.timeSlot.startDate,
|
|
63
|
+
endDate: item.timeSlot.endDate,
|
|
64
|
+
timezone: params.timezone,
|
|
65
|
+
eventId: item.timeSlot.eventInfo?.eventId ?? undefined,
|
|
66
|
+
resource: resource
|
|
67
|
+
? { _id: resource._id, name: resource.name }
|
|
68
|
+
: undefined,
|
|
69
|
+
location: location
|
|
70
|
+
? { _id: location.id, name: location.name }
|
|
71
|
+
: undefined,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
participantNotification: {
|
|
76
|
+
notifyParticipants: true,
|
|
77
|
+
},
|
|
78
|
+
sendSmsReminder: true,
|
|
79
|
+
formSubmission: formSubmission ?? undefined,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the createCheckout request
|
|
3
|
+
*/
|
|
4
|
+
import type { CreateCheckoutRequest } from '@wix/auto_sdk_ecom_checkout';
|
|
5
|
+
import type { ContactDetails } from '@wix/auto_sdk_bookings_bookings';
|
|
6
|
+
/**
|
|
7
|
+
* Parameters for building checkout request
|
|
8
|
+
*/
|
|
9
|
+
export interface BuildCheckoutParams {
|
|
10
|
+
bookingId: string;
|
|
11
|
+
contactDetails?: ContactDetails;
|
|
12
|
+
businessLocationId?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Builds a checkout request from the booking ID and contact details
|
|
16
|
+
*
|
|
17
|
+
* Contact details come from the booking response and are used to populate:
|
|
18
|
+
* - billingInfo.contactDetails (firstName, lastName, phone)
|
|
19
|
+
* - billingInfo.address (from fullAddress if available)
|
|
20
|
+
* - shippingInfo.shippingDestination.contactDetails
|
|
21
|
+
* - buyerInfo (email, contactId)
|
|
22
|
+
*
|
|
23
|
+
* @param params - The checkout parameters
|
|
24
|
+
* @returns CreateCheckoutRequest ready to be sent to the API
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildCheckoutRequest(params: BuildCheckoutParams): CreateCheckoutRequest;
|