@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.
- package/README.md +509 -0
- package/dist/ai-YMnynb-t.mjs +3347 -0
- package/dist/ai-YMnynb-t.mjs.map +1 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/client-CTzJVFU5.mjs +9 -0
- package/dist/client-CTzJVFU5.mjs.map +1 -0
- package/dist/client-CcFTauAh.mjs +54 -0
- package/dist/client-CcFTauAh.mjs.map +1 -0
- package/dist/client-CeOLjbac.mjs +281 -0
- package/dist/client-CeOLjbac.mjs.map +1 -0
- package/dist/client-D339NFJS.mjs +267 -0
- package/dist/client-D339NFJS.mjs.map +1 -0
- package/dist/client-next.d.mts +62 -0
- package/dist/client-next.d.mts.map +1 -0
- package/dist/client-next.mjs +525 -0
- package/dist/client-next.mjs.map +1 -0
- package/dist/client.d.mts +30 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +186 -0
- package/dist/client.mjs.map +1 -0
- package/dist/config-DPS6bSYo.d.mts +34 -0
- package/dist/config-DPS6bSYo.d.mts.map +1 -0
- package/dist/config-P6P5adJg.mjs +287 -0
- package/dist/config-P6P5adJg.mjs.map +1 -0
- package/dist/console-8bND3mMU.mjs +128 -0
- package/dist/console-8bND3mMU.mjs.map +1 -0
- package/dist/ecommerce-Cgu4wlux.mjs +993 -0
- package/dist/ecommerce-Cgu4wlux.mjs.map +1 -0
- package/dist/emitters-6-nKo8i-.mjs +208 -0
- package/dist/emitters-6-nKo8i-.mjs.map +1 -0
- package/dist/emitters-DldkVSPp.d.mts +12 -0
- package/dist/emitters-DldkVSPp.d.mts.map +1 -0
- package/dist/index-BfNWgfa5.d.mts +1494 -0
- package/dist/index-BfNWgfa5.d.mts.map +1 -0
- package/dist/index-BkIWe--N.d.mts +953 -0
- package/dist/index-BkIWe--N.d.mts.map +1 -0
- package/dist/index-jPzXRn52.d.mts +184 -0
- package/dist/index-jPzXRn52.d.mts.map +1 -0
- package/dist/manager-DvRRjza6.d.mts +76 -0
- package/dist/manager-DvRRjza6.d.mts.map +1 -0
- package/dist/posthog-bootstrap-CYfIy_WS.mjs +1769 -0
- package/dist/posthog-bootstrap-CYfIy_WS.mjs.map +1 -0
- package/dist/posthog-bootstrap-DWxFrxlt.d.mts +81 -0
- package/dist/posthog-bootstrap-DWxFrxlt.d.mts.map +1 -0
- package/dist/providers-http-client.d.mts +37 -0
- package/dist/providers-http-client.d.mts.map +1 -0
- package/dist/providers-http-client.mjs +320 -0
- package/dist/providers-http-client.mjs.map +1 -0
- package/dist/providers-http-server.d.mts +31 -0
- package/dist/providers-http-server.d.mts.map +1 -0
- package/dist/providers-http-server.mjs +297 -0
- package/dist/providers-http-server.mjs.map +1 -0
- package/dist/providers-http.d.mts +46 -0
- package/dist/providers-http.d.mts.map +1 -0
- package/dist/providers-http.mjs +4 -0
- package/dist/server-edge.d.mts +9 -0
- package/dist/server-edge.d.mts.map +1 -0
- package/dist/server-edge.mjs +373 -0
- package/dist/server-edge.mjs.map +1 -0
- package/dist/server-next.d.mts +67 -0
- package/dist/server-next.d.mts.map +1 -0
- package/dist/server-next.mjs +193 -0
- package/dist/server-next.mjs.map +1 -0
- package/dist/server.d.mts +10 -0
- package/dist/server.mjs +7 -0
- package/dist/service-cYtBBL8x.mjs +945 -0
- package/dist/service-cYtBBL8x.mjs.map +1 -0
- package/dist/shared.d.mts +16 -0
- package/dist/shared.d.mts.map +1 -0
- package/dist/shared.mjs +93 -0
- package/dist/shared.mjs.map +1 -0
- package/dist/types-BxBnNQ0V.d.mts +354 -0
- package/dist/types-BxBnNQ0V.d.mts.map +1 -0
- package/dist/types-CBvxUEaF.d.mts +216 -0
- package/dist/types-CBvxUEaF.d.mts.map +1 -0
- package/dist/types.d.mts +4 -0
- package/dist/types.mjs +0 -0
- package/dist/vercel-types-lwakUfoI.d.mts +102 -0
- package/dist/vercel-types-lwakUfoI.d.mts.map +1 -0
- package/package.json +129 -0
- package/src/client/index.ts +164 -0
- package/src/client/manager.ts +71 -0
- package/src/client/next/components.tsx +270 -0
- package/src/client/next/hooks.ts +217 -0
- package/src/client/next/manager.ts +141 -0
- package/src/client/next.ts +144 -0
- package/src/client-next.ts +101 -0
- package/src/client.ts +89 -0
- package/src/examples/ai-sdk-patterns.ts +583 -0
- package/src/examples/emitter-patterns.ts +476 -0
- package/src/examples/nextjs-emitter-patterns.tsx +403 -0
- package/src/next/app-router.tsx +564 -0
- package/src/next/client.ts +419 -0
- package/src/next/index.ts +84 -0
- package/src/next/middleware.ts +429 -0
- package/src/next/rsc.tsx +300 -0
- package/src/next/server.ts +253 -0
- package/src/next/types.d.ts +220 -0
- package/src/providers/base-provider.ts +419 -0
- package/src/providers/console/client.ts +10 -0
- package/src/providers/console/index.ts +152 -0
- package/src/providers/console/server.ts +6 -0
- package/src/providers/console/types.ts +15 -0
- package/src/providers/http/client.ts +464 -0
- package/src/providers/http/index.ts +30 -0
- package/src/providers/http/server.ts +396 -0
- package/src/providers/http/types.ts +135 -0
- package/src/providers/posthog/client.ts +518 -0
- package/src/providers/posthog/index.ts +11 -0
- package/src/providers/posthog/server.ts +329 -0
- package/src/providers/posthog/types.ts +104 -0
- package/src/providers/segment/client.ts +113 -0
- package/src/providers/segment/index.ts +11 -0
- package/src/providers/segment/server.ts +115 -0
- package/src/providers/segment/types.ts +51 -0
- package/src/providers/vercel/client.ts +102 -0
- package/src/providers/vercel/index.ts +11 -0
- package/src/providers/vercel/server.ts +89 -0
- package/src/providers/vercel/types.ts +27 -0
- package/src/server/index.ts +103 -0
- package/src/server/manager.ts +62 -0
- package/src/server/next.ts +210 -0
- package/src/server-edge.ts +442 -0
- package/src/server-next.ts +39 -0
- package/src/server.ts +106 -0
- package/src/shared/emitters/ai/README.md +981 -0
- package/src/shared/emitters/ai/events/agent.ts +130 -0
- package/src/shared/emitters/ai/events/artifacts.ts +167 -0
- package/src/shared/emitters/ai/events/chat.ts +126 -0
- package/src/shared/emitters/ai/events/chatbot-ecommerce.ts +133 -0
- package/src/shared/emitters/ai/events/completion.ts +103 -0
- package/src/shared/emitters/ai/events/content-generation.ts +347 -0
- package/src/shared/emitters/ai/events/conversation.ts +332 -0
- package/src/shared/emitters/ai/events/product-features.ts +1402 -0
- package/src/shared/emitters/ai/events/streaming.ts +114 -0
- package/src/shared/emitters/ai/events/tool.ts +93 -0
- package/src/shared/emitters/ai/index.ts +69 -0
- package/src/shared/emitters/ai/track-ai-sdk.ts +74 -0
- package/src/shared/emitters/ai/track-ai.ts +50 -0
- package/src/shared/emitters/ai/types.ts +1041 -0
- package/src/shared/emitters/ai/utils.ts +468 -0
- package/src/shared/emitters/ecommerce/events/cart-checkout.ts +106 -0
- package/src/shared/emitters/ecommerce/events/coupon.ts +49 -0
- package/src/shared/emitters/ecommerce/events/engagement.ts +61 -0
- package/src/shared/emitters/ecommerce/events/marketplace.ts +119 -0
- package/src/shared/emitters/ecommerce/events/order.ts +199 -0
- package/src/shared/emitters/ecommerce/events/product.ts +205 -0
- package/src/shared/emitters/ecommerce/events/registry.ts +123 -0
- package/src/shared/emitters/ecommerce/events/wishlist-sharing.ts +140 -0
- package/src/shared/emitters/ecommerce/index.ts +46 -0
- package/src/shared/emitters/ecommerce/track-ecommerce.ts +53 -0
- package/src/shared/emitters/ecommerce/types.ts +314 -0
- package/src/shared/emitters/ecommerce/utils.ts +216 -0
- package/src/shared/emitters/emitter-types.ts +974 -0
- package/src/shared/emitters/emitters.ts +292 -0
- package/src/shared/emitters/helpers.ts +419 -0
- package/src/shared/emitters/index.ts +66 -0
- package/src/shared/index.ts +142 -0
- package/src/shared/ingestion/index.ts +66 -0
- package/src/shared/ingestion/schemas.ts +386 -0
- package/src/shared/ingestion/service.ts +628 -0
- package/src/shared/node22-features.ts +848 -0
- package/src/shared/providers/console-provider.ts +160 -0
- package/src/shared/types/base-types.ts +54 -0
- package/src/shared/types/console-types.ts +19 -0
- package/src/shared/types/posthog-types.ts +131 -0
- package/src/shared/types/segment-types.ts +15 -0
- package/src/shared/types/types.ts +397 -0
- package/src/shared/types/vercel-types.ts +19 -0
- package/src/shared/utils/config-client.ts +19 -0
- package/src/shared/utils/config.ts +250 -0
- package/src/shared/utils/emitter-adapter.ts +212 -0
- package/src/shared/utils/manager.test.ts +36 -0
- package/src/shared/utils/manager.ts +1322 -0
- package/src/shared/utils/posthog-bootstrap.ts +136 -0
- package/src/shared/utils/posthog-client-utils.ts +48 -0
- package/src/shared/utils/posthog-next-utils.ts +282 -0
- package/src/shared/utils/posthog-server-utils.ts +210 -0
- package/src/shared/utils/rate-limit.ts +289 -0
- package/src/shared/utils/security.ts +545 -0
- package/src/shared/utils/validation-client.ts +161 -0
- package/src/shared/utils/validation.ts +399 -0
- package/src/shared.ts +155 -0
- 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
|
+
}
|