@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.
- package/LICENSE.txt +51 -51
- package/README.md +139 -139
- package/dist/{chunk-VEP3DOCQ.mjs → chunk-5QXKDYWY.mjs} +103 -19
- package/dist/{chunk-GZIV2XAD.mjs → chunk-7GPX3YIB.mjs} +146 -183
- package/dist/{chunk-J5V6OVZO.mjs → chunk-DWRLPGX5.mjs} +690 -211
- package/dist/{chunk-4TN6YJDF.mjs → chunk-KTIU7VCP.mjs} +971 -214
- 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 +827 -326
- package/dist/form-builder/index.mjs +1 -1
- package/dist/index.d.mts +111 -10
- package/dist/index.d.ts +111 -10
- package/dist/index.js +3525 -971
- package/dist/index.mjs +1424 -124
- package/dist/response-viewer/index.d.mts +1 -1
- package/dist/response-viewer/index.d.ts +1 -1
- package/dist/response-viewer/index.js +146 -183
- 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 +103 -19
- package/dist/templates/index.mjs +1 -1
- package/dist/theme-editor/index.d.mts +12 -7
- package/dist/theme-editor/index.d.ts +12 -7
- package/dist/theme-editor/index.js +971 -214
- 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 +29 -18
- 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-
|
|
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,
|
|
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.
|
|
13
|
-
function
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
|
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({
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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", {
|
|
100
|
-
amount != null && /* @__PURE__ */ jsxs("p", {
|
|
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(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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", {
|
|
118
|
-
/* @__PURE__ */ jsxs(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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", {
|
|
144
|
-
/* @__PURE__ */ jsxs(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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", {
|
|
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", {
|
|
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
|
-
|
|
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", {
|
|
171
|
-
/* @__PURE__ */ jsx(
|
|
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", {
|
|
179
|
-
/* @__PURE__ */ jsxs(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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", {
|
|
187
|
-
/* @__PURE__ */ jsxs(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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", {
|
|
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
|
-
|
|
1537
|
+
PayKitCheckout,
|
|
201
1538
|
{
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
232
|
-
|
|
233
|
-
|
|
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 };
|