diff options
| -rw-r--r-- | frontend/app/App.tsx | 8 | ||||
| -rw-r--r-- | frontend/app/pages/DashboardPage.tsx | 5 | ||||
| -rw-r--r-- | frontend/app/pages/GolfProblemPreviewPage.tsx | 60 |
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> + ); +} |
