@squaredr/fieldcraft-pro 1.8.0 → 1.9.1

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/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
- export { DEFAULT_PALETTE, DEFAULT_SCHEMA, FormBuilder, FormBuilderThemeProvider, QUESTION_TYPE_INFO, addOption, addQuestion, addSection, duplicateQuestion, duplicateSection, findQuestion, findSection, generateId, generateOptionId, generateQuestionId, generateSectionId, moveOption, moveQuestion, moveSection, removeOption, removeQuestion, removeSection, updateOption, updateQuestion, updateSection, useBuilderState, useBuilderTheme, useDragDrop, useUndoRedo } from './chunk-GXKQCXIB.mjs';
2
- export { ResponseViewer } from './chunk-MLP5EDWP.mjs';
1
+ export { DEFAULT_PALETTE, DEFAULT_SCHEMA, FormBuilder, FormBuilderThemeProvider, QUESTION_TYPE_INFO, addOption, addQuestion, addSection, duplicateQuestion, duplicateSection, findQuestion, findSection, generateId, generateOptionId, generateQuestionId, generateSectionId, moveOption, moveQuestion, moveSection, removeOption, removeQuestion, removeSection, updateOption, updateQuestion, updateSection, useBuilderState, useBuilderTheme, useDragDrop, useUndoRedo } from './chunk-DWRLPGX5.mjs';
2
+ export { ResponseViewer } from './chunk-7GPX3YIB.mjs';
3
3
  export { cn } from './chunk-QQ4JZGTD.mjs';
4
- export { consultationBooking, consultationBookingMeta, consultationBookingSchema, ecommerceCheckout, ecommerceCheckoutMeta, ecommerceCheckoutSchema, proTemplates } from './chunk-IW654Z3M.mjs';
5
- export { PRESET_FAMILIES, PREVIEW_SCHEMA, ThemeEditor, ThemeEditorThemeProvider, resolveThemeFromDOM, themeEditorDarkPreset, themeEditorLightPreset, useEditorTheme } from './chunk-CYXJGOQH.mjs';
4
+ export { consultationBooking, consultationBookingMeta, consultationBookingSchema, ecommerceCheckout, ecommerceCheckoutMeta, ecommerceCheckoutSchema, proTemplates } from './chunk-5QXKDYWY.mjs';
5
+ export { PRESET_FAMILIES, PREVIEW_SCHEMA, ThemeEditor, ThemeEditorThemeProvider, resolveThemeFromDOM, themeEditorDarkPreset, themeEditorLightPreset, useEditorTheme } from './chunk-KTIU7VCP.mjs';
6
6
  export { FieldCraftProProvider, UnlicensedOverlay, isProductionEnvironment, requireLicense, useLicense, validateLicense } from './chunk-4MMKB2EW.mjs';
7
- import { useState, useEffect, useMemo } from 'react';
7
+ import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
8
8
  import { FieldWrapper } from '@squaredr/fieldcraft-react';
9
9
  import { PayKitProvider, CheckoutForm } from '@squaredr/paykit-react';
10
10
  import { StripeClientAdapter } from '@squaredr/paykit/stripe/client';
@@ -12,7 +12,1088 @@ import { formatAmount } from '@squaredr/paykit';
12
12
  import { jsx, jsxs } from 'react/jsx-runtime';
13
13
 
14
14
  // package.json
