Files
cluedo-hp-webapp/frontend/src/components/InvitePage.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

43 lines
3.4 KiB
React

import React, { useEffect, useState } from "react";
import { api } from "../api/client";
import { styles } from "../styles/styles";
import { stylesTokens } from "../styles/theme";
import { useLanguage } from "../i18n";
export default function InvitePage({ token }) {
const { language, t } = useLanguage();
const [info, setInfo] = useState(null);
const [password, setPassword] = useState("");
const [confirm, setConfirm] = useState("");
const [message, setMessage] = useState("");
const [saving, setSaving] = useState(false);
useEffect(() => { api(`/auth/invite/${encodeURIComponent(token)}`).then(setInfo).catch((e) => setMessage("❌ " + (e?.message || "Diese Einladung ist nicht mehr gültig."))); }, [token]);
const submit = async (event) => {
event.preventDefault(); setMessage("");
if (password.length < 8) return setMessage(`❌ ${language === "en" ? "Password must be at least 8 characters." : "Das Passwort muss mindestens 8 Zeichen haben."}`);
if (password !== confirm) return setMessage(`❌ ${language === "en" ? "Passwords do not match." : "Die Passwörter stimmen nicht überein."}`);
setSaving(true);
try { await api(`/auth/invite/${encodeURIComponent(token)}`, { method: "POST", body: JSON.stringify({ password }) }); setMessage("✅ Passwort gesetzt. Du wirst weitergeleitet …"); setTimeout(() => { window.location.href = "/"; }, 700); }
catch (e) { setMessage("❌ " + (e?.message || "Einladung konnte nicht aktiviert werden.")); setSaving(false); }
};
return <div style={styles.loginPage}>
<div style={styles.bgFixed} aria-hidden="true"><div style={styles.bgMap} /></div>
<main className="hp-invite-card" style={{ ...styles.loginCard, position: "relative", zIndex: 1 }}>
<div className="hp-invite-seal"></div>
<div style={{ color: stylesTokens.textGold, fontWeight: 1000, fontSize: 24 }}>Notizbogen</div>
<div style={{ color: stylesTokens.textDim, marginTop: 5 }}>{t("invite")}</div>
{info ? <>
<div style={{ marginTop: 22, padding: "14px 16px", borderRadius: 14, background: "rgba(233,216,166,.08)", border: "1px solid rgba(233,216,166,.18)", textAlign: "left" }}><div style={{ color: stylesTokens.textMain, fontWeight: 900, fontSize: 18 }}>{info.display_name}</div><div style={{ color: stylesTokens.textDim, fontSize: 13, marginTop: 4 }}>{info.email} · {info.role}</div></div>
<form onSubmit={submit} style={{ marginTop: 18, display: "grid", gap: 10, textAlign: "left" }}>
<label style={styles.adminFieldLabel}>{t("password")}<input autoFocus type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder={t("passwordMin")} style={styles.input} /></label>
<label style={styles.adminFieldLabel}>{t("confirmPassword")}<input type="password" value={confirm} onChange={(e) => setConfirm(e.target.value)} placeholder={t("passwordAgain")} style={styles.input} /></label>
{message && <div style={{ color: message.startsWith("✅") ? "#baf3c9" : "#ffb3b3", fontSize: 13 }}>{message}</div>}
<button type="submit" style={{ ...styles.primaryBtn, width: "100%", marginTop: 5 }} disabled={saving}>{saving ? (language === "en" ? "Activating …" : "Wird aktiviert …") : t("acceptInvite")}</button>
</form>
</> : <div style={{ marginTop: 24, color: message ? "#ffb3b3" : stylesTokens.textDim }}>{message || "Einladung wird geprüft …"}</div>}
</main>
</div>;
}