From fd94753dcb43f72b7400420505b03ca2118f9f7f Mon Sep 17 00:00:00 2001 From: nessi Date: Sun, 2 Aug 2026 09:17:17 +0200 Subject: [PATCH] Add pre-game lobby with live player list, host status, and start confirmation Implemented comprehensive lobby UI for games that haven't started yet. Added player count display, numbered player list with host crown indicator, and minimum 2-player requirement for game start. Included confirmation dialog before starting and error handling with user feedback. Enhanced visual styling with dedicated lobby container, player slots, and status messages. Updated README to reflect new lobby feature. --- README.md | 1 + frontend/src/components/GamePickerCard.jsx | 107 +++++++++++++++++---- 2 files changed, 91 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 0877aac..7665f9f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ A small multiplayer web app that acts as a digital note sheet for a Harry Potter - Multiple games per user - Join games using a short join code - Host-controlled game start with automatic player chips +- Pre-game lobby with live player list, host status, and start confirmation - Player chips are generated from the first name initial and first two surname letters, e.g. `SNE` for Sascha Nesterovic - Automatic player list with host indication - Personal note sheet for each player and game diff --git a/frontend/src/components/GamePickerCard.jsx b/frontend/src/components/GamePickerCard.jsx index c2b7e20..6a253ba 100644 --- a/frontend/src/components/GamePickerCard.jsx +++ b/frontend/src/components/GamePickerCard.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import { styles } from "../styles/styles"; import { stylesTokens } from "../styles/theme"; @@ -16,6 +16,8 @@ export default function GamePickerCard({ onStartGame, }) { const cur = games.find((x) => x.id === gameId); + const [starting, setStarting] = useState(false); + const [startError, setStartError] = useState(""); const renderMemberName = (m) => { const base = ((m.display_name || "").trim() || (m.email || "").trim() || "—"); @@ -46,6 +48,21 @@ export default function GamePickerCard({ whiteSpace: "nowrap", }); + const handleStartGame = async () => { + if (!onStartGame || starting || members.length < 2) return; + if (!window.confirm("Spiel jetzt starten? Danach können keine weiteren Spieler beitreten.")) return; + + setStarting(true); + setStartError(""); + try { + await onStartGame(); + } catch (e) { + setStartError(e?.message || "Das Spiel konnte nicht gestartet werden."); + } finally { + setStarting(false); + } + }; + return (
@@ -89,26 +106,82 @@ export default function GamePickerCard({
)} -
- {!started ? ( - isHost ? ( - - ) : ( -
- Warte auf den Host, der das Spiel startet. + {!started ? ( +
+
+
+
+ Lobby +
+
+ {isHost ? "Du bist der Host dieses Spiels." : "Du bist der Lobby beigetreten."} +
+
+
+
{members.length}
+
Spieler
- ) - ) : ( -
- ✓ Spiel läuft · {chipCount} Spieler-Chips erstellt
- )} -
+ +
+ {members.length > 0 ? members.map((m, index) => { + const isMe = String(me?.id) === String(m.id); + const isMemberHost = String(hostUserId) === String(m.id); + const name = ((m.display_name || "").trim() || (m.email || "").trim() || "Spieler"); + return ( +
+ {index + 1} + + {name}{isMe ? " (du)" : ""} + + {isMemberHost && 👑} +
+ ); + }) : ( +
+ Warte auf Spieler … +
+ )} +
+ + {isHost ? ( + <> + +
+ {members.length < 2 ? "Mindestens 2 Spieler werden benötigt." : "Beim Start werden die Spieler-Chips erstellt."} +
+ + ) : ( +
+ Warte, bis der Host das Spiel startet. +
+ )} + + {startError &&
{startError}
} +
+ ) : ( +
+ ✓ Spiel läuft · {chipCount} Spieler-Chips erstellt +
+ )} {/* Spieler */} - {members?.length > 0 && ( + {started && members?.length > 0 && (