'use client' import { useEffect, useState, useMemo, useRef } from 'react' import { useParams, useRouter } from 'next/navigation' import { Header } from '@/components/Header' import { Footer } from '@/components/Footer' import { useStore } from '@/lib/store' import { api } from '@/lib/api' import { ArrowLeft, TrendingUp, TrendingDown, Minus, Calendar, Globe, Building, ExternalLink, Bell, Search, ChevronRight, Sparkles, Check, X, Lock, RefreshCw, Clock, Shield, Zap, } from 'lucide-react' import Link from 'next/link' import clsx from 'clsx' interface TldDetails { tld: string type: string description: string registry: string introduced: number trend: string trend_reason: string pricing: { avg: number min: number max: number } registrars: Array<{ name: string registration_price: number renewal_price: number transfer_price: number }> cheapest_registrar: string } interface TldHistory { tld: string current_price: number price_change_7d: number price_change_30d: number price_change_90d: number trend?: string trend_reason?: string history: Array<{ date: string price: number }> } interface DomainCheckResult { domain: string is_available: boolean status: string registrar?: string | null creation_date?: string | null expiration_date?: string | null } // Registrar URLs with affiliate parameters // Note: Replace REF_CODE with actual affiliate IDs when available const REGISTRAR_URLS: Record = { 'Namecheap': 'https://www.namecheap.com/domains/registration/results/?domain=', 'Porkbun': 'https://porkbun.com/checkout/search?q=', 'Cloudflare': 'https://www.cloudflare.com/products/registrar/', 'Google Domains': 'https://domains.google.com/registrar/search?searchTerm=', 'GoDaddy': 'https://www.godaddy.com/domainsearch/find?domainToCheck=', 'porkbun': 'https://porkbun.com/checkout/search?q=', 'Dynadot': 'https://www.dynadot.com/domain/search?domain=', 'Hover': 'https://www.hover.com/domains/results?q=', } // Related TLDs const RELATED_TLDS: Record = { 'com': ['net', 'org', 'co', 'io'], 'net': ['com', 'org', 'io', 'dev'], 'org': ['com', 'net', 'ngo', 'foundation'], 'io': ['dev', 'app', 'tech', 'ai'], 'ai': ['io', 'tech', 'dev', 'ml'], 'dev': ['io', 'app', 'tech', 'code'], 'app': ['dev', 'io', 'mobile', 'software'], 'co': ['com', 'io', 'biz', 'inc'], 'de': ['at', 'ch', 'eu', 'com'], 'ch': ['de', 'at', 'li', 'eu'], } type ChartPeriod = '1M' | '3M' | '1Y' | 'ALL' // Shimmer component for unauthenticated users function Shimmer({ className }: { className?: string }) { return (
) } // Premium Chart Component function PriceChart({ data, isAuthenticated, chartStats, }: { data: Array<{ date: string; price: number }> isAuthenticated: boolean chartStats: { high: number; low: number; avg: number } }) { const [hoveredIndex, setHoveredIndex] = useState(null) const [tooltipPos, setTooltipPos] = useState({ x: 0, y: 0 }) const containerRef = useRef(null) if (!isAuthenticated) { return (
Sign in to view price history
) } if (data.length === 0) { return (
No price history available
) } const range = chartStats.high - chartStats.low || 1 const padding = range * 0.1 // Create smooth path const points = data.map((point, i) => { const x = (i / (data.length - 1)) * 100 const y = 100 - ((point.price - chartStats.low + padding) / (range + padding * 2)) * 100 return { x, y, ...point } }) // Create SVG path for smooth curve const linePath = points.reduce((path, point, i) => { if (i === 0) return `M ${point.x} ${point.y}` // Use bezier curves for smoothness const prev = points[i - 1] const cpx = (prev.x + point.x) / 2 return `${path} C ${cpx} ${prev.y}, ${cpx} ${point.y}, ${point.x} ${point.y}` }, '') // Area path const areaPath = `${linePath} L 100 100 L 0 100 Z` const handleMouseMove = (e: React.MouseEvent) => { if (!containerRef.current) return const rect = containerRef.current.getBoundingClientRect() const x = e.clientX - rect.left const percentage = x / rect.width const index = Math.round(percentage * (data.length - 1)) if (index >= 0 && index < data.length) { setHoveredIndex(index) setTooltipPos({ x: e.clientX - rect.left, y: points[index].y * rect.height / 100 }) } } return (
setHoveredIndex(null)} > {/* Grid lines */} {[20, 40, 60, 80].map(y => ( ))} {/* Area fill */} {/* Main line */} {/* Hover indicator */} {hoveredIndex !== null && ( )} {/* Hover dot */} {hoveredIndex !== null && containerRef.current && (
)} {/* Tooltip */} {hoveredIndex !== null && (

${data[hoveredIndex].price.toFixed(2)}

{new Date(data[hoveredIndex].date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}

)}
) } // Domain Check Result Card (like landing page) function DomainResultCard({ result, tld, cheapestPrice, cheapestRegistrar, onClose }: { result: DomainCheckResult tld: string cheapestPrice: number cheapestRegistrar: string onClose: () => void }) { const registrarUrl = REGISTRAR_URLS[cheapestRegistrar] || '#' return (
{result.is_available ? ( ) : ( )}

