mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat(web): integrate TanStack React Router for client-side routing
Replace state-based screen navigation with proper URL-based routing. This includes: - New router configuration with routes for sessions, machines, and spawn pages - App context provider to share API and token across the app - useAppGoBack hook for handling browser and Telegram back navigation - Refactored App component to render outlet and use router hooks - Memory history for Telegram app, browser history for web
This commit is contained in:
+22
-204
@@ -1,65 +1,24 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
import { Outlet, useLocation, useMatchRoute } from '@tanstack/react-router'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { getTelegramWebApp, isTelegramApp } from '@/hooks/useTelegram'
|
||||
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 { useMessages } from '@/hooks/queries/useMessages'
|
||||
import { useMachines } from '@/hooks/queries/useMachines'
|
||||
import { useSession } from '@/hooks/queries/useSession'
|
||||
import { useSessions } from '@/hooks/queries/useSessions'
|
||||
import { useSendMessage } from '@/hooks/mutations/useSendMessage'
|
||||
import { SessionList } from '@/components/SessionList'
|
||||
import { SessionChat } from '@/components/SessionChat'
|
||||
import { MachineList } from '@/components/MachineList'
|
||||
import { SpawnSession } from '@/components/SpawnSession'
|
||||
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'
|
||||
|
||||
type Screen =
|
||||
| { type: 'sessions' }
|
||||
| { type: 'session'; sessionId: string }
|
||||
| { type: 'machines' }
|
||||
| { type: 'spawn'; machineId: string }
|
||||
|
||||
function getStartParam(): string | null {
|
||||
const query = new URLSearchParams(window.location.search)
|
||||
const fromQuery = query.get('startapp') || query.get('tgWebAppStartParam')
|
||||
if (fromQuery) return fromQuery
|
||||
|
||||
return getTelegramWebApp()?.initDataUnsafe?.start_param ?? null
|
||||
}
|
||||
|
||||
function getDeepLinkedSessionId(): string | null {
|
||||
const startParam = getStartParam()
|
||||
if (startParam?.startsWith('session_')) {
|
||||
return startParam.slice('session_'.length)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function App() {
|
||||
const { authSource, isLoading: isAuthSourceLoading, setAccessToken } = useAuthSource()
|
||||
const { token, api, isLoading: isAuthLoading, error: authError } = useAuth(authSource)
|
||||
|
||||
const [screen, setScreen] = useState<Screen>(() => {
|
||||
const deepLinkedSessionId = getDeepLinkedSessionId()
|
||||
if (deepLinkedSessionId) {
|
||||
return { type: 'session', sessionId: deepLinkedSessionId }
|
||||
}
|
||||
return { type: 'sessions' }
|
||||
})
|
||||
|
||||
// Navigate and sync browser history (browser only, not Telegram)
|
||||
const navigateTo = useCallback((newScreen: Screen) => {
|
||||
setScreen(newScreen)
|
||||
if (!isTelegramApp()) {
|
||||
history.pushState({ screen: newScreen }, '')
|
||||
}
|
||||
}, [])
|
||||
const goBack = useAppGoBack()
|
||||
const pathname = useLocation({ select: (location) => location.pathname })
|
||||
const matchRoute = useMatchRoute()
|
||||
|
||||
useEffect(() => {
|
||||
const tg = getTelegramWebApp()
|
||||
@@ -104,45 +63,12 @@ export function App() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Handle browser back button (browser only, not Telegram)
|
||||
useEffect(() => {
|
||||
if (isTelegramApp()) return
|
||||
|
||||
const handlePopState = (event: PopStateEvent) => {
|
||||
if (event.state?.screen) {
|
||||
setScreen(event.state.screen)
|
||||
} else {
|
||||
setScreen({ type: 'sessions' })
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', handlePopState)
|
||||
history.replaceState({ screen }, '')
|
||||
|
||||
return () => window.removeEventListener('popstate', handlePopState)
|
||||
}, [])
|
||||
|
||||
const goBack = useCallback(() => {
|
||||
// Browser: use native back (triggers popstate)
|
||||
if (!isTelegramApp()) {
|
||||
history.back()
|
||||
return
|
||||
}
|
||||
// Telegram: update state directly
|
||||
setScreen((prev) => {
|
||||
if (prev.type === 'session') return { type: 'sessions' }
|
||||
if (prev.type === 'machines') return { type: 'sessions' }
|
||||
if (prev.type === 'spawn') return { type: 'machines' }
|
||||
return prev
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const tg = getTelegramWebApp()
|
||||
const backButton = tg?.BackButton
|
||||
if (!backButton) return
|
||||
|
||||
if (screen.type === 'sessions') {
|
||||
if (pathname === '/' || pathname === '/sessions') {
|
||||
backButton.offClick(goBack)
|
||||
backButton.hide()
|
||||
return
|
||||
@@ -154,49 +80,12 @@ export function App() {
|
||||
backButton.offClick(goBack)
|
||||
backButton.hide()
|
||||
}
|
||||
}, [goBack, screen.type])
|
||||
}, [goBack, pathname])
|
||||
const queryClient = useQueryClient()
|
||||
const selectedSessionId = screen.type === 'session' ? screen.sessionId : null
|
||||
const machinesEnabled = screen.type === 'machines' || screen.type === 'spawn'
|
||||
|
||||
const {
|
||||
sessions,
|
||||
isLoading: sessionsLoading,
|
||||
error: sessionsError,
|
||||
refetch: refetchSessions,
|
||||
} = useSessions(api)
|
||||
const {
|
||||
session: selectedSession,
|
||||
refetch: refetchSession,
|
||||
} = useSession(api, selectedSessionId)
|
||||
const {
|
||||
messages,
|
||||
warning: messagesWarning,
|
||||
isLoading: messagesLoading,
|
||||
isLoadingMore: messagesLoadingMore,
|
||||
hasMore: messagesHasMore,
|
||||
loadMore: loadMoreMessages,
|
||||
refetch: refetchMessages,
|
||||
} = useMessages(api, selectedSessionId)
|
||||
const {
|
||||
machines,
|
||||
error: machinesError,
|
||||
} = useMachines(api, machinesEnabled)
|
||||
const {
|
||||
sendMessage,
|
||||
retryMessage,
|
||||
isSending,
|
||||
} = useSendMessage(api, selectedSessionId)
|
||||
|
||||
const refreshSessions = useCallback(() => {
|
||||
void refetchSessions()
|
||||
}, [refetchSessions])
|
||||
|
||||
const refreshSelectedSession = useCallback(() => {
|
||||
if (!selectedSessionId) return
|
||||
void refetchSession()
|
||||
void refetchMessages()
|
||||
}, [selectedSessionId, refetchMessages, refetchSession])
|
||||
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 })
|
||||
@@ -209,14 +98,14 @@ export function App() {
|
||||
const handleSseEvent = useCallback(() => {}, [])
|
||||
|
||||
const eventSubscription = useMemo(() => {
|
||||
if (screen.type === 'session') {
|
||||
return { sessionId: screen.sessionId }
|
||||
if (selectedSessionId) {
|
||||
return { sessionId: selectedSessionId }
|
||||
}
|
||||
if (screen.type === 'spawn') {
|
||||
return { machineId: screen.machineId }
|
||||
if (spawnMachineId) {
|
||||
return { machineId: spawnMachineId }
|
||||
}
|
||||
return { all: true }
|
||||
}, [screen])
|
||||
}, [selectedSessionId, spawnMachineId])
|
||||
|
||||
useSSE({
|
||||
enabled: Boolean(api && token),
|
||||
@@ -275,84 +164,13 @@ export function App() {
|
||||
)
|
||||
}
|
||||
|
||||
const machineForSpawn = screen.type === 'spawn'
|
||||
? machines.find(m => m.id === screen.machineId) ?? null
|
||||
: null
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppContextProvider value={{ api, token }}>
|
||||
<OfflineBanner />
|
||||
<div className="h-full flex flex-col">
|
||||
{screen.type === 'sessions' ? (
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||||
{sessionsError ? <div className="text-sm text-red-600">{sessionsError}</div> : null}
|
||||
<SessionList
|
||||
sessions={sessions}
|
||||
onSelect={(sessionId) => navigateTo({ type: 'session', sessionId })}
|
||||
onNewSession={() => navigateTo({ type: 'machines' })}
|
||||
onRefresh={refreshSessions}
|
||||
isLoading={sessionsLoading}
|
||||
/>
|
||||
</div>
|
||||
) : screen.type === 'session' ? (
|
||||
selectedSession ? (
|
||||
<SessionChat
|
||||
api={api}
|
||||
session={selectedSession}
|
||||
messages={messages}
|
||||
messagesWarning={messagesWarning}
|
||||
hasMoreMessages={messagesHasMore}
|
||||
isLoadingMessages={messagesLoading}
|
||||
isLoadingMoreMessages={messagesLoadingMore}
|
||||
isSending={isSending}
|
||||
onBack={goBack}
|
||||
onRefresh={refreshSelectedSession}
|
||||
onLoadMore={() => {
|
||||
void loadMoreMessages()
|
||||
}}
|
||||
onSend={sendMessage}
|
||||
onRetryMessage={retryMessage}
|
||||
/>
|
||||
) : (
|
||||
<div className="p-4 text-sm text-[var(--app-hint)]">Loading session…</div>
|
||||
)
|
||||
) : screen.type === 'machines' ? (
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="flex items-center gap-2 border-b border-[var(--app-border)] bg-[var(--app-bg)] p-3">
|
||||
<div className="flex-1 font-semibold">Machines</div>
|
||||
</div>
|
||||
|
||||
{machinesError ? (
|
||||
<div className="p-3 text-sm text-red-600">
|
||||
{machinesError}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<MachineList
|
||||
machines={machines}
|
||||
onSelect={(machineId) => navigateTo({ type: 'spawn', machineId })}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="flex items-center gap-2 border-b border-[var(--app-border)] bg-[var(--app-bg)] p-3">
|
||||
<div className="flex-1 font-semibold">Create Session</div>
|
||||
</div>
|
||||
|
||||
<SpawnSession
|
||||
api={api}
|
||||
machineId={screen.machineId}
|
||||
machine={machineForSpawn}
|
||||
onCancel={goBack}
|
||||
onSuccess={(sessionId) => {
|
||||
refreshSessions()
|
||||
navigateTo({ type: 'session', sessionId })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Outlet />
|
||||
</div>
|
||||
<InstallPrompt />
|
||||
</>
|
||||
</AppContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useLocation, useNavigate, useRouter } from '@tanstack/react-router'
|
||||
import { isTelegramApp } from '@/hooks/useTelegram'
|
||||
|
||||
export function useAppGoBack(): () => void {
|
||||
const navigate = useNavigate()
|
||||
const router = useRouter()
|
||||
const pathname = useLocation({ select: (location) => location.pathname })
|
||||
|
||||
return useCallback(() => {
|
||||
if (!isTelegramApp()) {
|
||||
router.history.back()
|
||||
return
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/sessions/')) {
|
||||
navigate({ to: '/sessions' })
|
||||
return
|
||||
}
|
||||
|
||||
if (pathname.endsWith('/spawn')) {
|
||||
navigate({ to: '/machines' })
|
||||
return
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/machines')) {
|
||||
navigate({ to: '/sessions' })
|
||||
}
|
||||
}, [navigate, pathname, router])
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createContext, useContext, type ReactNode } from 'react'
|
||||
import type { ApiClient } from '@/api/client'
|
||||
|
||||
type AppContextValue = {
|
||||
api: ApiClient
|
||||
token: string
|
||||
}
|
||||
|
||||
const AppContext = createContext<AppContextValue | null>(null)
|
||||
|
||||
export function AppContextProvider(props: {
|
||||
value: AppContextValue
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<AppContext.Provider value={props.value}>
|
||||
{props.children}
|
||||
</AppContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useAppContext(): AppContextValue {
|
||||
const context = useContext(AppContext)
|
||||
if (!context) {
|
||||
throw new Error('AppContext is not available')
|
||||
}
|
||||
return context
|
||||
}
|
||||
+32
-4
@@ -2,15 +2,38 @@ import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { QueryClientProvider } from '@tanstack/react-query'
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
import { App } from './App'
|
||||
import { RouterProvider, createMemoryHistory } from '@tanstack/react-router'
|
||||
import './index.css'
|
||||
import { registerSW } from 'virtual:pwa-register'
|
||||
import { isTelegramEnvironment, loadTelegramSdk } from './hooks/useTelegram'
|
||||
import { getTelegramWebApp, isTelegramEnvironment, loadTelegramSdk } from './hooks/useTelegram'
|
||||
import { queryClient } from './lib/query-client'
|
||||
import { createAppRouter } from './router'
|
||||
|
||||
function getStartParam(): string | null {
|
||||
const query = new URLSearchParams(window.location.search)
|
||||
const fromQuery = query.get('startapp') || query.get('tgWebAppStartParam')
|
||||
if (fromQuery) return fromQuery
|
||||
|
||||
return getTelegramWebApp()?.initDataUnsafe?.start_param ?? null
|
||||
}
|
||||
|
||||
function getDeepLinkedSessionId(): string | null {
|
||||
const startParam = getStartParam()
|
||||
if (startParam?.startsWith('session_')) {
|
||||
return startParam.slice('session_'.length)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function getInitialPath(): string {
|
||||
const sessionId = getDeepLinkedSessionId()
|
||||
return sessionId ? `/sessions/${sessionId}` : '/sessions'
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
// Only load Telegram SDK in Telegram environment (with 3s timeout)
|
||||
if (isTelegramEnvironment()) {
|
||||
const isTelegram = isTelegramEnvironment()
|
||||
if (isTelegram) {
|
||||
await loadTelegramSdk()
|
||||
}
|
||||
|
||||
@@ -35,10 +58,15 @@ async function bootstrap() {
|
||||
}
|
||||
})
|
||||
|
||||
const history = isTelegram
|
||||
? createMemoryHistory({ initialEntries: [getInitialPath()] })
|
||||
: undefined
|
||||
const router = createAppRouter(history)
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
<RouterProvider router={router} />
|
||||
{import.meta.env.DEV ? <ReactQueryDevtools initialIsOpen={false} /> : null}
|
||||
</QueryClientProvider>
|
||||
</React.StrictMode>
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
Navigate,
|
||||
createRootRoute,
|
||||
createRoute,
|
||||
createRouter,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from '@tanstack/react-router'
|
||||
import { App } from '@/App'
|
||||
import { SessionChat } from '@/components/SessionChat'
|
||||
import { SessionList } from '@/components/SessionList'
|
||||
import { MachineList } from '@/components/MachineList'
|
||||
import { SpawnSession } from '@/components/SpawnSession'
|
||||
import { useAppContext } from '@/lib/app-context'
|
||||
import { useAppGoBack } from '@/hooks/useAppGoBack'
|
||||
import { useMessages } from '@/hooks/queries/useMessages'
|
||||
import { useMachines } from '@/hooks/queries/useMachines'
|
||||
import { useSession } from '@/hooks/queries/useSession'
|
||||
import { useSessions } from '@/hooks/queries/useSessions'
|
||||
import { useSendMessage } from '@/hooks/mutations/useSendMessage'
|
||||
import { queryKeys } from '@/lib/query-keys'
|
||||
|
||||
function SessionsPage() {
|
||||
const { api } = useAppContext()
|
||||
const navigate = useNavigate()
|
||||
const { sessions, isLoading, error, refetch } = useSessions(api)
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
void refetch()
|
||||
}, [refetch])
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||||
{error ? <div className="text-sm text-red-600">{error}</div> : null}
|
||||
<SessionList
|
||||
sessions={sessions}
|
||||
onSelect={(sessionId) => navigate({
|
||||
to: '/sessions/$sessionId',
|
||||
params: { sessionId },
|
||||
})}
|
||||
onNewSession={() => navigate({ to: '/machines' })}
|
||||
onRefresh={handleRefresh}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionPage() {
|
||||
const { api } = useAppContext()
|
||||
const goBack = useAppGoBack()
|
||||
const { sessionId } = useParams({ from: '/sessions/$sessionId' })
|
||||
const {
|
||||
session,
|
||||
refetch: refetchSession,
|
||||
} = useSession(api, sessionId)
|
||||
const {
|
||||
messages,
|
||||
warning: messagesWarning,
|
||||
isLoading: messagesLoading,
|
||||
isLoadingMore: messagesLoadingMore,
|
||||
hasMore: messagesHasMore,
|
||||
loadMore: loadMoreMessages,
|
||||
refetch: refetchMessages,
|
||||
} = useMessages(api, sessionId)
|
||||
const {
|
||||
sendMessage,
|
||||
retryMessage,
|
||||
isSending,
|
||||
} = useSendMessage(api, sessionId)
|
||||
|
||||
const refreshSelectedSession = useCallback(() => {
|
||||
void refetchSession()
|
||||
void refetchMessages()
|
||||
}, [refetchMessages, refetchSession])
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<div className="p-4 text-sm text-[var(--app-hint)]">
|
||||
Loading session…
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SessionChat
|
||||
api={api}
|
||||
session={session}
|
||||
messages={messages}
|
||||
messagesWarning={messagesWarning}
|
||||
hasMoreMessages={messagesHasMore}
|
||||
isLoadingMessages={messagesLoading}
|
||||
isLoadingMoreMessages={messagesLoadingMore}
|
||||
isSending={isSending}
|
||||
onBack={goBack}
|
||||
onRefresh={refreshSelectedSession}
|
||||
onLoadMore={() => {
|
||||
void loadMoreMessages()
|
||||
}}
|
||||
onSend={sendMessage}
|
||||
onRetryMessage={retryMessage}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function MachinesPage() {
|
||||
const { api } = useAppContext()
|
||||
const navigate = useNavigate()
|
||||
const { machines, error: machinesError } = useMachines(api, true)
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="flex items-center gap-2 border-b border-[var(--app-border)] bg-[var(--app-bg)] p-3">
|
||||
<div className="flex-1 font-semibold">Machines</div>
|
||||
</div>
|
||||
|
||||
{machinesError ? (
|
||||
<div className="p-3 text-sm text-red-600">
|
||||
{machinesError}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<MachineList
|
||||
machines={machines}
|
||||
onSelect={(machineId) => navigate({
|
||||
to: '/machines/$machineId/spawn',
|
||||
params: { machineId },
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SpawnPage() {
|
||||
const { api } = useAppContext()
|
||||
const { machineId } = useParams({ from: '/machines/$machineId/spawn' })
|
||||
const { machines } = useMachines(api, true)
|
||||
const navigate = useNavigate()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const machineForSpawn = machines.find((machine) => machine.id === machineId) ?? null
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
navigate({ to: '/machines' })
|
||||
}, [navigate])
|
||||
|
||||
const handleSuccess = useCallback((sessionId: string) => {
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.sessions })
|
||||
navigate({
|
||||
to: '/sessions/$sessionId',
|
||||
params: { sessionId },
|
||||
})
|
||||
}, [navigate, queryClient])
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="flex items-center gap-2 border-b border-[var(--app-border)] bg-[var(--app-bg)] p-3">
|
||||
<div className="flex-1 font-semibold">Create Session</div>
|
||||
</div>
|
||||
|
||||
<SpawnSession
|
||||
api={api}
|
||||
machineId={machineId}
|
||||
machine={machineForSpawn}
|
||||
onCancel={handleCancel}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const rootRoute = createRootRoute({
|
||||
component: App,
|
||||
})
|
||||
|
||||
const indexRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/',
|
||||
component: () => <Navigate to="/sessions" replace />,
|
||||
})
|
||||
|
||||
const sessionsRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/sessions',
|
||||
component: SessionsPage,
|
||||
})
|
||||
|
||||
const sessionRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/sessions/$sessionId',
|
||||
component: SessionPage,
|
||||
})
|
||||
|
||||
const machinesRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/machines',
|
||||
component: MachinesPage,
|
||||
})
|
||||
|
||||
const spawnRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/machines/$machineId/spawn',
|
||||
component: SpawnPage,
|
||||
})
|
||||
|
||||
export const routeTree = rootRoute.addChildren([
|
||||
indexRoute,
|
||||
sessionsRoute,
|
||||
sessionRoute,
|
||||
machinesRoute,
|
||||
spawnRoute,
|
||||
])
|
||||
|
||||
type RouterHistory = Parameters<typeof createRouter>[0]['history']
|
||||
|
||||
export function createAppRouter(history?: RouterHistory) {
|
||||
return createRouter({ routeTree, history })
|
||||
}
|
||||
|
||||
export type AppRouter = ReturnType<typeof createAppRouter>
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: AppRouter
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user