@uptrademedia/site-kit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +305 -0
  2. package/dist/analytics/index.js +88 -0
  3. package/dist/analytics/index.js.map +1 -0
  4. package/dist/analytics/index.mjs +70 -0
  5. package/dist/analytics/index.mjs.map +1 -0
  6. package/dist/api-N35S3EES.js +57 -0
  7. package/dist/api-N35S3EES.js.map +1 -0
  8. package/dist/api-SYBTK7Z7.mjs +4 -0
  9. package/dist/api-SYBTK7Z7.mjs.map +1 -0
  10. package/dist/blog/index.js +200 -0
  11. package/dist/blog/index.js.map +1 -0
  12. package/dist/blog/index.mjs +194 -0
  13. package/dist/blog/index.mjs.map +1 -0
  14. package/dist/chunk-3MUOUXHV.js +3721 -0
  15. package/dist/chunk-3MUOUXHV.js.map +1 -0
  16. package/dist/chunk-4HVYXYQL 2.mjs +255 -0
  17. package/dist/chunk-4HVYXYQL.mjs +255 -0
  18. package/dist/chunk-4HVYXYQL.mjs.map +1 -0
  19. package/dist/chunk-7H6I3ECV.mjs +120 -0
  20. package/dist/chunk-7H6I3ECV.mjs.map +1 -0
  21. package/dist/chunk-COI6GOX2.mjs +3679 -0
  22. package/dist/chunk-COI6GOX2.mjs.map +1 -0
  23. package/dist/chunk-EQCVQC35.js +35 -0
  24. package/dist/chunk-EQCVQC35.js 2.map +1 -0
  25. package/dist/chunk-EQCVQC35.js.map +1 -0
  26. package/dist/chunk-FEBYQGY4 2.mjs +251 -0
  27. package/dist/chunk-FEBYQGY4.mjs +251 -0
  28. package/dist/chunk-FEBYQGY4.mjs.map +1 -0
  29. package/dist/chunk-FKVJOT2F.mjs +796 -0
  30. package/dist/chunk-FKVJOT2F.mjs.map +1 -0
  31. package/dist/chunk-GQ6ZOU2N.mjs +134 -0
  32. package/dist/chunk-GQ6ZOU2N.mjs.map +1 -0
  33. package/dist/chunk-HCFPU7TU.js +137 -0
  34. package/dist/chunk-HCFPU7TU.js.map +1 -0
  35. package/dist/chunk-NYKRE2FL 2.mjs +31 -0
  36. package/dist/chunk-NYKRE2FL.mjs +31 -0
  37. package/dist/chunk-NYKRE2FL.mjs 2.map +1 -0
  38. package/dist/chunk-NYKRE2FL.mjs.map +1 -0
  39. package/dist/chunk-QP5NCO2E.js +133 -0
  40. package/dist/chunk-QP5NCO2E.js.map +1 -0
  41. package/dist/chunk-RV7H3I6J.js +255 -0
  42. package/dist/chunk-RV7H3I6J.js 2.map +1 -0
  43. package/dist/chunk-RV7H3I6J.js.map +1 -0
  44. package/dist/chunk-SBVEYCSV.js +140 -0
  45. package/dist/chunk-SBVEYCSV.js.map +1 -0
  46. package/dist/chunk-TUKGA3UK.js +257 -0
  47. package/dist/chunk-TUKGA3UK.js 2.map +1 -0
  48. package/dist/chunk-TUKGA3UK.js.map +1 -0
  49. package/dist/chunk-V3F5J6CV.js +801 -0
  50. package/dist/chunk-V3F5J6CV.js.map +1 -0
  51. package/dist/chunk-WPSRS352.mjs +135 -0
  52. package/dist/chunk-WPSRS352.mjs.map +1 -0
  53. package/dist/commerce/index.js +157 -0
  54. package/dist/commerce/index.js.map +1 -0
  55. package/dist/commerce/index.mjs +4 -0
  56. package/dist/commerce/index.mjs.map +1 -0
  57. package/dist/commerce/server.js +186 -0
  58. package/dist/commerce/server.js.map +1 -0
  59. package/dist/commerce/server.mjs +176 -0
  60. package/dist/commerce/server.mjs.map +1 -0
  61. package/dist/engage/index.js +50 -0
  62. package/dist/engage/index.js.map +1 -0
  63. package/dist/engage/index.mjs +44 -0
  64. package/dist/engage/index.mjs.map +1 -0
  65. package/dist/forms/index.js +1053 -0
  66. package/dist/forms/index.js.map +1 -0
  67. package/dist/forms/index.mjs +1035 -0
  68. package/dist/forms/index.mjs.map +1 -0
  69. package/dist/generators-7Y5ABRYV 2.mjs +161 -0
  70. package/dist/generators-7Y5ABRYV.mjs +161 -0
  71. package/dist/generators-7Y5ABRYV.mjs 2.map +1 -0
  72. package/dist/generators-7Y5ABRYV.mjs.map +1 -0
  73. package/dist/generators-GWIYCA5M.js +171 -0
  74. package/dist/generators-GWIYCA5M.js 2.map +1 -0
  75. package/dist/generators-GWIYCA5M.js.map +1 -0
  76. package/dist/index 2.mjs +74 -0
  77. package/dist/index.js +326 -0
  78. package/dist/index.js 2.map +1 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/index.mjs +222 -0
  81. package/dist/index.mjs.map +1 -0
  82. package/dist/migrator-V6KS75EA 2.mjs +265 -0
  83. package/dist/migrator-V6KS75EA.mjs +265 -0
  84. package/dist/migrator-V6KS75EA.mjs 2.map +1 -0
  85. package/dist/migrator-V6KS75EA.mjs.map +1 -0
  86. package/dist/migrator-XKM7YQCY.js +272 -0
  87. package/dist/migrator-XKM7YQCY.js 2.map +1 -0
  88. package/dist/migrator-XKM7YQCY.js.map +1 -0
  89. package/dist/scanner-MF7P3CDE 2.mjs +14386 -0
  90. package/dist/scanner-MF7P3CDE.mjs +14386 -0
  91. package/dist/scanner-MF7P3CDE.mjs 2.map +1 -0
  92. package/dist/scanner-MF7P3CDE.mjs.map +1 -0
  93. package/dist/scanner-NT6YG4TD 2.js +14397 -0
  94. package/dist/scanner-NT6YG4TD.js +14397 -0
  95. package/dist/scanner-NT6YG4TD.js 2.map +1 -0
  96. package/dist/scanner-NT6YG4TD.js.map +1 -0
  97. package/dist/seo/index.js +447 -0
  98. package/dist/seo/index.js.map +1 -0
  99. package/dist/seo/index.mjs +411 -0
  100. package/dist/seo/index.mjs.map +1 -0
  101. package/dist/seo/server.js +66 -0
  102. package/dist/seo/server.js.map +1 -0
  103. package/dist/seo/server.mjs +5 -0
  104. package/dist/seo/server.mjs.map +1 -0
  105. package/dist/setup/index.js +1050 -0
  106. package/dist/setup/index.js.map +1 -0
  107. package/dist/setup/index.mjs +1046 -0
  108. package/dist/setup/index.mjs.map +1 -0
  109. package/dist/sitemap/index.js +212 -0
  110. package/dist/sitemap/index.js.map +1 -0
  111. package/dist/sitemap/index.mjs +206 -0
  112. package/dist/sitemap/index.mjs.map +1 -0
  113. package/dist/web-vitals-BH55V7EJ.js +252 -0
  114. package/dist/web-vitals-BH55V7EJ.js 2.map +1 -0
  115. package/dist/web-vitals-BH55V7EJ.js.map +1 -0
  116. package/dist/web-vitals-RJYPWAR3 2.mjs +241 -0
  117. package/dist/web-vitals-RJYPWAR3.mjs +241 -0
  118. package/dist/web-vitals-RJYPWAR3.mjs 2.map +1 -0
  119. package/dist/web-vitals-RJYPWAR3.mjs.map +1 -0
  120. package/package.json +118 -0
