@squaredr/fieldcraft-pro 1.7.0 → 1.9.0

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.
Files changed (33) hide show
  1. package/LICENSE.txt +51 -51
  2. package/README.md +139 -139
  3. package/dist/{chunk-VEP3DOCQ.mjs → chunk-5QXKDYWY.mjs} +103 -19
  4. package/dist/{chunk-GZIV2XAD.mjs → chunk-7GPX3YIB.mjs} +146 -183
  5. package/dist/{chunk-J5V6OVZO.mjs → chunk-DWRLPGX5.mjs} +690 -211
  6. package/dist/{chunk-4TN6YJDF.mjs → chunk-KTIU7VCP.mjs} +971 -214
  7. package/dist/default-schema-BGxf9Jrg.d.ts +174 -0
  8. package/dist/default-schema-Bwq8FATF.d.mts +174 -0
  9. package/dist/form-builder/index.d.mts +14 -170
  10. package/dist/form-builder/index.d.ts +14 -170
  11. package/dist/form-builder/index.js +827 -326
  12. package/dist/form-builder/index.mjs +1 -1
  13. package/dist/index.d.mts +111 -10
  14. package/dist/index.d.ts +111 -10
  15. package/dist/index.js +3525 -971
  16. package/dist/index.mjs +1424 -124
  17. package/dist/response-viewer/index.d.mts +1 -1
  18. package/dist/response-viewer/index.d.ts +1 -1
  19. package/dist/response-viewer/index.js +146 -183
  20. package/dist/response-viewer/index.mjs +1 -1
  21. package/dist/styles.css +2 -2
  22. package/dist/templates/index.d.mts +1 -1
  23. package/dist/templates/index.d.ts +1 -1
  24. package/dist/templates/index.js +103 -19
  25. package/dist/templates/index.mjs +1 -1
  26. package/dist/theme-editor/index.d.mts +12 -7
  27. package/dist/theme-editor/index.d.ts +12 -7
  28. package/dist/theme-editor/index.js +971 -214
  29. package/dist/theme-editor/index.mjs +1 -1
  30. package/dist/{types-bFHsZT1n.d.mts → types-BQOeI3LX.d.mts} +1 -1
  31. package/dist/{types-bFHsZT1n.d.ts → types-BQOeI3LX.d.ts} +1 -1
  32. package/package.json +29 -18
  33. package/theme-editor/styles.css +497 -466
package/dist/index.mjs CHANGED
@@ -1,33 +1,1205 @@
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-J5V6OVZO.mjs';
2
- export { ResponseViewer } from './chunk-GZIV2XAD.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-VEP3DOCQ.mjs';
5
- export { PRESET_FAMILIES, PREVIEW_SCHEMA, ThemeEditor, ThemeEditorThemeProvider, resolveThemeFromDOM, themeEditorDarkPreset, themeEditorLightPreset, useEditorTheme } from './chunk-4TN6YJDF.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, Suspense } from 'react';
7
+ import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
8
8
  import { FieldWrapper } from '@squaredr/fieldcraft-react';
9
+ import { PayKitProvider, CheckoutForm } from '@squaredr/paykit-react';
10
+ import { StripeClientAdapter } from '@squaredr/paykit/stripe/client';
11
+ import { formatAmount } from '@squaredr/paykit';
9
12
  import { jsx, jsxs } from 'react/jsx-runtime';
10
13
 
11
14
  // package.json
