diff --git a/FrontendV2/src/components/Changelog.json b/FrontendV2/src/components/Changelog.json new file mode 100644 index 0000000..df87241 --- /dev/null +++ b/FrontendV2/src/components/Changelog.json @@ -0,0 +1,50 @@ +{ + "title": "Changelog", + "items": [ + { + "version": "v2.1.0", + "date": "2025-10-24", + "changes": [ + { + "type": "Hinzugefügt", + "text": [ + "Neue Changelog-Komponente mit zentriertem Layout.", + "Unterstützung für mehrsprachige Einträge (Englisch und Deutsch)." + ] + }, + { + "type": "Verbessert", + "text": [ + "Performance-Optimierungen beim Laden der Listenansichten.", + "Verbesserte Barrierefreiheit durch ARIA-Attribute." + ] + }, + { + "type": "Behoben", + "text": [ + "Fehler bei der Datumsauswahl im Safari-Browser.", + "Anzeigeprobleme bei hohen DPI-Einstellungen." + ] + } + ] + }, + { + "version": "v2.0.3", + "date": "2025-10-10", + "changes": [ + { + "type": "Geändert", + "text": [ + "Standard-Timeout für API-Requests auf 10s erhöht." + ] + }, + { + "type": "Sicherheit", + "text": [ + "Abhängigkeiten aktualisiert (kritische CVEs behoben)." + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/FrontendV2/src/components/Changelog.tsx b/FrontendV2/src/components/Changelog.tsx new file mode 100644 index 0000000..7ca3095 --- /dev/null +++ b/FrontendV2/src/components/Changelog.tsx @@ -0,0 +1,263 @@ +import { useEffect, useRef, useState } from "react"; + +const STORAGE_KEY = "changelog"; + +type ChangeType = + | "Hinzugefügt" + | "Geändert" + | "Behoben" + | "Entfernt" + | "Verbessert" + | "Sicherheit" + | "Veraltet" + | string; + +type ChangeEntry = { + type: ChangeType; + text: string | string[]; // aus localStorage kann es eine Liste sein +}; + +type ChangelogItem = { + version?: string; + date: string; + changes: ChangeEntry[]; +}; + +type StoredChangelog = { + title: string; + items: ChangelogItem[]; +}; + +const typeStyles: Record = { + Hinzugefügt: + "bg-emerald-500/15 text-emerald-300 ring-1 ring-inset ring-emerald-500/30", + Geändert: "bg-blue-500/15 text-blue-300 ring-1 ring-inset ring-blue-500/30", + Behoben: "bg-amber-500/15 text-amber-300 ring-1 ring-inset ring-amber-500/30", + Entfernt: "bg-rose-500/15 text-rose-300 ring-1 ring-inset ring-rose-500/30", + Verbessert: + "bg-indigo-500/15 text-indigo-300 ring-1 ring-inset ring-indigo-500/30", + Sicherheit: "bg-red-500/15 text-red-300 ring-1 ring-inset ring-red-500/30", + Veraltet: "bg-zinc-700/30 text-zinc-300 ring-1 ring-inset ring-zinc-600/40", +}; + +export default function Changelog() { + const [open, setOpen] = useState(true); + const [mounted, setMounted] = useState(false); + const [data, setData] = useState(null); + const [error, setError] = useState(null); + const cardRef = useRef(null); + + useEffect(() => setMounted(true), []); + + const loadFromStorage = () => { + try { + setError(null); + const raw = + typeof window !== "undefined" + ? localStorage.getItem(STORAGE_KEY) + : null; + if (!raw) { + setData(null); + return; + } + const parsed = JSON.parse(raw) as StoredChangelog; + if (!parsed || !Array.isArray(parsed.items)) { + throw new Error("Ungültiges Format"); + } + setData(parsed); + } catch (e) { + setError("Changelog konnte nicht aus localStorage geladen werden."); + setData(null); + } + }; + + useEffect(() => { + loadFromStorage(); + }, []); + + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") setOpen(false); + }; + const onClickOutside = (e: MouseEvent) => { + if (cardRef.current && !cardRef.current.contains(e.target as Node)) { + setOpen(false); + } + }; + const onStorage = (e: StorageEvent) => { + if (e.key === STORAGE_KEY) loadFromStorage(); + }; + window.addEventListener("keydown", onKey); + document.addEventListener("mousedown", onClickOutside); + window.addEventListener("storage", onStorage); + return () => { + window.removeEventListener("keydown", onKey); + document.removeEventListener("mousedown", onClickOutside); + window.removeEventListener("storage", onStorage); + }; + }, []); + + if (!open) return null; + + const title = data?.title ?? "Changelog"; + const items = data?.items ?? []; + + return ( +
+
+ {/* Gradient border wrapper */} +
+ {/* Card */} +
+ {/* Accent top line */} +
+ + {/* Close button */} + + + {/* Header */} +
+
+
+ + + +
+
+

+ {title} +

+

+ Aktuelle Änderungen und Updates +

+
+
+
+ + {/* Body */} +
+
+
+ + {error && ( +
+
+ {error} +
+
+ )} + + {!error && items.length === 0 && ( +
+

+ Kein Changelog im localStorage gefunden (Key: {STORAGE_KEY} + ). +

+
+ )} + +
    + {items.map((entry, idx) => ( +
  • + {/* Kopfzeile je Release */} +
    + {entry.version && ( + + {entry.version} + + )} + +
    + + {/* Zweispaltiges Layout: Typ links, Text rechts (mit schöner Leselänge) */} +
    + {entry.changes.map((c, i) => ( +
    +
    + + {c.type} + +
    + +
    + {Array.isArray(c.text) ? ( +
      + {c.text.map((t, k) => ( +
    • + {t} +
    • + ))} +
    + ) : ( +

    {c.text}

    + )} +
    +
    + ))} +
    +
  • + ))} +
+
+ + {/* soft bottom glow */} +
+
+
+
+
+ ); +}