feat: Phase 0 – Gerüst (FastAPI+SQLite, Multi-User-Login, React-App-Shell, Deploy)
Grundgerüst für Certain Finance: - Backend: FastAPI + SQLAlchemy + SQLite, Alembic-Migrationen (Batch-Mode), argon2-Passwort-Hashing, server-seitige Sitzungen im Cookie, Erst-Admin, Benutzerverwaltung mit Temporär-Passwort, Einstellungen je Benutzer. - Ein Prozess, ein Port: uvicorn liefert API (/api) und gebautes SPA aus, inkl. SPA-Deeplink-Fallback. - Frontend: React 18 + Vite + JSX, Design-Tokens (Light/Dark), i18n DE/EN, Inline-SVG-Icons (keine Emojis), Login, Passwort-ändern, Dashboard-Platzhalter, Benutzer- und Einstellungsseiten. - Deploy: nativer LXC-Installer + Update-Skript (systemd), env-Vorlage, Doku. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSettings } from "../settings";
|
||||
import { useToast } from "../toast";
|
||||
|
||||
export default function Settings() {
|
||||
const { t } = useTranslation();
|
||||
const s = useSettings();
|
||||
const toast = useToast();
|
||||
|
||||
async function set(key, value) {
|
||||
try {
|
||||
await s.update(key, value);
|
||||
toast(t("settings.saved"));
|
||||
} catch (err) {
|
||||
toast(err.message, "error");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{t("settings.title")}</h1>
|
||||
|
||||
<div className="card" style={{ maxWidth: 520 }}>
|
||||
<label>
|
||||
<span>{t("settings.baseCurrency")}</span>
|
||||
<select value={s.base_currency} onChange={(e) => set("base_currency", e.target.value)}>
|
||||
<option value="EUR">Euro (EUR)</option>
|
||||
<option value="CHF">Schweizer Franken (CHF)</option>
|
||||
</select>
|
||||
</label>
|
||||
<p className="muted small" style={{ marginTop: -8 }}>{t("settings.baseCurrencyHint")}</p>
|
||||
|
||||
<label>
|
||||
<span>{t("settings.locale")}</span>
|
||||
<select value={s.locale} onChange={(e) => set("locale", e.target.value)}>
|
||||
<option value="de">{t("settings.languageDe")}</option>
|
||||
<option value="en">{t("settings.languageEn")}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>{t("settings.theme")}</span>
|
||||
<select value={s.theme} onChange={(e) => set("theme", e.target.value)}>
|
||||
<option value="system">{t("settings.themeSystem")}</option>
|
||||
<option value="light">{t("settings.themeLight")}</option>
|
||||
<option value="dark">{t("settings.themeDark")}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>{t("settings.budgetStartDay")}</span>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="31"
|
||||
value={s.budget_start_day}
|
||||
onChange={(e) => {
|
||||
const day = Math.max(1, Math.min(31, Number(e.target.value) || 1));
|
||||
set("budget_start_day", String(day));
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<p className="muted small" style={{ marginTop: -8 }}>{t("settings.budgetStartDayHint")}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user