feat(web): add multiplayer layout route and landing page

- multiplayer.tsx: layout route wrapping all multiplayer children
  with WsProvider, auth guard via beforeLoad
- multiplayer/index.tsx: create/join landing page
  - POST /api/v1/lobbies to create, navigates to lobby waiting room
  - POST /api/v1/lobbies/:code/join to join, normalizes code to
    uppercase before sending
  - loading states per action, error display, Enter key on join input
  - imports Lobby type from @lila/shared (single source of truth)
This commit is contained in:
lila 2026-04-17 21:33:40 +02:00
parent 9affe339c6
commit 4d4715b4ee
5 changed files with 292 additions and 3 deletions

View file

@ -10,15 +10,24 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as PlayRouteImport } from './routes/play'
import { Route as MultiplayerRouteImport } from './routes/multiplayer'
import { Route as LoginRouteImport } from './routes/login'
import { Route as AboutRouteImport } from './routes/about'
import { Route as IndexRouteImport } from './routes/index'
import { Route as MultiplayerIndexRouteImport } from './routes/multiplayer/index'
import { Route as MultiplayerLobbyCodeRouteImport } from './routes/multiplayer/lobby.$code'
import { Route as MultiplayerGameCodeRouteImport } from './routes/multiplayer/game.$code'
const PlayRoute = PlayRouteImport.update({
id: '/play',
path: '/play',
getParentRoute: () => rootRouteImport,
} as any)
const MultiplayerRoute = MultiplayerRouteImport.update({
id: '/multiplayer',
path: '/multiplayer',
getParentRoute: () => rootRouteImport,
} as any)
const LoginRoute = LoginRouteImport.update({
id: '/login',
path: '/login',
@ -34,38 +43,89 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const MultiplayerIndexRoute = MultiplayerIndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => MultiplayerRoute,
} as any)
const MultiplayerLobbyCodeRoute = MultiplayerLobbyCodeRouteImport.update({
id: '/lobby/$code',
path: '/lobby/$code',
getParentRoute: () => MultiplayerRoute,
} as any)
const MultiplayerGameCodeRoute = MultiplayerGameCodeRouteImport.update({
id: '/game/$code',
path: '/game/$code',
getParentRoute: () => MultiplayerRoute,
} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/about': typeof AboutRoute
'/login': typeof LoginRoute
'/multiplayer': typeof MultiplayerRouteWithChildren
'/play': typeof PlayRoute
'/multiplayer/': typeof MultiplayerIndexRoute
'/multiplayer/game/$code': typeof MultiplayerGameCodeRoute
'/multiplayer/lobby/$code': typeof MultiplayerLobbyCodeRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/about': typeof AboutRoute
'/login': typeof LoginRoute
'/play': typeof PlayRoute
'/multiplayer': typeof MultiplayerIndexRoute
'/multiplayer/game/$code': typeof MultiplayerGameCodeRoute
'/multiplayer/lobby/$code': typeof MultiplayerLobbyCodeRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/about': typeof AboutRoute
'/login': typeof LoginRoute
'/multiplayer': typeof MultiplayerRouteWithChildren
'/play': typeof PlayRoute
'/multiplayer/': typeof MultiplayerIndexRoute
'/multiplayer/game/$code': typeof MultiplayerGameCodeRoute
'/multiplayer/lobby/$code': typeof MultiplayerLobbyCodeRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/about' | '/login' | '/play'
fullPaths:
| '/'
| '/about'
| '/login'
| '/multiplayer'
| '/play'
| '/multiplayer/'
| '/multiplayer/game/$code'
| '/multiplayer/lobby/$code'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/about' | '/login' | '/play'
id: '__root__' | '/' | '/about' | '/login' | '/play'
to:
| '/'
| '/about'
| '/login'
| '/play'
| '/multiplayer'
| '/multiplayer/game/$code'
| '/multiplayer/lobby/$code'
id:
| '__root__'
| '/'
| '/about'
| '/login'
| '/multiplayer'
| '/play'
| '/multiplayer/'
| '/multiplayer/game/$code'
| '/multiplayer/lobby/$code'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AboutRoute: typeof AboutRoute
LoginRoute: typeof LoginRoute
MultiplayerRoute: typeof MultiplayerRouteWithChildren
PlayRoute: typeof PlayRoute
}
@ -78,6 +138,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PlayRouteImport
parentRoute: typeof rootRouteImport
}
'/multiplayer': {
id: '/multiplayer'
path: '/multiplayer'
fullPath: '/multiplayer'
preLoaderRoute: typeof MultiplayerRouteImport
parentRoute: typeof rootRouteImport
}
'/login': {
id: '/login'
path: '/login'
@ -99,13 +166,51 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/multiplayer/': {
id: '/multiplayer/'
path: '/'
fullPath: '/multiplayer/'
preLoaderRoute: typeof MultiplayerIndexRouteImport
parentRoute: typeof MultiplayerRoute
}
'/multiplayer/lobby/$code': {
id: '/multiplayer/lobby/$code'
path: '/lobby/$code'
fullPath: '/multiplayer/lobby/$code'
preLoaderRoute: typeof MultiplayerLobbyCodeRouteImport
parentRoute: typeof MultiplayerRoute
}
'/multiplayer/game/$code': {
id: '/multiplayer/game/$code'
path: '/game/$code'
fullPath: '/multiplayer/game/$code'
preLoaderRoute: typeof MultiplayerGameCodeRouteImport
parentRoute: typeof MultiplayerRoute
}
}
}
interface MultiplayerRouteChildren {
MultiplayerIndexRoute: typeof MultiplayerIndexRoute
MultiplayerGameCodeRoute: typeof MultiplayerGameCodeRoute
MultiplayerLobbyCodeRoute: typeof MultiplayerLobbyCodeRoute
}
const MultiplayerRouteChildren: MultiplayerRouteChildren = {
MultiplayerIndexRoute: MultiplayerIndexRoute,
MultiplayerGameCodeRoute: MultiplayerGameCodeRoute,
MultiplayerLobbyCodeRoute: MultiplayerLobbyCodeRoute,
}
const MultiplayerRouteWithChildren = MultiplayerRoute._addFileChildren(
MultiplayerRouteChildren,
)
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AboutRoute: AboutRoute,
LoginRoute: LoginRoute,
MultiplayerRoute: MultiplayerRouteWithChildren,
PlayRoute: PlayRoute,
}
export const routeTree = rootRouteImport