'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 { ExternalLink, Loader2, TrendingUp, TrendingDown, Globe, DollarSign, AlertTriangle, RefreshCw, Search, ChevronDown, ChevronUp, Info, ArrowRight, Lock, Sparkles, BarChart3, Activity, Zap, Filter, Check, Eye, ShieldCheck, Diamond, Minus } from 'lucide-react' import clsx from 'clsx' import Link from 'next/link' // ============================================================================ // TIER ACCESS LEVELS // ============================================================================ type UserTier = 'scout' | 'trader' | 'tycoon' function getTierLevel(tier: UserTier): number { switch (tier) { case 'tycoon': return 3 case 'trader': return 2 case 'scout': return 1 default: return 1 } } // ============================================================================ // SHARED COMPONENTS // ============================================================================ const Tooltip = memo(({ children, content }: { children: React.ReactNode; content: string }) => (
{children}
{content}
)) Tooltip.displayName = 'Tooltip' const LockedFeature = memo(({ requiredTier, currentTier }: { requiredTier: UserTier; currentTier: UserTier }) => { const tierNames = { scout: 'Scout', trader: 'Trader', tycoon: 'Tycoon' } return (
Locked
) }) LockedFeature.displayName = 'LockedFeature' const StatCard = memo(({ label, value, subValue, icon: Icon, highlight, locked = false, lockTooltip }: { label: string value: string | number subValue?: string icon: any highlight?: boolean locked?: boolean lockTooltip?: string }) => (
{label}
{locked ? (
) : (
{value} {subValue && {subValue}}
)} {highlight && (
● LIVE
)}
)) StatCard.displayName = 'StatCard' const FilterToggle = memo(({ active, onClick, label, icon: Icon }: { active: boolean onClick: () => void label: string icon?: any }) => ( )) FilterToggle.displayName = 'FilterToggle' type SortField = 'tld' | 'price' | 'renewal' | 'change' | 'change3y' | 'risk' | 'popularity' type SortDirection = 'asc' | 'desc' const SortableHeader = memo(({ label, field, currentSort, currentDirection, onSort, align = 'left', tooltip, locked = false, lockTooltip }: { label: string; field: SortField; currentSort: SortField; currentDirection: SortDirection; onSort: (field: SortField) => void; align?: 'left'|'center'|'right'; tooltip?: string; locked?: boolean; lockTooltip?: string }) => { const isActive = currentSort === field return (
{tooltip && !locked && ( )}
) }) SortableHeader.displayName = 'SortableHeader' // ============================================================================ // TYPES // ============================================================================ 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 } // ============================================================================ // MAIN PAGE // ============================================================================ export default function IntelPage() { const { subscription } = useStore() // Determine user tier const userTier: UserTier = (subscription?.tier as UserTier) || 'scout' const tierLevel = getTierLevel(userTier) // Feature access checks const canSeeRenewal = tierLevel >= 2 // Trader+ const canSee3yTrend = tierLevel >= 3 // Tycoon only const canSeeFullHistory = tierLevel >= 3 // Tycoon only // Data const [tldData, setTldData] = useState([]) const [loading, setLoading] = useState(true) const [refreshing, setRefreshing] = useState(false) const [total, setTotal] = useState(0) // Filters const [searchQuery, setSearchQuery] = useState('') const [filterType, setFilterType] = useState<'all' | 'tech' | 'geo' | 'budget'>('all') // Sort const [sortField, setSortField] = useState('popularity') const [sortDirection, setSortDirection] = useState('asc') // Load Data const loadData = useCallback(async () => { setLoading(true) try { const response = await api.getTldOverview(100, 0, 'popularity') const mapped: TLDData[] = (response.tlds || []).map((tld: any) => ({ 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) } }, []) useEffect(() => { loadData() }, [loadData]) const handleRefresh = useCallback(async () => { setRefreshing(true) await loadData() setRefreshing(false) }, [loadData]) const handleSort = useCallback((field: SortField) => { if (field === 'renewal' && !canSeeRenewal) return if (field === 'change3y' && !canSee3yTrend) return if (sortField === field) setSortDirection(d => d === 'asc' ? 'desc' : 'asc') else { setSortField(field) setSortDirection(field === 'price' || field === 'renewal' || field === 'risk' ? 'asc' : 'desc') } }, [sortField, canSeeRenewal, canSee3yTrend]) // Transform & Filter const filteredData = useMemo(() => { let data = tldData if (filterType === 'tech') data = data.filter(t => ['ai', 'io', 'app', 'dev', 'tech', 'cloud'].includes(t.tld)) if (filterType === 'geo') data = data.filter(t => ['us', 'uk', 'de', 'ch', 'fr', 'eu'].includes(t.tld)) if (filterType === 'budget') data = data.filter(t => t.min_price < 10) if (searchQuery) { data = data.filter(t => t.tld.toLowerCase().includes(searchQuery.toLowerCase())) } const mult = sortDirection === 'asc' ? 1 : -1 data.sort((a, b) => { switch (sortField) { case 'tld': return mult * a.tld.localeCompare(b.tld) case 'price': return mult * (a.min_price - b.min_price) case 'renewal': return mult * (a.min_renewal_price - b.min_renewal_price) case 'change': return mult * ((a.price_change_1y || 0) - (b.price_change_1y || 0)) case 'change3y': return mult * ((a.price_change_3y || 0) - (b.price_change_3y || 0)) case 'risk': const riskMap = { low: 1, medium: 2, high: 3 } return mult * (riskMap[a.risk_level] - riskMap[b.risk_level]) case 'popularity': return mult * ((a.popularity_rank || 999) - (b.popularity_rank || 999)) default: return 0 } }) return data }, [tldData, filterType, searchQuery, sortField, sortDirection]) const stats = useMemo(() => { const lowest = tldData.length > 0 ? Math.min(...tldData.map(t => t.min_price)) : 0 const hottest = tldData.reduce((prev, current) => (prev.price_change_1y > current.price_change_1y) ? prev : current, tldData[0] || {}) const traps = tldData.filter(t => t.risk_level === 'high').length const avgRenewal = tldData.length > 0 ? tldData.reduce((sum, t) => sum + t.min_renewal_price, 0) / tldData.length : 0 return { lowest, hottest, traps, avgRenewal } }, [tldData]) const formatPrice = (p: number) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(p) return (
{/* Ambient Background Glow */}
{/* Header Section */}

