aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app/pages/DashboardPage.tsx
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/pages/DashboardPage.tsx
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/pages/DashboardPage.tsx')
-rw-r--r--frontend/app/pages/DashboardPage.tsx108
1 files changed, 108 insertions, 0 deletions
diff --git a/frontend/app/pages/DashboardPage.tsx b/frontend/app/pages/DashboardPage.tsx
new file mode 100644
index 0000000..c81014d
--- /dev/null
+++ b/frontend/app/pages/DashboardPage.tsx
@@ -0,0 +1,108 @@
+import { useEffect, useState } from "react";
+import { useLocation } from "wouter";
+import { createApiClient } from "../api/client";
+import type { components } from "../api/schema";
+import { getToken } from "../auth";
+import BorderedContainerWithCaption from "../components/BorderedContainerWithCaption";
+import NavigateLink from "../components/NavigateLink";
+import UserIcon from "../components/UserIcon";
+import { APP_NAME, BASE_PATH } from "../config";
+import { useAuth } from "../hooks/useAuth";
+import { usePageTitle } from "../hooks/usePageTitle";
+
+type Game = components["schemas"]["Game"];
+
+export default function DashboardPage() {
+ usePageTitle(`Dashboard | ${APP_NAME}`);
+
+ const { user, logout } = useAuth();
+ const [, navigate] = useLocation();
+
+ const [games, setGames] = useState<Game[]>([]);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const token = getToken();
+ if (!token) return;
+ const apiClient = createApiClient(token);
+ apiClient
+ .getGames()
+ .then(({ games }) => setGames(games))
+ .finally(() => setLoading(false));
+ }, []);
+
+ function handleLogout() {
+ logout();
+ navigate("/");
+ }
+
+ if (loading) {
+ return (
+ <div className="min-h-screen bg-gray-100 flex items-center justify-center">
+ <p className="text-gray-500">Loading...</p>
+ </div>
+ );
+ }
+
+ 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>
+ <button
+ type="button"
+ onClick={handleLogout}
+ 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>
+ {user?.is_admin && (
+ <a
+ href={
+ import.meta.env.DEV
+ ? `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>
+ );
+}