From cd59ee30d615c0b35f816a337b2e94bfd31ffd9c Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 21:53:42 +0200 Subject: [PATCH] =?UTF-8?q?feat(P5B.T5):=20GameModal=20=E2=80=94=20picks?= =?UTF-8?q?=20board=20+=20turn/winner=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/desktop/src/components/GameModal.tsx | 108 ++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 apps/desktop/src/components/GameModal.tsx diff --git a/apps/desktop/src/components/GameModal.tsx b/apps/desktop/src/components/GameModal.tsx new file mode 100644 index 0000000..55ff32a --- /dev/null +++ b/apps/desktop/src/components/GameModal.tsx @@ -0,0 +1,108 @@ +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useAuth } from '../context/AuthContext'; +import { useGame } from '../hooks/useGame'; +import { ConnectFourBoard } from './ConnectFourBoard'; +import { TicTacToeBoard } from './TicTacToeBoard'; +import { XIcon } from './icons'; + +interface Props { + gameId: string; + onClose: () => void; +} + +export function GameModal({ gameId, onClose }: Props) { + const { t } = useTranslation(); + const { game, loading, error, makeMove } = useGame(gameId); + const { session: auth } = useAuth(); + + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, [onClose]); + + const myUserId = auth?.user.id ?? null; + const myPlayerIdx: 0 | 1 | null = + !game || !myUserId + ? null + : game.players[0] === myUserId + ? 0 + : game.players[1] === myUserId + ? 1 + : null; + const isMyTurn = !!game && game.currentTurnUserId === myUserId; + const finished = !!game?.finishedAt; + const winnerIdx: 0 | 1 | null = + !game?.winnerUserId + ? null + : game.players[0] === game.winnerUserId + ? 0 + : game.players[1] === game.winnerUserId + ? 1 + : null; + const title = + game?.gameType === 'c4' + ? t('app:game.c4', { defaultValue: 'Vier-Gewinnt' }) + : t('app:game.ttt', { defaultValue: 'Tic-Tac-Toe' }); + + const statusLine = (() => { + if (loading) return t('app:game.loading', { defaultValue: 'Lädt…' }); + if (error) return error; + if (!game) return t('app:game.missing', { defaultValue: 'Spiel nicht gefunden.' }); + if (finished) { + if (winnerIdx === null) return t('app:game.draw', { defaultValue: 'Unentschieden!' }); + if (winnerIdx === myPlayerIdx) return t('app:game.you_win', { defaultValue: 'Du hast gewonnen!' }); + return t('app:game.you_lose', { defaultValue: 'Du hast verloren.' }); + } + if (myPlayerIdx === null) return t('app:game.spectator', { defaultValue: 'Du schaust nur zu.' }); + if (isMyTurn) return t('app:game.your_turn', { defaultValue: 'Du bist dran' }); + return t('app:game.opponent_turn', { defaultValue: 'Gegner ist dran…' }); + })(); + + return ( +
+
+

{title}

+ +
+ +
+ {game?.gameType === 'ttt' ? ( + void makeMove({ cell }).catch(() => {})} + /> + ) : game?.gameType === 'c4' ? ( + void makeMove({ column }).catch(() => {})} + /> + ) : null} +
+ + +
+ ); +}