@uptrademedia/site-kit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +305 -0
  2. package/dist/analytics/index.js +88 -0
  3. package/dist/analytics/index.js.map +1 -0
  4. package/dist/analytics/index.mjs +70 -0
  5. package/dist/analytics/index.mjs.map +1 -0
  6. package/dist/api-N35S3EES.js +57 -0
  7. package/dist/api-N35S3EES.js.map +1 -0
  8. package/dist/api-SYBTK7Z7.mjs +4 -0
  9. package/dist/api-SYBTK7Z7.mjs.map +1 -0
  10. package/dist/blog/index.js +200 -0
  11. package/dist/blog/index.js.map +1 -0
  12. package/dist/blog/index.mjs +194 -0
  13. package/dist/blog/index.mjs.map +1 -0
  14. package/dist/chunk-3MUOUXHV.js +3721 -0
  15. package/dist/chunk-3MUOUXHV.js.map +1 -0
  16. package/dist/chunk-4HVYXYQL 2.mjs +255 -0
  17. package/dist/chunk-4HVYXYQL.mjs +255 -0
  18. package/dist/chunk-4HVYXYQL.mjs.map +1 -0
  19. package/dist/chunk-7H6I3ECV.mjs +120 -0
  20. package/dist/chunk-7H6I3ECV.mjs.map +1 -0
  21. package/dist/chunk-COI6GOX2.mjs +3679 -0
  22. package/dist/chunk-COI6GOX2.mjs.map +1 -0
  23. package/dist/chunk-EQCVQC35.js +35 -0
  24. package/dist/chunk-EQCVQC35.js 2.map +1 -0
  25. package/dist/chunk-EQCVQC35.js.map +1 -0
  26. package/dist/chunk-FEBYQGY4 2.mjs +251 -0
  27. package/dist/chunk-FEBYQGY4.mjs +251 -0
  28. package/dist/chunk-FEBYQGY4.mjs.map +1 -0
  29. package/dist/chunk-FKVJOT2F.mjs +796 -0
  30. package/dist/chunk-FKVJOT2F.mjs.map +1 -0
  31. package/dist/chunk-GQ6ZOU2N.mjs +134 -0
  32. package/dist/chunk-GQ6ZOU2N.mjs.map +1 -0
  33. package/dist/chunk-HCFPU7TU.js +137 -0
  34. package/dist/chunk-HCFPU7TU.js.map +1 -0
  35. package/dist/chunk-NYKRE2FL 2.mjs +31 -0
  36. package/dist/chunk-NYKRE2FL.mjs +31 -0
  37. package/dist/chunk-NYKRE2FL.mjs 2.map +1 -0
  38. package/dist/chunk-NYKRE2FL.mjs.map +1 -0
  39. package/dist/chunk-QP5NCO2E.js +133 -0
  40. package/dist/chunk-QP5NCO2E.js.map +1 -0
  41. package/dist/chunk-RV7H3I6J.js +255 -0
  42. package/dist/chunk-RV7H3I6J.js 2.map +1 -0
  43. package/dist/chunk-RV7H3I6J.js.map +1 -0
  44. package/dist/chunk-SBVEYCSV.js +140 -0
  45. package/dist/chunk-SBVEYCSV.js.map +1 -0
  46. package/dist/chunk-TUKGA3UK.js +257 -0
  47. package/dist/chunk-TUKGA3UK.js 2.map +1 -0
  48. package/dist/chunk-TUKGA3UK.js.map +1 -0
  49. package/dist/chunk-V3F5J6CV.js +801 -0
  50. package/dist/chunk-V3F5J6CV.js.map +1 -0
  51. package/dist/chunk-WPSRS352.mjs +135 -0
  52. package/dist/chunk-WPSRS352.mjs.map +1 -0
  53. package/dist/commerce/index.js +157 -0
  54. package/dist/commerce/index.js.map +1 -0
  55. package/dist/commerce/index.mjs +4 -0
  56. package/dist/commerce/index.mjs.map +1 -0
  57. package/dist/commerce/server.js +186 -0
  58. package/dist/commerce/server.js.map +1 -0
  59. package/dist/commerce/server.mjs +176 -0
  60. package/dist/commerce/server.mjs.map +1 -0
  61. package/dist/engage/index.js +50 -0
  62. package/dist/engage/index.js.map +1 -0
  63. package/dist/engage/index.mjs +44 -0
  64. package/dist/engage/index.mjs.map +1 -0
  65. package/dist/forms/index.js +1053 -0
  66. package/dist/forms/index.js.map +1 -0
  67. package/dist/forms/index.mjs +1035 -0
  68. package/dist/forms/index.mjs.map +1 -0
  69. package/dist/generators-7Y5ABRYV 2.mjs +161 -0
  70. package/dist/generators-7Y5ABRYV.mjs +161 -0
  71. package/dist/generators-7Y5ABRYV.mjs 2.map +1 -0
  72. package/dist/generators-7Y5ABRYV.mjs.map +1 -0
  73. package/dist/generators-GWIYCA5M.js +171 -0
  74. package/dist/generators-GWIYCA5M.js 2.map +1 -0
  75. package/dist/generators-GWIYCA5M.js.map +1 -0
  76. package/dist/index 2.mjs +74 -0
  77. package/dist/index.js +326 -0
  78. package/dist/index.js 2.map +1 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/index.mjs +222 -0
  81. package/dist/index.mjs.map +1 -0
  82. package/dist/migrator-V6KS75EA 2.mjs +265 -0
  83. package/dist/migrator-V6KS75EA.mjs +265 -0
  84. package/dist/migrator-V6KS75EA.mjs 2.map +1 -0
  85. package/dist/migrator-V6KS75EA.mjs.map +1 -0
  86. package/dist/migrator-XKM7YQCY.js +272 -0
  87. package/dist/migrator-XKM7YQCY.js 2.map +1 -0
  88. package/dist/migrator-XKM7YQCY.js.map +1 -0
  89. package/dist/scanner-MF7P3CDE 2.mjs +14386 -0
  90. package/dist/scanner-MF7P3CDE.mjs +14386 -0
  91. package/dist/scanner-MF7P3CDE.mjs 2.map +1 -0
  92. package/dist/scanner-MF7P3CDE.mjs.map +1 -0
  93. package/dist/scanner-NT6YG4TD 2.js +14397 -0
  94. package/dist/scanner-NT6YG4TD.js +14397 -0
  95. package/dist/scanner-NT6YG4TD.js 2.map +1 -0
  96. package/dist/scanner-NT6YG4TD.js.map +1 -0
  97. package/dist/seo/index.js +447 -0
  98. package/dist/seo/index.js.map +1 -0
  99. package/dist/seo/index.mjs +411 -0
  100. package/dist/seo/index.mjs.map +1 -0
  101. package/dist/seo/server.js +66 -0
  102. package/dist/seo/server.js.map +1 -0
  103. package/dist/seo/server.mjs +5 -0
  104. package/dist/seo/server.mjs.map +1 -0
  105. package/dist/setup/index.js +1050 -0
  106. package/dist/setup/index.js.map +1 -0
  107. package/dist/setup/index.mjs +1046 -0
  108. package/dist/setup/index.mjs.map +1 -0
  109. package/dist/sitemap/index.js +212 -0
  110. package/dist/sitemap/index.js.map +1 -0
  111. package/dist/sitemap/index.mjs +206 -0
  112. package/dist/sitemap/index.mjs.map +1 -0
  113. package/dist/web-vitals-BH55V7EJ.js +252 -0
  114. package/dist/web-vitals-BH55V7EJ.js 2.map +1 -0
  115. package/dist/web-vitals-BH55V7EJ.js.map +1 -0
  116. package/dist/web-vitals-RJYPWAR3 2.mjs +241 -0
  117. package/dist/web-vitals-RJYPWAR3.mjs +241 -0
  118. package/dist/web-vitals-RJYPWAR3.mjs 2.map +1 -0
  119. package/dist/web-vitals-RJYPWAR3.mjs.map +1 -0
  120. package/package.json +118 -0
