import React from "react"; import { Button } from '@/components/ui/button'; import { Sheet, SheetTrigger, SheetContent, SheetTitle, SheetDescription, SheetFooter, } from '@/components/ui/sheet'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Settings, ArrowLeft, FileText, RefreshCcw, ChevronRight, UserCircle } from 'lucide-react'; import { useOptionalGuestIdentity } from '../context/GuestIdentityContext'; import { LegalMarkdown } from './legal-markdown'; const legalPages = [ { slug: 'impressum', label: 'Impressum' }, { slug: 'datenschutz', label: 'Datenschutz' }, { slug: 'agb', label: 'AGB' }, ] as const; type ViewState = | { mode: 'home' } | { mode: 'legal'; slug: (typeof legalPages)[number]['slug']; label: string }; type LegalDocumentState = | { phase: 'idle'; title: string; body: string } | { phase: 'loading'; title: string; body: string } | { phase: 'ready'; title: string; body: string } | { phase: 'error'; title: string; body: string }; type NameStatus = 'idle' | 'saved'; export function SettingsSheet() { const [open, setOpen] = React.useState(false); const [view, setView] = React.useState({ mode: 'home' }); const identity = useOptionalGuestIdentity(); const [nameDraft, setNameDraft] = React.useState(identity?.name ?? ''); const [nameStatus, setNameStatus] = React.useState('idle'); const [savingName, setSavingName] = React.useState(false); const isLegal = view.mode === 'legal'; const legalDocument = useLegalDocument(isLegal ? view.slug : null); React.useEffect(() => { if (open && identity?.hydrated) { setNameDraft(identity.name ?? ''); setNameStatus('idle'); } }, [open, identity?.hydrated, identity?.name]); const handleBack = React.useCallback(() => { setView({ mode: 'home' }); }, []); const handleOpenLegal = React.useCallback( (slug: (typeof legalPages)[number]['slug'], label: string) => { setView({ mode: 'legal', slug, label }); }, [] ); const handleOpenChange = React.useCallback((next: boolean) => { setOpen(next); if (!next) { setView({ mode: 'home' }); setNameStatus('idle'); } }, []); const canSaveName = Boolean( identity?.hydrated && nameDraft.trim() && nameDraft.trim() !== (identity?.name ?? '') ); const handleSaveName = React.useCallback(() => { if (!identity || !canSaveName) { return; } setSavingName(true); try { identity.setName(nameDraft); setNameStatus('saved'); window.setTimeout(() => setNameStatus('idle'), 2000); } finally { setSavingName(false); } }, [identity, nameDraft, canSaveName]); const handleResetName = React.useCallback(() => { if (!identity) return; identity.clearName(); setNameDraft(''); setNameStatus('idle'); }, [identity]); return (
{isLegal ? (
{legalDocument.phase === 'ready' && legalDocument.title ? legalDocument.title : view.label} {legalDocument.phase === 'loading' ? 'Laedt...' : 'Rechtlicher Hinweis'}
) : (
Einstellungen Verwalte deinen Gastzugang, rechtliche Dokumente und lokale Daten.
)}
{isLegal ? ( handleOpenChange(false)} /> ) : ( )}
Gastbereich - Daten werden lokal im Browser gespeichert.
); } function LegalView({ document, onClose }: { document: LegalDocumentState; onClose: () => void }) { if (document.phase === 'error') { return (
Das Dokument konnte nicht geladen werden. Bitte versuche es spaeter erneut.
); } if (document.phase === 'loading' || document.phase === 'idle') { return
Dokument wird geladen...
; } return (
{document.title || 'Rechtlicher Hinweis'}
); } interface HomeViewProps { identity: ReturnType; nameDraft: string; onNameChange: (value: string) => void; onSaveName: () => void; onResetName: () => void; canSaveName: boolean; savingName: boolean; nameStatus: NameStatus; onOpenLegal: (slug: (typeof legalPages)[number]['slug'], label: string) => void; } function HomeView({ identity, nameDraft, onNameChange, onSaveName, onResetName, canSaveName, savingName, nameStatus, onOpenLegal, }: HomeViewProps) { return (
{identity && ( Dein Name Passe an, wie wir dich im Event begruessen. Der Name wird nur lokal gespeichert.
onNameChange(event.target.value)} autoComplete="name" disabled={!identity.hydrated || savingName} />
{nameStatus === 'saved' && ( Gespeichert (ok) )} {!identity.hydrated && ( Lade gespeicherten Namen... )}
)}
Rechtliches
Die rechtlich verbindlichen Texte sind jederzeit hier abrufbar.
{legalPages.map((page) => ( ))}
Offline Cache Loesche lokale Daten, falls Inhalte veraltet erscheinen oder Uploads haengen bleiben.
Dies betrifft nur diesen Browser und muss pro Geraet erneut ausgefuehrt werden.
); } function useLegalDocument(slug: string | null): LegalDocumentState { const [state, setState] = React.useState({ phase: 'idle', title: '', body: '', }); React.useEffect(() => { if (!slug) { setState({ phase: 'idle', title: '', body: '' }); return; } const controller = new AbortController(); setState({ phase: 'loading', title: '', body: '' }); fetch(`/api/v1/legal/${encodeURIComponent(slug)}?lang=de`, { headers: { 'Cache-Control': 'no-store' }, signal: controller.signal, }) .then(async (res) => { if (!res.ok) { throw new Error('failed'); } const payload = await res.json(); setState({ phase: 'ready', title: payload.title ?? '', body: payload.body_markdown ?? '', }); }) .catch((error) => { if (controller.signal.aborted) { return; } console.error('Failed to load legal page', error); setState({ phase: 'error', title: '', body: '' }); }); return () => controller.abort(); }, [slug]); return state; } function ClearCacheButton() { const [busy, setBusy] = React.useState(false); const [done, setDone] = React.useState(false); async function clearAll() { setBusy(true); setDone(false); try { if ('caches' in window) { const keys = await caches.keys(); await Promise.all(keys.map((key) => caches.delete(key))); } if ('indexedDB' in window) { try { await new Promise((resolve) => { const request = indexedDB.deleteDatabase('upload-queue'); request.onsuccess = () => resolve(null); request.onerror = () => resolve(null); }); } catch (error) { console.warn('IndexedDB cleanup failed', error); } } setDone(true); } finally { setBusy(false); window.setTimeout(() => setDone(false), 2500); } } return (
{done &&
Cache geloescht.
}
); }