From 09fb4e2931e09abbdce397336c1c263890a4b46d Mon Sep 17 00:00:00 2001 From: Yves Gugger Date: Sat, 13 Dec 2025 14:28:45 +0100 Subject: [PATCH] Watchlist: Buy button + Intel: Unified techy mobile design --- frontend/src/app/terminal/intel/page.tsx | 874 +++++++++++++------ frontend/src/app/terminal/watchlist/page.tsx | 61 +- 2 files changed, 660 insertions(+), 275 deletions(-) diff --git a/frontend/src/app/terminal/intel/page.tsx b/frontend/src/app/terminal/intel/page.tsx index 462326e..9e8ceb3 100755 --- a/frontend/src/app/terminal/intel/page.tsx +++ b/frontend/src/app/terminal/intel/page.tsx @@ -3,7 +3,8 @@ import { useEffect, useState, useMemo, useCallback } from 'react' import { useStore } from '@/lib/store' import { api } from '@/lib/api' -import { CommandCenterLayout } from '@/components/CommandCenterLayout' +import { Sidebar } from '@/components/Sidebar' +import { Toast, useToast } from '@/components/Toast' import { Loader2, TrendingUp, @@ -20,10 +21,24 @@ import { Sparkles, BarChart3, Zap, - Minus + Minus, + Eye, + Gavel, + Target, + X, + Menu, + Settings, + Shield, + LogOut, + Crown, + Coins, + Tag, + ChevronRight, + Filter } from 'lucide-react' import clsx from 'clsx' import Link from 'next/link' +import Image from 'next/image' // ============================================================================ // TYPES @@ -60,7 +75,7 @@ function getTierLevel(tier: UserTier): number { const formatPrice = (p: number) => { if (typeof p !== 'number' || isNaN(p)) return '$0' - return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(p) + return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(p) } // ============================================================================ @@ -68,7 +83,8 @@ const formatPrice = (p: number) => { // ============================================================================ export default function IntelPage() { - const { subscription } = useStore() + const { subscription, user, logout, checkAuth } = useStore() + const { toast, showToast, hideToast } = useToast() const userTier: UserTier = (subscription?.tier as UserTier) || 'scout' const tierLevel = getTierLevel(userTier) @@ -83,16 +99,25 @@ export default function IntelPage() { const [total, setTotal] = useState(0) const [searchQuery, setSearchQuery] = useState('') + const [searchFocused, setSearchFocused] = useState(false) const [filterType, setFilterType] = useState<'all' | 'tech' | 'geo' | 'budget'>('all') + const [filtersOpen, setFiltersOpen] = useState(false) const [sortField, setSortField] = useState('popularity') const [sortDirection, setSortDirection] = useState('asc') + + // Mobile Menu + const [menuOpen, setMenuOpen] = useState(false) + + // Check auth on mount + useEffect(() => { + checkAuth() + }, [checkAuth]) const loadData = useCallback(async () => { setLoading(true) setError(null) try { - // Fetch multiple pages to get more TLDs (API limit is 100 per request) const allTlds: TLDData[] = [] let totalRecords = 0 @@ -123,7 +148,6 @@ export default function IntelPage() { allTlds.push(...mapped) - // Stop if we got less than requested (no more data) if (response.tlds.length < 100) break } @@ -204,285 +228,623 @@ export default function IntelPage() { return { lowest, traps, avgRenewal } }, [tldData]) + // Active filters count + const activeFiltersCount = filterType !== 'all' ? 1 : 0 + + // 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: false }, + { href: '/terminal/intel', label: 'Intel', icon: TrendingUp, active: true }, + ] + + 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 ( - - {/* ═══════════════════════════════════════════════════════════════════════ */} - {/* HEADER */} - {/* ═══════════════════════════════════════════════════════════════════════ */} -
-
-
-
- - Pricing Analytics +
+ {/* Desktop Sidebar */} +
+ +
+ + {/* Main Content */} +
+ + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* MOBILE HEADER */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+
+ {/* Top Row */} +
+
+
+ TLD Intel +
+
+ {total} extensions + {stats.traps} risky +
-

- TLD Intel - {total} -

-
- -
-
-
{formatPrice(stats.lowest)}
-
Lowest Entry
-
-
-
{stats.traps}
-
High Risk
-
- {canSeeRenewal && ( -
-
{formatPrice(stats.avgRenewal)}
-
Avg Renewal
+ {/* Stats Grid */} +
+
+
{total}
+
TLDs
+
+
+
{formatPrice(stats.lowest)}
+
Lowest
+
+
+
{stats.traps}
+
High Risk
- )} -
-
-
- - {/* ═══════════════════════════════════════════════════════════════════════ */} - {/* FILTERS */} - {/* ═══════════════════════════════════════════════════════════════════════ */} -
-
- - - - - -
- -
- - setSearchQuery(e.target.value)} - placeholder="Search TLDs..." - className="bg-[#050505] border border-white/10 pl-9 pr-4 py-2 text-sm text-white placeholder:text-white/25 outline-none focus:border-accent/40 w-48 lg:w-64" - /> -
- - -
-
- - {/* ═══════════════════════════════════════════════════════════════════════ */} - {/* TABLE */} - {/* ═══════════════════════════════════════════════════════════════════════ */} -
- {loading ? ( -
- -
- ) : error ? ( -
-
-
-

{error}

-
- ) : filteredData.length === 0 ? ( -
-
- + + + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* MOBILE SEARCH & FILTERS */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+ {/* Search */} +
+
+ + setSearchQuery(e.target.value)} + onFocus={() => setSearchFocused(true)} + onBlur={() => setSearchFocused(false)} + placeholder="Search TLDs..." + className="flex-1 bg-transparent px-3 py-3 text-sm text-white placeholder:text-white/20 outline-none font-mono" + /> + {searchQuery && ( + + )}
-

No TLDs found

- ) : ( -
- - - - - - - - - - - - - + + {/* Filter Toggle */} + + + {/* Filters Panel */} + {filtersOpen && ( +
+
Category
+
+ {[ + { value: 'all', label: 'All TLDs', icon: null }, + { value: 'tech', label: 'Tech', icon: Zap }, + { value: 'geo', label: 'National', icon: Globe }, + { value: 'budget', label: '<$10', icon: DollarSign }, + ].map((item) => ( + + ))} +
+
+ )} + + + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* DESKTOP HEADER */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+
+
+
+
+ Pricing Analytics +
+ +

+ TLD Intel + {total} +

+
+ +
+
+
{formatPrice(stats.lowest)}
+
Lowest Entry
+
+
+
{stats.traps}
+
High Risk
+
+ {canSeeRenewal && ( +
+
{formatPrice(stats.avgRenewal)}
+
Avg Renewal
+
+ )} +
+
+
+ + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* DESKTOP FILTERS */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+
+ + + + + +
+ +
+ + setSearchQuery(e.target.value)} + placeholder="Search..." + className="bg-white/[0.02] border border-white/10 pl-9 pr-4 py-2 text-sm text-white placeholder:text-white/25 outline-none focus:border-accent/40 w-48 font-mono" + /> +
+ + +
+
+ + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* CONTENT */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+ {loading ? ( +
+ +
+ ) : error ? ( +
+ +

{error}

+ +
+ ) : filteredData.length === 0 ? ( +
+ +

No TLDs found

+
+ ) : ( + <> + {/* TLD List */} +
+ {/* Desktop Header */} +
+ + + + + +
Risk
+
+
+ {filteredData.map((tld) => { const isTrap = (tld.min_renewal_price || 0) > (tld.min_price || 1) * 1.5 const trend = tld.price_change_1y || 0 const trend3y = tld.price_change_3y || 0 return ( -
- - - - - - - - + + ) })} - -
- - - - - - - - - - Risk
- +
+ {/* Mobile Row */} + +
+
+
+ +
+ .{tld.tld} +
+
+
{formatPrice(tld.min_price)}
+
register
+
+
+
+
+ 5 ? "text-orange-400 bg-orange-400/10" : + trend < -5 ? "text-accent bg-accent/10" : + "text-white/40 bg-white/5" + )}> + {trend > 0 ? : trend < 0 ? : } + {Math.abs(trend)}% + +
+
+
+
+ +
+ + + {/* Desktop Row */} +
+ .{tld.tld} -
- {formatPrice(tld.min_price)} - - {canSeeRenewal ? ( -
- - {formatPrice(tld.min_renewal_price)} +
{formatPrice(tld.min_price)}
+
+ {canSeeRenewal ? ( +
+ + {formatPrice(tld.min_renewal_price)} + + {isTrap && } +
+ ) : ( + + - {isTrap && } -
- ) : ( - - - Trader+ - - )} -
- 5 ? "text-orange-400 bg-orange-400/10" : - trend < -5 ? "text-accent bg-accent/10" : - "text-white/40 bg-white/5" - )}> - {trend > 0 ? : trend < 0 ? : } - {Math.abs(trend)}% - - - {canSee3yTrend ? ( + )} + +
10 ? "text-orange-400 bg-orange-400/10" : - trend3y < -10 ? "text-accent bg-accent/10" : + "inline-flex items-center gap-0.5 text-xs font-mono px-1.5 py-0.5", + trend > 5 ? "text-orange-400 bg-orange-400/10" : + trend < -5 ? "text-accent bg-accent/10" : "text-white/40 bg-white/5" )}> - {trend3y > 0 ? : trend3y < 0 ? : } - {Math.abs(trend3y)}% + {trend > 0 ? : trend < 0 ? : } + {Math.abs(trend)}% - ) : ( - - )} -
-
-
-
+
+ {canSee3yTrend ? ( + 10 ? "text-orange-400 bg-orange-400/10" : + trend3y < -10 ? "text-accent bg-accent/10" : + "text-white/40 bg-white/5" + )}> + {trend3y > 0 ? : trend3y < 0 ? : } + {Math.abs(trend3y)}% + + ) : ( + + )} +
+
+
+
+
+
- + -
+
+ + {/* Upgrade CTA */} + {userTier === 'scout' && ( +
+ +

Unlock Full Intel

+

+ See renewal prices, traps & 3-year trends +

+ + + Upgrade + +
+ )} + + )} +
+ + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* 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 + + )} + + +
+
)} -
- - {/* ═══════════════════════════════════════════════════════════════════════ */} - {/* UPGRADE CTA */} - {/* ═══════════════════════════════════════════════════════════════════════ */} - {userTier === 'scout' && ( -
-
- -

Unlock Full Intel

-

- See renewal prices, identify traps, and access 3-year price history with Trader or Tycoon. -

- - Upgrade Now - -
-
- )} -
+ + + {toast && } + ) } diff --git a/frontend/src/app/terminal/watchlist/page.tsx b/frontend/src/app/terminal/watchlist/page.tsx index fc22dcd..1ef96ad 100755 --- a/frontend/src/app/terminal/watchlist/page.tsx +++ b/frontend/src/app/terminal/watchlist/page.tsx @@ -455,25 +455,37 @@ export default function WatchlistPage() { {/* Actions */}
- + {domain.is_available ? ( + + + Register + + ) : ( + + )}