diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index cd1ddb7..f78ea8e 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -3,6 +3,7 @@ import CreateGame from "@/components/home/CreateGame"; import JoinGame from "@/components/home/JoinGame"; import PublicGames from "@/components/home/PublicGames/PublicGames"; +import LiveGames from "@/components/home/LiveGames"; import Link from "next/link"; import { useSession } from "@/context/session"; @@ -78,6 +79,9 @@ export default function Home() { + {/* Laufende Partien – Live-Kacheln */} + + {/* Öffentliche Spiele + Beitreten */}
diff --git a/client/src/app/tournament/[code]/page.tsx b/client/src/app/tournament/[code]/page.tsx index ac133d8..a79199f 100644 --- a/client/src/app/tournament/[code]/page.tsx +++ b/client/src/app/tournament/[code]/page.tsx @@ -242,6 +242,11 @@ export default function TournamentDetailPage() { Spielen )} + {!isMyGame && g.gameCode && !g.result && ( + + Zuschauen + + )} {g.gameCode && g.result && ( Ansehen diff --git a/client/src/components/home/LiveGames.tsx b/client/src/components/home/LiveGames.tsx new file mode 100644 index 0000000..cb3a7ad --- /dev/null +++ b/client/src/components/home/LiveGames.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Chessboard } from "react-chessboard"; +import Link from "next/link"; +import { fetchLiveGames, type LiveGameEntry } from "@/lib/game"; + +export default function LiveGames() { + const [games, setGames] = useState([]); + + useEffect(() => { + let mounted = true; + const load = async () => { + const data = await fetchLiveGames(); + if (mounted) setGames(data); + }; + load(); + const interval = setInterval(load, 3000); + return () => { mounted = false; clearInterval(interval); }; + }, []); + + if (!games.length) return null; + + return ( +
+

Laufende Partien

+
+ {games.map((game) => ( + + +
+ {game.black?.name} + vs + {game.white?.name} +
+ + ))} +
+
+ ); +} diff --git a/client/src/lib/game.ts b/client/src/lib/game.ts index 1c8fd7b..213751e 100644 --- a/client/src/lib/game.ts +++ b/client/src/lib/game.ts @@ -35,6 +35,25 @@ export const fetchActiveGame = async (code: string) => { } }; +export type LiveGameEntry = { + code: string; + white: { name?: string }; + black: { name?: string }; + fen: string; + timeControl?: number; + tournamentCode?: string; +}; + +export const fetchLiveGames = async (): Promise => { + try { + const res = await fetch(`${API_URL}/v1/games/live`, { cache: "no-store" }); + if (res.ok) return res.json(); + } catch (err) { + console.error(err); + } + return []; +}; + export const fetchPublicGames = async () => { try { const res = await fetch(`${API_URL}/v1/games`, { cache: "no-store" }); diff --git a/server/src/controllers/games.controller.ts b/server/src/controllers/games.controller.ts index 33da8d7..4972feb 100644 --- a/server/src/controllers/games.controller.ts +++ b/server/src/controllers/games.controller.ts @@ -1,9 +1,28 @@ import type { Game, User } from "@michess/types"; import type { Request, Response } from "express"; import { nanoid } from "nanoid"; +import { Chess } from "chess.js"; import GameModel, { activeGames } from "../db/models/game.model.js"; +export const getLiveGames = (_req: Request, res: Response) => { + const live = activeGames + .filter(g => g.white?.id && g.black?.id && !g.winner) + .map(g => { + const chess = new Chess(); + if (g.pgn) chess.loadPgn(g.pgn); + return { + code: g.code, + white: { name: g.white?.name }, + black: { name: g.black?.name }, + fen: chess.fen(), + timeControl: g.timeControl, + tournamentCode: g.tournamentCode + }; + }); + res.status(200).json(live); +}; + export const getGames = async (req: Request, res: Response) => { try { if (!req.query.id && !req.query.userid) { diff --git a/server/src/routes/games.route.ts b/server/src/routes/games.route.ts index 13b0be6..2fac086 100644 --- a/server/src/routes/games.route.ts +++ b/server/src/routes/games.route.ts @@ -4,6 +4,7 @@ import * as controller from "../controllers/games.controller.js"; const router = Router(); +router.get("/live", controller.getLiveGames); router.route("/").get(controller.getGames).post(controller.createGame); router.route("/:code").get(controller.getActiveGame);