@wix/headless-bookings 0.0.23 → 0.0.25
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/react/booking-form/BookingForm.d.ts +156 -0
- package/cjs/dist/react/booking-form/BookingForm.js +126 -0
- package/cjs/dist/react/core/booking-form/BookingForm.d.ts +139 -0
- package/cjs/dist/react/core/booking-form/BookingForm.js +181 -0
- package/cjs/dist/react/index.d.ts +1 -0
- package/cjs/dist/react/index.js +1 -0
- package/cjs/dist/services/booking-form/booking-form.d.ts +140 -0
- package/cjs/dist/services/booking-form/booking-form.js +171 -0
- package/cjs/dist/services/index.d.ts +1 -0
- package/cjs/dist/services/index.js +1 -0
- package/dist/__mocks__/services.d.ts +75 -0
- package/dist/__mocks__/services.js +308 -0
- package/dist/react/booking-form/BookingForm.d.ts +156 -0
- package/dist/react/booking-form/BookingForm.js +126 -0
- package/dist/react/core/booking-form/BookingForm.d.ts +139 -0
- package/dist/react/core/booking-form/BookingForm.js +181 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +1 -0
- package/dist/services/booking-form/booking-form.d.ts +140 -0
- package/dist/services/booking-form/booking-form.js +171 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/package.json +3 -2
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BookingForm - High-level component for rendering booking forms
|
|
3
|
+
* Provides components for displaying and managing booking form fields
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { Form } from '@wix/headless-forms/react';
|
|
7
|
+
type FieldMap = Parameters<typeof Form.Fields>[0]['fieldMap'];
|
|
8
|
+
/**
|
|
9
|
+
* Props for BookingForm.Root component
|
|
10
|
+
*/
|
|
11
|
+
export interface RootProps {
|
|
12
|
+
/** The form ID to load (required - standalone mode) */
|
|
13
|
+
formId: string;
|
|
14
|
+
/** Optional additional metadata to pass to FormService */
|
|
15
|
+
additionalMetadata?: Record<string, string | string[]>;
|
|
16
|
+
/** Optional partial field map to override default booking field components */
|
|
17
|
+
fieldMap?: Partial<FieldMap>;
|
|
18
|
+
/** Child components - if not provided, Form.Fields is rendered with default field map */
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
/** Whether to render as a child component */
|
|
21
|
+
asChild?: boolean;
|
|
22
|
+
/** CSS classes to apply to the root element */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** Row gap class name for Form.Fields when using default rendering */
|
|
25
|
+
rowGapClassname?: string;
|
|
26
|
+
/** Column gap class name for Form.Fields when using default rendering */
|
|
27
|
+
columnGapClassname?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Root component for booking form that wraps CoreBookingForm.Root.
|
|
31
|
+
* Automatically loads form and handles submission in standalone mode.
|
|
32
|
+
* Uses DEFAULT_BOOKING_FIELD_MAP for field rendering with optional user overrides.
|
|
33
|
+
*
|
|
34
|
+
* @order 1
|
|
35
|
+
* @component
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* import { BookingForm } from '@wix/headless-bookings/react';
|
|
39
|
+
*
|
|
40
|
+
* // Minimal usage - uses default field components
|
|
41
|
+
* <BookingForm.Root formId="your-form-id" />
|
|
42
|
+
*
|
|
43
|
+
* // With custom styling
|
|
44
|
+
* <BookingForm.Root
|
|
45
|
+
* formId="your-form-id"
|
|
46
|
+
* className="my-form-container"
|
|
47
|
+
* rowGapClassname="gap-y-6"
|
|
48
|
+
* columnGapClassname="gap-x-4"
|
|
49
|
+
* />
|
|
50
|
+
*
|
|
51
|
+
* // With custom field overrides
|
|
52
|
+
* <BookingForm.Root
|
|
53
|
+
* formId="your-form-id"
|
|
54
|
+
* fieldMap={{ TEXT_INPUT: MyCustomTextInput }}
|
|
55
|
+
* />
|
|
56
|
+
*
|
|
57
|
+
* // With full control over form structure
|
|
58
|
+
* <BookingForm.Root formId="your-form-id">
|
|
59
|
+
* <Form.Loading className="flex justify-center p-4" />
|
|
60
|
+
* <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
|
|
61
|
+
* <BookingForm.Fields />
|
|
62
|
+
* <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
|
|
63
|
+
* <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
|
|
64
|
+
* </BookingForm.Root>
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare const Root: React.ForwardRefExoticComponent<RootProps & React.RefAttributes<HTMLDivElement>>;
|
|
68
|
+
/**
|
|
69
|
+
* Props for BookingForm.Fields component
|
|
70
|
+
*/
|
|
71
|
+
export interface FieldsProps {
|
|
72
|
+
/** Optional override for row gap class name */
|
|
73
|
+
rowGapClassname?: string;
|
|
74
|
+
/** Optional override for column gap class name */
|
|
75
|
+
columnGapClassname?: string;
|
|
76
|
+
/** CSS classes to apply */
|
|
77
|
+
className?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Fields component that renders Form.Fields with the merged field map.
|
|
81
|
+
* Must be used within BookingForm.Root.
|
|
82
|
+
*
|
|
83
|
+
* @component
|
|
84
|
+
* @example
|
|
85
|
+
* ```tsx
|
|
86
|
+
* <BookingForm.Root formId="your-form-id">
|
|
87
|
+
* <Form.Loading className="p-4" />
|
|
88
|
+
* <BookingForm.Fields rowGapClassname="gap-y-6" />
|
|
89
|
+
* <Form.Error className="text-destructive" />
|
|
90
|
+
* </BookingForm.Root>
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare const Fields: React.ForwardRefExoticComponent<FieldsProps & React.RefAttributes<HTMLDivElement>>;
|
|
94
|
+
/**
|
|
95
|
+
* Render props for BookingForm.Data component
|
|
96
|
+
*/
|
|
97
|
+
export interface DataRenderProps {
|
|
98
|
+
/** The form ID being used */
|
|
99
|
+
formId: string;
|
|
100
|
+
/** Merged field map (DEFAULT_BOOKING_FIELD_MAP + user overrides) */
|
|
101
|
+
fields: FieldMap;
|
|
102
|
+
/** Current form values from FormService (live state of form fields) */
|
|
103
|
+
formValues: Record<string, unknown>;
|
|
104
|
+
/** Current form submission data */
|
|
105
|
+
formSubmission: Record<string, unknown> | null;
|
|
106
|
+
/** Action to store form submission data */
|
|
107
|
+
setFormSubmission: (formValues: Record<string, unknown>) => void;
|
|
108
|
+
/** Action to validate the form */
|
|
109
|
+
validateFormSubmission: () => Promise<{
|
|
110
|
+
valid: boolean;
|
|
111
|
+
validationFailures: string[];
|
|
112
|
+
}>;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Props for BookingForm.Data component
|
|
116
|
+
*/
|
|
117
|
+
export interface DataProps {
|
|
118
|
+
/** The form ID to load (required - standalone mode) */
|
|
119
|
+
formId: string;
|
|
120
|
+
/** Optional additional metadata to pass to FormService */
|
|
121
|
+
additionalMetadata?: Record<string, string | string[]>;
|
|
122
|
+
/** Optional partial field map to override default booking field components */
|
|
123
|
+
fieldMap?: Partial<FieldMap>;
|
|
124
|
+
/** Render prop function that receives form data and actions */
|
|
125
|
+
children: (data: DataRenderProps) => React.ReactNode;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Data component that provides booking form data via render props.
|
|
129
|
+
* Use this when you need programmatic access to form state and actions.
|
|
130
|
+
*
|
|
131
|
+
* @component
|
|
132
|
+
* @example
|
|
133
|
+
* ```tsx
|
|
134
|
+
* <BookingForm.Data formId="your-form-id">
|
|
135
|
+
* {({ formId, fields, formValues, setFormSubmission, validateFormSubmission }) => (
|
|
136
|
+
* <div>
|
|
137
|
+
* <Form.Fields fieldMap={fields} rowGapClassname="gap-y-4" columnGapClassname="gap-x-2" />
|
|
138
|
+
* <button onClick={async () => {
|
|
139
|
+
* const result = await validateFormSubmission();
|
|
140
|
+
* if (result.valid) {
|
|
141
|
+
* // Store current form values before submission
|
|
142
|
+
* setFormSubmission(formValues);
|
|
143
|
+
* }
|
|
144
|
+
* }}>
|
|
145
|
+
* Validate & Submit
|
|
146
|
+
* </button>
|
|
147
|
+
* </div>
|
|
148
|
+
* )}
|
|
149
|
+
* </BookingForm.Data>
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export declare function Data(props: DataProps): React.ReactNode;
|
|
153
|
+
export declare namespace Data {
|
|
154
|
+
var displayName: string;
|
|
155
|
+
}
|
|
156
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* BookingForm - High-level component for rendering booking forms
|
|
4
|
+
* Provides components for displaying and managing booking form fields
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { AsChildSlot } from '@wix/headless-utils/react';
|
|
8
|
+
import { Form } from '@wix/headless-forms/react';
|
|
9
|
+
import * as CoreBookingForm from '../core/booking-form/BookingForm.js';
|
|
10
|
+
var TestIds;
|
|
11
|
+
(function (TestIds) {
|
|
12
|
+
TestIds["bookingFormRoot"] = "booking-form-root";
|
|
13
|
+
TestIds["bookingFormFields"] = "booking-form-fields";
|
|
14
|
+
})(TestIds || (TestIds = {}));
|
|
15
|
+
/**
|
|
16
|
+
* Root component for booking form that wraps CoreBookingForm.Root.
|
|
17
|
+
* Automatically loads form and handles submission in standalone mode.
|
|
18
|
+
* Uses DEFAULT_BOOKING_FIELD_MAP for field rendering with optional user overrides.
|
|
19
|
+
*
|
|
20
|
+
* @order 1
|
|
21
|
+
* @component
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* import { BookingForm } from '@wix/headless-bookings/react';
|
|
25
|
+
*
|
|
26
|
+
* // Minimal usage - uses default field components
|
|
27
|
+
* <BookingForm.Root formId="your-form-id" />
|
|
28
|
+
*
|
|
29
|
+
* // With custom styling
|
|
30
|
+
* <BookingForm.Root
|
|
31
|
+
* formId="your-form-id"
|
|
32
|
+
* className="my-form-container"
|
|
33
|
+
* rowGapClassname="gap-y-6"
|
|
34
|
+
* columnGapClassname="gap-x-4"
|
|
35
|
+
* />
|
|
36
|
+
*
|
|
37
|
+
* // With custom field overrides
|
|
38
|
+
* <BookingForm.Root
|
|
39
|
+
* formId="your-form-id"
|
|
40
|
+
* fieldMap={{ TEXT_INPUT: MyCustomTextInput }}
|
|
41
|
+
* />
|
|
42
|
+
*
|
|
43
|
+
* // With full control over form structure
|
|
44
|
+
* <BookingForm.Root formId="your-form-id">
|
|
45
|
+
* <Form.Loading className="flex justify-center p-4" />
|
|
46
|
+
* <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
|
|
47
|
+
* <BookingForm.Fields />
|
|
48
|
+
* <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
|
|
49
|
+
* <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
|
|
50
|
+
* </BookingForm.Root>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export const Root = React.forwardRef((props, ref) => {
|
|
54
|
+
const { children, asChild, className, formId, additionalMetadata, fieldMap, rowGapClassname = 'gap-y-4', columnGapClassname = 'gap-x-2', ...otherProps } = props;
|
|
55
|
+
return (_jsx(CoreBookingForm.Root, { formId: formId, additionalMetadata: additionalMetadata, fieldMap: fieldMap, children: ({ fields }) => (_jsx(BookingFormContext.Provider, { value: { fields, rowGapClassname, columnGapClassname }, children: _jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.bookingFormRoot, ...otherProps, children: _jsx("div", { children: children ?? (_jsxs(_Fragment, { children: [_jsx(Form.Loading, { className: "flex justify-center p-4 text-foreground" }), _jsx(Form.LoadingError, { className: "bg-destructive/10 border border-destructive/20 text-destructive px-4 py-3 rounded-lg mb-4" }), _jsx(Form.Fields, { fieldMap: fields, rowGapClassname: rowGapClassname, columnGapClassname: columnGapClassname }), _jsx(Form.Error, { className: "mt-4 bg-destructive/10 border border-destructive/20 text-destructive px-4 py-3 rounded-lg" }), _jsx(Form.Submitted, { className: "mt-4 bg-green-500/10 border border-green-500/20 text-green-500 px-4 py-3 rounded-lg" })] })) }) }) })) }));
|
|
56
|
+
});
|
|
57
|
+
Root.displayName = 'BookingForm.Root';
|
|
58
|
+
const BookingFormContext = React.createContext(null);
|
|
59
|
+
/**
|
|
60
|
+
* Hook to access BookingForm context
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
function useBookingFormContext() {
|
|
64
|
+
const context = React.useContext(BookingFormContext);
|
|
65
|
+
if (!context) {
|
|
66
|
+
throw new Error('useBookingFormContext must be used within a BookingForm.Root component');
|
|
67
|
+
}
|
|
68
|
+
return context;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Fields component that renders Form.Fields with the merged field map.
|
|
72
|
+
* Must be used within BookingForm.Root.
|
|
73
|
+
*
|
|
74
|
+
* @component
|
|
75
|
+
* @example
|
|
76
|
+
* ```tsx
|
|
77
|
+
* <BookingForm.Root formId="your-form-id">
|
|
78
|
+
* <Form.Loading className="p-4" />
|
|
79
|
+
* <BookingForm.Fields rowGapClassname="gap-y-6" />
|
|
80
|
+
* <Form.Error className="text-destructive" />
|
|
81
|
+
* </BookingForm.Root>
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const Fields = React.forwardRef((props, ref) => {
|
|
85
|
+
const context = useBookingFormContext();
|
|
86
|
+
const { rowGapClassname = context.rowGapClassname, columnGapClassname = context.columnGapClassname, className, ...otherProps } = props;
|
|
87
|
+
return (_jsx("div", { ref: ref, className: className, "data-testid": TestIds.bookingFormFields, ...otherProps, children: _jsx(Form.Fields, { fieldMap: context.fields, rowGapClassname: rowGapClassname, columnGapClassname: columnGapClassname }) }));
|
|
88
|
+
});
|
|
89
|
+
Fields.displayName = 'BookingForm.Fields';
|
|
90
|
+
/**
|
|
91
|
+
* Data component that provides booking form data via render props.
|
|
92
|
+
* Use this when you need programmatic access to form state and actions.
|
|
93
|
+
*
|
|
94
|
+
* @component
|
|
95
|
+
* @example
|
|
96
|
+
* ```tsx
|
|
97
|
+
* <BookingForm.Data formId="your-form-id">
|
|
98
|
+
* {({ formId, fields, formValues, setFormSubmission, validateFormSubmission }) => (
|
|
99
|
+
* <div>
|
|
100
|
+
* <Form.Fields fieldMap={fields} rowGapClassname="gap-y-4" columnGapClassname="gap-x-2" />
|
|
101
|
+
* <button onClick={async () => {
|
|
102
|
+
* const result = await validateFormSubmission();
|
|
103
|
+
* if (result.valid) {
|
|
104
|
+
* // Store current form values before submission
|
|
105
|
+
* setFormSubmission(formValues);
|
|
106
|
+
* }
|
|
107
|
+
* }}>
|
|
108
|
+
* Validate & Submit
|
|
109
|
+
* </button>
|
|
110
|
+
* </div>
|
|
111
|
+
* )}
|
|
112
|
+
* </BookingForm.Data>
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export function Data(props) {
|
|
116
|
+
const { formId, additionalMetadata, fieldMap, children } = props;
|
|
117
|
+
return (_jsx(CoreBookingForm.Root, { formId: formId, additionalMetadata: additionalMetadata, fieldMap: fieldMap, children: ({ formId: id, fields, formValues, formSubmission, setFormSubmission, validateFormSubmission, }) => children({
|
|
118
|
+
formId: id,
|
|
119
|
+
fields,
|
|
120
|
+
formValues,
|
|
121
|
+
formSubmission,
|
|
122
|
+
setFormSubmission,
|
|
123
|
+
validateFormSubmission,
|
|
124
|
+
}) }));
|
|
125
|
+
}
|
|
126
|
+
Data.displayName = 'BookingForm.Data';
|