'use client' import { useEffect, useState, useMemo, useCallback } from 'react' import { useStore } from '@/lib/store' import { api, PortfolioDomain, PortfolioSummary } from '@/lib/api' import { Sidebar } from '@/components/Sidebar' import { Toast, useToast } from '@/components/Toast' import { Plus, Trash2, RefreshCw, Loader2, Briefcase, X, Target, ExternalLink, Gavel, TrendingUp, Menu, Settings, Shield, ShieldCheck, ShieldAlert, LogOut, Crown, Sparkles, Coins, Tag, Zap, Eye, ChevronUp, ChevronDown, DollarSign, Calendar, Edit3, CheckCircle, AlertCircle, TrendingDown, BarChart3, Copy, Check } from 'lucide-react' import clsx from 'clsx' import Link from 'next/link' import Image from 'next/image' // ============================================================================ // HELPERS // ============================================================================ function getDaysUntilRenewal(renewalDate: string | null): number | null { if (!renewalDate) return null const renDate = new Date(renewalDate) const now = new Date() const diffTime = renDate.getTime() - now.getTime() return Math.ceil(diffTime / (1000 * 60 * 60 * 24)) } function formatDate(date: string | null): string { if (!date) return '—' return new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) } function formatCurrency(value: number | null): string { if (value === null || value === undefined) return '—' return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(value) } function formatROI(roi: number | null): string { if (roi === null || roi === undefined) return '—' const sign = roi >= 0 ? '+' : '' return `${sign}${roi.toFixed(0)}%` } // ============================================================================ // MAIN PAGE // ============================================================================ export default function PortfolioPage() { const { subscription, user, logout, checkAuth } = useStore() const { toast, showToast, hideToast } = useToast() const [domains, setDomains] = useState([]) const [summary, setSummary] = useState(null) const [loading, setLoading] = useState(true) const [refreshingId, setRefreshingId] = useState(null) const [deletingId, setDeletingId] = useState(null) const [showAddModal, setShowAddModal] = useState(false) const [selectedDomain, setSelectedDomain] = useState(null) const [verifyingDomain, setVerifyingDomain] = useState(null) const [filter, setFilter] = useState<'all' | 'active' | 'sold'>('all') // Sorting const [sortField, setSortField] = useState<'domain' | 'value' | 'roi' | 'renewal'>('domain') const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc') // Mobile Menu const [menuOpen, setMenuOpen] = useState(false) // Tier-based access for listing (same as listing page) const tier = subscription?.tier || 'scout' const isScout = tier === 'scout' const canListForSale = !isScout // Only Trader & Tycoon can list useEffect(() => { checkAuth() }, [checkAuth]) const loadData = useCallback(async () => { setLoading(true) try { const [domainsData, summaryData] = await Promise.all([ api.getPortfolio(), api.getPortfolioSummary() ]) setDomains(domainsData) setSummary(summaryData) } catch (err) { console.error('Failed to load portfolio:', err) } finally { setLoading(false) } }, []) useEffect(() => { loadData() }, [loadData]) // Stats const stats = useMemo(() => { const active = domains.filter(d => !d.is_sold).length const sold = domains.filter(d => d.is_sold).length const renewingSoon = domains.filter(d => { if (d.is_sold || !d.renewal_date) return false const days = getDaysUntilRenewal(d.renewal_date) return days !== null && days <= 30 && days > 0 }).length return { total: domains.length, active, sold, renewingSoon } }, [domains]) // Filtered & Sorted const filteredDomains = useMemo(() => { let filtered = domains.filter(d => { if (filter === 'active') return !d.is_sold if (filter === 'sold') return d.is_sold return true }) const mult = sortDirection === 'asc' ? 1 : -1 filtered.sort((a, b) => { switch (sortField) { case 'domain': return mult * a.domain.localeCompare(b.domain) case 'value': return mult * ((a.estimated_value || 0) - (b.estimated_value || 0)) case 'roi': return mult * ((a.roi || 0) - (b.roi || 0)) case 'renewal': const aDate = a.renewal_date ? new Date(a.renewal_date).getTime() : Infinity const bDate = b.renewal_date ? new Date(b.renewal_date).getTime() : Infinity return mult * (aDate - bDate) default: return 0 } }) return filtered }, [domains, filter, sortField, sortDirection]) const handleSort = useCallback((field: typeof sortField) => { if (sortField === field) { setSortDirection(d => d === 'asc' ? 'desc' : 'asc') } else { setSortField(field) setSortDirection('asc') } }, [sortField]) const handleRefreshValue = useCallback(async (id: number) => { setRefreshingId(id) try { const updated = await api.refreshDomainValue(id) setDomains(prev => prev.map(d => d.id === id ? updated : d)) showToast('Value updated', 'success') } catch { showToast('Update failed', 'error') } finally { setRefreshingId(null) } }, [showToast]) const handleDelete = useCallback(async (id: number, name: string) => { if (!confirm(`Remove ${name} from portfolio?`)) return setDeletingId(id) try { await api.deletePortfolioDomain(id) setDomains(prev => prev.filter(d => d.id !== id)) showToast('Domain removed', 'success') loadData() // Refresh summary } catch { showToast('Failed', 'error') } finally { setDeletingId(null) } }, [showToast, loadData]) const tierName = subscription?.tier_name || subscription?.tier || 'Scout' const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap 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: false }, { href: '/terminal/intel', label: 'Intel', icon: TrendingUp, active: false }, ] 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/portfolio', label: 'Portfolio', icon: Briefcase, active: true }, { 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 (
{/* MOBILE HEADER */}
My Portfolio
{stats.total} domains
{/* Stats Grid */}
{stats.active}
Active
{formatCurrency(summary?.total_value || 0).replace('$', '')}
Value
= 0 ? "text-accent" : "text-rose-400")}> {formatROI(summary?.overall_roi || 0)}
ROI
{stats.renewingSoon}
Renewing
{/* DESKTOP HEADER */}
Domain Assets