@@ -0,0 +1,1053 @@
1
+ 'use strict';
2
+
3
+ var chunkRV7H3I6J_js = require('../chunk-RV7H3I6J.js');
4
+ require('../chunk-EQCVQC35.js');
5
+ var react = require('react');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+
8
+ function useFormTracking({
9
+ formId,
10
+ totalSteps,
11
+ enabled = true,
12
+ debug = false
13
+ }) {
14
+ const sessionIdRef = react.useRef("");
15
+ const startTimeRef = react.useRef(0);
16
+ const stepStartTimeRef = react.useRef(0);
17
+ const stepTimesRef = react.useRef({});
18
+ const currentStepRef = react.useRef(1);
19
+ const maxStepRef = react.useRef(1);
20
+ const analyticsIdRef = react.useRef(null);
21
+ react.useEffect(() => {
22
+ if (!enabled) return;
23
+ if (!formId) {
24
+ if (debug) console.warn("[Forms] Tracking skipped: formId is empty");
25
+ return;
26
+ }
27
+ sessionIdRef.current = crypto.randomUUID();
28
+ startTimeRef.current = Date.now();
29
+ stepStartTimeRef.current = Date.now();
30
+ const startTracking = async () => {
31
+ const apiUrl = window.__SITE_KIT_API_URL__;
32
+ const apiKey = window.__SITE_KIT_API_KEY__;
33
+ if (!apiUrl || !apiKey) {
34
+ if (debug) console.error("[Forms] Missing API URL or API key");
35
+ return;
36
+ }
37
+ try {
38
+ const response = await fetch(`${apiUrl}/api/public/forms/analytics/start`, {
39
+ method: "POST",
40
+ headers: {
41
+ "Content-Type": "application/json",
42
+ "x-api-key": apiKey
43
+ },
44
+ body: JSON.stringify({
45
+ formId,
46
+ sessionId: sessionIdRef.current,
47
+ deviceType: getDeviceType()
48
+ })
49
+ });
50
+ if (!response.ok) {
51
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
52
+ }
53
+ const data = await response.json();
54
+ analyticsIdRef.current = data.id;
55
+ if (debug) console.log("[Forms] Started tracking:", data.id);
56
+ } catch (error) {
57
+ if (debug) console.error("[Forms] Error starting tracking:", error);
58
+ }
59
+ };
60
+ startTracking();
61
+ const handleBeforeUnload = () => {
62
+ if (!analyticsIdRef.current) return;
63
+ const apiUrl = window.__SITE_KIT_API_URL__;
64
+ const apiKey = window.__SITE_KIT_API_KEY__;
65
+ if (!apiUrl || !apiKey) return;
66
+ const now = Date.now();
67
+ const currentStepTime = Math.floor((now - stepStartTimeRef.current) / 1e3);
68
+ stepTimesRef.current[currentStepRef.current] = currentStepTime;
69
+ const payload = JSON.stringify({
70
+ analyticsId: analyticsIdRef.current,
71
+ step: currentStepRef.current,
72
+ maxStep: maxStepRef.current,
73
+ stepTimes: stepTimesRef.current,
74
+ totalTimeSeconds: Math.floor((now - startTimeRef.current) / 1e3)
75
+ });
76
+ const blob = new Blob([payload], { type: "application/json" });
77
+ new Headers({
78
+ "Content-Type": "application/json",
79
+ "x-api-key": apiKey
80
+ });
81
+ const sent = navigator.sendBeacon(
82
+ `${apiUrl}/api/public/forms/analytics/abandon`,
83
+ blob
84
+ );
85
+ if (debug) console.log("[Forms] Abandonment tracked:", sent ? "success" : "failed");
86
+ };
87
+ window.addEventListener("beforeunload", handleBeforeUnload);
88
+ return () => {
89
+ window.removeEventListener("beforeunload", handleBeforeUnload);
90
+ };
91
+ }, [formId, enabled, debug]);
92
+ const trackStepChange = react.useCallback(async (step) => {
93
+ if (!enabled || !analyticsIdRef.current) return;
94
+ const apiUrl = window.__SITE_KIT_API_URL__;
95
+ const apiKey = window.__SITE_KIT_API_KEY__;
96
+ if (!apiUrl || !apiKey) {
97
+ if (debug) console.error("[Forms] Missing API URL or API key");
98
+ return;
99
+ }
100
+ const now = Date.now();
101
+ const prevStepTime = Math.floor((now - stepStartTimeRef.current) / 1e3);
102
+ stepTimesRef.current[currentStepRef.current] = prevStepTime;
103
+ currentStepRef.current = step;
104
+ maxStepRef.current = Math.max(maxStepRef.current, step);
105
+ stepStartTimeRef.current = now;
106
+ try {
107
+ const response = await fetch(`${apiUrl}/api/public/forms/analytics/step`, {
108
+ method: "POST",
109
+ headers: {
110
+ "Content-Type": "application/json",
111
+ "x-api-key": apiKey
112
+ },
113
+ body: JSON.stringify({
114
+ analyticsId: analyticsIdRef.current,
115
+ step,
116
+ maxStep: maxStepRef.current,
117
+ stepTimes: stepTimesRef.current
118
+ })
119
+ });
120
+ if (!response.ok) {
121
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
122
+ }
123
+ if (debug) console.log("[Forms] Step changed to:", step);
124
+ } catch (error) {
125
+ if (debug) console.error("[Forms] Error updating step:", error);
126
+ }
127
+ }, [enabled, debug]);
128
+ const trackComplete = react.useCallback(async () => {
129
+ if (!enabled || !analyticsIdRef.current) return;
130
+ const apiUrl = window.__SITE_KIT_API_URL__;
131
+ const apiKey = window.__SITE_KIT_API_KEY__;
132
+ if (!apiUrl || !apiKey) {
133
+ if (debug) console.error("[Forms] Missing API URL or API key");
134
+ return;
135
+ }
136
+ const now = Date.now();
137
+ const finalStepTime = Math.floor((now - stepStartTimeRef.current) / 1e3);
138
+ stepTimesRef.current[currentStepRef.current] = finalStepTime;
139
+ try {
140
+ const response = await fetch(`${apiUrl}/api/public/forms/analytics/complete`, {
141
+ method: "POST",
142
+ headers: {
143
+ "Content-Type": "application/json",
144
+ "x-api-key": apiKey
145
+ },
146
+ body: JSON.stringify({
147
+ analyticsId: analyticsIdRef.current,
148
+ totalSteps,
149
+ stepTimes: stepTimesRef.current,
150
+ totalTimeSeconds: Math.floor((now - startTimeRef.current) / 1e3)
151
+ })
152
+ });
153
+ if (!response.ok) {
154
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
155
+ }
156
+ if (debug) console.log("[Forms] Form completed (conversion auto-logged via trigger)");
157
+ } catch (error) {
158
+ if (debug) console.error("[Forms] Error completing tracking:", error);
159
+ }
160
+ }, [enabled, totalSteps, formId, debug]);
161
+ return {
162
+ trackStepChange,
163
+ trackComplete,
164
+ sessionId: sessionIdRef.current
165
+ };
166
+ }
167
+ function getDeviceType() {
168
+ if (typeof window === "undefined") return "desktop";
169
+ const ua = navigator.userAgent;
170
+ if (/tablet|ipad|playbook|silk/i.test(ua)) return "tablet";
171
+ if (/mobile|iphone|ipod|android|blackberry|opera mini|iemobile/i.test(ua)) return "mobile";
172
+ return "desktop";
173
+ }
174
+
175
+ // src/forms/useForm.ts
176
+ function getUTMParams() {
177
+ if (typeof window === "undefined") return {};
178
+ const params = new URLSearchParams(window.location.search);
179
+ return {
180
+ utm_source: params.get("utm_source"),
181
+ utm_medium: params.get("utm_medium"),
182
+ utm_campaign: params.get("utm_campaign"),
183
+ utm_term: params.get("utm_term"),
184
+ utm_content: params.get("utm_content")
185
+ };
186
+ }
187
+ function useForm(formIdOrSlug, options = {}) {
188
+ const {
189
+ projectId: optionsProjectId,
190
+ onSuccess,
191
+ onError,
192
+ initialValues = {},
193
+ redirectUrl
194
+ } = options;
195
+ const projectId = optionsProjectId || (typeof window !== "undefined" ? window.__SITE_KIT_PROJECT_ID__ : void 0);
196
+ const [form, setForm] = react.useState(null);
197
+ const [isLoading, setIsLoading] = react.useState(true);
198
+ const [fetchError, setFetchError] = react.useState(null);
199
+ const [values, setValuesState] = react.useState(initialValues);
200
+ const [errors, setErrors] = react.useState({});
201
+ const [step, setStep] = react.useState(1);
202
+ const [isSubmitting, setIsSubmitting] = react.useState(false);
203
+ const [isComplete, setIsComplete] = react.useState(false);
204
+ const totalSteps = form?.total_steps || 1;
205
+ const isMultiStep = form?.is_multi_step || false;
206
+ const { trackStepChange, trackComplete } = useFormTracking({
207
+ formId: form?.id || "",
208
+ totalSteps
209
+ });
210
+ react.useEffect(() => {
211
+ if (!projectId) {
212
+ setFetchError(new Error("projectId is required. Provide it via SiteKitProvider or useForm options."));
213
+ setIsLoading(false);
214
+ return;
215
+ }
216
+ async function fetchForm() {
217
+ try {
218
+ setIsLoading(true);
219
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
220
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
221
+ if (!apiKey) {
222
+ throw new Error("API key is required. Set NEXT_PUBLIC_UPTRADE_API_KEY in your .env");
223
+ }
224
+ const response = await fetch(`${apiUrl}/api/public/forms/config`, {
225
+ method: "POST",
226
+ headers: {
227
+ "Content-Type": "application/json",
228
+ "Authorization": `Bearer ${apiKey}`
229
+ },
230
+ body: JSON.stringify({
231
+ formIdOrSlug
232
+ })
233
+ });
234
+ if (!response.ok) {
235
+ throw new Error(`Failed to fetch form: ${response.statusText}`);
236
+ }
237
+ const data = await response.json();
238
+ if (!data) throw new Error("Form not found");
239
+ if (data.steps) {
240
+ data.steps.sort((a, b) => (a.step_number || 0) - (b.step_number || 0));
241
+ }
242
+ if (data.fields) {
243
+ data.fields.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0));
244
+ }
245
+ const total_steps = data.steps?.length || 1;
246
+ const is_multi_step = total_steps > 1;
247
+ setForm({
248
+ ...data,
249
+ total_steps,
250
+ is_multi_step
251
+ });
252
+ const defaults = {};
253
+ for (const field2 of data.fields || []) {
254
+ if (field2.default_value !== void 0 && field2.default_value !== null) {
255
+ defaults[field2.slug] = field2.default_value;
256
+ }
257
+ }
258
+ setValuesState({ ...defaults, ...initialValues });
259
+ } catch (err) {
260
+ setFetchError(err);
261
+ } finally {
262
+ setIsLoading(false);
263
+ }
264
+ }
265
+ fetchForm();
266
+ }, [formIdOrSlug, projectId]);
267
+ const allFields = react.useMemo(() => form?.fields || [], [form]);
268
+ const fields = react.useMemo(() => {
269
+ if (!isMultiStep) return allFields;
270
+ const currentStepConfig = form?.steps?.find((s) => s.step_number === step);
271
+ if (!currentStepConfig) return allFields;
272
+ return allFields.filter((f) => f.step_id === currentStepConfig.id);
273
+ }, [form, step, allFields, isMultiStep]);
274
+ const isFieldVisible = react.useCallback((field2) => {
275
+ if (!field2.conditional?.show_when) return true;
276
+ const { field: condField, operator, value } = field2.conditional.show_when;
277
+ const fieldValue = values[condField];
278
+ switch (operator) {
279
+ case "equals":
280
+ return fieldValue === value;
281
+ case "not_equals":
282
+ return fieldValue !== value;
283
+ case "contains":
284
+ return String(fieldValue || "").includes(String(value));
285
+ case "not_contains":
286
+ return !String(fieldValue || "").includes(String(value));
287
+ case "is_empty":
288
+ return !fieldValue || fieldValue === "";
289
+ case "not_empty":
290
+ return !!fieldValue && fieldValue !== "";
291
+ case "greater_than":
292
+ return Number(fieldValue) > Number(value);
293
+ case "less_than":
294
+ return Number(fieldValue) < Number(value);
295
+ default:
296
+ return true;
297
+ }
298
+ }, [values]);
299
+ const visibleFields = react.useMemo(() => {
300
+ return fields.filter(isFieldVisible);
301
+ }, [fields, isFieldVisible]);
302
+ const validateField = react.useCallback((field2) => {
303
+ if (!isFieldVisible(field2)) return null;
304
+ const value = values[field2.slug];
305
+ const rules = field2.validation || {};
306
+ if (field2.is_required && (!value || value === "")) {
307
+ return rules.custom_error || `${field2.label} is required`;
308
+ }
309
+ if (!value) return null;
310
+ const strValue = String(value);
311
+ if (rules.min_length && strValue.length < rules.min_length) {
312
+ return `${field2.label} must be at least ${rules.min_length} characters`;
313
+ }
314
+ if (rules.max_length && strValue.length > rules.max_length) {
315
+ return `${field2.label} must be no more than ${rules.max_length} characters`;
316
+ }
317
+ if (rules.min !== void 0 && Number(value) < rules.min) {
318
+ return `${field2.label} must be at least ${rules.min}`;
319
+ }
320
+ if (rules.max !== void 0 && Number(value) > rules.max) {
321
+ return `${field2.label} must be no more than ${rules.max}`;
322
+ }
323
+ if (rules.pattern && !new RegExp(rules.pattern).test(strValue)) {
324
+ return rules.custom_error || `${field2.label} is invalid`;
325
+ }
326
+ if (field2.field_type === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(strValue)) {
327
+ return "Please enter a valid email address";
328
+ }
329
+ if (field2.field_type === "phone" && !/^[\d\s\-\+\(\)]+$/.test(strValue)) {
330
+ return "Please enter a valid phone number";
331
+ }
332
+ return null;
333
+ }, [values, isFieldVisible]);
334
+ const validate = react.useCallback(() => {
335
+ const newErrors = {};
336
+ for (const field2 of fields) {
337
+ const error = validateField(field2);
338
+ if (error) {
339
+ newErrors[field2.slug] = error;
340
+ }
341
+ }
342
+ setErrors(newErrors);
343
+ return Object.keys(newErrors).length === 0;
344
+ }, [fields, validateField]);
345
+ const setFieldValue = react.useCallback((key, value) => {
346
+ setValuesState((prev) => ({ ...prev, [key]: value }));
347
+ setErrors((prev) => {
348
+ if (prev[key]) {
349
+ const next = { ...prev };
350
+ delete next[key];
351
+ return next;
352
+ }
353
+ return prev;
354
+ });
355
+ }, []);
356
+ const setValues = react.useCallback((newValues) => {
357
+ setValuesState((prev) => ({ ...prev, ...newValues }));
358
+ }, []);
359
+ const clearErrors = react.useCallback(() => {
360
+ setErrors({});
361
+ }, []);
362
+ const canGoNext = step < totalSteps;
363
+ const canGoPrev = step > 1;
364
+ const isLastStep = step === totalSteps;
365
+ const progress = Math.round(step / totalSteps * 100);
366
+ const nextStep = react.useCallback(() => {
367
+ if (!validate()) return false;
368
+ if (step < totalSteps) {
369
+ const newStep = step + 1;
370
+ setStep(newStep);
371
+ trackStepChange(newStep);
372
+ return true;
373
+ }
374
+ return false;
375
+ }, [step, totalSteps, validate, trackStepChange]);
376
+ const prevStep = react.useCallback(() => {
377
+ if (step > 1) {
378
+ const newStep = step - 1;
379
+ setStep(newStep);
380
+ trackStepChange(newStep);
381
+ }
382
+ }, [step, trackStepChange]);
383
+ const goToStep = react.useCallback((targetStep) => {
384
+ if (targetStep >= 1 && targetStep <= totalSteps) {
385
+ setStep(targetStep);
386
+ trackStepChange(targetStep);
387
+ }
388
+ }, [totalSteps, trackStepChange]);
389
+ const submit = react.useCallback(async () => {
390
+ if (!form) return;
391
+ if (!validate()) return;
392
+ setIsSubmitting(true);
393
+ try {
394
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
395
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
396
+ if (!apiKey) {
397
+ throw new Error("API key is required. Set NEXT_PUBLIC_UPTRADE_API_KEY in your .env");
398
+ }
399
+ const submission = {
400
+ formId: form.id,
401
+ data: values,
402
+ metadata: {
403
+ pageUrl: typeof window !== "undefined" ? window.location.href : null,
404
+ referrer: typeof document !== "undefined" ? document.referrer || null : null,
405
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : null,
406
+ sessionId: typeof sessionStorage !== "undefined" ? sessionStorage.getItem("_uptrade_sid") : null,
407
+ ...getUTMParams()
408
+ }
409
+ };
410
+ const response = await fetch(`${apiUrl}/api/public/forms/submit`, {
411
+ method: "POST",
412
+ headers: {
413
+ "Content-Type": "application/json",
414
+ "Authorization": `Bearer ${apiKey}`
415
+ },
416
+ body: JSON.stringify(submission)
417
+ });
418
+ if (!response.ok) {
419
+ throw new Error(`Form submission failed: ${response.statusText}`);
420
+ }
421
+ const data = await response.json();
422
+ trackComplete();
423
+ setIsComplete(true);
424
+ onSuccess?.(data);
425
+ const finalRedirect = redirectUrl !== false ? redirectUrl || form.redirect_url : void 0;
426
+ if (finalRedirect) {
427
+ window.location.href = finalRedirect;
428
+ }
429
+ } catch (error) {
430
+ console.error("[useForm] Submission error:", error);
431
+ onError?.(error);
432
+ } finally {
433
+ setIsSubmitting(false);
434
+ }
435
+ }, [form, values, validate, trackComplete, onSuccess, onError, redirectUrl]);
436
+ const reset = react.useCallback(() => {
437
+ setValuesState(initialValues);
438
+ setErrors({});
439
+ setStep(1);
440
+ setIsComplete(false);
441
+ }, [initialValues]);
442
+ return {
443
+ form,
444
+ isLoading,
445
+ fetchError,
446
+ allFields,
447
+ fields,
448
+ visibleFields,
449
+ values,
450
+ errors,
451
+ setFieldValue,
452
+ setValues,
453
+ clearErrors,
454
+ step,
455
+ totalSteps,
456
+ isMultiStep,
457
+ progress,
458
+ nextStep,
459
+ prevStep,
460
+ goToStep,
461
+ canGoNext,
462
+ canGoPrev,
463
+ isLastStep,
464
+ validate,
465
+ validateField,
466
+ isFieldVisible,
467
+ submit,
468
+ isSubmitting,
469
+ isComplete,
470
+ reset
471
+ };
472
+ }
473
+ function FormField({ field: field2, value, error, onChange, classPrefix = "uptrade-form" }) {
474
+ const baseInputStyle = {
475
+ width: "100%",
476
+ padding: "var(--uptrade-input-padding, 10px 12px)",
477
+ fontSize: "var(--uptrade-font-size, 16px)",
478
+ border: error ? "1px solid var(--uptrade-input-border-error, #ef4444)" : "1px solid var(--uptrade-input-border, #d1d5db)",
479
+ borderRadius: "var(--uptrade-input-radius, 6px)",
480
+ backgroundColor: "var(--uptrade-input-bg, #ffffff)",
481
+ outline: "none",
482
+ fontFamily: "inherit"
483
+ };
484
+ const labelStyle = {
485
+ display: "block",
486
+ marginBottom: 6,
487
+ fontWeight: "var(--uptrade-label-weight, 500)",
488
+ color: "var(--uptrade-label-color, inherit)"
489
+ };
490
+ const errorStyle = {
491
+ color: "var(--uptrade-error-color, #ef4444)",
492
+ fontSize: 14,
493
+ marginTop: 4
494
+ };
495
+ const helpStyle = {
496
+ color: "var(--uptrade-help-color, #6b7280)",
497
+ fontSize: 14,
498
+ marginTop: 4
499
+ };
500
+ if (field2.field_type === "heading") {
501
+ return /* @__PURE__ */ jsxRuntime.jsx("h3", { className: `${classPrefix}__heading`, style: { margin: "16px 0 8px" }, children: field2.label });
502
+ }
503
+ if (field2.field_type === "paragraph") {
504
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { className: `${classPrefix}__paragraph`, style: { color: "var(--uptrade-help-color, #6b7280)", margin: "8px 0" }, children: field2.help_text || field2.label });
505
+ }
506
+ if (field2.field_type === "hidden") {
507
+ return /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: field2.slug, value: String(value || "") });
508
+ }
509
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${classPrefix}__field ${classPrefix}__field--${field2.field_type}`, children: [
510
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: `${classPrefix}__label`, style: labelStyle, children: [
511
+ field2.label,
512
+ field2.is_required && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "var(--uptrade-error-color, #ef4444)" }, children: " *" })
513
+ ] }),
514
+ ["text", "email", "phone", "number"].includes(field2.field_type) && /* @__PURE__ */ jsxRuntime.jsx(
515
+ "input",
516
+ {
517
+ className: `${classPrefix}__input`,
518
+ type: field2.field_type === "phone" ? "tel" : field2.field_type,
519
+ name: field2.slug,
520
+ value: String(value || ""),
521
+ placeholder: field2.placeholder,
522
+ required: field2.is_required,
523
+ onChange: (e) => onChange(e.target.value),
524
+ style: baseInputStyle
525
+ }
526
+ ),
527
+ field2.field_type === "textarea" && /* @__PURE__ */ jsxRuntime.jsx(
528
+ "textarea",
529
+ {
530
+ className: `${classPrefix}__textarea`,
531
+ name: field2.slug,
532
+ value: String(value || ""),
533
+ placeholder: field2.placeholder,
534
+ required: field2.is_required,
535
+ rows: 4,
536
+ onChange: (e) => onChange(e.target.value),
537
+ style: { ...baseInputStyle, resize: "vertical" }
538
+ }
539
+ ),
540
+ field2.field_type === "select" && /* @__PURE__ */ jsxRuntime.jsxs(
541
+ "select",
542
+ {
543
+ className: `${classPrefix}__select`,
544
+ name: field2.slug,
545
+ value: String(value || ""),
546
+ required: field2.is_required,
547
+ onChange: (e) => onChange(e.target.value),
548
+ style: baseInputStyle,
549
+ children: [
550
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: field2.placeholder || "Select an option" }),
551
+ field2.options?.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, children: option.label }, option.value))
552
+ ]
553
+ }
554
+ ),
555
+ field2.field_type === "multi-select" && /* @__PURE__ */ jsxRuntime.jsx(
556
+ "select",
557
+ {
558
+ className: `${classPrefix}__select ${classPrefix}__select--multi`,
559
+ name: field2.slug,
560
+ value: Array.isArray(value) ? value : [],
561
+ required: field2.is_required,
562
+ multiple: true,
563
+ onChange: (e) => {
564
+ const selected = Array.from(e.target.selectedOptions, (opt) => opt.value);
565
+ onChange(selected);
566
+ },
567
+ style: { ...baseInputStyle, height: 120 },
568
+ children: field2.options?.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, children: option.label }, option.value))
569
+ }
570
+ ),
571
+ field2.field_type === "radio" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classPrefix}__radio-group`, style: { display: "flex", flexDirection: "column", gap: 8 }, children: field2.options?.map((option) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: `${classPrefix}__radio-option`, style: { display: "flex", alignItems: "center", gap: 8 }, children: [
572
+ /* @__PURE__ */ jsxRuntime.jsx(
573
+ "input",
574
+ {
575
+ type: "radio",
576
+ name: field2.slug,
577
+ value: option.value,
578
+ checked: value === option.value,
579
+ onChange: () => onChange(option.value)
580
+ }
581
+ ),
582
+ option.label
583
+ ] }, option.value)) }),
584
+ field2.field_type === "checkbox" && (!field2.options || field2.options.length === 0) && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: `${classPrefix}__checkbox`, style: { display: "flex", alignItems: "center", gap: 8 }, children: [
585
+ /* @__PURE__ */ jsxRuntime.jsx(
586
+ "input",
587
+ {
588
+ type: "checkbox",
589
+ name: field2.slug,
590
+ checked: !!value,
591
+ onChange: (e) => onChange(e.target.checked)
592
+ }
593
+ ),
594
+ field2.help_text || field2.label
595
+ ] }),
596
+ field2.field_type === "checkbox" && field2.options && field2.options.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classPrefix}__checkbox-group`, style: { display: "flex", flexDirection: "column", gap: 8 }, children: field2.options.map((option) => {
597
+ const selectedValues = Array.isArray(value) ? value : [];
598
+ const isChecked = selectedValues.includes(option.value);
599
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: `${classPrefix}__checkbox-option`, style: { display: "flex", alignItems: "center", gap: 8 }, children: [
600
+ /* @__PURE__ */ jsxRuntime.jsx(
601
+ "input",
602
+ {
603
+ type: "checkbox",
604
+ checked: isChecked,
605
+ onChange: () => {
606
+ if (isChecked) {
607
+ onChange(selectedValues.filter((v) => v !== option.value));
608
+ } else {
609
+ onChange([...selectedValues, option.value]);
610
+ }
611
+ }
612
+ }
613
+ ),
614
+ option.label
615
+ ] }, option.value);
616
+ }) }),
617
+ field2.field_type === "date" && /* @__PURE__ */ jsxRuntime.jsx(
618
+ "input",
619
+ {
620
+ className: `${classPrefix}__input ${classPrefix}__input--date`,
621
+ type: "date",
622
+ name: field2.slug,
623
+ value: String(value || ""),
624
+ required: field2.is_required,
625
+ onChange: (e) => onChange(e.target.value),
626
+ style: baseInputStyle
627
+ }
628
+ ),
629
+ field2.field_type === "time" && /* @__PURE__ */ jsxRuntime.jsx(
630
+ "input",
631
+ {
632
+ className: `${classPrefix}__input ${classPrefix}__input--time`,
633
+ type: "time",
634
+ name: field2.slug,
635
+ value: String(value || ""),
636
+ required: field2.is_required,
637
+ onChange: (e) => onChange(e.target.value),
638
+ style: baseInputStyle
639
+ }
640
+ ),
641
+ field2.field_type === "datetime" && /* @__PURE__ */ jsxRuntime.jsx(
642
+ "input",
643
+ {
644
+ className: `${classPrefix}__input ${classPrefix}__input--datetime`,
645
+ type: "datetime-local",
646
+ name: field2.slug,
647
+ value: String(value || ""),
648
+ required: field2.is_required,
649
+ onChange: (e) => onChange(e.target.value),
650
+ style: baseInputStyle
651
+ }
652
+ ),
653
+ field2.field_type === "file" && /* @__PURE__ */ jsxRuntime.jsx(
654
+ "input",
655
+ {
656
+ className: `${classPrefix}__input ${classPrefix}__input--file`,
657
+ type: "file",
658
+ name: field2.slug,
659
+ required: field2.is_required,
660
+ onChange: (e) => {
661
+ const file = e.target.files?.[0];
662
+ if (file) {
663
+ onChange(file.name);
664
+ }
665
+ },
666
+ style: baseInputStyle
667
+ }
668
+ ),
669
+ field2.field_type === "rating" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classPrefix}__rating`, style: { display: "flex", gap: 4 }, children: [1, 2, 3, 4, 5].map((star) => /* @__PURE__ */ jsxRuntime.jsx(
670
+ "button",
671
+ {
672
+ type: "button",
673
+ onClick: () => onChange(star),
674
+ className: `${classPrefix}__rating-star`,
675
+ style: {
676
+ background: "none",
677
+ border: "none",
678
+ fontSize: 24,
679
+ cursor: "pointer",
680
+ color: value >= star ? "var(--uptrade-rating-active, #fbbf24)" : "var(--uptrade-rating-inactive, #d1d5db)"
681
+ },
682
+ children: "\u2605"
683
+ },
684
+ star
685
+ )) }),
686
+ field2.field_type === "slider" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${classPrefix}__slider`, children: [
687
+ /* @__PURE__ */ jsxRuntime.jsx(
688
+ "input",
689
+ {
690
+ type: "range",
691
+ name: field2.slug,
692
+ value: Number(value || 50),
693
+ min: field2.validation?.min || 0,
694
+ max: field2.validation?.max || 100,
695
+ onChange: (e) => onChange(Number(e.target.value)),
696
+ style: { width: "100%" }
697
+ }
698
+ ),
699
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classPrefix}__slider-value`, style: { textAlign: "center", marginTop: 4 }, children: value || 50 })
700
+ ] }),
701
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: `${classPrefix}__error`, style: errorStyle, children: error }),
702
+ field2.help_text && !error && field2.field_type !== "checkbox" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: `${classPrefix}__help`, style: helpStyle, children: field2.help_text })
703
+ ] });
704
+ }
705
+ function FormClient({
706
+ config,
707
+ className,
708
+ onSuccess,
709
+ onError,
710
+ customRender
711
+ }) {
712
+ const [values, setValues] = react.useState({});
713
+ const [errors, setErrors] = react.useState({});
714
+ const [step, setStep] = react.useState(1);
715
+ const [isSubmitting, setIsSubmitting] = react.useState(false);
716
+ const [isComplete, setIsComplete] = react.useState(false);
717
+ const { trackStepChange, trackComplete } = useFormTracking({
718
+ formId: config.id,
719
+ totalSteps: config.total_steps
720
+ });
721
+ const currentFields = react.useMemo(() => {
722
+ if (!config.is_multi_step) {
723
+ return config.fields || [];
724
+ }
725
+ const currentStepConfig = config.steps?.find((s) => s.step_number === step);
726
+ if (!currentStepConfig) return config.fields || [];
727
+ return (config.fields || []).filter((f) => f.step_id === currentStepConfig.id);
728
+ }, [config, step]);
729
+ const isFieldVisible = react.useCallback((field2) => {
730
+ if (!field2.conditional?.show_when) return true;
731
+ const { field: condField, operator, value } = field2.conditional.show_when;
732
+ const fieldValue = values[condField];
733
+ switch (operator) {
734
+ case "equals":
735
+ return fieldValue === value;
736
+ case "not_equals":
737
+ return fieldValue !== value;
738
+ case "contains":
739
+ return String(fieldValue).includes(String(value));
740
+ case "not_contains":
741
+ return !String(fieldValue).includes(String(value));
742
+ case "is_empty":
743
+ return !fieldValue || fieldValue === "";
744
+ case "not_empty":
745
+ return !!fieldValue && fieldValue !== "";
746
+ case "greater_than":
747
+ return Number(fieldValue) > Number(value);
748
+ case "less_than":
749
+ return Number(fieldValue) < Number(value);
750
+ default:
751
+ return true;
752
+ }
753
+ }, [values]);
754
+ const validateField = react.useCallback((field2) => {
755
+ if (!isFieldVisible(field2)) return null;
756
+ const value = values[field2.slug];
757
+ const rules = field2.validation || {};
758
+ if (field2.is_required && (!value || value === "")) {
759
+ return rules.custom_error || `${field2.label} is required`;
760
+ }
761
+ if (!value) return null;
762
+ const strValue = String(value);
763
+ if (rules.min_length && strValue.length < rules.min_length) {
764
+ return `${field2.label} must be at least ${rules.min_length} characters`;
765
+ }
766
+ if (rules.max_length && strValue.length > rules.max_length) {
767
+ return `${field2.label} must be no more than ${rules.max_length} characters`;
768
+ }
769
+ if (rules.min !== void 0 && Number(value) < rules.min) {
770
+ return `${field2.label} must be at least ${rules.min}`;
771
+ }
772
+ if (rules.max !== void 0 && Number(value) > rules.max) {
773
+ return `${field2.label} must be no more than ${rules.max}`;
774
+ }
775
+ if (rules.pattern && !new RegExp(rules.pattern).test(strValue)) {
776
+ return rules.custom_error || `${field2.label} is invalid`;
777
+ }
778
+ if (field2.field_type === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(strValue)) {
779
+ return "Please enter a valid email address";
780
+ }
781
+ if (field2.field_type === "phone" && !/^[\d\s\-\+\(\)]+$/.test(strValue)) {
782
+ return "Please enter a valid phone number";
783
+ }
784
+ return null;
785
+ }, [values, isFieldVisible]);
786
+ const validateStep = react.useCallback(() => {
787
+ const newErrors = {};
788
+ for (const field2 of currentFields) {
789
+ const error = validateField(field2);
790
+ if (error) {
791
+ newErrors[field2.slug] = error;
792
+ }
793
+ }
794
+ setErrors(newErrors);
795
+ return Object.keys(newErrors).length === 0;
796
+ }, [currentFields, validateField]);
797
+ const setFieldValue = react.useCallback((key, value) => {
798
+ setValues((prev) => ({ ...prev, [key]: value }));
799
+ if (errors[key]) {
800
+ setErrors((prev) => {
801
+ const next = { ...prev };
802
+ delete next[key];
803
+ return next;
804
+ });
805
+ }
806
+ }, [errors]);
807
+ const nextStep = react.useCallback(() => {
808
+ if (!validateStep()) return false;
809
+ if (step < config.total_steps) {
810
+ const newStep = step + 1;
811
+ setStep(newStep);
812
+ trackStepChange(newStep);
813
+ return true;
814
+ }
815
+ return false;
816
+ }, [step, config.total_steps, validateStep, trackStepChange]);
817
+ const prevStep = react.useCallback(() => {
818
+ if (step > 1) {
819
+ const newStep = step - 1;
820
+ setStep(newStep);
821
+ trackStepChange(newStep);
822
+ }
823
+ }, [step, trackStepChange]);
824
+ const goToStep = react.useCallback((targetStep) => {
825
+ if (targetStep >= 1 && targetStep <= config.total_steps) {
826
+ setStep(targetStep);
827
+ trackStepChange(targetStep);
828
+ }
829
+ }, [config.total_steps, trackStepChange]);
830
+ const submit = react.useCallback(async () => {
831
+ if (!validateStep()) return;
832
+ setIsSubmitting(true);
833
+ try {
834
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
835
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
836
+ if (!apiKey) {
837
+ throw new Error("API key is required. Set NEXT_PUBLIC_UPTRADE_API_KEY in your .env");
838
+ }
839
+ const submission = {
840
+ form_id: config.id,
841
+ project_id: config.project_id,
842
+ data: values,
843
+ routing_type: config.form_type,
844
+ status: "new",
845
+ page_url: typeof window !== "undefined" ? window.location.href : null,
846
+ referrer: typeof document !== "undefined" ? document.referrer || null : null,
847
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : null,
848
+ session_id: typeof sessionStorage !== "undefined" ? sessionStorage.getItem("_uptrade_sid") : null,
849
+ ...getUTMParams2()
850
+ };
851
+ const response = await fetch(`${apiUrl}/api/public/forms/submit`, {
852
+ method: "POST",
853
+ headers: {
854
+ "Content-Type": "application/json",
855
+ "Authorization": `Bearer ${apiKey}`
856
+ },
857
+ body: JSON.stringify(submission)
858
+ });
859
+ if (!response.ok) {
860
+ throw new Error(`Failed to submit form: ${response.statusText}`);
861
+ }
862
+ const data = await response.json();
863
+ trackComplete();
864
+ setIsComplete(true);
865
+ onSuccess?.(data);
866
+ if (config.redirect_url) {
867
+ window.location.href = config.redirect_url;
868
+ }
869
+ } catch (error) {
870
+ console.error("[Forms] Submission error:", error);
871
+ onError?.(error);
872
+ } finally {
873
+ setIsSubmitting(false);
874
+ }
875
+ }, [config, values, validateStep, trackComplete, onSuccess, onError]);
876
+ const progress = react.useMemo(() => {
877
+ return Math.round(step / config.total_steps * 100);
878
+ }, [step, config.total_steps]);
879
+ const renderProps = {
880
+ config,
881
+ fields: currentFields,
882
+ step,
883
+ totalSteps: config.total_steps,
884
+ values,
885
+ errors,
886
+ isSubmitting,
887
+ progress,
888
+ nextStep,
889
+ prevStep,
890
+ goToStep,
891
+ submit,
892
+ setFieldValue,
893
+ isFieldVisible
894
+ };
895
+ if (isComplete) {
896
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: config.success_message }) });
897
+ }
898
+ if (customRender) {
899
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: customRender(renderProps) });
900
+ }
901
+ return /* @__PURE__ */ jsxRuntime.jsxs(
902
+ "form",
903
+ {
904
+ className: `uptrade-form ${className || ""}`,
905
+ onSubmit: (e) => {
906
+ e.preventDefault();
907
+ if (step < config.total_steps) {
908
+ nextStep();
909
+ } else {
910
+ submit();
911
+ }
912
+ },
913
+ children: [
914
+ config.is_multi_step && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "uptrade-form__progress", style: { marginBottom: 20 }, children: [
915
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "uptrade-form__progress-header", style: { display: "flex", justifyContent: "space-between", marginBottom: 8 }, children: [
916
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "uptrade-form__progress-step", children: [
917
+ "Step ",
918
+ step,
919
+ " of ",
920
+ config.total_steps
921
+ ] }),
922
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "uptrade-form__progress-percent", children: [
923
+ progress,
924
+ "%"
925
+ ] })
926
+ ] }),
927
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "uptrade-form__progress-track", style: {
928
+ height: 4,
929
+ backgroundColor: "var(--uptrade-progress-bg, #e5e7eb)",
930
+ borderRadius: 2,
931
+ overflow: "hidden"
932
+ }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "uptrade-form__progress-bar", style: {
933
+ width: `${progress}%`,
934
+ height: "100%",
935
+ backgroundColor: "var(--uptrade-progress-fill, #3b82f6)",
936
+ transition: "width 0.3s ease"
937
+ } }) })
938
+ ] }),
939
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "uptrade-form__fields", style: { display: "flex", flexWrap: "wrap", gap: 16 }, children: currentFields.map((field2) => {
940
+ if (!isFieldVisible(field2)) return null;
941
+ const widthMap = {
942
+ full: "100%",
943
+ half: "calc(50% - 8px)",
944
+ third: "calc(33.33% - 11px)",
945
+ quarter: "calc(25% - 12px)"
946
+ };
947
+ const widthStyle = widthMap[field2.width] || "100%";
948
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `uptrade-form__field-wrapper uptrade-form__field-wrapper--${field2.width}`, style: { width: widthStyle }, children: /* @__PURE__ */ jsxRuntime.jsx(
949
+ FormField,
950
+ {
951
+ field: field2,
952
+ value: values[field2.slug],
953
+ error: errors[field2.slug],
954
+ onChange: (value) => setFieldValue(field2.slug, value)
955
+ }
956
+ ) }, field2.id);
957
+ }) }),
958
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "uptrade-form__actions", style: {
959
+ display: "flex",
960
+ justifyContent: step > 1 ? "space-between" : "flex-end",
961
+ marginTop: 24
962
+ }, children: [
963
+ step > 1 && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "uptrade-form__btn uptrade-form__btn--back", onClick: prevStep, children: "Back" }),
964
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "uptrade-form__btn uptrade-form__btn--submit", disabled: isSubmitting, children: isSubmitting ? "Submitting..." : step < config.total_steps ? "Next" : config.submit_button_text })
965
+ ] }),
966
+ config.honeypot_enabled && /* @__PURE__ */ jsxRuntime.jsx(
967
+ "input",
968
+ {
969
+ type: "text",
970
+ name: "website",
971
+ tabIndex: -1,
972
+ autoComplete: "off",
973
+ style: {
974
+ position: "absolute",
975
+ left: -9999,
976
+ opacity: 0,
977
+ pointerEvents: "none"
978
+ },
979
+ onChange: (e) => {
980
+ if (e.target.value) {
981
+ console.warn("[Forms] Honeypot triggered");
982
+ }
983
+ }
984
+ }
985
+ )
986
+ ]
987
+ }
988
+ );
989
+ }
990
+ function getUTMParams2() {
991
+ if (typeof window === "undefined") return {};
992
+ const params = new URLSearchParams(window.location.search);
993
+ const utmParams = {};
994
+ for (const key of ["utm_source", "utm_medium", "utm_campaign"]) {
995
+ const value = params.get(key);
996
+ if (value) utmParams[key] = value;
997
+ }
998
+ return utmParams;
999
+ }
1000
+ function ManagedForm({
1001
+ formId,
1002
+ projectId,
1003
+ className,
1004
+ onSuccess,
1005
+ onError,
1006
+ children
1007
+ }) {
1008
+ const {
1009
+ form,
1010
+ isLoading,
1011
+ fetchError
1012
+ } = useForm(formId, {
1013
+ projectId,
1014
+ onSuccess,
1015
+ onError
1016
+ });
1017
+ if (isLoading) {
1018
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "#6b7280" }, children: "Loading form..." }) });
1019
+ }
1020
+ if (fetchError || !form) {
1021
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "#ef4444" }, children: fetchError?.message || "Form not found or inactive." }) });
1022
+ }
1023
+ return /* @__PURE__ */ jsxRuntime.jsx(
1024
+ FormClient,
1025
+ {
1026
+ config: form,
1027
+ className,
1028
+ onSuccess,
1029
+ onError,
1030
+ customRender: children
1031
+ }
1032
+ );
1033
+ }
1034
+
1035
+ Object.defineProperty(exports, "configureFormsApi", {
1036
+ enumerable: true,
1037
+ get: function () { return chunkRV7H3I6J_js.configureFormsApi; }
1038
+ });
1039
+ Object.defineProperty(exports, "field", {
1040
+ enumerable: true,
1041
+ get: function () { return chunkRV7H3I6J_js.field; }
1042
+ });
1043
+ Object.defineProperty(exports, "formsApi", {
1044
+ enumerable: true,
1045
+ get: function () { return chunkRV7H3I6J_js.formsApi; }
1046
+ });
1047
+ exports.FormClient = FormClient;
1048
+ exports.FormFieldComponent = FormField;
1049
+ exports.ManagedForm = ManagedForm;
1050
+ exports.useForm = useForm;
1051
+ exports.useFormTracking = useFormTracking;
1052
+ //# sourceMappingURL=index.js.map
1053
+ //# sourceMappingURL=index.js.map