import { useEffect, useState } from 'react' import type { EnrichedOrdinal } from '../lib/scanner' import { getServices } from '../lib/services' import { SWEEP_BATCH_SIZE } from '../lib/sweeper' import { Button } from './ui/button' import { Input } from './ui/input' export function OpnsSection({ opnsNames, selectedOpns, onToggle, onSelectPage, onDeselectAll, onSweep, onSweepAll, onSend, onBurn, walletConnected, }: { opnsNames: EnrichedOrdinal[] selectedOpns: Set onToggle: (outpoint: string) => void onSelectPage: (outpoints: string[]) => void onDeselectAll: () => void onSweep: () => void onSweepAll: () => void onSend?: (destination: string) => void onBurn?: () => void walletConnected: boolean }) { const [page, setPage] = useState(0) const [address, setAddress] = useState('') const [resolvedNames, setResolvedNames] = useState>( new Map(), ) const [overlayValid, setOverlayValid] = useState>( new Map(), ) const [validating, setValidating] = useState(false) useEffect(() => { if (opnsNames.length === 0) return const controller = new AbortController() const pending = new Map() Promise.all( opnsNames.map(async (item) => { try { const res = await fetch(item.contentUrl, { signal: controller.signal, }) if (res.ok) pending.set(item.outpoint, await res.text()) } catch { /* fetch aborted or failed */ } }), ).then(() => { if (!controller.signal.aborted) setResolvedNames(new Map(pending)) }) return () => controller.abort() }, [opnsNames]) useEffect(() => { if (opnsNames.length === 0) return let cancelled = false setValidating(true) const originToOutpoints = new Map() for (const item of opnsNames) { const origin = item.origin ?? item.outpoint const list = originToOutpoints.get(origin) ?? [] list.push(item.outpoint) originToOutpoints.set(origin, list) } getServices() .opns.validateOrigins([...originToOutpoints.keys()]) .then((result) => { if (cancelled) return const map = new Map() for (const [origin, valid] of Object.entries(result)) { for (const outpoint of originToOutpoints.get(origin) ?? []) map.set(outpoint, valid) } setOverlayValid(map) }) .catch(() => {}) .finally(() => { if (!cancelled) setValidating(false) }) return () => { cancelled = true } }, [opnsNames]) if (opnsNames.length === 0) return null const totalPages = Math.ceil(opnsNames.length / SWEEP_BATCH_SIZE) const safePage = Math.min(page, totalPages - 1) const start = safePage * SWEEP_BATCH_SIZE const pageItems = opnsNames.slice(start, start + SWEEP_BATCH_SIZE) const pageOutpoints = pageItems.map((o) => o.outpoint) const selectedCount = selectedOpns.size const batchCount = Math.ceil(opnsNames.length / SWEEP_BATCH_SIZE) return (
OPNS Domains
{opnsNames.length} domain{opnsNames.length !== 1 ? 's' : ''} {selectedCount > 0 && ( ({selectedCount} selected) )} ยท {SWEEP_BATCH_SIZE}/page
{pageItems.map((item) => { const isSelected = selectedOpns.has(item.outpoint) const displayName = resolvedNames.get(item.outpoint) ?? item.outpoint.substring(0, 8) + '...' return (
onToggle(item.outpoint)} >
{isSelected && '\u2713'}
{displayName} {!validating && overlayValid.has(item.outpoint) && (overlayValid.get(item.outpoint) ? ( valid ) : ( invalid ))}
) })}
{totalPages > 1 && (
Page {safePage + 1} of {totalPages}
)}
{selectedCount > 0 && onSend && ( setAddress(e.target.value)} className="font-mono text-xs" /> )}
{selectedCount > 0 && onSend && ( )} {selectedCount > 0 && ( )} {selectedCount > 0 && onBurn && ( )}
) }