Files
cluedo-hp-webapp/frontend/src/components/NewGameModal.jsx
T
nessi 33a972e502 Add internationalization support with English/German language toggle across all components
Integrated i18n system throughout the application with `useLanguage` hook providing translation function and language state. Added bilingual support to all user-facing strings in App.jsx (password validation, error messages, game creation, sheet sections), AdminPanel (user management, form validation, status messages), AdminSettingsModal (SMTP configuration), ChipModal, DesignModal, GamePickerCard (lobby interface,
2026-08-02 10:33:40 +02:00

268 lines
8.9 KiB
React

import React, { useEffect, useMemo, useState } from "react";
import { styles } from "../styles/styles";
import { stylesTokens } from "../styles/theme";
import { useLanguage } from "../i18n";
export default function NewGameModal({
open,
onClose,
onCreate,
onJoin,
// ✅ neu:
currentCode = "",
gameFinished = false,
hasGame = false,
}) {
const { language, t } = useLanguage();
// modes: running | choice | create | join
const [mode, setMode] = useState("choice");
const [joinCode, setJoinCode] = useState("");
const [err, setErr] = useState("");
const [created, setCreated] = useState(null); // { code }
const [toast, setToast] = useState("");
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;
const showToast = (msg) => {
setToast(msg);
setTimeout(() => setToast(""), 1100);
};
const doCreate = async () => {
setErr("");
try {
const res = await onCreate();
setCreated({ code: res.code });
setMode("create");
} catch (e) {
setErr("❌ " + (language === "en" ? "Error: " : "Fehler: ") + (e?.message || "unknown"));
}
};
const doJoin = async () => {
setErr("");
try {
await onJoin(joinCode.trim().toUpperCase());
onClose();
} catch (e) {
setErr("❌ " + (language === "en" ? "Error: " : "Fehler: ") + (e?.message || "unknown"));
}
};
const copyText = async (text, okMsg = language === "en" ? "✅ Code copied" : "✅ Code kopiert") => {
try {
await navigator.clipboard.writeText(text || "");
showToast(okMsg);
} catch {
showToast(language === "en" ? "❌ Copy failed" : "❌ Copy nicht möglich");
}
};
const codeToShow =
(created?.code || "").trim() ||
(currentCode || "").trim();
return (
<div style={styles.modalOverlay} onMouseDown={onClose}>
<div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}>
<div style={styles.modalHeader}>
<div style={{ fontWeight: 1000, color: stylesTokens.textGold }}>
{t("game")}
</div>
<button onClick={onClose} style={styles.modalCloseBtn} aria-label={t("close")}>
</button>
</div>
{/* Toast */}
{toast && (
<div
style={{
marginTop: 10,
padding: "10px 12px",
borderRadius: 12,
border: `1px solid ${stylesTokens.panelBorder}`,
background: stylesTokens.panelBg,
color: stylesTokens.textMain,
fontWeight: 900,
textAlign: "center",
animation: "fadeIn 120ms ease-out",
}}
>
{toast}
</div>
)}
<div style={{ marginTop: 12, display: "grid", gap: 10 }}>
{/* ✅ RUNNING: Nur Code anzeigen, keine Choice */}
{mode === "running" && (
<>
<div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>
{language === "en" ? <>The game is in progress. Here is the <b>join code</b>:</> : <>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 }}>
{t("gameCode")}
</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 ? (language === "en" ? "No code available" : "Kein Code verfügbar") : (language === "en" ? "Copy code" : "Code kopieren")}
>
{t("copy")} {language === "en" ? "code" : "Code"}
</button>
</div>
<div style={{ fontSize: 12, opacity: 0.75, color: stylesTokens.textDim }}>
{language === "en" ? "Once a winner is selected, you can create or join a new game here." : "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}>
{language === "en" ? "Done" : "Fertig"}
</button>
</div>
</>
)}
{/* ✅ CHOICE: nur wenn Spiel beendet oder kein Spiel selected */}
{mode === "choice" && (
<>
<div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>
{language === "en" ? <>Would you like to <b>create</b> a game or <b>join</b> one?</> : <>Willst du ein Spiel <b>erstellen</b> oder einem Spiel <b>beitreten</b>?</>}
</div>
<button onClick={doCreate} style={styles.primaryBtn}>
{t("createGame")}
</button>
<button onClick={() => setMode("join")} style={styles.secondaryBtn}>
{t("joinGame")}
</button>
</>
)}
{mode === "join" && (
<>
<div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>
{language === "en" ? <>Enter the <b>code</b>:</> : <>Gib den <b>Code</b> ein:</>}
</div>
<input
value={joinCode}
onChange={(e) => setJoinCode(e.target.value.toUpperCase())}
placeholder="z.B. 8K3MZQ"
style={styles.input}
autoFocus
/>
<div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
<button onClick={() => setMode("choice")} style={styles.secondaryBtn}>
{language === "en" ? "Back" : "Zurück"}
</button>
<button onClick={doJoin} style={styles.primaryBtn} disabled={!canJoin}>
{t("join")}
</button>
</div>
</>
)}
{mode === "create" && created && (
<>
<div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>
{language === "en" ? "Your game was created. This code will remain visible in your old games:" : "Dein Spiel wurde erstellt. Dieser Code bleibt auch bei „Alte Spiele“ sichtbar:"}
</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 }}>
{t("gameCode")}
</div>
<div
style={{
fontSize: 28,
fontWeight: 1100,
letterSpacing: 2,
color: stylesTokens.textGold,
fontFamily: '"Cinzel Decorative", "IM Fell English", system-ui',
}}
>
{created.code}
</div>
<button onClick={() => copyText(created?.code || "")} style={styles.primaryBtn}>
{t("copy")} {language === "en" ? "code" : "Code"}
</button>
</div>
<div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
<button onClick={onClose} style={styles.primaryBtn}>
{language === "en" ? "Done" : "Fertig"}
</button>
</div>
</>
)}
{err && <div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>{err}</div>}
</div>
</div>
</div>
);
}