From 1f72f2664dc065f61777ebcbbb6a0bd1d1d8106d Mon Sep 17 00:00:00 2001 From: Yves Gugger Date: Sun, 14 Dec 2025 21:44:40 +0100 Subject: [PATCH] fix: Portfolio page redesign, unified DNS verification, fix API route conflicts --- backend/app/api/listings.py | 9 +- backend/app/api/portfolio.py | 208 +-- frontend/src/app/about/page.tsx | 2 +- frontend/src/app/acquire/metadata.ts | 8 +- frontend/src/app/acquire/page.tsx | 4 +- frontend/src/app/blog/page.tsx | 50 +- frontend/src/app/contact/page.tsx | 16 +- frontend/src/app/layout.tsx | 13 +- frontend/src/app/metadata.ts | 10 +- frontend/src/app/pricing/metadata.ts | 6 +- frontend/src/app/pricing/page.tsx | 14 +- frontend/src/app/register/page.tsx | 2 +- frontend/src/app/terminal/listing/page.tsx | 27 +- frontend/src/app/terminal/portfolio/page.tsx | 1421 +++++------------ frontend/src/app/terminal/radar/page.tsx | 73 +- frontend/src/app/terminal/settings/page.tsx | 2 +- frontend/src/app/terminal/welcome/page.tsx | 7 +- frontend/src/app/terminal/yield/page.tsx | 4 +- .../src/app/tld/[tld]/TldDetailClient.tsx | 2 +- frontend/src/components/Footer.tsx | 52 +- frontend/src/components/Header.tsx | 8 +- frontend/src/components/SEO.tsx | 4 +- frontend/src/lib/seo.ts | 12 +- 23 files changed, 740 insertions(+), 1214 deletions(-) diff --git a/backend/app/api/listings.py b/backend/app/api/listings.py index 01e45ce..32c7d34 100644 --- a/backend/app/api/listings.py +++ b/backend/app/api/listings.py @@ -548,6 +548,10 @@ async def create_listing( detail="Cannot list a sold domain for sale.", ) + # Check if domain is DNS verified in portfolio + # If verified in portfolio, listing inherits verification + is_portfolio_verified = getattr(portfolio_domain, 'is_dns_verified', False) or False + # Check if domain is already listed existing = await db.execute( select(DomainListing).where(DomainListing.domain == domain_lower) @@ -600,6 +604,7 @@ async def create_listing( estimated_value = None # Create listing + # If portfolio domain is already DNS verified, listing is auto-verified listing = DomainListing( user_id=current_user.id, domain=domain_lower, @@ -614,7 +619,9 @@ async def create_listing( allow_offers=data.allow_offers, pounce_score=pounce_score, estimated_value=estimated_value, - verification_code=_generate_verification_code(), + verification_code=portfolio_domain.verification_code if is_portfolio_verified else _generate_verification_code(), + verification_status=VerificationStatus.VERIFIED.value if is_portfolio_verified else VerificationStatus.PENDING.value, + verified_at=portfolio_domain.verified_at if is_portfolio_verified else None, status=ListingStatus.DRAFT.value, ) diff --git a/backend/app/api/portfolio.py b/backend/app/api/portfolio.py index 8bcd729..c6521c6 100644 --- a/backend/app/api/portfolio.py +++ b/backend/app/api/portfolio.py @@ -176,7 +176,112 @@ class ValuationResponse(BaseModel): disclaimer: str +# ============== Helper Functions ============== + +def _generate_verification_code() -> str: + """Generate a unique verification code.""" + return f"pounce-verify-{secrets.token_hex(8)}" + + +def _domain_to_response(domain: PortfolioDomain) -> PortfolioDomainResponse: + """Convert PortfolioDomain to response schema.""" + return PortfolioDomainResponse( + id=domain.id, + domain=domain.domain, + purchase_date=domain.purchase_date, + purchase_price=domain.purchase_price, + purchase_registrar=domain.purchase_registrar, + registrar=domain.registrar, + renewal_date=domain.renewal_date, + renewal_cost=domain.renewal_cost, + auto_renew=domain.auto_renew, + estimated_value=domain.estimated_value, + value_updated_at=domain.value_updated_at, + is_sold=domain.is_sold, + sale_date=domain.sale_date, + sale_price=domain.sale_price, + status=domain.status, + notes=domain.notes, + tags=domain.tags, + roi=domain.roi, + is_dns_verified=getattr(domain, 'is_dns_verified', False) or False, + verification_status=getattr(domain, 'verification_status', 'unverified') or 'unverified', + verification_code=getattr(domain, 'verification_code', None), + verified_at=getattr(domain, 'verified_at', None), + created_at=domain.created_at, + updated_at=domain.updated_at, + ) + + # ============== Portfolio Endpoints ============== +# IMPORTANT: Static routes must come BEFORE dynamic routes like /{domain_id} + +@router.get("/verified", response_model=List[PortfolioDomainResponse]) +async def get_verified_domains( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + """ + Get only DNS-verified portfolio domains. + + These domains can be used for Yield or For Sale listings. + """ + result = await db.execute( + select(PortfolioDomain).where( + and_( + PortfolioDomain.user_id == current_user.id, + PortfolioDomain.is_dns_verified == True, + PortfolioDomain.is_sold == False, + ) + ).order_by(PortfolioDomain.domain.asc()) + ) + domains = result.scalars().all() + + return [_domain_to_response(d) for d in domains] + + +@router.get("/summary", response_model=PortfolioSummary) +async def get_portfolio_summary( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + """Get portfolio summary statistics.""" + result = await db.execute( + select(PortfolioDomain).where(PortfolioDomain.user_id == current_user.id) + ) + domains = result.scalars().all() + + total_domains = len(domains) + active_domains = sum(1 for d in domains if d.status == "active" and not d.is_sold) + sold_domains = sum(1 for d in domains if d.is_sold) + + total_invested = sum(d.purchase_price or 0 for d in domains) + total_value = sum(d.estimated_value or 0 for d in domains if not d.is_sold) + total_sold_value = sum(d.sale_price or 0 for d in domains if d.is_sold) + + # Calculate active investment for ROI + active_investment = sum(d.purchase_price or 0 for d in domains if not d.is_sold) + sold_investment = sum(d.purchase_price or 0 for d in domains if d.is_sold) + + unrealized_profit = total_value - active_investment + realized_profit = total_sold_value - sold_investment + + overall_roi = 0.0 + if total_invested > 0: + overall_roi = ((total_value + total_sold_value - total_invested) / total_invested) * 100 + + return PortfolioSummary( + total_domains=total_domains, + active_domains=active_domains, + sold_domains=sold_domains, + total_invested=round(total_invested, 2), + total_value=round(total_value, 2), + total_sold_value=round(total_sold_value, 2), + unrealized_profit=round(unrealized_profit, 2), + realized_profit=round(realized_profit, 2), + overall_roi=round(overall_roi, 2), + ) + @router.get("", response_model=List[PortfolioDomainResponse]) async def get_portfolio( @@ -242,49 +347,6 @@ async def get_portfolio( return responses -@router.get("/summary", response_model=PortfolioSummary) -async def get_portfolio_summary( - current_user: User = Depends(get_current_user), - db: AsyncSession = Depends(get_db), -): - """Get portfolio summary statistics.""" - result = await db.execute( - select(PortfolioDomain).where(PortfolioDomain.user_id == current_user.id) - ) - domains = result.scalars().all() - - total_domains = len(domains) - active_domains = sum(1 for d in domains if d.status == "active" and not d.is_sold) - sold_domains = sum(1 for d in domains if d.is_sold) - - total_invested = sum(d.purchase_price or 0 for d in domains) - total_value = sum(d.estimated_value or 0 for d in domains if not d.is_sold) - total_sold_value = sum(d.sale_price or 0 for d in domains if d.is_sold) - - # Calculate active investment for ROI - active_investment = sum(d.purchase_price or 0 for d in domains if not d.is_sold) - sold_investment = sum(d.purchase_price or 0 for d in domains if d.is_sold) - - unrealized_profit = total_value - active_investment - realized_profit = total_sold_value - sold_investment - - overall_roi = 0.0 - if total_invested > 0: - overall_roi = ((total_value + total_sold_value - total_invested) / total_invested) * 100 - - return PortfolioSummary( - total_domains=total_domains, - active_domains=active_domains, - sold_domains=sold_domains, - total_invested=round(total_invested, 2), - total_value=round(total_value, 2), - total_sold_value=round(total_sold_value, 2), - unrealized_profit=round(unrealized_profit, 2), - realized_profit=round(realized_profit, 2), - overall_roi=round(overall_roi, 2), - ) - - @router.post("", response_model=PortfolioDomainResponse, status_code=status.HTTP_201_CREATED) async def add_portfolio_domain( data: PortfolioDomainCreate, @@ -670,42 +732,6 @@ async def get_domain_valuation( # ============== DNS Verification Endpoints ============== -def _generate_verification_code() -> str: - """Generate a unique verification code.""" - return f"pounce-verify-{secrets.token_hex(8)}" - - -def _domain_to_response(domain: PortfolioDomain) -> PortfolioDomainResponse: - """Convert PortfolioDomain to response schema.""" - return PortfolioDomainResponse( - id=domain.id, - domain=domain.domain, - purchase_date=domain.purchase_date, - purchase_price=domain.purchase_price, - purchase_registrar=domain.purchase_registrar, - registrar=domain.registrar, - renewal_date=domain.renewal_date, - renewal_cost=domain.renewal_cost, - auto_renew=domain.auto_renew, - estimated_value=domain.estimated_value, - value_updated_at=domain.value_updated_at, - is_sold=domain.is_sold, - sale_date=domain.sale_date, - sale_price=domain.sale_price, - status=domain.status, - notes=domain.notes, - tags=domain.tags, - roi=domain.roi, - # Use getattr with defaults for new fields that may not exist in DB yet - is_dns_verified=getattr(domain, 'is_dns_verified', False) or False, - verification_status=getattr(domain, 'verification_status', 'unverified') or 'unverified', - verification_code=getattr(domain, 'verification_code', None), - verified_at=getattr(domain, 'verified_at', None), - created_at=domain.created_at, - updated_at=domain.updated_at, - ) - - @router.post("/{domain_id}/verify-dns", response_model=DNSVerificationStartResponse) async def start_dns_verification( domain_id: int, @@ -860,27 +886,3 @@ async def check_dns_verification( message=f"TXT record found but value doesn't match. Expected: {domain.verification_code}", ) - -@router.get("/verified", response_model=List[PortfolioDomainResponse]) -async def get_verified_domains( - current_user: User = Depends(get_current_user), - db: AsyncSession = Depends(get_db), -): - """ - Get only DNS-verified portfolio domains. - - These domains can be used for Yield or For Sale listings. - """ - result = await db.execute( - select(PortfolioDomain).where( - and_( - PortfolioDomain.user_id == current_user.id, - PortfolioDomain.is_dns_verified == True, - PortfolioDomain.is_sold == False, - ) - ).order_by(PortfolioDomain.domain.asc()) - ) - domains = result.scalars().all() - - return [_domain_to_response(d) for d in domains] - diff --git a/frontend/src/app/about/page.tsx b/frontend/src/app/about/page.tsx index c2222f7..a0f6d76 100644 --- a/frontend/src/app/about/page.tsx +++ b/frontend/src/app/about/page.tsx @@ -143,7 +143,7 @@ export default function AboutPage() { className="px-8 py-4 bg-accent text-black text-xs font-bold uppercase tracking-[0.2em] hover:bg-white transition-all shadow-[0_0_20px_rgba(16,185,129,0.2)]" style={{ clipPath: 'polygon(10px 0, 100% 0, 100% 100%, 0 100%, 0 10px)' }} > - Start Hunting + Enter Terminal