12
- var version = "1.6.4";
13
- function formatCurrency(amount, currency) {
14
- if (amount == null) return "";
15
- try {
16
- return new Intl.NumberFormat(void 0, {
17
- style: "currency",
18
- currency: currency ?? "USD"
19
- }).format(amount);
20
- } catch {
21
- return `${currency ?? "USD"} ${amount.toFixed(2)}`;
15
+ var version = "1.9.0";
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);
22
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;
1097
+ function resolvePath(obj, path) {
1098
+ let current = obj;
1099
+ for (const key of path.split(".")) {
1100
+ if (current == null || typeof current !== "object") return void 0;
1101
+ current = current[key];
1102
+ }
1103
+ return current;
1104
+ }
1105
+ function isDarkTheme(theme) {
1106
+ const bg = theme.colors?.background ?? "#FFFFFF";
1107
+ let hex = bg.replace("#", "");
1108
+ if (hex.length === 3) {
1109
+ hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
1110
+ }
1111
+ if (hex.length < 6) return false;
1112
+ const r = parseInt(hex.slice(0, 2), 16);
1113
+ const g = parseInt(hex.slice(2, 4), 16);
1114
+ const b = parseInt(hex.slice(4, 6), 16);
1115
+ return 0.299 * r + 0.587 * g + 0.114 * b < 128;
1116
+ }
1117
+ function resolveAppearance(theme) {
1118
+ const c = theme.colors;
1119
+ const isDark = isDarkTheme(theme);
1120
+ return {
1121
+ theme: isDark ? "night" : "default",
1122
+ variables: {
1123
+ colorPrimary: c?.primary,
1124
+ colorBackground: c?.surface || c?.background,
1125
+ colorText: c?.text,
1126
+ colorDanger: c?.error,
1127
+ colorSuccess: c?.success || "#22c55e",
1128
+ borderRadius: theme.shape?.inputRadius || "6px",
1129
+ fontFamily: theme.typography?.fontFamily
1130
+ }
1131
+ };
1132
+ }
1133
+ var PAYKIT_STYLES = `
1134
+ [data-paykit-form] {
1135
+ display: flex;
1136
+ flex-direction: column;
1137
+ gap: 16px;
1138
+ }
1139
+ [data-paykit-submit] {
1140
+ display: inline-flex;
1141
+ align-items: center;
1142
+ justify-content: center;
1143
+ width: 100%;
1144
+ padding: 10px 20px;
1145
+ font-size: 0.9375rem;
1146
+ font-weight: 600;
1147
+ font-family: var(--paykit-font-family, inherit);
1148
+ line-height: 1.5;
1149
+ color: #fff;
1150
+ background: var(--paykit-color-primary, #635BFF);
1151
+ border: none;
1152
+ border-radius: var(--paykit-border-radius, 6px);
1153
+ cursor: pointer;
1154
+ transition: opacity 150ms ease, box-shadow 150ms ease, filter 150ms ease;
1155
+ }
1156
+ [data-paykit-submit]:hover:not(:disabled) {
1157
+ filter: brightness(1.1);
1158
+ box-shadow: 0 2px 8px color-mix(in srgb, var(--paykit-color-primary, #635BFF) 35%, transparent);
1159
+ }
1160
+ [data-paykit-submit]:active:not(:disabled) {
1161
+ filter: brightness(0.95);
1162
+ }
1163
+ [data-paykit-submit]:disabled {
1164
+ opacity: 0.5;
1165
+ cursor: not-allowed;
1166
+ }
1167
+ `;
1168
+ var paykitStylesInjected = false;
1169
+ function injectPayKitStyles() {
1170
+ if (paykitStylesInjected || typeof document === "undefined") return;
1171
+ const style = document.createElement("style");
1172
+ style.setAttribute("data-paykit-pro", "");
1173
+ style.textContent = PAYKIT_STYLES;
1174
+ document.head.appendChild(style);
1175
+ paykitStylesInjected = true;
1176
+ }
1177
+ function formatCurrency(amountCents, currency) {
1178
+ if (amountCents == null) return "";
1179
+ return formatAmount(amountCents, currency ?? "USD");
23
1180
  }
24
1181
  function ProPaymentField(props) {
25
- const { field, value, error, touched, disabled, readonly, onChange, onBlur, customProps } = props;
1182
+ injectPayKitStyles();
1183
+ const {
1184
+ field,
1185
+ value,
1186
+ error,
1187
+ touched,
1188
+ disabled,
1189
+ readonly,
1190
+ onChange,
1191
+ onBlur,
1192
+ customProps,
1193
+ theme: formTheme,
1194
+ fieldValues
1195
+ } = props;
26
1196
  const config = field.config;
27
1197
  const current = value ?? { status: "pending" };
28
1198
  const provider = config?.provider ?? "stripe";
29
1199
  const publicKey = config?.publicKey;
30
- 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;
31
1203
  const currency = config?.currency ?? "USD";
32
1204
  const directSecret = customProps?.clientSecret;
33
1205
  const onCreateIntent = customProps?.onCreatePaymentIntent;
@@ -36,6 +1208,15 @@ function ProPaymentField(props) {
36
1208
  const [clientSecret, setClientSecret] = useState(directSecret);
37
1209
  const [intentLoading, setIntentLoading] = useState(false);
38
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]);
39
1220
  const mode = directSecret ? "direct" : onCreateIntent ? "callback" : serverUrl ? "url" : "setup";
40
1221
  useEffect(() => {
41
1222
  if (directSecret) {
@@ -44,31 +1225,77 @@ function ProPaymentField(props) {
44
1225
  }
45
1226
  if (mode === "setup" || !amount || !publicKey) return;
46
1227
  if (clientSecret) return;
1228
+ let cancelled = false;
1229
+ const controller = mode === "url" ? new AbortController() : void 0;
47
1230
  const fetchIntent = async () => {
48
1231
  setIntentLoading(true);
49
1232
  setIntentError(null);
50
1233
  try {
51
1234
  if (mode === "callback" && onCreateIntent) {
52
- const result = await onCreateIntent({ amount, currency, provider, metadata: { fieldId: field.id } });
53
- setClientSecret(result.clientSecret);
1235
+ const result = await onCreateIntent({
1236
+ amount,
1237
+ currency,
1238
+ provider,
1239
+ metadata: { fieldId: field.id }
1240
+ });
1241
+ if (!cancelled) setClientSecret(result.clientSecret);
54
1242
  } else if (mode === "url" && serverUrl) {
55
1243
  const res = await fetch(serverUrl, {
56
1244
  method: "POST",
57
1245
  headers: { "Content-Type": "application/json" },
58
- body: JSON.stringify({ amount, currency, provider, metadata: { fieldId: field.id } })
1246
+ body: JSON.stringify({ amount, currency, provider, metadata: { fieldId: field.id } }),
1247
+ signal: controller?.signal
59
1248
  });
60
1249
  if (!res.ok) throw new Error(`Server error (${res.status})`);
61
1250
  const data = await res.json();
62
- setClientSecret(data.clientSecret);
1251
+ const secretPath = config?.responseMapping?.clientSecretPath;
1252
+ const secret = secretPath ? resolvePath(data, secretPath) : data.clientSecret;
1253
+ if (!secret)
1254
+ throw new Error(
1255
+ "No clientSecret in response" + (secretPath ? ` at path "${secretPath}"` : "")
1256
+ );
1257
+ if (!cancelled) setClientSecret(secret);
63
1258
  }
64
1259
  } catch (err) {
1260
+ if (cancelled) return;
65
1261
  setIntentError(err instanceof Error ? err.message : "Failed to create payment intent");
66
1262
  } finally {
67
- setIntentLoading(false);
1263
+ if (!cancelled) setIntentLoading(false);
68
1264
  }
69
1265
  };
70
1266
  fetchIntent();
71
- }, [mode, amount, currency, provider, publicKey, directSecret, clientSecret, onCreateIntent, serverUrl, field.id]);
1267
+ return () => {
1268
+ cancelled = true;
1269
+ controller?.abort();
1270
+ };
1271
+ }, [
1272
+ mode,
1273
+ amount,
1274
+ currency,
1275
+ provider,
1276
+ publicKey,
1277
+ directSecret,
1278
+ clientSecret,
1279
+ onCreateIntent,
1280
+ serverUrl,
1281
+ field.id
1282
+ ]);
1283
+ const c = formTheme.colors;
1284
+ const cardStyle = {
1285
+ borderRadius: formTheme.shape?.inputRadius || "8px",
1286
+ border: `1px solid ${c?.border || "var(--rule)"}`,
1287
+ padding: "16px",
1288
+ background: c?.surface || c?.background,
1289
+ color: c?.text
1290
+ };
1291
+ const mutedTextStyle = { color: c?.textMuted, fontSize: "0.875rem" };
1292
+ const badgeStyle = {
1293
+ fontSize: "0.75rem",
1294
+ padding: "2px 8px",
1295
+ borderRadius: formTheme.shape?.inputRadius || "4px",
1296
+ background: c?.secondary || c?.surface,
1297
+ color: c?.secondaryForeground || c?.textMuted
1298
+ };
72
1299
  const handleSuccess = (chargeId) => {
73
1300
  const result = { status: "succeeded", chargeId };
74
1301
  onChange(result);
@@ -83,7 +1310,8 @@ function ProPaymentField(props) {
83
1310
  };
84
1311
  if (!publicKey) {
85
1312
  return /* @__PURE__ */ jsx(FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsx("div", { className: "rounded-lg border-2 border-dashed border-input p-4 text-center", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
86
- "Payment field \u2014 configure a ",
1313
+ "Payment field \u2014 configure a",
1314
+ " ",
87
1315
  /* @__PURE__ */ jsx("code", { className: "bg-muted px-1 rounded text-xs", children: "publicKey" }),
88
1316
  " in the field properties panel."
89
1317
  ] }) }) });
