@wix/headless-bookings 0.0.63 → 0.0.65
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/react/core/time-slot-list/TimeSlotList.d.ts +4 -2
- package/cjs/dist/react/core/time-slot-list/TimeSlotList.js +8 -2
- package/cjs/dist/react/time-slot-list/TimeSlotList.d.ts +5 -3
- package/cjs/dist/react/time-slot-list/TimeSlotList.js +2 -2
- package/cjs/dist/services/booking/booking.js +24 -36
- package/cjs/dist/utils/navigation.d.ts +11 -1
- package/cjs/dist/utils/navigation.js +53 -22
- package/dist/react/core/time-slot-list/TimeSlotList.d.ts +4 -2
- package/dist/react/core/time-slot-list/TimeSlotList.js +8 -2
- package/dist/react/time-slot-list/TimeSlotList.d.ts +5 -3
- package/dist/react/time-slot-list/TimeSlotList.js +2 -2
- package/dist/services/booking/booking.js +24 -36
- package/dist/utils/navigation.d.ts +11 -1
- package/dist/utils/navigation.js +53 -22
- package/package.json +2 -2
|
@@ -12,9 +12,10 @@ import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
|
12
12
|
*/
|
|
13
13
|
export interface TimeSlotListRootRenderProps {
|
|
14
14
|
/** Function to navigate to the booking form with the selected time slot */
|
|
15
|
-
navigateToBookingForm: ({ timeSlot, service, }: {
|
|
15
|
+
navigateToBookingForm: ({ timeSlot, service, timezone, }: {
|
|
16
16
|
timeSlot?: ServiceSelectionTimeSlot;
|
|
17
17
|
service?: Service;
|
|
18
|
+
timezone?: string;
|
|
18
19
|
}) => Promise<void>;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
@@ -44,9 +45,10 @@ export interface RootProps {
|
|
|
44
45
|
* </Root>
|
|
45
46
|
* ```
|
|
46
47
|
*/
|
|
47
|
-
navigateToBookingForm: ({ timeSlot, service, }: {
|
|
48
|
+
navigateToBookingForm: ({ timeSlot, service, timezone, }: {
|
|
48
49
|
timeSlot?: ServiceSelectionTimeSlot;
|
|
49
50
|
service?: Service;
|
|
51
|
+
timezone?: string;
|
|
50
52
|
}) => Promise<void>;
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
@@ -46,11 +46,16 @@ function RootContent({ navigateToBookingForm: providedNavigateToBookingForm, chi
|
|
|
46
46
|
catch {
|
|
47
47
|
bookingService = null;
|
|
48
48
|
}
|
|
49
|
-
const navigateToBookingForm = async ({ timeSlot, service, }) => {
|
|
49
|
+
const navigateToBookingForm = async ({ timeSlot, service, timezone, }) => {
|
|
50
50
|
let resolvedTimeSlot = timeSlot;
|
|
51
51
|
let resolvedService = service;
|
|
52
|
-
|
|
52
|
+
let resolvedTimezone = timezone;
|
|
53
|
+
if ((!resolvedTimeSlot || !resolvedService || !resolvedTimezone) &&
|
|
54
|
+
bookingService) {
|
|
53
55
|
const serviceSelections = bookingService.serviceSelections.get();
|
|
56
|
+
if (!resolvedTimezone) {
|
|
57
|
+
resolvedTimezone = bookingService.timezone.get();
|
|
58
|
+
}
|
|
54
59
|
if (serviceSelections.length > 0) {
|
|
55
60
|
const firstSelection = serviceSelections[0];
|
|
56
61
|
if (firstSelection) {
|
|
@@ -68,6 +73,7 @@ function RootContent({ navigateToBookingForm: providedNavigateToBookingForm, chi
|
|
|
68
73
|
return providedNavigateToBookingForm({
|
|
69
74
|
timeSlot: resolvedTimeSlot,
|
|
70
75
|
service: resolvedService,
|
|
76
|
+
timezone: resolvedTimezone,
|
|
71
77
|
});
|
|
72
78
|
};
|
|
73
79
|
return _jsx(_Fragment, { children: children({ navigateToBookingForm }) });
|
|
@@ -52,21 +52,23 @@ export declare const Timezone: React.ForwardRefExoticComponent<TimezoneProps & R
|
|
|
52
52
|
*/
|
|
53
53
|
export interface RootProps {
|
|
54
54
|
children: React.ReactNode | ((data: {
|
|
55
|
-
navigateToBookingForm: ({ timeSlot, service, }: {
|
|
55
|
+
navigateToBookingForm: ({ timeSlot, service, timezone, }: {
|
|
56
56
|
timeSlot?: ServiceSelectionTimeSlot;
|
|
57
57
|
service?: Service;
|
|
58
|
+
timezone?: string;
|
|
58
59
|
}) => Promise<void>;
|
|
59
60
|
}) => React.ReactNode);
|
|
60
61
|
config: TimeSlotListServiceConfig;
|
|
61
62
|
/**
|
|
62
63
|
* Function to navigate to the booking form.
|
|
63
|
-
* The timeSlot and
|
|
64
|
+
* The timeSlot, service, and timezone parameters are optional - if not provided,
|
|
64
65
|
* BookingService will be used to get them from the current service selections.
|
|
65
66
|
* If not provided, will use the default navigation utility.
|
|
66
67
|
*/
|
|
67
|
-
navigateToBookingForm?: ({ timeSlot, service, }: {
|
|
68
|
+
navigateToBookingForm?: ({ timeSlot, service, timezone, }: {
|
|
68
69
|
timeSlot?: ServiceSelectionTimeSlot;
|
|
69
70
|
service?: Service;
|
|
71
|
+
timezone?: string;
|
|
70
72
|
}) => Promise<void>;
|
|
71
73
|
className?: string;
|
|
72
74
|
variant?: ListVariant;
|
|
@@ -87,11 +87,11 @@ Timezone.displayName = 'TimeSlotList.Timezone';
|
|
|
87
87
|
export const Root = React.forwardRef((props, ref) => {
|
|
88
88
|
const { children, config, className, variant, navigateToBookingForm: providedNavigateToBookingForm, } = props;
|
|
89
89
|
const navigateToBookingForm = providedNavigateToBookingForm ||
|
|
90
|
-
(async ({ timeSlot, service, }) => {
|
|
90
|
+
(async ({ timeSlot, service, timezone, }) => {
|
|
91
91
|
if (!timeSlot || !service) {
|
|
92
92
|
throw new Error('timeSlot and service are required when using default navigation. Either provide them or ensure BookingService has service selections.');
|
|
93
93
|
}
|
|
94
|
-
return defaultNavigateToBookingForm({ timeSlot, service });
|
|
94
|
+
return defaultNavigateToBookingForm({ timeSlot, service, timezone });
|
|
95
95
|
});
|
|
96
96
|
return (_jsx(CoreTimeSlotList.Root, { config: config, navigateToBookingForm: navigateToBookingForm, children: (coreData) => (_jsx(RootContent, { className: className, ref: ref, variant: variant, children: typeof children === 'function'
|
|
97
97
|
? children({
|
|
@@ -71,6 +71,16 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
|
|
|
71
71
|
const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
|
|
72
72
|
const formSubmissionSignal = signalsService.signal(config.formSubmission || null);
|
|
73
73
|
// ========================================================================
|
|
74
|
+
// Helpers
|
|
75
|
+
// ========================================================================
|
|
76
|
+
function getTargetItem(instanceId) {
|
|
77
|
+
const current = serviceSelectionsSignal.get();
|
|
78
|
+
const targetId = instanceId ?? current[0]?.instanceId;
|
|
79
|
+
if (!targetId)
|
|
80
|
+
return undefined;
|
|
81
|
+
return current.find((item) => item.instanceId === targetId);
|
|
82
|
+
}
|
|
83
|
+
// ========================================================================
|
|
74
84
|
// Actions Implementation
|
|
75
85
|
// ========================================================================
|
|
76
86
|
const actions = {
|
|
@@ -116,45 +126,23 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
|
|
|
116
126
|
serviceSelectionsSignal.set([]);
|
|
117
127
|
},
|
|
118
128
|
setResource(resource, instanceId) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const targetId = instanceId ?? current[0]?.instanceId;
|
|
122
|
-
if (!targetId)
|
|
129
|
+
const targetItem = getTargetItem(instanceId);
|
|
130
|
+
if (!targetItem)
|
|
123
131
|
return;
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
newResources.push(resource);
|
|
129
|
-
return { ...item, resources: newResources };
|
|
130
|
-
});
|
|
131
|
-
serviceSelectionsSignal.set(updated);
|
|
132
|
+
const newResources = targetItem.resources
|
|
133
|
+
.filter((r) => r.type !== resource.type)
|
|
134
|
+
.concat(resource);
|
|
135
|
+
this.updateItem({ resources: newResources, timeSlot: undefined }, targetItem.instanceId);
|
|
132
136
|
},
|
|
133
137
|
getResourceByType(resourceType, instanceId) {
|
|
134
|
-
|
|
135
|
-
// If no instanceId provided, use the first item
|
|
136
|
-
const targetId = instanceId ?? current[0]?.instanceId;
|
|
137
|
-
if (!targetId)
|
|
138
|
-
return undefined;
|
|
139
|
-
const targetItem = current.find((item) => item.instanceId === targetId);
|
|
140
|
-
if (!targetItem)
|
|
141
|
-
return undefined;
|
|
142
|
-
return targetItem.resources.find((r) => r.type === resourceType);
|
|
138
|
+
return getTargetItem(instanceId)?.resources.find((r) => r.type === resourceType);
|
|
143
139
|
},
|
|
144
140
|
clearResourceByType(resourceType, instanceId) {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
const targetId = instanceId ?? current[0]?.instanceId;
|
|
148
|
-
if (!targetId)
|
|
141
|
+
const targetItem = getTargetItem(instanceId);
|
|
142
|
+
if (!targetItem)
|
|
149
143
|
return;
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
return item;
|
|
153
|
-
// Filter out the resource with matching type
|
|
154
|
-
const newResources = item.resources.filter((r) => r.type !== resourceType);
|
|
155
|
-
return { ...item, resources: newResources };
|
|
156
|
-
});
|
|
157
|
-
serviceSelectionsSignal.set(updated);
|
|
144
|
+
const newResources = targetItem.resources.filter((r) => r.type !== resourceType);
|
|
145
|
+
this.updateItem({ resources: newResources }, targetItem.instanceId);
|
|
158
146
|
},
|
|
159
147
|
// Location actions
|
|
160
148
|
setLocation(location) {
|
|
@@ -178,9 +166,9 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
|
|
|
178
166
|
},
|
|
179
167
|
// Clear all
|
|
180
168
|
clearAll() {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
169
|
+
this.clearItems();
|
|
170
|
+
this.clearLocation();
|
|
171
|
+
this.clearFormSubmission();
|
|
184
172
|
},
|
|
185
173
|
// Book action - collects data from signals and delegates to executeBookAction
|
|
186
174
|
// Loading/error state management is handled in Book.tsx component
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import type { ServiceSelectionTimeSlot } from '../services/booking/booking.js';
|
|
2
2
|
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
3
3
|
export declare function navigateToCheckout(checkoutId: string): Promise<void>;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Cleans a redirect URL by extracting the redirectUrl param and removing:
|
|
6
|
+
* - The 'origin' query parameter
|
|
7
|
+
* - Any query parameters with empty values
|
|
8
|
+
*
|
|
9
|
+
* @param fullUrl - The full URL containing the redirectUrl query parameter
|
|
10
|
+
* @returns The cleaned redirect URL
|
|
11
|
+
*/
|
|
12
|
+
export declare function cleanRedirectUrl(fullUrl: string): string;
|
|
13
|
+
export declare function navigateToBookingForm({ timeSlot, service, timezone, }: {
|
|
5
14
|
timeSlot: ServiceSelectionTimeSlot;
|
|
6
15
|
service: Service;
|
|
16
|
+
timezone?: string;
|
|
7
17
|
}): Promise<void>;
|
|
@@ -27,7 +27,32 @@ function mapTimeSlotLocationToBookingLocation(timeSlotLocationType) {
|
|
|
27
27
|
return BookingsLocationType.UNDEFINED;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Cleans a redirect URL by extracting the redirectUrl param and removing:
|
|
32
|
+
* - The 'origin' query parameter
|
|
33
|
+
* - Any query parameters with empty values
|
|
34
|
+
*
|
|
35
|
+
* @param fullUrl - The full URL containing the redirectUrl query parameter
|
|
36
|
+
* @returns The cleaned redirect URL
|
|
37
|
+
*/
|
|
38
|
+
export function cleanRedirectUrl(fullUrl) {
|
|
39
|
+
const url = new URL(fullUrl);
|
|
40
|
+
const redirectUrl = url.searchParams.get('redirectUrl');
|
|
41
|
+
if (!redirectUrl) {
|
|
42
|
+
return fullUrl;
|
|
43
|
+
}
|
|
44
|
+
const redirectParsed = new URL(redirectUrl);
|
|
45
|
+
// Remove origin param
|
|
46
|
+
redirectParsed.searchParams.delete('origin');
|
|
47
|
+
// Remove empty params
|
|
48
|
+
for (const [key, value] of [...redirectParsed.searchParams.entries()]) {
|
|
49
|
+
if (!value) {
|
|
50
|
+
redirectParsed.searchParams.delete(key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return redirectParsed.toString();
|
|
54
|
+
}
|
|
55
|
+
export async function navigateToBookingForm({ timeSlot, service, timezone, }) {
|
|
31
56
|
if (!timeSlot) {
|
|
32
57
|
throw new Error('No time slot provided');
|
|
33
58
|
}
|
|
@@ -36,30 +61,36 @@ export async function navigateToBookingForm({ timeSlot, service, }) {
|
|
|
36
61
|
}
|
|
37
62
|
// Get the first resource (staff member) if available
|
|
38
63
|
const selectedResource = timeSlot.resources?.[0];
|
|
64
|
+
// Destructure to exclude properties we handle separately (location has incompatible types)
|
|
65
|
+
const { location: _location, resources: _resources, ...restTimeSlot } = timeSlot;
|
|
66
|
+
// Build slot object, only including optional properties when they have values
|
|
67
|
+
const slot = {
|
|
68
|
+
...restTimeSlot,
|
|
69
|
+
scheduleId: timeSlot.scheduleId,
|
|
70
|
+
startDate: timeSlot.startDate,
|
|
71
|
+
endDate: timeSlot.endDate,
|
|
72
|
+
...(service._id && { serviceId: service._id }),
|
|
73
|
+
...(timezone && { timezone }),
|
|
74
|
+
...(selectedResource && {
|
|
75
|
+
resource: {
|
|
76
|
+
_id: selectedResource._id,
|
|
77
|
+
name: selectedResource.name,
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
...(timeSlot.location && {
|
|
81
|
+
location: {
|
|
82
|
+
_id: timeSlot.location._id,
|
|
83
|
+
name: timeSlot.location.name,
|
|
84
|
+
formattedAddress: timeSlot.location.formattedAddress,
|
|
85
|
+
locationType: mapTimeSlotLocationToBookingLocation(timeSlot.location.locationType),
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
};
|
|
39
89
|
const { redirectSession } = await redirects.createRedirectSession({
|
|
40
90
|
bookingsCheckout: {
|
|
41
91
|
slotAvailability: {
|
|
42
92
|
bookable: true,
|
|
43
|
-
slot
|
|
44
|
-
...timeSlot,
|
|
45
|
-
serviceId: service._id ?? undefined,
|
|
46
|
-
scheduleId: timeSlot.scheduleId,
|
|
47
|
-
startDate: timeSlot.startDate,
|
|
48
|
-
endDate: timeSlot.endDate,
|
|
49
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
50
|
-
resource: selectedResource
|
|
51
|
-
? {
|
|
52
|
-
_id: selectedResource._id,
|
|
53
|
-
name: selectedResource.name,
|
|
54
|
-
}
|
|
55
|
-
: undefined,
|
|
56
|
-
location: {
|
|
57
|
-
_id: timeSlot.location?._id,
|
|
58
|
-
name: timeSlot.location?.name,
|
|
59
|
-
formattedAddress: timeSlot.location?.formattedAddress,
|
|
60
|
-
locationType: mapTimeSlotLocationToBookingLocation(timeSlot.location?.locationType),
|
|
61
|
-
},
|
|
62
|
-
},
|
|
93
|
+
slot,
|
|
63
94
|
},
|
|
64
95
|
},
|
|
65
96
|
callbacks: {
|
|
@@ -67,7 +98,7 @@ export async function navigateToBookingForm({ timeSlot, service, }) {
|
|
|
67
98
|
},
|
|
68
99
|
});
|
|
69
100
|
if (redirectSession?.fullUrl) {
|
|
70
|
-
window.location.href = redirectSession.fullUrl;
|
|
101
|
+
window.location.href = cleanRedirectUrl(redirectSession.fullUrl);
|
|
71
102
|
}
|
|
72
103
|
else {
|
|
73
104
|
throw new Error('Failed to create redirect session');
|
|
@@ -12,9 +12,10 @@ import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
|
12
12
|
*/
|
|
13
13
|
export interface TimeSlotListRootRenderProps {
|
|
14
14
|
/** Function to navigate to the booking form with the selected time slot */
|
|
15
|
-
navigateToBookingForm: ({ timeSlot, service, }: {
|
|
15
|
+
navigateToBookingForm: ({ timeSlot, service, timezone, }: {
|
|
16
16
|
timeSlot?: ServiceSelectionTimeSlot;
|
|
17
17
|
service?: Service;
|
|
18
|
+
timezone?: string;
|
|
18
19
|
}) => Promise<void>;
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
@@ -44,9 +45,10 @@ export interface RootProps {
|
|
|
44
45
|
* </Root>
|
|
45
46
|
* ```
|
|
46
47
|
*/
|
|
47
|
-
navigateToBookingForm: ({ timeSlot, service, }: {
|
|
48
|
+
navigateToBookingForm: ({ timeSlot, service, timezone, }: {
|
|
48
49
|
timeSlot?: ServiceSelectionTimeSlot;
|
|
49
50
|
service?: Service;
|
|
51
|
+
timezone?: string;
|
|
50
52
|
}) => Promise<void>;
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
@@ -46,11 +46,16 @@ function RootContent({ navigateToBookingForm: providedNavigateToBookingForm, chi
|
|
|
46
46
|
catch {
|
|
47
47
|
bookingService = null;
|
|
48
48
|
}
|
|
49
|
-
const navigateToBookingForm = async ({ timeSlot, service, }) => {
|
|
49
|
+
const navigateToBookingForm = async ({ timeSlot, service, timezone, }) => {
|
|
50
50
|
let resolvedTimeSlot = timeSlot;
|
|
51
51
|
let resolvedService = service;
|
|
52
|
-
|
|
52
|
+
let resolvedTimezone = timezone;
|
|
53
|
+
if ((!resolvedTimeSlot || !resolvedService || !resolvedTimezone) &&
|
|
54
|
+
bookingService) {
|
|
53
55
|
const serviceSelections = bookingService.serviceSelections.get();
|
|
56
|
+
if (!resolvedTimezone) {
|
|
57
|
+
resolvedTimezone = bookingService.timezone.get();
|
|
58
|
+
}
|
|
54
59
|
if (serviceSelections.length > 0) {
|
|
55
60
|
const firstSelection = serviceSelections[0];
|
|
56
61
|
if (firstSelection) {
|
|
@@ -68,6 +73,7 @@ function RootContent({ navigateToBookingForm: providedNavigateToBookingForm, chi
|
|
|
68
73
|
return providedNavigateToBookingForm({
|
|
69
74
|
timeSlot: resolvedTimeSlot,
|
|
70
75
|
service: resolvedService,
|
|
76
|
+
timezone: resolvedTimezone,
|
|
71
77
|
});
|
|
72
78
|
};
|
|
73
79
|
return _jsx(_Fragment, { children: children({ navigateToBookingForm }) });
|
|
@@ -52,21 +52,23 @@ export declare const Timezone: React.ForwardRefExoticComponent<TimezoneProps & R
|
|
|
52
52
|
*/
|
|
53
53
|
export interface RootProps {
|
|
54
54
|
children: React.ReactNode | ((data: {
|
|
55
|
-
navigateToBookingForm: ({ timeSlot, service, }: {
|
|
55
|
+
navigateToBookingForm: ({ timeSlot, service, timezone, }: {
|
|
56
56
|
timeSlot?: ServiceSelectionTimeSlot;
|
|
57
57
|
service?: Service;
|
|
58
|
+
timezone?: string;
|
|
58
59
|
}) => Promise<void>;
|
|
59
60
|
}) => React.ReactNode);
|
|
60
61
|
config: TimeSlotListServiceConfig;
|
|
61
62
|
/**
|
|
62
63
|
* Function to navigate to the booking form.
|
|
63
|
-
* The timeSlot and
|
|
64
|
+
* The timeSlot, service, and timezone parameters are optional - if not provided,
|
|
64
65
|
* BookingService will be used to get them from the current service selections.
|
|
65
66
|
* If not provided, will use the default navigation utility.
|
|
66
67
|
*/
|
|
67
|
-
navigateToBookingForm?: ({ timeSlot, service, }: {
|
|
68
|
+
navigateToBookingForm?: ({ timeSlot, service, timezone, }: {
|
|
68
69
|
timeSlot?: ServiceSelectionTimeSlot;
|
|
69
70
|
service?: Service;
|
|
71
|
+
timezone?: string;
|
|
70
72
|
}) => Promise<void>;
|
|
71
73
|
className?: string;
|
|
72
74
|
variant?: ListVariant;
|
|
@@ -87,11 +87,11 @@ Timezone.displayName = 'TimeSlotList.Timezone';
|
|
|
87
87
|
export const Root = React.forwardRef((props, ref) => {
|
|
88
88
|
const { children, config, className, variant, navigateToBookingForm: providedNavigateToBookingForm, } = props;
|
|
89
89
|
const navigateToBookingForm = providedNavigateToBookingForm ||
|
|
90
|
-
(async ({ timeSlot, service, }) => {
|
|
90
|
+
(async ({ timeSlot, service, timezone, }) => {
|
|
91
91
|
if (!timeSlot || !service) {
|
|
92
92
|
throw new Error('timeSlot and service are required when using default navigation. Either provide them or ensure BookingService has service selections.');
|
|
93
93
|
}
|
|
94
|
-
return defaultNavigateToBookingForm({ timeSlot, service });
|
|
94
|
+
return defaultNavigateToBookingForm({ timeSlot, service, timezone });
|
|
95
95
|
});
|
|
96
96
|
return (_jsx(CoreTimeSlotList.Root, { config: config, navigateToBookingForm: navigateToBookingForm, children: (coreData) => (_jsx(RootContent, { className: className, ref: ref, variant: variant, children: typeof children === 'function'
|
|
97
97
|
? children({
|
|
@@ -71,6 +71,16 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
|
|
|
71
71
|
const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
|
|
72
72
|
const formSubmissionSignal = signalsService.signal(config.formSubmission || null);
|
|
73
73
|
// ========================================================================
|
|
74
|
+
// Helpers
|
|
75
|
+
// ========================================================================
|
|
76
|
+
function getTargetItem(instanceId) {
|
|
77
|
+
const current = serviceSelectionsSignal.get();
|
|
78
|
+
const targetId = instanceId ?? current[0]?.instanceId;
|
|
79
|
+
if (!targetId)
|
|
80
|
+
return undefined;
|
|
81
|
+
return current.find((item) => item.instanceId === targetId);
|
|
82
|
+
}
|
|
83
|
+
// ========================================================================
|
|
74
84
|
// Actions Implementation
|
|
75
85
|
// ========================================================================
|
|
76
86
|
const actions = {
|
|
@@ -116,45 +126,23 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
|
|
|
116
126
|
serviceSelectionsSignal.set([]);
|
|
117
127
|
},
|
|
118
128
|
setResource(resource, instanceId) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const targetId = instanceId ?? current[0]?.instanceId;
|
|
122
|
-
if (!targetId)
|
|
129
|
+
const targetItem = getTargetItem(instanceId);
|
|
130
|
+
if (!targetItem)
|
|
123
131
|
return;
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
newResources.push(resource);
|
|
129
|
-
return { ...item, resources: newResources };
|
|
130
|
-
});
|
|
131
|
-
serviceSelectionsSignal.set(updated);
|
|
132
|
+
const newResources = targetItem.resources
|
|
133
|
+
.filter((r) => r.type !== resource.type)
|
|
134
|
+
.concat(resource);
|
|
135
|
+
this.updateItem({ resources: newResources, timeSlot: undefined }, targetItem.instanceId);
|
|
132
136
|
},
|
|
133
137
|
getResourceByType(resourceType, instanceId) {
|
|
134
|
-
|
|
135
|
-
// If no instanceId provided, use the first item
|
|
136
|
-
const targetId = instanceId ?? current[0]?.instanceId;
|
|
137
|
-
if (!targetId)
|
|
138
|
-
return undefined;
|
|
139
|
-
const targetItem = current.find((item) => item.instanceId === targetId);
|
|
140
|
-
if (!targetItem)
|
|
141
|
-
return undefined;
|
|
142
|
-
return targetItem.resources.find((r) => r.type === resourceType);
|
|
138
|
+
return getTargetItem(instanceId)?.resources.find((r) => r.type === resourceType);
|
|
143
139
|
},
|
|
144
140
|
clearResourceByType(resourceType, instanceId) {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
const targetId = instanceId ?? current[0]?.instanceId;
|
|
148
|
-
if (!targetId)
|
|
141
|
+
const targetItem = getTargetItem(instanceId);
|
|
142
|
+
if (!targetItem)
|
|
149
143
|
return;
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
return item;
|
|
153
|
-
// Filter out the resource with matching type
|
|
154
|
-
const newResources = item.resources.filter((r) => r.type !== resourceType);
|
|
155
|
-
return { ...item, resources: newResources };
|
|
156
|
-
});
|
|
157
|
-
serviceSelectionsSignal.set(updated);
|
|
144
|
+
const newResources = targetItem.resources.filter((r) => r.type !== resourceType);
|
|
145
|
+
this.updateItem({ resources: newResources }, targetItem.instanceId);
|
|
158
146
|
},
|
|
159
147
|
// Location actions
|
|
160
148
|
setLocation(location) {
|
|
@@ -178,9 +166,9 @@ export const BookingService = implementService.withConfig()(BookingServiceDefini
|
|
|
178
166
|
},
|
|
179
167
|
// Clear all
|
|
180
168
|
clearAll() {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
169
|
+
this.clearItems();
|
|
170
|
+
this.clearLocation();
|
|
171
|
+
this.clearFormSubmission();
|
|
184
172
|
},
|
|
185
173
|
// Book action - collects data from signals and delegates to executeBookAction
|
|
186
174
|
// Loading/error state management is handled in Book.tsx component
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import type { ServiceSelectionTimeSlot } from '../services/booking/booking.js';
|
|
2
2
|
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
3
3
|
export declare function navigateToCheckout(checkoutId: string): Promise<void>;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Cleans a redirect URL by extracting the redirectUrl param and removing:
|
|
6
|
+
* - The 'origin' query parameter
|
|
7
|
+
* - Any query parameters with empty values
|
|
8
|
+
*
|
|
9
|
+
* @param fullUrl - The full URL containing the redirectUrl query parameter
|
|
10
|
+
* @returns The cleaned redirect URL
|
|
11
|
+
*/
|
|
12
|
+
export declare function cleanRedirectUrl(fullUrl: string): string;
|
|
13
|
+
export declare function navigateToBookingForm({ timeSlot, service, timezone, }: {
|
|
5
14
|
timeSlot: ServiceSelectionTimeSlot;
|
|
6
15
|
service: Service;
|
|
16
|
+
timezone?: string;
|
|
7
17
|
}): Promise<void>;
|
package/dist/utils/navigation.js
CHANGED
|
@@ -27,7 +27,32 @@ function mapTimeSlotLocationToBookingLocation(timeSlotLocationType) {
|
|
|
27
27
|
return BookingsLocationType.UNDEFINED;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Cleans a redirect URL by extracting the redirectUrl param and removing:
|
|
32
|
+
* - The 'origin' query parameter
|
|
33
|
+
* - Any query parameters with empty values
|
|
34
|
+
*
|
|
35
|
+
* @param fullUrl - The full URL containing the redirectUrl query parameter
|
|
36
|
+
* @returns The cleaned redirect URL
|
|
37
|
+
*/
|
|
38
|
+
export function cleanRedirectUrl(fullUrl) {
|
|
39
|
+
const url = new URL(fullUrl);
|
|
40
|
+
const redirectUrl = url.searchParams.get('redirectUrl');
|
|
41
|
+
if (!redirectUrl) {
|
|
42
|
+
return fullUrl;
|
|
43
|
+
}
|
|
44
|
+
const redirectParsed = new URL(redirectUrl);
|
|
45
|
+
// Remove origin param
|
|
46
|
+
redirectParsed.searchParams.delete('origin');
|
|
47
|
+
// Remove empty params
|
|
48
|
+
for (const [key, value] of [...redirectParsed.searchParams.entries()]) {
|
|
49
|
+
if (!value) {
|
|
50
|
+
redirectParsed.searchParams.delete(key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return redirectParsed.toString();
|
|
54
|
+
}
|
|
55
|
+
export async function navigateToBookingForm({ timeSlot, service, timezone, }) {
|
|
31
56
|
if (!timeSlot) {
|
|
32
57
|
throw new Error('No time slot provided');
|
|
33
58
|
}
|
|
@@ -36,30 +61,36 @@ export async function navigateToBookingForm({ timeSlot, service, }) {
|
|
|
36
61
|
}
|
|
37
62
|
// Get the first resource (staff member) if available
|
|
38
63
|
const selectedResource = timeSlot.resources?.[0];
|
|
64
|
+
// Destructure to exclude properties we handle separately (location has incompatible types)
|
|
65
|
+
const { location: _location, resources: _resources, ...restTimeSlot } = timeSlot;
|
|
66
|
+
// Build slot object, only including optional properties when they have values
|
|
67
|
+
const slot = {
|
|
68
|
+
...restTimeSlot,
|
|
69
|
+
scheduleId: timeSlot.scheduleId,
|
|
70
|
+
startDate: timeSlot.startDate,
|
|
71
|
+
endDate: timeSlot.endDate,
|
|
72
|
+
...(service._id && { serviceId: service._id }),
|
|
73
|
+
...(timezone && { timezone }),
|
|
74
|
+
...(selectedResource && {
|
|
75
|
+
resource: {
|
|
76
|
+
_id: selectedResource._id,
|
|
77
|
+
name: selectedResource.name,
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
...(timeSlot.location && {
|
|
81
|
+
location: {
|
|
82
|
+
_id: timeSlot.location._id,
|
|
83
|
+
name: timeSlot.location.name,
|
|
84
|
+
formattedAddress: timeSlot.location.formattedAddress,
|
|
85
|
+
locationType: mapTimeSlotLocationToBookingLocation(timeSlot.location.locationType),
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
};
|
|
39
89
|
const { redirectSession } = await redirects.createRedirectSession({
|
|
40
90
|
bookingsCheckout: {
|
|
41
91
|
slotAvailability: {
|
|
42
92
|
bookable: true,
|
|
43
|
-
slot
|
|
44
|
-
...timeSlot,
|
|
45
|
-
serviceId: service._id ?? undefined,
|
|
46
|
-
scheduleId: timeSlot.scheduleId,
|
|
47
|
-
startDate: timeSlot.startDate,
|
|
48
|
-
endDate: timeSlot.endDate,
|
|
49
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
50
|
-
resource: selectedResource
|
|
51
|
-
? {
|
|
52
|
-
_id: selectedResource._id,
|
|
53
|
-
name: selectedResource.name,
|
|
54
|
-
}
|
|
55
|
-
: undefined,
|
|
56
|
-
location: {
|
|
57
|
-
_id: timeSlot.location?._id,
|
|
58
|
-
name: timeSlot.location?.name,
|
|
59
|
-
formattedAddress: timeSlot.location?.formattedAddress,
|
|
60
|
-
locationType: mapTimeSlotLocationToBookingLocation(timeSlot.location?.locationType),
|
|
61
|
-
},
|
|
62
|
-
},
|
|
93
|
+
slot,
|
|
63
94
|
},
|
|
64
95
|
},
|
|
65
96
|
callbacks: {
|
|
@@ -67,7 +98,7 @@ export async function navigateToBookingForm({ timeSlot, service, }) {
|
|
|
67
98
|
},
|
|
68
99
|
});
|
|
69
100
|
if (redirectSession?.fullUrl) {
|
|
70
|
-
window.location.href = redirectSession.fullUrl;
|
|
101
|
+
window.location.href = cleanRedirectUrl(redirectSession.fullUrl);
|
|
71
102
|
}
|
|
72
103
|
else {
|
|
73
104
|
throw new Error('Failed to create redirect session');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-bookings",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.65",
|
|
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": "
|
|
75
|
+
"falconPackageHash": "a7381beae4b122c7bdbd9d7b7f4079f31688f2a11cd3c6d246e683c5"
|
|
76
76
|
}
|