@flamingo-stack/openframe-frontend-core 0.0.594 → 0.0.595-snapshot.20260905150417
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/dist/{chunk-HQWZ4IXU.cjs → chunk-3CH6VRJX.cjs} +24 -24
- package/dist/{chunk-HQWZ4IXU.cjs.map → chunk-3CH6VRJX.cjs.map} +1 -1
- package/dist/{chunk-LKN2KKYZ.js → chunk-4K7TCJ35.js} +6 -59
- package/dist/{chunk-LKN2KKYZ.js.map → chunk-4K7TCJ35.js.map} +1 -1
- package/dist/{chunk-YLVPQNLO.cjs → chunk-5MSHWXJ5.cjs} +170 -223
- package/dist/chunk-5MSHWXJ5.cjs.map +1 -0
- package/dist/{chunk-CCLPJSY3.cjs → chunk-73BECFSU.cjs} +15 -15
- package/dist/{chunk-CCLPJSY3.cjs.map → chunk-73BECFSU.cjs.map} +1 -1
- package/dist/{chunk-F5Q3DTP5.js → chunk-7EUE3YTC.js} +8 -8
- package/dist/{chunk-GBIC3VNA.js → chunk-7KGN7DV6.js} +6 -6
- package/dist/{chunk-YZLUOZKR.js → chunk-7Q66A4CF.js} +2 -2
- package/dist/{chunk-DRZ224RU.js → chunk-ANSZVKI2.js} +8 -8
- package/dist/{chunk-N3RHX26N.js → chunk-AZZVSEP5.js} +3 -3
- package/dist/{chunk-66I2QUFE.cjs → chunk-B26UT36N.cjs} +6 -6
- package/dist/{chunk-66I2QUFE.cjs.map → chunk-B26UT36N.cjs.map} +1 -1
- package/dist/{chunk-UBRVIVY4.cjs → chunk-B2CSYMKW.cjs} +298 -285
- package/dist/chunk-B2CSYMKW.cjs.map +1 -0
- package/dist/{chunk-ZK3AZ5YF.cjs → chunk-BTOL222V.cjs} +11 -11
- package/dist/{chunk-ZK3AZ5YF.cjs.map → chunk-BTOL222V.cjs.map} +1 -1
- package/dist/{chunk-S7VW4HVL.cjs → chunk-DODE3PD4.cjs} +49 -49
- package/dist/{chunk-S7VW4HVL.cjs.map → chunk-DODE3PD4.cjs.map} +1 -1
- package/dist/{chunk-ZZHHKE2Q.js → chunk-ECXQX4EP.js} +2 -2
- package/dist/{chunk-NEFNARFF.cjs → chunk-EEV7SSBJ.cjs} +30 -30
- package/dist/{chunk-NEFNARFF.cjs.map → chunk-EEV7SSBJ.cjs.map} +1 -1
- package/dist/{chunk-3OQIZPUX.js → chunk-EO3NEGWB.js} +4 -4
- package/dist/{chunk-MOHSA65M.js → chunk-EZM5DNII.js} +2 -2
- package/dist/{chunk-IKQVXZB5.js → chunk-GSPS6Y5U.js} +6 -6
- package/dist/{chunk-CQ35OMPJ.cjs → chunk-I2BNLWC3.cjs} +3 -3
- package/dist/{chunk-CQ35OMPJ.cjs.map → chunk-I2BNLWC3.cjs.map} +1 -1
- package/dist/{chunk-HOLK4ZMT.js → chunk-II2GBWNW.js} +4 -4
- package/dist/{chunk-PETYVGEQ.cjs → chunk-JKGVXLTT.cjs} +5 -5
- package/dist/{chunk-PETYVGEQ.cjs.map → chunk-JKGVXLTT.cjs.map} +1 -1
- package/dist/{chunk-5EM6NGBK.cjs → chunk-KOQCA5X5.cjs} +17 -27
- package/dist/chunk-KOQCA5X5.cjs.map +1 -0
- package/dist/{chunk-ACU7J5BL.cjs → chunk-KPE7OQF6.cjs} +84 -84
- package/dist/{chunk-ACU7J5BL.cjs.map → chunk-KPE7OQF6.cjs.map} +1 -1
- package/dist/{chunk-C4YJC5X7.cjs → chunk-KRKW63LG.cjs} +9 -9
- package/dist/{chunk-C4YJC5X7.cjs.map → chunk-KRKW63LG.cjs.map} +1 -1
- package/dist/{chunk-ZEG3ZJPY.cjs → chunk-LE5SZPMN.cjs} +33 -33
- package/dist/{chunk-ZEG3ZJPY.cjs.map → chunk-LE5SZPMN.cjs.map} +1 -1
- package/dist/{chunk-A5AKGC4S.js → chunk-LPMDCQLL.js} +17 -27
- package/dist/chunk-LPMDCQLL.js.map +1 -0
- package/dist/{chunk-EWOFXJGH.cjs → chunk-M3OQLL7D.cjs} +63 -63
- package/dist/{chunk-EWOFXJGH.cjs.map → chunk-M3OQLL7D.cjs.map} +1 -1
- package/dist/{chunk-FKXDDJEQ.cjs → chunk-M4YQUHOR.cjs} +5 -5
- package/dist/{chunk-FKXDDJEQ.cjs.map → chunk-M4YQUHOR.cjs.map} +1 -1
- package/dist/{chunk-5XQMEDGE.cjs → chunk-OHJ4QOWN.cjs} +12 -12
- package/dist/{chunk-5XQMEDGE.cjs.map → chunk-OHJ4QOWN.cjs.map} +1 -1
- package/dist/{chunk-Y6I2DGKC.cjs → chunk-OMKI5T2G.cjs} +1 -1
- package/dist/chunk-OMKI5T2G.cjs.map +1 -0
- package/dist/{chunk-M5HGULIY.js → chunk-OQODJ26H.js} +5 -5
- package/dist/{chunk-MEWAFY6X.js → chunk-OT5IQMSI.js} +4 -4
- package/dist/{chunk-Y22VQDVW.js → chunk-QB244ZDI.js} +4 -4
- package/dist/{chunk-3T7ZP3TL.js → chunk-QL6ECU6Y.js} +3 -3
- package/dist/{chunk-3XDPXOMS.js → chunk-R2SZYRB2.js} +7 -7
- package/dist/{chunk-QAVT6P5F.cjs → chunk-RFA5T5MW.cjs} +28 -28
- package/dist/{chunk-QAVT6P5F.cjs.map → chunk-RFA5T5MW.cjs.map} +1 -1
- package/dist/{chunk-BTF3CNDP.js → chunk-ROQO355A.js} +1 -1
- package/dist/chunk-ROQO355A.js.map +1 -0
- package/dist/{chunk-43ADGEWL.cjs → chunk-UJAMXLA3.cjs} +40 -40
- package/dist/{chunk-43ADGEWL.cjs.map → chunk-UJAMXLA3.cjs.map} +1 -1
- package/dist/{chunk-KVNA3HU2.js → chunk-XANQTIHS.js} +3 -3
- package/dist/{chunk-PMN7WDXI.js → chunk-YUHOKPKS.js} +26 -13
- package/dist/chunk-YUHOKPKS.js.map +1 -0
- package/dist/components/case-studies/index.cjs +16 -16
- package/dist/components/case-studies/index.js +6 -6
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +4 -4
- package/dist/components/chat/index.js +3 -3
- package/dist/components/contact/index.cjs +7 -7
- package/dist/components/contact/index.js +6 -6
- package/dist/components/docs/index.cjs +10 -10
- package/dist/components/docs/index.js +9 -9
- package/dist/components/embeds/index.cjs +7 -7
- package/dist/components/embeds/index.js +6 -6
- package/dist/components/faq/index.cjs +7 -7
- package/dist/components/faq/index.js +6 -6
- package/dist/components/features/index.cjs +6 -6
- package/dist/components/features/index.js +5 -5
- package/dist/components/help-center-pages/index.cjs +26 -26
- package/dist/components/help-center-pages/index.js +17 -17
- package/dist/components/icons/index.cjs +2 -2
- package/dist/components/icons/index.js +1 -1
- package/dist/components/icons/mlg-logo.d.ts.map +1 -1
- package/dist/components/index.cjs +56 -56
- package/dist/components/index.js +17 -17
- package/dist/components/meeting-scheduler/booking-form.d.ts +49 -5
- package/dist/components/meeting-scheduler/booking-form.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/context-panel.d.ts +9 -1
- package/dist/components/meeting-scheduler/context-panel.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +369 -186
- package/dist/components/meeting-scheduler/index.cjs.map +1 -1
- package/dist/components/meeting-scheduler/index.d.ts +55 -1
- package/dist/components/meeting-scheduler/index.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.js +344 -161
- package/dist/components/meeting-scheduler/index.js.map +1 -1
- package/dist/components/meeting-scheduler/slot-picker.d.ts +8 -1
- package/dist/components/meeting-scheduler/slot-picker.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +9 -9
- package/dist/components/navigation/index.js +8 -8
- package/dist/components/onboarding-guides/index.cjs +9 -9
- package/dist/components/onboarding-guides/index.js +8 -8
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +10 -10
- package/dist/components/tickets/index.js +9 -9
- package/dist/components/ui/benefit-card.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +6 -6
- package/dist/components/ui/index.js +5 -5
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +10 -10
- package/dist/index.js +9 -9
- package/dist/schemas/meeting-booking-schema.cjs +19 -4
- package/dist/schemas/meeting-booking-schema.cjs.map +1 -1
- package/dist/schemas/meeting-booking-schema.d.ts +41 -16
- package/dist/schemas/meeting-booking-schema.d.ts.map +1 -1
- package/dist/schemas/meeting-booking-schema.js +19 -5
- package/dist/schemas/meeting-booking-schema.js.map +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.cjs +18 -40
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +18 -40
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/source-icons.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/entity-cards/dispatch.tsx +0 -57
- package/src/components/icons/mlg-logo.tsx +1 -7
- package/src/components/meeting-scheduler/booking-form.tsx +371 -152
- package/src/components/meeting-scheduler/context-panel.tsx +13 -3
- package/src/components/meeting-scheduler/index.tsx +316 -69
- package/src/components/meeting-scheduler/slot-picker.tsx +17 -1
- package/src/components/ui/benefit-card.tsx +30 -9
- package/src/schemas/meeting-booking-schema.ts +58 -4
- package/src/types/tmcg.ts +0 -2
- package/src/utils/format.ts +1 -1
- package/src/utils/source-icons.ts +0 -24
- package/dist/chunk-5EM6NGBK.cjs.map +0 -1
- package/dist/chunk-A5AKGC4S.js.map +0 -1
- package/dist/chunk-BTF3CNDP.js.map +0 -1
- package/dist/chunk-PMN7WDXI.js.map +0 -1
- package/dist/chunk-UBRVIVY4.cjs.map +0 -1
- package/dist/chunk-Y6I2DGKC.cjs.map +0 -1
- package/dist/chunk-YLVPQNLO.cjs.map +0 -1
- /package/dist/{chunk-F5Q3DTP5.js.map → chunk-7EUE3YTC.js.map} +0 -0
- /package/dist/{chunk-GBIC3VNA.js.map → chunk-7KGN7DV6.js.map} +0 -0
- /package/dist/{chunk-YZLUOZKR.js.map → chunk-7Q66A4CF.js.map} +0 -0
- /package/dist/{chunk-DRZ224RU.js.map → chunk-ANSZVKI2.js.map} +0 -0
- /package/dist/{chunk-N3RHX26N.js.map → chunk-AZZVSEP5.js.map} +0 -0
- /package/dist/{chunk-ZZHHKE2Q.js.map → chunk-ECXQX4EP.js.map} +0 -0
- /package/dist/{chunk-3OQIZPUX.js.map → chunk-EO3NEGWB.js.map} +0 -0
- /package/dist/{chunk-MOHSA65M.js.map → chunk-EZM5DNII.js.map} +0 -0
- /package/dist/{chunk-IKQVXZB5.js.map → chunk-GSPS6Y5U.js.map} +0 -0
- /package/dist/{chunk-HOLK4ZMT.js.map → chunk-II2GBWNW.js.map} +0 -0
- /package/dist/{chunk-M5HGULIY.js.map → chunk-OQODJ26H.js.map} +0 -0
- /package/dist/{chunk-MEWAFY6X.js.map → chunk-OT5IQMSI.js.map} +0 -0
- /package/dist/{chunk-Y22VQDVW.js.map → chunk-QB244ZDI.js.map} +0 -0
- /package/dist/{chunk-3T7ZP3TL.js.map → chunk-QL6ECU6Y.js.map} +0 -0
- /package/dist/{chunk-3XDPXOMS.js.map → chunk-R2SZYRB2.js.map} +0 -0
- /package/dist/{chunk-KVNA3HU2.js.map → chunk-XANQTIHS.js.map} +0 -0
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
AlertDescription,
|
|
12
12
|
Autocomplete,
|
|
13
13
|
BackButton,
|
|
14
|
+
CheckboxBlock,
|
|
14
15
|
DatePickerCalendar,
|
|
15
16
|
HoneypotField,
|
|
16
17
|
MAX_MONTH_OFFSET,
|
|
@@ -22,13 +23,13 @@ import {
|
|
|
22
23
|
SelectTrigger,
|
|
23
24
|
SelectValue,
|
|
24
25
|
Textarea
|
|
25
|
-
} from "../../chunk-
|
|
26
|
+
} from "../../chunk-YUHOKPKS.js";
|
|
26
27
|
import "../../chunk-L2SLFCXP.js";
|
|
27
28
|
import "../../chunk-BJXDHKUX.js";
|
|
28
29
|
import {
|
|
29
30
|
useHumanitySignals,
|
|
30
31
|
useToast
|
|
31
|
-
} from "../../chunk-
|
|
32
|
+
} from "../../chunk-EZM5DNII.js";
|
|
32
33
|
import "../../chunk-ZRIVCLPR.js";
|
|
33
34
|
import "../../chunk-GBON2OCG.js";
|
|
34
35
|
import {
|
|
@@ -42,10 +43,10 @@ import {
|
|
|
42
43
|
} from "../../chunk-T2NQSKH4.js";
|
|
43
44
|
import {
|
|
44
45
|
useIsHydrated
|
|
45
|
-
} from "../../chunk-
|
|
46
|
+
} from "../../chunk-4K7TCJ35.js";
|
|
46
47
|
import "../../chunk-5LIDR7HL.js";
|
|
47
48
|
import "../../chunk-XXMHLZ3Y.js";
|
|
48
|
-
import "../../chunk-
|
|
49
|
+
import "../../chunk-LPMDCQLL.js";
|
|
49
50
|
import "../../chunk-IMDG23LW.js";
|
|
50
51
|
import "../../chunk-W72U7OU7.js";
|
|
51
52
|
import {
|
|
@@ -53,7 +54,7 @@ import {
|
|
|
53
54
|
SquareAvatar,
|
|
54
55
|
StatusBadge,
|
|
55
56
|
formatDurationCompact
|
|
56
|
-
} from "../../chunk-
|
|
57
|
+
} from "../../chunk-ROQO355A.js";
|
|
57
58
|
import "../../chunk-MDKCTSDS.js";
|
|
58
59
|
import "../../chunk-MYBXCWEI.js";
|
|
59
60
|
import "../../chunk-OTX6XNSA.js";
|
|
@@ -76,7 +77,7 @@ import "../../chunk-3DTTSBDF.js";
|
|
|
76
77
|
import "../../chunk-DCHYNTHI.js";
|
|
77
78
|
|
|
78
79
|
// src/components/meeting-scheduler/index.tsx
|
|
79
|
-
import { useCallback as useCallback3, useEffect as
|
|
80
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo5, useRef as useRef2, useState as useState3 } from "react";
|
|
80
81
|
|
|
81
82
|
// src/hooks/use-meeting-booking.ts
|
|
82
83
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
@@ -113,7 +114,7 @@ function isValidBcp47Locale(locale) {
|
|
|
113
114
|
return false;
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
|
-
function
|
|
117
|
+
function buildBookingSchema(formFields, legalConsent, slot) {
|
|
117
118
|
const answers = {};
|
|
118
119
|
for (const field of formFields) {
|
|
119
120
|
if (!isSupportedFormField(field)) continue;
|
|
@@ -150,12 +151,12 @@ function makeBookingSchema(formFields, legalConsent) {
|
|
|
150
151
|
const answersObject = z.object(answers);
|
|
151
152
|
return z.object({
|
|
152
153
|
meetingId: z.string().min(1),
|
|
153
|
-
startTimeMs:
|
|
154
|
-
durationMs:
|
|
154
|
+
startTimeMs: slot.startTimeMs,
|
|
155
|
+
durationMs: slot.durationMs,
|
|
155
156
|
firstName: z.string().min(1, { message: "First name is required" }).max(255),
|
|
156
157
|
lastName: z.string().min(1, { message: "Last name is required" }).max(255),
|
|
157
158
|
email: z.string().email({ message: "Please enter a valid email address" }).max(255),
|
|
158
|
-
timezone:
|
|
159
|
+
timezone: slot.timezone,
|
|
159
160
|
locale: z.string().refine(isValidBcp47Locale, { message: "Invalid locale" }).optional(),
|
|
160
161
|
// Plain `.optional()` (no `.default()`) so zod's input and output types
|
|
161
162
|
// match — react-hook-form's zodResolver needs them identical, and the
|
|
@@ -177,6 +178,13 @@ function makeBookingSchema(formFields, legalConsent) {
|
|
|
177
178
|
}
|
|
178
179
|
});
|
|
179
180
|
}
|
|
181
|
+
function makeDeferredBookingSchema(formFields, legalConsent) {
|
|
182
|
+
return buildBookingSchema(formFields, legalConsent, {
|
|
183
|
+
startTimeMs: z.number().int().positive().nullish(),
|
|
184
|
+
durationMs: z.number().int().positive().nullish(),
|
|
185
|
+
timezone: z.string().refine(isValidIanaTimezone, { message: "Invalid timezone" }).nullish()
|
|
186
|
+
});
|
|
187
|
+
}
|
|
180
188
|
|
|
181
189
|
// src/hooks/use-meeting-booking.ts
|
|
182
190
|
var AVAILABILITY_STALE_MS = 0;
|
|
@@ -268,15 +276,27 @@ function useMeetingBooking(options) {
|
|
|
268
276
|
|
|
269
277
|
// src/components/meeting-scheduler/booking-form.tsx
|
|
270
278
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
271
|
-
import { useMemo } from "react";
|
|
279
|
+
import { Fragment, useEffect, useMemo } from "react";
|
|
272
280
|
import { useForm, Controller } from "react-hook-form";
|
|
273
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
281
|
+
import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
|
|
282
|
+
var SPAN_CLASS = {
|
|
283
|
+
1: "md:col-span-1",
|
|
284
|
+
2: "md:col-span-2",
|
|
285
|
+
3: "md:col-span-3",
|
|
286
|
+
4: "md:col-span-4"
|
|
287
|
+
};
|
|
288
|
+
var evenSpan = (count) => Math.min(4, Math.max(1, Math.floor(4 / Math.max(1, count))));
|
|
274
289
|
function BookingForm({
|
|
275
290
|
availability,
|
|
276
291
|
meetingId,
|
|
277
292
|
startTimeMs,
|
|
278
293
|
durationMs,
|
|
279
294
|
timezone,
|
|
295
|
+
deferSlot = false,
|
|
296
|
+
initialValues,
|
|
297
|
+
submitLabel,
|
|
298
|
+
footerNote,
|
|
299
|
+
fieldRows,
|
|
280
300
|
isSubmitting,
|
|
281
301
|
onSubmit,
|
|
282
302
|
honeypotInputProps,
|
|
@@ -284,31 +304,57 @@ function BookingForm({
|
|
|
284
304
|
}) {
|
|
285
305
|
const { formFields, legalConsent } = availability;
|
|
286
306
|
const supportedFields = useMemo(() => formFields.filter(isSupportedFormField), [formFields]);
|
|
287
|
-
const schema = useMemo(
|
|
307
|
+
const schema = useMemo(
|
|
308
|
+
() => makeDeferredBookingSchema(supportedFields, legalConsent),
|
|
309
|
+
[supportedFields, legalConsent]
|
|
310
|
+
);
|
|
311
|
+
const consentDefaults = useMemo(
|
|
312
|
+
() => (legalConsent?.communicationConsentCheckboxes ?? []).map((c) => ({
|
|
313
|
+
communicationTypeId: c.communicationTypeId,
|
|
314
|
+
consented: false
|
|
315
|
+
})),
|
|
316
|
+
[legalConsent]
|
|
317
|
+
);
|
|
288
318
|
const {
|
|
289
319
|
register,
|
|
290
320
|
control,
|
|
291
321
|
handleSubmit,
|
|
322
|
+
setValue,
|
|
292
323
|
formState: { errors }
|
|
293
324
|
} = useForm({
|
|
294
325
|
resolver: zodResolver(schema),
|
|
295
326
|
defaultValues: {
|
|
296
327
|
meetingId,
|
|
297
|
-
|
|
298
|
-
durationMs,
|
|
328
|
+
// Genuinely OMITTED, not present-as-undefined, when the slot is deferred.
|
|
329
|
+
...deferSlot ? {} : { startTimeMs, durationMs },
|
|
299
330
|
timezone,
|
|
300
331
|
locale: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
301
332
|
firstName: "",
|
|
302
333
|
lastName: "",
|
|
303
334
|
email: "",
|
|
304
335
|
formFields: {},
|
|
305
|
-
legalConsentResponses:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
336
|
+
legalConsentResponses: consentDefaults,
|
|
337
|
+
// A remount (back edge, or an error return) would otherwise lose every
|
|
338
|
+
// answer — `defaultValues` is snapshotted once and never re-read.
|
|
339
|
+
...initialValues ?? {}
|
|
309
340
|
}
|
|
310
341
|
});
|
|
342
|
+
const priorConsents = initialValues?.legalConsentResponses;
|
|
343
|
+
useEffect(() => {
|
|
344
|
+
setValue(
|
|
345
|
+
"legalConsentResponses",
|
|
346
|
+
consentDefaults.map((next) => ({
|
|
347
|
+
...next,
|
|
348
|
+
consented: priorConsents?.find((r) => r.communicationTypeId === next.communicationTypeId)?.consented ?? false
|
|
349
|
+
})),
|
|
350
|
+
{ shouldDirty: false }
|
|
351
|
+
);
|
|
352
|
+
}, [consentDefaults, priorConsents, setValue]);
|
|
311
353
|
const submit = handleSubmit(async (data) => {
|
|
354
|
+
if (deferSlot) {
|
|
355
|
+
await onSubmit({ ...data, meetingId });
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
312
358
|
await onSubmit({ ...data, meetingId, startTimeMs, durationMs, timezone, ...getSignals() });
|
|
313
359
|
});
|
|
314
360
|
const fieldError = (name) => {
|
|
@@ -317,9 +363,8 @@ function BookingForm({
|
|
|
317
363
|
};
|
|
318
364
|
const inputClass = "bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-11 md:h-12";
|
|
319
365
|
const FORM_STACK = "flex flex-col gap-[var(--spacing-system-l)]";
|
|
320
|
-
|
|
321
|
-
/* @__PURE__ */ jsx(
|
|
322
|
-
/* @__PURE__ */ jsx(FieldWrapper, { label: "Email", htmlFor: "ms-email", error: errors.email?.message, children: /* @__PURE__ */ jsx(
|
|
366
|
+
const builtInFields = {
|
|
367
|
+
email: /* @__PURE__ */ jsx(FieldWrapper, { label: "Email", htmlFor: "ms-email", error: errors.email?.message, children: /* @__PURE__ */ jsx(
|
|
323
368
|
Input,
|
|
324
369
|
{
|
|
325
370
|
id: "ms-email",
|
|
@@ -332,129 +377,195 @@ function BookingForm({
|
|
|
332
377
|
...register("email")
|
|
333
378
|
}
|
|
334
379
|
) }),
|
|
335
|
-
/* @__PURE__ */
|
|
336
|
-
|
|
337
|
-
Input,
|
|
338
|
-
{
|
|
339
|
-
id: "ms-first-name",
|
|
340
|
-
required: true,
|
|
341
|
-
autoComplete: "given-name",
|
|
342
|
-
placeholder: "Jane",
|
|
343
|
-
"aria-invalid": !!errors.firstName,
|
|
344
|
-
className: inputClass,
|
|
345
|
-
...register("firstName")
|
|
346
|
-
}
|
|
347
|
-
) }),
|
|
348
|
-
/* @__PURE__ */ jsx(FieldWrapper, { label: "Last Name", htmlFor: "ms-last-name", error: errors.lastName?.message, children: /* @__PURE__ */ jsx(
|
|
349
|
-
Input,
|
|
350
|
-
{
|
|
351
|
-
id: "ms-last-name",
|
|
352
|
-
required: true,
|
|
353
|
-
autoComplete: "family-name",
|
|
354
|
-
placeholder: "Doe",
|
|
355
|
-
"aria-invalid": !!errors.lastName,
|
|
356
|
-
className: inputClass,
|
|
357
|
-
...register("lastName")
|
|
358
|
-
}
|
|
359
|
-
) })
|
|
360
|
-
] }),
|
|
361
|
-
supportedFields.map((field) => /* @__PURE__ */ jsxs(
|
|
362
|
-
FieldWrapper,
|
|
380
|
+
firstName: /* @__PURE__ */ jsx(FieldWrapper, { label: "First Name", htmlFor: "ms-first-name", error: errors.firstName?.message, children: /* @__PURE__ */ jsx(
|
|
381
|
+
Input,
|
|
363
382
|
{
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
383
|
+
id: "ms-first-name",
|
|
384
|
+
required: true,
|
|
385
|
+
autoComplete: "given-name",
|
|
386
|
+
placeholder: "Jane",
|
|
387
|
+
"aria-invalid": !!errors.firstName,
|
|
388
|
+
className: inputClass,
|
|
389
|
+
...register("firstName")
|
|
390
|
+
}
|
|
391
|
+
) }),
|
|
392
|
+
lastName: /* @__PURE__ */ jsx(FieldWrapper, { label: "Last Name", htmlFor: "ms-last-name", error: errors.lastName?.message, children: /* @__PURE__ */ jsx(
|
|
393
|
+
Input,
|
|
394
|
+
{
|
|
395
|
+
id: "ms-last-name",
|
|
396
|
+
required: true,
|
|
397
|
+
autoComplete: "family-name",
|
|
398
|
+
placeholder: "Doe",
|
|
399
|
+
"aria-invalid": !!errors.lastName,
|
|
400
|
+
className: inputClass,
|
|
401
|
+
...register("lastName")
|
|
402
|
+
}
|
|
403
|
+
) })
|
|
404
|
+
};
|
|
405
|
+
const renderDeclaredField = (field) => /* @__PURE__ */ jsxs(FieldWrapper, { label: field.label, htmlFor: `ms-q-${field.name}`, error: fieldError(field.name), children: [
|
|
406
|
+
field.type === "textarea" && /* @__PURE__ */ jsx(
|
|
407
|
+
Textarea,
|
|
408
|
+
{
|
|
409
|
+
id: `ms-q-${field.name}`,
|
|
410
|
+
className: "border-ods-border bg-ods-card px-3 text-ods-text-primary placeholder-ods-text-secondary",
|
|
411
|
+
...register(`formFields.${field.name}`)
|
|
412
|
+
}
|
|
413
|
+
),
|
|
414
|
+
field.type === "text" && /* @__PURE__ */ jsx(
|
|
415
|
+
Input,
|
|
416
|
+
{
|
|
417
|
+
id: `ms-q-${field.name}`,
|
|
418
|
+
className: "h-11 border-ods-border bg-ods-card px-3 text-ods-text-primary placeholder-ods-text-secondary md:h-12",
|
|
419
|
+
...register(`formFields.${field.name}`)
|
|
420
|
+
}
|
|
421
|
+
),
|
|
422
|
+
(field.type === "select" || field.type === "radio") && /* @__PURE__ */ jsx(
|
|
423
|
+
Controller,
|
|
424
|
+
{
|
|
425
|
+
control,
|
|
426
|
+
name: `formFields.${field.name}`,
|
|
427
|
+
render: ({ field: rhf }) => field.type === "select" ? /* @__PURE__ */ jsxs(Select, { value: rhf.value ?? "", onValueChange: rhf.onChange, children: [
|
|
428
|
+
/* @__PURE__ */ jsx(SelectTrigger, { id: `ms-q-${field.name}`, children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select\u2026" }) }),
|
|
429
|
+
/* @__PURE__ */ jsx(SelectContent, { children: (field.options ?? []).map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt, children: opt }, opt)) })
|
|
430
|
+
] }) : /* @__PURE__ */ jsx(RadioGroup, { value: rhf.value ?? "", onValueChange: rhf.onChange, children: (field.options ?? []).map((opt) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[var(--spacing-system-xs)]", children: [
|
|
431
|
+
/* @__PURE__ */ jsx(RadioGroupItem, { id: `ms-q-${field.name}-${opt}`, value: opt }),
|
|
432
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `ms-q-${field.name}-${opt}`, children: opt })
|
|
433
|
+
] }, opt)) })
|
|
434
|
+
}
|
|
435
|
+
),
|
|
436
|
+
field.type === "checkbox" && /* @__PURE__ */ jsx(
|
|
437
|
+
Controller,
|
|
438
|
+
{
|
|
439
|
+
control,
|
|
440
|
+
name: `formFields.${field.name}`,
|
|
441
|
+
render: ({ field: rhf }) => (
|
|
442
|
+
// The question's Label above already carries the text and
|
|
443
|
+
// binds to this id — a second inline label would double the
|
|
444
|
+
// visible text AND the accessible name.
|
|
445
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-[var(--spacing-system-xs)]", children: /* @__PURE__ */ jsx(
|
|
446
|
+
Checkbox,
|
|
378
447
|
{
|
|
379
448
|
id: `ms-q-${field.name}`,
|
|
380
|
-
|
|
381
|
-
|
|
449
|
+
checked: Boolean(rhf.value),
|
|
450
|
+
onCheckedChange: (v) => rhf.onChange(v === true)
|
|
382
451
|
}
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
452
|
+
) })
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
)
|
|
456
|
+
] }, field.name);
|
|
457
|
+
const declaredByName = new Map(supportedFields.map((f) => [f.name, f]));
|
|
458
|
+
const slotResolves = (name) => Boolean(builtInFields[name] || declaredByName.has(name));
|
|
459
|
+
const slotNode = (name) => {
|
|
460
|
+
const builtIn = builtInFields[name];
|
|
461
|
+
if (builtIn) return builtIn;
|
|
462
|
+
const declared = declaredByName.get(name);
|
|
463
|
+
return declared ? renderDeclaredField(declared) : null;
|
|
464
|
+
};
|
|
465
|
+
const unplacedFields = fieldRows ? supportedFields.filter((f) => !fieldRows.some((row) => row.some((slot) => slot.name === f.name))) : [];
|
|
466
|
+
const placedRows = (fieldRows ?? []).map((row) => row.filter((slot) => slotResolves(slot.name))).filter((row) => row.length > 0);
|
|
467
|
+
const submitButton = /* @__PURE__ */ jsx(
|
|
468
|
+
Button,
|
|
469
|
+
{
|
|
470
|
+
type: "submit",
|
|
471
|
+
loading: isSubmitting,
|
|
472
|
+
disabled: isSubmitting,
|
|
473
|
+
className: footerNote ? "md:w-60" : void 0,
|
|
474
|
+
children: submitLabel ?? "Confirm Booking"
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
return /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: FORM_STACK, noValidate: true, children: [
|
|
478
|
+
/* @__PURE__ */ jsx(HoneypotField, { ...honeypotInputProps }),
|
|
479
|
+
fieldRows ? /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
480
|
+
placedRows.map((row, rowIndex) => /* @__PURE__ */ jsx(
|
|
481
|
+
"div",
|
|
482
|
+
{
|
|
483
|
+
className: "grid grid-cols-2 gap-[var(--spacing-system-m)] md:grid-cols-4",
|
|
484
|
+
children: row.map((slot) => /* @__PURE__ */ jsx(
|
|
485
|
+
"div",
|
|
400
486
|
{
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
487
|
+
className: cn(
|
|
488
|
+
// A two-field row stays side by side on a phone — the rule
|
|
489
|
+
// the built-in name pair has always followed: two short
|
|
490
|
+
// fields cost one line instead of two on the layout that can
|
|
491
|
+
// least afford them.
|
|
492
|
+
row.length === 2 ? "col-span-1" : "col-span-2",
|
|
493
|
+
SPAN_CLASS[slot.span ?? evenSpan(row.length)]
|
|
494
|
+
),
|
|
495
|
+
children: slotNode(slot.name)
|
|
496
|
+
},
|
|
497
|
+
slot.name
|
|
498
|
+
))
|
|
499
|
+
},
|
|
500
|
+
row.map((s) => s.name).join("|") || rowIndex
|
|
501
|
+
)),
|
|
502
|
+
unplacedFields.map((field) => /* @__PURE__ */ jsx(Fragment, { children: renderDeclaredField(field) }, field.name))
|
|
503
|
+
] }) : /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
504
|
+
builtInFields.email,
|
|
505
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-[var(--spacing-system-m)]", children: [
|
|
506
|
+
builtInFields.firstName,
|
|
507
|
+
builtInFields.lastName
|
|
508
|
+
] }),
|
|
509
|
+
supportedFields.map((field) => /* @__PURE__ */ jsx(Fragment, { children: renderDeclaredField(field) }, field.name))
|
|
510
|
+
] }),
|
|
422
511
|
legalConsent && // Same out-of-flow message as the fields above, so a missed consent
|
|
423
512
|
// box doesn't shove the submit button down the card. With no error the
|
|
424
513
|
// wrapper is `display:contents` and the panel below is the flex item,
|
|
425
514
|
// exactly as it was.
|
|
426
|
-
/* @__PURE__ */ jsx(FieldWrapper, { error: errors.legalConsentResponses?.message, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--spacing-system-xs)]
|
|
427
|
-
/* @__PURE__ */ jsx("p", { className: "text-ods-text-secondary text-h6", children: legalConsent.processingConsentText }),
|
|
428
|
-
legalConsent.communicationConsentText && /* @__PURE__ */ jsx("p", { className: "text-ods-text-secondary text-h6", children: legalConsent.communicationConsentText }),
|
|
515
|
+
/* @__PURE__ */ jsx(FieldWrapper, { error: errors.legalConsentResponses?.message, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--spacing-system-xs)]", children: [
|
|
429
516
|
/* @__PURE__ */ jsx(
|
|
430
517
|
Controller,
|
|
431
518
|
{
|
|
432
519
|
control,
|
|
433
520
|
name: "legalConsentResponses",
|
|
434
|
-
render: ({ field: rhf }) => /* @__PURE__ */ jsx(
|
|
521
|
+
render: ({ field: rhf }) => /* @__PURE__ */ jsx(Fragment2, { children: legalConsent.communicationConsentCheckboxes.map((box, index) => {
|
|
435
522
|
const responses = rhf.value ?? [];
|
|
436
523
|
const current = responses.find((r) => r.communicationTypeId === box.communicationTypeId);
|
|
437
|
-
return
|
|
524
|
+
return (
|
|
525
|
+
// The design system's consent row (`checkbox-block`, the
|
|
526
|
+
// same block the waitlist form uses): box, label, caption
|
|
527
|
+
// in one bordered row. GDPR surface — HubSpot's copy is
|
|
528
|
+
// rendered VERBATIM, never edited. The processing statement
|
|
529
|
+
// rides as the caption of the FIRST row, once: it describes
|
|
530
|
+
// the consent as a whole, not each channel.
|
|
438
531
|
/* @__PURE__ */ jsx(
|
|
439
|
-
|
|
532
|
+
CheckboxBlock,
|
|
440
533
|
{
|
|
441
534
|
id: `ms-consent-${box.communicationTypeId}`,
|
|
442
535
|
checked: current?.consented ?? false,
|
|
443
536
|
onCheckedChange: (v) => rhf.onChange(
|
|
444
537
|
responses.map(
|
|
445
|
-
(r) => r.communicationTypeId === box.communicationTypeId ? { ...r, consented: v
|
|
538
|
+
(r) => r.communicationTypeId === box.communicationTypeId ? { ...r, consented: v } : r
|
|
446
539
|
)
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
540
|
+
),
|
|
541
|
+
label: box.label,
|
|
542
|
+
description: index === 0 ? legalConsent.processingConsentText : void 0
|
|
543
|
+
},
|
|
544
|
+
box.communicationTypeId
|
|
545
|
+
)
|
|
546
|
+
);
|
|
452
547
|
}) })
|
|
453
548
|
}
|
|
454
549
|
),
|
|
550
|
+
legalConsent.communicationConsentText && /* @__PURE__ */ jsx("p", { className: "text-ods-text-secondary text-h6", children: legalConsent.communicationConsentText }),
|
|
455
551
|
legalConsent.privacyPolicyText && /* @__PURE__ */ jsx("p", { className: "text-ods-text-secondary text-h6", children: legalConsent.privacyPolicyText })
|
|
456
552
|
] }) }),
|
|
457
|
-
/* @__PURE__ */
|
|
553
|
+
footerNote ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-[var(--spacing-system-m)]", children: [
|
|
554
|
+
/* @__PURE__ */ jsx("p", { className: "text-ods-text-secondary text-h6", children: footerNote }),
|
|
555
|
+
submitButton
|
|
556
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "flex", children: submitButton })
|
|
557
|
+
] });
|
|
558
|
+
}
|
|
559
|
+
function BookingFormSkeleton() {
|
|
560
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-[var(--spacing-system-l)] p-[var(--spacing-system-l)] lg:p-0", children: [
|
|
561
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-[var(--spacing-system-m)] md:grid-cols-2", children: [
|
|
562
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-[4.75rem] w-full" }),
|
|
563
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-[4.75rem] w-full" })
|
|
564
|
+
] }),
|
|
565
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-[4.75rem] w-full" }),
|
|
566
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-[7.75rem] w-full" }),
|
|
567
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-[4.25rem] w-full" }),
|
|
568
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-40" }) })
|
|
458
569
|
] });
|
|
459
570
|
}
|
|
460
571
|
|
|
@@ -542,6 +653,7 @@ function SchedulerContextPanel({
|
|
|
542
653
|
onBack,
|
|
543
654
|
backLabel = "Back",
|
|
544
655
|
locked = false,
|
|
656
|
+
fixedDuration = false,
|
|
545
657
|
showTimezone = true,
|
|
546
658
|
className
|
|
547
659
|
}) {
|
|
@@ -585,8 +697,9 @@ function SchedulerContextPanel({
|
|
|
585
697
|
" hosts on this calendar"
|
|
586
698
|
] })
|
|
587
699
|
] }),
|
|
588
|
-
locked || durationsMs.length <= 1 ? (
|
|
589
|
-
// Post-selection, or a link that offers one
|
|
700
|
+
locked || fixedDuration || durationsMs.length <= 1 ? (
|
|
701
|
+
// Post-selection, a fixed-length flow, or a link that offers one
|
|
702
|
+
// length: a static line,
|
|
590
703
|
// which is what sits beside the host on the header-strip layouts.
|
|
591
704
|
selectedDurationMs != null && /* @__PURE__ */ jsxs3("p", { className: "shrink-0 text-ods-text-secondary text-h6", children: [
|
|
592
705
|
formatDurationCompact(selectedDurationMs / 1e3),
|
|
@@ -596,7 +709,7 @@ function SchedulerContextPanel({
|
|
|
596
709
|
] }),
|
|
597
710
|
title && /* @__PURE__ */ jsx3("p", { className: "text-ods-text-primary text-h3", children: title }),
|
|
598
711
|
description && /* @__PURE__ */ jsx3("p", { className: "text-ods-text-secondary text-h6", children: description }),
|
|
599
|
-
!locked && durationsMs.length > 1 && /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-[var(--spacing-system-xxs)]", children: [
|
|
712
|
+
!locked && !fixedDuration && durationsMs.length > 1 && /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-[var(--spacing-system-xxs)]", children: [
|
|
600
713
|
/* @__PURE__ */ jsx3("p", { className: FIELD_LABEL_CLASS, children: "Duration" }),
|
|
601
714
|
/* @__PURE__ */ jsx3("div", { className: "flex flex-wrap gap-[var(--spacing-system-xs)]", children: durationsMs.map((ms) => /* @__PURE__ */ jsx3(
|
|
602
715
|
Button,
|
|
@@ -639,7 +752,7 @@ function SchedulerContextPanel({
|
|
|
639
752
|
// src/components/meeting-scheduler/slot-picker.tsx
|
|
640
753
|
import { ChevronRight } from "lucide-react";
|
|
641
754
|
import { useCallback as useCallback2, useMemo as useMemo3 } from "react";
|
|
642
|
-
import { Fragment as
|
|
755
|
+
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
643
756
|
function dayKeyInZone(ms, timeZone) {
|
|
644
757
|
return new Intl.DateTimeFormat("en-CA", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" }).format(
|
|
645
758
|
new Date(ms)
|
|
@@ -750,7 +863,7 @@ function SlotPickerSkeleton({ monthOffset = 0 }) {
|
|
|
750
863
|
] });
|
|
751
864
|
}
|
|
752
865
|
function TimeChipsSkeleton() {
|
|
753
|
-
return /* @__PURE__ */ jsxs4(
|
|
866
|
+
return /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
754
867
|
/* @__PURE__ */ jsx4("div", { className: COLUMN_HEADING_CLASS, children: /* @__PURE__ */ jsx4(Skeleton, { className: "inline-block h-4 w-40 align-middle" }) }),
|
|
755
868
|
/* @__PURE__ */ jsx4("div", { className: CHIP_GRID_CLASS, children: Array.from({ length: 16 }, (_, i) => (
|
|
756
869
|
// h-11 md:h-12 — the loaded chip's own default-size height.
|
|
@@ -767,7 +880,8 @@ function SlotPicker({
|
|
|
767
880
|
onSelectSlot,
|
|
768
881
|
selectedDay,
|
|
769
882
|
onSelectDay,
|
|
770
|
-
isLoading = false
|
|
883
|
+
isLoading = false,
|
|
884
|
+
isSubmitting = false
|
|
771
885
|
}) {
|
|
772
886
|
const slotsByDay = useMemo3(() => {
|
|
773
887
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -801,7 +915,7 @@ function SlotPicker({
|
|
|
801
915
|
}
|
|
802
916
|
)
|
|
803
917
|
] }),
|
|
804
|
-
/* @__PURE__ */ jsx4("div", { className: cn(COLUMN_STACK, COLUMN_BOX, "min-w-0 md:flex-1"), children: isLoading ? /* @__PURE__ */ jsx4(TimeChipsSkeleton, {}) : selectedDay ? /* @__PURE__ */ jsxs4(
|
|
918
|
+
/* @__PURE__ */ jsx4("div", { className: cn(COLUMN_STACK, COLUMN_BOX, "min-w-0 md:flex-1"), children: isLoading ? /* @__PURE__ */ jsx4(TimeChipsSkeleton, {}) : selectedDay ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
805
919
|
/* @__PURE__ */ jsx4("p", { className: COLUMN_HEADING_CLASS, children: new Intl.DateTimeFormat(void 0, {
|
|
806
920
|
weekday: "long",
|
|
807
921
|
month: "long",
|
|
@@ -813,7 +927,12 @@ function SlotPicker({
|
|
|
813
927
|
Button,
|
|
814
928
|
{
|
|
815
929
|
variant: isSelected ? void 0 : "outline",
|
|
816
|
-
|
|
930
|
+
disabled: isSubmitting,
|
|
931
|
+
loading: isSubmitting && isSelected,
|
|
932
|
+
onClick: () => {
|
|
933
|
+
if (isSubmitting) return;
|
|
934
|
+
onSelectSlot(ms);
|
|
935
|
+
},
|
|
817
936
|
className: CHIP_CLASS,
|
|
818
937
|
children: timeLabelInZone(ms, timezone)
|
|
819
938
|
},
|
|
@@ -849,7 +968,7 @@ function SlotPicker({
|
|
|
849
968
|
|
|
850
969
|
// src/components/meeting-scheduler/directory.tsx
|
|
851
970
|
import { ChevronRight as ChevronRight2 } from "lucide-react";
|
|
852
|
-
import { useEffect, useMemo as useMemo4, useState as useState2 } from "react";
|
|
971
|
+
import { useEffect as useEffect2, useMemo as useMemo4, useState as useState2 } from "react";
|
|
853
972
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
854
973
|
function useNextAvailableLabel(ms) {
|
|
855
974
|
const hydrated = useIsHydrated();
|
|
@@ -930,7 +1049,7 @@ function MeetingSchedulerDirectory({
|
|
|
930
1049
|
const [isLoading, setIsLoading] = useState2(initialData === null);
|
|
931
1050
|
const [error, setError] = useState2(null);
|
|
932
1051
|
const [page, setPage] = useState2(1);
|
|
933
|
-
|
|
1052
|
+
useEffect2(() => {
|
|
934
1053
|
if (initialData !== null) return void 0;
|
|
935
1054
|
const controller = new AbortController();
|
|
936
1055
|
let active = true;
|
|
@@ -1019,16 +1138,18 @@ var CONTEXT_PANEL_CLASS = "p-[var(--spacing-system-l)] shrink-0 lg:w-[280px] bor
|
|
|
1019
1138
|
var ACTION_PANEL_CLASS = "flex-1 min-w-0 flex flex-col md:min-h-0 lg:p-[var(--spacing-system-l)]";
|
|
1020
1139
|
var PANEL_STEP_CLASS = "p-[var(--spacing-system-l)] md:min-h-0 md:overflow-y-auto lg:p-0";
|
|
1021
1140
|
var MEETING_SCHEDULER_H = "md:h-[34.375rem] lg:h-[23.75rem]";
|
|
1141
|
+
var MEETING_SCHEDULER_DETAILS_FIRST_H = "md:h-[39.875rem]";
|
|
1022
1142
|
var CARD_CLASS = cn(
|
|
1023
1143
|
"overflow-hidden rounded-md border border-ods-border bg-ods-card",
|
|
1024
|
-
"md:flex md:flex-col"
|
|
1025
|
-
|
|
1144
|
+
"md:flex md:flex-col"
|
|
1145
|
+
// The height is NOT baked in any more: it is chosen per flow at each render
|
|
1146
|
+
// site (`flowHeight`), because details-first needs a taller box and both
|
|
1147
|
+
// constants are module scope.
|
|
1026
1148
|
);
|
|
1027
1149
|
var CARD_INNER_CLASS = "flex flex-col md:min-h-0 md:flex-1 lg:h-full lg:flex-row";
|
|
1028
1150
|
var CARD_DEGRADED_CLASS = cn(
|
|
1029
1151
|
"flex flex-col items-start gap-[var(--spacing-system-m)] rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-lf)]",
|
|
1030
|
-
"md:justify-center md:overflow-y-auto"
|
|
1031
|
-
MEETING_SCHEDULER_H
|
|
1152
|
+
"md:justify-center md:overflow-y-auto"
|
|
1032
1153
|
);
|
|
1033
1154
|
function isNativelyBookable(availability) {
|
|
1034
1155
|
if (!availability.formFields.every(isSupportedFormField)) return false;
|
|
@@ -1050,7 +1171,9 @@ function HubSpotMeetingScheduler({
|
|
|
1050
1171
|
fallbackUrl,
|
|
1051
1172
|
onBack,
|
|
1052
1173
|
onBooked,
|
|
1053
|
-
className
|
|
1174
|
+
className,
|
|
1175
|
+
flow = "slot-first",
|
|
1176
|
+
detailsForm: DetailsForm = BookingForm
|
|
1054
1177
|
}) {
|
|
1055
1178
|
const {
|
|
1056
1179
|
availability,
|
|
@@ -1063,6 +1186,7 @@ function HubSpotMeetingScheduler({
|
|
|
1063
1186
|
book,
|
|
1064
1187
|
isSubmitting
|
|
1065
1188
|
} = useMeetingBooking({ meetingId, apiBaseUrl, initialAvailability });
|
|
1189
|
+
const detailsFirst = flow === "details-first";
|
|
1066
1190
|
const hydrated = useIsHydrated();
|
|
1067
1191
|
const [pickedTimezone, setTimezone] = useState3(null);
|
|
1068
1192
|
const resolvedLocalTimezone = useMemo5(() => {
|
|
@@ -1074,7 +1198,9 @@ function HubSpotMeetingScheduler({
|
|
|
1074
1198
|
}
|
|
1075
1199
|
}, [hydrated]);
|
|
1076
1200
|
const timezone = pickedTimezone ?? displayTimezone ?? resolvedLocalTimezone;
|
|
1077
|
-
const [step, setStep] = useState3("slot");
|
|
1201
|
+
const [step, setStep] = useState3(detailsFirst ? "details" : "slot");
|
|
1202
|
+
const [stash, setStash] = useState3(null);
|
|
1203
|
+
const formOnly = detailsFirst && step === "details";
|
|
1078
1204
|
const [pickedDurationMs, setDurationMs] = useState3(null);
|
|
1079
1205
|
const [pickedDay, setSelectedDay] = useState3(null);
|
|
1080
1206
|
const [selectedSlot, setSelectedSlot] = useState3(null);
|
|
@@ -1086,14 +1212,15 @@ function HubSpotMeetingScheduler({
|
|
|
1086
1212
|
const [machineFor, setMachineFor] = useState3(meetingId);
|
|
1087
1213
|
if (machineFor !== meetingId) {
|
|
1088
1214
|
setMachineFor(meetingId);
|
|
1089
|
-
setStep("slot");
|
|
1215
|
+
setStep(detailsFirst ? "details" : "slot");
|
|
1090
1216
|
setDurationMs(null);
|
|
1091
1217
|
setSelectedDay(null);
|
|
1092
1218
|
setSelectedSlot(null);
|
|
1093
1219
|
setConfirmation(null);
|
|
1094
1220
|
setBookingError(null);
|
|
1221
|
+
setStash(null);
|
|
1095
1222
|
}
|
|
1096
|
-
|
|
1223
|
+
useEffect3(() => {
|
|
1097
1224
|
autoAdvanceCount.current = 0;
|
|
1098
1225
|
}, [meetingId]);
|
|
1099
1226
|
const durations = useMemo5(() => availability?.durationsMs ?? [], [availability?.durationsMs]);
|
|
@@ -1116,7 +1243,7 @@ function HubSpotMeetingScheduler({
|
|
|
1116
1243
|
if (pickedDay && dayKeys.has(pickedDay)) return pickedDay;
|
|
1117
1244
|
return dayKeyInZone(inMonth[0], timezone);
|
|
1118
1245
|
}, [step, timezone, slots, pickedDay, monthOffset]);
|
|
1119
|
-
|
|
1246
|
+
useEffect3(() => {
|
|
1120
1247
|
if (step === "slot" && availability && !isFetchingAvailability && durationMs != null && slots.length === 0 && availability.hasMore && autoAdvanceCount.current < 3) {
|
|
1121
1248
|
autoAdvanceCount.current += 1;
|
|
1122
1249
|
setMonthOffset(monthOffset + 1);
|
|
@@ -1126,10 +1253,30 @@ function HubSpotMeetingScheduler({
|
|
|
1126
1253
|
setStep("slot");
|
|
1127
1254
|
setBookingError(null);
|
|
1128
1255
|
}, []);
|
|
1256
|
+
const backToDetails = useCallback3(() => {
|
|
1257
|
+
setStep("details");
|
|
1258
|
+
setBookingError(null);
|
|
1259
|
+
}, []);
|
|
1260
|
+
const stashDetails = useCallback3(
|
|
1261
|
+
(payload) => {
|
|
1262
|
+
setBookingError(null);
|
|
1263
|
+
setStash({ payload, signals: getSignals() });
|
|
1264
|
+
setStep("slot");
|
|
1265
|
+
autoAdvanceCount.current = 0;
|
|
1266
|
+
void refetchAvailability();
|
|
1267
|
+
return Promise.resolve();
|
|
1268
|
+
},
|
|
1269
|
+
[getSignals, refetchAvailability]
|
|
1270
|
+
);
|
|
1129
1271
|
const handleSubmit = useCallback3(
|
|
1130
1272
|
async (payload) => {
|
|
1273
|
+
const priorError = bookingError;
|
|
1131
1274
|
setBookingError(null);
|
|
1132
1275
|
const result = await book(payload);
|
|
1276
|
+
if (!result.ok && result.message === "Already submitting") {
|
|
1277
|
+
setBookingError(priorError);
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1133
1280
|
if (result.ok && result.confirmation) {
|
|
1134
1281
|
setConfirmation(result.confirmation);
|
|
1135
1282
|
setStep("confirmed");
|
|
@@ -1141,8 +1288,10 @@ function HubSpotMeetingScheduler({
|
|
|
1141
1288
|
if (code === "SLOT_TAKEN") {
|
|
1142
1289
|
setSelectedSlot(null);
|
|
1143
1290
|
setStep("slot");
|
|
1144
|
-
resetSignals();
|
|
1291
|
+
if (!detailsFirst) resetSignals();
|
|
1145
1292
|
void refetchAvailability();
|
|
1293
|
+
} else if (detailsFirst && (code === "VALIDATION" || code === "INVALID_EMAIL")) {
|
|
1294
|
+
setStep("details");
|
|
1146
1295
|
} else {
|
|
1147
1296
|
toast({
|
|
1148
1297
|
variant: "error",
|
|
@@ -1151,29 +1300,31 @@ function HubSpotMeetingScheduler({
|
|
|
1151
1300
|
});
|
|
1152
1301
|
}
|
|
1153
1302
|
},
|
|
1154
|
-
[book, onBooked, refetchAvailability, resetSignals, toast]
|
|
1303
|
+
[book, bookingError, detailsFirst, onBooked, refetchAvailability, resetSignals, toast]
|
|
1155
1304
|
);
|
|
1156
1305
|
const escapeHatch = fallbackUrl ? /* @__PURE__ */ jsx6(Button, { variant: "outline", size: "small-legacy", href: fallbackUrl, openInNewTab: true, children: "Open in HubSpot" }) : null;
|
|
1306
|
+
const flowHeight = detailsFirst ? MEETING_SCHEDULER_DETAILS_FIRST_H : MEETING_SCHEDULER_H;
|
|
1307
|
+
const unseededDetailsFirstFailure = detailsFirst && step === "details" && !availability && !initialAvailability && !hydrated;
|
|
1308
|
+
const degradedCard = /* @__PURE__ */ jsxs6("div", { className: cn(CARD_DEGRADED_CLASS, flowHeight, className), children: [
|
|
1309
|
+
/* @__PURE__ */ jsx6("p", { className: "text-ods-text-secondary text-h6", children: "We couldn't load available call times. Please try again shortly." }),
|
|
1310
|
+
escapeHatch
|
|
1311
|
+
] });
|
|
1312
|
+
if (unseededDetailsFirstFailure) return degradedCard;
|
|
1157
1313
|
if (isLoadingAvailability && !availability) {
|
|
1158
|
-
return /* @__PURE__ */ jsx6("div", { className: cn(CARD_CLASS, className), children: /* @__PURE__ */ jsxs6("div", { className: CARD_INNER_CLASS, children: [
|
|
1159
|
-
/* @__PURE__ */ jsx6(ContextPanelSkeleton, { onBack, className: CONTEXT_PANEL_CLASS }),
|
|
1160
|
-
/* @__PURE__ */ jsx6("div", { className: ACTION_PANEL_CLASS, children: /* @__PURE__ */ jsx6(SlotPickerSkeleton, { monthOffset }) })
|
|
1314
|
+
return /* @__PURE__ */ jsx6("div", { className: cn(CARD_CLASS, flowHeight, formOnly && "bg-ods-bg", className), children: /* @__PURE__ */ jsxs6("div", { className: CARD_INNER_CLASS, children: [
|
|
1315
|
+
!formOnly && /* @__PURE__ */ jsx6(ContextPanelSkeleton, { onBack, className: CONTEXT_PANEL_CLASS }),
|
|
1316
|
+
/* @__PURE__ */ jsx6("div", { className: ACTION_PANEL_CLASS, children: detailsFirst && step === "details" ? /* @__PURE__ */ jsx6(BookingFormSkeleton, {}) : /* @__PURE__ */ jsx6(SlotPickerSkeleton, { monthOffset }) })
|
|
1161
1317
|
] }) });
|
|
1162
1318
|
}
|
|
1163
|
-
if (availabilityError || !availability)
|
|
1164
|
-
return /* @__PURE__ */ jsxs6("div", { className: cn(CARD_DEGRADED_CLASS, className), children: [
|
|
1165
|
-
/* @__PURE__ */ jsx6("p", { className: "text-ods-text-secondary text-h6", children: "We couldn't load available call times. Please try again shortly." }),
|
|
1166
|
-
escapeHatch
|
|
1167
|
-
] });
|
|
1168
|
-
}
|
|
1319
|
+
if (availabilityError || !availability) return degradedCard;
|
|
1169
1320
|
if (!isNativelyBookable(availability)) {
|
|
1170
|
-
return /* @__PURE__ */ jsxs6("div", { className: cn(CARD_DEGRADED_CLASS, className), children: [
|
|
1321
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn(CARD_DEGRADED_CLASS, flowHeight, className), children: [
|
|
1171
1322
|
/* @__PURE__ */ jsx6("p", { className: "text-ods-text-secondary text-h6", children: "This meeting type is booked directly on HubSpot." }),
|
|
1172
1323
|
escapeHatch
|
|
1173
1324
|
] });
|
|
1174
1325
|
}
|
|
1175
|
-
return /* @__PURE__ */ jsx6("div", { className: cn(CARD_CLASS, className), children: /* @__PURE__ */ jsxs6("div", { className: CARD_INNER_CLASS, children: [
|
|
1176
|
-
/* @__PURE__ */ jsx6(
|
|
1326
|
+
return /* @__PURE__ */ jsx6("div", { className: cn(CARD_CLASS, flowHeight, formOnly && "bg-ods-bg", className), children: /* @__PURE__ */ jsxs6("div", { className: CARD_INNER_CLASS, children: [
|
|
1327
|
+
!formOnly && /* @__PURE__ */ jsx6(
|
|
1177
1328
|
SchedulerContextPanel,
|
|
1178
1329
|
{
|
|
1179
1330
|
hosts: shownHosts,
|
|
@@ -1185,18 +1336,24 @@ function HubSpotMeetingScheduler({
|
|
|
1185
1336
|
setDurationMs(ms);
|
|
1186
1337
|
setSelectedDay(null);
|
|
1187
1338
|
setSelectedSlot(null);
|
|
1188
|
-
if (step === "details") setStep("slot");
|
|
1339
|
+
if (!detailsFirst && step === "details") setStep("slot");
|
|
1189
1340
|
},
|
|
1190
1341
|
timezone,
|
|
1191
1342
|
onTimezoneChange: setTimezone,
|
|
1192
|
-
onBack: step === "details" ? backToSlot : onBack,
|
|
1193
|
-
locked: step !== "slot",
|
|
1343
|
+
onBack: detailsFirst ? step === "slot" ? backToDetails : onBack : step === "details" ? backToSlot : onBack,
|
|
1344
|
+
locked: detailsFirst ? step === "confirmed" || isSubmitting : step !== "slot",
|
|
1194
1345
|
showTimezone: step !== "confirmed",
|
|
1346
|
+
fixedDuration: detailsFirst,
|
|
1195
1347
|
className: CONTEXT_PANEL_CLASS
|
|
1196
1348
|
}
|
|
1197
1349
|
),
|
|
1198
|
-
/* @__PURE__ */ jsx6("div", { className: ACTION_PANEL_CLASS, children: step === "confirmed" && confirmation && timezone ? /* @__PURE__ */ jsx6("div", { className: cn("flex flex-1 flex-col", PANEL_STEP_CLASS), children: /* @__PURE__ */ jsx6(Confirmation, { confirmation, timezone }) }) : step === "details" &&
|
|
1199
|
-
|
|
1350
|
+
/* @__PURE__ */ jsx6("div", { className: ACTION_PANEL_CLASS, children: step === "confirmed" && confirmation && timezone ? /* @__PURE__ */ jsx6("div", { className: cn("flex flex-1 flex-col", PANEL_STEP_CLASS), children: /* @__PURE__ */ jsx6(Confirmation, { confirmation, timezone }) }) : step === "details" && (detailsFirst ? (
|
|
1351
|
+
// The form is step ONE here: no slot yet, and `timezone` is null
|
|
1352
|
+
// on the server render, so neither may gate the PANEL. A link
|
|
1353
|
+
// publishing no durations has no form worth showing.
|
|
1354
|
+
durations.length > 0
|
|
1355
|
+
) : durationMs != null && selectedSlot != null && timezone) ? /* @__PURE__ */ jsxs6("div", { className: cn("flex flex-1 flex-col gap-[var(--spacing-system-m)]", PANEL_STEP_CLASS), children: [
|
|
1356
|
+
selectedSlot != null && durationMs != null && timezone != null && /* @__PURE__ */ jsxs6("p", { className: "text-ods-text-primary text-h4", children: [
|
|
1200
1357
|
new Intl.DateTimeFormat(void 0, {
|
|
1201
1358
|
timeZone: timezone,
|
|
1202
1359
|
weekday: "long",
|
|
@@ -1211,15 +1368,19 @@ function HubSpotMeetingScheduler({
|
|
|
1211
1368
|
formatDurationCompact(durationMs / 1e3)
|
|
1212
1369
|
] }),
|
|
1213
1370
|
/* @__PURE__ */ jsx6(
|
|
1214
|
-
|
|
1371
|
+
DetailsForm,
|
|
1215
1372
|
{
|
|
1216
1373
|
availability,
|
|
1217
1374
|
meetingId,
|
|
1218
|
-
startTimeMs: selectedSlot,
|
|
1219
|
-
durationMs,
|
|
1375
|
+
startTimeMs: selectedSlot ?? void 0,
|
|
1376
|
+
durationMs: durationMs ?? void 0,
|
|
1220
1377
|
timezone,
|
|
1378
|
+
deferSlot: detailsFirst,
|
|
1379
|
+
submitLabel: detailsFirst ? "Continue" : void 0,
|
|
1380
|
+
footerNote: detailsFirst ? "Next step: pick a slot that works. Calendar invite lands right after." : void 0,
|
|
1381
|
+
initialValues: detailsFirst ? stash?.payload : void 0,
|
|
1221
1382
|
isSubmitting,
|
|
1222
|
-
onSubmit: handleSubmit,
|
|
1383
|
+
onSubmit: detailsFirst ? stashDetails : handleSubmit,
|
|
1223
1384
|
honeypotInputProps,
|
|
1224
1385
|
getSignals
|
|
1225
1386
|
}
|
|
@@ -1250,12 +1411,31 @@ function HubSpotMeetingScheduler({
|
|
|
1250
1411
|
},
|
|
1251
1412
|
selectedSlot,
|
|
1252
1413
|
onSelectSlot: (ms) => {
|
|
1414
|
+
if (!detailsFirst) {
|
|
1415
|
+
setSelectedSlot(ms);
|
|
1416
|
+
setStep("details");
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
if (isSubmitting || !stash) return;
|
|
1253
1420
|
setSelectedSlot(ms);
|
|
1254
|
-
|
|
1421
|
+
void handleSubmit({
|
|
1422
|
+
...stash.payload,
|
|
1423
|
+
...stash.signals,
|
|
1424
|
+
meetingId,
|
|
1425
|
+
// `ms`, NOT `selectedSlot` — the setState above has not
|
|
1426
|
+
// committed inside this handler, so state holds null on
|
|
1427
|
+
// the first booking and the PREVIOUS slot on a retry.
|
|
1428
|
+
startTimeMs: ms,
|
|
1429
|
+
// Live values: both stay editable after Continue, so the
|
|
1430
|
+
// copies frozen at that point can be stale.
|
|
1431
|
+
durationMs,
|
|
1432
|
+
timezone
|
|
1433
|
+
});
|
|
1255
1434
|
},
|
|
1256
1435
|
selectedDay,
|
|
1257
1436
|
onSelectDay: setSelectedDay,
|
|
1258
|
-
isLoading: isLoadingAvailability
|
|
1437
|
+
isLoading: isLoadingAvailability,
|
|
1438
|
+
isSubmitting: detailsFirst ? isSubmitting : void 0
|
|
1259
1439
|
}
|
|
1260
1440
|
)
|
|
1261
1441
|
) : durations.length === 0 && !isFetchingAvailability ? (
|
|
@@ -1276,7 +1456,10 @@ function HubSpotMeetingScheduler({
|
|
|
1276
1456
|
] }) });
|
|
1277
1457
|
}
|
|
1278
1458
|
export {
|
|
1459
|
+
BookingForm,
|
|
1460
|
+
BookingFormSkeleton,
|
|
1279
1461
|
HubSpotMeetingScheduler,
|
|
1462
|
+
MEETING_SCHEDULER_DETAILS_FIRST_H,
|
|
1280
1463
|
MEETING_SCHEDULER_H,
|
|
1281
1464
|
MeetingSchedulerDirectory,
|
|
1282
1465
|
MeetingSchedulerDirectoryRowSkeleton
|