From 155280a84ef5af9763c539fd0b6ccff0bef88aa3 Mon Sep 17 00:00:00 2001 From: Yves Gugger Date: Sat, 13 Dec 2025 13:12:50 +0100 Subject: [PATCH] Radar: Fix hydration, award-winning mobile PWA design --- frontend/src/app/terminal/radar/page.tsx | 883 ++++++++--------------- 1 file changed, 309 insertions(+), 574 deletions(-) diff --git a/frontend/src/app/terminal/radar/page.tsx b/frontend/src/app/terminal/radar/page.tsx index 431fef1..1dffefe 100644 --- a/frontend/src/app/terminal/radar/page.tsx +++ b/frontend/src/app/terminal/radar/page.tsx @@ -1,16 +1,13 @@ 'use client' -import { useEffect, useState, useMemo, useCallback, useRef } from 'react' +import { useEffect, useState, useCallback, useRef } 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 { Eye, Gavel, - ExternalLink, - Plus, - Activity, ArrowRight, CheckCircle2, XCircle, @@ -21,13 +18,14 @@ import { Target, Search, X, - Home, TrendingUp, Settings, - Bell, - ChevronRight, Clock, - Wifi + Wifi, + ChevronRight, + Sparkles, + Radio, + Activity } from 'lucide-react' import clsx from 'clsx' import Link from 'next/link' @@ -56,288 +54,12 @@ interface SearchResult { auctionData?: HotAuction } -// ============================================================================ -// MOBILE BOTTOM NAV -// ============================================================================ - -function MobileBottomNav({ currentPath }: { currentPath: string }) { - const navItems = [ - { href: '/terminal/radar', label: 'Radar', icon: Target }, - { href: '/terminal/market', label: 'Market', icon: Gavel }, - { href: '/terminal/watchlist', label: 'Watch', icon: Eye }, - { href: '/terminal/intel', label: 'Intel', icon: TrendingUp }, - { href: '/terminal/settings', label: 'Settings', icon: Settings }, - ] - - return ( - - ) -} - -// ============================================================================ -// MOBILE HEADER -// ============================================================================ - -function MobileHeader({ stats }: { stats: { tracking: number; available: number } }) { - return ( -
-
-
- Pounce -
-

Radar

-
-
- Live -
-
-
- -
-
-
{stats.tracking}
-
tracking
-
-
-
{stats.available}
-
available
-
-
-
-
- ) -} - -// ============================================================================ -// MOBILE SEARCH CARD -// ============================================================================ - -function MobileSearchCard({ - searchQuery, - setSearchQuery, - searchResult, - searchFocused, - setSearchFocused, - handleAddToWatchlist, - addingToWatchlist, -}: { - searchQuery: string - setSearchQuery: (q: string) => void - searchResult: SearchResult | null - searchFocused: boolean - setSearchFocused: (f: boolean) => void - handleAddToWatchlist: () => void - addingToWatchlist: boolean -}) { - return ( -
- {/* Search Input - Full width, app-like */} -
-
- - setSearchQuery(e.target.value)} - onFocus={() => setSearchFocused(true)} - onBlur={() => setSearchFocused(false)} - placeholder="Search domains..." - className="flex-1 bg-transparent px-3 py-4 text-base text-white placeholder:text-white/25 outline-none" - /> - {searchQuery && ( - - )} -
-
- - {/* Results - App-like card */} - {searchResult && ( -
- {searchResult.loading ? ( -
- - Checking... -
- ) : ( -
- {/* Status Banner */} -
-
- {searchResult.is_available ? ( - - ) : ( - - )} -
-
{searchResult.domain}
- {!searchResult.is_available && searchResult.registrar && ( -
{searchResult.registrar}
- )} -
-
- - {searchResult.is_available ? 'Available' : 'Taken'} - -
- - {/* Actions - Large touch targets */} -
- - - {searchResult.is_available && ( - - Register Now - - - )} -
-
- )} -
- )} -
- ) -} - -// ============================================================================ -// MOBILE AUCTION CARD -// ============================================================================ - -function MobileAuctionCard({ auction }: { auction: HotAuction }) { - return ( - -
- -
-
-
{auction.domain}
-
- {auction.platform} - · - - - {auction.time_remaining} - -
-
-
-
${auction.current_bid.toLocaleString()}
-
current bid
-
- -
- ) -} - -// ============================================================================ -// MOBILE QUICK ACTION -// ============================================================================ - -function MobileQuickAction({ href, icon: Icon, label, badge }: { href: string; icon: any; label: string; badge?: number }) { - return ( - - - {label} - {badge !== undefined && badge > 0 && ( - - {badge} - - )} - - ) -} - -// ============================================================================ -// LIVE TICKER (Desktop) -// ============================================================================ - -function LiveTicker({ items }: { items: { label: string; value: string; highlight?: boolean }[] }) { - return ( -
-
-
- -
- {[...items, ...items, ...items].map((item, i) => ( -
- {item.label} - {item.value} -
- ))} -
-
- ) -} - // ============================================================================ // MAIN PAGE // ============================================================================ export default function RadarPage() { - const { isAuthenticated, user, domains, addDomain } = useStore() + const { isAuthenticated, domains, addDomain } = useStore() const { toast, showToast, hideToast } = useToast() const [hotAuctions, setHotAuctions] = useState([]) @@ -348,12 +70,13 @@ export default function RadarPage() { const [searchResult, setSearchResult] = useState(null) const [addingToWatchlist, setAddingToWatchlist] = useState(false) const [searchFocused, setSearchFocused] = useState(false) + const searchInputRef = useRef(null) // Load Data const loadDashboardData = useCallback(async () => { try { const summary = await api.getDashboardSummary() - setHotAuctions((summary.market.ending_soon_preview || []).slice(0, 5)) + setHotAuctions((summary.market.ending_soon_preview || []).slice(0, 6)) setMarketStats({ totalAuctions: summary.market.total_auctions || 0, endingSoon: summary.market.ending_soon || 0, @@ -367,6 +90,7 @@ export default function RadarPage() { useEffect(() => { if (isAuthenticated) loadDashboardData() + else setLoadingData(false) }, [isAuthenticated, loadDashboardData]) // Search @@ -401,11 +125,11 @@ export default function RadarPage() { setAddingToWatchlist(true) try { await addDomain(searchQuery.trim()) - showToast(`Target acquired: ${searchQuery.trim()}`, 'success') + showToast(`Added: ${searchQuery.trim()}`, 'success') setSearchQuery('') setSearchResult(null) } catch (err: any) { - showToast(err.message || 'Mission failed', 'error') + showToast(err.message || 'Failed', 'error') } finally { setAddingToWatchlist(false) } @@ -422,144 +146,87 @@ export default function RadarPage() { // Computed const availableDomains = domains?.filter(d => d.is_available) || [] const totalDomains = domains?.length || 0 - - const tickerItems = [ - { label: 'Status', value: 'ONLINE', highlight: true }, - { label: 'Tracking', value: totalDomains.toString() }, - { label: 'Available', value: availableDomains.length.toString(), highlight: availableDomains.length > 0 }, - { label: 'Auctions', value: marketStats.totalAuctions.toString() }, + + // Nav Items for Mobile + 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 }, + { href: '/terminal/settings', label: 'More', icon: Settings, active: false }, ] - // ============================================================================ - // MOBILE VIEW - // ============================================================================ - - const MobileView = () => ( -
- {/* Mobile Header */} - - - {/* Search Section */} - - - {/* Quick Actions Grid */} -
-
- - - -
+ return ( +
+ {/* Desktop Sidebar */} +
+
- {/* Live Status Bar */} -
-
- - Live monitoring active -
-
- {marketStats.totalAuctions} auctions - {marketStats.endingSoon} ending soon -
-
- - {/* Hot Auctions Section */} -
-
-

- - Hot Auctions -

- - View all - -
+ {/* Main Content */} +
- {loadingData ? ( -
- + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* MOBILE HEADER - Premium App Style */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+
+
+ {/* Brand */} +
+
+
+ +
+
+
+
+

Radar

+

Domain Intelligence

+
+
+ + {/* Stats Pills */} +
+
+
{totalDomains}
+
tracking
+
+
+
{availableDomains.length}
+
available
+
+
+
- ) : hotAuctions.length > 0 ? ( -
- {hotAuctions.map((auction, i) => ( - - ))} -
- ) : ( -
- No active auctions at the moment -
- )} -
- - {/* Bottom Navigation */} - - - {toast && } -
- ) + - // ============================================================================ - // DESKTOP VIEW (Original with minor tweaks) - // ============================================================================ - - const DesktopView = () => ( - - {toast && } - - {/* HERO */} -
-
- - {/* Left: Typography */} -
-
-
- Intelligence Hub + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* SEARCH SECTION */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+ {/* Desktop Hero Text */} +
+
+
+ Intelligence Hub
- -

