aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/app/.server/api/schema.d.ts78
-rw-r--r--frontend/app/.server/auth.ts12
-rw-r--r--frontend/app/routes/dashboard.tsx44
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>