import { useState } from 'react'; import { nip19 } from 'nostr-tools'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { useTradeAttestation, TRADE_METHODS, type TradeMethod } from '@/hooks/useTradeAttestations'; import { useMarkListingSold } from '@/hooks/useMarkListingSold'; import { useToast } from '@/hooks/useToast'; import { cn } from '@/lib/utils'; import { Check, Loader2 } from 'lucide-react'; import type { NostrEvent } from '@nostrify/nostrify'; function resolvePubkey(input: string): string | null { const t = input.trim(); if (/^[a-f0-9]{64}$/i.test(t)) return t.toLowerCase(); if (t.startsWith('npub1')) { try { const decoded = nip19.decode(t); return decoded.type === 'npub' ? decoded.data : null; } catch { return null; } } return null; } /** * Seller's "close the deal" flow: mark the listing sold AND publish a trade * attestation (kind 31871) naming the buyer, so both sides can build * reputation. Payment method is self-reported — the app can't see cash/trade * deals and never treats it as proof of payment. */ export function MarkSoldDialog({ open, onOpenChange, event, listing, }: { open: boolean; onOpenChange: (open: boolean) => void; event: NostrEvent; listing: { pubkey: string; identifier: string }; }) { const { publishTrade, isPending: publishing } = useTradeAttestation(); const { markSold, isPending: marking } = useMarkListingSold(); const { toast } = useToast(); const [buyer, setBuyer] = useState(''); const [method, setMethod] = useState('cash'); const [amount, setAmount] = useState(''); const buyerPubkey = resolvePubkey(buyer); const busy = publishing || marking; const submit = async () => { if (!buyerPubkey) { toast({ title: 'Enter the buyer’s npub', variant: 'destructive' }); return; } try { await markSold(event); await publishTrade({ listing, role: 'sold', counterparty: buyerPubkey, method, amountSats: amount.trim() ? Number(amount) : undefined, }); toast({ title: 'Deal closed 🎉', description: 'Listing marked sold and the trade was published for reputation.', }); onOpenChange(false); setBuyer(''); setAmount(''); } catch (e) { toast({ title: 'Could not close the deal', description: e instanceof Error ? e.message : 'Something went wrong.', variant: 'destructive', }); } }; return ( Close the deal Marks this listing sold and records a signed trade attestation for you and the buyer.
setBuyer(e.target.value)} placeholder="npub1…" className="h-11 border-gray-700 bg-gray-900 font-mono text-xs text-white" />
{(Object.keys(TRADE_METHODS) as TradeMethod[]).map((m) => ( ))}
setAmount(e.target.value)} inputMode="numeric" placeholder="e.g. 45000" className="h-11 border-gray-700 bg-gray-900 text-white" />
); }