Files
tessera-ctl/apps/web/src/components/release-notice/release-notice-host.tsx
T
schalli 187fb76c91 feat(260925-bow): Was-ist-neu-Fenster im Portal-Rahmen
- release-notes.ts: reine Auswahl der Versionsabschnitte aus CHANGELOG.md
- release-notice-actions.ts ('use server'): Abruf und Merken ueber die API
- ReleaseNoticeDialog: barrierefreies Fenster, Eintraege ueber ChangelogView (variant plain)
- ReleaseNoticeHost in AppShell: einmal je Seitenladung, merkt erst beim Schliessen
- Texte releaseNotice in de.json und en.json

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 08:48:25 +02:00

70 lines
2.5 KiB
TypeScript

'use client';
import { usePathname } from 'next/navigation';
import { lazy, Suspense, useEffect, useRef, useState } from 'react';
import type { ReleaseNotice } from '@/lib/release-notes';
import { fetchReleaseNotice, markReleaseSeenAction } from '@/lib/release-notice-actions';
/**
* Laedt das "Was ist neu"-Fenster einmal je Seitenladung im Portal-Rahmen
* (quick-260925-bow, D-03/D-05/D-08).
*
* Eingebunden nur in `AppShell` — die Anmeldeseite liegt im `(auth)`-Layout
* ohne AppShell, der Erststart-Dialog der Desktop-App ist die lokale
* `apps/desktop/src/setup.html` vor dem Portal. Auf `/change-password`
* (liegt im Portal-Rahmen, Pflicht-Passwortwechsel) wird nicht abgefragt;
* erst die naechste Seite danach fragt. Eine Ref-Sperre verhindert einen
* zweiten Abruf (StrictMode-Doppeleffekt, Seitenwechsel ohne Neuladen).
*
* Gemerkt wird erst beim Schliessen, nie beim Oeffnen: zuerst verschwindet
* das Fenster, dann geht `markReleaseSeenAction` an die API. Schlaegt das
* Merken fehl, erscheint das Fenster beim naechsten Laden erneut — gewollt,
* statt die Nachricht stumm zu verlieren.
*
* Das Fenster (mit dem Markdown-Renderer) wird per `React.lazy` nur geladen,
* wenn wirklich eine Nachricht da ist.
*/
const ReleaseNoticeDialog = lazy(() =>
import('./release-notice-dialog').then((mod) => ({ default: mod.ReleaseNoticeDialog })),
);
export function ReleaseNoticeHost() {
const pathname = usePathname();
const [notice, setNotice] = useState<ReleaseNotice | null>(null);
const requestedRef = useRef(false);
useEffect(() => {
if (requestedRef.current) return;
if (pathname?.startsWith('/change-password')) return;
requestedRef.current = true;
// Bewusst ohne Abbruch-Flag im Aufraeumen: der StrictMode-Doppeleffekt
// raeumt nach dem ersten Lauf auf, der zweite Lauf fragt wegen der Sperre
// nicht erneut — ein Flag wuerde das einzige Ergebnis verwerfen.
fetchReleaseNotice()
.then((result) => {
if (result) setNotice(result);
})
.catch(() => {
// Still: ohne Nachricht kein Fenster.
});
}, [pathname]);
if (!notice) return null;
const handleClose = () => {
const version = notice.currentRelease;
setNotice(null);
void markReleaseSeenAction(version).catch(() => {
// Still: das Fenster erscheint beim naechsten Laden erneut.
});
};
return (
<Suspense fallback={null}>
<ReleaseNoticeDialog notice={notice} onClose={handleClose} />
</Suspense>
);
}