Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
253 lines
9.2 KiB
TypeScript
253 lines
9.2 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { useStore } from '@/lib/store'
|
|
import { api } from '@/lib/api'
|
|
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
|
|
import {
|
|
Search,
|
|
Filter,
|
|
Clock,
|
|
TrendingUp,
|
|
Flame,
|
|
Sparkles,
|
|
ExternalLink,
|
|
ChevronDown,
|
|
Globe,
|
|
Gavel,
|
|
ArrowUpDown,
|
|
} from 'lucide-react'
|
|
import clsx from 'clsx'
|
|
|
|
type ViewType = 'all' | 'ending' | 'hot' | 'opportunities'
|
|
|
|
interface Auction {
|
|
domain: string
|
|
platform: string
|
|
current_bid: number
|
|
num_bids: number
|
|
end_time: string
|
|
time_remaining: string
|
|
affiliate_url: string
|
|
tld: string
|
|
}
|
|
|
|
export default function MarketPage() {
|
|
const router = useRouter()
|
|
const { subscription } = useStore()
|
|
|
|
const [auctions, setAuctions] = useState<Auction[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [activeView, setActiveView] = useState<ViewType>('all')
|
|
const [searchQuery, setSearchQuery] = useState('')
|
|
const [platformFilter, setPlatformFilter] = useState<string>('all')
|
|
const [sortBy, setSortBy] = useState<string>('end_time')
|
|
|
|
useEffect(() => {
|
|
loadAuctions()
|
|
}, [activeView])
|
|
|
|
const loadAuctions = async () => {
|
|
setLoading(true)
|
|
try {
|
|
let data
|
|
switch (activeView) {
|
|
case 'ending':
|
|
data = await api.getEndingSoonAuctions(50)
|
|
break
|
|
case 'hot':
|
|
data = await api.getHotAuctions(50)
|
|
break
|
|
case 'opportunities':
|
|
const oppData = await api.getAuctionOpportunities()
|
|
data = oppData.opportunities || []
|
|
break
|
|
default:
|
|
data = await api.getAuctions(undefined, undefined, undefined, undefined, undefined, false, sortBy, 50)
|
|
}
|
|
setAuctions(data)
|
|
} catch (error) {
|
|
console.error('Failed to load auctions:', error)
|
|
setAuctions([])
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
// Filter auctions
|
|
const filteredAuctions = auctions.filter(auction => {
|
|
if (searchQuery && !auction.domain.toLowerCase().includes(searchQuery.toLowerCase())) {
|
|
return false
|
|
}
|
|
if (platformFilter !== 'all' && auction.platform !== platformFilter) {
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
|
|
const platforms = ['GoDaddy', 'Sedo', 'NameJet', 'DropCatch', 'ExpiredDomains']
|
|
|
|
const views = [
|
|
{ id: 'all', label: 'All Auctions', icon: Gavel },
|
|
{ id: 'ending', label: 'Ending Soon', icon: Clock },
|
|
{ id: 'hot', label: 'Hot', icon: Flame },
|
|
{ id: 'opportunities', label: 'Opportunities', icon: Sparkles },
|
|
]
|
|
|
|
return (
|
|
<CommandCenterLayout
|
|
title="Market Scanner"
|
|
subtitle="Live auctions from all major platforms"
|
|
>
|
|
<div className="max-w-7xl mx-auto space-y-6">
|
|
{/* View Tabs */}
|
|
<div className="flex flex-wrap gap-2 p-1 bg-background-secondary/50 rounded-xl border border-border">
|
|
{views.map((view) => (
|
|
<button
|
|
key={view.id}
|
|
onClick={() => setActiveView(view.id as ViewType)}
|
|
className={clsx(
|
|
"flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium transition-all",
|
|
activeView === view.id
|
|
? "bg-foreground text-background"
|
|
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5"
|
|
)}
|
|
>
|
|
<view.icon className="w-4 h-4" />
|
|
{view.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Filters */}
|
|
<div className="flex flex-col sm:flex-row gap-3">
|
|
{/* Search */}
|
|
<div className="relative flex-1">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-muted" />
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
placeholder="Search domains..."
|
|
className="w-full h-10 pl-10 pr-4 bg-background-secondary border border-border rounded-lg
|
|
text-sm text-foreground placeholder:text-foreground-subtle
|
|
focus:outline-none focus:border-accent"
|
|
/>
|
|
</div>
|
|
|
|
{/* Platform Filter */}
|
|
<div className="relative">
|
|
<select
|
|
value={platformFilter}
|
|
onChange={(e) => setPlatformFilter(e.target.value)}
|
|
className="h-10 pl-4 pr-10 bg-background-secondary border border-border rounded-lg
|
|
text-sm text-foreground appearance-none cursor-pointer
|
|
focus:outline-none focus:border-accent"
|
|
>
|
|
<option value="all">All Platforms</option>
|
|
{platforms.map((p) => (
|
|
<option key={p} value={p}>{p}</option>
|
|
))}
|
|
</select>
|
|
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-muted pointer-events-none" />
|
|
</div>
|
|
|
|
{/* Sort */}
|
|
<div className="relative">
|
|
<select
|
|
value={sortBy}
|
|
onChange={(e) => {
|
|
setSortBy(e.target.value)
|
|
loadAuctions()
|
|
}}
|
|
className="h-10 pl-4 pr-10 bg-background-secondary border border-border rounded-lg
|
|
text-sm text-foreground appearance-none cursor-pointer
|
|
focus:outline-none focus:border-accent"
|
|
>
|
|
<option value="end_time">Ending Soon</option>
|
|
<option value="bid_asc">Price: Low to High</option>
|
|
<option value="bid_desc">Price: High to Low</option>
|
|
<option value="bids">Most Bids</option>
|
|
</select>
|
|
<ArrowUpDown className="absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-muted pointer-events-none" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Bar */}
|
|
<div className="flex items-center gap-6 text-sm text-foreground-muted">
|
|
<span>{filteredAuctions.length} auctions</span>
|
|
<span>•</span>
|
|
<span className="flex items-center gap-1.5">
|
|
<Globe className="w-3.5 h-3.5" />
|
|
{platforms.length} platforms
|
|
</span>
|
|
</div>
|
|
|
|
{/* Auction List */}
|
|
{loading ? (
|
|
<div className="space-y-3">
|
|
{[...Array(5)].map((_, i) => (
|
|
<div key={i} className="h-20 bg-background-secondary/50 border border-border rounded-xl animate-pulse" />
|
|
))}
|
|
</div>
|
|
) : filteredAuctions.length === 0 ? (
|
|
<div className="text-center py-16 bg-background-secondary/30 border border-border rounded-xl">
|
|
<Gavel className="w-12 h-12 text-foreground-subtle mx-auto mb-4" />
|
|
<p className="text-foreground-muted">No auctions found</p>
|
|
<p className="text-sm text-foreground-subtle mt-1">Try adjusting your filters</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{filteredAuctions.map((auction, idx) => (
|
|
<div
|
|
key={`${auction.domain}-${idx}`}
|
|
className="group p-4 sm:p-5 bg-background-secondary/50 border border-border rounded-xl
|
|
hover:border-foreground/20 transition-all"
|
|
>
|
|
<div className="flex flex-col sm:flex-row sm:items-center gap-4">
|
|
{/* Domain Info */}
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-3 mb-1">
|
|
<h3 className="text-lg font-semibold text-foreground truncate">{auction.domain}</h3>
|
|
<span className="shrink-0 px-2 py-0.5 bg-foreground/5 text-foreground-muted text-xs rounded">
|
|
{auction.platform}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-4 text-sm text-foreground-muted">
|
|
<span className="flex items-center gap-1.5">
|
|
<Clock className="w-3.5 h-3.5" />
|
|
{auction.time_remaining}
|
|
</span>
|
|
<span>{auction.num_bids} bids</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Price + Action */}
|
|
<div className="flex items-center gap-4 shrink-0">
|
|
<div className="text-right">
|
|
<p className="text-xl font-semibold text-foreground">${auction.current_bid.toLocaleString()}</p>
|
|
<p className="text-xs text-foreground-subtle">Current bid</p>
|
|
</div>
|
|
<a
|
|
href={auction.affiliate_url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="flex items-center gap-2 px-4 py-2.5 bg-foreground text-background rounded-lg
|
|
font-medium text-sm hover:bg-foreground/90 transition-colors"
|
|
>
|
|
Bid
|
|
<ExternalLink className="w-3.5 h-3.5" />
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CommandCenterLayout>
|
|
)
|
|
}
|
|
|