@od-oneapp/analytics 2026.1.1301

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 (184) hide show
  1. package/README.md +509 -0
  2. package/dist/ai-YMnynb-t.mjs +3347 -0
  3. package/dist/ai-YMnynb-t.mjs.map +1 -0
  4. package/dist/chunk-DQk6qfdC.mjs +18 -0
  5. package/dist/client-CTzJVFU5.mjs +9 -0
  6. package/dist/client-CTzJVFU5.mjs.map +1 -0
  7. package/dist/client-CcFTauAh.mjs +54 -0
  8. package/dist/client-CcFTauAh.mjs.map +1 -0
  9. package/dist/client-CeOLjbac.mjs +281 -0
  10. package/dist/client-CeOLjbac.mjs.map +1 -0
  11. package/dist/client-D339NFJS.mjs +267 -0
  12. package/dist/client-D339NFJS.mjs.map +1 -0
  13. package/dist/client-next.d.mts +62 -0
  14. package/dist/client-next.d.mts.map +1 -0
  15. package/dist/client-next.mjs +525 -0
  16. package/dist/client-next.mjs.map +1 -0
  17. package/dist/client.d.mts +30 -0
  18. package/dist/client.d.mts.map +1 -0
  19. package/dist/client.mjs +186 -0
  20. package/dist/client.mjs.map +1 -0
  21. package/dist/config-DPS6bSYo.d.mts +34 -0
  22. package/dist/config-DPS6bSYo.d.mts.map +1 -0
  23. package/dist/config-P6P5adJg.mjs +287 -0
  24. package/dist/config-P6P5adJg.mjs.map +1 -0
  25. package/dist/console-8bND3mMU.mjs +128 -0
  26. package/dist/console-8bND3mMU.mjs.map +1 -0
  27. package/dist/ecommerce-Cgu4wlux.mjs +993 -0
  28. package/dist/ecommerce-Cgu4wlux.mjs.map +1 -0
  29. package/dist/emitters-6-nKo8i-.mjs +208 -0
  30. package/dist/emitters-6-nKo8i-.mjs.map +1 -0
  31. package/dist/emitters-DldkVSPp.d.mts +12 -0
  32. package/dist/emitters-DldkVSPp.d.mts.map +1 -0
  33. package/dist/index-BfNWgfa5.d.mts +1494 -0
  34. package/dist/index-BfNWgfa5.d.mts.map +1 -0
  35. package/dist/index-BkIWe--N.d.mts +953 -0
  36. package/dist/index-BkIWe--N.d.mts.map +1 -0
  37. package/dist/index-jPzXRn52.d.mts +184 -0
  38. package/dist/index-jPzXRn52.d.mts.map +1 -0
  39. package/dist/manager-DvRRjza6.d.mts +76 -0
  40. package/dist/manager-DvRRjza6.d.mts.map +1 -0
  41. package/dist/posthog-bootstrap-CYfIy_WS.mjs +1769 -0
  42. package/dist/posthog-bootstrap-CYfIy_WS.mjs.map +1 -0
  43. package/dist/posthog-bootstrap-DWxFrxlt.d.mts +81 -0
  44. package/dist/posthog-bootstrap-DWxFrxlt.d.mts.map +1 -0
  45. package/dist/providers-http-client.d.mts +37 -0
  46. package/dist/providers-http-client.d.mts.map +1 -0
  47. package/dist/providers-http-client.mjs +320 -0
  48. package/dist/providers-http-client.mjs.map +1 -0
  49. package/dist/providers-http-server.d.mts +31 -0
  50. package/dist/providers-http-server.d.mts.map +1 -0
  51. package/dist/providers-http-server.mjs +297 -0
  52. package/dist/providers-http-server.mjs.map +1 -0
  53. package/dist/providers-http.d.mts +46 -0
  54. package/dist/providers-http.d.mts.map +1 -0
  55. package/dist/providers-http.mjs +4 -0
  56. package/dist/server-edge.d.mts +9 -0
  57. package/dist/server-edge.d.mts.map +1 -0
  58. package/dist/server-edge.mjs +373 -0
  59. package/dist/server-edge.mjs.map +1 -0
  60. package/dist/server-next.d.mts +67 -0
  61. package/dist/server-next.d.mts.map +1 -0
  62. package/dist/server-next.mjs +193 -0
  63. package/dist/server-next.mjs.map +1 -0
  64. package/dist/server.d.mts +10 -0
  65. package/dist/server.mjs +7 -0
  66. package/dist/service-cYtBBL8x.mjs +945 -0
  67. package/dist/service-cYtBBL8x.mjs.map +1 -0
  68. package/dist/shared.d.mts +16 -0
  69. package/dist/shared.d.mts.map +1 -0
  70. package/dist/shared.mjs +93 -0
  71. package/dist/shared.mjs.map +1 -0
  72. package/dist/types-BxBnNQ0V.d.mts +354 -0
  73. package/dist/types-BxBnNQ0V.d.mts.map +1 -0
  74. package/dist/types-CBvxUEaF.d.mts +216 -0
  75. package/dist/types-CBvxUEaF.d.mts.map +1 -0
  76. package/dist/types.d.mts +4 -0
  77. package/dist/types.mjs +0 -0
  78. package/dist/vercel-types-lwakUfoI.d.mts +102 -0
  79. package/dist/vercel-types-lwakUfoI.d.mts.map +1 -0
  80. package/package.json +129 -0
  81. package/src/client/index.ts +164 -0
  82. package/src/client/manager.ts +71 -0
  83. package/src/client/next/components.tsx +270 -0
  84. package/src/client/next/hooks.ts +217 -0
  85. package/src/client/next/manager.ts +141 -0
  86. package/src/client/next.ts +144 -0
  87. package/src/client-next.ts +101 -0
  88. package/src/client.ts +89 -0
  89. package/src/examples/ai-sdk-patterns.ts +583 -0
  90. package/src/examples/emitter-patterns.ts +476 -0
  91. package/src/examples/nextjs-emitter-patterns.tsx +403 -0
  92. package/src/next/app-router.tsx +564 -0
  93. package/src/next/client.ts +419 -0
  94. package/src/next/index.ts +84 -0
  95. package/src/next/middleware.ts +429 -0
  96. package/src/next/rsc.tsx +300 -0
  97. package/src/next/server.ts +253 -0
  98. package/src/next/types.d.ts +220 -0
  99. package/src/providers/base-provider.ts +419 -0
  100. package/src/providers/console/client.ts +10 -0
  101. package/src/providers/console/index.ts +152 -0
  102. package/src/providers/console/server.ts +6 -0
  103. package/src/providers/console/types.ts +15 -0
  104. package/src/providers/http/client.ts +464 -0
  105. package/src/providers/http/index.ts +30 -0
  106. package/src/providers/http/server.ts +396 -0
  107. package/src/providers/http/types.ts +135 -0
  108. package/src/providers/posthog/client.ts +518 -0
  109. package/src/providers/posthog/index.ts +11 -0
  110. package/src/providers/posthog/server.ts +329 -0
  111. package/src/providers/posthog/types.ts +104 -0
  112. package/src/providers/segment/client.ts +113 -0
  113. package/src/providers/segment/index.ts +11 -0
  114. package/src/providers/segment/server.ts +115 -0
  115. package/src/providers/segment/types.ts +51 -0
  116. package/src/providers/vercel/client.ts +102 -0
  117. package/src/providers/vercel/index.ts +11 -0
  118. package/src/providers/vercel/server.ts +89 -0
  119. package/src/providers/vercel/types.ts +27 -0
  120. package/src/server/index.ts +103 -0
  121. package/src/server/manager.ts +62 -0
  122. package/src/server/next.ts +210 -0
  123. package/src/server-edge.ts +442 -0
  124. package/src/server-next.ts +39 -0
  125. package/src/server.ts +106 -0
  126. package/src/shared/emitters/ai/README.md +981 -0
  127. package/src/shared/emitters/ai/events/agent.ts +130 -0
  128. package/src/shared/emitters/ai/events/artifacts.ts +167 -0
  129. package/src/shared/emitters/ai/events/chat.ts +126 -0
  130. package/src/shared/emitters/ai/events/chatbot-ecommerce.ts +133 -0
  131. package/src/shared/emitters/ai/events/completion.ts +103 -0
  132. package/src/shared/emitters/ai/events/content-generation.ts +347 -0
  133. package/src/shared/emitters/ai/events/conversation.ts +332 -0
  134. package/src/shared/emitters/ai/events/product-features.ts +1402 -0
  135. package/src/shared/emitters/ai/events/streaming.ts +114 -0
  136. package/src/shared/emitters/ai/events/tool.ts +93 -0
  137. package/src/shared/emitters/ai/index.ts +69 -0
  138. package/src/shared/emitters/ai/track-ai-sdk.ts +74 -0
  139. package/src/shared/emitters/ai/track-ai.ts +50 -0
  140. package/src/shared/emitters/ai/types.ts +1041 -0
  141. package/src/shared/emitters/ai/utils.ts +468 -0
  142. package/src/shared/emitters/ecommerce/events/cart-checkout.ts +106 -0
  143. package/src/shared/emitters/ecommerce/events/coupon.ts +49 -0
  144. package/src/shared/emitters/ecommerce/events/engagement.ts +61 -0
  145. package/src/shared/emitters/ecommerce/events/marketplace.ts +119 -0
  146. package/src/shared/emitters/ecommerce/events/order.ts +199 -0
  147. package/src/shared/emitters/ecommerce/events/product.ts +205 -0
  148. package/src/shared/emitters/ecommerce/events/registry.ts +123 -0
  149. package/src/shared/emitters/ecommerce/events/wishlist-sharing.ts +140 -0
  150. package/src/shared/emitters/ecommerce/index.ts +46 -0
  151. package/src/shared/emitters/ecommerce/track-ecommerce.ts +53 -0
  152. package/src/shared/emitters/ecommerce/types.ts +314 -0
  153. package/src/shared/emitters/ecommerce/utils.ts +216 -0
  154. package/src/shared/emitters/emitter-types.ts +974 -0
  155. package/src/shared/emitters/emitters.ts +292 -0
  156. package/src/shared/emitters/helpers.ts +419 -0
  157. package/src/shared/emitters/index.ts +66 -0
  158. package/src/shared/index.ts +142 -0
  159. package/src/shared/ingestion/index.ts +66 -0
  160. package/src/shared/ingestion/schemas.ts +386 -0
  161. package/src/shared/ingestion/service.ts +628 -0
  162. package/src/shared/node22-features.ts +848 -0
  163. package/src/shared/providers/console-provider.ts +160 -0
  164. package/src/shared/types/base-types.ts +54 -0
  165. package/src/shared/types/console-types.ts +19 -0
  166. package/src/shared/types/posthog-types.ts +131 -0
  167. package/src/shared/types/segment-types.ts +15 -0
  168. package/src/shared/types/types.ts +397 -0
  169. package/src/shared/types/vercel-types.ts +19 -0
  170. package/src/shared/utils/config-client.ts +19 -0
  171. package/src/shared/utils/config.ts +250 -0
  172. package/src/shared/utils/emitter-adapter.ts +212 -0
  173. package/src/shared/utils/manager.test.ts +36 -0
  174. package/src/shared/utils/manager.ts +1322 -0
  175. package/src/shared/utils/posthog-bootstrap.ts +136 -0
  176. package/src/shared/utils/posthog-client-utils.ts +48 -0
  177. package/src/shared/utils/posthog-next-utils.ts +282 -0
  178. package/src/shared/utils/posthog-server-utils.ts +210 -0
  179. package/src/shared/utils/rate-limit.ts +289 -0
  180. package/src/shared/utils/security.ts +545 -0
  181. package/src/shared/utils/validation-client.ts +161 -0
  182. package/src/shared/utils/validation.ts +399 -0
  183. package/src/shared.ts +155 -0
  184. package/src/types/index.ts +62 -0
