import { useEffect, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPhone, faRotateLeft } from '@fortawesome/pro-duotone-svg-icons'; import { Button } from '@/components/base/buttons/button'; import { TopBar } from '@/components/layout/top-bar'; import { TelephonyForm, emptyTelephonyFormValues, type TelephonyFormValues, } from '@/components/forms/telephony-form'; import { apiClient } from '@/lib/api-client'; import { notify } from '@/lib/toast'; import { markSetupStepComplete } from '@/lib/setup-state'; // /settings/telephony — Pattern B page against the sidecar's // /api/config/telephony endpoint. The sidecar masks secrets on GET (agent // password + Exotel API token become '***masked***') and treats that sentinel // as "no change" on PUT, so we just round-trip the form values directly. // // Changes take effect immediately — TelephonyConfigService keeps an in-memory // cache that all consumers read via getters, no restart required. export const TelephonySettingsPage = () => { const [values, setValues] = useState(emptyTelephonyFormValues); const [loading, setLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [isResetting, setIsResetting] = useState(false); const loadConfig = async () => { try { const data = await apiClient.get('/api/config/telephony'); setValues({ ozonetel: { agentId: data.ozonetel?.agentId ?? '', agentPassword: data.ozonetel?.agentPassword ?? '', did: data.ozonetel?.did ?? '', sipId: data.ozonetel?.sipId ?? '', campaignName: data.ozonetel?.campaignName ?? '', }, sip: { domain: data.sip?.domain ?? 'blr-pub-rtc4.ozonetel.com', wsPort: data.sip?.wsPort ?? '444', }, exotel: { apiKey: data.exotel?.apiKey ?? '', apiToken: data.exotel?.apiToken ?? '', accountSid: data.exotel?.accountSid ?? '', subdomain: data.exotel?.subdomain ?? 'api.exotel.com', }, }); } catch { // toast already shown } finally { setLoading(false); } }; useEffect(() => { loadConfig(); }, []); const handleSave = async () => { setIsSaving(true); try { await apiClient.put('/api/config/telephony', { ozonetel: values.ozonetel, sip: values.sip, exotel: values.exotel, }); notify.success('Telephony updated', 'Changes are live — no restart needed.'); // Mark the wizard step complete if the required Ozonetel fields are // all filled in. Keeps the setup hub badges in sync with reality. const complete = !!values.ozonetel.agentId && !!values.ozonetel.did && !!values.ozonetel.sipId && !!values.ozonetel.campaignName; if (complete) { markSetupStepComplete('telephony').catch(() => {}); } await loadConfig(); } catch (err) { console.error('[telephony] save failed', err); } finally { setIsSaving(false); } }; const handleReset = async () => { if (!confirm('Reset telephony settings to defaults? Your Ozonetel and Exotel credentials will be cleared.')) { return; } setIsResetting(true); try { await apiClient.post('/api/config/telephony/reset'); notify.info('Telephony reset', 'All fields have been cleared.'); await loadConfig(); } catch (err) { console.error('[telephony] reset failed', err); } finally { setIsResetting(false); } }; return (

Credentials are stored locally

Values are written to the sidecar's data/telephony.json. API tokens are masked when loaded — leave the ***masked***{' '} placeholder to keep the existing value.

{loading ? (

Loading telephony settings...

) : (
)}
); };