import { updateOwnProfile } from '@chat-app/shared/auth'; import { changeLocale as changeLocaleI18n, SUPPORTED_LOCALES, type SupportedLocale, } from '@chat-app/shared/i18n'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { LockIcon } from '../components/icons'; import { useAuth } from '../context/AuthContext'; import { loadDevicePrivateKey, saveDevicePrivateKey } from '@chat-app/shared/auth'; import { exportDeviceKey, importDeviceKey } from '../lib/deviceBackup'; import { devLocalSecretStore } from '../lib/secretStore'; import { getPttSettings, keyCodeToLabel, type PttSettings, subscribePttSettings, updatePttSettings, } from '../lib/pttSettings'; import { AUDIO_QUALITY_ORDER, type AudioQuality, type AudioSettings, getAudioQualityParams, getAudioSettings, subscribeAudioSettings, updateAudioSettings, } from '../lib/audioSettings'; import { type CallE2EESettings, getCallE2EESettings, isE2EESupported, subscribeCallE2EESettings, updateCallE2EESettings, } from '../lib/callE2EE'; import { getPresetParams, getScreenShareSettings, PRESET_ORDER, type ScreenSharePreset, type ScreenShareSettings, subscribeScreenShareSettings, updateScreenShareSettings, } from '../lib/screenShareSettings'; import { supabase } from '../lib/supabase'; const LOCALE_LABELS: Record = { en: 'English', de: 'Deutsch', }; export function SettingsPage() { const { t, i18n } = useTranslation(['app', 'common', 'auth']); const { profile, device, refreshProfile, signOut } = useAuth(); const [busy, setBusy] = useState(false); async function patchProfile(patch: Parameters[1]) { setBusy(true); try { await updateOwnProfile(supabase, patch); await refreshProfile(); } catch (err: unknown) { console.error('updateProfile failed', err); } finally { setBusy(false); } } async function handleLocaleChange(locale: SupportedLocale) { await changeLocaleI18n(locale); void patchProfile({ locale }); } return (

{t('app:settings.title')}

{/* Account */}
{/* Appearance */}
{SUPPORTED_LOCALES.map((locale) => { const active = (i18n.resolvedLanguage ?? i18n.language) === locale; return ( ); })}
{/* Privacy */}
void patchProfile({ showReadReceipts: v })} /> void patchProfile({ allowDmsFromStrangers: v })} />
{/* Voice / Push-to-Talk + Audio Quality + E2EE */}
{/* Screen-share quality */}
{/* Devices */}
{device && (
{t('app:settings.this_device')}
)}
{/* Danger zone */}
); } function PttControls() { const { t } = useTranslation(['app']); const [ptt, setPtt] = useState(() => getPttSettings()); const [capturing, setCapturing] = useState(false); useEffect(() => { return subscribePttSettings(setPtt); }, []); useEffect(() => { if (!capturing) return; const onKey = (e: KeyboardEvent) => { e.preventDefault(); if (e.code === 'Escape') { setCapturing(false); return; } updatePttSettings({ key: e.code, keyLabel: keyCodeToLabel(e.code) }); setCapturing(false); }; window.addEventListener('keydown', onKey, { capture: true }); return () => window.removeEventListener('keydown', onKey, { capture: true }); }, [capturing]); return ( <> updatePttSettings({ enabled: v })} /> ); } function CallE2EEControls() { const { t } = useTranslation(['app']); const [cfg, setCfg] = useState(() => getCallE2EESettings()); const [supported] = useState(() => isE2EESupported()); useEffect(() => subscribeCallE2EESettings(setCfg), []); return ( <> updateCallE2EESettings({ enabled: v })} /> ); } function AudioQualityControls() { const { t } = useTranslation(['app']); const [cfg, setCfg] = useState(() => getAudioSettings()); useEffect(() => subscribeAudioSettings(setCfg), []); const params = getAudioQualityParams(cfg.quality); const labels: Record = { voice: t('app:settings.audio_voice', { defaultValue: 'Sprache (Empfohlen)' }), hifi: t('app:settings.audio_hifi', { defaultValue: 'HiFi / Musik' }), }; return ( <>
{AUDIO_QUALITY_ORDER.map((q) => { const active = cfg.quality === q; return ( ); })}

{cfg.quality === 'hifi' ? t('app:settings.audio_hifi_hint', { defaultValue: 'Stereo 510 kbps Opus ohne Noise-Suppression/Echo-Cancellation — bester Musik/Broadcast-Sound. Erfordert ruhige Umgebung.', }) : t('app:settings.audio_voice_hint', { defaultValue: 'Mono 48 kbps mit Noise-Suppression, Echo-Cancellation und Auto-Gain. Optimiert für Sprache im Raum.', })}

); } function DeviceKeyBackupControls() { const { t } = useTranslation(['app']); const { profile, device } = useAuth(); const [busy, setBusy] = useState(false); const [backupOut, setBackupOut] = useState(null); const [exportPass, setExportPass] = useState(''); const [importPass, setImportPass] = useState(''); const [importBlob, setImportBlob] = useState(''); const [msg, setMsg] = useState<{ kind: 'ok' | 'err'; text: string } | null>(null); const canRun = !!profile?.userId && !!device?.id; async function handleExport() { if (!canRun) return; setMsg(null); setBusy(true); try { const priv = await loadDevicePrivateKey(devLocalSecretStore, profile.userId, device.id); if (!priv) throw new Error('No device key on this install'); const out = await exportDeviceKey(priv, exportPass); setBackupOut(out); setExportPass(''); setMsg({ kind: 'ok', text: t('app:settings.backup_export_ok', { defaultValue: 'Backup erstellt — kopiere und bewahre es sicher auf.', }), }); } catch (err: unknown) { setMsg({ kind: 'err', text: err instanceof Error ? err.message : 'export failed', }); } finally { setBusy(false); } } async function handleImport() { if (!canRun) return; setMsg(null); setBusy(true); try { const priv = await importDeviceKey(importBlob.trim(), importPass); await saveDevicePrivateKey(devLocalSecretStore, profile.userId, device.id, priv); setImportBlob(''); setImportPass(''); setMsg({ kind: 'ok', text: t('app:settings.backup_import_ok', { defaultValue: 'Schlüssel importiert. Beim nächsten Reload sollten alte Nachrichten lesbar sein.', }), }); } catch (err: unknown) { setMsg({ kind: 'err', text: err instanceof Error ? err.message : 'import failed', }); } finally { setBusy(false); } } return (
{t('app:settings.device_key_backup', { defaultValue: 'Geräteschlüssel-Backup' })}

{t('app:settings.device_key_backup_hint', { defaultValue: 'Sichere deinen privaten Schlüssel passwortgeschützt, damit du auf neuen Geräten alte Nachrichten weiter lesen kannst.', })}

{t('app:settings.backup_export', { defaultValue: 'Export' })}
setExportPass(e.target.value)} placeholder={t('app:settings.backup_passphrase', { defaultValue: 'Passphrase (min 8)' })} className="flex-1 rounded-lg border border-white/10 bg-ink-800 px-3 py-2 text-sm text-white placeholder-neutral-500 focus:border-brand-400 focus:outline-none" />
{backupOut && (