import { useState } from 'react'; import { useParams, Link, useNavigate } from 'react-router-dom'; import { useSeoMeta } from '@unhead/react'; import { nip19 } from 'nostr-tools'; import { Layout } from '@/components/Layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { ImageWithFallback } from '@/components/ImageWithFallback'; import { ImageLightbox, type LightboxImage } from '@/components/ImageLightbox'; import { TrustBadge } from '@/components/TrustBadge'; import { TradeReputationCard } from '@/components/TradeReputationCard'; import { ListingTradeActions } from '@/components/ListingTradeActions'; import { ListingOffersPanel } from '@/components/ListingOffersPanel'; import { OfferDialog } from '@/components/OfferDialog'; import { MarkSoldDialog } from '@/components/MarkSoldDialog'; import { DeleteEntityDialog } from '@/components/DeleteEntityDialog'; import { useMarketplaceItem } from '@/hooks/useMarketplace'; import { useMarkListingSold } from '@/hooks/useMarkListingSold'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAuthor } from '@/hooks/useAuthor'; import { useSellerInfo } from '@/hooks/useSellerInfo'; import { useToast } from '@/hooks/useToast'; import { formatPrice, GEAR_CONDITIONS, GEAR_CATEGORIES } from '@/lib/types'; import { genUserName } from '@/lib/genUserName'; import { cn } from '@/lib/utils'; import { ArrowLeft, CheckCircle, RotateCcw, Trash2, Package, MapPin, Calendar, Tag, Handshake, Share2, } from 'lucide-react'; /** * Full marketplace listing page (`/listing/:pubkey/:identifier`). * * Gives a listing a dedicated, shareable surface: a large gallery, the full * description, seller trust + trade reputation, and the shared trade/escrow * actions (`ListingTradeActions`, also used by the grid card). */ export default function ListingDetailPage() { const { pubkey, identifier } = useParams<{ pubkey: string; identifier: string }>(); const navigate = useNavigate(); const { user } = useCurrentUser(); const { toast } = useToast(); const { data: item, isLoading } = useMarketplaceItem(pubkey, identifier); const author = useAuthor(pubkey); const metadata = author.data?.metadata; const { data: sellerInfo } = useSellerInfo(pubkey); const { relist, isPending: isUpdatingStatus } = useMarkListingSold(); const [lightboxIndex, setLightboxIndex] = useState(null); const [showMarkSold, setShowMarkSold] = useState(false); const [showDelete, setShowDelete] = useState(false); const [showOffer, setShowOffer] = useState(false); useSeoMeta({ title: item ? `${item.title} โ€” SkateSpots Marketplace` : 'Listing โ€” SkateSpots', description: item?.summary || item?.description || 'A listing on the SkateSpots marketplace.', }); if (isLoading) { return (
); } if (!item) { return (

Listing not found

It may have been deleted, or the link is wrong.

); } const isOwnListing = user?.pubkey === item.pubkey; const isWTB = item.listingType === 'wtb'; const isSold = item.status === 'sold'; const priceDisplay = item.price ? `${formatPrice(item.price, item.priceType)}${item.price.frequency ? `/${item.price.frequency}` : ''}` : formatPrice(undefined, item.priceType); const conditionInfo = item.condition ? GEAR_CONDITIONS[item.condition] : null; const categoryLabel = item.category.find((c) => GEAR_CATEGORIES[c as keyof typeof GEAR_CATEGORIES]); const displayName = metadata?.name ?? (pubkey ? genUserName(pubkey) : 'Unknown'); const npub = pubkey ? nip19.npubEncode(pubkey) : ''; const lightboxImages: LightboxImage[] = item.images.map((url) => ({ url, isOwner: isOwnListing })); const handleShare = async () => { try { await navigator.clipboard.writeText(window.location.href); toast({ title: 'Link copied', description: 'Share it with a skater.' }); } catch { toast({ title: 'Could not copy the link', variant: 'destructive' }); } }; const handleRelist = async () => { try { await relist(item.event); toast({ title: 'Relisted', description: 'Your listing is active again.' }); } catch (error) { toast({ title: 'Could not update', description: error instanceof Error ? error.message : 'Something went wrong', variant: 'destructive', }); } }; return (
{/* Main column */}
{/* Gallery */} {item.images.length > 0 && (
{item.images.map((url, i) => ( ))}
)} {/* Title + price */}
{isWTB && ( WANT TO BUY )} {isSold && ( SOLD )} {item.status && item.status !== 'active' && !isSold && ( {item.status.toUpperCase()} )}

{item.title}

{priceDisplay}

{/* Meta badges */}
{conditionInfo && ( {conditionInfo.emoji} {conditionInfo.label} )} {item.size && ( {item.size} )} {categoryLabel && ( {categoryLabel} )}
{/* Description */} {item.description && ( Description

{item.description}

)} {/* Details */} {item.location && (
{item.location}
)} {item.availability && (
Usually here {item.availability}
)}
Listed{' '} {new Date(item.createdAt * 1000).toLocaleDateString()}
{/* Sidebar */}
setLightboxIndex(null)} /> {!isOwnListing && ( )} {isOwnListing && ( )} {isOwnListing && ( )}
); }