feat: Complete redesign of Hunt tabs with award-winning UI
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
- DropsTab: Fixed domain display, added alert banner explaining zone file data - AuctionsTab: Improved table layout, cleaner action buttons - Both: Consistent header stats, unified search, better filters
This commit is contained in:
@ -23,6 +23,10 @@ import {
|
||||
Filter,
|
||||
Shield,
|
||||
Gavel,
|
||||
Clock,
|
||||
DollarSign,
|
||||
Timer,
|
||||
CheckCircle2,
|
||||
} from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
@ -277,130 +281,117 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
|
||||
const activeFiltersCount = [sourceFilter !== 'all', priceRange !== 'all', tldFilter !== 'all', hideSpam].filter(Boolean).length
|
||||
|
||||
// Loading State
|
||||
if (loading && items.length === 0) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<Loader2 className="w-6 h-6 text-accent animate-spin" />
|
||||
<div className="flex flex-col items-center justify-center py-24">
|
||||
<Loader2 className="w-8 h-8 text-accent animate-spin mb-4" />
|
||||
<span className="text-xs font-mono text-white/30 uppercase tracking-widest">Loading marketplace...</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-6">
|
||||
{/* 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 className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-accent/20 to-accent/5 border border-accent/30 flex items-center justify-center">
|
||||
<Gavel className="w-6 h-6 text-accent" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xl font-bold text-white font-mono">
|
||||
<div className="text-2xl font-black text-white font-mono tracking-tight">
|
||||
{stats.total.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Live auctions & fixed price</div>
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase tracking-widest">Live auctions & listings</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"
|
||||
className="p-3 border border-white/10 text-white/40 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
||||
title="Refresh auctions"
|
||||
>
|
||||
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
|
||||
<RefreshCw className={clsx("w-5 h-5", refreshing && "animate-spin")} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className={clsx(
|
||||
"relative border transition-all duration-200",
|
||||
"relative border-2 transition-all duration-200",
|
||||
searchFocused ? "border-accent/50 bg-accent/[0.02]" : "border-white/[0.08] bg-white/[0.02]"
|
||||
)}>
|
||||
<div className="flex items-center">
|
||||
<Search className={clsx("w-4 h-4 ml-3 transition-colors", searchFocused ? "text-accent" : "text-white/30")} />
|
||||
<Search className={clsx("w-5 h-5 ml-4 transition-colors", searchFocused ? "text-accent" : "text-white/30")} />
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onFocus={() => setSearchFocused(true)}
|
||||
onBlur={() => setSearchFocused(false)}
|
||||
placeholder="Search auctions..."
|
||||
className="flex-1 bg-transparent px-3 py-3 text-sm text-white placeholder:text-white/20 outline-none font-mono"
|
||||
placeholder="Search auctions and listings..."
|
||||
className="flex-1 bg-transparent px-4 py-4 text-sm text-white placeholder:text-white/20 outline-none font-mono"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button onClick={() => setSearchQuery('')} className="p-3 text-white/30 hover:text-white transition-colors">
|
||||
<X className="w-4 h-4" />
|
||||
<button onClick={() => setSearchQuery('')} className="p-4 text-white/30 hover:text-white transition-colors">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filter Toggle */}
|
||||
{/* Advanced Filters */}
|
||||
<button
|
||||
onClick={() => setFiltersOpen(!filtersOpen)}
|
||||
className={clsx(
|
||||
"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]"
|
||||
"flex items-center justify-between w-full py-3 px-5 border transition-all",
|
||||
filtersOpen ? "border-accent/30 bg-accent/[0.03]" : "border-white/[0.08] bg-white/[0.02] hover:border-white/20"
|
||||
)}
|
||||
>
|
||||
<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 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 className="flex items-center gap-3">
|
||||
<Filter className={clsx("w-4 h-4", filtersOpen ? "text-accent" : "text-white/40")} />
|
||||
<span className={clsx("text-xs font-mono uppercase tracking-widest", filtersOpen ? "text-accent" : "text-white/50")}>
|
||||
Market Filters
|
||||
</span>
|
||||
{activeFiltersCount > 0 && (
|
||||
<span className="px-2 py-0.5 text-[9px] font-black bg-accent text-black">{activeFiltersCount}</span>
|
||||
)}
|
||||
</div>
|
||||
<ChevronRight className={clsx("w-4 h-4 text-white/30 transition-transform", filtersOpen && "rotate-90")} />
|
||||
<ChevronRight className={clsx("w-4 h-4 transition-transform", filtersOpen ? "rotate-90 text-accent" : "text-white/30")} />
|
||||
</button>
|
||||
|
||||
{/* Filters Panel */}
|
||||
{filtersOpen && (
|
||||
<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-widest mb-2.5">Source</div>
|
||||
<div className="flex gap-2">
|
||||
{[
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'pounce', label: 'Pounce', icon: Diamond },
|
||||
{ value: 'external', label: 'External' },
|
||||
].map((item) => (
|
||||
<button
|
||||
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.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" />}
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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-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}`}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="p-5 border border-white/[0.08] bg-white/[0.01] space-y-5 animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
{/* Source Filter */}
|
||||
<div>
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase tracking-widest mb-3">Source</div>
|
||||
<div className="flex gap-2">
|
||||
{[
|
||||
{ value: 'all', label: 'All Sources' },
|
||||
{ value: 'pounce', label: 'Pounce Direct', icon: Diamond },
|
||||
{ value: 'external', label: 'External' },
|
||||
].map((item) => (
|
||||
<button
|
||||
key={item.value}
|
||||
onClick={() => setSourceFilter(item.value as SourceFilter)}
|
||||
className={clsx(
|
||||
"flex-1 py-3 text-xs font-bold uppercase tracking-wider border transition-all flex items-center justify-center gap-2",
|
||||
sourceFilter === item.value
|
||||
? "border-accent bg-accent/10 text-accent"
|
||||
: "border-white/[0.08] text-white/40 hover:border-white/20"
|
||||
)}
|
||||
>
|
||||
{item.icon && <item.icon className="w-4 h-4" />}
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Price */}
|
||||
{/* Price & TLD */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<div>
|
||||
<div className="text-[9px] font-mono text-white/30 uppercase tracking-widest mb-2.5">Price Range</div>
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase tracking-widest mb-3">Price Range</div>
|
||||
<div className="flex gap-2">
|
||||
{[
|
||||
{ value: 'all', label: 'All' },
|
||||
@ -412,8 +403,10 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
key={item.value}
|
||||
onClick={() => setPriceRange(item.value as PriceRange)}
|
||||
className={clsx(
|
||||
"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"
|
||||
"flex-1 py-3 text-xs font-mono border transition-all",
|
||||
priceRange === item.value
|
||||
? "border-amber-400 bg-amber-400/10 text-amber-400 font-bold"
|
||||
: "border-white/[0.08] text-white/40 hover:border-white/20"
|
||||
)}
|
||||
>
|
||||
{item.label}
|
||||
@ -422,74 +415,116 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
</div>
|
||||
</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-4 border transition-colors",
|
||||
hideSpam ? "border-accent/30 bg-accent/5" : "border-white/[0.08]"
|
||||
)}
|
||||
>
|
||||
<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-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 className="text-[10px] font-mono text-white/40 uppercase tracking-widest mb-3">TLD Filter</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{['all', 'com', 'ai', 'io', 'net', 'ch'].map((tld) => (
|
||||
<button
|
||||
key={tld}
|
||||
onClick={() => setTldFilter(tld)}
|
||||
className={clsx(
|
||||
"px-4 py-3 text-xs font-mono uppercase border transition-all",
|
||||
tldFilter === tld
|
||||
? "border-accent bg-accent/10 text-accent font-bold"
|
||||
: "border-white/[0.08] text-white/40 hover:border-white/20"
|
||||
)}
|
||||
>
|
||||
{tld === 'all' ? 'All' : `.${tld}`}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hide Spam */}
|
||||
<button
|
||||
onClick={() => setHideSpam(!hideSpam)}
|
||||
className={clsx(
|
||||
"flex items-center justify-between w-full py-3 px-4 border transition-all",
|
||||
hideSpam ? "border-accent/30 bg-accent/5" : "border-white/[0.08] hover:border-white/20"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Ban className={clsx("w-4 h-4", hideSpam ? "text-accent" : "text-white/30")} />
|
||||
<span className={clsx("text-xs font-mono uppercase tracking-wider", hideSpam ? "text-accent" : "text-white/50")}>
|
||||
Hide spam domains (numbers, hyphens)
|
||||
</span>
|
||||
</div>
|
||||
<div className={clsx(
|
||||
"w-5 h-5 border flex items-center justify-center transition-all",
|
||||
hideSpam ? "border-accent bg-accent" : "border-white/20"
|
||||
)}>
|
||||
{hideSpam && <CheckCircle2 className="w-3 h-3 text-black" />}
|
||||
</div>
|
||||
</button>
|
||||
</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>}
|
||||
{/* Results Info */}
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<div className="flex items-center gap-3 text-[11px] font-mono text-white/40 uppercase tracking-widest">
|
||||
<div className="w-1.5 h-1.5 bg-accent rounded-full animate-pulse" />
|
||||
<span>{filteredItems.length} active listings</span>
|
||||
{stats.highScore > 0 && (
|
||||
<>
|
||||
<span className="text-white/20">•</span>
|
||||
<span className="text-accent">{stats.highScore} premium</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{totalPages > 1 && (
|
||||
<span className="text-[11px] font-mono text-white/30 uppercase tracking-widest">
|
||||
Page {page} of {totalPages}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Results Table */}
|
||||
{filteredItems.length === 0 ? (
|
||||
<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">
|
||||
<Search className="w-16 h-16 text-white/5 mx-auto mb-6" />
|
||||
<p className="text-white/50 text-sm font-mono uppercase tracking-widest font-bold">No auctions found</p>
|
||||
<p className="text-white/20 text-xs font-mono mt-3 uppercase tracking-wider max-w-sm mx-auto leading-relaxed">
|
||||
Try adjusting your search criteria or filters
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<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_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>
|
||||
<div className="border border-white/[0.08] bg-[#020202] overflow-hidden">
|
||||
{/* Table Header */}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_80px_120px_100px_180px] gap-6 px-6 py-4 text-[10px] font-mono text-white/40 uppercase tracking-[0.15em] 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"
|
||||
>
|
||||
<span className={clsx(sortField === 'domain' && "text-accent font-bold")}>Domain</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-2 justify-center hover:text-white transition-colors group">
|
||||
<button
|
||||
onClick={() => handleSort('score')}
|
||||
className="flex items-center gap-2 justify-center hover:text-white transition-colors"
|
||||
>
|
||||
<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-2 justify-end hover:text-white transition-colors group pr-4">
|
||||
<button
|
||||
onClick={() => handleSort('price')}
|
||||
className="flex items-center gap-2 justify-end hover:text-white transition-colors"
|
||||
>
|
||||
<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-2 justify-center hover:text-white transition-colors group">
|
||||
<span className={clsx(sortField === 'time' && "text-accent font-bold")}>Time</span>
|
||||
<button
|
||||
onClick={() => handleSort('time')}
|
||||
className="flex items-center gap-2 justify-center hover:text-white transition-colors"
|
||||
>
|
||||
<span className={clsx(sortField === 'time' && "text-accent font-bold")}>Ends</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 pr-2">Actions</div>
|
||||
<div className="text-right">Actions</div>
|
||||
</div>
|
||||
|
||||
{/* Table Body */}
|
||||
<div className="divide-y divide-white/[0.04]">
|
||||
{filteredItems.map((item) => {
|
||||
const timeLeftSec = getSecondsUntilEnd(item.end_time)
|
||||
@ -500,28 +535,40 @@ 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 group", isPounce && "bg-accent/[0.01]")}>
|
||||
<div key={item.id} className={clsx("group hover:bg-white/[0.02] transition-all", isPounce && "bg-accent/[0.02]")}>
|
||||
{/* Mobile Row */}
|
||||
<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">
|
||||
<div className="lg:hidden p-5">
|
||||
<div className="flex items-start justify-between gap-4 mb-4">
|
||||
<div className="min-w-0">
|
||||
<button
|
||||
onClick={() => openAnalyze(item.domain)}
|
||||
className="text-lg font-bold text-white font-mono truncate block text-left hover:text-accent transition-colors"
|
||||
>
|
||||
{item.domain}
|
||||
</button>
|
||||
<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 font-bold")}>{isPounce ? 'INSTANT' : displayTime || 'N/A'}</span>
|
||||
<div className="flex items-center gap-2 mt-2 text-[10px] font-mono text-white/30 uppercase tracking-wider">
|
||||
<span className="bg-white/5 px-2 py-0.5 border border-white/5">{item.source}</span>
|
||||
{isPounce && item.verified && <ShieldCheck className="w-3.5 h-3.5 text-accent" />}
|
||||
<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 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"
|
||||
"text-lg font-black font-mono tracking-tight",
|
||||
isPounce ? "text-accent" : "text-white"
|
||||
)}>
|
||||
SCORE: {item.pounce_score}
|
||||
{formatPrice(item.price)}
|
||||
</div>
|
||||
<div className={clsx(
|
||||
"text-[10px] font-mono px-2 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>
|
||||
@ -531,81 +578,118 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
onClick={() => handleTrack(item.domain)}
|
||||
disabled={isTracking}
|
||||
className={clsx(
|
||||
"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"
|
||||
"flex-1 h-12 text-xs 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/50 hover:bg-white/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" />}
|
||||
{isTracking ? <Loader2 className="w-4 h-4 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-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
|
||||
onClick={() => openAnalyze(item.domain)}
|
||||
className="w-14 h-12 border border-white/10 text-white/50 flex items-center justify-center hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
||||
>
|
||||
<Shield className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<a
|
||||
href={item.url}
|
||||
target={isPounce ? '_self' : '_blank'}
|
||||
rel={isPounce ? undefined : 'noopener noreferrer'}
|
||||
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")}
|
||||
className={clsx(
|
||||
"flex-1 h-12 text-xs font-black uppercase tracking-widest flex items-center justify-center gap-2 transition-all",
|
||||
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.5 h-3.5" />}
|
||||
{!isPounce && <ExternalLink className="w-4 h-4" />}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop Row */}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_80px_100px_100px_140px] gap-4 items-center px-5 py-3.5">
|
||||
<div className="hidden lg:grid grid-cols-[1fr_80px_120px_100px_180px] gap-6 items-center px-6 py-4">
|
||||
{/* Domain */}
|
||||
<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">
|
||||
<button
|
||||
onClick={() => openAnalyze(item.domain)}
|
||||
className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left"
|
||||
>
|
||||
{item.domain}
|
||||
</button>
|
||||
<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">
|
||||
<div className="flex items-center gap-2 text-[9px] font-mono text-white/20 uppercase tracking-wider 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>
|
||||
|
||||
{/* Score */}
|
||||
<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"
|
||||
"text-xs font-mono font-bold px-3 py-1 border inline-block",
|
||||
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="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>
|
||||
{/* Price */}
|
||||
<div className="text-right">
|
||||
<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-wider">
|
||||
{item.price_type === 'bid' ? 'Current Bid' : 'Buy Now'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Time */}
|
||||
<div className="text-center">
|
||||
{isPounce ? (
|
||||
<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" />
|
||||
<span className="text-xs text-accent font-mono font-bold flex items-center justify-center gap-1.5 uppercase tracking-wider">
|
||||
<Zap className="w-3.5 h-3.5" />
|
||||
Instant
|
||||
</span>
|
||||
) : (
|
||||
<span className={clsx("text-[10px] font-mono uppercase tracking-widest", isUrgent ? "text-orange-400 font-bold" : "text-white/40")}>{displayTime || 'N/A'}</span>
|
||||
<span className={clsx(
|
||||
"text-xs font-mono uppercase tracking-wider flex items-center justify-center gap-1.5",
|
||||
isUrgent ? "text-orange-400 font-bold" : "text-white/40"
|
||||
)}>
|
||||
{isUrgent && <Timer className="w-3.5 h-3.5" />}
|
||||
{displayTime || 'N/A'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<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">
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-end gap-2 opacity-40 group-hover:opacity-100 transition-all">
|
||||
<button
|
||||
onClick={() => handleTrack(item.domain)}
|
||||
disabled={isTracking}
|
||||
className={clsx(
|
||||
"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"
|
||||
"w-10 h-10 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/50 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-4 h-4" /> : <EyeOff className="w-4 h-4" />}
|
||||
{isTracking ? <Loader2 className="w-4 h-4 animate-spin" /> : isTracked ? <Eye className="w-4 h-4" /> : <EyeOff className="w-4 h-4" />}
|
||||
</button>
|
||||
|
||||
<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">
|
||||
<button
|
||||
onClick={() => openAnalyze(item.domain)}
|
||||
className="w-10 h-10 flex items-center justify-center border border-white/10 text-white/50 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
||||
title="Analyze"
|
||||
>
|
||||
<Shield className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
@ -613,9 +697,15 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
href={item.url}
|
||||
target={isPounce ? '_self' : '_blank'}
|
||||
rel={isPounce ? undefined : 'noopener noreferrer'}
|
||||
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")}
|
||||
className={clsx(
|
||||
"h-10 px-5 flex items-center gap-2 text-[10px] font-black uppercase tracking-widest transition-all",
|
||||
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.5 h-3.5" />}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -627,23 +717,23 @@ export function AuctionsTab({ showToast }: AuctionsTabProps) {
|
||||
|
||||
{/* Pagination */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-center gap-1 pt-6">
|
||||
<div className="flex items-center justify-center gap-2 pt-4">
|
||||
<button
|
||||
onClick={() => handlePageChange(page - 1)}
|
||||
disabled={page === 1}
|
||||
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"
|
||||
className="w-12 h-12 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-5 h-5" />
|
||||
</button>
|
||||
<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}
|
||||
<div className="flex items-center bg-white/[0.02] border border-white/[0.08] px-6 h-12">
|
||||
<span className="text-xs text-white/50 font-mono uppercase tracking-widest">
|
||||
Page <span className="text-white font-bold mx-1">{page}</span> / {totalPages}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handlePageChange(page + 1)}
|
||||
disabled={page === totalPages}
|
||||
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"
|
||||
className="w-12 h-12 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-5 h-5" />
|
||||
</button>
|
||||
|
||||
@ -21,6 +21,9 @@ import {
|
||||
Filter,
|
||||
Ban,
|
||||
Hash,
|
||||
CheckCircle2,
|
||||
AlertCircle,
|
||||
Clock,
|
||||
} from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
@ -43,8 +46,7 @@ interface ZoneStats {
|
||||
daily_drops: number
|
||||
}
|
||||
|
||||
// All supported TLDs
|
||||
type SupportedTld = 'ch' | 'li' | 'xyz' | 'org' | 'online' | 'info' | 'dev' | 'app'
|
||||
type SupportedTld = 'ch' | 'li' | 'xyz' | 'org' | 'online' | 'info' | 'dev' | 'app' | 'club' | 'biz'
|
||||
|
||||
const ALL_TLDS: { tld: SupportedTld; flag: string }[] = [
|
||||
{ tld: 'ch', flag: '🇨🇭' },
|
||||
@ -55,6 +57,8 @@ const ALL_TLDS: { tld: SupportedTld; flag: string }[] = [
|
||||
{ tld: 'info', flag: 'ℹ️' },
|
||||
{ tld: 'dev', flag: '👨💻' },
|
||||
{ tld: 'app', flag: '📱' },
|
||||
{ tld: 'club', flag: '🎯' },
|
||||
{ tld: 'biz', flag: '💼' },
|
||||
]
|
||||
|
||||
// ============================================================================
|
||||
@ -107,7 +111,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Load Drops (only last 24h)
|
||||
// Load Drops
|
||||
const loadDrops = useCallback(async (currentPage = 1, isRefresh = false) => {
|
||||
if (isRefresh) setRefreshing(true)
|
||||
else setLoading(true)
|
||||
@ -115,7 +119,7 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
try {
|
||||
const result = await api.getDrops({
|
||||
tld: selectedTld || undefined,
|
||||
hours: 24, // Only last 24h - fresh drops only!
|
||||
hours: 24,
|
||||
min_length: minLength,
|
||||
max_length: maxLength,
|
||||
exclude_numeric: excludeNumeric,
|
||||
@ -139,7 +143,6 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
}
|
||||
}, [selectedTld, minLength, maxLength, excludeNumeric, excludeHyphen, searchQuery, showToast])
|
||||
|
||||
// Initial Load
|
||||
useEffect(() => {
|
||||
loadStats()
|
||||
}, [loadStats])
|
||||
@ -216,128 +219,137 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
return `${diffH}h ago`
|
||||
}
|
||||
|
||||
// Loading State
|
||||
if (loading && items.length === 0) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<Loader2 className="w-6 h-6 text-accent animate-spin" />
|
||||
<div className="flex flex-col items-center justify-center py-24">
|
||||
<Loader2 className="w-8 h-8 text-accent animate-spin mb-4" />
|
||||
<span className="text-xs font-mono text-white/30 uppercase tracking-widest">Loading zone file drops...</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-6">
|
||||
{/* 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">
|
||||
<Zap className="w-5 h-5 text-accent" />
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-accent/20 to-accent/5 border border-accent/30 flex items-center justify-center">
|
||||
<Zap className="w-6 h-6 text-accent" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xl font-bold text-white font-mono">
|
||||
<div className="text-2xl font-black text-white font-mono tracking-tight">
|
||||
{stats?.daily_drops?.toLocaleString() || total.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Fresh drops detected (24h)</div>
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase tracking-widest">Fresh drops in last 24h</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"
|
||||
className="p-3 border border-white/10 text-white/40 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
||||
title="Refresh drops"
|
||||
>
|
||||
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
|
||||
<RefreshCw className={clsx("w-5 h-5", refreshing && "animate-spin")} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className={clsx(
|
||||
"relative border transition-all duration-200",
|
||||
"relative border-2 transition-all duration-200",
|
||||
searchFocused ? "border-accent/50 bg-accent/[0.02]" : "border-white/[0.08] bg-white/[0.02]"
|
||||
)}>
|
||||
<div className="flex items-center">
|
||||
<Search className={clsx("w-4 h-4 ml-3 transition-colors", searchFocused ? "text-accent" : "text-white/30")} />
|
||||
<Search className={clsx("w-5 h-5 ml-4 transition-colors", searchFocused ? "text-accent" : "text-white/30")} />
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onFocus={() => setSearchFocused(true)}
|
||||
onBlur={() => setSearchFocused(false)}
|
||||
placeholder="Search drops..."
|
||||
className="flex-1 bg-transparent px-3 py-3 text-sm text-white placeholder:text-white/20 outline-none font-mono"
|
||||
placeholder="Search dropped domains..."
|
||||
className="flex-1 bg-transparent px-4 py-4 text-sm text-white placeholder:text-white/20 outline-none font-mono"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button onClick={() => setSearchQuery('')} className="p-3 text-white/30 hover:text-white transition-colors">
|
||||
<X className="w-4 h-4" />
|
||||
<button onClick={() => setSearchQuery('')} className="p-4 text-white/30 hover:text-white transition-colors">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TLD Quick Filter */}
|
||||
<div className="flex gap-1.5 flex-wrap">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<button
|
||||
onClick={() => setSelectedTld(null)}
|
||||
className={clsx(
|
||||
"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"
|
||||
"px-4 py-2.5 text-xs font-mono uppercase tracking-wider border transition-all",
|
||||
selectedTld === null
|
||||
? "border-accent bg-accent/10 text-accent font-bold"
|
||||
: "border-white/[0.08] text-white/40 hover:border-white/20 hover:text-white/60"
|
||||
)}
|
||||
>
|
||||
All
|
||||
All TLDs
|
||||
</button>
|
||||
{ALL_TLDS.map(({ tld, flag }) => (
|
||||
<button
|
||||
key={tld}
|
||||
onClick={() => setSelectedTld(tld)}
|
||||
className={clsx(
|
||||
"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"
|
||||
"px-4 py-2.5 text-xs font-mono uppercase tracking-wider border transition-all flex items-center gap-2",
|
||||
selectedTld === tld
|
||||
? "border-accent bg-accent/10 text-accent font-bold"
|
||||
: "border-white/[0.08] text-white/40 hover:border-white/20 hover:text-white/60"
|
||||
)}
|
||||
>
|
||||
<span className="text-xs">{flag}</span>.{tld}
|
||||
<span className="text-sm">{flag}</span>
|
||||
.{tld}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Filter Toggle */}
|
||||
{/* Advanced Filters */}
|
||||
<button
|
||||
onClick={() => setFiltersOpen(!filtersOpen)}
|
||||
className={clsx(
|
||||
"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]"
|
||||
"flex items-center justify-between w-full py-3 px-5 border transition-all",
|
||||
filtersOpen ? "border-accent/30 bg-accent/[0.03]" : "border-white/[0.08] bg-white/[0.02] hover:border-white/20"
|
||||
)}
|
||||
>
|
||||
<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 uppercase tracking-widest">Advanced Filters</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<Filter className={clsx("w-4 h-4", filtersOpen ? "text-accent" : "text-white/40")} />
|
||||
<span className={clsx("text-xs font-mono uppercase tracking-widest", filtersOpen ? "text-accent" : "text-white/50")}>
|
||||
Advanced Filters
|
||||
</span>
|
||||
{activeFiltersCount > 0 && (
|
||||
<span className="px-1.5 py-0.5 text-[9px] font-bold bg-accent text-black ml-1">{activeFiltersCount}</span>
|
||||
<span className="px-2 py-0.5 text-[9px] font-black bg-accent text-black">{activeFiltersCount}</span>
|
||||
)}
|
||||
</div>
|
||||
<ChevronRight className={clsx("w-4 h-4 text-white/30 transition-transform", filtersOpen && "rotate-90")} />
|
||||
<ChevronRight className={clsx("w-4 h-4 transition-transform", filtersOpen ? "rotate-90 text-accent" : "text-white/30")} />
|
||||
</button>
|
||||
|
||||
{/* Filters Panel */}
|
||||
{filtersOpen && (
|
||||
<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="p-5 border border-white/[0.08] bg-white/[0.01] space-y-5 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-widest mb-2.5">Domain Length</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase tracking-widest mb-3">Domain Length</div>
|
||||
<div className="flex gap-3 items-center">
|
||||
<input
|
||||
type="number"
|
||||
value={minLength || ''}
|
||||
onChange={(e) => setMinLength(e.target.value ? Number(e.target.value) : undefined)}
|
||||
placeholder="Min"
|
||||
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"
|
||||
className="w-24 bg-white/[0.02] border border-white/10 px-4 py-3 text-sm text-white placeholder:text-white/20 outline-none font-mono focus:border-accent/30 transition-colors"
|
||||
min={1}
|
||||
max={63}
|
||||
/>
|
||||
<span className="text-white/10 px-1 font-mono text-xs">TO</span>
|
||||
<span className="text-white/20 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-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"
|
||||
className="w-24 bg-white/[0.02] border border-white/10 px-4 py-3 text-sm text-white placeholder:text-white/20 outline-none font-mono focus:border-accent/30 transition-colors"
|
||||
min={1}
|
||||
max={63}
|
||||
/>
|
||||
@ -345,205 +357,259 @@ export function DropsTab({ showToast }: DropsTabProps) {
|
||||
</div>
|
||||
|
||||
{/* Quality Filters */}
|
||||
<div className="flex gap-2">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
onClick={() => setExcludeNumeric(!excludeNumeric)}
|
||||
className={clsx(
|
||||
"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]"
|
||||
"flex items-center justify-between py-3 px-4 border transition-all",
|
||||
excludeNumeric ? "border-accent/30 bg-accent/5" : "border-white/[0.08] hover:border-white/20"
|
||||
)}
|
||||
>
|
||||
<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 className="flex items-center gap-3">
|
||||
<Hash className={clsx("w-4 h-4", excludeNumeric ? "text-accent" : "text-white/30")} />
|
||||
<span className={clsx("text-xs font-mono uppercase tracking-wider", excludeNumeric ? "text-accent" : "text-white/50")}>
|
||||
No Numbers
|
||||
</span>
|
||||
</div>
|
||||
<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 className={clsx(
|
||||
"w-5 h-5 border flex items-center justify-center transition-all",
|
||||
excludeNumeric ? "border-accent bg-accent" : "border-white/20"
|
||||
)}>
|
||||
{excludeNumeric && <CheckCircle2 className="w-3 h-3 text-black" />}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setExcludeHyphen(!excludeHyphen)}
|
||||
className={clsx(
|
||||
"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]"
|
||||
"flex items-center justify-between py-3 px-4 border transition-all",
|
||||
excludeHyphen ? "border-accent/30 bg-accent/5" : "border-white/[0.08] hover:border-white/20"
|
||||
)}
|
||||
>
|
||||
<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 className="flex items-center gap-3">
|
||||
<Ban className={clsx("w-4 h-4", excludeHyphen ? "text-accent" : "text-white/30")} />
|
||||
<span className={clsx("text-xs font-mono uppercase tracking-wider", excludeHyphen ? "text-accent" : "text-white/50")}>
|
||||
No Hyphens
|
||||
</span>
|
||||
</div>
|
||||
<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 className={clsx(
|
||||
"w-5 h-5 border flex items-center justify-center transition-all",
|
||||
excludeHyphen ? "border-accent bg-accent" : "border-white/20"
|
||||
)}>
|
||||
{excludeHyphen && <CheckCircle2 className="w-3 h-3 text-black" />}
|
||||
</div>
|
||||
</button>
|
||||
</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" />
|
||||
<span>{total.toLocaleString()} domains matching criteria</span>
|
||||
{/* Results Info */}
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<div className="flex items-center gap-3 text-[11px] font-mono text-white/40 uppercase tracking-widest">
|
||||
<div className="w-1.5 h-1.5 bg-accent rounded-full animate-pulse" />
|
||||
<span>{total.toLocaleString()} domains detected</span>
|
||||
</div>
|
||||
{totalPages > 1 && (
|
||||
<span className="text-[11px] font-mono text-white/30 uppercase tracking-widest">
|
||||
Page {page} of {totalPages}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Alert Banner */}
|
||||
<div className="flex items-start gap-3 p-4 border border-amber-500/20 bg-amber-500/5">
|
||||
<AlertCircle className="w-5 h-5 text-amber-400 shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<div className="text-xs font-bold text-amber-400 uppercase tracking-wider mb-1">Zone File Analysis</div>
|
||||
<div className="text-[11px] text-white/50 leading-relaxed">
|
||||
Domains detected as dropped via zone file comparison. Some may have been re-registered. Click "Check" to verify live availability.
|
||||
</div>
|
||||
</div>
|
||||
{totalPages > 1 && <span>Page {page} of {totalPages}</span>}
|
||||
</div>
|
||||
|
||||
{/* Results Table */}
|
||||
{sortedItems.length === 0 ? (
|
||||
<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
|
||||
<Globe className="w-16 h-16 text-white/5 mx-auto mb-6" />
|
||||
<p className="text-white/50 text-sm font-mono uppercase tracking-widest font-bold">No drops found</p>
|
||||
<p className="text-white/20 text-xs font-mono mt-3 uppercase tracking-wider max-w-sm mx-auto leading-relaxed">
|
||||
Zone file comparison runs daily. Try adjusting your filters.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<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_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>
|
||||
<div className="border border-white/[0.08] bg-[#020202] overflow-hidden">
|
||||
{/* Table Header */}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_100px_120px_180px] gap-6 px-6 py-4 text-[10px] font-mono text-white/40 uppercase tracking-[0.15em] 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"
|
||||
>
|
||||
<span className={clsx(sortField === 'domain' && "text-accent font-bold")}>Domain</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-2 justify-center hover:text-white transition-colors group">
|
||||
<button
|
||||
onClick={() => handleSort('length')}
|
||||
className="flex items-center gap-2 justify-center hover:text-white transition-colors"
|
||||
>
|
||||
<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-2 justify-center hover:text-white transition-colors group">
|
||||
<span className={clsx(sortField === 'date' && "text-accent font-bold")}>Dropped</span>
|
||||
<button
|
||||
onClick={() => handleSort('date')}
|
||||
className="flex items-center gap-2 justify-center hover:text-white transition-colors"
|
||||
>
|
||||
<span className={clsx(sortField === 'date' && "text-accent font-bold")}>Detected</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 pr-2">Actions</div>
|
||||
<div className="text-right">Actions</div>
|
||||
</div>
|
||||
|
||||
{/* Table Body */}
|
||||
<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 group">
|
||||
{/* Mobile Row */}
|
||||
<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 className="flex items-center gap-3 mt-1.5">
|
||||
<span className={clsx(
|
||||
"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"
|
||||
)}>
|
||||
LEN: {item.length}
|
||||
</span>
|
||||
<span className="text-[10px] font-mono text-white/20 uppercase tracking-wider">{formatTime(item.dropped_date)}</span>
|
||||
{sortedItems.map((item) => {
|
||||
const fullDomain = `${item.domain}.${item.tld}`
|
||||
const isTracking = tracking === fullDomain
|
||||
|
||||
return (
|
||||
<div key={fullDomain} className="group hover:bg-white/[0.02] transition-all">
|
||||
{/* Mobile Row */}
|
||||
<div className="lg:hidden p-5">
|
||||
<div className="flex items-start justify-between gap-4 mb-4">
|
||||
<div className="min-w-0">
|
||||
<button
|
||||
onClick={() => openAnalyze(fullDomain)}
|
||||
className="text-lg font-bold text-white font-mono truncate block text-left hover:text-accent transition-colors"
|
||||
>
|
||||
{item.domain}<span className="text-white/30">.{item.tld}</span>
|
||||
</button>
|
||||
<div className="flex items-center gap-3 mt-2">
|
||||
<span className={clsx(
|
||||
"text-[10px] font-mono font-bold px-2.5 py-1 border",
|
||||
item.length <= 4 ? "text-accent border-accent/20 bg-accent/5" :
|
||||
item.length <= 6 ? "text-amber-400 border-amber-400/20 bg-amber-400/5" :
|
||||
"text-white/40 border-white/10 bg-white/5"
|
||||
)}>
|
||||
{item.length} chars
|
||||
</span>
|
||||
<span className="text-[10px] font-mono text-white/30 uppercase flex items-center gap-1.5">
|
||||
<Clock className="w-3 h-3" />
|
||||
{formatTime(item.dropped_date)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => track(fullDomain)}
|
||||
disabled={isTracking}
|
||||
className="flex-1 h-12 text-xs font-bold uppercase tracking-widest border border-white/10 text-white/50 flex items-center justify-center gap-2 hover:bg-white/5 active:scale-[0.98] transition-all"
|
||||
>
|
||||
{isTracking ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
|
||||
Track
|
||||
</button>
|
||||
<button
|
||||
onClick={() => openAnalyze(fullDomain)}
|
||||
className="w-14 h-12 border border-white/10 text-white/50 flex items-center justify-center hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
||||
>
|
||||
<Shield className="w-5 h-5" />
|
||||
</button>
|
||||
<a
|
||||
href={`https://www.namecheap.com/domains/registration/results/?domain=${fullDomain}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-1 h-12 bg-accent text-black text-xs font-black uppercase tracking-widest flex items-center justify-center gap-2 hover:bg-white active:scale-[0.98] transition-all"
|
||||
>
|
||||
Check & Buy
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => track(`${item.domain}.${item.tld}`)}
|
||||
disabled={tracking === `${item.domain}.${item.tld}`}
|
||||
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.5 h-3.5 animate-spin" /> : <Eye className="w-4 h-4" />}
|
||||
Track
|
||||
</button>
|
||||
<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 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"
|
||||
>
|
||||
Buy Now
|
||||
</a>
|
||||
{/* Desktop Row */}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_100px_120px_180px] gap-6 items-center px-6 py-4">
|
||||
{/* Domain */}
|
||||
<div className="min-w-0">
|
||||
<button
|
||||
onClick={() => openAnalyze(fullDomain)}
|
||||
className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left block"
|
||||
>
|
||||
{item.domain}<span className="text-white/30 group-hover:text-accent/40">.{item.tld}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Length */}
|
||||
<div className="text-center">
|
||||
<span className={clsx(
|
||||
"text-xs font-mono font-bold px-3 py-1 border inline-block",
|
||||
item.length <= 4 ? "text-accent border-accent/20 bg-accent/5" :
|
||||
item.length <= 6 ? "text-amber-400 border-amber-400/20 bg-amber-400/5" :
|
||||
"text-white/40 border-white/10 bg-white/5"
|
||||
)}>
|
||||
{item.length}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Time */}
|
||||
<div className="text-center">
|
||||
<span className="text-xs font-mono text-white/40 uppercase tracking-wider">
|
||||
{formatTime(item.dropped_date)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-end gap-2 opacity-40 group-hover:opacity-100 transition-all">
|
||||
<button
|
||||
onClick={() => track(fullDomain)}
|
||||
disabled={isTracking}
|
||||
className="w-10 h-10 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 transition-all"
|
||||
title="Add to Watchlist"
|
||||
>
|
||||
{isTracking ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => openAnalyze(fullDomain)}
|
||||
className="w-10 h-10 flex items-center justify-center border border-white/10 text-white/50 hover:text-accent hover:border-accent/30 hover:bg-accent/5 transition-all"
|
||||
title="Analyze Domain"
|
||||
>
|
||||
<Shield className="w-4 h-4" />
|
||||
</button>
|
||||
<a
|
||||
href={`https://www.namecheap.com/domains/registration/results/?domain=${fullDomain}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="h-10 px-5 bg-accent text-black text-[10px] font-black uppercase tracking-widest flex items-center gap-2 hover:bg-white transition-all"
|
||||
>
|
||||
Check & Buy
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop Row */}
|
||||
<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 tracking-tight"
|
||||
>
|
||||
{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-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/40 uppercase tracking-wider">{formatTime(item.dropped_date)}</span>
|
||||
</div>
|
||||
|
||||
<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-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.5 h-3.5 animate-spin" /> : <Eye className="w-4 h-4" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => openAnalyze(`${item.domain}.${item.tld}`)}
|
||||
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-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-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)]"
|
||||
>
|
||||
Buy
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-center gap-1 pt-6">
|
||||
<div className="flex items-center justify-center gap-2 pt-4">
|
||||
<button
|
||||
onClick={() => handlePageChange(page - 1)}
|
||||
disabled={page === 1}
|
||||
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"
|
||||
className="w-12 h-12 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-5 h-5" />
|
||||
</button>
|
||||
<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}
|
||||
<div className="flex items-center bg-white/[0.02] border border-white/[0.08] px-6 h-12">
|
||||
<span className="text-xs text-white/50 font-mono uppercase tracking-widest">
|
||||
Page <span className="text-white font-bold mx-1">{page}</span> / {totalPages}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handlePageChange(page + 1)}
|
||||
disabled={page === totalPages}
|
||||
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"
|
||||
className="w-12 h-12 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-5 h-5" />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user