'use client' import { useEffect, useState, useCallback } from 'react' import { useSearchParams } from 'next/navigation' import { useStore } from '@/lib/store' import { api } from '@/lib/api' import { Sidebar } from '@/components/Sidebar' import { Plus, Shield, Eye, ExternalLink, Loader2, Trash2, CheckCircle, AlertCircle, Copy, X, Tag, Sparkles, TrendingUp, Gavel, Target, Menu, Settings, LogOut, Crown, Zap, Coins, ArrowRight, RefreshCw, Globe, Lock, Briefcase } from 'lucide-react' import Link from 'next/link' import Image from 'next/image' import clsx from 'clsx' // ============================================================================ // TYPES // ============================================================================ interface Listing { id: number domain: string slug: string title: string | null description: string | null asking_price: number | null min_offer: number | null currency: string price_type: string pounce_score: number | null verification_status: string is_verified: boolean status: string view_count: number inquiry_count: number public_url: string created_at: string } // ============================================================================ // MAIN PAGE // ============================================================================ export default function MyListingsPage() { const { subscription, user, logout, checkAuth } = useStore() const searchParams = useSearchParams() const prefillDomain = searchParams.get('domain') const [listings, setListings] = useState([]) const [loading, setLoading] = useState(true) const [showCreateWizard, setShowCreateWizard] = useState(false) const [selectedListing, setSelectedListing] = useState(null) const [deletingId, setDeletingId] = useState(null) 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 isTycoon = tier === 'tycoon' const activeListings = listings.filter(l => l.status === 'active').length const draftListings = listings.filter(l => l.status === 'draft').length const totalViews = listings.reduce((sum, l) => sum + l.view_count, 0) const totalInquiries = listings.reduce((sum, l) => sum + l.inquiry_count, 0) useEffect(() => { checkAuth() }, [checkAuth]) useEffect(() => { if (prefillDomain && !isScout) setShowCreateWizard(true) }, [prefillDomain, isScout]) 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]) const handleDelete = async (id: number, domain: string) => { if (!confirm(`Delete listing for ${domain}?`)) return setDeletingId(id) try { await api.deleteListing(id) await loadListings() } catch (err: any) { alert(err.message || 'Failed') } finally { setDeletingId(null) } } const handlePublish = async (listing: Listing) => { if (!listing.is_verified) { setSelectedListing(listing) return } try { await api.updateListing(listing.id, { status: 'active' }) await loadListings() } catch (err: any) { alert(err.message || 'Failed to publish') } } const mobileNavItems = [ { href: '/terminal/radar', label: 'Radar', icon: Target, active: false }, { href: '/terminal/market', label: 'Market', icon: Gavel, active: false }, { href: '/terminal/watchlist', label: 'Watch', icon: Eye, active: false }, { href: '/terminal/intel', label: 'Intel', icon: TrendingUp, active: false }, ] const tierName = subscription?.tier_name || subscription?.tier || 'Scout' const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap const drawerNavSections = [ { title: 'Discover', items: [ { href: '/terminal/radar', label: 'Radar', icon: Target }, { href: '/terminal/market', label: 'Market', icon: Gavel }, { href: '/terminal/intel', label: 'Intel', icon: TrendingUp }, ]}, { title: 'Manage', items: [ { href: '/terminal/watchlist', label: 'Watchlist', icon: Eye }, { href: '/terminal/portfolio', label: 'Portfolio', icon: Briefcase }, { href: '/terminal/sniper', label: 'Sniper', icon: Target }, ]}, { title: 'Monetize', items: [ { href: '/terminal/yield', label: 'Yield', icon: Coins }, { href: '/terminal/listing', label: 'For Sale', icon: Tag, active: true }, ]} ] // ============================================================================ // 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) // ============================================================================ return (
{/* MOBILE HEADER */}
Pounce Direct
{listings.length}/{maxListings}
{activeListings}
Live
{draftListings}
Draft
{totalViews}
Views
{totalInquiries}
Leads
{/* DESKTOP HEADER */}
Pounce Direct

For Sale {listings.length}/{maxListings}

List your domains for sale. 0% commission, verified ownership, direct buyer contact.

