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 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 canAddMore = listings.length < maxListings
const isTycoon = tier === 'tycoon'
const isUnlimited = tier === 'tycoon'
const formatLimit = (limit: number) => limit === Infinity ? '∞' : String(limit)
const activeListings = listings.filter(l => l.status === 'active').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" />
<span className="text-[10px] font-mono tracking-[0.2em] text-accent uppercase">For Sale</span>
</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 className="grid grid-cols-4 gap-2">
<div className="bg-accent/[0.05] border border-accent/20 p-2">
@ -197,7 +198,7 @@ export default function MyListingsPage() {
</div>
<h1 className="font-display text-[2.5rem] leading-[1] tracking-[-0.02em]">
<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>
<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.
@ -260,13 +261,13 @@ export default function MyListingsPage() {
) : (
<div className="border border-white/[0.08] bg-[#020202] overflow-hidden">
{/* 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 className="text-right">Price</div>
<div className="text-center">Status</div>
<div className="text-right">Views</div>
<div className="text-right">Leads</div>
<div className="text-right pr-1">Actions</div>
<div className="text-center">Views</div>
<div className="text-center">Leads</div>
<div className="text-right">Actions</div>
</div>
{/* Table Body */}
@ -462,7 +463,7 @@ function ListingRow({
</div>
{/* 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">
<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">
@ -478,8 +479,8 @@ function ListingRow({
"bg-white/5 text-white/40 border-white/10"
)}>{listing.status}</span>
</div>
<div className="text-right 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.view_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">
{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">

View File

@ -696,6 +696,11 @@ export default function PortfolioPage() {
const tier = subscription?.tier || 'scout'
const tierName = subscription?.tier_name || tier
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])
@ -1127,6 +1132,7 @@ export default function PortfolioPage() {
</div>
<h1 className="font-display text-[2.5rem] leading-[1] tracking-[-0.02em] text-white">
My Portfolio
<span className="text-white/30 ml-3 font-mono text-[2rem]">{stats.total}/{formatLimit(maxPortfolio)}</span>
</h1>
<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.

View File

@ -174,6 +174,12 @@ export default function WatchlistPage() {
}, [checkAuth])
// 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 available = domains?.filter(d => d.is_available) || []
const expiringSoon = domains?.filter(d => {
@ -452,7 +458,7 @@ export default function WatchlistPage() {
{/* Stats Grid */}
<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="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>
<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="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>
<div className="text-right">