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,74 @@
|
||||
import { Component } from "react";
|
||||
import Icon from "./Icon";
|
||||
|
||||
/**
|
||||
* Fängt Fehler beim Rendern ab.
|
||||
*
|
||||
* Ohne so eine Grenze hängt React den kompletten Baum aus, sobald irgendwo eine
|
||||
* Ausnahme fliegt – die Seite wird schlagartig weiß und nur ein Neuladen hilft.
|
||||
* Man sieht dann NICHT, was passiert ist, und kann es folglich auch nicht
|
||||
* melden. Genau das war hier das Problem.
|
||||
*
|
||||
* Die Grenze zeigt deshalb die Meldung selbst an, nicht nur ein „Ups". Wer sie
|
||||
* einmal sieht, kann sie weitergeben, und dann ist die Ursache in einer Minute
|
||||
* gefunden statt in einer Stunde.
|
||||
*
|
||||
* `bereich` beschreibt, was gekapselt ist – für die Meldung.
|
||||
*/
|
||||
export default class ErrorBoundary extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { fehler: null, info: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(fehler) {
|
||||
return { fehler };
|
||||
}
|
||||
|
||||
componentDidCatch(fehler, info) {
|
||||
// React schreibt selbst schon in die Konsole; der Komponentenpfad hilft
|
||||
// zusätzlich beim Einordnen.
|
||||
console.error("Fehler beim Rendern:", fehler, info?.componentStack);
|
||||
this.setState({ info });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { fehler, info } = this.state;
|
||||
if (!fehler) return this.props.children;
|
||||
|
||||
return (
|
||||
<div style={{ padding: "var(--sp-4)" }}>
|
||||
<div className="alert error" style={{ alignItems: "flex-start" }}>
|
||||
<Icon name="alert" size={16} />
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div className="strong">
|
||||
{this.props.bereich ? `${this.props.bereich} konnte nicht angezeigt werden.` : "Etwas ist schiefgelaufen."}
|
||||
</div>
|
||||
<div className="small" style={{ marginTop: 4, wordBreak: "break-word" }}>
|
||||
{fehler.message || String(fehler)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="field-inline" style={{ marginTop: "var(--sp-3)" }}>
|
||||
<button className="btn primary" onClick={() => this.setState({ fehler: null, info: null })}>
|
||||
Nochmal versuchen
|
||||
</button>
|
||||
<button className="btn" onClick={() => window.location.reload()}>
|
||||
Seite neu laden
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Aufgeklappt nur auf Wunsch: die Angaben sind für einen Fehlerbericht
|
||||
gedacht, nicht für den Alltag. */}
|
||||
<details style={{ marginTop: "var(--sp-3)" }}>
|
||||
<summary className="muted small">Technische Angaben</summary>
|
||||
<pre className="muted small" style={{ whiteSpace: "pre-wrap", overflowX: "auto" }}>
|
||||
{fehler.stack || String(fehler)}
|
||||
{info?.componentStack || ""}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user