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>
76 lines
2.1 KiB
React
76 lines
2.1 KiB
React
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);
|
||
}
|