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'; import { useTranslation } from '@/contexts/language'; 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 { t } = useTranslation(); 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: t('marketUi.markSold.enterBuyer'), variant: 'destructive' }); return; } try { await markSold(event); await publishTrade({ listing, role: 'sold', counterparty: buyerPubkey, method, amountSats: amount.trim() ? Number(amount) : undefined, }); toast({ title: t('marketUi.markSold.dealClosed'), description: t('marketUi.markSold.dealClosedBody'), }); onOpenChange(false); setBuyer(''); setAmount(''); } catch (e) { toast({ title: t('marketUi.markSold.closeFailed'), description: e instanceof Error ? e.message : t('marketUi.markSold.somethingWrong'), variant: 'destructive', }); } }; return ( {t('marketUi.markSold.title')} {t('marketUi.markSold.description')}
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={t('marketUi.offer.amountPlaceholder')} className="h-11 border-gray-700 bg-gray-900 text-white" />
); }