diff --git a/backend/app/api/listings.py b/backend/app/api/listings.py index 49c43ec..5250fe6 100644 --- a/backend/app/api/listings.py +++ b/backend/app/api/listings.py @@ -675,16 +675,18 @@ async def create_listing( ) listing_count = user_listings.scalar() or 0 - # Listing limits by tier (from pounce_pricing.md) - # Load subscription separately to avoid async lazy loading issues - from app.models.subscription import Subscription + # Listing limits by tier - using TIER_CONFIG + from app.models.subscription import Subscription, TIER_CONFIG, SubscriptionTier sub_result = await db.execute( select(Subscription).where(Subscription.user_id == current_user.id) ) subscription = sub_result.scalar_one_or_none() - tier = subscription.tier if subscription else "scout" - limits = {"scout": 0, "trader": 5, "tycoon": 50} - max_listings = limits.get(tier, 0) + tier = subscription.tier if subscription else SubscriptionTier.SCOUT + tier_config = TIER_CONFIG.get(tier, TIER_CONFIG[SubscriptionTier.SCOUT]) + max_listings = tier_config.get("listing_limit", 0) + # -1 means unlimited + if max_listings == -1: + max_listings = 999999 if listing_count >= max_listings: raise HTTPException( diff --git a/backend/app/api/sniper_alerts.py b/backend/app/api/sniper_alerts.py index 80c3ab7..3b5c703 100644 --- a/backend/app/api/sniper_alerts.py +++ b/backend/app/api/sniper_alerts.py @@ -187,9 +187,10 @@ async def create_sniper_alert( ) alert_count = user_alerts.scalar() or 0 - tier = current_user.subscription.tier if current_user.subscription else "scout" - limits = {"scout": 2, "trader": 10, "tycoon": 50} - max_alerts = limits.get(tier, 2) + from app.models.subscription import TIER_CONFIG, SubscriptionTier + tier = current_user.subscription.tier if current_user.subscription else SubscriptionTier.SCOUT + tier_config = TIER_CONFIG.get(tier, TIER_CONFIG[SubscriptionTier.SCOUT]) + max_alerts = tier_config.get("sniper_limit", 2) if alert_count >= max_alerts: raise HTTPException( diff --git a/backend/app/api/yield_domains.py b/backend/app/api/yield_domains.py index 323af73..0e5764c 100644 --- a/backend/app/api/yield_domains.py +++ b/backend/app/api/yield_domains.py @@ -343,7 +343,12 @@ async def activate_domain_for_yield( tier = subscription.tier if subscription else SubscriptionTier.SCOUT tier_value = tier.value if hasattr(tier, "value") else str(tier) - if tier_value == "scout": + # Check if tier has yield feature + from app.models.subscription import TIER_CONFIG + tier_config = TIER_CONFIG.get(tier, {}) + has_yield = tier_config.get("features", {}).get("yield", False) + + if not has_yield: raise HTTPException( status_code=403, detail="Yield is not available on Scout plan. Upgrade to Trader or Tycoon.", diff --git a/backend/app/models/subscription.py b/backend/app/models/subscription.py index 28ab5f7..fc2e818 100644 --- a/backend/app/models/subscription.py +++ b/backend/app/models/subscription.py @@ -12,13 +12,13 @@ class SubscriptionTier(str, Enum): """ Subscription tiers for pounce.ch - Scout (Free): 5 domains, daily checks, email alerts - Trader (€19/mo): 50 domains, hourly checks, portfolio, valuation - Tycoon (€49/mo): 500+ domains, 10-min checks, API, bulk tools + Scout (Free): 10 watchlist, 3 portfolio, 1 listing, daily checks + Trader ($9/mo): 100 watchlist, 50 portfolio, 10 listings, hourly checks + Tycoon ($29/mo): Unlimited, 5-min checks, API, bulk tools, exclusive drops """ SCOUT = "scout" # Free tier - TRADER = "trader" # €19/month - TYCOON = "tycoon" # €49/month + TRADER = "trader" # $9/month + TYCOON = "tycoon" # $29/month class SubscriptionStatus(str, Enum): @@ -31,35 +31,42 @@ class SubscriptionStatus(str, Enum): # Plan configuration - matches frontend pricing page +# Updated 2024: Better conversion funnel with taste-before-pay model TIER_CONFIG = { SubscriptionTier.SCOUT: { "name": "Scout", "price": 0, "currency": "USD", - "domain_limit": 5, - "portfolio_limit": 0, + "domain_limit": 10, # Watchlist: 10 (was 5) + "portfolio_limit": 3, # Portfolio: 3 (was 0) - taste the feature + "listing_limit": 1, # Listings: 1 (was 0) - try selling + "sniper_limit": 2, # Sniper alerts "check_frequency": "daily", - "history_days": 0, + "history_days": 7, # 7 days history (was 0) "features": { "email_alerts": True, "sms_alerts": False, "priority_alerts": False, "full_whois": False, "expiration_tracking": False, - "domain_valuation": False, + "domain_valuation": True, # Basic score enabled "market_insights": False, "api_access": False, "webhooks": False, "bulk_tools": False, "seo_metrics": False, + "yield": False, + "exclusive_drops": False, } }, SubscriptionTier.TRADER: { "name": "Trader", "price": 9, "currency": "USD", - "domain_limit": 50, - "portfolio_limit": 25, + "domain_limit": 100, # Watchlist: 100 (was 50) + "portfolio_limit": 50, # Portfolio: 50 (was 25) + "listing_limit": 10, # Listings: 10 (was 5) + "sniper_limit": 10, # Sniper alerts "check_frequency": "hourly", "history_days": 90, "features": { @@ -74,16 +81,20 @@ TIER_CONFIG = { "webhooks": False, "bulk_tools": False, "seo_metrics": False, + "yield": True, + "exclusive_drops": False, } }, SubscriptionTier.TYCOON: { "name": "Tycoon", "price": 29, "currency": "USD", - "domain_limit": 500, - "portfolio_limit": -1, # Unlimited - "check_frequency": "realtime", # Every 10 minutes - "history_days": -1, # Unlimited + "domain_limit": -1, # Unlimited watchlist + "portfolio_limit": -1, # Unlimited portfolio + "listing_limit": -1, # Unlimited listings + "sniper_limit": 50, # Sniper alerts + "check_frequency": "5min", # Every 5 minutes (was 10min) + "history_days": -1, # Unlimited "features": { "email_alerts": True, "sms_alerts": True, @@ -96,6 +107,8 @@ TIER_CONFIG = { "webhooks": True, "bulk_tools": True, "seo_metrics": True, + "yield": True, + "exclusive_drops": True, # Tycoon exclusive: 24h early access } }, } diff --git a/frontend/src/app/pricing/page.tsx b/frontend/src/app/pricing/page.tsx index 728b921..06a39b5 100644 --- a/frontend/src/app/pricing/page.tsx +++ b/frontend/src/app/pricing/page.tsx @@ -17,16 +17,16 @@ const tiers = [ icon: Zap, price: '0', period: '', - description: 'Recon access. No commitment.', + description: 'Taste the system. No commitment.', features: [ { text: 'Market Feed', highlight: false, available: true, sublabel: 'Raw' }, { text: 'Alert Speed', highlight: false, available: true, sublabel: 'Daily' }, - { text: '5 Watchlist Domains', highlight: false, available: true }, + { text: '10 Watchlist Domains', highlight: false, available: true }, + { text: '3 Portfolio Domains', highlight: false, available: true }, + { text: '1 Listing', highlight: false, available: true, sublabel: 'Try it' }, { text: '2 Sniper Alerts', highlight: false, available: true }, + { text: 'Pounce Score', highlight: false, available: true, sublabel: 'Basic' }, { 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 (Beta)', highlight: false, available: false }, ], cta: 'Enter Terminal', highlighted: false, @@ -43,13 +43,13 @@ const tiers = [ features: [ { 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: '100 Watchlist Domains', highlight: true, available: true }, + { text: '50 Portfolio Domains', highlight: true, available: true }, + { text: '10 Listings', highlight: true, available: true, sublabel: '0% Fee' }, { text: '10 Sniper Alerts', highlight: true, available: true }, + { text: 'Pounce Score', highlight: true, available: true, sublabel: 'Full' }, { 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: 'Portfolio', highlight: true, available: true, sublabel: '25 Domains' }, - { text: 'Yield (Beta)', highlight: false, available: true, sublabel: 'Optional' }, + { text: 'Yield', highlight: true, available: true }, ], cta: 'Upgrade to Trader', highlighted: true, @@ -62,17 +62,17 @@ const tiers = [ icon: Crown, price: '29', period: '/mo', - description: 'Full firepower. Priority alerts.', + description: 'Full firepower. No limits.', features: [ { text: 'Market Feed', highlight: true, available: true, sublabel: 'Priority' }, - { text: 'Alert Speed', highlight: true, available: true, sublabel: '10 min' }, - { 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: 'Alert Speed', highlight: true, available: true, sublabel: '5 min' }, + { text: 'Unlimited Watchlist', highlight: true, available: true }, { text: 'Unlimited Portfolio', highlight: true, available: true }, - { text: 'Yield (Beta)', highlight: false, available: true, sublabel: 'Optional' }, + { text: 'Unlimited Listings', highlight: true, available: true, sublabel: 'Featured' }, + { text: '50 Sniper Alerts', highlight: true, available: true }, + { text: 'Score + SEO Data', highlight: true, available: true }, + { text: 'API + Webhooks', highlight: true, available: true }, + { text: 'Exclusive Drops', highlight: true, available: true, sublabel: '24h Early' }, ], cta: 'Go Tycoon', highlighted: false, @@ -82,21 +82,23 @@ const tiers = [ ] const comparisonFeatures = [ - { name: 'Market Feed', scout: 'Raw (Unfiltered)', trader: 'Curated (Spam-Free)', tycoon: 'Curated + Priority' }, - { name: 'Alert Speed', scout: 'Daily', trader: 'Hourly', tycoon: 'Every 10 minutes' }, - { name: 'Watchlist', scout: '5 Domains', trader: '50 Domains', tycoon: '500 Domains' }, + { name: 'Market Feed', scout: 'Raw', trader: 'Curated', tycoon: 'Priority + Early' }, + { name: 'Alert Speed', scout: 'Daily', trader: 'Hourly', tycoon: 'Every 5 min' }, + { name: 'Watchlist', scout: '10 Domains', trader: '100 Domains', tycoon: 'Unlimited' }, + { name: 'Portfolio', scout: '3 Domains', trader: '50 Domains', tycoon: 'Unlimited' }, + { name: 'Listings', scout: '1 (Try it)', trader: '10 (0% Fee)', tycoon: 'Unlimited + 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: 'Marketplace', scout: 'Buy Only', trader: '5 Listings (0% Fee)', tycoon: '50 Featured' }, - { name: 'Portfolio', scout: '—', trader: '25 Domains', tycoon: 'Unlimited' }, - { name: 'Yield (Beta)', scout: '—', trader: 'Optional', tycoon: 'Optional' }, + { name: 'Valuation', scout: 'Basic Score', trader: 'Pounce Score', tycoon: 'Score + SEO' }, + { name: 'TLD Intel', scout: 'Public', trader: 'Renewal Prices', tycoon: 'Full History' }, + { name: 'Yield', scout: '—', trader: '✓', tycoon: '✓' }, + { name: 'Exclusive Drops', scout: '—', trader: '—', tycoon: '24h Early Access' }, + { name: 'API Access', scout: '—', trader: '—', tycoon: '✓' }, ] const faqs = [ { q: 'How fast will I know when a domain drops?', - a: 'Depends on your plan. Scout: daily. Trader: hourly. Tycoon: every 10 minutes. When it drops, you\'ll know.', + a: 'Depends on your plan. Scout: daily. Trader: hourly. Tycoon: every 5 minutes. When it drops, you\'ll know.', }, { q: 'What\'s domain valuation?', diff --git a/frontend/src/app/terminal/inbox/page.tsx b/frontend/src/app/terminal/inbox/page.tsx index 6063475..56ce475 100644 --- a/frontend/src/app/terminal/inbox/page.tsx +++ b/frontend/src/app/terminal/inbox/page.tsx @@ -111,7 +111,8 @@ export default function InboxPage() { const tierName = subscription?.tier_name || subscription?.tier || 'Scout' const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap - const isSeller = tierName !== 'Scout' // Scout can't list domains + // All tiers can now list domains (Scout=1, Trader=10, Tycoon=unlimited) + const isSeller = true // Load buyer threads const loadBuyerThreads = useCallback(async () => { diff --git a/frontend/src/app/terminal/listing/page.tsx b/frontend/src/app/terminal/listing/page.tsx index 30ecbce..fe01e1f 100755 --- a/frontend/src/app/terminal/listing/page.tsx +++ b/frontend/src/app/terminal/listing/page.tsx @@ -75,11 +75,11 @@ export default function MyListingsPage() { const [menuOpen, setMenuOpen] = useState(false) const tier = subscription?.tier || 'scout' - const isScout = tier === 'scout' - const listingLimits: Record = { scout: 0, trader: 5, tycoon: 50 } - const maxListings = listingLimits[tier] || 0 - const canAddMore = listings.length < maxListings && !isScout + const listingLimits: Record = { scout: 1, trader: 10, tycoon: 999999 } + const maxListings = listingLimits[tier] || 1 + const canAddMore = listings.length < maxListings const isTycoon = tier === 'tycoon' + const isUnlimited = tier === 'tycoon' const activeListings = listings.filter(l => l.status === 'active').length const draftListings = listings.filter(l => l.status === 'draft').length @@ -87,20 +87,16 @@ export default function MyListingsPage() { const totalInquiries = listings.reduce((sum, l) => sum + l.inquiry_count, 0) useEffect(() => { checkAuth() }, [checkAuth]) - useEffect(() => { if (prefillDomain && !isScout) setShowCreateWizard(true) }, [prefillDomain, isScout]) + useEffect(() => { if (prefillDomain) setShowCreateWizard(true) }, [prefillDomain]) const loadListings = useCallback(async () => { - if (isScout) { - setLoading(false) - return - } setLoading(true) try { const data = await api.getMyListings() setListings(data) } catch (err) { console.error(err) } finally { setLoading(false) } - }, [isScout]) + }, []) useEffect(() => { loadListings() }, [loadListings]) @@ -153,68 +149,7 @@ export default function MyListingsPage() { ] // ============================================================================ - // SCOUT UPGRADE PROMPT (Feature not available for free tier) - // ============================================================================ - if (isScout) { - return ( -
-
-
-
-
-
- -
-

