import { useState, useEffect } from 'react' import { configStorage } from '@/lib/configStorage' import type { Nip95Config } from '@/lib/configStorageTypes' interface Nip95ConfigManagerProps { onConfigChange?: () => void } export function Nip95ConfigManager({ onConfigChange }: Nip95ConfigManagerProps) { const [apis, setApis] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [editingId, setEditingId] = useState(null) const [newUrl, setNewUrl] = useState('') const [showAddForm, setShowAddForm] = useState(false) useEffect(() => { void loadApis() }, []) async function loadApis() { try { setLoading(true) setError(null) const config = await configStorage.getConfig() setApis(config.nip95Apis.sort((a, b) => a.priority - b.priority)) } catch (e) { const errorMessage = e instanceof Error ? e.message : 'Failed to load NIP-95 APIs' setError(errorMessage) console.error('Error loading NIP-95 APIs:', e) } finally { setLoading(false) } } async function handleToggleEnabled(id: string, enabled: boolean) { try { await configStorage.updateNip95Api(id, { enabled }) await loadApis() onConfigChange?.() } catch (e) { const errorMessage = e instanceof Error ? e.message : 'Failed to update API' setError(errorMessage) console.error('Error updating NIP-95 API:', e) } } async function handleUpdatePriority(id: string, priority: number) { try { await configStorage.updateNip95Api(id, { priority }) await loadApis() onConfigChange?.() } catch (e) { const errorMessage = e instanceof Error ? e.message : 'Failed to update priority' setError(errorMessage) console.error('Error updating priority:', e) } } async function handleUpdateUrl(id: string, url: string) { try { await configStorage.updateNip95Api(id, { url }) await loadApis() setEditingId(null) onConfigChange?.() } catch (e) { const errorMessage = e instanceof Error ? e.message : 'Failed to update URL' setError(errorMessage) console.error('Error updating URL:', e) } } async function handleAddApi() { if (!newUrl.trim()) { setError('URL is required') return } try { // Validate URL format new URL(newUrl) await configStorage.addNip95Api(newUrl.trim(), false) setNewUrl('') setShowAddForm(false) await loadApis() onConfigChange?.() } catch (e) { if (e instanceof TypeError && e.message.includes('Invalid URL')) { setError('Invalid URL format') } else { const errorMessage = e instanceof Error ? e.message : 'Failed to add API' setError(errorMessage) } console.error('Error adding NIP-95 API:', e) } } async function handleRemoveApi(id: string) { if (!confirm('Are you sure you want to remove this endpoint?')) { return } try { await configStorage.removeNip95Api(id) await loadApis() onConfigChange?.() } catch (e) { const errorMessage = e instanceof Error ? e.message : 'Failed to remove API' setError(errorMessage) console.error('Error removing NIP-95 API:', e) } } if (loading) { return (
Loading...
) } return (
{error && (
{error}
)}

NIP-95 Upload Endpoints

{showAddForm && (
setNewUrl(e.target.value)} placeholder="https://example.com/upload" className="w-full px-4 py-2 bg-cyber-darker border border-cyber-accent/30 rounded text-cyber-light focus:border-neon-cyan focus:outline-none" />
)}
{apis.length === 0 ? (
No NIP-95 endpoints configured
) : ( apis.map((api) => (
{editingId === api.id ? (
{ if (e.target.value !== api.url) { void handleUpdateUrl(api.id, e.target.value) } else { setEditingId(null) } }} onKeyDown={(e) => { if (e.key === 'Enter') { e.currentTarget.blur() } else if (e.key === 'Escape') { setEditingId(null) } }} className="w-full px-3 py-2 bg-cyber-darker border border-neon-cyan/50 rounded text-cyber-light focus:border-neon-cyan focus:outline-none" autoFocus />
) : (
setEditingId(api.id)} title="Click to edit URL" > {api.url}
)}
Priority: {api.priority} | ID: {api.id}
)) )}

Note: Endpoints are tried in priority order (lower number = higher priority). Only enabled endpoints will be used for uploads.

If an endpoint fails, the next enabled endpoint will be tried automatically.

) }