- {[
- { value: 'all', label: 'All', count: stats.total },
- { value: 'active', label: 'Active', count: stats.active },
- { value: 'sold', label: 'Sold', count: stats.sold },
- { value: 'expiring', label: 'Expiring Soon', count: stats.expiringSoon },
- ].map((item) => (
-
- ))}
+
+
+
+
+ {/* Asset Filters - only show when assets tab active */}
+ {activeTab === 'assets' && (
+
+ {[
+ { value: 'all', label: 'All', count: stats.total },
+ { value: 'active', label: 'Active', count: stats.active },
+ { value: 'sold', label: 'Sold', count: stats.sold },
+ { value: 'expiring', label: 'Expiring Soon', count: stats.expiringSoon },
+ ].map((item) => (
+
+ ))}
+
+ )}
- {/* DOMAIN LIST */}
+ {/* TAB CONTENT */}
+ {/* FINANCIALS TAB */}
+ {activeTab === 'financials' && (
+
+ {cfoLoading ? (
+
+
+
+ ) : !cfoData ? (
+
+
+
No financial data
+
Add domains to your portfolio to see costs
+
+
+ ) : (
+ <>
+ {/* Summary Cards */}
+
+
+
+
+ Next 30 Days
+
+
${Math.round(cfoData.upcoming_30d_total_usd)}
+
{cfoData.upcoming_30d_rows.length} renewals due
+
+
+
+
+
+ Annual Burn
+
+
+ ${Math.round(cfoData.monthly.reduce((sum, m) => sum + m.total_cost_usd, 0))}
+
+
Based on 12-month forecast
+
+
+
+
+
+ Drop Candidates
+
+
{cfoData.kill_list.length}
+
No yield, expiring soon
+
+
+
+ {/* Upcoming Renewals */}
+ {cfoData.upcoming_30d_rows.length > 0 && (
+
+
+
Upcoming Costs
+
Next 30 days
+
+
+ {cfoData.upcoming_30d_rows.slice(0, 10).map((r) => (
+
+ {r.domain}
+
+ {r.renewal_date ? r.renewal_date.slice(0, 10) : '—'} · ${Math.round(r.renewal_cost_usd || 0)}
+
+
+ ))}
+
+
+ )}
+
+ {/* Burn Rate Timeline */}
+
+
+ {/* Kill List */}
+
+
+ {/* Tips */}
+
+
What to do next
+
+
- If a renewal cost is missing, fill it in on the domain in Assets → Edit.
+
- "Set to Drop" is a local flag — you still need to disable auto-renew at your registrar.
+
- Want to cover costs? Activate Yield only for DNS‑verified domains.
+
+
+ >
+ )}
+
+ )}
+
+ {/* ASSETS TAB (Domain List) */}
+ {activeTab === 'assets' && (
+ <>
{loading ? (
@@ -1390,6 +1571,8 @@ export default function PortfolioPage() {
})}
)}
+ >
+ )}
{/* MOBILE BOTTOM NAV */}
diff --git a/frontend/src/app/terminal/radar/page.tsx b/frontend/src/app/terminal/radar/page.tsx
deleted file mode 100644
index db6d550..0000000
--- a/frontend/src/app/terminal/radar/page.tsx
+++ /dev/null
@@ -1,882 +0,0 @@
-'use client'
-
-import { useEffect, useState, useCallback, useRef, useMemo } from 'react'
-import { useStore } from '@/lib/store'
-import { api } from '@/lib/api'
-import { Sidebar } from '@/components/Sidebar'
-import { Toast, useToast } from '@/components/Toast'
-import {
- Eye,
- Gavel,
- ArrowRight,
- CheckCircle2,
- XCircle,
- Loader2,
- Crosshair,
- Zap,
- Globe,
- Target,
- Search,
- X,
- TrendingUp,
- Settings,
- Clock,
- ChevronRight,
- ChevronUp,
- ChevronDown,
- Sparkles,
- Radio,
- Activity,
- Menu,
- Tag,
- Coins,
- Shield,
- LogOut,
- Crown,
- ExternalLink,
- Briefcase
-} from 'lucide-react'
-import clsx from 'clsx'
-import Link from 'next/link'
-import Image from 'next/image'
-
-// ============================================================================
-// TYPES
-// ============================================================================
-
-interface HotAuction {
- domain: string
- current_bid: number
- end_time?: string
- platform: string
- affiliate_url?: string
-}
-
-function calcTimeRemaining(endTimeIso?: string): string {
- if (!endTimeIso) return 'N/A'
- const end = new Date(endTimeIso).getTime()
- const now = Date.now()
- const diff = end - now
-
- if (diff <= 0) return 'Ended'
-
- const seconds = Math.floor(diff / 1000)
- const days = Math.floor(seconds / 86400)
- const hours = Math.floor((seconds % 86400) / 3600)
- const mins = Math.floor((seconds % 3600) / 60)
-
- if (days > 0) return `${days}d ${hours}h`
- if (hours > 0) return `${hours}h ${mins}m`
- if (mins > 0) return `${mins}m`
- return '< 1m'
-}
-
-interface SearchResult {
- domain: string
- status: string
- is_available: boolean | null
- registrar: string | null
- expiration_date: string | null
- loading: boolean
- inAuction: boolean
- auctionData?: HotAuction
-}
-
-// ============================================================================
-// MAIN PAGE
-// ============================================================================
-
-export default function RadarPage() {
- const { isAuthenticated, isLoading: authLoading, domains, addDomain, user, subscription, logout, checkAuth } = useStore()
- const { toast, showToast, hideToast } = useToast()
-
- const [hotAuctions, setHotAuctions] = useState
([])
- const [marketStats, setMarketStats] = useState({ totalAuctions: 0, endingSoon: 0 })
- const [loadingData, setLoadingData] = useState(true)
- const [tick, setTick] = useState(0)
-
- const [searchQuery, setSearchQuery] = useState('')
- const [searchResult, setSearchResult] = useState(null)
- const [addingToWatchlist, setAddingToWatchlist] = useState(false)
- const [searchFocused, setSearchFocused] = useState(false)
- const searchInputRef = useRef(null)
-
- // Mobile Menu State
- const [menuOpen, setMenuOpen] = useState(false)
-
- // Sorting for Auctions
- const [auctionSort, setAuctionSort] = useState<'domain' | 'time' | 'bid'>('time')
- const [auctionSortDir, setAuctionSortDir] = useState<'asc' | 'desc'>('asc')
-
- // Check auth on mount
- useEffect(() => {
- checkAuth()
- }, [checkAuth])
-
- // Load Data - Using same API as Market page for consistency
- const loadDashboardData = useCallback(async () => {
- try {
- // External auctions only (Pounce Direct has no end_time)
- const [feed, ending24h] = await Promise.all([
- api.getMarketFeed({ source: 'external', sortBy: 'time', limit: 10 }),
- api.getMarketFeed({ source: 'external', endingWithin: 24, sortBy: 'time', limit: 1 }),
- ])
-
- const auctions: HotAuction[] = (feed.items || [])
- .filter((item: any) => item.status === 'auction' && item.end_time)
- .slice(0, 6)
- .map((item: any) => ({
- domain: item.domain,
- current_bid: item.price || 0,
- end_time: item.end_time || undefined,
- platform: item.source || 'Unknown',
- affiliate_url: item.url || '',
- }))
-
- setHotAuctions(auctions)
- setMarketStats({
- totalAuctions: feed.total || feed.auction_count || 0,
- endingSoon: ending24h.total || 0,
- })
- } catch (error) {
- console.error('Failed to load data:', error)
- } finally {
- setLoadingData(false)
- }
- }, [])
-
- useEffect(() => {
- if (!authLoading) {
- if (isAuthenticated) {
- loadDashboardData()
- const interval = setInterval(() => setTick(t => t + 1), 30000)
- return () => clearInterval(interval)
- } else {
- setLoadingData(false)
- }
- }
- }, [authLoading, isAuthenticated, loadDashboardData])
-
- // Sorted auctions
- const sortedAuctions = useMemo(() => {
- const mult = auctionSortDir === 'asc' ? 1 : -1
- return [...hotAuctions].sort((a, b) => {
- switch (auctionSort) {
- case 'domain': return mult * a.domain.localeCompare(b.domain)
- case 'bid': return mult * (a.current_bid - b.current_bid)
- case 'time':
- const aTime = a.end_time ? new Date(a.end_time).getTime() : Infinity
- const bTime = b.end_time ? new Date(b.end_time).getTime() : Infinity
- return mult * (aTime - bTime)
- default: return 0
- }
- })
- }, [hotAuctions, auctionSort, auctionSortDir, tick])
-
- const handleAuctionSort = useCallback((field: typeof auctionSort) => {
- if (auctionSort === field) {
- setAuctionSortDir(d => d === 'asc' ? 'desc' : 'asc')
- } else {
- setAuctionSort(field)
- setAuctionSortDir(field === 'bid' ? 'desc' : 'asc')
- }
- }, [auctionSort])
-
- // Search
- const handleSearch = useCallback(async (domainInput: string) => {
- if (!domainInput.trim()) { setSearchResult(null); return }
- const cleanDomain = domainInput.trim().toLowerCase()
- setSearchResult({ domain: cleanDomain, status: 'checking', is_available: null, registrar: null, expiration_date: null, loading: true, inAuction: false })
-
- try {
- const [whoisResult, auctionsResult] = await Promise.all([
- api.checkDomain(cleanDomain).catch(() => null),
- api.getAuctions(cleanDomain).catch(() => ({ auctions: [] })),
- ])
- const auctionMatch = (auctionsResult as any).auctions?.find((a: any) => a.domain.toLowerCase() === cleanDomain)
- setSearchResult({
- domain: whoisResult?.domain || cleanDomain,
- status: whoisResult?.status || 'unknown',
- is_available: whoisResult?.is_available ?? null,
- registrar: whoisResult?.registrar || null,
- expiration_date: whoisResult?.expiration_date || null,
- loading: false,
- inAuction: !!auctionMatch,
- auctionData: auctionMatch,
- })
- } catch {
- setSearchResult({ domain: cleanDomain, status: 'error', is_available: null, registrar: null, expiration_date: null, loading: false, inAuction: false })
- }
- }, [])
-
- const handleAddToWatchlist = useCallback(async () => {
- if (!searchQuery.trim()) return
- setAddingToWatchlist(true)
- try {
- await addDomain(searchQuery.trim())
- showToast(`Added: ${searchQuery.trim()}`, 'success')
- setSearchQuery('')
- setSearchResult(null)
- } catch (err: any) {
- showToast(err.message || 'Failed', 'error')
- } finally {
- setAddingToWatchlist(false)
- }
- }, [searchQuery, addDomain, showToast])
-
- useEffect(() => {
- const timer = setTimeout(() => {
- if (searchQuery.length > 3) handleSearch(searchQuery)
- else setSearchResult(null)
- }, 500)
- return () => clearTimeout(timer)
- }, [searchQuery, handleSearch])
-
- // Computed
- const availableDomains = domains?.filter(d => d.is_available) || []
- const totalDomains = domains?.length || 0
-
- // Nav Items for Mobile Bottom Bar
- const mobileNavItems = [
- { href: '/terminal/radar', label: 'Radar', icon: Target, active: true },
- { 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 },
- ]
-
- // Full Navigation for Drawer
- 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/portfolio', label: 'Portfolio', icon: Briefcase },
- { 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 - Techy Angular */}
- {/* ═══════════════════════════════════════════════════════════════════════ */}
-
-
- {/* ═══════════════════════════════════════════════════════════════════════ */}
- {/* SEARCH SECTION */}
- {/* ═══════════════════════════════════════════════════════════════════════ */}
-
- {/* Desktop Hero Text */}
-
-
-
- Domain Radar
-
-
- Check domain availability, track your watchlist, and discover live auctions.
-
-
-
- {/* Search Card */}
-
- {/* Desktop Glow */}
-
-
-
- {/* Terminal Header */}
-
-
-
- Domain Search
-
-
-
-
- {/* Search Input */}
-
-
-
-
- setSearchQuery(e.target.value)}
- onFocus={() => setSearchFocused(true)}
- onBlur={() => setSearchFocused(false)}
- placeholder="example.com"
- className="flex-1 bg-transparent px-3 py-4 text-base lg:text-lg text-white placeholder:text-white/20 outline-none font-mono"
- />
- {searchQuery && (
-
- )}
-
-
-
- {/* Search Result */}
- {searchResult && (
-
- {searchResult.loading ? (
-
-
- Scanning...
-
- ) : (
-
- {/* Result Header */}
-
-
- {searchResult.is_available ? (
-
- ) : (
-
- )}
-
-
{searchResult.domain}
- {!searchResult.is_available && searchResult.registrar && (
-
Registrar: {searchResult.registrar}
- )}
-
-
-
- {searchResult.is_available ? 'Available' : 'Taken'}
-
-
-
- {/* Actions */}
-
-
-
- {searchResult.is_available && (
-
- Register
-
-
- )}
-
-
- )}
-
- )}
-
- {/* Hint */}
- {!searchResult && (
-
- Enter domain to check availability
-
- )}
-
-
-
-
-
- {/* ═══════════════════════════════════════════════════════════════════════ */}
- {/* WATCHLIST PREVIEW */}
- {/* ═══════════════════════════════════════════════════════════════════════ */}
- {domains && domains.length > 0 && (
-
-
-
-
- Your Watchlist
- ({domains.length})
-
-
- Manage
-
-
-
-
- {/* Domain Grid */}
-
- {domains.slice(0, 6).map((domain) => (
-
-
-
- {domain.is_available ? (
-
- ) : (
-
- )}
-
-
-
{domain.name}
-
- {domain.registrar || 'Unknown registrar'}
-
-
-
-
-
- {domain.is_available ? 'AVAILABLE' : 'TAKEN'}
-
- {domain.last_checked && (
-
- {new Date(domain.last_checked).toLocaleDateString()}
-
- )}
-
-
- ))}
-
-
- {domains.length > 6 && (
-
- View all {domains.length} domains
-
-
- )}
-
- )}
-
- {/* ═══════════════════════════════════════════════════════════════════════ */}
- {/* HOT AUCTIONS */}
- {/* ═══════════════════════════════════════════════════════════════════════ */}
-
-
-
-
-
Market Feed
-
({sortedAuctions.length})
-
-
- View all
-
-
-
-
- {loadingData ? (
-
-
-
- ) : sortedAuctions.length > 0 ? (
- <>
- {/* MOBILE Auction List */}
-
-
- {/* DESKTOP Table */}
-
- >
- ) : (
-
- )}
-
- {/* Desktop Quick Links */}
-
- {[
- { href: '/terminal/watchlist', icon: Eye, label: 'Watchlist', desc: 'Track domain availability' },
- { href: '/terminal/market', icon: Gavel, label: 'Market', desc: 'Browse all auctions' },
- { href: '/terminal/intel', icon: Globe, label: 'Intel', desc: 'TLD price analysis' },
- ].map((item) => (
-
-
-
-
-
-
{item.label}
-
{item.desc}
-
-
-
- ))}
-
-
-
- {/* ═══════════════════════════════════════════════════════════════════════ */}
- {/* MOBILE BOTTOM NAV */}
- {/* ═══════════════════════════════════════════════════════════════════════ */}
-
-
- {/* ═══════════════════════════════════════════════════════════════════════ */}
- {/* MOBILE DRAWER */}
- {/* ═══════════════════════════════════════════════════════════════════════ */}
- {menuOpen && (
-
- {/* Backdrop */}
-
setMenuOpen(false)}
- />
-
- {/* Drawer Panel */}
-
-
- {/* Drawer Header */}
-
-
-
-
-
POUNCE
-
Terminal v1.0
-
-
-
-
-
- {/* Navigation Sections */}
-
- {drawerNavSections.map((section) => (
-
-
-
- {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
- )}
-
- ))}
-
-
- ))}
-
- {/* Settings */}
-
- 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 Card */}
-
-
-
-
-
-
-
- {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
-
- )}
-
-
-
-
-
- )}
-
-
- {/* Toast */}
- {toast &&
}
-
- )
-}
diff --git a/frontend/src/app/terminal/settings/page.tsx b/frontend/src/app/terminal/settings/page.tsx
index b41293c..6578f35 100644
--- a/frontend/src/app/terminal/settings/page.tsx
+++ b/frontend/src/app/terminal/settings/page.tsx
@@ -270,7 +270,7 @@ export default function SettingsPage() {
// Mobile Nav - same as Intel page
const mobileNavItems = [
- { href: '/terminal/radar', label: 'Radar', icon: Target, active: false },
+ { 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 },
@@ -278,7 +278,7 @@ export default function SettingsPage() {
const drawerNavSections = [
{ title: 'Discover', items: [
- { href: '/terminal/radar', label: 'Radar', icon: Target },
+ { href: '/terminal/hunt', label: 'Radar', icon: Target },
{ href: '/terminal/market', label: 'Market', icon: Gavel },
{ href: '/terminal/intel', label: 'Intel', icon: TrendingUp },
]},
diff --git a/frontend/src/app/terminal/sniper/page.tsx b/frontend/src/app/terminal/sniper/page.tsx
index ab45d6d..bca8909 100644
--- a/frontend/src/app/terminal/sniper/page.tsx
+++ b/frontend/src/app/terminal/sniper/page.tsx
@@ -145,7 +145,7 @@ export default function SniperAlertsPage() {
// Mobile Nav
const mobileNavItems = [
- { href: '/terminal/radar', label: 'Radar', icon: Target, active: false },
+ { 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 },
@@ -156,7 +156,7 @@ export default function SniperAlertsPage() {
const drawerNavSections = [
{ title: 'Discover', items: [
- { href: '/terminal/radar', label: 'Radar', icon: Target },
+ { href: '/terminal/hunt', label: 'Radar', icon: Target },
{ href: '/terminal/market', label: 'Market', icon: Gavel },
{ href: '/terminal/intel', label: 'Intel', icon: TrendingUp },
]},
diff --git a/frontend/src/app/terminal/watchlist/page.tsx b/frontend/src/app/terminal/watchlist/page.tsx
index adc5a49..595012a 100755
--- a/frontend/src/app/terminal/watchlist/page.tsx
+++ b/frontend/src/app/terminal/watchlist/page.tsx
@@ -247,9 +247,9 @@ export default function WatchlistPage() {
// Mobile Nav
const mobileNavItems = [
- { href: '/terminal/radar', label: 'Radar', icon: Target, active: false },
- { href: '/terminal/market', label: 'Market', icon: Gavel, active: false },
+ { href: '/terminal/hunt', label: 'Hunt', icon: Target, active: false },
{ href: '/terminal/watchlist', label: 'Watch', icon: Eye, active: true },
+ { href: '/terminal/portfolio', label: 'Portfolio', icon: Briefcase, active: false },
{ href: '/terminal/intel', label: 'Intel', icon: TrendingUp, active: false },
]
@@ -260,8 +260,7 @@ export default function WatchlistPage() {
{
title: 'Discover',
items: [
- { href: '/terminal/radar', label: 'Radar', icon: Target },
- { href: '/terminal/market', label: 'Market', icon: Gavel },
+ { href: '/terminal/hunt', label: 'Hunt', icon: Target },
{ href: '/terminal/intel', label: 'Intel', icon: TrendingUp },
]
},
diff --git a/frontend/src/app/terminal/welcome/page.tsx b/frontend/src/app/terminal/welcome/page.tsx
index fb3456e..abc6a01 100644
--- a/frontend/src/app/terminal/welcome/page.tsx
+++ b/frontend/src/app/terminal/welcome/page.tsx
@@ -188,7 +188,7 @@ export default function WelcomePage() {
{/* Go to Dashboard */}
diff --git a/frontend/src/app/terminal/yield/page.tsx b/frontend/src/app/terminal/yield/page.tsx
index 45bcc14..3001ec4 100644
--- a/frontend/src/app/terminal/yield/page.tsx
+++ b/frontend/src/app/terminal/yield/page.tsx
@@ -360,7 +360,7 @@ export default function YieldPage() {
const stats = dashboard?.stats
const mobileNavItems = [
- { href: '/terminal/radar', label: 'Radar', icon: Target, active: false },
+ { 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 },
@@ -371,7 +371,7 @@ export default function YieldPage() {
const drawerNavSections = [
{ title: 'Discover', items: [
- { href: '/terminal/radar', label: 'Radar', icon: Target },
+ { href: '/terminal/hunt', label: 'Radar', icon: Target },
{ href: '/terminal/market', label: 'Market', icon: Gavel },
{ href: '/terminal/intel', label: 'Intel', icon: TrendingUp },
]},
diff --git a/frontend/src/components/AdminLayout.tsx b/frontend/src/components/AdminLayout.tsx
index cb6668d..1d83ec7 100644
--- a/frontend/src/components/AdminLayout.tsx
+++ b/frontend/src/components/AdminLayout.tsx
@@ -92,7 +92,7 @@ export function AdminLayout({
Access Denied
Admin privileges required