From 460074d01f8e67886dd214df2879c0a52ab79dc5 Mon Sep 17 00:00:00 2001 From: Yves Gugger Date: Thu, 18 Dec 2025 09:30:50 +0100 Subject: [PATCH] Hunt Module UI Optimization: unified award-winning design - Unified table headers, rows, and action alignments across all tabs - Enhanced SearchTab: improved result display and global TLD grid - Refined DropsTab: standardized table layouts and mobile views - Optimized AuctionsTab: improved column alignment and source badges - Modernized TrendSurfer: better viral topics grid and keyword builder - Polished BrandableForge: refined mode selectors and synthesis config - Standardized all borders, backgrounds, and spacing for Terminal v1.0 - All UI text in English with enhanced tracking and monospaced typography --- frontend/src/components/hunt/AuctionsTab.tsx | 500 +++++++++--------- .../src/components/hunt/BrandableForgeTab.tsx | 360 +++++-------- frontend/src/components/hunt/DropsTab.tsx | 296 ++++++----- frontend/src/components/hunt/SearchTab.tsx | 222 ++++---- .../src/components/hunt/TrendSurferTab.tsx | 336 ++++++------ 5 files changed, 847 insertions(+), 867 deletions(-) diff --git a/frontend/src/components/hunt/AuctionsTab.tsx b/frontend/src/components/hunt/AuctionsTab.tsx index d64a921..cb39653 100644 --- a/frontend/src/components/hunt/AuctionsTab.tsx +++ b/frontend/src/components/hunt/AuctionsTab.tsx @@ -1,9 +1,9 @@ 'use client' import { useEffect, useState, useMemo, useCallback } from 'react' -import { useStore } from '@/lib/store' import { api } from '@/lib/api' import { useAnalyzePanelStore } from '@/lib/analyze-store' +import { useStore } from '@/lib/store' import { ExternalLink, Loader2, @@ -14,7 +14,6 @@ import { ChevronUp, ChevronDown, RefreshCw, - Clock, Search, Eye, EyeOff, @@ -23,6 +22,7 @@ import { X, Filter, Shield, + Gavel, } from 'lucide-react' import clsx from 'clsx' @@ -109,6 +109,8 @@ interface AuctionsTabProps { export function AuctionsTab({ showToast }: AuctionsTabProps) { const openAnalyze = useAnalyzePanelStore((s) => s.open) + const addDomain = useStore((s) => s.addDomain) + const deleteDomain = useStore((s) => s.deleteDomain) const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) @@ -202,7 +204,7 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) { const result = await api.getDomains(1, 100) const domainToDelete = result.domains.find((d: any) => d.name === domain) if (domainToDelete) { - await api.deleteDomain(domainToDelete.id) + await deleteDomain(domainToDelete.id) setTrackedDomains((prev) => { const next = new Set(Array.from(prev)) next.delete(domain) @@ -211,7 +213,7 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) { showToast(`Removed: ${domain}`, 'success') } } else { - await api.addDomain(domain) + await addDomain(domain) setTrackedDomains((prev) => new Set([...Array.from(prev), domain])) showToast(`Tracking: ${domain}`, 'success') } @@ -220,7 +222,7 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) { } finally { setTrackingInProgress(null) } - }, [trackedDomains, trackingInProgress, showToast]) + }, [trackedDomains, trackingInProgress, showToast, addDomain, deleteDomain]) const filteredItems = useMemo(() => { let filtered = items @@ -285,57 +287,75 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) { return (
- {/* Search & Filters */} -
- {/* Search */} -
-
- - setSearchQuery(e.target.value)} - onFocus={() => setSearchFocused(true)} - onBlur={() => setSearchFocused(false)} - placeholder="Filter auctions..." - className="flex-1 bg-transparent px-3 py-3 text-sm text-white placeholder:text-white/20 outline-none font-mono" - /> - {searchQuery && ( - - )} - + {/* Header Stats */} +
+
+
+ +
+
+
+ {stats.total.toLocaleString()} +
+
Live auctions & fixed price
- - {/* Filter Toggle */} +
- {/* Filters Panel */} - {filtersOpen && ( -
+ {/* Search */} +
+
+ + setSearchQuery(e.target.value)} + onFocus={() => setSearchFocused(true)} + onBlur={() => setSearchFocused(false)} + placeholder="Search auctions..." + className="flex-1 bg-transparent px-3 py-3 text-sm text-white placeholder:text-white/20 outline-none font-mono" + /> + {searchQuery && ( + + )} +
+
+ + {/* Filter Toggle */} + + + {/* Filters Panel */} + {filtersOpen && ( +
+
{/* Source */}
-
Source
+
Source
{[ { value: 'all', label: 'All' }, @@ -346,8 +366,8 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) { key={item.value} onClick={() => setSourceFilter(item.value as SourceFilter)} className={clsx( - "flex-1 py-2 text-[10px] font-bold uppercase tracking-wider border transition-colors flex items-center justify-center gap-1", - sourceFilter === item.value ? "border-accent bg-accent/10 text-accent" : "border-white/[0.08] text-white/40" + "flex-1 py-2 text-[10px] font-bold uppercase tracking-wider border transition-colors flex items-center justify-center gap-1.5", + sourceFilter === item.value ? "border-accent bg-accent/10 text-accent font-bold" : "border-white/[0.08] text-white/40" )} > {item.icon && } @@ -359,15 +379,15 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) { {/* TLD */}
-
TLD
+
Quick TLD
{['all', 'com', 'ai', 'io', 'net'].map((tld) => (
+
+
{/* Price */}
-
Price
+
Price Range
{[ { value: 'all', label: 'All' }, @@ -390,8 +412,8 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) { key={item.value} onClick={() => setPriceRange(item.value as PriceRange)} className={clsx( - "flex-1 py-1.5 text-[10px] font-mono border transition-colors", - priceRange === item.value ? "border-amber-400 bg-amber-400/10 text-amber-400" : "border-white/[0.08] text-white/40" + "flex-1 py-2 text-[10px] font-mono border transition-colors", + priceRange === item.value ? "border-amber-400 bg-amber-400/10 text-amber-400 font-bold" : "border-white/[0.08] text-white/40" )} > {item.label} @@ -401,234 +423,230 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
{/* Spam Filter */} - +
+
Visibility
+ +
- )} +
+ )} + + {/* Results Header */} +
+
+
+ {filteredItems.length} active listings found +
+
+ {stats.highScore} premium assets + {totalPages > 1 && Page {page} / {totalPages}} +
- {/* Stats Bar */} -
- {filteredItems.length} domains shown - {filteredItems.filter((i) => i.pounce_score >= 80).length} high score -
- - {/* Results */} + {/* Results Table */} {filteredItems.length === 0 ? ( -
- -

No domains found

-

Try adjusting filters

+
+ +

No assets found

+

+ Try adjusting your search criteria or filters +

) : ( <> -
+
{/* Desktop Table Header */} -
- - - - -
Actions
+
Actions
- {filteredItems.map((item) => { - const timeLeftSec = getSecondsUntilEnd(item.end_time) - const isUrgent = timeLeftSec > 0 && timeLeftSec < 3600 - const isPounce = item.is_pounce - const displayTime = item.status === 'auction' ? calcTimeRemaining(item.end_time) : null - const isTracked = trackedDomains.has(item.domain) - const isTracking = trackingInProgress === item.domain +
+ {filteredItems.map((item) => { + const timeLeftSec = getSecondsUntilEnd(item.end_time) + const isUrgent = timeLeftSec > 0 && timeLeftSec < 3600 + const isPounce = item.is_pounce + const displayTime = item.status === 'auction' ? calcTimeRemaining(item.end_time) : null + const isTracked = trackedDomains.has(item.domain) + const isTracking = trackingInProgress === item.domain - return ( -
- {/* Mobile Row */} -
-
-
-
- -
- {item.source} +
+ {item.source} | - {isPounce ? 'Instant' : displayTime || 'N/A'} + {isPounce ? 'INSTANT' : displayTime || 'N/A'} +
+
+ +
+
{formatPrice(item.price)}
+
= 80 ? "text-accent bg-accent/5 border-accent/20" : item.pounce_score >= 50 ? "text-amber-400 bg-amber-400/5 border-amber-400/20" : "text-white/30 bg-white/5 border-white/5" + )}> + SCORE: {item.pounce_score}
-
-
{formatPrice(item.price)}
-
= 80 ? "text-accent bg-accent/10" : item.pounce_score >= 50 ? "text-amber-400 bg-amber-400/10" : "text-white/30 bg-white/5")}> - Score {item.pounce_score} -
+
+ + + + + + {isPounce ? 'Buy' : 'Bid'} + {!isPounce && } +
-
- - - - - - {isPounce ? 'Buy' : 'Bid'} - {!isPounce && } - -
-
- - {/* Desktop Row */} -
-
-
- -
- {item.source} - {isPounce && item.verified && ( - <> - | - - - Verified - - - )} - {item.num_bids ? ( - <> - | - {item.num_bids} bids - - ) : null} +
+ {item.source} + {isPounce && item.verified && }
-
-
- = 80 ? "text-accent bg-accent/10" : item.pounce_score >= 50 ? "text-amber-400 bg-amber-400/10" : "text-white/40 bg-white/5")}> - {item.pounce_score} - -
- -
-
{formatPrice(item.price)}
-
{item.price_type === 'bid' ? 'Bid' : 'Buy Now'}
-
- -
- {isPounce ? ( - - - Instant +
+ = 80 ? "text-accent bg-accent/5 border-accent/20" : item.pounce_score >= 50 ? "text-amber-400 bg-amber-400/5 border-amber-400/20" : "text-white/30 bg-white/5 border-white/5" + )}> + {item.pounce_score} - ) : ( - {displayTime || 'N/A'} - )} -
+
-
- +
- +
+ - - {isPounce ? 'Buy' : 'Bid'} - {!isPounce && } - + + + + {isPounce ? 'Buy' : 'Bid'} + +
-
- ) - })} + ) + })} +
{/* Pagination */} {totalPages > 1 && ( -
-
- Page {page}/{totalPages} -
-
- - - - {page}/{totalPages} +
+ +
+ + Page {page} / {totalPages} - -
+
)} diff --git a/frontend/src/components/hunt/BrandableForgeTab.tsx b/frontend/src/components/hunt/BrandableForgeTab.tsx index 7f48da7..6f79969 100644 --- a/frontend/src/components/hunt/BrandableForgeTab.tsx +++ b/frontend/src/components/hunt/BrandableForgeTab.tsx @@ -16,6 +16,8 @@ import { RefreshCw, Brain, Dices, + ChevronRight, + X, } from 'lucide-react' import Link from 'next/link' import { api } from '@/lib/api' @@ -71,7 +73,7 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type try { const res = await api.huntBrandables({ pattern, tlds, limit: 24, max_checks: 300 }) setResults(res.items.map(i => ({ domain: i.domain }))) - showToast(`Found ${res.items.length} domains!`, 'success') + showToast(`Generated ${res.items.length} assets!`, 'success') } catch (e) { showToast('Generation failed', 'error') } finally { @@ -90,7 +92,7 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type const checkRes = await api.huntKeywords({ keywords: res.names, tlds }) const available = checkRes.items.filter(i => i.status === 'available') setResults(available.map(i => ({ domain: i.domain }))) - showToast(`Found ${available.length} available from ${res.names.length} AI names!`, 'success') + showToast(`Found ${available.length} free domains via AI!`, 'success') } } catch (e) { showToast('AI generation failed', 'error') @@ -118,7 +120,7 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type await addDomain(domain) showToast(`Added: ${domain}`, 'success') } catch (e) { - showToast('Failed', 'error') + showToast('Failed to track', 'error') } finally { setTracking(null) } @@ -128,52 +130,42 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type return (
- {/* ═══════════════════════════════════════════════════════════════════════ */} - {/* HEADER */} - {/* ═══════════════════════════════════════════════════════════════════════ */} -
-

- - Brandable Forge -

-

- Generate unique, memorable domain names -

+ {/* Header */} +
+
+ +
+
+

Brandable Forge

+

Synthesize unique, high-value naming assets

+
- {/* ═══════════════════════════════════════════════════════════════════════ */} - {/* MODE SELECTOR - BIG CLEAR BUTTONS */} - {/* ═══════════════════════════════════════════════════════════════════════ */} -
+ {/* Mode Selector */} +
{/* Pattern Mode */} {/* AI Mode */} @@ -181,76 +173,83 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type onClick={() => hasAI && setMode('ai')} disabled={!hasAI} className={clsx( - "p-4 sm:p-5 border-2 transition-all text-left relative", - !hasAI && "opacity-60", + "relative p-5 border transition-all duration-300 text-left group overflow-hidden", + !hasAI && "opacity-50 grayscale", mode === 'ai' - ? "border-purple-500 bg-purple-500/10" - : "border-white/10 bg-white/[0.02] hover:border-white/20" + ? "border-purple-500 bg-purple-500/5 shadow-[0_0_30px_-10px_rgba(168,85,247,0.2)]" + : "border-white/[0.08] bg-white/[0.01] hover:border-white/20 hover:bg-white/[0.03]" )} > - {!hasAI && ( -
- -
- )} -
+ {!hasAI &&
} +
- +
-

- AI Concept -

-

- {hasAI ? 'Describe your brand' : 'Trader+ only'} -

+

Vision Core AI

+

Concept to Name

-

- {hasAI ? 'AI generates names based on your concept' : 'Upgrade to unlock AI naming'} -

+ {mode === 'ai' &&
}
- {/* ═══════════════════════════════════════════════════════════════════════ */} - {/* PATTERN MODE */} - {/* ═══════════════════════════════════════════════════════════════════════ */} - {mode === 'pattern' && ( -
- {/* Pattern Selection */} -
-

Choose Pattern

-
- {PATTERNS.map(p => ( - - ))} -
+ {/* Config Panel */} +
+
+
+ Synthesis Configuration
+
+
+ {mode.toUpperCase()} MODE +
+
- {/* TLDs */} -
-

Select TLDs

-
+
+ {mode === 'pattern' ? ( +
+ Select Linguistic Pattern +
+ {PATTERNS.map(p => ( + + ))} +
+
+ ) : ( +
+ Describe Your Identity Concept +