@flamingo-stack/openframe-frontend-core 0.0.574 → 0.0.575-snapshot.20260827225211

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.
@@ -27,6 +27,7 @@ require('../../chunk-GQWM3ECA.cjs');
27
27
  require('../../chunk-TON3YCCW.cjs');
28
28
 
29
29
 
30
+
30
31
  var _chunk2BFZHTSDcjs = require('../../chunk-2BFZHTSD.cjs');
31
32
  require('../../chunk-D55IENZJ.cjs');
32
33
  require('../../chunk-7Z64COGD.cjs');
@@ -79,102 +80,17 @@ var _react = require('react');
79
80
  // src/hooks/use-meeting-booking.ts
80
81
  var _reactquery = require('@tanstack/react-query');
81
82
 
82
- var KNOWN_CODES = [
83
+
84
+ // src/schemas/meeting-booking-schema.ts
85
+ var _zod = require('zod');
86
+ var MEETING_BOOKING_ERROR_CODES = [
83
87
  "SLOT_TAKEN",
84
88
  "VALIDATION",
89
+ "INVALID_EMAIL",
85
90
  "LINK_GONE",
86
91
  "TEMPORARILY_UNAVAILABLE",
87
92
  "MEETING_UNAVAILABLE"
88
93
  ];
89
- var AVAILABILITY_STALE_MS = 0;
90
- function meetingAvailabilityKey(apiBaseUrl, meetingId, monthOffset) {
91
- return ["meeting-availability", apiBaseUrl, meetingId, monthOffset];
92
- }
93
- function useMeetingBooking(options) {
94
- const { meetingId, apiBaseUrl = "", initialAvailability } = options;
95
- const queryClient = _reactquery.useQueryClient.call(void 0, );
96
- const seededOffset = _optionalChain([initialAvailability, 'optionalAccess', _2 => _2.meetingId]) === meetingId ? initialAvailability.monthOffset : void 0;
97
- const [monthOffset, setMonthOffsetState] = _react.useState.call(void 0, _nullishCoalesce(seededOffset, () => ( 0)));
98
- const setMonthOffset = _react.useCallback.call(void 0, (offset) => {
99
- setMonthOffsetState(Math.max(0, Math.min(_chunkQDOBY3TPcjs.MAX_MONTH_OFFSET, offset)));
100
- }, []);
101
- const prevMeetingIdRef = _react.useRef.call(void 0, meetingId);
102
- _react.useLayoutEffect.call(void 0, () => {
103
- if (prevMeetingIdRef.current === meetingId) return;
104
- prevMeetingIdRef.current = meetingId;
105
- setMonthOffsetState(0);
106
- }, [meetingId]);
107
- const seed = _optionalChain([initialAvailability, 'optionalAccess', _3 => _3.meetingId]) === meetingId && initialAvailability.monthOffset === monthOffset ? initialAvailability : void 0;
108
- const availabilityQuery = _reactquery.useQuery.call(void 0, {
109
- queryKey: meetingAvailabilityKey(apiBaseUrl, meetingId, monthOffset),
110
- queryFn: async ({ signal }) => {
111
- const params = new URLSearchParams({ meeting: meetingId, monthOffset: String(monthOffset) });
112
- const res = await _chunkTRC37VRCcjs.contentFetch.call(void 0, `${apiBaseUrl}/api/meetings/availability?${params}`, { signal });
113
- if (!res.ok) throw new Error(`availability ${res.status}`);
114
- return await res.json();
115
- },
116
- staleTime: AVAILABILITY_STALE_MS,
117
- initialData: seed,
118
- // Stamped as infinitely old on purpose: the seed is first-paint
119
- // scaffolding for a 60-second-volatile resource, not truth, so it paints
120
- // immediately AND is refetched immediately.
121
- initialDataUpdatedAt: seed ? 0 : void 0
122
- });
123
- const refetchAvailability = _react.useCallback.call(void 0, async () => {
124
- await queryClient.invalidateQueries({ queryKey: meetingAvailabilityKey(apiBaseUrl, meetingId, monthOffset) }).catch(() => void 0);
125
- }, [queryClient, apiBaseUrl, meetingId, monthOffset]);
126
- const bookMutation = _reactquery.useMutation.call(void 0, {
127
- mutationFn: async (payload) => {
128
- try {
129
- const res = await _chunkTRC37VRCcjs.contentFetch.call(void 0, `${apiBaseUrl}/api/meetings/book`, {
130
- method: "POST",
131
- headers: { "Content-Type": "application/json" },
132
- body: JSON.stringify(payload)
133
- });
134
- const data = await res.json().catch(() => ({}));
135
- if (res.ok) return { ok: true, confirmation: data };
136
- const rawCode = typeof data.code === "string" ? data.code : "";
137
- const code = KNOWN_CODES.includes(rawCode) ? rawCode : "TEMPORARILY_UNAVAILABLE";
138
- return { ok: false, code, message: typeof data.error === "string" ? data.error : void 0 };
139
- } catch (e) {
140
- return { ok: false, code: "TEMPORARILY_UNAVAILABLE" };
141
- }
142
- },
143
- // A booking is not a read — never retried, at any layer.
144
- retry: false
145
- });
146
- const submittingRef = _react.useRef.call(void 0, null);
147
- const { mutateAsync } = bookMutation;
148
- const book = _react.useCallback.call(void 0,
149
- async (payload) => {
150
- if (submittingRef.current !== null) return { ok: false, code: "VALIDATION", message: "Already submitting" };
151
- const submitToken = {};
152
- submittingRef.current = submitToken;
153
- try {
154
- return await mutateAsync(payload);
155
- } finally {
156
- if (submittingRef.current === submitToken) submittingRef.current = null;
157
- }
158
- },
159
- [mutateAsync]
160
- );
161
- return {
162
- availability: _nullishCoalesce(availabilityQuery.data, () => ( null)),
163
- /** No availability for this month yet — the times column has nothing to show. */
164
- isLoadingAvailability: availabilityQuery.isPending,
165
- /** ANY request in flight, including a silent revalidation of cached data. */
166
- isFetchingAvailability: availabilityQuery.isFetching,
167
- availabilityError: availabilityQuery.error ? availabilityQuery.error.message : null,
168
- monthOffset,
169
- setMonthOffset,
170
- refetchAvailability,
171
- book,
172
- isSubmitting: bookMutation.isPending
173
- };
174
- }
175
-
176
- // src/schemas/meeting-booking-schema.ts
177
- var _zod = require('zod');
178
94
  var SUPPORTED_FORM_FIELD_TYPES = ["text", "textarea", "select", "radio", "checkbox"];
179
95
  function isSupportedFormField(field) {
180
96
  return SUPPORTED_FORM_FIELD_TYPES.includes(field.type);
@@ -185,14 +101,14 @@ function isValidIanaTimezone(tz) {
185
101
  try {
186
102
  new Intl.DateTimeFormat("en-US", { timeZone: tz });
187
103
  return true;
188
- } catch (e2) {
104
+ } catch (e) {
189
105
  return false;
190
106
  }
191
107
  }
192
108
  function isValidBcp47Locale(locale) {
193
109
  try {
194
110
  return Intl.getCanonicalLocales(locale).length > 0;
195
- } catch (e3) {
111
+ } catch (e2) {
196
112
  return false;
197
113
  }
198
114
  }
@@ -228,7 +144,7 @@ function makeBookingSchema(formFields, legalConsent) {
228
144
  }
229
145
  answers[field.name] = validator;
230
146
  }
231
- const requiredConsentIds = (_nullishCoalesce(_optionalChain([legalConsent, 'optionalAccess', _4 => _4.communicationConsentCheckboxes]), () => ( []))).filter((c) => c.required).map((c) => c.communicationTypeId);
147
+ const requiredConsentIds = (_nullishCoalesce(_optionalChain([legalConsent, 'optionalAccess', _2 => _2.communicationConsentCheckboxes]), () => ( []))).filter((c) => c.required).map((c) => c.communicationTypeId);
232
148
  const hasRequiredAnswers = formFields.some((f) => isSupportedFormField(f) && f.required);
233
149
  const answersObject = _zod.z.object(answers);
234
150
  return _zod.z.object({
@@ -261,6 +177,94 @@ function makeBookingSchema(formFields, legalConsent) {
261
177
  });
262
178
  }
263
179
 
180
+ // src/hooks/use-meeting-booking.ts
181
+ var AVAILABILITY_STALE_MS = 0;
182
+ function meetingAvailabilityKey(apiBaseUrl, meetingId, monthOffset) {
183
+ return ["meeting-availability", apiBaseUrl, meetingId, monthOffset];
184
+ }
185
+ function useMeetingBooking(options) {
186
+ const { meetingId, apiBaseUrl = "", initialAvailability } = options;
187
+ const queryClient = _reactquery.useQueryClient.call(void 0, );
188
+ const seededOffset = _optionalChain([initialAvailability, 'optionalAccess', _3 => _3.meetingId]) === meetingId ? initialAvailability.monthOffset : void 0;
189
+ const [monthOffset, setMonthOffsetState] = _react.useState.call(void 0, _nullishCoalesce(seededOffset, () => ( 0)));
190
+ const setMonthOffset = _react.useCallback.call(void 0, (offset) => {
191
+ setMonthOffsetState(Math.max(0, Math.min(_chunkQDOBY3TPcjs.MAX_MONTH_OFFSET, offset)));
192
+ }, []);
193
+ const prevMeetingIdRef = _react.useRef.call(void 0, meetingId);
194
+ _react.useLayoutEffect.call(void 0, () => {
195
+ if (prevMeetingIdRef.current === meetingId) return;
196
+ prevMeetingIdRef.current = meetingId;
197
+ setMonthOffsetState(0);
198
+ }, [meetingId]);
199
+ const seed = _optionalChain([initialAvailability, 'optionalAccess', _4 => _4.meetingId]) === meetingId && initialAvailability.monthOffset === monthOffset ? initialAvailability : void 0;
200
+ const availabilityQuery = _reactquery.useQuery.call(void 0, {
201
+ queryKey: meetingAvailabilityKey(apiBaseUrl, meetingId, monthOffset),
202
+ queryFn: async ({ signal }) => {
203
+ const params = new URLSearchParams({ meeting: meetingId, monthOffset: String(monthOffset) });
204
+ const res = await _chunkTRC37VRCcjs.contentFetch.call(void 0, `${apiBaseUrl}/api/meetings/availability?${params}`, { signal });
205
+ if (!res.ok) throw new Error(`availability ${res.status}`);
206
+ return await res.json();
207
+ },
208
+ staleTime: AVAILABILITY_STALE_MS,
209
+ initialData: seed,
210
+ // Stamped as infinitely old on purpose: the seed is first-paint
211
+ // scaffolding for a 60-second-volatile resource, not truth, so it paints
212
+ // immediately AND is refetched immediately.
213
+ initialDataUpdatedAt: seed ? 0 : void 0
214
+ });
215
+ const refetchAvailability = _react.useCallback.call(void 0, async () => {
216
+ await queryClient.invalidateQueries({ queryKey: meetingAvailabilityKey(apiBaseUrl, meetingId, monthOffset) }).catch(() => void 0);
217
+ }, [queryClient, apiBaseUrl, meetingId, monthOffset]);
218
+ const bookMutation = _reactquery.useMutation.call(void 0, {
219
+ mutationFn: async (payload) => {
220
+ try {
221
+ const res = await _chunkTRC37VRCcjs.contentFetch.call(void 0, `${apiBaseUrl}/api/meetings/book`, {
222
+ method: "POST",
223
+ headers: { "Content-Type": "application/json" },
224
+ body: JSON.stringify(payload)
225
+ });
226
+ const data = await res.json().catch(() => ({}));
227
+ if (res.ok) return { ok: true, confirmation: data };
228
+ const rawCode = typeof data.code === "string" ? data.code : "";
229
+ const code = MEETING_BOOKING_ERROR_CODES.includes(rawCode) ? rawCode : "TEMPORARILY_UNAVAILABLE";
230
+ return { ok: false, code, message: typeof data.error === "string" ? data.error : void 0 };
231
+ } catch (e3) {
232
+ return { ok: false, code: "TEMPORARILY_UNAVAILABLE" };
233
+ }
234
+ },
235
+ // A booking is not a read — never retried, at any layer.
236
+ retry: false
237
+ });
238
+ const submittingRef = _react.useRef.call(void 0, null);
239
+ const { mutateAsync } = bookMutation;
240
+ const book = _react.useCallback.call(void 0,
241
+ async (payload) => {
242
+ if (submittingRef.current !== null) return { ok: false, code: "VALIDATION", message: "Already submitting" };
243
+ const submitToken = {};
244
+ submittingRef.current = submitToken;
245
+ try {
246
+ return await mutateAsync(payload);
247
+ } finally {
248
+ if (submittingRef.current === submitToken) submittingRef.current = null;
249
+ }
250
+ },
251
+ [mutateAsync]
252
+ );
253
+ return {
254
+ availability: _nullishCoalesce(availabilityQuery.data, () => ( null)),
255
+ /** No availability for this month yet — the times column has nothing to show. */
256
+ isLoadingAvailability: availabilityQuery.isPending,
257
+ /** ANY request in flight, including a silent revalidation of cached data. */
258
+ isFetchingAvailability: availabilityQuery.isFetching,
259
+ availabilityError: availabilityQuery.error ? availabilityQuery.error.message : null,
260
+ monthOffset,
261
+ setMonthOffset,
262
+ refetchAvailability,
263
+ book,
264
+ isSubmitting: bookMutation.isPending
265
+ };
266
+ }
267
+
264
268
  // src/components/meeting-scheduler/booking-form.tsx
265
269
  var _zod3 = require('@hookform/resolvers/zod');
266
270
 
@@ -1076,6 +1080,7 @@ function HubSpotMeetingScheduler({
1076
1080
  const [confirmation, setConfirmation] = _react.useState.call(void 0, null);
1077
1081
  const [bookingError, setBookingError] = _react.useState.call(void 0, null);
1078
1082
  const { honeypotInputProps, getSignals, resetSignals } = _chunk2BFZHTSDcjs.useHumanitySignals.call(void 0, );
1083
+ const { toast } = _chunk2BFZHTSDcjs.useToast.call(void 0, );
1079
1084
  const autoAdvanceCount = _react.useRef.call(void 0, 0);
1080
1085
  const [machineFor, setMachineFor] = _react.useState.call(void 0, meetingId);
1081
1086
  if (machineFor !== meetingId) {
@@ -1137,9 +1142,15 @@ function HubSpotMeetingScheduler({
1137
1142
  setStep("slot");
1138
1143
  resetSignals();
1139
1144
  void refetchAvailability();
1145
+ } else {
1146
+ toast({
1147
+ variant: "error",
1148
+ title: "Booking failed",
1149
+ description: _nullishCoalesce(BOOKING_ERROR_COPY[code], () => ( BOOKING_ERROR_COPY.VALIDATION))
1150
+ });
1140
1151
  }
1141
1152
  },
1142
- [book, onBooked, refetchAvailability, resetSignals]
1153
+ [book, onBooked, refetchAvailability, resetSignals, toast]
1143
1154
  );
1144
1155
  const escapeHatch = fallbackUrl ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkS6LZN66Dcjs.Button, { variant: "outline", size: "small-legacy", href: fallbackUrl, openInNewTab: true, children: "Open in HubSpot" }) : null;
1145
1156
  if (isLoadingAvailability && !availability) {
@@ -1198,8 +1209,8 @@ function HubSpotMeetingScheduler({
1198
1209
  "\xB7 ",
1199
1210
  _chunkSPAAKHQAcjs.formatDurationCompact.call(void 0, durationMs / 1e3)
1200
1211
  ] }),
1201
- bookingError && bookingError !== "SLOT_TAKEN" && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkQDOBY3TPcjs.Alert, { variant: "error", className: "flex flex-col items-start gap-[var(--spacing-system-xs)]", children: [
1202
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkQDOBY3TPcjs.AlertDescription, { children: _nullishCoalesce(BOOKING_ERROR_COPY[bookingError], () => ( BOOKING_ERROR_COPY.VALIDATION)) }),
1212
+ bookingError && bookingError !== "SLOT_TAKEN" && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col items-start gap-[var(--spacing-system-xs)]", children: [
1213
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-ods-error text-h6", role: "alert", children: _nullishCoalesce(BOOKING_ERROR_COPY[bookingError], () => ( BOOKING_ERROR_COPY.VALIDATION)) }),
1203
1214
  escapeHatch
1204
1215
  ] }),
1205
1216
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,