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

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.
@@ -79,104 +79,13 @@ import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMem
79
79
  // src/hooks/use-meeting-booking.ts
80
80
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
81
81
  import { useCallback, useLayoutEffect, useRef, useState } from "react";
82
-
83
- // src/schemas/meeting-booking-schema.ts
84
- import { z } from "zod";
85
- var MEETING_BOOKING_ERROR_CODES = [
82
+ var KNOWN_CODES = [
86
83
  "SLOT_TAKEN",
87
84
  "VALIDATION",
88
- "INVALID_EMAIL",
89
85
  "LINK_GONE",
90
86
  "TEMPORARILY_UNAVAILABLE",
91
87
  "MEETING_UNAVAILABLE"
92
88
  ];
93
- var SUPPORTED_FORM_FIELD_TYPES = ["text", "textarea", "select", "radio", "checkbox"];
94
- function isSupportedFormField(field) {
95
- return SUPPORTED_FORM_FIELD_TYPES.includes(field.type);
96
- }
97
- var IANA_TZ_RE = /^(?:UTC|[A-Za-z_]+(?:\/[A-Za-z0-9_+-]+)+)$/;
98
- function isValidIanaTimezone(tz) {
99
- if (!IANA_TZ_RE.test(tz)) return false;
100
- try {
101
- new Intl.DateTimeFormat("en-US", { timeZone: tz });
102
- return true;
103
- } catch {
104
- return false;
105
- }
106
- }
107
- function isValidBcp47Locale(locale) {
108
- try {
109
- return Intl.getCanonicalLocales(locale).length > 0;
110
- } catch {
111
- return false;
112
- }
113
- }
114
- function makeBookingSchema(formFields, legalConsent) {
115
- const answers = {};
116
- for (const field of formFields) {
117
- if (!isSupportedFormField(field)) continue;
118
- let validator;
119
- switch (field.type) {
120
- case "checkbox":
121
- validator = z.boolean();
122
- break;
123
- case "select":
124
- case "radio":
125
- validator = z.string().refine((v) => !v || (field.options ?? []).includes(v), {
126
- message: `Please choose a valid option for ${field.label}`
127
- });
128
- break;
129
- case "textarea":
130
- validator = z.string().max(5e3, { message: `${field.label} is too long` });
131
- break;
132
- case "text":
133
- default:
134
- validator = z.string().max(1e3, { message: `${field.label} is too long` });
135
- break;
136
- }
137
- if (field.required) {
138
- validator = field.type === "checkbox" ? z.literal(true, { message: `${field.label} is required` }) : validator.min(1, { message: `${field.label} is required` });
139
- } else if (field.type !== "checkbox") {
140
- validator = validator.optional().or(z.literal(""));
141
- } else {
142
- validator = z.boolean().optional();
143
- }
144
- answers[field.name] = validator;
145
- }
146
- const requiredConsentIds = (legalConsent?.communicationConsentCheckboxes ?? []).filter((c) => c.required).map((c) => c.communicationTypeId);
147
- const hasRequiredAnswers = formFields.some((f) => isSupportedFormField(f) && f.required);
148
- const answersObject = z.object(answers);
149
- return z.object({
150
- meetingId: z.string().min(1),
151
- startTimeMs: z.number().int().positive(),
152
- durationMs: z.number().int().positive(),
153
- firstName: z.string().min(1, { message: "First name is required" }).max(255),
154
- lastName: z.string().min(1, { message: "Last name is required" }).max(255),
155
- email: z.string().email({ message: "Please enter a valid email address" }).max(255),
156
- timezone: z.string().refine(isValidIanaTimezone, { message: "Invalid timezone" }),
157
- locale: z.string().refine(isValidBcp47Locale, { message: "Invalid locale" }).optional(),
158
- // Plain `.optional()` (no `.default()`) so zod's input and output types
159
- // match — react-hook-form's zodResolver needs them identical, and the
160
- // server handles `undefined` explicitly anyway. REQUIRED when the link
161
- // declares required questions (see `hasRequiredAnswers` above).
162
- formFields: hasRequiredAnswers ? answersObject : answersObject.optional(),
163
- legalConsentResponses: z.array(z.object({ communicationTypeId: z.string(), consented: z.boolean() })).optional()
164
- }).superRefine((data, ctx) => {
165
- const responses = data.legalConsentResponses ?? [];
166
- const ok = requiredConsentIds.every(
167
- (id) => responses.some((r) => r.communicationTypeId === id && r.consented === true)
168
- );
169
- if (!ok) {
170
- ctx.addIssue({
171
- code: z.ZodIssueCode.custom,
172
- path: ["legalConsentResponses"],
173
- message: "Required consent checkboxes must be accepted"
174
- });
175
- }
176
- });
177
- }
178
-
179
- // src/hooks/use-meeting-booking.ts
180
89
  var AVAILABILITY_STALE_MS = 0;
181
90
  function meetingAvailabilityKey(apiBaseUrl, meetingId, monthOffset) {
182
91
  return ["meeting-availability", apiBaseUrl, meetingId, monthOffset];
@@ -225,7 +134,7 @@ function useMeetingBooking(options) {
225
134
  const data = await res.json().catch(() => ({}));
226
135
  if (res.ok) return { ok: true, confirmation: data };
227
136
  const rawCode = typeof data.code === "string" ? data.code : "";
228
- const code = MEETING_BOOKING_ERROR_CODES.includes(rawCode) ? rawCode : "TEMPORARILY_UNAVAILABLE";
137
+ const code = KNOWN_CODES.includes(rawCode) ? rawCode : "TEMPORARILY_UNAVAILABLE";
229
138
  return { ok: false, code, message: typeof data.error === "string" ? data.error : void 0 };
230
139
  } catch {
231
140
  return { ok: false, code: "TEMPORARILY_UNAVAILABLE" };
@@ -264,6 +173,94 @@ function useMeetingBooking(options) {
264
173
  };
265
174
  }
266
175
 
176
+ // src/schemas/meeting-booking-schema.ts
177
+ import { z } from "zod";
178
+ var SUPPORTED_FORM_FIELD_TYPES = ["text", "textarea", "select", "radio", "checkbox"];
179
+ function isSupportedFormField(field) {
180
+ return SUPPORTED_FORM_FIELD_TYPES.includes(field.type);
181
+ }
182
+ var IANA_TZ_RE = /^(?:UTC|[A-Za-z_]+(?:\/[A-Za-z0-9_+-]+)+)$/;
183
+ function isValidIanaTimezone(tz) {
184
+ if (!IANA_TZ_RE.test(tz)) return false;
185
+ try {
186
+ new Intl.DateTimeFormat("en-US", { timeZone: tz });
187
+ return true;
188
+ } catch {
189
+ return false;
190
+ }
191
+ }
192
+ function isValidBcp47Locale(locale) {
193
+ try {
194
+ return Intl.getCanonicalLocales(locale).length > 0;
195
+ } catch {
196
+ return false;
197
+ }
198
+ }
199
+ function makeBookingSchema(formFields, legalConsent) {
200
+ const answers = {};
201
+ for (const field of formFields) {
202
+ if (!isSupportedFormField(field)) continue;
203
+ let validator;
204
+ switch (field.type) {
205
+ case "checkbox":
206
+ validator = z.boolean();
207
+ break;
208
+ case "select":
209
+ case "radio":
210
+ validator = z.string().refine((v) => !v || (field.options ?? []).includes(v), {
211
+ message: `Please choose a valid option for ${field.label}`
212
+ });
213
+ break;
214
+ case "textarea":
215
+ validator = z.string().max(5e3, { message: `${field.label} is too long` });
216
+ break;
217
+ case "text":
218
+ default:
219
+ validator = z.string().max(1e3, { message: `${field.label} is too long` });
220
+ break;
221
+ }
222
+ if (field.required) {
223
+ validator = field.type === "checkbox" ? z.literal(true, { message: `${field.label} is required` }) : validator.min(1, { message: `${field.label} is required` });
224
+ } else if (field.type !== "checkbox") {
225
+ validator = validator.optional().or(z.literal(""));
226
+ } else {
227
+ validator = z.boolean().optional();
228
+ }
229
+ answers[field.name] = validator;
230
+ }
231
+ const requiredConsentIds = (legalConsent?.communicationConsentCheckboxes ?? []).filter((c) => c.required).map((c) => c.communicationTypeId);
232
+ const hasRequiredAnswers = formFields.some((f) => isSupportedFormField(f) && f.required);
233
+ const answersObject = z.object(answers);
234
+ return z.object({
235
+ meetingId: z.string().min(1),
236
+ startTimeMs: z.number().int().positive(),
237
+ durationMs: z.number().int().positive(),
238
+ firstName: z.string().min(1, { message: "First name is required" }).max(255),
239
+ lastName: z.string().min(1, { message: "Last name is required" }).max(255),
240
+ email: z.string().email({ message: "Please enter a valid email address" }).max(255),
241
+ timezone: z.string().refine(isValidIanaTimezone, { message: "Invalid timezone" }),
242
+ locale: z.string().refine(isValidBcp47Locale, { message: "Invalid locale" }).optional(),
243
+ // Plain `.optional()` (no `.default()`) so zod's input and output types
244
+ // match — react-hook-form's zodResolver needs them identical, and the
245
+ // server handles `undefined` explicitly anyway. REQUIRED when the link
246
+ // declares required questions (see `hasRequiredAnswers` above).
247
+ formFields: hasRequiredAnswers ? answersObject : answersObject.optional(),
248
+ legalConsentResponses: z.array(z.object({ communicationTypeId: z.string(), consented: z.boolean() })).optional()
249
+ }).superRefine((data, ctx) => {
250
+ const responses = data.legalConsentResponses ?? [];
251
+ const ok = requiredConsentIds.every(
252
+ (id) => responses.some((r) => r.communicationTypeId === id && r.consented === true)
253
+ );
254
+ if (!ok) {
255
+ ctx.addIssue({
256
+ code: z.ZodIssueCode.custom,
257
+ path: ["legalConsentResponses"],
258
+ message: "Required consent checkboxes must be accepted"
259
+ });
260
+ }
261
+ });
262
+ }
263
+
267
264
  // src/components/meeting-scheduler/booking-form.tsx
268
265
  import { zodResolver } from "@hookform/resolvers/zod";
269
266
  import { useMemo } from "react";