'use client' import { useState, useEffect, useCallback } from 'react' import { useSearchParams } from 'next/navigation' import { TrendingUp, DollarSign, Zap, Plus, CheckCircle2, Clock, AlertCircle, MousePointer, Target, Wallet, RefreshCw, ChevronRight, Copy, Check, XCircle, Sparkles, Loader2, Eye, Gavel, Menu, Settings, Shield, LogOut, Crown, Coins, Tag, X, Briefcase, Trash2 } from 'lucide-react' import { api, YieldDomain, YieldTransaction } from '@/lib/api' import { useStore } from '@/lib/store' import { Sidebar } from '@/components/Sidebar' import clsx from 'clsx' import Link from 'next/link' import Image from 'next/image' // ============================================================================ // STATUS BADGE // ============================================================================ function StatusBadge({ status }: { status: string }) { const config: Record = { active: { color: 'bg-accent/10 text-accent border-accent/20', icon: CheckCircle2 }, pending: { color: 'bg-amber-400/10 text-amber-400 border-amber-400/20', icon: Clock }, verifying: { color: 'bg-blue-400/10 text-blue-400 border-blue-400/20', icon: RefreshCw }, paused: { color: 'bg-white/5 text-white/40 border-white/10', icon: AlertCircle }, error: { color: 'bg-red-400/10 text-red-400 border-red-400/20', icon: XCircle }, } const { color, icon: Icon } = config[status] || config.pending return ( {status} ) } // ============================================================================ // ACTIVATE MODAL - Only verified portfolio domains // ============================================================================ function ActivateModal({ isOpen, onClose, onSuccess, prefillDomain, }: { isOpen: boolean onClose: () => void onSuccess: () => void prefillDomain?: string | null }) { const subscription = useStore((s) => s.subscription) const tier = (subscription?.tier || 'scout').toLowerCase() const isTycoon = tier === 'tycoon' const canPreview = tier === 'trader' || tier === 'tycoon' const [selectedDomain, setSelectedDomain] = useState('') const [verifiedDomains, setVerifiedDomains] = useState<{ id: number; domain: string }[]>([]) const [loadingDomains, setLoadingDomains] = useState(true) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [step, setStep] = useState<1 | 2>(1) const [activation, setActivation] = useState(null) const [dnsChecking, setDnsChecking] = useState(false) const [dnsResult, setDnsResult] = useState(null) const [copied, setCopied] = useState(null) const [previewLoading, setPreviewLoading] = useState(false) const [previewError, setPreviewError] = useState(null) const [preview, setPreview] = useState(null) useEffect(() => { if (!isOpen) return if (prefillDomain) { setSelectedDomain(prefillDomain) setStep(1) setActivation(null) setDnsResult(null) } const fetchVerifiedDomains = async () => { setLoadingDomains(true) try { const domains = await api.getVerifiedPortfolioDomains() setVerifiedDomains(domains.map(d => ({ id: d.id, domain: d.domain }))) } catch (err) { console.error('Failed to load verified domains:', err) } finally { setLoadingDomains(false) } } fetchVerifiedDomains() }, [isOpen]) useEffect(() => { if (!isOpen) return setStep(1) setActivation(null) setDnsResult(null) setDnsChecking(false) setError(null) setSelectedDomain('') setPreview(null) setPreviewError(null) setPreviewLoading(false) }, [isOpen]) const copyToClipboard = async (value: string, key: string) => { try { await navigator.clipboard.writeText(value) setCopied(key) setTimeout(() => setCopied(null), 1200) } catch { // ignore } } const handleActivate = async () => { if (!selectedDomain) return setLoading(true) setError(null) try { const res = await api.activateYieldDomain(selectedDomain, true) setActivation({ domain_id: res.domain_id, domain: res.domain, status: res.status, dns_instructions: res.dns_instructions, landing: res.landing || null, }) setStep(2) } catch (err: any) { setError(err.message || 'Failed') } finally { setLoading(false) } } const handlePreview = async (refresh: boolean = false) => { if (!selectedDomain) return if (!canPreview) return setPreviewLoading(true) setPreviewError(null) setPreview(null) try { const res = await api.getYieldLandingPreview(selectedDomain, refresh) setPreview({ template: res.result.template, headline: res.result.headline, seo_intro: res.result.seo_intro, cta_label: res.result.cta_label, niche: res.result.niche, color_scheme: res.result.color_scheme, model: res.model, generated_at: res.generated_at, cached: res.cached, }) } catch (err: any) { setPreviewError(err.message || 'Preview failed') } finally { setPreviewLoading(false) } } const checkDNS = useCallback(async (domainId: number) => { setDnsChecking(true) setError(null) try { const res = await api.verifyYieldDomainDNS(domainId) setDnsResult({ verified: res.verified, expected_ns: res.expected_ns, actual_ns: res.actual_ns, cname_ok: res.cname_ok, error: res.error, }) if (res.verified) { onSuccess() } } catch (err: any) { setError(err.message || 'DNS check failed') } finally { setDnsChecking(false) } }, [onSuccess]) if (!isOpen) return null return (
e.stopPropagation()}>
{isTycoon ? 'Activate Yield' : 'Yield Preview'}
{step === 1 && loadingDomains ? (
) : step === 1 && verifiedDomains.length === 0 ? (

No Verified Domains

You need to add domains to your portfolio and verify DNS ownership before activating Yield.

Go to Portfolio
) : step === 1 ? ( <>
{isTycoon ? (

Only DNS-verified domains from your portfolio can be activated for Yield.

) : (

Yield is Tycoon-only. On Trader you can preview the landing page that will be generated.

)}
{error &&
{error}
} {!isTycoon && ( <>
Upgrade
{previewError && (
{previewError}
)} {preview && (
Landing Page Preview
{preview.headline}
{preview.seo_intro}
CTA: {preview.cta_label} {preview.cached ? 'Cached' : 'Fresh'} • {preview.model}
)} )} {isTycoon && ( )} ) : ( <>
Domain
{activation?.domain}
{activation?.landing?.headline && (
Landing Page (Generated)
{activation.landing.headline}
{activation.landing.seo_intro}
CTA: {activation.landing.cta_label} {activation.landing.model ? • {activation.landing.model} : null}
)}
Option A (Recommended): Nameservers
{(activation?.dns_instructions.nameservers || []).map((ns, idx) => (
0 && "border-t border-white/[0.06]")}> {ns}
))}
Option B: CNAME / ALIAS
Host: {activation?.dns_instructions.cname_host} → Target: {activation?.dns_instructions.cname_target}

