import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import utc from "dayjs/plugin/utc"; import { ArrowDownIcon, ArrowDownUpIcon, ArrowUpDownIcon, ArrowUpIcon, ChevronDownIcon, ChevronUpIcon, TagIcon, XIcon, } from "lucide-react"; import { nip19 } from "nostr-tools"; import React from "react"; import { Link } from "react-router"; import AppAvatar from "src/components/AppAvatar"; import ExternalLink from "src/components/ExternalLink"; import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount"; import FormattedFiatAmount from "src/components/FormattedFiatAmount"; import { PaymentFailedAlert } from "src/components/PaymentFailedAlert"; import PodcastingInfo from "src/components/PodcastingInfo"; import { TransactionDetailRow } from "src/components/TransactionDetailRow"; import TransactionLabels from "src/components/TransactionLabels"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "src/components/ui/dialog"; import { useApp } from "src/hooks/useApp"; import { useSwap } from "src/hooks/useSwaps"; import { cn, getAppDisplayName } from "src/lib/utils"; import { Transaction } from "src/types"; dayjs.extend(relativeTime); dayjs.extend(utc); type Props = { tx: Transaction; transactionListKey: string; }; function safeNpubEncode(hex: string): string | undefined { try { return nip19.npubEncode(hex); } catch { return undefined; } } function safeNeventEncode(id: string): string | undefined { try { return nip19.neventEncode({ id, }); } catch { return undefined; } } function TransactionItem({ tx, transactionListKey }: Props) { const { data: app } = useApp(tx.appId); const swapId = tx.metadata?.swap_id; const { data: swap } = useSwap(swapId); const [showDetails, setShowDetails] = React.useState(false); const labels = tx.metadata?.user_labels ?? {}; const labelEntries = Object.entries(labels); const type = tx.type; const updatedAt = dayjs(tx.updatedAt).local(); const pubkey = tx.metadata?.nostr?.pubkey; const npub = pubkey ? safeNpubEncode(pubkey) : undefined; const payerName = tx.metadata?.payer_data?.name; const from = type === "incoming" ? payerName ? `from ${payerName}` : npub ? `zap from ${npub.substring(0, 12)}...` : swap ? `swap from ${swap.lockupAddress}` : undefined : undefined; const recipientIdentifier = tx.metadata?.recipient_data?.identifier; const to = type === "outgoing" ? npub ? `zap to ${npub.substring(0, 12)}...` : swap?.type === "out" ? `swap to ${swap.destinationAddress}` : recipientIdentifier ? `${tx.state === "failed" ? "payment " : ""}to ${recipientIdentifier}` : undefined : undefined; const eventId = tx.metadata?.nostr?.tags?.find((t) => t[0] === "e")?.[1]; const nevent = eventId ? safeNeventEncode(eventId) : undefined; const bolt12Offer = tx.metadata?.offer; const description = tx.description || tx.metadata?.comment || bolt12Offer?.payer_note; const typeStateText = type == "incoming" ? "Received" : tx.state === "settled" // we only fetch settled incoming payments ? "Sent" : tx.state === "pending" ? "Sending" : "Failed"; const Icon = tx.state === "failed" ? XIcon : tx.type === "outgoing" ? swapId ? ArrowUpDownIcon : ArrowUpIcon : swapId ? ArrowDownUpIcon : ArrowDownIcon; const typeStateIcon = (