'use client' import { useEffect, useState, useMemo, useCallback } from 'react' import { useStore } from '@/lib/store' import { api, DomainHealthReport, HealthStatus } from '@/lib/api' import { Sidebar } from '@/components/Sidebar' import { Toast, useToast } from '@/components/Toast' import { Plus, Trash2, RefreshCw, Loader2, Bell, BellOff, Eye, X, Activity, AlertTriangle, CheckCircle2, XCircle, Target, ExternalLink, Gavel, TrendingUp, Menu, Settings, Shield, LogOut, Crown, Sparkles, Coins, Tag, Zap, Search } from 'lucide-react' import clsx from 'clsx' import Link from 'next/link' import Image from 'next/image' // ============================================================================ // HELPERS // ============================================================================ function getDaysUntilExpiry(expirationDate: string | null): number | null { if (!expirationDate) return null const expDate = new Date(expirationDate) const now = new Date() const diffTime = expDate.getTime() - now.getTime() return Math.ceil(diffTime / (1000 * 60 * 60 * 24)) } function formatExpiryDate(expirationDate: string | null): string { if (!expirationDate) return '—' return new Date(expirationDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) } const healthConfig: Record = { healthy: { label: 'Healthy', color: 'text-accent', bg: 'bg-accent/10 border-accent/20' }, weakening: { label: 'Weak', color: 'text-amber-400', bg: 'bg-amber-500/10 border-amber-500/20' }, parked: { label: 'Parked', color: 'text-blue-400', bg: 'bg-blue-500/10 border-blue-500/20' }, critical: { label: 'Critical', color: 'text-rose-400', bg: 'bg-rose-500/10 border-rose-500/20' }, unknown: { label: 'Unknown', color: 'text-white/40', bg: 'bg-white/5 border-white/10' }, } // ============================================================================ // MAIN PAGE // ============================================================================ export default function WatchlistPage() { const { domains, addDomain, deleteDomain, refreshDomain, updateDomain, subscription, user, logout, checkAuth } = useStore() const { toast, showToast, hideToast } = useToast() const [newDomain, setNewDomain] = useState('') const [adding, setAdding] = useState(false) const [searchFocused, setSearchFocused] = useState(false) const [refreshingId, setRefreshingId] = useState(null) const [deletingId, setDeletingId] = useState(null) const [togglingNotifyId, setTogglingNotifyId] = useState(null) const [healthReports, setHealthReports] = useState>({}) const [loadingHealth, setLoadingHealth] = useState>({}) const [selectedDomain, setSelectedDomain] = useState(null) const [filter, setFilter] = useState<'all' | 'available' | 'expiring'>('all') // Mobile Menu const [menuOpen, setMenuOpen] = useState(false) // Check auth on mount useEffect(() => { checkAuth() }, [checkAuth]) // Stats const stats = useMemo(() => { const available = domains?.filter(d => d.is_available) || [] const expiringSoon = domains?.filter(d => { if (d.is_available || !d.expiration_date) return false const days = getDaysUntilExpiry(d.expiration_date) return days !== null && days <= 30 && days > 0 }) || [] return { total: domains?.length || 0, available: available.length, expiring: expiringSoon.length } }, [domains]) // Filtered const filteredDomains = useMemo(() => { if (!domains) return [] return domains.filter(d => { if (filter === 'available') return d.is_available if (filter === 'expiring') { const days = getDaysUntilExpiry(d.expiration_date) return days !== null && days <= 30 && days > 0 } return true }).sort((a, b) => { if (a.is_available && !b.is_available) return -1 if (!a.is_available && b.is_available) return 1 return a.name.localeCompare(b.name) }) }, [domains, filter]) // Handlers const handleAdd = useCallback(async (e: React.FormEvent) => { e.preventDefault() if (!newDomain.trim()) return const domainName = newDomain.trim().toLowerCase() setAdding(true) try { await addDomain(domainName) showToast(`Added: ${domainName}`, 'success') setNewDomain('') } catch (err: any) { showToast(err.message || 'Failed', 'error') } finally { setAdding(false) } }, [newDomain, addDomain, showToast]) // Auto-trigger health check for newly added domains useEffect(() => { if (!domains?.length) return domains.forEach(domain => { if (!healthReports[domain.id] && !loadingHealth[domain.id]) { setLoadingHealth(prev => ({ ...prev, [domain.id]: true })) api.getDomainHealth(domain.id, { refresh: false }) .then(report => { setHealthReports(prev => ({ ...prev, [domain.id]: report })) }) .catch(() => {}) .finally(() => { setLoadingHealth(prev => ({ ...prev, [domain.id]: false })) }) } }) }, [domains]) const handleRefresh = useCallback(async (id: number) => { setRefreshingId(id) try { await refreshDomain(id) showToast('Intel updated', 'success') } catch { showToast('Update failed', 'error') } finally { setRefreshingId(null) } }, [refreshDomain, showToast]) const handleDelete = useCallback(async (id: number, name: string) => { if (!confirm(`Drop target: ${name}?`)) return setDeletingId(id) try { await deleteDomain(id) showToast('Target dropped', 'success') } catch { showToast('Failed', 'error') } finally { setDeletingId(null) } }, [deleteDomain, showToast]) const handleToggleNotify = useCallback(async (id: number, current: boolean) => { setTogglingNotifyId(id) try { await api.updateDomainNotify(id, !current) updateDomain(id, { notify_on_available: !current }) showToast(!current ? 'Alerts armed' : 'Alerts disarmed', 'success') } catch { showToast('Failed', 'error') } finally { setTogglingNotifyId(null) } }, [updateDomain, showToast]) const handleHealthCheck = useCallback(async (id: number) => { if (loadingHealth[id]) return setLoadingHealth(prev => ({ ...prev, [id]: true })) try { const report = await api.getDomainHealth(id, { refresh: true }) setHealthReports(prev => ({ ...prev, [id]: report })) } catch {} finally { setLoadingHealth(prev => ({ ...prev, [id]: false })) } }, [loadingHealth]) // Load health useEffect(() => { const load = async () => { if (!domains?.length) return try { const data = await api.getDomainsHealthCache() if (data?.reports) setHealthReports(prev => ({ ...prev, ...data.reports })) } catch {} } load() }, [domains]) // Selected domain for detail view const selectedDomainData = selectedDomain ? domains?.find(d => d.id === selectedDomain) : null const selectedHealth = selectedDomain ? healthReports[selectedDomain] : null // Mobile Nav const mobileNavItems = [ { href: '/terminal/radar', label: 'Radar', icon: Target, active: false }, { href: '/terminal/market', label: 'Market', icon: Gavel, active: false }, { href: '/terminal/watchlist', label: 'Watch', icon: Eye, active: true }, { href: '/terminal/intel', label: 'Intel', icon: TrendingUp, active: false }, ] const tierName = subscription?.tier_name || subscription?.tier || 'Scout' const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap const drawerNavSections = [ { title: 'Discover', items: [ { href: '/terminal/radar', label: 'Radar', icon: Target }, { href: '/terminal/market', label: 'Market', icon: Gavel }, { href: '/terminal/intel', label: 'Intel', icon: TrendingUp }, ] }, { title: 'Manage', items: [ { href: '/terminal/watchlist', label: 'Watchlist', icon: Eye }, { href: '/terminal/sniper', label: 'Sniper', icon: Target }, ] }, { title: 'Monetize', items: [ { href: '/terminal/yield', label: 'Yield', icon: Coins, isNew: true }, { href: '/terminal/listing', label: 'For Sale', icon: Tag }, ] } ] return (
{/* Desktop Sidebar */}
{/* Main Content */}
{/* ═══════════════════════════════════════════════════════════════════════ */} {/* MOBILE HEADER */} {/* ═══════════════════════════════════════════════════════════════════════ */}
{/* Top Row */}
Watchlist
{stats.total} domains {stats.available} available
{/* Stats Grid */}
{stats.total}
Tracked
{stats.available}
Available
{stats.expiring}
Expiring
{/* ═══════════════════════════════════════════════════════════════════════ */} {/* ADD DOMAIN */} {/* ═══════════════════════════════════════════════════════════════════════ */}
setNewDomain(e.target.value)} onFocus={() => setSearchFocused(true)} onBlur={() => setSearchFocused(false)} placeholder="Add domain to watch..." className="flex-1 bg-transparent px-3 py-3 text-sm text-white placeholder:text-white/20 outline-none font-mono" />
{/* ═══════════════════════════════════════════════════════════════════════ */} {/* FILTERS */} {/* ═══════════════════════════════════════════════════════════════════════ */}
{[ { value: 'all', label: 'All', count: stats.total }, { value: 'available', label: 'Available', count: stats.available }, { value: 'expiring', label: 'Expiring', count: stats.expiring }, ].map((item) => ( ))}
{/* ═══════════════════════════════════════════════════════════════════════ */} {/* DESKTOP HEADER */} {/* ═══════════════════════════════════════════════════════════════════════ */}
Domain Surveillance

