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
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:
@ -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">
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user