Enhance new game modal with running game state handling
Introduced functionality to detect and handle ongoing games in the new game modal. The modal now switches between "running" and "choice" modes based on game state, improving clarity for users. Added a dedicated section to display the game code when a game is active.
This commit is contained in:
@@ -263,6 +263,15 @@ export default function App() {
|
|||||||
|
|
||||||
// ===== New game flow =====
|
// ===== New game flow =====
|
||||||
const createGame = async () => {
|
const createGame = async () => {
|
||||||
|
// ✅ wichtig: alten Game-State weg, damit nix "hängen" bleibt
|
||||||
|
setSheet(null);
|
||||||
|
setGameMeta(null);
|
||||||
|
setMembers([]);
|
||||||
|
setWinnerUserId("");
|
||||||
|
setPulseId(null);
|
||||||
|
setChipOpen(false);
|
||||||
|
setChipEntry(null);
|
||||||
|
|
||||||
const g = await api("/games", {
|
const g = await api("/games", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ name: "Spiel " + new Date().toLocaleString() }),
|
body: JSON.stringify({ name: "Spiel " + new Date().toLocaleString() }),
|
||||||
@@ -270,9 +279,10 @@ export default function App() {
|
|||||||
|
|
||||||
const gs = await api("/games");
|
const gs = await api("/games");
|
||||||
setGames(gs);
|
setGames(gs);
|
||||||
|
|
||||||
|
// ✅ auf neues Spiel wechseln (triggered dann reloadSheet/loadGameMeta via effect)
|
||||||
setGameId(g.id);
|
setGameId(g.id);
|
||||||
|
|
||||||
// meta/members will load via gameId effect
|
|
||||||
return g; // includes code
|
return g; // includes code
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -503,6 +513,9 @@ export default function App() {
|
|||||||
onClose={() => setNewGameOpen(false)}
|
onClose={() => setNewGameOpen(false)}
|
||||||
onCreate={createGame}
|
onCreate={createGame}
|
||||||
onJoin={joinGame}
|
onJoin={joinGame}
|
||||||
|
currentCode={gameMeta?.code || ""}
|
||||||
|
gameFinished={!!gameMeta?.winner_user_id}
|
||||||
|
hasGame={!!gameId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ChipModal
|
<ChipModal
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { styles } from "../styles/styles";
|
import { styles } from "../styles/styles";
|
||||||
import { stylesTokens } from "../styles/theme";
|
import { stylesTokens } from "../styles/theme";
|
||||||
|
|
||||||
@@ -7,8 +7,14 @@ export default function NewGameModal({
|
|||||||
onClose,
|
onClose,
|
||||||
onCreate,
|
onCreate,
|
||||||
onJoin,
|
onJoin,
|
||||||
|
|
||||||
|
// ✅ neu:
|
||||||
|
currentCode = "",
|
||||||
|
gameFinished = false,
|
||||||
|
hasGame = false,
|
||||||
}) {
|
}) {
|
||||||
const [mode, setMode] = useState("choice"); // choice | create | join
|
// modes: running | choice | create | join
|
||||||
|
const [mode, setMode] = useState("choice");
|
||||||
const [joinCode, setJoinCode] = useState("");
|
const [joinCode, setJoinCode] = useState("");
|
||||||
const [err, setErr] = useState("");
|
const [err, setErr] = useState("");
|
||||||
const [created, setCreated] = useState(null); // { code }
|
const [created, setCreated] = useState(null); // { code }
|
||||||
@@ -16,6 +22,23 @@ export default function NewGameModal({
|
|||||||
|
|
||||||
const canJoin = useMemo(() => joinCode.trim().length >= 4, [joinCode]);
|
const canJoin = useMemo(() => joinCode.trim().length >= 4, [joinCode]);
|
||||||
|
|
||||||
|
// ✅ wichtig: beim Öffnen entscheidet der Modus anhand "läuft vs beendet"
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
|
||||||
|
setErr("");
|
||||||
|
setToast("");
|
||||||
|
setJoinCode("");
|
||||||
|
setCreated(null);
|
||||||
|
|
||||||
|
// Wenn ein Spiel läuft (und nicht finished) -> nur Code anzeigen
|
||||||
|
if (hasGame && !gameFinished) {
|
||||||
|
setMode("running");
|
||||||
|
} else {
|
||||||
|
setMode("choice");
|
||||||
|
}
|
||||||
|
}, [open, hasGame, gameFinished]);
|
||||||
|
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
const showToast = (msg) => {
|
const showToast = (msg) => {
|
||||||
@@ -44,15 +67,19 @@ export default function NewGameModal({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyCode = async () => {
|
const copyText = async (text, okMsg = "✅ Code kopiert") => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(created?.code || "");
|
await navigator.clipboard.writeText(text || "");
|
||||||
showToast("✅ Code kopiert");
|
showToast(okMsg);
|
||||||
} catch {
|
} catch {
|
||||||
showToast("❌ Copy nicht möglich");
|
showToast("❌ Copy nicht möglich");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const codeToShow =
|
||||||
|
(created?.code || "").trim() ||
|
||||||
|
(currentCode || "").trim();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={styles.modalOverlay} onMouseDown={onClose}>
|
<div style={styles.modalOverlay} onMouseDown={onClose}>
|
||||||
<div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}>
|
<div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}>
|
||||||
@@ -85,6 +112,63 @@ export default function NewGameModal({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ marginTop: 12, display: "grid", gap: 10 }}>
|
<div style={{ marginTop: 12, display: "grid", gap: 10 }}>
|
||||||
|
{/* ✅ RUNNING: Nur Code anzeigen, keine Choice */}
|
||||||
|
{mode === "running" && (
|
||||||
|
<>
|
||||||
|
<div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>
|
||||||
|
Das Spiel läuft noch. Hier ist der <b>Join-Code</b>:
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gap: 8,
|
||||||
|
padding: 12,
|
||||||
|
borderRadius: 16,
|
||||||
|
border: `1px solid ${stylesTokens.panelBorder}`,
|
||||||
|
background: stylesTokens.panelBg,
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: 12, opacity: 0.8, color: stylesTokens.textDim }}>
|
||||||
|
Spiel-Code
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: 1100,
|
||||||
|
letterSpacing: 2,
|
||||||
|
color: stylesTokens.textGold,
|
||||||
|
fontFamily: '"Cinzel Decorative", "IM Fell English", system-ui',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{codeToShow || "—"}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => copyText(codeToShow)}
|
||||||
|
style={styles.primaryBtn}
|
||||||
|
disabled={!codeToShow}
|
||||||
|
title={!codeToShow ? "Kein Code verfügbar" : "Code kopieren"}
|
||||||
|
>
|
||||||
|
⧉ Code kopieren
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ fontSize: 12, opacity: 0.75, color: stylesTokens.textDim }}>
|
||||||
|
Sobald ein Sieger gesetzt wurde, kannst du hier ein neues Spiel erstellen oder beitreten.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
|
||||||
|
<button onClick={onClose} style={styles.primaryBtn}>
|
||||||
|
Fertig
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ✅ CHOICE: nur wenn Spiel beendet oder kein Spiel selected */}
|
||||||
{mode === "choice" && (
|
{mode === "choice" && (
|
||||||
<>
|
<>
|
||||||
<div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>
|
<div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>
|
||||||
@@ -159,7 +243,7 @@ export default function NewGameModal({
|
|||||||
{created.code}
|
{created.code}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onClick={copyCode} style={styles.primaryBtn}>
|
<button onClick={() => copyText(created?.code || "")} style={styles.primaryBtn}>
|
||||||
⧉ Code kopieren
|
⧉ Code kopieren
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user