diff options
| author | nsfisis <nsfisis@gmail.com> | 2024-07-28 16:01:41 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2024-07-28 17:12:45 +0900 |
| commit | 2d5f913a431c4223a16c88551ffff4100ac483c4 (patch) | |
| tree | 4ea9f9db9dbe7cf1b7720205ae281a6b8bcca8e9 /frontend | |
| parent | 0dd94cbea6e857896c46d17493725f97369d99f9 (diff) | |
| download | iosdc-japan-2024-albatross-2d5f913a431c4223a16c88551ffff4100ac483c4.tar.gz iosdc-japan-2024-albatross-2d5f913a431c4223a16c88551ffff4100ac483c4.tar.zst iosdc-japan-2024-albatross-2d5f913a431c4223a16c88551ffff4100ac483c4.zip | |
feat: implement game entry
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/app/.server/api/schema.d.ts | 78 | ||||
| -rw-r--r-- | frontend/app/.server/auth.ts | 12 | ||||
| -rw-r--r-- | frontend/app/routes/dashboard.tsx | 44 |
3 files changed, 125 insertions, 9 deletions
diff --git a/frontend/app/.server/api/schema.d.ts b/frontend/app/.server/api/schema.d.ts index d9ce187..cd87705 100644 --- a/frontend/app/.server/api/schema.d.ts +++ b/frontend/app/.server/api/schema.d.ts @@ -64,6 +64,60 @@ export interface paths { patch?: never; trace?: never; }; + "/games": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List games */ + get: { + parameters: { + query?: { + player_id?: number; + }; + header: { + Authorization: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of games */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + games: components["schemas"]["Game"][]; + }; + }; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @example Forbidden operation */ + message: string; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record<string, never>; export interface components { @@ -80,6 +134,30 @@ export interface components { /** @example false */ is_admin: boolean; }; + Game: { + /** @example 1 */ + game_id: number; + /** + * @example active + * @enum {string} + */ + state: "closed" | "waiting_entries" | "waiting_start" | "prepare" | "starting" | "gaming" | "finished"; + /** @example Game 1 */ + display_name: string; + /** @example 360 */ + duration_seconds: number; + /** @example 946684800 */ + started_at?: number; + problem?: components["schemas"]["Problem"]; + }; + Problem: { + /** @example 1 */ + problem_id: number; + /** @example Problem 1 */ + title: string; + /** @example This is a problem */ + description: string; + }; }; responses: never; parameters: never; diff --git a/frontend/app/.server/auth.ts b/frontend/app/.server/auth.ts index 822d4b9..988b30c 100644 --- a/frontend/app/.server/auth.ts +++ b/frontend/app/.server/auth.ts @@ -39,7 +39,7 @@ export async function isAuthenticated( failureRedirect?: never; headers?: never; }, -): Promise<User | null>; +): Promise<{ user: User; token: string } | null>; export async function isAuthenticated( request: Request | Session, options: { @@ -55,7 +55,7 @@ export async function isAuthenticated( failureRedirect: string; headers?: HeadersInit; }, -): Promise<User>; +): Promise<{ user: User; token: string }>; export async function isAuthenticated( request: Request | Session, options: { @@ -87,7 +87,7 @@ export async function isAuthenticated( failureRedirect: string; headers?: HeadersInit; } = {}, -): Promise<User | null> { +): Promise<{ user: User; token: string } | null> { // This function's signature should be compatible with `authenticator.isAuthenticated` but TypeScript does not infer it correctly. let jwt; const { successRedirect, failureRedirect, headers } = options; @@ -114,5 +114,9 @@ export async function isAuthenticated( if (!jwt) { return null; } - return jwtDecode<User>(jwt); + const user = jwtDecode<User>(jwt); + return { + user, + token: jwt, + }; } diff --git a/frontend/app/routes/dashboard.tsx b/frontend/app/routes/dashboard.tsx index 3ad465f..9836d1b 100644 --- a/frontend/app/routes/dashboard.tsx +++ b/frontend/app/routes/dashboard.tsx @@ -1,15 +1,33 @@ import type { LoaderFunctionArgs } from "@remix-run/node"; +import { Link, useLoaderData } from "@remix-run/react"; import { isAuthenticated } from "../.server/auth"; -import { useLoaderData } from "@remix-run/react"; +import { apiClient } from "../.server/api/client"; export async function loader({ request }: LoaderFunctionArgs) { - return await isAuthenticated(request, { + const { user, token } = await isAuthenticated(request, { failureRedirect: "/login", }); + const { data, error } = await apiClient.GET("/games", { + params: { + query: { + player_id: user.user_id, + }, + header: { + Authorization: `Bearer ${token}`, + }, + }, + }); + if (error) { + throw new Error(error.message); + } + return { + user, + games: data.games, + }; } export default function Dashboard() { - const user = useLoaderData<typeof loader>()!; + const { user, games } = useLoaderData<typeof loader>()!; return ( <div className="min-h-screen p-8"> @@ -24,10 +42,26 @@ export default function Dashboard() { <li>Name: {user.display_name}</li> </ul> </div> - <h2 className="text-2xl font-semibold mb-2">Game</h2> + <h2 className="text-2xl font-semibold mb-2">Games</h2> <div> <ul className="list-disc list-inside"> - <li>TODO</li> + {games.map((game) => ( + <li key={game.game_id}> + {game.display_name}{" "} + {game.state === "closed" || game.state === "finished" ? ( + <span className="inline-block px-6 py-2 text-gray-400 bg-gray-200 cursor-not-allowed rounded"> + Entry + </span> + ) : ( + <Link + to={`/game/${game.game_id}/play`} + className="inline-block px-6 py-2 text-white bg-blue-500 hover:bg-blue-700 rounded" + > + Entry + </Link> + )} + </li> + ))} </ul> </div> </div> |
