aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app/routes
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-13 22:40:45 +0900
committernsfisis <nsfisis@gmail.com>2026-02-13 23:07:26 +0900
commite239fe743fc66a8712cf9886d3dfed3cc41fce36 (patch)
treee3452fb13dce114cea0e8371dbb049118aa1229e /frontend/app/routes
parent482c3a52a0fcc5870a7db4a190475caf61b211a3 (diff)
downloadphperkaigi-2026-albatross-e239fe743fc66a8712cf9886d3dfed3cc41fce36.tar.gz
phperkaigi-2026-albatross-e239fe743fc66a8712cf9886d3dfed3cc41fce36.tar.zst
phperkaigi-2026-albatross-e239fe743fc66a8712cf9886d3dfed3cc41fce36.zip
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 <noreply@anthropic.com>
Diffstat (limited to 'frontend/app/routes')
-rw-r--r--frontend/app/routes/_index.tsx45
-rw-r--r--frontend/app/routes/dashboard.tsx88
-rw-r--r--frontend/app/routes/golf.$gameId.play.tsx62
-rw-r--r--frontend/app/routes/golf.$gameId.watch.tsx63
-rw-r--r--frontend/app/routes/login.tsx115
-rw-r--r--frontend/app/routes/logout.tsx7
-rw-r--r--frontend/app/routes/tournament.tsx440
7 files changed, 0 insertions, 820 deletions
diff --git a/frontend/app/routes/_index.tsx b/frontend/app/routes/_index.tsx
deleted file mode 100644
index 207b175..0000000
--- a/frontend/app/routes/_index.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import type { LoaderFunctionArgs, MetaFunction } from "react-router";
-import { ensureUserNotLoggedIn } from "../.server/auth";
-import BorderedContainer from "../components/BorderedContainer";
-import NavigateLink from "../components/NavigateLink";
-import { APP_NAME, BASE_PATH } from "../config";
-
-export const meta: MetaFunction = () => [{ title: APP_NAME }];
-
-export async function loader({ request }: LoaderFunctionArgs) {
- await ensureUserNotLoggedIn(request);
- return null;
-}
-
-export default function Index() {
- return (
- <div className="min-h-screen bg-gray-100 flex flex-col items-center justify-center gap-y-6">
- <img
- src={`${BASE_PATH}logo.svg`}
- alt="iOSDC Japan 2025"
- className="w-96 h-auto"
- />
- <div className="text-center">
- <div className="font-bold text-transparent bg-clip-text bg-iosdc-japan">
- <div className="text-6xl">Swift Code Battle</div>
- </div>
- </div>
- <div className="mx-2">
- <BorderedContainer>
- <p className="text-gray-900 max-w-prose">
- Swift コードバトルは指示された動作をする Swift
- コードをより短く書けた方が勝ち、という 1 対 1
- の対戦コンテンツです。9/6
- に実施された予選を勝ち抜いたプレイヤーによるトーナメント形式での
- コードバトルを 9/19 (金) day0
- に実施します。ここでは短いコードが正義です!
- 可読性も保守性も放り投げた、イベントならではのコードをお楽しみください!
- </p>
- </BorderedContainer>
- </div>
- <div>
- <NavigateLink to="/login">ログイン</NavigateLink>
- </div>
- </div>
- );
-}
diff --git a/frontend/app/routes/dashboard.tsx b/frontend/app/routes/dashboard.tsx
deleted file mode 100644
index f44fe79..0000000
--- a/frontend/app/routes/dashboard.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import type { LoaderFunctionArgs, MetaFunction } from "react-router";
-import { Form, useLoaderData } from "react-router";
-import { ensureUserLoggedIn } from "../.server/auth";
-import { createApiClient } from "../api/client";
-import BorderedContainerWithCaption from "../components/BorderedContainerWithCaption";
-import NavigateLink from "../components/NavigateLink";
-import UserIcon from "../components/UserIcon";
-import { APP_NAME, BASE_PATH } from "../config";
-
-export const meta: MetaFunction = () => [{ title: `Dashboard | ${APP_NAME}` }];
-
-export async function loader({ request }: LoaderFunctionArgs) {
- const { user, token } = await ensureUserLoggedIn(request);
- const apiClient = createApiClient(token);
-
- const { games } = await apiClient.getGames();
- return {
- user,
- games,
- };
-}
-
-export default function Dashboard() {
- const { user, games } = useLoaderData<typeof loader>()!;
-
- return (
- <div className="p-6 bg-gray-100 min-h-screen flex flex-col items-center gap-4">
- {user.icon_path && (
- <UserIcon
- iconPath={user.icon_path}
- displayName={user.display_name}
- className="w-24 h-24"
- />
- )}
- <h1 className="text-3xl font-bold text-gray-800">{user.display_name}</h1>
- <BorderedContainerWithCaption caption="試合一覧">
- <div className="px-4">
- {games.length === 0 ? (
- <p>エントリーできる試合はありません</p>
- ) : (
- <ul className="divide-y divide-gray-300">
- {games.map((game) => (
- <li
- key={game.game_id}
- className="flex justify-between items-center py-2 gap-4"
- >
- <div>
- <span className="font-medium text-gray-800">
- {game.display_name}
- </span>
- </div>
- <div className="flex gap-2">
- <NavigateLink to={`/golf/${game.game_id}/play`}>
- 対戦
- </NavigateLink>
- <NavigateLink to={`/golf/${game.game_id}/watch`}>
- 観戦
- </NavigateLink>
- </div>
- </li>
- ))}
- </ul>
- )}
- </div>
- </BorderedContainerWithCaption>
- <Form method="post" action="/logout">
- <button
- type="submit"
- className="px-4 py-2 bg-red-500 text-white rounded-sm transition duration-300 hover:bg-red-700 focus:ring-3 focus:ring-red-400 focus:outline-hidden"
- >
- ログアウト
- </button>
- </Form>
- {user.is_admin && (
- <a
- href={
- process.env.NODE_ENV === "development"
- ? `http://localhost:8004${BASE_PATH}admin/dashboard`
- : `${BASE_PATH}admin/dashboard`
- }
- className="text-lg text-white bg-sky-600 px-4 py-2 rounded-sm transition duration-300 hover:bg-sky-500 focus:ring-3 focus:ring-sky-400 focus:outline-hidden"
- >
- Admin Dashboard
- </a>
- )}
- </div>
- );
-}
diff --git a/frontend/app/routes/golf.$gameId.play.tsx b/frontend/app/routes/golf.$gameId.play.tsx
deleted file mode 100644
index c063d05..0000000
--- a/frontend/app/routes/golf.$gameId.play.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Provider as JotaiProvider, createStore } from "jotai";
-import { useMemo } from "react";
-import type { LoaderFunctionArgs, MetaFunction } from "react-router";
-import { redirect, useLoaderData } from "react-router";
-import { ensureUserLoggedIn } from "../.server/auth";
-import { ApiClientContext, createApiClient } from "../api/client";
-import GolfPlayApp from "../components/GolfPlayApp";
-import { APP_NAME } from "../config";
-
-export const meta: MetaFunction<typeof loader> = ({ data }) => [
- {
- title: data
- ? `Golf Playing ${data.game.display_name} | ${APP_NAME}`
- : `Golf Playing | ${APP_NAME}`,
- },
-];
-
-export async function loader({ params, request }: LoaderFunctionArgs) {
- const { token, user } = await ensureUserLoggedIn(request);
- const apiClient = createApiClient(token);
-
- const gameId = Number(params.gameId);
-
- try {
- const [{ game }, { state: gameState }] = await Promise.all([
- apiClient.getGame(gameId),
- apiClient.getGamePlayLatestState(gameId),
- ]);
-
- return {
- apiToken: token,
- game,
- player: user,
- gameState,
- };
- } catch {
- throw redirect("/dashboard");
- }
-}
-
-export default function GolfPlay() {
- const { apiToken, game, player, gameState } = useLoaderData<typeof loader>();
-
- const store = useMemo(() => {
- void game.game_id;
- void player.user_id;
- return createStore();
- }, [game.game_id, player.user_id]);
-
- return (
- <JotaiProvider store={store}>
- <ApiClientContext.Provider value={createApiClient(apiToken)}>
- <GolfPlayApp
- key={game.game_id}
- game={game}
- player={player}
- initialGameState={gameState}
- />
- </ApiClientContext.Provider>
- </JotaiProvider>
- );
-}
diff --git a/frontend/app/routes/golf.$gameId.watch.tsx b/frontend/app/routes/golf.$gameId.watch.tsx
deleted file mode 100644
index 7468be5..0000000
--- a/frontend/app/routes/golf.$gameId.watch.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Provider as JotaiProvider, createStore } from "jotai";
-import { useMemo } from "react";
-import type { LoaderFunctionArgs, MetaFunction } from "react-router";
-import { redirect, useLoaderData } from "react-router";
-import { ensureUserLoggedIn } from "../.server/auth";
-import { ApiClientContext, createApiClient } from "../api/client";
-import GolfWatchApp from "../components/GolfWatchApp";
-import { APP_NAME } from "../config";
-
-export const meta: MetaFunction<typeof loader> = ({ data }) => [
- {
- title: data
- ? `Golf Watching ${data.game.display_name} | ${APP_NAME}`
- : `Golf Watching | ${APP_NAME}`,
- },
-];
-
-export async function loader({ params, request }: LoaderFunctionArgs) {
- const { token } = await ensureUserLoggedIn(request);
- const apiClient = createApiClient(token);
-
- const gameId = Number(params.gameId);
-
- try {
- const [{ game }, { ranking }, { states: gameStates }] = await Promise.all([
- await apiClient.getGame(gameId),
- await apiClient.getGameWatchRanking(gameId),
- await apiClient.getGameWatchLatestStates(gameId),
- ]);
-
- return {
- apiToken: token,
- game,
- ranking,
- gameStates,
- };
- } catch {
- throw redirect("/dashboard");
- }
-}
-
-export default function GolfWatch() {
- const { apiToken, game, ranking, gameStates } =
- useLoaderData<typeof loader>();
-
- const store = useMemo(() => {
- void game.game_id;
- return createStore();
- }, [game.game_id]);
-
- return (
- <JotaiProvider store={store}>
- <ApiClientContext.Provider value={createApiClient(apiToken)}>
- <GolfWatchApp
- key={game.game_id}
- game={game}
- initialGameStates={gameStates}
- initialRanking={ranking}
- />
- </ApiClientContext.Provider>
- </JotaiProvider>
- );
-}
diff --git a/frontend/app/routes/login.tsx b/frontend/app/routes/login.tsx
deleted file mode 100644
index e394a6c..0000000
--- a/frontend/app/routes/login.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import type {
- ActionFunctionArgs,
- LoaderFunctionArgs,
- MetaFunction,
-} from "react-router";
-import { Form, data, useActionData } from "react-router";
-import { ensureUserNotLoggedIn, login } from "../.server/auth";
-import BorderedContainer from "../components/BorderedContainer";
-import InputText from "../components/InputText";
-import SubmitButton from "../components/SubmitButton";
-import { APP_NAME } from "../config";
-
-export const meta: MetaFunction = () => [{ title: `Login | ${APP_NAME}` }];
-
-export async function loader({ request }: LoaderFunctionArgs) {
- return await ensureUserNotLoggedIn(request);
-}
-
-export async function action({ request }: ActionFunctionArgs) {
- const formData = await request.clone().formData();
- const username = String(formData.get("username"));
- const password = String(formData.get("password"));
- if (username === "" || password === "") {
- return data(
- {
- message: "ユーザー名またはパスワードが誤っています",
- errors: {
- username:
- username === "" ? "ユーザー名を入力してください" : undefined,
- password:
- password === "" ? "パスワードを入力してください" : undefined,
- },
- },
- { status: 400 },
- );
- }
-
- try {
- await login(request);
- } catch (error) {
- if (error instanceof Error) {
- return data(
- {
- message: error.message,
- errors: {
- username: undefined,
- password: undefined,
- },
- },
- { status: 400 },
- );
- } else {
- throw error;
- }
- }
- return null;
-}
-
-export default function Login() {
- const loginErrors = useActionData<typeof action>();
-
- return (
- <div className="min-h-screen bg-gray-100 flex items-center justify-center">
- <div className="mx-2">
- <BorderedContainer>
- <Form method="post" className="w-full max-w-sm p-2">
- <h2 className="text-2xl mb-6 text-center">
- fortee アカウントでログイン
- </h2>
- {loginErrors?.message && (
- <p className="text-sky-500 text-sm mb-4">{loginErrors.message}</p>
- )}
- <div className="mb-4 flex flex-col gap-1">
- <label
- htmlFor="username"
- className="block text-sm font-medium text-gray-700"
- >
- ユーザー名
- </label>
- <InputText type="text" name="username" id="username" required />
- {loginErrors?.errors?.username && (
- <p className="text-red-500 text-sm">
- {loginErrors.errors.username}
- </p>
- )}
- </div>
- <div className="mb-6 flex flex-col gap-1">
- <label
- htmlFor="password"
- className="block text-sm font-medium text-gray-700"
- >
- パスワード
- </label>
- <InputText
- type="password"
- name="password"
- id="password"
- autoComplete="current-password"
- required
- />
- {loginErrors?.errors?.password && (
- <p className="text-red-500 text-sm">
- {loginErrors.errors.password}
- </p>
- )}
- </div>
- <div className="flex justify-center">
- <SubmitButton type="submit">ログイン</SubmitButton>
- </div>
- </Form>
- </BorderedContainer>
- </div>
- </div>
- );
-}
diff --git a/frontend/app/routes/logout.tsx b/frontend/app/routes/logout.tsx
deleted file mode 100644
index 9616b4d..0000000
--- a/frontend/app/routes/logout.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { ActionFunctionArgs } from "react-router";
-import { logout } from "../.server/auth";
-
-export async function action({ request }: ActionFunctionArgs) {
- await logout(request);
- return null;
-}
diff --git a/frontend/app/routes/tournament.tsx b/frontend/app/routes/tournament.tsx
deleted file mode 100644
index 162bd1a..0000000
--- a/frontend/app/routes/tournament.tsx
+++ /dev/null
@@ -1,440 +0,0 @@
-import type { LoaderFunctionArgs, MetaFunction } from "react-router";
-import { useLoaderData } from "react-router";
-import { ensureUserLoggedIn } from "../.server/auth";
-import { createApiClient } from "../api/client";
-import type { components } from "../api/schema";
-import BorderedContainer from "../components/BorderedContainer";
-import UserIcon from "../components/UserIcon";
-import { APP_NAME } from "../config";
-
-export const meta: MetaFunction = () => [{ title: `Tournament | ${APP_NAME}` }];
-
-export async function loader({ request }: LoaderFunctionArgs) {
- const { token } = await ensureUserLoggedIn(request);
- const apiClient = createApiClient(token);
-
- const url = new URL(request.url);
- const game1Param = url.searchParams.get("game1");
- const game2Param = url.searchParams.get("game2");
- const game3Param = url.searchParams.get("game3");
- const game4Param = url.searchParams.get("game4");
- const game5Param = url.searchParams.get("game5");
- const player1Param = url.searchParams.get("player1");
- const player2Param = url.searchParams.get("player2");
- const player3Param = url.searchParams.get("player3");
- const player4Param = url.searchParams.get("player4");
- const player5Param = url.searchParams.get("player5");
- const player6Param = url.searchParams.get("player6");
-
- if (!game1Param || !game2Param || !game3Param || !game4Param || !game5Param) {
- throw new Response(
- "Missing required query parameters: game1, game2, game3, game4, game5",
- {
- status: 400,
- },
- );
- }
- if (
- !player1Param ||
- !player2Param ||
- !player3Param ||
- !player4Param ||
- !player5Param ||
- !player6Param
- ) {
- throw new Response(
- "Missing required query parameters: player1, player2, player3, player4, player5, player6",
- {
- status: 400,
- },
- );
- }
-
- const game1 = Number(game1Param);
- const game2 = Number(game2Param);
- const game3 = Number(game3Param);
- const game4 = Number(game4Param);
- const game5 = Number(game5Param);
-
- if (!game1 || !game2 || !game3 || !game4 || !game5) {
- throw new Response("Invalid game IDs: must be positive integers", {
- status: 400,
- });
- }
-
- const { tournament } = await apiClient.getTournament(
- game1,
- game2,
- game3,
- game4,
- game5,
- );
- return {
- tournament,
- playerIDs: [
- Number(player1Param),
- Number(player2Param),
- Number(player3Param),
- Number(player4Param),
- Number(player5Param),
- Number(player6Param),
- ],
- };
-}
-
-type TournamentMatch = components["schemas"]["TournamentMatch"];
-type User = components["schemas"]["User"];
-
-function Player({ player, rank }: { player: User | null; rank: number }) {
- return (
- <BorderedContainer>
- <div className="flex flex-col items-center gap-2">
- <span className="text-gray-800 text-md">予選 {rank} 位</span>
- <span className="font-medium text-lg">{player?.display_name}</span>
- {player?.icon_path && (
- <UserIcon
- iconPath={player.icon_path}
- displayName={player.display_name}
- className="w-16 h-16 my-auto"
- />
- )}
- </div>
- </BorderedContainer>
- );
-}
-
-function BranchVL({ className = "" }: { className?: string }) {
- return (
- <div className="grid grid-cols-2">
- <div></div>
- <div className={`border-l-4 ${className}`}></div>
- </div>
- );
-}
-
-function BranchVR({ className = "" }: { className?: string }) {
- return (
- <div className="grid grid-cols-2">
- <div className={`border-r-4 ${className}`}></div>
- <div></div>
- </div>
- );
-}
-
-function BranchVL2({
- score,
- className = "",
-}: { score: number | null; className?: string }) {
- return (
- <div className="grid grid-cols-3">
- <div className={`border-r-4 ${className}`}></div>
- <div className={`border-t-4 p-2 font-bold text-xl ${className}`}>
- {score}
- </div>
- <div className={`border-t-4 ${className}`}></div>
- </div>
- );
-}
-
-function BranchVR2({
- score,
- className = "",
-}: { score: number | null; className?: string }) {
- return (
- <div className="grid grid-cols-3">
- <div className={`border-t-4 ${className}`}></div>
- <div className={`border-t-4 p-2 font-bold text-xl ${className}`}>
- {score}
- </div>
- <div className={`border-l-4 ${className}`}></div>
- </div>
- );
-}
-
-function BranchV3({ className = "" }: { className?: string }) {
- return <div className={`border-r-4 ${className}`}></div>;
-}
-
-function BranchH({
- score,
- className1,
- className2,
- className3,
-}: {
- score?: number | null;
- className1: string;
- className2: string;
- className3: string;
-}) {
- return (
- <div className="grid grid-cols-3">
- <div className={`border-t-4 ${className1}`}></div>
- <div className={`border-t-4 ${className2}`}></div>
- <div className={`border-t-4 p-2 font-bold text-xl ${className3}`}>
- {score}
- </div>
- </div>
- );
-}
-
-function BranchH2({
- score,
- className1,
- className2,
- className3,
-}: {
- score?: number | null;
- className1: string;
- className2: string;
- className3: string;
-}) {
- return (
- <div className="grid grid-cols-3">
- <div
- className={`border-t-4 p-2 font-bold text-xl text-right ${className1}`}
- >
- {score}
- </div>
- <div className={`border-t-4 ${className2}`}></div>
- <div className={`border-t-4 ${className3}`}></div>
- </div>
- );
-}
-
-function BranchL({
- score,
- className = "",
-}: { score: number | null; className?: string }) {
- return (
- <div className="grid grid-cols-2">
- <div></div>
- <div
- className={`border-l-4 border-t-4 p-2 font-bold text-xl ${className}`}
- >
- {score}
- </div>
- </div>
- );
-}
-
-function BranchR({
- score,
- className = "",
-}: { score: number | null; className?: string }) {
- return (
- <div className="grid grid-cols-2">
- <div
- className={`border-r-4 border-t-4 p-2 font-bold text-xl text-right ${className}`}
- >
- {score}
- </div>
- <div></div>
- </div>
- );
-}
-
-function BranchL2({ className = "" }: { className?: string }) {
- return (
- <div className="grid grid-cols-2">
- <div className={`border-l-4 ${className}`}></div>
- <div></div>
- </div>
- );
-}
-
-function BranchR2({ className = "" }: { className?: string }) {
- return (
- <div className="grid grid-cols-2">
- <div></div>
- <div className={`border-r-4 ${className}`}></div>
- </div>
- );
-}
-
-function getPlayer(match: TournamentMatch, playerID: number): User | null {
- if (match.player1?.user_id === playerID) return match.player1;
- else if (match.player2?.user_id === playerID) return match.player2;
- else 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;
- else return null;
-}
-
-function getBorderColor(match: TournamentMatch, playerIDs: number[]): string {
- if (!match.winner) {
- return "border-black";
- } else if (playerIDs.includes(match.winner)) {
- return "border-pink-700";
- } else {
- return "border-gray-400";
- }
-}
-
-export default function Tournament() {
- const { tournament, playerIDs } = useLoaderData<typeof loader>();
-
- 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 (
- <div className="p-6 bg-gray-100 min-h-screen">
- <div className="max-w-5xl mx-auto">
- <h1 className="text-3xl font-bold text-transparent bg-clip-text bg-iosdc-japan text-center mb-8">
- iOSDC Japan 2025 Swift Code Battle
- </h1>
-
- <div className="grid grid-rows-5">
- <div className="grid grid-cols-6">
- <div></div>
- <div></div>
- <BranchV3
- className={getBorderColor(match5, [
- playerID1,
- playerID5,
- playerID4,
- playerID3,
- playerID6,
- playerID2,
- ])}
- />
- <div></div>
- <div></div>
- <div></div>
- </div>
- <div className="grid grid-cols-6">
- <div></div>
- <BranchVL2
- score={getScore(match5, [playerID1, playerID5, playerID4])}
- className={getBorderColor(match5, [
- playerID1,
- playerID5,
- playerID4,
- ])}
- />
- <BranchH
- className1={getBorderColor(match5, [
- playerID1,
- playerID5,
- playerID4,
- ])}
- className2={getBorderColor(match5, [
- playerID1,
- playerID5,
- playerID4,
- ])}
- className3={getBorderColor(match5, [
- playerID1,
- playerID5,
- playerID4,
- ])}
- />
- <BranchH
- className1={getBorderColor(match5, [
- playerID3,
- playerID6,
- playerID2,
- ])}
- className2={getBorderColor(match5, [
- playerID3,
- playerID6,
- playerID2,
- ])}
- className3={getBorderColor(match5, [
- playerID3,
- playerID6,
- playerID2,
- ])}
- />
- <BranchVR2
- score={getScore(match5, [playerID3, playerID6, playerID2])}
- className={getBorderColor(match5, [
- playerID3,
- playerID6,
- playerID2,
- ])}
- />
- <div></div>
- </div>
- <div className="grid grid-cols-6">
- <BranchL
- score={getScore(match3, [playerID1])}
- className={getBorderColor(match3, [playerID1])}
- />
- <BranchH
- score={getScore(match3, [playerID5, playerID4])}
- className1={getBorderColor(match3, [playerID1])}
- className2={getBorderColor(match3, [playerID5, playerID4])}
- className3={getBorderColor(match3, [playerID5, playerID4])}
- />
- <BranchL2
- className={getBorderColor(match3, [playerID5, playerID4])}
- />
- <BranchR2
- className={getBorderColor(match4, [playerID3, playerID6])}
- />
- <BranchH2
- score={getScore(match4, [playerID3, playerID6])}
- className1={getBorderColor(match4, [playerID3, playerID6])}
- className2={getBorderColor(match4, [playerID3, playerID6])}
- className3={getBorderColor(match4, [playerID2])}
- />
- <BranchR
- score={getScore(match4, [playerID2])}
- className={getBorderColor(match4, [playerID2])}
- />
- </div>
- <div className="grid grid-cols-6">
- <BranchVL className={getBorderColor(match3, [playerID1])} />
- <BranchL
- score={getScore(match1, [playerID5])}
- className={getBorderColor(match1, [playerID5])}
- />
- <BranchR
- score={getScore(match1, [playerID4])}
- className={getBorderColor(match1, [playerID4])}
- />
- <BranchL
- score={getScore(match2, [playerID3])}
- className={getBorderColor(match2, [playerID3])}
- />
- <BranchR
- score={getScore(match2, [playerID6])}
- className={getBorderColor(match2, [playerID6])}
- />
- <BranchVR className={getBorderColor(match4, [playerID2])} />
- </div>
- <div className="grid grid-cols-6 gap-6">
- <Player player={player1} rank={1} />
- <Player player={player5} rank={5} />
- <Player player={player4} rank={4} />
- <Player player={player3} rank={3} />
- <Player player={player6} rank={6} />
- <Player player={player2} rank={2} />
- </div>
- </div>
- </div>
- </div>
- );
-}