import { useState, useEffect } from 'react'; import { Modal } from '../ui/Modal'; import { Avatar } from '../ui/Avatar'; import { SourceCodeLink } from '../ui/SourceCodeLink'; import { api } from '../../api/client'; import type { InstanceInfoResponse } from '@backspace/shared'; import { useUIStore } from '../../stores/uiStore'; import { useAuthStore } from '../../stores/authStore'; import { AccountPanel } from './settingsPanels/AccountPanel'; import { VoicePanel } from './settingsPanels/VoicePanel'; import { PrivacyPanel } from './settingsPanels/PrivacyPanel'; import { LanguagePanel } from './settingsPanels/LanguagePanel'; import { useT } from '../../i18n'; import { ConnectionsPanel } from './settingsPanels/ConnectionsPanel'; import { DesktopPanel } from './settingsPanels/DesktopPanel'; import { InstancePanel } from './settingsPanels/InstancePanel'; import { KeybindsPanel } from './settingsPanels/KeybindsPanel'; import { isElectron } from '../../platform/platform'; import { SettingsSectionsProvider, useSettingsSectionsContext } from './SettingsSectionsContext'; type SettingsTab = 'account' | 'voice' | 'privacy' | 'connections' | 'keybinds' | 'language' | 'desktop' | 'instance'; function SidebarSubLinks() { const ctx = useSettingsSectionsContext(); if (!ctx || ctx.sections.length === 0) return null; return (
{ctx.sections.map((section) => ( ))}
); } function SettingsScrollContainer({ children }: { children: React.ReactNode }) { const ctx = useSettingsSectionsContext(); return (
{children}
); } export function UserSettingsModal() { const activeModal = useUIStore((s) => s.activeModal); const modalData = useUIStore((s) => s.modalData); const closeModal = useUIStore((s) => s.closeModal); const isMobile = useUIStore((s) => s.isMobile); const isAdmin = useAuthStore((s) => s.user?.isAdmin); const user = useAuthStore((s) => s.user); const logout = useAuthStore((s) => s.logout); const t = useT(); const [tab, setTab] = useState('account'); const [mobileView, setMobileView] = useState<'tabs' | 'content'>('tabs'); // AGPL § 13: home-instance source offer. Fetched from the public info endpoint // so the source link reflects the version this instance is actually running. const [instanceInfo, setInstanceInfo] = useState(null); const isOpen = activeModal === 'userSettings'; useEffect(() => { if (!isOpen) return; let cancelled = false; api.instance.info() .then((info) => { if (!cancelled) setInstanceInfo(info); }) .catch(() => { /* Non-critical — link falls back to hidden if unreachable. */ }); return () => { cancelled = true; }; }, [isOpen]); // Deep-linking: read modalData.tab when opening useEffect(() => { if (isOpen) { const requested = modalData.tab as SettingsTab | undefined; if (requested && ['account', 'voice', 'privacy', 'connections', 'keybinds', 'language', 'instance'].includes(requested)) { // Only allow instance tab for admins if (requested === 'instance' && !isAdmin) { setTab('account'); } else { setTab(requested); } } else { setTab('account'); } // On mobile, if deep-linking to a tab, show content directly setMobileView(requested ? 'content' : 'tabs'); } }, [isOpen, modalData.tab, isAdmin]); const handleLogout = () => { logout(); closeModal(); }; const tabClass = (t: SettingsTab) => `w-full text-left px-3 py-2 rounded-md text-sm transition-colors ${ tab === t ? 'bg-interactive-selected text-txt-primary font-medium' : 'text-txt-tertiary hover:text-txt-secondary hover:bg-interactive-hover' }`; const handleTabClick = (t: SettingsTab) => { setTab(t); if (isMobile) setMobileView('content'); }; return (
{/* Desktop Sidebar */}
{/* User card */}
{user?.displayName || user?.username}
@{user?.username}
{/* Nav list */}
User Settings
App Settings
{isElectron() && } {isAdmin && ( <>
Administration
{tab === 'instance' && } )}
{instanceInfo && (
)}
{/* Mobile: Tab list */} {isMobile && mobileView === 'tabs' && (
{/* Mobile user card */}
{user?.displayName || user?.username}
@{user?.username}
User Settings
App Settings
{isElectron() && } {isAdmin && ( <>
Administration
)}
{instanceInfo && (
)}
)} {/* Content area (desktop always, mobile only when viewing content) */} {(!isMobile || mobileView === 'content') && (
{/* Mobile back button */} {isMobile && ( )} {tab === 'account' && } {tab === 'voice' && } {tab === 'privacy' && } {tab === 'connections' && } {tab === 'keybinds' && } {tab === 'language' && } {tab === 'desktop' && } {tab === 'instance' && isAdmin && }
)}
); }