'use client' import { useRouter } from 'next/navigation' import { isSome } from '@solana/kit' import { useListing } from '@/hooks/useListing' import { useOrdersForListing } from '@/hooks/useOrdersForListing' import { Badge, STATUS_COLORS } from '@/components/ui/Badge' import { FieldRow, MonoChip } from '@/components/ui/FieldRow' import { Button } from '@/components/ui/Button' import { abbrev, fmtSol, fmtTok, priceStr } from '@/lib/format' import type { Address } from '@solisting/sdk' interface Props { pk: Address walletAddress?: string | null onUpdate?: () => void onClose?: () => void onPlaceOrder?: () => void } export function ListingDetail({ pk, walletAddress, onUpdate, onClose, onPlaceOrder }: Props) { const router = useRouter() const { data: listing, isLoading, error } = useListing(pk) const { data: orders = [] } = useOrdersForListing(pk) if (isLoading) return
Loading…
if (error || !listing) return
Listing account not found.
const d = listing.data const avail = d.quantity - d.quantityReserved const availPct = d.quantity > 0 ? `${(avail / d.quantity) * 100}%` : '0%' const reservedPct = d.quantity > 0 ? `${(d.quantityReserved / d.quantity) * 100}%` : '0%' const sc = STATUS_COLORS[d.isActive ? 'active' : 'inactive'] const isSeller = walletAddress && walletAddress === d.seller const canPlace = !isSeller && d.isActive && avail > 0 && walletAddress const canonicalOracleAddr = isSome(d.canonicalOracle) ? d.canonicalOracle.value : null function fmtListingPrice() { const cur = d.canonicalCurrency if (cur.__kind === 'Sol') return fmtSol(d.price) return fmtTok(d.price, cur.decimals, 'SPL') } return (
LISTING ACCOUNT · #{String(d.listingId)}

{d.name || abbrev(pk)}

{d.isActive ? 'Active' : 'Inactive'}
{(d.description || d.metadataUri) && (

{d.description || ( {d.metadataUri} )}

)}
{/* Left: account fields */}
ACCOUNT FIELDS
navigator.clipboard.writeText(pk)} /> navigator.clipboard.writeText(d.seller)} onClick={() => router.push(`/search?q=${d.seller}`)} /> #{String(d.listingId)} {d.canonicalCurrency.__kind === 'Sol' ? 'SOL' : `SPL · ${abbrev((d.canonicalCurrency as { mint: string }).mint)}`} {fmtListingPrice()} {canonicalOracleAddr ? navigator.clipboard.writeText(canonicalOracleAddr)} /> : None — priced directly } {d.isActive ? 'Active' : 'Inactive'} {d.bump} {d.metadataUri && ( <>
METADATA URI
{d.metadataUri} )}
ALT CURRENCIES
{d.altCurrencies.length === 0 ?
None configured.
: d.altCurrencies.map((alt, i) => (
{alt.currency.__kind === 'Sol' ? 'SOL' : `SPL · ${abbrev((alt.currency as { mint: string }).mint)}`} {isSome(alt.usdOracle) ? abbrev(alt.usdOracle.value) : 'stablecoin ($1.00)'}
)) }
ACCEPTED RESOLVERS
{d.acceptedResolvers.length === 0 ?
Empty — any registered resolver is accepted.
: d.acceptedResolvers.map((r) => (
router.push(`/resolver/${r}`)} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '11px 13px', background: 'var(--bg3)', borderRadius: 9, marginBottom: 7, cursor: 'pointer' }}> {abbrev(r)}
)) }
{/* Right: qty + actions */}
QUANTITY
{avail}
available
{d.quantityReserved}
reserved
{d.quantity}
total
ACTIONS
{isSeller && (
)} {canPlace && } {!walletAddress &&
Connect a wallet to place an order or manage this listing.
} {walletAddress && !isSeller && !canPlace &&
No actions available — listing is inactive or out of stock.
}
{/* Orders table */}
ORDERS ON THIS LISTING
ORDER
BUYER
AMOUNT
ORDER ID
ESCROW ID
{orders.length === 0 &&
No orders yet.
} {orders.map((o) => (
router.push(`/order/${o.address}`)} style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr', gap: 12, padding: '14px 20px', borderBottom: '1px solid var(--bdSoft)', cursor: 'pointer', alignItems: 'center' }} >
{abbrev(o.address)}
{abbrev(o.data.buyer)}
{priceStr(o.data.paymentCurrency, o.data.amount)}
#{String(o.data.orderId)}
#{String(o.data.escrowId)}
))}
) }