feat: add PostHog product analytics for key user actions (#110)

Tracks 9 key events: account_created, login_success, flow_viewed,
session_started, session_completed, export_generated, ai_feature_used,
psa_connected, session_shared. Identifies users on login, resets on
logout. Autocapture disabled — only explicit discrete events.

Set VITE_POSTHOG_KEY in environment to enable.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #110.
This commit is contained in:
chihlasm
2026-03-16 18:24:31 -04:00
committed by GitHub
parent 8534dbfb5f
commit 1e8ed09fbd
7 changed files with 550 additions and 7 deletions

View File

@@ -0,0 +1,102 @@
/**
* 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.
*
* Free tier: 1M events/month (more than enough for current scale).
*/
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
}
/** Identify a logged-in user. Call after login/fetchUser. */
export function identifyUser(user: {
id: string
email: string
role?: string
is_super_admin?: boolean
account_id?: string
}) {
if (!initialized) return
posthog.identify(user.id, {
email: user.email,
role: user.role,
is_super_admin: user.is_super_admin,
})
if (user.account_id) {
posthog.group('account', user.account_id)
}
}
/** Reset identity on logout. */
export function resetAnalytics() {
if (!initialized) return
posthog.reset()
}
// ─── Event Tracking ─────────────────────────────────────────────
/** Track a named event with optional properties. */
function track(event: string, properties?: Record<string, unknown>) {
if (!initialized) return
posthog.capture(event, properties)
}
// Activation events
export const analytics = {
/** User created a new account */
accountCreated: () => track('account_created'),
/** User logged in successfully */
loginSuccess: () => track('login_success'),
/** User viewed a flow (opened the navigate page) */
flowViewed: (props: { flow_id: string; flow_type: string; flow_name: string }) =>
track('flow_viewed', props),
/** User started a session */
sessionStarted: (props: { session_id: string; flow_id: string; flow_type: string }) =>
track('session_started', props),
/** User completed a session */
sessionCompleted: (props: { session_id: string; flow_type: string; outcome: string }) =>
track('session_completed', props),
/** User generated an export (markdown, html, PDF, PSA) */
exportGenerated: (props: { session_id: string; format: string }) =>
track('export_generated', props),
/** User used an AI feature */
aiFeatureUsed: (props: { feature: 'copilot' | 'flow_assist' | 'session_to_flow' | 'kb_accelerator' | 'assistant_chat' }) =>
track('ai_feature_used', props),
/** User connected a PSA integration */
psaConnected: (props: { provider: string }) =>
track('psa_connected', props),
/** User created a shared session link */
sessionShared: (props: { session_id: string; visibility: string }) =>
track('session_shared', props),
/** User created a new flow */
flowCreated: (props: { flow_type: string; method: 'manual' | 'ai' | 'import' | 'session_to_flow' }) =>
track('flow_created', props),
}