@@ -96,40 +1324,97 @@ function ProPaymentField(props) {
96
1324
  ] }) }) });
97
1325
  }
98
1326
  if (current.status === "succeeded") {
99
- return /* @__PURE__ */ jsx(FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-input p-4", children: [
100
- amount != null && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mb-2", children: [
1327
+ return /* @__PURE__ */ jsx(FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
1328
+ amount != null && /* @__PURE__ */ jsxs("p", { style: { ...mutedTextStyle, marginBottom: "8px" }, children: [
101
1329
  formatCurrency(amount, currency),
102
1330
  config?.description && /* @__PURE__ */ jsxs("span", { children: [
103
1331
  " \u2014 ",
104
1332
  config.description
105
1333
  ] })
106
1334
  ] }),
107
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium", style: { color: "var(--success, #22c55e)" }, children: [
108
- /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, width: 16, height: 16, children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) }),
109
- /* @__PURE__ */ jsxs("span", { children: [
110
- "Payment successful",
111
- current.chargeId ? ` (${current.chargeId})` : ""
112
- ] })
113
- ] })
1335
+ /* @__PURE__ */ jsxs(
1336
+ "div",
1337
+ {
1338
+ style: {
1339
+ display: "flex",
1340
+ alignItems: "center",
1341
+ gap: "8px",
1342
+ fontSize: "0.875rem",
1343
+ fontWeight: 500,
1344
+ color: c?.success || "#22c55e"
1345
+ },
1346
+ children: [
1347
+ /* @__PURE__ */ jsx(
1348
+ "svg",
1349
+ {
1350
+ viewBox: "0 0 24 24",
1351
+ fill: "none",
1352
+ stroke: "currentColor",
1353
+ strokeWidth: 2,
1354
+ width: 16,
1355
+ height: 16,
1356
+ children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
1357
+ }
1358
+ ),
1359
+ /* @__PURE__ */ jsxs("span", { children: [
1360
+ "Payment successful",
1361
+ current.chargeId ? ` (${current.chargeId})` : ""
1362
+ ] })
1363
+ ]
1364
+ }
1365
+ )
114
1366
  ] }) });
