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,41 @@ 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+ | "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
4480const 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+
84132function 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