Files
Termix/src/ui/Mobile/MobileApp.tsx
2025-09-11 22:01:14 -05:00

196 lines
7.1 KiB
TypeScript

import React, {useRef, FC, useState, useEffect} 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, getCookie} from "@/ui/main-axios.ts";
import {HomepageAuth} from "@/ui/Mobile/Homepage/HomepageAuth.tsx";
import {useTranslation} from "react-i18next";
const AppContent: FC = () => {
const {t} = useTranslation();
const {tabs, currentTab, getTab, removeTab} = useTabs();
const [isSidebarOpen, setIsSidebarOpen] = React.useState(true);
const [ready, setReady] = React.useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [username, setUsername] = useState<string | null>(null);
const [isAdmin, setIsAdmin] = useState(false);
const [authLoading, setAuthLoading] = useState(true);
useEffect(() => {
const checkAuth = () => {
const jwt = getCookie("jwt");
if (jwt) {
setAuthLoading(true);
getUserInfo()
.then((meRes) => {
setIsAuthenticated(true);
setIsAdmin(!!meRes.is_admin);
setUsername(meRes.username || null);
})
.catch((err) => {
setIsAuthenticated(false);
setIsAdmin(false);
setUsername(null);
document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
})
.finally(() => setAuthLoading(false));
} else {
setIsAuthenticated(false);
setIsAdmin(false);
setUsername(null);
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 (
<div className="h-screen w-screen flex items-center justify-center bg-dark-bg-darkest">
<p className="text-white">{t('common.loading')}</p>
</div>
)
}
if (!isAuthenticated) {
return (
<div className="h-screen w-screen flex items-center justify-center bg-dark-bg p-4">
<HomepageAuth
setLoggedIn={setIsAuthenticated}
setIsAdmin={setIsAdmin}
setUsername={setUsername}
setUserId={(id) => {
}}
loggedIn={isAuthenticated}
authLoading={authLoading}
dbError={null}
setDbError={(err) => {
}}
onAuthSuccess={handleAuthSuccess}
/>
</div>
)
}
return (
<div className="h-screen w-screen flex flex-col bg-dark-bg-darkest overflow-y-hidden overflow-x-hidden relative">
<div className="flex-1 min-h-0 relative">
{tabs.map(tab => (
<div
key={tab.id}
className={`absolute inset-0 mb-2 ${tab.id === currentTab ? 'visible' : 'invisible'} ${ready ? 'opacity-100' : 'opacity-0'}`}
>
<Terminal
ref={tab.terminalRef}
hostConfig={tab.hostConfig}
isVisible={tab.id === currentTab}
onClose={() => removeTab(tab.id)}
/>
</div>
))}
{tabs.length === 0 && (
<div className="flex flex-col items-center justify-center h-full text-white gap-3 px-4 text-center">
<h1 className="text-lg font-semibold">
{t('mobile.selectHostToStart')}
</h1>
<p className="text-sm text-gray-300 max-w-xs">
{t('mobile.limitedSupportMessage')}
</p>
</div>
)}
</div>
{currentTab &&
<div className="mb-1 z-10">
<TerminalKeyboard onSendInput={handleKeyboardInput} onLayoutChange={handleKeyboardLayoutChange}/>
</div>
}
<BottomNavbar
onSidebarOpenClick={() => setIsSidebarOpen(true)}
/>
{isSidebarOpen && (
<div
className="absolute inset-0 bg-black/30 backdrop-blur-sm z-10"
onClick={() => setIsSidebarOpen(false)}
/>
)}
<div className="absolute top-0 left-0 h-full z-20 pointer-events-none">
<div onClick={(e) => {
e.stopPropagation();
}} className="pointer-events-auto">
<LeftSidebar
isSidebarOpen={isSidebarOpen}
setIsSidebarOpen={setIsSidebarOpen}
onHostConnect={closeSidebar}
disabled={!isAuthenticated || authLoading}
username={username}
/>
</div>
</div>
</div>
);
}
export const MobileApp: FC = () => {
return (
<TabProvider>
<AppContent/>
</TabProvider>
);
}