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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
<div style={{ marginTop: 14 }}>
|
||||
<div style={styles.card}>
|
||||
@@ -89,26 +106,82 @@ export default function GamePickerCard({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ padding: "0 12px 12px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, flexWrap: "wrap" }}>
|
||||
{!started ? (
|
||||
isHost ? (
|
||||
<button onClick={onStartGame} style={styles.primaryBtn} disabled={!members.length}>
|
||||
▶ Spiel starten
|
||||
</button>
|
||||
) : (
|
||||
<div style={{ fontSize: 12, color: stylesTokens.textDim }}>
|
||||
Warte auf den Host, der das Spiel startet.
|
||||
{!started ? (
|
||||
<div
|
||||
className="hp-lobby"
|
||||
style={{
|
||||
margin: "0 12px 12px",
|
||||
padding: 12,
|
||||
borderRadius: 16,
|
||||
border: `1px solid ${stylesTokens.panelBorder}`,
|
||||
background: "rgba(8,8,11,0.28)",
|
||||
}}
|
||||
>
|
||||
<div className="hp-lobby-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12 }}>
|
||||
<div>
|
||||
<div style={{ color: stylesTokens.textGold, fontWeight: 1000, fontSize: 15 }}>
|
||||
Lobby
|
||||
</div>
|
||||
<div style={{ marginTop: 3, color: stylesTokens.textDim, fontSize: 12 }}>
|
||||
{isHost ? "Du bist der Host dieses Spiels." : "Du bist der Lobby beigetreten."}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: "right", color: stylesTokens.textGold, fontWeight: 1000 }}>
|
||||
<div style={{ fontSize: 20, lineHeight: 1 }}>{members.length}</div>
|
||||
<div style={{ fontSize: 11, color: stylesTokens.textDim, fontWeight: 700 }}>Spieler</div>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div style={{ fontSize: 12, color: stylesTokens.textDim }}>
|
||||
✓ Spiel läuft · {chipCount} Spieler-Chips erstellt
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="hp-lobby-slots" style={{ marginTop: 12, display: "grid", gap: 6 }}>
|
||||
{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 (
|
||||
<div key={m.id} className="hp-lobby-player" style={{ display: "flex", alignItems: "center", gap: 9, padding: "8px 10px", borderRadius: 11, background: "rgba(255,255,255,0.055)", border: "1px solid rgba(233,216,166,0.10)" }}>
|
||||
<span style={{ color: stylesTokens.textDim, fontSize: 12, minWidth: 16 }}>{index + 1}</span>
|
||||
<span style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: stylesTokens.textMain, fontWeight: 900 }}>
|
||||
{name}{isMe ? " (du)" : ""}
|
||||
</span>
|
||||
{isMemberHost && <span title="Host" style={{ color: stylesTokens.textGold }}>👑</span>}
|
||||
</div>
|
||||
);
|
||||
}) : (
|
||||
<div style={{ padding: "10px 4px", color: stylesTokens.textDim, fontSize: 13 }}>
|
||||
Warte auf Spieler …
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isHost ? (
|
||||
<>
|
||||
<button
|
||||
onClick={handleStartGame}
|
||||
style={{ ...styles.primaryBtn, width: "100%", marginTop: 12 }}
|
||||
disabled={starting || members.length < 2}
|
||||
>
|
||||
{starting ? "Spiel wird gestartet …" : "▶ Spiel starten"}
|
||||
</button>
|
||||
<div style={{ marginTop: 7, textAlign: "center", color: stylesTokens.textDim, fontSize: 11 }}>
|
||||
{members.length < 2 ? "Mindestens 2 Spieler werden benötigt." : "Beim Start werden die Spieler-Chips erstellt."}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ marginTop: 11, padding: "9px 10px", borderRadius: 11, background: "rgba(233,216,166,0.07)", color: stylesTokens.textDim, fontSize: 12, textAlign: "center" }}>
|
||||
Warte, bis der Host das Spiel startet.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{startError && <div style={{ marginTop: 8, color: "#ffb3b3", fontSize: 12 }}>{startError}</div>}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ margin: "0 12px 12px", padding: "10px 12px", borderRadius: 13, border: `1px solid ${stylesTokens.panelBorder}`, background: "rgba(124,255,182,0.07)", color: stylesTokens.textDim, fontSize: 12 }}>
|
||||
✓ Spiel läuft · {chipCount} Spieler-Chips erstellt
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Spieler */}
|
||||
{members?.length > 0 && (
|
||||
{started && members?.length > 0 && (
|
||||
<div style={{ padding: "0 12px 12px" }}>
|
||||
<div
|
||||
style={{
|
||||
|
||||
Reference in New Issue
Block a user