@squaredr/fieldcraft-pro 1.8.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.
- package/dist/{chunk-IW654Z3M.mjs → chunk-5QXKDYWY.mjs} +92 -5
- package/dist/{chunk-MLP5EDWP.mjs → chunk-7GPX3YIB.mjs} +35 -61
- package/dist/{chunk-GXKQCXIB.mjs → chunk-DWRLPGX5.mjs} +395 -86
- package/dist/{chunk-CYXJGOQH.mjs → chunk-KTIU7VCP.mjs} +68 -9
- package/dist/default-schema-BGxf9Jrg.d.ts +174 -0
- package/dist/default-schema-Bwq8FATF.d.mts +174 -0
- package/dist/form-builder/index.d.mts +14 -170
- package/dist/form-builder/index.d.ts +14 -170
- package/dist/form-builder/index.js +396 -85
- package/dist/form-builder/index.mjs +1 -1
- package/dist/index.d.mts +109 -8
- package/dist/index.d.ts +109 -8
- package/dist/index.js +1697 -217
- package/dist/index.mjs +1106 -11
- package/dist/response-viewer/index.js +36 -61
- package/dist/response-viewer/index.mjs +1 -1
- package/dist/styles.css +2 -2
- package/dist/templates/index.d.mts +1 -1
- package/dist/templates/index.d.ts +1 -1
- package/dist/templates/index.js +92 -5
- package/dist/templates/index.mjs +1 -1
- package/dist/theme-editor/index.d.mts +1 -2
- package/dist/theme-editor/index.d.ts +1 -2
- package/dist/theme-editor/index.js +68 -9
- package/dist/theme-editor/index.mjs +1 -1
- package/dist/{types-bFHsZT1n.d.mts → types-BQOeI3LX.d.mts} +1 -1
- package/dist/{types-bFHsZT1n.d.ts → types-BQOeI3LX.d.ts} +1 -1
- package/package.json +21 -21
- package/theme-editor/styles.css +29 -0
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-
|
|
2
|
-
export { ResponseViewer } from './chunk-
|
|
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-
|
|
5
|
-
export { PRESET_FAMILIES, PREVIEW_SCHEMA, ThemeEditor, ThemeEditorThemeProvider, resolveThemeFromDOM, themeEditorDarkPreset, themeEditorLightPreset, useEditorTheme } from './chunk-
|
|
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.
|
|
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);
|
|
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
|
|
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 || "
|
|
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 };
|