Watchlist {stats.total}

{stats.available}
Available
{stats.expiring}
Expiring
{/* ═══════════════════════════════════════════════════════════════════════ */} {/* DOMAIN LIST */} {/* ═══════════════════════════════════════════════════════════════════════ */}
{!filteredDomains.length ? (

No domains in your watchlist

Add a domain above to start monitoring

) : (
{filteredDomains.map((domain) => { const health = healthReports[domain.id] const healthStatus = health?.status || 'unknown' const config = healthConfig[healthStatus] const days = getDaysUntilExpiry(domain.expiration_date) return (
{/* Mobile Row */}
{domain.is_available ? ( ) : ( )}
{domain.name}
{domain.registrar || 'Unknown registrar'}
{domain.is_available ? 'AVAIL' : 'TAKEN'}
{/* Actions */}
{/* Desktop Row */}
{domain.is_available ? ( ) : ( )}
{domain.name}
{domain.registrar || 'Unknown'}
{/* Status */}
{domain.is_available ? 'AVAIL' : 'TAKEN'}
{/* Health */} {/* Expires */}
{days !== null && days <= 30 && days > 0 ? ( {days}d ) : ( formatExpiryDate(domain.expiration_date) )}
{/* Alert */} {/* Actions */}
) })}
)}
{/* ═══════════════════════════════════════════════════════════════════════ */} {/* MOBILE BOTTOM NAV */} {/* ═══════════════════════════════════════════════════════════════════════ */} {/* ═══════════════════════════════════════════════════════════════════════ */} {/* MOBILE DRAWER */} {/* ═══════════════════════════════════════════════════════════════════════ */} {menuOpen && (
setMenuOpen(false)} />
Pounce

POUNCE

Terminal v1.0

{drawerNavSections.map((section) => (
{section.title}
{section.items.map((item: any) => ( setMenuOpen(false)} className="flex items-center gap-3 px-4 py-2.5 text-white/60 active:text-white active:bg-white/[0.03] transition-colors border-l-2 border-transparent active:border-accent" > {item.label} {item.isNew && ( NEW )} ))}
))}
setMenuOpen(false)} className="flex items-center gap-3 py-2.5 text-white/50 active:text-white transition-colors" > Settings {user?.is_admin && ( setMenuOpen(false)} className="flex items-center gap-3 py-2.5 text-amber-500/70 active:text-amber-400 transition-colors" > Admin )}

