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:
@@ -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