From cc8b39c4f17db2483bd342fcc1826ba1e59e2f23 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Mon, 16 Mar 2026 19:18:15 -0400 Subject: [PATCH] fix: switch to PostHogProvider per official React integration guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- frontend/package-lock.json | 17 ++++++++++++++ frontend/package.json | 1 + frontend/src/lib/analytics.ts | 37 +++++++++++++---------------- frontend/src/main.tsx | 44 +++++++++++++++++++++-------------- 4 files changed, 60 insertions(+), 39 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c1637f18..6b603c47 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index 6a255463..ae7e4618 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/lib/analytics.ts b/frontend/src/lib/analytics.ts index 820713f9..c9de1ded 100644 --- a/frontend/src/lib/analytics.ts +++ b/frontend/src/lib/analytics.ts @@ -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) { - if (!initialized) return + if (!isReady()) return posthog.capture(event, properties) } diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 3af4502a..74a2c1b9 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -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( - - {/* Toast notification system - theme syncs automatically via CSS custom properties */} - - - + + + {/* Toast notification system - theme syncs automatically via CSS custom properties */} + + + + , ) -- 2.49.1