- Global liquidity pool. Verified assets only. - Aggregated from GoDaddy, Sedo, and Pounce Direct. + External auctions + Pounce Direct listings. + Pounce Direct owners are DNS-verified. External auctions are sourced from GoDaddy, Sedo, and other partners.

diff --git a/frontend/src/app/blog/page.tsx b/frontend/src/app/blog/page.tsx index 45584ac..d6efb8b 100644 --- a/frontend/src/app/blog/page.tsx +++ b/frontend/src/app/blog/page.tsx @@ -47,6 +47,9 @@ export default function BlogPage() { const [loading, setLoading] = useState(true) const [selectedCategory, setSelectedCategory] = useState(null) const [total, setTotal] = useState(0) + const [newsletterEmail, setNewsletterEmail] = useState('') + const [newsletterState, setNewsletterState] = useState<'idle' | 'loading' | 'success' | 'error'>('idle') + const [newsletterError, setNewsletterError] = useState(null) useEffect(() => { loadBlogData() @@ -237,7 +240,7 @@ export default function BlogPage() {
- Read Article + Full briefing @@ -326,7 +329,7 @@ export default function BlogPage() { }} className="group inline-flex items-center gap-3 px-8 py-4 bg-background-secondary/50 border border-border rounded-full text-foreground font-medium hover:border-accent/50 hover:bg-accent/5 transition-all duration-300" > - Load More Articles + Load more briefings @@ -339,21 +342,50 @@ export default function BlogPage() {

- Get hunting tips in your inbox + Mission Briefings

- Join domain hunters who receive weekly insights, market trends, and exclusive strategies. + Weekly intel on drops, pricing traps, and market moves. No spam.

-
+
{ + e.preventDefault() + const email = newsletterEmail.trim() + if (!email || !email.includes('@') || !email.includes('.')) return + if (newsletterState === 'loading') return + + setNewsletterState('loading') + setNewsletterError(null) + try { + await api.subscribeNewsletter(email) + setNewsletterState('success') + } catch (err: any) { + setNewsletterState('error') + setNewsletterError(err?.message || 'Subscription failed. Retry.') + } + }} + > setNewsletterEmail(e.target.value)} + placeholder="Email address" className="flex-1 px-5 py-3.5 bg-background border border-border rounded-xl text-foreground placeholder:text-foreground-subtle focus:outline-none focus:border-accent/50 transition-colors" /> - -
+ + {newsletterState === 'success' ? ( +

Subscribed. Briefings inbound.

+ ) : newsletterState === 'error' ? ( +

{newsletterError}

+ ) : null}
diff --git a/frontend/src/app/contact/page.tsx b/frontend/src/app/contact/page.tsx index cacad4c..5e1e5d4 100644 --- a/frontend/src/app/contact/page.tsx +++ b/frontend/src/app/contact/page.tsx @@ -11,22 +11,22 @@ const contactMethods = [ { icon: Mail, title: 'Secure Comms', - description: 'Encrypted channel for general inquiries.', + description: 'Support, partnerships, and product intel.', value: 'hello@pounce.ch', href: 'mailto:hello@pounce.ch', }, { icon: MessageSquare, - title: 'Live Support', - description: 'Mon-Fri, 0900-1800 CET', - value: 'Open Channel', - href: '#', + title: 'Support Desk', + description: 'Email support. Mon–Fri, 0900–1800 CET.', + value: 'Send ticket', + href: 'mailto:hello@pounce.ch?subject=Support', }, { icon: Clock, title: 'Response Time', - description: 'Average ticket resolution.', - value: '< 4 Hours', + description: 'Typical response window.', + value: '< 24 Hours', href: null, }, ] @@ -110,7 +110,7 @@ export default function ContactPage() { Establish Contact.

- Question? Idea? Glitch in the matrix? We're listening. + Support request, bug report, or partnership intel. We'll respond fast.

diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index ce4ff5f..4dc4082 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -6,6 +6,7 @@ import Script from 'next/script' const inter = Inter({ subsets: ['latin'] }) const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pounce.ch' +const googleSiteVerification = process.env.NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION export const viewport: Viewport = { width: 'device-width', @@ -19,7 +20,7 @@ export const metadata: Metadata = { default: 'Pounce - Domain Intelligence Platform | Find, Track & Trade Domains', template: '%s | Pounce', }, - description: 'The #1 domain intelligence platform. Real-time auction aggregation from GoDaddy, Sedo, DropCatch & more. TLD price tracking, spam-free market feed, and portfolio management. Find undervalued domains before anyone else.', + description: 'Domain intelligence for serious investors. Scan live auctions, track drops, compare TLD pricing, and manage portfolios with a clean market feed and verified listings.', keywords: [ 'domain intelligence', 'domain auctions', @@ -65,7 +66,7 @@ export const metadata: Metadata = { url: siteUrl, siteName: 'Pounce', title: 'Pounce - Domain Intelligence Platform | Find, Track & Trade Domains', - description: 'The #1 domain intelligence platform. Real-time auction aggregation, TLD price tracking, spam-free market feed. Find undervalued domains before anyone else.', + description: 'Domain intelligence for serious investors. Live auctions, TLD pricing intel, a clean market feed, and verified listings.', images: [ { url: `${siteUrl}/og-image.png`, @@ -78,14 +79,12 @@ export const metadata: Metadata = { twitter: { card: 'summary_large_image', title: 'Pounce - Domain Intelligence Platform', - description: 'The #1 domain intelligence platform. Real-time auctions, TLD pricing, spam-free feed. Find undervalued domains.', + description: 'Domain intelligence for serious investors. Live auctions, TLD pricing intel, a clean market feed, and verified listings.', creator: '@pouncedomains', site: '@pouncedomains', images: [`${siteUrl}/og-image.png`], }, - verification: { - google: 'YOUR_GOOGLE_VERIFICATION_CODE', // Add your Google Search Console verification - }, + verification: googleSiteVerification ? { google: googleSiteVerification } : undefined, robots: { index: true, follow: true, @@ -139,7 +138,7 @@ export default function RootLayout({ ], contactPoint: { '@type': 'ContactPoint', - email: 'hello@pounce.com', + email: 'hello@pounce.ch', contactType: 'Customer Service', }, }), diff --git a/frontend/src/app/metadata.ts b/frontend/src/app/metadata.ts index a63df61..deb9001 100644 --- a/frontend/src/app/metadata.ts +++ b/frontend/src/app/metadata.ts @@ -4,7 +4,7 @@ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pounce.com' export const homeMetadata: Metadata = { title: 'Pounce - Domain Intelligence for Investors | The Market Never Sleeps', - description: 'Domain intelligence platform for investors. Real-time drops, spam-filtered auctions, TLD price tracking, portfolio monitoring. Scout, track, and trade premium domains. 0% marketplace commission.', + description: 'Domain intelligence for investors. Live auctions, drops, TLD price tracking, and portfolio monitoring with a clean market feed and verified listings. 0% marketplace commission.', keywords: [ 'domain intelligence', 'domain marketplace', @@ -23,7 +23,7 @@ export const homeMetadata: Metadata = { ], openGraph: { title: 'Pounce - Domain Intelligence for Investors', - description: 'The market never sleeps. You should. Real-time domain intelligence, auctions, and market data.', + description: 'The market never sleeps. You should. Live domain intelligence, auctions, and market data.', url: siteUrl, type: 'website', images: [ @@ -38,7 +38,7 @@ export const homeMetadata: Metadata = { twitter: { card: 'summary_large_image', title: 'Pounce - Domain Intelligence for Investors', - description: 'The market never sleeps. You should. Real-time domain intelligence.', + description: 'The market never sleeps. You should. Live domain intelligence.', images: [`${siteUrl}/og-image.png`], }, alternates: { @@ -66,7 +66,7 @@ export function getHomeStructuredData() { ], contactPoint: { '@type': 'ContactPoint', - email: 'hello@pounce.com', + email: 'hello@pounce.ch', contactType: 'Customer Service', availableLanguage: ['en'], }, @@ -108,7 +108,7 @@ export function getHomeStructuredData() { worstRating: '1', }, featureList: [ - 'Real-time domain monitoring', + 'Live domain monitoring', 'Spam-filtered auction feed', 'TLD price intelligence', 'Portfolio management', diff --git a/frontend/src/app/pricing/metadata.ts b/frontend/src/app/pricing/metadata.ts index f024b24..71772e2 100644 --- a/frontend/src/app/pricing/metadata.ts +++ b/frontend/src/app/pricing/metadata.ts @@ -4,7 +4,7 @@ const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://pounce.com' export const pricingMetadata: Metadata = { title: 'Pricing Plans - Domain Intelligence & Market Access', - description: 'Choose your domain intelligence plan. Scout (Free), Trader ($9/mo), or Tycoon ($29/mo). Real-time market data, spam-filtered auctions, and portfolio monitoring. 0% commission on marketplace sales.', + description: 'Choose your domain intelligence plan. Scout (Free), Trader ($9/mo), or Tycoon ($29/mo). Live market data, spam-filtered auctions, and portfolio monitoring. 0% commission on Pounce Direct sales.', keywords: [ 'domain intelligence pricing', 'domain monitoring subscription', @@ -17,7 +17,7 @@ export const pricingMetadata: Metadata = { ], openGraph: { title: 'Pricing Plans - Pounce Domain Intelligence', - description: 'Scout (Free), Trader ($9/mo), Tycoon ($29/mo). Real-time market data, spam-filtered auctions, portfolio monitoring.', + description: 'Scout (Free), Trader ($9/mo), Tycoon ($29/mo). Live market data, spam-filtered auctions, and portfolio monitoring.', url: `${siteUrl}/pricing`, type: 'website', images: [ @@ -32,7 +32,7 @@ export const pricingMetadata: Metadata = { twitter: { card: 'summary_large_image', title: 'Pricing Plans - Pounce Domain Intelligence', - description: 'Scout (Free), Trader ($9/mo), Tycoon ($29/mo). Start hunting domains today.', + description: 'Scout (Free), Trader ($9/mo), Tycoon ($29/mo). Clean market feed, faster monitoring, and verified listings.', images: [`${siteUrl}/og-pricing.png`], }, alternates: { diff --git a/frontend/src/app/pricing/page.tsx b/frontend/src/app/pricing/page.tsx index 75edfe1..c128fd8 100644 --- a/frontend/src/app/pricing/page.tsx +++ b/frontend/src/app/pricing/page.tsx @@ -17,7 +17,7 @@ const tiers = [ icon: Zap, price: '0', period: '', - description: 'Test the waters. Zero risk.', + description: 'Recon access. No commitment.', features: [ { text: 'Market Feed', highlight: false, available: true, sublabel: 'Raw' }, { text: 'Alert Speed', highlight: false, available: true, sublabel: 'Daily' }, @@ -28,7 +28,7 @@ const tiers = [ { text: 'Marketplace', highlight: false, available: true, sublabel: 'Buy Only' }, { text: 'Yield (Intent Routing)', highlight: false, available: false }, ], - cta: 'Start Free', + cta: 'Enter Terminal', highlighted: false, badge: null, isPaid: false, @@ -39,7 +39,7 @@ const tiers = [ icon: TrendingUp, price: '9', period: '/mo', - description: 'The smart investor\'s choice.', + description: 'Cut noise. Move faster.', features: [ { text: 'Market Feed', highlight: true, available: true, sublabel: 'Curated' }, { text: 'Alert Speed', highlight: true, available: true, sublabel: 'Hourly' }, @@ -62,10 +62,10 @@ const tiers = [ icon: Crown, price: '29', period: '/mo', - description: 'For serious domain investors.', + description: 'Full firepower. Priority routes.', features: [ { text: 'Market Feed', highlight: true, available: true, sublabel: 'Priority' }, - { text: 'Alert Speed', highlight: true, available: true, sublabel: 'Real-Time' }, + { 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' }, @@ -83,7 +83,7 @@ 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: 'Real-Time (10 min)' }, + { name: 'Alert Speed', scout: 'Daily', trader: 'Hourly', tycoon: 'Every 10 minutes' }, { name: 'Watchlist', scout: '5 Domains', trader: '50 Domains', tycoon: '500 Domains' }, { name: 'Sniper Alerts', scout: '2', trader: '10', tycoon: '50' }, { name: 'TLD Intel', scout: 'Public Trends', trader: 'Renewal Prices', tycoon: 'Full History' }, @@ -211,7 +211,7 @@ export default function PricingPage() { Pick your weapon.

- Start free. Scale when you're ready. All plans include core features. + Enter Terminal. Upgrade when you need speed, filters, and deeper intel.

diff --git a/frontend/src/app/register/page.tsx b/frontend/src/app/register/page.tsx index 5944b27..fabf5de 100644 --- a/frontend/src/app/register/page.tsx +++ b/frontend/src/app/register/page.tsx @@ -255,7 +255,7 @@ function RegisterForm() { ) : ( <> - Start Hunting + Enter Terminal )} diff --git a/frontend/src/app/terminal/listing/page.tsx b/frontend/src/app/terminal/listing/page.tsx index ec4944c..cc4f22e 100755 --- a/frontend/src/app/terminal/listing/page.tsx +++ b/frontend/src/app/terminal/listing/page.tsx @@ -146,12 +146,12 @@ export default function MyListingsPage() {
-

Sell Your Domains

+

For Sale

- List your domains directly on Pounce Market. + List domains on Pounce Direct.

- 0% commission. Verified ownership. Instant visibility. + 0% commission. DNS-verified ownership. Direct buyer contact.

@@ -185,7 +185,7 @@ export default function MyListingsPage() {
- Upgrade Now + Upgrade @@ -239,7 +239,7 @@ export default function MyListingsPage() {
- πŸ’Ž Pounce Direct + Pounce Direct

For Sale @@ -569,10 +569,17 @@ function CreateListingWizard({ onClose, onSuccess, prefillDomain }: { }) setCreatedListing(listing) - // Start DNS verification - const verification = await api.startDnsVerification(listing.id) - setVerificationData(verification) - setStep(2) + // Check if domain was already verified in portfolio + if (listing.is_verified) { + // Skip verification step, go directly to publish + setVerified(true) + setStep(3) + } else { + // Start DNS verification + const verification = await api.startDnsVerification(listing.id) + setVerificationData(verification) + setStep(2) + } } catch (err: any) { setError(err.message || 'Failed to create listing') } finally { @@ -823,7 +830,7 @@ function CreateListingWizard({ onClose, onSuccess, prefillDomain }: {

- βœ… Your listing will appear in the Market Feed with the πŸ’Ž Pounce Direct badge. + Your listing will appear in the Market Feed with the Pounce Direct badge.
))} -
+ - {/* Add Domain Button - RIGHT */} + {/* Add Domain Button */} + {/* ═══════════════════════════════════════════════════════════════════════ */} {/* DOMAIN LIST */} + {/* ═══════════════════════════════════════════════════════════════════════ */}
{loading ? (
@@ -519,26 +358,26 @@ export default function PortfolioPage() {
) : ( -
- {/* Desktop Table Header - Matches Watchlist Style */} -
+
+ {/* Desktop Table Header */} +
- + -
Alert
+
Status
Actions
@@ -549,35 +388,27 @@ export default function PortfolioPage() { return (
- {/* Mobile Row - Extended with Health & Alerts */} + {/* Mobile Row */}
-
+
{domain.is_sold ? : }
{domain.domain}
-
- {domain.registrar || 'Unknown'} - {/* Health Badge - Mobile */} - {!domain.is_sold && domain.is_dns_verified && (() => { - const health = healthReports[domain.id] - if (!health) return null - const config = healthConfig[health.status] - return ( - - ) - })()} +
+ {domain.registrar && ( + {domain.registrar} + )} + {domain.is_dns_verified && ( + + Verified + + )}
@@ -590,237 +421,134 @@ export default function PortfolioPage() {
- {/* Info Row - Renewal & Alerts */} + {/* Mobile Actions */} {!domain.is_sold && ( -
-
- {/* Renewal Info */} - {daysUntilRenewal && ( -
- - - {isRenewingSoon ? `${daysUntilRenewal}d` : formatDate(domain.renewal_date)} - -
- )} -
- - {/* Alert Toggles - Mobile */} -
- + )} +
)} - - {/* Action Buttons */} -
- {!domain.is_sold && ( - domain.is_dns_verified ? ( - <> - {canListForSale && ( - - Sell - - )} - {canUseYield && ( - - )} - - ) : ( - - ) - )} - - -
- {/* Desktop Row - Matches Watchlist Style */} -
- {/* Domain Info */} -
+ {/* Desktop Row */} +
+ {/* Domain */} +
- {domain.is_sold ? : - domain.is_dns_verified ? : } + {domain.is_sold ? : }
-
{domain.domain}
-
- {domain.registrar || 'Unknown'} - {domain.is_sold && β€’ Sold} - {!domain.is_sold && domain.is_dns_verified && β€’ Verified} -
-
- - - -
- - {/* Health - Like Watchlist */} - - - {/* Value + ROI combined */} -
-
{formatCurrency(domain.estimated_value)}
-
- {roiPositive ? '+' : ''}{formatROI(domain.roi)} +
{domain.domain}
+
{domain.registrar || 'Unknown'}
- {/* Expiry - Like Watchlist */} -
- {domain.is_sold ? ( - β€” - ) : isRenewingSoon ? ( - {daysUntilRenewal}d - ) : daysUntilRenewal ? ( - {daysUntilRenewal}d - ) : ( - formatDate(domain.renewal_date) + {/* Value */} +
+
{formatCurrency(domain.estimated_value)}
+ {domain.purchase_price && ( +
Cost: {formatCurrency(domain.purchase_price)}
)}
- {/* Alert Toggle - Like Watchlist Bell */} - + {/* ROI */} +
+
+ {formatROI(domain.roi)} +
+
- {/* Actions - Like Watchlist */} -
- {/* Primary Action - Verify or Sell */} - {!domain.is_sold && ( - domain.is_dns_verified ? ( - canListForSale && ( - - Sell - - - ) - ) : ( - - ) + {/* Expires */} +
+ {daysUntilRenewal !== null ? ( + + {daysUntilRenewal}d + + ) : ( + β€” + )} +
+ + {/* Status */} +
+ {domain.is_sold ? ( + Sold + ) : domain.is_dns_verified ? ( + + Verified + + ) : ( + + )} +
+ + {/* Actions */} +
+ {!domain.is_sold && domain.is_dns_verified && ( + + Sell + )} -
@@ -831,192 +559,125 @@ export default function PortfolioPage() { )}
- {/* MOBILE BOTTOM NAV */} -