Skip to content

Commit 02e5fcd

Browse files
committed
feat: instrument product analytics and AI errors
1 parent acaac0d commit 02e5fcd

14 files changed

Lines changed: 1263 additions & 103 deletions

‎src/App.tsx‎

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// src/App.tsx
2-
import { Component, Suspense, lazy, type ErrorInfo, type ReactNode } from "react";
2+
import { Component, Suspense, lazy, useEffect, type ErrorInfo, type ReactNode } from "react";
33
import { Toaster } from "@/components/ui/toaster";
44
import { Toaster as Sonner } from "@/components/ui/sonner";
55
import { TooltipProvider } from "@/components/ui/tooltip";
@@ -14,6 +14,7 @@ import { ThemeProvider } from "@/components/ThemeProvider";
1414
import { useSessionStorageSync } from "@/hooks/use-session-storage-sync";
1515
import { Loader2 } from "lucide-react";
1616
import { OfflineIndicator } from "@/components/OfflineIndicator";
17+
import { captureException, captureProductEvent } from "@/lib/observability";
1718

1819
// Lazy load all pages for code splitting
1920
const LandingPage = lazy(() => import("./pages/LandingPage"));
@@ -40,6 +41,41 @@ const NotFound = lazy(() => import("./pages/NotFound"));
4041

4142
const queryClient = new QueryClient();
4243

44+
type ToolName =
45+
| "generate"
46+
| "attack"
47+
| "brainstorm"
48+
| "factoids"
49+
| "ask"
50+
| "crossfire"
51+
| "history"
52+
| "library"
53+
| "timer"
54+
| "sparring"
55+
| "flowchart"
56+
| "settings"
57+
| "admin";
58+
59+
const TOOL_BY_ROUTE = new Map<string, ToolName>([
60+
["/app/generate", "generate"],
61+
["/app/attack", "attack"],
62+
["/app/brainstorm", "brainstorm"],
63+
["/app/factoids", "factoids"],
64+
["/app/ask", "ask"],
65+
["/app/crossfire", "crossfire"],
66+
["/app/history", "history"],
67+
["/app/library", "library"],
68+
["/app/timer", "timer"],
69+
["/app/sparring", "sparring"],
70+
["/app/flowchart", "flowchart"],
71+
["/app/settings", "settings"],
72+
["/app/admin", "admin"],
73+
]);
74+
75+
function getToolForPathname(pathname: string): ToolName | undefined {
76+
return TOOL_BY_ROUTE.get(pathname);
77+
}
78+
4379
// Loading fallback component
4480
const PageLoader = () => (
4581
<div className="motion-loading-shell flex min-h-[60vh] items-center justify-center">
@@ -55,7 +91,7 @@ const PageLoader = () => (
5591
</div>
5692
);
5793

58-
class RouteErrorBoundary extends Component<{ children: ReactNode }, { failed: boolean }> {
94+
export class RouteErrorBoundary extends Component<{ children: ReactNode }, { failed: boolean }> {
5995
state = { failed: false };
6096

6197
static getDerivedStateFromError() {
@@ -64,6 +100,7 @@ class RouteErrorBoundary extends Component<{ children: ReactNode }, { failed: bo
64100

65101
componentDidCatch(error: Error, info: ErrorInfo) {
66102
console.error('Route rendering failed', error, info);
103+
captureException(error, { boundary: "route" });
67104
}
68105

69106
render() {
@@ -81,6 +118,17 @@ class RouteErrorBoundary extends Component<{ children: ReactNode }, { failed: bo
81118
}
82119
}
83120

121+
export function ToolRouteAnalytics(): null {
122+
const location = useLocation();
123+
124+
useEffect(() => {
125+
const tool = getToolForPathname(location.pathname);
126+
if (tool) captureProductEvent("tool_opened", { tool });
127+
}, [location.pathname]);
128+
129+
return null;
130+
}
131+
84132
function AnimatedRoutes() {
85133
const location = useLocation();
86134
const shouldReduceMotion = useReducedMotion();
@@ -96,6 +144,7 @@ function AnimatedRoutes() {
96144
transition={{ duration: shouldReduceMotion ? 0 : 0.16, ease: [0.22, 1, 0.36, 1] }}
97145
className="min-h-screen motion-reduce:transform-none"
98146
>
147+
<ToolRouteAnalytics />
99148
<Routes location={location}>
100149
{/* Public Routes */}
101150
<Route path="/" element={<LandingPage />} />

0 commit comments

Comments
 (0)