@flamingo-stack/openframe-frontend-core 0.0.561-1852.4997.2 → 0.0.561
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-KGFCHGCW.js → chunk-44U25GD5.js} +2 -2
- package/dist/{chunk-DCB4EBA5.cjs → chunk-4LHEQMOV.cjs} +3 -3
- package/dist/chunk-4LHEQMOV.cjs.map +1 -0
- package/dist/{chunk-PYN65LCP.js → chunk-5AEHCGQV.js} +6 -6
- package/dist/{chunk-K4UCFJNL.cjs → chunk-AGQ4VTUX.cjs} +29 -29
- package/dist/{chunk-K4UCFJNL.cjs.map → chunk-AGQ4VTUX.cjs.map} +1 -1
- package/dist/{chunk-HI3A5HFI.js → chunk-B6TM3QAL.js} +4 -4
- package/dist/{chunk-NISIZABN.cjs → chunk-CR2CMLTQ.cjs} +4 -4
- package/dist/{chunk-NISIZABN.cjs.map → chunk-CR2CMLTQ.cjs.map} +1 -1
- package/dist/{chunk-K535WIFS.cjs → chunk-D3OY4CJU.cjs} +3 -3
- package/dist/{chunk-K535WIFS.cjs.map → chunk-D3OY4CJU.cjs.map} +1 -1
- package/dist/{chunk-RVHAX7VP.cjs → chunk-DAVVVBNL.cjs} +26 -26
- package/dist/{chunk-RVHAX7VP.cjs.map → chunk-DAVVVBNL.cjs.map} +1 -1
- package/dist/{chunk-V5VJTNOG.cjs → chunk-DHRJFUBR.cjs} +11 -11
- package/dist/{chunk-V5VJTNOG.cjs.map → chunk-DHRJFUBR.cjs.map} +1 -1
- package/dist/{chunk-LNWLK4TC.cjs → chunk-EK346HD7.cjs} +39 -39
- package/dist/{chunk-LNWLK4TC.cjs.map → chunk-EK346HD7.cjs.map} +1 -1
- package/dist/{chunk-SKQ7AMUD.cjs → chunk-FSWJCGRJ.cjs} +7 -7
- package/dist/{chunk-SKQ7AMUD.cjs.map → chunk-FSWJCGRJ.cjs.map} +1 -1
- package/dist/{chunk-3IBCKS3I.js → chunk-GKGQ4DWE.js} +121 -49
- package/dist/chunk-GKGQ4DWE.js.map +1 -0
- package/dist/{chunk-XTDUFU5O.cjs → chunk-GWRQ5EEI.cjs} +954 -882
- package/dist/{chunk-XTDUFU5O.cjs.map → chunk-GWRQ5EEI.cjs.map} +1 -1
- package/dist/{chunk-IJ6HBN2J.cjs → chunk-GYC3OTBI.cjs} +9 -9
- package/dist/{chunk-IJ6HBN2J.cjs.map → chunk-GYC3OTBI.cjs.map} +1 -1
- package/dist/{chunk-SFU6A36Q.js → chunk-JCQUWIPT.js} +3 -3
- package/dist/{chunk-NZNKHIFA.js → chunk-KRYLVEXU.js} +2 -2
- package/dist/{chunk-LSWJ5QYP.js → chunk-KWNVQZ5Y.js} +4 -4
- package/dist/{chunk-ZD5QQJH4.js → chunk-MLSDBNKT.js} +2 -2
- package/dist/{chunk-NTTSXF7Z.js → chunk-NGY4KET4.js} +2 -2
- package/dist/{chunk-O45VULK4.js → chunk-OCJG3X4U.js} +2 -2
- package/dist/{chunk-KKIAKLI6.js → chunk-ONDI64M3.js} +2 -2
- package/dist/{chunk-FMQO7OMJ.js → chunk-QGQ26NEE.js} +2 -2
- package/dist/{chunk-RSZXB6J2.js → chunk-RDKJCZHY.js} +3 -3
- package/dist/chunk-RDKJCZHY.js.map +1 -0
- package/dist/{chunk-ELFXMLSX.cjs → chunk-SUPBFRUK.cjs} +5 -5
- package/dist/{chunk-ELFXMLSX.cjs.map → chunk-SUPBFRUK.cjs.map} +1 -1
- package/dist/{chunk-BODITFZI.cjs → chunk-UJ7MSY6F.cjs} +31 -31
- package/dist/{chunk-BODITFZI.cjs.map → chunk-UJ7MSY6F.cjs.map} +1 -1
- package/dist/{chunk-INN323YY.cjs → chunk-UV53Z64O.cjs} +14 -14
- package/dist/{chunk-INN323YY.cjs.map → chunk-UV53Z64O.cjs.map} +1 -1
- package/dist/{chunk-TCQ3VL5R.cjs → chunk-WCM4BGHO.cjs} +97 -97
- package/dist/{chunk-TCQ3VL5R.cjs.map → chunk-WCM4BGHO.cjs.map} +1 -1
- package/dist/{chunk-DQI2S2SY.js → chunk-YUAKZNK5.js} +6 -6
- package/dist/{chunk-5XPYQ7NU.cjs → chunk-ZENRHE55.cjs} +62 -62
- package/dist/{chunk-5XPYQ7NU.cjs.map → chunk-ZENRHE55.cjs.map} +1 -1
- package/dist/{chunk-CLQ6L4JG.js → chunk-ZJ7RI62F.js} +2 -2
- package/dist/components/calendar.cjs +81 -4
- package/dist/components/calendar.cjs.map +1 -1
- package/dist/components/calendar.js +81 -4
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/index.cjs +3 -3
- package/dist/components/chat/index.js +2 -2
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +6 -6
- package/dist/components/docs/index.js +5 -5
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +5 -3
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.d.ts +1 -1
- package/dist/components/features/index.d.ts.map +1 -1
- package/dist/components/features/index.js +4 -2
- package/dist/components/features/use-walkthrough-video.d.ts +1 -1
- package/dist/components/features/use-walkthrough-video.d.ts.map +1 -1
- package/dist/components/features/{floating-walkthrough-video.d.ts → walkthrough-video.d.ts} +61 -7
- package/dist/components/features/walkthrough-video.d.ts.map +1 -0
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +144 -140
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +16 -12
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/booking-form.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/context-panel.d.ts +24 -6
- package/dist/components/meeting-scheduler/context-panel.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +529 -425
- package/dist/components/meeting-scheduler/index.cjs.map +1 -1
- package/dist/components/meeting-scheduler/index.d.ts +54 -1
- package/dist/components/meeting-scheduler/index.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.js +507 -403
- package/dist/components/meeting-scheduler/index.js.map +1 -1
- package/dist/components/meeting-scheduler/slot-picker.d.ts +35 -31
- package/dist/components/meeting-scheduler/slot-picker.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +3 -3
- package/dist/components/navigation/index.js +2 -2
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/related-content/index.cjs +4 -4
- package/dist/components/related-content/index.js +3 -3
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/date-picker.d.ts +62 -1
- package/dist/components/ui/date-picker.d.ts.map +1 -1
- package/dist/components/ui/field-wrapper.d.ts +7 -0
- package/dist/components/ui/field-wrapper.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +36 -36
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +5 -3
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +4 -2
- package/dist/hooks/use-meeting-booking.d.ts +31 -8
- package/dist/hooks/use-meeting-booking.d.ts.map +1 -1
- package/dist/index.cjs +7 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -2
- package/package.json +1 -1
- package/src/components/features/{.floating-walkthrough-video.md → .walkthrough-video.md} +8 -5
- package/src/components/features/board/types.ts +1 -1
- package/src/components/features/index.ts +1 -1
- package/src/components/features/use-walkthrough-video.ts +1 -1
- package/src/components/features/{floating-walkthrough-video.tsx → walkthrough-video.tsx} +108 -22
- package/src/components/meeting-scheduler/booking-form.tsx +60 -51
- package/src/components/meeting-scheduler/confirmation.tsx +1 -1
- package/src/components/meeting-scheduler/context-panel.tsx +166 -68
- package/src/components/meeting-scheduler/index.tsx +227 -81
- package/src/components/meeting-scheduler/slot-picker.tsx +275 -127
- package/src/components/ui/date-picker.tsx +200 -47
- package/src/components/ui/field-wrapper.tsx +9 -2
- package/src/hooks/use-meeting-booking.ts +97 -73
- package/dist/chunk-3IBCKS3I.js.map +0 -1
- package/dist/chunk-7J4DGFHZ.js +0 -88
- package/dist/chunk-7J4DGFHZ.js.map +0 -1
- package/dist/chunk-DCB4EBA5.cjs.map +0 -1
- package/dist/chunk-MKPJYEOR.cjs +0 -88
- package/dist/chunk-MKPJYEOR.cjs.map +0 -1
- package/dist/chunk-RSZXB6J2.js.map +0 -1
- package/dist/components/features/floating-walkthrough-video.d.ts.map +0 -1
- /package/dist/{chunk-KGFCHGCW.js.map → chunk-44U25GD5.js.map} +0 -0
- /package/dist/{chunk-PYN65LCP.js.map → chunk-5AEHCGQV.js.map} +0 -0
- /package/dist/{chunk-HI3A5HFI.js.map → chunk-B6TM3QAL.js.map} +0 -0
- /package/dist/{chunk-SFU6A36Q.js.map → chunk-JCQUWIPT.js.map} +0 -0
- /package/dist/{chunk-NZNKHIFA.js.map → chunk-KRYLVEXU.js.map} +0 -0
- /package/dist/{chunk-LSWJ5QYP.js.map → chunk-KWNVQZ5Y.js.map} +0 -0
- /package/dist/{chunk-ZD5QQJH4.js.map → chunk-MLSDBNKT.js.map} +0 -0
- /package/dist/{chunk-NTTSXF7Z.js.map → chunk-NGY4KET4.js.map} +0 -0
- /package/dist/{chunk-O45VULK4.js.map → chunk-OCJG3X4U.js.map} +0 -0
- /package/dist/{chunk-KKIAKLI6.js.map → chunk-ONDI64M3.js.map} +0 -0
- /package/dist/{chunk-FMQO7OMJ.js.map → chunk-QGQ26NEE.js.map} +0 -0
- /package/dist/{chunk-DQI2S2SY.js.map → chunk-YUAKZNK5.js.map} +0 -0
- /package/dist/{chunk-CLQ6L4JG.js.map → chunk-ZJ7RI62F.js.map} +0 -0
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
"use client";
|
|
3
|
-
import {
|
|
4
|
-
Calendar
|
|
5
|
-
} from "../../chunk-7J4DGFHZ.js";
|
|
6
3
|
import {
|
|
7
4
|
PersistentPaginationWrapper
|
|
8
|
-
} from "../../chunk-
|
|
5
|
+
} from "../../chunk-ZJ7RI62F.js";
|
|
9
6
|
import {
|
|
10
7
|
EmptyState
|
|
11
|
-
} from "../../chunk-
|
|
8
|
+
} from "../../chunk-ONDI64M3.js";
|
|
12
9
|
import {
|
|
13
10
|
Autocomplete,
|
|
14
11
|
AvatarStack,
|
|
15
12
|
BackButton,
|
|
13
|
+
DatePickerCalendar,
|
|
16
14
|
HoneypotField,
|
|
17
15
|
MAX_MONTH_OFFSET,
|
|
18
16
|
RadioGroup,
|
|
@@ -26,7 +24,7 @@ import {
|
|
|
26
24
|
StatusBadge,
|
|
27
25
|
Textarea,
|
|
28
26
|
formatDurationCompact
|
|
29
|
-
} from "../../chunk-
|
|
27
|
+
} from "../../chunk-GKGQ4DWE.js";
|
|
30
28
|
import "../../chunk-7EUIW46X.js";
|
|
31
29
|
import "../../chunk-LXC6P2EO.js";
|
|
32
30
|
import "../../chunk-EL5YVPD5.js";
|
|
@@ -36,17 +34,16 @@ import {
|
|
|
36
34
|
} from "../../chunk-ZNEVGXMU.js";
|
|
37
35
|
import "../../chunk-ZJJERZUA.js";
|
|
38
36
|
import "../../chunk-GJAHN7LH.js";
|
|
39
|
-
import
|
|
40
|
-
ClockIcon
|
|
41
|
-
} from "../../chunk-JMZH4KMJ.js";
|
|
37
|
+
import "../../chunk-JMZH4KMJ.js";
|
|
42
38
|
import "../../chunk-W72U7OU7.js";
|
|
43
39
|
import "../../chunk-UWY722RH.js";
|
|
44
40
|
import {
|
|
45
41
|
Checkbox,
|
|
42
|
+
FieldWrapper,
|
|
46
43
|
Input,
|
|
47
44
|
Label,
|
|
48
45
|
Skeleton
|
|
49
|
-
} from "../../chunk-
|
|
46
|
+
} from "../../chunk-RDKJCZHY.js";
|
|
50
47
|
import "../../chunk-6U3IUD57.js";
|
|
51
48
|
import {
|
|
52
49
|
Button
|
|
@@ -64,10 +61,11 @@ import "../../chunk-GGWZFCYS.js";
|
|
|
64
61
|
|
|
65
62
|
// src/components/meeting-scheduler/index.tsx
|
|
66
63
|
init_cn();
|
|
67
|
-
import { useCallback as
|
|
64
|
+
import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo5, useRef as useRef2, useState as useState3 } from "react";
|
|
68
65
|
|
|
69
66
|
// src/hooks/use-meeting-booking.ts
|
|
70
|
-
import {
|
|
67
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
68
|
+
import { useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
71
69
|
var KNOWN_CODES = [
|
|
72
70
|
"SLOT_TAKEN",
|
|
73
71
|
"VALIDATION",
|
|
@@ -75,15 +73,15 @@ var KNOWN_CODES = [
|
|
|
75
73
|
"TEMPORARILY_UNAVAILABLE",
|
|
76
74
|
"MEETING_UNAVAILABLE"
|
|
77
75
|
];
|
|
76
|
+
var AVAILABILITY_STALE_MS = 3e4;
|
|
77
|
+
function meetingAvailabilityKey(apiBaseUrl, meetingId, monthOffset) {
|
|
78
|
+
return ["meeting-availability", apiBaseUrl, meetingId, monthOffset];
|
|
79
|
+
}
|
|
78
80
|
function useMeetingBooking(options) {
|
|
79
81
|
const { meetingId, apiBaseUrl = "", initialAvailability } = options;
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const [monthOffset, setMonthOffsetState] = useState(
|
|
83
|
-
const [isLoadingAvailability, setIsLoadingAvailability] = useState(!seededFor);
|
|
84
|
-
const [availabilityError, setAvailabilityError] = useState(null);
|
|
85
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
86
|
-
const submittingRef = useRef(false);
|
|
82
|
+
const queryClient = useQueryClient();
|
|
83
|
+
const seededOffset = initialAvailability?.meetingId === meetingId ? initialAvailability.monthOffset : void 0;
|
|
84
|
+
const [monthOffset, setMonthOffsetState] = useState(seededOffset ?? 0);
|
|
87
85
|
const setMonthOffset = useCallback((offset) => {
|
|
88
86
|
setMonthOffsetState(Math.max(0, Math.min(MAX_MONTH_OFFSET, offset)));
|
|
89
87
|
}, []);
|
|
@@ -93,48 +91,27 @@ function useMeetingBooking(options) {
|
|
|
93
91
|
prevMeetingIdRef.current = meetingId;
|
|
94
92
|
setMonthOffsetState(0);
|
|
95
93
|
}, [meetingId]);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
setAvailabilityError(null);
|
|
101
|
-
if (!availability || availability.meetingId !== meetingId || availability.monthOffset !== monthOffset) {
|
|
102
|
-
setIsLoadingAvailability(true);
|
|
103
|
-
}
|
|
104
|
-
try {
|
|
105
|
-
const params = new URLSearchParams({ meeting: meetingId, monthOffset: String(monthOffset) });
|
|
106
|
-
const res = await contentFetch(`${apiBaseUrl}/api/meetings/availability?${params}`, {
|
|
107
|
-
signal: controller.signal
|
|
108
|
-
});
|
|
109
|
-
if (!res.ok) throw new Error(`availability ${res.status}`);
|
|
110
|
-
const data = await res.json();
|
|
111
|
-
if (active) setAvailability(data);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
if (!active || err instanceof DOMException && err.name === "AbortError") return;
|
|
114
|
-
setAvailabilityError(err instanceof Error ? err.message : "Failed to load availability");
|
|
115
|
-
} finally {
|
|
116
|
-
if (active) setIsLoadingAvailability(false);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
void load();
|
|
120
|
-
return () => {
|
|
121
|
-
active = false;
|
|
122
|
-
controller.abort();
|
|
123
|
-
};
|
|
124
|
-
}, [meetingId, monthOffset, apiBaseUrl]);
|
|
125
|
-
const refetchAvailability = useCallback(async () => {
|
|
126
|
-
try {
|
|
94
|
+
const seed = initialAvailability?.meetingId === meetingId && initialAvailability.monthOffset === monthOffset ? initialAvailability : void 0;
|
|
95
|
+
const availabilityQuery = useQuery({
|
|
96
|
+
queryKey: meetingAvailabilityKey(apiBaseUrl, meetingId, monthOffset),
|
|
97
|
+
queryFn: async ({ signal }) => {
|
|
127
98
|
const params = new URLSearchParams({ meeting: meetingId, monthOffset: String(monthOffset) });
|
|
128
|
-
const res = await contentFetch(`${apiBaseUrl}/api/meetings/availability?${params}
|
|
129
|
-
if (res.ok)
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
99
|
+
const res = await contentFetch(`${apiBaseUrl}/api/meetings/availability?${params}`, { signal });
|
|
100
|
+
if (!res.ok) throw new Error(`availability ${res.status}`);
|
|
101
|
+
return await res.json();
|
|
102
|
+
},
|
|
103
|
+
staleTime: AVAILABILITY_STALE_MS,
|
|
104
|
+
initialData: seed,
|
|
105
|
+
// Stamped as infinitely old on purpose: the seed is first-paint
|
|
106
|
+
// scaffolding for a 60-second-volatile resource, not truth, so it paints
|
|
107
|
+
// immediately AND is refetched immediately.
|
|
108
|
+
initialDataUpdatedAt: seed ? 0 : void 0
|
|
109
|
+
});
|
|
110
|
+
const refetchAvailability = useCallback(async () => {
|
|
111
|
+
await queryClient.invalidateQueries({ queryKey: meetingAvailabilityKey(apiBaseUrl, meetingId, monthOffset) }).catch(() => void 0);
|
|
112
|
+
}, [queryClient, apiBaseUrl, meetingId, monthOffset]);
|
|
113
|
+
const bookMutation = useMutation({
|
|
114
|
+
mutationFn: async (payload) => {
|
|
138
115
|
try {
|
|
139
116
|
const res = await contentFetch(`${apiBaseUrl}/api/meetings/book`, {
|
|
140
117
|
method: "POST",
|
|
@@ -148,22 +125,37 @@ function useMeetingBooking(options) {
|
|
|
148
125
|
return { ok: false, code, message: typeof data.error === "string" ? data.error : void 0 };
|
|
149
126
|
} catch {
|
|
150
127
|
return { ok: false, code: "TEMPORARILY_UNAVAILABLE" };
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
// A booking is not a read — never retried, at any layer.
|
|
131
|
+
retry: false
|
|
132
|
+
});
|
|
133
|
+
const submittingRef = useRef(false);
|
|
134
|
+
const { mutateAsync } = bookMutation;
|
|
135
|
+
const book = useCallback(
|
|
136
|
+
async (payload) => {
|
|
137
|
+
if (submittingRef.current) return { ok: false, code: "VALIDATION", message: "Already submitting" };
|
|
138
|
+
submittingRef.current = true;
|
|
139
|
+
try {
|
|
140
|
+
return await mutateAsync(payload);
|
|
151
141
|
} finally {
|
|
152
142
|
submittingRef.current = false;
|
|
153
|
-
setIsSubmitting(false);
|
|
154
143
|
}
|
|
155
144
|
},
|
|
156
|
-
[
|
|
145
|
+
[mutateAsync]
|
|
157
146
|
);
|
|
158
147
|
return {
|
|
159
|
-
availability,
|
|
160
|
-
|
|
161
|
-
|
|
148
|
+
availability: availabilityQuery.data ?? null,
|
|
149
|
+
/** No availability for this month yet — the times column has nothing to show. */
|
|
150
|
+
isLoadingAvailability: availabilityQuery.isPending,
|
|
151
|
+
/** ANY request in flight, including a silent revalidation of cached data. */
|
|
152
|
+
isFetchingAvailability: availabilityQuery.isFetching,
|
|
153
|
+
availabilityError: availabilityQuery.error ? availabilityQuery.error.message : null,
|
|
162
154
|
monthOffset,
|
|
163
155
|
setMonthOffset,
|
|
164
156
|
refetchAvailability,
|
|
165
157
|
book,
|
|
166
|
-
isSubmitting
|
|
158
|
+
isSubmitting: bookMutation.isPending
|
|
167
159
|
};
|
|
168
160
|
}
|
|
169
161
|
|
|
@@ -258,7 +250,14 @@ function makeBookingSchema(formFields, legalConsent) {
|
|
|
258
250
|
// src/components/meeting-scheduler/context-panel.tsx
|
|
259
251
|
import { useMemo } from "react";
|
|
260
252
|
init_cn();
|
|
261
|
-
import {
|
|
253
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
254
|
+
var PANEL_STACK = "flex flex-col gap-[var(--spacing-system-l)]";
|
|
255
|
+
var IDENTITY_ROW = "flex items-center justify-between gap-[var(--spacing-system-m)] lg:flex-col lg:items-stretch lg:gap-[var(--spacing-system-l)]";
|
|
256
|
+
var STRIP_BODY = "flex flex-col gap-[var(--spacing-system-l)] md:flex-row md:items-start lg:flex-col";
|
|
257
|
+
var STRIP_MAIN = "flex min-w-0 flex-col gap-[var(--spacing-system-l)] md:flex-1";
|
|
258
|
+
var STRIP_ASIDE = "flex flex-col gap-[var(--spacing-system-xxs)] md:w-[280px] md:shrink-0 lg:w-full";
|
|
259
|
+
var BACK_BUTTON_CLASS = "py-0";
|
|
260
|
+
var FIELD_LABEL_CLASS = "text-h4 text-ods-text-primary";
|
|
262
261
|
function zoneLabel(tz) {
|
|
263
262
|
try {
|
|
264
263
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
@@ -271,25 +270,28 @@ function zoneLabel(tz) {
|
|
|
271
270
|
return tz.replace(/_/g, " ");
|
|
272
271
|
}
|
|
273
272
|
}
|
|
274
|
-
function ContextPanelSkeleton({
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
273
|
+
function ContextPanelSkeleton({
|
|
274
|
+
className,
|
|
275
|
+
onBack,
|
|
276
|
+
backLabel = "Back"
|
|
277
|
+
}) {
|
|
278
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(PANEL_STACK, className), children: [
|
|
279
|
+
onBack && /* @__PURE__ */ jsx(BackButton, { label: backLabel, onClick: onBack, className: BACK_BUTTON_CLASS }),
|
|
280
|
+
/* @__PURE__ */ jsxs("div", { className: STRIP_BODY, children: [
|
|
281
|
+
/* @__PURE__ */ jsx("div", { className: STRIP_MAIN, children: /* @__PURE__ */ jsxs("div", { className: IDENTITY_ROW, children: [
|
|
282
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-[var(--spacing-system-xs)] lg:flex-none", children: [
|
|
283
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-8 shrink-0 rounded-full" }),
|
|
284
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-[var(--spacing-system-xxs)]", children: [
|
|
285
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-32" }),
|
|
286
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" })
|
|
287
|
+
] })
|
|
288
|
+
] }),
|
|
289
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20 shrink-0" })
|
|
290
|
+
] }) }),
|
|
291
|
+
/* @__PURE__ */ jsxs("div", { className: STRIP_ASIDE, children: [
|
|
292
|
+
/* @__PURE__ */ jsx("p", { className: FIELD_LABEL_CLASS, children: "Timezone" }),
|
|
293
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full" })
|
|
288
294
|
] })
|
|
289
|
-
] }),
|
|
290
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--spacing-system-xs)]", children: [
|
|
291
|
-
/* @__PURE__ */ jsx("p", { className: "text-h5 text-ods-text-secondary", children: "Timezone" }),
|
|
292
|
-
/* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full" })
|
|
293
295
|
] })
|
|
294
296
|
] });
|
|
295
297
|
}
|
|
@@ -302,7 +304,10 @@ function SchedulerContextPanel({
|
|
|
302
304
|
onSelectDuration,
|
|
303
305
|
timezone,
|
|
304
306
|
onTimezoneChange,
|
|
307
|
+
onBack,
|
|
308
|
+
backLabel = "Back",
|
|
305
309
|
locked = false,
|
|
310
|
+
showTimezone = true,
|
|
306
311
|
className
|
|
307
312
|
}) {
|
|
308
313
|
const zoneOptions = useMemo(() => {
|
|
@@ -317,49 +322,67 @@ function SchedulerContextPanel({
|
|
|
317
322
|
if (!zones.includes(timezone)) zones = [timezone, ...zones];
|
|
318
323
|
return zones.map((tz) => ({ value: tz, label: zoneLabel(tz) }));
|
|
319
324
|
}, [timezone]);
|
|
320
|
-
return /* @__PURE__ */ jsxs("div", { className: cn(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
/* @__PURE__ */ jsxs("div", { className:
|
|
324
|
-
/* @__PURE__ */
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
"
|
|
325
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(PANEL_STACK, className), children: [
|
|
326
|
+
onBack && /* @__PURE__ */ jsx(BackButton, { label: backLabel, onClick: onBack, className: BACK_BUTTON_CLASS }),
|
|
327
|
+
/* @__PURE__ */ jsxs("div", { className: STRIP_BODY, children: [
|
|
328
|
+
/* @__PURE__ */ jsxs("div", { className: STRIP_MAIN, children: [
|
|
329
|
+
/* @__PURE__ */ jsxs("div", { className: IDENTITY_ROW, children: [
|
|
330
|
+
hosts.length > 0 && hosts.length <= 3 && /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-[var(--spacing-system-s)]", children: hosts.map((host) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[var(--spacing-system-xs)]", children: [
|
|
331
|
+
/* @__PURE__ */ jsx(
|
|
332
|
+
SquareAvatar,
|
|
333
|
+
{
|
|
334
|
+
variant: "round",
|
|
335
|
+
size: "sm",
|
|
336
|
+
src: host.avatarUrl ?? void 0,
|
|
337
|
+
alt: host.name,
|
|
338
|
+
fallback: host.name
|
|
339
|
+
}
|
|
340
|
+
),
|
|
341
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col min-w-0", children: [
|
|
342
|
+
/* @__PURE__ */ jsx("p", { className: "text-h4 text-ods-text-primary truncate", children: host.name }),
|
|
343
|
+
host.title && /* @__PURE__ */ jsx("p", { className: "text-h6 text-ods-text-secondary truncate", children: host.title })
|
|
344
|
+
] })
|
|
345
|
+
] }, host.name)) }),
|
|
346
|
+
hosts.length > 3 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--spacing-system-xs)]", children: [
|
|
347
|
+
/* @__PURE__ */ jsx(AvatarStack, { people: hosts, max: 4, size: "lg" }),
|
|
348
|
+
/* @__PURE__ */ jsxs("p", { className: "text-h6 text-ods-text-secondary", children: [
|
|
349
|
+
hosts.length,
|
|
350
|
+
" hosts on this calendar"
|
|
351
|
+
] })
|
|
352
|
+
] }),
|
|
353
|
+
locked || durationsMs.length <= 1 ? (
|
|
354
|
+
// Post-selection, or a link that offers one length: a static line,
|
|
355
|
+
// which is what sits beside the host on the header-strip layouts.
|
|
356
|
+
selectedDurationMs != null && /* @__PURE__ */ jsxs("p", { className: "shrink-0 text-h6 text-ods-text-secondary", children: [
|
|
357
|
+
formatDurationCompact(selectedDurationMs / 1e3),
|
|
358
|
+
" call"
|
|
359
|
+
] })
|
|
360
|
+
) : null
|
|
361
|
+
] }),
|
|
362
|
+
title && /* @__PURE__ */ jsx("p", { className: "text-h3 text-ods-text-primary", children: title }),
|
|
363
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-h6 text-ods-text-secondary", children: description }),
|
|
364
|
+
!locked && durationsMs.length > 1 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--spacing-system-xxs)]", children: [
|
|
365
|
+
/* @__PURE__ */ jsx("p", { className: FIELD_LABEL_CLASS, children: "Duration" }),
|
|
366
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-[var(--spacing-system-xs)]", children: durationsMs.map((ms) => /* @__PURE__ */ jsx(
|
|
367
|
+
Button,
|
|
368
|
+
{
|
|
369
|
+
variant: selectedDurationMs === ms ? void 0 : "outline",
|
|
370
|
+
size: "small-legacy",
|
|
371
|
+
onClick: () => onSelectDuration(ms),
|
|
372
|
+
children: formatDurationCompact(ms / 1e3)
|
|
373
|
+
},
|
|
374
|
+
ms
|
|
375
|
+
)) })
|
|
376
|
+
] })
|
|
360
377
|
] }),
|
|
361
|
-
|
|
362
|
-
|
|
378
|
+
showTimezone && // Once a slot is chosen the picker is desktop-only, which is what all
|
|
379
|
+
// three mocks draw. On the narrow layouts this panel is a strip
|
|
380
|
+
// COMPETING with the form for the screen's height, and the step's own
|
|
381
|
+
// summary line states the zone one row below — so the fact survives,
|
|
382
|
+
// only the control steps aside. The 280px sidebar has the room, so
|
|
383
|
+
// there it stays.
|
|
384
|
+
/* @__PURE__ */ jsxs("div", { className: cn(STRIP_ASIDE, locked && "max-lg:hidden"), children: [
|
|
385
|
+
/* @__PURE__ */ jsx("p", { className: FIELD_LABEL_CLASS, children: "Timezone" }),
|
|
363
386
|
timezone ? /* @__PURE__ */ jsx(
|
|
364
387
|
Autocomplete,
|
|
365
388
|
{
|
|
@@ -369,7 +392,6 @@ function SchedulerContextPanel({
|
|
|
369
392
|
},
|
|
370
393
|
options: zoneOptions,
|
|
371
394
|
placeholder: "Search timezone\u2026",
|
|
372
|
-
startAdornment: /* @__PURE__ */ jsx(ClockIcon, { className: "size-4 shrink-0 text-ods-text-secondary" }),
|
|
373
395
|
noOptionsText: "No matching timezone",
|
|
374
396
|
showClearAll: false
|
|
375
397
|
}
|
|
@@ -380,10 +402,10 @@ function SchedulerContextPanel({
|
|
|
380
402
|
}
|
|
381
403
|
|
|
382
404
|
// src/components/meeting-scheduler/slot-picker.tsx
|
|
383
|
-
import { useMemo as useMemo2 } from "react";
|
|
384
|
-
import {
|
|
405
|
+
import { useCallback as useCallback2, useMemo as useMemo2 } from "react";
|
|
406
|
+
import { ChevronRight } from "lucide-react";
|
|
385
407
|
init_cn();
|
|
386
|
-
import { Fragment
|
|
408
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
387
409
|
function dayKeyInZone(ms, timeZone) {
|
|
388
410
|
return new Intl.DateTimeFormat("en-CA", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" }).format(
|
|
389
411
|
new Date(ms)
|
|
@@ -400,38 +422,109 @@ function dateFromDayKey(key) {
|
|
|
400
422
|
function timeLabelInZone(ms, timeZone) {
|
|
401
423
|
return new Intl.DateTimeFormat(void 0, { timeZone, hour: "numeric", minute: "2-digit" }).format(new Date(ms));
|
|
402
424
|
}
|
|
403
|
-
var
|
|
404
|
-
var
|
|
425
|
+
var CALENDAR_W = "md:w-[19rem] md:shrink-0 lg:w-[16rem]";
|
|
426
|
+
var COLUMN_HEADING_CLASS = "shrink-0 text-h4 text-ods-text-primary";
|
|
427
|
+
var COLUMN_STACK = "flex min-h-0 flex-col gap-[var(--spacing-system-xxs)]";
|
|
428
|
+
var SLOT_PICKER_HEADING = "Select Date & Time";
|
|
429
|
+
var CHIP_GRID_CLASS = cn(
|
|
430
|
+
"grid grid-cols-[repeat(auto-fill,minmax(5.5rem,1fr))] content-start gap-[var(--spacing-system-xs)]",
|
|
431
|
+
"overflow-y-auto",
|
|
432
|
+
// PHONE: a stated window (~3 rows, the mock's 156px) that scrolls, rather
|
|
433
|
+
// than a list that runs the length of the day. Nothing else on the phone
|
|
434
|
+
// card has a height — the design lets each step be as tall as it needs —
|
|
435
|
+
// but a busy day is forty slots, and letting them all through turns a
|
|
436
|
+
// 700px card into a 1500px one and buries whatever the page puts below
|
|
437
|
+
// it. This is the one nested scroller worth having: a small, obviously
|
|
438
|
+
// bounded list with a visible edge, exactly as drawn.
|
|
439
|
+
"max-h-[9.75rem] md:max-h-none",
|
|
440
|
+
// No height of its own, and none borrowed from the calendar either: the card
|
|
441
|
+
// states one height, the panel and this row pass it down, and what is left
|
|
442
|
+
// after the column heading IS the scroll box. A long day scrolls inside it.
|
|
443
|
+
//
|
|
444
|
+
// `min-h-0` is the load-bearing half — a flex item's default
|
|
445
|
+
// `min-height:auto` refuses to shrink below its content, so without it the
|
|
446
|
+
// chips grow the column, then the row, then the card, and `overflow-y-auto`
|
|
447
|
+
// never gets a box smaller than its contents to scroll inside. That is the
|
|
448
|
+
// exact failure that kept this card at 404px when it was told to be 318.
|
|
449
|
+
"flex-1 min-h-0"
|
|
450
|
+
);
|
|
451
|
+
var CHIP_CLASS = "w-full px-[var(--spacing-system-xxs)]";
|
|
452
|
+
var SLOT_ROW_CLASS = cn(
|
|
453
|
+
"flex min-w-0 flex-col md:flex-row",
|
|
454
|
+
// Default `stretch` at every width, deliberately. Stacked, that is what
|
|
455
|
+
// makes each section span the card — an `items-start` here (it was here, to
|
|
456
|
+
// stop a column growing taller than its month) collapses both sections to
|
|
457
|
+
// their intrinsic WIDTH instead, which on a phone left the calendar and the
|
|
458
|
+
// chips huddled against the left edge with half the card empty. The
|
|
459
|
+
// calendar no longer needs protecting: its height is fixed, not derived.
|
|
460
|
+
"md:min-h-0 md:flex-1 lg:gap-[var(--spacing-system-lf)]"
|
|
461
|
+
);
|
|
462
|
+
var COLUMN_BOX = "p-[var(--spacing-system-l)] lg:p-0";
|
|
463
|
+
var COLUMN_DIVIDER = "border-b border-ods-border md:border-b-0 md:border-r lg:border-0";
|
|
405
464
|
function monthLabelFor(offset) {
|
|
406
465
|
const now = /* @__PURE__ */ new Date();
|
|
407
466
|
const month = new Date(now.getFullYear(), now.getMonth() + offset, 1);
|
|
408
467
|
return new Intl.DateTimeFormat("en-US", { month: "long", year: "numeric" }).format(month);
|
|
409
468
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
469
|
+
var DISABLE_EVERY_DAY = () => true;
|
|
470
|
+
var NOOP = () => void 0;
|
|
471
|
+
function DayCalendar({ monthOffset, onMonthOffsetChange, selectedDay, onSelectDay, slotsByDay }) {
|
|
472
|
+
const now = /* @__PURE__ */ new Date();
|
|
473
|
+
const startMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
474
|
+
const endMonth = new Date(now.getFullYear(), now.getMonth() + MAX_MONTH_OFFSET + 1, 0);
|
|
475
|
+
const visibleMonth = new Date(now.getFullYear(), now.getMonth() + monthOffset, 1);
|
|
476
|
+
const dayHasNoSlots = useCallback2(
|
|
477
|
+
(day) => !slotsByDay?.has(calendarDayKey(day)),
|
|
478
|
+
[slotsByDay]
|
|
479
|
+
);
|
|
480
|
+
return /* @__PURE__ */ jsx2("div", { className: "flex min-h-0 flex-1 flex-col", "aria-busy": slotsByDay === null, children: /* @__PURE__ */ jsx2(
|
|
481
|
+
DatePickerCalendar,
|
|
482
|
+
{
|
|
483
|
+
mode: "single",
|
|
484
|
+
bare: true,
|
|
485
|
+
month: visibleMonth,
|
|
486
|
+
onMonthChange: (month) => {
|
|
487
|
+
const offset = (month.getFullYear() - now.getFullYear()) * 12 + (month.getMonth() - now.getMonth());
|
|
488
|
+
onMonthOffsetChange(Math.max(0, Math.min(MAX_MONTH_OFFSET, offset)));
|
|
489
|
+
},
|
|
490
|
+
fromDate: startMonth,
|
|
491
|
+
toDate: endMonth,
|
|
492
|
+
selected: selectedDay ? dateFromDayKey(selectedDay) : void 0,
|
|
493
|
+
onSelect: (value) => {
|
|
494
|
+
const day = value;
|
|
495
|
+
if (!day) return;
|
|
496
|
+
const key = calendarDayKey(day);
|
|
497
|
+
if (slotsByDay?.has(key)) onSelectDay(key);
|
|
498
|
+
},
|
|
499
|
+
disabledDays: slotsByDay === null ? DISABLE_EVERY_DAY : dayHasNoSlots
|
|
500
|
+
}
|
|
501
|
+
) });
|
|
424
502
|
}
|
|
425
503
|
function SlotPickerSkeleton({ monthOffset = 0 }) {
|
|
426
|
-
return /* @__PURE__ */ jsxs2("div", { className:
|
|
427
|
-
/* @__PURE__ */
|
|
428
|
-
|
|
504
|
+
return /* @__PURE__ */ jsxs2("div", { className: SLOT_ROW_CLASS, children: [
|
|
505
|
+
/* @__PURE__ */ jsxs2("div", { className: cn(COLUMN_STACK, COLUMN_BOX, COLUMN_DIVIDER, CALENDAR_W, "md:overflow-y-auto"), children: [
|
|
506
|
+
/* @__PURE__ */ jsx2("p", { className: COLUMN_HEADING_CLASS, children: SLOT_PICKER_HEADING }),
|
|
507
|
+
/* @__PURE__ */ jsx2(
|
|
508
|
+
DayCalendar,
|
|
509
|
+
{
|
|
510
|
+
monthOffset,
|
|
511
|
+
onMonthOffsetChange: NOOP,
|
|
512
|
+
selectedDay: null,
|
|
513
|
+
onSelectDay: NOOP,
|
|
514
|
+
slotsByDay: null
|
|
515
|
+
}
|
|
516
|
+
)
|
|
517
|
+
] }),
|
|
518
|
+
/* @__PURE__ */ jsx2("div", { className: cn(COLUMN_STACK, COLUMN_BOX, "min-w-0 md:flex-1"), children: /* @__PURE__ */ jsx2(TimeChipsSkeleton, {}) })
|
|
429
519
|
] });
|
|
430
520
|
}
|
|
431
521
|
function TimeChipsSkeleton() {
|
|
432
|
-
return /* @__PURE__ */ jsxs2(
|
|
433
|
-
/* @__PURE__ */ jsx2(Skeleton, { className: "
|
|
434
|
-
/* @__PURE__ */ jsx2("div", { className:
|
|
522
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
523
|
+
/* @__PURE__ */ jsx2("div", { className: COLUMN_HEADING_CLASS, children: /* @__PURE__ */ jsx2(Skeleton, { className: "inline-block h-4 w-40 align-middle" }) }),
|
|
524
|
+
/* @__PURE__ */ jsx2("div", { className: CHIP_GRID_CLASS, children: Array.from({ length: 16 }, (_, i) => (
|
|
525
|
+
// h-11 md:h-12 — the loaded chip's own default-size height.
|
|
526
|
+
/* @__PURE__ */ jsx2(Skeleton, { className: cn(CHIP_CLASS, "h-11 md:h-12") }, i)
|
|
527
|
+
)) })
|
|
435
528
|
] });
|
|
436
529
|
}
|
|
437
530
|
function SlotPicker({
|
|
@@ -456,58 +549,70 @@ function SlotPicker({
|
|
|
456
549
|
for (const list of map.values()) list.sort((a, b) => a - b);
|
|
457
550
|
return map;
|
|
458
551
|
}, [slots, timezone]);
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
552
|
+
const visibleMonth = useMemo2(() => {
|
|
553
|
+
const now = /* @__PURE__ */ new Date();
|
|
554
|
+
return new Date(now.getFullYear(), now.getMonth() + monthOffset, 1);
|
|
555
|
+
}, [monthOffset]);
|
|
463
556
|
const daySlots = selectedDay ? slotsByDay.get(selectedDay) ?? [] : [];
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
onMonthOffsetChange
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
},
|
|
483
|
-
disabled: (day) => !slotsByDay.has(calendarDayKey(day)),
|
|
484
|
-
className: "p-0"
|
|
485
|
-
}
|
|
486
|
-
) }),
|
|
487
|
-
/* @__PURE__ */ jsx2("div", { className: "flex-1 min-w-0 flex flex-col gap-[var(--spacing-system-s)]", children: isLoading ? /* @__PURE__ */ jsx2(TimeChipsSkeleton, {}) : selectedDay ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
488
|
-
/* @__PURE__ */ jsx2("p", { className: "text-h6 text-ods-text-primary", children: new Intl.DateTimeFormat(void 0, {
|
|
557
|
+
const monthPrefix = `${visibleMonth.getFullYear()}-${String(visibleMonth.getMonth() + 1).padStart(2, "0")}`;
|
|
558
|
+
const monthHasSlots = [...slotsByDay.keys()].some((key) => key.startsWith(monthPrefix));
|
|
559
|
+
return /* @__PURE__ */ jsxs2("div", { className: SLOT_ROW_CLASS, children: [
|
|
560
|
+
/* @__PURE__ */ jsxs2("div", { className: cn(COLUMN_STACK, COLUMN_BOX, COLUMN_DIVIDER, CALENDAR_W, "md:overflow-y-auto"), children: [
|
|
561
|
+
/* @__PURE__ */ jsx2("p", { className: COLUMN_HEADING_CLASS, children: SLOT_PICKER_HEADING }),
|
|
562
|
+
/* @__PURE__ */ jsx2(
|
|
563
|
+
DayCalendar,
|
|
564
|
+
{
|
|
565
|
+
monthOffset,
|
|
566
|
+
onMonthOffsetChange,
|
|
567
|
+
selectedDay,
|
|
568
|
+
onSelectDay,
|
|
569
|
+
slotsByDay: isLoading ? null : slotsByDay
|
|
570
|
+
}
|
|
571
|
+
)
|
|
572
|
+
] }),
|
|
573
|
+
/* @__PURE__ */ jsx2("div", { className: cn(COLUMN_STACK, COLUMN_BOX, "min-w-0 md:flex-1"), children: isLoading ? /* @__PURE__ */ jsx2(TimeChipsSkeleton, {}) : selectedDay ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
574
|
+
/* @__PURE__ */ jsx2("p", { className: COLUMN_HEADING_CLASS, children: new Intl.DateTimeFormat(void 0, {
|
|
489
575
|
weekday: "long",
|
|
490
576
|
month: "long",
|
|
491
577
|
day: "numeric"
|
|
492
578
|
}).format(dateFromDayKey(selectedDay)) }),
|
|
493
|
-
/* @__PURE__ */ jsx2("div", { className:
|
|
579
|
+
/* @__PURE__ */ jsx2("div", { className: CHIP_GRID_CLASS, children: daySlots.map((ms) => {
|
|
494
580
|
const isSelected = selectedSlot === ms;
|
|
495
581
|
return /* @__PURE__ */ jsx2(
|
|
496
582
|
Button,
|
|
497
583
|
{
|
|
498
584
|
variant: isSelected ? void 0 : "outline",
|
|
499
|
-
size: "small-legacy",
|
|
500
585
|
onClick: () => onSelectSlot(ms),
|
|
586
|
+
className: CHIP_CLASS,
|
|
501
587
|
children: timeLabelInZone(ms, timezone)
|
|
502
588
|
},
|
|
503
589
|
ms
|
|
504
590
|
);
|
|
505
591
|
}) })
|
|
506
|
-
] }) : /* @__PURE__ */ jsx2("p", { className: "text-h6 text-ods-text-secondary", children:
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
592
|
+
] }) : monthHasSlots ? /* @__PURE__ */ jsx2("p", { className: "text-h6 text-ods-text-secondary", children: "Pick a day to see available times." }) : (
|
|
593
|
+
// A fully-booked month used to just SAY "try the next one" and leave
|
|
594
|
+
// the visitor to find the chevron in the other column. The way out
|
|
595
|
+
// of a dead end belongs in the dead end.
|
|
596
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-start gap-[var(--spacing-system-s)]", children: [
|
|
597
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-h6 text-ods-text-secondary", children: [
|
|
598
|
+
"No available times in ",
|
|
599
|
+
monthLabelFor(monthOffset),
|
|
600
|
+
"."
|
|
601
|
+
] }),
|
|
602
|
+
monthOffset < MAX_MONTH_OFFSET && /* @__PURE__ */ jsxs2(
|
|
603
|
+
Button,
|
|
604
|
+
{
|
|
605
|
+
variant: "outline",
|
|
606
|
+
onClick: () => onMonthOffsetChange(monthOffset + 1),
|
|
607
|
+
rightIcon: /* @__PURE__ */ jsx2(ChevronRight, { className: "size-5" }),
|
|
608
|
+
children: [
|
|
609
|
+
"Check ",
|
|
610
|
+
monthLabelFor(monthOffset + 1)
|
|
611
|
+
]
|
|
612
|
+
}
|
|
613
|
+
)
|
|
614
|
+
] })
|
|
615
|
+
) })
|
|
511
616
|
] });
|
|
512
617
|
}
|
|
513
618
|
|
|
@@ -515,7 +620,7 @@ function SlotPicker({
|
|
|
515
620
|
import { useMemo as useMemo3 } from "react";
|
|
516
621
|
import { useForm, Controller } from "react-hook-form";
|
|
517
622
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
518
|
-
import { Fragment as
|
|
623
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
519
624
|
function BookingForm({
|
|
520
625
|
availability,
|
|
521
626
|
meetingId,
|
|
@@ -560,124 +665,115 @@ function BookingForm({
|
|
|
560
665
|
const err = errors.formFields?.[name];
|
|
561
666
|
return err?.message;
|
|
562
667
|
};
|
|
563
|
-
const inputClass = "bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-12";
|
|
564
|
-
|
|
668
|
+
const inputClass = "bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-11 md:h-12";
|
|
669
|
+
const FORM_STACK = "flex flex-col gap-[var(--spacing-system-l)]";
|
|
670
|
+
return /* @__PURE__ */ jsxs3("form", { onSubmit: submit, className: FORM_STACK, noValidate: true, children: [
|
|
565
671
|
/* @__PURE__ */ jsx3(HoneypotField, { ...honeypotInputProps }),
|
|
566
|
-
/* @__PURE__ */
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
582
|
-
),
|
|
583
|
-
errors.firstName && /* @__PURE__ */ jsx3("span", { className: "text-ods-error text-h6 mt-1", children: errors.firstName.message })
|
|
584
|
-
] }),
|
|
585
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex flex-col", children: [
|
|
586
|
-
/* @__PURE__ */ jsxs3(Label, { htmlFor: "ms-last-name", children: [
|
|
587
|
-
"Last name",
|
|
588
|
-
/* @__PURE__ */ jsx3("span", { className: "text-ods-accent", children: "*" })
|
|
589
|
-
] }),
|
|
590
|
-
/* @__PURE__ */ jsx3(
|
|
591
|
-
Input,
|
|
592
|
-
{
|
|
593
|
-
id: "ms-last-name",
|
|
594
|
-
autoComplete: "family-name",
|
|
595
|
-
placeholder: "Doe",
|
|
596
|
-
"aria-invalid": !!errors.lastName,
|
|
597
|
-
className: inputClass,
|
|
598
|
-
...register("lastName")
|
|
599
|
-
}
|
|
600
|
-
),
|
|
601
|
-
errors.lastName && /* @__PURE__ */ jsx3("span", { className: "text-ods-error text-h6 mt-1", children: errors.lastName.message })
|
|
602
|
-
] })
|
|
603
|
-
] }),
|
|
604
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex flex-col", children: [
|
|
605
|
-
/* @__PURE__ */ jsxs3(Label, { htmlFor: "ms-email", children: [
|
|
606
|
-
"Email",
|
|
607
|
-
/* @__PURE__ */ jsx3("span", { className: "text-ods-accent", children: "*" })
|
|
608
|
-
] }),
|
|
609
|
-
/* @__PURE__ */ jsx3(
|
|
672
|
+
/* @__PURE__ */ jsx3(FieldWrapper, { label: "Email", htmlFor: "ms-email", error: errors.email?.message, children: /* @__PURE__ */ jsx3(
|
|
673
|
+
Input,
|
|
674
|
+
{
|
|
675
|
+
id: "ms-email",
|
|
676
|
+
type: "email",
|
|
677
|
+
required: true,
|
|
678
|
+
autoComplete: "email",
|
|
679
|
+
placeholder: "jane@company.com",
|
|
680
|
+
"aria-invalid": !!errors.email,
|
|
681
|
+
className: inputClass,
|
|
682
|
+
...register("email")
|
|
683
|
+
}
|
|
684
|
+
) }),
|
|
685
|
+
/* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 gap-[var(--spacing-system-m)]", children: [
|
|
686
|
+
/* @__PURE__ */ jsx3(FieldWrapper, { label: "First Name", htmlFor: "ms-first-name", error: errors.firstName?.message, children: /* @__PURE__ */ jsx3(
|
|
610
687
|
Input,
|
|
611
688
|
{
|
|
612
|
-
id: "ms-
|
|
613
|
-
|
|
614
|
-
autoComplete: "
|
|
615
|
-
placeholder: "
|
|
616
|
-
"aria-invalid": !!errors.
|
|
689
|
+
id: "ms-first-name",
|
|
690
|
+
required: true,
|
|
691
|
+
autoComplete: "given-name",
|
|
692
|
+
placeholder: "Jane",
|
|
693
|
+
"aria-invalid": !!errors.firstName,
|
|
617
694
|
className: inputClass,
|
|
618
|
-
...register("
|
|
695
|
+
...register("firstName")
|
|
619
696
|
}
|
|
620
|
-
),
|
|
621
|
-
|
|
622
|
-
] }),
|
|
623
|
-
supportedFields.map((field) => /* @__PURE__ */ jsxs3("div", { className: "flex flex-col", children: [
|
|
624
|
-
/* @__PURE__ */ jsxs3(Label, { htmlFor: `ms-q-${field.name}`, children: [
|
|
625
|
-
field.label,
|
|
626
|
-
field.required && /* @__PURE__ */ jsx3("span", { className: "text-ods-accent", children: "*" })
|
|
627
|
-
] }),
|
|
628
|
-
field.type === "textarea" && /* @__PURE__ */ jsx3(
|
|
629
|
-
Textarea,
|
|
630
|
-
{
|
|
631
|
-
id: `ms-q-${field.name}`,
|
|
632
|
-
className: "bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3",
|
|
633
|
-
...register(`formFields.${field.name}`)
|
|
634
|
-
}
|
|
635
|
-
),
|
|
636
|
-
field.type === "text" && /* @__PURE__ */ jsx3(
|
|
697
|
+
) }),
|
|
698
|
+
/* @__PURE__ */ jsx3(FieldWrapper, { label: "Last Name", htmlFor: "ms-last-name", error: errors.lastName?.message, children: /* @__PURE__ */ jsx3(
|
|
637
699
|
Input,
|
|
638
700
|
{
|
|
639
|
-
id:
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
{
|
|
647
|
-
control,
|
|
648
|
-
name: `formFields.${field.name}`,
|
|
649
|
-
render: ({ field: rhf }) => field.type === "select" ? /* @__PURE__ */ jsxs3(Select, { value: rhf.value ?? "", onValueChange: rhf.onChange, children: [
|
|
650
|
-
/* @__PURE__ */ jsx3(SelectTrigger, { id: `ms-q-${field.name}`, children: /* @__PURE__ */ jsx3(SelectValue, { placeholder: "Select\u2026" }) }),
|
|
651
|
-
/* @__PURE__ */ jsx3(SelectContent, { children: (field.options ?? []).map((opt) => /* @__PURE__ */ jsx3(SelectItem, { value: opt, children: opt }, opt)) })
|
|
652
|
-
] }) : /* @__PURE__ */ jsx3(RadioGroup, { value: rhf.value ?? "", onValueChange: rhf.onChange, children: (field.options ?? []).map((opt) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-[var(--spacing-system-xs)]", children: [
|
|
653
|
-
/* @__PURE__ */ jsx3(RadioGroupItem, { id: `ms-q-${field.name}-${opt}`, value: opt }),
|
|
654
|
-
/* @__PURE__ */ jsx3(Label, { htmlFor: `ms-q-${field.name}-${opt}`, children: opt })
|
|
655
|
-
] }, opt)) })
|
|
701
|
+
id: "ms-last-name",
|
|
702
|
+
required: true,
|
|
703
|
+
autoComplete: "family-name",
|
|
704
|
+
placeholder: "Doe",
|
|
705
|
+
"aria-invalid": !!errors.lastName,
|
|
706
|
+
className: inputClass,
|
|
707
|
+
...register("lastName")
|
|
656
708
|
}
|
|
657
|
-
)
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
{
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
709
|
+
) })
|
|
710
|
+
] }),
|
|
711
|
+
supportedFields.map((field) => /* @__PURE__ */ jsxs3(
|
|
712
|
+
FieldWrapper,
|
|
713
|
+
{
|
|
714
|
+
label: field.label,
|
|
715
|
+
htmlFor: `ms-q-${field.name}`,
|
|
716
|
+
error: fieldError(field.name),
|
|
717
|
+
children: [
|
|
718
|
+
field.type === "textarea" && /* @__PURE__ */ jsx3(
|
|
719
|
+
Textarea,
|
|
720
|
+
{
|
|
721
|
+
id: `ms-q-${field.name}`,
|
|
722
|
+
className: "bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3",
|
|
723
|
+
...register(`formFields.${field.name}`)
|
|
724
|
+
}
|
|
725
|
+
),
|
|
726
|
+
field.type === "text" && /* @__PURE__ */ jsx3(
|
|
727
|
+
Input,
|
|
728
|
+
{
|
|
729
|
+
id: `ms-q-${field.name}`,
|
|
730
|
+
className: "bg-ods-card border-ods-border text-ods-text-primary placeholder-ods-text-secondary px-3 h-11 md:h-12",
|
|
731
|
+
...register(`formFields.${field.name}`)
|
|
732
|
+
}
|
|
733
|
+
),
|
|
734
|
+
(field.type === "select" || field.type === "radio") && /* @__PURE__ */ jsx3(
|
|
735
|
+
Controller,
|
|
736
|
+
{
|
|
737
|
+
control,
|
|
738
|
+
name: `formFields.${field.name}`,
|
|
739
|
+
render: ({ field: rhf }) => field.type === "select" ? /* @__PURE__ */ jsxs3(Select, { value: rhf.value ?? "", onValueChange: rhf.onChange, children: [
|
|
740
|
+
/* @__PURE__ */ jsx3(SelectTrigger, { id: `ms-q-${field.name}`, children: /* @__PURE__ */ jsx3(SelectValue, { placeholder: "Select\u2026" }) }),
|
|
741
|
+
/* @__PURE__ */ jsx3(SelectContent, { children: (field.options ?? []).map((opt) => /* @__PURE__ */ jsx3(SelectItem, { value: opt, children: opt }, opt)) })
|
|
742
|
+
] }) : /* @__PURE__ */ jsx3(RadioGroup, { value: rhf.value ?? "", onValueChange: rhf.onChange, children: (field.options ?? []).map((opt) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-[var(--spacing-system-xs)]", children: [
|
|
743
|
+
/* @__PURE__ */ jsx3(RadioGroupItem, { id: `ms-q-${field.name}-${opt}`, value: opt }),
|
|
744
|
+
/* @__PURE__ */ jsx3(Label, { htmlFor: `ms-q-${field.name}-${opt}`, children: opt })
|
|
745
|
+
] }, opt)) })
|
|
746
|
+
}
|
|
747
|
+
),
|
|
748
|
+
field.type === "checkbox" && /* @__PURE__ */ jsx3(
|
|
749
|
+
Controller,
|
|
750
|
+
{
|
|
751
|
+
control,
|
|
752
|
+
name: `formFields.${field.name}`,
|
|
753
|
+
render: ({ field: rhf }) => (
|
|
754
|
+
// The question's Label above already carries the text and
|
|
755
|
+
// binds to this id — a second inline label would double the
|
|
756
|
+
// visible text AND the accessible name.
|
|
757
|
+
/* @__PURE__ */ jsx3("div", { className: "flex items-center gap-[var(--spacing-system-xs)]", children: /* @__PURE__ */ jsx3(
|
|
758
|
+
Checkbox,
|
|
759
|
+
{
|
|
760
|
+
id: `ms-q-${field.name}`,
|
|
761
|
+
checked: Boolean(rhf.value),
|
|
762
|
+
onCheckedChange: (v) => rhf.onChange(v === true)
|
|
763
|
+
}
|
|
764
|
+
) })
|
|
765
|
+
)
|
|
766
|
+
}
|
|
675
767
|
)
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
legalConsent &&
|
|
768
|
+
]
|
|
769
|
+
},
|
|
770
|
+
field.name
|
|
771
|
+
)),
|
|
772
|
+
legalConsent && // Same out-of-flow message as the fields above, so a missed consent
|
|
773
|
+
// box doesn't shove the submit button down the card. With no error the
|
|
774
|
+
// wrapper is `display:contents` and the panel below is the flex item,
|
|
775
|
+
// exactly as it was.
|
|
776
|
+
/* @__PURE__ */ jsx3(FieldWrapper, { error: errors.legalConsentResponses?.message, children: /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-[var(--spacing-system-xs)] border border-ods-border rounded-md p-[var(--spacing-system-m)]", children: [
|
|
681
777
|
/* @__PURE__ */ jsx3("p", { className: "text-h6 text-ods-text-secondary", children: legalConsent.processingConsentText }),
|
|
682
778
|
legalConsent.communicationConsentText && /* @__PURE__ */ jsx3("p", { className: "text-h6 text-ods-text-secondary", children: legalConsent.communicationConsentText }),
|
|
683
779
|
/* @__PURE__ */ jsx3(
|
|
@@ -685,7 +781,7 @@ function BookingForm({
|
|
|
685
781
|
{
|
|
686
782
|
control,
|
|
687
783
|
name: "legalConsentResponses",
|
|
688
|
-
render: ({ field: rhf }) => /* @__PURE__ */ jsx3(
|
|
784
|
+
render: ({ field: rhf }) => /* @__PURE__ */ jsx3(Fragment2, { children: legalConsent.communicationConsentCheckboxes.map((box) => {
|
|
689
785
|
const responses = rhf.value ?? [];
|
|
690
786
|
const current = responses.find((r) => r.communicationTypeId === box.communicationTypeId);
|
|
691
787
|
return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-[var(--spacing-system-xs)]", children: [
|
|
@@ -701,18 +797,14 @@ function BookingForm({
|
|
|
701
797
|
)
|
|
702
798
|
}
|
|
703
799
|
),
|
|
704
|
-
/* @__PURE__ */
|
|
705
|
-
box.label,
|
|
706
|
-
box.required ? " *" : ""
|
|
707
|
-
] })
|
|
800
|
+
/* @__PURE__ */ jsx3(Label, { htmlFor: `ms-consent-${box.communicationTypeId}`, children: box.label })
|
|
708
801
|
] }, box.communicationTypeId);
|
|
709
802
|
}) })
|
|
710
803
|
}
|
|
711
804
|
),
|
|
712
|
-
legalConsent.privacyPolicyText && /* @__PURE__ */ jsx3("p", { className: "text-h6 text-ods-text-secondary", children: legalConsent.privacyPolicyText })
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
/* @__PURE__ */ jsx3("div", { className: "flex", children: /* @__PURE__ */ jsx3(Button, { type: "submit", loading: isSubmitting, disabled: isSubmitting, className: "w-full md:w-auto", children: "Confirm booking" }) })
|
|
805
|
+
legalConsent.privacyPolicyText && /* @__PURE__ */ jsx3("p", { className: "text-h6 text-ods-text-secondary", children: legalConsent.privacyPolicyText })
|
|
806
|
+
] }) }),
|
|
807
|
+
/* @__PURE__ */ jsx3("div", { className: "flex", children: /* @__PURE__ */ jsx3(Button, { type: "submit", loading: isSubmitting, disabled: isSubmitting, children: "Confirm Booking" }) })
|
|
716
808
|
] });
|
|
717
809
|
}
|
|
718
810
|
|
|
@@ -728,7 +820,7 @@ function Confirmation({ confirmation, timezone }) {
|
|
|
728
820
|
minute: "2-digit",
|
|
729
821
|
timeZoneName: "short"
|
|
730
822
|
}).format(new Date(confirmation.startTimeMs));
|
|
731
|
-
return /* @__PURE__ */ jsxs4("div", { className: "flex flex-1 flex-col items-center justify-center gap-[var(--spacing-system-
|
|
823
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex flex-1 flex-col items-center justify-center gap-[var(--spacing-system-m)] text-center", children: [
|
|
732
824
|
/* @__PURE__ */ jsx4("p", { className: "text-h1", role: "img", "aria-label": "Celebration", children: "\u{1F389}" }),
|
|
733
825
|
/* @__PURE__ */ jsx4("h3", { className: "text-h3 text-ods-text-primary", children: "You're booked" }),
|
|
734
826
|
/* @__PURE__ */ jsx4("p", { className: "text-h5 text-ods-text-primary", children: confirmation.title }),
|
|
@@ -743,13 +835,13 @@ function Confirmation({ confirmation, timezone }) {
|
|
|
743
835
|
|
|
744
836
|
// src/components/meeting-scheduler/directory.tsx
|
|
745
837
|
init_next_link();
|
|
746
|
-
import { useEffect
|
|
838
|
+
import { useEffect, useMemo as useMemo4, useState as useState2 } from "react";
|
|
747
839
|
import { ChevronRight as ChevronRight2 } from "lucide-react";
|
|
748
840
|
init_cn();
|
|
749
841
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
750
842
|
function useNextAvailableLabel(ms) {
|
|
751
843
|
const [label, setLabel] = useState2(null);
|
|
752
|
-
|
|
844
|
+
useEffect(() => {
|
|
753
845
|
if (ms == null) return;
|
|
754
846
|
try {
|
|
755
847
|
setLabel(
|
|
@@ -829,7 +921,7 @@ function MeetingSchedulerDirectory({
|
|
|
829
921
|
const [isLoading, setIsLoading] = useState2(initialData === null);
|
|
830
922
|
const [error, setError] = useState2(null);
|
|
831
923
|
const [page, setPage] = useState2(1);
|
|
832
|
-
|
|
924
|
+
useEffect(() => {
|
|
833
925
|
if (initialData !== null) return;
|
|
834
926
|
const controller = new AbortController();
|
|
835
927
|
let active = true;
|
|
@@ -907,6 +999,21 @@ function MeetingSchedulerDirectory({
|
|
|
907
999
|
|
|
908
1000
|
// src/components/meeting-scheduler/index.tsx
|
|
909
1001
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1002
|
+
var CONTEXT_PANEL_CLASS = "p-[var(--spacing-system-l)] shrink-0 lg:w-[280px] border-b lg:border-b-0 lg:border-r border-ods-border lg:min-h-0 lg:overflow-y-auto";
|
|
1003
|
+
var ACTION_PANEL_CLASS = "flex-1 min-w-0 flex flex-col md:min-h-0 lg:p-[var(--spacing-system-l)]";
|
|
1004
|
+
var PANEL_STEP_CLASS = "p-[var(--spacing-system-l)] md:min-h-0 md:overflow-y-auto lg:p-0";
|
|
1005
|
+
var MEETING_SCHEDULER_H = "md:h-[34.375rem] lg:h-[23.75rem]";
|
|
1006
|
+
var CARD_CLASS = cn(
|
|
1007
|
+
"rounded-md border border-ods-border bg-ods-card overflow-hidden",
|
|
1008
|
+
"md:flex md:flex-col",
|
|
1009
|
+
MEETING_SCHEDULER_H
|
|
1010
|
+
);
|
|
1011
|
+
var CARD_INNER_CLASS = "flex flex-col md:min-h-0 md:flex-1 lg:h-full lg:flex-row";
|
|
1012
|
+
var CARD_DEGRADED_CLASS = cn(
|
|
1013
|
+
"rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-lf)] flex flex-col items-start gap-[var(--spacing-system-m)]",
|
|
1014
|
+
"md:justify-center md:overflow-y-auto",
|
|
1015
|
+
MEETING_SCHEDULER_H
|
|
1016
|
+
);
|
|
910
1017
|
function isNativelyBookable(availability) {
|
|
911
1018
|
if (!availability.formFields.every(isSupportedFormField)) return false;
|
|
912
1019
|
const consent = availability.legalConsent;
|
|
@@ -925,12 +1032,14 @@ function HubSpotMeetingScheduler({
|
|
|
925
1032
|
hosts,
|
|
926
1033
|
displayTimezone,
|
|
927
1034
|
fallbackUrl,
|
|
1035
|
+
onBack,
|
|
928
1036
|
onBooked,
|
|
929
1037
|
className
|
|
930
1038
|
}) {
|
|
931
1039
|
const {
|
|
932
1040
|
availability,
|
|
933
1041
|
isLoadingAvailability,
|
|
1042
|
+
isFetchingAvailability,
|
|
934
1043
|
availabilityError,
|
|
935
1044
|
monthOffset,
|
|
936
1045
|
setMonthOffset,
|
|
@@ -939,7 +1048,7 @@ function HubSpotMeetingScheduler({
|
|
|
939
1048
|
isSubmitting
|
|
940
1049
|
} = useMeetingBooking({ meetingId, apiBaseUrl, initialAvailability });
|
|
941
1050
|
const [timezone, setTimezone] = useState3(displayTimezone ?? null);
|
|
942
|
-
|
|
1051
|
+
useEffect2(() => {
|
|
943
1052
|
if (displayTimezone) return;
|
|
944
1053
|
try {
|
|
945
1054
|
setTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC");
|
|
@@ -954,7 +1063,7 @@ function HubSpotMeetingScheduler({
|
|
|
954
1063
|
const [confirmation, setConfirmation] = useState3(null);
|
|
955
1064
|
const [bookingError, setBookingError] = useState3(null);
|
|
956
1065
|
const { honeypotInputProps, getSignals, resetSignals } = useHumanitySignals();
|
|
957
|
-
|
|
1066
|
+
useEffect2(() => {
|
|
958
1067
|
setStep("slot");
|
|
959
1068
|
setDurationMs(null);
|
|
960
1069
|
setSelectedDay(null);
|
|
@@ -965,14 +1074,14 @@ function HubSpotMeetingScheduler({
|
|
|
965
1074
|
}, [meetingId]);
|
|
966
1075
|
const durations = availability?.durationsMs ?? [];
|
|
967
1076
|
const shownHosts = hosts ?? availability?.hosts ?? [];
|
|
968
|
-
|
|
1077
|
+
useEffect2(() => {
|
|
969
1078
|
if (durationMs == null && durations.length > 0) setDurationMs(durations[0]);
|
|
970
1079
|
}, [durationMs, durations]);
|
|
971
1080
|
const slots = useMemo5(() => {
|
|
972
1081
|
if (!availability || durationMs == null) return [];
|
|
973
1082
|
return availability.slotsByDurationMs[String(durationMs)] ?? [];
|
|
974
1083
|
}, [availability, durationMs]);
|
|
975
|
-
|
|
1084
|
+
useEffect2(() => {
|
|
976
1085
|
if (step !== "slot" || !timezone) return;
|
|
977
1086
|
const now = /* @__PURE__ */ new Date();
|
|
978
1087
|
const visible = new Date(now.getFullYear(), now.getMonth() + monthOffset, 1);
|
|
@@ -987,13 +1096,17 @@ function HubSpotMeetingScheduler({
|
|
|
987
1096
|
setSelectedDay(dayKeyInZone(inMonth[0], timezone));
|
|
988
1097
|
}, [step, timezone, slots, selectedDay, monthOffset]);
|
|
989
1098
|
const autoAdvanceCount = useRef2(0);
|
|
990
|
-
|
|
991
|
-
if (step === "slot" && availability && !
|
|
1099
|
+
useEffect2(() => {
|
|
1100
|
+
if (step === "slot" && availability && !isFetchingAvailability && durationMs != null && slots.length === 0 && availability.hasMore && autoAdvanceCount.current < 3) {
|
|
992
1101
|
autoAdvanceCount.current += 1;
|
|
993
1102
|
setMonthOffset(monthOffset + 1);
|
|
994
1103
|
}
|
|
995
|
-
}, [step, availability,
|
|
996
|
-
const
|
|
1104
|
+
}, [step, availability, isFetchingAvailability, durationMs, slots, monthOffset, setMonthOffset]);
|
|
1105
|
+
const backToSlot = useCallback3(() => {
|
|
1106
|
+
setStep("slot");
|
|
1107
|
+
setBookingError(null);
|
|
1108
|
+
}, []);
|
|
1109
|
+
const handleSubmit = useCallback3(
|
|
997
1110
|
async (payload) => {
|
|
998
1111
|
setBookingError(null);
|
|
999
1112
|
const result = await book(payload);
|
|
@@ -1016,22 +1129,16 @@ function HubSpotMeetingScheduler({
|
|
|
1016
1129
|
);
|
|
1017
1130
|
const escapeHatch = fallbackUrl ? /* @__PURE__ */ jsx6(Button, { variant: "outline", size: "small-legacy", href: fallbackUrl, openInNewTab: true, children: "Open in HubSpot" }) : null;
|
|
1018
1131
|
if (isLoadingAvailability && !availability) {
|
|
1019
|
-
return /* @__PURE__ */ jsx6("div", { className: cn(
|
|
1020
|
-
/* @__PURE__ */ jsx6(ContextPanelSkeleton, { className:
|
|
1021
|
-
/* @__PURE__ */
|
|
1022
|
-
/* @__PURE__ */ jsx6("p", { className: "text-h5 text-ods-text-secondary", children: "Select a date & time" }),
|
|
1023
|
-
/* @__PURE__ */ jsx6(SlotPickerSkeleton, {})
|
|
1024
|
-
] })
|
|
1132
|
+
return /* @__PURE__ */ jsx6("div", { className: cn(CARD_CLASS, className), children: /* @__PURE__ */ jsxs6("div", { className: CARD_INNER_CLASS, children: [
|
|
1133
|
+
/* @__PURE__ */ jsx6(ContextPanelSkeleton, { onBack, className: CONTEXT_PANEL_CLASS }),
|
|
1134
|
+
/* @__PURE__ */ jsx6("div", { className: ACTION_PANEL_CLASS, children: /* @__PURE__ */ jsx6(SlotPickerSkeleton, { monthOffset }) })
|
|
1025
1135
|
] }) });
|
|
1026
1136
|
}
|
|
1027
1137
|
if (availabilityError || !availability) {
|
|
1028
1138
|
return /* @__PURE__ */ jsxs6(
|
|
1029
1139
|
"div",
|
|
1030
1140
|
{
|
|
1031
|
-
className: cn(
|
|
1032
|
-
"rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-lf)] flex flex-col items-start gap-[var(--spacing-system-md)]",
|
|
1033
|
-
className
|
|
1034
|
-
),
|
|
1141
|
+
className: cn(CARD_DEGRADED_CLASS, className),
|
|
1035
1142
|
children: [
|
|
1036
1143
|
/* @__PURE__ */ jsx6("p", { className: "text-h6 text-ods-text-secondary", children: "We couldn't load available call times. Please try again shortly." }),
|
|
1037
1144
|
escapeHatch
|
|
@@ -1043,10 +1150,7 @@ function HubSpotMeetingScheduler({
|
|
|
1043
1150
|
return /* @__PURE__ */ jsxs6(
|
|
1044
1151
|
"div",
|
|
1045
1152
|
{
|
|
1046
|
-
className: cn(
|
|
1047
|
-
"rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-lf)] flex flex-col items-start gap-[var(--spacing-system-md)]",
|
|
1048
|
-
className
|
|
1049
|
-
),
|
|
1153
|
+
className: cn(CARD_DEGRADED_CLASS, className),
|
|
1050
1154
|
children: [
|
|
1051
1155
|
/* @__PURE__ */ jsx6("p", { className: "text-h6 text-ods-text-secondary", children: "This meeting type is booked directly on HubSpot." }),
|
|
1052
1156
|
escapeHatch
|
|
@@ -1054,7 +1158,7 @@ function HubSpotMeetingScheduler({
|
|
|
1054
1158
|
}
|
|
1055
1159
|
);
|
|
1056
1160
|
}
|
|
1057
|
-
return /* @__PURE__ */ jsx6("div", { className: cn(
|
|
1161
|
+
return /* @__PURE__ */ jsx6("div", { className: cn(CARD_CLASS, className), children: /* @__PURE__ */ jsxs6("div", { className: CARD_INNER_CLASS, children: [
|
|
1058
1162
|
/* @__PURE__ */ jsx6(
|
|
1059
1163
|
SchedulerContextPanel,
|
|
1060
1164
|
{
|
|
@@ -1071,92 +1175,92 @@ function HubSpotMeetingScheduler({
|
|
|
1071
1175
|
},
|
|
1072
1176
|
timezone,
|
|
1073
1177
|
onTimezoneChange: setTimezone,
|
|
1178
|
+
onBack: step === "details" ? backToSlot : onBack,
|
|
1074
1179
|
locked: step !== "slot",
|
|
1075
|
-
|
|
1180
|
+
showTimezone: step !== "confirmed",
|
|
1181
|
+
className: CONTEXT_PANEL_CLASS
|
|
1076
1182
|
}
|
|
1077
1183
|
),
|
|
1078
|
-
/* @__PURE__ */ jsx6("div", { className:
|
|
1184
|
+
/* @__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" && durationMs != null && selectedSlot != null && timezone ? /* @__PURE__ */ jsxs6("div", { className: cn("flex flex-1 flex-col gap-[var(--spacing-system-m)]", PANEL_STEP_CLASS), children: [
|
|
1185
|
+
/* @__PURE__ */ jsxs6("p", { className: "text-h4 text-ods-text-primary", children: [
|
|
1186
|
+
new Intl.DateTimeFormat(void 0, {
|
|
1187
|
+
timeZone: timezone,
|
|
1188
|
+
weekday: "long",
|
|
1189
|
+
month: "long",
|
|
1190
|
+
day: "numeric",
|
|
1191
|
+
hour: "numeric",
|
|
1192
|
+
minute: "2-digit",
|
|
1193
|
+
timeZoneName: "short"
|
|
1194
|
+
}).format(new Date(selectedSlot)),
|
|
1195
|
+
" ",
|
|
1196
|
+
"\xB7 ",
|
|
1197
|
+
formatDurationCompact(durationMs / 1e3)
|
|
1198
|
+
] }),
|
|
1199
|
+
bookingError && bookingError !== "SLOT_TAKEN" && /* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-start gap-[var(--spacing-system-xs)]", children: [
|
|
1200
|
+
/* @__PURE__ */ jsx6("p", { className: "text-h6 text-ods-error", children: bookingError === "TEMPORARILY_UNAVAILABLE" ? "Scheduling is briefly unavailable \u2014 please try again in a minute." : bookingError === "MEETING_UNAVAILABLE" ? "This meeting type has reached its booking limit for today \u2014 try another time or contact us." : bookingError === "LINK_GONE" ? "This meeting type is no longer available." : "Please double-check your details and try again." }),
|
|
1201
|
+
escapeHatch
|
|
1202
|
+
] }),
|
|
1079
1203
|
/* @__PURE__ */ jsx6(
|
|
1080
|
-
|
|
1204
|
+
BookingForm,
|
|
1081
1205
|
{
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1206
|
+
availability,
|
|
1207
|
+
meetingId,
|
|
1208
|
+
startTimeMs: selectedSlot,
|
|
1209
|
+
durationMs,
|
|
1210
|
+
timezone,
|
|
1211
|
+
isSubmitting,
|
|
1212
|
+
onSubmit: handleSubmit,
|
|
1213
|
+
honeypotInputProps,
|
|
1214
|
+
getSignals
|
|
1087
1215
|
}
|
|
1088
|
-
)
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
day: "numeric",
|
|
1096
|
-
hour: "numeric",
|
|
1097
|
-
minute: "2-digit",
|
|
1098
|
-
timeZoneName: "short"
|
|
1099
|
-
}).format(new Date(selectedSlot)),
|
|
1100
|
-
" ",
|
|
1101
|
-
"\xB7 ",
|
|
1102
|
-
formatDurationCompact(durationMs / 1e3)
|
|
1103
|
-
] }),
|
|
1104
|
-
bookingError && bookingError !== "SLOT_TAKEN" && /* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-start gap-[var(--spacing-system-xs)]", children: [
|
|
1105
|
-
/* @__PURE__ */ jsx6("p", { className: "text-h6 text-ods-error", children: bookingError === "TEMPORARILY_UNAVAILABLE" ? "Scheduling is briefly unavailable \u2014 please try again in a minute." : bookingError === "MEETING_UNAVAILABLE" ? "This meeting type has reached its booking limit for today \u2014 try another time or contact us." : bookingError === "LINK_GONE" ? "This meeting type is no longer available." : "Please double-check your details and try again." }),
|
|
1106
|
-
escapeHatch
|
|
1107
|
-
] }),
|
|
1216
|
+
)
|
|
1217
|
+
] }) : /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-[var(--spacing-system-m)] md:min-h-0 md:flex-1", children: [
|
|
1218
|
+
bookingError === "SLOT_TAKEN" && /* @__PURE__ */ jsx6("p", { className: "px-[var(--spacing-system-l)] pt-[var(--spacing-system-l)] text-h6 text-ods-error lg:p-0", children: "That time is no longer available \u2014 if you just submitted, check your email for a confirmation before rebooking." }),
|
|
1219
|
+
timezone && durationMs != null ? (
|
|
1220
|
+
// The picker owns BOTH the loading and the empty month now —
|
|
1221
|
+
// it keeps the calendar up either way, so there is no branch
|
|
1222
|
+
// here that can take it off screen mid-flow.
|
|
1108
1223
|
/* @__PURE__ */ jsx6(
|
|
1109
|
-
|
|
1224
|
+
SlotPicker,
|
|
1110
1225
|
{
|
|
1111
|
-
|
|
1112
|
-
meetingId,
|
|
1113
|
-
startTimeMs: selectedSlot,
|
|
1114
|
-
durationMs,
|
|
1226
|
+
slots,
|
|
1115
1227
|
timezone,
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1228
|
+
monthOffset,
|
|
1229
|
+
onMonthOffsetChange: (o) => {
|
|
1230
|
+
setSelectedDay(null);
|
|
1231
|
+
setSelectedSlot(null);
|
|
1232
|
+
setMonthOffset(o);
|
|
1233
|
+
},
|
|
1234
|
+
selectedSlot,
|
|
1235
|
+
onSelectSlot: (ms) => {
|
|
1236
|
+
setSelectedSlot(ms);
|
|
1237
|
+
setStep("details");
|
|
1238
|
+
},
|
|
1239
|
+
selectedDay,
|
|
1240
|
+
onSelectDay: setSelectedDay,
|
|
1241
|
+
isLoading: isLoadingAvailability
|
|
1120
1242
|
}
|
|
1121
1243
|
)
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
monthOffset,
|
|
1132
|
-
onMonthOffsetChange: (o) => {
|
|
1133
|
-
setSelectedDay(null);
|
|
1134
|
-
setSelectedSlot(null);
|
|
1135
|
-
setMonthOffset(o);
|
|
1136
|
-
},
|
|
1137
|
-
selectedSlot,
|
|
1138
|
-
onSelectSlot: (ms) => {
|
|
1139
|
-
setSelectedSlot(ms);
|
|
1140
|
-
setStep("details");
|
|
1141
|
-
},
|
|
1142
|
-
selectedDay,
|
|
1143
|
-
onSelectDay: setSelectedDay,
|
|
1144
|
-
isLoading: isLoadingAvailability
|
|
1145
|
-
}
|
|
1146
|
-
) : /* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-start gap-[var(--spacing-system-md)]", children: [
|
|
1147
|
-
/* @__PURE__ */ jsx6("p", { className: "text-h6 text-ods-text-secondary", children: isLoadingAvailability ? "Checking more dates\u2026" : "No call times are published right now." }),
|
|
1148
|
-
!isLoadingAvailability && escapeHatch
|
|
1149
|
-
] }) : (
|
|
1244
|
+
) : durations.length === 0 && !isFetchingAvailability ? (
|
|
1245
|
+
// The LINK publishes nothing at all — a different thing from a
|
|
1246
|
+
// fully-booked month, and the only case the escape hatch is
|
|
1247
|
+
// the right answer to.
|
|
1248
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-start gap-[var(--spacing-system-m)]", children: [
|
|
1249
|
+
/* @__PURE__ */ jsx6("p", { className: "text-h6 text-ods-text-secondary", children: "No call times are published right now." }),
|
|
1250
|
+
escapeHatch
|
|
1251
|
+
] })
|
|
1252
|
+
) : (
|
|
1150
1253
|
// Pre-mount timezone-resolution window (every SSR load hits
|
|
1151
|
-
// this for one frame-batch) —
|
|
1152
|
-
//
|
|
1153
|
-
/* @__PURE__ */ jsx6(SlotPickerSkeleton, {})
|
|
1254
|
+
// this for one frame-batch) — same layout, real calendar, so
|
|
1255
|
+
// nothing shifts when the picker takes over.
|
|
1256
|
+
/* @__PURE__ */ jsx6(SlotPickerSkeleton, { monthOffset })
|
|
1154
1257
|
)
|
|
1155
1258
|
] }) })
|
|
1156
1259
|
] }) });
|
|
1157
1260
|
}
|
|
1158
1261
|
export {
|
|
1159
1262
|
HubSpotMeetingScheduler,
|
|
1263
|
+
MEETING_SCHEDULER_H,
|
|
1160
1264
|
MeetingSchedulerDirectory,
|
|
1161
1265
|
MeetingSchedulerDirectoryRowSkeleton
|
|
1162
1266
|
};
|