- Domain Radar - Find your next acquisition. +

+ Domain Radar

- -

- Real-time monitoring across {marketStats.totalAuctions.toLocaleString()}+ auctions. - Your targets. Your intel. +

+ Real-time monitoring across {marketStats.totalAuctions.toLocaleString()}+ auctions

- - {/* Stats Row */} -
-
-
{totalDomains}
-
Tracking
-
-
-
{availableDomains.length}
-
Available
-
-
-
{marketStats.endingSoon}
-
Ending Soon
-
-
- {/* Right: Search Terminal */} + {/* Search Card */}
-
+ {/* Desktop Glow */} +
-
- {/* Header Bar */} -
+
+ {/* Terminal Header - Desktop only */} +
Domain Search @@ -571,88 +238,112 @@ export default function RadarPage() {
-
- {/* Input */} + {/* Search Input */} +
- setSearchQuery(e.target.value)} - onFocus={() => setSearchFocused(true)} - onBlur={() => setSearchFocused(false)} - placeholder="example.com" - className="w-full bg-transparent px-4 py-4 text-lg text-white placeholder:text-white/20 outline-none" - /> - {searchQuery && ( - - )} +
+ + setSearchQuery(e.target.value)} + onFocus={() => setSearchFocused(true)} + onBlur={() => setSearchFocused(false)} + placeholder="Search any domain..." + className="flex-1 bg-transparent px-4 py-4 lg:py-5 text-base lg:text-lg text-white placeholder:text-white/25 outline-none" + /> + {searchQuery && ( + + )} +
- {/* Results */} + {/* Search Result */} {searchResult && ( -
+
{searchResult.loading ? ( -
- - Checking availability... +
+ + Checking availability...
) : (
-
+ {/* Result Header */} +
{searchResult.is_available ? ( - +
+ +
) : ( - +
+ +
)} - {searchResult.domain} +
+
{searchResult.domain}
+ {!searchResult.is_available && searchResult.registrar && ( +
{searchResult.registrar}
+ )} +
{searchResult.is_available ? 'Available' : 'Taken'}
- {!searchResult.is_available && searchResult.registrar && ( -

Registered with {searchResult.registrar}

- )} - -
+ {/* Actions */} +
+ {searchResult.is_available && ( - Register Now + Register Now + )}
@@ -661,137 +352,181 @@ export default function RadarPage() {
)} + {/* Hint */} {!searchResult && ( -

Enter a domain name to check availability

+

+ Enter a domain to check availability instantly +

)}
-
-
+
- {/* Ticker */} - - - {/* CONTENT GRID */} -
-
- - {/* Hot Auctions - 2 cols */} -
-
-
- - Live Auctions -
- - View all → + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* QUICK ACTIONS - Mobile */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+
+ {[ + { href: '/terminal/watchlist', icon: Eye, label: 'Watchlist', badge: availableDomains.length, accent: true }, + { href: '/terminal/market', icon: Gavel, label: 'Market' }, + { href: '/terminal/intel', icon: TrendingUp, label: 'Intel' }, + ].map((item) => ( + + + {item.label} + {item.badge !== undefined && item.badge > 0 && ( + + {item.badge} + + )} + ))} +
+
+ + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* STATUS BAR */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+
+
+ + Live monitoring
- - {loadingData ? ( -
- -
- ) : hotAuctions.length > 0 ? ( - - ) : ( -
No active auctions
- )} +
+ {marketStats.totalAuctions.toLocaleString()} auctions + {marketStats.endingSoon} ending soon +
+
+
+ + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* HOT AUCTIONS */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+
+

+ + Hot Auctions +

+ + View all + +
- {/* Quick Links */} -
-
- - Quick Access + {loadingData ? ( +
+
- -
- {[ - { label: 'Watchlist', href: '/terminal/watchlist', icon: Eye }, - { label: 'Market', href: '/terminal/market', icon: Gavel }, - { label: 'Intel', href: '/terminal/intel', icon: Globe }, - ].map((item) => ( - 0 ? ( + - -
-
-
- System online -
+ ) : ( +
+ +

No active auctions

+ )} + + {/* 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}
+
+ + + ))}
- -
-
- - - - ) +
- // ============================================================================ - // RENDER - // ============================================================================ - - return ( - <> - {/* Mobile View */} -
- -
+ {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* MOBILE BOTTOM NAV - Premium Tab Bar */} + {/* ═══════════════════════════════════════════════════════════════════════ */} + +
- {/* Desktop View */} -
- -
- - {/* Global Styles for Safe Areas */} - - + {/* Toast */} + {toast && } +
) }