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/TournamentPage.tsx | 429 ++++++++++++++++++++++++++++++++++ 1 file changed, 429 insertions(+) create mode 100644 frontend/app/pages/TournamentPage.tsx (limited to 'frontend/app/pages/TournamentPage.tsx') diff --git a/frontend/app/pages/TournamentPage.tsx b/frontend/app/pages/TournamentPage.tsx new file mode 100644 index 0000000..43ea790 --- /dev/null +++ b/frontend/app/pages/TournamentPage.tsx @@ -0,0 +1,429 @@ +import { useEffect, useState } from "react"; +import { createApiClient } from "../api/client"; +import type { components } from "../api/schema"; +import { getToken } from "../auth"; +import BorderedContainer from "../components/BorderedContainer"; +import UserIcon from "../components/UserIcon"; +import { APP_NAME } from "../config"; +import { usePageTitle } from "../hooks/usePageTitle"; + +type TournamentMatch = components["schemas"]["TournamentMatch"]; +type User = components["schemas"]["User"]; + +function Player({ player, rank }: { player: User | null; rank: number }) { + return ( + +
+ 予選 {rank} 位 + {player?.display_name} + {player?.icon_path && ( + + )} +
+
+ ); +} + +function BranchVL({ className = "" }: { className?: string }) { + return ( +
+
+
+
+ ); +} + +function BranchVR({ className = "" }: { className?: string }) { + return ( +
+
+
+
+ ); +} + +function BranchVL2({ + score, + className = "", +}: { score: number | null; className?: string }) { + return ( +
+
+
+ {score} +
+
+
+ ); +} + +function BranchVR2({ + score, + className = "", +}: { score: number | null; className?: string }) { + return ( +
+
+
+ {score} +
+
+
+ ); +} + +function BranchV3({ className = "" }: { className?: string }) { + return
; +} + +function BranchH({ + score, + className1, + className2, + className3, +}: { + score?: number | null; + className1: string; + className2: string; + className3: string; +}) { + return ( +
+
+
+
+ {score} +
+
+ ); +} + +function BranchH2({ + score, + className1, + className2, + className3, +}: { + score?: number | null; + className1: string; + className2: string; + className3: string; +}) { + return ( +
+
+ {score} +
+
+
+
+ ); +} + +function BranchL({ + score, + className = "", +}: { score: number | null; className?: string }) { + return ( +
+
+
+ {score} +
+
+ ); +} + +function BranchR({ + score, + className = "", +}: { score: number | null; className?: string }) { + return ( +
+
+ {score} +
+
+
+ ); +} + +function BranchL2({ className = "" }: { className?: string }) { + return ( +
+
+
+
+ ); +} + +function BranchR2({ className = "" }: { className?: string }) { + return ( +
+
+
+
+ ); +} + +function getPlayer(match: TournamentMatch, playerID: number): User | null { + if (match.player1?.user_id === playerID) return match.player1; + if (match.player2?.user_id === playerID) return match.player2; + return null; +} + +function getScore(match: TournamentMatch, playerIDs: number[]): number | null { + if (match.player1 && playerIDs.includes(match.player1.user_id)) + return match.player1_score ?? null; + if (match.player2 && playerIDs.includes(match.player2.user_id)) + return match.player2_score ?? null; + return null; +} + +function getBorderColor(match: TournamentMatch, playerIDs: number[]): string { + if (!match.winner) { + return "border-black"; + } + if (playerIDs.includes(match.winner)) { + return "border-pink-700"; + } + return "border-gray-400"; +} + +export default function TournamentPage() { + usePageTitle(`Tournament | ${APP_NAME}`); + + const [tournament, setTournament] = useState<{ + matches: TournamentMatch[]; + } | null>(null); + const [playerIDs, setPlayerIDs] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const game1 = Number(params.get("game1")); + const game2 = Number(params.get("game2")); + const game3 = Number(params.get("game3")); + const game4 = Number(params.get("game4")); + const game5 = Number(params.get("game5")); + + if (!game1 || !game2 || !game3 || !game4 || !game5) { + setError("Missing or invalid game parameters"); + setLoading(false); + return; + } + + const pIDs = [ + Number(params.get("player1")), + Number(params.get("player2")), + Number(params.get("player3")), + Number(params.get("player4")), + Number(params.get("player5")), + Number(params.get("player6")), + ]; + + if (pIDs.some((id) => !id)) { + setError("Missing or invalid player parameters"); + setLoading(false); + return; + } + + setPlayerIDs(pIDs); + + const token = getToken(); + if (!token) return; + const apiClient = createApiClient(token); + apiClient + .getTournament(game1, game2, game3, game4, game5) + .then(({ tournament }) => setTournament(tournament)) + .catch(() => setError("Failed to load tournament")) + .finally(() => setLoading(false)); + }, []); + + if (loading) { + return ( +
+

Loading...

+
+ ); + } + + if (error || !tournament) { + return ( +
+

{error || "Failed to load tournament"}

+
+ ); + } + + const match1 = tournament.matches[0]!; + const match2 = tournament.matches[1]!; + const match3 = tournament.matches[2]!; + const match4 = tournament.matches[3]!; + const match5 = tournament.matches[4]!; + + const playerID1 = playerIDs[0]!; + const playerID2 = playerIDs[1]!; + const playerID3 = playerIDs[2]!; + const playerID4 = playerIDs[3]!; + const playerID5 = playerIDs[4]!; + const playerID6 = playerIDs[5]!; + + const player5 = getPlayer(match1, playerID5); + const player4 = getPlayer(match1, playerID4); + const player3 = getPlayer(match2, playerID3); + const player6 = getPlayer(match2, playerID6); + const player1 = getPlayer(match3, playerID1); + const player2 = getPlayer(match4, playerID2); + + return ( +
+
+

+ iOSDC Japan 2025 Swift Code Battle +

+ +
+
+
+
+ +
+
+
+
+
+
+ + + + +
+
+
+ + + + + + +
+
+ + + + + + +
+
+ + + + + + +
+
+
+
+ ); +} -- cgit v1.3.1