From e239fe743fc66a8712cf9886d3dfed3cc41fce36 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Fri, 13 Feb 2026 22:40:45 +0900 Subject: refactor(frontend): replace React Router BFF with Wouter SPA Remove React Router 7 SSR/BFF architecture (server-side loaders, actions, sessions, remix-auth) and replace with a client-side SPA using Wouter for routing and cookie-based JWT auth. - Replace reactRouter() Vite plugin with @vitejs/plugin-react - Add index.html + app/main.tsx as SPA entry points - Add Wouter routing with auth guards (ProtectedRoute/PublicOnlyRoute) - Add client-side auth (app/auth.ts) and useAuth hook - Migrate all route files to app/pages/ with client-side data fetching - Update NavigateLink and GolfPlayAppGaming to use Wouter Link - Remove .server/, routes/, root.tsx, react-router.config.ts - Clean up tsconfig.json (remove .react-router references) Co-Authored-By: Claude Opus 4.6 --- frontend/app/pages/GolfWatchPage.tsx | 78 ++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 frontend/app/pages/GolfWatchPage.tsx (limited to 'frontend/app/pages/GolfWatchPage.tsx') diff --git a/frontend/app/pages/GolfWatchPage.tsx b/frontend/app/pages/GolfWatchPage.tsx new file mode 100644 index 0000000..317f860 --- /dev/null +++ b/frontend/app/pages/GolfWatchPage.tsx @@ -0,0 +1,78 @@ +import { Provider as JotaiProvider, createStore } from "jotai"; +import { useEffect, useMemo, useState } from "react"; +import { useLocation } from "wouter"; +import { ApiClientContext, createApiClient } from "../api/client"; +import type { components } from "../api/schema"; +import { getToken } from "../auth"; +import GolfWatchApp from "../components/GolfWatchApp"; +import { APP_NAME } from "../config"; +import { usePageTitle } from "../hooks/usePageTitle"; + +type Game = components["schemas"]["Game"]; +type LatestGameState = components["schemas"]["LatestGameState"]; +type RankingEntry = components["schemas"]["RankingEntry"]; + +export default function GolfWatchPage({ gameId }: { gameId: string }) { + const [, navigate] = useLocation(); + + const [game, setGame] = useState(null); + const [ranking, setRanking] = useState([]); + const [gameStates, setGameStates] = useState<{ + [key: string]: LatestGameState; + }>({}); + const [loading, setLoading] = useState(true); + + const gameIdNum = Number(gameId); + + usePageTitle( + game + ? `Golf Watching ${game.display_name} | ${APP_NAME}` + : `Golf Watching | ${APP_NAME}`, + ); + + useEffect(() => { + const token = getToken(); + if (!token) return; + const apiClient = createApiClient(token); + Promise.all([ + apiClient.getGame(gameIdNum), + apiClient.getGameWatchRanking(gameIdNum), + apiClient.getGameWatchLatestStates(gameIdNum), + ]) + .then(([{ game }, { ranking }, { states }]) => { + setGame(game); + setRanking(ranking); + setGameStates(states); + }) + .catch(() => navigate("/dashboard")) + .finally(() => setLoading(false)); + }, [gameIdNum, navigate]); + + const store = useMemo(() => { + if (!game) return null; + return createStore(); + }, [game]); + + if (loading || !game || !store) { + return ( +
+

Loading...

+
+ ); + } + + const token = getToken()!; + + return ( + + + + + + ); +} -- cgit v1.3.1