115
1367
  }
116
1368
  if (current.status === "failed") {
117
- return /* @__PURE__ */ jsx(FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-destructive/50 p-4", children: [
118
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-destructive font-medium", children: [
119
- /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, width: 16, height: 16, children: [
120
- /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
121
- /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
122
- ] }),
123
- /* @__PURE__ */ jsxs("span", { children: [
124
- "Payment failed",
125
- current.error ? `: ${current.error}` : ""
126
- ] })
127
- ] }),
1369
+ return /* @__PURE__ */ jsx(FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxs("div", { style: { ...cardStyle, borderColor: c?.error || "#dc2626" }, children: [
1370
+ /* @__PURE__ */ jsxs(
1371
+ "div",
1372
+ {
1373
+ style: {
1374
+ display: "flex",
1375
+ alignItems: "center",
1376
+ gap: "8px",
1377
+ fontSize: "0.875rem",
1378
+ fontWeight: 500,
1379
+ color: c?.error || "#dc2626"
1380
+ },
1381
+ children: [
1382
+ /* @__PURE__ */ jsxs(
1383
+ "svg",
1384
+ {
1385
+ viewBox: "0 0 24 24",
1386
+ fill: "none",
1387
+ stroke: "currentColor",
1388
+ strokeWidth: 2,
1389
+ width: 16,
1390
+ height: 16,
1391
+ children: [
1392
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1393
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1394
+ ]
1395
+ }
1396
+ ),
1397
+ /* @__PURE__ */ jsxs("span", { children: [
1398
+ "Payment failed",
1399
+ current.error ? `: ${current.error}` : ""
1400
+ ] })
1401
+ ]
1402
+ }
1403
+ ),
128
1404
  /* @__PURE__ */ jsx(
129
1405
  "button",
130
1406
  {
131
1407
  type: "button",
132
- className: "mt-2 text-xs text-primary hover:underline",
1408
+ style: {
1409
+ marginTop: "8px",
1410
+ fontSize: "0.75rem",
1411
+ color: c?.primary,
1412
+ background: "none",
1413
+ border: "none",
1414
+ cursor: "pointer",
1415
+ textDecoration: "underline",
1416
+ padding: 0
1417
+ },
133
1418
  onClick: () => {
134
1419
  onChange({ status: "pending" });
135
1420
  setClientSecret(void 0);
@@ -140,26 +1425,46 @@ function ProPaymentField(props) {
140
1425
  ] }) });
141
1426
  }
142
1427
  if (intentLoading || mode !== "setup" && mode !== "direct" && !clientSecret) {
143
- return /* @__PURE__ */ jsx(FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-input p-4", children: [
144
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
145
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Payment" }),
146
- /* @__PURE__ */ jsx("span", { className: "text-xs bg-muted px-2 py-0.5 rounded", children: provider })
147
- ] }),
148
- amount != null && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mb-3", children: [
1428
+ return /* @__PURE__ */ jsx(FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
1429
+ /* @__PURE__ */ jsxs(
1430
+ "div",
1431
+ {
1432
+ style: {
1433
+ display: "flex",
1434
+ alignItems: "center",
1435
+ justifyContent: "space-between",
1436
+ marginBottom: "12px"
1437
+ },
1438
+ children: [
1439
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", fontWeight: 500 }, children: "Payment" }),
1440
+ /* @__PURE__ */ jsx("span", { style: badgeStyle, children: provider })
1441
+ ]
1442
+ }
1443
+ ),
1444
+ amount != null && /* @__PURE__ */ jsxs("p", { style: { ...mutedTextStyle, marginBottom: "12px" }, children: [
149
1445
  "Amount: ",
150
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: formatCurrency(amount, currency) }),
1446
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: formatCurrency(amount, currency) }),
151
1447
  config?.description && /* @__PURE__ */ jsxs("span", { children: [
152
1448
  " \u2014 ",
153
1449
  config.description
154
1450
  ] })
155
1451
  ] }),
