@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,1008 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var navigation = require('next/navigation');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+
8
+ // src/analytics/WebVitals.tsx
9
+ function getApiConfig() {
10
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
11
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
12
+ return { apiUrl, apiKey };
13
+ }
14
+ function WebVitals({ apiUrl: propApiUrl, apiKey: propApiKey, debug = false }) {
15
+ const pathname = navigation.usePathname();
16
+ react.useEffect(() => {
17
+ import('./web-vitals-444RLW3B.js').then(({ onCLS, onLCP, onTTFB, onINP, onFCP }) => {
18
+ const vitals = {};
19
+ let reported = false;
20
+ const reportVitals = async () => {
21
+ if (reported) return;
22
+ if (Object.keys(vitals).length === 0) return;
23
+ reported = true;
24
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig();
25
+ const apiUrl = propApiUrl || globalApiUrl;
26
+ const apiKey = propApiKey || globalApiKey;
27
+ if (!apiKey) {
28
+ if (debug) console.warn("[Analytics] No API key configured for Web Vitals");
29
+ return;
30
+ }
31
+ for (const [name, value] of Object.entries(vitals)) {
32
+ const data = {
33
+ pagePath: pathname,
34
+ metricName: name,
35
+ metricValue: value,
36
+ metricRating: getRating(name, value)
37
+ };
38
+ if (debug) {
39
+ console.log("[Analytics] Web Vital:", data);
40
+ }
41
+ try {
42
+ await fetch(`${apiUrl}/api/public/analytics/web-vitals`, {
43
+ method: "POST",
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ "x-api-key": apiKey
47
+ },
48
+ body: JSON.stringify(data)
49
+ });
50
+ } catch (error) {
51
+ if (debug) console.error("[Analytics] Error reporting Web Vital:", error);
52
+ }
53
+ }
54
+ };
55
+ onLCP((metric) => {
56
+ vitals.LCP = metric.value;
57
+ if (debug) console.log("[Analytics] LCP:", metric.value);
58
+ });
59
+ onCLS((metric) => {
60
+ vitals.CLS = metric.value;
61
+ if (debug) console.log("[Analytics] CLS:", metric.value);
62
+ });
63
+ onTTFB((metric) => {
64
+ vitals.TTFB = metric.value;
65
+ if (debug) console.log("[Analytics] TTFB:", metric.value);
66
+ });
67
+ onINP((metric) => {
68
+ vitals.INP = metric.value;
69
+ if (debug) console.log("[Analytics] INP:", metric.value);
70
+ });
71
+ onFCP((metric) => {
72
+ vitals.FCP = metric.value;
73
+ if (debug) console.log("[Analytics] FCP:", metric.value);
74
+ });
75
+ const handleVisibilityChange = () => {
76
+ if (document.visibilityState === "hidden") {
77
+ reportVitals();
78
+ }
79
+ };
80
+ document.addEventListener("visibilitychange", handleVisibilityChange);
81
+ const timeout = setTimeout(reportVitals, 1e4);
82
+ return () => {
83
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
84
+ clearTimeout(timeout);
85
+ };
86
+ }).catch((err) => {
87
+ if (debug) console.warn("[Analytics] Web Vitals import failed:", err);
88
+ });
89
+ }, [pathname, propApiUrl, propApiKey, debug]);
90
+ return null;
91
+ }
92
+ function getRating(name, value) {
93
+ const thresholds = {
94
+ LCP: [2500, 4e3],
95
+ CLS: [0.1, 0.25],
96
+ TTFB: [800, 1800],
97
+ INP: [200, 500],
98
+ FCP: [1800, 3e3]
99
+ };
100
+ const [good, poor] = thresholds[name] || [0, 0];
101
+ if (value <= good) return "good";
102
+ if (value <= poor) return "needs-improvement";
103
+ return "poor";
104
+ }
105
+ var AnalyticsContext = react.createContext(null);
106
+ function generateId() {
107
+ return crypto.randomUUID();
108
+ }
109
+ function scheduleIdleTask(callback, timeout = 2e3) {
110
+ if (typeof window !== "undefined" && "requestIdleCallback" in window) {
111
+ window.requestIdleCallback(
112
+ callback,
113
+ { timeout }
114
+ );
115
+ } else {
116
+ setTimeout(callback, 0);
117
+ }
118
+ }
119
+ function getOrCreateVisitorId() {
120
+ if (typeof window === "undefined") return "";
121
+ const key = "_uptrade_vid";
122
+ let visitorId = localStorage.getItem(key);
123
+ if (!visitorId) {
124
+ visitorId = generateId();
125
+ localStorage.setItem(key, visitorId);
126
+ }
127
+ return visitorId;
128
+ }
129
+ function getSessionId(timeout) {
130
+ if (typeof window === "undefined") return "";
131
+ const key = "_uptrade_sid";
132
+ const timeKey = "_uptrade_stime";
133
+ const now = Date.now();
134
+ const timeoutMs = timeout * 60 * 1e3;
135
+ const existingSession = sessionStorage.getItem(key);
136
+ const lastActivity = sessionStorage.getItem(timeKey);
137
+ if (existingSession && lastActivity) {
138
+ const elapsed = now - parseInt(lastActivity, 10);
139
+ if (elapsed < timeoutMs) {
140
+ sessionStorage.setItem(timeKey, now.toString());
141
+ return existingSession;
142
+ }
143
+ }
144
+ const newSession = generateId();
145
+ sessionStorage.setItem(key, newSession);
146
+ sessionStorage.setItem(timeKey, now.toString());
147
+ return newSession;
148
+ }
149
+ function getDeviceType() {
150
+ if (typeof window === "undefined") return "desktop";
151
+ const ua = navigator.userAgent;
152
+ if (/tablet|ipad|playbook|silk/i.test(ua)) return "tablet";
153
+ if (/mobile|iphone|ipod|android|blackberry|opera mini|iemobile/i.test(ua)) return "mobile";
154
+ return "desktop";
155
+ }
156
+ function getBrowser() {
157
+ if (typeof window === "undefined") return "unknown";
158
+ const ua = navigator.userAgent;
159
+ if (ua.includes("Firefox")) return "Firefox";
160
+ if (ua.includes("Edg")) return "Edge";
161
+ if (ua.includes("Chrome")) return "Chrome";
162
+ if (ua.includes("Safari")) return "Safari";
163
+ if (ua.includes("Opera") || ua.includes("OPR")) return "Opera";
164
+ return "Other";
165
+ }
166
+ function getOS() {
167
+ if (typeof window === "undefined") return "unknown";
168
+ const ua = navigator.userAgent;
169
+ if (ua.includes("Windows")) return "Windows";
170
+ if (ua.includes("Mac OS X") || ua.includes("Macintosh")) return "macOS";
171
+ if (ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
172
+ if (ua.includes("Android")) return "Android";
173
+ if (ua.includes("Linux")) return "Linux";
174
+ if (ua.includes("CrOS")) return "ChromeOS";
175
+ return "Other";
176
+ }
177
+ function getUserAgent() {
178
+ if (typeof window === "undefined") return "";
179
+ return navigator.userAgent;
180
+ }
181
+ function getUTMParams() {
182
+ if (typeof window === "undefined") return {};
183
+ const params = new URLSearchParams(window.location.search);
184
+ const utmParams = {};
185
+ for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"]) {
186
+ const value = params.get(key);
187
+ if (value) utmParams[key] = value;
188
+ }
189
+ return utmParams;
190
+ }
191
+ function getPageMetadata() {
192
+ if (typeof document === "undefined") return {};
193
+ const getMeta = (name) => {
194
+ const el = document.querySelector(`meta[name="${name}"], meta[property="${name}"]`);
195
+ return el?.getAttribute("content") || null;
196
+ };
197
+ const getCanonical = () => {
198
+ const el = document.querySelector('link[rel="canonical"]');
199
+ return el?.getAttribute("href") || null;
200
+ };
201
+ const getRobots = () => {
202
+ return getMeta("robots");
203
+ };
204
+ const getH1 = () => {
205
+ const h1 = document.querySelector("h1");
206
+ return h1?.textContent?.trim() || null;
207
+ };
208
+ const getH1Count = () => {
209
+ return document.querySelectorAll("h1").length;
210
+ };
211
+ const getWordCount = () => {
212
+ const main = document.querySelector('main, article, [role="main"]') || document.body;
213
+ const text = main.textContent || "";
214
+ return text.split(/\s+/).filter((w) => w.length > 0).length;
215
+ };
216
+ const getElementPosition = (el) => {
217
+ let current = el;
218
+ while (current) {
219
+ const tag = current.tagName?.toLowerCase();
220
+ const role = current.getAttribute("role");
221
+ const className = current.className?.toString?.() || "";
222
+ if (className.includes("hero") || current.id?.includes("hero")) return "hero";
223
+ if (tag === "header" || role === "banner") return "header";
224
+ if (tag === "nav" || role === "navigation") return "header";
225
+ if (tag === "footer" || role === "contentinfo") return "footer";
226
+ if (tag === "aside" || role === "complementary") return "sidebar";
227
+ if (tag === "main" || tag === "article" || role === "main") return "content";
228
+ current = current.parentElement;
229
+ }
230
+ return "unknown";
231
+ };
232
+ const getSurroundingText = (el) => {
233
+ const container = el.closest("figure, p, div, section, article");
234
+ if (!container) return "";
235
+ const text = container.textContent?.replace(/\s+/g, " ").trim() || "";
236
+ return text.slice(0, 300);
237
+ };
238
+ const getImageDetails = () => {
239
+ const images = document.querySelectorAll("img, [data-managed-image]");
240
+ const seen = /* @__PURE__ */ new Set();
241
+ const results = [];
242
+ images.forEach((el) => {
243
+ const img = el;
244
+ const src = img.getAttribute("src") || img.getAttribute("data-src") || "";
245
+ if (!src || src.startsWith("data:") || seen.has(src)) return;
246
+ seen.add(src);
247
+ const slotId = img.getAttribute("data-slot-id") || void 0;
248
+ const isManagedImage = img.hasAttribute("data-managed-image") || !!slotId;
249
+ results.push({
250
+ src,
251
+ alt: img.alt || null,
252
+ elementType: isManagedImage ? "ManagedImage" : "img",
253
+ slotId,
254
+ position: getElementPosition(img),
255
+ width: img.naturalWidth || parseInt(img.getAttribute("width") || "0") || void 0,
256
+ height: img.naturalHeight || parseInt(img.getAttribute("height") || "0") || void 0,
257
+ surroundingText: getSurroundingText(img)
258
+ });
259
+ });
260
+ return results;
261
+ };
262
+ const getImageStats = () => {
263
+ const images = getImageDetails();
264
+ let withoutAlt = 0;
265
+ images.forEach((img) => {
266
+ if (!img.alt || img.alt.trim() === "") withoutAlt++;
267
+ });
268
+ return { count: images.length, withoutAlt, images };
269
+ };
270
+ const getLinkStats = () => {
271
+ const links = document.querySelectorAll("a[href]");
272
+ const currentHost = window.location.host;
273
+ let internal = 0;
274
+ let external = 0;
275
+ const internalLinks = [];
276
+ const getLinkPosition = (el) => {
277
+ let current = el;
278
+ while (current) {
279
+ const tag = current.tagName?.toLowerCase();
280
+ const role = current.getAttribute("role");
281
+ if (tag === "header" || role === "banner") return "header";
282
+ if (tag === "nav" || role === "navigation") return "nav";
283
+ if (tag === "footer" || role === "contentinfo") return "footer";
284
+ if (tag === "aside" || role === "complementary") return "sidebar";
285
+ if (tag === "main" || tag === "article" || role === "main") return "content";
286
+ current = current.parentElement;
287
+ }
288
+ return "unknown";
289
+ };
290
+ links.forEach((link) => {
291
+ const href = link.getAttribute("href") || "";
292
+ const rel = link.getAttribute("rel") || "";
293
+ const isNofollow = rel.includes("nofollow");
294
+ let isInternal = false;
295
+ let targetPath = "";
296
+ if (href.startsWith("/") && !href.startsWith("//")) {
297
+ isInternal = true;
298
+ targetPath = href.split("?")[0].split("#")[0];
299
+ } else if (href.startsWith("#")) {
300
+ internal++;
301
+ return;
302
+ } else if (href.startsWith("http")) {
303
+ try {
304
+ const url = new URL(href);
305
+ if (url.host === currentHost) {
306
+ isInternal = true;
307
+ targetPath = url.pathname;
308
+ } else {
309
+ external++;
310
+ }
311
+ } catch {
312
+ }
313
+ }
314
+ if (isInternal && targetPath) {
315
+ internal++;
316
+ if (!targetPath.startsWith("/")) targetPath = "/" + targetPath;
317
+ if (targetPath !== "/" && targetPath.endsWith("/")) {
318
+ targetPath = targetPath.slice(0, -1);
319
+ }
320
+ internalLinks.push({
321
+ targetPath,
322
+ anchorText: (link.textContent || "").trim().slice(0, 200),
323
+ position: getLinkPosition(link),
324
+ isNofollow
325
+ });
326
+ }
327
+ });
328
+ return { internal, external, internalLinks };
329
+ };
330
+ const getHeadingStructure = () => {
331
+ const headings = [];
332
+ const elements = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
333
+ elements.forEach((el) => {
334
+ const level = parseInt(el.tagName[1]);
335
+ const text = el.textContent?.trim() || "";
336
+ if (text) {
337
+ headings.push({
338
+ level,
339
+ text: text.slice(0, 200),
340
+ // Limit length
341
+ id: el.id || void 0
342
+ });
343
+ }
344
+ });
345
+ return headings;
346
+ };
347
+ const getContentText = () => {
348
+ const main = document.querySelector('main, article, [role="main"]') || document.body;
349
+ const clone = main.cloneNode(true);
350
+ clone.querySelectorAll('script, style, nav, header, footer, [role="navigation"], [role="banner"], [role="contentinfo"]').forEach((el) => el.remove());
351
+ let text = clone.textContent || "";
352
+ text = text.replace(/\s+/g, " ").trim();
353
+ text = text.slice(0, 1e4);
354
+ let hash = 0;
355
+ for (let i = 0; i < text.length; i++) {
356
+ const chr = text.charCodeAt(i);
357
+ hash = (hash << 5) - hash + chr;
358
+ hash |= 0;
359
+ }
360
+ return { text, hash: hash.toString(16) };
361
+ };
362
+ const getContentSections = () => {
363
+ const sections = [];
364
+ const main = document.querySelector('main, article, [role="main"]') || document.body;
365
+ const headings = main.querySelectorAll("h1, h2, h3");
366
+ headings.forEach((heading, idx) => {
367
+ const headingText = heading.textContent?.trim() || "";
368
+ const level = parseInt(heading.tagName[1]);
369
+ let content = "";
370
+ let sibling = heading.nextElementSibling;
371
+ while (sibling && !["H1", "H2", "H3"].includes(sibling.tagName)) {
372
+ if (sibling.tagName === "P" || sibling.tagName === "UL" || sibling.tagName === "OL" || sibling.tagName === "DIV") {
373
+ content += (sibling.textContent || "") + " ";
374
+ }
375
+ sibling = sibling.nextElementSibling;
376
+ }
377
+ content = content.replace(/\s+/g, " ").trim().slice(0, 1e3);
378
+ const wordCount2 = content.split(/\s+/).filter((w) => w.length > 0).length;
379
+ if (content && wordCount2 > 10) {
380
+ sections.push({
381
+ heading: headingText.slice(0, 200),
382
+ headingLevel: level,
383
+ text: content,
384
+ wordCount: wordCount2
385
+ });
386
+ }
387
+ });
388
+ return sections.slice(0, 20);
389
+ };
390
+ const detectFAQContent = () => {
391
+ const faqs = [];
392
+ const scripts = document.querySelectorAll('script[type="application/ld+json"]');
393
+ scripts.forEach((script) => {
394
+ try {
395
+ const data = JSON.parse(script.textContent || "");
396
+ if (data["@type"] === "FAQPage" && data.mainEntity) {
397
+ data.mainEntity.forEach((item) => {
398
+ if (item["@type"] === "Question") {
399
+ faqs.push({
400
+ question: item.name?.slice(0, 200) || "",
401
+ answer: (item.acceptedAnswer?.text || "").slice(0, 500)
402
+ });
403
+ }
404
+ });
405
+ }
406
+ } catch {
407
+ }
408
+ });
409
+ if (faqs.length === 0) {
410
+ const details = document.querySelectorAll("details");
411
+ details.forEach((detail) => {
412
+ const summary = detail.querySelector("summary");
413
+ if (summary) {
414
+ const question = summary.textContent?.trim() || "";
415
+ const answer = detail.textContent?.replace(question, "").trim().slice(0, 500) || "";
416
+ if (question && answer) {
417
+ faqs.push({ question: question.slice(0, 200), answer });
418
+ }
419
+ }
420
+ });
421
+ const accordionItems = document.querySelectorAll('[class*="accordion"], [class*="faq"], [data-faq]');
422
+ accordionItems.forEach((item) => {
423
+ const questionEl = item.querySelector('[class*="question"], [class*="title"], button, h3, h4');
424
+ const answerEl = item.querySelector('[class*="answer"], [class*="content"], [class*="panel"], p');
425
+ if (questionEl && answerEl) {
426
+ const question = questionEl.textContent?.trim() || "";
427
+ const answer = answerEl.textContent?.trim().slice(0, 500) || "";
428
+ if (question && answer && question.includes("?")) {
429
+ faqs.push({ question: question.slice(0, 200), answer });
430
+ }
431
+ }
432
+ });
433
+ }
434
+ return faqs.slice(0, 20);
435
+ };
436
+ const detectLists = () => {
437
+ const lists = [];
438
+ const main = document.querySelector('main, article, [role="main"]') || document.body;
439
+ main.querySelectorAll("ul, ol").forEach((list) => {
440
+ const type = list.tagName.toLowerCase();
441
+ const items = [];
442
+ list.querySelectorAll(":scope > li").forEach((li) => {
443
+ const text = li.textContent?.trim().slice(0, 200) || "";
444
+ if (text) items.push(text);
445
+ });
446
+ if (items.length >= 3) {
447
+ lists.push({ type, items: items.slice(0, 10) });
448
+ }
449
+ });
450
+ return lists.slice(0, 10);
451
+ };
452
+ const estimateReadingTime = (wordCount2) => {
453
+ return Math.ceil(wordCount2 / 225);
454
+ };
455
+ const imageStats = getImageStats();
456
+ const linkStats = getLinkStats();
457
+ const headingStructure = getHeadingStructure();
458
+ const contentData = getContentText();
459
+ const contentSections = getContentSections();
460
+ const faqContent = detectFAQContent();
461
+ const listContent = detectLists();
462
+ const wordCount = getWordCount();
463
+ return {
464
+ metaDescription: getMeta("description"),
465
+ canonical: getCanonical(),
466
+ robots: getRobots(),
467
+ ogTitle: getMeta("og:title"),
468
+ ogDescription: getMeta("og:description"),
469
+ ogImage: getMeta("og:image"),
470
+ h1: getH1(),
471
+ h1Count: getH1Count(),
472
+ wordCount,
473
+ imagesCount: imageStats.count,
474
+ imagesWithoutAlt: imageStats.withoutAlt,
475
+ images: imageStats.images,
476
+ // Full image details for SEO optimization
477
+ internalLinks: linkStats.internal,
478
+ internalLinkTargets: linkStats.internalLinks,
479
+ // Full link graph data
480
+ externalLinks: linkStats.external,
481
+ // NEW: Content analysis data
482
+ content: {
483
+ text: contentData.text,
484
+ hash: contentData.hash,
485
+ headings: headingStructure,
486
+ sections: contentSections,
487
+ faqs: faqContent.length > 0 ? faqContent : void 0,
488
+ lists: listContent.length > 0 ? listContent : void 0,
489
+ readingTime: estimateReadingTime(wordCount)
490
+ }
491
+ };
492
+ }
493
+ function getApiConfig2() {
494
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
495
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
496
+ return { apiUrl, apiKey };
497
+ }
498
+ function AnalyticsProvider({
499
+ children,
500
+ apiUrl: propApiUrl,
501
+ apiKey: propApiKey,
502
+ trackPageViews = true,
503
+ trackWebVitals = true,
504
+ trackScrollDepth = true,
505
+ trackClicks = true,
506
+ trackJourneys = true,
507
+ // NEW: Enable journey tracking by default
508
+ sessionTimeout = 30,
509
+ excludePaths = [],
510
+ validateAgainstSitemap = true,
511
+ debug = false,
512
+ externalVisitorId,
513
+ externalSessionId,
514
+ onPageMetadata
515
+ }) {
516
+ const pathname = navigation.usePathname();
517
+ const [queryString, setQueryString] = react.useState("");
518
+ react.useEffect(() => {
519
+ if (typeof window === "undefined") return;
520
+ setQueryString(window.location.search);
521
+ const handlePopState = () => {
522
+ setQueryString(window.location.search);
523
+ };
524
+ window.addEventListener("popstate", handlePopState);
525
+ return () => window.removeEventListener("popstate", handlePopState);
526
+ }, [pathname]);
527
+ const visitorIdRef = react.useRef("");
528
+ const sessionIdRef = react.useRef("");
529
+ const lastPathRef = react.useRef("");
530
+ const validPathsRef = react.useRef(null);
531
+ const journeyStartTimeRef = react.useRef(0);
532
+ const pageEnterTimeRef = react.useRef(0);
533
+ const currentScrollDepthRef = react.useRef(0);
534
+ react.useEffect(() => {
535
+ visitorIdRef.current = externalVisitorId || getOrCreateVisitorId();
536
+ sessionIdRef.current = externalSessionId || getSessionId(sessionTimeout);
537
+ }, [sessionTimeout, externalVisitorId, externalSessionId]);
538
+ react.useEffect(() => {
539
+ if (!validateAgainstSitemap) {
540
+ if (debug) console.log("[Analytics] Page validation disabled");
541
+ return;
542
+ }
543
+ const fetchValidPages = async () => {
544
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
545
+ const apiUrl = propApiUrl || globalApiUrl;
546
+ const apiKey = propApiKey || globalApiKey;
547
+ if (!apiKey) {
548
+ if (debug) console.warn("[Analytics] No API key for page validation");
549
+ return;
550
+ }
551
+ try {
552
+ const response = await fetch(`${apiUrl}/api/public/seo/pages`, {
553
+ method: "GET",
554
+ headers: {
555
+ "Content-Type": "application/json",
556
+ "x-api-key": apiKey
557
+ }
558
+ });
559
+ if (response.ok) {
560
+ const data = await response.json();
561
+ const pages = data?.pages || [];
562
+ validPathsRef.current = new Set(
563
+ pages.map((p) => p.path).filter(Boolean)
564
+ );
565
+ if (debug) {
566
+ console.log("[Analytics] Loaded", validPathsRef.current.size, "valid pages from seo_pages");
567
+ }
568
+ } else if (debug) {
569
+ console.error("[Analytics] Pages fetch failed:", response.statusText);
570
+ }
571
+ } catch (error) {
572
+ if (debug) console.error("[Analytics] Error fetching valid pages:", error);
573
+ }
574
+ };
575
+ fetchValidPages();
576
+ }, [propApiUrl, propApiKey, validateAgainstSitemap, debug]);
577
+ react.useEffect(() => {
578
+ if (!trackPageViews) return;
579
+ if (!pathname) return;
580
+ if (excludePaths.some((p) => pathname.startsWith(p))) return;
581
+ if (pathname === lastPathRef.current) return;
582
+ if (validateAgainstSitemap) {
583
+ if (validPathsRef.current && validPathsRef.current.size > 0) {
584
+ if (!validPathsRef.current.has(pathname)) {
585
+ if (debug) {
586
+ console.log("[Analytics] Skipping unregistered path:", pathname);
587
+ }
588
+ return;
589
+ }
590
+ } else if (debug) {
591
+ console.log("[Analytics] Sitemap not yet loaded, tracking anyway:", pathname);
592
+ }
593
+ }
594
+ lastPathRef.current = pathname;
595
+ const trackPageView = async () => {
596
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
597
+ const apiUrl = propApiUrl || globalApiUrl;
598
+ const apiKey = propApiKey || globalApiKey;
599
+ if (!apiKey) {
600
+ if (debug) console.warn("[Analytics] No API key configured");
601
+ return;
602
+ }
603
+ const utmParams = getUTMParams();
604
+ const pageMetadata = getPageMetadata();
605
+ if (onPageMetadata) {
606
+ onPageMetadata(pageMetadata);
607
+ }
608
+ const pageView = {
609
+ sessionId: sessionIdRef.current,
610
+ visitorId: visitorIdRef.current,
611
+ pagePath: pathname,
612
+ pageTitle: document.title,
613
+ referrer: document.referrer || null,
614
+ deviceType: getDeviceType(),
615
+ browser: getBrowser(),
616
+ os: getOS(),
617
+ userAgent: getUserAgent(),
618
+ utmSource: utmParams.utm_source,
619
+ utmMedium: utmParams.utm_medium,
620
+ utmCampaign: utmParams.utm_campaign,
621
+ utmTerm: utmParams.utm_term,
622
+ utmContent: utmParams.utm_content,
623
+ // SEO enrichment data - updates seo_pages
624
+ seo: pageMetadata
625
+ };
626
+ if (debug) {
627
+ console.log("[Analytics] Page view:", pageView);
628
+ }
629
+ try {
630
+ const response = await fetch(`${apiUrl}/api/public/analytics/page-view`, {
631
+ method: "POST",
632
+ headers: {
633
+ "Content-Type": "application/json",
634
+ "x-api-key": apiKey
635
+ },
636
+ body: JSON.stringify(pageView)
637
+ });
638
+ if (!response.ok && debug) {
639
+ console.error("[Analytics] Error tracking page view:", response.statusText);
640
+ }
641
+ } catch (error) {
642
+ if (debug) console.error("[Analytics] Error tracking page view:", error);
643
+ }
644
+ };
645
+ scheduleIdleTask(() => trackPageView());
646
+ }, [pathname, queryString, propApiUrl, propApiKey, trackPageViews, excludePaths, debug, validateAgainstSitemap]);
647
+ react.useEffect(() => {
648
+ if (!trackJourneys) return;
649
+ if (!pathname) return;
650
+ if (typeof window === "undefined") return;
651
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
652
+ const apiUrl = propApiUrl || globalApiUrl;
653
+ const apiKey = propApiKey || globalApiKey;
654
+ if (!apiKey) return;
655
+ const now = Date.now();
656
+ const previousPath = lastPathRef.current;
657
+ const previousDuration = pageEnterTimeRef.current > 0 ? Math.round((now - pageEnterTimeRef.current) / 1e3) : 0;
658
+ const previousScrollDepth = currentScrollDepthRef.current;
659
+ pageEnterTimeRef.current = now;
660
+ currentScrollDepthRef.current = 0;
661
+ const isNewSession = !previousPath || journeyStartTimeRef.current === 0;
662
+ if (isNewSession) {
663
+ journeyStartTimeRef.current = now;
664
+ }
665
+ const trackJourneyStep = async () => {
666
+ const sessionData = {
667
+ sessionId: sessionIdRef.current,
668
+ visitorId: visitorIdRef.current,
669
+ action: isNewSession ? "start" : "update",
670
+ lastPage: pathname,
671
+ userAgent: getUserAgent(),
672
+ // Journey step data
673
+ journeyStep: {
674
+ page: pathname,
675
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
676
+ }
677
+ };
678
+ if (!isNewSession && previousDuration > 0) {
679
+ sessionData.previousPageDuration = previousDuration;
680
+ sessionData.previousPageScrollDepth = previousScrollDepth;
681
+ }
682
+ if (isNewSession) {
683
+ sessionData.firstPage = pathname;
684
+ const utmParams = getUTMParams();
685
+ sessionData.referrer = document.referrer || null;
686
+ sessionData.utmSource = utmParams.utm_source;
687
+ sessionData.utmMedium = utmParams.utm_medium;
688
+ sessionData.utmCampaign = utmParams.utm_campaign;
689
+ sessionData.utmTerm = utmParams.utm_term;
690
+ sessionData.utmContent = utmParams.utm_content;
691
+ sessionData.screenWidth = window.screen.width;
692
+ sessionData.screenHeight = window.screen.height;
693
+ }
694
+ if (debug) {
695
+ console.log("[Analytics] Journey step:", sessionData);
696
+ }
697
+ try {
698
+ await fetch(`${apiUrl}/api/public/analytics/session`, {
699
+ method: "POST",
700
+ headers: {
701
+ "Content-Type": "application/json",
702
+ "x-api-key": apiKey
703
+ },
704
+ body: JSON.stringify(sessionData)
705
+ });
706
+ } catch (error) {
707
+ if (debug) console.error("[Analytics] Error tracking journey:", error);
708
+ }
709
+ };
710
+ scheduleIdleTask(() => trackJourneyStep());
711
+ const handleUnload = () => {
712
+ const duration = Math.round((Date.now() - journeyStartTimeRef.current) / 1e3);
713
+ const payload = JSON.stringify({
714
+ sessionId: sessionIdRef.current,
715
+ action: "end",
716
+ duration,
717
+ lastPage: pathname,
718
+ previousPageDuration: Math.round((Date.now() - pageEnterTimeRef.current) / 1e3),
719
+ previousPageScrollDepth: currentScrollDepthRef.current
720
+ });
721
+ if (navigator.sendBeacon) {
722
+ const blob = new Blob([payload], { type: "application/json" });
723
+ navigator.sendBeacon(
724
+ `${apiUrl}/api/public/analytics/session?key=${encodeURIComponent(apiKey)}`,
725
+ blob
726
+ );
727
+ }
728
+ };
729
+ window.addEventListener("beforeunload", handleUnload);
730
+ return () => {
731
+ window.removeEventListener("beforeunload", handleUnload);
732
+ };
733
+ }, [pathname, propApiUrl, propApiKey, trackJourneys, debug]);
734
+ react.useEffect(() => {
735
+ if (typeof window === "undefined") return;
736
+ if (debug) console.log("[Analytics] Scroll tracking setup:", { trackScrollDepth, hasApiKey: !!propApiKey });
737
+ if (!trackScrollDepth) return;
738
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
739
+ const apiUrl = propApiUrl || globalApiUrl;
740
+ const apiKey = propApiKey || globalApiKey;
741
+ if (!apiKey) {
742
+ if (debug) console.warn("[Analytics] Scroll tracking disabled - no API key");
743
+ return;
744
+ }
745
+ if (debug) console.log("[Analytics] Scroll tracking enabled for:", pathname);
746
+ let maxDepth = 0;
747
+ let startTime = Date.now();
748
+ let milestone25 = null;
749
+ let milestone50 = null;
750
+ let milestone75 = null;
751
+ let milestone100 = null;
752
+ let hasTracked = false;
753
+ const calculateScrollDepth = () => {
754
+ const scrollTop = window.scrollY;
755
+ const docHeight = document.documentElement.scrollHeight;
756
+ const winHeight = window.innerHeight;
757
+ const scrollableHeight = docHeight - winHeight;
758
+ if (scrollableHeight <= 0) return 100;
759
+ return Math.min(100, Math.round(scrollTop / scrollableHeight * 100));
760
+ };
761
+ const handleScroll = () => {
762
+ const depth = calculateScrollDepth();
763
+ const elapsed = (Date.now() - startTime) / 1e3;
764
+ if (depth > maxDepth) {
765
+ maxDepth = depth;
766
+ currentScrollDepthRef.current = depth;
767
+ if (debug && depth % 25 === 0) console.log("[Analytics] Scroll milestone:", depth + "%");
768
+ if (depth >= 25 && milestone25 === null) milestone25 = elapsed;
769
+ if (depth >= 50 && milestone50 === null) milestone50 = elapsed;
770
+ if (depth >= 75 && milestone75 === null) milestone75 = elapsed;
771
+ if (depth >= 100 && milestone100 === null) milestone100 = elapsed;
772
+ }
773
+ };
774
+ const sendScrollData = async (useBeacon = false) => {
775
+ if (hasTracked || maxDepth === 0) return;
776
+ hasTracked = true;
777
+ const totalTime = (Date.now() - startTime) / 1e3;
778
+ const payload = JSON.stringify({
779
+ sessionId: sessionIdRef.current,
780
+ visitorId: visitorIdRef.current,
781
+ pagePath: pathname,
782
+ maxDepthPercent: maxDepth,
783
+ timeTo25: milestone25,
784
+ timeTo50: milestone50,
785
+ timeTo75: milestone75,
786
+ timeTo100: milestone100,
787
+ totalTimeSeconds: totalTime,
788
+ deviceType: getDeviceType()
789
+ });
790
+ if (useBeacon && navigator.sendBeacon) {
791
+ const blob = new Blob([payload], { type: "application/json" });
792
+ new Headers({ "x-api-key": apiKey });
793
+ navigator.sendBeacon(
794
+ `${apiUrl}/api/public/analytics/scroll-depth?key=${encodeURIComponent(apiKey)}`,
795
+ blob
796
+ );
797
+ if (debug) console.log("[Analytics] Scroll depth (beacon):", { maxDepth, totalTime });
798
+ } else {
799
+ try {
800
+ await fetch(`${apiUrl}/api/public/analytics/scroll-depth`, {
801
+ method: "POST",
802
+ headers: {
803
+ "Content-Type": "application/json",
804
+ "x-api-key": apiKey
805
+ },
806
+ body: payload,
807
+ keepalive: true
808
+ // Allows request to outlive the page
809
+ });
810
+ if (debug) console.log("[Analytics] Scroll depth:", { maxDepth, totalTime });
811
+ } catch (error) {
812
+ if (debug) console.error("[Analytics] Error tracking scroll depth:", error);
813
+ }
814
+ }
815
+ };
816
+ maxDepth = 0;
817
+ startTime = Date.now();
818
+ milestone25 = null;
819
+ milestone50 = null;
820
+ milestone75 = null;
821
+ milestone100 = null;
822
+ hasTracked = false;
823
+ window.addEventListener("scroll", handleScroll, { passive: true });
824
+ const handleBeforeUnload = () => sendScrollData(true);
825
+ const handleVisibilityChange = () => {
826
+ if (document.visibilityState === "hidden") sendScrollData(true);
827
+ };
828
+ window.addEventListener("beforeunload", handleBeforeUnload);
829
+ document.addEventListener("visibilitychange", handleVisibilityChange);
830
+ return () => {
831
+ window.removeEventListener("scroll", handleScroll);
832
+ window.removeEventListener("beforeunload", handleBeforeUnload);
833
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
834
+ sendScrollData(false);
835
+ };
836
+ }, [pathname, propApiUrl, propApiKey, trackScrollDepth, debug]);
837
+ react.useEffect(() => {
838
+ if (!trackClicks) return;
839
+ if (typeof window === "undefined") return;
840
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
841
+ const apiUrl = propApiUrl || globalApiUrl;
842
+ const apiKey = propApiKey || globalApiKey;
843
+ if (!apiKey) return;
844
+ const handleClick = async (e) => {
845
+ const target = e.target;
846
+ if (!target) return;
847
+ const docHeight = document.documentElement.scrollHeight;
848
+ const viewportWidth = window.innerWidth;
849
+ const viewportHeight = window.innerHeight;
850
+ const xPercent = Math.round(e.pageX / viewportWidth * 100);
851
+ const yPercent = Math.round(e.pageY / docHeight * 100);
852
+ const elementTag = target.tagName.toLowerCase();
853
+ const elementId = target.id || null;
854
+ const elementClass = target.className && typeof target.className === "string" ? target.className.split(" ").slice(0, 3).join(" ") : null;
855
+ const elementText = target.textContent?.slice(0, 50) || null;
856
+ const clickData = {
857
+ sessionId: sessionIdRef.current,
858
+ pagePath: pathname,
859
+ xPercent,
860
+ yPercent,
861
+ xAbsolute: e.pageX,
862
+ yAbsolute: e.pageY,
863
+ viewportWidth,
864
+ viewportHeight,
865
+ pageHeight: docHeight,
866
+ elementTag,
867
+ elementId,
868
+ elementClass,
869
+ elementText
870
+ };
871
+ if (debug) console.log("[Analytics] Click:", clickData);
872
+ scheduleIdleTask(async () => {
873
+ try {
874
+ await fetch(`${apiUrl}/api/public/analytics/heatmap-click`, {
875
+ method: "POST",
876
+ headers: {
877
+ "Content-Type": "application/json",
878
+ "x-api-key": apiKey
879
+ },
880
+ body: JSON.stringify(clickData)
881
+ });
882
+ } catch (error) {
883
+ if (debug) console.error("[Analytics] Error tracking click:", error);
884
+ }
885
+ }, 500);
886
+ };
887
+ document.addEventListener("click", handleClick, { passive: true });
888
+ return () => {
889
+ document.removeEventListener("click", handleClick);
890
+ };
891
+ }, [pathname, propApiUrl, propApiKey, trackClicks, debug]);
892
+ const trackEvent = react.useCallback((options) => {
893
+ const doTrack = async () => {
894
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
895
+ const apiUrl = propApiUrl || globalApiUrl;
896
+ const apiKey = propApiKey || globalApiKey;
897
+ if (!apiKey) {
898
+ if (debug) console.warn("[Analytics] No API key configured");
899
+ return;
900
+ }
901
+ const event = {
902
+ sessionId: sessionIdRef.current,
903
+ visitorId: visitorIdRef.current,
904
+ eventName: options.name,
905
+ eventCategory: options.category,
906
+ eventLabel: options.label,
907
+ eventValue: options.value,
908
+ properties: options.properties,
909
+ pagePath: pathname
910
+ };
911
+ if (debug) {
912
+ console.log("[Analytics] Event:", event);
913
+ }
914
+ try {
915
+ const response = await fetch(`${apiUrl}/api/public/analytics/event`, {
916
+ method: "POST",
917
+ headers: {
918
+ "Content-Type": "application/json",
919
+ "x-api-key": apiKey
920
+ },
921
+ body: JSON.stringify(event)
922
+ });
923
+ if (!response.ok && debug) {
924
+ console.error("[Analytics] Error tracking event:", response.statusText);
925
+ }
926
+ } catch (error) {
927
+ if (debug) console.error("[Analytics] Error tracking event:", error);
928
+ }
929
+ };
930
+ scheduleIdleTask(doTrack, 1e3);
931
+ }, [propApiUrl, propApiKey, pathname, debug]);
932
+ const trackConversion = react.useCallback((options) => {
933
+ const doTrack = async () => {
934
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
935
+ const apiUrl = propApiUrl || globalApiUrl;
936
+ const apiKey = propApiKey || globalApiKey;
937
+ if (!apiKey) {
938
+ if (debug) console.warn("[Analytics] No API key configured");
939
+ return;
940
+ }
941
+ const conversion = {
942
+ sessionId: sessionIdRef.current,
943
+ visitorId: visitorIdRef.current,
944
+ conversionType: options.type,
945
+ value: options.value,
946
+ currency: options.currency,
947
+ metadata: options.metadata,
948
+ pagePath: pathname,
949
+ referrer: document.referrer || null,
950
+ deviceType: getDeviceType()
951
+ };
952
+ if (debug) {
953
+ console.log("[Analytics] Conversion:", conversion);
954
+ }
955
+ try {
956
+ const response = await fetch(`${apiUrl}/api/public/analytics/conversion`, {
957
+ method: "POST",
958
+ headers: {
959
+ "Content-Type": "application/json",
960
+ "x-api-key": apiKey
961
+ },
962
+ body: JSON.stringify(conversion)
963
+ });
964
+ if (!response.ok && debug) {
965
+ console.error("[Analytics] Error tracking conversion:", response.statusText);
966
+ }
967
+ } catch (error) {
968
+ if (debug) console.error("[Analytics] Error tracking conversion:", error);
969
+ }
970
+ };
971
+ doTrack();
972
+ }, [propApiUrl, propApiKey, pathname, debug]);
973
+ const contextValue = react.useMemo(() => ({
974
+ trackEvent,
975
+ trackConversion,
976
+ sessionId: sessionIdRef.current,
977
+ visitorId: visitorIdRef.current
978
+ }), [trackEvent, trackConversion]);
979
+ return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsContext.Provider, { value: contextValue, children: [
980
+ trackWebVitals && /* @__PURE__ */ jsxRuntime.jsx(
981
+ WebVitals,
982
+ {
983
+ apiUrl: propApiUrl,
984
+ apiKey: propApiKey,
985
+ debug
986
+ }
987
+ ),
988
+ children
989
+ ] });
990
+ }
991
+ function useAnalytics() {
992
+ const context = react.useContext(AnalyticsContext);
993
+ if (!context) {
994
+ throw new Error("useAnalytics must be used within an AnalyticsProvider");
995
+ }
996
+ return context;
997
+ }
998
+ function useTrackEvent() {
999
+ const { trackEvent, trackConversion } = useAnalytics();
1000
+ return { trackEvent, trackConversion };
1001
+ }
1002
+
1003
+ exports.AnalyticsProvider = AnalyticsProvider;
1004
+ exports.WebVitals = WebVitals;
1005
+ exports.useAnalytics = useAnalytics;
1006
+ exports.useTrackEvent = useTrackEvent;
1007
+ //# sourceMappingURL=chunk-WECQ6KOB.js.map
1008
+ //# sourceMappingURL=chunk-WECQ6KOB.js.map