'use client' import { useCallback, useEffect, useMemo, useState } from 'react' import clsx from 'clsx' import { ExternalLink, Loader2, Search, Shield, Sparkles, Eye, TrendingUp, RefreshCw, Filter, ChevronRight, Globe, Zap, X } from 'lucide-react' import { api } from '@/lib/api' import { useAnalyzePanelStore } from '@/lib/analyze-store' import { useStore } from '@/lib/store' // ============================================================================ // HELPERS // ============================================================================ function normalizeKeyword(s: string) { return s.trim().replace(/\s+/g, ' ') } // ============================================================================ // COMPONENT // ============================================================================ export function TrendSurferTab({ showToast }: { showToast: (message: string, type?: any) => void }) { const openAnalyze = useAnalyzePanelStore((s) => s.open) const addDomain = useStore((s) => s.addDomain) // Trends State const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [geo, setGeo] = useState('US') const [trends, setTrends] = useState>([]) const [selected, setSelected] = useState('') const [refreshing, setRefreshing] = useState(false) // Keyword Check State const [keywordInput, setKeywordInput] = useState('') const [keywordFocused, setKeywordFocused] = useState(false) const [availability, setAvailability] = useState>([]) const [checking, setChecking] = useState(false) // Typo Check State const [brand, setBrand] = useState('') const [brandFocused, setBrandFocused] = useState(false) const [typos, setTypos] = useState>([]) const [typoLoading, setTypoLoading] = useState(false) // Tracking State const [tracking, setTracking] = useState(null) const track = useCallback( async (domain: string) => { if (tracking) return setTracking(domain) try { await addDomain(domain) showToast(`Tracked ${domain}`, 'success') } catch (e) { showToast(e instanceof Error ? e.message : 'Failed to track domain', 'error') } finally { setTracking(null) } }, [addDomain, showToast, tracking] ) const loadTrends = useCallback(async (isRefresh = false) => { if (isRefresh) setRefreshing(true) setError(null) try { const res = await api.getHuntTrends(geo) setTrends(res.items || []) if (!selected && res.items?.[0]?.title) setSelected(res.items[0].title) } catch (e) { const msg = e instanceof Error ? e.message : String(e) setError(msg) showToast(msg, 'error') setTrends([]) } finally { if (isRefresh) setRefreshing(false) } }, [geo, selected, showToast]) useEffect(() => { let cancelled = false const run = async () => { setLoading(true) try { await loadTrends() } catch (e) { if (!cancelled) setError(e instanceof Error ? e.message : String(e)) } finally { if (!cancelled) setLoading(false) } } run() return () => { cancelled = true } }, [loadTrends]) const keyword = useMemo(() => normalizeKeyword(keywordInput || selected || ''), [keywordInput, selected]) const runCheck = useCallback(async () => { if (!keyword) return setChecking(true) try { const kw = keyword.toLowerCase().replace(/\s+/g, '') const res = await api.huntKeywords({ keywords: [kw], tlds: ['com', 'io', 'ai', 'net', 'org'] }) setAvailability(res.items.map((r) => ({ domain: r.domain, status: r.status, is_available: r.is_available }))) } catch (e) { const msg = e instanceof Error ? e.message : 'Failed to check availability' showToast(msg, 'error') setAvailability([]) } finally { setChecking(false) } }, [keyword, showToast]) const runTypos = useCallback(async () => { const b = brand.trim() if (!b) return setTypoLoading(true) try { const res = await api.huntTypos({ brand: b, tlds: ['com'], limit: 50 }) setTypos(res.items.map((i) => ({ domain: i.domain, status: i.status }))) } catch (e) { const msg = e instanceof Error ? e.message : 'Failed to run typo check' showToast(msg, 'error') setTypos([]) } finally { setTypoLoading(false) } }, [brand, showToast]) if (loading) { return (
) } return (
{/* Trends Header */}
Google Trends (24h)
Real-time trending topics
{error ? (
{error}
) : (
{trends.slice(0, 20).map((t) => { const active = selected === t.title return ( ) })}
)}
{/* Keyword Availability Check */}
Domain Availability
Check {keyword || 'keyword'} across TLDs
setKeywordInput(e.target.value)} onFocus={() => setKeywordFocused(true)} onBlur={() => setKeywordFocused(false)} placeholder="Type a keyword..." className="flex-1 bg-transparent px-3 py-3 text-sm text-white placeholder:text-white/20 outline-none font-mono" /> {(keywordInput || selected) && ( )}
{/* Results Grid */} {availability.length > 0 && (
{availability.map((a) => (
{a.status.toUpperCase()} {a.status === 'available' && ( Buy )}
))}
)} {availability.length === 0 && keyword && !checking && (

Click "Check" to find available domains

)}
{/* Typo Finder */}
Typo Finder
Find available typos of big brands
setBrand(e.target.value)} onFocus={() => setBrandFocused(true)} onBlur={() => setBrandFocused(false)} placeholder="e.g. Shopify, Amazon, Google..." className="flex-1 bg-transparent px-3 py-3 text-sm text-white placeholder:text-white/20 outline-none font-mono" /> {brand && ( )}
{/* Typo Results Grid */} {typos.length > 0 && (
{typos.map((t) => (
{t.status.toUpperCase()}
))}
)} {typos.length === 0 && !typoLoading && (
Enter a brand name to find available typo domains
)}
) }