156
1452
  intentError ? /* @__PURE__ */ jsxs("div", { children: [
157
- /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: intentError }),
1453
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: c?.error || "#dc2626" }, children: intentError }),
158
1454
  /* @__PURE__ */ jsx(
159
1455
  "button",
160
1456
  {
161
1457
  type: "button",
162
- className: "mt-2 text-xs text-primary hover:underline",
1458
+ style: {
1459
+ marginTop: "8px",
1460
+ fontSize: "0.75rem",
1461
+ color: c?.primary,
1462
+ background: "none",
1463
+ border: "none",
1464
+ cursor: "pointer",
1465
+ textDecoration: "underline",
1466
+ padding: 0
1467
+ },
163
1468
  onClick: () => {
164
1469
  setIntentError(null);
165
1470
  setClientSecret(void 0);
@@ -167,54 +1472,78 @@ function ProPaymentField(props) {
167
1472
  children: "Retry"
168
1473
  }
169
1474
  )
170
- ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
171
- /* @__PURE__ */ jsx("svg", { className: "animate-spin size-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", strokeDasharray: "60", strokeDashoffset: "20" }) }),
1475
+ ] }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", ...mutedTextStyle }, children: [
1476
+ /* @__PURE__ */ jsx(
1477
+ "svg",
1478
+ {
1479
+ style: { width: "16px", height: "16px", animation: "spin 1s linear infinite" },
1480
+ viewBox: "0 0 24 24",
1481
+ fill: "none",
1482
+ stroke: "currentColor",
1483
+ strokeWidth: 2,
1484
+ children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", strokeDasharray: "60", strokeDashoffset: "20" })
1485
+ }
1486
+ ),
172
1487
  /* @__PURE__ */ jsx("span", { children: "Preparing payment..." })
