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,75 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
||||
import i18n from "./i18n";
|
||||
import { api } from "./api";
|
||||
import { useAuth } from "./auth";
|
||||
import { applyTheme } from "./theme";
|
||||
import { formatMoney as fmtMoney, formatDate as fmtDate } from "./format";
|
||||
|
||||
const DEFAULTS = {
|
||||
base_currency: "EUR",
|
||||
locale: "de",
|
||||
theme: "system",
|
||||
date_format: "de",
|
||||
budget_start_day: "1",
|
||||
};
|
||||
|
||||
const SettingsContext = createContext({
|
||||
...DEFAULTS,
|
||||
formatMoney: (cents, currency) => fmtMoney(cents, currency),
|
||||
formatDate: (iso) => fmtDate(iso, "de"),
|
||||
update: async () => {},
|
||||
reload: async () => {},
|
||||
});
|
||||
|
||||
export function SettingsProvider({ children }) {
|
||||
const { user } = useAuth();
|
||||
const [values, setValues] = useState(DEFAULTS);
|
||||
|
||||
// Sprache und Thema anwenden, sobald sich die Werte ändern.
|
||||
useEffect(() => {
|
||||
if (i18n.language !== values.locale) i18n.changeLanguage(values.locale);
|
||||
}, [values.locale]);
|
||||
|
||||
useEffect(() => {
|
||||
applyTheme(values.theme);
|
||||
}, [values.theme]);
|
||||
|
||||
const reload = useCallback(async () => {
|
||||
try {
|
||||
const rows = await api.listSettings();
|
||||
const map = { ...DEFAULTS };
|
||||
for (const r of rows) {
|
||||
if (r.value != null) map[r.key] = r.value;
|
||||
}
|
||||
setValues(map);
|
||||
} catch {
|
||||
/* Einstellungen sind optional – Standardwerte bleiben bestehen. */
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (user) reload();
|
||||
else setValues(DEFAULTS); // nach Logout auf Standard zurück
|
||||
}, [user, reload]);
|
||||
|
||||
// Eine Einstellung setzen: erst lokal (sofort sichtbar), dann speichern.
|
||||
const update = useCallback(async (key, value) => {
|
||||
setValues((prev) => ({ ...prev, [key]: value }));
|
||||
await api.setSetting(key, value);
|
||||
}, []);
|
||||
|
||||
const value = {
|
||||
...values,
|
||||
baseCurrency: values.base_currency,
|
||||
formatMoney: (cents, currency) => fmtMoney(cents, currency ?? values.base_currency),
|
||||
formatDate: (iso) => fmtDate(iso, values.locale),
|
||||
update,
|
||||
reload,
|
||||
};
|
||||
|
||||
return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>;
|
||||
}
|
||||
|
||||
export function useSettings() {
|
||||
return useContext(SettingsContext);
|
||||
}
|
||||
Reference in New Issue
Block a user