TLD Intelligence

Inflation Monitor & Pricing Analytics across 800+ TLDs.

{/* Quick Stats Pills */}
{userTier === 'tycoon' ? 'Tycoon Access' : userTier === 'trader' ? 'Trader Access' : 'Scout Access'}
{total} Tracked
{/* Metric Grid */}
{/* Control Bar */}
{/* Filter Pills */}
setFilterType('all')} label="All TLDs" /> setFilterType('tech')} label="Tech" icon={Zap} /> setFilterType('geo')} label="Geo / National" icon={Globe} /> setFilterType('budget')} label="Budget <$10" icon={DollarSign} />
{/* Refresh Button (Mobile) */} {/* Search Filter */}
setSearchQuery(e.target.value)} placeholder="Search TLDs..." className="w-full bg-black/50 border border-white/10 rounded-lg pl-9 pr-4 py-2 text-sm text-white placeholder:text-zinc-600 focus:outline-none focus:border-white/20 transition-all" />
{/* DATA GRID */}
{/* Unified Table Header - Use a wrapper with min-width to force scrolling instead of breaking */}
{/* Force minimum width */}
{canSee3yTrend ? ( ) : ( Trend (3y) )}
Action
{/* Rows */} {loading ? (

Analyzing registry data...

) : filteredData.length === 0 ? (

No TLDs found

Try adjusting your filters or search query.

) : (
{filteredData.map((tld) => { const isTrap = tld.min_renewal_price > tld.min_price * 1.5 const trend = tld.price_change_1y || 0 const trend3y = tld.price_change_3y || 0 return (
{/* TLD */}
.{tld.tld}
{/* Price */}
{formatPrice(tld.min_price)}
{/* Renewal (Trader+) */}
{canSeeRenewal ? ( <> {formatPrice(tld.min_renewal_price)} {isTrap && ( )} ) : ( )}
{/* Trend 1y */}
5 ? "text-orange-400 bg-orange-400/5 border border-orange-400/20" : trend < -5 ? "text-emerald-400 bg-emerald-400/5 border border-emerald-400/20" : "text-zinc-400 bg-zinc-800/50 border border-zinc-700" )}> {trend > 0 ? : trend < 0 ? : } {Math.abs(trend)}%
{/* Trend 3y */}
{canSee3yTrend ? (
10 ? "text-orange-400 bg-orange-400/5 border border-orange-400/20" : trend3y < -10 ? "text-emerald-400 bg-emerald-400/5 border border-emerald-400/20" : "text-zinc-400 bg-zinc-800/50 border border-zinc-700" )}> {trend3y > 0 ? : trend3y < 0 ? : } {Math.abs(trend3y)}%
) : ( )}
{/* Risk */}
{/* Action */}
) })}
)}
{/* Upgrade CTA for Scout users */} {userTier === 'scout' && (

Unlock Full TLD Intelligence

See renewal prices, identify renewal traps, and access detailed price history charts with Trader or Tycoon.

Upgrade Now
)}
) }