aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--frontend/app/App.tsx8
-rw-r--r--frontend/app/pages/DashboardPage.tsx5
-rw-r--r--frontend/app/pages/GolfProblemPreviewPage.tsx60
3 files changed, 73 insertions, 0 deletions
diff --git a/frontend/app/App.tsx b/frontend/app/App.tsx
index 9ac8007..3f1333a 100644
--- a/frontend/app/App.tsx
+++ b/frontend/app/App.tsx
@@ -4,6 +4,7 @@ import PublicOnlyRoute from "./components/PublicOnlyRoute";
import { BASE_PATH } from "./config";
import DashboardPage from "./pages/DashboardPage";
import GolfPlayPage from "./pages/GolfPlayPage";
+import GolfProblemPreviewPage from "./pages/GolfProblemPreviewPage";
import GolfWatchPage from "./pages/GolfWatchPage";
import IndexPage from "./pages/IndexPage";
import LoginPage from "./pages/LoginPage";
@@ -29,6 +30,13 @@ export default function App() {
<DashboardPage />
</ProtectedRoute>
</Route>
+ <Route path="/golf/:gameId/preview">
+ {(params) => (
+ <ProtectedRoute>
+ <GolfProblemPreviewPage gameId={params.gameId} />
+ </ProtectedRoute>
+ )}
+ </Route>
<Route path="/golf/:gameId/play">
{(params) => (
<ProtectedRoute>
diff --git a/frontend/app/pages/DashboardPage.tsx b/frontend/app/pages/DashboardPage.tsx
index 3191f1b..00db3f0 100644
--- a/frontend/app/pages/DashboardPage.tsx
+++ b/frontend/app/pages/DashboardPage.tsx
@@ -68,6 +68,11 @@ export default function DashboardPage() {
</span>
</div>
<div className="flex gap-2">
+ {game.started_at == null && (
+ <NavigateLink to={`/golf/${game.game_id}/preview`}>
+ 問題を見る
+ </NavigateLink>
+ )}
<NavigateLink to={`/golf/${game.game_id}/play`}>
対戦
</NavigateLink>
diff --git a/frontend/app/pages/GolfProblemPreviewPage.tsx b/frontend/app/pages/GolfProblemPreviewPage.tsx
new file mode 100644
index 0000000..01ac5b2
--- /dev/null
+++ b/frontend/app/pages/GolfProblemPreviewPage.tsx
@@ -0,0 +1,60 @@
+import { useEffect, useState } from "react";
+import { useLocation } from "wouter";
+import { createApiClient } from "../api/client";
+import type { components } from "../api/schema";
+import ProblemColumnContent from "../components/Gaming/ProblemColumnContent";
+import NavigateLink from "../components/NavigateLink";
+import { APP_NAME } from "../config";
+import { usePageTitle } from "../hooks/usePageTitle";
+
+type Game = components["schemas"]["Game"];
+
+export default function GolfProblemPreviewPage({ gameId }: { gameId: string }) {
+ const [, navigate] = useLocation();
+ const [game, setGame] = useState<Game | null>(null);
+ const [loading, setLoading] = useState(true);
+
+ const gameIdNum = Number(gameId);
+
+ usePageTitle(
+ game
+ ? `${game.display_name} - 問題プレビュー | ${APP_NAME}`
+ : `問題プレビュー | ${APP_NAME}`,
+ );
+
+ useEffect(() => {
+ const apiClient = createApiClient();
+ apiClient
+ .getGame(gameIdNum)
+ .then(({ game }) => setGame(game))
+ .catch(() => navigate("/dashboard"))
+ .finally(() => setLoading(false));
+ }, [gameIdNum, navigate]);
+
+ if (loading || !game) {
+ 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">
+ <h1 className="text-3xl font-bold text-gray-800">{game.display_name}</h1>
+ <div className="w-full max-w-3xl flex flex-col gap-4">
+ <ProblemColumnContent
+ description={game.problem.description}
+ language={game.problem.language}
+ sampleCode={game.problem.sample_code}
+ />
+ </div>
+ {game.started_at != null && (
+ <NavigateLink to={`/golf/${game.game_id}/play`}>
+ 対戦ページへ
+ </NavigateLink>
+ )}
+ <NavigateLink to="/dashboard">ダッシュボードへ戻る</NavigateLink>
+ </div>
+ );
+}