15
- var version = "1.8.0";
15
+ var version = "1.9.1";
16
+ function formatBytes(bytes, decimals = 1) {
17
+ if (bytes === 0) return "0 B";
18
+ const k = 1024;
19
+ const dm = decimals < 0 ? 0 : decimals;
20
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
21
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
22
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
23
+ }
24
+ function isFileAccepted(file, acceptList) {
25
+ if (!acceptList || acceptList.length === 0) return true;
26
+ const fileName = file.name.toLowerCase();
27
+ const fileType = file.type.toLowerCase();
28
+ return acceptList.some((pattern) => {
29
+ const p = pattern.trim().toLowerCase();
30
+ if (!p) return false;
31
+ if (p === "*/*") return true;
32
+ if (p.startsWith(".")) {
33
+ return fileName.endsWith(p);
34
+ }
35
+ if (p.endsWith("/*")) {
36
+ const baseType = p.slice(0, -2);
37
+ return fileType.startsWith(baseType);
38
+ }
39
+ return fileType === p;
40
+ });
41
+ }
42
+ async function decipherPresignPayload(encryptedHex, ivHex, secretKeyStr) {
43
+ const enc = new TextEncoder();
44
+ const rawKey = enc.encode(secretKeyStr.padEnd(32, "0").slice(0, 32));
45
+ const cryptoObj = typeof window !== "undefined" ? window.crypto : globalThis.crypto;
46
+ if (!cryptoObj || !cryptoObj.subtle) {
47
+ throw new Error("Web Crypto API is not supported in this environment.");
48
+ }
49
+ const cryptoKey = await cryptoObj.subtle.importKey(
50
+ "raw",
51
+ rawKey,
52
+ { name: "AES-GCM" },
53
+ false,
54
+ ["decrypt"]
55
+ );
56
+ let iv;
57
+ if (/^[0-9a-fA-F]+$/.test(ivHex) && ivHex.length % 2 === 0) {
58
+ iv = new Uint8Array(ivHex.match(/.{1,2}/g).map((b) => parseInt(b, 16)));
59
+ } else {
60
+ iv = Uint8Array.from(atob(ivHex), (c) => c.charCodeAt(0));
61
+ }
62
+ let ciphertext;
63
+ if (/^[0-9a-fA-F]+$/.test(encryptedHex) && encryptedHex.length % 2 === 0) {
64
+ ciphertext = new Uint8Array(encryptedHex.match(/.{1,2}/g).map((b) => parseInt(b, 16)));
65
+ } else {
66
+ ciphertext = Uint8Array.from(atob(encryptedHex), (c) => c.charCodeAt(0));
67
+ }
68
+ const decryptedBuffer = await cryptoObj.subtle.decrypt(
69
+ { name: "AES-GCM", iv },
70
+ cryptoKey,
71
+ ciphertext
72
+ );
73
+ const plaintext = new TextDecoder().decode(decryptedBuffer);
74
+ return JSON.parse(plaintext);
75
+ }
76
+ function bytesToHex(bytes) {
77
+ let hex = "";
78
+ for (let i = 0; i < bytes.length; i++) {
79
+ hex += (bytes[i] ?? 0).toString(16).padStart(2, "0");
80
+ }
81
+ return hex;
82
+ }
83
+ async function generateEphemeralKeyPair() {
84
+ const cryptoObj = typeof window !== "undefined" ? window.crypto : globalThis.crypto;
85
+ if (!cryptoObj || !cryptoObj.subtle) {
86
+ throw new Error("Web Crypto API is not supported in this environment.");
87
+ }
88
+ return await cryptoObj.subtle.generateKey(
89
+ {
90
+ name: "RSA-OAEP",
91
+ modulusLength: 2048,
92
+ publicExponent: new Uint8Array([1, 0, 1]),
93
+ hash: "SHA-256"
94
+ },
95
+ true,
96
+ ["encrypt", "decrypt"]
97
+ );
98
+ }
99
+ async function exportPublicKeySpki(publicKey) {
100
+ const cryptoObj = typeof window !== "undefined" ? window.crypto : globalThis.crypto;
101
+ const exported = await cryptoObj.subtle.exportKey("spki", publicKey);
102
+ const bytes = new Uint8Array(exported);
103
+ let binary = "";
104
+ for (let i = 0; i < bytes.byteLength; i++) {
105
+ binary += String.fromCharCode(bytes[i] ?? 0);
106
+ }
107
+ return btoa(binary);
108
+ }
109
+ async function decryptEphemeralPayload(payload, privateKey) {
110
+ const cryptoObj = typeof window !== "undefined" ? window.crypto : globalThis.crypto;
111
+ const cipher = payload.encryptedPayload || payload.ciphertext;
112
+ if (!cipher) throw new Error("Missing encryptedPayload in response");
113
+ let ciphertext;
114
+ if (/^[0-9a-fA-F]+$/.test(cipher) && cipher.length % 2 === 0) {
115
+ ciphertext = new Uint8Array(cipher.match(/.{1,2}/g).map((b) => parseInt(b, 16)));
116
+ } else {
117
+ ciphertext = Uint8Array.from(atob(cipher), (c) => c.charCodeAt(0));
118
+ }
119
+ if (payload.encryptedKey && payload.iv) {
120
+ let encKeyBytes;
121
+ if (/^[0-9a-fA-F]+$/.test(payload.encryptedKey) && payload.encryptedKey.length % 2 === 0) {
122
+ encKeyBytes = new Uint8Array(payload.encryptedKey.match(/.{1,2}/g).map((b) => parseInt(b, 16)));
123
+ } else {
124
+ encKeyBytes = Uint8Array.from(atob(payload.encryptedKey), (c) => c.charCodeAt(0));
125
+ }
126
+ const rawAesKey = await cryptoObj.subtle.decrypt(
127
+ { name: "RSA-OAEP" },
128
+ privateKey,
129
+ encKeyBytes
130
+ );
131
+ const aesKey = await cryptoObj.subtle.importKey(
132
+ "raw",
133
+ rawAesKey,
134
+ { name: "AES-GCM" },
135
+ false,
136
+ ["decrypt"]
137
+ );
138
+ let iv;
139
+ if (/^[0-9a-fA-F]+$/.test(payload.iv) && payload.iv.length % 2 === 0) {
140
+ iv = new Uint8Array(payload.iv.match(/.{1,2}/g).map((b) => parseInt(b, 16)));
141
+ } else {
142
+ iv = Uint8Array.from(atob(payload.iv), (c) => c.charCodeAt(0));
143
+ }
144
+ const decrypted2 = await cryptoObj.subtle.decrypt(
145
+ { name: "AES-GCM", iv },
146
+ aesKey,
147
+ ciphertext
148
+ );
149
+ return JSON.parse(new TextDecoder().decode(decrypted2));
150
+ }
151
+ const decrypted = await cryptoObj.subtle.decrypt(
152
+ { name: "RSA-OAEP" },
153
+ privateKey,
154
+ ciphertext
155
+ );
156
+ return JSON.parse(new TextDecoder().decode(decrypted));
157
+ }
158
+ async function encryptPresignPayload(data, secretKeyStr) {
159
+ const enc = new TextEncoder();
160
+ const rawKey = enc.encode(secretKeyStr.padEnd(32, "0").slice(0, 32));
161
+ const cryptoObj = typeof window !== "undefined" ? window.crypto : globalThis.crypto;
162
+ if (!cryptoObj || !cryptoObj.subtle) {
163
+ throw new Error("Web Crypto API is not supported in this environment.");
164
+ }
165
+ const cryptoKey = await cryptoObj.subtle.importKey(
166
+ "raw",
167
+ rawKey,
168
+ { name: "AES-GCM" },
169
+ false,
170
+ ["encrypt"]
171
+ );
172
+ const iv = cryptoObj.getRandomValues(new Uint8Array(12));
173
+ const plaintextBytes = enc.encode(JSON.stringify(data));
174
+ const encryptedBuffer = await cryptoObj.subtle.decrypt ? await cryptoObj.subtle.encrypt({ name: "AES-GCM", iv }, cryptoKey, plaintextBytes) : new ArrayBuffer(0);
175
+ const encryptedPayload = bytesToHex(new Uint8Array(encryptedBuffer));
176
+ const ivHex = bytesToHex(iv);
177
+ return { encryptedPayload, iv: ivHex };
178
+ }
179
+ function ProFileUploadField(props) {
180
+ const {
181
+ value,
182
+ onChange,
183
+ onBlur,
184
+ error,
185
+ touched,
186
+ disabled = false,
187
+ customProps
188
+ } = props;
189
+ const field = props.field || props.question;
190
+ const config = field?.config ?? {};
191
+ const maxFiles = config.maxFiles ?? 1;
192
+ const maxSizeMb = config.maxSizeMb ?? 10;
193
+ const maxSizeBytes = maxSizeMb * 1024 * 1024;
194
+ const acceptList = config.accept;
195
+ const [items, setItems] = useState(() => {
196
+ if (!value) return [];
197
+ const arrayVal = Array.isArray(value) ? value : [value];
198
+ return arrayVal.filter((v) => Boolean(v && typeof v === "object" && "url" in v)).map((v, idx) => ({
199
+ id: `init-${idx}-${v.name}`,
200
+ name: v.name,
201
+ size: v.size || 0,
202
+ type: v.type || "application/octet-stream",
203
+ progress: 100,
204
+ status: "succeeded",
205
+ url: v.url,
206
+ key: v.key
207
+ }));
208
+ });
209
+ const [isDragOver, setIsDragOver] = useState(false);
210
+ const [generalError, setGeneralError] = useState(null);
211
+ const fileInputRef = useRef(null);
212
+ const activeXhrsRef = useRef(/* @__PURE__ */ new Map());
213
+ const notifyChange = useCallback(
214
+ (newItems) => {
215
+ const succeeded = newItems.filter((item) => item.status === "succeeded" && item.url).map((item) => ({
216
+ name: item.name,
217
+ size: item.size,
218
+ type: item.type,
219
+ url: item.url,
220
+ key: item.key,
221
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
222
+ }));
223
+ if (maxFiles === 1) {
224
+ onChange(succeeded.length > 0 ? succeeded[0] : null);
225
+ } else {
226
+ onChange(succeeded);
227
+ }
228
+ },
229
+ [maxFiles, onChange]
230
+ );
231
+ useEffect(() => {
232
+ return () => {
233
+ activeXhrsRef.current.forEach((xhr) => xhr.abort());
234
+ activeXhrsRef.current.clear();
235
+ };
236
+ }, []);
237
+ const uploadSingleFile = useCallback(
238
+ async (fileState) => {
239
+ const { file, id } = fileState;
240
+ if (!file) return;
241
+ const uploadEndpoint = config.uploadUrl || customProps?.uploadUrl || customProps?.presignUrl;
242
+ if (!uploadEndpoint) {
243
+ setItems((prev) => {
244
+ const next = prev.map(
245
+ (item) => item.id === id ? {
246
+ ...item,
247
+ progress: 0,
248
+ status: "error",
249
+ error: "No upload endpoint configured. Please configure storage settings or pass an uploadUrl."
250
+ } : item
251
+ );
252
+ setTimeout(() => notifyChange(next), 0);
253
+ return next;
254
+ });
255
+ return;
256
+ }
257
+ try {
258
+ let ephemeralKeyPair = null;
259
+ let ephemeralPublicKeyBase64 = void 0;
260
+ try {
261
+ const cryptoObj = typeof window !== "undefined" ? window.crypto : globalThis.crypto;
262
+ if (cryptoObj?.subtle) {
263
+ ephemeralKeyPair = await generateEphemeralKeyPair();
264
+ ephemeralPublicKeyBase64 = await exportPublicKeySpki(ephemeralKeyPair.publicKey);
265
+ }
266
+ } catch {
267
+ }
268
+ const headers = {
269
+ "Content-Type": "application/json",
270
+ ...ephemeralPublicKeyBase64 ? { "X-Ephemeral-Public-Key": ephemeralPublicKeyBase64 } : {},
271
+ ...config.publicKey ? { "X-Public-Key": config.publicKey } : {},
272
+ ...config.headers || {}
273
+ };
274
+ const presignRes = await fetch(uploadEndpoint, {
275
+ method: "POST",
276
+ headers,
277
+ body: JSON.stringify({
278
+ filename: file.name,
279
+ contentType: file.type || "application/octet-stream",
280
+ size: file.size,
281
+ ephemeralPublicKey: ephemeralPublicKeyBase64,
282
+ publicKey: config.publicKey,
283
+ storageProvider: config.storageProvider
284
+ })
285
+ });
286
+ if (!presignRes.ok) {
287
+ const errData = await presignRes.json().catch(() => null);
288
+ throw new Error(
289
+ errData?.message || `Presign request failed with status ${presignRes.status}`
290
+ );
291
+ }
292
+ let presignData = await presignRes.json();
293
+ if (ephemeralKeyPair && (presignData.encryptedPayload || presignData.ciphertext)) {
294
+ presignData = await decryptEphemeralPayload(presignData, ephemeralKeyPair.privateKey);
295
+ } else if (presignData.encryptedPayload || presignData.ciphertext) {
296
+ const cipher = presignData.encryptedPayload || presignData.ciphertext;
297
+ const iv = presignData.iv || presignData.nonce;
298
+ const key2 = config.publicKey || customProps?.publicKey;
299
+ if (key2) {
300
+ presignData = await decipherPresignPayload(cipher, iv, key2);
301
+ } else {
302
+ throw new Error(
303
+ "Cannot decipher encrypted upload payload: Missing public key in question configuration."
304
+ );
305
+ }
306
+ }
307
+ const uploadUrl = presignData.uploadUrl || presignData.url || presignData.signedUrl;
308
+ const fileUrl = presignData.fileUrl || presignData.publicUrl || uploadUrl.split("?")[0];
309
+ const key = presignData.key || presignData.fileKey;
310
+ if (!uploadUrl) {
311
+ throw new Error("Presign response did not contain an uploadUrl");
312
+ }
313
+ const xhr = new XMLHttpRequest();
314
+ activeXhrsRef.current.set(id, xhr);
315
+ xhr.upload.onprogress = (e) => {
316
+ if (e.lengthComputable) {
317
+ const pct = Math.round(e.loaded / e.total * 100);
318
+ setItems(
319
+ (prev) => prev.map((item) => item.id === id ? { ...item, progress: pct } : item)
320
+ );
321
+ }
322
+ };
323
+ xhr.onload = () => {
324
+ activeXhrsRef.current.delete(id);
325
+ if (xhr.status >= 200 && xhr.status < 300) {
326
+ let finalUrl = fileUrl;
327
+ let finalKey = key;
328
+ try {
329
+ if (xhr.responseText) {
330
+ const resp = JSON.parse(xhr.responseText);
331
+ if (resp.secure_url) finalUrl = resp.secure_url;
332
+ else if (resp.url && !resp.url.includes("?")) finalUrl = resp.url;
333
+ if (resp.public_id) finalKey = resp.public_id;
334
+ else if (resp.key) finalKey = resp.key;
335
+ }
336
+ } catch {
337
+ }
338
+ setItems((prev) => {
339
+ const next = prev.map(
340
+ (item) => item.id === id ? {
341
+ ...item,
342
+ progress: 100,
343
+ status: "succeeded",
344
+ url: finalUrl,
345
+ key: finalKey
346
+ } : item
347
+ );
348
+ setTimeout(() => notifyChange(next), 0);
349
+ return next;
350
+ });
351
+ } else {
352
+ setItems(
353
+ (prev) => prev.map(
354
+ (item) => item.id === id ? {
355
+ ...item,
356
+ status: "error",
357
+ error: `Upload failed (Status ${xhr.status})`
358
+ } : item
359
+ )
360
+ );
361
+ }
362
+ };
363
+ xhr.onerror = () => {
364
+ activeXhrsRef.current.delete(id);
365
+ setItems(
366
+ (prev) => prev.map(
367
+ (item) => item.id === id ? { ...item, status: "error", error: "Network error during upload" } : item
368
+ )
369
+ );
370
+ };
371
+ if (presignData.fields && typeof presignData.fields === "object") {
372
+ const formData = new FormData();
373
+ Object.entries(presignData.fields).forEach(([k, v]) => {
374
+ formData.append(k, String(v));
375
+ });
376
+ formData.append("file", file);
377
+ xhr.open("POST", uploadUrl);
378
+ xhr.send(formData);
379
+ } else {
380
+ xhr.open("PUT", uploadUrl);
381
+ xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
382
+ if (presignData.headers && typeof presignData.headers === "object") {
383
+ Object.entries(presignData.headers).forEach(([k, v]) => {
384
+ xhr.setRequestHeader(k, String(v));
385
+ });
386
+ }
387
+ xhr.send(file);
388
+ }
389
+ } catch (err) {
390
+ setItems(
391
+ (prev) => prev.map(
392
+ (item) => item.id === id ? {
393
+ ...item,
394
+ status: "error",
395
+ error: err.message || "Failed to initialize upload"
396
+ } : item
397
+ )
398
+ );
399
+ }
400
+ },
401
+ [config, customProps]
402
+ );
403
+ const handleFilesAdded = useCallback(
404
+ (files) => {
405
+ setGeneralError(null);
406
+ const incoming = Array.from(files);
407
+ if (incoming.length === 0) return;
408
+ const currentCount = items.filter((i) => i.status !== "error").length;
409
+ if (currentCount + incoming.length > maxFiles) {
410
+ setGeneralError(`You can upload a maximum of ${maxFiles} file${maxFiles === 1 ? "" : "s"}.`);
411
+ return;
412
+ }
413
+ const validFiles = [];
414
+ for (const file of incoming) {
415
+ if (!isFileAccepted(file, acceptList)) {
416
+ setGeneralError(`File "${file.name}" has an unsupported format.`);
417
+ return;
418
+ }
419
+ if (file.size > maxSizeBytes) {
420
+ setGeneralError(
421
+ `File "${file.name}" exceeds the maximum size limit of ${maxSizeMb} MB.`
422
+ );
423
+ return;
424
+ }
425
+ const newId = `file-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
426
+ validFiles.push({
427
+ id: newId,
428
+ file,
429
+ name: file.name,
430
+ size: file.size,
431
+ type: file.type,
432
+ progress: 0,
433
+ status: "uploading"
434
+ });
435
+ }
436
+ setItems((prev) => {
437
+ const next = maxFiles === 1 ? validFiles : [...prev, ...validFiles];
438
+ return next;
439
+ });
440
+ validFiles.forEach((f) => uploadSingleFile(f));
441
+ },
442
+ [acceptList, items, maxFiles, maxSizeBytes, maxSizeMb, uploadSingleFile]
443
+ );
444
+ const removeItem = useCallback(
445
+ (id) => {
446
+ const activeXhr = activeXhrsRef.current.get(id);
447
+ if (activeXhr) {
448
+ activeXhr.abort();
449
+ activeXhrsRef.current.delete(id);
450
+ }
451
+ setItems((prev) => {
452
+ const next = prev.filter((item) => item.id !== id);
453
+ setTimeout(() => notifyChange(next), 0);
454
+ return next;
455
+ });
456
+ },
457
+ [notifyChange]
458
+ );
459
+ const retryItem = useCallback(
460
+ (id) => {
461
+ const target = items.find((item) => item.id === id);
462
+ if (!target || !target.file) return;
463
+ setItems(
464
+ (prev) => prev.map(
465
+ (item) => item.id === id ? { ...item, progress: 0, status: "uploading", error: void 0 } : item
466
+ )
467
+ );
468
+ uploadSingleFile({ ...target, progress: 0, status: "uploading", error: void 0 });
469
+ },
470
+ [items, uploadSingleFile]
471
+ );
472
+ const canAddMore = items.length < maxFiles && !disabled;
473
+ const combinedError = error || generalError ? [
474
+ ...Array.isArray(error) ? error : error ? [String(error)] : [],
475
+ ...generalError ? [generalError] : []
476
+ ] : void 0;
477
+ const c = props.theme?.colors;
478
+ const inputRadius = props.theme?.shape?.inputRadius || "8px";
479
+ const dropzoneStyle = {
480
+ borderRadius: inputRadius,
481
+ ...c?.border && !isDragOver ? { borderColor: c.border } : {},
482
+ ...c?.surface ? { backgroundColor: isDragOver ? `${c.primary || "#3b82f6"}15` : c.surface } : {},
483
+ ...c?.text ? { color: c.text } : {}
484
+ };
485
+ const cardStyle = {
486
+ borderRadius: inputRadius,
487
+ ...c?.border ? { borderColor: c.border } : {},
488
+ ...c?.surface ? { backgroundColor: c.surface } : {},
489
+ ...c?.text ? { color: c.text } : {}
490
+ };
491
+ return /* @__PURE__ */ jsx(
492
+ FieldWrapper,
493
+ {
494
+ field,
495
+ error: combinedError,
496
+ touched: touched || Boolean(generalError),
497
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 font-sans", children: [
498
+ /* @__PURE__ */ jsx(
499
+ "input",
500
+ {
501
+ ref: fileInputRef,
502
+ type: "file",
503
+ multiple: maxFiles > 1,
504
+ accept: acceptList?.join(","),
505
+ className: "hidden",
506
+ disabled,
507
+ onChange: (e) => {
508
+ if (e.target.files) {
509
+ handleFilesAdded(e.target.files);
510
+ e.target.value = "";
511
+ }
512
+ },
513
+ onBlur
514
+ }
515
+ ),
516
+ canAddMore && /* @__PURE__ */ jsx(
517
+ "div",
518
+ {
519
+ style: dropzoneStyle,
520
+ onDragOver: (e) => {
521
+ e.preventDefault();
522
+ if (!disabled) setIsDragOver(true);
523
+ },
524
+ onDragLeave: () => setIsDragOver(false),
525
+ onDrop: (e) => {
526
+ e.preventDefault();
527
+ setIsDragOver(false);
528
+ if (!disabled && e.dataTransfer.files) {
529
+ handleFilesAdded(e.dataTransfer.files);
530
+ }
531
+ },
532
+ onClick: () => {
533
+ if (!disabled) fileInputRef.current?.click();
534
+ },
535
+ className: `
536
+ relative flex flex-col items-center justify-center p-6 border-2 border-dashed rounded-xl cursor-pointer transition-all duration-200
537
+ ${isDragOver ? "border-primary bg-primary/5 scale-[0.99]" : "border-muted-foreground/25 hover:border-primary/60 hover:bg-muted/30 bg-muted/10"}
538
+ ${disabled ? "opacity-50 cursor-not-allowed" : ""}
539
+ `,
540
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center text-center space-y-2", children: [
541
+ /* @__PURE__ */ jsx("div", { className: "p-3 bg-primary/10 text-primary rounded-full", children: /* @__PURE__ */ jsx(
542
+ "svg",
543
+ {
544
+ xmlns: "http://www.w3.org/2000/svg",
545
+ className: "w-6 h-6",
546
+ fill: "none",
547
+ viewBox: "0 0 24 24",
548
+ stroke: "currentColor",
549
+ children: /* @__PURE__ */ jsx(
550
+ "path",
551
+ {
552
+ strokeLinecap: "round",
553
+ strokeLinejoin: "round",
554
+ strokeWidth: 2,
555
+ d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
556
+ }
557
+ )
558
+ }
559
+ ) }),
560
+ /* @__PURE__ */ jsxs("div", { className: "text-sm", children: [
561
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Click to upload" }),
562
+ " ",
563
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "or drag and drop" })
564
+ ] }),
565
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
566
+ acceptList && acceptList.length > 0 ? `Supported: ${acceptList.join(", ")}` : "All file types supported",
567
+ " ",
568
+ "(Max ",
569
+ maxSizeMb,
570
+ " MB)"
571
+ ] })
572
+ ] })
573
+ }
574
+ ),
575
+ items.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-2", children: items.map((item) => {
576
+ const isImage = item.type.startsWith("image/") && item.url;
577
+ return /* @__PURE__ */ jsxs(
578
+ "div",
579
+ {
580
+ style: cardStyle,
581
+ className: "flex items-center justify-between p-3 bg-card border rounded-lg shadow-sm text-sm",
582
+ children: [
583
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-3 min-w-0 flex-1 mr-3", children: [
584
+ isImage ? /* @__PURE__ */ jsx(
585
+ "img",
586
+ {
587
+ src: item.url,
588
+ alt: item.name,
589
+ className: "w-10 h-10 rounded object-cover shrink-0 border"
590
+ }
591
+ ) : /* @__PURE__ */ jsx("div", { className: "w-10 h-10 rounded bg-muted flex items-center justify-center shrink-0 text-muted-foreground font-mono text-xs uppercase font-bold", children: item.name.split(".").pop()?.slice(0, 4) || "FILE" }),
592
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
593
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
594
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground truncate", children: item.name }),
595
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground shrink-0 ml-2", children: formatBytes(item.size) })
596
+ ] }),
597
+ item.status === "uploading" && /* @__PURE__ */ jsx("div", { className: "mt-1.5 w-full bg-muted rounded-full h-1.5 overflow-hidden", children: /* @__PURE__ */ jsx(
598
+ "div",
599
+ {
600
+ className: "bg-primary h-1.5 rounded-full transition-all duration-150",
601
+ style: { width: `${item.progress}%` }
602
+ }
603
+ ) }),
604
+ item.status === "error" && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-0.5", children: item.error || "Upload failed" }),
605
+ item.status === "succeeded" && /* @__PURE__ */ jsxs("p", { className: "text-xs text-emerald-600 dark:text-emerald-400 mt-0.5 flex items-center gap-1", children: [
606
+ /* @__PURE__ */ jsx("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx(
607
+ "path",
608
+ {
609
+ fillRule: "evenodd",
610
+ d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
611
+ clipRule: "evenodd"
612
+ }
613
+ ) }),
614
+ "Uploaded"
615
+ ] })
616
+ ] })
617
+ ] }),
618
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-1.5 shrink-0", children: [
619
+ item.status === "error" && /* @__PURE__ */ jsx(
620
+ "button",
621
+ {
622
+ type: "button",
623
+ onClick: () => retryItem(item.id),
624
+ className: "p-1 text-xs text-primary hover:underline",
625
+ title: "Retry upload",
626
+ children: "Retry"
627
+ }
628
+ ),
629
+ item.status === "succeeded" && item.url && /* @__PURE__ */ jsx(
630
+ "a",
631
+ {
632
+ href: item.url,
633
+ target: "_blank",
634
+ rel: "noreferrer",
635
+ className: "p-1.5 text-muted-foreground hover:text-foreground rounded transition-colors",
636
+ title: "View file",
637
+ children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx(
638
+ "path",
639
+ {
640
+ strokeLinecap: "round",
641
+ strokeLinejoin: "round",
642
+ strokeWidth: 2,
643
+ d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
644
+ }
645
+ ) })
646
+ }
647
+ ),
648
+ !disabled && /* @__PURE__ */ jsx(
649
+ "button",
650
+ {
651
+ type: "button",
652
+ onClick: () => removeItem(item.id),
653
+ className: "p-1.5 text-muted-foreground hover:text-destructive rounded transition-colors",
654
+ title: "Remove file",
655
+ children: /* @__PURE__ */ jsx("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx(
656
+ "path",
657
+ {
658
+ strokeLinecap: "round",
659
+ strokeLinejoin: "round",
660
+ strokeWidth: 2,
661
+ d: "M6 18L18 6M6 6l12 12"
662
+ }
663
+ ) })
664
+ }
665
+ )
666
+ ] })
667
+ ]
668
+ },
669
+ item.id
670
+ );
671
+ }) })
672
+ ] })
673
+ }
674
+ );
675
+ }
676
+ var ProFileUploadField_default = ProFileUploadField;
677
+ function extractAttendeeInfo(fieldValues) {
678
+ if (!fieldValues) return {};
679
+ let name;
680
+ let email;
681
+ for (const [key, val] of Object.entries(fieldValues)) {
682
+ const k = key.toLowerCase();
683
+ if (!name && (k.includes("name") || k === "fullname" || k === "first_name")) {
684
+ if (typeof val === "string" && val.trim()) {
685
+ name = val.trim();
686
+ } else if (val && typeof val === "object" && "first" in val) {
687
+ const obj = val;
688
+ name = [obj.first, obj.middle, obj.last].filter(Boolean).join(" ").trim();
689
+ }
690
+ }
691
+ if (!email && (k.includes("email") || k.includes("mail"))) {
692
+ if (typeof val === "string" && val.includes("@")) {
693
+ email = val.trim();
694
+ }
695
+ }
696
+ }
697
+ return { name, email };
698
+ }
699
+ function formatTime12h(timeStr) {
700
+ const [hStr, mStr] = timeStr.split(":");
701
+ const h = parseInt(hStr, 10);
702
+ if (isNaN(h)) return timeStr;
703
+ const ampm = h >= 12 ? "PM" : "AM";
704
+ const h12 = h % 12 || 12;
705
+ return `${h12}:${mStr || "00"} ${ampm}`;
706
+ }
707
+ function ProAppointmentField(props) {
708
+ const {
709
+ value,
710
+ onChange,
711
+ onBlur,
712
+ error,
713
+ touched,
714
+ disabled = false,
715
+ fieldValues,
716
+ customProps
717
+ } = props;
718
+ const onNext = customProps?.onNext || props.onNext;
719
+ const field = props.field || props.question;
720
+ const config = field?.config ?? {};
721
+ const provider = useMemo(() => {
722
+ if (config.embedUrl) {
723
+ if (config.embedProvider === "calendly" || config.embedUrl.includes("calendly.com")) {
724
+ return "calendly";
725
+ }
726
+ return "cal_com";
727
+ }
728
+ return "slots";
729
+ }, [config.embedUrl, config.embedProvider]);
730
+ const booking = value ?? null;
731
+ const isConfirmed = booking?.status === "confirmed";
732
+ const resolvedTimezone = useMemo(() => {
733
+ if (config.timezoneField && fieldValues?.[config.timezoneField]) {
734
+ const dynamicVal = String(fieldValues[config.timezoneField]);
735
+ if (dynamicVal.trim()) return dynamicVal.trim();
736
+ }
737
+ if (config.timezone) return config.timezone;
738
+ try {
739
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
740
+ } catch {
741
+ return "UTC";
742
+ }
743
+ }, [config.timezone, config.timezoneField, fieldValues]);
744
+ const [selectedDate, setSelectedDate] = useState(() => {
745
+ if (booking?.date) return booking.date;
746
+ if (config.slots && config.slots.length > 0) return config.slots[0].date;
747
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
748
+ });
749
+ const [selectedTime, setSelectedTime] = useState(() => booking?.time || null);
750
+ const [remoteSlots, setRemoteSlots] = useState(config.slots || []);
751
+ const [isLoadingSlots, setIsLoadingSlots] = useState(false);
752
+ const [slotsFetchError, setSlotsFetchError] = useState(null);
753
+ useEffect(() => {
754
+ if (provider !== "slots" || !config.slotsUrl) return;
755
+ let isMounted = true;
756
+ setIsLoadingSlots(true);
757
+ setSlotsFetchError(null);
758
+ fetch(config.slotsUrl).then((res) => {
759
+ if (!res.ok) throw new Error(`Slots API returned status ${res.status}`);
760
+ return res.json();
761
+ }).then((data) => {
762
+ if (isMounted) {
763
+ const slotsList = Array.isArray(data) ? data : data.slots || [];
764
+ setRemoteSlots(slotsList);
765
+ if (slotsList.length > 0 && !selectedDate) {
766
+ setSelectedDate(slotsList[0].date);
767
+ }
768
+ setIsLoadingSlots(false);
769
+ }
770
+ }).catch((err) => {
771
+ if (isMounted) {
772
+ setSlotsFetchError(err.message || "Failed to load available slots");
773
+ setIsLoadingSlots(false);
774
+ }
775
+ });
776
+ return () => {
777
+ isMounted = false;
778
+ };
779
+ }, [config.slotsUrl, provider, selectedDate]);
780
+ const embedSrc = useMemo(() => {
781
+ if (!config.embedUrl) return "";
782
+ try {
783
+ const url = new URL(config.embedUrl);
784
+ const { name, email } = extractAttendeeInfo(fieldValues);
785
+ if (name && !url.searchParams.has("name")) url.searchParams.set("name", name);
786
+ if (email && !url.searchParams.has("email")) url.searchParams.set("email", email);
787
+ if (resolvedTimezone && !url.searchParams.has("timezone")) {
788
+ url.searchParams.set("timezone", resolvedTimezone);
789
+ }
790
+ if (provider === "cal_com") {
791
+ if (!url.searchParams.has("embed")) url.searchParams.set("embed", "true");
792
+ if (!url.searchParams.has("layout")) url.searchParams.set("layout", "month_view");
793
+ } else if (provider === "calendly") {
794
+ if (!url.searchParams.has("embed_type")) url.searchParams.set("embed_type", "Inline");
795
+ }
796
+ return url.toString();
797
+ } catch {
798
+ return config.embedUrl;
799
+ }
800
+ }, [config.embedUrl, fieldValues, provider, resolvedTimezone]);
801
+ useEffect(() => {
802
+ const handleMessage = (event) => {
803
+ let data = event.data;
804
+ if (!data) return;
805
+ if (typeof data === "string") {
806
+ try {
807
+ data = JSON.parse(data);
808
+ } catch {
809
+ }
810
+ }
811
+ if (typeof data !== "object" || data === null) return;
812
+ if (data.type === "cal:bookingSuccessful" || data.origin === "CAL" && data.action === "bookingSuccessful" || data.event === "cal:bookingSuccessful") {
813
+ const payload = data.data || data.payload || {};
814
+ const newBooking = {
815
+ provider: "cal_com",
816
+ status: "confirmed",
817
+ bookingId: String(payload.bookingId || payload.id || `cal-${Date.now()}`),
818
+ date: payload.date || payload.startTime?.split("T")[0],
819
+ time: payload.time || payload.startTime?.split("T")[1]?.slice(0, 5),
820
+ duration: payload.duration || config.duration || 30,
821
+ timezone: payload.timezone || resolvedTimezone,
822
+ eventTitle: payload.eventTitle || payload.title,
823
+ attendeeName: payload.name || payload.attendeeName,
824
+ attendeeEmail: payload.email || payload.attendeeEmail,
825
+ confirmedAt: (/* @__PURE__ */ new Date()).toISOString()
826
+ };
827
+ onChange(newBooking);
828
+ if (config.autoAdvance !== false && typeof onNext === "function") {
829
+ setTimeout(() => onNext(), 400);
830
+ }
831
+ }
832
+ if (data.event === "calendly.event_scheduled" || data.action === "calendly.event_scheduled" || data.type === "calendly.event_scheduled" || data.event === "event_scheduled") {
833
+ const payload = data.payload || data.data || {};
834
+ const newBooking = {
835
+ provider: "calendly",
836
+ status: "confirmed",
837
+ eventUri: payload.event?.uri,
838
+ inviteeUri: payload.invitee?.uri,
839
+ bookingId: payload.event?.uri ? String(payload.event.uri).split("/").pop() : `cal-${Date.now()}`,
840
+ duration: config.duration || 30,
841
+ timezone: resolvedTimezone,
842
+ confirmedAt: (/* @__PURE__ */ new Date()).toISOString()
843
+ };
844
+ onChange(newBooking);
845
+ if (config.autoAdvance !== false && typeof onNext === "function") {
846
+ setTimeout(() => onNext(), 400);
847
+ }
848
+ }
849
+ };
850
+ window.addEventListener("message", handleMessage);
851
+ return () => window.removeEventListener("message", handleMessage);
852
+ }, [config.autoAdvance, config.duration, onChange, onNext, resolvedTimezone]);
853
+ const handleSlotSelect = useCallback(
854
+ (time) => {
855
+ setSelectedTime(time);
856
+ const newBooking = {
857
+ provider: "slots",
858
+ status: "confirmed",
859
+ date: selectedDate,
860
+ time,
861
+ duration: config.duration || 30,
862
+ timezone: resolvedTimezone,
863
+ confirmedAt: (/* @__PURE__ */ new Date()).toISOString()
864
+ };
865
+ onChange(newBooking);
866
+ },
867
+ [config.duration, onChange, resolvedTimezone, selectedDate]
868
+ );
869
+ const handleResetBooking = useCallback(() => {
870
+ onChange(null);
871
+ setSelectedTime(null);
872
+ }, [onChange]);
873
+ const availableTimes = useMemo(() => {
874
+ const dayMatch = remoteSlots.find((s) => s.date === selectedDate);
875
+ if (dayMatch && Array.isArray(dayMatch.times) && dayMatch.times.length > 0) {
876
+ return dayMatch.times;
877
+ }
878
+ return [
879
+ "09:00",
880
+ "09:30",
881
+ "10:00",
882
+ "10:30",
883
+ "11:00",
884
+ "11:30",
885
+ "13:00",
886
+ "13:30",
887
+ "14:00",
888
+ "14:30",
889
+ "15:00",
890
+ "15:30",
891
+ "16:00"
892
+ ];
893
+ }, [remoteSlots, selectedDate]);
894
+ const combinedError = error || slotsFetchError ? [
895
+ ...Array.isArray(error) ? error : error ? [String(error)] : [],
896
+ ...slotsFetchError ? [slotsFetchError] : []
897
+ ] : void 0;
898
+ const c = props.theme?.colors;
899
+ const inputRadius = props.theme?.shape?.inputRadius || "8px";
900
+ const cardStyle = {
901
+ borderRadius: inputRadius,
902
+ ...c?.border ? { borderColor: c.border } : {},
903
+ ...c?.surface ? { backgroundColor: c.surface } : {},
904
+ ...c?.text ? { color: c.text } : {}
905
+ };
906
+ const activeSlotStyle = {
907
+ borderRadius: inputRadius,
908
+ ...c?.primary ? { backgroundColor: c.primary, borderColor: c.primary } : {},
909
+ ...c?.primaryForeground ? { color: c.primaryForeground } : {}
910
+ };
911
+ const inactiveSlotStyle = {
912
+ borderRadius: inputRadius,
913
+ ...c?.border ? { borderColor: c.border } : {},
914
+ ...c?.surface ? { backgroundColor: c.surface } : {},
915
+ ...c?.text ? { color: c.text } : {}
916
+ };
917
+ return /* @__PURE__ */ jsx(FieldWrapper, { field, error: combinedError, touched: touched || Boolean(slotsFetchError), children: /* @__PURE__ */ jsxs("div", { className: "space-y-4 font-sans", children: [
918
+ isConfirmed && booking && /* @__PURE__ */ jsxs(
919
+ "div",
920
+ {
921
+ style: cardStyle,
922
+ className: "p-5 border border-emerald-500/30 bg-emerald-500/5 rounded-xl text-foreground space-y-3",
923
+ children: [
924
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
925
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2.5", children: [
926
+ /* @__PURE__ */ jsx("div", { className: "p-2 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 rounded-full", children: /* @__PURE__ */ jsx("svg", { className: "w-5 h-5", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx(
927
+ "path",
928
+ {
929
+ fillRule: "evenodd",
930
+ d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
931
+ clipRule: "evenodd"
932
+ }
933
+ ) }) }),
934
+ /* @__PURE__ */ jsxs("div", { children: [
935
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-foreground", children: "Appointment Confirmed" }),
936
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground capitalize", children: [
937
+ "via ",
938
+ booking.provider.replace("_", ".")
939
+ ] })
940
+ ] })
941
+ ] }),
942
+ !disabled && /* @__PURE__ */ jsx(
943
+ "button",
944
+ {
945
+ type: "button",
946
+ onClick: handleResetBooking,
947
+ className: "text-xs text-muted-foreground hover:text-foreground underline transition-colors",
948
+ children: "Reschedule"
949
+ }
950
+ )
951
+ ] }),
952
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2 text-xs pt-1 border-t border-emerald-500/20", children: [
953
+ booking.date && /* @__PURE__ */ jsxs("div", { children: [
954
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Date:" }),
955
+ " ",
956
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: booking.date })
957
+ ] }),
958
+ booking.time && /* @__PURE__ */ jsxs("div", { children: [
959
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Time:" }),
960
+ " ",
961
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: formatTime12h(booking.time) })
962
+ ] }),
963
+ /* @__PURE__ */ jsxs("div", { children: [
964
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Timezone:" }),
965
+ " ",
966
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: booking.timezone || resolvedTimezone })
967
+ ] }),
968
+ booking.duration && /* @__PURE__ */ jsxs("div", { children: [
969
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Duration:" }),
970
+ " ",
971
+ /* @__PURE__ */ jsxs("span", { className: "font-medium text-foreground", children: [
972
+ booking.duration,
973
+ " mins"
974
+ ] })
975
+ ] })
976
+ ] }),
977
+ typeof onNext === "function" && /* @__PURE__ */ jsx("div", { className: "pt-2 border-t border-emerald-500/20 flex justify-end", children: /* @__PURE__ */ jsxs(
978
+ "button",
979
+ {
980
+ type: "button",
981
+ onClick: onNext,
982
+ className: "px-3.5 py-1.5 text-xs font-semibold rounded-lg bg-emerald-600 hover:bg-emerald-500 text-white transition-colors flex items-center gap-1",
983
+ children: [
984
+ "Continue",
985
+ /* @__PURE__ */ jsx("svg", { className: "w-3.5 h-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) })
986
+ ]
987
+ }
988
+ ) })
989
+ ]
990
+ }
991
+ ),
992
+ !isConfirmed && (provider === "cal_com" || provider === "calendly") && embedSrc && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
993
+ /* @__PURE__ */ jsx(
994
+ "div",
995
+ {
996
+ style: cardStyle,
997
+ className: "w-full border rounded-xl overflow-hidden shadow-sm bg-card",
998
+ children: /* @__PURE__ */ jsx(
999
+ "iframe",
1000
+ {
1001
+ src: embedSrc,
1002
+ title: "Schedule Appointment",
1003
+ className: "w-full h-[580px] border-0",
1004
+ allow: "camera; microphone; autoplay; fullscreen; payment",
1005
+ loading: "lazy"
1006
+ }
1007
+ )
1008
+ }
1009
+ ),
1010
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center justify-between gap-2 text-xs text-muted-foreground px-1", children: [
1011
+ /* @__PURE__ */ jsx("span", { children: "Finished scheduling in the calendar above?" }),
1012
+ /* @__PURE__ */ jsx(
1013
+ "button",
1014
+ {
1015
+ type: "button",
1016
+ onClick: () => {
1017
+ const manualBooking = {
1018
+ provider,
1019
+ status: "confirmed",
1020
+ bookingId: `manual-${Date.now()}`,
1021
+ timezone: resolvedTimezone,
1022
+ confirmedAt: (/* @__PURE__ */ new Date()).toISOString()
1023
+ };
1024
+ onChange(manualBooking);
1025
+ if (typeof onNext === "function") {
1026
+ setTimeout(() => onNext(), 300);
1027
+ }
1028
+ },
1029
+ className: "px-3 py-1.5 font-medium rounded-lg border border-border bg-background hover:bg-muted text-foreground transition-colors flex items-center justify-center gap-1 shadow-sm self-end sm:self-auto",
1030
+ children: "\u2713 I've Completed My Booking"
1031
+ }
1032
+ )
1033
+ ] })
1034
+ ] }),
1035
+ !isConfirmed && provider === "slots" && /* @__PURE__ */ jsxs(
1036
+ "div",
1037
+ {
1038
+ style: cardStyle,
1039
+ className: "space-y-4 p-4 border rounded-xl bg-card shadow-sm",
1040
+ children: [
1041
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center justify-between gap-3 pb-3 border-b", children: [
1042
+ /* @__PURE__ */ jsxs("div", { children: [
1043
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-semibold text-foreground block mb-1", children: "Select Date" }),
1044
+ /* @__PURE__ */ jsx(
1045
+ "input",
1046
+ {
1047
+ type: "date",
1048
+ value: selectedDate,
1049
+ min: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1050
+ disabled,
1051
+ onChange: (e) => setSelectedDate(e.target.value),
1052
+ onBlur,
1053
+ className: "px-3 py-1.5 text-sm border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/40"
1054
+ }
1055
+ )
1056
+ ] }),
1057
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground flex items-center gap-1.5 self-start sm:self-auto", children: [
1058
+ /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 text-muted-foreground/70", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
1059
+ /* @__PURE__ */ jsxs("span", { children: [
1060
+ "Timezone: ",
1061
+ /* @__PURE__ */ jsx("strong", { className: "text-foreground", children: resolvedTimezone })
1062
+ ] })
1063
+ ] })
1064
+ ] }),
1065
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1066
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-foreground block", children: [
1067
+ "Available Times (",
1068
+ config.duration || 30,
1069
+ " mins)"
1070
+ ] }),
1071
+ isLoadingSlots ? /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-xs text-muted-foreground", children: "Loading available slots..." }) : availableTimes.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 gap-2", children: availableTimes.map((timeStr) => {
1072
+ const isSelected = selectedTime === timeStr;
1073
+ return /* @__PURE__ */ jsx(
1074
+ "button",
1075
+ {
1076
+ type: "button",
1077
+ style: isSelected ? activeSlotStyle : inactiveSlotStyle,
1078
+ disabled,
1079
+ onClick: () => handleSlotSelect(timeStr),
1080
+ className: `
1081
+ px-2.5 py-2 text-xs font-medium rounded-lg border transition-all duration-150 text-center
1082
+ ${isSelected ? "bg-primary text-primary-foreground border-primary shadow-sm ring-2 ring-primary/20 scale-[1.02]" : "bg-background text-foreground border-border hover:border-primary/60 hover:bg-muted/50"}
1083
+ ${disabled ? "opacity-50 cursor-not-allowed" : ""}
1084
+ `,
1085
+ children: formatTime12h(timeStr)
1086
+ },
1087
+ timeStr
1088
+ );
1089
+ }) }) : /* @__PURE__ */ jsx("div", { className: "py-6 text-center text-xs text-muted-foreground", children: "No slots available on this date. Please select another date." })
1090
+ ] })
1091
+ ]
1092
+ }
1093
+ )
1094
+ ] }) });
1095
+ }
1096
+ var ProAppointmentField_default = ProAppointmentField;
16
1097
  function resolvePath(obj, path) {
17
1098
  let current = obj;
18
1099
  for (const key of path.split(".")) {
@@ -109,13 +1190,16 @@ function ProPaymentField(props) {
109
1190
  onChange,
110
1191
  onBlur,
111
1192
  customProps,
112
- theme: formTheme
1193
+ theme: formTheme,
1194
+ fieldValues
113
1195
  } = props;
114
1196
  const config = field.config;
115
1197
  const current = value ?? { status: "pending" };
116
1198
  const provider = config?.provider ?? "stripe";
117
1199
  const publicKey = config?.publicKey;
118
- const amount = customProps?.amount ?? config?.amount;
1200
+ const rawDynamicAmount = config?.amountField && fieldValues ? fieldValues[config.amountField] : void 0;
1201
+ const dynamicAmountCents = typeof rawDynamicAmount === "number" ? Math.round(rawDynamicAmount * 100) : typeof rawDynamicAmount === "string" && !isNaN(Number(rawDynamicAmount)) && rawDynamicAmount !== "" ? Math.round(Number(rawDynamicAmount) * 100) : void 0;
1202
+ const amount = customProps?.amount ?? dynamicAmountCents ?? config?.amount;
119
1203
  const currency = config?.currency ?? "USD";
120
1204
  const directSecret = customProps?.clientSecret;
121
1205
  const onCreateIntent = customProps?.onCreatePaymentIntent;
@@ -124,6 +1208,15 @@ function ProPaymentField(props) {
124
1208
  const [clientSecret, setClientSecret] = useState(directSecret);
125
1209
  const [intentLoading, setIntentLoading] = useState(false);
126
1210
  const [intentError, setIntentError] = useState(null);
1211
+ const prevAmountRef = useRef(amount);
1212
+ useEffect(() => {
1213
+ if (prevAmountRef.current !== amount) {
1214
+ prevAmountRef.current = amount;
1215
+ if (!directSecret) {
1216
+ setClientSecret(void 0);
1217
+ }
1218
+ }
1219
+ }, [amount, directSecret]);
127
1220
  const mode = directSecret ? "direct" : onCreateIntent ? "callback" : serverUrl ? "url" : "setup";
128
1221
  useEffect(() => {
129
1222
  if (directSecret) {
@@ -190,7 +1283,7 @@ function ProPaymentField(props) {
190
1283
  const c = formTheme.colors;
191
1284
  const cardStyle = {
192
1285
  borderRadius: formTheme.shape?.inputRadius || "8px",
193
- border: `1px solid ${c?.border || "#e2e8f0"}`,
1286
+ border: `1px solid ${c?.border || "var(--rule)"}`,
194
1287
  padding: "16px",
195
1288
  background: c?.surface || c?.background,
196
1289
  color: c?.text
@@ -478,7 +1571,9 @@ function PayKitCheckout({
478
1571
  ) });
479
1572
  }
480
1573
  var PRO_FIELD_OVERRIDES = {
481
- payment: ProPaymentField
1574
+ payment: ProPaymentField,
1575
+ file_upload: ProFileUploadField_default,
1576
+ appointment: ProAppointmentField_default
482
1577
  };
483
1578
 
484
1579
  // src/index.ts
@@ -502,4 +1597,4 @@ Need a license? \u2192 https://fieldcraft.squaredr.tech/pro#pricing
502
1597
  );
503
1598
  }
504
1599
 
505
- export { PRO_FIELD_OVERRIDES, ProPaymentField };
1600
+ export { PRO_FIELD_OVERRIDES, ProAppointmentField, ProFileUploadField, ProPaymentField, decipherPresignPayload, decryptEphemeralPayload, encryptPresignPayload, exportPublicKeySpki, generateEphemeralKeyPair };