@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
|
@@ -11,13 +11,16 @@
|
|
|
11
11
|
* their own backend — the book route is IP-rate-limited and bot-gated).
|
|
12
12
|
*
|
|
13
13
|
* LAYOUT (Calendly-anatomy, conversion-first): one bordered card, two
|
|
14
|
-
* panels. Left/top — the CONTEXT panel:
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* panels. Left/top — the CONTEXT panel: an optional host-level back edge
|
|
15
|
+
* (`onBack`), host identity (avatar + name + title from
|
|
16
|
+
* `availability.hosts`), meeting title/description, duration chips (the
|
|
17
|
+
* duration choice lives here, NOT as a wizard step — the visitor always
|
|
18
|
+
* lands straight on the calendar), and the resolved timezone.
|
|
18
19
|
* Right/bottom — the ACTION panel: calendar + time slots (first available
|
|
19
20
|
* day auto-selected so slots are visible immediately), then the details
|
|
20
|
-
* form, then the confirmation.
|
|
21
|
+
* form, then the confirmation. On the slot step that panel splits again —
|
|
22
|
+
* calendar column, then times column — each carrying its own heading (owned
|
|
23
|
+
* by `SlotPicker`, so loading and loaded agree). Panels stack on mobile.
|
|
21
24
|
*
|
|
22
25
|
* Two modes, one component:
|
|
23
26
|
* - SSR mode: pass `initialAvailability` (host-fetched) — the
|
|
@@ -36,7 +39,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
36
39
|
import { cn } from '../../utils/cn'
|
|
37
40
|
import { formatDurationCompact } from '../../utils/format'
|
|
38
41
|
import { Button } from '../ui'
|
|
39
|
-
import { BackButton } from '../layout/back-button'
|
|
40
42
|
import { useHumanitySignals } from '../../hooks/use-humanity-signals'
|
|
41
43
|
import { useMeetingBooking } from '../../hooks/use-meeting-booking'
|
|
42
44
|
import {
|
|
@@ -68,12 +70,149 @@ export interface HubSpotMeetingSchedulerProps {
|
|
|
68
70
|
displayTimezone?: string
|
|
69
71
|
/** The link's public HubSpot booking URL — the "Open in HubSpot" escape hatch target. */
|
|
70
72
|
fallbackUrl?: string
|
|
73
|
+
/**
|
|
74
|
+
* Host-level exit, rendered as a back edge at the top of the context panel
|
|
75
|
+
* (and in the loading skeleton, so it doesn't pop in). This is "leave the
|
|
76
|
+
* scheduler", NOT the details step's back edge — it is suppressed once a
|
|
77
|
+
* slot is chosen so only one Back is ever on screen. Omit it and no back
|
|
78
|
+
* affordance renders at all.
|
|
79
|
+
*/
|
|
80
|
+
onBack?: () => void
|
|
71
81
|
onBooked?: (b: BookingConfirmation) => void
|
|
72
82
|
className?: string
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
type Step = 'slot' | 'details' | 'confirmed'
|
|
76
86
|
|
|
87
|
+
/** Context-panel geometry — ONE definition for the loaded card and the
|
|
88
|
+
* loading skeleton, which is what keeps the two footprint-identical. */
|
|
89
|
+
const CONTEXT_PANEL_CLASS =
|
|
90
|
+
'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'
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Action panel: the elastic half, scrolling inside the fixed card.
|
|
94
|
+
*
|
|
95
|
+
* Padded only from `lg`. Below it the calendar and times are separate sections
|
|
96
|
+
* with a divider between them, and each pads itself — one padding here would
|
|
97
|
+
* inset that divider from the card's edges instead of letting it run the full
|
|
98
|
+
* width the way every other rule in this card does.
|
|
99
|
+
*/
|
|
100
|
+
const ACTION_PANEL_CLASS = 'flex-1 min-w-0 flex flex-col md:min-h-0 lg:p-[var(--spacing-system-l)]'
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The action panel's inset for the steps that are ONE block — details,
|
|
104
|
+
* confirmation, the slot step's error line.
|
|
105
|
+
*
|
|
106
|
+
* The panel itself is padded only from `lg`, because below that the slot step
|
|
107
|
+
* is two sections with a rule between them and each pads itself: one padding
|
|
108
|
+
* on the panel would inset that rule instead of letting it run the card's full
|
|
109
|
+
* width, the way every other rule here does. Every step that is NOT split into
|
|
110
|
+
* sections has to bring the same inset with it — without it the form ran edge
|
|
111
|
+
* to edge on tablet, its inputs touching the card's border.
|
|
112
|
+
*
|
|
113
|
+
* `md:min-h-0 md:overflow-y-auto` comes with it: from `md` up the card states
|
|
114
|
+
* a height, and these steps have no inner scroller of their own the way the
|
|
115
|
+
* times column does, so this is where a long form gets to scroll instead of
|
|
116
|
+
* being cut off at the card's edge.
|
|
117
|
+
*/
|
|
118
|
+
const PANEL_STEP_CLASS = 'p-[var(--spacing-system-l)] md:min-h-0 md:overflow-y-auto lg:p-0'
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The widget's height on desktop: a FIXED 380px, and the ONLY size the whole
|
|
122
|
+
* card states.
|
|
123
|
+
*
|
|
124
|
+
* A floor was not enough. Every stage has a different natural height — the
|
|
125
|
+
* context panel with two hosts ~340, the calendar column ~290, a degraded
|
|
126
|
+
* stage two lines — so `min-height` only pinned the SHORT ones and let the
|
|
127
|
+
* tall one push the wrapper, which is exactly the screen-shake this removes.
|
|
128
|
+
* With a fixed height the box is stated once and nothing inside can move it;
|
|
129
|
+
* anything taller scrolls in place.
|
|
130
|
+
*
|
|
131
|
+
* The elastic half derives from it: the panel passes it to the slot row and
|
|
132
|
+
* the times column scrolls inside what is left. The calendar does not stretch
|
|
133
|
+
* — a date grid has a size, and stretching one is how a month turns into
|
|
134
|
+
* page-tall bands the moment a layout stops pinning a height. It states a
|
|
135
|
+
* WIDTH (`CALENDAR_W`) and its square cells make it as tall as it is wide;
|
|
136
|
+
* that width is chosen to fit inside this number, and the two are checked
|
|
137
|
+
* against each other in one place rather than three.
|
|
138
|
+
*
|
|
139
|
+
* Six week rows, always — `fixedWeeks` — is why a height can be stated at all:
|
|
140
|
+
* a 5-week month and a 6-week month occupy the same box, so paging the
|
|
141
|
+
* calendar cannot resize the card.
|
|
142
|
+
*
|
|
143
|
+
* Exported because the stability has to survive OUTSIDE the card too: a host
|
|
144
|
+
* that swaps the scheduler in and out of a slot (the onboarding "Book a call"
|
|
145
|
+
* promo does exactly that) reserves the same box for whatever it shows
|
|
146
|
+
* instead, so the swap moves nothing below it.
|
|
147
|
+
*
|
|
148
|
+
* Goes on the BORDERED element, always. Under `box-sizing: border-box` the
|
|
149
|
+
* declared height swallows the 1px edges, so a card that carries the border
|
|
150
|
+
* and a host box that carries its own both come out at exactly 380 — put it on
|
|
151
|
+
* an inner wrapper instead and the border lands OUTSIDE the 380, making that
|
|
152
|
+
* card 2px taller than everything it is supposed to match.
|
|
153
|
+
*
|
|
154
|
+
* TWO numbers, because the card has two shapes: 380 as a sidebar beside the
|
|
155
|
+
* calendar (`lg`), 550 as a header strip over it (`md`). Phones get neither —
|
|
156
|
+
* there a card the height of the hand holding it is the right answer and the
|
|
157
|
+
* page is the scroller.
|
|
158
|
+
*
|
|
159
|
+
* A host that reserves this box owes it one thing: content that ADAPTS to the
|
|
160
|
+
* height rather than assuming it. The onboarding promo does it by letting its
|
|
161
|
+
* video take the leftover space at 16:9; a stand-in that just stacks fixed
|
|
162
|
+
* blocks will overflow the shorter of the two.
|
|
163
|
+
*/
|
|
164
|
+
export const MEETING_SCHEDULER_H = 'md:h-[34.375rem] lg:h-[23.75rem]'
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The two-panel card's box, shared verbatim by the loading skeleton and the
|
|
168
|
+
* loaded card so neither can drift from the other.
|
|
169
|
+
*
|
|
170
|
+
* Both heights come from {@link MEETING_SCHEDULER_H}; what this adds is the
|
|
171
|
+
* flex column that makes them work. On TABLET the height is stated for the
|
|
172
|
+
* same reason the desktop one is: a CEILING only pins the tall stages. The short ones — the empty
|
|
173
|
+
* month ("No available times in September"), a five-week month, the cold-start
|
|
174
|
+
* skeleton — each settled at their own natural height, so paging a month or
|
|
175
|
+
* waiting out a fetch resized the card under the visitor. Stating the height
|
|
176
|
+
* makes every stage the same box and moves the variation inside, where the
|
|
177
|
+
* times column already scrolls.
|
|
178
|
+
*
|
|
179
|
+
* 550, not the mock's 498: the mock draws one host and no timezone field, and
|
|
180
|
+
* a real link has two hosts and a zone the visitor must be able to change.
|
|
181
|
+
* That is ~55px of content the mock never budgeted for, and the month below it
|
|
182
|
+
* is what would otherwise pay for it.
|
|
183
|
+
*
|
|
184
|
+
* The strip is `shrink-0`, so what is left goes to the slot row, which spends
|
|
185
|
+
* it on a calendar sized to fit (no scroll) and a times list that scrolls on
|
|
186
|
+
* its own.
|
|
187
|
+
*
|
|
188
|
+
* `md:flex md:flex-col` is what makes that possible at all. A stated height on
|
|
189
|
+
* a plain block only clips (this card is `overflow-hidden` for its corners) —
|
|
190
|
+
* it is the flex column plus the inner wrapper's `min-h-0` that lets the
|
|
191
|
+
* content shrink into the height instead of being cut off by it.
|
|
192
|
+
*/
|
|
193
|
+
const CARD_CLASS = cn(
|
|
194
|
+
'rounded-md border border-ods-border bg-ods-card overflow-hidden',
|
|
195
|
+
'md:flex md:flex-col',
|
|
196
|
+
MEETING_SCHEDULER_H,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
/** Context strip over action panel, side by side from `lg`. `md:flex-1
|
|
200
|
+
* md:min-h-0` is the pair that makes it exactly as tall as the card states —
|
|
201
|
+
* grow into a stated height that content does not reach, shrink into one it
|
|
202
|
+
* overruns — so neither a short stage nor a long one changes the box. */
|
|
203
|
+
const CARD_INNER_CLASS = 'flex flex-col md:min-h-0 md:flex-1 lg:h-full lg:flex-row'
|
|
204
|
+
|
|
205
|
+
/** The two-line stages (load failure, "booked on HubSpot") — the SAME box as
|
|
206
|
+
* the booking card, because these are stages of one widget in one slot and a
|
|
207
|
+
* stage that collapses to two lines would snap the page shut under the
|
|
208
|
+
* visitor. Their content is centred in it and scrolls if a message ever
|
|
209
|
+
* outgrows it. */
|
|
210
|
+
const CARD_DEGRADED_CLASS = cn(
|
|
211
|
+
'rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-lf)] flex flex-col items-start gap-[var(--spacing-system-m)]',
|
|
212
|
+
'md:justify-center md:overflow-y-auto',
|
|
213
|
+
MEETING_SCHEDULER_H,
|
|
214
|
+
)
|
|
215
|
+
|
|
77
216
|
/**
|
|
78
217
|
* Fail-closed gate: a link whose declared questions include an unsupported
|
|
79
218
|
* type, or whose consent block is malformed, must NOT render a half-working
|
|
@@ -99,12 +238,14 @@ export function HubSpotMeetingScheduler({
|
|
|
99
238
|
hosts,
|
|
100
239
|
displayTimezone,
|
|
101
240
|
fallbackUrl,
|
|
241
|
+
onBack,
|
|
102
242
|
onBooked,
|
|
103
243
|
className,
|
|
104
244
|
}: HubSpotMeetingSchedulerProps) {
|
|
105
245
|
const {
|
|
106
246
|
availability,
|
|
107
247
|
isLoadingAvailability,
|
|
248
|
+
isFetchingAvailability,
|
|
108
249
|
availabilityError,
|
|
109
250
|
monthOffset,
|
|
110
251
|
setMonthOffset,
|
|
@@ -187,7 +328,7 @@ export function HubSpotMeetingScheduler({
|
|
|
187
328
|
if (
|
|
188
329
|
step === 'slot' &&
|
|
189
330
|
availability &&
|
|
190
|
-
!
|
|
331
|
+
!isFetchingAvailability &&
|
|
191
332
|
durationMs != null &&
|
|
192
333
|
slots.length === 0 &&
|
|
193
334
|
availability.hasMore &&
|
|
@@ -196,7 +337,14 @@ export function HubSpotMeetingScheduler({
|
|
|
196
337
|
autoAdvanceCount.current += 1
|
|
197
338
|
setMonthOffset(monthOffset + 1)
|
|
198
339
|
}
|
|
199
|
-
}, [step, availability,
|
|
340
|
+
}, [step, availability, isFetchingAvailability, durationMs, slots, monthOffset, setMonthOffset])
|
|
341
|
+
|
|
342
|
+
/** The back edge's destination from the form: the calendar, with any
|
|
343
|
+
* submit error cleared so the visitor doesn't carry it back. */
|
|
344
|
+
const backToSlot = useCallback(() => {
|
|
345
|
+
setStep('slot')
|
|
346
|
+
setBookingError(null)
|
|
347
|
+
}, [])
|
|
200
348
|
|
|
201
349
|
const handleSubmit = useCallback(
|
|
202
350
|
async (payload: Record<string, unknown>) => {
|
|
@@ -231,18 +379,16 @@ export function HubSpotMeetingScheduler({
|
|
|
231
379
|
// ---- terminal / degraded states -----------------------------------------
|
|
232
380
|
|
|
233
381
|
if (isLoadingAvailability && !availability) {
|
|
234
|
-
//
|
|
235
|
-
// loaded
|
|
236
|
-
// (
|
|
237
|
-
//
|
|
382
|
+
// COLD start only — a month already in the query cache renders straight
|
|
383
|
+
// into the loaded card. Composed inside the SAME wrappers as that card
|
|
384
|
+
// (`ContextPanelSkeleton` beside the real slot-area layout), so nothing
|
|
385
|
+
// shifts when it swaps.
|
|
238
386
|
return (
|
|
239
|
-
<div className={cn(
|
|
240
|
-
<div className=
|
|
241
|
-
<ContextPanelSkeleton className=
|
|
242
|
-
<div className=
|
|
243
|
-
{
|
|
244
|
-
<p className="text-h5 text-ods-text-secondary">Select a date & time</p>
|
|
245
|
-
<SlotPickerSkeleton />
|
|
387
|
+
<div className={cn(CARD_CLASS, className)}>
|
|
388
|
+
<div className={CARD_INNER_CLASS}>
|
|
389
|
+
<ContextPanelSkeleton onBack={onBack} className={CONTEXT_PANEL_CLASS} />
|
|
390
|
+
<div className={ACTION_PANEL_CLASS}>
|
|
391
|
+
<SlotPickerSkeleton monthOffset={monthOffset} />
|
|
246
392
|
</div>
|
|
247
393
|
</div>
|
|
248
394
|
</div>
|
|
@@ -252,10 +398,7 @@ export function HubSpotMeetingScheduler({
|
|
|
252
398
|
if (availabilityError || !availability) {
|
|
253
399
|
return (
|
|
254
400
|
<div
|
|
255
|
-
className={cn(
|
|
256
|
-
'rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-lf)] flex flex-col items-start gap-[var(--spacing-system-md)]',
|
|
257
|
-
className,
|
|
258
|
-
)}
|
|
401
|
+
className={cn(CARD_DEGRADED_CLASS, className)}
|
|
259
402
|
>
|
|
260
403
|
<p className="text-h6 text-ods-text-secondary">
|
|
261
404
|
We couldn't load available call times. Please try again shortly.
|
|
@@ -270,10 +413,7 @@ export function HubSpotMeetingScheduler({
|
|
|
270
413
|
// questions or consent we can't faithfully reproduce.
|
|
271
414
|
return (
|
|
272
415
|
<div
|
|
273
|
-
className={cn(
|
|
274
|
-
'rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-lf)] flex flex-col items-start gap-[var(--spacing-system-md)]',
|
|
275
|
-
className,
|
|
276
|
-
)}
|
|
416
|
+
className={cn(CARD_DEGRADED_CLASS, className)}
|
|
277
417
|
>
|
|
278
418
|
<p className="text-h6 text-ods-text-secondary">This meeting type is booked directly on HubSpot.</p>
|
|
279
419
|
{escapeHatch}
|
|
@@ -284,8 +424,8 @@ export function HubSpotMeetingScheduler({
|
|
|
284
424
|
// ---- the card ------------------------------------------------------------
|
|
285
425
|
|
|
286
426
|
return (
|
|
287
|
-
<div className={cn(
|
|
288
|
-
<div className=
|
|
427
|
+
<div className={cn(CARD_CLASS, className)}>
|
|
428
|
+
<div className={CARD_INNER_CLASS}>
|
|
289
429
|
<SchedulerContextPanel
|
|
290
430
|
hosts={shownHosts}
|
|
291
431
|
title={title}
|
|
@@ -300,29 +440,35 @@ export function HubSpotMeetingScheduler({
|
|
|
300
440
|
}}
|
|
301
441
|
timezone={timezone}
|
|
302
442
|
onTimezoneChange={setTimezone}
|
|
443
|
+
// ONE back edge for the whole card, in the panel that never swaps.
|
|
444
|
+
// Its destination is simply "the previous step": from the form back
|
|
445
|
+
// to the calendar, from the calendar out of the scheduler (if the
|
|
446
|
+
// host gave us an exit at all). Two Backs on screen reading the same
|
|
447
|
+
// word with different destinations was the ambiguity the details
|
|
448
|
+
// step used to carry.
|
|
449
|
+
onBack={step === 'details' ? backToSlot : onBack}
|
|
303
450
|
locked={step !== 'slot'}
|
|
304
|
-
|
|
451
|
+
// The zone still governs every time on screen — including the
|
|
452
|
+
// summary line on the form — so the picker stays until there is
|
|
453
|
+
// nothing left to re-read in it.
|
|
454
|
+
showTimezone={step !== 'confirmed'}
|
|
455
|
+
className={CONTEXT_PANEL_CLASS}
|
|
305
456
|
/>
|
|
306
457
|
|
|
307
|
-
{/*
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
<div className=
|
|
458
|
+
{/* Scrolls INSIDE the fixed card rather than growing it — see
|
|
459
|
+
MEETING_SCHEDULER_H. `min-h-0` is what lets it: a flex item's
|
|
460
|
+
default `min-height:auto` refuses to shrink below its content. */}
|
|
461
|
+
<div className={ACTION_PANEL_CLASS}>
|
|
311
462
|
{step === 'confirmed' && confirmation && timezone ? (
|
|
312
|
-
<
|
|
463
|
+
<div className={cn('flex flex-1 flex-col', PANEL_STEP_CLASS)}>
|
|
464
|
+
<Confirmation confirmation={confirmation} timezone={timezone} />
|
|
465
|
+
</div>
|
|
313
466
|
) : step === 'details' && durationMs != null && selectedSlot != null && timezone ? (
|
|
314
|
-
<div className=
|
|
315
|
-
{/*
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
label="Back"
|
|
320
|
-
onClick={() => {
|
|
321
|
-
setStep('slot')
|
|
322
|
-
setBookingError(null)
|
|
323
|
-
}}
|
|
324
|
-
/>
|
|
325
|
-
<div className="flex flex-1 flex-col justify-center gap-[var(--spacing-system-md)]">
|
|
467
|
+
<div className={cn('flex flex-1 flex-col gap-[var(--spacing-system-m)]', PANEL_STEP_CLASS)}>
|
|
468
|
+
{/* Top-aligned, and no back edge of its own: the ONE back edge
|
|
469
|
+
lives in the context panel at every step (see `contextBack`),
|
|
470
|
+
which is where the design puts it and the only spot that
|
|
471
|
+
stays put while this side swaps between calendar and form. */}
|
|
326
472
|
<p className="text-h4 text-ods-text-primary">
|
|
327
473
|
{new Intl.DateTimeFormat(undefined, {
|
|
328
474
|
timeZone: timezone,
|
|
@@ -360,50 +506,50 @@ export function HubSpotMeetingScheduler({
|
|
|
360
506
|
honeypotInputProps={honeypotInputProps}
|
|
361
507
|
getSignals={getSignals}
|
|
362
508
|
/>
|
|
363
|
-
</div>
|
|
364
509
|
</div>
|
|
365
510
|
) : (
|
|
366
|
-
<div className="flex flex-col gap-[var(--spacing-system-
|
|
367
|
-
<p className="text-h5 text-ods-text-secondary">Select a date & time</p>
|
|
511
|
+
<div className="flex flex-col gap-[var(--spacing-system-m)] md:min-h-0 md:flex-1">
|
|
368
512
|
{bookingError === 'SLOT_TAKEN' && (
|
|
369
|
-
<p className="text-h6 text-ods-error">
|
|
513
|
+
<p className="px-[var(--spacing-system-l)] pt-[var(--spacing-system-l)] text-h6 text-ods-error lg:p-0">
|
|
370
514
|
That time is no longer available — if you just submitted, check your email for a confirmation
|
|
371
515
|
before rebooking.
|
|
372
516
|
</p>
|
|
373
517
|
)}
|
|
374
518
|
{timezone && durationMs != null ? (
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
519
|
+
// The picker owns BOTH the loading and the empty month now —
|
|
520
|
+
// it keeps the calendar up either way, so there is no branch
|
|
521
|
+
// here that can take it off screen mid-flow.
|
|
522
|
+
<SlotPicker
|
|
523
|
+
slots={slots}
|
|
524
|
+
timezone={timezone}
|
|
525
|
+
monthOffset={monthOffset}
|
|
526
|
+
onMonthOffsetChange={(o) => {
|
|
527
|
+
setSelectedDay(null)
|
|
528
|
+
setSelectedSlot(null)
|
|
529
|
+
setMonthOffset(o)
|
|
530
|
+
}}
|
|
531
|
+
selectedSlot={selectedSlot}
|
|
532
|
+
onSelectSlot={(ms) => {
|
|
533
|
+
setSelectedSlot(ms)
|
|
534
|
+
setStep('details')
|
|
535
|
+
}}
|
|
536
|
+
selectedDay={selectedDay}
|
|
537
|
+
onSelectDay={setSelectedDay}
|
|
538
|
+
isLoading={isLoadingAvailability}
|
|
539
|
+
/>
|
|
540
|
+
) : durations.length === 0 && !isFetchingAvailability ? (
|
|
541
|
+
// The LINK publishes nothing at all — a different thing from a
|
|
542
|
+
// fully-booked month, and the only case the escape hatch is
|
|
543
|
+
// the right answer to.
|
|
544
|
+
<div className="flex flex-col items-start gap-[var(--spacing-system-m)]">
|
|
545
|
+
<p className="text-h6 text-ods-text-secondary">No call times are published right now.</p>
|
|
546
|
+
{escapeHatch}
|
|
547
|
+
</div>
|
|
402
548
|
) : (
|
|
403
549
|
// Pre-mount timezone-resolution window (every SSR load hits
|
|
404
|
-
// this for one frame-batch) —
|
|
405
|
-
//
|
|
406
|
-
<SlotPickerSkeleton />
|
|
550
|
+
// this for one frame-batch) — same layout, real calendar, so
|
|
551
|
+
// nothing shifts when the picker takes over.
|
|
552
|
+
<SlotPickerSkeleton monthOffset={monthOffset} />
|
|
407
553
|
)}
|
|
408
554
|
</div>
|
|
409
555
|
)}
|