import { useState, useEffect } from 'react'; import type { AppSettings, AppSettingsUpdate, RadioConfig, RadioConfigUpdate } from '../types'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from './ui/dialog'; import { Input } from './ui/input'; import { Label } from './ui/label'; import { Button } from './ui/button'; import { Separator } from './ui/separator'; import { Alert, AlertDescription } from './ui/alert'; interface ConfigModalProps { open: boolean; config: RadioConfig | null; appSettings: AppSettings | null; onClose: () => void; onSave: (update: RadioConfigUpdate) => Promise; onSaveAppSettings: (update: AppSettingsUpdate) => Promise; onSetPrivateKey: (key: string) => Promise; onReboot: () => Promise; } export function ConfigModal({ open, config, appSettings, onClose, onSave, onSaveAppSettings, onSetPrivateKey, onReboot, }: ConfigModalProps) { const [name, setName] = useState(''); const [lat, setLat] = useState(''); const [lon, setLon] = useState(''); const [txPower, setTxPower] = useState(''); const [freq, setFreq] = useState(''); const [bw, setBw] = useState(''); const [sf, setSf] = useState(''); const [cr, setCr] = useState(''); const [privateKey, setPrivateKey] = useState(''); const [maxRadioContacts, setMaxRadioContacts] = useState(''); const [loading, setLoading] = useState(false); const [rebooting, setRebooting] = useState(false); const [error, setError] = useState(''); useEffect(() => { if (config) { setName(config.name); setLat(String(config.lat)); setLon(String(config.lon)); setTxPower(String(config.tx_power)); setFreq(String(config.radio.freq)); setBw(String(config.radio.bw)); setSf(String(config.radio.sf)); setCr(String(config.radio.cr)); } }, [config]); useEffect(() => { if (appSettings) { setMaxRadioContacts(String(appSettings.max_radio_contacts)); } }, [appSettings]); const handleSave = async () => { setError(''); setLoading(true); try { const update: RadioConfigUpdate = { name, lat: parseFloat(lat), lon: parseFloat(lon), tx_power: parseInt(txPower, 10), radio: { freq: parseFloat(freq), bw: parseFloat(bw), sf: parseInt(sf, 10), cr: parseInt(cr, 10), }, }; await onSave(update); const newMaxRadioContacts = parseInt(maxRadioContacts, 10); if (!isNaN(newMaxRadioContacts) && newMaxRadioContacts !== appSettings?.max_radio_contacts) { await onSaveAppSettings({ max_radio_contacts: newMaxRadioContacts }); } onClose(); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to save'); } finally { setLoading(false); } }; const handleSetPrivateKey = async () => { if (!privateKey.trim()) { setError('Private key is required'); return; } setError(''); setLoading(true); try { await onSetPrivateKey(privateKey.trim()); setPrivateKey(''); onClose(); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to set private key'); } finally { setLoading(false); } }; const handleReboot = async () => { if (!confirm('Are you sure you want to reboot the radio? The connection will drop temporarily.')) { return; } setError(''); setRebooting(true); try { await onReboot(); onClose(); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to reboot radio'); } finally { setRebooting(false); } }; return ( !isOpen && onClose()}> Radio Configuration {!config ? (
Loading configuration...
) : (
setName(e.target.value)} />
setLat(e.target.value)} />
setLon(e.target.value)} />
setFreq(e.target.value)} />
setBw(e.target.value)} />
setSf(e.target.value)} />
setCr(e.target.value)} />
setTxPower(e.target.value)} />
setMaxRadioContacts(e.target.value)} />

Recent non-repeater contacts loaded to radio for DM auto-ACK (1-1000)

setPrivateKey(e.target.value)} placeholder="64-character hex private key" className="flex-1" />
Some configuration changes (like name) require a radio reboot to take effect. The connection will temporarily drop and automatically reconnect.
{error && (
{error}
)}
)}
); }