@@ -0,0 +1,564 @@
1
+ /**
2
+ * @fileoverview Next.js App Router specific analytics helpers
3
+ *
4
+ * This module provides React hooks and components optimized for Next.js App Router:
5
+ *
6
+ * - **Hooks**: `useAnalytics()`, `usePageTracking()`, `useTrackEvent()`, `useIdentifyUser()`
7
+ * - **Components**: `AnalyticsProvider`, `TrackedButton`, `TrackedLink`, `withViewTracking()`
8
+ * - **Ecommerce Tracking**: `useEcommerceTracking()` hook
9
+ * - **Form Tracking**: `useFormTracking()` hook
10
+ *
11
+ * **NOTE**: This file is Next.js-specific. Client components can use Next.js hooks
12
+ * as they're already framework-bound.
13
+ *
14
+ * @module @od-oneapp/analytics/next/app-router
15
+ */
16
+
17
+ 'use client';
18
+
19
+ // Note: These are Next.js hooks required for App Router integration
20
+ // Client components are already Next.js-specific, so this is acceptable
21
+ import { useCallback, useEffect, useRef, useState } from 'react';
22
+
23
+ import type { Route } from 'next';
24
+
25
+ import { useParams, usePathname, useRouter, useSearchParams } from 'next/navigation';
26
+
27
+ import { createNextJSClientAnalytics } from './client';
28
+
29
+ import type { NextJSClientAnalyticsConfig, NextJSClientAnalyticsManager } from './client';
30
+ import type { BootstrapData } from '../shared/types/posthog-types';
31
+ import type { AnalyticsConfig, TrackingOptions } from '../shared/types/types';
32
+
33
+ // Global analytics instance
34
+ let globalAnalytics: NextJSClientAnalyticsManager | null = null;
35
+
36
+ /**
37
+ * Hook to get or create analytics instance.
38
+ *
39
+ * Returns the global analytics instance, creating it if it doesn't exist.
40
+ * The instance is shared across all components using this hook.
41
+ *
42
+ * @param {AnalyticsConfig} [config] - Analytics configuration (only used on first call)
43
+ * @returns {NextJSClientAnalyticsManager | null} Analytics manager instance or null if not initialized
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * function MyComponent() {
48
+ * const analytics = useAnalytics(config);
49
+ * if (!analytics) return null;
50
+ *
51
+ * const handleClick = () => {
52
+ * analytics.track('Button Clicked');
53
+ * };
54
+ * }
55
+ * ```
56
+ */
57
+ export function useAnalytics(config?: AnalyticsConfig): NextJSClientAnalyticsManager | null {
58
+ const [analytics, setAnalytics] = useState<NextJSClientAnalyticsManager | null>(null);
59
+
60
+ useEffect(() => {
61
+ if (!globalAnalytics && config) {
62
+ // Convert AnalyticsConfig to NextJSClientAnalyticsConfig
63
+ // Preserve all bootstrap properties (distinctID is required for BootstrapData)
64
+ const nextjsConfig = {
65
+ ...config,
66
+ nextjs: config.nextjs
67
+ ? ({
68
+ ...config.nextjs,
69
+ posthog: config.nextjs.posthog
70
+ ? {
71
+ ...config.nextjs.posthog,
72
+ bootstrap:
73
+ config.nextjs.posthog.bootstrap?.distinctID !== undefined
74
+ ? ({
75
+ distinctID: config.nextjs.posthog.bootstrap.distinctID,
76
+ // Preserve other bootstrap properties for PostHog functionality
77
+ ...(config.nextjs.posthog.bootstrap.isIdentifiedID !== undefined && {
78
+ isIdentifiedID: config.nextjs.posthog.bootstrap.isIdentifiedID,
79
+ }),
80
+ ...(config.nextjs.posthog.bootstrap.featureFlags && {
81
+ featureFlags: config.nextjs.posthog.bootstrap.featureFlags,
82
+ }),
83
+ ...(config.nextjs.posthog.bootstrap.featureFlagPayloads && {
84
+ featureFlagPayloads:
85
+ config.nextjs.posthog.bootstrap.featureFlagPayloads,
86
+ }),
87
+ } as BootstrapData)
88
+ : undefined,
89
+ }
90
+ : undefined,
91
+ } as NextJSClientAnalyticsConfig['nextjs'])
92
+ : undefined,
93
+ } as NextJSClientAnalyticsConfig;
94
+ globalAnalytics = createNextJSClientAnalytics(nextjsConfig);
95
+ void globalAnalytics.initialize();
96
+ // Use setTimeout to avoid synchronous setState in effect
97
+ setTimeout(() => {
98
+ setAnalytics(globalAnalytics);
99
+ }, 0);
100
+ } else if (globalAnalytics) {
101
+ // Use setTimeout to avoid synchronous setState in effect
102
+ setTimeout(() => {
103
+ setAnalytics(globalAnalytics);
104
+ }, 0);
105
+ }
106
+ }, [config]);
107
+
108
+ return analytics;
109
+ }
110
+
111
+ /**
112
+ * Hook for automatic page view tracking in App Router.
113
+ *
114
+ * Automatically tracks page views when the pathname, search params, or route params change.
115
+ * Uses Next.js `usePathname()`, `useSearchParams()`, and `useParams()` hooks.
116
+ *
117
+ * @param {object} [options] - Page tracking options
118
+ * @param {boolean} [options.trackSearch] - Whether to track search params changes (default: false)
119
+ * @param {boolean} [options.trackParams] - Whether to track route params changes (default: false)
120
+ * @param {Record<string, any>} [options.properties] - Additional properties to include with page views
121
+ * @param {boolean} [options.skip] - Skip tracking (useful for conditional tracking)
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * function Page() {
126
+ * usePageTracking({
127
+ * trackSearch: true,
128
+ * properties: { section: 'products' }
129
+ * });
130
+ * return <div>Page content</div>;
131
+ * }
132
+ * ```
133
+ */
134
+ export function usePageTracking(options?: {
135
+ trackSearch?: boolean;
136
+ trackParams?: boolean;
137
+ properties?: Record<string, any>;
138
+ skip?: boolean;
139
+ }) {
140
+ const pathname = usePathname();
141
+ const searchParams = useSearchParams();
142
+ const params = useParams();
143
+ const tracked = useRef<string>('');
144
+
145
+ useEffect(() => {
146
+ if (options?.skip || !globalAnalytics) return;
147
+
148
+ // Create unique key for this page view
149
+ const searchString = options?.trackSearch ? searchParams.toString() : '';
150
+ const pageKey = `${pathname}${searchString}`;
151
+
152
+ // Avoid duplicate tracking
153
+ if (tracked.current === pageKey) return;
154
+ tracked.current = pageKey;
155
+
156
+ // Build properties
157
+ const properties: Record<string, any> = {
158
+ ...options?.properties,
159
+ url: window.location.href,
160
+ path: pathname,
161
+ referrer: document.referrer,
162
+ title: document.title,
163
+ };
164
+
165
+ if (options?.trackSearch) {
166
+ properties.search = searchString;
167
+ properties.search_params = Object.fromEntries(searchParams.entries());
168
+ }
169
+
170
+ if (options?.trackParams && params) {
171
+ properties.route_params = params;
172
+ }
173
+
174
+ // Track page view
175
+ void globalAnalytics.page(pathname, properties);
176
+ }, [
177
+ pathname,
178
+ searchParams,
179
+ params,
180
+ options?.skip,
181
+ options?.trackSearch,
182
+ options?.trackParams,
183
+ options?.properties,
184
+ ]);
185
+ }
186
+
187
+ /**
188
+ * Hook for tracking events
189
+ */
190
+ export function useTrackEvent() {
191
+ return useCallback((event: string, properties?: any, options?: TrackingOptions) => {
192
+ if (!globalAnalytics) return;
193
+ void globalAnalytics.track(event, properties, options);
194
+ }, []);
195
+ }
196
+
197
+ /**
198
+ * Hook for user identification
199
+ */
200
+ export function useIdentifyUser() {
201
+ return useCallback((userId: string, traits?: any, options?: TrackingOptions) => {
202
+ if (!globalAnalytics) return;
203
+ void globalAnalytics.identify(userId, traits, options);
204
+ }, []);
205
+ }
206
+
207
+ /**
208
+ * Hook for analytics consent management
209
+ */
210
+ export function useAnalyticsConsent() {
211
+ const [consentGiven, setConsentGiven] = useState(false);
212
+ const [isLoading, setIsLoading] = useState(false);
213
+
214
+ const grantConsent = useCallback(async () => {
215
+ if (!globalAnalytics) return;
216
+
217
+ setIsLoading(true);
218
+ try {
219
+ await globalAnalytics.grantConsent();
220
+ setConsentGiven(true);
221
+ } finally {
222
+ setIsLoading(false);
223
+ }
224
+ }, []);
225
+
226
+ const revokeConsent = useCallback(() => {
227
+ if (!globalAnalytics) return;
228
+
229
+ void globalAnalytics.revokeConsent();
230
+ setConsentGiven(false);
231
+ }, []);
232
+
233
+ useEffect(() => {
234
+ if (!globalAnalytics) return;
235
+
236
+ const status = globalAnalytics.getStatus();
237
+ // Use setTimeout to avoid synchronous setState in effect
238
+ setTimeout(() => {
239
+ setConsentGiven(status.consentGiven);
240
+ }, 0);
241
+ }, []);
242
+
243
+ return {
244
+ consentGiven,
245
+ grantConsent,
246
+ isLoading,
247
+ revokeConsent,
248
+ };
249
+ }
250
+
251
+ /**
252
+ * Analytics provider component for App Router
253
+ */
254
+ export function AnalyticsProvider({
255
+ autoPageTracking = true,
256
+ bootstrapData,
257
+ children,
258
+ config,
259
+ pageTrackingOptions,
260
+ }: {
261
+ children: React.ReactNode;
262
+ config: AnalyticsConfig;
263
+ bootstrapData?: BootstrapData;
264
+ autoPageTracking?: boolean;
265
+ pageTrackingOptions?: {
266
+ trackSearch?: boolean;
267
+ trackParams?: boolean;
268
+ properties?: Record<string, unknown>;
269
+ skip?: boolean;
270
+ };
271
+ }) {
272
+ // Initialize analytics
273
+ useEffect(() => {
274
+ if (!globalAnalytics) {
275
+ const analyticsConfig = { ...config };
276
+
277
+ // Add bootstrap data if provided
278
+ if (bootstrapData && config.providers.posthog) {
279
+ analyticsConfig.nextjs = {
280
+ ...analyticsConfig.nextjs,
281
+ posthog: {
282
+ ...analyticsConfig.nextjs?.posthog,
283
+ bootstrap: bootstrapData,
284
+ },
285
+ };
286
+ }
287
+
288
+ // Convert AnalyticsConfig to NextJSClientAnalyticsConfig
289
+ // Preserve all bootstrap properties (distinctID is required for BootstrapData)
290
+ const nextjsConfig = {
291
+ ...analyticsConfig,
292
+ nextjs: analyticsConfig.nextjs
293
+ ? ({
294
+ ...analyticsConfig.nextjs,
295
+ posthog: analyticsConfig.nextjs.posthog
296
+ ? {
297
+ ...analyticsConfig.nextjs.posthog,
298
+ bootstrap:
299
+ analyticsConfig.nextjs.posthog.bootstrap?.distinctID !== undefined
300
+ ? ({
301
+ distinctID: analyticsConfig.nextjs.posthog.bootstrap.distinctID,
302
+ // Preserve other bootstrap properties for PostHog functionality
303
+ ...(analyticsConfig.nextjs.posthog.bootstrap.isIdentifiedID !==
304
+ undefined && {
305
+ isIdentifiedID:
306
+ analyticsConfig.nextjs.posthog.bootstrap.isIdentifiedID,
307
+ }),
308
+ ...(analyticsConfig.nextjs.posthog.bootstrap.featureFlags && {
309
+ featureFlags: analyticsConfig.nextjs.posthog.bootstrap.featureFlags,
310
+ }),
311
+ ...(analyticsConfig.nextjs.posthog.bootstrap.featureFlagPayloads && {
312
+ featureFlagPayloads:
313
+ analyticsConfig.nextjs.posthog.bootstrap.featureFlagPayloads,
314
+ }),
315
+ } as BootstrapData)
316
+ : analyticsConfig.nextjs.posthog.bootstrap,
317
+ }
318
+ : undefined,
319
+ } as NextJSClientAnalyticsConfig['nextjs'])
320
+ : undefined,
321
+ } as NextJSClientAnalyticsConfig;
322
+
323
+ globalAnalytics = createNextJSClientAnalytics(nextjsConfig);
324
+ void globalAnalytics.initialize();
325
+ }
326
+ }, [config, bootstrapData]);
327
+
328
+ // Auto page tracking
329
+ usePageTracking(autoPageTracking ? pageTrackingOptions : { skip: true });
330
+
331
+ return children as React.ReactElement;
332
+ }
333
+
334
+ /**
335
+ * Higher-order component for tracking component views
336
+ */
337
+ export function withViewTracking<P extends object>(
338
+ Component: React.ComponentType<P>,
339
+ eventName: string,
340
+ getProperties?: (props: P) => Record<string, any>,
341
+ ) {
342
+ return function TrackedComponent(props: P) {
343
+ const track = useTrackEvent();
344
+ const tracked = useRef(false);
345
+
346
+ useEffect(() => {
347
+ if (!tracked.current) {
348
+ tracked.current = true;
349
+ const properties = getProperties ? getProperties(props) : {};
350
+ track(eventName, properties);
351
+ }
352
+ }, [track, props]);
353
+
354
+ return <Component {...props} />;
355
+ };
356
+ }
357
+
358
+ /**
359
+ * Component for tracking clicks
360
+ */
361
+ export function TrackedButton({
362
+ children,
363
+ eventName,
364
+ onClick,
365
+ properties,
366
+ ...props
367
+ }: React.ButtonHTMLAttributes<HTMLButtonElement> & {
368
+ eventName: string;
369
+ properties?: Record<string, any>;
370
+ }) {
371
+ const track = useTrackEvent();
372
+
373
+ const handleClick = useCallback(
374
+ (e: React.MouseEvent<HTMLButtonElement>) => {
375
+ track(eventName, properties);
376
+ onClick?.(e);
377
+ },
378
+ [track, eventName, properties, onClick],
379
+ );
380
+
381
+ return (
382
+ <button type="button" {...props} onClick={handleClick}>
383
+ {children}
384
+ </button>
385
+ );
386
+ }
387
+
388
+ /**
389
+ * Component for tracking link clicks
390
+ */
391
+ export function TrackedLink({
392
+ children,
393
+ eventName,
394
+ href,
395
+ onClick,
396
+ properties,
397
+ ...props
398
+ }: React.AnchorHTMLAttributes<HTMLAnchorElement> & {
399
+ eventName: string;
400
+ properties?: Record<string, any>;
401
+ }) {
402
+ const track = useTrackEvent();
403
+ const router = useRouter();
404
+
405
+ const handleClick = useCallback(
406
+ (e: React.MouseEvent<HTMLAnchorElement>) => {
407
+ // Track the event
408
+ const enhancedProperties = {
409
+ ...properties,
410
+ href,
411
+ link_text: typeof children === 'string' ? children : undefined,
412
+ };
413
+ void track(eventName, enhancedProperties);
414
+
415
+ // Handle navigation
416
+ if (onClick) {
417
+ void onClick(e);
418
+ } else if (href && !props.target && href.startsWith('/')) {
419
+ // Internal navigation
420
+ e.preventDefault();
421
+ router.push(href as Route);
422
+ }
423
+ },
424
+ [track, eventName, properties, href, onClick, router, children, props.target],
425
+ );
426
+
427
+ return (
428
+ <a {...props} href={href} onClick={handleClick}>
429
+ {children}
430
+ </a>
431
+ );
432
+ }
433
+
434
+ /**
435
+ * Hook for tracking form submissions
436
+ */
437
+ export function useFormTracking(formName: string) {
438
+ const track = useTrackEvent();
439
+
440
+ const trackFormStart = useCallback(() => {
441
+ track('Form Started', { form_name: formName });
442
+ }, [track, formName]);
443
+
444
+ const trackFormSubmit = useCallback(
445
+ (data?: any) => {
446
+ track('Form Submitted', {
447
+ form_name: formName,
448
+ field_count: data ? Object.keys(data).length : undefined,
449
+ });
450
+ },
451
+ [track, formName],
452
+ );
453
+
454
+ const trackFormError = useCallback(
455
+ (error: any) => {
456
+ track('Form Error', {
457
+ form_name: formName,
458
+ error_message: error?.message ?? String(error),
459
+ });
460
+ },
461
+ [track, formName],
462
+ );
463
+
464
+ const trackFieldInteraction = useCallback(
465
+ (fieldName: string) => {
466
+ track('Form Field Interacted', {
467
+ field_name: fieldName,
468
+ form_name: formName,
469
+ });
470
+ },
471
+ [track, formName],
472
+ );
473
+
474
+ return {
475
+ trackFieldInteraction,
476
+ trackFormError,
477
+ trackFormStart,
478
+ trackFormSubmit,
479
+ };
480
+ }
481
+
482
+ /**
483
+ * Hook for e-commerce tracking helpers
484
+ */
485
+ export function useEcommerceTracking() {
486
+ const track = useTrackEvent();
487
+
488
+ const trackProductView = useCallback(
489
+ (product: {
490
+ id: string;
491
+ name: string;
492
+ price?: number;
493
+ category?: string;
494
+ [key: string]: any;
495
+ }) => {
496
+ void track('Product Viewed', {
497
+ product_id: product.id,
498
+ product_name: product.name,
499
+ category: product.category,
500
+ price: product.price,
501
+ ...product,
502
+ });
503
+ },
504
+ [track],
505
+ );
506
+
507
+ const trackAddToCart = useCallback(
508
+ (product: {
509
+ id: string;
510
+ name: string;
511
+ price?: number;
512
+ quantity?: number;
513
+ [key: string]: any;
514
+ }) => {
515
+ track('Product Added to Cart', {
516
+ product_id: product.id,
517
+ product_name: product.name,
518
+ price: product.price,
519
+ quantity: product.quantity ?? 1,
520
+ ...product,
521
+ });
522
+ },
523
+ [track],
524
+ );
525
+
526
+ const trackCheckout = useCallback(
527
+ (cart: { total: number; items: any[]; [key: string]: any }) => {
528
+ const { items, total, ...otherProps } = cart;
529
+ track('Checkout Started', {
530
+ item_count: items.length,
531
+ total,
532
+ ...otherProps,
533
+ });
534
+ },
535
+ [track],
536
+ );
537
+
538
+ const trackPurchase = useCallback(
539
+ (order: { orderId: string; total: number; items: any[]; [key: string]: any }) => {
540
+ const { items, orderId, total, ...otherProps } = order;
541
+ track('Order Completed', {
542
+ order_id: orderId,
543
+ item_count: items.length,
544
+ total,
545
+ ...otherProps,
546
+ });
547
+ },
548
+ [track],
549
+ );
550
+
551
+ return {
552
+ trackAddToCart,
553
+ trackCheckout,
554
+ trackProductView,
555
+ trackPurchase,
556
+ };
557
+ }
558
+
559
+ /**
560
+ * Utility to reset analytics (useful for testing)
561
+ */
562
+ export function resetAnalytics() {
563
+ globalAnalytics = null;
564
+ }