@@ -0,0 +1,796 @@
1
+ import { createContext, useEffect, useRef, useCallback, useMemo, useContext } from 'react';
2
+ import { usePathname, useSearchParams } from 'next/navigation';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ // src/analytics/WebVitals.tsx
6
+ function getApiConfig() {
7
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
8
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
9
+ return { apiUrl, apiKey };
10
+ }
11
+ function WebVitals({ apiUrl: propApiUrl, apiKey: propApiKey, debug = false }) {
12
+ const pathname = usePathname();
13
+ useEffect(() => {
14
+ import('./web-vitals-RJYPWAR3.mjs').then(({ onCLS, onLCP, onTTFB, onINP, onFCP }) => {
15
+ const vitals = {};
16
+ let reported = false;
17
+ const reportVitals = async () => {
18
+ if (reported) return;
19
+ if (Object.keys(vitals).length === 0) return;
20
+ reported = true;
21
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig();
22
+ const apiUrl = propApiUrl || globalApiUrl;
23
+ const apiKey = propApiKey || globalApiKey;
24
+ if (!apiKey) {
25
+ if (debug) console.warn("[Analytics] No API key configured for Web Vitals");
26
+ return;
27
+ }
28
+ for (const [name, value] of Object.entries(vitals)) {
29
+ const data = {
30
+ pagePath: pathname,
31
+ metricName: name,
32
+ metricValue: value,
33
+ metricRating: getRating(name, value)
34
+ };
35
+ if (debug) {
36
+ console.log("[Analytics] Web Vital:", data);
37
+ }
38
+ try {
39
+ await fetch(`${apiUrl}/api/public/analytics/web-vitals`, {
40
+ method: "POST",
41
+ headers: {
42
+ "Content-Type": "application/json",
43
+ "x-api-key": apiKey
44
+ },
45
+ body: JSON.stringify(data)
46
+ });
47
+ } catch (error) {
48
+ if (debug) console.error("[Analytics] Error reporting Web Vital:", error);
49
+ }
50
+ }
51
+ };
52
+ onLCP((metric) => {
53
+ vitals.LCP = metric.value;
54
+ if (debug) console.log("[Analytics] LCP:", metric.value);
55
+ });
56
+ onCLS((metric) => {
57
+ vitals.CLS = metric.value;
58
+ if (debug) console.log("[Analytics] CLS:", metric.value);
59
+ });
60
+ onTTFB((metric) => {
61
+ vitals.TTFB = metric.value;
62
+ if (debug) console.log("[Analytics] TTFB:", metric.value);
63
+ });
64
+ onINP((metric) => {
65
+ vitals.INP = metric.value;
66
+ if (debug) console.log("[Analytics] INP:", metric.value);
67
+ });
68
+ onFCP((metric) => {
69
+ vitals.FCP = metric.value;
70
+ if (debug) console.log("[Analytics] FCP:", metric.value);
71
+ });
72
+ const handleVisibilityChange = () => {
73
+ if (document.visibilityState === "hidden") {
74
+ reportVitals();
75
+ }
76
+ };
77
+ document.addEventListener("visibilitychange", handleVisibilityChange);
78
+ const timeout = setTimeout(reportVitals, 1e4);
79
+ return () => {
80
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
81
+ clearTimeout(timeout);
82
+ };
83
+ });
84
+ }, [pathname, propApiUrl, propApiKey, debug]);
85
+ return null;
86
+ }
87
+ function getRating(name, value) {
88
+ const thresholds = {
89
+ LCP: [2500, 4e3],
90
+ CLS: [0.1, 0.25],
91
+ TTFB: [800, 1800],
92
+ INP: [200, 500],
93
+ FCP: [1800, 3e3]
94
+ };
95
+ const [good, poor] = thresholds[name] || [0, 0];
96
+ if (value <= good) return "good";
97
+ if (value <= poor) return "needs-improvement";
98
+ return "poor";
99
+ }
100
+ var AnalyticsContext = createContext(null);
101
+ function generateId() {
102
+ return crypto.randomUUID();
103
+ }
104
+ function scheduleIdleTask(callback, timeout = 2e3) {
105
+ if (typeof window !== "undefined" && "requestIdleCallback" in window) {
106
+ window.requestIdleCallback(
107
+ callback,
108
+ { timeout }
109
+ );
110
+ } else {
111
+ setTimeout(callback, 0);
112
+ }
113
+ }
114
+ function getOrCreateVisitorId() {
115
+ if (typeof window === "undefined") return "";
116
+ const key = "_uptrade_vid";
117
+ let visitorId = localStorage.getItem(key);
118
+ if (!visitorId) {
119
+ visitorId = generateId();
120
+ localStorage.setItem(key, visitorId);
121
+ }
122
+ return visitorId;
123
+ }
124
+ function getSessionId(timeout) {
125
+ if (typeof window === "undefined") return "";
126
+ const key = "_uptrade_sid";
127
+ const timeKey = "_uptrade_stime";
128
+ const now = Date.now();
129
+ const timeoutMs = timeout * 60 * 1e3;
130
+ const existingSession = sessionStorage.getItem(key);
131
+ const lastActivity = sessionStorage.getItem(timeKey);
132
+ if (existingSession && lastActivity) {
133
+ const elapsed = now - parseInt(lastActivity, 10);
134
+ if (elapsed < timeoutMs) {
135
+ sessionStorage.setItem(timeKey, now.toString());
136
+ return existingSession;
137
+ }
138
+ }
139
+ const newSession = generateId();
140
+ sessionStorage.setItem(key, newSession);
141
+ sessionStorage.setItem(timeKey, now.toString());
142
+ return newSession;
143
+ }
144
+ function getDeviceType() {
145
+ if (typeof window === "undefined") return "desktop";
146
+ const ua = navigator.userAgent;
147
+ if (/tablet|ipad|playbook|silk/i.test(ua)) return "tablet";
148
+ if (/mobile|iphone|ipod|android|blackberry|opera mini|iemobile/i.test(ua)) return "mobile";
149
+ return "desktop";
150
+ }
151
+ function getBrowser() {
152
+ if (typeof window === "undefined") return "unknown";
153
+ const ua = navigator.userAgent;
154
+ if (ua.includes("Firefox")) return "Firefox";
155
+ if (ua.includes("Edg")) return "Edge";
156
+ if (ua.includes("Chrome")) return "Chrome";
157
+ if (ua.includes("Safari")) return "Safari";
158
+ if (ua.includes("Opera") || ua.includes("OPR")) return "Opera";
159
+ return "Other";
160
+ }
161
+ function getOS() {
162
+ if (typeof window === "undefined") return "unknown";
163
+ const ua = navigator.userAgent;
164
+ if (ua.includes("Windows")) return "Windows";
165
+ if (ua.includes("Mac OS X") || ua.includes("Macintosh")) return "macOS";
166
+ if (ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
167
+ if (ua.includes("Android")) return "Android";
168
+ if (ua.includes("Linux")) return "Linux";
169
+ if (ua.includes("CrOS")) return "ChromeOS";
170
+ return "Other";
171
+ }
172
+ function getUserAgent() {
173
+ if (typeof window === "undefined") return "";
174
+ return navigator.userAgent;
175
+ }
176
+ function getUTMParams() {
177
+ if (typeof window === "undefined") return {};
178
+ const params = new URLSearchParams(window.location.search);
179
+ const utmParams = {};
180
+ for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"]) {
181
+ const value = params.get(key);
182
+ if (value) utmParams[key] = value;
183
+ }
184
+ return utmParams;
185
+ }
186
+ function getPageMetadata() {
187
+ if (typeof document === "undefined") return {};
188
+ const getMeta = (name) => {
189
+ const el = document.querySelector(`meta[name="${name}"], meta[property="${name}"]`);
190
+ return el?.getAttribute("content") || null;
191
+ };
192
+ const getCanonical = () => {
193
+ const el = document.querySelector('link[rel="canonical"]');
194
+ return el?.getAttribute("href") || null;
195
+ };
196
+ const getRobots = () => {
197
+ return getMeta("robots");
198
+ };
199
+ const getH1 = () => {
200
+ const h1 = document.querySelector("h1");
201
+ return h1?.textContent?.trim() || null;
202
+ };
203
+ const getH1Count = () => {
204
+ return document.querySelectorAll("h1").length;
205
+ };
206
+ const getWordCount = () => {
207
+ const main = document.querySelector('main, article, [role="main"]') || document.body;
208
+ const text = main.textContent || "";
209
+ return text.split(/\s+/).filter((w) => w.length > 0).length;
210
+ };
211
+ const getImageStats = () => {
212
+ const images = document.querySelectorAll("img");
213
+ let withoutAlt = 0;
214
+ images.forEach((img) => {
215
+ if (!img.alt || img.alt.trim() === "") withoutAlt++;
216
+ });
217
+ return { count: images.length, withoutAlt };
218
+ };
219
+ const getLinkStats = () => {
220
+ const links = document.querySelectorAll("a[href]");
221
+ const currentHost = window.location.host;
222
+ let internal = 0;
223
+ let external = 0;
224
+ const internalLinks = [];
225
+ const getLinkPosition = (el) => {
226
+ let current = el;
227
+ while (current) {
228
+ const tag = current.tagName?.toLowerCase();
229
+ const role = current.getAttribute("role");
230
+ if (tag === "header" || role === "banner") return "header";
231
+ if (tag === "nav" || role === "navigation") return "nav";
232
+ if (tag === "footer" || role === "contentinfo") return "footer";
233
+ if (tag === "aside" || role === "complementary") return "sidebar";
234
+ if (tag === "main" || tag === "article" || role === "main") return "content";
235
+ current = current.parentElement;
236
+ }
237
+ return "unknown";
238
+ };
239
+ links.forEach((link) => {
240
+ const href = link.getAttribute("href") || "";
241
+ const rel = link.getAttribute("rel") || "";
242
+ const isNofollow = rel.includes("nofollow");
243
+ let isInternal = false;
244
+ let targetPath = "";
245
+ if (href.startsWith("/") && !href.startsWith("//")) {
246
+ isInternal = true;
247
+ targetPath = href.split("?")[0].split("#")[0];
248
+ } else if (href.startsWith("#")) {
249
+ internal++;
250
+ return;
251
+ } else if (href.startsWith("http")) {
252
+ try {
253
+ const url = new URL(href);
254
+ if (url.host === currentHost) {
255
+ isInternal = true;
256
+ targetPath = url.pathname;
257
+ } else {
258
+ external++;
259
+ }
260
+ } catch {
261
+ }
262
+ }
263
+ if (isInternal && targetPath) {
264
+ internal++;
265
+ if (!targetPath.startsWith("/")) targetPath = "/" + targetPath;
266
+ if (targetPath !== "/" && targetPath.endsWith("/")) {
267
+ targetPath = targetPath.slice(0, -1);
268
+ }
269
+ internalLinks.push({
270
+ targetPath,
271
+ anchorText: (link.textContent || "").trim().slice(0, 200),
272
+ position: getLinkPosition(link),
273
+ isNofollow
274
+ });
275
+ }
276
+ });
277
+ return { internal, external, internalLinks };
278
+ };
279
+ const imageStats = getImageStats();
280
+ const linkStats = getLinkStats();
281
+ return {
282
+ metaDescription: getMeta("description"),
283
+ canonical: getCanonical(),
284
+ robots: getRobots(),
285
+ ogTitle: getMeta("og:title"),
286
+ ogDescription: getMeta("og:description"),
287
+ ogImage: getMeta("og:image"),
288
+ h1: getH1(),
289
+ h1Count: getH1Count(),
290
+ wordCount: getWordCount(),
291
+ imagesCount: imageStats.count,
292
+ imagesWithoutAlt: imageStats.withoutAlt,
293
+ internalLinks: linkStats.internal,
294
+ internalLinkTargets: linkStats.internalLinks,
295
+ // Full link graph data
296
+ externalLinks: linkStats.external
297
+ };
298
+ }
299
+ function getApiConfig2() {
300
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
301
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
302
+ return { apiUrl, apiKey };
303
+ }
304
+ function AnalyticsProvider({
305
+ children,
306
+ apiUrl: propApiUrl,
307
+ apiKey: propApiKey,
308
+ trackPageViews = true,
309
+ trackWebVitals = true,
310
+ trackScrollDepth = true,
311
+ trackClicks = true,
312
+ trackJourneys = true,
313
+ // NEW: Enable journey tracking by default
314
+ sessionTimeout = 30,
315
+ excludePaths = [],
316
+ validateAgainstSitemap = true,
317
+ debug = false
318
+ }) {
319
+ const pathname = usePathname();
320
+ const searchParams = useSearchParams();
321
+ const visitorIdRef = useRef("");
322
+ const sessionIdRef = useRef("");
323
+ const lastPathRef = useRef("");
324
+ const validPathsRef = useRef(null);
325
+ const journeyStartTimeRef = useRef(0);
326
+ const pageEnterTimeRef = useRef(0);
327
+ const currentScrollDepthRef = useRef(0);
328
+ useEffect(() => {
329
+ visitorIdRef.current = getOrCreateVisitorId();
330
+ sessionIdRef.current = getSessionId(sessionTimeout);
331
+ }, [sessionTimeout]);
332
+ useEffect(() => {
333
+ if (!validateAgainstSitemap) {
334
+ if (debug) console.log("[Analytics] Page validation disabled");
335
+ return;
336
+ }
337
+ const fetchValidPages = async () => {
338
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
339
+ const apiUrl = propApiUrl || globalApiUrl;
340
+ const apiKey = propApiKey || globalApiKey;
341
+ if (!apiKey) {
342
+ if (debug) console.warn("[Analytics] No API key for page validation");
343
+ return;
344
+ }
345
+ try {
346
+ const response = await fetch(`${apiUrl}/api/public/seo/pages`, {
347
+ method: "GET",
348
+ headers: {
349
+ "Content-Type": "application/json",
350
+ "x-api-key": apiKey
351
+ }
352
+ });
353
+ if (response.ok) {
354
+ const data = await response.json();
355
+ const pages = data?.pages || [];
356
+ validPathsRef.current = new Set(
357
+ pages.map((p) => p.path).filter(Boolean)
358
+ );
359
+ if (debug) {
360
+ console.log("[Analytics] Loaded", validPathsRef.current.size, "valid pages from seo_pages");
361
+ }
362
+ } else if (debug) {
363
+ console.error("[Analytics] Pages fetch failed:", response.statusText);
364
+ }
365
+ } catch (error) {
366
+ if (debug) console.error("[Analytics] Error fetching valid pages:", error);
367
+ }
368
+ };
369
+ fetchValidPages();
370
+ }, [propApiUrl, propApiKey, validateAgainstSitemap, debug]);
371
+ useEffect(() => {
372
+ if (!trackPageViews) return;
373
+ if (!pathname) return;
374
+ if (excludePaths.some((p) => pathname.startsWith(p))) return;
375
+ if (pathname === lastPathRef.current) return;
376
+ if (validateAgainstSitemap) {
377
+ if (validPathsRef.current && validPathsRef.current.size > 0) {
378
+ if (!validPathsRef.current.has(pathname)) {
379
+ if (debug) {
380
+ console.log("[Analytics] Skipping unregistered path:", pathname);
381
+ }
382
+ return;
383
+ }
384
+ } else if (debug) {
385
+ console.log("[Analytics] Sitemap not yet loaded, tracking anyway:", pathname);
386
+ }
387
+ }
388
+ lastPathRef.current = pathname;
389
+ const trackPageView = async () => {
390
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
391
+ const apiUrl = propApiUrl || globalApiUrl;
392
+ const apiKey = propApiKey || globalApiKey;
393
+ if (!apiKey) {
394
+ if (debug) console.warn("[Analytics] No API key configured");
395
+ return;
396
+ }
397
+ const utmParams = getUTMParams();
398
+ const pageMetadata = getPageMetadata();
399
+ const pageView = {
400
+ sessionId: sessionIdRef.current,
401
+ visitorId: visitorIdRef.current,
402
+ pagePath: pathname,
403
+ pageTitle: document.title,
404
+ referrer: document.referrer || null,
405
+ deviceType: getDeviceType(),
406
+ browser: getBrowser(),
407
+ os: getOS(),
408
+ userAgent: getUserAgent(),
409
+ utmSource: utmParams.utm_source,
410
+ utmMedium: utmParams.utm_medium,
411
+ utmCampaign: utmParams.utm_campaign,
412
+ utmTerm: utmParams.utm_term,
413
+ utmContent: utmParams.utm_content,
414
+ // SEO enrichment data - updates seo_pages
415
+ seo: pageMetadata
416
+ };
417
+ if (debug) {
418
+ console.log("[Analytics] Page view:", pageView);
419
+ }
420
+ try {
421
+ const response = await fetch(`${apiUrl}/api/public/analytics/page-view`, {
422
+ method: "POST",
423
+ headers: {
424
+ "Content-Type": "application/json",
425
+ "x-api-key": apiKey
426
+ },
427
+ body: JSON.stringify(pageView)
428
+ });
429
+ if (!response.ok && debug) {
430
+ console.error("[Analytics] Error tracking page view:", response.statusText);
431
+ }
432
+ } catch (error) {
433
+ if (debug) console.error("[Analytics] Error tracking page view:", error);
434
+ }
435
+ };
436
+ scheduleIdleTask(() => trackPageView());
437
+ }, [pathname, searchParams, propApiUrl, propApiKey, trackPageViews, excludePaths, debug]);
438
+ useEffect(() => {
439
+ if (!trackJourneys) return;
440
+ if (!pathname) return;
441
+ if (typeof window === "undefined") return;
442
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
443
+ const apiUrl = propApiUrl || globalApiUrl;
444
+ const apiKey = propApiKey || globalApiKey;
445
+ if (!apiKey) return;
446
+ const now = Date.now();
447
+ const previousPath = lastPathRef.current;
448
+ const previousDuration = pageEnterTimeRef.current > 0 ? Math.round((now - pageEnterTimeRef.current) / 1e3) : 0;
449
+ const previousScrollDepth = currentScrollDepthRef.current;
450
+ pageEnterTimeRef.current = now;
451
+ currentScrollDepthRef.current = 0;
452
+ const isNewSession = !previousPath || journeyStartTimeRef.current === 0;
453
+ if (isNewSession) {
454
+ journeyStartTimeRef.current = now;
455
+ }
456
+ const trackJourneyStep = async () => {
457
+ const sessionData = {
458
+ sessionId: sessionIdRef.current,
459
+ visitorId: visitorIdRef.current,
460
+ action: isNewSession ? "start" : "update",
461
+ lastPage: pathname,
462
+ userAgent: getUserAgent(),
463
+ // Journey step data
464
+ journeyStep: {
465
+ page: pathname,
466
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
467
+ }
468
+ };
469
+ if (!isNewSession && previousDuration > 0) {
470
+ sessionData.previousPageDuration = previousDuration;
471
+ sessionData.previousPageScrollDepth = previousScrollDepth;
472
+ }
473
+ if (isNewSession) {
474
+ sessionData.firstPage = pathname;
475
+ const utmParams = getUTMParams();
476
+ sessionData.referrer = document.referrer || null;
477
+ sessionData.utmSource = utmParams.utm_source;
478
+ sessionData.utmMedium = utmParams.utm_medium;
479
+ sessionData.utmCampaign = utmParams.utm_campaign;
480
+ sessionData.utmTerm = utmParams.utm_term;
481
+ sessionData.utmContent = utmParams.utm_content;
482
+ sessionData.screenWidth = window.screen.width;
483
+ sessionData.screenHeight = window.screen.height;
484
+ }
485
+ if (debug) {
486
+ console.log("[Analytics] Journey step:", sessionData);
487
+ }
488
+ try {
489
+ await fetch(`${apiUrl}/api/public/analytics/session`, {
490
+ method: "POST",
491
+ headers: {
492
+ "Content-Type": "application/json",
493
+ "x-api-key": apiKey
494
+ },
495
+ body: JSON.stringify(sessionData)
496
+ });
497
+ } catch (error) {
498
+ if (debug) console.error("[Analytics] Error tracking journey:", error);
499
+ }
500
+ };
501
+ scheduleIdleTask(() => trackJourneyStep());
502
+ const handleUnload = () => {
503
+ const duration = Math.round((Date.now() - journeyStartTimeRef.current) / 1e3);
504
+ const payload = JSON.stringify({
505
+ sessionId: sessionIdRef.current,
506
+ action: "end",
507
+ duration,
508
+ lastPage: pathname,
509
+ previousPageDuration: Math.round((Date.now() - pageEnterTimeRef.current) / 1e3),
510
+ previousPageScrollDepth: currentScrollDepthRef.current
511
+ });
512
+ if (navigator.sendBeacon) {
513
+ const blob = new Blob([payload], { type: "application/json" });
514
+ navigator.sendBeacon(
515
+ `${apiUrl}/api/public/analytics/session?key=${encodeURIComponent(apiKey)}`,
516
+ blob
517
+ );
518
+ }
519
+ };
520
+ window.addEventListener("beforeunload", handleUnload);
521
+ return () => {
522
+ window.removeEventListener("beforeunload", handleUnload);
523
+ };
524
+ }, [pathname, propApiUrl, propApiKey, trackJourneys, debug]);
525
+ useEffect(() => {
526
+ if (typeof window === "undefined") return;
527
+ if (debug) console.log("[Analytics] Scroll tracking setup:", { trackScrollDepth, hasApiKey: !!propApiKey });
528
+ if (!trackScrollDepth) return;
529
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
530
+ const apiUrl = propApiUrl || globalApiUrl;
531
+ const apiKey = propApiKey || globalApiKey;
532
+ if (!apiKey) {
533
+ if (debug) console.warn("[Analytics] Scroll tracking disabled - no API key");
534
+ return;
535
+ }
536
+ if (debug) console.log("[Analytics] Scroll tracking enabled for:", pathname);
537
+ let maxDepth = 0;
538
+ let startTime = Date.now();
539
+ let milestone25 = null;
540
+ let milestone50 = null;
541
+ let milestone75 = null;
542
+ let milestone100 = null;
543
+ let hasTracked = false;
544
+ const calculateScrollDepth = () => {
545
+ const scrollTop = window.scrollY;
546
+ const docHeight = document.documentElement.scrollHeight;
547
+ const winHeight = window.innerHeight;
548
+ const scrollableHeight = docHeight - winHeight;
549
+ if (scrollableHeight <= 0) return 100;
550
+ return Math.min(100, Math.round(scrollTop / scrollableHeight * 100));
551
+ };
552
+ const handleScroll = () => {
553
+ const depth = calculateScrollDepth();
554
+ const elapsed = (Date.now() - startTime) / 1e3;
555
+ if (depth > maxDepth) {
556
+ maxDepth = depth;
557
+ currentScrollDepthRef.current = depth;
558
+ if (debug && depth % 25 === 0) console.log("[Analytics] Scroll milestone:", depth + "%");
559
+ if (depth >= 25 && milestone25 === null) milestone25 = elapsed;
560
+ if (depth >= 50 && milestone50 === null) milestone50 = elapsed;
561
+ if (depth >= 75 && milestone75 === null) milestone75 = elapsed;
562
+ if (depth >= 100 && milestone100 === null) milestone100 = elapsed;
563
+ }
564
+ };
565
+ const sendScrollData = async (useBeacon = false) => {
566
+ if (hasTracked || maxDepth === 0) return;
567
+ hasTracked = true;
568
+ const totalTime = (Date.now() - startTime) / 1e3;
569
+ const payload = JSON.stringify({
570
+ sessionId: sessionIdRef.current,
571
+ visitorId: visitorIdRef.current,
572
+ pagePath: pathname,
573
+ maxDepthPercent: maxDepth,
574
+ timeTo25: milestone25,
575
+ timeTo50: milestone50,
576
+ timeTo75: milestone75,
577
+ timeTo100: milestone100,
578
+ totalTimeSeconds: totalTime,
579
+ deviceType: getDeviceType()
580
+ });
581
+ if (useBeacon && navigator.sendBeacon) {
582
+ const blob = new Blob([payload], { type: "application/json" });
583
+ new Headers({ "x-api-key": apiKey });
584
+ navigator.sendBeacon(
585
+ `${apiUrl}/api/public/analytics/scroll-depth?key=${encodeURIComponent(apiKey)}`,
586
+ blob
587
+ );
588
+ if (debug) console.log("[Analytics] Scroll depth (beacon):", { maxDepth, totalTime });
589
+ } else {
590
+ try {
591
+ await fetch(`${apiUrl}/api/public/analytics/scroll-depth`, {
592
+ method: "POST",
593
+ headers: {
594
+ "Content-Type": "application/json",
595
+ "x-api-key": apiKey
596
+ },
597
+ body: payload,
598
+ keepalive: true
599
+ // Allows request to outlive the page
600
+ });
601
+ if (debug) console.log("[Analytics] Scroll depth:", { maxDepth, totalTime });
602
+ } catch (error) {
603
+ if (debug) console.error("[Analytics] Error tracking scroll depth:", error);
604
+ }
605
+ }
606
+ };
607
+ maxDepth = 0;
608
+ startTime = Date.now();
609
+ milestone25 = null;
610
+ milestone50 = null;
611
+ milestone75 = null;
612
+ milestone100 = null;
613
+ hasTracked = false;
614
+ window.addEventListener("scroll", handleScroll, { passive: true });
615
+ const handleBeforeUnload = () => sendScrollData(true);
616
+ const handleVisibilityChange = () => {
617
+ if (document.visibilityState === "hidden") sendScrollData(true);
618
+ };
619
+ window.addEventListener("beforeunload", handleBeforeUnload);
620
+ document.addEventListener("visibilitychange", handleVisibilityChange);
621
+ return () => {
622
+ window.removeEventListener("scroll", handleScroll);
623
+ window.removeEventListener("beforeunload", handleBeforeUnload);
624
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
625
+ sendScrollData(false);
626
+ };
627
+ }, [pathname, propApiUrl, propApiKey, trackScrollDepth, debug]);
628
+ useEffect(() => {
629
+ if (!trackClicks) return;
630
+ if (typeof window === "undefined") return;
631
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
632
+ const apiUrl = propApiUrl || globalApiUrl;
633
+ const apiKey = propApiKey || globalApiKey;
634
+ if (!apiKey) return;
635
+ const handleClick = async (e) => {
636
+ const target = e.target;
637
+ if (!target) return;
638
+ const docHeight = document.documentElement.scrollHeight;
639
+ const viewportWidth = window.innerWidth;
640
+ const viewportHeight = window.innerHeight;
641
+ const xPercent = Math.round(e.pageX / viewportWidth * 100);
642
+ const yPercent = Math.round(e.pageY / docHeight * 100);
643
+ const elementTag = target.tagName.toLowerCase();
644
+ const elementId = target.id || null;
645
+ const elementClass = target.className && typeof target.className === "string" ? target.className.split(" ").slice(0, 3).join(" ") : null;
646
+ const elementText = target.textContent?.slice(0, 50) || null;
647
+ const clickData = {
648
+ sessionId: sessionIdRef.current,
649
+ pagePath: pathname,
650
+ xPercent,
651
+ yPercent,
652
+ xAbsolute: e.pageX,
653
+ yAbsolute: e.pageY,
654
+ viewportWidth,
655
+ viewportHeight,
656
+ pageHeight: docHeight,
657
+ elementTag,
658
+ elementId,
659
+ elementClass,
660
+ elementText
661
+ };
662
+ if (debug) console.log("[Analytics] Click:", clickData);
663
+ scheduleIdleTask(async () => {
664
+ try {
665
+ await fetch(`${apiUrl}/api/public/analytics/heatmap-click`, {
666
+ method: "POST",
667
+ headers: {
668
+ "Content-Type": "application/json",
669
+ "x-api-key": apiKey
670
+ },
671
+ body: JSON.stringify(clickData)
672
+ });
673
+ } catch (error) {
674
+ if (debug) console.error("[Analytics] Error tracking click:", error);
675
+ }
676
+ }, 500);
677
+ };
678
+ document.addEventListener("click", handleClick, { passive: true });
679
+ return () => {
680
+ document.removeEventListener("click", handleClick);
681
+ };
682
+ }, [pathname, propApiUrl, propApiKey, trackClicks, debug]);
683
+ const trackEvent = useCallback((options) => {
684
+ const doTrack = async () => {
685
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
686
+ const apiUrl = propApiUrl || globalApiUrl;
687
+ const apiKey = propApiKey || globalApiKey;
688
+ if (!apiKey) {
689
+ if (debug) console.warn("[Analytics] No API key configured");
690
+ return;
691
+ }
692
+ const event = {
693
+ sessionId: sessionIdRef.current,
694
+ visitorId: visitorIdRef.current,
695
+ eventName: options.name,
696
+ eventCategory: options.category,
697
+ eventLabel: options.label,
698
+ eventValue: options.value,
699
+ properties: options.properties,
700
+ pagePath: pathname
701
+ };
702
+ if (debug) {
703
+ console.log("[Analytics] Event:", event);
704
+ }
705
+ try {
706
+ const response = await fetch(`${apiUrl}/api/public/analytics/event`, {
707
+ method: "POST",
708
+ headers: {
709
+ "Content-Type": "application/json",
710
+ "x-api-key": apiKey
711
+ },
712
+ body: JSON.stringify(event)
713
+ });
714
+ if (!response.ok && debug) {
715
+ console.error("[Analytics] Error tracking event:", response.statusText);
716
+ }
717
+ } catch (error) {
718
+ if (debug) console.error("[Analytics] Error tracking event:", error);
719
+ }
720
+ };
721
+ scheduleIdleTask(doTrack, 1e3);
722
+ }, [propApiUrl, propApiKey, pathname, debug]);
723
+ const trackConversion = useCallback((options) => {
724
+ const doTrack = async () => {
725
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
726
+ const apiUrl = propApiUrl || globalApiUrl;
727
+ const apiKey = propApiKey || globalApiKey;
728
+ if (!apiKey) {
729
+ if (debug) console.warn("[Analytics] No API key configured");
730
+ return;
731
+ }
732
+ const conversion = {
733
+ sessionId: sessionIdRef.current,
734
+ visitorId: visitorIdRef.current,
735
+ conversionType: options.type,
736
+ value: options.value,
737
+ currency: options.currency,
738
+ metadata: options.metadata,
739
+ pagePath: pathname,
740
+ referrer: document.referrer || null,
741
+ deviceType: getDeviceType()
742
+ };
743
+ if (debug) {
744
+ console.log("[Analytics] Conversion:", conversion);
745
+ }
746
+ try {
747
+ const response = await fetch(`${apiUrl}/api/public/analytics/conversion`, {
748
+ method: "POST",
749
+ headers: {
750
+ "Content-Type": "application/json",
751
+ "x-api-key": apiKey
752
+ },
753
+ body: JSON.stringify(conversion)
754
+ });
755
+ if (!response.ok && debug) {
756
+ console.error("[Analytics] Error tracking conversion:", response.statusText);
757
+ }
758
+ } catch (error) {
759
+ if (debug) console.error("[Analytics] Error tracking conversion:", error);
760
+ }
761
+ };
762
+ doTrack();
763
+ }, [propApiUrl, propApiKey, pathname, debug]);
764
+ const contextValue = useMemo(() => ({
765
+ trackEvent,
766
+ trackConversion,
767
+ sessionId: sessionIdRef.current,
768
+ visitorId: visitorIdRef.current
769
+ }), [trackEvent, trackConversion]);
770
+ return /* @__PURE__ */ jsxs(AnalyticsContext.Provider, { value: contextValue, children: [
771
+ trackWebVitals && /* @__PURE__ */ jsx(
772
+ WebVitals,
773
+ {
774
+ apiUrl: propApiUrl,
775
+ apiKey: propApiKey,
776
+ debug
777
+ }
778
+ ),
779
+ children
780
+ ] });
781
+ }
782
+ function useAnalytics() {
783
+ const context = useContext(AnalyticsContext);
784
+ if (!context) {
785
+ throw new Error("useAnalytics must be used within an AnalyticsProvider");
786
+ }
787
+ return context;
788
+ }
789
+ function useTrackEvent() {
790
+ const { trackEvent, trackConversion } = useAnalytics();
791
+ return { trackEvent, trackConversion };
792
+ }
793
+
794
+ export { AnalyticsProvider, WebVitals, useAnalytics, useTrackEvent };
795
+ //# sourceMappingURL=chunk-FKVJOT2F.mjs.map
796
+ //# sourceMappingURL=chunk-FKVJOT2F.mjs.map