11import { useEffect , useState } from 'react' ;
2- import { Route , Routes } from 'react-router-dom' ;
2+ import { Route , Routes , useNavigate } from 'react-router-dom' ;
33import { useQueryClient } from '@tanstack/react-query' ;
4- import { getUserId } from './lib/config' ;
5- import { graphqlQuery , queries } from './lib/graphql-client' ;
64import { User , UserProvider } from './context/user-context' ;
75import { useTeamContext } from './context/team-context' ;
86import { Layout } from './components/layout/layout' ;
@@ -17,22 +15,80 @@ import { AgentDetailPage } from './pages/agents/[id]';
1715import { SessionDetailPage } from './pages/sessions/[id]' ;
1816import { DatasetsPage } from './pages/datasets' ;
1917import { ArtifactsPage } from './pages/artifacts' ;
18+ import { LoginPage } from './pages/login' ;
2019import type { Team } from './types' ;
2120
21+ // Helper to decode JWT
22+ function decodeJWT ( token : string ) : any {
23+ try {
24+ const base64Url = token . split ( '.' ) [ 1 ] ;
25+ const base64 = base64Url . replace ( / - / g, '+' ) . replace ( / _ / g, '/' ) ;
26+ const jsonPayload = decodeURIComponent (
27+ atob ( base64 )
28+ . split ( '' )
29+ . map ( ( c ) => '%' + ( '00' + c . charCodeAt ( 0 ) . toString ( 16 ) ) . slice ( - 2 ) )
30+ . join ( '' )
31+ ) ;
32+ return JSON . parse ( jsonPayload ) ;
33+ } catch {
34+ return null ;
35+ }
36+ }
37+
2238function App ( ) {
2339 const [ currentUser , setCurrentUser ] = useState < User | null > ( null ) ;
2440 const [ loading , setLoading ] = useState ( true ) ;
2541 const [ error , setError ] = useState < Error | null > ( null ) ;
2642 const { selectedTeamId, setSelectedTeamId } = useTeamContext ( ) ;
2743 const queryClient = useQueryClient ( ) ;
44+ const navigate = useNavigate ( ) ;
2845
2946 useEffect ( ( ) => {
3047 async function initialize ( ) {
3148 try {
32- // Step 1: Get userId and orgId from config
33- const config = await fetch ( '/api/config' ) . then ( res => res . json ( ) ) ;
34- const userId = config . userId ;
35- const orgId = config . orgId ;
49+ // Check for JWT token
50+ const token = localStorage . getItem ( 'alphatrion_token' ) ;
51+ const storedUser = localStorage . getItem ( 'alphatrion_user' ) ;
52+
53+ console . log ( 'Initializing app...' , {
54+ hasToken : ! ! token ,
55+ hasUser : ! ! storedUser ,
56+ currentPath : window . location . pathname
57+ } ) ;
58+
59+ if ( ! token || ! storedUser ) {
60+ // No token, redirect to login (only if not already there)
61+ console . log ( 'No token or user, redirecting to login' ) ;
62+ if ( window . location . pathname !== '/login' ) {
63+ navigate ( '/login' ) ;
64+ }
65+ setLoading ( false ) ;
66+ return ;
67+ }
68+
69+ // Decode JWT to check expiration
70+ const payload = decodeJWT ( token ) ;
71+ const isExpired = ! payload || payload . exp * 1000 < Date . now ( ) ;
72+ console . log ( 'Token validation:' , { isExpired, exp : payload ?. exp } ) ;
73+
74+ if ( isExpired ) {
75+ // Token expired, clear and redirect to login
76+ console . log ( 'Token expired, clearing and redirecting' ) ;
77+ localStorage . removeItem ( 'alphatrion_token' ) ;
78+ localStorage . removeItem ( 'alphatrion_user' ) ;
79+ if ( window . location . pathname !== '/login' ) {
80+ navigate ( '/login' ) ;
81+ }
82+ setLoading ( false ) ;
83+ return ;
84+ }
85+
86+ console . log ( 'User authenticated, loading dashboard' ) ;
87+
88+ // Parse stored user info (already has teams from login response)
89+ const user = JSON . parse ( storedUser ) ;
90+ const userId = user . id ;
91+ const orgId = payload . org_id ;
3692
3793 // Check if user ID has changed from previous session
3894 const previousUserId = localStorage . getItem ( 'alphatrion_user_id' ) ;
@@ -41,54 +97,32 @@ function App() {
4197 console . log ( 'User ID changed, clearing cache' ) ;
4298 queryClient . clear ( ) ;
4399 }
100+
44101 // Store current user ID and org ID for GraphQL headers
45102 localStorage . setItem ( 'alphatrion_user_id' , userId ) ;
46- if ( orgId ) {
47- localStorage . setItem ( 'alphatrion_org_id' , orgId ) ;
48- }
49-
50- // Step 2: Query user information
51- const data = await graphqlQuery < { user : User } > (
52- queries . getUser ,
53- { id : userId }
54- ) ;
55-
56- if ( ! data . user ) {
57- throw new Error ( `User with ID ${ userId } not found` ) ;
58- }
103+ localStorage . setItem ( 'alphatrion_org_id' , orgId ) ;
59104
60- setCurrentUser ( data . user ) ;
105+ // Use stored user info (already complete from login)
106+ setCurrentUser ( user ) ;
61107
62- // Step 3: Query user's teams and auto-select team
63- const teamsData = await graphqlQuery < { teams : Team [ ] } > (
64- queries . listTeams
65- ) ;
66-
67- if ( teamsData . teams && teamsData . teams . length > 0 ) {
68- // Check if this user has a saved team preference in localStorage
108+ // Handle team selection
109+ if ( user . teams && user . teams . length > 0 ) {
110+ // Check for saved team preference in localStorage
69111 const teamKey = `alphatrion_selected_team_${ userId } ` ;
70112 const savedTeamId = localStorage . getItem ( teamKey ) ;
71113
72- let selectedTeam : Team ;
114+ let selectedTeamId : string ;
73115
74116 if ( savedTeamId ) {
75- // Verify saved team still exists in user's teams
76- const savedTeam = teamsData . teams . find ( t => t . id === savedTeamId ) ;
77- if ( savedTeam ) {
78- selectedTeam = savedTeam ;
79- } else {
80- // Saved team not found, use first team
81- selectedTeam = teamsData . teams [ 0 ] ;
82- }
117+ const savedTeam = user . teams . find ( ( t : any ) => t . id === savedTeamId ) ;
118+ selectedTeamId = savedTeam ? savedTeamId : user . teams [ 0 ] . id ;
83119 } else {
84120 // No saved team, use first team
85- selectedTeam = teamsData . teams [ 0 ] ;
121+ selectedTeamId = user . teams [ 0 ] . id ;
86122 }
87123
88- // Store team_id for UI (org_id already set from config)
89- localStorage . setItem ( 'alphatrion_team_id' , selectedTeam . id ) ;
90-
91- setSelectedTeamId ( selectedTeam . id , userId ) ;
124+ localStorage . setItem ( 'alphatrion_team_id' , selectedTeamId ) ;
125+ setSelectedTeamId ( selectedTeamId , userId ) ;
92126 }
93127 } catch ( err ) {
94128 console . error ( 'Failed to initialize app:' , err ) ;
@@ -99,7 +133,7 @@ function App() {
99133 }
100134
101135 initialize ( ) ;
102- } , [ setSelectedTeamId , queryClient ] ) ;
136+ } , [ setSelectedTeamId , queryClient , navigate ] ) ;
103137
104138 if ( loading ) {
105139 return (
@@ -117,31 +151,28 @@ function App() {
117151 < div className = "flex h-screen items-center justify-center" >
118152 < div className = "text-center max-w-md" >
119153 < h1 className = "text-2xl font-bold text-red-600 mb-4" >
120- Error Loading User
154+ Error Initializing Dashboard
121155 </ h1 >
122156 < p className = "text-gray-700 mb-2" > { error . message } </ p >
123157 < p className = "text-gray-500 text-sm" >
124- Please verify :
158+ Please try :
125159 </ p >
126160 < ul className = "text-gray-500 text-sm text-left mt-2 space-y-1" >
127- < li > • The user ID exists in the database </ li >
128- < li > • The backend server is running</ li >
129- < li > • The dashboard was started with correct --user-id flag </ li >
161+ < li > • Clear browser cache and localStorage </ li >
162+ < li > • Verify the backend server is running</ li >
163+ < li > • < button onClick = { ( ) => { localStorage . clear ( ) ; window . location . href = '/login' ; } } className = "text-blue-600 underline" > Logout and login again </ button > </ li >
130164 </ ul >
131165 </ div >
132166 </ div >
133167 ) ;
134168 }
135169
136- if ( ! currentUser ) {
137- return null ;
138- }
139-
140170 return (
141171 < div className = "h-full" >
142- < UserProvider user = { currentUser } >
143- < Routes >
144- < Route path = "/" element = { < Layout /> } >
172+ < Routes >
173+ < Route path = "/login" element = { < LoginPage /> } />
174+ { currentUser ? (
175+ < Route path = "/" element = { < UserProvider user = { currentUser } > < Layout /> </ UserProvider > } >
145176 < Route index element = { < DashboardPage /> } />
146177 < Route path = "experiments" >
147178 < Route index element = { < ExperimentsPage /> } />
@@ -162,8 +193,8 @@ function App() {
162193 < Route path = "datasets" element = { < DatasetsPage /> } />
163194 < Route path = "artifacts" element = { < ArtifactsPage /> } />
164195 </ Route >
165- </ Routes >
166- </ UserProvider >
196+ ) : null }
197+ </ Routes >
167198 </ div >
168199 ) ;
169200}
0 commit comments