'use client' import { useCallback, useEffect, useMemo, useState } from 'react' import clsx from 'clsx' import { X, RefreshCw, Copy, ExternalLink, Shield, TrendingUp, AlertTriangle, DollarSign, Check, Zap, Globe, ChevronRight, CheckCircle2, XCircle, Sparkles, Clock, } from 'lucide-react' import { api } from '@/lib/api' import { useAnalyzePanelStore } from '@/lib/analyze-store' import { formatCountdown, parseIsoAsUtc } from '@/lib/time' import type { AnalyzeResponse, AnalyzeSection, AnalyzeItem } from '@/components/analyze/types' import { VisionSection } from '@/components/analyze/VisionSection' // ============================================================================ // HELPERS // ============================================================================ function getStatusColor(status: string) { switch (status) { case 'pass': return { bg: 'bg-accent/10', text: 'text-accent', border: 'border-accent/30', icon: CheckCircle2 } case 'warn': return { bg: 'bg-amber-400/10', text: 'text-amber-400', border: 'border-amber-400/30', icon: AlertTriangle } case 'fail': return { bg: 'bg-rose-500/10', text: 'text-rose-400', border: 'border-rose-500/30', icon: XCircle } default: return { bg: 'bg-white/5', text: 'text-white/40', border: 'border-white/10', icon: null } } } function getSectionConfig(key: string) { // Minimalist monochrome style matching Hunt pages const base = { bg: 'bg-white/[0.02]', border: 'border-white/[0.08]', color: 'text-white/60' } switch (key) { case 'authority': return { ...base, icon: Shield, description: 'Age, backlinks, trust signals', tooltip: 'Authority measures how established and trusted the domain is.' } case 'market': return { ...base, icon: TrendingUp, description: 'Search demand, CPC, TLD availability', tooltip: 'Market data shows commercial potential.' } case 'risk': return { ...base, icon: AlertTriangle, description: 'Trademarks, blacklists, history', tooltip: 'Risk checks help avoid legal issues.' } case 'value': return { ...base, icon: DollarSign, description: 'Estimated worth, comparable sales', tooltip: 'Value estimation based on market data.' } case 'vision': return { ...base, icon: Sparkles, color: 'text-accent', description: 'AI business insights', tooltip: 'AI-powered analysis for this domain.' } default: return { ...base, icon: Globe, description: '', tooltip: '' } } } async function copyToClipboard(text: string) { try { await navigator.clipboard.writeText(text) return true } catch { return false } } function formatValue(value: unknown, key?: string): string { if (value === null || value === undefined) return '—' if (typeof value === 'string') return value if (typeof value === 'number') { // Format USD values with currency symbol const usdKeys = ['cheapest_registration', 'cheapest_renewal', 'cheapest_transfer', 'renewal_burn', 'estimated_value', 'cpc'] if (key && usdKeys.some(k => key.toLowerCase().includes(k.replace('_', '')))) { return `$${value.toFixed(2)}` } return String(value) } if (typeof value === 'boolean') return value ? 'Yes' : 'No' if (Array.isArray(value)) return `${value.length} items` return 'Details' } function isMatrix(item: AnalyzeItem) { return item.key === 'tld_matrix' && Array.isArray(item.value) } function getItemTooltip(key: string): string { const tooltips: Record = { // Authority domain_age: 'Registration age of the domain. Older domains typically have more authority and trust in search engines. 5+ years is excellent.', age: 'Registration age of the domain. Older domains typically have more authority and trust in search engines. 5+ years is excellent.', backlinks: 'Number of external websites linking to this domain. More backlinks = higher authority. Quality matters more than quantity.', trust_flow: 'Majestic Trust Flow score (0-100). Measures the quality of backlinks. Higher = more trusted by search engines.', citation_flow: 'Majestic Citation Flow score (0-100). Measures the quantity of backlinks regardless of quality.', radio_test: 'Pronounceability test. Can someone spell the domain correctly after hearing it once? Important for word-of-mouth.', syllables: 'Number of syllables. Fewer is better - 2-3 syllables is ideal for brandability.', // Market search_volume: 'Monthly Google searches for the main keyword. Higher = more organic traffic potential.', cpc: 'Google Ads Cost-Per-Click. Higher CPC = more commercial intent. $5+ indicates strong buyer intent.', tld_matrix: 'Availability across popular TLDs (.com, .net, .org etc). Green = available for registration.', competition: 'SEO competition level. Lower = easier to rank. "Low" is ideal for new sites.', // Risk trademark: 'USPTO trademark database check. "Clear" means no conflicts found. Always verify before buying.', blacklist: 'Spam and malware blacklist check. "Clean" means domain is not flagged by security services.', archive: 'Wayback Machine first capture date. Shows domain history and previous content.', spam_score: 'Moz Spam Score (0-100). Lower = cleaner history. Above 30% is concerning.', // Value estimated_value: 'AI-estimated market value based on comparable sales, length, keywords, and extension.', comps: 'Recently sold domains with similar characteristics. Used to determine market value.', price_range: 'Suggested listing price range based on market analysis.', // DNS dns_records: 'Active DNS records. Shows if domain is currently configured.', nameservers: 'Current nameservers. Indicates where domain is hosted.', mx_records: 'Mail exchange records. Shows if email is configured.', // General length: 'Character count. Shorter is generally more valuable. Under 8 characters is premium.', extension: 'Top-level domain (.com, .io, etc). .com is most valuable, followed by ccTLDs and new gTLDs.', } return tooltips[key] || '' } // ============================================================================ // COMPONENT // ============================================================================ export function AnalyzePanel() { const { isOpen, domain, close, fastMode, setFastMode, sectionVisibility, setSectionVisibility, dropStatus, } = useAnalyzePanelStore() const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [data, setData] = useState(null) const [copied, setCopied] = useState(false) const [expandedSections, setExpandedSections] = useState>({ authority: true, market: true, risk: true, value: true, vision: true, }) const refresh = useCallback(async () => { if (!domain) return setLoading(true) setError(null) try { const res = await api.analyzeDomain(domain, { fast: fastMode, refresh: true }) setData(res) } catch (e) { setError(e instanceof Error ? e.message : String(e)) setData(null) } finally { setLoading(false) } }, [domain, fastMode]) useEffect(() => { if (!isOpen || !domain) return let cancelled = false const run = async () => { setLoading(true) setError(null) try { const res = await api.analyzeDomain(domain, { fast: fastMode, refresh: false }) if (!cancelled) setData(res) } catch (e) { if (!cancelled) { setError(e instanceof Error ? e.message : String(e)) setData(null) } } finally { if (!cancelled) setLoading(false) } } run() return () => { cancelled = true } }, [isOpen, domain, fastMode]) // ESC to close useEffect(() => { if (!isOpen) return const onKey = (ev: KeyboardEvent) => { if (ev.key === 'Escape') close() } window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) }, [isOpen, close]) const toggleSection = useCallback((key: string) => { setExpandedSections(prev => ({ ...prev, [key]: !prev[key] })) }, []) const visibleSections = useMemo(() => { const sections = data?.sections || [] const order = ['authority', 'market', 'risk', 'value'] const sorted = [...sections] .sort((a, b) => order.indexOf(a.key) - order.indexOf(b.key)) .filter((s) => sectionVisibility[s.key] !== false) // Append VISION section if (sectionVisibility.vision !== false) { const visionSection: AnalyzeSection = { key: 'vision', title: 'VISION', items: [] } return [...sorted, visionSection] } return sorted }, [data, sectionVisibility]) // Calculate overall score const overallScore = useMemo(() => { if (!data?.sections) return null let pass = 0, warn = 0, fail = 0 data.sections.forEach(s => { s.items.forEach(item => { if (item.status === 'pass') pass++ else if (item.status === 'warn') warn++ else if (item.status === 'fail') fail++ }) }) const total = pass + warn + fail if (total === 0) return null const score = Math.round((pass * 100 + warn * 50) / total) return { score, pass, warn, fail, total } }, [data]) const headerDomain = data?.domain || domain || '' const dropCountdown = useMemo(() => formatCountdown(dropStatus?.deletion_date ?? null), [dropStatus]) if (!isOpen) return null return (
{/* Backdrop */}
{/* Panel */}
{/* Header */}
{/* Top Bar */}
Domain Analysis
{headerDomain}
{/* Score Bar */} {overallScore && !loading && (
= 70 ? "border-accent bg-accent/10 text-accent" : overallScore.score >= 40 ? "border-amber-400 bg-amber-400/10 text-amber-400" : "border-rose-500 bg-rose-500/10 text-rose-400" )} > {overallScore.score}
Health Score
{overallScore.pass} passed {overallScore.warn} warnings {overallScore.fail} failed
)} {/* Drop Status Banner */} {dropStatus && (
{dropStatus.status === 'available' ? ( ) : dropStatus.status === 'dropping_soon' ? ( ) : dropStatus.status === 'taken' ? ( ) : ( )}
{dropStatus.status === 'available' ? 'Available Now' : dropStatus.status === 'dropping_soon' ? 'In Transition' : dropStatus.status === 'taken' ? 'Re-registered' : 'Status Unknown'}
{dropStatus.status === 'dropping_soon' && dropStatus.deletion_date && (
{dropCountdown ? `Drops in ${dropCountdown} • ${parseIsoAsUtc(dropStatus.deletion_date).toLocaleDateString()}` : `Drops: ${parseIsoAsUtc(dropStatus.deletion_date).toLocaleDateString()}`}
)}
{dropStatus.status === 'available' && domain && ( Buy Now )}
)} {/* Controls */}
{data?.cached && ( Cached )}
{/* Body */}
{loading ? (
Analyzing domain...
) : error ? (
Analysis Failed
{error}
) : !data ? (
No data available
) : (
{visibleSections.map((section) => { const config = getSectionConfig(section.key) const SectionIcon = config.icon const isExpanded = expandedSections[section.key] !== false return (
{/* Section Header */} {/* Section Content */} {isExpanded && (
{section.key === 'vision' ? (
) : (
{section.items.map((item) => { const statusStyle = getStatusColor(item.status) const tooltip = getItemTooltip(item.key) return (
{isMatrix(item) ? ( /* TLD Matrix - Full Width Layout */
{item.label} {item.source && ( {item.source} )}
{(item.value as any[]).slice(0, 12).map((row: any) => (
.{String(row.domain).split('.').pop()}
))}
) : ( /* Regular Item - Row Layout */
{item.label} {item.source && ( {item.source} )}
{formatValue(item.value, item.key)}
)}
) })}
)}
)}
) })}
)}
{/* Footer */}
Press ESC to close
) }