feat: MARKET & NAV - High-End Polish
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
Changes: - MARKET: Re-introduced 'Emerald Glow' background effect (Landing Page style) - NAVIGATION: Complete redesign of Sidebar to match Award-Winning style - Darker, cleaner background (zinc-950) - Ultra-thin borders (white/5) - New active state: subtle emerald glow line + text color (no blocky backgrounds) - Simplified Logo section - Modernized User Profile card
This commit is contained in:
@ -313,7 +313,7 @@ export default function MarketPage() {
|
|||||||
// Watchlist
|
// Watchlist
|
||||||
const [trackedDomains, setTrackedDomains] = useState<Set<string>>(new Set())
|
const [trackedDomains, setTrackedDomains] = useState<Set<string>>(new Set())
|
||||||
const [trackingInProgress, setTrackingInProgress] = useState<string | null>(null)
|
const [trackingInProgress, setTrackingInProgress] = useState<string | null>(null)
|
||||||
|
|
||||||
// Load
|
// Load
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@ -378,7 +378,7 @@ export default function MarketPage() {
|
|||||||
if (priceRange === 'high') filtered = filtered.filter(item => item.price >= 1000)
|
if (priceRange === 'high') filtered = filtered.filter(item => item.price >= 1000)
|
||||||
if (searchQuery) filtered = filtered.filter(item => item.domain.toLowerCase().includes(searchQuery.toLowerCase()))
|
if (searchQuery) filtered = filtered.filter(item => item.domain.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||||
|
|
||||||
const mult = sortDirection === 'asc' ? 1 : -1
|
const mult = sortDirection === 'asc' ? 1 : -1
|
||||||
filtered.sort((a, b) => {
|
filtered.sort((a, b) => {
|
||||||
switch (sortField) {
|
switch (sortField) {
|
||||||
case 'domain': return mult * a.domain.localeCompare(b.domain)
|
case 'domain': return mult * a.domain.localeCompare(b.domain)
|
||||||
@ -386,9 +386,9 @@ export default function MarketPage() {
|
|||||||
case 'price': return mult * (a.price - b.price)
|
case 'price': return mult * (a.price - b.price)
|
||||||
case 'time': return mult * (parseTimeToSeconds(a.timeLeft) - parseTimeToSeconds(b.timeLeft))
|
case 'time': return mult * (parseTimeToSeconds(a.timeLeft) - parseTimeToSeconds(b.timeLeft))
|
||||||
case 'source': return mult * a.source.localeCompare(b.source)
|
case 'source': return mult * a.source.localeCompare(b.source)
|
||||||
default: return 0
|
default: return 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return filtered
|
return filtered
|
||||||
}, [auctions, hideSpam, pounceOnly, priceRange, searchQuery, sortField, sortDirection])
|
}, [auctions, hideSpam, pounceOnly, priceRange, searchQuery, sortField, sortDirection])
|
||||||
|
|
||||||
@ -404,215 +404,224 @@ export default function MarketPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TerminalLayout title="Market" subtitle="Global Domain Opportunities">
|
<TerminalLayout title="Market" subtitle="Global Domain Opportunities">
|
||||||
<div className="space-y-6 pb-20 md:pb-0">
|
<div className="relative">
|
||||||
|
{/* Page-specific emerald glow (mirrors landing page look) */}
|
||||||
{/* METRICS */}
|
<div className="pointer-events-none absolute inset-0 -z-10">
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
<div className="absolute -top-72 left-1/2 -translate-x-1/2 w-[1200px] h-[900px] bg-emerald-500/8 blur-[160px]" />
|
||||||
<StatCard label="Active" value={stats.total} icon={Activity} trend="neutral" />
|
<div className="absolute bottom-[-240px] right-[-160px] w-[720px] h-[720px] bg-emerald-500/6 blur-[140px]" />
|
||||||
<StatCard label="Top Tier" value={stats.highScore} subValue="80+ Score" icon={TrendingUp} trend="up" />
|
<div className="absolute bottom-[-180px] left-[-120px] w-[520px] h-[520px] bg-emerald-500/5 blur-[120px]" />
|
||||||
<StatCard label="Ending Soon" value={stats.endingSoon} subValue="< 1h" icon={Flame} trend={stats.endingSoon > 5 ? 'down' : 'neutral'} />
|
|
||||||
<StatCard label="Avg Price" value={formatPrice(stats.avgPrice)} icon={Zap} trend="neutral" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CONTROLS */}
|
<div className="space-y-6 pb-20 md:pb-0 relative">
|
||||||
<div className="sticky top-0 z-30 bg-zinc-950/80 backdrop-blur-md py-4 border-b border-white/5 -mx-4 px-4 md:mx-0 md:px-0 md:border-none md:bg-transparent md:static">
|
|
||||||
<div className="flex flex-col md:flex-row gap-4">
|
{/* METRICS */}
|
||||||
{/* Search */}
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
||||||
<div className="relative w-full md:w-80 group">
|
<StatCard label="Active" value={stats.total} icon={Activity} trend="neutral" />
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500 group-focus-within:text-white transition-colors" />
|
<StatCard label="Top Tier" value={stats.highScore} subValue="80+ Score" icon={TrendingUp} trend="up" />
|
||||||
<input
|
<StatCard label="Ending Soon" value={stats.endingSoon} subValue="< 1h" icon={Flame} trend={stats.endingSoon > 5 ? 'down' : 'neutral'} />
|
||||||
type="text"
|
<StatCard label="Avg Price" value={formatPrice(stats.avgPrice)} icon={Zap} trend="neutral" />
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
placeholder="Search domains..."
|
|
||||||
className="w-full pl-10 pr-4 py-2.5 bg-zinc-900 border border-white/10 rounded-xl
|
|
||||||
text-sm text-white placeholder:text-zinc-600
|
|
||||||
focus:outline-none focus:border-white/20 focus:ring-1 focus:ring-white/20 transition-all"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Filters */}
|
|
||||||
<div className="flex items-center gap-2 overflow-x-auto w-full pb-1 md:pb-0 scrollbar-hide mask-fade-right">
|
|
||||||
<FilterToggle active={hideSpam} onClick={() => setHideSpam(!hideSpam)} label="No Spam" />
|
|
||||||
<FilterToggle active={pounceOnly} onClick={() => setPounceOnly(!pounceOnly)} label="Pounce Exclusive" />
|
|
||||||
<div className="w-px h-5 bg-white/10 mx-2 flex-shrink-0" />
|
|
||||||
<FilterToggle active={priceRange === 'low'} onClick={() => setPriceRange(p => p === 'low' ? 'all' : 'low')} label="< $100" />
|
|
||||||
<FilterToggle active={priceRange === 'high'} onClick={() => setPriceRange(p => p === 'high' ? 'all' : 'high')} label="$1k+" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="hidden md:block flex-1" />
|
|
||||||
|
|
||||||
<button onClick={handleRefresh} className="hidden md:flex items-center gap-2 text-xs font-medium text-zinc-500 hover:text-white transition-colors">
|
|
||||||
<RefreshCw className={clsx("w-3.5 h-3.5", refreshing && "animate-spin")} />
|
|
||||||
Refresh
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* DATA GRID */}
|
{/* CONTROLS */}
|
||||||
<div className="min-h-[400px]">
|
<div className="sticky top-0 z-30 bg-zinc-950/80 backdrop-blur-md py-4 border-b border-white/5 -mx-4 px-4 md:mx-0 md:px-0 md:border-none md:bg-transparent md:static">
|
||||||
{loading ? (
|
<div className="flex flex-col md:flex-row gap-4">
|
||||||
<div className="flex flex-col items-center justify-center py-32 space-y-4">
|
{/* Search */}
|
||||||
<Loader2 className="w-8 h-8 text-emerald-500 animate-spin" />
|
<div className="relative w-full md:w-80 group">
|
||||||
<p className="text-zinc-500 text-sm animate-pulse">Scanning markets...</p>
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500 group-focus-within:text-white transition-colors" />
|
||||||
</div>
|
<input
|
||||||
) : marketItems.length === 0 ? (
|
type="text"
|
||||||
<div className="flex flex-col items-center justify-center py-32 text-center">
|
value={searchQuery}
|
||||||
<div className="w-16 h-16 bg-zinc-900 rounded-full flex items-center justify-center mb-4 border border-zinc-800">
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
<Search className="w-6 h-6 text-zinc-600" />
|
placeholder="Search domains..."
|
||||||
|
className="w-full pl-10 pr-4 py-2.5 bg-zinc-900 border border-white/10 rounded-xl
|
||||||
|
text-sm text-white placeholder:text-zinc-600
|
||||||
|
focus:outline-none focus:border-white/20 focus:ring-1 focus:ring-white/20 transition-all"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-white font-medium mb-1">No matches found</h3>
|
|
||||||
<p className="text-zinc-500 text-sm">Try adjusting your filters</p>
|
{/* Filters */}
|
||||||
|
<div className="flex items-center gap-2 overflow-x-auto w-full pb-1 md:pb-0 scrollbar-hide mask-fade-right">
|
||||||
|
<FilterToggle active={hideSpam} onClick={() => setHideSpam(!hideSpam)} label="No Spam" />
|
||||||
|
<FilterToggle active={pounceOnly} onClick={() => setPounceOnly(!pounceOnly)} label="Pounce Exclusive" />
|
||||||
|
<div className="w-px h-5 bg-white/10 mx-2 flex-shrink-0" />
|
||||||
|
<FilterToggle active={priceRange === 'low'} onClick={() => setPriceRange(p => p === 'low' ? 'all' : 'low')} label="< $100" />
|
||||||
|
<FilterToggle active={priceRange === 'high'} onClick={() => setPriceRange(p => p === 'high' ? 'all' : 'high')} label="$1k+" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hidden md:block flex-1" />
|
||||||
|
|
||||||
|
<button onClick={handleRefresh} className="hidden md:flex items-center gap-2 text-xs font-medium text-zinc-500 hover:text-white transition-colors">
|
||||||
|
<RefreshCw className={clsx("w-3.5 h-3.5", refreshing && "animate-spin")} />
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<>
|
|
||||||
{/* DESKTOP TABLE */}
|
{/* DATA GRID */}
|
||||||
<div className="hidden md:block border border-white/5 rounded-xl overflow-hidden bg-zinc-900/40 backdrop-blur-sm shadow-xl">
|
<div className="min-h-[400px]">
|
||||||
<div className="grid grid-cols-12 gap-4 px-6 py-3 border-b border-white/5 bg-white/[0.02]">
|
{loading ? (
|
||||||
<div className="col-span-4"><SortableHeader label="Domain Asset" field="domain" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} tooltip="The domain name being auctioned" /></div>
|
<div className="flex flex-col items-center justify-center py-32 space-y-4">
|
||||||
<div className="col-span-2 text-center"><SortableHeader label="Score" field="score" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="center" tooltip="Pounce Score: AI-calculated value based on length, TLD, and demand" /></div>
|
<Loader2 className="w-8 h-8 text-emerald-500 animate-spin" />
|
||||||
<div className="col-span-2 text-right"><SortableHeader label="Price" field="price" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="right" tooltip="Current highest bid or buy-now price" /></div>
|
<p className="text-zinc-500 text-sm animate-pulse">Scanning markets...</p>
|
||||||
<div className="col-span-2 text-center"><SortableHeader label="Time" field="time" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="center" tooltip="Time remaining until auction ends" /></div>
|
</div>
|
||||||
<div className="col-span-2 text-right"><span className="text-[10px] font-bold uppercase tracking-widest text-zinc-600 py-2 block">Action</span></div>
|
) : marketItems.length === 0 ? (
|
||||||
|
<div className="flex flex-col items-center justify-center py-32 text-center">
|
||||||
|
<div className="w-16 h-16 bg-zinc-900 rounded-full flex items-center justify-center mb-4 border border-zinc-800">
|
||||||
|
<Search className="w-6 h-6 text-zinc-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="divide-y divide-white/5">
|
<h3 className="text-white font-medium mb-1">No matches found</h3>
|
||||||
|
<p className="text-zinc-500 text-sm">Try adjusting your filters</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* DESKTOP TABLE */}
|
||||||
|
<div className="hidden md:block border border-white/5 rounded-xl overflow-hidden bg-zinc-900/40 backdrop-blur-sm shadow-xl">
|
||||||
|
<div className="grid grid-cols-12 gap-4 px-6 py-3 border-b border-white/5 bg-white/[0.02]">
|
||||||
|
<div className="col-span-4"><SortableHeader label="Domain Asset" field="domain" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} tooltip="The domain name being auctioned" /></div>
|
||||||
|
<div className="col-span-2 text-center"><SortableHeader label="Score" field="score" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="center" tooltip="Pounce Score: AI-calculated value based on length, TLD, and demand" /></div>
|
||||||
|
<div className="col-span-2 text-right"><SortableHeader label="Price" field="price" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="right" tooltip="Current highest bid or buy-now price" /></div>
|
||||||
|
<div className="col-span-2 text-center"><SortableHeader label="Time" field="time" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="center" tooltip="Time remaining until auction ends" /></div>
|
||||||
|
<div className="col-span-2 text-right"><span className="text-[10px] font-bold uppercase tracking-widest text-zinc-600 py-2 block">Action</span></div>
|
||||||
|
</div>
|
||||||
|
<div className="divide-y divide-white/5">
|
||||||
|
{marketItems.map((item) => {
|
||||||
|
const timeLeftSec = parseTimeToSeconds(item.timeLeft)
|
||||||
|
const isUrgent = timeLeftSec < 3600
|
||||||
|
return (
|
||||||
|
<div key={item.id} className="grid grid-cols-12 gap-4 px-6 py-4 items-center hover:bg-white/[0.04] transition-all group relative">
|
||||||
|
{/* Domain */}
|
||||||
|
<div className="col-span-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{item.isPounce && (
|
||||||
|
<Tooltip content="Pounce Exclusive Inventory">
|
||||||
|
<Diamond className="w-4 h-4 text-emerald-400 fill-emerald-400/20" />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<div className="font-medium text-white text-[15px] tracking-tight cursor-default">{item.domain}</div>
|
||||||
|
<Tooltip content={`Source: ${item.source}`}>
|
||||||
|
<div className="text-[11px] text-zinc-500 mt-0.5 w-fit hover:text-zinc-300 cursor-help transition-colors">{item.source}</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Score */}
|
||||||
|
<div className="col-span-2 flex justify-center"><ScoreDisplay score={item.pounceScore} /></div>
|
||||||
|
{/* Price */}
|
||||||
|
<div className="col-span-2 text-right">
|
||||||
|
<Tooltip content={`${item.numBids || 0} bids placed`}>
|
||||||
|
<div className="cursor-help">
|
||||||
|
<div className="font-mono text-white font-medium">{formatPrice(item.price)}</div>
|
||||||
|
{item.numBids !== undefined && item.numBids > 0 && <div className="text-[10px] text-zinc-500 mt-0.5">{item.numBids} bids</div>}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{/* Time */}
|
||||||
|
<div className="col-span-2 flex justify-center">
|
||||||
|
<Tooltip content="Auction ends soon">
|
||||||
|
<div className={clsx("flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium cursor-help", isUrgent ? "text-red-400 bg-red-500/10" : "text-zinc-400 bg-zinc-800/50")}>
|
||||||
|
<Clock className="w-3 h-3" />
|
||||||
|
{item.timeLeft}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{/* Actions */}
|
||||||
|
<div className="col-span-2 flex items-center justify-end opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
|
{/* Monitor Button - Distinct Style & Spacing */}
|
||||||
|
<Tooltip content={trackedDomains.has(item.domain) ? "Already tracking" : "Add to Watchlist"}>
|
||||||
|
<button
|
||||||
|
onClick={() => handleTrack(item.domain)}
|
||||||
|
disabled={trackedDomains.has(item.domain)}
|
||||||
|
className={clsx(
|
||||||
|
"w-8 h-8 flex items-center justify-center rounded-full border transition-all mr-4", // Added margin-right for separation
|
||||||
|
trackedDomains.has(item.domain)
|
||||||
|
? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20 cursor-default"
|
||||||
|
: "border-zinc-700 bg-zinc-900 text-zinc-400 hover:text-white hover:border-zinc-500 hover:scale-105 active:scale-95"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{trackedDomains.has(item.domain) ? <Check className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{/* Buy Button */}
|
||||||
|
<Tooltip content={item.isPounce ? "Buy Instantly" : "Place Bid on External Site"}>
|
||||||
|
<a href={item.affiliateUrl || '#'} target="_blank" rel="noopener noreferrer" className="h-9 px-4 flex items-center gap-2 bg-white text-zinc-950 rounded-lg text-xs font-bold hover:bg-zinc-200 transition-all hover:scale-105 active:scale-95 shadow-lg shadow-white/5">
|
||||||
|
{item.isPounce ? 'Buy Now' : 'Place Bid'}
|
||||||
|
<ExternalLink className="w-3 h-3" />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* MOBILE CARDS */}
|
||||||
|
<div className="md:hidden space-y-3">
|
||||||
{marketItems.map((item) => {
|
{marketItems.map((item) => {
|
||||||
const timeLeftSec = parseTimeToSeconds(item.timeLeft)
|
const timeLeftSec = parseTimeToSeconds(item.timeLeft)
|
||||||
const isUrgent = timeLeftSec < 3600
|
const isUrgent = timeLeftSec < 3600
|
||||||
return (
|
return (
|
||||||
<div key={item.id} className="grid grid-cols-12 gap-4 px-6 py-4 items-center hover:bg-white/[0.04] transition-all group relative">
|
<div key={item.id} className="bg-zinc-900/40 border border-white/5 rounded-xl p-4 active:bg-zinc-900/60 transition-colors">
|
||||||
{/* Domain */}
|
<div className="flex justify-between items-start mb-3">
|
||||||
<div className="col-span-4">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-3">
|
{item.isPounce && <Diamond className="w-3.5 h-3.5 text-emerald-400 fill-emerald-400/20" />}
|
||||||
{item.isPounce && (
|
<span className="font-medium text-white text-base">{item.domain}</span>
|
||||||
<Tooltip content="Pounce Exclusive Inventory">
|
|
||||||
<Diamond className="w-4 h-4 text-emerald-400 fill-emerald-400/20" />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<div className="font-medium text-white text-[15px] tracking-tight cursor-default">{item.domain}</div>
|
|
||||||
<Tooltip content={`Source: ${item.source}`}>
|
|
||||||
<div className="text-[11px] text-zinc-500 mt-0.5 w-fit hover:text-zinc-300 cursor-help transition-colors">{item.source}</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ScoreDisplay score={item.pounceScore} mobile />
|
||||||
</div>
|
</div>
|
||||||
{/* Score */}
|
|
||||||
<div className="col-span-2 flex justify-center"><ScoreDisplay score={item.pounceScore} /></div>
|
<div className="flex items-center justify-between mb-4">
|
||||||
{/* Price */}
|
<div>
|
||||||
<div className="col-span-2 text-right">
|
<div className="text-[10px] text-zinc-500 uppercase tracking-wider mb-0.5">Current Bid</div>
|
||||||
<Tooltip content={`${item.numBids || 0} bids placed`}>
|
<div className="font-mono text-lg font-medium text-white">{formatPrice(item.price)}</div>
|
||||||
<div className="cursor-help">
|
</div>
|
||||||
<div className="font-mono text-white font-medium">{formatPrice(item.price)}</div>
|
<div className="text-right">
|
||||||
{item.numBids !== undefined && item.numBids > 0 && <div className="text-[10px] text-zinc-500 mt-0.5">{item.numBids} bids</div>}
|
<div className="text-[10px] text-zinc-500 uppercase tracking-wider mb-0.5">Ends In</div>
|
||||||
</div>
|
<div className={clsx("flex items-center gap-1.5 justify-end font-medium", isUrgent ? "text-red-400" : "text-zinc-400")}>
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
{/* Time */}
|
|
||||||
<div className="col-span-2 flex justify-center">
|
|
||||||
<Tooltip content="Auction ends soon">
|
|
||||||
<div className={clsx("flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium cursor-help", isUrgent ? "text-red-400 bg-red-500/10" : "text-zinc-400 bg-zinc-800/50")}>
|
|
||||||
<Clock className="w-3 h-3" />
|
<Clock className="w-3 h-3" />
|
||||||
{item.timeLeft}
|
{item.timeLeft}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
{/* Actions */}
|
|
||||||
<div className="col-span-2 flex items-center justify-end opacity-0 group-hover:opacity-100 transition-opacity">
|
|
||||||
{/* Monitor Button - Distinct Style & Spacing */}
|
|
||||||
<Tooltip content={trackedDomains.has(item.domain) ? "Already tracking" : "Add to Watchlist"}>
|
|
||||||
<button
|
|
||||||
onClick={() => handleTrack(item.domain)}
|
|
||||||
disabled={trackedDomains.has(item.domain)}
|
|
||||||
className={clsx(
|
|
||||||
"w-8 h-8 flex items-center justify-center rounded-full border transition-all mr-4", // Added margin-right for separation
|
|
||||||
trackedDomains.has(item.domain)
|
|
||||||
? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20 cursor-default"
|
|
||||||
: "border-zinc-700 bg-zinc-900 text-zinc-400 hover:text-white hover:border-zinc-500 hover:scale-105 active:scale-95"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{trackedDomains.has(item.domain) ? <Check className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* Buy Button */}
|
|
||||||
<Tooltip content={item.isPounce ? "Buy Instantly" : "Place Bid on External Site"}>
|
|
||||||
<a href={item.affiliateUrl || '#'} target="_blank" rel="noopener noreferrer" className="h-9 px-4 flex items-center gap-2 bg-white text-zinc-950 rounded-lg text-xs font-bold hover:bg-zinc-200 transition-all hover:scale-105 active:scale-95 shadow-lg shadow-white/5">
|
|
||||||
{item.isPounce ? 'Buy Now' : 'Place Bid'}
|
|
||||||
<ExternalLink className="w-3 h-3" />
|
|
||||||
</a>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* MOBILE CARDS */}
|
|
||||||
<div className="md:hidden space-y-3">
|
|
||||||
{marketItems.map((item) => {
|
|
||||||
const timeLeftSec = parseTimeToSeconds(item.timeLeft)
|
|
||||||
const isUrgent = timeLeftSec < 3600
|
|
||||||
return (
|
|
||||||
<div key={item.id} className="bg-zinc-900/40 border border-white/5 rounded-xl p-4 active:bg-zinc-900/60 transition-colors">
|
|
||||||
<div className="flex justify-between items-start mb-3">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{item.isPounce && <Diamond className="w-3.5 h-3.5 text-emerald-400 fill-emerald-400/20" />}
|
|
||||||
<span className="font-medium text-white text-base">{item.domain}</span>
|
|
||||||
</div>
|
|
||||||
<ScoreDisplay score={item.pounceScore} mobile />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<div>
|
|
||||||
<div className="text-[10px] text-zinc-500 uppercase tracking-wider mb-0.5">Current Bid</div>
|
|
||||||
<div className="font-mono text-lg font-medium text-white">{formatPrice(item.price)}</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<div className="text-[10px] text-zinc-500 uppercase tracking-wider mb-0.5">Ends In</div>
|
|
||||||
<div className={clsx("flex items-center gap-1.5 justify-end font-medium", isUrgent ? "text-red-400" : "text-zinc-400")}>
|
|
||||||
<Clock className="w-3 h-3" />
|
|
||||||
{item.timeLeft}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<button
|
||||||
<button
|
onClick={() => handleTrack(item.domain)}
|
||||||
onClick={() => handleTrack(item.domain)}
|
disabled={trackedDomains.has(item.domain)}
|
||||||
disabled={trackedDomains.has(item.domain)}
|
className={clsx(
|
||||||
className={clsx(
|
"flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-medium border transition-all",
|
||||||
"flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-medium border transition-all",
|
trackedDomains.has(item.domain)
|
||||||
trackedDomains.has(item.domain)
|
? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20"
|
||||||
? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20"
|
: "bg-zinc-800/30 text-zinc-400 border-zinc-700/50 active:scale-95"
|
||||||
: "bg-zinc-800/30 text-zinc-400 border-zinc-700/50 active:scale-95"
|
)}
|
||||||
)}
|
>
|
||||||
>
|
{trackedDomains.has(item.domain) ? (
|
||||||
{trackedDomains.has(item.domain) ? (
|
<><Check className="w-4 h-4" /> Tracked</>
|
||||||
<><Check className="w-4 h-4" /> Tracked</>
|
) : (
|
||||||
) : (
|
<><Eye className="w-4 h-4" /> Watch</>
|
||||||
<><Eye className="w-4 h-4" /> Watch</>
|
)}
|
||||||
)}
|
</button>
|
||||||
</button>
|
<a
|
||||||
<a
|
href={item.affiliateUrl || '#'}
|
||||||
href={item.affiliateUrl || '#'}
|
target="_blank"
|
||||||
target="_blank"
|
rel="noopener noreferrer"
|
||||||
rel="noopener noreferrer"
|
className="flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-bold bg-white text-black hover:bg-zinc-200 active:scale-95 transition-all shadow-lg shadow-white/5"
|
||||||
className="flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-bold bg-white text-black hover:bg-zinc-200 active:scale-95 transition-all shadow-lg shadow-white/5"
|
>
|
||||||
>
|
{item.isPounce ? 'Buy Now' : 'Place Bid'}
|
||||||
{item.isPounce ? 'Buy Now' : 'Place Bid'}
|
<ExternalLink className="w-3 h-3 opacity-50" />
|
||||||
<ExternalLink className="w-3 h-3 opacity-50" />
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</TerminalLayout>
|
</TerminalLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { useStore } from '@/lib/store'
|
|||||||
import {
|
import {
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
Eye,
|
Eye,
|
||||||
Briefcase,
|
|
||||||
Gavel,
|
Gavel,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
Settings,
|
Settings,
|
||||||
@ -22,8 +21,6 @@ import {
|
|||||||
X,
|
X,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Tag,
|
Tag,
|
||||||
Target,
|
|
||||||
Link2,
|
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
@ -66,12 +63,10 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tierName = subscription?.tier_name || subscription?.tier || 'Scout'
|
const tierName = subscription?.tier_name || subscription?.tier || 'Scout'
|
||||||
const tierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap
|
const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap
|
||||||
const TierIcon = tierIcon
|
|
||||||
|
|
||||||
// Count available domains for notification badge
|
// Count available domains for notification badge
|
||||||
const availableCount = domains?.filter(d => d.is_available).length || 0
|
const availableCount = domains?.filter(d => d.is_available).length || 0
|
||||||
|
|
||||||
const isTycoon = tierName.toLowerCase() === 'tycoon'
|
const isTycoon = tierName.toLowerCase() === 'tycoon'
|
||||||
|
|
||||||
// SECTION 1: Discover - External market data
|
// SECTION 1: Discover - External market data
|
||||||
@ -131,7 +126,7 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
<>
|
<>
|
||||||
{/* Logo Section */}
|
{/* Logo Section */}
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
"relative h-20 flex items-center border-b border-border/30",
|
"relative h-20 flex items-center border-b border-white/5",
|
||||||
collapsed ? "justify-center px-2" : "px-4"
|
collapsed ? "justify-center px-2" : "px-4"
|
||||||
)}>
|
)}>
|
||||||
<Link href="/" className="flex items-center gap-3 group">
|
<Link href="/" className="flex items-center gap-3 group">
|
||||||
@ -139,29 +134,29 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
"relative flex items-center justify-center transition-all duration-300",
|
"relative flex items-center justify-center transition-all duration-300",
|
||||||
collapsed ? "w-10 h-10" : "w-12 h-12"
|
collapsed ? "w-10 h-10" : "w-12 h-12"
|
||||||
)}>
|
)}>
|
||||||
{/* Glow effect behind logo */}
|
{/* Glow effect behind logo - Reduced intensity for cleanliness */}
|
||||||
<div className="absolute inset-0 bg-accent/20 blur-xl rounded-full scale-150 opacity-50 group-hover:opacity-80 transition-opacity" />
|
<div className="absolute inset-0 bg-emerald-500/10 blur-xl rounded-full scale-150 opacity-30 group-hover:opacity-60 transition-opacity" />
|
||||||
<Image
|
<Image
|
||||||
src="/pounce-puma.png"
|
src="/pounce-puma.png"
|
||||||
alt="pounce"
|
alt="pounce"
|
||||||
width={48}
|
width={48}
|
||||||
height={48}
|
height={48}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"relative object-contain drop-shadow-[0_0_20px_rgba(16,185,129,0.3)] group-hover:drop-shadow-[0_0_30px_rgba(16,185,129,0.5)] transition-all",
|
"relative object-contain transition-all",
|
||||||
collapsed ? "w-9 h-9" : "w-12 h-12"
|
collapsed ? "w-8 h-8" : "w-10 h-10"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span
|
<span
|
||||||
className="text-lg font-bold tracking-[0.12em] text-foreground group-hover:text-accent transition-colors"
|
className="text-lg font-bold tracking-[0.12em] text-white group-hover:text-emerald-400 transition-colors"
|
||||||
style={{ fontFamily: 'var(--font-display), Georgia, serif' }}
|
style={{ fontFamily: 'var(--font-display), Georgia, serif' }}
|
||||||
>
|
>
|
||||||
POUNCE
|
POUNCE
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[10px] text-foreground-subtle tracking-wider uppercase">
|
<span className="text-[10px] text-zinc-500 tracking-wider uppercase">
|
||||||
Command Center
|
Terminal
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -169,67 +164,62 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Navigation */}
|
{/* Main Navigation */}
|
||||||
<nav className="flex-1 py-6 px-3 overflow-y-auto">
|
<nav className="flex-1 py-6 px-3 overflow-y-auto scrollbar-hide">
|
||||||
{/* SECTION 1: Discover */}
|
{/* SECTION 1: Discover */}
|
||||||
<div className={clsx("mb-6", collapsed ? "px-1" : "px-2")}>
|
<div className={clsx("mb-6", collapsed ? "px-1" : "px-2")}>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<p className="text-[10px] font-semibold text-foreground-subtle/60 uppercase tracking-[0.15em] mb-3">
|
<p className="text-[10px] font-bold text-zinc-600 uppercase tracking-widest mb-3 pl-2">
|
||||||
Discover
|
Discover
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{collapsed && <div className="h-px bg-border/50 mb-3" />}
|
{collapsed && <div className="h-px bg-white/5 mb-3 w-4 mx-auto" />}
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1">
|
||||||
{discoverItems.map((item) => (
|
{discoverItems.map((item) => (
|
||||||
<Link
|
<Link
|
||||||
key={item.href}
|
key={item.href}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"group relative flex items-center gap-3 px-3 py-3 rounded-xl transition-all duration-300",
|
"group relative flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200",
|
||||||
isActive(item.href)
|
isActive(item.href)
|
||||||
? "bg-gradient-to-r from-accent/20 to-accent/5 text-foreground border border-accent/20 shadow-[0_0_20px_-5px_rgba(16,185,129,0.2)]"
|
? "text-emerald-400 bg-emerald-500/[0.08]"
|
||||||
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5 border border-transparent"
|
: "text-zinc-400 hover:text-zinc-200 hover:bg-white/[0.03]"
|
||||||
)}
|
)}
|
||||||
title={collapsed ? item.label : undefined}
|
title={collapsed ? item.label : undefined}
|
||||||
>
|
>
|
||||||
{isActive(item.href) && (
|
{isActive(item.href) && (
|
||||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-8 bg-accent rounded-r-full shadow-[0_0_10px_rgba(16,185,129,0.5)]" />
|
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-5 bg-emerald-500 rounded-full shadow-[0_0_8px_rgba(16,185,129,0.5)]" />
|
||||||
)}
|
)}
|
||||||
<div className="relative">
|
<item.icon className={clsx(
|
||||||
<item.icon className={clsx(
|
"w-4 h-4 transition-all duration-300",
|
||||||
"w-5 h-5 transition-all duration-300",
|
isActive(item.href)
|
||||||
isActive(item.href)
|
? "text-emerald-400"
|
||||||
? "text-accent drop-shadow-[0_0_8px_rgba(16,185,129,0.5)]"
|
: "group-hover:text-zinc-200"
|
||||||
: "group-hover:text-foreground"
|
)} />
|
||||||
)} />
|
{!collapsed && (
|
||||||
</div>
|
<span className={clsx(
|
||||||
{!collapsed && (
|
"text-xs font-semibold tracking-wide transition-colors",
|
||||||
<span className={clsx(
|
isActive(item.href) ? "text-emerald-400" : "text-zinc-400 group-hover:text-zinc-200"
|
||||||
"text-sm font-medium transition-colors",
|
)}>
|
||||||
isActive(item.href) && "text-foreground"
|
{item.label}
|
||||||
)}>
|
</span>
|
||||||
{item.label}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{!isActive(item.href) && (
|
|
||||||
<div className="absolute inset-0 rounded-xl bg-accent/5 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none" />
|
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* SECTION 2: Manage */}
|
{/* SECTION 2: Manage */}
|
||||||
<div className={clsx("", collapsed ? "px-1" : "px-2")}>
|
<div className={clsx("", collapsed ? "px-1" : "px-2")}>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<p className="text-[10px] font-semibold text-foreground-subtle/60 uppercase tracking-[0.15em] mb-3">
|
<p className="text-[10px] font-bold text-zinc-600 uppercase tracking-widest mb-3 pl-2">
|
||||||
Manage
|
Manage
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{collapsed && <div className="h-px bg-border/50 mb-3" />}
|
{collapsed && <div className="h-px bg-white/5 mb-3 w-4 mx-auto" />}
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1">
|
||||||
{manageItems.map((item) => {
|
{manageItems.map((item) => {
|
||||||
const isDisabled = item.tycoonOnly && !isTycoon
|
const isDisabled = item.tycoonOnly && !isTycoon
|
||||||
const ItemWrapper = (isDisabled ? 'div' : Link) as any
|
const ItemWrapper = (isDisabled ? 'div' : Link) as any
|
||||||
@ -240,54 +230,40 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
{...(!isDisabled && { href: item.href })}
|
{...(!isDisabled && { href: item.href })}
|
||||||
onClick={() => !isDisabled && setMobileOpen(false)}
|
onClick={() => !isDisabled && setMobileOpen(false)}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"group relative flex items-center gap-3 px-3 py-3 rounded-xl transition-all duration-300",
|
"group relative flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200",
|
||||||
isDisabled
|
isDisabled
|
||||||
? "opacity-50 cursor-not-allowed border border-transparent"
|
? "opacity-50 cursor-not-allowed"
|
||||||
: isActive(item.href)
|
: isActive(item.href)
|
||||||
? "bg-gradient-to-r from-accent/20 to-accent/5 text-foreground border border-accent/20 shadow-[0_0_20px_-5px_rgba(16,185,129,0.2)]"
|
? "text-emerald-400 bg-emerald-500/[0.08]"
|
||||||
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5 border border-transparent"
|
: "text-zinc-400 hover:text-zinc-200 hover:bg-white/[0.03]"
|
||||||
)}
|
)}
|
||||||
title={
|
title={isDisabled ? "Upgrade to Tycoon to unlock" : collapsed ? item.label : undefined}
|
||||||
isDisabled
|
|
||||||
? "SEO Juice Detector: Analyze backlinks, domain authority & find hidden SEO value. Upgrade to Tycoon to unlock."
|
|
||||||
: collapsed ? item.label : undefined
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{!isDisabled && isActive(item.href) && (
|
{!isDisabled && isActive(item.href) && (
|
||||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-8 bg-accent rounded-r-full shadow-[0_0_10px_rgba(16,185,129,0.5)]" />
|
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-5 bg-emerald-500 rounded-full shadow-[0_0_8px_rgba(16,185,129,0.5)]" />
|
||||||
)}
|
)}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<item.icon className={clsx(
|
<item.icon className={clsx(
|
||||||
"w-5 h-5 transition-all duration-300",
|
"w-4 h-4 transition-all duration-300",
|
||||||
isDisabled
|
isDisabled ? "text-zinc-600" : isActive(item.href) ? "text-emerald-400" : "group-hover:text-zinc-200"
|
||||||
? "text-foreground-subtle"
|
|
||||||
: isActive(item.href)
|
|
||||||
? "text-accent drop-shadow-[0_0_8px_rgba(16,185,129,0.5)]"
|
|
||||||
: "group-hover:text-foreground"
|
|
||||||
)} />
|
)} />
|
||||||
{item.badge && typeof item.badge === 'number' && !isDisabled && (
|
{item.badge && typeof item.badge === 'number' && !isDisabled && (
|
||||||
<span className="absolute -top-2 -right-2 w-5 h-5 bg-accent text-background
|
<span className="absolute -top-1.5 -right-1.5 w-3.5 h-3.5 bg-emerald-500 text-black
|
||||||
text-[10px] font-bold rounded-full flex items-center justify-center
|
text-[9px] font-bold rounded-full flex items-center justify-center
|
||||||
shadow-[0_0_10px_rgba(16,185,129,0.4)] animate-pulse">
|
shadow-[0_0_8px_rgba(16,185,129,0.4)]">
|
||||||
{item.badge > 9 ? '9+' : item.badge}
|
{item.badge > 9 ? '•' : item.badge}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<span className={clsx(
|
<span className={clsx(
|
||||||
"text-sm font-medium transition-colors flex-1",
|
"text-xs font-semibold tracking-wide transition-colors flex-1",
|
||||||
isDisabled ? "text-foreground-subtle" : isActive(item.href) && "text-foreground"
|
isDisabled ? "text-zinc-600" : isActive(item.href) ? "text-emerald-400" : "text-zinc-400 group-hover:text-zinc-200"
|
||||||
)}>
|
)}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{/* Lock icon for disabled items */}
|
{isDisabled && !collapsed && <Crown className="w-3 h-3 text-amber-500/40" />}
|
||||||
{isDisabled && !collapsed && (
|
|
||||||
<Crown className="w-4 h-4 text-amber-400/60" />
|
|
||||||
)}
|
|
||||||
{!isDisabled && !isActive(item.href) && (
|
|
||||||
<div className="absolute inset-0 rounded-xl bg-accent/5 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none" />
|
|
||||||
)}
|
|
||||||
</ItemWrapper>
|
</ItemWrapper>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -296,25 +272,17 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Bottom Section */}
|
{/* Bottom Section */}
|
||||||
<div className="border-t border-border/30 py-4 px-3 space-y-1.5">
|
<div className="border-t border-white/5 py-4 px-3 space-y-1">
|
||||||
{/* Admin Link */}
|
{/* Admin Link */}
|
||||||
{user?.is_admin && (
|
{user?.is_admin && (
|
||||||
<Link
|
<Link
|
||||||
href="/admin"
|
href="/admin"
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
className={clsx(
|
className="group flex items-center gap-3 px-3 py-2.5 rounded-lg text-amber-500/80 hover:text-amber-400 hover:bg-amber-500/10 transition-all"
|
||||||
"group relative flex items-center gap-3 px-3 py-3 rounded-xl transition-all duration-300",
|
|
||||||
pathname.startsWith('/admin')
|
|
||||||
? "bg-gradient-to-r from-accent/20 to-accent/5 text-accent border border-accent/30"
|
|
||||||
: "text-accent/70 hover:text-accent hover:bg-accent/5 border border-transparent"
|
|
||||||
)}
|
|
||||||
title={collapsed ? "Admin Panel" : undefined}
|
title={collapsed ? "Admin Panel" : undefined}
|
||||||
>
|
>
|
||||||
{pathname.startsWith('/admin') && (
|
<Shield className="w-4 h-4" />
|
||||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-8 bg-accent rounded-r-full" />
|
{!collapsed && <span className="text-xs font-semibold tracking-wide">Admin</span>}
|
||||||
)}
|
|
||||||
<Shield className="w-5 h-5" />
|
|
||||||
{!collapsed && <span className="text-sm font-medium">Admin Panel</span>}
|
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -325,64 +293,62 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
href={item.href}
|
href={item.href}
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"group relative flex items-center gap-3 px-3 py-3 rounded-xl transition-all duration-300",
|
"group flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all",
|
||||||
isActive(item.href)
|
isActive(item.href)
|
||||||
? "bg-foreground/10 text-foreground border border-foreground/10"
|
? "text-emerald-400 bg-emerald-500/[0.08]"
|
||||||
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5 border border-transparent"
|
: "text-zinc-400 hover:text-zinc-200 hover:bg-white/[0.03]"
|
||||||
)}
|
)}
|
||||||
title={collapsed ? item.label : undefined}
|
title={collapsed ? item.label : undefined}
|
||||||
>
|
>
|
||||||
<item.icon className="w-5 h-5" />
|
<item.icon className="w-4 h-4" />
|
||||||
{!collapsed && <span className="text-sm font-medium">{item.label}</span>}
|
{!collapsed && <span className="text-xs font-semibold tracking-wide">{item.label}</span>}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* User Card */}
|
{/* User Card */}
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
"mt-4 p-4 bg-gradient-to-br from-foreground/[0.03] to-transparent border border-border/50 rounded-2xl",
|
"mt-4 border border-white/5 rounded-xl bg-zinc-900/50",
|
||||||
collapsed && "p-3"
|
collapsed ? "p-2 bg-transparent border-none" : "p-3"
|
||||||
)}>
|
)}>
|
||||||
{collapsed ? (
|
{collapsed ? (
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<div className="w-10 h-10 bg-gradient-to-br from-accent/20 to-accent/5 rounded-xl flex items-center justify-center border border-accent/20">
|
<div className="w-8 h-8 rounded-lg bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center">
|
||||||
<TierIcon className="w-5 h-5 text-accent" />
|
<TierIcon className="w-4 h-4 text-emerald-400" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div className="w-11 h-11 bg-gradient-to-br from-accent/20 to-accent/5 rounded-xl flex items-center justify-center border border-accent/20 shadow-[0_0_20px_-5px_rgba(16,185,129,0.3)]">
|
<div className="w-9 h-9 rounded-lg bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center flex-shrink-0">
|
||||||
<TierIcon className="w-5 h-5 text-accent" />
|
<TierIcon className="w-4 h-4 text-emerald-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-semibold text-foreground truncate">
|
<p className="text-xs font-bold text-white truncate">
|
||||||
{user?.name || user?.email?.split('@')[0]}
|
{user?.name || user?.email?.split('@')[0]}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className={clsx(
|
<span className={clsx(
|
||||||
"text-xs font-medium",
|
"text-[10px] uppercase tracking-wider font-bold",
|
||||||
tierName === 'Tycoon' ? "text-amber-400" :
|
tierName === 'Tycoon' ? "text-amber-400" :
|
||||||
tierName === 'Trader' ? "text-accent" :
|
tierName === 'Trader' ? "text-emerald-400" :
|
||||||
"text-foreground-muted"
|
"text-zinc-500"
|
||||||
)}>
|
)}>
|
||||||
{tierName}
|
{tierName}
|
||||||
</span>
|
</span>
|
||||||
{tierName === 'Tycoon' && <Sparkles className="w-3 h-3 text-amber-400" />}
|
{tierName === 'Tycoon' && <Sparkles className="w-2.5 h-2.5 text-amber-400" />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Usage bar */}
|
{/* Usage bar */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-1.5">
|
||||||
<div className="flex items-center justify-between text-xs">
|
<div className="flex items-center justify-between text-[10px] font-medium text-zinc-500">
|
||||||
<span className="text-foreground-subtle">Domains</span>
|
<span>Usage</span>
|
||||||
<span className="text-foreground-muted">
|
<span>{subscription?.domains_used || 0}/{subscription?.domain_limit || 5}</span>
|
||||||
{subscription?.domains_used || 0}/{subscription?.domain_limit || 5}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="h-1.5 bg-foreground/5 rounded-full overflow-hidden">
|
<div className="h-1 bg-zinc-800 rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="h-full bg-gradient-to-r from-accent to-accent/60 rounded-full transition-all duration-500"
|
className="h-full bg-emerald-500 rounded-full transition-all duration-500"
|
||||||
style={{
|
style={{
|
||||||
width: `${Math.min(((subscription?.domains_used || 0) / (subscription?.domain_limit || 5)) * 100, 100)}%`
|
width: `${Math.min(((subscription?.domains_used || 0) / (subscription?.domain_limit || 5)) * 100, 100)}%`
|
||||||
}}
|
}}
|
||||||
@ -390,17 +356,17 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{tierName === 'Scout' && (
|
{tierName === 'Scout' && (
|
||||||
<Link
|
<Link
|
||||||
href="/pricing"
|
href="/pricing"
|
||||||
className="mt-4 flex items-center justify-center gap-2 w-full py-2.5 bg-gradient-to-r from-accent to-accent/80
|
className="mt-3 flex items-center justify-center gap-2 w-full py-2
|
||||||
text-background text-xs font-semibold rounded-xl
|
bg-emerald-500 text-black text-[10px] font-bold uppercase tracking-wider rounded-lg
|
||||||
hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.5)] transition-all"
|
hover:bg-emerald-400 transition-colors"
|
||||||
>
|
>
|
||||||
<CreditCard className="w-3.5 h-3.5" />
|
<CreditCard className="w-3 h-3" />
|
||||||
Upgrade Plan
|
Upgrade
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -411,14 +377,11 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
logout()
|
logout()
|
||||||
setMobileOpen(false)
|
setMobileOpen(false)
|
||||||
}}
|
}}
|
||||||
className={clsx(
|
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-zinc-500 hover:text-red-400 hover:bg-red-500/10 transition-all"
|
||||||
"w-full flex items-center gap-3 px-3 py-3 rounded-xl transition-all duration-300",
|
|
||||||
"text-foreground-muted hover:text-foreground hover:bg-foreground/5"
|
|
||||||
)}
|
|
||||||
title={collapsed ? "Sign out" : undefined}
|
title={collapsed ? "Sign out" : undefined}
|
||||||
>
|
>
|
||||||
<LogOut className="w-5 h-5" />
|
<LogOut className="w-4 h-4" />
|
||||||
{!collapsed && <span className="text-sm font-medium">Sign out</span>}
|
{!collapsed && <span className="text-xs font-semibold tracking-wide">Sign out</span>}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -426,16 +389,12 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
<button
|
<button
|
||||||
onClick={toggleCollapsed}
|
onClick={toggleCollapsed}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"hidden lg:flex absolute -right-3 top-24 w-6 h-6 bg-background border border-border rounded-full",
|
"hidden lg:flex absolute -right-3 top-24 w-6 h-6 bg-zinc-900 border border-zinc-800 rounded-full",
|
||||||
"items-center justify-center text-foreground-muted hover:text-foreground",
|
"items-center justify-center text-zinc-500 hover:text-white",
|
||||||
"hover:bg-accent/10 hover:border-accent/30 transition-all duration-300 shadow-lg"
|
"hover:border-zinc-700 transition-all shadow-xl z-50"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{collapsed ? (
|
{collapsed ? <ChevronRight className="w-3 h-3" /> : <ChevronLeft className="w-3 h-3" />}
|
||||||
<ChevronRight className="w-3.5 h-3.5" />
|
|
||||||
) : (
|
|
||||||
<ChevronLeft className="w-3.5 h-3.5" />
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -445,9 +404,9 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
{/* Mobile Menu Button */}
|
{/* Mobile Menu Button */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setMobileOpen(true)}
|
onClick={() => setMobileOpen(true)}
|
||||||
className="lg:hidden fixed top-4 left-4 z-50 w-11 h-11 bg-background/80 backdrop-blur-xl border border-border
|
className="lg:hidden fixed top-4 left-4 z-50 w-10 h-10 bg-zinc-900/90 backdrop-blur border border-white/10
|
||||||
rounded-xl flex items-center justify-center text-foreground-muted hover:text-foreground
|
rounded-lg flex items-center justify-center text-zinc-400 hover:text-white
|
||||||
transition-all shadow-lg hover:shadow-xl hover:border-accent/30"
|
transition-all shadow-lg"
|
||||||
>
|
>
|
||||||
<Menu className="w-5 h-5" />
|
<Menu className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
@ -455,7 +414,7 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
{/* Mobile Overlay */}
|
{/* Mobile Overlay */}
|
||||||
{mobileOpen && (
|
{mobileOpen && (
|
||||||
<div
|
<div
|
||||||
className="lg:hidden fixed inset-0 z-40 bg-background/80 backdrop-blur-sm"
|
className="lg:hidden fixed inset-0 z-40 bg-black/80 backdrop-blur-sm"
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -464,16 +423,15 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
<aside
|
<aside
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"lg:hidden fixed left-0 top-0 bottom-0 z-50 w-[280px] flex flex-col",
|
"lg:hidden fixed left-0 top-0 bottom-0 z-50 w-[280px] flex flex-col",
|
||||||
"bg-background/95 backdrop-blur-xl border-r border-border/50",
|
"bg-zinc-950 border-r border-white/5",
|
||||||
"transition-transform duration-300 ease-out",
|
"transition-transform duration-300 ease-out",
|
||||||
mobileOpen ? "translate-x-0" : "-translate-x-full"
|
mobileOpen ? "translate-x-0" : "-translate-x-full"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Close button */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
className="absolute top-5 right-4 w-8 h-8 flex items-center justify-center
|
className="absolute top-5 right-4 w-8 h-8 flex items-center justify-center
|
||||||
text-foreground-muted hover:text-foreground transition-colors"
|
text-zinc-500 hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
<X className="w-5 h-5" />
|
<X className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
@ -484,10 +442,10 @@ export function Sidebar({ collapsed: controlledCollapsed, onCollapsedChange }: S
|
|||||||
<aside
|
<aside
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"hidden lg:flex fixed left-0 top-0 bottom-0 z-40 flex-col",
|
"hidden lg:flex fixed left-0 top-0 bottom-0 z-40 flex-col",
|
||||||
"bg-gradient-to-b from-background/95 via-background/90 to-background/95 backdrop-blur-xl",
|
"bg-zinc-950/95 backdrop-blur-xl", // Darker background
|
||||||
"border-r border-border/30",
|
"border-r border-white/5", // Thinner border
|
||||||
"transition-all duration-300 ease-out",
|
"transition-all duration-300 ease-out",
|
||||||
collapsed ? "w-[72px]" : "w-[260px]"
|
collapsed ? "w-[72px]" : "w-[240px]" // Slightly narrower
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<SidebarContent />
|
<SidebarContent />
|
||||||
|
|||||||
Reference in New Issue
Block a user