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

This commit is contained in:
2025-12-13 18:16:17 +01:00
parent e3250baaf7
commit ddeb25446e
3 changed files with 99 additions and 82 deletions

View File

@ -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' },

View File

@ -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>
</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" />
<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>
</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,35 +518,42 @@ 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>
)
)}
{/* Secondary Actions - Icon Buttons */}
<div className="flex items-center gap-0.5 ml-1">
<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"
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}
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"
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")} />
<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}
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"
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>
)
})}
</div>

View File

@ -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) */}