{result.domain}

{result.is_available ? 'Available for registration' : 'Already registered'}

{result.is_available ? (
Register from ${cheapestPrice.toFixed(2)}/yr
Register at {cheapestRegistrar}
) : (
{result.registrar && (
Registrar: {result.registrar}
)} {result.expiration_date && (
Expires: {new Date(result.expiration_date).toLocaleDateString()}
)}
)}
) } export default function TldDetailPage() { const params = useParams() const router = useRouter() const { isAuthenticated, checkAuth, isLoading: authLoading } = useStore() const tld = params.tld as string const [details, setDetails] = useState(null) const [history, setHistory] = useState(null) const [relatedTlds, setRelatedTlds] = useState>([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [chartPeriod, setChartPeriod] = useState('1Y') const [domainSearch, setDomainSearch] = useState('') const [checkingDomain, setCheckingDomain] = useState(false) const [domainResult, setDomainResult] = useState(null) const [alertEnabled, setAlertEnabled] = useState(false) useEffect(() => { checkAuth() }, [checkAuth]) useEffect(() => { if (tld) { loadData() loadRelatedTlds() } }, [tld]) const loadData = async () => { try { const [historyData, compareData] = await Promise.all([ api.getTldHistory(tld, 365), api.getTldCompare(tld), ]) if (historyData && compareData) { const registrars = compareData.registrars || [] const sortedRegistrars = [...registrars].sort((a, b) => a.registration_price - b.registration_price ) const minPrice = sortedRegistrars[0]?.registration_price || historyData.current_price || 0 const maxPrice = sortedRegistrars[sortedRegistrars.length - 1]?.registration_price || historyData.current_price || 0 const avgPrice = registrars.length > 0 ? registrars.reduce((sum, r) => sum + r.registration_price, 0) / registrars.length : historyData.current_price || 0 // Determine trend from price change const trend = historyData.price_change_30d > 5 ? 'up' : historyData.price_change_30d < -5 ? 'down' : 'stable' setDetails({ tld: compareData.tld || tld, type: 'generic', description: `Domain extension .${tld}`, registry: 'Various', introduced: 0, trend: trend, trend_reason: trend === 'up' ? 'Price increase' : trend === 'down' ? 'Price decrease' : 'Stable pricing', pricing: { avg: avgPrice, min: minPrice, max: maxPrice, }, registrars: sortedRegistrars, cheapest_registrar: sortedRegistrars[0]?.name || 'N/A', }) setHistory({ ...historyData, trend: trend, trend_reason: trend === 'up' ? 'Price increase' : trend === 'down' ? 'Price decrease' : 'Stable pricing', }) } else { setError('Failed to load TLD data') } } catch (err) { console.error('Error loading TLD data:', err) setError('Failed to load TLD data') } finally { setLoading(false) } } const loadRelatedTlds = async () => { const related = RELATED_TLDS[tld.toLowerCase()] || ['com', 'net', 'org', 'io'] const relatedData: Array<{ tld: string; price: number }> = [] for (const relatedTld of related.slice(0, 4)) { try { const data = await api.getTldHistory(relatedTld, 30) if (data) { relatedData.push({ tld: relatedTld, price: data.current_price }) } } catch { // Skip failed } } setRelatedTlds(relatedData) } const filteredHistory = useMemo(() => { if (!history?.history) return [] const now = new Date() let cutoffDays = 365 switch (chartPeriod) { case '1M': cutoffDays = 30; break case '3M': cutoffDays = 90; break case '1Y': cutoffDays = 365; break case 'ALL': cutoffDays = 9999; break } const cutoff = new Date(now.getTime() - cutoffDays * 24 * 60 * 60 * 1000) return history.history.filter(h => new Date(h.date) >= cutoff) }, [history, chartPeriod]) const chartStats = useMemo(() => { if (filteredHistory.length === 0) return { high: 0, low: 0, avg: 0 } const prices = filteredHistory.map(h => h.price) return { high: Math.max(...prices), low: Math.min(...prices), avg: prices.reduce((a, b) => a + b, 0) / prices.length, } }, [filteredHistory]) const handleDomainCheck = async () => { if (!domainSearch.trim()) return setCheckingDomain(true) setDomainResult(null) try { const domain = domainSearch.includes('.') ? domainSearch : `${domainSearch}.${tld}` const result = await api.checkDomain(domain, false) setDomainResult({ domain, is_available: result.is_available, status: result.status, registrar: result.registrar, creation_date: result.creation_date, expiration_date: result.expiration_date, }) } catch (err) { console.error('Domain check failed:', err) } finally { setCheckingDomain(false) } } const getRegistrarUrl = (registrarName: string, domain?: string) => { const baseUrl = REGISTRAR_URLS[registrarName] if (!baseUrl) return '#' if (domain) return `${baseUrl}${domain}` return baseUrl } const savings = useMemo(() => { if (!details || details.registrars.length < 2) return null const cheapest = details.registrars[0].registration_price const mostExpensive = details.registrars[details.registrars.length - 1].registration_price return { amount: mostExpensive - cheapest, cheapestName: details.registrars[0].name, expensiveName: details.registrars[details.registrars.length - 1].name, } }, [details]) const getTrendIcon = (trend: string) => { switch (trend) { case 'up': return case 'down': return default: return } } if (loading || authLoading) { return (
{[1, 2, 3, 4].map(i => )}
) } if (error || !details) { return (

TLD Not Found

{error || `The TLD .${tld} could not be found.`}

Back to TLD Overview
) } return (
{/* Subtle ambient */}
{/* Breadcrumb */} {/* Hero */}
{/* Left: TLD Info */}

.{details.tld}

{getTrendIcon(details.trend)} {details.trend === 'up' ? 'Rising' : details.trend === 'down' ? 'Falling' : 'Stable'}

{details.description}

{details.trend_reason}

{/* Quick Stats - Only for authenticated */}

Average

{isAuthenticated ? (

${details.pricing.avg.toFixed(2)}

) : ( )}

Range

{isAuthenticated ? (

${details.pricing.min.toFixed(0)}–${details.pricing.max.toFixed(0)}

) : ( )}

30d Change

{isAuthenticated && history ? (

0 ? "text-orange-400" : history.price_change_30d < 0 ? "text-accent" : "text-foreground" )}> {history.price_change_30d > 0 ? '+' : ''}{history.price_change_30d.toFixed(1)}%

) : ( )}

Registrars

{isAuthenticated ? (

{details.registrars.length}

) : ( )}
{/* Right: Price Card */}
{isAuthenticated ? ( <>
${details.pricing.min.toFixed(2)} /yr

Cheapest at {details.cheapest_registrar}

Register Domain
{savings && savings.amount > 0.5 && (

Save ${savings.amount.toFixed(2)}/yr vs {savings.expensiveName}

)} ) : ( <> Sign in to View Prices )}
{/* Price Chart */}

Price History

{isAuthenticated && (
{(['1M', '3M', '1Y', 'ALL'] as ChartPeriod[]).map(period => ( ))}
)}
{isAuthenticated && filteredHistory.length > 0 && (
{new Date(filteredHistory[0]?.date).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
High ${chartStats.high.toFixed(2)}
Low ${chartStats.low.toFixed(2)}
Today
)}
{/* Domain Search */}

Check .{details.tld} Availability

setDomainSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleDomainCheck()} placeholder="Enter domain name" className="w-full px-4 py-3.5 pr-20 bg-background border border-border rounded-xl text-body text-foreground placeholder:text-foreground-subtle focus:outline-none focus:ring-2 focus:ring-accent/30 focus:border-accent transition-all" /> .{tld}
{domainResult && ( setDomainResult(null)} /> )}
{/* Registrar Comparison */}

Compare Registrars

{isAuthenticated ? (
{details.registrars.map((registrar, i) => ( ))}
Registrar Register Renew Transfer
{registrar.name} {i === 0 && ( Best )}
${registrar.registration_price.toFixed(2)} ${registrar.renewal_price.toFixed(2)} {registrar.renewal_price > registrar.registration_price * 1.5 && ( )} ${registrar.transfer_price.toFixed(2)} Visit
) : (
{[1, 2, 3, 4].map(i => (
))}

Sign in to compare registrar prices

Get Started Free
)}
{/* TLD Info */}

About .{details.tld}

Registry

{details.registry}

Introduced

{details.introduced || 'Unknown'}

Type

{details.type}

{/* Related TLDs */} {relatedTlds.length > 0 && (

Similar Extensions

{relatedTlds.map(related => (

.{related.tld}

{isAuthenticated ? (

from ${related.price.toFixed(2)}/yr

) : ( )} ))}
)} {/* CTA */}

Track .{details.tld} Domains

Monitor specific domains and get instant notifications when they become available.

{isAuthenticated ? 'Go to Dashboard' : 'Start Monitoring Free'}
) }