import { useState, useEffect } from 'react' import { configStorage } from '@/lib/configStorage' import type { Nip95Config } from '@/lib/configStorageTypes' import { t } from '@/lib/i18n' import { userConfirm } from '@/lib/userConfirm' interface Nip95ConfigManagerProps { onConfigChange?: () => void } export function Nip95ConfigManager({ onConfigChange }: Nip95ConfigManagerProps): React.ReactElement { 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) const [draggedId, setDraggedId] = useState(null) const [dragOverId, setDragOverId] = useState(null) useEffect(() => { void loadApis() }, []) async function loadApis(): Promise { 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 : t('settings.nip95.error.loadFailed') setError(errorMessage) console.error('Error loading NIP-95 APIs:', e) } finally { setLoading(false) } } async function handleToggleEnabled(id: string, enabled: boolean): Promise { try { await configStorage.updateNip95Api(id, { enabled }) await loadApis() onConfigChange?.() } catch (e) { const errorMessage = e instanceof Error ? e.message : t('settings.nip95.error.updateFailed') setError(errorMessage) console.error('Error updating NIP-95 API:', e) } } async function handleUpdatePriorities(newOrder: Nip95Config[]): Promise { try { // Update priorities based on new order (priority = index + 1, lower number = higher priority) const updatePromises = newOrder.map((api, index) => { const newPriority = index + 1 if (api.priority !== newPriority) { return configStorage.updateNip95Api(api.id, { priority: newPriority }) } return Promise.resolve() }) await Promise.all(updatePromises) await loadApis() onConfigChange?.() } catch (e) { const errorMessage = e instanceof Error ? e.message : t('settings.nip95.error.priorityFailed') setError(errorMessage) console.error('Error updating priorities:', e) } } function handleDragStart(e: React.DragEvent, id: string): void { setDraggedId(id) e.dataTransfer.effectAllowed = 'move' e.dataTransfer.setData('text/plain', id) } function handleDragOver(e: React.DragEvent, id: string): void { e.preventDefault() e.dataTransfer.dropEffect = 'move' setDragOverId(id) } function handleDragLeave(): void { setDragOverId(null) } function handleDrop(e: React.DragEvent, targetId: string): void { e.preventDefault() setDragOverId(null) if (!draggedId || draggedId === targetId) { setDraggedId(null) return } const draggedIndex = apis.findIndex((api) => api.id === draggedId) const targetIndex = apis.findIndex((api) => api.id === targetId) if (draggedIndex === -1 || targetIndex === -1) { setDraggedId(null) return } // Reorder the array const newApis = [...apis] const removed = newApis[draggedIndex] if (!removed) { setDraggedId(null) return } newApis.splice(draggedIndex, 1) newApis.splice(targetIndex, 0, removed) setApis(newApis) setDraggedId(null) // Update priorities based on new order void handleUpdatePriorities(newApis) } function DragHandle(): React.ReactElement { return (
) } async function handleUpdateUrl(id: string, url: string): Promise { try { await configStorage.updateNip95Api(id, { url }) await loadApis() setEditingId(null) onConfigChange?.() } catch (e) { const errorMessage = e instanceof Error ? e.message : t('settings.nip95.error.urlFailed') setError(errorMessage) console.error('Error updating URL:', e) } } async function handleAddApi(): Promise { if (!newUrl.trim()) { setError(t('settings.nip95.error.urlRequired')) return } try { // Validate URL format - throws if invalid void 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(t('settings.nip95.error.invalidUrl')) } else { const errorMessage = e instanceof Error ? e.message : t('settings.nip95.error.addFailed') setError(errorMessage) } console.error('Error adding NIP-95 API:', e) } } async function handleRemoveApi(id: string): Promise { if (!userConfirm(t('settings.nip95.remove.confirm'))) { return } try { await configStorage.removeNip95Api(id) await loadApis() onConfigChange?.() } catch (e) { const errorMessage = e instanceof Error ? e.message : t('settings.nip95.error.removeFailed') setError(errorMessage) console.error('Error removing NIP-95 API:', e) } } if (loading) { return (
{t('settings.nip95.loading')}
) } return (
{error && (
{error}
)}

{t('settings.nip95.title')}

{showAddForm && (
setNewUrl(e.target.value)} placeholder={t('settings.nip95.add.placeholder')} 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 ? (
{t('settings.nip95.empty')}
) : ( apis.map((api, index) => (
{ handleDragOver(e, api.id) }} onDragLeave={handleDragLeave} onDrop={(e) => { handleDrop(e, api.id) }} className={`bg-cyber-dark border rounded p-4 space-y-3 transition-all ${ draggedId === api.id ? 'opacity-50 border-neon-cyan' : dragOverId === api.id ? 'border-neon-green shadow-lg' : 'border-neon-cyan/30' }`} >
{ handleDragStart(e, api.id) e.stopPropagation() }} onMouseDown={(e) => { e.stopPropagation() }} >
{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={t('settings.nip95.list.editUrl')} > {api.url}
)}
{t('settings.nip95.list.priorityLabel', { priority: index + 1, id: api.id })}
)) )}

{t('settings.nip95.note.title')} {t('settings.nip95.note.priority')}

{t('settings.nip95.note.fallback')}

) }