Portfolio {stats.total}

{formatCurrency(summary?.total_invested || 0)}
Invested
{formatCurrency(summary?.total_value || 0)}
Value
= 0 ? "text-accent" : "text-rose-400")}> {formatROI(summary?.overall_roi || 0)}
ROI
{stats.renewingSoon}
Renewing
{/* ADD DOMAIN + FILTERS */}
{/* Filters */}
{[ { value: 'all', label: 'All', count: stats.total }, { value: 'active', label: 'Active', count: stats.active }, { value: 'sold', label: 'Sold', count: stats.sold }, ].map((item) => ( ))}
{/* DOMAIN LIST */}
{loading ? (
) : !filteredDomains.length ? (

No domains in your portfolio

Add your first domain to start tracking

) : (
{/* Desktop Table Header */}
Purchase
Actions
{filteredDomains.map((domain) => { const daysUntilRenewal = getDaysUntilRenewal(domain.renewal_date) const isRenewingSoon = daysUntilRenewal !== null && daysUntilRenewal <= 30 && daysUntilRenewal > 0 const roiPositive = (domain.roi || 0) >= 0 return (
{/* Mobile Row */}
{domain.is_sold ? : }
{domain.domain}
{domain.registrar || 'Unknown registrar'}
{formatCurrency(domain.estimated_value)}
{formatROI(domain.roi)}
{!domain.is_sold && ( domain.is_dns_verified ? ( canListForSale && ( Sell ) ) : ( ) )}
{/* Desktop Row */}
{domain.is_sold ? : domain.is_dns_verified ? : }
{domain.domain}
{domain.registrar || 'Unknown'} {domain.is_sold && SOLD} {!domain.is_sold && domain.is_dns_verified && VERIFIED} {!domain.is_sold && !domain.is_dns_verified && UNVERIFIED}
{/* Purchase */}
{formatCurrency(domain.purchase_price)}
{/* Value */}
{formatCurrency(domain.estimated_value)}
{/* ROI */}
{roiPositive ? : } {formatROI(domain.roi)}
{/* Renewal */}
{domain.is_sold ? ( ) : isRenewingSoon ? ( {daysUntilRenewal}d ) : ( {formatDate(domain.renewal_date)} )}
{/* Actions */}
{/* Verification Status & Actions */} {!domain.is_sold && ( domain.is_dns_verified ? ( canListForSale && ( Sell ) ) : ( ) )}
) })}
)}
{/* MOBILE BOTTOM NAV */} {/* MOBILE DRAWER */} {menuOpen && setMenuOpen(false)} onLogout={() => { logout(); setMenuOpen(false) }} />}
{/* ADD DOMAIN MODAL */} {showAddModal && setShowAddModal(false)} onSuccess={() => { loadData(); setShowAddModal(false) }} />} {/* DOMAIN DETAIL MODAL */} {selectedDomain && setSelectedDomain(null)} onUpdate={loadData} canListForSale={canListForSale} />} {/* DNS VERIFICATION MODAL */} {verifyingDomain && setVerifyingDomain(null)} onSuccess={() => { loadData(); setVerifyingDomain(null) }} />} {toast && }
) } // ============================================================================ // ADD DOMAIN MODAL // ============================================================================ function AddDomainModal({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) { const [domain, setDomain] = useState('') const [purchasePrice, setPurchasePrice] = useState('') const [purchaseDate, setPurchaseDate] = useState('') const [registrar, setRegistrar] = useState('') const [renewalDate, setRenewalDate] = useState('') const [renewalCost, setRenewalCost] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!domain.trim()) return setLoading(true) setError(null) try { await api.addPortfolioDomain({ domain: domain.trim(), purchase_price: purchasePrice ? parseFloat(purchasePrice) : undefined, purchase_date: purchaseDate || undefined, registrar: registrar || undefined, renewal_date: renewalDate || undefined, renewal_cost: renewalCost ? parseFloat(renewalCost) : undefined, }) onSuccess() } catch (err: any) { setError(err.message || 'Failed to add domain') } finally { setLoading(false) } } return (
e.stopPropagation()}>
Add to Portfolio
{error &&
{error}
}
setDomain(e.target.value)} required className="w-full px-3 py-2.5 bg-white/5 border border-white/10 text-white text-sm font-mono placeholder:text-white/20 outline-none focus:border-accent/50" placeholder="example.com" />
setPurchasePrice(e.target.value)} min="0" className="w-full px-3 py-2.5 bg-white/5 border border-white/10 text-white text-sm font-mono placeholder:text-white/20 outline-none focus:border-accent/50" placeholder="0" />
setPurchaseDate(e.target.value)} className="w-full px-3 py-2.5 bg-white/5 border border-white/10 text-white text-sm font-mono outline-none focus:border-accent/50" />
setRegistrar(e.target.value)} className="w-full px-3 py-2.5 bg-white/5 border border-white/10 text-white text-sm font-mono placeholder:text-white/20 outline-none focus:border-accent/50" placeholder="Namecheap, GoDaddy, etc." />
setRenewalDate(e.target.value)} className="w-full px-3 py-2.5 bg-white/5 border border-white/10 text-white text-sm font-mono outline-none focus:border-accent/50" />
setRenewalCost(e.target.value)} min="0" className="w-full px-3 py-2.5 bg-white/5 border border-white/10 text-white text-sm font-mono placeholder:text-white/20 outline-none focus:border-accent/50" placeholder="0" />
) } // ============================================================================ // DOMAIN DETAIL MODAL // ============================================================================ function DomainDetailModal({ domain, onClose, onUpdate, canListForSale }: { domain: PortfolioDomain; onClose: () => void; onUpdate: () => void; canListForSale: boolean }) { const [notes, setNotes] = useState(domain.notes || '') const [tags, setTags] = useState(domain.tags || '') const [showSellModal, setShowSellModal] = useState(false) const [saving, setSaving] = useState(false) const handleSave = async () => { setSaving(true) try { await api.updatePortfolioDomain(domain.id, { notes, tags }) onUpdate() onClose() } catch {} finally { setSaving(false) } } const handleMarkSold = async (saleDate: string, salePrice: number) => { try { await api.markDomainSold(domain.id, saleDate, salePrice) onUpdate() onClose() } catch {} } return (
e.stopPropagation()}>
Domain Details
{/* Domain Header */}

{domain.domain}

{domain.registrar || 'Unknown registrar'}

{/* Stats Grid */}
{formatCurrency(domain.purchase_price)}
Purchased
{formatCurrency(domain.estimated_value)}
Est. Value
= 0 ? "bg-accent/[0.05] border-accent/20" : "bg-rose-500/[0.05] border-rose-500/20")}>
= 0 ? "text-accent" : "text-rose-400")}>{formatROI(domain.roi)}
ROI
{/* Dates */}
Purchase Date
{formatDate(domain.purchase_date)}
Renewal Date
{formatDate(domain.renewal_date)}
{/* Notes */}