import { useCallback, useEffect, useMemo } from 'react' import { Outlet, useLocation, useMatchRoute } from '@tanstack/react-router' import { useQueryClient } from '@tanstack/react-query' import { getTelegramWebApp } from '@/hooks/useTelegram' import { initializeTheme } from '@/hooks/useTheme' import { useAuth } from '@/hooks/useAuth' import { useAuthSource } from '@/hooks/useAuthSource' import { useSSE } from '@/hooks/useSSE' import { queryKeys } from '@/lib/query-keys' import { AppContextProvider } from '@/lib/app-context' import { useAppGoBack } from '@/hooks/useAppGoBack' import { LoginPrompt } from '@/components/LoginPrompt' import { InstallPrompt } from '@/components/InstallPrompt' import { OfflineBanner } from '@/components/OfflineBanner' export function App() { const { authSource, isLoading: isAuthSourceLoading, setAccessToken } = useAuthSource() const { token, api, isLoading: isAuthLoading, error: authError } = useAuth(authSource) const goBack = useAppGoBack() const pathname = useLocation({ select: (location) => location.pathname }) const matchRoute = useMatchRoute() useEffect(() => { const tg = getTelegramWebApp() tg?.ready() tg?.expand() initializeTheme() }, []) useEffect(() => { const preventDefault = (event: Event) => { event.preventDefault() } const onWheel = (event: WheelEvent) => { if (event.ctrlKey) { event.preventDefault() } } const onKeyDown = (event: KeyboardEvent) => { const modifier = event.ctrlKey || event.metaKey if (!modifier) return if (event.key === '+' || event.key === '-' || event.key === '=' || event.key === '0') { event.preventDefault() } } document.addEventListener('gesturestart', preventDefault as EventListener, { passive: false }) document.addEventListener('gesturechange', preventDefault as EventListener, { passive: false }) document.addEventListener('gestureend', preventDefault as EventListener, { passive: false }) window.addEventListener('wheel', onWheel, { passive: false }) window.addEventListener('keydown', onKeyDown) return () => { document.removeEventListener('gesturestart', preventDefault as EventListener) document.removeEventListener('gesturechange', preventDefault as EventListener) document.removeEventListener('gestureend', preventDefault as EventListener) window.removeEventListener('wheel', onWheel) window.removeEventListener('keydown', onKeyDown) } }, []) useEffect(() => { const tg = getTelegramWebApp() const backButton = tg?.BackButton if (!backButton) return if (pathname === '/' || pathname === '/sessions') { backButton.offClick(goBack) backButton.hide() return } backButton.show() backButton.onClick(goBack) return () => { backButton.offClick(goBack) backButton.hide() } }, [goBack, pathname]) const queryClient = useQueryClient() const sessionMatch = matchRoute({ to: '/sessions/$sessionId' }) const spawnMatch = matchRoute({ to: '/machines/$machineId/spawn' }) const selectedSessionId = sessionMatch ? sessionMatch.sessionId : null const spawnMachineId = spawnMatch ? spawnMatch.machineId : null const handleSseConnect = useCallback(() => { void queryClient.invalidateQueries({ queryKey: queryKeys.sessions }) if (selectedSessionId) { void queryClient.invalidateQueries({ queryKey: queryKeys.session(selectedSessionId) }) void queryClient.invalidateQueries({ queryKey: queryKeys.messages(selectedSessionId) }) } }, [queryClient, selectedSessionId]) const handleSseEvent = useCallback(() => {}, []) const eventSubscription = useMemo(() => { if (selectedSessionId) { return { sessionId: selectedSessionId } } if (spawnMachineId) { return { machineId: spawnMachineId } } return { all: true } }, [selectedSessionId, spawnMachineId]) useSSE({ enabled: Boolean(api && token), token: token ?? '', subscription: eventSubscription, onConnect: handleSseConnect, onEvent: handleSseEvent, }) // Loading auth source if (isAuthSourceLoading) { return (
Loading…
) } // No auth source (browser environment, not logged in) if (!authSource) { return } // Authenticating if (isAuthLoading) { return (
Authorizing…
) } // Auth error if (authError || !token || !api) { // If using access token and auth failed, show login again if (authSource.type === 'accessToken') { return ( ) } // Telegram auth failed return (
Hapi
{authError ?? 'Not authorized'}
Open this page from Telegram using the bot's "Open App" button (not "Open in browser").
) } return (
) }