173
1488
  ] })
174
1489
  ] }) });
175
1490
  }
176
1491
  if (!clientSecret) {
177
1492
  const waitingForAmount = config?.amountField && !amount;
178
- return /* @__PURE__ */ jsx(FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-input p-4", children: [
179
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
180
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Payment" }),
181
- /* @__PURE__ */ jsx("span", { className: "text-xs bg-muted px-2 py-0.5 rounded", children: provider })
182
- ] }),
183
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: waitingForAmount ? "Select an option above to proceed with payment." : "Provide a clientSecret via customProps, an onCreatePaymentIntent callback, or a serverUrl." })
1493
+ return /* @__PURE__ */ jsx(FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
1494
+ /* @__PURE__ */ jsxs(
1495
+ "div",
1496
+ {
1497
+ style: {
1498
+ display: "flex",
1499
+ alignItems: "center",
1500
+ justifyContent: "space-between",
1501
+ marginBottom: "12px"
1502
+ },
1503
+ children: [
1504
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", fontWeight: 500 }, children: "Payment" }),
1505
+ /* @__PURE__ */ jsx("span", { style: badgeStyle, children: provider })
1506
+ ]
1507
+ }
1508
+ ),
1509
+ /* @__PURE__ */ jsx("p", { style: mutedTextStyle, children: waitingForAmount ? "Select an option above to proceed with payment." : "Provide a clientSecret via customProps, an onCreatePaymentIntent callback, or a serverUrl." })
184
1510
  ] }) });
185
1511
  }
186
- return /* @__PURE__ */ jsx(FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-input p-4", children: [
187
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
188
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Payment" }),
189
- /* @__PURE__ */ jsx("span", { className: "text-xs bg-muted px-2 py-0.5 rounded", children: provider })
190
- ] }),
191
- amount != null && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mb-3", children: [
1512
+ return /* @__PURE__ */ jsx(FieldWrapper, { field, error, touched, children: /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
1513
+ /* @__PURE__ */ jsxs(
1514
+ "div",
1515
+ {
1516
+ style: {
1517
+ display: "flex",
1518
+ alignItems: "center",
1519
+ justifyContent: "space-between",
1520
+ marginBottom: "12px"
1521
+ },
1522
+ children: [
1523
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", fontWeight: 500 }, children: "Payment" }),
1524
+ /* @__PURE__ */ jsx("span", { style: badgeStyle, children: provider })
1525
+ ]
1526
+ }
1527
+ ),
1528
+ amount != null && /* @__PURE__ */ jsxs("p", { style: { ...mutedTextStyle, marginBottom: "12px" }, children: [
192
1529
  "Amount: ",
193
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: formatCurrency(amount, currency) }),
1530
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: formatCurrency(amount, currency) }),
194
1531
  config?.description && /* @__PURE__ */ jsxs("span", { children: [
195
1532
  " \u2014 ",
196
1533
  config.description
197
1534
  ] })
198
1535
  ] }),
