import React, { useEffect, useState } from 'react' import { Plane, Save } from 'lucide-react' import { useTranslation } from '../../i18n' import { useToast } from '../shared/Toast' import { airtrailApi } from '../../api/client' import Section from './Section' import ToggleSwitch from './ToggleSwitch' /** * Settings → Integrations → AirTrail. Per-user connection to a self-hosted * AirTrail instance (URL + Bearer API key). Mirrors the photo-provider (Immich) * connection layout: stacked fields, a toggle, then Save / Test-connection with * a status badge. The key is stored encrypted and never prefilled. */ export default function AirTrailConnectionSection(): React.ReactElement { const { t } = useTranslation() const toast = useToast() const [url, setUrl] = useState('') const [apiKey, setApiKey] = useState('') const [allowInsecureTls, setAllowInsecureTls] = useState(false) const [writeEnabled, setWriteEnabled] = useState(false) const [connected, setConnected] = useState(false) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [testing, setTesting] = useState(false) useEffect(() => { airtrailApi .getSettings() .then(d => { setUrl(d.url || '') setAllowInsecureTls(!!d.allowInsecureTls) setWriteEnabled(!!d.writeEnabled) setConnected(!!d.connected) }) .catch(() => {}) .finally(() => setLoading(false)) }, []) // Send the key only when the user typed a new one — never prefilled, so a blank // field means "keep the stored key". const keyPayload = (): { apiKey?: string } => { const k = apiKey.trim() return k ? { apiKey: k } : {} } const handleSave = async () => { setSaving(true) try { const d = await airtrailApi.saveSettings({ url: url.trim(), allowInsecureTls, writeEnabled, ...keyPayload() }) const status = await airtrailApi.status().catch(() => ({ connected: false })) setConnected(!!status.connected) setApiKey('') if (d?.warning) toast.warning(d.warning) else toast.success(t('settings.airtrail.toast.saved')) } catch (err: any) { toast.error(err?.response?.data?.error || t('settings.airtrail.toast.saveError')) } finally { setSaving(false) } } const handleTest = async () => { setTesting(true) try { const d = await airtrailApi.test({ url: url.trim(), allowInsecureTls, ...keyPayload() }) setConnected(!!d.connected) if (d.connected) toast.success(t('settings.airtrail.test.success', { count: d.flightCount ?? 0 })) else toast.error(d.error || t('settings.airtrail.test.failed')) } catch { toast.error(t('settings.airtrail.test.failed')) } finally { setTesting(false) } } const canSave = !!url.trim() && (connected || !!apiKey.trim()) return (
setUrl(e.target.value)} placeholder="https://airtrail.example.com" className="w-full px-3 py-2.5 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-300" />
setApiKey(e.target.value)} autoComplete="off" placeholder={connected && !apiKey ? '••••••••' : t('settings.airtrail.apiKeyPlaceholder')} className="w-full px-3 py-2.5 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-300" />

{t('settings.airtrail.apiKeyHint')}

setAllowInsecureTls(v => !v)} /> {t('settings.airtrail.allowInsecureTls')}
setWriteEnabled(v => !v)} /> {t('settings.airtrail.writeBack')}

{t('settings.airtrail.writeBackHint')}

{connected ? ( {t('settings.airtrail.connected')} ) : ( {t('settings.airtrail.notConnected')} )}

{t('settings.airtrail.hint')}

) }