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

@@ -31,6 +31,7 @@ import { TicketLinkIndicator } from '@/components/session/TicketLinkIndicator'
import { UpdateTicketModal } from '@/components/session/UpdateTicketModal'
import type { PSATicketInfo } from '@/types/integrations'
import { addRecentFlow } from '@/lib/recentFlows'
import { analytics } from '@/lib/analytics'
import { useTicketContext } from '@/hooks/useTicketContext'
import { TicketContextPanel } from '@/components/session/TicketContextPanel'
@@ -227,6 +228,7 @@ export function ProceduralNavigationPage() {
}
setTree(treeData)
addRecentFlow({ id: treeData.id, name: treeData.name, tree_type: treeData.tree_type })
analytics.flowViewed({ flow_id: treeData.id, flow_type: treeData.tree_type, flow_name: treeData.name })
// If resuming an existing session
if (locationState?.sessionId) {
@@ -252,6 +254,7 @@ export function ProceduralNavigationPage() {
session_variables: Object.keys(variables).length > 0 ? variables : undefined,
})
setSession(newSession)
analytics.sessionStarted({ session_id: newSession.id, flow_id: id, flow_type: treeData?.tree_type || 'procedural' })
setSessionVariables(variables)
// Initialize step states — use passed treeData since `tree` state may not have committed yet
@@ -401,6 +404,7 @@ export function ProceduralNavigationPage() {
})
setCompletedAt(completedTime)
setIsComplete(true)
analytics.sessionCompleted({ session_id: session.id, flow_type: tree?.tree_type || 'procedural', outcome: 'resolved' })
if (!hasBeenRated(session.id)) {
setShowCsatModal(true)
}

View File

@@ -28,6 +28,7 @@ import { TicketLinkIndicator } from '@/components/session/TicketLinkIndicator'
import { UpdateTicketModal } from '@/components/session/UpdateTicketModal'
import type { PSATicketInfo } from '@/types/integrations'
import { addRecentFlow } from '@/lib/recentFlows'
import { analytics } from '@/lib/analytics'
import { useTicketContext } from '@/hooks/useTicketContext'
import { TicketContextPanel } from '@/components/session/TicketContextPanel'
@@ -335,6 +336,7 @@ export function TreeNavigationPage() {
setTree(treeData)
addRecentFlow({ id: treeData.id, name: treeData.name, tree_type: treeData.tree_type })
analytics.flowViewed({ flow_id: treeData.id, flow_type: treeData.tree_type, flow_name: treeData.name })
// If resuming a session
if (locationState?.sessionId) {
@@ -367,6 +369,7 @@ export function TreeNavigationPage() {
client_name: clientName || undefined,
})
setSession(newSession)
analytics.sessionStarted({ session_id: newSession.id, flow_id: tree.id, flow_type: tree.tree_type })
// Initialize currentNodeId to the tree's actual root (may not be 'root')
const rootId = tree.tree_structure?.id || 'root'
setCurrentNodeId(rootId)
@@ -512,6 +515,7 @@ export function TreeNavigationPage() {
}
await sessionsApi.complete(session.id, data)
analytics.sessionCompleted({ session_id: session.id, flow_type: tree?.tree_type || 'troubleshooting', outcome: data.outcome })
setShowOutcomeModal(false)
setPendingCompletionDecision(null)