199
1536
  /* @__PURE__ */ jsx(
200
- Suspense,
1537
+ PayKitCheckout,
201
1538
  {
202
- fallback: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground py-4", children: [
203
- /* @__PURE__ */ jsx("svg", { className: "animate-spin size-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", strokeDasharray: "60", strokeDashoffset: "20" }) }),
204
- "Loading payment form..."
205
- ] }),
206
- children: /* @__PURE__ */ jsx(
207
- PayKitCheckout,
208
- {
209
- publicKey,
210
- clientSecret,
211
- amount,
212
- currency,
213
- disabled: disabled || readonly,
214
- onSuccess: handleSuccess,
215
- onError: handleError
216
- }
217
- )
1539
+ publicKey,
1540
+ clientSecret,
1541
+ amount,
1542
+ currency,
1543
+ appearance: resolveAppearance(formTheme),
1544
+ disabled: disabled || readonly,
1545
+ onSuccess: handleSuccess,
1546
+ onError: handleError
218
1547
  }
219
1548
  )
220
1549
  ] }) });
@@ -224,48 +1553,17 @@ function PayKitCheckout({
224
1553
  clientSecret,
225
1554
  amount,
226
1555
  currency,
1556
+ appearance,
227
1557
  disabled,
228
1558
  onSuccess,
229
1559
  onError
230
1560
  }) {
231
- const [PayKit, setPayKit] = useState(null);
232
- const [loadError, setLoadError] = useState(null);
233
- useEffect(() => {
234
- let cancelled = false;
235
- Promise.all([
236
- import('@squaredr/paykit-react'),
237
- // @ts-expect-error — optional peer dep, types not available at build time
238
- import('@squaredr/paykit/stripe/client')
239
- ]).then(([paykit, stripe]) => {
240
- if (cancelled) return;
241
- setPayKit({
242
- Provider: paykit.PayKitProvider,
243
- Form: paykit.CheckoutForm,
244
- adapter: new stripe.StripeClientAdapter(publicKey)
245
- });
246
- }).catch((err) => {
247
- if (cancelled) return;
248
- setLoadError(
249
- `Failed to load payment SDK. Ensure @squaredr/paykit-react and @squaredr/paykit are installed. (${err.message})`
250
- );
251
- });
252
- return () => {
253
- cancelled = true;
254
- };
255
- }, [publicKey]);
256
- if (loadError) {
257
- return /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: loadError });
258
- }
259
- if (!PayKit) {
260
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground py-2", children: [
261
- /* @__PURE__ */ jsx("svg", { className: "animate-spin size-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", strokeDasharray: "60", strokeDashoffset: "20" }) }),
262
- "Loading Stripe..."
263
- ] });
264
- }
265
- return /* @__PURE__ */ jsx(PayKit.Provider, { clientAdapter: PayKit.adapter, children: /* @__PURE__ */ jsx(
266
- PayKit.Form,
1561
+ const clientAdapter = useMemo(() => new StripeClientAdapter(publicKey), [publicKey]);
1562
+ return /* @__PURE__ */ jsx(PayKitProvider, { clientAdapter, children: /* @__PURE__ */ jsx(
1563
+ CheckoutForm,
267
1564
  {
268
1565
  clientSecret,
1566
+ appearance,
269
1567
  submitLabel: disabled ? "Payment disabled" : `Pay ${formatCurrency(amount, currency)}`,
270
1568
  onSuccess: (result) => onSuccess(result.chargeId),
271
1569
  onError: (err) => onError(err.message)
@@ -273,7 +1571,9 @@ function PayKitCheckout({
273
1571
  ) });
274
1572
  }
275
1573
  var PRO_FIELD_OVERRIDES = {
276
- payment: ProPaymentField
1574
+ payment: ProPaymentField,
1575
+ file_upload: ProFileUploadField_default,
1576
+ appointment: ProAppointmentField_default
277
1577
  };
278
1578
 
279
1579
  // src/index.ts
@@ -297,4 +1597,4 @@ Need a license? \u2192 https://fieldcraft.squaredr.tech/pro#pricing
297
1597
  );
298
1598
  }
299
1599
 
300
- export { PRO_FIELD_OVERRIDES, ProPaymentField };
1600
+ export { PRO_FIELD_OVERRIDES, ProAppointmentField, ProFileUploadField, ProPaymentField, decipherPresignPayload, decryptEphemeralPayload, encryptPresignPayload, exportPublicKeySpki, generateEphemeralKeyPair };