import { useState, useMemo } from "react"; import { Copy, Check, Plus, X, ExternalLink } from "lucide-react"; import { parseDecodeCommand, decodeNostr, reencodeWithRelays, type DecodedData, } from "@/lib/decode-parser"; import { useGrimoire } from "@/core/state"; import { useCopy } from "../hooks/useCopy"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { KindBadge } from "./KindBadge"; import { normalizeRelayURL } from "@/lib/relay-url"; interface DecodeViewerProps { args: string[]; } export default function DecodeViewer({ args }: DecodeViewerProps) { const { addWindow } = useGrimoire(); const { copy, copied } = useCopy(); const [relays, setRelays] = useState([]); const [newRelay, setNewRelay] = useState(""); const [error, setError] = useState(null); // Parse and decode const decoded = useMemo<{ bech32: string; data: DecodedData } | null>(() => { try { const parsed = parseDecodeCommand(args); const data = decodeNostr(parsed.bech32); // Initialize relays from decoded data if (data.type === "nprofile") { setRelays(data.data.relays || []); } else if (data.type === "nevent") { setRelays(data.data.relays || []); } else if (data.type === "naddr") { setRelays(data.data.relays || []); } setError(null); return { bech32: parsed.bech32, data }; } catch (err) { setError(err instanceof Error ? err.message : "Decode error"); return null; } }, [args]); // Re-encode with current relays const reencoded = useMemo(() => { if (!decoded) return null; try { return reencodeWithRelays(decoded.data, relays, decoded.bech32); } catch { return decoded.bech32; } }, [decoded, relays]); const copyToClipboard = () => { if (reencoded) { copy(reencoded); } }; const addRelay = () => { if (!newRelay.trim()) return; // Auto-add wss:// if no protocol let relayUrl = newRelay.trim(); if (!relayUrl.startsWith("ws://") && !relayUrl.startsWith("wss://")) { relayUrl = `wss://${relayUrl}`; } try { const url = new URL(relayUrl); if (!url.protocol.startsWith("ws")) { setError("Relay must be a WebSocket URL (ws:// or wss://)"); return; } setRelays([...relays, normalizeRelayURL(relayUrl)]); setNewRelay(""); setError(null); } catch { setError("Invalid relay URL"); } }; const removeRelay = (index: number) => { setRelays(relays.filter((_, i) => i !== index)); }; const openEvent = () => { if (!decoded) return; if (decoded.data.type === "note") { addWindow("open", { pointer: { id: decoded.data.data, relays } }); } else if (decoded.data.type === "nevent") { addWindow("open", { pointer: { id: decoded.data.data.id, relays } }); } else if (decoded.data.type === "naddr") { const { kind, pubkey, identifier } = decoded.data.data; addWindow("open", { pointer: { kind, pubkey, identifier, relays } }); } }; const openProfile = () => { if (!decoded) return; let pubkey: string | undefined; if (decoded.data.type === "npub") { pubkey = decoded.data.data; } else if (decoded.data.type === "nprofile") { pubkey = decoded.data.data.pubkey; } else if (decoded.data.type === "naddr") { pubkey = decoded.data.data.pubkey; } if (pubkey) { addWindow("profile", { pubkey }); } }; if (error) { return (
{error}
); } if (!decoded) { return (
Loading...
); } const { type, data } = decoded.data; const supportsRelays = ["nprofile", "nevent", "naddr"].includes(type); const canOpenEvent = ["note", "nevent", "naddr"].includes(type); const canOpenProfile = ["npub", "nprofile", "naddr"].includes(type); return (
{/* Header */}

DECODE {type.toUpperCase()}

{/* Content */}
{/* Decoded Information */}
Decoded Data
{type === "npub" && (
Public Key
{data}
)} {type === "note" && (
Event ID
{data}
)} {type === "nsec" && (
⚠️ Private Key (Keep Secret!)
{data}
)} {type === "nprofile" && (
Public Key
{(data as any).pubkey}
)} {type === "nevent" && ( <>
Event ID
{(data as any).id}
{(data as any).author && (
Author
{(data as any).author}
)} )} {type === "naddr" && ( <>
Kind
Identifier
{(data as any).identifier}
Public Key
{(data as any).pubkey}
)}
{/* Relay Editor */} {supportsRelays && (
Relays ({relays.length})
{relays.map((relay, index) => (
{relay}
))}
setNewRelay(e.target.value)} onKeyDown={(e) => e.key === "Enter" && addRelay()} className="font-mono text-xs" />
)} {/* Updated Identifier */}
{supportsRelays && relays.length > 0 ? "Updated Identifier" : "Original Identifier"}
{reencoded}
{/* Actions */}
{canOpenEvent && ( )} {canOpenProfile && ( )}
); }