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" ;
33import { Toaster } from "@/components/ui/toaster" ;
44import { Toaster as Sonner } from "@/components/ui/sonner" ;
55import { TooltipProvider } from "@/components/ui/tooltip" ;
@@ -14,6 +14,7 @@ import { ThemeProvider } from "@/components/ThemeProvider";
1414import { useSessionStorageSync } from "@/hooks/use-session-storage-sync" ;
1515import { Loader2 } from "lucide-react" ;
1616import { OfflineIndicator } from "@/components/OfflineIndicator" ;
17+ import { captureException , captureProductEvent } from "@/lib/observability" ;
1718
1819// Lazy load all pages for code splitting
1920const LandingPage = lazy ( ( ) => import ( "./pages/LandingPage" ) ) ;
@@ -40,6 +41,40 @@ const NotFound = lazy(() => import("./pages/NotFound"));
4041
4142const 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+
58+ const TOOL_BY_ROUTE = new Map < string , ToolName > ( [
59+ [ "/app/generate" , "generate" ] ,
60+ [ "/app/attack" , "attack" ] ,
61+ [ "/app/brainstorm" , "brainstorm" ] ,
62+ [ "/app/factoids" , "factoids" ] ,
63+ [ "/app/ask" , "ask" ] ,
64+ [ "/app/crossfire" , "crossfire" ] ,
65+ [ "/app/history" , "history" ] ,
66+ [ "/app/library" , "library" ] ,
67+ [ "/app/timer" , "timer" ] ,
68+ [ "/app/sparring" , "sparring" ] ,
69+ [ "/app/flowchart" , "flowchart" ] ,
70+ [ "/app/settings" , "settings" ] ,
71+ ] ) ;
72+
73+ function getToolForPathname ( pathname : string ) : ToolName | undefined {
74+ const normalizedPathname = pathname . length > 1 ? pathname . replace ( / \/ + $ / , "" ) : pathname ;
75+ return TOOL_BY_ROUTE . get ( normalizedPathname ) ;
76+ }
77+
4378// Loading fallback component
4479const PageLoader = ( ) => (
4580 < div className = "motion-loading-shell flex min-h-[60vh] items-center justify-center" >
@@ -55,7 +90,7 @@ const PageLoader = () => (
5590 </ div >
5691) ;
5792
58- class RouteErrorBoundary extends Component < { children : ReactNode } , { failed : boolean } > {
93+ export class RouteErrorBoundary extends Component < { children : ReactNode } , { failed : boolean } > {
5994 state = { failed : false } ;
6095
6196 static getDerivedStateFromError ( ) {
@@ -64,6 +99,7 @@ class RouteErrorBoundary extends Component<{ children: ReactNode }, { failed: bo
6499
65100 componentDidCatch ( error : Error , info : ErrorInfo ) {
66101 console . error ( 'Route rendering failed' , error , info ) ;
102+ captureException ( new Error ( "Route rendering failed" ) , { boundary : "route" } ) ;
67103 }
68104
69105 render ( ) {
@@ -81,6 +117,17 @@ class RouteErrorBoundary extends Component<{ children: ReactNode }, { failed: bo
81117 }
82118}
83119
120+ export function ToolRouteAnalytics ( ) : null {
121+ const location = useLocation ( ) ;
122+
123+ useEffect ( ( ) => {
124+ const tool = getToolForPathname ( location . pathname ) ;
125+ if ( tool ) captureProductEvent ( "tool_opened" , { tool } ) ;
126+ } , [ location . pathname ] ) ;
127+
128+ return null ;
129+ }
130+
84131function AnimatedRoutes ( ) {
85132 const location = useLocation ( ) ;
86133 const shouldReduceMotion = useReducedMotion ( ) ;
@@ -109,6 +156,7 @@ function AnimatedRoutes() {
109156 < Route
110157 element = {
111158 < RequireAuth >
159+ < ToolRouteAnalytics />
112160 < OnboardingTour />
113161 < AppLayout />
114162 </ RequireAuth >
0 commit comments