@wix/headless-bookings 0.0.23 → 0.0.24
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/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/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,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core BookingForm Component
|
|
3
|
+
*
|
|
4
|
+
* Provides low-level access to booking form functionality via render props.
|
|
5
|
+
* Wraps CoreForm.Root and provides merged field map with booking defaults.
|
|
6
|
+
*
|
|
7
|
+
* Supports two modes:
|
|
8
|
+
* 1. Client-side: Provide formId to load the form dynamically
|
|
9
|
+
* 2. SSR/SSG: Provide a pre-loaded form object for server-side rendering
|
|
10
|
+
*/
|
|
11
|
+
import * as React from 'react';
|
|
12
|
+
import type { FormValues } from '@wix/form-public';
|
|
13
|
+
import { forms } from '@wix/forms';
|
|
14
|
+
import { type ValidationResult } from '../../../services/booking-form/booking-form.js';
|
|
15
|
+
import { Form } from '@wix/headless-forms/react';
|
|
16
|
+
type FieldMap = Parameters<typeof Form.Fields>[0]['fieldMap'];
|
|
17
|
+
/**
|
|
18
|
+
* Render props data provided to BookingForm children
|
|
19
|
+
*/
|
|
20
|
+
export interface BookingFormRenderProps {
|
|
21
|
+
/** The form ID being used */
|
|
22
|
+
formId: string;
|
|
23
|
+
/** Current form values from FormService (live state of form fields) */
|
|
24
|
+
formValues: FormValues;
|
|
25
|
+
/** Current form submission data (values stored before submission) */
|
|
26
|
+
formSubmission: FormValues | null;
|
|
27
|
+
/** Action to store form submission data */
|
|
28
|
+
setFormSubmission: (formValues: FormValues) => void;
|
|
29
|
+
/** Action to validate the form */
|
|
30
|
+
validateFormSubmission: () => Promise<ValidationResult>;
|
|
31
|
+
/** Merged field map (DEFAULT_BOOKING_FIELD_MAP + user overrides) */
|
|
32
|
+
fields: FieldMap;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Base props shared by all BookingForm config patterns
|
|
36
|
+
*/
|
|
37
|
+
interface BookingFormBaseProps {
|
|
38
|
+
/** Optional additional metadata to pass to FormService */
|
|
39
|
+
additionalMetadata?: Record<string, string | string[]>;
|
|
40
|
+
/** Optional partial field map to override default booking field components */
|
|
41
|
+
fieldMap?: Partial<FieldMap>;
|
|
42
|
+
/** Render prop function that receives form data and actions */
|
|
43
|
+
children: (data: BookingFormRenderProps) => React.ReactNode;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Props for BookingForm with formId (client-side loading)
|
|
47
|
+
*/
|
|
48
|
+
interface BookingFormWithFormIdProps extends BookingFormBaseProps {
|
|
49
|
+
/** The form ID to load (client-side loading) */
|
|
50
|
+
formId: string;
|
|
51
|
+
/** Pre-loaded form - not used in this mode */
|
|
52
|
+
form?: never;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Props for BookingForm with pre-loaded form (SSR/SSG)
|
|
56
|
+
*/
|
|
57
|
+
interface BookingFormWithFormProps extends BookingFormBaseProps {
|
|
58
|
+
/** Pre-loaded form object (SSR/SSG) - must be from bookings namespace */
|
|
59
|
+
form: forms.Form;
|
|
60
|
+
/** Form ID - not used in this mode (extracted from form) */
|
|
61
|
+
formId?: never;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Props for BookingForm render prop component.
|
|
65
|
+
* Supports two patterns:
|
|
66
|
+
* 1. Client-side: Provide formId to load form dynamically
|
|
67
|
+
* 2. SSR/SSG: Provide pre-loaded form object
|
|
68
|
+
*/
|
|
69
|
+
export type BookingFormProps = BookingFormWithFormIdProps | BookingFormWithFormProps;
|
|
70
|
+
/**
|
|
71
|
+
* Core component that provides booking form data via render props.
|
|
72
|
+
* Uses BookingFormService to manage form submission state.
|
|
73
|
+
*
|
|
74
|
+
* Supports two modes:
|
|
75
|
+
* 1. Client-side: Provide formId to load the form dynamically
|
|
76
|
+
* 2. SSR/SSG: Provide a pre-loaded form object
|
|
77
|
+
*
|
|
78
|
+
* This component:
|
|
79
|
+
* - Sets up both FormService and BookingFormService in the same context
|
|
80
|
+
* - BookingFormService internally accesses FormService for formValues
|
|
81
|
+
* - Merges user's fieldMap with DEFAULT_BOOKING_FIELD_MAP
|
|
82
|
+
* - Provides form data and actions via render props
|
|
83
|
+
* - Form.* components (Form.Fields, Form.Loading, etc.) work because FormService is in context
|
|
84
|
+
*
|
|
85
|
+
* @component
|
|
86
|
+
* @example
|
|
87
|
+
* ```tsx
|
|
88
|
+
* // Pattern 1: Client-side loading
|
|
89
|
+
* import * as CoreBookingForm from '@wix/headless-bookings/react/core/booking-form';
|
|
90
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
91
|
+
*
|
|
92
|
+
* <CoreBookingForm.Root formId="form-123">
|
|
93
|
+
* {({ formId, setFormSubmission, fields }) => (
|
|
94
|
+
* <Form.Fields
|
|
95
|
+
* fieldMap={fields}
|
|
96
|
+
* rowGapClassname="gap-y-4"
|
|
97
|
+
* columnGapClassname="gap-x-2"
|
|
98
|
+
* />
|
|
99
|
+
* )}
|
|
100
|
+
* </CoreBookingForm.Root>
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```tsx
|
|
105
|
+
* // Pattern 2: SSR with pre-loaded form
|
|
106
|
+
* import * as CoreBookingForm from '@wix/headless-bookings/react/core/booking-form';
|
|
107
|
+
* import { loadBookingFormConfig } from '@wix/headless-bookings/services';
|
|
108
|
+
*
|
|
109
|
+
* // Server-side: Load form
|
|
110
|
+
* const { form } = await loadBookingFormConfig('form-123');
|
|
111
|
+
*
|
|
112
|
+
* // Client-side: Render with pre-loaded form
|
|
113
|
+
* <CoreBookingForm.Root form={form}>
|
|
114
|
+
* {({ formId, setFormSubmission, fields }) => (
|
|
115
|
+
* <Form.Fields fieldMap={fields} />
|
|
116
|
+
* )}
|
|
117
|
+
* </CoreBookingForm.Root>
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```tsx
|
|
122
|
+
* // With custom field overrides and saving form values
|
|
123
|
+
* <CoreBookingForm.Root
|
|
124
|
+
* formId="form-123"
|
|
125
|
+
* fieldMap={{ TEXT_INPUT: MyCustomTextInput }}
|
|
126
|
+
* >
|
|
127
|
+
* {({ fields, formValues, setFormSubmission }) => (
|
|
128
|
+
* <>
|
|
129
|
+
* <Form.Fields fieldMap={fields} rowGapClassname="gap-y-4" columnGapClassname="gap-x-2" />
|
|
130
|
+
* <button onClick={() => setFormSubmission(formValues)}>
|
|
131
|
+
* Save
|
|
132
|
+
* </button>
|
|
133
|
+
* </>
|
|
134
|
+
* )}
|
|
135
|
+
* </CoreBookingForm.Root>
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export declare function Root(props: BookingFormProps): React.ReactNode;
|
|
139
|
+
export {};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { WixServices, useService } from '@wix/services-manager-react';
|
|
4
|
+
import { createServicesMap } from '@wix/services-manager';
|
|
5
|
+
import { BookingFormServiceDefinition, BookingFormService, BOOKING_FORM_NAMESPACE, } from '../../../services/booking-form/booking-form.js';
|
|
6
|
+
import { FormServiceDefinition, FormService, } from '@wix/headless-forms/services';
|
|
7
|
+
// TODO: remove this when form will be updated to support optional field map
|
|
8
|
+
const DEFAULT_BOOKING_FIELD_MAP = {
|
|
9
|
+
TEXT_INPUT: () => null,
|
|
10
|
+
TEXT_AREA: () => null,
|
|
11
|
+
PHONE_INPUT: () => null,
|
|
12
|
+
EMAIL_INPUT: () => null,
|
|
13
|
+
NUMBER_INPUT: () => null,
|
|
14
|
+
CHECKBOX: () => null,
|
|
15
|
+
DROPDOWN: () => null,
|
|
16
|
+
DATE_PICKER: () => null,
|
|
17
|
+
SUBMIT_BUTTON: () => null,
|
|
18
|
+
RADIO_GROUP: () => null,
|
|
19
|
+
CHECKBOX_GROUP: () => null,
|
|
20
|
+
DATE_INPUT: () => null,
|
|
21
|
+
DATE_TIME_INPUT: () => null,
|
|
22
|
+
TIME_INPUT: () => null,
|
|
23
|
+
FILE_UPLOAD: () => null,
|
|
24
|
+
SIGNATURE: () => null,
|
|
25
|
+
TEXT: () => null,
|
|
26
|
+
MULTILINE_ADDRESS: () => null,
|
|
27
|
+
RATING_INPUT: () => null,
|
|
28
|
+
TAGS: () => null,
|
|
29
|
+
PRODUCT_LIST: () => null,
|
|
30
|
+
FIXED_PAYMENT: () => null,
|
|
31
|
+
PAYMENT_INPUT: () => null,
|
|
32
|
+
DONATION: () => null,
|
|
33
|
+
APPOINTMENT: () => null,
|
|
34
|
+
IMAGE_CHOICE: () => null,
|
|
35
|
+
};
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Component
|
|
38
|
+
// ============================================================================
|
|
39
|
+
/**
|
|
40
|
+
* Extracts the form ID from props (either directly or from form object)
|
|
41
|
+
*/
|
|
42
|
+
function extractFormIdFromProps(props) {
|
|
43
|
+
if ('formId' in props && props.formId) {
|
|
44
|
+
return props.formId;
|
|
45
|
+
}
|
|
46
|
+
if ('form' in props && props.form) {
|
|
47
|
+
const form = props.form;
|
|
48
|
+
const formId = form.id ||
|
|
49
|
+
form._id;
|
|
50
|
+
if (!formId) {
|
|
51
|
+
throw new Error('BookingForm: Could not extract form ID from provided form object.');
|
|
52
|
+
}
|
|
53
|
+
return formId;
|
|
54
|
+
}
|
|
55
|
+
throw new Error('BookingForm: Either formId or form must be provided.');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Core component that provides booking form data via render props.
|
|
59
|
+
* Uses BookingFormService to manage form submission state.
|
|
60
|
+
*
|
|
61
|
+
* Supports two modes:
|
|
62
|
+
* 1. Client-side: Provide formId to load the form dynamically
|
|
63
|
+
* 2. SSR/SSG: Provide a pre-loaded form object
|
|
64
|
+
*
|
|
65
|
+
* This component:
|
|
66
|
+
* - Sets up both FormService and BookingFormService in the same context
|
|
67
|
+
* - BookingFormService internally accesses FormService for formValues
|
|
68
|
+
* - Merges user's fieldMap with DEFAULT_BOOKING_FIELD_MAP
|
|
69
|
+
* - Provides form data and actions via render props
|
|
70
|
+
* - Form.* components (Form.Fields, Form.Loading, etc.) work because FormService is in context
|
|
71
|
+
*
|
|
72
|
+
* @component
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* // Pattern 1: Client-side loading
|
|
76
|
+
* import * as CoreBookingForm from '@wix/headless-bookings/react/core/booking-form';
|
|
77
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
78
|
+
*
|
|
79
|
+
* <CoreBookingForm.Root formId="form-123">
|
|
80
|
+
* {({ formId, setFormSubmission, fields }) => (
|
|
81
|
+
* <Form.Fields
|
|
82
|
+
* fieldMap={fields}
|
|
83
|
+
* rowGapClassname="gap-y-4"
|
|
84
|
+
* columnGapClassname="gap-x-2"
|
|
85
|
+
* />
|
|
86
|
+
* )}
|
|
87
|
+
* </CoreBookingForm.Root>
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```tsx
|
|
92
|
+
* // Pattern 2: SSR with pre-loaded form
|
|
93
|
+
* import * as CoreBookingForm from '@wix/headless-bookings/react/core/booking-form';
|
|
94
|
+
* import { loadBookingFormConfig } from '@wix/headless-bookings/services';
|
|
95
|
+
*
|
|
96
|
+
* // Server-side: Load form
|
|
97
|
+
* const { form } = await loadBookingFormConfig('form-123');
|
|
98
|
+
*
|
|
99
|
+
* // Client-side: Render with pre-loaded form
|
|
100
|
+
* <CoreBookingForm.Root form={form}>
|
|
101
|
+
* {({ formId, setFormSubmission, fields }) => (
|
|
102
|
+
* <Form.Fields fieldMap={fields} />
|
|
103
|
+
* )}
|
|
104
|
+
* </CoreBookingForm.Root>
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* // With custom field overrides and saving form values
|
|
110
|
+
* <CoreBookingForm.Root
|
|
111
|
+
* formId="form-123"
|
|
112
|
+
* fieldMap={{ TEXT_INPUT: MyCustomTextInput }}
|
|
113
|
+
* >
|
|
114
|
+
* {({ fields, formValues, setFormSubmission }) => (
|
|
115
|
+
* <>
|
|
116
|
+
* <Form.Fields fieldMap={fields} rowGapClassname="gap-y-4" columnGapClassname="gap-x-2" />
|
|
117
|
+
* <button onClick={() => setFormSubmission(formValues)}>
|
|
118
|
+
* Save
|
|
119
|
+
* </button>
|
|
120
|
+
* </>
|
|
121
|
+
* )}
|
|
122
|
+
* </CoreBookingForm.Root>
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export function Root(props) {
|
|
126
|
+
const { additionalMetadata, fieldMap: userFieldMap, children } = props;
|
|
127
|
+
// Determine if we have a pre-loaded form
|
|
128
|
+
const hasPreloadedForm = 'form' in props && props.form;
|
|
129
|
+
const preloadedForm = hasPreloadedForm ? props.form : null;
|
|
130
|
+
// Extract formId from props
|
|
131
|
+
const formId = extractFormIdFromProps(props);
|
|
132
|
+
// Merge user's partial field map with booking defaults
|
|
133
|
+
const fields = useMemo(() => ({
|
|
134
|
+
// ...DEFAULT_BOOKING_FIELD_MAP, // TODO: Add default field map
|
|
135
|
+
...userFieldMap,
|
|
136
|
+
}), [userFieldMap]);
|
|
137
|
+
// Build formServiceConfig for FormService
|
|
138
|
+
// If we have a pre-loaded form, pass it directly; otherwise pass formId
|
|
139
|
+
const formServiceConfig = useMemo(() => {
|
|
140
|
+
if (preloadedForm) {
|
|
141
|
+
return { form: preloadedForm };
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
formId,
|
|
145
|
+
namespace: BOOKING_FORM_NAMESPACE,
|
|
146
|
+
...(additionalMetadata && { additionalMetadata }),
|
|
147
|
+
};
|
|
148
|
+
}, [formId, additionalMetadata, preloadedForm]);
|
|
149
|
+
// Build bookingFormServiceConfig
|
|
150
|
+
const bookingFormServiceConfig = useMemo(() => {
|
|
151
|
+
if (preloadedForm) {
|
|
152
|
+
return { form: preloadedForm };
|
|
153
|
+
}
|
|
154
|
+
return { formId };
|
|
155
|
+
}, [formId, preloadedForm]);
|
|
156
|
+
// Both FormService and BookingFormService are added to the same WixServices context
|
|
157
|
+
// This allows BookingFormService to access FormService via getService internally
|
|
158
|
+
return (_jsx(WixServices, { servicesMap: createServicesMap()
|
|
159
|
+
.addService(FormServiceDefinition, FormService, formServiceConfig)
|
|
160
|
+
.addService(BookingFormServiceDefinition, BookingFormService, bookingFormServiceConfig), children: _jsx(BookingFormContent, { formId: formId, fields: { ...DEFAULT_BOOKING_FIELD_MAP, ...fields }, children: children }) }));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Internal component that consumes BookingFormService and provides render props.
|
|
164
|
+
* Separated to ensure useService is called within the WixServices context.
|
|
165
|
+
*
|
|
166
|
+
* Note: BookingFormService internally accesses FormService to get formValues,
|
|
167
|
+
* so we only need to consume BookingFormService here.
|
|
168
|
+
*/
|
|
169
|
+
function BookingFormContent({ formId, fields, children, }) {
|
|
170
|
+
const bookingFormService = useService(BookingFormServiceDefinition);
|
|
171
|
+
const formSubmission = bookingFormService.formSubmission.get();
|
|
172
|
+
const formValues = bookingFormService.formValues.get();
|
|
173
|
+
return children({
|
|
174
|
+
formId,
|
|
175
|
+
formValues,
|
|
176
|
+
formSubmission,
|
|
177
|
+
setFormSubmission: bookingFormService.setFormSubmission,
|
|
178
|
+
validateFormSubmission: bookingFormService.validateFormSubmission,
|
|
179
|
+
fields,
|
|
180
|
+
});
|
|
181
|
+
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * as Booking from './booking/Booking.js';
|
|
2
|
+
export * as BookingForm from './booking-form/BookingForm.js';
|
|
2
3
|
export * as Service from './service/Service.js';
|
|
3
4
|
export * as TimeSlotList from './time-slot-list/TimeSlotList.js';
|
|
4
5
|
export * as ServiceList from './service-list/ServiceList.js';
|
package/dist/react/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * as Booking from './booking/Booking.js';
|
|
2
|
+
export * as BookingForm from './booking-form/BookingForm.js';
|
|
2
3
|
export * as Service from './service/Service.js';
|
|
3
4
|
export * as TimeSlotList from './time-slot-list/TimeSlotList.js';
|
|
4
5
|
export * as ServiceList from './service-list/ServiceList.js';
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BookingForm Service - Manages form submission state for booking flows
|
|
3
|
+
*
|
|
4
|
+
* This service supports two modes:
|
|
5
|
+
* 1. Client-side: Provide formId to load the form dynamically
|
|
6
|
+
* 2. SSR/SSG: Provide a pre-loaded form object for server-side rendering
|
|
7
|
+
*
|
|
8
|
+
* When using a pre-loaded form, it must belong to the bookings namespace.
|
|
9
|
+
*/
|
|
10
|
+
import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
|
|
11
|
+
import type { FormValues } from '@wix/form-public';
|
|
12
|
+
import { forms } from '@wix/forms';
|
|
13
|
+
/**
|
|
14
|
+
* The required namespace for booking forms.
|
|
15
|
+
* All booking forms must belong to this namespace.
|
|
16
|
+
*/
|
|
17
|
+
export declare const BOOKING_FORM_NAMESPACE = "wix.bookings.v2.bookings";
|
|
18
|
+
/**
|
|
19
|
+
* Validation result type for form validation.
|
|
20
|
+
* Fields with errors will be marked according to the validation failures.
|
|
21
|
+
*/
|
|
22
|
+
export interface ValidationResult {
|
|
23
|
+
/** Whether the form is valid */
|
|
24
|
+
valid: boolean;
|
|
25
|
+
/** Array of validation failure messages */
|
|
26
|
+
validationFailures: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Configuration for BookingFormService.
|
|
30
|
+
* Supports two modes:
|
|
31
|
+
* 1. Client-side loading: Provide formId to fetch the form dynamically
|
|
32
|
+
* 2. SSR/SSG: Provide a pre-loaded form object (must be from bookings namespace)
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // Pattern 1: Client-side loading
|
|
37
|
+
* const config = { formId: 'form-123' };
|
|
38
|
+
*
|
|
39
|
+
* // Pattern 2: Pre-loaded form (SSR/SSG)
|
|
40
|
+
* const config = { form: preloadedForm };
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export type BookingFormServiceConfig = {
|
|
44
|
+
/** The form ID to load (client-side loading) */
|
|
45
|
+
formId: string;
|
|
46
|
+
} | {
|
|
47
|
+
/** Pre-loaded form object (SSR/SSG) - must be from bookings namespace */
|
|
48
|
+
form: forms.Form;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* API interface for the BookingForm service.
|
|
52
|
+
* Manages form submission state and validation for booking forms.
|
|
53
|
+
*
|
|
54
|
+
* Supports two modes:
|
|
55
|
+
* - Client-side: formId is provided via config, form is loaded dynamically
|
|
56
|
+
* - SSR/SSG: Pre-loaded form is provided via config
|
|
57
|
+
*/
|
|
58
|
+
export interface BookingFormServiceAPI {
|
|
59
|
+
/** Signal containing the current form values from FormService (live state of form fields) */
|
|
60
|
+
formValues: ReadOnlySignal<FormValues>;
|
|
61
|
+
/** Signal containing form submission data (set before actual submission) */
|
|
62
|
+
formSubmission: Signal<FormValues | null>;
|
|
63
|
+
/** Submitted form ID (won't be supported on first phase) */
|
|
64
|
+
formSubmissionId: ReadOnlySignal<string | null>;
|
|
65
|
+
/** Signal containing the form ID (from config or extracted from form) */
|
|
66
|
+
formId: ReadOnlySignal<string>;
|
|
67
|
+
/** Signal containing the pre-loaded form (if provided via SSR) */
|
|
68
|
+
form: ReadOnlySignal<forms.Form | null>;
|
|
69
|
+
/** Action to store form submission data */
|
|
70
|
+
setFormSubmission: (formValues: FormValues) => void;
|
|
71
|
+
/** Action to validate form - internally delegates to Form.Fields ref */
|
|
72
|
+
validateFormSubmission: () => Promise<ValidationResult>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Internal API interface that extends public API.
|
|
76
|
+
* Contains methods for internal use by core components.
|
|
77
|
+
*/
|
|
78
|
+
export interface BookingFormServiceInternalAPI extends BookingFormServiceAPI {
|
|
79
|
+
/** Internal: Register validate callback (called by core component) */
|
|
80
|
+
_setValidateCallback: (callback: () => Promise<ValidationResult>) => void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Type guard to check if config has formId
|
|
84
|
+
*/
|
|
85
|
+
export declare function hasFormId(config: BookingFormServiceConfig): config is {
|
|
86
|
+
formId: string;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Type guard to check if config has pre-loaded form
|
|
90
|
+
*/
|
|
91
|
+
export declare function hasForm(config: BookingFormServiceConfig): config is {
|
|
92
|
+
form: forms.Form;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Service definition for BookingForm.
|
|
96
|
+
* Defines the contract that the BookingFormService must implement.
|
|
97
|
+
*/
|
|
98
|
+
export declare const BookingFormServiceDefinition: string & {
|
|
99
|
+
__api: BookingFormServiceAPI;
|
|
100
|
+
__config: {};
|
|
101
|
+
isServiceDefinition?: boolean;
|
|
102
|
+
} & BookingFormServiceAPI;
|
|
103
|
+
/**
|
|
104
|
+
* Implementation of BookingFormService.
|
|
105
|
+
*
|
|
106
|
+
* This service manages form submission state for booking flows.
|
|
107
|
+
* Supports two modes:
|
|
108
|
+
* 1. Client-side: formId is provided, form is loaded dynamically
|
|
109
|
+
* 2. SSR/SSG: Pre-loaded form is provided (must be from bookings namespace)
|
|
110
|
+
*
|
|
111
|
+
* NOTE: The Form.Fields ref is managed internally - the core component passes
|
|
112
|
+
* a validate callback that the service stores and calls when validateFormSubmission() is invoked.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```tsx
|
|
116
|
+
* // Pattern 1: Client-side loading
|
|
117
|
+
* const bookingFormService = useService(
|
|
118
|
+
* BookingFormServiceDefinition.withConfig({ formId: 'form-123' })
|
|
119
|
+
* );
|
|
120
|
+
*
|
|
121
|
+
* // Pattern 2: SSR with pre-loaded form
|
|
122
|
+
* const bookingFormService = useService(
|
|
123
|
+
* BookingFormServiceDefinition.withConfig({ form: preloadedForm })
|
|
124
|
+
* );
|
|
125
|
+
*
|
|
126
|
+
* // Store form submission
|
|
127
|
+
* bookingFormService.setFormSubmission(formValues);
|
|
128
|
+
*
|
|
129
|
+
* // Validate form
|
|
130
|
+
* const result = await bookingFormService.validateFormSubmission();
|
|
131
|
+
* if (result.valid) {
|
|
132
|
+
* // Proceed with booking
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export declare const BookingFormService: import("@wix/services-definitions").ServiceFactory<string & {
|
|
137
|
+
__api: BookingFormServiceAPI;
|
|
138
|
+
__config: {};
|
|
139
|
+
isServiceDefinition?: boolean;
|
|
140
|
+
} & BookingFormServiceAPI, BookingFormServiceConfig>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BookingForm Service - Manages form submission state for booking flows
|
|
3
|
+
*
|
|
4
|
+
* This service supports two modes:
|
|
5
|
+
* 1. Client-side: Provide formId to load the form dynamically
|
|
6
|
+
* 2. SSR/SSG: Provide a pre-loaded form object for server-side rendering
|
|
7
|
+
*
|
|
8
|
+
* When using a pre-loaded form, it must belong to the bookings namespace.
|
|
9
|
+
*/
|
|
10
|
+
import { defineService, implementService } from '@wix/services-definitions';
|
|
11
|
+
import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
|
|
12
|
+
import { FormServiceDefinition } from '@wix/headless-forms/services';
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Constants
|
|
15
|
+
// ============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* The required namespace for booking forms.
|
|
18
|
+
* All booking forms must belong to this namespace.
|
|
19
|
+
*/
|
|
20
|
+
export const BOOKING_FORM_NAMESPACE = 'wix.bookings.v2.bookings';
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Helper Types
|
|
23
|
+
// ============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* Type guard to check if config has formId
|
|
26
|
+
*/
|
|
27
|
+
export function hasFormId(config) {
|
|
28
|
+
return 'formId' in config;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Type guard to check if config has pre-loaded form
|
|
32
|
+
*/
|
|
33
|
+
export function hasForm(config) {
|
|
34
|
+
return 'form' in config;
|
|
35
|
+
}
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Service Definition
|
|
38
|
+
// ============================================================================
|
|
39
|
+
/**
|
|
40
|
+
* Service definition for BookingForm.
|
|
41
|
+
* Defines the contract that the BookingFormService must implement.
|
|
42
|
+
*/
|
|
43
|
+
export const BookingFormServiceDefinition = defineService('bookingForm');
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Service Implementation
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Validation Helpers
|
|
49
|
+
// ============================================================================
|
|
50
|
+
/**
|
|
51
|
+
* Validates that a form belongs to the bookings namespace.
|
|
52
|
+
* @param form The form to validate
|
|
53
|
+
* @throws Error if the form is not from the bookings namespace
|
|
54
|
+
*/
|
|
55
|
+
function validateBookingFormNamespace(form) {
|
|
56
|
+
// Check if form has namespace property and validate it
|
|
57
|
+
// The namespace might be in different locations depending on the form structure
|
|
58
|
+
const formNamespace = form.namespace ||
|
|
59
|
+
form._namespace;
|
|
60
|
+
if (formNamespace && formNamespace !== BOOKING_FORM_NAMESPACE) {
|
|
61
|
+
throw new Error(`BookingFormService: Invalid form namespace. ` +
|
|
62
|
+
`Expected "${BOOKING_FORM_NAMESPACE}", but received "${formNamespace}". ` +
|
|
63
|
+
`Ensure the form is a booking form.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extracts the form ID from either config type.
|
|
68
|
+
* @param config The service config
|
|
69
|
+
* @returns The form ID
|
|
70
|
+
*/
|
|
71
|
+
function extractFormId(config) {
|
|
72
|
+
if ('formId' in config) {
|
|
73
|
+
return config.formId;
|
|
74
|
+
}
|
|
75
|
+
// Extract from form object - forms typically have an 'id' or '_id' property
|
|
76
|
+
const form = config.form;
|
|
77
|
+
const formId = form.id ||
|
|
78
|
+
form._id;
|
|
79
|
+
if (!formId) {
|
|
80
|
+
throw new Error('BookingFormService: Could not extract form ID from provided form object.');
|
|
81
|
+
}
|
|
82
|
+
return formId;
|
|
83
|
+
}
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// Service Implementation
|
|
86
|
+
// ============================================================================
|
|
87
|
+
/**
|
|
88
|
+
* Implementation of BookingFormService.
|
|
89
|
+
*
|
|
90
|
+
* This service manages form submission state for booking flows.
|
|
91
|
+
* Supports two modes:
|
|
92
|
+
* 1. Client-side: formId is provided, form is loaded dynamically
|
|
93
|
+
* 2. SSR/SSG: Pre-loaded form is provided (must be from bookings namespace)
|
|
94
|
+
*
|
|
95
|
+
* NOTE: The Form.Fields ref is managed internally - the core component passes
|
|
96
|
+
* a validate callback that the service stores and calls when validateFormSubmission() is invoked.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```tsx
|
|
100
|
+
* // Pattern 1: Client-side loading
|
|
101
|
+
* const bookingFormService = useService(
|
|
102
|
+
* BookingFormServiceDefinition.withConfig({ formId: 'form-123' })
|
|
103
|
+
* );
|
|
104
|
+
*
|
|
105
|
+
* // Pattern 2: SSR with pre-loaded form
|
|
106
|
+
* const bookingFormService = useService(
|
|
107
|
+
* BookingFormServiceDefinition.withConfig({ form: preloadedForm })
|
|
108
|
+
* );
|
|
109
|
+
*
|
|
110
|
+
* // Store form submission
|
|
111
|
+
* bookingFormService.setFormSubmission(formValues);
|
|
112
|
+
*
|
|
113
|
+
* // Validate form
|
|
114
|
+
* const result = await bookingFormService.validateFormSubmission();
|
|
115
|
+
* if (result.valid) {
|
|
116
|
+
* // Proceed with booking
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export const BookingFormService = implementService.withConfig()(BookingFormServiceDefinition, ({ getService, config }) => {
|
|
121
|
+
const signalsService = getService(SignalsServiceDefinition);
|
|
122
|
+
const formService = getService(FormServiceDefinition);
|
|
123
|
+
// Determine if we have a pre-loaded form
|
|
124
|
+
const hasPreloadedForm = 'form' in config;
|
|
125
|
+
// If form is provided, validate namespace
|
|
126
|
+
if (hasPreloadedForm) {
|
|
127
|
+
validateBookingFormNamespace(config.form);
|
|
128
|
+
}
|
|
129
|
+
// Extract formId from config (either directly or from form object)
|
|
130
|
+
const formIdValue = extractFormId(config);
|
|
131
|
+
// Initialize formSubmission signal (stores form data to be submitted)
|
|
132
|
+
const formSubmission = signalsService.signal(null);
|
|
133
|
+
// Initialize submissionId signal (for future use)
|
|
134
|
+
const formSubmissionId = signalsService.signal(null);
|
|
135
|
+
// formId signal - readonly
|
|
136
|
+
const formId = signalsService.signal(formIdValue);
|
|
137
|
+
// form signal - contains pre-loaded form if provided
|
|
138
|
+
const form = signalsService.signal(hasPreloadedForm ? config.form : null);
|
|
139
|
+
// Internal: Store validate callback (set by core component, not exposed publicly)
|
|
140
|
+
let _validateCallback = null;
|
|
141
|
+
// Internal: Register validate callback (called by core component via closure)
|
|
142
|
+
const _setValidateCallback = (callback) => {
|
|
143
|
+
_validateCallback = callback;
|
|
144
|
+
};
|
|
145
|
+
// Action to set form submission data
|
|
146
|
+
const setFormSubmission = (formValues) => {
|
|
147
|
+
formSubmission.set(formValues);
|
|
148
|
+
};
|
|
149
|
+
// Action to validate form - delegates to internally registered callback
|
|
150
|
+
const validateFormSubmission = async () => {
|
|
151
|
+
if (!_validateCallback) {
|
|
152
|
+
console.error('BookingFormService: Validate callback not registered. ' +
|
|
153
|
+
'Ensure BookingForm component is mounted.');
|
|
154
|
+
return { valid: false, validationFailures: ['Form not initialized'] };
|
|
155
|
+
}
|
|
156
|
+
return _validateCallback();
|
|
157
|
+
};
|
|
158
|
+
// Public API (what's exposed in BookingFormServiceAPI interface)
|
|
159
|
+
const publicApi = {
|
|
160
|
+
formValues: formService.formValuesSignal,
|
|
161
|
+
formSubmission,
|
|
162
|
+
formSubmissionId,
|
|
163
|
+
formId,
|
|
164
|
+
form,
|
|
165
|
+
setFormSubmission,
|
|
166
|
+
validateFormSubmission,
|
|
167
|
+
// Internal method for core component access
|
|
168
|
+
_setValidateCallback,
|
|
169
|
+
};
|
|
170
|
+
return publicApi;
|
|
171
|
+
});
|
package/dist/services/index.d.ts
CHANGED
|
@@ -6,3 +6,4 @@ export { ServiceService, ServiceServiceDefinition, type ServiceServiceAPI, type
|
|
|
6
6
|
export { BookingService, BookingServiceDefinition, type BookingServiceConfig, type Location, type BookingServiceActions, type ServiceSelection, type ServiceSelectionTimeSlot, } from './booking/booking.js';
|
|
7
7
|
export { TimeSlotListService, TimeSlotListServiceDefinition, ViewMode, loadTimeSlotListConfig, type TimeSlotListServiceAPI, type TimeSlotListServiceActions, type TimeSlotListServiceConfig, type SlotSelection, type TimeSlot, type LoadTimeSlotListConfigOptions, type LoadTimeSlotListConfigResult, } from './time-slot-list/index.js';
|
|
8
8
|
export { ServiceListService, ServiceListServiceDefinition, type ServiceListServiceAPI, type ServiceListServiceConfig, type ServiceListFilters, type ServiceListActions, type QueryOptions, loadServiceListServiceInitialData, type SuccessServiceListServiceConfigResult, type ErrorServiceListServiceConfigResult, } from './service-list/service-list.js';
|
|
9
|
+
export { BookingFormService, BookingFormServiceDefinition, type BookingFormServiceAPI, type BookingFormServiceConfig, type BookingFormServiceInternalAPI, type ValidationResult, } from './booking-form/booking-form.js';
|
package/dist/services/index.js
CHANGED
|
@@ -6,3 +6,4 @@ export { ServiceService, ServiceServiceDefinition, loadServiceServiceInitialData
|
|
|
6
6
|
export { BookingService, BookingServiceDefinition, } from './booking/booking.js';
|
|
7
7
|
export { TimeSlotListService, TimeSlotListServiceDefinition, ViewMode, loadTimeSlotListConfig, } from './time-slot-list/index.js';
|
|
8
8
|
export { ServiceListService, ServiceListServiceDefinition, loadServiceListServiceInitialData, } from './service-list/service-list.js';
|
|
9
|
+
export { BookingFormService, BookingFormServiceDefinition, } from './booking-form/booking-form.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-bookings",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@wix/auto_sdk_bookings_event-time-slots": "^1.0.64",
|
|
37
37
|
"@wix/bookings": "^1.0.122",
|
|
38
38
|
"@wix/form-public": "^0.64.0",
|
|
39
|
+
"@wix/headless-forms": "0.0.20",
|
|
39
40
|
"@wix/headless-media": "0.0.18",
|
|
40
41
|
"@wix/headless-utils": "0.0.8",
|
|
41
42
|
"@wix/services-definitions": "^0.1.4",
|
|
@@ -70,5 +71,5 @@
|
|
|
70
71
|
"groupId": "com.wixpress.headless-components"
|
|
71
72
|
}
|
|
72
73
|
},
|
|
73
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "54198290a5192d2c6af4f5640d150d453c2d0c586edd1b238a1164d0"
|
|
74
75
|
}
|