feat: Display tier limits with infinity symbol on all pages
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

- Listing: 1/10/∞ limits, fixed table column alignment
- Watchlist: 5/50/∞ limits with display
- Portfolio: 5/50/∞ limits with display in header
- Sniper: Already had limits (2/10/50)
- Yield: Tycoon-only, no limit needed

Uses formatLimit() helper to show ∞ for Infinity values
This commit is contained in:
2025-12-18 14:31:55 +01:00
parent 8d91caefae
commit 42e09b46ab
3 changed files with 25 additions and 12 deletions

View File

@ -75,11 +75,12 @@ export default function MyListingsPage() {
const [menuOpen, setMenuOpen] = useState(false) const [menuOpen, setMenuOpen] = useState(false)
const tier = subscription?.tier || 'scout' const tier = subscription?.tier || 'scout'
const listingLimits: Record<string, number> = { scout: 1, trader: 10, tycoon: 999999 } const listingLimits: Record<string, number> = { scout: 1, trader: 10, tycoon: Infinity }
const maxListings = listingLimits[tier] || 1 const maxListings = listingLimits[tier] || 1
const canAddMore = listings.length < maxListings const canAddMore = listings.length < maxListings
const isTycoon = tier === 'tycoon' const isTycoon = tier === 'tycoon'
const isUnlimited = tier === 'tycoon' const isUnlimited = tier === 'tycoon'
const formatLimit = (limit: number) => limit === Infinity ? '∞' : String(limit)
const activeListings = listings.filter(l => l.status === 'active').length const activeListings = listings.filter(l => l.status === 'active').length
const draftListings = listings.filter(l => l.status === 'draft').length const draftListings = listings.filter(l => l.status === 'draft').length
@ -164,7 +165,7 @@ export default function MyListingsPage() {
<div className="w-1.5 h-1.5 bg-accent animate-pulse" /> <div className="w-1.5 h-1.5 bg-accent animate-pulse" />
<span className="text-[10px] font-mono tracking-[0.2em] text-accent uppercase">For Sale</span> <span className="text-[10px] font-mono tracking-[0.2em] text-accent uppercase">For Sale</span>
</div> </div>
<span className="text-[10px] font-mono text-white/40">{listings.length}{isUnlimited ? '' : `/${maxListings}`}</span> <span className="text-[10px] font-mono text-white/40">{listings.length}/{formatLimit(maxListings)}</span>
</div> </div>
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-2">
<div className="bg-accent/[0.05] border border-accent/20 p-2"> <div className="bg-accent/[0.05] border border-accent/20 p-2">
@ -197,7 +198,7 @@ export default function MyListingsPage() {
</div> </div>
<h1 className="font-display text-[2.5rem] leading-[1] tracking-[-0.02em]"> <h1 className="font-display text-[2.5rem] leading-[1] tracking-[-0.02em]">
<span className="text-white">For Sale</span> <span className="text-white">For Sale</span>
<span className="text-white/30 ml-3 font-mono text-[2rem]">{listings.length}/{maxListings}</span> <span className="text-white/30 ml-3 font-mono text-[2rem]">{listings.length}/{formatLimit(maxListings)}</span>
</h1> </h1>
<p className="text-sm text-white/40 font-mono mt-2 max-w-lg"> <p className="text-sm text-white/40 font-mono mt-2 max-w-lg">
List your domains for sale. 0% commission, verified ownership, direct buyer contact. List your domains for sale. 0% commission, verified ownership, direct buyer contact.
@ -260,13 +261,13 @@ export default function MyListingsPage() {
) : ( ) : (
<div className="border border-white/[0.08] bg-[#020202] overflow-hidden"> <div className="border border-white/[0.08] bg-[#020202] overflow-hidden">
{/* Header */} {/* Header */}
<div className="hidden lg:grid grid-cols-[1fr_100px_90px_60px_60px_auto] gap-4 px-5 py-3 text-[10px] font-mono text-white/40 uppercase tracking-[0.12em] border-b border-white/[0.08] bg-white/[0.02]"> <div className="hidden lg:grid grid-cols-[1fr_100px_90px_70px_70px_140px] gap-4 px-5 py-3 text-[10px] font-mono text-white/40 uppercase tracking-[0.12em] border-b border-white/[0.08] bg-white/[0.02]">
<div>Domain</div> <div>Domain</div>
<div className="text-right">Price</div> <div className="text-right">Price</div>
<div className="text-center">Status</div> <div className="text-center">Status</div>
<div className="text-right">Views</div> <div className="text-center">Views</div>
<div className="text-right">Leads</div> <div className="text-center">Leads</div>
<div className="text-right pr-1">Actions</div> <div className="text-right">Actions</div>
</div> </div>
{/* Table Body */} {/* Table Body */}
@ -462,7 +463,7 @@ function ListingRow({
</div> </div>
{/* Desktop */} {/* Desktop */}
<div className="hidden lg:grid grid-cols-[1fr_100px_90px_60px_60px_auto] gap-4 items-center px-5 py-3 group-hover:bg-white/[0.02]"> <div className="hidden lg:grid grid-cols-[1fr_100px_90px_70px_70px_140px] gap-4 items-center px-5 py-3 group-hover:bg-white/[0.02]">
<div className="flex items-center gap-3 min-w-0"> <div className="flex items-center gap-3 min-w-0">
<span className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors">{listing.domain}</span> <span className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors">{listing.domain}</span>
<div className="flex items-center gap-2 text-[9px] font-mono text-white/20 uppercase tracking-wider opacity-0 group-hover:opacity-100 transition-opacity"> <div className="flex items-center gap-2 text-[9px] font-mono text-white/20 uppercase tracking-wider opacity-0 group-hover:opacity-100 transition-opacity">
@ -478,8 +479,8 @@ function ListingRow({
"bg-white/5 text-white/40 border-white/10" "bg-white/5 text-white/40 border-white/10"
)}>{listing.status}</span> )}>{listing.status}</span>
</div> </div>
<div className="text-right text-sm font-mono text-white/50">{listing.view_count}</div> <div className="text-center text-sm font-mono text-white/50">{listing.view_count}</div>
<div className="text-right text-sm font-mono text-white/50">{listing.inquiry_count}</div> <div className="text-center text-sm font-mono text-white/50">{listing.inquiry_count}</div>
<div className="flex items-center justify-end gap-1 opacity-40 group-hover:opacity-100 transition-all"> <div className="flex items-center justify-end gap-1 opacity-40 group-hover:opacity-100 transition-all">
{isDraft && needsVerification && ( {isDraft && needsVerification && (
<button onClick={onVerify} className="h-8 px-3 bg-amber-400/10 border border-amber-400/20 text-amber-400 text-[9px] font-mono uppercase hover:bg-amber-400/20 transition-colors"> <button onClick={onVerify} className="h-8 px-3 bg-amber-400/10 border border-amber-400/20 text-amber-400 text-[9px] font-mono uppercase hover:bg-amber-400/20 transition-colors">

View File

@ -697,6 +697,11 @@ export default function PortfolioPage() {
const tierName = subscription?.tier_name || tier const tierName = subscription?.tier_name || tier
const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap
// Tier limits
const portfolioLimits: Record<string, number> = { scout: 5, trader: 50, tycoon: Infinity }
const maxPortfolio = portfolioLimits[tier] || 5
const formatLimit = (limit: number) => limit === Infinity ? '' : String(limit)
useEffect(() => { checkAuth() }, [checkAuth]) useEffect(() => { checkAuth() }, [checkAuth])
// Load all data // Load all data
@ -1127,6 +1132,7 @@ export default function PortfolioPage() {
</div> </div>
<h1 className="font-display text-[2.5rem] leading-[1] tracking-[-0.02em] text-white"> <h1 className="font-display text-[2.5rem] leading-[1] tracking-[-0.02em] text-white">
My Portfolio My Portfolio
<span className="text-white/30 ml-3 font-mono text-[2rem]">{stats.total}/{formatLimit(maxPortfolio)}</span>
</h1> </h1>
<p className="text-sm text-white/40 font-mono max-w-lg mt-2"> <p className="text-sm text-white/40 font-mono max-w-lg mt-2">
Track your domain investments. Add purchase details, monitor values, verify ownership, and list for sale. Track your domain investments. Add purchase details, monitor values, verify ownership, and list for sale.

View File

@ -174,6 +174,12 @@ export default function WatchlistPage() {
}, [checkAuth]) }, [checkAuth])
// Stats // Stats
// Tier limits
const tier = subscription?.tier || 'scout'
const watchlistLimits: Record<string, number> = { scout: 5, trader: 50, tycoon: Infinity }
const maxWatchlist = watchlistLimits[tier] || 5
const formatLimit = (limit: number) => limit === Infinity ? '' : String(limit)
const stats = useMemo(() => { const stats = useMemo(() => {
const available = domains?.filter(d => d.is_available) || [] const available = domains?.filter(d => d.is_available) || []
const expiringSoon = domains?.filter(d => { const expiringSoon = domains?.filter(d => {
@ -452,7 +458,7 @@ export default function WatchlistPage() {
{/* Stats Grid */} {/* Stats Grid */}
<div className="grid grid-cols-3 gap-2"> <div className="grid grid-cols-3 gap-2">
<div className="bg-white/[0.02] border border-white/[0.08] p-2 text-center"> <div className="bg-white/[0.02] border border-white/[0.08] p-2 text-center">
<div className="text-lg font-bold text-white tabular-nums">{stats.total}</div> <div className="text-lg font-bold text-white tabular-nums">{stats.total}<span className="text-white/30">/{formatLimit(maxWatchlist)}</span></div>
<div className="text-[8px] font-mono text-white/30 uppercase">Tracked</div> <div className="text-[8px] font-mono text-white/30 uppercase">Tracked</div>
</div> </div>
<div className="bg-accent/[0.05] border border-accent/20 p-2 text-center"> <div className="bg-accent/[0.05] border border-accent/20 p-2 text-center">
@ -488,7 +494,7 @@ export default function WatchlistPage() {
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
<div className="text-right"> <div className="text-right">
<div className="text-2xl font-bold text-white font-mono">{stats.total}</div> <div className="text-2xl font-bold text-white font-mono">{stats.total}<span className="text-white/30">/{formatLimit(maxWatchlist)}</span></div>
<div className="text-[9px] font-mono text-white/30 uppercase tracking-wider">Tracked</div> <div className="text-[9px] font-mono text-white/30 uppercase tracking-wider">Tracked</div>
</div> </div>
<div className="text-right"> <div className="text-right">