import { useState, useRef, useCallback, useEffect } from 'react' import { X } from '@phosphor-icons/react' import type { Settings } from '../types' import { trackEvent } from '../lib/telemetry' import { Switch } from './ui/switch' interface SettingsPanelProps { open: boolean settings: Settings onSave: (settings: Settings) => void explicitOrganizationEnabled?: boolean onSaveExplicitOrganization?: (enabled: boolean) => void onClose: () => void } // --- Settings Panel --- function isSaveShortcut(event: React.KeyboardEvent): boolean { return event.key === 'Enter' && (event.metaKey || event.ctrlKey) } export function SettingsPanel({ open, settings, onSave, explicitOrganizationEnabled = true, onSaveExplicitOrganization, onClose, }: SettingsPanelProps) { if (!open) return null return ( ) } type SettingsPanelInnerProps = Omit & { explicitOrganizationEnabled: boolean } function SettingsPanelInner({ settings, onSave, explicitOrganizationEnabled, onSaveExplicitOrganization, onClose, }: SettingsPanelInnerProps) { const [pullInterval, setPullInterval] = useState(settings.auto_pull_interval_minutes ?? 5) const [releaseChannel, setReleaseChannel] = useState(settings.release_channel ?? 'stable') const [crashReporting, setCrashReporting] = useState(settings.crash_reporting_enabled ?? false) const [analytics, setAnalytics] = useState(settings.analytics_enabled ?? false) const [explicitOrganization, setExplicitOrganization] = useState(explicitOrganizationEnabled) const panelRef = useRef(null) // Auto-focus first input when settings panel opens useEffect(() => { const timer = setTimeout(() => { const input = panelRef.current?.querySelector('input') input?.focus() }, 50) return () => clearTimeout(timer) }, []) const buildSettings = useCallback((): Settings => ({ auto_pull_interval_minutes: pullInterval, telemetry_consent: (crashReporting || analytics) ? true : (settings.telemetry_consent === null ? null : false), crash_reporting_enabled: crashReporting, analytics_enabled: analytics, anonymous_id: (crashReporting || analytics) ? (settings.anonymous_id ?? crypto.randomUUID()) : settings.anonymous_id, release_channel: releaseChannel === 'stable' ? null : releaseChannel, }), [pullInterval, releaseChannel, crashReporting, analytics, settings.telemetry_consent, settings.anonymous_id]) const handleSave = () => { const prevAnalytics = settings.analytics_enabled ?? false const newAnalytics = analytics if (!prevAnalytics && newAnalytics) trackEvent('telemetry_opted_in') if (prevAnalytics && !newAnalytics) trackEvent('telemetry_opted_out') onSave(buildSettings()) onSaveExplicitOrganization?.(explicitOrganization) onClose() } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Escape') { e.stopPropagation() onClose() } if (isSaveShortcut(e)) { e.preventDefault() handleSave() } } return (
{ if (e.target === e.currentTarget) onClose() }} onKeyDown={handleKeyDown} data-testid="settings-panel" >
) } function SettingsHeader({ onClose }: { onClose: () => void }) { return (
Settings
) } interface SettingsBodyProps { pullInterval: number; setPullInterval: (v: number) => void releaseChannel: string; setReleaseChannel: (v: string) => void explicitOrganization: boolean; setExplicitOrganization: (v: boolean) => void crashReporting: boolean; setCrashReporting: (v: boolean) => void analytics: boolean; setAnalytics: (v: boolean) => void } function SettingsBody(props: SettingsBodyProps) { return (
Sync
Automatically pull vault changes from Git in the background.
Release Channel
Controls which features are visible. Alpha users see all features. Beta/Stable see features as they are promoted.
Privacy & Telemetry
Anonymous data helps us fix bugs and improve Tolaria. No vault content, note titles, or file paths are ever sent.
) } function OrganizationWorkflowSection({ checked, onChange, }: { checked: boolean onChange: (value: boolean) => void }) { return ( <>
Workflow
Choose whether Tolaria shows the Inbox workflow and the organized toggle.
) } function TelemetryToggle({ label, description, checked, onChange, testId }: { label: string; description: string; checked: boolean; onChange: (v: boolean) => void; testId: string }) { return ( ) } function SettingsFooter({ onClose, onSave }: { onClose: () => void; onSave: () => void }) { return (
{'\u2318'}, to open settings
) }