fix: Yield slider fixed, pricing features aligned with docs
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
This commit is contained in:
@ -22,10 +22,11 @@ const tiers = [
|
||||
{ text: 'Market Overview', highlight: false, available: true },
|
||||
{ text: 'Basic Search', highlight: false, available: true },
|
||||
{ text: '5 Watchlist Domains', highlight: false, available: true },
|
||||
{ text: 'Market Feed', highlight: false, available: true, sublabel: '🌪️ Raw' },
|
||||
{ text: 'Alert Speed', highlight: false, available: true, sublabel: '🐢 Daily' },
|
||||
{ text: 'Market Feed', highlight: false, available: true, sublabel: 'Raw' },
|
||||
{ text: 'Alert Speed', highlight: false, available: true, sublabel: 'Daily' },
|
||||
{ text: 'Pounce Score', highlight: false, available: false },
|
||||
{ text: '2 Sniper Alerts', highlight: false, available: true },
|
||||
{ text: 'Marketplace', highlight: false, available: true, sublabel: 'Buy Only' },
|
||||
],
|
||||
cta: 'Start Free',
|
||||
highlighted: false,
|
||||
@ -41,13 +42,13 @@ const tiers = [
|
||||
description: 'The smart investor\'s choice.',
|
||||
features: [
|
||||
{ text: '50 Watchlist Domains', highlight: true, available: true },
|
||||
{ text: 'Market Feed', highlight: true, available: true, sublabel: '✨ Curated' },
|
||||
{ text: 'Alert Speed', highlight: true, available: true, sublabel: '🐇 Hourly' },
|
||||
{ text: 'Renewal Price Intel', highlight: true, available: true },
|
||||
{ text: 'Market Feed', highlight: true, available: true, sublabel: 'Curated' },
|
||||
{ text: 'Alert Speed', highlight: true, available: true, sublabel: 'Hourly' },
|
||||
{ text: 'TLD Intel', highlight: true, available: true, sublabel: 'Renewal Prices' },
|
||||
{ text: 'Pounce Score', highlight: true, available: true },
|
||||
{ text: '5 Listings', highlight: true, available: true, sublabel: '0% Fee' },
|
||||
{ text: '10 Sniper Alerts', highlight: true, available: true },
|
||||
{ text: 'Portfolio (25 domains)', highlight: true, available: true },
|
||||
{ text: 'Portfolio', highlight: true, available: true, sublabel: '25 Domains' },
|
||||
],
|
||||
cta: 'Upgrade to Trader',
|
||||
highlighted: true,
|
||||
@ -63,13 +64,13 @@ const tiers = [
|
||||
description: 'For serious domain investors.',
|
||||
features: [
|
||||
{ text: '500 Watchlist Domains', highlight: true, available: true },
|
||||
{ text: 'Market Feed', highlight: true, available: true, sublabel: '⚡ Priority' },
|
||||
{ text: 'Alert Speed', highlight: true, available: true, sublabel: '⚡ 10 min' },
|
||||
{ text: 'Unlimited Portfolio', highlight: true, available: true },
|
||||
{ text: 'Market Feed', highlight: true, available: true, sublabel: 'Priority' },
|
||||
{ text: 'Alert Speed', highlight: true, available: true, sublabel: 'Real-Time' },
|
||||
{ text: 'TLD Intel', highlight: true, available: true, sublabel: 'Full History' },
|
||||
{ text: 'Score + SEO Data', highlight: true, available: true },
|
||||
{ text: '50 Listings', highlight: true, available: true, sublabel: 'Featured' },
|
||||
{ text: '50 Sniper Alerts', highlight: true, available: true },
|
||||
{ text: 'Full Price History', highlight: true, available: true },
|
||||
{ text: 'Unlimited Portfolio', highlight: true, available: true },
|
||||
],
|
||||
cta: 'Go Tycoon',
|
||||
highlighted: false,
|
||||
@ -79,10 +80,10 @@ const tiers = [
|
||||
]
|
||||
|
||||
const comparisonFeatures = [
|
||||
{ name: 'Market Feed', scout: 'Raw', trader: 'Curated', tycoon: 'Priority' },
|
||||
{ name: 'Alert Speed', scout: 'Daily', trader: 'Hourly', tycoon: '10 min' },
|
||||
{ name: 'Market Feed', scout: 'Raw (Unfiltered)', trader: 'Curated (Spam-Free)', tycoon: 'Curated + Priority' },
|
||||
{ name: 'Alert Speed', scout: 'Daily', trader: 'Hourly', tycoon: 'Real-Time (10 min)' },
|
||||
{ name: 'Watchlist', scout: '5 Domains', trader: '50 Domains', tycoon: '500 Domains' },
|
||||
{ name: 'Listings', scout: '—', trader: '5 (0% Fee)', tycoon: '50 Featured' },
|
||||
{ name: 'Marketplace', scout: 'Buy Only', trader: 'Sell (0% Fee)', tycoon: 'Sell + Featured' },
|
||||
{ name: 'TLD Intel', scout: 'Public Trends', trader: 'Renewal Prices', tycoon: 'Full History' },
|
||||
{ name: 'Valuation', scout: 'Locked', trader: 'Pounce Score', tycoon: 'Score + SEO' },
|
||||
{ name: 'Sniper Alerts', scout: '2', trader: '10', tycoon: '50' },
|
||||
|
||||
@ -298,15 +298,8 @@ export default function PortfolioPage() {
|
||||
|
||||
{/* ADD DOMAIN + FILTERS */}
|
||||
<section className="px-4 lg:px-10 py-4 border-b border-white/[0.08]">
|
||||
<div className="flex items-center justify-between gap-4 mb-4">
|
||||
<button
|
||||
onClick={() => setShowAddModal(true)}
|
||||
className="flex items-center gap-2 px-4 py-2.5 bg-accent text-black text-xs font-bold uppercase tracking-wider hover:bg-white transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" />Add Domain
|
||||
</button>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
{/* Filters - LEFT */}
|
||||
<div className="flex items-center gap-1">
|
||||
{[
|
||||
{ value: 'all', label: 'All', count: stats.total },
|
||||
@ -327,6 +320,14 @@ export default function PortfolioPage() {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Add Domain Button - RIGHT */}
|
||||
<button
|
||||
onClick={() => setShowAddModal(true)}
|
||||
className="flex items-center gap-2 px-4 py-2.5 bg-accent text-black text-xs font-bold uppercase tracking-wider hover:bg-white transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" />Add Domain
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -351,7 +352,7 @@ export default function PortfolioPage() {
|
||||
) : (
|
||||
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
|
||||
{/* Desktop Table Header */}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_100px_100px_100px_80px_100px] gap-4 px-3 py-2 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08]">
|
||||
<div className="hidden lg:grid grid-cols-[1fr_90px_90px_80px_70px_160px] gap-4 px-4 py-2.5 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08]">
|
||||
<button onClick={() => handleSort('domain')} className="flex items-center gap-1 hover:text-white/60 text-left">
|
||||
Domain
|
||||
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||
@ -366,7 +367,7 @@ export default function PortfolioPage() {
|
||||
{sortField === 'roi' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||
</button>
|
||||
<button onClick={() => handleSort('renewal')} className="flex items-center gap-1 justify-center hover:text-white/60">
|
||||
Renewal
|
||||
Expires
|
||||
{sortField === 'renewal' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
||||
</button>
|
||||
<div className="text-right">Actions</div>
|
||||
@ -442,44 +443,47 @@ export default function PortfolioPage() {
|
||||
</div>
|
||||
|
||||
{/* Desktop Row */}
|
||||
<div className="hidden lg:grid grid-cols-[1fr_100px_100px_100px_80px_100px] gap-4 items-center p-3">
|
||||
<div className="hidden lg:grid grid-cols-[1fr_90px_90px_80px_70px_160px] gap-4 items-center px-4 py-3 hover:bg-white/[0.02] transition-colors">
|
||||
{/* Domain Info */}
|
||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||
<div className={clsx(
|
||||
"w-8 h-8 flex items-center justify-center border shrink-0",
|
||||
"w-9 h-9 flex items-center justify-center border shrink-0",
|
||||
domain.is_sold ? "bg-white/[0.02] border-white/[0.06]" :
|
||||
domain.is_dns_verified ? "bg-accent/10 border-accent/20" : "bg-blue-400/10 border-blue-400/20"
|
||||
)}>
|
||||
{domain.is_sold ? <CheckCircle className="w-4 h-4 text-white/30" /> :
|
||||
domain.is_dns_verified ? <ShieldCheck className="w-4 h-4 text-accent" /> : <ShieldAlert className="w-4 h-4 text-blue-400" />}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors">{domain.domain}</div>
|
||||
<div className="flex items-center gap-2 text-[10px] font-mono text-white/30">
|
||||
<span>{domain.registrar || 'Unknown'}</span>
|
||||
{domain.is_sold && <span className="px-1 py-0.5 bg-white/5 text-white/40">SOLD</span>}
|
||||
{!domain.is_sold && domain.is_dns_verified && <span className="px-1 py-0.5 bg-accent/10 text-accent">VERIFIED</span>}
|
||||
{!domain.is_sold && !domain.is_dns_verified && <span className="px-1 py-0.5 bg-blue-400/10 text-blue-400">UNVERIFIED</span>}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-bold text-white font-mono truncate">{domain.domain}</span>
|
||||
<a href={`https://${domain.domain}`} target="_blank" rel="noopener noreferrer" className="opacity-0 group-hover:opacity-40 hover:!opacity-100 transition-opacity">
|
||||
<ExternalLink className="w-3 h-3 text-white" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 mt-0.5">
|
||||
<span className="text-[10px] font-mono text-white/30">{domain.registrar || '—'}</span>
|
||||
{domain.is_sold && <span className="text-[9px] font-mono px-1.5 py-0.5 bg-white/5 text-white/40 uppercase">Sold</span>}
|
||||
{!domain.is_sold && domain.is_dns_verified && <span className="text-[9px] font-mono px-1.5 py-0.5 bg-accent/10 text-accent uppercase">Verified</span>}
|
||||
{!domain.is_sold && !domain.is_dns_verified && <span className="text-[9px] font-mono px-1.5 py-0.5 bg-blue-400/10 text-blue-400 uppercase">Unverified</span>}
|
||||
</div>
|
||||
</div>
|
||||
<a href={`https://${domain.domain}`} target="_blank" className="opacity-0 group-hover:opacity-50 hover:!opacity-100 transition-opacity ml-2">
|
||||
<ExternalLink className="w-3.5 h-3.5 text-white/40" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Purchase */}
|
||||
<div className="text-right text-xs font-mono text-white/50">
|
||||
{/* Purchase Price */}
|
||||
<div className="text-right text-xs font-mono text-white/40 tabular-nums">
|
||||
{formatCurrency(domain.purchase_price)}
|
||||
</div>
|
||||
|
||||
{/* Value */}
|
||||
<div className="text-right text-sm font-bold font-mono text-accent">
|
||||
{/* Estimated Value */}
|
||||
<div className="text-right text-sm font-bold font-mono text-accent tabular-nums">
|
||||
{formatCurrency(domain.estimated_value)}
|
||||
</div>
|
||||
|
||||
{/* ROI */}
|
||||
<div className="text-center">
|
||||
{/* ROI Badge */}
|
||||
<div className="flex justify-center">
|
||||
<span className={clsx(
|
||||
"text-xs font-mono font-bold px-2 py-0.5 flex items-center justify-center gap-1",
|
||||
"inline-flex items-center gap-0.5 text-[11px] font-mono font-bold px-1.5 py-0.5 tabular-nums",
|
||||
roiPositive ? "text-accent bg-accent/10" : "text-rose-400 bg-rose-400/10"
|
||||
)}>
|
||||
{roiPositive ? <TrendingUp className="w-3 h-3" /> : <TrendingDown className="w-3 h-3" />}
|
||||
@ -487,26 +491,26 @@ export default function PortfolioPage() {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Renewal */}
|
||||
<div className="text-center text-xs font-mono">
|
||||
{/* Renewal/Expiry */}
|
||||
<div className="text-center text-xs font-mono tabular-nums">
|
||||
{domain.is_sold ? (
|
||||
<span className="text-white/30">—</span>
|
||||
<span className="text-white/20">—</span>
|
||||
) : isRenewingSoon ? (
|
||||
<span className="text-orange-400 font-bold">{daysUntilRenewal}d</span>
|
||||
) : (
|
||||
<span className="text-white/50">{formatDate(domain.renewal_date)}</span>
|
||||
<span className="text-white/40">{formatDate(domain.renewal_date)}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-1 justify-end opacity-50 group-hover:opacity-100 transition-opacity">
|
||||
{/* Verification Status & Actions */}
|
||||
{/* Actions - Better organized */}
|
||||
<div className="flex items-center gap-1.5 justify-end">
|
||||
{/* Primary Action - Verify or Sell */}
|
||||
{!domain.is_sold && (
|
||||
domain.is_dns_verified ? (
|
||||
canListForSale && (
|
||||
<Link
|
||||
href={`/terminal/listing?domain=${encodeURIComponent(domain.domain)}`}
|
||||
className="h-7 px-2 flex items-center gap-1 text-amber-400 text-[9px] font-bold uppercase border border-amber-400/20 bg-amber-400/10 hover:bg-amber-400/20 transition-all"
|
||||
className="h-7 px-2.5 flex items-center gap-1.5 text-amber-400 text-[10px] font-bold uppercase tracking-wide border border-amber-400/30 bg-amber-400/10 hover:bg-amber-400/20 transition-all"
|
||||
>
|
||||
<Tag className="w-3 h-3" />Sell
|
||||
</Link>
|
||||
@ -514,32 +518,39 @@ export default function PortfolioPage() {
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setVerifyingDomain(domain)}
|
||||
className="h-7 px-2 flex items-center gap-1 text-blue-400 text-[9px] font-bold uppercase border border-blue-400/20 bg-blue-400/10 hover:bg-blue-400/20 transition-all"
|
||||
className="h-7 px-2.5 flex items-center gap-1.5 text-blue-400 text-[10px] font-bold uppercase tracking-wide border border-blue-400/30 bg-blue-400/10 hover:bg-blue-400/20 transition-all"
|
||||
>
|
||||
<ShieldAlert className="w-3 h-3" />Verify
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
<button
|
||||
onClick={() => setSelectedDomain(domain)}
|
||||
className="w-7 h-7 flex items-center justify-center text-white/20 hover:text-white border border-white/10 hover:bg-white/5 transition-all"
|
||||
>
|
||||
<Edit3 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleRefreshValue(domain.id)}
|
||||
disabled={refreshingId === domain.id}
|
||||
className="w-7 h-7 flex items-center justify-center text-white/20 hover:text-white border border-white/10 hover:bg-white/5 transition-all"
|
||||
>
|
||||
<RefreshCw className={clsx("w-3.5 h-3.5", refreshingId === domain.id && "animate-spin")} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(domain.id, domain.domain)}
|
||||
disabled={deletingId === domain.id}
|
||||
className="w-7 h-7 flex items-center justify-center text-white/20 hover:text-rose-400 border border-white/10 hover:border-rose-400/20 hover:bg-rose-500/10 transition-all"
|
||||
>
|
||||
{deletingId === domain.id ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Trash2 className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
|
||||
{/* Secondary Actions - Icon Buttons */}
|
||||
<div className="flex items-center gap-0.5 ml-1">
|
||||
<button
|
||||
onClick={() => setSelectedDomain(domain)}
|
||||
title="Edit Details"
|
||||
className="w-7 h-7 flex items-center justify-center text-white/30 hover:text-white border border-transparent hover:border-white/10 hover:bg-white/5 transition-all rounded-sm"
|
||||
>
|
||||
<Edit3 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleRefreshValue(domain.id)}
|
||||
disabled={refreshingId === domain.id}
|
||||
title="Refresh Valuation"
|
||||
className="w-7 h-7 flex items-center justify-center text-white/30 hover:text-accent border border-transparent hover:border-accent/20 hover:bg-accent/5 transition-all rounded-sm disabled:opacity-30"
|
||||
>
|
||||
<RefreshCw className={clsx("w-3.5 h-3.5", refreshingId === domain.id && "animate-spin text-accent")} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(domain.id, domain.domain)}
|
||||
disabled={deletingId === domain.id}
|
||||
title="Delete Domain"
|
||||
className="w-7 h-7 flex items-center justify-center text-white/30 hover:text-rose-400 border border-transparent hover:border-rose-400/20 hover:bg-rose-500/10 transition-all rounded-sm disabled:opacity-30"
|
||||
>
|
||||
{deletingId === domain.id ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Trash2 className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -78,7 +78,7 @@ function YieldSimulator() {
|
||||
<span className="text-white/60">Monthly Traffic</span>
|
||||
<span className="text-white">{traffic.toLocaleString()} Visits</span>
|
||||
</div>
|
||||
<div className="relative h-4 flex items-center">
|
||||
<div className="relative h-6 flex items-center">
|
||||
<input
|
||||
type="range"
|
||||
min="100"
|
||||
@ -86,17 +86,22 @@ function YieldSimulator() {
|
||||
step="100"
|
||||
value={traffic}
|
||||
onChange={(e) => setTraffic(parseInt(e.target.value))}
|
||||
className="absolute w-full h-1 bg-white/10 rounded-full appearance-none cursor-pointer z-20
|
||||
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3
|
||||
[&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:rounded-full
|
||||
[&::-webkit-slider-thumb]:shadow-[0_0_10px_rgba(255,255,255,0.8)]"
|
||||
/>
|
||||
<div className="absolute top-1/2 left-0 right-0 h-px bg-white/10 z-10" />
|
||||
<div
|
||||
className="absolute top-1/2 left-0 h-px bg-white/50 z-10"
|
||||
style={{ width: `${((traffic - 100) / (10000 - 100)) * 100}%` }}
|
||||
className="w-full h-2 bg-white/10 appearance-none cursor-pointer
|
||||
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4
|
||||
[&::-webkit-slider-thumb]:bg-accent [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-white
|
||||
[&::-webkit-slider-thumb]:cursor-pointer
|
||||
[&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4
|
||||
[&::-moz-range-thumb]:bg-accent [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-white
|
||||
[&::-moz-range-thumb]:cursor-pointer [&::-moz-range-thumb]:rounded-none"
|
||||
style={{
|
||||
background: `linear-gradient(to right, rgb(16 185 129) 0%, rgb(16 185 129) ${((traffic - 100) / (5000 - 100)) * 100}%, rgba(255,255,255,0.1) ${((traffic - 100) / (5000 - 100)) * 100}%, rgba(255,255,255,0.1) 100%)`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between text-[10px] font-mono text-white/30 mt-1">
|
||||
<span>100</span>
|
||||
<span>5,000</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Input 2: Vertical Selector (Grid) */}
|
||||
|
||||
Reference in New Issue
Block a user