import { useEffect, useState } from "react"; import { QueryClientProvider } from "@tanstack/react-query"; import { RouterProvider } from "@tanstack/react-router"; import { fetchMe, type SessionUser } from "./api.js"; import { Login } from "./Login.js"; import { ConnectScreen } from "./ConnectScreen.js"; import { queryClient } from "./lib/query.js"; import { setLanguage } from "./lib/i18n/index.js"; import { applyTheme, applyFontScale } from "./lib/theme.js"; import { router } from "./router.js"; import { initApiBase, inTauri } from "./lib/origin.js"; // App root: bootstraps the session (cookie-based, from /api/auth/me), then hands // off to TanStack Router inside the QueryClient provider. The router renders the // terminal chrome + screens; auth gating stays here (Login until signed in), and // the signed-in user flows into the router context for role-based route guards. // See wiki/entities/react-vite-spa.md and local-jwt-auth.md. // // Desktop shell only: BEFORE any of that, the backend origin itself must be // known — the same installer is used at every booth (see lib/origin.ts / // backend-config.ts), so on first launch (or after the operator clears it) // there is no server to call fetchMe() against yet. ConnectScreen gates that; // a browser build always has a same-origin backend, so `needsConnect` is // always false there and this is skipped entirely. export function App() { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [needsConnect, setNeedsConnect] = useState(false); useEffect(() => { initApiBase().then((saved) => { if (inTauri() && !saved) { setNeedsConnect(true); setLoading(false); return; } fetchMe() .then(setUser) .finally(() => setLoading(false)); }); }, []); // Route-context consumers (RootLayout's nav, route beforeLoad guards) only re-read // the router context on navigation — NOT when this `user` state changes. So after // any session refresh (login, profile edit, a venue-module flip in Setup → Site) // re-validate the current matches once React has committed the new context. useEffect(() => { if (user) void router.invalidate(); }, [user]); // Apply the signed-in user's preferred language + theme + font scale whenever they // resolve/change (login, bootstrap, or a toggle). Albanian + dark + 100% are the defaults // before auth resolves; on logout, fall back so the Login screen is consistent. useEffect(() => { if (user) { setLanguage(user.language); applyTheme(user.theme); applyFontScale(user.fontScale); } else { applyTheme("dark"); applyFontScale(100); } }, [user]); if (loading) { return
loading…
; } if (needsConnect) { return ( { setNeedsConnect(false); setLoading(true); fetchMe() .then(setUser) .finally(() => setLoading(false)); }} /> ); } if (!user) { return ( ); } return ( ); }