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,
43 lines
3.4 KiB
React
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>;
|
|
}
|