'use client'
import { useEffect, useState, useMemo, useCallback } from 'react'
import { api } from '@/lib/api'
import { TerminalLayout } from '@/components/TerminalLayout'
import {
PageContainer,
StatCard,
Badge,
SearchInput,
FilterBar,
SelectDropdown,
ActionButton,
} from '@/components/PremiumTable'
import {
Search,
Shield,
Loader2,
ExternalLink,
Store,
Tag,
DollarSign,
Filter,
} from 'lucide-react'
import Link from 'next/link'
import clsx from 'clsx'
interface Listing {
domain: string
slug: string
title: string | null
description: string | null
asking_price: number | null
currency: string
price_type: string
pounce_score: number | null
estimated_value: number | null
is_verified: boolean
allow_offers: boolean
public_url: string
seller_verified: boolean
}
type SortOption = 'newest' | 'price_asc' | 'price_desc' | 'score'
export default function CommandMarketplacePage() {
const [listings, setListings] = useState
([])
const [loading, setLoading] = useState(true)
const [searchQuery, setSearchQuery] = useState('')
const [minPrice, setMinPrice] = useState('')
const [maxPrice, setMaxPrice] = useState('')
const [verifiedOnly, setVerifiedOnly] = useState(false)
const [sortBy, setSortBy] = useState('newest')
const [showFilters, setShowFilters] = useState(false)
const loadListings = useCallback(async () => {
setLoading(true)
try {
const params = new URLSearchParams()
params.set('limit', '100')
if (sortBy === 'price_asc') params.set('sort', 'price_asc')
if (sortBy === 'price_desc') params.set('sort', 'price_desc')
if (verifiedOnly) params.set('verified_only', 'true')
const data = await api.request(`/listings?${params.toString()}`)
setListings(data)
} catch (err) {
console.error('Failed to load listings:', err)
} finally {
setLoading(false)
}
}, [sortBy, verifiedOnly])
useEffect(() => {
loadListings()
}, [loadListings])
const formatPrice = (price: number | null, currency: string) => {
if (!price) return 'Make Offer'
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
minimumFractionDigits: 0,
}).format(price)
}
// Memoized filtered and sorted listings
const sortedListings = useMemo(() => {
let result = listings.filter(listing => {
if (searchQuery && !listing.domain.toLowerCase().includes(searchQuery.toLowerCase())) return false
if (minPrice && listing.asking_price && listing.asking_price < parseFloat(minPrice)) return false
if (maxPrice && listing.asking_price && listing.asking_price > parseFloat(maxPrice)) return false
return true
})
return result.sort((a, b) => {
switch (sortBy) {
case 'price_asc': return (a.asking_price || 0) - (b.asking_price || 0)
case 'price_desc': return (b.asking_price || 0) - (a.asking_price || 0)
case 'score': return (b.pounce_score || 0) - (a.pounce_score || 0)
default: return 0
}
})
}, [listings, searchQuery, minPrice, maxPrice, sortBy])
// Memoized stats
const stats = useMemo(() => {
const verifiedCount = listings.filter(l => l.is_verified).length
const pricesWithValue = listings.filter(l => l.asking_price)
const avgPrice = pricesWithValue.length > 0
? pricesWithValue.reduce((sum, l) => sum + (l.asking_price || 0), 0) / pricesWithValue.length
: 0
return { verifiedCount, avgPrice }
}, [listings])
return (
My Listings
}
>
{/* Stats */}
0 ? `$${Math.round(stats.avgPrice).toLocaleString()}` : '—'}
icon={DollarSign}
/>
{/* Search & Filters */}
{/* Search */}
setSearchQuery(e.target.value)}
className="w-full pl-12 pr-4 py-3 bg-background border border-border rounded-xl
text-foreground placeholder:text-foreground-subtle
focus:outline-none focus:ring-2 focus:ring-accent/30 focus:border-accent"
/>
{/* Sort */}
{/* Filter Toggle */}
{/* Expanded Filters */}
{showFilters && (
)}
{/* Listings Grid */}
{loading ? (
) : sortedListings.length === 0 ? (
No Domains Found
{searchQuery || minPrice || maxPrice
? 'Try adjusting your filters'
: 'No domains are currently listed for sale'}
List Your Domain
) : (
{sortedListings.map((listing) => (
{listing.domain}
{listing.title && (
{listing.title}
)}
{listing.is_verified && (
)}
{listing.description && (
{listing.description}
)}
{listing.pounce_score && (
{listing.pounce_score}
)}
{listing.allow_offers && (
Offers
)}
{formatPrice(listing.asking_price, listing.currency)}
{listing.price_type === 'negotiable' && (
Negotiable
)}
))}
)}
)
}