{user?.name || user?.email?.split('@')[0] || 'User'}

{tierName}

{tierName === 'Scout' && ( setMenuOpen(false)} className="flex items-center justify-center gap-2 w-full py-2.5 bg-accent text-black text-xs font-bold uppercase tracking-wider active:scale-[0.98] transition-all mb-2" > Upgrade )}
)} {/* ═══════════════════════════════════════════════════════════════════════ */} {/* HEALTH MODAL */} {/* ═══════════════════════════════════════════════════════════════════════ */} {selectedDomainData && (
setSelectedDomain(null)} >
e.stopPropagation()} > {/* Header */}
Health Report
{/* Content */}
{/* Domain */}

{selectedDomainData.name}

{healthConfig[selectedHealth?.status || 'unknown'].label} {selectedHealth?.score !== undefined && ( Score: {selectedHealth.score}/100 )}
{/* Checks */} {selectedHealth ? (
{/* DNS */}
DNS {selectedHealth.dns?.has_a || selectedHealth.dns?.has_ns ? (
OK
) : (
FAIL
)}
{/* HTTP */}
HTTP {selectedHealth.http?.is_reachable ? (
OK
) : (
{selectedHealth.http?.error || 'FAIL'}
)}
{/* SSL */}
SSL {selectedHealth.ssl?.has_certificate ? (
VALID
) : (
MISSING
)}
{/* Parked */}
NOT PARKED {!selectedHealth.dns?.is_parked && !selectedHealth.http?.is_parked ? (
OK
) : (
PARKED
)}
) : (
Run health check to see results
)} {/* Refresh Button */}
)}
{toast && }
) }