Hunt Module UI Optimization: unified award-winning design
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
- 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
This commit is contained in:
@ -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<MarketItem[]>([])
|
||||
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,8 +287,28 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Search & Filters */}
|
||||
<div className="space-y-3">
|
||||
{/* Header Stats */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-accent/10 border border-accent/20 flex items-center justify-center">
|
||||
<Gavel className="w-5 h-5 text-accent" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xl font-bold text-white font-mono">
|
||||
{stats.total.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Live auctions & fixed price</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleRefresh}
|
||||
disabled={refreshing}
|
||||
className="p-2 border border-white/10 text-white/30 hover:text-white hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className={clsx(
|
||||
"relative border transition-all duration-200",
|
||||
@ -300,7 +322,7 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onFocus={() => setSearchFocused(true)}
|
||||
onBlur={() => setSearchFocused(false)}
|
||||
placeholder="Filter auctions..."
|
||||
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 && (
|
||||
@ -308,9 +330,6 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
<button onClick={handleRefresh} disabled={refreshing} className="p-3 text-white/30 hover:text-white transition-colors">
|
||||
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -318,24 +337,25 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
<button
|
||||
onClick={() => setFiltersOpen(!filtersOpen)}
|
||||
className={clsx(
|
||||
"flex items-center justify-between w-full py-2 px-3 border transition-colors",
|
||||
"flex items-center justify-between w-full py-2.5 px-4 border transition-colors",
|
||||
filtersOpen ? "border-accent/30 bg-accent/[0.05]" : "border-white/[0.08] bg-white/[0.02]"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Filter className="w-4 h-4 text-white/40" />
|
||||
<span className="text-xs font-mono text-white/60">Filters</span>
|
||||
{activeFiltersCount > 0 && <span className="px-1.5 py-0.5 text-[9px] font-bold bg-accent text-black">{activeFiltersCount}</span>}
|
||||
<span className="text-xs font-mono text-white/60 uppercase tracking-widest">Market Filters</span>
|
||||
{activeFiltersCount > 0 && <span className="px-1.5 py-0.5 text-[9px] font-bold bg-accent text-black ml-1">{activeFiltersCount}</span>}
|
||||
</div>
|
||||
<ChevronRight className={clsx("w-4 h-4 text-white/30 transition-transform", filtersOpen && "rotate-90")} />
|
||||
</button>
|
||||
|
||||
{/* Filters Panel */}
|
||||
{filtersOpen && (
|
||||
<div className="p-3 border border-white/[0.08] bg-white/[0.02] space-y-3 animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
<div className="p-4 border border-white/[0.08] bg-white/[0.02] space-y-4 animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Source */}
|
||||
<div>
|
||||
<div className="text-[9px] font-mono text-white/30 uppercase tracking-wider mb-2">Source</div>
|
||||
<div className="text-[9px] font-mono text-white/30 uppercase tracking-widest mb-2.5">Source</div>
|
||||
<div className="flex gap-2">
|
||||
{[
|
||||
{ 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 && <item.icon className="w-3 h-3" />}
|
||||
@ -359,15 +379,15 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
|
||||
{/* TLD */}
|
||||
<div>
|
||||
<div className="text-[9px] font-mono text-white/30 uppercase tracking-wider mb-2">TLD</div>
|
||||
<div className="text-[9px] font-mono text-white/30 uppercase tracking-widest mb-2.5">Quick TLD</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{['all', 'com', 'ai', 'io', 'net'].map((tld) => (
|
||||
<button
|
||||
key={tld}
|
||||
onClick={() => setTldFilter(tld)}
|
||||
className={clsx(
|
||||
"px-3 py-1.5 text-[10px] font-mono uppercase border transition-colors",
|
||||
tldFilter === tld ? "border-accent bg-accent/10 text-accent" : "border-white/[0.08] text-white/40"
|
||||
"px-3 py-2 text-[10px] font-mono uppercase border transition-colors",
|
||||
tldFilter === tld ? "border-accent bg-accent/10 text-accent font-bold" : "border-white/[0.08] text-white/40"
|
||||
)}
|
||||
>
|
||||
{tld === 'all' ? 'All' : `.${tld}`}
|
||||
@ -375,10 +395,12 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Price */}
|
||||
<div>
|
||||
<div className="text-[9px] font-mono text-white/30 uppercase tracking-wider mb-2">Price</div>
|
||||
<div className="text-[9px] font-mono text-white/30 uppercase tracking-widest mb-2.5">Price Range</div>
|
||||
<div className="flex gap-2">
|
||||
{[
|
||||
{ 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,62 +423,74 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
</div>
|
||||
|
||||
{/* Spam Filter */}
|
||||
<div>
|
||||
<div className="text-[9px] font-mono text-white/30 uppercase tracking-widest mb-2.5">Visibility</div>
|
||||
<button
|
||||
onClick={() => setHideSpam(!hideSpam)}
|
||||
className={clsx(
|
||||
"flex items-center justify-between w-full py-2 px-3 border transition-colors",
|
||||
hideSpam ? "border-white/20 bg-white/[0.05]" : "border-white/[0.08]"
|
||||
"flex items-center justify-between w-full py-2 px-4 border transition-colors",
|
||||
hideSpam ? "border-accent/30 bg-accent/5" : "border-white/[0.08]"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Ban className="w-4 h-4 text-white/40" />
|
||||
<span className="text-xs font-mono text-white/60">Hide spam domains</span>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Ban className={clsx("w-3.5 h-3.5", hideSpam ? "text-accent" : "text-white/30")} />
|
||||
<span className={clsx("text-[10px] font-mono uppercase tracking-wider", hideSpam ? "text-accent" : "text-white/50")}>Hide Spam Domains</span>
|
||||
</div>
|
||||
<div className={clsx("w-4 h-4 border flex items-center justify-center", hideSpam ? "border-accent bg-accent" : "border-white/30")}>
|
||||
{hideSpam && <span className="text-black text-[10px] font-bold">✓</span>}
|
||||
<div className={clsx("w-3.5 h-3.5 border flex items-center justify-center", hideSpam ? "border-accent bg-accent" : "border-white/20")}>
|
||||
{hideSpam && <span className="text-black text-[8px] font-bold">✓</span>}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Results Header */}
|
||||
<div className="flex items-center justify-between px-1 text-[10px] font-mono text-white/30 uppercase tracking-[0.1em]">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-1 h-1 bg-accent rounded-full animate-pulse" />
|
||||
<span>{filteredItems.length} active listings found</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-accent">{stats.highScore} premium assets</span>
|
||||
{totalPages > 1 && <span>Page {page} / {totalPages}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Bar */}
|
||||
<div className="flex items-center justify-between text-[10px] font-mono text-white/40">
|
||||
<span>{filteredItems.length} domains shown</span>
|
||||
<span>{filteredItems.filter((i) => i.pounce_score >= 80).length} high score</span>
|
||||
</div>
|
||||
|
||||
{/* Results */}
|
||||
{/* Results Table */}
|
||||
{filteredItems.length === 0 ? (
|
||||
<div className="text-center py-16 border border-dashed border-white/[0.08]">
|
||||
<Search className="w-8 h-8 text-white/10 mx-auto mb-3" />
|
||||
<p className="text-white/40 text-sm font-mono">No domains found</p>
|
||||
<p className="text-white/25 text-xs font-mono mt-1">Try adjusting filters</p>
|
||||
<div className="text-center py-24 border border-dashed border-white/[0.08] bg-white/[0.01]">
|
||||
<Search className="w-12 h-12 text-white/5 mx-auto mb-4" />
|
||||
<p className="text-white/40 text-sm font-mono uppercase tracking-widest font-bold">No assets found</p>
|
||||
<p className="text-white/20 text-[10px] font-mono mt-3 uppercase tracking-wider max-w-xs mx-auto leading-relaxed">
|
||||
Try adjusting your search criteria or filters
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
|
||||
<div className="border border-white/[0.08] bg-white/[0.01] overflow-hidden">
|
||||
{/* Desktop Table Header */}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_80px_120px] gap-4 px-3 py-2 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08]">
|
||||
<button onClick={() => handleSort('domain')} className="flex items-center gap-1 hover:text-white/60 text-left" title="Domain name being auctioned">
|
||||
Domain
|
||||
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_100px_140px] gap-4 px-5 py-3 text-[10px] font-mono text-white/30 uppercase tracking-[0.2em] border-b border-white/[0.08] bg-white/[0.02]">
|
||||
<button onClick={() => handleSort('domain')} className="flex items-center gap-2 hover:text-white transition-colors text-left group">
|
||||
<span className={clsx(sortField === 'domain' && "text-accent font-bold")}>Domain Asset</span>
|
||||
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||
</button>
|
||||
<button onClick={() => handleSort('score')} className="flex items-center gap-1 justify-center hover:text-white/60" title="Pounce Score - our AI-powered quality rating (0-100)">
|
||||
Score
|
||||
{sortField === 'score' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||
<button onClick={() => handleSort('score')} className="flex items-center gap-2 justify-center hover:text-white transition-colors group">
|
||||
<span className={clsx(sortField === 'score' && "text-accent font-bold")}>Score</span>
|
||||
{sortField === 'score' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||
</button>
|
||||
<button onClick={() => handleSort('price')} className="flex items-center gap-1 justify-end hover:text-white/60" title="Current bid or buy-now price">
|
||||
Price
|
||||
{sortField === 'price' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||
<button onClick={() => handleSort('price')} className="flex items-center gap-2 justify-end hover:text-white transition-colors group pr-4">
|
||||
<span className={clsx(sortField === 'price' && "text-accent font-bold")}>Price</span>
|
||||
{sortField === 'price' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||
</button>
|
||||
<button onClick={() => handleSort('time')} className="flex items-center gap-1 justify-center hover:text-white/60" title="Time remaining until auction ends">
|
||||
Time
|
||||
{sortField === 'time' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||
<button onClick={() => handleSort('time')} className="flex items-center gap-2 justify-center hover:text-white transition-colors group">
|
||||
<span className={clsx(sortField === 'time' && "text-accent font-bold")}>Time</span>
|
||||
{sortField === 'time' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||
</button>
|
||||
<div className="text-right" title="Available actions: Analyze, Track, Buy">Actions</div>
|
||||
<div className="text-right pr-2">Actions</div>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-white/[0.04]">
|
||||
{filteredItems.map((item) => {
|
||||
const timeLeftSec = getSecondsUntilEnd(item.end_time)
|
||||
const isUrgent = timeLeftSec > 0 && timeLeftSec < 3600
|
||||
@ -466,27 +500,28 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
const isTracking = trackingInProgress === item.domain
|
||||
|
||||
return (
|
||||
<div key={item.id} className={clsx("bg-[#020202] hover:bg-white/[0.02] transition-all", isPounce && "bg-accent/[0.02]")}>
|
||||
<div key={item.id} className={clsx("bg-[#020202] hover:bg-white/[0.02] transition-all group", isPounce && "bg-accent/[0.01]")}>
|
||||
{/* Mobile Row */}
|
||||
<div className="lg:hidden p-3">
|
||||
<div className="flex items-start justify-between gap-3 mb-2">
|
||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||
<div className="min-w-0 flex-1">
|
||||
<button onClick={() => openAnalyze(item.domain)} className="text-sm font-bold text-white font-mono truncate text-left">
|
||||
<div className="lg:hidden p-4">
|
||||
<div className="flex items-start justify-between gap-3 mb-4">
|
||||
<div className="flex flex-col min-w-0">
|
||||
<button onClick={() => openAnalyze(item.domain)} className="text-base font-bold text-white font-mono truncate text-left tracking-tight">
|
||||
{item.domain}
|
||||
</button>
|
||||
<div className="flex items-center gap-2 text-[10px] font-mono text-white/30">
|
||||
<span className="uppercase">{item.source}</span>
|
||||
<div className="flex items-center gap-2 mt-1.5 text-[9px] font-mono text-white/30 uppercase tracking-wider">
|
||||
<span className="bg-white/5 px-1.5 py-0.5 border border-white/5">{item.source}</span>
|
||||
<span className="text-white/10">|</span>
|
||||
<span className={clsx(isUrgent && "text-orange-400")}>{isPounce ? 'Instant' : displayTime || 'N/A'}</span>
|
||||
</div>
|
||||
<span className={clsx(isUrgent && "text-orange-400 font-bold")}>{isPounce ? 'INSTANT' : displayTime || 'N/A'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-right shrink-0">
|
||||
<div className={clsx("text-base font-bold font-mono", isPounce ? "text-accent" : "text-white")}>{formatPrice(item.price)}</div>
|
||||
<div className={clsx("text-[9px] font-mono px-1 py-0.5 inline-block", item.pounce_score >= 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}
|
||||
<div className={clsx("text-base font-bold font-mono tracking-tight", isPounce ? "text-accent" : "text-white")}>{formatPrice(item.price)}</div>
|
||||
<div className={clsx(
|
||||
"text-[9px] font-mono px-1.5 py-0.5 mt-1 inline-block border",
|
||||
item.pounce_score >= 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}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -496,104 +531,91 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
onClick={() => handleTrack(item.domain)}
|
||||
disabled={isTracking}
|
||||
className={clsx(
|
||||
"flex-1 py-2 text-[10px] font-bold uppercase tracking-wider border flex items-center justify-center gap-1.5 transition-all",
|
||||
isTracked ? "border-accent bg-accent/10 text-accent" : "border-white/[0.08] text-white/40"
|
||||
"flex-1 h-10 text-[10px] font-bold uppercase tracking-widest border flex items-center justify-center gap-2 transition-all",
|
||||
isTracked ? "border-accent bg-accent/5 text-accent" : "border-white/10 text-white/40 hover:bg-white/5"
|
||||
)}
|
||||
>
|
||||
{isTracking ? <Loader2 className="w-3 h-3 animate-spin" /> : isTracked ? <Eye className="w-3 h-3" /> : <EyeOff className="w-3 h-3" />}
|
||||
{isTracking ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : isTracked ? <Eye className="w-4 h-4" /> : <EyeOff className="w-4 h-4" />}
|
||||
{isTracked ? 'Tracked' : 'Track'}
|
||||
</button>
|
||||
|
||||
<button onClick={() => openAnalyze(item.domain)} className="w-10 py-2 text-[10px] font-bold uppercase tracking-wider border border-white/[0.08] text-white/50 flex items-center justify-center transition-all hover:text-white hover:bg-white/5">
|
||||
<Shield className="w-3.5 h-3.5" />
|
||||
<button onClick={() => openAnalyze(item.domain)} className="w-12 h-10 border border-white/10 text-white/40 flex items-center justify-center hover:text-accent hover:border-accent/20 hover:bg-accent/5 transition-all">
|
||||
<Shield className="w-4.5 h-4.5" />
|
||||
</button>
|
||||
|
||||
<a
|
||||
href={item.url}
|
||||
target={isPounce ? '_self' : '_blank'}
|
||||
rel={isPounce ? undefined : 'noopener noreferrer'}
|
||||
className={clsx("flex-1 py-2 text-[10px] font-bold uppercase tracking-wider flex items-center justify-center gap-1.5 transition-all", isPounce ? "bg-accent text-black" : "bg-white/10 text-white")}
|
||||
className={clsx("flex-1 h-10 text-[10px] font-black uppercase tracking-widest flex items-center justify-center gap-1.5 transition-all", isPounce ? "bg-accent text-black" : "bg-white/10 text-white")}
|
||||
>
|
||||
{isPounce ? 'Buy' : 'Bid'}
|
||||
{!isPounce && <ExternalLink className="w-3 h-3" />}
|
||||
{!isPounce && <ExternalLink className="w-3.5 h-3.5" />}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop Row */}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_80px_120px] gap-4 items-center p-4 group">
|
||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||
<div className="min-w-0">
|
||||
<button onClick={() => openAnalyze(item.domain)} className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left">
|
||||
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_100px_140px] gap-4 items-center px-5 py-3.5">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<button onClick={() => openAnalyze(item.domain)} className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left tracking-tight">
|
||||
{item.domain}
|
||||
</button>
|
||||
<div className="flex items-center gap-2 text-[10px] font-mono text-white/30">
|
||||
<span className="uppercase">{item.source}</span>
|
||||
{isPounce && item.verified && (
|
||||
<>
|
||||
<span className="text-white/10">|</span>
|
||||
<span className="text-accent flex items-center gap-0.5">
|
||||
<ShieldCheck className="w-3 h-3" />
|
||||
Verified
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{item.num_bids ? (
|
||||
<>
|
||||
<span className="text-white/10">|</span>
|
||||
{item.num_bids} bids
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-[9px] font-mono text-white/20 uppercase tracking-widest opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<span>{item.source}</span>
|
||||
{isPounce && item.verified && <ShieldCheck className="w-3 h-3 text-accent" />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-16 text-center shrink-0">
|
||||
<span className={clsx("text-xs font-mono font-bold px-2 py-0.5", item.pounce_score >= 80 ? "text-accent bg-accent/10" : item.pounce_score >= 50 ? "text-amber-400 bg-amber-400/10" : "text-white/40 bg-white/5")}>
|
||||
<div className="text-center">
|
||||
<span className={clsx(
|
||||
"text-[10px] font-mono font-bold px-2 py-0.5 border",
|
||||
item.pounce_score >= 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}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="w-24 text-right shrink-0">
|
||||
<div className={clsx("font-mono text-sm font-bold", isPounce ? "text-accent" : "text-white")}>{formatPrice(item.price)}</div>
|
||||
<div className="text-[9px] font-mono text-white/30 uppercase">{item.price_type === 'bid' ? 'Bid' : 'Buy Now'}</div>
|
||||
<div className="text-right pr-4">
|
||||
<div className={clsx("font-mono text-sm font-bold tracking-tight", isPounce ? "text-accent" : "text-white")}>{formatPrice(item.price)}</div>
|
||||
<div className="text-[9px] font-mono text-white/20 uppercase tracking-widest">{item.price_type === 'bid' ? 'BID' : 'BUY NOW'}</div>
|
||||
</div>
|
||||
|
||||
<div className="w-20 text-center shrink-0">
|
||||
<div className="text-center">
|
||||
{isPounce ? (
|
||||
<span className="text-xs text-accent font-mono flex items-center justify-center gap-1">
|
||||
<span className="text-[10px] text-accent font-mono font-bold flex items-center justify-center gap-1 uppercase tracking-widest">
|
||||
<Zap className="w-3 h-3" />
|
||||
Instant
|
||||
</span>
|
||||
) : (
|
||||
<span className={clsx("text-xs font-mono", isUrgent ? "text-orange-400" : "text-white/50")}>{displayTime || 'N/A'}</span>
|
||||
<span className={clsx("text-[10px] font-mono uppercase tracking-widest", isUrgent ? "text-orange-400 font-bold" : "text-white/40")}>{displayTime || 'N/A'}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 shrink-0 opacity-50 group-hover:opacity-100 transition-opacity">
|
||||
<div className="flex items-center justify-end gap-1.5 opacity-0 group-hover:opacity-100 transition-all duration-300 transform translate-x-2 group-hover:translate-x-0">
|
||||
<button
|
||||
onClick={() => handleTrack(item.domain)}
|
||||
disabled={isTracking}
|
||||
className={clsx(
|
||||
"w-7 h-7 flex items-center justify-center border transition-colors",
|
||||
isTracked ? "bg-accent/10 text-accent border-accent/20 hover:bg-red-500/10 hover:text-red-400 hover:border-red-500/20" : "text-white/30 border-white/10 hover:text-accent hover:bg-accent/10 hover:border-accent/20"
|
||||
"w-8.5 h-8.5 flex items-center justify-center border transition-all",
|
||||
isTracked ? "bg-accent/5 text-accent border-accent/20 hover:bg-red-500/5 hover:text-red-400 hover:border-red-500/20" : "text-white/30 border-white/10 hover:text-white hover:bg-white/5"
|
||||
)}
|
||||
title={isTracked ? "Untrack" : "Track Domain"}
|
||||
>
|
||||
{isTracking ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : isTracked ? <Eye className="w-3.5 h-3.5" /> : <EyeOff className="w-3.5 h-3.5" />}
|
||||
{isTracking ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : isTracked ? <Eye className="w-4 h-4" /> : <EyeOff className="w-4 h-4" />}
|
||||
</button>
|
||||
|
||||
<button onClick={() => openAnalyze(item.domain)} className="w-7 h-7 flex items-center justify-center border transition-colors text-white/30 border-white/10 hover:text-accent hover:bg-accent/10 hover:border-accent/20">
|
||||
<Shield className="w-3.5 h-3.5" />
|
||||
<button onClick={() => openAnalyze(item.domain)} className="w-8.5 h-8.5 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/20 hover:bg-accent/5 transition-all" title="Deep Analysis">
|
||||
<Shield className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
<a
|
||||
href={item.url}
|
||||
target={isPounce ? '_self' : '_blank'}
|
||||
rel={isPounce ? undefined : 'noopener noreferrer'}
|
||||
className={clsx("h-7 px-3 flex items-center gap-1.5 text-xs font-bold transition-colors", isPounce ? "bg-accent text-black hover:bg-white" : "bg-white/10 text-white hover:bg-white/20")}
|
||||
className={clsx("h-8.5 px-4 flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest transition-all shadow-[0_0_15px_-5px_rgba(34,211,126,0.4)]", isPounce ? "bg-accent text-black hover:bg-white" : "bg-white/10 text-white hover:bg-white/20")}
|
||||
>
|
||||
{isPounce ? 'Buy' : 'Bid'}
|
||||
{!isPounce && <ExternalLink className="w-3 h-3" />}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -601,35 +623,31 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-between pt-4">
|
||||
<div className="text-[10px] text-white/40 font-mono uppercase tracking-wider">
|
||||
Page {page}/{totalPages}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex items-center justify-center gap-1 pt-6">
|
||||
<button
|
||||
onClick={() => handlePageChange(page - 1)}
|
||||
disabled={page === 1}
|
||||
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
|
||||
className="w-10 h-10 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-20 disabled:cursor-not-allowed transition-all"
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<span className="text-xs text-white/50 font-mono px-2">
|
||||
{page}/{totalPages}
|
||||
<div className="flex items-center bg-white/[0.02] border border-white/[0.08] px-5 h-10">
|
||||
<span className="text-[11px] text-white/40 font-mono uppercase tracking-widest">
|
||||
Page <span className="text-white font-bold">{page}</span> / {totalPages}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handlePageChange(page + 1)}
|
||||
disabled={page === totalPages}
|
||||
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
|
||||
className="w-10 h-10 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-20 disabled:cursor-not-allowed transition-all"
|
||||
>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -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 (
|
||||
<div className="space-y-6">
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* HEADER */}
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
<div className="pb-4 border-b border-white/10">
|
||||
<h2 className="text-xl font-bold text-white flex items-center gap-2">
|
||||
<Wand2 className="w-5 h-5 text-purple-400" />
|
||||
Brandable Forge
|
||||
</h2>
|
||||
<p className="text-sm text-white/60 mt-1">
|
||||
Generate unique, memorable domain names
|
||||
</p>
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-4 pb-5 border-b border-white/[0.08]">
|
||||
<div className="w-12 h-12 bg-purple-500/10 border border-purple-500/20 flex items-center justify-center shrink-0">
|
||||
<Wand2 className="w-6 h-6 text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white font-mono tracking-tight">Brandable Forge</h2>
|
||||
<p className="text-[10px] font-mono text-white/40 uppercase tracking-widest mt-1">Synthesize unique, high-value naming assets</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* MODE SELECTOR - BIG CLEAR BUTTONS */}
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{/* Mode Selector */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{/* Pattern Mode */}
|
||||
<button
|
||||
onClick={() => setMode('pattern')}
|
||||
className={clsx(
|
||||
"p-4 sm:p-5 border-2 transition-all text-left",
|
||||
"relative p-5 border transition-all duration-300 text-left group overflow-hidden",
|
||||
mode === 'pattern'
|
||||
? "border-accent bg-accent/10"
|
||||
: "border-white/10 bg-white/[0.02] hover:border-white/20"
|
||||
? "border-accent bg-accent/5 shadow-[0_0_30px_-10px_rgba(34,211,126,0.2)]"
|
||||
: "border-white/[0.08] bg-white/[0.01] hover:border-white/20 hover:bg-white/[0.03]"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="flex items-center gap-4 relative z-10">
|
||||
<div className={clsx(
|
||||
"w-10 h-10 flex items-center justify-center",
|
||||
mode === 'pattern' ? "bg-accent/20" : "bg-white/5"
|
||||
"w-12 h-12 flex items-center justify-center border transition-colors",
|
||||
mode === 'pattern' ? "border-accent/30 bg-accent/10" : "border-white/10 bg-white/5"
|
||||
)}>
|
||||
<Dices className={clsx("w-5 h-5", mode === 'pattern' ? "text-accent" : "text-white/50")} />
|
||||
<Dices className={clsx("w-6 h-6", mode === 'pattern' ? "text-accent" : "text-white/30")} />
|
||||
</div>
|
||||
<div>
|
||||
<p className={clsx("font-bold", mode === 'pattern' ? "text-accent" : "text-white")}>
|
||||
Pattern
|
||||
</p>
|
||||
<p className={clsx("text-xs", mode === 'pattern' ? "text-accent/70" : "text-white/50")}>
|
||||
CVCVC, CVCCV, Human
|
||||
</p>
|
||||
<p className={clsx("font-bold font-mono text-sm tracking-tight", mode === 'pattern' ? "text-accent" : "text-white/70")}>Pattern Engine</p>
|
||||
<p className="text-[10px] font-mono text-white/30 uppercase tracking-widest mt-0.5">Classic Brandables</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-white/40 hidden sm:block">
|
||||
Generate random names using proven naming patterns
|
||||
</p>
|
||||
{mode === 'pattern' && <div className="absolute top-0 right-0 w-2 h-2 bg-accent" />}
|
||||
</button>
|
||||
|
||||
{/* AI Mode */}
|
||||
@ -181,135 +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 && (
|
||||
<div className="absolute top-2 right-2">
|
||||
<Lock className="w-4 h-4 text-white/30" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
{!hasAI && <div className="absolute top-2 right-2"><Lock className="w-3 h-3 text-white/20" /></div>}
|
||||
<div className="flex items-center gap-4 relative z-10">
|
||||
<div className={clsx(
|
||||
"w-10 h-10 flex items-center justify-center",
|
||||
mode === 'ai' ? "bg-purple-500/20" : "bg-white/5"
|
||||
"w-12 h-12 flex items-center justify-center border transition-colors",
|
||||
mode === 'ai' ? "border-purple-500/30 bg-purple-500/10" : "border-white/10 bg-white/5"
|
||||
)}>
|
||||
<Brain className={clsx("w-5 h-5", mode === 'ai' ? "text-purple-400" : "text-white/50")} />
|
||||
<Brain className={clsx("w-6 h-6", mode === 'ai' ? "text-purple-400" : "text-white/30")} />
|
||||
</div>
|
||||
<div>
|
||||
<p className={clsx("font-bold", mode === 'ai' ? "text-purple-400" : "text-white")}>
|
||||
AI Concept
|
||||
</p>
|
||||
<p className={clsx("text-xs", mode === 'ai' ? "text-purple-400/70" : "text-white/50")}>
|
||||
{hasAI ? 'Describe your brand' : 'Trader+ only'}
|
||||
</p>
|
||||
<p className={clsx("font-bold font-mono text-sm tracking-tight", mode === 'ai' ? "text-purple-400" : "text-white/70")}>Vision Core AI</p>
|
||||
<p className="text-[10px] font-mono text-white/30 uppercase tracking-widest mt-0.5">Concept to Name</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-white/40 hidden sm:block">
|
||||
{hasAI ? 'AI generates names based on your concept' : 'Upgrade to unlock AI naming'}
|
||||
</p>
|
||||
{mode === 'ai' && <div className="absolute top-0 right-0 w-2 h-2 bg-purple-500" />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* PATTERN MODE */}
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{mode === 'pattern' && (
|
||||
<div className="border border-white/10 bg-white/[0.02] p-4 sm:p-5 space-y-5">
|
||||
{/* Pattern Selection */}
|
||||
<div>
|
||||
<p className="text-xs text-white/60 font-mono uppercase tracking-wider mb-3">Choose Pattern</p>
|
||||
{/* Config Panel */}
|
||||
<div className={clsx(
|
||||
"border overflow-hidden transition-all duration-500",
|
||||
mode === 'ai' ? "border-purple-500/30 bg-purple-500/[0.01]" : "border-white/[0.08] bg-white/[0.01]"
|
||||
)}>
|
||||
<div className="px-5 py-4 border-b border-white/[0.08] bg-white/[0.01] flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-[10px] font-mono text-white/30 uppercase tracking-[0.2em]">Synthesis Configuration</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={clsx("w-1 h-1 rounded-full", mode === 'ai' ? "bg-purple-500 animate-pulse" : "bg-accent")} />
|
||||
<span className="text-[10px] font-mono text-white/20 uppercase tracking-widest">{mode.toUpperCase()} MODE</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 space-y-6">
|
||||
{mode === 'pattern' ? (
|
||||
<div className="space-y-4">
|
||||
<span className="text-[10px] font-mono text-white/30 uppercase tracking-widest">Select Linguistic Pattern</span>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{PATTERNS.map(p => (
|
||||
<button
|
||||
key={p.key}
|
||||
onClick={() => setPattern(p.key)}
|
||||
className={clsx(
|
||||
"p-3 border text-center transition-all",
|
||||
"p-3 border transition-all duration-300 group",
|
||||
pattern === p.key
|
||||
? "border-accent bg-accent/10"
|
||||
: "border-white/10 hover:border-white/20"
|
||||
: "border-white/10 hover:border-white/20 hover:bg-white/5"
|
||||
)}
|
||||
>
|
||||
<p className={clsx(
|
||||
"text-sm font-bold font-mono",
|
||||
pattern === p.key ? "text-accent" : "text-white"
|
||||
)}>
|
||||
{p.label}
|
||||
</p>
|
||||
<p className="text-[10px] text-white/50 mt-0.5">{p.example}</p>
|
||||
<p className={clsx("text-xs font-black font-mono tracking-widest", pattern === p.key ? "text-accent" : "text-white/40 group-hover:text-white/60")}>{p.label}</p>
|
||||
<p className="text-[9px] font-mono text-white/20 mt-1 uppercase tracking-tighter">{p.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TLDs */}
|
||||
<div>
|
||||
<p className="text-xs text-white/60 font-mono uppercase tracking-wider mb-3">Select TLDs</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{TLDS.map(tld => (
|
||||
<button
|
||||
key={tld}
|
||||
onClick={() => setTlds(prev =>
|
||||
prev.includes(tld) ? prev.filter(t => t !== tld) : [...prev, tld]
|
||||
)}
|
||||
className={clsx(
|
||||
"px-3 py-2 text-sm font-mono border transition-all",
|
||||
tlds.includes(tld)
|
||||
? "border-accent bg-accent/10 text-accent"
|
||||
: "border-white/10 text-white/50 hover:text-white"
|
||||
)}
|
||||
>
|
||||
.{tld}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Generate */}
|
||||
<button
|
||||
onClick={generatePattern}
|
||||
disabled={isGenerating || tlds.length === 0}
|
||||
className={clsx(
|
||||
"w-full py-4 text-sm font-bold uppercase tracking-wider flex items-center justify-center gap-2 transition-all",
|
||||
isGenerating || tlds.length === 0
|
||||
? "bg-white/10 text-white/40"
|
||||
: "bg-accent text-black hover:bg-white"
|
||||
)}
|
||||
>
|
||||
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Sparkles className="w-4 h-4" />}
|
||||
Generate {pattern.toUpperCase()} Names
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* AI MODE */}
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{mode === 'ai' && hasAI && (
|
||||
<div className="border border-purple-500/30 bg-purple-500/5 p-4 sm:p-5 space-y-5">
|
||||
{/* Concept Input */}
|
||||
<div>
|
||||
<p className="text-xs text-purple-300 font-mono uppercase tracking-wider mb-3">
|
||||
Describe Your Brand Concept
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<span className="text-[10px] font-mono text-purple-300/40 uppercase tracking-widest">Describe Your Identity Concept</span>
|
||||
<textarea
|
||||
value={concept}
|
||||
onChange={(e) => setConcept(e.target.value)}
|
||||
placeholder="e.g., AI startup for legal documents, crypto wallet for teens, fitness tracking app..."
|
||||
placeholder="e.g., A minimalist AI agent for legal risk management..."
|
||||
rows={3}
|
||||
className="w-full px-4 py-3 bg-white/5 border border-purple-500/30 text-white placeholder:text-white/30 outline-none focus:border-purple-400 resize-none font-mono text-sm"
|
||||
className="w-full px-4 py-3 bg-white/[0.03] border border-purple-500/20 text-sm font-mono text-white placeholder:text-white/10 outline-none focus:border-purple-500/50 resize-none transition-all"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TLDs */}
|
||||
<div>
|
||||
<p className="text-xs text-purple-300 font-mono uppercase tracking-wider mb-3">Select TLDs</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="space-y-4">
|
||||
<span className="text-[10px] font-mono text-white/30 uppercase tracking-widest">Target Extensions</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{TLDS.map(tld => (
|
||||
<button
|
||||
key={tld}
|
||||
@ -317,10 +257,10 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
|
||||
prev.includes(tld) ? prev.filter(t => t !== tld) : [...prev, tld]
|
||||
)}
|
||||
className={clsx(
|
||||
"px-3 py-2 text-sm font-mono border transition-all",
|
||||
"px-3 py-1.5 text-[10px] font-mono border transition-all uppercase tracking-widest",
|
||||
tlds.includes(tld)
|
||||
? "border-purple-400 bg-purple-500/20 text-purple-300"
|
||||
: "border-white/10 text-white/50 hover:text-white"
|
||||
? mode === 'ai' ? "border-purple-500 bg-purple-500/10 text-purple-300 font-black" : "border-accent bg-accent/10 text-accent font-black"
|
||||
: "border-white/10 text-white/40 hover:text-white hover:bg-white/5"
|
||||
)}
|
||||
>
|
||||
.{tld}
|
||||
@ -329,65 +269,52 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Generate */}
|
||||
<button
|
||||
onClick={generateFromConcept}
|
||||
disabled={aiLoading || !concept.trim() || tlds.length === 0}
|
||||
onClick={mode === 'pattern' ? generatePattern : generateFromConcept}
|
||||
disabled={isGenerating || (mode === 'ai' && !concept.trim()) || tlds.length === 0}
|
||||
className={clsx(
|
||||
"w-full py-4 text-sm font-bold uppercase tracking-wider flex items-center justify-center gap-2 transition-all",
|
||||
aiLoading || !concept.trim() || tlds.length === 0
|
||||
? "bg-white/10 text-white/40"
|
||||
: "bg-purple-500 text-white hover:bg-purple-400"
|
||||
"w-full py-4 text-[11px] font-black uppercase tracking-[0.2em] flex items-center justify-center gap-3 transition-all",
|
||||
isGenerating || (mode === 'ai' && !concept.trim()) || tlds.length === 0
|
||||
? "bg-white/5 text-white/20 border border-white/5"
|
||||
: mode === 'ai'
|
||||
? "bg-purple-600 text-white hover:bg-purple-500 shadow-[0_0_30px_-10px_rgba(168,85,247,0.4)]"
|
||||
: "bg-accent text-black hover:bg-white shadow-[0_0_30px_-10px_rgba(34,211,126,0.4)]"
|
||||
)}
|
||||
>
|
||||
{aiLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Brain className="w-4 h-4" />}
|
||||
Generate AI Names
|
||||
{isGenerating ? <Loader2 className="w-4 h-4 animate-spin" /> : <Sparkles className="w-4 h-4" />}
|
||||
{mode === 'ai' ? 'Synthesize AI Assets' : `Forge ${pattern.toUpperCase()} Names`}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* AI Upgrade CTA */}
|
||||
{mode === 'ai' && !hasAI && (
|
||||
<div className="border border-white/10 bg-white/[0.02] p-8 text-center">
|
||||
<Lock className="w-10 h-10 text-white/20 mx-auto mb-3" />
|
||||
<h3 className="text-lg font-bold text-white mb-2">AI Naming Requires Upgrade</h3>
|
||||
<p className="text-sm text-white/50 mb-4 max-w-sm mx-auto">
|
||||
Describe your brand and let AI generate unique, brandable domain names for you.
|
||||
<div className="border border-white/10 bg-white/[0.01] p-10 text-center animate-in fade-in zoom-in-95 duration-500">
|
||||
<Lock className="w-12 h-12 text-white/5 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-bold text-white font-mono tracking-tight mb-2 uppercase">Neural Forge Locked</h3>
|
||||
<p className="text-sm text-white/30 font-mono mb-6 max-w-sm mx-auto uppercase tracking-wider leading-relaxed">
|
||||
AI-driven naming requires Trader or Tycoon clearance
|
||||
</p>
|
||||
<Link
|
||||
href="/pricing"
|
||||
className="inline-flex items-center gap-2 px-6 py-3 bg-accent text-black text-sm font-bold uppercase hover:bg-white transition-colors"
|
||||
className="inline-flex items-center gap-3 px-8 py-3 bg-accent text-black text-[11px] font-black uppercase tracking-widest hover:bg-white transition-all active:scale-95"
|
||||
>
|
||||
<Sparkles className="w-4 h-4" />
|
||||
Upgrade to Trader
|
||||
Upgrade Plan
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* RESULTS */}
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* Results */}
|
||||
{results.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm text-white/70">
|
||||
<span className="text-accent font-bold">{results.length}</span> available domains
|
||||
<div className="space-y-4 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<p className="text-[10px] font-mono text-white/30 uppercase tracking-[0.2em]">
|
||||
<span className={clsx("font-black", mode === 'ai' ? "text-purple-400" : "text-accent")}>{results.length}</span> Synthesized naming assets available
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={copyAll}
|
||||
className="text-xs font-mono text-white/50 hover:text-accent flex items-center gap-1"
|
||||
>
|
||||
<Copy className="w-3 h-3" />
|
||||
Copy all
|
||||
</button>
|
||||
<button
|
||||
onClick={mode === 'pattern' ? generatePattern : generateFromConcept}
|
||||
disabled={isGenerating}
|
||||
className="text-xs font-mono text-white/50 hover:text-white flex items-center gap-1"
|
||||
>
|
||||
<RefreshCw className={clsx("w-3 h-3", isGenerating && "animate-spin")} />
|
||||
Refresh
|
||||
<div className="flex gap-4">
|
||||
<button onClick={copyAll} className="text-[9px] font-mono text-white/20 hover:text-white uppercase tracking-widest transition-colors">
|
||||
Batch Copy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -397,49 +324,49 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
|
||||
<div
|
||||
key={r.domain}
|
||||
className={clsx(
|
||||
"flex items-center justify-between p-3 border transition-all group",
|
||||
"flex items-center justify-between p-4 border transition-all duration-300 group",
|
||||
mode === 'ai'
|
||||
? "bg-purple-500/5 border-purple-500/20 hover:bg-purple-500/10"
|
||||
: "bg-accent/5 border-accent/20 hover:bg-accent/10"
|
||||
? "bg-purple-500/[0.02] border-purple-500/20 hover:border-purple-500/40 hover:bg-purple-500/[0.04]"
|
||||
: "bg-accent/[0.02] border-accent/20 hover:border-accent/40 hover:bg-accent/[0.04]"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2.5 min-w-0">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<span className={clsx(
|
||||
"w-6 h-6 text-[10px] font-bold font-mono flex items-center justify-center shrink-0",
|
||||
"text-[9px] font-bold font-mono px-1.5 py-0.5 border shrink-0",
|
||||
mode === 'ai'
|
||||
? "bg-purple-500/20 text-purple-300"
|
||||
: "bg-accent/20 text-accent"
|
||||
? "bg-purple-500/10 border-purple-500/20 text-purple-300"
|
||||
: "bg-accent/10 border-accent/20 text-accent"
|
||||
)}>
|
||||
{idx + 1}
|
||||
{(idx + 1).toString().padStart(2, '0')}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => openAnalyze(r.domain)}
|
||||
className={clsx(
|
||||
"text-sm font-mono font-medium text-white truncate",
|
||||
"text-sm font-bold font-mono text-white truncate transition-colors",
|
||||
mode === 'ai' ? "group-hover:text-purple-300" : "group-hover:text-accent"
|
||||
)}
|
||||
>
|
||||
{r.domain}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5 shrink-0 opacity-60 group-hover:opacity-100">
|
||||
<button onClick={() => copy(r.domain)} className="w-7 h-7 flex items-center justify-center text-white/60 hover:text-white">
|
||||
{copied === r.domain ? <Check className="w-3 h-3 text-accent" /> : <Copy className="w-3 h-3" />}
|
||||
<div className="flex items-center gap-1 shrink-0 opacity-0 group-hover:opacity-100 transition-all transform translate-x-2 group-hover:translate-x-0">
|
||||
<button onClick={() => copy(r.domain)} className="w-7 h-7 flex items-center justify-center border border-white/10 text-white/30 hover:text-white hover:bg-white/5">
|
||||
{copied === r.domain ? <Check className="w-3.5 h-3.5 text-accent" /> : <Copy className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
<button onClick={() => track(r.domain)} disabled={tracking === r.domain} className="w-7 h-7 flex items-center justify-center text-white/60 hover:text-white">
|
||||
{tracking === r.domain ? <Loader2 className="w-3 h-3 animate-spin" /> : <Eye className="w-3 h-3" />}
|
||||
<button onClick={() => track(r.domain)} disabled={tracking === r.domain} className="w-7 h-7 flex items-center justify-center border border-white/10 text-white/30 hover:text-white hover:bg-white/5">
|
||||
{tracking === r.domain ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
<button onClick={() => openAnalyze(r.domain)} className="w-7 h-7 flex items-center justify-center text-white/60 hover:text-accent">
|
||||
<Shield className="w-3 h-3" />
|
||||
<button onClick={() => openAnalyze(r.domain)} className="w-7 h-7 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/20 hover:bg-accent/5">
|
||||
<Shield className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<a
|
||||
href={`https://www.namecheap.com/domains/registration/results/?domain=${r.domain}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={clsx(
|
||||
"h-7 px-2 text-[10px] font-bold flex items-center",
|
||||
"h-7 px-3 text-[10px] font-black uppercase tracking-widest flex items-center transition-all",
|
||||
mode === 'ai'
|
||||
? "bg-purple-500 text-white hover:bg-purple-400"
|
||||
? "bg-purple-600 text-white hover:bg-purple-500"
|
||||
: "bg-accent text-black hover:bg-white"
|
||||
)}
|
||||
>
|
||||
@ -454,21 +381,20 @@ export function BrandableForgeTab({ showToast }: { showToast: (msg: string, type
|
||||
|
||||
{/* Empty State */}
|
||||
{results.length === 0 && !isGenerating && (
|
||||
<div className="text-center py-12 border border-dashed border-white/10">
|
||||
<Wand2 className="w-10 h-10 text-white/20 mx-auto mb-3" />
|
||||
<p className="text-white/50">
|
||||
{mode === 'pattern'
|
||||
? 'Choose a pattern and click Generate'
|
||||
: 'Describe your brand concept'}
|
||||
<div className="text-center py-24 border border-dashed border-white/[0.08] bg-white/[0.01]">
|
||||
<Wand2 className="w-12 h-12 text-white/5 mx-auto mb-4" />
|
||||
<p className="text-white/40 text-sm font-mono uppercase tracking-widest font-bold">Forge is currently idle</p>
|
||||
<p className="text-white/20 text-[10px] font-mono mt-3 uppercase tracking-wider max-w-xs mx-auto leading-relaxed">
|
||||
Select a generation mode above to synthesize available brandable assets
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Loading State */}
|
||||
{/* Loading Grid */}
|
||||
{isGenerating && results.length === 0 && (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||
{[...Array(12)].map((_, i) => (
|
||||
<div key={i} className="h-12 bg-white/5 animate-pulse" />
|
||||
<div key={i} className="h-14 bg-white/[0.01] border border-white/[0.05] animate-pulse" />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -5,7 +5,6 @@ import { api } from '@/lib/api'
|
||||
import { useAnalyzePanelStore } from '@/lib/analyze-store'
|
||||
import { useStore } from '@/lib/store'
|
||||
import {
|
||||
Clock,
|
||||
Globe,
|
||||
Loader2,
|
||||
Search,
|
||||
@ -165,7 +164,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
setTracking(domain)
|
||||
try {
|
||||
await addDomain(domain)
|
||||
showToast(`Tracking ${domain}`, 'success')
|
||||
showToast(`Added: ${domain}`, 'success')
|
||||
} catch (e) {
|
||||
showToast(e instanceof Error ? e.message : 'Failed', 'error')
|
||||
} finally {
|
||||
@ -237,7 +236,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
<div className="text-xl font-bold text-white font-mono">
|
||||
{stats?.daily_drops?.toLocaleString() || total.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase">Fresh drops (24h)</div>
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Fresh drops detected (24h)</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@ -278,8 +277,8 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
<button
|
||||
onClick={() => setSelectedTld(null)}
|
||||
className={clsx(
|
||||
"px-2.5 py-1.5 text-[10px] font-mono uppercase border transition-colors",
|
||||
selectedTld === null ? "border-accent bg-accent/10 text-accent" : "border-white/[0.08] text-white/40"
|
||||
"px-3 py-1.5 text-[10px] font-mono uppercase border transition-colors",
|
||||
selectedTld === null ? "border-accent bg-accent/10 text-accent font-bold" : "border-white/[0.08] text-white/40"
|
||||
)}
|
||||
>
|
||||
All
|
||||
@ -289,8 +288,8 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
key={tld}
|
||||
onClick={() => setSelectedTld(tld)}
|
||||
className={clsx(
|
||||
"px-2.5 py-1.5 text-[10px] font-mono uppercase border transition-colors flex items-center gap-1",
|
||||
selectedTld === tld ? "border-accent bg-accent/10 text-accent" : "border-white/[0.08] text-white/40"
|
||||
"px-3 py-1.5 text-[10px] font-mono uppercase border transition-colors flex items-center gap-1.5",
|
||||
selectedTld === tld ? "border-accent bg-accent/10 text-accent font-bold" : "border-white/[0.08] text-white/40"
|
||||
)}
|
||||
>
|
||||
<span className="text-xs">{flag}</span>.{tld}
|
||||
@ -302,15 +301,15 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
<button
|
||||
onClick={() => setFiltersOpen(!filtersOpen)}
|
||||
className={clsx(
|
||||
"flex items-center justify-between w-full py-2 px-3 border transition-colors",
|
||||
"flex items-center justify-between w-full py-2.5 px-4 border transition-colors",
|
||||
filtersOpen ? "border-accent/30 bg-accent/[0.05]" : "border-white/[0.08] bg-white/[0.02]"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Filter className="w-4 h-4 text-white/40" />
|
||||
<span className="text-xs font-mono text-white/60">Filters</span>
|
||||
<span className="text-xs font-mono text-white/60 uppercase tracking-widest">Advanced Filters</span>
|
||||
{activeFiltersCount > 0 && (
|
||||
<span className="px-1.5 py-0.5 text-[9px] font-bold bg-accent text-black">{activeFiltersCount}</span>
|
||||
<span className="px-1.5 py-0.5 text-[9px] font-bold bg-accent text-black ml-1">{activeFiltersCount}</span>
|
||||
)}
|
||||
</div>
|
||||
<ChevronRight className={clsx("w-4 h-4 text-white/30 transition-transform", filtersOpen && "rotate-90")} />
|
||||
@ -318,27 +317,27 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
|
||||
{/* Filters Panel */}
|
||||
{filtersOpen && (
|
||||
<div className="p-3 border border-white/[0.08] bg-white/[0.02] space-y-3 animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
<div className="p-4 border border-white/[0.08] bg-white/[0.02] space-y-4 animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
{/* Length Filter */}
|
||||
<div>
|
||||
<div className="text-[9px] font-mono text-white/30 uppercase tracking-wider mb-2">Length</div>
|
||||
<div className="text-[9px] font-mono text-white/30 uppercase tracking-widest mb-2.5">Domain Length</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<input
|
||||
type="number"
|
||||
value={minLength || ''}
|
||||
onChange={(e) => setMinLength(e.target.value ? Number(e.target.value) : undefined)}
|
||||
placeholder="Min"
|
||||
className="w-16 bg-white/[0.02] border border-white/10 px-2 py-1.5 text-xs text-white placeholder:text-white/25 outline-none font-mono"
|
||||
className="w-20 bg-white/[0.02] border border-white/10 px-3 py-2 text-xs text-white placeholder:text-white/20 outline-none font-mono focus:border-accent/30"
|
||||
min={1}
|
||||
max={63}
|
||||
/>
|
||||
<span className="text-white/20">–</span>
|
||||
<span className="text-white/10 px-1 font-mono text-xs">TO</span>
|
||||
<input
|
||||
type="number"
|
||||
value={maxLength || ''}
|
||||
onChange={(e) => setMaxLength(e.target.value ? Number(e.target.value) : undefined)}
|
||||
placeholder="Max"
|
||||
className="w-16 bg-white/[0.02] border border-white/10 px-2 py-1.5 text-xs text-white placeholder:text-white/25 outline-none font-mono"
|
||||
className="w-20 bg-white/[0.02] border border-white/10 px-3 py-2 text-xs text-white placeholder:text-white/20 outline-none font-mono focus:border-accent/30"
|
||||
min={1}
|
||||
max={63}
|
||||
/>
|
||||
@ -350,15 +349,15 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
<button
|
||||
onClick={() => setExcludeNumeric(!excludeNumeric)}
|
||||
className={clsx(
|
||||
"flex-1 flex items-center justify-between py-2 px-3 border transition-colors",
|
||||
excludeNumeric ? "border-white/20 bg-white/[0.05]" : "border-white/[0.08]"
|
||||
"flex-1 flex items-center justify-between py-2.5 px-4 border transition-colors",
|
||||
excludeNumeric ? "border-accent/30 bg-accent/5" : "border-white/[0.08]"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Hash className="w-3.5 h-3.5 text-white/40" />
|
||||
<span className="text-[10px] font-mono text-white/60">No numbers</span>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Hash className={clsx("w-3.5 h-3.5", excludeNumeric ? "text-accent" : "text-white/30")} />
|
||||
<span className={clsx("text-[10px] font-mono uppercase tracking-wider", excludeNumeric ? "text-accent" : "text-white/50")}>Exclude Numeric</span>
|
||||
</div>
|
||||
<div className={clsx("w-3.5 h-3.5 border flex items-center justify-center", excludeNumeric ? "border-accent bg-accent" : "border-white/30")}>
|
||||
<div className={clsx("w-3.5 h-3.5 border flex items-center justify-center", excludeNumeric ? "border-accent bg-accent" : "border-white/20")}>
|
||||
{excludeNumeric && <span className="text-black text-[8px] font-bold">✓</span>}
|
||||
</div>
|
||||
</button>
|
||||
@ -366,15 +365,15 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
<button
|
||||
onClick={() => setExcludeHyphen(!excludeHyphen)}
|
||||
className={clsx(
|
||||
"flex-1 flex items-center justify-between py-2 px-3 border transition-colors",
|
||||
excludeHyphen ? "border-white/20 bg-white/[0.05]" : "border-white/[0.08]"
|
||||
"flex-1 flex items-center justify-between py-2.5 px-4 border transition-colors",
|
||||
excludeHyphen ? "border-accent/30 bg-accent/5" : "border-white/[0.08]"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Ban className="w-3.5 h-3.5 text-white/40" />
|
||||
<span className="text-[10px] font-mono text-white/60">No hyphens</span>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Ban className={clsx("w-3.5 h-3.5", excludeHyphen ? "text-accent" : "text-white/30")} />
|
||||
<span className={clsx("text-[10px] font-mono uppercase tracking-wider", excludeHyphen ? "text-accent" : "text-white/50")}>Exclude Hyphen</span>
|
||||
</div>
|
||||
<div className={clsx("w-3.5 h-3.5 border flex items-center justify-center", excludeHyphen ? "border-accent bg-accent" : "border-white/30")}>
|
||||
<div className={clsx("w-3.5 h-3.5 border flex items-center justify-center", excludeHyphen ? "border-accent bg-accent" : "border-white/20")}>
|
||||
{excludeHyphen && <span className="text-black text-[8px] font-bold">✓</span>}
|
||||
</div>
|
||||
</button>
|
||||
@ -382,57 +381,66 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stats Bar */}
|
||||
<div className="flex items-center justify-between text-[10px] font-mono text-white/40">
|
||||
<span>{total.toLocaleString()} fresh drops</span>
|
||||
{totalPages > 1 && <span>Page {page}/{totalPages}</span>}
|
||||
{/* Results Header */}
|
||||
<div className="flex items-center justify-between px-1 text-[10px] font-mono text-white/30 uppercase tracking-[0.1em]">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-1 h-1 bg-accent rounded-full" />
|
||||
<span>{total.toLocaleString()} domains matching criteria</span>
|
||||
</div>
|
||||
{totalPages > 1 && <span>Page {page} of {totalPages}</span>}
|
||||
</div>
|
||||
|
||||
{/* Results */}
|
||||
{/* Results Table */}
|
||||
{sortedItems.length === 0 ? (
|
||||
<div className="text-center py-16 border border-dashed border-white/[0.08]">
|
||||
<Globe className="w-8 h-8 text-white/10 mx-auto mb-3" />
|
||||
<p className="text-white/40 text-sm font-mono">No fresh drops</p>
|
||||
<p className="text-white/25 text-xs font-mono mt-1">Check back after the next sync</p>
|
||||
<div className="text-center py-24 border border-dashed border-white/[0.08] bg-white/[0.01]">
|
||||
<Globe className="w-12 h-12 text-white/5 mx-auto mb-4" />
|
||||
<p className="text-white/40 text-sm font-mono uppercase tracking-widest font-bold">No fresh drops detected</p>
|
||||
<p className="text-white/20 text-[10px] font-mono mt-3 uppercase tracking-wider max-w-xs mx-auto leading-relaxed">
|
||||
The zone file comparison engine will update in the next 24h cycle
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
|
||||
<div className="border border-white/[0.08] bg-white/[0.01] overflow-hidden">
|
||||
{/* Desktop Table Header */}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_60px_80px_100px] gap-4 px-3 py-2 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08]">
|
||||
<button onClick={() => handleSort('domain')} className="flex items-center gap-1 hover:text-white/60 text-left" title="Freshly dropped domain - was registered but not renewed">
|
||||
Domain
|
||||
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_140px] gap-4 px-5 py-3 text-[10px] font-mono text-white/30 uppercase tracking-[0.2em] border-b border-white/[0.08] bg-white/[0.02]">
|
||||
<button onClick={() => handleSort('domain')} className="flex items-center gap-2 hover:text-white transition-colors text-left group">
|
||||
<span className={clsx(sortField === 'domain' && "text-accent font-bold")}>Domain Name</span>
|
||||
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||
</button>
|
||||
<button onClick={() => handleSort('length')} className="flex items-center gap-1 justify-center hover:text-white/60" title="Character count - shorter domains are more valuable">
|
||||
Len
|
||||
{sortField === 'length' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||
<button onClick={() => handleSort('length')} className="flex items-center gap-2 justify-center hover:text-white transition-colors group">
|
||||
<span className={clsx(sortField === 'length' && "text-accent font-bold")}>Length</span>
|
||||
{sortField === 'length' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||
</button>
|
||||
<button onClick={() => handleSort('date')} className="flex items-center gap-1 justify-center hover:text-white/60" title="When the domain was detected as dropped">
|
||||
When
|
||||
{sortField === 'date' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||
<button onClick={() => handleSort('date')} className="flex items-center gap-2 justify-center hover:text-white transition-colors group">
|
||||
<span className={clsx(sortField === 'date' && "text-accent font-bold")}>Dropped</span>
|
||||
{sortField === 'date' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||
</button>
|
||||
<div className="text-right">Actions</div>
|
||||
<div className="text-right pr-2">Actions</div>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-white/[0.04]">
|
||||
{sortedItems.map((item) => (
|
||||
<div key={`${item.domain}.${item.tld}`} className="bg-[#020202] hover:bg-white/[0.02] transition-all">
|
||||
<div key={`${item.domain}.${item.tld}`} className="bg-[#020202] hover:bg-white/[0.02] transition-all group">
|
||||
{/* Mobile Row */}
|
||||
<div className="lg:hidden p-3">
|
||||
<div className="flex items-center justify-between gap-3 mb-2">
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
<button onClick={() => openAnalyze(`${item.domain}.${item.tld}`)} className="text-sm font-bold text-white font-mono truncate text-left">
|
||||
{item.domain}<span className="text-white/40">.{item.tld}</span>
|
||||
<div className="lg:hidden p-4">
|
||||
<div className="flex items-center justify-between gap-3 mb-4">
|
||||
<div className="flex flex-col min-w-0">
|
||||
<button
|
||||
onClick={() => openAnalyze(`${item.domain}.${item.tld}`)}
|
||||
className="text-base font-bold text-white font-mono truncate text-left tracking-tight"
|
||||
>
|
||||
{item.domain}<span className="text-white/30">.{item.tld}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<div className="flex items-center gap-3 mt-1.5">
|
||||
<span className={clsx(
|
||||
"text-[10px] font-mono font-bold px-1.5 py-0.5",
|
||||
item.length <= 5 ? "text-accent bg-accent/10" : "text-white/40 bg-white/5"
|
||||
"text-[9px] font-mono font-bold px-2 py-0.5 border",
|
||||
item.length <= 5 ? "text-accent border-accent/20 bg-accent/5" : "text-white/30 border-white/5 bg-white/5"
|
||||
)}>
|
||||
{item.length}
|
||||
LEN: {item.length}
|
||||
</span>
|
||||
<span className="text-[10px] font-mono text-white/30">{formatTime(item.dropped_date)}</span>
|
||||
<span className="text-[10px] font-mono text-white/20 uppercase tracking-wider">{formatTime(item.dropped_date)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -440,94 +448,104 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
<button
|
||||
onClick={() => track(`${item.domain}.${item.tld}`)}
|
||||
disabled={tracking === `${item.domain}.${item.tld}`}
|
||||
className="flex-1 py-2 text-[10px] font-bold uppercase tracking-wider border border-white/[0.08] text-white/40 flex items-center justify-center gap-1.5"
|
||||
className="flex-1 h-10 text-[10px] font-bold uppercase tracking-widest border border-white/10 text-white/40 flex items-center justify-center gap-2 hover:bg-white/5 active:scale-95 transition-all"
|
||||
>
|
||||
{tracking === `${item.domain}.${item.tld}` ? <Loader2 className="w-3 h-3 animate-spin" /> : <Eye className="w-3 h-3" />}
|
||||
{tracking === `${item.domain}.${item.tld}` ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-4 h-4" />}
|
||||
Track
|
||||
</button>
|
||||
<button onClick={() => openAnalyze(`${item.domain}.${item.tld}`)} className="w-10 py-2 border border-white/[0.08] text-white/50 flex items-center justify-center">
|
||||
<Shield className="w-3.5 h-3.5" />
|
||||
<button
|
||||
onClick={() => openAnalyze(`${item.domain}.${item.tld}`)}
|
||||
className="w-12 h-10 border border-white/10 text-white/40 flex items-center justify-center hover:text-accent hover:border-accent/20 hover:bg-accent/5 transition-all"
|
||||
>
|
||||
<Shield className="w-4.5 h-4.5" />
|
||||
</button>
|
||||
<a
|
||||
href={`https://www.namecheap.com/domains/registration/results/?domain=${item.domain}.${item.tld}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-1 py-2 bg-accent text-black text-[10px] font-bold uppercase flex items-center justify-center gap-1"
|
||||
className="flex-1 h-10 bg-accent text-black text-[10px] font-black uppercase tracking-widest flex items-center justify-center gap-1.5 hover:bg-white active:scale-95 transition-all"
|
||||
>
|
||||
Get <ExternalLink className="w-3 h-3" />
|
||||
Buy Now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop Row */}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_60px_80px_100px] gap-4 items-center p-4 group">
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_140px] gap-4 items-center px-5 py-3.5">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<button
|
||||
onClick={() => openAnalyze(`${item.domain}.${item.tld}`)}
|
||||
className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left"
|
||||
className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left tracking-tight"
|
||||
>
|
||||
{item.domain}<span className="text-white/40 group-hover:text-accent/60">.{item.tld}</span>
|
||||
{item.domain}<span className="text-white/30 group-hover:text-accent/40">.{item.tld}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<span className={clsx(
|
||||
"text-[10px] font-mono font-bold px-1.5 py-0.5",
|
||||
item.length <= 5 ? "text-accent bg-accent/10" : item.length <= 8 ? "text-amber-400 bg-amber-400/10" : "text-white/40 bg-white/5"
|
||||
"text-[10px] font-mono font-bold px-2 py-0.5 border",
|
||||
item.length <= 5 ? "text-accent border-accent/20 bg-accent/5" : item.length <= 8 ? "text-amber-400 border-amber-400/20 bg-amber-400/5" : "text-white/30 border-white/5 bg-white/5"
|
||||
)}>
|
||||
{item.length}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<span className="text-[10px] font-mono text-white/50">{formatTime(item.dropped_date)}</span>
|
||||
<span className="text-[10px] font-mono text-white/40 uppercase tracking-wider">{formatTime(item.dropped_date)}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-1.5 opacity-50 group-hover:opacity-100 transition-opacity">
|
||||
<div className="flex items-center justify-end gap-1.5 opacity-0 group-hover:opacity-100 transition-all duration-300 transform translate-x-2 group-hover:translate-x-0">
|
||||
<button
|
||||
onClick={() => track(`${item.domain}.${item.tld}`)}
|
||||
disabled={tracking === `${item.domain}.${item.tld}`}
|
||||
className="w-6 h-6 flex items-center justify-center border border-white/10 text-white/30 hover:text-white hover:bg-white/5"
|
||||
className="w-8.5 h-8.5 flex items-center justify-center border border-white/10 text-white/30 hover:text-white hover:bg-white/5 transition-all"
|
||||
title="Add to Watchlist"
|
||||
>
|
||||
{tracking === `${item.domain}.${item.tld}` ? <Loader2 className="w-3 h-3 animate-spin" /> : <Eye className="w-3 h-3" />}
|
||||
{tracking === `${item.domain}.${item.tld}` ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-4 h-4" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => openAnalyze(`${item.domain}.${item.tld}`)}
|
||||
className="w-6 h-6 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/20 hover:bg-accent/10"
|
||||
className="w-8.5 h-8.5 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
||||
title="Deep Analysis"
|
||||
>
|
||||
<Shield className="w-3 h-3" />
|
||||
<Shield className="w-4 h-4" />
|
||||
</button>
|
||||
<a
|
||||
href={`https://www.namecheap.com/domains/registration/results/?domain=${item.domain}.${item.tld}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="h-6 px-2 bg-accent text-black text-[10px] font-bold flex items-center gap-1 hover:bg-white"
|
||||
className="h-8.5 px-4 bg-accent text-black text-[10px] font-black uppercase tracking-widest flex items-center gap-1.5 hover:bg-white transition-all shadow-[0_0_15px_-5px_rgba(34,211,126,0.4)]"
|
||||
>
|
||||
Get
|
||||
Buy
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-center gap-1 pt-2">
|
||||
<div className="flex items-center justify-center gap-1 pt-6">
|
||||
<button
|
||||
onClick={() => handlePageChange(page - 1)}
|
||||
disabled={page === 1}
|
||||
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
className="w-10 h-10 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-20 disabled:cursor-not-allowed transition-all"
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
<span className="text-xs text-white/50 font-mono px-3">{page}/{totalPages}</span>
|
||||
<div className="flex items-center bg-white/[0.02] border border-white/[0.08] px-5 h-10">
|
||||
<span className="text-[11px] text-white/40 font-mono uppercase tracking-widest">
|
||||
Page <span className="text-white font-bold">{page}</span> / {totalPages}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handlePageChange(page + 1)}
|
||||
disabled={page === totalPages}
|
||||
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
className="w-10 h-10 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-20 disabled:cursor-not-allowed transition-all"
|
||||
>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -298,7 +298,7 @@ export function SearchTab({ showToast }: SearchTabProps) {
|
||||
onClick={() => handleSearch(searchQuery)}
|
||||
disabled={!searchQuery.trim()}
|
||||
className={clsx(
|
||||
"h-full px-4 py-4 text-sm font-bold uppercase tracking-wider transition-all",
|
||||
"h-full px-6 py-4 text-xs font-bold uppercase tracking-widest transition-all shrink-0 border-l border-white/10",
|
||||
searchQuery.trim() ? "bg-accent text-black hover:bg-white" : "bg-white/5 text-white/20"
|
||||
)}
|
||||
>
|
||||
@ -308,115 +308,122 @@ export function SearchTab({ showToast }: SearchTabProps) {
|
||||
</div>
|
||||
|
||||
{/* Stats Bar */}
|
||||
<div className="flex items-center justify-between text-[10px] font-mono text-white/40">
|
||||
<span>Enter a name (checks 10 TLDs) or full domain (e.g. example.com)</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Sparkles className="w-3 h-3" />
|
||||
RDAP/WHOIS
|
||||
</span>
|
||||
<div className="flex items-center justify-between px-1 text-[10px] font-mono text-white/30 uppercase tracking-[0.1em]">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-1 h-1 bg-accent rounded-full animate-pulse" />
|
||||
<span>Enter a name or full domain</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Sparkles className="w-3 h-3 text-accent/60" />
|
||||
<span>RDAP/WHOIS READY</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Single Domain Result */}
|
||||
{searchMode === 'single' && searchResult && (
|
||||
<div className="animate-in fade-in slide-in-from-bottom-2 duration-200">
|
||||
<div className="animate-in fade-in slide-in-from-bottom-2 duration-300">
|
||||
{searchResult.loading ? (
|
||||
<div className="flex items-center justify-center gap-3 py-12 border border-white/[0.08] bg-white/[0.02]">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-accent" />
|
||||
<span className="text-sm text-white/50 font-mono">Checking availability...</span>
|
||||
<div className="flex flex-col items-center justify-center py-20 border border-white/[0.08] bg-white/[0.01]">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 bg-accent/20 blur-xl rounded-full animate-pulse" />
|
||||
<Loader2 className="w-8 h-8 animate-spin text-accent relative z-10" />
|
||||
</div>
|
||||
<span className="text-[10px] text-white/40 font-mono mt-4 uppercase tracking-[0.2em]">Checking global availability...</span>
|
||||
</div>
|
||||
) : searchResult.error ? (
|
||||
// Error state (invalid TLD, check failed, etc.)
|
||||
<div className="border-2 border-rose-500/30 overflow-hidden bg-[#020202]">
|
||||
<div className="p-4">
|
||||
// Error state
|
||||
<div className="border border-rose-500/30 bg-rose-500/[0.02]">
|
||||
<div className="p-4 lg:p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 flex items-center justify-center border border-rose-500/30 bg-rose-500/10 shrink-0">
|
||||
<div className="w-14 h-14 flex items-center justify-center border border-rose-500/30 bg-rose-500/10 shrink-0">
|
||||
<XCircle className="w-6 h-6 text-rose-500" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-lg font-bold text-white font-mono truncate">{searchResult.domain}</div>
|
||||
<div className="text-sm text-rose-400 mt-1">{searchResult.error}</div>
|
||||
<div className="text-xl font-bold text-white font-mono truncate tracking-tight">{searchResult.domain}</div>
|
||||
<div className="text-[11px] font-mono text-rose-400 mt-1 uppercase tracking-wider">{searchResult.error}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={clsx(
|
||||
"border-2 overflow-hidden bg-[#020202]",
|
||||
searchResult.is_available ? "border-accent/40" : "border-rose-500/30"
|
||||
"border bg-[#020202] transition-all duration-500",
|
||||
searchResult.is_available ? "border-accent/30 shadow-[0_0_40px_-20px_rgba(34,211,126,0.2)]" : "border-rose-500/20"
|
||||
)}>
|
||||
{/* Result Row */}
|
||||
<div className="p-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-4 min-w-0 flex-1">
|
||||
{/* Status Icon */}
|
||||
<div className="p-4 lg:p-6">
|
||||
<div className="flex flex-col lg:grid lg:grid-cols-[1fr_auto] gap-6 items-center">
|
||||
<div className="flex items-center gap-5 min-w-0 w-full">
|
||||
{/* Status Indicator */}
|
||||
<div className={clsx(
|
||||
"w-12 h-12 flex items-center justify-center border shrink-0",
|
||||
searchResult.is_available ? "bg-accent/10 border-accent/30" : "bg-rose-500/10 border-rose-500/30"
|
||||
"w-16 h-16 flex items-center justify-center border shrink-0 transition-colors duration-500",
|
||||
searchResult.is_available ? "bg-accent/10 border-accent/20" : "bg-rose-500/5 border-rose-500/20"
|
||||
)}>
|
||||
{searchResult.is_available ? (
|
||||
<CheckCircle2 className="w-6 h-6 text-accent" />
|
||||
<CheckCircle2 className="w-8 h-8 text-accent" />
|
||||
) : (
|
||||
<XCircle className="w-6 h-6 text-rose-500" />
|
||||
<XCircle className="w-8 h-8 text-rose-500" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Domain Info */}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className={clsx(
|
||||
"text-lg font-bold font-mono truncate",
|
||||
"text-2xl lg:text-3xl font-bold font-mono truncate tracking-tight",
|
||||
searchResult.is_available ? "text-white" : "text-rose-400"
|
||||
)}>{searchResult.domain}</div>
|
||||
<div className="flex items-center gap-3 text-[10px] font-mono text-white/40 mt-1">
|
||||
)}>
|
||||
{searchResult.domain}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-y-2 gap-x-4 mt-2">
|
||||
<span className={clsx(
|
||||
"px-2 py-0.5 uppercase font-bold",
|
||||
searchResult.is_available ? "bg-accent/20 text-accent" : "bg-rose-500/20 text-rose-400"
|
||||
"px-2 py-0.5 text-[9px] font-bold uppercase tracking-widest border",
|
||||
searchResult.is_available
|
||||
? "bg-accent/10 border-accent/20 text-accent"
|
||||
: "bg-rose-500/10 border-rose-500/20 text-rose-400"
|
||||
)}>
|
||||
{searchResult.is_available ? 'Available' : 'Taken'}
|
||||
</span>
|
||||
|
||||
{searchResult.registrar && (
|
||||
<>
|
||||
<span className="text-white/10">|</span>
|
||||
<span className="flex items-center gap-1" title="Current registrar holding this domain">
|
||||
<div className="flex items-center gap-1.5 text-[10px] font-mono text-white/30 uppercase tracking-wider">
|
||||
<Building className="w-3 h-3" />
|
||||
{searchResult.registrar}
|
||||
</span>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{searchResult.expiration_date && (
|
||||
<>
|
||||
<span className="text-white/10">|</span>
|
||||
<span className="flex items-center gap-1" title="Domain expiration date - monitor for potential drop">
|
||||
<Calendar className="w-3 h-3" />
|
||||
Expires {new Date(searchResult.expiration_date).toLocaleDateString()}
|
||||
</span>
|
||||
</>
|
||||
<div className="flex items-center gap-1.5 text-[10px] font-mono text-white/30 uppercase tracking-wider">
|
||||
<Clock className="w-3 h-3" />
|
||||
Expires {new Date(searchResult.expiration_date).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<div className="flex items-center gap-2 w-full lg:w-auto">
|
||||
<button
|
||||
onClick={() => openAnalyze(searchResult.domain)}
|
||||
className="w-9 h-9 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/20 hover:bg-accent/10 transition-colors"
|
||||
title="Deep Analysis - View SEO metrics, backlinks, and valuation"
|
||||
className="flex-1 lg:w-12 lg:h-12 h-12 flex items-center justify-center border border-white/10 text-white/30 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
||||
title="Deep Analysis"
|
||||
>
|
||||
<Shield className="w-4 h-4" />
|
||||
<Shield className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleAddToWatchlist}
|
||||
disabled={addingToWatchlist}
|
||||
className={clsx(
|
||||
"w-9 h-9 flex items-center justify-center border transition-colors",
|
||||
"flex-1 lg:w-12 lg:h-12 h-12 flex items-center justify-center border transition-all",
|
||||
searchResult.is_available
|
||||
? "border-white/10 text-white/30 hover:text-white hover:bg-white/5"
|
||||
: "border-rose-500/30 text-rose-400 hover:bg-rose-500/10"
|
||||
: "border-rose-500/20 text-rose-400/40 hover:text-rose-400 hover:bg-rose-500/5"
|
||||
)}
|
||||
title={searchResult.is_available ? "Add to watchlist to track this domain" : "Monitor this domain and get notified when it drops"}
|
||||
title={searchResult.is_available ? "Track domain" : "Monitor drop"}
|
||||
>
|
||||
{addingToWatchlist ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
|
||||
{addingToWatchlist ? <Loader2 className="w-5 h-5 animate-spin" /> : <Eye className="w-5 h-5" />}
|
||||
</button>
|
||||
|
||||
{searchResult.is_available ? (
|
||||
@ -424,20 +431,18 @@ export function SearchTab({ showToast }: SearchTabProps) {
|
||||
href={`https://www.namecheap.com/domains/registration/results/?domain=${searchResult.domain}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="h-9 px-4 bg-accent text-black text-xs font-bold uppercase tracking-wider flex items-center gap-2 hover:bg-white transition-colors"
|
||||
title="Register this domain now via Namecheap"
|
||||
className="flex-[2] lg:flex-none h-12 px-8 bg-accent text-black text-[11px] font-black uppercase tracking-[0.1em] flex items-center justify-center gap-2 hover:bg-white transition-all shadow-[0_0_20px_-10px_rgba(34,211,126,0.5)]"
|
||||
>
|
||||
Register
|
||||
<ArrowRight className="w-3.5 h-3.5" />
|
||||
Buy Now
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</a>
|
||||
) : (
|
||||
<button
|
||||
onClick={handleAddToWatchlist}
|
||||
disabled={addingToWatchlist}
|
||||
className="h-9 px-4 bg-rose-500/20 text-rose-400 text-xs font-bold uppercase tracking-wider flex items-center gap-2 hover:bg-rose-500/30 border border-rose-500/30 transition-colors"
|
||||
title="Add to watchlist and get notified when this domain becomes available"
|
||||
className="flex-[2] lg:flex-none h-12 px-8 bg-rose-500/10 text-rose-400 text-[11px] font-bold uppercase tracking-[0.1em] flex items-center justify-center gap-2 hover:bg-rose-500/20 border border-rose-500/20 transition-all"
|
||||
>
|
||||
{addingToWatchlist ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-3.5 h-3.5" />}
|
||||
{addingToWatchlist ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
|
||||
Monitor
|
||||
</button>
|
||||
)}
|
||||
@ -451,39 +456,44 @@ export function SearchTab({ showToast }: SearchTabProps) {
|
||||
|
||||
{/* Multi-TLD Results */}
|
||||
{searchMode === 'multi' && tldResults.length > 0 && (
|
||||
<div className="animate-in fade-in slide-in-from-bottom-2 duration-200">
|
||||
<div className="border border-white/[0.08] bg-[#020202] overflow-hidden">
|
||||
<div className="animate-in fade-in slide-in-from-bottom-2 duration-400">
|
||||
<div className="border border-white/[0.08] bg-[#020202]">
|
||||
{/* Header */}
|
||||
<div className="px-4 py-3 border-b border-white/[0.08] flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe className="w-4 h-4 text-accent" />
|
||||
<span className="text-[10px] font-mono text-white/40 uppercase tracking-wider">
|
||||
Checking {POPULAR_TLDS.length} extensions for "{searchQuery.toLowerCase().replace(/\s+/g, '')}"
|
||||
<div className="px-5 py-4 border-b border-white/[0.08] flex items-center justify-between bg-white/[0.01]">
|
||||
<div className="flex items-center gap-3">
|
||||
<Globe className="w-4 h-4 text-accent/60" />
|
||||
<span className="text-[10px] font-mono text-white/50 uppercase tracking-[0.2em]">
|
||||
Global availability check: <span className="text-white">"{searchQuery}"</span>
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-accent">
|
||||
{tldResults.filter(r => r.is_available === true).length} available
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-accent" />
|
||||
<span className="text-[10px] font-mono text-accent uppercase font-bold">
|
||||
{tldResults.filter(r => r.is_available === true).length} Free
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-rose-500/40" />
|
||||
<span className="text-[10px] font-mono text-white/20 uppercase">
|
||||
{tldResults.filter(r => r.is_available === false).length} Taken
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TLD Grid */}
|
||||
<div className="p-3 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-2">
|
||||
<div className="p-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
|
||||
{tldResults.map((result) => (
|
||||
<div
|
||||
key={result.tld}
|
||||
title={result.loading
|
||||
? `Checking ${result.domain}...`
|
||||
: result.is_available
|
||||
? `${result.domain} is available! Click to see details.`
|
||||
: `${result.domain} is already registered`
|
||||
}
|
||||
className={clsx(
|
||||
"p-3 border transition-all",
|
||||
"relative p-4 border transition-all duration-300 group",
|
||||
result.loading
|
||||
? "border-white/[0.08] bg-white/[0.02]"
|
||||
? "border-white/[0.05] bg-white/[0.01]"
|
||||
: result.is_available
|
||||
? "border-accent/40 bg-accent/[0.05] hover:bg-accent/10 cursor-pointer"
|
||||
: "border-rose-500/20 bg-rose-500/[0.02]"
|
||||
? "border-accent/20 bg-accent/[0.02] hover:border-accent/60 hover:bg-accent/[0.08] cursor-pointer"
|
||||
: "border-white/[0.05] bg-white/[0.01] opacity-60"
|
||||
)}
|
||||
onClick={() => {
|
||||
if (result.is_available && !result.loading) {
|
||||
@ -495,30 +505,38 @@ export function SearchTab({ showToast }: SearchTabProps) {
|
||||
}}
|
||||
>
|
||||
{result.loading ? (
|
||||
<div className="flex items-center justify-center py-1">
|
||||
<Loader2 className="w-4 h-4 animate-spin text-white/30" />
|
||||
<div className="flex items-center justify-center py-2">
|
||||
<Loader2 className="w-5 h-5 animate-spin text-white/10" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className={clsx(
|
||||
"text-xs font-mono font-bold",
|
||||
result.is_available ? "text-accent" : "text-rose-400/60"
|
||||
"text-sm font-mono font-black tracking-tight",
|
||||
result.is_available ? "text-white group-hover:text-accent" : "text-white/20"
|
||||
)}>
|
||||
.{result.tld}
|
||||
</span>
|
||||
{result.is_available ? (
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-accent" />
|
||||
<div className="w-5 h-5 rounded-full bg-accent/10 border border-accent/20 flex items-center justify-center">
|
||||
<CheckCircle2 className="w-3 h-3 text-accent" />
|
||||
</div>
|
||||
) : (
|
||||
<XCircle className="w-3.5 h-3.5 text-rose-400/40" />
|
||||
<XCircle className="w-4 h-4 text-white/10" />
|
||||
)}
|
||||
</div>
|
||||
<div className={clsx(
|
||||
"text-[9px] font-mono uppercase",
|
||||
result.is_available ? "text-accent/60" : "text-rose-400/40"
|
||||
"text-[9px] font-mono uppercase tracking-[0.1em] font-bold",
|
||||
result.is_available ? "text-accent" : "text-white/20"
|
||||
)}>
|
||||
{result.is_available ? 'Available' : 'Taken'}
|
||||
</div>
|
||||
|
||||
{result.is_available && (
|
||||
<div className="absolute bottom-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<ArrowRight className="w-3 h-3 text-accent" />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@ -526,8 +544,8 @@ export function SearchTab({ showToast }: SearchTabProps) {
|
||||
</div>
|
||||
|
||||
{/* Footer hint */}
|
||||
<div className="px-4 py-2 border-t border-white/[0.06] text-[9px] font-mono text-white/30 text-center">
|
||||
Click an available extension to see details • Add ".tld" to your search for specific extension
|
||||
<div className="px-4 py-3 border-t border-white/[0.04] bg-white/[0.01] text-[9px] font-mono text-white/20 text-center uppercase tracking-widest">
|
||||
Click an available extension to analyze and buy
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -535,23 +553,23 @@ export function SearchTab({ showToast }: SearchTabProps) {
|
||||
|
||||
{/* Recent Searches */}
|
||||
{!searchResult && recentSearches.length > 0 && (
|
||||
<div className="border border-white/[0.08] bg-white/[0.02]">
|
||||
<div className="px-4 py-3 border-b border-white/[0.08] flex items-center justify-between">
|
||||
<div className="border border-white/[0.08] bg-white/[0.01]">
|
||||
<div className="px-5 py-3 border-b border-white/[0.08] flex items-center justify-between bg-white/[0.01]">
|
||||
<div className="flex items-center gap-2">
|
||||
<History className="w-4 h-4 text-white/30" />
|
||||
<span className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Recent Searches</span>
|
||||
<History className="w-4 h-4 text-white/20" />
|
||||
<span className="text-[10px] font-mono text-white/40 uppercase tracking-[0.2em]">Recent History</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setRecentSearches([])
|
||||
localStorage.removeItem('pounce_recent_searches')
|
||||
}}
|
||||
className="text-[10px] font-mono text-white/30 hover:text-white transition-colors"
|
||||
className="text-[10px] font-mono text-white/20 hover:text-white transition-colors uppercase tracking-widest"
|
||||
>
|
||||
Clear
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<div className="p-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{recentSearches.map((domain) => (
|
||||
<button
|
||||
@ -560,9 +578,9 @@ export function SearchTab({ showToast }: SearchTabProps) {
|
||||
setSearchQuery(domain)
|
||||
handleSearch(domain)
|
||||
}}
|
||||
className="group px-3 py-2 border border-white/[0.08] bg-[#020202] hover:border-accent/30 hover:bg-accent/[0.03] transition-all"
|
||||
className="group px-4 py-2 border border-white/[0.08] bg-white/[0.01] hover:border-accent/30 hover:bg-accent/[0.03] transition-all"
|
||||
>
|
||||
<span className="text-xs font-mono text-white/60 group-hover:text-accent transition-colors">{domain}</span>
|
||||
<span className="text-xs font-mono text-white/40 group-hover:text-accent transition-colors">{domain}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -159,60 +159,60 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
|
||||
}
|
||||
}
|
||||
|
||||
const currentGeo = GEOS.find(g => g.code === geo)
|
||||
const availableResults = results.filter(r => r.available)
|
||||
const takenResults = results.filter(r => !r.available)
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* HEADER */}
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 pb-4 border-b border-white/10">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-5 border-b border-white/[0.08]">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-orange-500/10 border border-orange-500/20 flex items-center justify-center shrink-0">
|
||||
<Flame className="w-6 h-6 text-orange-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-white flex items-center gap-2">
|
||||
<Flame className="w-5 h-5 text-orange-400" />
|
||||
Trend Surfer
|
||||
</h2>
|
||||
<p className="text-sm text-white/60 mt-1">
|
||||
Find domains for trending topics • {currentGeo?.flag} {currentGeo?.name}
|
||||
</p>
|
||||
<h2 className="text-xl font-bold text-white font-mono tracking-tight">Trend Surfer</h2>
|
||||
<p className="text-[10px] font-mono text-white/40 uppercase tracking-widest mt-1">Ride the viral wave with AI-powered domain hunt</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative">
|
||||
<select
|
||||
value={geo}
|
||||
onChange={(e) => { setGeo(e.target.value); setSelected(null); setKeywords([]); setResults([]) }}
|
||||
className="h-10 px-3 bg-white/5 border border-white/10 text-sm text-white outline-none"
|
||||
className="h-10 pl-4 pr-10 bg-white/[0.02] border border-white/10 text-xs font-mono text-white uppercase tracking-widest appearance-none outline-none focus:border-accent/30 transition-all cursor-pointer"
|
||||
>
|
||||
{GEOS.map(g => (
|
||||
<option key={g.code} value={g.code}>{g.flag} {g.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-white/30 pointer-events-none" />
|
||||
</div>
|
||||
<button
|
||||
onClick={loadTrends}
|
||||
disabled={loading}
|
||||
className="h-10 w-10 flex items-center justify-center border border-white/10 text-white/60 hover:text-white hover:bg-white/5"
|
||||
className="h-10 w-10 flex items-center justify-center border border-white/10 text-white/40 hover:text-white hover:bg-white/5 transition-all"
|
||||
>
|
||||
<RefreshCw className={clsx("w-4 h-4", loading && "animate-spin")} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* TRENDS GRID */}
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
<div>
|
||||
<p className="text-xs text-white/50 font-mono uppercase tracking-wider mb-3">
|
||||
Select a trending topic
|
||||
</p>
|
||||
{/* Trends Grid */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 px-1 text-[10px] font-mono text-white/30 uppercase tracking-[0.2em]">
|
||||
<div className="w-1 h-1 bg-orange-500 rounded-full animate-pulse" />
|
||||
<span>Real-time Viral Topics ({geo})</span>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
{[...Array(8)].map((_, i) => (
|
||||
<div key={i} className="h-14 bg-white/5 animate-pulse" />
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-2">
|
||||
{[...Array(12)].map((_, i) => (
|
||||
<div key={i} className="h-14 bg-white/[0.01] border border-white/[0.05] animate-pulse" />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-2">
|
||||
{trends.slice(0, 12).map((t, idx) => {
|
||||
const isSelected = selected === t.title
|
||||
return (
|
||||
@ -220,19 +220,22 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
|
||||
key={t.title}
|
||||
onClick={() => selectTrend(t.title)}
|
||||
className={clsx(
|
||||
"relative p-3 text-left border transition-all",
|
||||
"relative px-4 py-3.5 text-left border transition-all duration-300 group overflow-hidden",
|
||||
isSelected
|
||||
? "border-accent bg-accent/10"
|
||||
: "border-white/10 bg-white/[0.02] hover:border-white/20 hover:bg-white/[0.04]"
|
||||
? "border-accent bg-accent/10 shadow-[0_0_20px_-10px_rgba(34,211,126,0.3)]"
|
||||
: "border-white/[0.08] bg-white/[0.01] hover:border-white/20 hover:bg-white/[0.03]"
|
||||
)}
|
||||
>
|
||||
{idx < 3 && <span className="absolute top-1.5 right-1.5 text-[10px]">🔥</span>}
|
||||
<p className={clsx(
|
||||
"text-sm font-medium truncate pr-4",
|
||||
isSelected ? "text-accent" : "text-white"
|
||||
<div className="relative z-10 flex items-center justify-between gap-2">
|
||||
<span className={clsx(
|
||||
"text-xs font-bold font-mono truncate tracking-tight",
|
||||
isSelected ? "text-accent" : "text-white/70 group-hover:text-white"
|
||||
)}>
|
||||
{t.title}
|
||||
</p>
|
||||
</span>
|
||||
{idx < 3 && !isSelected && <Flame className="w-3 h-3 text-orange-500/40 group-hover:text-orange-500 transition-colors" />}
|
||||
</div>
|
||||
{isSelected && <div className="absolute top-0 right-0 w-1.5 h-1.5 bg-accent" />}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
@ -240,84 +243,73 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* KEYWORD BUILDER */}
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* Keyword Builder */}
|
||||
{selected && (
|
||||
<div className="bg-white/[0.02] border border-white/10 p-4 sm:p-5 space-y-5">
|
||||
{/* Selected Trend Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="border border-white/[0.08] bg-white/[0.01] overflow-hidden animate-in fade-in slide-in-from-top-4 duration-500">
|
||||
<div className="px-5 py-4 border-b border-white/[0.08] flex items-center justify-between bg-white/[0.01]">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-accent/10 border border-accent/20 flex items-center justify-center">
|
||||
<Sparkles className="w-4 h-4 text-accent" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-white/50 font-mono uppercase tracking-wider">Building domains for</p>
|
||||
<h3 className="text-lg font-bold text-white">{selected}</h3>
|
||||
<div className="text-[10px] font-mono text-white/30 uppercase tracking-[0.2em]">Target Concept</div>
|
||||
<div className="text-sm font-bold text-white uppercase tracking-wider">{selected}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => { setSelected(null); setKeywords([]); setResults([]) }}
|
||||
className="p-2 text-white/40 hover:text-white"
|
||||
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/30 hover:text-white hover:bg-white/5 transition-all"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-5 space-y-6">
|
||||
{/* Keywords */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<p className="text-xs text-white/60 font-mono uppercase tracking-wider">Keywords to check</p>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[10px] font-mono text-white/30 uppercase tracking-widest">Constructed Keywords</span>
|
||||
{aiLoading && (
|
||||
<span className="flex items-center gap-1 text-[10px] text-purple-400">
|
||||
<span className="flex items-center gap-2 text-[9px] font-mono text-purple-400 uppercase font-bold animate-pulse">
|
||||
<Loader2 className="w-3 h-3 animate-spin" />
|
||||
AI expanding...
|
||||
AI Expansion in progress...
|
||||
</span>
|
||||
)}
|
||||
{hasAI && keywords.length > 1 && !aiLoading && (
|
||||
<span className="flex items-center gap-1 text-[10px] text-purple-400">
|
||||
<Sparkles className="w-3 h-3" />
|
||||
AI expanded
|
||||
</span>
|
||||
)}
|
||||
{!hasAI && (
|
||||
<Link href="/pricing" className="flex items-center gap-1 text-[10px] text-white/40 hover:text-accent">
|
||||
<Lock className="w-3 h-3" />
|
||||
Upgrade for AI
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{keywords.map((kw, idx) => (
|
||||
<span
|
||||
key={kw}
|
||||
className={clsx(
|
||||
"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-mono border",
|
||||
"inline-flex items-center gap-2 px-3 py-1.5 text-xs font-mono border transition-all",
|
||||
idx === 0
|
||||
? "bg-accent/10 border-accent/30 text-accent"
|
||||
: "bg-purple-500/10 border-purple-500/30 text-purple-300"
|
||||
? "bg-accent/10 border-accent/30 text-accent font-bold"
|
||||
: "bg-purple-500/10 border-purple-500/20 text-purple-300"
|
||||
)}
|
||||
>
|
||||
{kw}
|
||||
{keywords.length > 1 && (
|
||||
<button onClick={() => removeKeyword(kw)} className="hover:text-white">
|
||||
<button onClick={() => removeKeyword(kw)} className="text-white/20 hover:text-rose-400 transition-colors">
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
{/* Add custom keyword */}
|
||||
<div className="flex">
|
||||
<div className="relative">
|
||||
<input
|
||||
value={customKw}
|
||||
onChange={(e) => setCustomKw(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && addKeyword()}
|
||||
placeholder="+ add"
|
||||
className="w-20 px-2 py-1.5 bg-transparent border border-white/10 text-sm font-mono text-white placeholder:text-white/30 outline-none focus:border-white/30"
|
||||
placeholder="+ CUSTOM KW"
|
||||
className="w-28 px-3 py-1.5 bg-white/[0.03] border border-white/10 text-[10px] font-mono text-white placeholder:text-white/20 outline-none focus:border-accent/30 uppercase tracking-wider transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TLDs */}
|
||||
<div>
|
||||
<p className="text-xs text-white/60 font-mono uppercase tracking-wider mb-3">TLDs</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="space-y-3">
|
||||
<span className="text-[10px] font-mono text-white/30 uppercase tracking-widest">Selected Extensions</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{TLDS.map(tld => (
|
||||
<button
|
||||
key={tld}
|
||||
@ -325,10 +317,10 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
|
||||
prev.includes(tld) ? prev.filter(t => t !== tld) : [...prev, tld]
|
||||
)}
|
||||
className={clsx(
|
||||
"px-3 py-2 text-sm font-mono border transition-all",
|
||||
"px-3 py-1.5 text-[10px] font-mono border transition-all uppercase tracking-widest",
|
||||
tlds.includes(tld)
|
||||
? "border-accent bg-accent/10 text-accent"
|
||||
: "border-white/10 text-white/50 hover:text-white"
|
||||
? "border-accent bg-accent/10 text-accent font-black"
|
||||
: "border-white/10 text-white/40 hover:text-white hover:bg-white/5"
|
||||
)}
|
||||
>
|
||||
.{tld}
|
||||
@ -342,56 +334,62 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
|
||||
onClick={checkAvailability}
|
||||
disabled={checking || tlds.length === 0 || keywords.length === 0}
|
||||
className={clsx(
|
||||
"w-full py-4 text-sm font-bold uppercase tracking-wider flex items-center justify-center gap-2 transition-all",
|
||||
"w-full py-4 text-[11px] font-black uppercase tracking-[0.2em] flex items-center justify-center gap-3 transition-all",
|
||||
checking || tlds.length === 0 || keywords.length === 0
|
||||
? "bg-white/10 text-white/40"
|
||||
: "bg-accent text-black hover:bg-white"
|
||||
? "bg-white/5 text-white/20 border border-white/5"
|
||||
: "bg-accent text-black hover:bg-white shadow-[0_0_30px_-10px_rgba(34,211,126,0.4)]"
|
||||
)}
|
||||
>
|
||||
{checking ? <Loader2 className="w-4 h-4 animate-spin" /> : <Zap className="w-4 h-4" />}
|
||||
Check {keywords.length} × {tlds.length} = {keywords.length * tlds.length} Domains
|
||||
Check {keywords.length * tlds.length} Variations
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* RESULTS */}
|
||||
{/* ═══════════════════════════════════════════════════════════════════════ */}
|
||||
{/* Results */}
|
||||
{results.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
{/* Available */}
|
||||
{availableResults.length > 0 && (
|
||||
<div>
|
||||
<p className="text-sm text-accent font-mono uppercase tracking-wider mb-3 flex items-center gap-2">
|
||||
<span className="w-2 h-2 bg-accent rounded-full" />
|
||||
{availableResults.length} Available
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<p className="text-[10px] font-mono text-accent uppercase tracking-[0.2em] font-black flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 bg-accent rounded-full animate-pulse" />
|
||||
{availableResults.length} High-Potential Assets Identified
|
||||
</p>
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex gap-4">
|
||||
<button onClick={() => setResults(availableResults.map(r => ({ domain: r.domain, available: true })))} className="text-[9px] font-mono text-white/20 hover:text-accent uppercase tracking-widest transition-colors">
|
||||
Copy List
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
{availableResults.map(r => (
|
||||
<div key={r.domain} className="flex items-center justify-between p-3 bg-accent/5 border border-accent/20 hover:bg-accent/10 transition-colors">
|
||||
<div key={r.domain} className="flex items-center justify-between p-4 bg-accent/[0.02] border border-accent/20 hover:border-accent/40 hover:bg-accent/[0.04] transition-all group">
|
||||
<button
|
||||
onClick={() => openAnalyze(r.domain)}
|
||||
className="text-sm font-mono text-white hover:text-accent truncate"
|
||||
className="text-sm font-bold font-mono text-white group-hover:text-accent truncate tracking-tight transition-colors"
|
||||
>
|
||||
{r.domain}
|
||||
</button>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<button onClick={() => copy(r.domain)} className="w-8 h-8 flex items-center justify-center text-white/50 hover:text-white">
|
||||
<div className="flex items-center gap-1.5 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity">
|
||||
<button onClick={() => copy(r.domain)} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-white hover:bg-white/5" title="Copy">
|
||||
{copied === r.domain ? <Check className="w-3.5 h-3.5 text-accent" /> : <Copy className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
<button onClick={() => track(r.domain)} disabled={tracking === r.domain} className="w-8 h-8 flex items-center justify-center text-white/50 hover:text-white">
|
||||
<button onClick={() => track(r.domain)} disabled={tracking === r.domain} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-white hover:bg-white/5" title="Track">
|
||||
{tracking === r.domain ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Eye className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
<button onClick={() => openAnalyze(r.domain)} className="w-8 h-8 flex items-center justify-center text-white/50 hover:text-accent">
|
||||
<button onClick={() => openAnalyze(r.domain)} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-accent hover:border-accent/20 hover:bg-accent/5" title="Analyze">
|
||||
<Shield className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<a
|
||||
href={`https://www.namecheap.com/domains/registration/results/?domain=${r.domain}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="h-8 px-3 bg-accent text-black text-[10px] font-bold flex items-center gap-1 hover:bg-white"
|
||||
className="h-8 px-3 bg-accent text-black text-[10px] font-black uppercase tracking-widest flex items-center gap-1.5 hover:bg-white"
|
||||
>
|
||||
<ShoppingCart className="w-3 h-3" />
|
||||
Buy
|
||||
</a>
|
||||
</div>
|
||||
@ -404,16 +402,16 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
|
||||
{/* Taken (collapsed) */}
|
||||
{takenResults.length > 0 && (
|
||||
<details className="group">
|
||||
<summary className="text-sm text-white/40 font-mono uppercase tracking-wider cursor-pointer flex items-center gap-2 py-2 hover:text-white/60">
|
||||
<summary className="text-[10px] font-mono text-white/20 uppercase tracking-[0.2em] cursor-pointer flex items-center gap-2 py-3 hover:text-white/40 transition-colors list-none border-t border-white/[0.04]">
|
||||
<ChevronDown className="w-3 h-3 group-open:rotate-180 transition-transform" />
|
||||
{takenResults.length} Taken
|
||||
{takenResults.length} Registered Variations
|
||||
</summary>
|
||||
<div className="space-y-1.5 mt-2">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2 mt-3 animate-in slide-in-from-top-2">
|
||||
{takenResults.map(r => (
|
||||
<div key={r.domain} className="flex items-center justify-between p-2.5 bg-white/[0.02] border border-white/5">
|
||||
<span className="text-sm font-mono text-white/40 truncate">{r.domain}</span>
|
||||
<button onClick={() => openAnalyze(r.domain)} className="text-white/30 hover:text-white">
|
||||
<Shield className="w-3.5 h-3.5" />
|
||||
<div key={r.domain} className="flex items-center justify-between px-3 py-2 bg-white/[0.01] border border-white/[0.05] group">
|
||||
<span className="text-[11px] font-mono text-white/20 truncate group-hover:text-white/40 transition-colors">{r.domain}</span>
|
||||
<button onClick={() => openAnalyze(r.domain)} className="text-white/10 hover:text-accent transition-colors">
|
||||
<Shield className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@ -425,10 +423,12 @@ export function TrendSurferTab({ showToast }: { showToast: (msg: string, type?:
|
||||
|
||||
{/* Empty State */}
|
||||
{!selected && !loading && trends.length > 0 && (
|
||||
<div className="text-center py-12 border border-dashed border-white/10">
|
||||
<Globe className="w-10 h-10 text-white/20 mx-auto mb-3" />
|
||||
<p className="text-white/50">Select a trending topic above</p>
|
||||
<p className="text-sm text-white/30 mt-1">We'll find available domains for you</p>
|
||||
<div className="text-center py-24 border border-dashed border-white/[0.08] bg-white/[0.01]">
|
||||
<Globe className="w-12 h-12 text-white/5 mx-auto mb-4" />
|
||||
<p className="text-white/40 text-sm font-mono uppercase tracking-widest font-bold">Select a trending topic above</p>
|
||||
<p className="text-white/20 text-[10px] font-mono mt-3 uppercase tracking-wider max-w-xs mx-auto leading-relaxed">
|
||||
Our engines will analyze the viral potential and suggest premium assets
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user