fix: switch to PostHogProvider per official React guide #112

Merged
chihlasm merged 1 commits from fix/posthog-react-provider into main 2026-03-16 23:18:31 +00:00
4 changed files with 60 additions and 39 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -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)
}

View File

@@ -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>,
)