'use client'
import { useEffect, useState, useMemo, useCallback, useRef } from 'react'
import { useStore } from '@/lib/store'
import { api } from '@/lib/api'
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
import { Toast, useToast } from '@/components/Toast'
import {
Eye,
Gavel,
ExternalLink,
Plus,
Activity,
ArrowRight,
CheckCircle2,
XCircle,
Loader2,
Crosshair,
Zap,
Globe,
Target,
Search,
Home,
BarChart3,
Settings,
Bell,
ChevronRight,
TrendingUp,
RefreshCw,
Menu,
X,
Tag,
Coins,
Shield,
LogOut,
User
} from 'lucide-react'
import clsx from 'clsx'
import Link from 'next/link'
// ============================================================================
// TYPES
// ============================================================================
interface HotAuction {
domain: string
current_bid: number
time_remaining: string
platform: string
affiliate_url?: string
}
interface SearchResult {
domain: string
status: string
is_available: boolean | null
registrar: string | null
expiration_date: string | null
loading: boolean
inAuction: boolean
auctionData?: HotAuction
}
// ============================================================================
// FULLSCREEN NAVIGATION MENU
// ============================================================================
function FullscreenNav({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
const { user, logout, subscription } = useStore()
const mainLinks = [
{ label: 'Radar', href: '/terminal/radar', icon: Crosshair, desc: 'Domain search & overview' },
{ label: 'Market', href: '/terminal/market', icon: Gavel, desc: 'Live auctions' },
{ label: 'Watchlist', href: '/terminal/watchlist', icon: Eye, desc: 'Track domains' },
{ label: 'Intel', href: '/terminal/intel', icon: BarChart3, desc: 'TLD pricing data' },
]
const secondaryLinks = [
{ label: 'Sniper', href: '/terminal/sniper', icon: Target },
{ label: 'Yield', href: '/terminal/yield', icon: Coins },
{ label: 'For Sale', href: '/terminal/listing', icon: Tag },
{ label: 'Settings', href: '/terminal/settings', icon: Settings },
]
if (!isOpen) return null
return (
{/* Header */}
{/* Main Links */}
{mainLinks.map((item) => (
))}
{/* Secondary Links */}
More
{secondaryLinks.map((item) => (
{item.label}
))}
{/* User Section */}
{user?.name || user?.email?.split('@')[0]}
{subscription?.tier || 'Scout'}
{ logout(); onClose() }}
className="w-10 h-10 flex items-center justify-center text-white/30 active:text-red-400"
>
)
}
// ============================================================================
// MOBILE BOTTOM NAV - With Menu Button
// ============================================================================
function MobileBottomNav({ active, onMenuOpen }: { active: string; onMenuOpen: () => void }) {
const navItems = [
{ id: 'radar', label: 'Radar', icon: Crosshair, href: '/terminal/radar' },
{ id: 'market', label: 'Market', icon: Gavel, href: '/terminal/market' },
{ id: 'watchlist', label: 'Watch', icon: Eye, href: '/terminal/watchlist' },
]
return (
{navItems.map((item) => {
const isActive = active === item.id
return (
{item.label}
)
})}
{/* Menu Button */}
More
)
}
// ============================================================================
// MOBILE HEADER
// ============================================================================
function MobileHeader({
onSearchOpen,
isRefreshing,
onRefresh
}: {
onSearchOpen: () => void
isRefreshing: boolean
onRefresh: () => void
}) {
return (
)
}
// ============================================================================
// MOBILE SEARCH MODAL
// ============================================================================
function MobileSearchModal({
isOpen,
onClose,
searchQuery,
setSearchQuery,
searchResult,
addingToWatchlist,
onAddToWatchlist
}: {
isOpen: boolean
onClose: () => void
searchQuery: string
setSearchQuery: (q: string) => void
searchResult: SearchResult | null
addingToWatchlist: boolean
onAddToWatchlist: () => void
}) {
const inputRef = useRef(null)
useEffect(() => {
if (isOpen && inputRef.current) {
setTimeout(() => inputRef.current?.focus(), 100)
}
}, [isOpen])
if (!isOpen) return null
return (
{/* Header */}
{/* Results */}
{searchResult?.loading && (
Checking availability...
)}
{searchResult && !searchResult.loading && (
{/* Status Banner */}
{searchResult.is_available ? (
) : (
)}
{searchResult.domain}
{searchResult.is_available ? 'Available for registration' : 'Already registered'}
{/* Details */}
{!searchResult.is_available && searchResult.registrar && (
Registrar
{searchResult.registrar}
)}
{/* Actions */}
{addingToWatchlist ? (
) : (
)}
{searchResult.is_available ? 'Add to Watchlist' : 'Track for Availability'}
{searchResult.is_available && (
Register Now
)}
)}
{!searchResult && searchQuery.length === 0 && (
Enter a domain to check availability
)}
)
}
// ============================================================================
// STAT CARD - Higher Contrast
// ============================================================================
function StatCard({ label, value, highlight, icon: Icon }: {
label: string
value: string | number
highlight?: boolean
icon: any
}) {
return (
)
}
// ============================================================================
// AUCTION CARD - Higher Contrast
// ============================================================================
function AuctionCard({ auction }: { auction: HotAuction }) {
return (
{auction.platform.substring(0, 2).toUpperCase()}
{auction.domain}
{auction.time_remaining}
${auction.current_bid.toLocaleString()}
Current bid
)
}
// ============================================================================
// DESKTOP LIVE TICKER
// ============================================================================
function LiveTicker({ items }: { items: { label: string; value: string; highlight?: boolean }[] }) {
return (
{[...items, ...items, ...items].map((item, i) => (
{item.label}
{item.value}
))}
)
}
// ============================================================================
// MAIN PAGE
// ============================================================================
export default function RadarPage() {
const { isAuthenticated, user, domains, addDomain } = useStore()
const { toast, showToast, hideToast } = useToast()
const [hotAuctions, setHotAuctions] = useState([])
const [marketStats, setMarketStats] = useState({ totalAuctions: 0, endingSoon: 0 })
const [loadingData, setLoadingData] = useState(true)
const [isRefreshing, setIsRefreshing] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
const [searchResult, setSearchResult] = useState(null)
const [addingToWatchlist, setAddingToWatchlist] = useState(false)
const [searchFocused, setSearchFocused] = useState(false)
const [mobileSearchOpen, setMobileSearchOpen] = useState(false)
const [menuOpen, setMenuOpen] = useState(false)
const searchInputRef = useRef(null)
// Load Data
const loadDashboardData = useCallback(async () => {
try {
const summary = await api.getDashboardSummary()
setHotAuctions((summary.market.ending_soon_preview || []).slice(0, 5))
setMarketStats({
totalAuctions: summary.market.total_auctions || 0,
endingSoon: summary.market.ending_soon || 0,
})
} catch (error) {
console.error('Failed to load data:', error)
} finally {
setLoadingData(false)
setIsRefreshing(false)
}
}, [])
const handleRefresh = useCallback(async () => {
setIsRefreshing(true)
await loadDashboardData()
}, [loadDashboardData])
useEffect(() => {
if (isAuthenticated) loadDashboardData()
}, [isAuthenticated, loadDashboardData])
// Search
const handleSearch = useCallback(async (domainInput: string) => {
if (!domainInput.trim()) { setSearchResult(null); return }
const cleanDomain = domainInput.trim().toLowerCase()
setSearchResult({ domain: cleanDomain, status: 'checking', is_available: null, registrar: null, expiration_date: null, loading: true, inAuction: false })
try {
const [whoisResult, auctionsResult] = await Promise.all([
api.checkDomain(cleanDomain).catch(() => null),
api.getAuctions(cleanDomain).catch(() => ({ auctions: [] })),
])
const auctionMatch = (auctionsResult as any).auctions?.find((a: any) => a.domain.toLowerCase() === cleanDomain)
setSearchResult({
domain: whoisResult?.domain || cleanDomain,
status: whoisResult?.status || 'unknown',
is_available: whoisResult?.is_available ?? null,
registrar: whoisResult?.registrar || null,
expiration_date: whoisResult?.expiration_date || null,
loading: false,
inAuction: !!auctionMatch,
auctionData: auctionMatch,
})
} catch {
setSearchResult({ domain: cleanDomain, status: 'error', is_available: null, registrar: null, expiration_date: null, loading: false, inAuction: false })
}
}, [])
const handleAddToWatchlist = useCallback(async () => {
if (!searchQuery.trim()) return
setAddingToWatchlist(true)
try {
await addDomain(searchQuery.trim())
showToast(`Added: ${searchQuery.trim()}`, 'success')
setSearchQuery('')
setSearchResult(null)
setMobileSearchOpen(false)
} catch (err: any) {
showToast(err.message || 'Failed', 'error')
} finally {
setAddingToWatchlist(false)
}
}, [searchQuery, addDomain, showToast])
useEffect(() => {
const timer = setTimeout(() => {
if (searchQuery.length > 3) handleSearch(searchQuery)
else setSearchResult(null)
}, 500)
return () => clearTimeout(timer)
}, [searchQuery, handleSearch])
// Computed
const availableDomains = domains?.filter(d => d.is_available) || []
const totalDomains = domains?.length || 0
const tickerItems = [
{ label: 'Status', value: 'ONLINE', highlight: true },
{ label: 'Tracking', value: totalDomains.toString() },
{ label: 'Available', value: availableDomains.length.toString(), highlight: availableDomains.length > 0 },
{ label: 'Auctions', value: marketStats.totalAuctions.toString() },
]
return (
<>
{/* Fullscreen Navigation Menu */}
setMenuOpen(false)} />
{/* Mobile Header */}
setMobileSearchOpen(true)}
isRefreshing={isRefreshing}
onRefresh={handleRefresh}
/>
{/* Mobile Search Modal */}
setMobileSearchOpen(false)}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
searchResult={searchResult}
addingToWatchlist={addingToWatchlist}
onAddToWatchlist={handleAddToWatchlist}
/>
{/* Mobile Content */}
{toast &&
}
{/* Stats Grid */}
0} icon={CheckCircle2} />
{/* Available Alert */}
{availableDomains.length > 0 && (
{availableDomains.length} Domain{availableDomains.length > 1 ? 's' : ''} Available!
Grab them now
)}
{/* Section: Live Auctions */}
{loadingData ? (
) : hotAuctions.length > 0 ? (
{hotAuctions.map((auction, i) => (
))}
) : (
)}
{/* Section: Quick Links */}
Quick Actions
{[
{ label: 'TLD Intel', desc: 'Price trends', href: '/terminal/intel', icon: TrendingUp },
{ label: 'Sniper', desc: 'Set alerts', href: '/terminal/sniper', icon: Target },
{ label: 'Yield', desc: 'Monetize', href: '/terminal/yield', icon: Coins },
{ label: 'For Sale', desc: 'List domains', href: '/terminal/listing', icon: Tag },
].map((item) => (
{item.label}
{item.desc}
))}
{/* Mobile Bottom Nav with Menu Button */}
setMenuOpen(true)} />
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* DESKTOP LAYOUT */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{toast && }
{/* HERO */}
{/* Left: Typography */}
Domain Radar
Find your next acquisition.
Real-time monitoring across {marketStats.totalAuctions.toLocaleString()}+ auctions.
Your targets. Your intel.
{/* Stats Row */}
{availableDomains.length}
Available
{marketStats.endingSoon}
Ending Soon
{/* Right: Search Terminal */}
{/* Header Bar */}
{/* Input */}
setSearchQuery(e.target.value)}
onFocus={() => setSearchFocused(true)}
onBlur={() => setSearchFocused(false)}
placeholder="example.com"
className="w-full bg-transparent px-4 py-4 text-lg text-white placeholder:text-white/20 outline-none"
/>
{searchQuery && (
{ setSearchQuery(''); setSearchResult(null) }}
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white transition-colors"
>
)}
{/* Results */}
{searchResult && (
{searchResult.loading ? (
Checking availability...
) : (
{/* Status Header */}
{searchResult.is_available ? (
) : (
)}
{searchResult.domain}
{searchResult.is_available ? 'Available' : 'Taken'}
{/* Registrar Info for taken domains */}
{!searchResult.is_available && searchResult.registrar && (
Registered with {searchResult.registrar}
)}
{/* Actions */}
{addingToWatchlist ? : }
{searchResult.is_available ? 'Add to Watchlist' : 'Track for Availability'}
{searchResult.is_available && (
Register Now
)}
)}
)}
{/* Hint */}
{!searchResult && (
Enter a domain name to check availability
)}
{/* Ticker */}
{/* CONTENT GRID */}
{/* Hot Auctions - 2 cols */}
{loadingData ? (
) : hotAuctions.length > 0 ? (
) : (
No active auctions
)}
{/* Quick Links */}
Quick Access
{[
{ label: 'Watchlist', href: '/terminal/watchlist', icon: Eye },
{ label: 'Market', href: '/terminal/market', icon: Gavel },
{ label: 'Intel', href: '/terminal/intel', icon: Globe },
].map((item) => (
{item.label}
))}
{/* Status */}
>
)
}