@sonordev/site-kit 1.2.7

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