For Sale

-

- List domains on Pounce Direct. -

-

- 0% commission. DNS-verified ownership. Direct buyer contact. -

- -
-
-
- - Trader -
- $9/mo -
-
    -
  • 5 Active Listings
  • -
  • DNS Ownership Verification
  • -
  • Direct Buyer Contact
  • -
-
- -
-
-
- - Tycoon -
- $29/mo -
-
    -
  • 50 Active Listings
  • -
  • Featured Placement
  • -
  • Priority in Market Feed
  • -
-
- - - Upgrade - -
-
-
-
- ) - } - - // ============================================================================ - // MAIN LISTING VIEW (For Trader & Tycoon) + // MAIN LISTING VIEW (All tiers - Scout has 1 listing, Trader 10, Tycoon unlimited) // ============================================================================ return (
@@ -229,7 +164,7 @@ export default function MyListingsPage() {
For Sale
- {listings.length}/{maxListings} + {listings.length}{isUnlimited ? '' : `/${maxListings}`}
diff --git a/frontend/src/app/terminal/sniper/page.tsx b/frontend/src/app/terminal/sniper/page.tsx index 023e68d..2c96a26 100644 --- a/frontend/src/app/terminal/sniper/page.tsx +++ b/frontend/src/app/terminal/sniper/page.tsx @@ -84,6 +84,7 @@ export default function SniperAlertsPage() { const tier = subscription?.tier || 'scout' const alertLimits: Record = { scout: 2, trader: 10, tycoon: 50 } const maxAlerts = alertLimits[tier] || 2 + // Limits match backend TIER_CONFIG.sniper_limit const canAddMore = alerts.length < maxAlerts const activeAlerts = alerts.filter(a => a.is_active).length diff --git a/frontend/src/app/terminal/yield/page.tsx b/frontend/src/app/terminal/yield/page.tsx index ecbf653..3001ec4 100644 --- a/frontend/src/app/terminal/yield/page.tsx +++ b/frontend/src/app/terminal/yield/page.tsx @@ -418,28 +418,8 @@ export default function YieldPage() {
- {/* COMING SOON BANNER */} -
-
-
- -
-
-
-

Feature in Development

- Coming Soon -
-

- We're building an automated yield system for your parked domains. - Soon you'll be able to monetize idle domains with intelligent traffic routing. - Stay tuned for updates! -

-
-
-
- {/* DESKTOP HEADER */} -
+