fix: switch to PostHogProvider per official React integration guide (#112)
- Install @posthog/react and wrap app with PostHogProvider - Use VITE_PUBLIC_POSTHOG_KEY and VITE_PUBLIC_POSTHOG_HOST env vars - Use defaults: '2026-01-30' for recommended settings - Remove manual initAnalytics() call — Provider handles initialization - Analytics module now checks posthog.__loaded for readiness Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #112.
This commit is contained in:
17
frontend/package-lock.json
generated
17
frontend/package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@posthog/react": "^1.8.2",
|
||||
"@sentry/react": "^10.42.0",
|
||||
"@sentry/vite-plugin": "^5.1.1",
|
||||
"@stripe/stripe-js": "^8.7.0",
|
||||
@@ -1597,6 +1598,22 @@
|
||||
"cross-spawn": "^7.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@posthog/react": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@posthog/react/-/react-1.8.2.tgz",
|
||||
"integrity": "sha512-KzUuXIcAR8fAjU7IeDq+XfEcUTNvzgEGB381WRrFUUsu7jFTcKZZ6crx/ukHRCzTnoEuy5EJDkL7b7sJecPlCg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.8.0",
|
||||
"posthog-js": ">=1.257.2",
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@posthog/types": {
|
||||
"version": "1.360.2",
|
||||
"resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.360.2.tgz",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@posthog/react": "^1.8.2",
|
||||
"@sentry/react": "^10.42.0",
|
||||
"@sentry/vite-plugin": "^5.1.1",
|
||||
"@stripe/stripe-js": "^8.7.0",
|
||||
|
||||
@@ -2,29 +2,24 @@
|
||||
* PostHog product analytics wrapper.
|
||||
*
|
||||
* Tracks key user actions to understand product usage, activation,
|
||||
* and engagement. All events are lightweight discrete actions — no
|
||||
* pageviews or mouse tracking.
|
||||
* and engagement. All events are lightweight discrete actions.
|
||||
*
|
||||
* Free tier: 1M events/month (more than enough for current scale).
|
||||
*
|
||||
* PostHog is initialized via PostHogProvider in main.tsx.
|
||||
* This module provides typed event helpers that import posthog-js directly
|
||||
* (valid per PostHog docs for non-component code).
|
||||
*/
|
||||
import posthog from 'posthog-js'
|
||||
|
||||
const POSTHOG_KEY = import.meta.env.VITE_POSTHOG_KEY as string | undefined
|
||||
const POSTHOG_HOST = (import.meta.env.VITE_POSTHOG_HOST as string) || 'https://us.i.posthog.com'
|
||||
|
||||
let initialized = false
|
||||
|
||||
/** Initialize PostHog. Call once on app startup. */
|
||||
export function initAnalytics() {
|
||||
if (initialized || !POSTHOG_KEY) return
|
||||
posthog.init(POSTHOG_KEY, {
|
||||
api_host: POSTHOG_HOST,
|
||||
autocapture: false, // We track events explicitly — no auto-capture
|
||||
capture_pageview: false, // SPA — we'll track meaningful navigations, not every route
|
||||
capture_pageleave: false,
|
||||
persistence: 'localStorage',
|
||||
})
|
||||
initialized = true
|
||||
/** Check if PostHog has been initialized (by the Provider). */
|
||||
function isReady(): boolean {
|
||||
try {
|
||||
// posthog-js sets __loaded when init completes
|
||||
return !!(posthog as unknown as { __loaded?: boolean }).__loaded
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** Identify a logged-in user. Call after login/fetchUser. */
|
||||
@@ -35,7 +30,7 @@ export function identifyUser(user: {
|
||||
is_super_admin?: boolean
|
||||
account_id?: string
|
||||
}) {
|
||||
if (!initialized) return
|
||||
if (!isReady()) return
|
||||
posthog.identify(user.id, {
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
@@ -48,7 +43,7 @@ export function identifyUser(user: {
|
||||
|
||||
/** Reset identity on logout. */
|
||||
export function resetAnalytics() {
|
||||
if (!initialized) return
|
||||
if (!isReady()) return
|
||||
posthog.reset()
|
||||
}
|
||||
|
||||
@@ -56,7 +51,7 @@ export function resetAnalytics() {
|
||||
|
||||
/** Track a named event with optional properties. */
|
||||
function track(event: string, properties?: Record<string, unknown>) {
|
||||
if (!initialized) return
|
||||
if (!isReady()) return
|
||||
posthog.capture(event, properties)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,44 @@
|
||||
import "./instrument"; // Sentry must init before any other imports
|
||||
import { initAnalytics } from './lib/analytics'
|
||||
|
||||
initAnalytics() // PostHog product analytics
|
||||
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { reactErrorHandler } from '@sentry/react'
|
||||
import { HelmetProvider } from 'react-helmet-async'
|
||||
import { PostHogProvider } from '@posthog/react'
|
||||
import { Toaster } from 'sonner'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
|
||||
const posthogOptions = {
|
||||
api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
|
||||
defaults: '2026-01-30',
|
||||
} as const
|
||||
|
||||
createRoot(document.getElementById('root')!, {
|
||||
onUncaughtError: reactErrorHandler(),
|
||||
onCaughtError: reactErrorHandler(),
|
||||
onRecoverableError: reactErrorHandler(),
|
||||
}).render(
|
||||
<StrictMode>
|
||||
<HelmetProvider>
|
||||
{/* Toast notification system - theme syncs automatically via CSS custom properties */}
|
||||
<Toaster
|
||||
position="top-right"
|
||||
expand={false}
|
||||
closeButton
|
||||
visibleToasts={3}
|
||||
gap={8}
|
||||
theme="dark"
|
||||
toastOptions={{
|
||||
className: 'sonner-toast-custom',
|
||||
}}
|
||||
/>
|
||||
<App />
|
||||
</HelmetProvider>
|
||||
<PostHogProvider
|
||||
apiKey={import.meta.env.VITE_PUBLIC_POSTHOG_KEY || ''}
|
||||
options={posthogOptions}
|
||||
>
|
||||
<HelmetProvider>
|
||||
{/* Toast notification system - theme syncs automatically via CSS custom properties */}
|
||||
<Toaster
|
||||
position="top-right"
|
||||
expand={false}
|
||||
closeButton
|
||||
visibleToasts={3}
|
||||
gap={8}
|
||||
theme="dark"
|
||||
toastOptions={{
|
||||
className: 'sonner-toast-custom',
|
||||
}}
|
||||
/>
|
||||
<App />
|
||||
</HelmetProvider>
|
||||
</PostHogProvider>
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user