import React from 'react'; import { Link } from 'react-router-dom'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Button } from '@tamagui/button'; import { Input } from '@tamagui/input'; import { Card } from '@tamagui/card'; import { Switch } from '@tamagui/switch'; import { Check, Moon, RotateCcw, Sun, Languages, FileText, LifeBuoy } from 'lucide-react'; import { useTranslation } from '@/guest/i18n/useTranslation'; import { useLocale } from '@/guest/i18n/LocaleContext'; import { useOptionalGuestIdentity } from '../context/GuestIdentityContext'; import { useHapticsPreference } from '@/guest/hooks/useHapticsPreference'; import { triggerHaptic } from '@/guest/lib/haptics'; import { useConsent } from '@/contexts/consent'; import { useAppearance } from '@/hooks/use-appearance'; import { useEventData } from '../context/EventDataContext'; import { buildEventPath } from '../lib/routes'; import { useGuestThemeVariant } from '../lib/guestTheme'; const legalLinks = [ { slug: 'impressum', labelKey: 'settings.legal.section.impressum', fallback: 'Impressum' }, { slug: 'datenschutz', labelKey: 'settings.legal.section.privacy', fallback: 'Datenschutz' }, { slug: 'agb', labelKey: 'settings.legal.section.terms', fallback: 'AGB' }, ] as const; type SettingsContentProps = { onNavigate?: () => void; showHeader?: boolean; onOpenLegal?: (slug: (typeof legalLinks)[number]['slug'], labelKey: (typeof legalLinks)[number]['labelKey']) => void; }; export default function SettingsContent({ onNavigate, showHeader = true, onOpenLegal }: SettingsContentProps) { const { t } = useTranslation(); const locale = useLocale(); const identity = useOptionalGuestIdentity(); const { enabled: hapticsEnabled, setEnabled: setHapticsEnabled, supported: hapticsSupported } = useHapticsPreference(); const { preferences, savePreferences } = useConsent(); const matomoEnabled = typeof window !== 'undefined' && Boolean((window as any).__MATOMO_GUEST__?.enabled); const { appearance, updateAppearance } = useAppearance(); const { isDark } = useGuestThemeVariant(); const { token } = useEventData(); const cardBackground = isDark ? 'rgba(15, 23, 42, 0.65)' : 'rgba(255, 255, 255, 0.82)'; const cardBorder = isDark ? 'rgba(148, 163, 184, 0.18)' : 'rgba(15, 23, 42, 0.12)'; const primaryText = isDark ? '#F8FAFF' : '#0F172A'; const mutedText = isDark ? 'rgba(226, 232, 240, 0.7)' : 'rgba(15, 23, 42, 0.6)'; const mutedButton = isDark ? 'rgba(248, 250, 255, 0.08)' : 'rgba(15, 23, 42, 0.06)'; const mutedButtonBorder = isDark ? 'rgba(248, 250, 255, 0.2)' : 'rgba(15, 23, 42, 0.12)'; const [nameDraft, setNameDraft] = React.useState(identity?.name ?? ''); const [status, setStatus] = React.useState<'idle' | 'saved'>('idle'); const helpPath = token ? buildEventPath(token, '/help') : '/help'; const supportsInlineLegal = Boolean(onOpenLegal); React.useEffect(() => { if (identity?.hydrated) { setNameDraft(identity.name ?? ''); setStatus('idle'); } }, [identity?.hydrated, identity?.name]); const canSaveName = Boolean( identity?.hydrated && nameDraft.trim() && nameDraft.trim() !== (identity?.name ?? '') ); const handleSaveName = React.useCallback(() => { if (!identity || !canSaveName) { return; } identity.setName(nameDraft); setStatus('saved'); window.setTimeout(() => setStatus('idle'), 2000); }, [identity, nameDraft, canSaveName]); const handleResetName = React.useCallback(() => { if (!identity) { return; } identity.clearName(); setNameDraft(''); setStatus('idle'); }, [identity]); return ( {showHeader ? ( {t('settings.title', 'Settings')} {t('settings.subtitle', 'Make this app yours.')} ) : null} {locale.availableLocales.map((option) => ( ))} {t('settings.name.title', 'Your name')} {status === 'saved' ? ( {t('settings.name.saved', 'Saved')} ) : null} {t('settings.haptics.label', 'Haptic feedback')} { setHapticsEnabled(checked); if (checked) { triggerHaptic('selection'); } }} aria-label="haptics-toggle" backgroundColor={hapticsEnabled ? '$primary' : mutedButton} borderColor={mutedButtonBorder} borderWidth={1} > {!hapticsSupported ? ( {t('settings.haptics.unsupported', 'Haptics are not available on this device.')} ) : null} {matomoEnabled ? ( {t('settings.analytics.label', 'Share anonymous analytics')} savePreferences({ analytics: checked })} backgroundColor={preferences?.analytics ? '$primary' : mutedButton} borderColor={mutedButtonBorder} borderWidth={1} > {t('settings.analytics.note', 'You can change this anytime.')} ) : null} {t('settings.legal.title', 'Legal')} {legalLinks.map((page) => { const label = t(page.labelKey, page.fallback); if (supportsInlineLegal) { return ( ); } return ( ); })} {t('settings.cache.title', 'Offline cache')} {t('settings.cache.note', 'This only affects this browser. Pending uploads may be lost.')} {t('settings.help.title', 'Help Center')} ); } function ClearCacheButton() { const { t } = useTranslation(); const [busy, setBusy] = React.useState(false); const [done, setDone] = React.useState(false); const { isDark } = useGuestThemeVariant(); const mutedButton = isDark ? 'rgba(248, 250, 255, 0.08)' : 'rgba(15, 23, 42, 0.06)'; const mutedButtonBorder = isDark ? 'rgba(248, 250, 255, 0.2)' : 'rgba(15, 23, 42, 0.12)'; const mutedText = isDark ? 'rgba(226, 232, 240, 0.7)' : 'rgba(15, 23, 42, 0.6)'; const clearAll = React.useCallback(async () => { 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) { const databases = ['guest-upload-queue', 'upload-queue']; await Promise.all( databases.map( (name) => new Promise((resolve) => { const request = indexedDB.deleteDatabase(name); request.onsuccess = () => resolve(null); request.onerror = () => resolve(null); }) ) ); } setDone(true); } finally { setBusy(false); window.setTimeout(() => setDone(false), 2500); } }, []); return ( {done ? ( {t('settings.cache.cleared', 'Cache cleared.')} ) : null} ); }