Some DNS providers use ALIAS/ANAME for apex. We accept both CNAME and ALIAS-style flattening.

{dnsResult && (
{dnsResult.verified ? 'Connected. Domain is active.' : 'Not connected yet. Waiting for DNS propagation.'} {dnsResult.verified ? : }
{dnsResult.error &&
Error: {dnsResult.error}
} {!dnsResult.verified && (
Expected NS: {dnsResult.expected_ns?.join(', ') || '—'}
Actual NS: {dnsResult.actual_ns?.join(', ') || '—'}
)}
)} {error &&
{error}
}
{dnsResult?.verified && ( )} )}
) } // ============================================================================ // MAIN PAGE // ============================================================================ export default function YieldPage() { const { subscription, user, logout, checkAuth } = useStore() const searchParams = useSearchParams() const [loading, setLoading] = useState(true) const [dashboard, setDashboard] = useState(null) const [showActivateModal, setShowActivateModal] = useState(false) const [prefillDomain, setPrefillDomain] = useState(null) const [refreshing, setRefreshing] = useState(false) const [menuOpen, setMenuOpen] = useState(false) const [deletingId, setDeletingId] = useState(null) const tier = (subscription?.tier || 'scout').toLowerCase() const tierName = subscription?.tier_name || (tier.charAt(0).toUpperCase() + tier.slice(1)) const isTycoon = tier === 'tycoon' useEffect(() => { checkAuth() }, [checkAuth]) const fetchDashboard = useCallback(async () => { try { const data = await api.getYieldDashboard() setDashboard(data) } catch (err) { console.error(err) } finally { setLoading(false); setRefreshing(false) } }, []) const handleDeleteYield = useCallback(async (domainId: number, domainName: string) => { if (!confirm(`Remove ${domainName} from Yield? This will stop all revenue tracking.`)) return setDeletingId(domainId) try { await api.deleteYieldDomain(domainId) fetchDashboard() } catch (err) { console.error('Failed to remove yield domain:', err) } finally { setDeletingId(null) } }, [fetchDashboard]) useEffect(() => { fetchDashboard() }, [fetchDashboard]) useEffect(() => { const activate = searchParams.get('activate') if (activate) { setPrefillDomain(activate) setShowActivateModal(true) } }, [searchParams]) const stats = dashboard?.stats const mobileNavItems = [ { href: '/terminal/hunt', 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 tierLabelForDrawer = subscription?.tier_name || subscription?.tier || 'Scout' const TierIcon = tierLabelForDrawer === 'Tycoon' ? Crown : tierLabelForDrawer === 'Trader' ? TrendingUp : Zap const drawerNavSections = [ { title: 'Discover', items: [ { href: '/terminal/hunt', 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 }, { href: '/terminal/sniper', label: 'Sniper', icon: Target }, ]}, { title: 'Monetize', items: [ { href: '/terminal/yield', label: 'Yield', icon: Coins, active: true }, { href: '/terminal/listing', label: 'For Sale', icon: Tag }, ]} ] return (
{/* MOBILE HEADER */}
Yield
{stats?.active_domains || 0} active
${stats?.monthly_revenue || 0}
Monthly
{stats?.monthly_clicks || 0}
Clicks
${stats?.pending_payout || 0}
Pending
{/* DESKTOP HEADER */}
Yield

