import { useEffect, useRef, useState, type ChangeEvent, type ReactElement } from 'react'; import type { Settings } from '../../../server/vault.js'; import { getVaultPath, loadSettings, saveSettings, vaultDownload } from '../../lib/api.js'; function getBasename(p: string) { return p.split('/').pop()?.split('\\').pop() ?? '.vault'; } export function SettingsTab(): ReactElement { const [settings, setSettings] = useState(null); const [vaultPath, setVaultPath] = useState(''); const [vaultPathLoaded, setVaultPathLoaded] = useState(false); const [copied, setCopied] = useState(false); const [downloading, setDownloading] = useState(false); const [error, setError] = useState(null); const savedVaultPathRef = useRef(''); useEffect(() => { loadSettings() .then(s => setSettings(s)) .catch(() => setError('Failed to load settings')); getVaultPath() .then(r => { setVaultPath(r.path); savedVaultPathRef.current = r.path; setVaultPathLoaded(true); }) .catch(() => setVaultPathLoaded(true)); }, []); function handleCopyPath() { void navigator.clipboard.writeText(vaultPath).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000); }); } async function autoSave(patch: Partial) { try { const updated = await saveSettings(patch); setSettings(updated); } catch { setError('Failed to save settings'); } } function handleToggle(key: 'showBrowser' | 'fetchBankOfIsraelRates' | 'concurrentScraping') { return (e: ChangeEvent) => { const value = e.target.checked; setSettings(s => (s ? { ...s, [key]: value } : s)); void autoSave({ [key]: value }); }; } function handleNumberBlur(e: ChangeEvent) { const parsed = e.target.value ? parseInt(e.target.value, 10) : undefined; if (parsed !== undefined && Number.isNaN(parsed)) return; // Fall back to current value when field is cleared; server default takes over on next load const value = parsed ?? settings?.defaultDateRangeMonths ?? 3; setSettings(s => (s ? { ...s, defaultDateRangeMonths: value } : s)); void autoSave({ defaultDateRangeMonths: parsed }); } function handleTextBlur(key: 'historyFilePath') { return (e: ChangeEvent) => { const value = e.target.value || settings?.[key] || './history.json'; setSettings(s => (s ? { ...s, [key]: value } : s)); void autoSave({ [key]: e.target.value || undefined }); }; } if (!settings) return

{error ?? 'Loading…'}

; return (

Settings

{error && (

{error}

)}
Scraper options
{vaultPathLoaded && (
Vault file
setVaultPath(e.target.value)} onBlur={async e => { const val = e.target.value.trim(); if (!val) { setVaultPath(savedVaultPathRef.current); return; } if (val === savedVaultPathRef.current) { setVaultPath(val); return; } try { const updated = await saveSettings({ vaultPath: val }); setSettings(updated); savedVaultPathRef.current = val; setVaultPath(val); } catch { setError('Failed to save settings'); } }} aria-label="Vault file path" style={{ padding: '4px 8px', width: '100%', maxWidth: 360 }} placeholder=".vault" />

Back up this file to preserve your credentials.

)}
); }