import React, { useState, useEffect, type FC } from "react"; import { Terminal } from "@/ui/mobile/apps/terminal/Terminal.tsx"; import { TerminalKeyboard } from "@/ui/mobile/apps/terminal/TerminalKeyboard.tsx"; import { BottomNavbar } from "@/ui/mobile/navigation/BottomNavbar.tsx"; import { LeftSidebar } from "@/ui/mobile/navigation/LeftSidebar.tsx"; import { TabProvider, useTabs, } from "@/ui/mobile/navigation/tabs/TabContext.tsx"; import { getUserInfo } from "@/ui/main-axios.ts"; import { Auth } from "@/ui/mobile/authentication/Auth.tsx"; import { useTranslation } from "react-i18next"; import { Toaster } from "@/components/ui/sonner.tsx"; function isReactNativeWebView(): boolean { return typeof window !== "undefined" && !!(window as any).ReactNativeWebView; } const AppContent: FC = () => { const { t } = useTranslation(); const { tabs, currentTab, getTab } = useTabs(); const [isSidebarOpen, setIsSidebarOpen] = React.useState(true); const [ready, setReady] = React.useState(true); const [isAuthenticated, setIsAuthenticated] = useState(false); const [username, setUsername] = useState(null); const [, setIsAdmin] = useState(false); const [authLoading, setAuthLoading] = useState(true); useEffect(() => { const checkAuth = () => { setAuthLoading(true); // Don't optimistically set isAuthenticated before checking getUserInfo() .then((meRes) => { if (typeof meRes === "string" || !meRes.username) { setIsAuthenticated(false); setIsAdmin(false); setUsername(null); // Clear invalid token localStorage.removeItem("jwt"); } else { setIsAuthenticated(true); setIsAdmin(!!meRes.is_admin); setUsername(meRes.username || null); } }) .catch((err) => { setIsAuthenticated(false); setIsAdmin(false); setUsername(null); // Clear invalid token on any auth error localStorage.removeItem("jwt"); const errorCode = err?.response?.data?.code; if (errorCode === "SESSION_EXPIRED") { console.warn("Session expired - please log in again"); } }) .finally(() => setAuthLoading(false)); }; checkAuth(); const handleStorageChange = () => checkAuth(); window.addEventListener("storage", handleStorageChange); return () => window.removeEventListener("storage", handleStorageChange); }, []); useEffect(() => { const interval = setInterval(() => { fitCurrentTerminal(); }, 2000); return () => clearInterval(interval); }, []); const handleAuthSuccess = (authData: { isAdmin: boolean; username: string | null; userId: string | null; }) => { setIsAuthenticated(true); setIsAdmin(authData.isAdmin); setUsername(authData.username); }; const fitCurrentTerminal = () => { const tab = getTab(currentTab as number); if (tab && tab.terminalRef?.current?.fit) { tab.terminalRef.current.fit(); } }; React.useEffect(() => { if (tabs.length > 0) { setReady(false); requestAnimationFrame(() => { requestAnimationFrame(() => { fitCurrentTerminal(); setReady(true); }); }); } }, [currentTab]); const closeSidebar = () => setIsSidebarOpen(false); const handleKeyboardLayoutChange = () => { fitCurrentTerminal(); }; function handleKeyboardInput(input: string) { const currentTerminalTab = getTab(currentTab as number); if ( currentTerminalTab && currentTerminalTab.terminalRef?.current?.sendInput ) { currentTerminalTab.terminalRef.current.sendInput(input); } } if (authLoading) { return (

{t("common.loading")}

); } if (!isAuthenticated || isReactNativeWebView()) { return (
{}} loggedIn={isAuthenticated} authLoading={authLoading} dbError={null} setDbError={() => {}} onAuthSuccess={handleAuthSuccess} />
); } return (
{tabs.map((tab) => (
))} {tabs.length === 0 && (

{t("mobile.selectHostToStart")}

{t("mobile.limitedSupportMessage")}

)}
{currentTab && (
)} setIsSidebarOpen(true)} /> {isSidebarOpen && (
setIsSidebarOpen(false)} /> )}
{ e.stopPropagation(); }} className="pointer-events-auto" >
); }; export const MobileApp: FC = () => { return ( ); };