Yield

Monetize your parked domains. Route visitor intent to earn passive income.

{!isTycoon && (
Yield activation is Tycoon-only. You can preview the landing page on Trader.
)}
{stats && (
${stats.monthly_revenue}
Monthly
{stats.active_domains}
Active
)}
{/* ADD BUTTON MOBILE */}
{/* CONTENT */}
{loading ? (
) : !dashboard?.domains?.length ? (

No yield domains yet

Activate domains to earn passive income

) : (
{/* Header */}
Domain
Status
Intent
Landing
Clicks
Conv.
Revenue
Action
{dashboard.domains.map((domain: YieldDomain) => (
{/* Mobile */}
{domain.domain}
Landing:{' '} {domain.landing_headline ? ( Ready ) : ( Missing )} expand
{domain.landing_headline ? (
{domain.landing_headline}
{domain.landing_intro &&
{domain.landing_intro}
}
CTA: {domain.landing_cta_label || '—'}
{domain.landing_generated_at ? new Date(domain.landing_generated_at).toLocaleString() : '—'} {domain.landing_model ? • {domain.landing_model} : null}
) : (
No landing config stored yet. (Older activation) Remove + re-activate on Tycoon to regenerate.
)}
{domain.total_clicks} clicks ${domain.total_revenue}
{/* Desktop */}
{domain.domain}
{domain.detected_intent?.replace('_', ' ') || '—'}
{domain.landing_headline ? ( Ready ) : ( Missing )} details
{domain.landing_headline ? ( <>
{domain.landing_headline}
{domain.landing_intro &&
{domain.landing_intro}
}
CTA: {domain.landing_cta_label || '—'}
{domain.landing_generated_at ? new Date(domain.landing_generated_at).toLocaleString() : '—'} {domain.landing_model ? • {domain.landing_model} : null}
) : (
No landing config stored yet. Remove + re-activate on Tycoon to regenerate.
)}
{domain.total_clicks}
{domain.total_conversions}
${domain.total_revenue}
))}
)}
{/* MOBILE BOTTOM NAV */} {/* DRAWER */} {menuOpen && (
setMenuOpen(false)} />
Pounce

POUNCE

Terminal v1.0

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

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

{tierLabelForDrawer}

{tierLabelForDrawer === '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 mb-2">Upgrade}
)}
{ setShowActivateModal(false); setPrefillDomain(null) }} onSuccess={fetchDashboard} />
) }