Intel, Sniper, Yield pages redesign
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

This commit is contained in:
2025-12-12 22:45:55 +01:00
parent b820690478
commit b7fa3632bf
4 changed files with 959 additions and 1281 deletions

View File

@ -1,11 +1,10 @@
'use client'
import { useEffect, useState, useMemo, useCallback, memo } from 'react'
import { useEffect, useState, useMemo, useCallback } from 'react'
import { useStore } from '@/lib/store'
import { api } from '@/lib/api'
import { TerminalLayout } from '@/components/TerminalLayout'
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
import {
ExternalLink,
Loader2,
TrendingUp,
TrendingDown,
@ -16,28 +15,39 @@ import {
Search,
ChevronDown,
ChevronUp,
Info,
ArrowRight,
Lock,
Sparkles,
BarChart3,
Activity,
Zap,
Filter,
Check,
Eye,
ShieldCheck,
Diamond,
Minus
} from 'lucide-react'
import clsx from 'clsx'
import Link from 'next/link'
// ============================================================================
// TIER ACCESS LEVELS
// TYPES
// ============================================================================
type UserTier = 'scout' | 'trader' | 'tycoon'
type SortField = 'tld' | 'price' | 'renewal' | 'change' | 'change3y' | 'risk' | 'popularity'
type SortDirection = 'asc' | 'desc'
interface TLDData {
tld: string
min_price: number
avg_price: number
max_price: number
min_renewal_price: number
avg_renewal_price: number
price_change_7d: number
price_change_1y: number
price_change_3y: number
risk_level: 'low' | 'medium' | 'high'
risk_reason: string
popularity_rank?: number
type?: string
}
function getTierLevel(tier: UserTier): number {
switch (tier) {
@ -48,175 +58,7 @@ function getTierLevel(tier: UserTier): number {
}
}
// ============================================================================
// SHARED COMPONENTS
// ============================================================================
const Tooltip = memo(({ children, content }: { children: React.ReactNode; content: string }) => (
<div className="relative flex items-center group/tooltip w-fit">
{children}
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-zinc-900 border border-zinc-800 rounded text-[10px] text-zinc-300 whitespace-nowrap opacity-0 group-hover/tooltip:opacity-100 transition-opacity pointer-events-none z-50 shadow-xl max-w-xs text-center">
{content}
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px border-4 border-transparent border-t-zinc-800" />
</div>
</div>
))
Tooltip.displayName = 'Tooltip'
const LockedFeature = memo(({ requiredTier, currentTier }: { requiredTier: UserTier; currentTier: UserTier }) => {
const tierNames = { scout: 'Scout', trader: 'Trader', tycoon: 'Tycoon' }
return (
<Tooltip content={`Upgrade to ${tierNames[requiredTier]} to unlock`}>
<div className="flex items-center gap-1.5 text-zinc-600 cursor-help px-2 py-1 rounded bg-zinc-900/50 border border-zinc-800 hover:bg-zinc-900 transition-colors">
<Lock className="w-3 h-3" />
<span className="text-[10px] font-medium uppercase tracking-wider">Locked</span>
</div>
</Tooltip>
)
})
LockedFeature.displayName = 'LockedFeature'
const StatCard = memo(({
label,
value,
subValue,
icon: Icon,
highlight,
locked = false,
lockTooltip
}: {
label: string
value: string | number
subValue?: string
icon: any
highlight?: boolean
locked?: boolean
lockTooltip?: string
}) => (
<div className={clsx(
"bg-zinc-900/40 border p-4 relative overflow-hidden group hover:border-white/10 transition-colors",
highlight ? "border-emerald-500/30" : "border-white/5"
)}>
<div className="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity">
<Icon className="w-16 h-16" />
</div>
<div className="relative z-10">
<div className="flex items-center gap-2 text-zinc-400 mb-1">
<Icon className={clsx("w-4 h-4", highlight && "text-emerald-400")} />
<span className="text-xs font-medium uppercase tracking-wider">{label}</span>
</div>
{locked ? (
<Tooltip content={lockTooltip || 'Upgrade to unlock'}>
<div className="flex items-center gap-2 text-zinc-600 cursor-help mt-1">
<Lock className="w-5 h-5" />
<span className="text-2xl font-bold"></span>
</div>
</Tooltip>
) : (
<div className="flex items-baseline gap-2">
<span className="text-2xl font-bold text-white tracking-tight">{value}</span>
{subValue && <span className="text-xs text-zinc-500 font-medium">{subValue}</span>}
</div>
)}
{highlight && (
<div className="mt-2 text-[10px] font-medium px-1.5 py-0.5 w-fit rounded border text-emerald-400 border-emerald-400/20 bg-emerald-400/5">
LIVE
</div>
)}
</div>
</div>
))
StatCard.displayName = 'StatCard'
const FilterToggle = memo(({ active, onClick, label, icon: Icon }: {
active: boolean
onClick: () => void
label: string
icon?: any
}) => (
<button
onClick={onClick}
className={clsx(
"px-4 py-1.5 rounded-md text-xs font-medium transition-all flex items-center gap-2 whitespace-nowrap border",
active
? "bg-zinc-800 text-white border-zinc-600 shadow-sm"
: "bg-transparent text-zinc-400 border-zinc-800 hover:text-zinc-200 hover:bg-white/5"
)}
>
{Icon && <Icon className="w-3.5 h-3.5" />}
{label}
</button>
))
FilterToggle.displayName = 'FilterToggle'
type SortField = 'tld' | 'price' | 'renewal' | 'change' | 'change3y' | 'risk' | 'popularity'
type SortDirection = 'asc' | 'desc'
const SortableHeader = memo(({
label, field, currentSort, currentDirection, onSort, align = 'left', tooltip, locked = false, lockTooltip
}: {
label: string; field: SortField; currentSort: SortField; currentDirection: SortDirection; onSort: (field: SortField) => void; align?: 'left'|'center'|'right'; tooltip?: string; locked?: boolean; lockTooltip?: string
}) => {
const isActive = currentSort === field
return (
<div className={clsx(
"flex items-center gap-1",
align === 'right' && "justify-end ml-auto",
align === 'center' && "justify-center mx-auto"
)}>
<button
onClick={() => !locked && onSort(field)}
disabled={locked}
className={clsx(
"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider transition-all group select-none py-2",
locked ? "text-zinc-600 cursor-not-allowed" : isActive ? "text-zinc-300" : "text-zinc-500 hover:text-zinc-400"
)}
>
{label}
{locked ? (
<Tooltip content={lockTooltip || 'Upgrade to unlock'}>
<Lock className="w-2.5 h-2.5 text-zinc-600" />
</Tooltip>
) : (
<div className={clsx("flex flex-col -space-y-1 transition-opacity", isActive ? "opacity-100" : "opacity-0 group-hover:opacity-30")}>
<ChevronUp className={clsx("w-2 h-2", isActive && currentDirection === 'asc' ? "text-zinc-300" : "text-zinc-600")} />
<ChevronDown className={clsx("w-2 h-2", isActive && currentDirection === 'desc' ? "text-zinc-300" : "text-zinc-600")} />
</div>
)}
</button>
{tooltip && !locked && (
<Tooltip content={tooltip}>
<Info className="w-3 h-3 text-zinc-700 hover:text-zinc-500 transition-colors cursor-help" />
</Tooltip>
)}
</div>
)
})
SortableHeader.displayName = 'SortableHeader'
// ============================================================================
// TYPES
// ============================================================================
interface TLDData {
tld: string
min_price: number
avg_price: number
max_price: number
min_renewal_price: number
avg_renewal_price: number
cheapest_registrar?: string
cheapest_registrar_url?: string
price_change_7d: number
price_change_1y: number
price_change_3y: number
risk_level: 'low' | 'medium' | 'high'
risk_reason: string
popularity_rank?: number
type?: string
}
const formatPrice = (p: number) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(p)
// ============================================================================
// MAIN PAGE
@ -225,30 +67,23 @@ interface TLDData {
export default function IntelPage() {
const { subscription } = useStore()
// Determine user tier
const userTier: UserTier = (subscription?.tier as UserTier) || 'scout'
const tierLevel = getTierLevel(userTier)
// Feature access checks
const canSeeRenewal = tierLevel >= 2 // Trader+
const canSee3yTrend = tierLevel >= 3 // Tycoon only
const canSeeFullHistory = tierLevel >= 3 // Tycoon only
const canSeeRenewal = tierLevel >= 2
const canSee3yTrend = tierLevel >= 3
// Data
const [tldData, setTldData] = useState<TLDData[]>([])
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
const [total, setTotal] = useState(0)
// Filters
const [searchQuery, setSearchQuery] = useState('')
const [filterType, setFilterType] = useState<'all' | 'tech' | 'geo' | 'budget'>('all')
// Sort
const [sortField, setSortField] = useState<SortField>('popularity')
const [sortDirection, setSortDirection] = useState<SortDirection>('asc')
// Load Data
const loadData = useCallback(async () => {
setLoading(true)
try {
@ -296,18 +131,12 @@ export default function IntelPage() {
}
}, [sortField, canSeeRenewal, canSee3yTrend])
// Transform & Filter
const filteredData = useMemo(() => {
let data = tldData
// Tech filter: common tech-related TLDs
const techTlds = ['ai', 'io', 'app', 'dev', 'tech', 'cloud', 'digital', 'software', 'code', 'systems', 'network', 'data', 'cyber', 'online', 'web', 'api', 'hosting']
if (filterType === 'tech') data = data.filter(t => techTlds.includes(t.tld) || t.tld === 'io' || t.tld === 'ai')
// Geo filter: all country-code TLDs (ccTLD type from backend)
if (filterType === 'geo') data = data.filter(t => t.type === 'ccTLD')
// Budget filter: registration under $10
if (filterType === 'budget') data = data.filter(t => t.min_price < 10)
if (searchQuery) {
@ -341,295 +170,277 @@ export default function IntelPage() {
return { lowest, hottest, traps, avgRenewal }
}, [tldData])
const formatPrice = (p: number) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(p)
return (
<TerminalLayout hideHeaderSearch={true}>
<div className="relative font-sans text-zinc-100 selection:bg-emerald-500/30">
{/* Ambient Background Glow */}
<div className="fixed inset-0 pointer-events-none overflow-hidden">
<div className="absolute top-0 right-1/4 w-[800px] h-[600px] bg-emerald-500/5 rounded-full blur-[120px] mix-blend-screen" />
<div className="absolute bottom-0 left-1/4 w-[600px] h-[500px] bg-blue-500/5 rounded-full blur-[100px] mix-blend-screen" />
<CommandCenterLayout minimal>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* HEADER */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<section className="pt-6 lg:pt-8 pb-6">
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-6">
<div className="space-y-3">
<div className="inline-flex items-center gap-2">
<BarChart3 className="w-4 h-4 text-accent" />
<span className="text-[10px] font-mono tracking-wide text-accent">Pricing Analytics</span>
</div>
<div className="relative z-10 max-w-[1600px] mx-auto p-4 md:p-8 space-y-8">
{/* Header Section */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-6 border-b border-white/5 pb-8">
<div className="space-y-2">
<div className="flex items-center gap-3">
<div className="h-8 w-1 bg-emerald-500 rounded-full shadow-[0_0_10px_rgba(16,185,129,0.5)]" />
<h1 className="text-3xl font-bold tracking-tight text-white">TLD Intelligence</h1>
</div>
<p className="text-zinc-400 max-w-lg">
Inflation Monitor & Pricing Analytics across 800+ TLDs.
</p>
<h1 className="font-display text-[2rem] lg:text-[2.5rem] leading-[1] tracking-[-0.02em]">
<span className="text-white">TLD Intel</span>
<span className="text-white/30 ml-3">{total}</span>
</h1>
</div>
{/* Quick Stats Pills */}
<div className="flex gap-2">
<div className={clsx(
"px-3 py-1.5 rounded-full border flex items-center gap-2 text-xs font-medium",
userTier === 'tycoon' ? "bg-amber-500/5 border-amber-500/20 text-amber-400" :
userTier === 'trader' ? "bg-blue-500/5 border-blue-500/20 text-blue-400" :
"bg-white/5 border-white/10 text-zinc-300"
)}>
<Diamond className="w-3.5 h-3.5" />
{userTier === 'tycoon' ? 'Tycoon Access' : userTier === 'trader' ? 'Trader Access' : 'Scout Access'}
</div>
<div className="px-3 py-1.5 rounded-full bg-white/5 border border-white/10 flex items-center gap-2 text-xs font-medium text-zinc-300">
<Activity className="w-3.5 h-3.5 text-emerald-400" />
{total} Tracked
<div className="flex gap-6 lg:gap-8">
<div className="text-right">
<div className="text-xl font-display text-accent">{formatPrice(stats.lowest)}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">Lowest Entry</div>
</div>
<div className="text-right">
<div className="text-xl font-display text-amber-400">{stats.traps}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">High Risk</div>
</div>
{canSeeRenewal && (
<div className="text-right">
<div className="text-xl font-display text-white">{formatPrice(stats.avgRenewal)}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">Avg Renewal</div>
</div>
)}
</div>
</div>
</section>
{/* Metric Grid */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<StatCard
label="Tracked TLDs"
value={total}
icon={Globe}
highlight={true}
/>
<StatCard
label="Lowest Entry"
value={formatPrice(stats.lowest)}
subValue="Registration"
icon={DollarSign}
/>
<StatCard
label="Avg. Renewal"
value={canSeeRenewal ? formatPrice(stats.avgRenewal) : '—'}
subValue={canSeeRenewal ? "/ year" : undefined}
icon={RefreshCw}
locked={!canSeeRenewal}
lockTooltip="Upgrade to Trader to see renewal prices"
/>
<StatCard
label="Renewal Traps"
value={stats.traps}
subValue="High Risk"
icon={AlertTriangle}
/>
</div>
{/* Control Bar */}
<div className="sticky top-4 z-30 bg-black/80 backdrop-blur-md border border-white/10 rounded-xl p-2 flex flex-col md:flex-row gap-4 items-center justify-between shadow-2xl">
{/* Filter Pills */}
<div className="flex items-center gap-2 overflow-x-auto w-full pb-1 md:pb-0 scrollbar-hide">
<FilterToggle active={filterType === 'all'} onClick={() => setFilterType('all')} label="All TLDs" />
<FilterToggle active={filterType === 'tech'} onClick={() => setFilterType('tech')} label="Tech" icon={Zap} />
<FilterToggle active={filterType === 'geo'} onClick={() => setFilterType('geo')} label="Geo / National" icon={Globe} />
<FilterToggle active={filterType === 'budget'} onClick={() => setFilterType('budget')} label="Budget <$10" icon={DollarSign} />
</div>
{/* Refresh Button (Mobile) */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* FILTERS */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<section className="pb-6 border-b border-white/[0.08]">
<div className="flex flex-wrap items-center gap-2">
<button
onClick={handleRefresh}
className="md:hidden p-2 text-zinc-400 hover:text-white"
onClick={() => setFilterType('all')}
className={clsx(
"px-3 py-1.5 text-xs font-medium transition-colors",
filterType === 'all' ? "bg-white/10 text-white" : "text-white/40 hover:text-white/60"
)}
>
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
All TLDs
</button>
<button
onClick={() => setFilterType('tech')}
className={clsx(
"px-3 py-1.5 text-xs font-medium transition-colors flex items-center gap-1.5",
filterType === 'tech' ? "bg-accent/20 text-accent" : "text-white/40 hover:text-white/60"
)}
>
<Zap className="w-3 h-3" />
Tech
</button>
<button
onClick={() => setFilterType('geo')}
className={clsx(
"px-3 py-1.5 text-xs font-medium transition-colors flex items-center gap-1.5",
filterType === 'geo' ? "bg-white/10 text-white" : "text-white/40 hover:text-white/60"
)}
>
<Globe className="w-3 h-3" />
Geo / National
</button>
<button
onClick={() => setFilterType('budget')}
className={clsx(
"px-3 py-1.5 text-xs font-medium transition-colors flex items-center gap-1.5",
filterType === 'budget' ? "bg-white/10 text-white" : "text-white/40 hover:text-white/60"
)}
>
<DollarSign className="w-3 h-3" />
Budget {'<'}$10
</button>
{/* Search Filter */}
<div className="relative w-full md:w-64 flex-shrink-0">
<Search className="absolute left-3 top-2.5 w-4 h-4 text-zinc-500" />
<div className="flex-1" />
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-white/30" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search TLDs..."
className="w-full bg-black/50 border border-white/10 rounded-lg pl-9 pr-4 py-2 text-sm text-white placeholder:text-zinc-600 focus:outline-none focus:border-white/20 transition-all"
className="bg-[#050505] border border-white/10 pl-9 pr-4 py-2 text-sm text-white placeholder:text-white/25 outline-none focus:border-accent/40 w-48 lg:w-64"
/>
</div>
</div>
{/* DATA GRID */}
<div className="bg-zinc-900/40 border border-white/5 rounded-xl overflow-hidden backdrop-blur-sm">
<button
onClick={handleRefresh}
disabled={refreshing}
className="p-2 text-white/30 hover:text-white transition-colors"
>
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
</button>
</div>
</section>
{/* Unified Table Header - Use a wrapper with min-width to force scrolling instead of breaking */}
<div className="overflow-x-auto">
<div className="min-w-[1000px]"> {/* Force minimum width */}
<div className="grid grid-cols-12 gap-4 px-6 py-3 bg-white/[0.02] border-b border-white/5 text-[11px] font-semibold text-zinc-500 uppercase tracking-wider sticky top-0 z-20 backdrop-blur-sm items-center">
<div className="col-span-2">
<SortableHeader label="Extension" field="tld" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} />
</div>
<div className="col-span-2 text-right">
<SortableHeader label="Reg. Price" field="price" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="right" />
</div>
<div className="col-span-2 text-right">
<SortableHeader
label="Renewal"
field="renewal"
currentSort={sortField}
currentDirection={sortDirection}
onSort={handleSort}
align="right"
locked={!canSeeRenewal}
lockTooltip="Upgrade to Trader to unlock"
/>
</div>
<div className="col-span-2 text-center">
<SortableHeader label="Trend (1y)" field="change" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="center" />
</div>
<div className="col-span-2 text-center">
{canSee3yTrend ? (
<SortableHeader
label="Trend (3y)"
field="change3y"
currentSort={sortField}
currentDirection={sortDirection}
onSort={handleSort}
align="center"
locked={!canSeeFullHistory}
/>
) : (
<span className="text-zinc-700 select-none">Trend (3y)</span>
)}
</div>
<div className="col-span-1 text-center">
<SortableHeader label="Risk" field="risk" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="center" />
</div>
<div className="col-span-1 text-right py-2">Action</div>
</div>
{/* Rows */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* TABLE */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<section className="py-6">
{loading ? (
<div className="flex flex-col items-center justify-center py-32 space-y-4">
<Loader2 className="w-8 h-8 text-emerald-500 animate-spin" />
<p className="text-zinc-500 text-sm animate-pulse">Analyzing registry data...</p>
<div className="flex items-center justify-center py-20">
<Loader2 className="w-6 h-6 text-accent animate-spin" />
</div>
) : filteredData.length === 0 ? (
<div className="flex flex-col items-center justify-center py-20 text-center">
<div className="w-16 h-16 rounded-full bg-white/5 flex items-center justify-center mb-4">
<Search className="w-8 h-8 text-zinc-600" />
<div className="text-center py-16">
<div className="w-14 h-14 mx-auto bg-white/[0.02] border border-white/10 flex items-center justify-center mb-4">
<Search className="w-6 h-6 text-white/20" />
</div>
<h3 className="text-lg font-medium text-white mb-1">No TLDs found</h3>
<p className="text-zinc-500 text-sm max-w-xs mx-auto">
Try adjusting your filters or search query.
</p>
<p className="text-white/40 text-sm">No TLDs found</p>
</div>
) : (
<div className="divide-y divide-white/5">
<div className="overflow-x-auto">
<table className="w-full min-w-[900px]">
<thead>
<tr className="text-xs text-white/40 border-b border-white/[0.06]">
<th className="text-left py-3 px-4 font-medium">
<button onClick={() => handleSort('tld')} className="flex items-center gap-1 hover:text-white/60">
Extension
{sortField === 'tld' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
</button>
</th>
<th className="text-right py-3 px-4 font-medium">
<button onClick={() => handleSort('price')} className="flex items-center gap-1 ml-auto hover:text-white/60">
Reg. Price
{sortField === 'price' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
</button>
</th>
<th className="text-right py-3 px-4 font-medium">
<button
onClick={() => canSeeRenewal && handleSort('renewal')}
className={clsx("flex items-center gap-1 ml-auto", canSeeRenewal ? "hover:text-white/60" : "opacity-50 cursor-not-allowed")}
>
Renewal
{!canSeeRenewal && <Lock className="w-3 h-3" />}
{sortField === 'renewal' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
</button>
</th>
<th className="text-center py-3 px-4 font-medium">
<button onClick={() => handleSort('change')} className="flex items-center gap-1 mx-auto hover:text-white/60">
1y Trend
{sortField === 'change' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
</button>
</th>
<th className="text-center py-3 px-4 font-medium">
<button
onClick={() => canSee3yTrend && handleSort('change3y')}
className={clsx("flex items-center gap-1 mx-auto", canSee3yTrend ? "hover:text-white/60" : "opacity-50 cursor-not-allowed")}
>
3y Trend
{!canSee3yTrend && <Lock className="w-3 h-3" />}
</button>
</th>
<th className="text-center py-3 px-4 font-medium">Risk</th>
<th className="text-right py-3 px-4 font-medium"></th>
</tr>
</thead>
<tbody>
{filteredData.map((tld) => {
const isTrap = tld.min_renewal_price > tld.min_price * 1.5
const trend = tld.price_change_1y || 0
const trend3y = tld.price_change_3y || 0
return (
<div key={tld.tld} className="grid grid-cols-12 gap-4 px-6 py-4 items-center hover:bg-white/[0.04] transition-all group relative">
{/* TLD */}
<div className="col-span-2">
<Link href={`/terminal/intel/${tld.tld}`} className="flex items-center gap-2 group/link">
<span className="font-mono font-bold text-white text-[15px] group-hover/link:text-emerald-400 transition-colors">.{tld.tld}</span>
<tr key={tld.tld} className="group border-b border-white/[0.04] hover:bg-white/[0.02]">
<td className="py-3 px-4">
<Link href={`/terminal/intel/${tld.tld}`} className="font-mono font-medium text-white hover:text-accent transition-colors">
.{tld.tld}
</Link>
</div>
{/* Price */}
<div className="col-span-2 text-right">
<span className="font-mono font-medium text-white whitespace-nowrap">{formatPrice(tld.min_price)}</span>
</div>
{/* Renewal (Trader+) */}
<div className="col-span-2 text-right flex items-center justify-end gap-2">
</td>
<td className="py-3 px-4 text-right font-mono text-white">
{formatPrice(tld.min_price)}
</td>
<td className="py-3 px-4 text-right">
{canSeeRenewal ? (
<>
<span className={clsx("font-mono font-medium whitespace-nowrap", isTrap ? "text-amber-400" : "text-zinc-400")}>
<div className="flex items-center justify-end gap-1.5">
<span className={clsx("font-mono", isTrap ? "text-amber-400" : "text-white/50")}>
{formatPrice(tld.min_renewal_price)}
</span>
{isTrap && (
<Tooltip content={`Renewal is ${(tld.min_renewal_price/tld.min_price).toFixed(1)}x higher!`}>
<AlertTriangle className="w-3.5 h-3.5 text-amber-400 cursor-help flex-shrink-0" />
</Tooltip>
)}
</>
) : (
<LockedFeature requiredTier="trader" currentTier={userTier} />
)}
{isTrap && <AlertTriangle className="w-3.5 h-3.5 text-amber-400" />}
</div>
{/* Trend 1y */}
<div className="col-span-2 flex justify-center">
<div className={clsx("flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium font-mono whitespace-nowrap",
trend > 5 ? "text-orange-400 bg-orange-400/5 border border-orange-400/20" :
trend < -5 ? "text-emerald-400 bg-emerald-400/5 border border-emerald-400/20" :
"text-zinc-400 bg-zinc-800/50 border border-zinc-700"
) : (
<span className="text-white/20 flex items-center gap-1 justify-end">
<Lock className="w-3 h-3" />
Trader+
</span>
)}
</td>
<td className="py-3 px-4 text-center">
<span className={clsx(
"inline-flex items-center gap-1 text-xs font-mono px-2 py-0.5",
trend > 5 ? "text-orange-400 bg-orange-400/10" :
trend < -5 ? "text-accent bg-accent/10" :
"text-white/40 bg-white/5"
)}>
{trend > 0 ? <TrendingUp className="w-3 h-3" /> : trend < 0 ? <TrendingDown className="w-3 h-3" /> : <Minus className="w-3 h-3" />}
{Math.abs(trend)}%
</div>
</div>
{/* Trend 3y */}
<div className="col-span-2 flex justify-center">
</span>
</td>
<td className="py-3 px-4 text-center">
{canSee3yTrend ? (
<div className={clsx("flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium font-mono whitespace-nowrap",
trend3y > 10 ? "text-orange-400 bg-orange-400/5 border border-orange-400/20" :
trend3y < -10 ? "text-emerald-400 bg-emerald-400/5 border border-emerald-400/20" :
"text-zinc-400 bg-zinc-800/50 border border-zinc-700"
<span className={clsx(
"inline-flex items-center gap-1 text-xs font-mono px-2 py-0.5",
trend3y > 10 ? "text-orange-400 bg-orange-400/10" :
trend3y < -10 ? "text-accent bg-accent/10" :
"text-white/40 bg-white/5"
)}>
{trend3y > 0 ? <TrendingUp className="w-3 h-3" /> : trend3y < 0 ? <TrendingDown className="w-3 h-3" /> : <Minus className="w-3 h-3" />}
{Math.abs(trend3y)}%
</div>
</span>
) : (
<span className="text-zinc-700 text-xs"></span>
<span className="text-white/20"></span>
)}
</div>
{/* Risk */}
<div className="col-span-1 flex justify-center">
<Tooltip content={tld.risk_reason || 'Standard risk profile'}>
<div className="w-16 h-1.5 rounded-full overflow-hidden bg-zinc-800 cursor-help">
<div className={clsx("h-full rounded-full",
tld.risk_level === 'low' ? "w-1/3 bg-emerald-500" :
tld.risk_level === 'medium' ? "w-2/3 bg-amber-500" :
"w-full bg-red-500"
</td>
<td className="py-3 px-4 text-center">
<div className="w-16 h-1.5 mx-auto bg-white/10 overflow-hidden" title={tld.risk_reason || 'Standard risk'}>
<div className={clsx(
"h-full",
tld.risk_level === 'low' ? "w-1/3 bg-accent" :
tld.risk_level === 'medium' ? "w-2/3 bg-amber-400" :
"w-full bg-red-400"
)} />
</div>
</Tooltip>
</div>
{/* Action */}
<div className="col-span-1 flex justify-end items-center gap-3">
</td>
<td className="py-3 px-4 text-right">
<Link
href={`/terminal/intel/${tld.tld}`}
className="h-8 px-3 flex items-center gap-2 rounded-lg text-xs font-bold transition-all bg-white text-black hover:bg-zinc-200 shadow-white/10 opacity-0 group-hover:opacity-100 uppercase tracking-wide whitespace-nowrap"
className="opacity-0 group-hover:opacity-100 inline-flex items-center gap-1 text-xs text-white/50 hover:text-white transition-all"
>
<ArrowRight className="w-3 h-3" />
<ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
)}
</div>
</div>
</div>
</section>
{/* Upgrade CTA for Scout users */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* UPGRADE CTA */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{userTier === 'scout' && (
<div className="mt-8 p-6 rounded-2xl bg-gradient-to-br from-zinc-900 to-zinc-900/50 border border-white/5 text-center">
<div className="w-12 h-12 bg-emerald-500/10 rounded-full flex items-center justify-center mx-auto mb-4 text-emerald-400">
<BarChart3 className="w-6 h-6" />
</div>
<h3 className="text-lg font-bold text-white mb-2">Unlock Full TLD Intelligence</h3>
<p className="text-sm text-zinc-400 mb-4 max-w-md mx-auto">
See renewal prices, identify renewal traps, and access detailed price history charts with Trader or Tycoon.
<section className="py-8 border-t border-white/[0.06]">
<div className="text-center max-w-md mx-auto">
<Sparkles className="w-8 h-8 text-accent mx-auto mb-4" />
<h3 className="font-display text-xl text-white mb-2">Unlock Full Intel</h3>
<p className="text-sm text-white/40 mb-6">
See renewal prices, identify traps, and access 3-year price history with Trader or Tycoon.
</p>
<Link
href="/pricing"
className="inline-flex items-center gap-2 px-6 py-3 bg-white text-black font-bold rounded-lg hover:bg-zinc-200 transition-all"
className="inline-flex items-center gap-2 px-6 py-3 bg-white text-black font-semibold hover:bg-white/90 transition-colors"
>
<Sparkles className="w-4 h-4" />
Upgrade Now
</Link>
</div>
</section>
)}
</div>
</div>
</TerminalLayout>
</CommandCenterLayout>
)
}

View File

@ -3,7 +3,7 @@
import { useEffect, useState, useCallback } from 'react'
import { useStore } from '@/lib/store'
import { api } from '@/lib/api'
import { TerminalLayout } from '@/components/TerminalLayout'
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
import {
Plus,
Target,
@ -12,62 +12,21 @@ import {
Trash2,
Power,
PowerOff,
Eye,
Bell,
MessageSquare,
Loader2,
X,
AlertCircle,
CheckCircle,
TrendingUp,
Filter,
Clock,
DollarSign,
Hash,
Tag,
Crown,
Activity
} from 'lucide-react'
import clsx from 'clsx'
import Link from 'next/link'
// ============================================================================
// SHARED COMPONENTS
// ============================================================================
const StatCard = ({ label, value, subValue, icon: Icon, highlight, trend }: {
label: string
value: string | number
subValue?: string
icon: any
highlight?: boolean
trend?: 'up' | 'down' | 'neutral' | 'active'
}) => (
<div className={clsx(
"bg-zinc-900/40 border p-4 relative overflow-hidden group hover:border-white/10 transition-colors h-full",
highlight ? "border-emerald-500/30" : "border-white/5"
)}>
<div className="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity">
<Icon className="w-16 h-16" />
</div>
<div className="relative z-10">
<div className="flex items-center gap-2 text-zinc-400 mb-1">
<Icon className={clsx("w-4 h-4", (highlight || trend === 'active' || trend === 'up') && "text-emerald-400")} />
<span className="text-xs font-medium uppercase tracking-wider">{label}</span>
</div>
<div className="flex items-baseline gap-2">
<span className="text-2xl font-bold text-white tracking-tight">{value}</span>
{subValue && <span className="text-xs text-zinc-500 font-medium">{subValue}</span>}
</div>
{highlight && (
<div className="mt-2 text-[10px] font-medium px-1.5 py-0.5 w-fit rounded border text-emerald-400 border-emerald-400/20 bg-emerald-400/5">
LIVE
</div>
)}
</div>
</div>
)
// ============================================================================
// INTERFACES
// ============================================================================
@ -111,19 +70,16 @@ export default function SniperAlertsPage() {
const [deletingId, setDeletingId] = useState<number | null>(null)
const [togglingId, setTogglingId] = useState<number | null>(null)
// Tier-based limits
const tier = subscription?.tier || 'scout'
const alertLimits: Record<string, number> = { scout: 2, trader: 10, tycoon: 50 }
const maxAlerts = alertLimits[tier] || 2
const canAddMore = alerts.length < maxAlerts
const isTycoon = tier === 'tycoon'
// Stats
const activeAlerts = alerts.filter(a => a.is_active).length
const totalMatches = alerts.reduce((sum, a) => sum + a.matches_count, 0)
const totalNotifications = alerts.reduce((sum, a) => sum + a.notifications_sent, 0)
// Load alerts
const loadAlerts = useCallback(async () => {
setLoading(true)
try {
@ -140,7 +96,6 @@ export default function SniperAlertsPage() {
loadAlerts()
}, [loadAlerts])
// Toggle alert active status
const handleToggle = async (id: number, currentStatus: boolean) => {
setTogglingId(id)
try {
@ -156,7 +111,6 @@ export default function SniperAlertsPage() {
}
}
// Delete alert
const handleDelete = async (id: number, name: string) => {
if (!confirm(`Delete alert "${name}"?`)) return
@ -172,129 +126,115 @@ export default function SniperAlertsPage() {
}
return (
<TerminalLayout>
<div className="relative min-h-screen">
{/* Ambient Background Glow */}
<div className="fixed inset-0 pointer-events-none">
<div className="absolute top-0 left-1/4 w-96 h-96 bg-emerald-500/5 rounded-full blur-3xl" />
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-blue-500/5 rounded-full blur-3xl" />
<CommandCenterLayout minimal>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* HEADER */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<section className="pt-6 lg:pt-8 pb-6">
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-6">
<div className="space-y-3">
<div className="inline-flex items-center gap-2">
<Target className="w-4 h-4 text-accent" />
<span className="text-[10px] font-mono tracking-wide text-accent">Automated Alerts</span>
</div>
{/* Content */}
<div className="relative z-10 space-y-8">
{/* Header */}
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<div className="flex items-center gap-3">
<div className="h-8 w-1 bg-emerald-500 rounded-full shadow-[0_0_10px_rgba(16,185,129,0.5)]" />
<h1 className="text-3xl font-bold tracking-tight text-white">Sniper Alerts</h1>
</div>
<p className="text-zinc-400 mt-1 max-w-2xl">
Get notified when domains matching your exact criteria hit the market. Set it, forget it, and pounce when the time is right.
<h1 className="font-display text-[2rem] lg:text-[2.5rem] leading-[1] tracking-[-0.02em]">
<span className="text-white">Sniper</span>
<span className="text-white/30 ml-3">{alerts.length}/{maxAlerts}</span>
</h1>
<p className="text-white/40 text-sm max-w-md">
Get notified when domains matching your criteria hit the market
</p>
</div>
<div className="flex items-center gap-4">
<div className="flex gap-6">
<div className="text-right">
<div className="text-xl font-display text-accent">{activeAlerts}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">Active</div>
</div>
<div className="text-right">
<div className="text-xl font-display text-white">{totalMatches}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">Matches</div>
</div>
<div className="text-right">
<div className="text-xl font-display text-white">{totalNotifications}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">Sent</div>
</div>
</div>
<button
onClick={() => setShowCreateModal(true)}
disabled={!canAddMore}
className="px-4 py-2 bg-emerald-500 text-white font-medium rounded-lg hover:bg-emerald-400 transition-all shadow-lg shadow-emerald-500/20 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2 whitespace-nowrap"
className={clsx(
"flex items-center gap-2 px-4 py-2 text-sm font-semibold transition-colors",
canAddMore
? "bg-accent text-black hover:bg-white"
: "bg-white/10 text-white/40 cursor-not-allowed"
)}
>
<Plus className="w-4 h-4" /> New Alert
<Plus className="w-4 h-4" />
New Alert
</button>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<StatCard
label="Active Alerts"
value={activeAlerts}
subValue={`/ ${maxAlerts} slots`}
icon={Target}
trend="active"
highlight={activeAlerts > 0}
/>
<StatCard
label="Total Matches"
value={totalMatches}
subValue="All time"
icon={Zap}
trend={totalMatches > 0 ? 'up' : 'neutral'}
/>
<StatCard
label="Notifications"
value={totalNotifications}
subValue="Sent"
icon={Bell}
trend={totalNotifications > 0 ? 'up' : 'neutral'}
/>
<StatCard
label="Monitoring"
value={alerts.length}
subValue="Total alerts"
icon={Activity}
trend="neutral"
/>
</div>
</section>
{/* Alerts List */}
<div className="bg-zinc-900/40 border border-white/5 rounded-xl overflow-hidden backdrop-blur-sm">
{/* Header */}
<div className="px-6 py-4 bg-white/[0.02] border-b border-white/5 flex items-center justify-between">
<h2 className="text-sm font-semibold text-zinc-400 uppercase tracking-wider flex items-center gap-2">
<Filter className="w-4 h-4" />
Your Alerts
</h2>
<span className="text-xs text-zinc-600">{alerts.length} / {maxAlerts}</span>
</div>
{/* Loading */}
{loading && (
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* ALERTS LIST */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<section className="py-6 border-t border-white/[0.08]">
{loading ? (
<div className="flex items-center justify-center py-20">
<Loader2 className="w-8 h-8 text-emerald-500 animate-spin" />
<Loader2 className="w-6 h-6 text-accent animate-spin" />
</div>
)}
{/* Empty State */}
{!loading && alerts.length === 0 && (
<div className="flex flex-col items-center justify-center py-20 text-center px-4">
<div className="w-16 h-16 rounded-full bg-white/5 flex items-center justify-center mb-4">
<Target className="w-8 h-8 text-zinc-600" />
) : alerts.length === 0 ? (
<div className="text-center py-16">
<div className="w-14 h-14 mx-auto bg-white/[0.02] border border-white/10 flex items-center justify-center mb-4">
<Target className="w-6 h-6 text-white/20" />
</div>
<h3 className="text-lg font-bold text-white mb-2">No Alerts Yet</h3>
<p className="text-sm text-zinc-500 max-w-md mb-6">
Create your first sniper alert to get notified when domains matching your criteria appear in auctions.
<p className="text-white/40 text-sm mb-2">No alerts yet</p>
<p className="text-white/25 text-xs mb-6 max-w-sm mx-auto">
Create your first sniper alert to get notified when matching domains appear
</p>
<button
onClick={() => setShowCreateModal(true)}
className="px-4 py-2 bg-emerald-500 text-white font-medium rounded-lg hover:bg-emerald-400 transition-all shadow-lg shadow-emerald-500/20 flex items-center gap-2"
className="inline-flex items-center gap-2 px-4 py-2 bg-accent text-black text-sm font-semibold hover:bg-white transition-colors"
>
<Plus className="w-4 h-4" /> Create First Alert
<Plus className="w-4 h-4" />
Create First Alert
</button>
</div>
)}
{/* Alerts Grid */}
{!loading && alerts.length > 0 && (
<div className="divide-y divide-white/5">
) : (
<div className="space-y-3">
{alerts.map((alert) => (
<div key={alert.id} className="p-6 hover:bg-white/[0.02] transition-colors group">
<div
key={alert.id}
className={clsx(
"group border transition-all",
alert.is_active
? "bg-white/[0.02] border-white/[0.08] hover:border-accent/30"
: "bg-white/[0.01] border-white/[0.04]"
)}
>
<div className="p-5">
<div className="flex items-start justify-between gap-4">
{/* Alert Info */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-lg font-bold text-white truncate">{alert.name}</h3>
<h3 className="text-base font-medium text-white truncate">{alert.name}</h3>
{alert.is_active ? (
<span className="px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 flex items-center gap-1">
<div className="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
ACTIVE
<span className="px-2 py-0.5 text-[9px] font-mono bg-accent/10 text-accent border border-accent/20 flex items-center gap-1">
<div className="w-1.5 h-1.5 bg-accent animate-pulse" />
Active
</span>
) : (
<span className="px-2 py-0.5 rounded-full text-[10px] font-bold bg-zinc-800 text-zinc-500 border border-zinc-700">
PAUSED
<span className="px-2 py-0.5 text-[9px] font-mono bg-white/5 text-white/40 border border-white/10">
Paused
</span>
)}
{isTycoon && alert.notify_sms && (
<span className="px-2 py-0.5 rounded-full text-[10px] font-bold bg-amber-500/10 text-amber-400 border border-amber-500/20 flex items-center gap-1">
<span className="px-2 py-0.5 text-[9px] font-mono bg-amber-400/10 text-amber-400 border border-amber-400/20 flex items-center gap-1">
<Crown className="w-3 h-3" />
SMS
</span>
@ -302,52 +242,50 @@ export default function SniperAlertsPage() {
</div>
{alert.description && (
<p className="text-sm text-zinc-400 mb-3">{alert.description}</p>
<p className="text-sm text-white/40 mb-3">{alert.description}</p>
)}
{/* Criteria Pills */}
<div className="flex flex-wrap gap-2 mb-3">
<div className="flex flex-wrap gap-1.5 mb-3">
{alert.tlds && (
<span className="px-2 py-1 rounded-lg bg-blue-500/10 text-blue-400 text-xs font-medium border border-blue-500/20">
<span className="px-2 py-0.5 text-[10px] font-mono bg-blue-400/10 text-blue-400 border border-blue-400/20">
{alert.tlds}
</span>
)}
{alert.keywords && (
<span className="px-2 py-1 rounded-lg bg-emerald-500/10 text-emerald-400 text-xs font-medium border border-emerald-500/20">
<span className="px-2 py-0.5 text-[10px] font-mono bg-accent/10 text-accent border border-accent/20">
+{alert.keywords}
</span>
)}
{alert.exclude_keywords && (
<span className="px-2 py-1 rounded-lg bg-rose-500/10 text-rose-400 text-xs font-medium border border-rose-500/20">
<span className="px-2 py-0.5 text-[10px] font-mono bg-rose-400/10 text-rose-400 border border-rose-400/20">
-{alert.exclude_keywords}
</span>
)}
{(alert.min_length || alert.max_length) && (
<span className="px-2 py-1 rounded-lg bg-zinc-800 text-zinc-400 text-xs font-medium border border-zinc-700 flex items-center gap-1">
<span className="px-2 py-0.5 text-[10px] font-mono bg-white/5 text-white/50 border border-white/10 flex items-center gap-1">
<Hash className="w-3 h-3" />
{alert.min_length || 1}-{alert.max_length || 63} chars
{alert.min_length || 1}-{alert.max_length || 63}
</span>
)}
{(alert.min_price || alert.max_price) && (
<span className="px-2 py-1 rounded-lg bg-zinc-800 text-zinc-400 text-xs font-medium border border-zinc-700 flex items-center gap-1">
<span className="px-2 py-0.5 text-[10px] font-mono bg-white/5 text-white/50 border border-white/10 flex items-center gap-1">
<DollarSign className="w-3 h-3" />
{alert.min_price ? `$${alert.min_price}+` : ''}{alert.max_price ? ` - $${alert.max_price}` : ''}
{alert.min_price ? `$${alert.min_price}` : ''}{alert.max_price ? ` - $${alert.max_price}` : '+'}
</span>
)}
{alert.no_numbers && (
<span className="px-2 py-1 rounded-lg bg-zinc-800 text-zinc-400 text-xs font-medium border border-zinc-700">
No numbers
<span className="px-2 py-0.5 text-[10px] font-mono bg-white/5 text-white/50 border border-white/10">
No digits
</span>
)}
{alert.no_hyphens && (
<span className="px-2 py-1 rounded-lg bg-zinc-800 text-zinc-400 text-xs font-medium border border-zinc-700">
<span className="px-2 py-0.5 text-[10px] font-mono bg-white/5 text-white/50 border border-white/10">
No hyphens
</span>
)}
</div>
{/* Stats */}
<div className="flex items-center gap-4 text-xs text-zinc-500">
<div className="flex items-center gap-4 text-[10px] text-white/30 font-mono">
<span className="flex items-center gap-1">
<Zap className="w-3 h-3" />
{alert.matches_count} matches
@ -359,24 +297,22 @@ export default function SniperAlertsPage() {
{alert.last_matched_at && (
<span className="flex items-center gap-1">
<Clock className="w-3 h-3" />
Last: {new Date(alert.last_matched_at).toLocaleDateString()}
{new Date(alert.last_matched_at).toLocaleDateString()}
</span>
)}
</div>
</div>
{/* Actions */}
<div className="flex items-center gap-2">
<button
onClick={() => handleToggle(alert.id, alert.is_active)}
disabled={togglingId === alert.id}
className={clsx(
"p-2 rounded-lg border transition-all",
"w-8 h-8 flex items-center justify-center border transition-colors",
alert.is_active
? "bg-emerald-500/10 border-emerald-500/20 text-emerald-400 hover:bg-emerald-500/20"
: "bg-zinc-800/50 border-zinc-700 text-zinc-500 hover:bg-zinc-800"
? "bg-accent/10 border-accent/20 text-accent hover:bg-accent/20"
: "bg-white/5 border-white/10 text-white/40 hover:bg-white/10"
)}
title={alert.is_active ? "Pause" : "Activate"}
>
{togglingId === alert.id ? (
<Loader2 className="w-4 h-4 animate-spin" />
@ -389,8 +325,7 @@ export default function SniperAlertsPage() {
<button
onClick={() => setEditingAlert(alert)}
className="p-2 rounded-lg border bg-zinc-800/50 border-zinc-700 text-zinc-400 hover:text-white hover:bg-zinc-800 transition-all"
title="Edit"
className="w-8 h-8 flex items-center justify-center border bg-white/5 border-white/10 text-white/40 hover:text-white hover:bg-white/10 transition-colors"
>
<Edit2 className="w-4 h-4" />
</button>
@ -398,8 +333,7 @@ export default function SniperAlertsPage() {
<button
onClick={() => handleDelete(alert.id, alert.name)}
disabled={deletingId === alert.id}
className="p-2 rounded-lg border bg-zinc-800/50 border-zinc-700 text-zinc-400 hover:text-rose-400 hover:border-rose-500/30 transition-all disabled:opacity-50"
title="Delete"
className="w-8 h-8 flex items-center justify-center border bg-white/5 border-white/10 text-white/40 hover:text-rose-400 hover:border-rose-400/30 transition-colors"
>
{deletingId === alert.id ? (
<Loader2 className="w-4 h-4 animate-spin" />
@ -410,36 +344,40 @@ export default function SniperAlertsPage() {
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
</section>
{/* Upgrade CTA */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* UPGRADE CTA */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{!canAddMore && (
<div className="p-6 bg-gradient-to-br from-amber-900/20 to-black border border-amber-500/20 rounded-2xl text-center">
<Crown className="w-12 h-12 text-amber-400 mx-auto mb-4" />
<h3 className="text-xl font-bold text-white mb-2">Alert Limit Reached</h3>
<p className="text-zinc-400 mb-4">
You've created {maxAlerts} alerts. Upgrade to add more.
<section className="py-8 border-t border-white/[0.06]">
<div className="text-center max-w-md mx-auto">
<Crown className="w-8 h-8 text-amber-400 mx-auto mb-4" />
<h3 className="font-display text-xl text-white mb-2">Alert Limit Reached</h3>
<p className="text-sm text-white/40 mb-4">
You've created {maxAlerts} alerts. Upgrade for more.
</p>
<div className="flex gap-4 justify-center text-sm">
<div className="px-4 py-2 bg-white/5 rounded-lg">
<span className="text-zinc-500">Trader:</span> <span className="text-white font-bold">10 alerts</span>
<div className="flex gap-4 justify-center text-xs mb-6">
<div className="px-3 py-2 bg-white/5 border border-white/10">
<span className="text-white/40">Trader:</span> <span className="text-white font-medium">10 alerts</span>
</div>
<div className="px-4 py-2 bg-amber-500/10 border border-amber-500/20 rounded-lg">
<span className="text-zinc-500">Tycoon:</span> <span className="text-amber-400 font-bold">50 alerts + SMS</span>
<div className="px-3 py-2 bg-amber-400/10 border border-amber-400/20">
<span className="text-white/40">Tycoon:</span> <span className="text-amber-400 font-medium">50 + SMS</span>
</div>
</div>
<Link
href="/pricing"
className="inline-flex items-center gap-2 px-6 py-3 bg-amber-500 text-white font-bold rounded-xl hover:bg-amber-400 transition-all shadow-lg shadow-amber-500/20 mt-4"
className="inline-flex items-center gap-2 px-6 py-3 bg-amber-400 text-black font-semibold hover:bg-amber-300 transition-colors"
>
Upgrade Now
</Link>
</div>
</section>
)}
</div>
{/* Create/Edit Modal */}
{(showCreateModal || editingAlert) && (
@ -457,8 +395,7 @@ export default function SniperAlertsPage() {
isTycoon={isTycoon}
/>
)}
</div>
</TerminalLayout>
</CommandCenterLayout>
)
}
@ -548,28 +485,26 @@ function CreateEditModal({ alert, onClose, onSuccess, isTycoon }: {
onClick={onClose}
>
<div
className="w-full max-w-2xl bg-[#0A0A0A] border border-white/10 rounded-2xl shadow-2xl my-8"
className="w-full max-w-2xl bg-[#050505] border border-white/10 my-8"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-white/5 bg-white/[0.02]">
<div className="flex items-center justify-between p-6 border-b border-white/[0.08]">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg border bg-emerald-500/10 border-emerald-500/20">
<Target className="w-5 h-5 text-emerald-400" />
</div>
<Target className="w-5 h-5 text-accent" />
<div>
<h3 className="font-bold text-lg text-white">{isEditing ? 'Edit Alert' : 'Create Sniper Alert'}</h3>
<p className="text-xs text-zinc-500">Set precise criteria for domain matching</p>
<h3 className="font-medium text-white">{isEditing ? 'Edit Alert' : 'Create Sniper Alert'}</h3>
<p className="text-xs text-white/40">Set criteria for domain matching</p>
</div>
</div>
<button onClick={onClose} className="p-2 hover:bg-white/10 rounded-full text-zinc-500 hover:text-white transition-colors">
<button onClick={onClose} className="p-2 hover:bg-white/10 text-white/40 hover:text-white transition-colors">
<X className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleSubmit} className="p-6 space-y-6 max-h-[70vh] overflow-y-auto">
{error && (
<div className="p-4 bg-rose-500/10 border border-rose-500/20 rounded-xl flex items-center gap-3 text-rose-400">
<div className="p-4 bg-rose-500/10 border border-rose-500/20 flex items-center gap-3 text-rose-400">
<AlertCircle className="w-5 h-5" />
<p className="text-sm flex-1">{error}</p>
</div>
@ -577,85 +512,85 @@ function CreateEditModal({ alert, onClose, onSuccess, isTycoon }: {
{/* Basic Info */}
<div className="space-y-4">
<h4 className="text-sm font-bold text-zinc-400 uppercase tracking-wider">Basic Info</h4>
<h4 className="text-xs font-mono text-white/40 tracking-wide">Basic Info</h4>
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Alert Name *</label>
<label className="block text-xs text-white/50 mb-2">Alert Name *</label>
<input
type="text"
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
placeholder="e.g. Premium 4L .com domains"
required
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all"
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50 transition-all"
/>
</div>
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Description</label>
<label className="block text-xs text-white/50 mb-2">Description</label>
<textarea
value={form.description}
onChange={(e) => setForm({ ...form, description: e.target.value })}
placeholder="Optional description"
rows={2}
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all resize-none"
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50 transition-all resize-none"
/>
</div>
</div>
{/* Filters */}
<div className="space-y-4">
<h4 className="text-sm font-bold text-zinc-400 uppercase tracking-wider">Criteria</h4>
<h4 className="text-xs font-mono text-white/40 tracking-wide">Criteria</h4>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">TLDs</label>
<label className="block text-xs text-white/50 mb-2">TLDs</label>
<input
type="text"
value={form.tlds}
onChange={(e) => setForm({ ...form, tlds: e.target.value })}
placeholder="com,io,ai"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all font-mono text-sm"
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50 font-mono text-sm"
/>
</div>
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Platforms</label>
<label className="block text-xs text-white/50 mb-2">Platforms</label>
<input
type="text"
value={form.platforms}
onChange={(e) => setForm({ ...form, platforms: e.target.value })}
placeholder="godaddy,sedo"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all font-mono text-sm"
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50 font-mono text-sm"
/>
</div>
</div>
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Must Contain</label>
<label className="block text-xs text-white/50 mb-2">Must Contain</label>
<input
type="text"
value={form.keywords}
onChange={(e) => setForm({ ...form, keywords: e.target.value })}
placeholder="crypto,web3,ai"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all font-mono text-sm"
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50 font-mono text-sm"
/>
</div>
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Must NOT Contain</label>
<label className="block text-xs text-white/50 mb-2">Must Not Contain</label>
<input
type="text"
value={form.exclude_keywords}
onChange={(e) => setForm({ ...form, exclude_keywords: e.target.value })}
placeholder="xxx,adult"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all font-mono text-sm"
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50 font-mono text-sm"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Min Length</label>
<label className="block text-xs text-white/50 mb-2">Min Length</label>
<input
type="number"
value={form.min_length}
@ -663,12 +598,12 @@ function CreateEditModal({ alert, onClose, onSuccess, isTycoon }: {
placeholder="1"
min="1"
max="63"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all"
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50"
/>
</div>
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Max Length</label>
<label className="block text-xs text-white/50 mb-2">Max Length</label>
<input
type="number"
value={form.max_length}
@ -676,14 +611,14 @@ function CreateEditModal({ alert, onClose, onSuccess, isTycoon }: {
placeholder="63"
min="1"
max="63"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all"
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Min Price (USD)</label>
<label className="block text-xs text-white/50 mb-2">Min Price (USD)</label>
<input
type="number"
value={form.min_price}
@ -691,12 +626,12 @@ function CreateEditModal({ alert, onClose, onSuccess, isTycoon }: {
placeholder="0"
min="0"
step="0.01"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all"
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50"
/>
</div>
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Max Price (USD)</label>
<label className="block text-xs text-white/50 mb-2">Max Price (USD)</label>
<input
type="number"
value={form.max_price}
@ -704,100 +639,62 @@ function CreateEditModal({ alert, onClose, onSuccess, isTycoon }: {
placeholder="10000"
min="0"
step="0.01"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all"
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Max Bids</label>
<input
type="number"
value={form.max_bids}
onChange={(e) => setForm({ ...form, max_bids: e.target.value })}
placeholder="Low competition"
min="0"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all"
/>
</div>
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Ending Within (hours)</label>
<input
type="number"
value={form.ending_within_hours}
onChange={(e) => setForm({ ...form, ending_within_hours: e.target.value })}
placeholder="24"
min="1"
max="168"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all"
/>
</div>
</div>
<div className="space-y-3">
<label className="flex items-center gap-3 cursor-pointer p-3 rounded-lg border border-white/5 hover:bg-white/5 transition-colors">
<div className="space-y-2">
<label className="flex items-center gap-3 cursor-pointer p-3 border border-white/[0.06] hover:bg-white/[0.02] transition-colors">
<input
type="checkbox"
checked={form.no_numbers}
onChange={(e) => setForm({ ...form, no_numbers: e.target.checked })}
className="w-5 h-5 rounded border-white/20 bg-black text-emerald-500 focus:ring-emerald-500 focus:ring-offset-0"
className="w-4 h-4 border-white/20 bg-black text-accent focus:ring-accent focus:ring-offset-0"
/>
<span className="text-sm text-zinc-300">No numbers in domain</span>
<span className="text-sm text-white/60">No numbers in domain</span>
</label>
<label className="flex items-center gap-3 cursor-pointer p-3 rounded-lg border border-white/5 hover:bg-white/5 transition-colors">
<label className="flex items-center gap-3 cursor-pointer p-3 border border-white/[0.06] hover:bg-white/[0.02] transition-colors">
<input
type="checkbox"
checked={form.no_hyphens}
onChange={(e) => setForm({ ...form, no_hyphens: e.target.checked })}
className="w-5 h-5 rounded border-white/20 bg-black text-emerald-500 focus:ring-emerald-500 focus:ring-offset-0"
className="w-4 h-4 border-white/20 bg-black text-accent focus:ring-accent focus:ring-offset-0"
/>
<span className="text-sm text-zinc-300">No hyphens in domain</span>
<span className="text-sm text-white/60">No hyphens in domain</span>
</label>
</div>
<div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Exclude Characters</label>
<input
type="text"
value={form.exclude_chars}
onChange={(e) => setForm({ ...form, exclude_chars: e.target.value })}
placeholder="q,x,z"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all font-mono text-sm"
/>
</div>
</div>
{/* Notifications */}
<div className="space-y-4">
<h4 className="text-sm font-bold text-zinc-400 uppercase tracking-wider">Notifications</h4>
<h4 className="text-xs font-mono text-white/40 tracking-wide">Notifications</h4>
<label className="flex items-center gap-3 cursor-pointer p-3 rounded-lg border border-white/5 hover:bg-white/5 transition-colors">
<label className="flex items-center gap-3 cursor-pointer p-3 border border-white/[0.06] hover:bg-white/[0.02] transition-colors">
<input
type="checkbox"
checked={form.notify_email}
onChange={(e) => setForm({ ...form, notify_email: e.target.checked })}
className="w-5 h-5 rounded border-white/20 bg-black text-emerald-500 focus:ring-emerald-500 focus:ring-offset-0"
className="w-4 h-4 border-white/20 bg-black text-accent focus:ring-accent focus:ring-offset-0"
/>
<Bell className="w-4 h-4 text-emerald-400" />
<span className="text-sm text-zinc-300 flex-1">Email notifications</span>
<Bell className="w-4 h-4 text-accent" />
<span className="text-sm text-white/60 flex-1">Email notifications</span>
</label>
<label className={clsx(
"flex items-center gap-3 cursor-pointer p-3 rounded-lg border transition-colors",
isTycoon ? "border-amber-500/20 hover:bg-amber-500/5" : "border-white/5 opacity-50 cursor-not-allowed"
"flex items-center gap-3 cursor-pointer p-3 border transition-colors",
isTycoon ? "border-amber-400/20 hover:bg-amber-400/[0.02]" : "border-white/[0.06] opacity-50 cursor-not-allowed"
)}>
<input
type="checkbox"
checked={form.notify_sms}
onChange={(e) => isTycoon && setForm({ ...form, notify_sms: e.target.checked })}
disabled={!isTycoon}
className="w-5 h-5 rounded border-white/20 bg-black text-amber-500 focus:ring-amber-500 focus:ring-offset-0 disabled:opacity-50"
className="w-4 h-4 border-white/20 bg-black text-amber-400 focus:ring-amber-400 focus:ring-offset-0 disabled:opacity-50"
/>
<MessageSquare className="w-4 h-4 text-amber-400" />
<span className="text-sm text-zinc-300 flex-1">SMS notifications</span>
<span className="text-sm text-white/60 flex-1">SMS notifications</span>
{!isTycoon && <Crown className="w-4 h-4 text-amber-400" />}
</label>
</div>
@ -807,14 +704,14 @@ function CreateEditModal({ alert, onClose, onSuccess, isTycoon }: {
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-3 border border-white/10 text-zinc-400 rounded-xl hover:bg-white/5 hover:text-white transition-all font-medium"
className="flex-1 px-4 py-3 border border-white/10 text-white/60 hover:bg-white/5 hover:text-white transition-all font-medium"
>
Cancel
</button>
<button
type="submit"
disabled={loading || !form.name}
className="flex-1 flex items-center justify-center gap-2 px-4 py-3 bg-emerald-500 text-white font-bold rounded-xl hover:bg-emerald-400 transition-all disabled:opacity-50 shadow-lg shadow-emerald-500/20"
className="flex-1 flex items-center justify-center gap-2 px-4 py-3 bg-accent text-black font-semibold hover:bg-white transition-all disabled:opacity-50"
>
{loading ? (
<>
@ -824,7 +721,7 @@ function CreateEditModal({ alert, onClose, onSuccess, isTycoon }: {
) : (
<>
<CheckCircle className="w-5 h-5" />
{isEditing ? 'Update Alert' : 'Create Alert'}
{isEditing ? 'Update' : 'Create'}
</>
)}
</button>
@ -834,4 +731,3 @@ function CreateEditModal({ alert, onClose, onSuccess, isTycoon }: {
</div>
)
}

View File

@ -9,7 +9,6 @@ import {
CheckCircle2,
Clock,
AlertCircle,
ArrowUpRight,
MousePointer,
Target,
Wallet,
@ -17,85 +16,42 @@ import {
ChevronRight,
Copy,
Check,
ExternalLink,
XCircle,
Sparkles,
Loader2
} from 'lucide-react'
import { api, YieldDomain, YieldTransaction } from '@/lib/api'
import { useStore } from '@/lib/store'
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
import clsx from 'clsx'
// Stats Card Component
function StatsCard({
label,
value,
subValue,
icon: Icon,
trend,
color = 'emerald'
}: {
label: string
value: string | number
subValue?: string
icon: any
trend?: number
color?: 'emerald' | 'blue' | 'amber' | 'purple'
}) {
const colorClasses = {
emerald: 'from-emerald-500/20 to-emerald-500/5 text-emerald-400 border-emerald-500/30',
blue: 'from-blue-500/20 to-blue-500/5 text-blue-400 border-blue-500/30',
amber: 'from-amber-500/20 to-amber-500/5 text-amber-400 border-amber-500/30',
purple: 'from-purple-500/20 to-purple-500/5 text-purple-400 border-purple-500/30',
}
// ============================================================================
// STATUS BADGE
// ============================================================================
return (
<div className={`
relative overflow-hidden rounded-xl border bg-gradient-to-br p-5
${colorClasses[color]}
`}>
<div className="flex items-start justify-between">
<div>
<p className="text-xs uppercase tracking-wider text-zinc-400 mb-1">{label}</p>
<p className="text-2xl font-bold text-white">{value}</p>
{subValue && (
<p className="text-sm text-zinc-400 mt-1">{subValue}</p>
)}
</div>
<div className="p-2 rounded-lg bg-black/20">
<Icon className="w-5 h-5" />
</div>
</div>
{trend !== undefined && (
<div className={`mt-3 flex items-center gap-1 text-xs ${trend >= 0 ? 'text-emerald-400' : 'text-red-400'}`}>
<ArrowUpRight className={`w-3 h-3 ${trend < 0 ? 'rotate-180' : ''}`} />
<span>{Math.abs(trend)}% vs last month</span>
</div>
)}
</div>
)
}
// Domain Status Badge
function StatusBadge({ status }: { status: string }) {
const config: Record<string, { color: string; icon: any }> = {
active: { color: 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30', icon: CheckCircle2 },
pending: { color: 'bg-amber-500/20 text-amber-400 border-amber-500/30', icon: Clock },
verifying: { color: 'bg-blue-500/20 text-blue-400 border-blue-500/30', icon: RefreshCw },
paused: { color: 'bg-zinc-500/20 text-zinc-400 border-zinc-500/30', icon: AlertCircle },
error: { color: 'bg-red-500/20 text-red-400 border-red-500/30', icon: XCircle },
active: { color: 'bg-accent/10 text-accent border-accent/20', icon: CheckCircle2 },
pending: { color: 'bg-amber-400/10 text-amber-400 border-amber-400/20', icon: Clock },
verifying: { color: 'bg-blue-400/10 text-blue-400 border-blue-400/20', icon: RefreshCw },
paused: { color: 'bg-white/5 text-white/40 border-white/10', icon: AlertCircle },
error: { color: 'bg-red-400/10 text-red-400 border-red-400/20', icon: XCircle },
}
const { color, icon: Icon } = config[status] || config.pending
return (
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs border ${color}`}>
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 text-[10px] font-mono border ${color}`}>
<Icon className="w-3 h-3" />
{status}
</span>
)
}
// Activate Domain Modal
// ============================================================================
// ACTIVATE MODAL
// ============================================================================
function ActivateModal({
isOpen,
onClose,
@ -115,7 +71,6 @@ function ActivateModal({
const handleAnalyze = async () => {
if (!domain.trim()) return
setLoading(true)
setError(null)
@ -163,19 +118,17 @@ function ActivateModal({
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" onClick={onClose} />
<div className="fixed inset-0 z-[100] flex items-center justify-center">
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onClose} />
<div className="relative bg-zinc-900 border border-zinc-800 rounded-2xl w-full max-w-lg mx-4 overflow-hidden">
<div className="relative bg-[#050505] border border-white/10 w-full max-w-lg mx-4">
{/* Header */}
<div className="p-6 border-b border-zinc-800">
<div className="p-6 border-b border-white/[0.08]">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-emerald-500/20">
<Sparkles className="w-5 h-5 text-emerald-400" />
</div>
<Sparkles className="w-5 h-5 text-accent" />
<div>
<h2 className="text-lg font-semibold text-white">Activate Domain for Yield</h2>
<p className="text-sm text-zinc-400">Turn your parked domains into passive income</p>
<h2 className="font-medium text-white">Activate Domain for Yield</h2>
<p className="text-xs text-white/40">Turn parked domains into passive income</p>
</div>
</div>
</div>
@ -185,19 +138,19 @@ function ActivateModal({
{step === 'input' && (
<div className="space-y-4">
<div>
<label className="block text-sm text-zinc-400 mb-2">Domain Name</label>
<label className="block text-xs text-white/50 mb-2">Domain Name</label>
<input
type="text"
value={domain}
onChange={(e) => setDomain(e.target.value)}
placeholder="e.g. zahnarzt-zuerich.ch"
className="w-full px-4 py-3 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:border-emerald-500"
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50"
onKeyDown={(e) => e.key === 'Enter' && handleAnalyze()}
/>
</div>
{error && (
<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/30 text-red-400 text-sm">
<div className="p-3 bg-red-400/10 border border-red-400/20 text-red-400 text-sm">
{error}
</div>
)}
@ -205,7 +158,7 @@ function ActivateModal({
<button
onClick={handleAnalyze}
disabled={loading || !domain.trim()}
className="w-full py-3 px-4 bg-emerald-600 hover:bg-emerald-500 disabled:bg-zinc-700 disabled:text-zinc-400 text-white font-medium rounded-lg transition-colors flex items-center justify-center gap-2"
className="w-full py-3 px-4 bg-accent text-black font-semibold hover:bg-white disabled:bg-white/10 disabled:text-white/40 transition-colors flex items-center justify-center gap-2"
>
{loading ? (
<>
@ -224,28 +177,28 @@ function ActivateModal({
{step === 'analyze' && analysis && (
<div className="space-y-5">
{/* Intent Detection Results */}
<div className="p-4 rounded-xl bg-zinc-800/50 border border-zinc-700">
<div className="p-4 bg-white/[0.02] border border-white/[0.08]">
<div className="flex items-center justify-between mb-3">
<span className="text-sm text-zinc-400">Detected Intent</span>
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${
analysis.intent.confidence > 0.7 ? 'bg-emerald-500/20 text-emerald-400' :
analysis.intent.confidence > 0.4 ? 'bg-amber-500/20 text-amber-400' :
'bg-zinc-500/20 text-zinc-400'
}`}>
<span className="text-xs text-white/40">Detected Intent</span>
<span className={clsx(
"px-2 py-0.5 text-[10px] font-mono",
analysis.intent.confidence > 0.7 ? 'bg-accent/10 text-accent' :
analysis.intent.confidence > 0.4 ? 'bg-amber-400/10 text-amber-400' :
'bg-white/5 text-white/40'
)}>
{Math.round(analysis.intent.confidence * 100)}% confidence
</span>
</div>
<p className="text-lg font-semibold text-white capitalize">
<p className="text-base font-medium text-white capitalize">
{analysis.intent.category.replace('_', ' ')}
{analysis.intent.subcategory && (
<span className="text-zinc-400"> / {analysis.intent.subcategory.replace('_', ' ')}</span>
<span className="text-white/40"> / {analysis.intent.subcategory.replace('_', ' ')}</span>
)}
</p>
{analysis.intent.keywords_matched.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1">
{analysis.intent.keywords_matched.map((kw: string, i: number) => (
<span key={i} className="px-2 py-0.5 bg-zinc-700 rounded text-xs text-zinc-300">
<span key={i} className="px-2 py-0.5 bg-white/5 text-[10px] font-mono text-white/60">
{kw.split('~')[0]}
</span>
))}
@ -253,37 +206,33 @@ function ActivateModal({
)}
</div>
{/* Value Estimate */}
<div className="p-4 rounded-xl bg-gradient-to-br from-emerald-500/10 to-transparent border border-emerald-500/30">
<span className="text-sm text-zinc-400">Estimated Monthly Revenue</span>
<div className="p-4 bg-accent/5 border border-accent/20">
<span className="text-xs text-white/40">Estimated Monthly Revenue</span>
<div className="flex items-baseline gap-2 mt-1">
<span className="text-3xl font-bold text-emerald-400">
<span className="text-2xl font-display text-accent">
{analysis.value.currency} {analysis.value.estimated_monthly_min}
</span>
<span className="text-zinc-400">-</span>
<span className="text-3xl font-bold text-emerald-400">
<span className="text-white/40">-</span>
<span className="text-2xl font-display text-accent">
{analysis.value.estimated_monthly_max}
</span>
</div>
<p className="text-xs text-zinc-500 mt-2">
Based on intent category, geo-targeting, and partner rates
</p>
</div>
{/* Monetization Potential */}
<div className="flex items-center justify-between p-3 rounded-lg bg-zinc-800/50">
<span className="text-sm text-zinc-400">Monetization Potential</span>
<span className={`font-medium ${
analysis.monetization_potential === 'high' ? 'text-emerald-400' :
<div className="flex items-center justify-between p-3 bg-white/[0.02] border border-white/[0.06]">
<span className="text-xs text-white/40">Monetization Potential</span>
<span className={clsx(
"font-medium text-sm",
analysis.monetization_potential === 'high' ? 'text-accent' :
analysis.monetization_potential === 'medium' ? 'text-amber-400' :
'text-zinc-400'
}`}>
'text-white/40'
)}>
{analysis.monetization_potential.toUpperCase()}
</span>
</div>
{error && (
<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/30 text-red-400 text-sm">
<div className="p-3 bg-red-400/10 border border-red-400/20 text-red-400 text-sm">
{error}
</div>
)}
@ -291,14 +240,14 @@ function ActivateModal({
<div className="flex gap-3">
<button
onClick={() => setStep('input')}
className="flex-1 py-3 px-4 bg-zinc-800 hover:bg-zinc-700 text-white font-medium rounded-lg transition-colors"
className="flex-1 py-3 px-4 bg-white/5 border border-white/10 text-white/60 hover:bg-white/10 hover:text-white transition-colors"
>
Back
</button>
<button
onClick={handleActivate}
disabled={loading}
className="flex-1 py-3 px-4 bg-emerald-600 hover:bg-emerald-500 disabled:bg-zinc-700 text-white font-medium rounded-lg transition-colors flex items-center justify-center gap-2"
className="flex-1 py-3 px-4 bg-accent text-black font-semibold hover:bg-white disabled:bg-white/10 disabled:text-white/40 transition-colors flex items-center justify-center gap-2"
>
{loading ? (
<>
@ -319,29 +268,27 @@ function ActivateModal({
{step === 'dns' && dnsInstructions && (
<div className="space-y-5">
<div className="text-center mb-4">
<div className="w-12 h-12 rounded-full bg-emerald-500/20 flex items-center justify-center mx-auto mb-3">
<CheckCircle2 className="w-6 h-6 text-emerald-400" />
<div className="w-12 h-12 bg-accent/10 flex items-center justify-center mx-auto mb-3">
<CheckCircle2 className="w-6 h-6 text-accent" />
</div>
<h3 className="text-lg font-semibold text-white">Domain Registered!</h3>
<p className="text-sm text-zinc-400 mt-1">Complete DNS setup to start earning</p>
<h3 className="font-medium text-white">Domain Registered!</h3>
<p className="text-xs text-white/40 mt-1">Complete DNS setup to start earning</p>
</div>
{/* Nameserver Option */}
<div className="p-4 rounded-xl bg-zinc-800/50 border border-zinc-700">
<h4 className="text-sm font-medium text-white mb-3">Option 1: Nameservers</h4>
<p className="text-xs text-zinc-500 mb-3">Point your domain to our nameservers:</p>
<div className="p-4 bg-white/[0.02] border border-white/[0.08]">
<h4 className="text-xs font-medium text-white mb-3">Option 1: Nameservers</h4>
<div className="space-y-2">
{dnsInstructions.nameservers.map((ns: string, i: number) => (
<div key={i} className="flex items-center justify-between p-2 rounded bg-zinc-900">
<code className="text-sm text-emerald-400">{ns}</code>
<div key={i} className="flex items-center justify-between p-2 bg-black/30">
<code className="text-sm text-accent font-mono">{ns}</code>
<button
onClick={() => copyToClipboard(ns, `ns-${i}`)}
className="p-1 hover:bg-zinc-700 rounded"
className="p-1 hover:bg-white/10"
>
{copied === `ns-${i}` ? (
<Check className="w-4 h-4 text-emerald-400" />
<Check className="w-4 h-4 text-accent" />
) : (
<Copy className="w-4 h-4 text-zinc-400" />
<Copy className="w-4 h-4 text-white/40" />
)}
</button>
</div>
@ -349,25 +296,23 @@ function ActivateModal({
</div>
</div>
{/* CNAME Option */}
<div className="p-4 rounded-xl bg-zinc-800/50 border border-zinc-700">
<h4 className="text-sm font-medium text-white mb-3">Option 2: CNAME Record</h4>
<p className="text-xs text-zinc-500 mb-3">Or add a CNAME record:</p>
<div className="flex items-center justify-between p-2 rounded bg-zinc-900">
<div>
<span className="text-xs text-zinc-500">Host: </span>
<code className="text-sm text-white">{dnsInstructions.cname_host}</code>
<span className="text-xs text-zinc-500 mx-2"></span>
<code className="text-sm text-emerald-400">{dnsInstructions.cname_target}</code>
<div className="p-4 bg-white/[0.02] border border-white/[0.08]">
<h4 className="text-xs font-medium text-white mb-3">Option 2: CNAME Record</h4>
<div className="flex items-center justify-between p-2 bg-black/30">
<div className="text-sm">
<span className="text-white/40">Host: </span>
<code className="text-white font-mono">{dnsInstructions.cname_host}</code>
<span className="text-white/40 mx-2"></span>
<code className="text-accent font-mono">{dnsInstructions.cname_target}</code>
</div>
<button
onClick={() => copyToClipboard(dnsInstructions.cname_target, 'cname')}
className="p-1 hover:bg-zinc-700 rounded"
className="p-1 hover:bg-white/10"
>
{copied === 'cname' ? (
<Check className="w-4 h-4 text-emerald-400" />
<Check className="w-4 h-4 text-accent" />
) : (
<Copy className="w-4 h-4 text-zinc-400" />
<Copy className="w-4 h-4 text-white/40" />
)}
</button>
</div>
@ -375,7 +320,7 @@ function ActivateModal({
<button
onClick={handleDone}
className="w-full py-3 px-4 bg-emerald-600 hover:bg-emerald-500 text-white font-medium rounded-lg transition-colors"
className="w-full py-3 px-4 bg-accent text-black font-semibold hover:bg-white transition-colors"
>
Done
</button>
@ -387,7 +332,10 @@ function ActivateModal({
)
}
// Main Yield Page
// ============================================================================
// MAIN PAGE
// ============================================================================
export default function YieldPage() {
const { subscription } = useStore()
const [loading, setLoading] = useState(true)
@ -416,162 +364,185 @@ export default function YieldPage() {
fetchDashboard()
}
if (loading) {
return (
<div className="flex items-center justify-center min-h-[60vh]">
<div className="flex flex-col items-center gap-4">
<div className="w-10 h-10 border-2 border-emerald-500 border-t-transparent rounded-full animate-spin" />
<p className="text-zinc-400">Loading yield dashboard...</p>
</div>
</div>
)
}
const stats = dashboard?.stats
return (
<div className="p-6 space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-white flex items-center gap-3">
<div className="p-2 rounded-lg bg-gradient-to-br from-emerald-500/20 to-purple-500/20">
<TrendingUp className="w-6 h-6 text-emerald-400" />
</div>
Yield
</h1>
<p className="text-zinc-400 mt-1">Turn parked domains into passive income</p>
<CommandCenterLayout minimal>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* HEADER */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<section className="pt-6 lg:pt-8 pb-6">
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-6">
<div className="space-y-3">
<div className="inline-flex items-center gap-2">
<TrendingUp className="w-4 h-4 text-accent" />
<span className="text-[10px] font-mono tracking-wide text-accent">Passive Income</span>
</div>
<div className="flex items-center gap-3">
<h1 className="font-display text-[2rem] lg:text-[2.5rem] leading-[1] tracking-[-0.02em]">
<span className="text-white">Yield</span>
</h1>
<p className="text-white/40 text-sm max-w-md">
Turn parked domains into passive income with intent-based monetization
</p>
</div>
<div className="flex items-center gap-4">
{stats && (
<div className="flex gap-6">
<div className="text-right">
<div className="text-xl font-display text-accent">{stats.currency} {stats.monthly_revenue.toLocaleString()}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">Monthly</div>
</div>
<div className="text-right">
<div className="text-xl font-display text-white">{stats.active_domains}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">Active</div>
</div>
<div className="text-right">
<div className="text-xl font-display text-white">{stats.monthly_clicks.toLocaleString()}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">Clicks</div>
</div>
</div>
)}
<div className="flex items-center gap-2">
<button
onClick={handleRefresh}
disabled={refreshing}
className="p-2 rounded-lg bg-zinc-800 hover:bg-zinc-700 text-zinc-400 hover:text-white transition-colors"
className="w-9 h-9 flex items-center justify-center border border-white/10 text-white/40 hover:text-white hover:bg-white/5 transition-colors"
>
<RefreshCw className={`w-5 h-5 ${refreshing ? 'animate-spin' : ''}`} />
<RefreshCw className={clsx("w-4 h-4", refreshing && "animate-spin")} />
</button>
<button
onClick={() => setShowActivateModal(true)}
className="flex items-center gap-2 px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white font-medium rounded-lg transition-colors"
className="flex items-center gap-2 px-4 py-2 bg-accent text-black text-sm font-semibold hover:bg-white transition-colors"
>
<Plus className="w-4 h-4" />
Add Domain
</button>
</div>
</div>
{/* Stats Grid */}
{stats && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<StatsCard
label="Monthly Revenue"
value={`${stats.currency} ${stats.monthly_revenue.toLocaleString()}`}
subValue={`Lifetime: ${stats.currency} ${stats.lifetime_revenue.toLocaleString()}`}
icon={DollarSign}
color="emerald"
/>
<StatsCard
label="Active Domains"
value={stats.active_domains}
subValue={`${stats.pending_domains} pending`}
icon={Zap}
color="blue"
/>
<StatsCard
label="Monthly Clicks"
value={stats.monthly_clicks.toLocaleString()}
subValue={`${stats.monthly_conversions} conversions`}
icon={MousePointer}
color="amber"
/>
<StatsCard
label="Pending Payout"
value={`${stats.currency} ${stats.pending_payout.toLocaleString()}`}
subValue={stats.next_payout_date ? `Next: ${new Date(stats.next_payout_date).toLocaleDateString()}` : undefined}
icon={Wallet}
color="purple"
/>
</div>
</section>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* STATS */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{stats && (
<section className="pb-6 border-b border-white/[0.08]">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div className="p-4 bg-accent/5 border border-accent/20">
<DollarSign className="w-5 h-5 text-accent mb-2" />
<div className="text-xl font-display text-white">{stats.currency} {stats.monthly_revenue.toLocaleString()}</div>
<div className="text-[10px] text-white/40 font-mono">Monthly Revenue</div>
<div className="text-[10px] text-white/30 mt-1">Lifetime: {stats.currency} {stats.lifetime_revenue.toLocaleString()}</div>
</div>
<div className="p-4 bg-white/[0.02] border border-white/[0.08]">
<Zap className="w-5 h-5 text-blue-400 mb-2" />
<div className="text-xl font-display text-white">{stats.active_domains}</div>
<div className="text-[10px] text-white/40 font-mono">Active Domains</div>
<div className="text-[10px] text-white/30 mt-1">{stats.pending_domains} pending</div>
</div>
<div className="p-4 bg-white/[0.02] border border-white/[0.08]">
<MousePointer className="w-5 h-5 text-amber-400 mb-2" />
<div className="text-xl font-display text-white">{stats.monthly_clicks.toLocaleString()}</div>
<div className="text-[10px] text-white/40 font-mono">Monthly Clicks</div>
<div className="text-[10px] text-white/30 mt-1">{stats.monthly_conversions} conversions</div>
</div>
<div className="p-4 bg-white/[0.02] border border-white/[0.08]">
<Wallet className="w-5 h-5 text-purple-400 mb-2" />
<div className="text-xl font-display text-white">{stats.currency} {stats.pending_payout.toLocaleString()}</div>
<div className="text-[10px] text-white/40 font-mono">Pending Payout</div>
{stats.next_payout_date && (
<div className="text-[10px] text-white/30 mt-1">Next: {new Date(stats.next_payout_date).toLocaleDateString()}</div>
)}
</div>
</div>
</section>
)}
{/* Domains Table */}
<div className="bg-zinc-900/50 border border-zinc-800 rounded-xl overflow-hidden">
<div className="p-4 border-b border-zinc-800 flex items-center justify-between">
<h2 className="text-lg font-semibold text-white">Your Yield Domains</h2>
<span className="text-sm text-zinc-400">{dashboard?.domains?.length || 0} domains</span>
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* DOMAINS TABLE */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
<section className="py-6">
{loading ? (
<div className="flex items-center justify-center py-20">
<Loader2 className="w-6 h-6 text-accent animate-spin" />
</div>
{dashboard?.domains?.length === 0 ? (
<div className="p-12 text-center">
<div className="w-16 h-16 rounded-full bg-zinc-800 flex items-center justify-center mx-auto mb-4">
<TrendingUp className="w-8 h-8 text-zinc-600" />
) : dashboard?.domains?.length === 0 ? (
<div className="text-center py-16">
<div className="w-14 h-14 mx-auto bg-white/[0.02] border border-white/10 flex items-center justify-center mb-4">
<TrendingUp className="w-6 h-6 text-white/20" />
</div>
<h3 className="text-lg font-medium text-white mb-2">No yield domains yet</h3>
<p className="text-zinc-400 mb-6 max-w-md mx-auto">
Activate your first domain to start generating passive income from visitor intent routing.
<p className="text-white/40 text-sm mb-2">No yield domains yet</p>
<p className="text-white/25 text-xs mb-6 max-w-sm mx-auto">
Activate your first domain to start generating passive income
</p>
<button
onClick={() => setShowActivateModal(true)}
className="inline-flex items-center gap-2 px-6 py-3 bg-emerald-600 hover:bg-emerald-500 text-white font-medium rounded-lg transition-colors"
className="inline-flex items-center gap-2 px-4 py-2 bg-accent text-black text-sm font-semibold hover:bg-white transition-colors"
>
<Plus className="w-4 h-4" />
Add Your First Domain
Add First Domain
</button>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full">
<table className="w-full min-w-[800px]">
<thead>
<tr className="text-left text-xs uppercase tracking-wider text-zinc-500 bg-zinc-800/50">
<th className="px-4 py-3">Domain</th>
<th className="px-4 py-3">Status</th>
<th className="px-4 py-3">Intent</th>
<th className="px-4 py-3">Clicks</th>
<th className="px-4 py-3">Conversions</th>
<th className="px-4 py-3 text-right">Revenue</th>
<th className="px-4 py-3"></th>
<tr className="text-xs text-white/40 border-b border-white/[0.06]">
<th className="text-left py-3 px-4 font-medium">Domain</th>
<th className="text-left py-3 px-4 font-medium">Status</th>
<th className="text-left py-3 px-4 font-medium">Intent</th>
<th className="text-right py-3 px-4 font-medium">Clicks</th>
<th className="text-right py-3 px-4 font-medium">Conversions</th>
<th className="text-right py-3 px-4 font-medium">Revenue</th>
<th className="text-right py-3 px-4 font-medium"></th>
</tr>
</thead>
<tbody className="divide-y divide-zinc-800">
<tbody>
{dashboard?.domains?.map((domain: YieldDomain) => (
<tr key={domain.id} className="hover:bg-zinc-800/30 transition-colors">
<td className="px-4 py-4">
<tr key={domain.id} className="group border-b border-white/[0.04] hover:bg-white/[0.02]">
<td className="py-3 px-4">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-zinc-800 flex items-center justify-center text-emerald-400 text-xs font-bold">
<div className="w-8 h-8 bg-white/5 border border-white/10 flex items-center justify-center text-accent text-xs font-bold">
{domain.domain.charAt(0).toUpperCase()}
</div>
<span className="font-medium text-white">{domain.domain}</span>
</div>
</td>
<td className="px-4 py-4">
<td className="py-3 px-4">
<StatusBadge status={domain.status} />
</td>
<td className="px-4 py-4">
<span className="text-sm text-zinc-300 capitalize">
<td className="py-3 px-4">
<span className="text-sm text-white/60 capitalize">
{domain.detected_intent?.replace('_', ' ') || '-'}
</span>
{domain.intent_confidence > 0 && (
<span className="text-xs text-zinc-500 ml-2">
<span className="text-[10px] text-white/30 ml-2 font-mono">
({Math.round(domain.intent_confidence * 100)}%)
</span>
)}
</td>
<td className="px-4 py-4 text-zinc-300">
<td className="py-3 px-4 text-right text-white/60 font-mono">
{domain.total_clicks.toLocaleString()}
</td>
<td className="px-4 py-4 text-zinc-300">
<td className="py-3 px-4 text-right text-white/60 font-mono">
{domain.total_conversions.toLocaleString()}
</td>
<td className="px-4 py-4 text-right">
<span className="font-medium text-emerald-400">
<td className="py-3 px-4 text-right">
<span className="font-medium text-accent font-mono">
{domain.currency} {domain.total_revenue.toLocaleString()}
</span>
</td>
<td className="px-4 py-4 text-right">
<button className="p-1.5 hover:bg-zinc-700 rounded-lg transition-colors">
<ChevronRight className="w-4 h-4 text-zinc-400" />
<td className="py-3 px-4 text-right">
<button className="p-1.5 hover:bg-white/10 transition-colors opacity-0 group-hover:opacity-100">
<ChevronRight className="w-4 h-4 text-white/40" />
</button>
</td>
</tr>
@ -580,48 +551,49 @@ export default function YieldPage() {
</table>
</div>
)}
</div>
</section>
{/* Recent Transactions */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{/* RECENT TRANSACTIONS */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{dashboard?.recent_transactions?.length > 0 && (
<div className="bg-zinc-900/50 border border-zinc-800 rounded-xl overflow-hidden">
<div className="p-4 border-b border-zinc-800">
<h2 className="text-lg font-semibold text-white">Recent Activity</h2>
</div>
<div className="divide-y divide-zinc-800">
<section className="py-6 border-t border-white/[0.06]">
<h2 className="text-xs font-mono text-white/40 tracking-wide mb-4">Recent Activity</h2>
<div className="space-y-px">
{dashboard.recent_transactions.slice(0, 5).map((tx: YieldTransaction) => (
<div key={tx.id} className="p-4 flex items-center justify-between hover:bg-zinc-800/30 transition-colors">
<div key={tx.id} className="flex items-center justify-between p-4 bg-white/[0.01] border border-white/[0.04] hover:bg-white/[0.02] transition-colors">
<div className="flex items-center gap-3">
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${
tx.event_type === 'sale' ? 'bg-emerald-500/20' :
tx.event_type === 'lead' ? 'bg-blue-500/20' :
'bg-zinc-700'
}`}>
<div className={clsx(
"w-8 h-8 flex items-center justify-center",
tx.event_type === 'sale' ? 'bg-accent/10' :
tx.event_type === 'lead' ? 'bg-blue-400/10' :
'bg-white/5'
)}>
{tx.event_type === 'sale' ? (
<DollarSign className="w-4 h-4 text-emerald-400" />
<DollarSign className="w-4 h-4 text-accent" />
) : tx.event_type === 'lead' ? (
<Target className="w-4 h-4 text-blue-400" />
) : (
<MousePointer className="w-4 h-4 text-zinc-400" />
<MousePointer className="w-4 h-4 text-white/40" />
)}
</div>
<div>
<p className="text-sm font-medium text-white capitalize">{tx.event_type}</p>
<p className="text-xs text-zinc-500">{tx.partner_slug}</p>
<p className="text-[10px] text-white/30 font-mono">{tx.partner_slug}</p>
</div>
</div>
<div className="text-right">
<p className="text-sm font-medium text-emerald-400">
<p className="text-sm font-medium text-accent font-mono">
+{tx.currency} {tx.net_amount.toFixed(2)}
</p>
<p className="text-xs text-zinc-500">
<p className="text-[10px] text-white/30 font-mono">
{new Date(tx.created_at).toLocaleDateString()}
</p>
</div>
</div>
))}
</div>
</div>
</section>
)}
{/* Activate Modal */}
@ -630,7 +602,6 @@ export default function YieldPage() {
onClose={() => setShowActivateModal(false)}
onSuccess={fetchDashboard}
/>
</div>
</CommandCenterLayout>
)
}