@@ -6,6 +6,14 @@ import { ExecutorPluginsProvider } from "@executor-js/sdk/client";
66import { OrganizationProvider } from "@executor-js/react/api/organization-context" ;
77import { OrgSlugGate } from "@executor-js/react/multiplayer/org-slug-gate" ;
88import { Toaster } from "@executor-js/react/components/sonner" ;
9+ import { Button } from "@executor-js/react/components/button" ;
10+ import {
11+ Card ,
12+ CardContent ,
13+ CardDescription ,
14+ CardHeader ,
15+ CardTitle ,
16+ } from "@executor-js/react/components/card" ;
917import { AuthProvider , useAuth } from "@executor-js/react/multiplayer/auth-context" ;
1018import { Shell , defaultShellNavItems } from "@executor-js/react/multiplayer/shell" ;
1119import { plugins as clientPlugins } from "virtual:executor/plugins-client" ;
@@ -48,26 +56,72 @@ const Loading = () => (
4856 </ div >
4957) ;
5058
59+ const SetupStatusErrorCard = ( { onRetry } : { onRetry : ( ) => void } ) => (
60+ < div className = "flex min-h-screen items-center justify-center bg-background p-6" >
61+ < Card className = "w-full max-w-md" >
62+ < CardHeader >
63+ < CardTitle > Can't reach the server</ CardTitle >
64+ < CardDescription >
65+ Executor couldn't check this instance's setup state. Make sure the server is running, then
66+ retry.
67+ </ CardDescription >
68+ </ CardHeader >
69+ < CardContent >
70+ < Button type = "button" onClick = { onRetry } >
71+ Retry
72+ </ Button >
73+ </ CardContent >
74+ </ Card >
75+ </ div >
76+ ) ;
77+
5178function AuthGate ( { children } : { children : ReactNode } ) {
5279 const auth = useAuth ( ) ;
5380 // When unauthenticated, decide between first-run setup and sign-in by asking
54- // the server whether the instance still has zero members. `null` = checking.
55- const [ needsSetup , setNeedsSetup ] = useState < boolean | null > ( null ) ;
81+ // the server whether the instance still has zero members.
82+ const [ setupStatus , setSetupStatus ] = useState <
83+ | { state : "checking" ; attempt : number }
84+ | { state : "ready" ; needsSetup : boolean }
85+ | { state : "error" ; attempt : number }
86+ > ( { state : "checking" , attempt : 0 } ) ;
5687 useEffect ( ( ) => {
5788 if ( auth . status !== "unauthenticated" ) return ;
5889 let alive = true ;
59- void fetchNeedsSetup ( ) . then ( ( value ) => {
60- if ( alive ) setNeedsSetup ( value ) ;
61- } ) ;
90+ setSetupStatus ( ( current ) => ( { state : "checking" , attempt : current . attempt } ) ) ;
91+ void fetchNeedsSetup ( ) . then (
92+ ( value ) => {
93+ if ( alive ) setSetupStatus ( { state : "ready" , needsSetup : value } ) ;
94+ } ,
95+ ( ) => {
96+ if ( alive ) {
97+ setSetupStatus ( ( current ) => ( {
98+ state : "error" ,
99+ attempt : current . state === "ready" ? 0 : current . attempt ,
100+ } ) ) ;
101+ }
102+ } ,
103+ ) ;
62104 return ( ) => {
63105 alive = false ;
64106 } ;
65- } , [ auth . status ] ) ;
107+ } , [ auth . status , setupStatus . attempt ] ) ;
66108
67109 if ( auth . status === "loading" ) return < Loading /> ;
68110 if ( auth . status === "unauthenticated" ) {
69- if ( needsSetup === null ) return < Loading /> ;
70- return needsSetup ? < SetupPage /> : < LoginPage /> ;
111+ if ( setupStatus . state === "checking" ) return < Loading /> ;
112+ if ( setupStatus . state === "error" ) {
113+ return (
114+ < SetupStatusErrorCard
115+ onRetry = { ( ) =>
116+ setSetupStatus ( ( current ) => ( {
117+ state : "checking" ,
118+ attempt : current . state === "ready" ? 0 : current . attempt + 1 ,
119+ } ) )
120+ }
121+ />
122+ ) ;
123+ }
124+ return setupStatus . needsSetup ? < SetupPage /> : < LoginPage /> ;
71125 }
72126 return < > { children } </ > ;
73127}
0 commit comments