Remove PostgreSQL and move to SQLite3
This commit is contained in:
+54
-1
@@ -30,6 +30,13 @@ export default function App() {
|
||||
|
||||
// Auth/Login UI state
|
||||
const [me, setMe] = useState(null);
|
||||
const [setupRequired, setSetupRequired] = useState(null);
|
||||
const [setupEmail, setSetupEmail] = useState("");
|
||||
const [setupDisplayName, setSetupDisplayName] = useState("");
|
||||
const [setupPassword, setSetupPassword] = useState("");
|
||||
const [setupPasswordConfirm, setSetupPasswordConfirm] = useState("");
|
||||
const [setupError, setSetupError] = useState("");
|
||||
const [setupSaving, setSetupSaving] = useState(false);
|
||||
const [loginEmail, setLoginEmail] = useState("");
|
||||
const [loginPassword, setLoginPassword] = useState("");
|
||||
const [showPw, setShowPw] = useState(false);
|
||||
@@ -182,8 +189,12 @@ export default function App() {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const status = await api("/setup/status");
|
||||
setSetupRequired(!!status.setup_required);
|
||||
await load();
|
||||
} catch {}
|
||||
} catch {
|
||||
// The login/setup screen handles unauthenticated sessions.
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@@ -278,6 +289,36 @@ export default function App() {
|
||||
await load();
|
||||
};
|
||||
|
||||
const doSetup = async () => {
|
||||
setSetupError("");
|
||||
if (setupPassword.length < 8) {
|
||||
setSetupError("Password must be at least 8 characters long.");
|
||||
return;
|
||||
}
|
||||
if (setupPassword !== setupPasswordConfirm) {
|
||||
setSetupError("Passwords do not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
setSetupSaving(true);
|
||||
try {
|
||||
await api("/setup/admin", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
email: setupEmail,
|
||||
display_name: setupDisplayName,
|
||||
password: setupPassword,
|
||||
}),
|
||||
});
|
||||
setSetupRequired(false);
|
||||
await load();
|
||||
} catch (e) {
|
||||
setSetupError(e?.message || "Setup failed.");
|
||||
} finally {
|
||||
setSetupSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const doLogout = async () => {
|
||||
await api("/auth/logout", { method: "POST" });
|
||||
setMe(null);
|
||||
@@ -540,6 +581,18 @@ export default function App() {
|
||||
showPw={showPw}
|
||||
setShowPw={setShowPw}
|
||||
doLogin={doLogin}
|
||||
setupRequired={setupRequired}
|
||||
setupEmail={setupEmail}
|
||||
setSetupEmail={setSetupEmail}
|
||||
setupDisplayName={setupDisplayName}
|
||||
setSetupDisplayName={setSetupDisplayName}
|
||||
setupPassword={setupPassword}
|
||||
setSetupPassword={setSetupPassword}
|
||||
setupPasswordConfirm={setupPasswordConfirm}
|
||||
setSetupPasswordConfirm={setSetupPasswordConfirm}
|
||||
setupError={setupError}
|
||||
setupSaving={setupSaving}
|
||||
doSetup={doSetup}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,21 @@ export default function LoginPage({
|
||||
showPw,
|
||||
setShowPw,
|
||||
doLogin,
|
||||
setupRequired,
|
||||
setupEmail,
|
||||
setSetupEmail,
|
||||
setupDisplayName,
|
||||
setSetupDisplayName,
|
||||
setupPassword,
|
||||
setSetupPassword,
|
||||
setupPasswordConfirm,
|
||||
setSetupPasswordConfirm,
|
||||
setupError,
|
||||
setupSaving,
|
||||
doSetup,
|
||||
}) {
|
||||
const isSetup = setupRequired === true;
|
||||
|
||||
return (
|
||||
<div style={styles.loginPage}>
|
||||
<div style={styles.bgFixed} aria-hidden="true">
|
||||
@@ -21,9 +35,67 @@ export default function LoginPage({
|
||||
<div style={styles.loginCard}>
|
||||
<div style={styles.loginTitle}>Zauber-Detektiv Notizbogen</div>
|
||||
|
||||
<div style={styles.loginSubtitle}>Melde dich an, um dein Cluedo-Magie-Sheet zu öffnen</div>
|
||||
<div style={styles.loginSubtitle}>
|
||||
{setupRequired === null
|
||||
? "Initialisiere Anwendung …"
|
||||
: isSetup
|
||||
? "Richte den ersten Administrator ein"
|
||||
: "Melde dich an, um dein Cluedo-Magie-Sheet zu öffnen"}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 18, display: "grid", gap: 12 }}>
|
||||
{isSetup ? (
|
||||
<div style={{ marginTop: 18, display: "grid", gap: 12 }}>
|
||||
<div style={styles.loginFieldWrap}>
|
||||
<input
|
||||
value={setupDisplayName}
|
||||
onChange={(e) => setSetupDisplayName(e.target.value)}
|
||||
placeholder="Display name"
|
||||
style={styles.loginInput}
|
||||
autoComplete="name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={styles.loginFieldWrap}>
|
||||
<input
|
||||
value={setupEmail}
|
||||
onChange={(e) => setSetupEmail(e.target.value)}
|
||||
placeholder="Admin email"
|
||||
style={styles.loginInput}
|
||||
inputMode="email"
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={styles.loginFieldWrap}>
|
||||
<input
|
||||
value={setupPassword}
|
||||
onChange={(e) => setSetupPassword(e.target.value)}
|
||||
placeholder="Password (min. 8 characters)"
|
||||
type="password"
|
||||
style={styles.loginInput}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={styles.loginFieldWrap}>
|
||||
<input
|
||||
value={setupPasswordConfirm}
|
||||
onChange={(e) => setSetupPasswordConfirm(e.target.value)}
|
||||
placeholder="Confirm password"
|
||||
type="password"
|
||||
style={styles.loginInput}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{setupError && <div style={{ color: "#ffb3b3", fontSize: 13 }}>{setupError}</div>}
|
||||
|
||||
<button onClick={doSetup} style={styles.loginBtn} disabled={setupSaving}>
|
||||
{setupSaving ? "Setting up …" : "✦ Create administrator"}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginTop: 18, display: "grid", gap: 12 }}>
|
||||
<div style={styles.loginFieldWrap}>
|
||||
<input
|
||||
value={loginEmail}
|
||||
@@ -60,10 +132,11 @@ export default function LoginPage({
|
||||
<button onClick={doLogin} style={styles.loginBtn}>
|
||||
✦ Anmelden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={styles.loginHint}>
|
||||
Deine Notizen bleiben privat – jeder Spieler sieht nur seinen eigenen Zettel.
|
||||
Your notes remain private – every player only sees their own sheet.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user