'use client' import { useEffect, useState, useMemo, useCallback, memo } from 'react' import { useStore } from '@/lib/store' import { api } from '@/lib/api' import { TerminalLayout } from '@/components/TerminalLayout' import { PremiumTable, StatCard, PageContainer, SearchInput, TabBar, FilterBar, SelectDropdown, ActionButton, } from '@/components/PremiumTable' import { TrendingUp, ChevronRight, Globe, DollarSign, RefreshCw, AlertTriangle, Cpu, MapPin, Coins, Crown, Info, Loader2, } from 'lucide-react' import clsx from 'clsx' interface TLDData { tld: string min_price: number avg_price: number max_price: number min_renewal_price: number avg_renewal_price: number cheapest_registrar?: string cheapest_registrar_url?: string price_change_7d: number price_change_1y: number price_change_3y: number risk_level: 'low' | 'medium' | 'high' risk_reason: string popularity_rank?: number type?: string } // Category definitions const CATEGORIES = [ { id: 'all', label: 'All', icon: Globe }, { id: 'tech', label: 'Tech', icon: Cpu }, { id: 'geo', label: 'Geo', icon: MapPin }, { id: 'budget', label: 'Budget', icon: Coins }, { id: 'premium', label: 'Premium', icon: Crown }, ] const CATEGORY_FILTERS: Record boolean> = { all: () => true, tech: (tld) => ['ai', 'io', 'app', 'dev', 'tech', 'code', 'cloud', 'data', 'api', 'software'].includes(tld.tld), geo: (tld) => ['ch', 'de', 'uk', 'us', 'fr', 'it', 'es', 'nl', 'at', 'eu', 'co', 'ca', 'au', 'nz', 'jp', 'cn', 'in', 'br', 'mx', 'nyc', 'london', 'paris', 'berlin', 'tokyo', 'swiss'].includes(tld.tld), budget: (tld) => tld.min_price < 5, premium: (tld) => tld.min_price >= 50, } const SORT_OPTIONS = [ { value: 'popularity', label: 'By Popularity' }, { value: 'price_asc', label: 'Price: Low → High' }, { value: 'price_desc', label: 'Price: High → Low' }, { value: 'change', label: 'By Price Change' }, { value: 'risk', label: 'By Risk Level' }, ] // Memoized Sparkline const Sparkline = memo(function Sparkline({ trend }: { trend: number }) { const isPositive = trend > 0 const isNeutral = trend === 0 return ( {isNeutral ? ( ) : isPositive ? ( ) : ( )} ) }) export default function TLDPricingPage() { const { subscription } = useStore() const [tldData, setTldData] = useState([]) const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) const [searchQuery, setSearchQuery] = useState('') const [sortBy, setSortBy] = useState('popularity') const [category, setCategory] = useState('all') const [page, setPage] = useState(0) const [total, setTotal] = useState(0) const loadTLDData = useCallback(async () => { setLoading(true) try { const response = await api.getTldOverview( 50, page * 50, sortBy === 'risk' || sortBy === 'change' ? 'popularity' : sortBy as any, ) const mapped: TLDData[] = (response.tlds || []).map((tld) => ({ tld: tld.tld, min_price: tld.min_registration_price, avg_price: tld.avg_registration_price, max_price: tld.max_registration_price, min_renewal_price: tld.min_renewal_price, avg_renewal_price: tld.avg_renewal_price, price_change_7d: tld.price_change_7d, price_change_1y: tld.price_change_1y, price_change_3y: tld.price_change_3y, risk_level: tld.risk_level, risk_reason: tld.risk_reason, popularity_rank: tld.popularity_rank, type: tld.type, })) setTldData(mapped) setTotal(response.total || 0) } catch (error) { console.error('Failed to load TLD data:', error) } finally { setLoading(false) } }, [page, sortBy]) useEffect(() => { loadTLDData() }, [loadTLDData]) const handleRefresh = useCallback(async () => { setRefreshing(true) await loadTLDData() setRefreshing(false) }, [loadTLDData]) // Memoized filtered and sorted data const sortedData = useMemo(() => { let data = tldData.filter(CATEGORY_FILTERS[category] || (() => true)) if (searchQuery) { const q = searchQuery.toLowerCase() data = data.filter(tld => tld.tld.toLowerCase().includes(q)) } if (sortBy === 'risk') { const riskOrder = { high: 0, medium: 1, low: 2 } data = [...data].sort((a, b) => riskOrder[a.risk_level] - riskOrder[b.risk_level]) } return data }, [tldData, category, searchQuery, sortBy]) // Memoized stats const stats = useMemo(() => { const lowestPrice = tldData.length > 0 ? tldData.reduce((min, tld) => Math.min(min, tld.min_price), Infinity) : 0.99 const hottestTld = tldData.find(tld => (tld.price_change_7d || 0) > 5)?.tld || 'ai' const trapCount = tldData.filter(tld => tld.risk_level === 'high' || tld.risk_level === 'medium').length return { lowestPrice, hottestTld, trapCount } }, [tldData]) const subtitle = useMemo(() => { if (loading && total === 0) return 'Loading TLD pricing data...' if (total === 0) return 'No TLD data available' return `Tracking ${total.toLocaleString()} TLDs • Updated daily` }, [loading, total]) // Memoized columns const columns = useMemo(() => [ { key: 'tld', header: 'TLD', width: '100px', render: (tld: TLDData) => ( .{tld.tld} ), }, { key: 'trend', header: 'Trend', width: '80px', hideOnMobile: true, render: (tld: TLDData) => , }, { key: 'buy_price', header: 'Buy (1y)', align: 'right' as const, width: '100px', render: (tld: TLDData) => ( ${tld.min_price.toFixed(2)} ), }, { key: 'renew_price', header: 'Renew (1y)', align: 'right' as const, width: '120px', render: (tld: TLDData) => { const ratio = tld.min_renewal_price / tld.min_price return (
${tld.min_renewal_price.toFixed(2)} {ratio > 2 && ( )}
) }, }, { key: 'change_1y', header: '1y', align: 'right' as const, width: '80px', hideOnMobile: true, render: (tld: TLDData) => { const change = tld.price_change_1y || 0 return ( 0 ? "text-orange-400" : change < 0 ? "text-accent" : "text-foreground-muted")}> {change > 0 ? '+' : ''}{change.toFixed(0)}% ) }, }, { key: 'change_3y', header: '3y', align: 'right' as const, width: '80px', hideOnMobile: true, render: (tld: TLDData) => { const change = tld.price_change_3y || 0 return ( 0 ? "text-orange-400" : change < 0 ? "text-accent" : "text-foreground-muted")}> {change > 0 ? '+' : ''}{change.toFixed(0)}% ) }, }, { key: 'cheapest', header: 'Cheapest At', align: 'left' as const, width: '140px', hideOnMobile: true, render: (tld: TLDData) => ( tld.cheapest_registrar ? ( e.stopPropagation()} className="text-xs text-accent hover:text-accent/80 hover:underline transition-colors" > {tld.cheapest_registrar} ) : ( ) ), }, { key: 'risk', header: 'Risk', align: 'center' as const, width: '120px', render: (tld: TLDData) => ( {tld.risk_reason} ), }, { key: 'actions', header: '', align: 'right' as const, width: '50px', render: () => , }, ], []) return ( {refreshing ? '' : 'Refresh'} } > {/* Stats Overview */}
0 ? total.toLocaleString() : '—'} subtitle="updated daily" icon={Globe} /> 0 ? `$${stats.lowestPrice.toFixed(2)}` : '—'} icon={DollarSign} /> 0 ? `.${stats.hottestTld}` : '—'} subtitle="rising prices" icon={TrendingUp} />
{/* Category Tabs */} ({ id: c.id, label: c.label, icon: c.icon }))} activeTab={category} onChange={setCategory} /> {/* Filters */} {/* Legend */}
Tip: Renewal traps show ⚠️ when renewal price is >2x registration
{/* TLD Table */} tld.tld} loading={loading} onRowClick={(tld) => window.location.href = `/terminal/intel/${tld.tld}`} emptyIcon={} emptyTitle="No TLDs found" emptyDescription={searchQuery ? `No TLDs matching "${searchQuery}"` : "Check back later for TLD data"} columns={columns} /> {/* Pagination */} {total > 50 && (
Page {page + 1} of {Math.ceil(total / 50)}
)}
) }