@@ -19,7 +19,7 @@ import "./index.css";
1919import { useListPageSize } from "./useListPageSize" ;
2020import { AuditSection } from "./AuditSection" ;
2121import { SettingsSection } from "./SettingsSection" ;
22- import { api , withMinRefreshDuration } from "./lib/api" ;
22+ import { api , ApiError , withMinRefreshDuration } from "./lib/api" ;
2323import {
2424 Button ,
2525 EmptyState ,
@@ -55,9 +55,12 @@ type Notice = {
5555
5656type AppSection = "bots" | "audit" | "settings" ;
5757
58+ type AuthState = "checking" | "signed-out" | "signed-in" ;
59+
5860function App ( ) {
5961 const reduceMotion = useReducedMotion ( ) ;
60- const [ isSignedIn , setIsSignedIn ] = useState ( false ) ;
62+ const [ authState , setAuthState ] = useState < AuthState > ( "checking" ) ;
63+ const isSignedIn = authState === "signed-in" ;
6164 const [ password , setPassword ] = useState ( "" ) ;
6265 const [ label , setLabel ] = useState ( "" ) ;
6366 const [ token , setToken ] = useState ( "" ) ;
@@ -100,20 +103,43 @@ function App() {
100103 const data = await api < BotsResponse > ( "/api/bots" ) ;
101104 setBots ( data . bots ) ;
102105 } ) ;
106+ } catch ( error ) {
107+ if ( error instanceof ApiError && error . status === 401 ) {
108+ setAuthState ( "signed-out" ) ;
109+ return ;
110+ }
111+ setNotice ( { kind : "error" , text : "Refresh failed." } ) ;
103112 } finally {
104113 setIsRefreshingBots ( false ) ;
105114 }
106115 } , [ ] ) ;
107116
117+ useEffect ( ( ) => {
118+ let cancelled = false ;
119+
120+ void api < void > ( "/api/session" )
121+ . then ( ( ) => {
122+ if ( ! cancelled ) {
123+ setAuthState ( "signed-in" ) ;
124+ }
125+ } )
126+ . catch ( ( ) => {
127+ if ( ! cancelled ) {
128+ setAuthState ( "signed-out" ) ;
129+ }
130+ } ) ;
131+
132+ return ( ) => {
133+ cancelled = true ;
134+ } ;
135+ } , [ ] ) ;
108136
109137 useEffect ( ( ) => {
110138 if ( ! isSignedIn ) {
111139 return ;
112140 }
113141
114- void loadBots ( ) . catch ( ( ) => {
115- setNotice ( { kind : "error" , text : "Refresh failed." } ) ;
116- } ) ;
142+ void loadBots ( ) ;
117143 } , [ isSignedIn , loadBots ] ) ;
118144
119145
@@ -128,7 +154,7 @@ function App() {
128154 body : JSON . stringify ( { password } ) ,
129155 } ) ;
130156 setPassword ( "" ) ;
131- setIsSignedIn ( true ) ;
157+ setAuthState ( "signed-in" ) ;
132158 setNotice ( { kind : "idle" , text : "" } ) ;
133159 } catch {
134160 setNotice ( { kind : "error" , text : "Wrong password." } ) ;
@@ -180,9 +206,17 @@ function App() {
180206
181207 const pageTransition = reduceMotion ? { duration : 0 } : { duration : 0.4 , ease : [ 0.25 , 0.1 , 0.25 , 1 ] as const } ;
182208
209+ if ( authState === "checking" ) {
210+ return (
211+ < motion . div className = "flex min-h-dvh items-center justify-center p-6" aria-busy aria-label = "Checking session" >
212+ < Loader2 size = { 28 } className = "animate-spin text-zinc-500" aria-hidden />
213+ </ motion . div >
214+ ) ;
215+ }
216+
183217 return (
184218 < AnimatePresence mode = "wait" >
185- { ! isSignedIn ? (
219+ { authState === "signed-out" ? (
186220 < motion . div
187221 key = "login"
188222 className = "flex min-h-dvh items-center justify-center p-6"
0 commit comments