{activeListings}
Live
{draftListings}
Draft
{totalViews}
Views
{totalInquiries}
Leads
{/* ADD BUTTON MOBILE */}
{/* CONTENT */}
{loading ? (
) : listings.length === 0 ? (

No listings yet

Create your first listing to start selling

) : (
{/* Header */}
Domain
Price
Status
Views
Leads
Actions
{listings.map((listing) => ( handleDelete(listing.id, listing.domain)} onVerify={() => setSelectedListing(listing)} onPublish={() => handlePublish(listing)} isDeleting={deletingId === listing.id} /> ))}
)} {!canAddMore && listings.length >= maxListings && (

Listing Limit Reached

{tier === 'trader' ? 'Upgrade to Tycoon for 50 listings' : 'Contact us for enterprise plans'}

{tier === 'trader' && ( Upgrade to Tycoon )}
)}
{/* MOBILE BOTTOM NAV */} {/* DRAWER */} {menuOpen && ( setMenuOpen(false)} onLogout={() => { logout(); setMenuOpen(false) }} /> )}
{/* CREATE WIZARD (3-Step) */} {showCreateWizard && ( setShowCreateWizard(false)} onSuccess={() => { loadListings(); setShowCreateWizard(false) }} prefillDomain={prefillDomain || ''} /> )} {/* DNS VERIFICATION MODAL */} {selectedListing && ( setSelectedListing(null)} onVerified={() => { loadListings(); setSelectedListing(null) }} /> )}
) } // ============================================================================ // LISTING ROW COMPONENT // ============================================================================ function ListingRow({ listing, isTycoon, onDelete, onVerify, onPublish, isDeleting }: { listing: Listing isTycoon: boolean onDelete: () => void onVerify: () => void onPublish: () => void isDeleting: boolean }) { const isDraft = listing.status === 'draft' const isActive = listing.status === 'active' const needsVerification = !listing.is_verified return (
{/* Mobile */}
{listing.is_verified ? : }
{listing.domain} {isTycoon && Featured}
{listing.status}
${listing.asking_price?.toLocaleString() || 'Make Offer'} {listing.view_count} views ยท {listing.inquiry_count} leads
{isDraft && needsVerification && ( )} {isDraft && !needsVerification && ( )} {isActive && ( View )}
{/* Desktop */}
{listing.is_verified ? : }
{listing.domain} {isTycoon && Featured} {!listing.is_verified && Unverified}
${listing.asking_price?.toLocaleString() || 'โ€”'}
{listing.status}
{listing.view_count}
{listing.inquiry_count}
{isDraft && needsVerification && ( )} {isDraft && !needsVerification && ( )} {isActive && ( )}
) } // ============================================================================ // CREATE LISTING WIZARD (3-Step Process) // ============================================================================ function CreateListingWizard({ onClose, onSuccess, prefillDomain }: { onClose: () => void onSuccess: () => void prefillDomain: string }) { const [step, setStep] = useState(1) const [domain, setDomain] = useState(prefillDomain) const [price, setPrice] = useState('') const [priceType, setPriceType] = useState<'fixed' | 'negotiable'>('negotiable') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [createdListing, setCreatedListing] = useState(null) const [verificationData, setVerificationData] = useState(null) const [verifying, setVerifying] = useState(false) const [verified, setVerified] = useState(false) // Portfolio domains (for dropdown selection) const [portfolioDomains, setPortfolioDomains] = useState<{ id: number; domain: string }[]>([]) const [loadingDomains, setLoadingDomains] = useState(true) useEffect(() => { const fetchPortfolioDomains = async () => { setLoadingDomains(true) try { const domains = await api.getPortfolio() // Filter out sold domains setPortfolioDomains(domains.filter(d => !d.is_sold).map(d => ({ id: d.id, domain: d.domain }))) } catch (err) { console.error('Failed to load portfolio domains:', err) } finally { setLoadingDomains(false) } } fetchPortfolioDomains() }, []) // Step 1: Create listing const handleCreateListing = async () => { if (!domain.trim()) return setLoading(true) setError(null) try { const listing = await api.createListing({ domain: domain.trim(), asking_price: price ? parseFloat(price) : null, currency: 'USD', price_type: priceType }) setCreatedListing(listing) // 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 { setLoading(false) } } // Step 2: Check DNS verification const handleCheckVerification = async () => { if (!createdListing) return setVerifying(true) try { const result = await api.checkDnsVerification(createdListing.id) if (result.verified) { setVerified(true) setStep(3) } else { setError(result.message || 'DNS record not found yet. Please wait for propagation.') } } catch (err: any) { setError(err.message || 'Verification check failed') } finally { setVerifying(false) } } // Step 3: Publish const handlePublish = async () => { if (!createdListing) return setLoading(true) try { await api.updateListing(createdListing.id, { status: 'active' }) onSuccess() } catch (err: any) { setError(err.message || 'Failed to publish') } finally { setLoading(false) } } const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text) } return (
e.stopPropagation()}> {/* Header */}
New Listing
{/* Step Indicators */}
{[1, 2, 3].map((s) => (
s ? "bg-accent/20 text-accent border-accent/40" : "bg-white/5 text-white/30 border-white/10" )}> {step > s ? : s}
))}
{/* Step Content */}
{error && (
{error}
)} {/* STEP 1: Domain & Price */} {step === 1 && (

Enter Domain Details

Step 1 of 3: Set your domain and price

{loadingDomains ? (
) : portfolioDomains.length === 0 ? (

No domains in your portfolio

Add Domains to Portfolio
) : ( )}

Only domains from your portfolio can be listed. DNS verification required after listing.

{priceType === 'fixed' && (
setPrice(e.target.value)} min="0" className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white text-sm font-mono placeholder:text-white/20 outline-none focus:border-accent/50" placeholder="Enter price" />
)}
)} {/* STEP 2: DNS Verification */} {step === 2 && verificationData && (

Verify Ownership

Step 2 of 3: Add a DNS TXT record to prove you own this domain

Add this TXT record to your domain's DNS settings. This proves you control the domain.
TXT
{verificationData.dns_record_name}
{verificationData.verification_code}
๐Ÿ’ก DNS changes can take 1-5 minutes to propagate. If verification fails, wait a moment and try again.
)} {/* STEP 3: Publish */} {step === 3 && (

Ownership Verified!

Step 3 of 3: Publish your listing to the Pounce Market

{domain}
{price ? `$${parseFloat(price).toLocaleString()}` : 'Make Offer'}
Your listing will appear in the Market Feed with the Pounce Direct badge.
)}
) } // ============================================================================ // DNS VERIFICATION MODAL (For existing drafts) // ============================================================================ function DnsVerificationModal({ listing, onClose, onVerified }: { listing: Listing onClose: () => void onVerified: () => void }) { const [verificationData, setVerificationData] = useState(null) const [loading, setLoading] = useState(true) const [verifying, setVerifying] = useState(false) const [error, setError] = useState(null) useEffect(() => { loadVerificationData() }, [listing.id]) const loadVerificationData = async () => { try { const data = await api.startDnsVerification(listing.id) setVerificationData(data) } catch (err: any) { setError(err.message || 'Failed to load verification data') } finally { setLoading(false) } } const handleCheck = async () => { setVerifying(true) setError(null) try { const result = await api.checkDnsVerification(listing.id) if (result.verified) { onVerified() } else { setError(result.message || 'DNS record not found yet') } } catch (err: any) { setError(err.message || 'Check failed') } finally { setVerifying(false) } } const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text) } return (
e.stopPropagation()}>
DNS Verification
{loading ? (
) : verificationData ? (
{error && (
{error}
)}

{listing.domain}

Add this TXT record to verify ownership

{verificationData.dns_record_name}
{verificationData.verification_code}
) : (
Failed to load verification data
)}
) } // ============================================================================ // MOBILE DRAWER // ============================================================================ function MobileDrawer({ user, tierName, TierIcon, drawerNavSections, onClose, onLogout }: any) { return (
Pounce

POUNCE

Terminal v1.0

{drawerNavSections.map((section: any) => (
{section.title}
{section.items.map((item: any) => ( {item.label} ))}
))}
Settings {user?.is_admin && Admin}

{user?.name || user?.email?.split('@')[0] || 'User'}

{tierName}

{tierName === 'Scout' && Upgrade}
) }