diff --git a/frontend/src/app/acquire/page.tsx b/frontend/src/app/acquire/page.tsx index 6070d2e..197083f 100644 --- a/frontend/src/app/acquire/page.tsx +++ b/frontend/src/app/acquire/page.tsx @@ -77,6 +77,26 @@ const PLATFORMS = [ const PREMIUM_TLDS = ['com', 'io', 'ai', 'co', 'de', 'ch', 'net', 'org', 'app', 'dev', 'xyz'] +// Live time calculation (same as Terminal) +function calcTimeRemaining(endTimeIso?: string): string { + if (!endTimeIso) return 'N/A' + const end = new Date(endTimeIso).getTime() + const now = Date.now() + const diff = end - now + + if (diff <= 0) return 'Ended' + + const seconds = Math.floor(diff / 1000) + const days = Math.floor(seconds / 86400) + const hours = Math.floor((seconds % 86400) / 3600) + const mins = Math.floor((seconds % 3600) / 60) + + if (days > 0) return `${days}d ${hours}h` + if (hours > 0) return `${hours}h ${mins}m` + if (mins > 0) return `${mins}m` + return '< 1m' +} + function isVanityDomain(auction: Auction): boolean { const parts = auction.domain.split('.') if (parts.length < 2) return false @@ -99,6 +119,9 @@ export default function AcquirePage() { const [loading, setLoading] = useState(true) const [activeTab, setActiveTab] = useState('all') + // Stats from API (real totals, not array lengths) + const [stats, setStats] = useState({ total: 0, ending: 0, direct: 0 }) + const [searchQuery, setSearchQuery] = useState('') const [selectedPlatform, setSelectedPlatform] = useState('All') const [searchFocused, setSearchFocused] = useState(false) @@ -113,10 +136,10 @@ export default function AcquirePage() { setLoading(true) try { const [allFeed, endingFeed, hotFeed, pounceFeed] = await Promise.all([ - api.getMarketFeed({ source: 'all', limit: 100, sortBy: 'time' }), - api.getMarketFeed({ source: 'external', endingWithin: 24, limit: 50, sortBy: 'time' }), + api.getMarketFeed({ source: 'external', limit: 150, sortBy: 'time' }), + api.getMarketFeed({ source: 'external', endingWithin: 24, limit: 100, sortBy: 'time' }), api.getMarketFeed({ source: 'external', limit: 50, sortBy: 'score' }), - api.getMarketFeed({ source: 'pounce', limit: 10 }), + api.getMarketFeed({ source: 'pounce', limit: 20 }), ]) const convertToAuction = (item: MarketItem): Auction => ({ @@ -136,12 +159,19 @@ export default function AcquirePage() { affiliate_url: item.url, }) - const externalOnly = (items: MarketItem[]) => items.filter(i => !i.is_pounce).map(convertToAuction) + const toAuctions = (items: MarketItem[]) => items.map(convertToAuction) - setAllAuctions(externalOnly(allFeed.items || [])) - setEndingSoon(externalOnly(endingFeed.items || [])) - setHotAuctions(externalOnly(hotFeed.items || [])) + setAllAuctions(toAuctions(allFeed.items || [])) + setEndingSoon(toAuctions(endingFeed.items || [])) + setHotAuctions(toAuctions(hotFeed.items || [])) setPounceItems(pounceFeed.items || []) + + // Set real totals from API + setStats({ + total: allFeed.total || allFeed.auction_count || 0, + ending: endingFeed.total || 0, + direct: pounceFeed.pounce_direct_count || pounceFeed.total || 0, + }) } catch (error) { console.error('Failed to load auctions:', error) } finally { @@ -173,9 +203,13 @@ export default function AcquirePage() { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(amount) } - const getTimeColor = (timeRemaining: string) => { - if (timeRemaining.includes('m') && !timeRemaining.includes('h')) return 'text-red-400' - if (timeRemaining.includes('h') && parseInt(timeRemaining) < 12) return 'text-amber-400' + const getTimeColor = (endTime: string) => { + if (!endTime) return 'text-white/40' + const diff = new Date(endTime).getTime() - Date.now() + if (diff <= 0) return 'text-white/20' + const hours = diff / (1000 * 60 * 60) + if (hours < 1) return 'text-red-400' + if (hours < 12) return 'text-amber-400' return 'text-white/40' } @@ -224,11 +258,11 @@ export default function AcquirePage() { {/* Stats Grid */}
-
{allAuctions.length}
+
{stats.total || allAuctions.length}
Total
-
{endingSoon.length}
+
{stats.ending || endingSoon.length}
Ending
@@ -411,15 +445,15 @@ export default function AcquirePage() {
-
{allAuctions.length.toLocaleString()}
+
{(stats.total || allAuctions.length).toLocaleString()}
Live Auctions
-
{endingSoon.length.toLocaleString()}
+
{(stats.ending || endingSoon.length).toLocaleString()}
Ending 24h
-
{pounceItems.length.toLocaleString()}
+
{(stats.direct || pounceItems.length).toLocaleString()}
Direct
diff --git a/frontend/src/app/pricing/page.tsx b/frontend/src/app/pricing/page.tsx index a66c56a..75edfe1 100644 --- a/frontend/src/app/pricing/page.tsx +++ b/frontend/src/app/pricing/page.tsx @@ -19,14 +19,14 @@ const tiers = [ period: '', description: 'Test the waters. Zero risk.', features: [ - { 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: 'Pounce Score', highlight: false, available: false }, + { text: '5 Watchlist Domains', highlight: false, available: true }, { text: '2 Sniper Alerts', highlight: false, available: true }, + { text: 'TLD Intel', highlight: false, available: true, sublabel: 'Public' }, + { text: 'Pounce Score', highlight: false, available: false }, { text: 'Marketplace', highlight: false, available: true, sublabel: 'Buy Only' }, + { text: 'Yield (Intent Routing)', highlight: false, available: false }, ], cta: 'Start Free', highlighted: false, @@ -41,14 +41,15 @@ const tiers = [ period: '/mo', 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: '50 Watchlist Domains', highlight: true, available: true }, + { text: '10 Sniper Alerts', highlight: true, available: true }, { 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', highlight: true, available: true, sublabel: '25 Domains' }, + { text: 'Yield (Intent Routing)', highlight: true, available: true, sublabel: '70% Rev Share' }, ], cta: 'Upgrade to Trader', highlighted: true, @@ -63,14 +64,15 @@ const tiers = [ period: '/mo', 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: 'Real-Time' }, + { text: '500 Watchlist Domains', highlight: true, available: true }, + { text: '50 Sniper Alerts', highlight: true, available: true }, { 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: 'Unlimited Portfolio', highlight: true, available: true }, + { text: 'Yield (Intent Routing)', highlight: true, available: true, sublabel: 'Priority Routes' }, ], cta: 'Go Tycoon', highlighted: false, @@ -83,11 +85,12 @@ const comparisonFeatures = [ { 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: 'Marketplace', scout: 'Buy Only', trader: 'Sell (0% Fee)', tycoon: 'Sell + Featured' }, + { name: 'Sniper Alerts', scout: '2', trader: '10', tycoon: '50' }, { 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' }, + { name: 'Marketplace', scout: 'Buy Only', trader: '5 Listings (0% Fee)', tycoon: '50 Featured' }, { name: 'Portfolio', scout: '—', trader: '25 Domains', tycoon: 'Unlimited' }, + { name: 'Yield (Intent Routing)', scout: '—', trader: '70% Rev Share', tycoon: 'Priority Routes' }, ] const faqs = [ diff --git a/frontend/src/app/terminal/market/page.tsx b/frontend/src/app/terminal/market/page.tsx index 1d750df..52d24c5 100644 --- a/frontend/src/app/terminal/market/page.tsx +++ b/frontend/src/app/terminal/market/page.tsx @@ -107,7 +107,11 @@ function formatPrice(price: number, currency = 'USD'): string { function isSpam(domain: string): boolean { const name = domain.split('.')[0] - return /[-\d]/.test(name) + // Spam = contains hyphens OR is mostly numbers OR very long with numbers + if (name.includes('-')) return true + if (name.length > 4 && /\d/.test(name)) return true // Short names with numbers are OK (e.g., "4chan") + if (/^\d+$/.test(name)) return true // Pure number domains + return false } // ============================================================================ @@ -645,13 +649,14 @@ export default function MarketPage() {