Fix Intel page, Listing page redesign
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

This commit is contained in:
2025-12-12 23:12:30 +01:00
parent b7fa3632bf
commit 2d5a36ea98
2 changed files with 538 additions and 636 deletions

View File

@ -58,7 +58,10 @@ function getTierLevel(tier: UserTier): number {
} }
} }
const formatPrice = (p: number) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(p) const formatPrice = (p: number) => {
if (typeof p !== 'number' || isNaN(p)) return '$0'
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(p)
}
// ============================================================================ // ============================================================================
// MAIN PAGE // MAIN PAGE
@ -75,6 +78,7 @@ export default function IntelPage() {
const [tldData, setTldData] = useState<TLDData[]>([]) const [tldData, setTldData] = useState<TLDData[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [refreshing, setRefreshing] = useState(false) const [refreshing, setRefreshing] = useState(false)
const [total, setTotal] = useState(0) const [total, setTotal] = useState(0)
@ -86,27 +90,39 @@ export default function IntelPage() {
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
setLoading(true) setLoading(true)
setError(null)
try { try {
const response = await api.getTldOverview(500, 0, 'popularity') const response = await api.getTldOverview(500, 0, 'popularity')
console.log('TLD API Response:', response)
if (!response || !response.tlds) {
setError('No TLD data available')
setTldData([])
setTotal(0)
return
}
const mapped: TLDData[] = (response.tlds || []).map((tld: any) => ({ const mapped: TLDData[] = (response.tlds || []).map((tld: any) => ({
tld: tld.tld, tld: tld.tld || '',
min_price: tld.min_registration_price, min_price: tld.min_registration_price || 0,
avg_price: tld.avg_registration_price, avg_price: tld.avg_registration_price || 0,
max_price: tld.max_registration_price, max_price: tld.max_registration_price || 0,
min_renewal_price: tld.min_renewal_price, min_renewal_price: tld.min_renewal_price || 0,
avg_renewal_price: tld.avg_renewal_price, avg_renewal_price: tld.avg_renewal_price || 0,
price_change_7d: tld.price_change_7d, price_change_7d: tld.price_change_7d || 0,
price_change_1y: tld.price_change_1y, price_change_1y: tld.price_change_1y || 0,
price_change_3y: tld.price_change_3y, price_change_3y: tld.price_change_3y || 0,
risk_level: tld.risk_level, risk_level: tld.risk_level || 'low',
risk_reason: tld.risk_reason, risk_reason: tld.risk_reason || '',
popularity_rank: tld.popularity_rank, popularity_rank: tld.popularity_rank,
type: tld.type, type: tld.type,
})) }))
setTldData(mapped) setTldData(mapped)
setTotal(response.total || 0) setTotal(response.total || mapped.length)
} catch (error) { } catch (err: any) {
console.error('Failed to load TLD data:', error) console.error('Failed to load TLD data:', err)
setError(err.message || 'Failed to load TLD data')
setTldData([])
} finally { } finally {
setLoading(false) setLoading(false)
} }
@ -132,7 +148,7 @@ export default function IntelPage() {
}, [sortField, canSeeRenewal, canSee3yTrend]) }, [sortField, canSeeRenewal, canSee3yTrend])
const filteredData = useMemo(() => { const filteredData = useMemo(() => {
let data = tldData let data = [...tldData]
const techTlds = ['ai', 'io', 'app', 'dev', 'tech', 'cloud', 'digital', 'software', 'code', 'systems', 'network', 'data', 'cyber', 'online', 'web', 'api', 'hosting'] const techTlds = ['ai', 'io', 'app', 'dev', 'tech', 'cloud', 'digital', 'software', 'code', 'systems', 'network', 'data', 'cyber', 'online', 'web', 'api', 'hosting']
if (filterType === 'tech') data = data.filter(t => techTlds.includes(t.tld) || t.tld === 'io' || t.tld === 'ai') if (filterType === 'tech') data = data.filter(t => techTlds.includes(t.tld) || t.tld === 'io' || t.tld === 'ai')
@ -147,13 +163,13 @@ export default function IntelPage() {
data.sort((a, b) => { data.sort((a, b) => {
switch (sortField) { switch (sortField) {
case 'tld': return mult * a.tld.localeCompare(b.tld) case 'tld': return mult * a.tld.localeCompare(b.tld)
case 'price': return mult * (a.min_price - b.min_price) case 'price': return mult * ((a.min_price || 0) - (b.min_price || 0))
case 'renewal': return mult * (a.min_renewal_price - b.min_renewal_price) case 'renewal': return mult * ((a.min_renewal_price || 0) - (b.min_renewal_price || 0))
case 'change': return mult * ((a.price_change_1y || 0) - (b.price_change_1y || 0)) case 'change': return mult * ((a.price_change_1y || 0) - (b.price_change_1y || 0))
case 'change3y': return mult * ((a.price_change_3y || 0) - (b.price_change_3y || 0)) case 'change3y': return mult * ((a.price_change_3y || 0) - (b.price_change_3y || 0))
case 'risk': case 'risk':
const riskMap = { low: 1, medium: 2, high: 3 } const riskMap: Record<string, number> = { low: 1, medium: 2, high: 3 }
return mult * (riskMap[a.risk_level] - riskMap[b.risk_level]) return mult * ((riskMap[a.risk_level] || 1) - (riskMap[b.risk_level] || 1))
case 'popularity': return mult * ((a.popularity_rank || 999) - (b.popularity_rank || 999)) case 'popularity': return mult * ((a.popularity_rank || 999) - (b.popularity_rank || 999))
default: return 0 default: return 0
} }
@ -163,11 +179,12 @@ export default function IntelPage() {
}, [tldData, filterType, searchQuery, sortField, sortDirection]) }, [tldData, filterType, searchQuery, sortField, sortDirection])
const stats = useMemo(() => { const stats = useMemo(() => {
const lowest = tldData.length > 0 ? Math.min(...tldData.map(t => t.min_price)) : 0 if (!tldData.length) return { lowest: 0, traps: 0, avgRenewal: 0 }
const hottest = tldData.reduce((prev, current) => (prev.price_change_1y > current.price_change_1y) ? prev : current, tldData[0] || {}) const prices = tldData.map(t => t.min_price).filter(p => p > 0)
const lowest = prices.length > 0 ? Math.min(...prices) : 0
const traps = tldData.filter(t => t.risk_level === 'high').length const traps = tldData.filter(t => t.risk_level === 'high').length
const avgRenewal = tldData.length > 0 ? tldData.reduce((sum, t) => sum + t.min_renewal_price, 0) / tldData.length : 0 const avgRenewal = tldData.length > 0 ? tldData.reduce((sum, t) => sum + (t.min_renewal_price || 0), 0) / tldData.length : 0
return { lowest, hottest, traps, avgRenewal } return { lowest, traps, avgRenewal }
}, [tldData]) }, [tldData])
return ( return (
@ -284,6 +301,14 @@ export default function IntelPage() {
<div className="flex items-center justify-center py-20"> <div className="flex items-center justify-center py-20">
<Loader2 className="w-6 h-6 text-accent animate-spin" /> <Loader2 className="w-6 h-6 text-accent animate-spin" />
</div> </div>
) : error ? (
<div className="text-center py-16">
<div className="w-14 h-14 mx-auto bg-red-500/10 border border-red-500/20 flex items-center justify-center mb-4">
<AlertTriangle className="w-6 h-6 text-red-400" />
</div>
<p className="text-red-400 text-sm mb-2">{error}</p>
<button onClick={handleRefresh} className="text-white/40 text-xs hover:text-white">Try again</button>
</div>
) : filteredData.length === 0 ? ( ) : filteredData.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-16">
<div className="w-14 h-14 mx-auto bg-white/[0.02] border border-white/10 flex items-center justify-center mb-4"> <div className="w-14 h-14 mx-auto bg-white/[0.02] border border-white/10 flex items-center justify-center mb-4">
@ -339,7 +364,7 @@ export default function IntelPage() {
</thead> </thead>
<tbody> <tbody>
{filteredData.map((tld) => { {filteredData.map((tld) => {
const isTrap = tld.min_renewal_price > tld.min_price * 1.5 const isTrap = (tld.min_renewal_price || 0) > (tld.min_price || 1) * 1.5
const trend = tld.price_change_1y || 0 const trend = tld.price_change_1y || 0
const trend3y = tld.price_change_3y || 0 const trend3y = tld.price_change_3y || 0

View File

@ -1,10 +1,10 @@
'use client' 'use client'
import { useEffect, useState, useMemo, useCallback } from 'react' import { useEffect, useState, useCallback } from 'react'
import { useSearchParams } from 'next/navigation' import { useSearchParams } from 'next/navigation'
import { useStore } from '@/lib/store' import { useStore } from '@/lib/store'
import { api } from '@/lib/api' import { api } from '@/lib/api'
import { TerminalLayout } from '@/components/TerminalLayout' import { CommandCenterLayout } from '@/components/CommandCenterLayout'
import { import {
Plus, Plus,
Shield, Shield,
@ -16,7 +16,6 @@ import {
CheckCircle, CheckCircle,
AlertCircle, AlertCircle,
Copy, Copy,
RefreshCw,
DollarSign, DollarSign,
X, X,
Tag, Tag,
@ -24,71 +23,12 @@ import {
ArrowRight, ArrowRight,
TrendingUp, TrendingUp,
Globe, Globe,
MoreHorizontal, Crown,
Crown Check
} from 'lucide-react' } from 'lucide-react'
import Link from 'next/link' import Link from 'next/link'
import clsx from 'clsx' import clsx from 'clsx'
// ============================================================================
// SHARED COMPONENTS
// ============================================================================
function Tooltip({ children, content }: { children: React.ReactNode; content: string }) {
return (
<div className="relative flex items-center group/tooltip w-fit">
{children}
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-zinc-900 border border-zinc-800 rounded text-[10px] text-zinc-300 whitespace-nowrap opacity-0 group-hover/tooltip:opacity-100 transition-opacity pointer-events-none z-50 shadow-xl">
{content}
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px border-4 border-transparent border-t-zinc-800" />
</div>
</div>
)
}
function StatCard({
label,
value,
subValue,
icon: Icon,
trend
}: {
label: string
value: string | number
subValue?: string
icon: any
trend?: 'up' | 'down' | 'neutral' | 'active'
}) {
return (
<div className="bg-zinc-900/40 border border-white/5 p-4 relative overflow-hidden group hover:border-white/10 transition-colors">
<div className="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity">
<Icon className="w-16 h-16" />
</div>
<div className="relative z-10">
<div className="flex items-center gap-2 text-zinc-400 mb-1">
<Icon className="w-4 h-4" />
<span className="text-xs font-medium uppercase tracking-wider">{label}</span>
</div>
<div className="flex items-baseline gap-2">
<span className="text-2xl font-bold text-white tracking-tight">{value}</span>
{subValue && <span className="text-xs text-zinc-500 font-medium">{subValue}</span>}
</div>
{trend && (
<div className={clsx(
"mt-2 text-[10px] font-medium px-1.5 py-0.5 w-fit rounded border",
trend === 'up' && "text-emerald-400 border-emerald-400/20 bg-emerald-400/5",
trend === 'down' && "text-rose-400 border-rose-400/20 bg-rose-400/5",
trend === 'active' && "text-blue-400 border-blue-400/20 bg-blue-400/5 animate-pulse",
trend === 'neutral' && "text-zinc-400 border-zinc-400/20 bg-zinc-400/5",
)}>
{trend === 'active' ? '● LIVE MONITORING' : trend === 'up' ? '▲ POSITIVE' : '▼ NEGATIVE'}
</div>
)}
</div>
</div>
)
}
// ============================================================================ // ============================================================================
// TYPES // TYPES
// ============================================================================ // ============================================================================
@ -151,7 +91,6 @@ export default function MyListingsPage() {
const [listings, setListings] = useState<Listing[]>([]) const [listings, setListings] = useState<Listing[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
// Modals
const [showCreateModal, setShowCreateModal] = useState(false) const [showCreateModal, setShowCreateModal] = useState(false)
const [showVerifyModal, setShowVerifyModal] = useState(false) const [showVerifyModal, setShowVerifyModal] = useState(false)
const [showInquiriesModal, setShowInquiriesModal] = useState(false) const [showInquiriesModal, setShowInquiriesModal] = useState(false)
@ -163,8 +102,8 @@ export default function MyListingsPage() {
const [creating, setCreating] = useState(false) const [creating, setCreating] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState<string | null>(null) const [success, setSuccess] = useState<string | null>(null)
const [copied, setCopied] = useState(false)
// Create form state
const [newListing, setNewListing] = useState({ const [newListing, setNewListing] = useState({
domain: '', domain: '',
title: '', title: '',
@ -174,6 +113,16 @@ export default function MyListingsPage() {
allow_offers: true, allow_offers: true,
}) })
const tier = subscription?.tier || 'scout'
const limits: Record<string, number> = { scout: 0, trader: 5, tycoon: 50 }
const maxListings = limits[tier] || 0
const canList = tier !== 'scout'
const isTycoon = tier === 'tycoon'
const activeCount = listings.filter(l => l.status === 'active').length
const totalViews = listings.reduce((sum, l) => sum + l.view_count, 0)
const totalInquiries = listings.reduce((sum, l) => sum + l.inquiry_count, 0)
const loadListings = useCallback(async () => { const loadListings = useCallback(async () => {
setLoading(true) setLoading(true)
try { try {
@ -214,7 +163,7 @@ export default function MyListingsPage() {
allow_offers: newListing.allow_offers, allow_offers: newListing.allow_offers,
}), }),
}) })
setSuccess('Listing created! Now verify ownership to publish.') setSuccess('Listing created! Verify ownership to publish.')
setShowCreateModal(false) setShowCreateModal(false)
setNewListing({ domain: '', title: '', description: '', asking_price: '', price_type: 'negotiable', allow_offers: true }) setNewListing({ domain: '', title: '', description: '', asking_price: '', price_type: 'negotiable', allow_offers: true })
loadListings() loadListings()
@ -268,7 +217,7 @@ export default function MyListingsPage() {
) )
if (result.verified) { if (result.verified) {
setSuccess('Domain verified! You can now publish your listing.') setSuccess('Domain verified! You can now publish.')
setShowVerifyModal(false) setShowVerifyModal(false)
loadListings() loadListings()
} else { } else {
@ -308,8 +257,8 @@ export default function MyListingsPage() {
const copyToClipboard = (text: string) => { const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text) navigator.clipboard.writeText(text)
setSuccess('Copied to clipboard!') setCopied(true)
setTimeout(() => setSuccess(null), 2000) setTimeout(() => setCopied(false), 2000)
} }
const formatPrice = (price: number | null, currency: string) => { const formatPrice = (price: number | null, currency: string) => {
@ -321,53 +270,67 @@ export default function MyListingsPage() {
}).format(price) }).format(price)
} }
// Tier limits (from pounce_pricing.md: Trader=5, Tycoon=50, Scout=0)
const tier = subscription?.tier || 'scout'
const limits = { scout: 0, trader: 5, tycoon: 50 }
const maxListings = limits[tier as keyof typeof limits] || 0
const canList = tier !== 'scout'
const isTycoon = tier === 'tycoon'
const activeCount = listings.filter(l => l.status === 'active').length
const totalViews = listings.reduce((sum, l) => sum + l.view_count, 0)
const totalInquiries = listings.reduce((sum, l) => sum + l.inquiry_count, 0)
return ( return (
<TerminalLayout hideHeaderSearch={true}> <CommandCenterLayout minimal>
<div className="relative font-sans text-zinc-100 selection:bg-emerald-500/30 pb-20"> {/* ═══════════════════════════════════════════════════════════════════════ */}
{/* HEADER */}
{/* Ambient Background Glow */} {/* ═══════════════════════════════════════════════════════════════════════ */}
<div className="fixed inset-0 pointer-events-none overflow-hidden"> <section className="pt-6 lg:pt-8 pb-6">
<div className="absolute top-0 right-1/4 w-[800px] h-[600px] bg-emerald-500/5 rounded-full blur-[120px] mix-blend-screen" /> <div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-6">
<div className="absolute bottom-0 left-1/4 w-[600px] h-[500px] bg-blue-500/5 rounded-full blur-[100px] mix-blend-screen" /> <div className="space-y-3">
<div className="inline-flex items-center gap-2">
<Tag className="w-4 h-4 text-accent" />
<span className="text-[10px] font-mono tracking-wide text-accent">Marketplace</span>
</div> </div>
<div className="relative z-10 max-w-[1600px] mx-auto p-4 md:p-8 space-y-8"> <h1 className="font-display text-[2rem] lg:text-[2.5rem] leading-[1] tracking-[-0.02em]">
<span className="text-white">For Sale</span>
<span className="text-white/30 ml-3">{listings.length}/{maxListings}</span>
</h1>
{/* Header Section */} <p className="text-white/40 text-sm max-w-md">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-6 border-b border-white/5 pb-8"> List domains on Pounce Marketplace. 0% commission.
<div className="space-y-2">
<div className="flex items-center gap-3">
<div className="h-8 w-1 bg-emerald-500 rounded-full shadow-[0_0_10px_rgba(16,185,129,0.5)]" />
<h1 className="text-3xl font-bold tracking-tight text-white">For Sale</h1>
</div>
<p className="text-zinc-400 max-w-lg">
List your domains on the Pounce Marketplace. 0% commission, instant visibility.
</p> </p>
</div> </div>
<div className="flex items-center gap-4">
{canList && (
<div className="flex gap-6">
<div className="text-right">
<div className="text-xl font-display text-accent">{activeCount}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">Active</div>
</div>
<div className="text-right">
<div className="text-xl font-display text-white">{totalViews}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">Views</div>
</div>
<div className="text-right">
<div className="text-xl font-display text-amber-400">{totalInquiries}</div>
<div className="text-[9px] tracking-wide text-white/30 font-mono">Inquiries</div>
</div>
</div>
)}
<button <button
onClick={() => setShowCreateModal(true)} onClick={() => setShowCreateModal(true)}
disabled={listings.length >= maxListings} disabled={!canList || listings.length >= maxListings}
className="px-4 py-2 bg-emerald-500 text-white font-medium rounded-lg hover:bg-emerald-400 transition-all shadow-lg shadow-emerald-500/20 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2" className={clsx(
"flex items-center gap-2 px-4 py-2 text-sm font-semibold transition-colors",
canList && listings.length < maxListings
? "bg-accent text-black hover:bg-white"
: "bg-white/10 text-white/40 cursor-not-allowed"
)}
> >
<Plus className="w-4 h-4" /> New Listing <Plus className="w-4 h-4" />
New Listing
</button> </button>
</div> </div>
</div>
</section>
{/* Messages */} {/* Messages */}
{error && ( {error && (
<div className="p-4 bg-rose-500/10 border border-rose-500/20 rounded-xl flex items-center gap-3 text-rose-400 animate-in fade-in slide-in-from-top-2"> <div className="mb-6 p-4 bg-red-500/10 border border-red-500/20 flex items-center gap-3 text-red-400">
<AlertCircle className="w-5 h-5" /> <AlertCircle className="w-5 h-5" />
<p className="text-sm flex-1">{error}</p> <p className="text-sm flex-1">{error}</p>
<button onClick={() => setError(null)}><X className="w-4 h-4" /></button> <button onClick={() => setError(null)}><X className="w-4 h-4" /></button>
@ -375,356 +338,277 @@ export default function MyListingsPage() {
)} )}
{success && ( {success && (
<div className="p-4 bg-emerald-500/10 border border-emerald-500/20 rounded-xl flex items-center gap-3 text-emerald-400 animate-in fade-in slide-in-from-top-2"> <div className="mb-6 p-4 bg-accent/10 border border-accent/20 flex items-center gap-3 text-accent">
<CheckCircle className="w-5 h-5" /> <CheckCircle className="w-5 h-5" />
<p className="text-sm flex-1">{success}</p> <p className="text-sm flex-1">{success}</p>
<button onClick={() => setSuccess(null)}><X className="w-4 h-4" /></button> <button onClick={() => setSuccess(null)}><X className="w-4 h-4" /></button>
</div> </div>
)} )}
{/* Paywall */} {/* ═══════════════════════════════════════════════════════════════════════ */}
{/* PAYWALL */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{!canList && ( {!canList && (
<div className="p-8 bg-gradient-to-br from-emerald-900/20 to-black border border-emerald-500/20 rounded-2xl text-center relative overflow-hidden"> <section className="py-12">
<div className="absolute inset-0 bg-grid-white/[0.02] bg-[length:20px_20px]" /> <div className="text-center max-w-md mx-auto">
<div className="relative z-10"> <Shield className="w-12 h-12 text-accent mx-auto mb-4" />
<Shield className="w-12 h-12 text-emerald-400 mx-auto mb-4" /> <h2 className="font-display text-2xl text-white mb-2">Unlock Domain Selling</h2>
<h2 className="text-2xl font-bold text-white mb-2">Unlock Domain Selling</h2> <p className="text-sm text-white/40 mb-6">
<p className="text-zinc-400 mb-6 max-w-md mx-auto"> List your domains with 0% commission on Pounce Marketplace.
List your domains with 0% commission on the Pounce Marketplace.
</p> </p>
{/* Plan comparison */} <div className="grid grid-cols-2 gap-4 mb-6">
<div className="grid grid-cols-2 gap-4 max-w-sm mx-auto mb-6"> <div className="p-4 bg-white/[0.02] border border-white/[0.08]">
<div className="p-4 bg-white/5 border border-white/10 rounded-xl text-left"> <TrendingUp className="w-5 h-5 text-accent mb-2" />
<div className="flex items-center gap-2 mb-2"> <div className="text-sm font-medium text-white">Trader</div>
<TrendingUp className="w-4 h-4 text-emerald-400" /> <div className="text-xs text-white/40">$9/mo</div>
<span className="text-sm font-bold text-emerald-400">Trader</span> <div className="text-lg font-display text-white mt-2">5 Listings</div>
</div> </div>
<p className="text-xs text-zinc-400 mb-1">$9/month</p> <div className="p-4 bg-amber-400/5 border border-amber-400/20">
<p className="text-white font-bold">5 Listings</p> <Crown className="w-5 h-5 text-amber-400 mb-2" />
</div> <div className="text-sm font-medium text-white">Tycoon</div>
<div className="p-4 bg-amber-500/10 border border-amber-500/20 rounded-xl text-left"> <div className="text-xs text-white/40">$29/mo</div>
<div className="flex items-center gap-2 mb-2"> <div className="text-lg font-display text-white mt-2">50 Listings</div>
<Crown className="w-4 h-4 text-amber-400" /> <div className="text-[10px] text-amber-400 mt-1">+ Featured Badge</div>
<span className="text-sm font-bold text-amber-400">Tycoon</span>
</div>
<p className="text-xs text-zinc-400 mb-1">$29/month</p>
<p className="text-white font-bold">50 Listings</p>
<p className="text-[10px] text-amber-400 mt-1">+ Featured Badge</p>
</div> </div>
</div> </div>
<Link <Link
href="/pricing" href="/pricing"
className="inline-flex items-center gap-2 px-6 py-3 bg-emerald-500 text-white font-bold rounded-xl hover:bg-emerald-400 transition-all shadow-lg shadow-emerald-500/20" className="inline-flex items-center gap-2 px-6 py-3 bg-accent text-black font-semibold hover:bg-white transition-colors"
> >
Upgrade Now <ArrowRight className="w-4 h-4" /> Upgrade Now <ArrowRight className="w-4 h-4" />
</Link> </Link>
</div> </div>
</div> </section>
)} )}
{/* Stats Grid */} {/* ═══════════════════════════════════════════════════════════════════════ */}
{/* LISTINGS TABLE */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{canList && ( {canList && (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4"> <section className="py-6 border-t border-white/[0.08]">
<StatCard
label="Inventory"
value={listings.length}
subValue={`/ ${maxListings} slots`}
icon={Tag}
trend="neutral"
/>
<StatCard
label="Active Listings"
value={activeCount}
subValue="Live on market"
icon={Globe}
trend="active"
/>
<StatCard
label="Total Views"
value={totalViews}
subValue="All time"
icon={Eye}
trend={totalViews > 0 ? 'up' : 'neutral'}
/>
<StatCard
label="Inquiries"
value={totalInquiries}
subValue="Pending"
icon={MessageSquare}
trend={totalInquiries > 0 ? 'up' : 'neutral'}
/>
</div>
)}
{/* Listings Table */}
{canList && (
<div className="bg-zinc-900/40 border border-white/5 rounded-xl overflow-hidden backdrop-blur-sm">
{/* Table Header */}
<div className="grid grid-cols-12 gap-4 px-6 py-3 bg-white/[0.02] border-b border-white/5 text-[11px] font-semibold text-zinc-500 uppercase tracking-wider">
<div className="col-span-12 md:col-span-4">Domain</div>
<div className="hidden md:block md:col-span-2 text-center">Status</div>
<div className="hidden md:block md:col-span-2 text-right">Price</div>
<div className="hidden md:block md:col-span-1 text-center">Views</div>
<div className="hidden md:block md:col-span-1 text-center">Inquiries</div>
<div className="hidden md:block md:col-span-2 text-right">Actions</div>
</div>
{loading ? ( {loading ? (
<div className="flex items-center justify-center py-20"> <div className="flex items-center justify-center py-20">
<Loader2 className="w-8 h-8 text-emerald-500 animate-spin" /> <Loader2 className="w-6 h-6 text-accent animate-spin" />
</div> </div>
) : listings.length === 0 ? ( ) : listings.length === 0 ? (
<div className="flex flex-col items-center justify-center py-20 text-center"> <div className="text-center py-16">
<div className="w-16 h-16 rounded-full bg-white/5 flex items-center justify-center mb-4"> <div className="w-14 h-14 mx-auto bg-white/[0.02] border border-white/10 flex items-center justify-center mb-4">
<Sparkles className="w-8 h-8 text-zinc-600" /> <Sparkles className="w-6 h-6 text-white/20" />
</div> </div>
<h3 className="text-lg font-medium text-white mb-1">No listings yet</h3> <p className="text-white/40 text-sm mb-2">No listings yet</p>
<p className="text-zinc-500 text-sm max-w-xs mx-auto mb-6"> <p className="text-white/25 text-xs mb-6">Create your first listing to start selling</p>
Create your first listing to start selling.
</p>
<button <button
onClick={() => setShowCreateModal(true)} onClick={() => setShowCreateModal(true)}
className="text-emerald-400 text-sm hover:text-emerald-300 transition-colors flex items-center gap-2 font-medium" className="inline-flex items-center gap-2 px-4 py-2 bg-accent text-black text-sm font-semibold hover:bg-white transition-colors"
> >
Create Listing <ArrowRight className="w-4 h-4" /> <Plus className="w-4 h-4" />
Create Listing
</button> </button>
</div> </div>
) : ( ) : (
<div className="divide-y divide-white/5"> <div className="space-y-px">
{listings.map((listing) => ( {listings.map((listing) => (
<div key={listing.id} className="grid grid-cols-12 gap-4 px-6 py-4 items-center hover:bg-white/[0.04] transition-all group relative"> <div
key={listing.id}
{/* Mobile View */} className={clsx(
<div className="md:hidden col-span-12"> "group border transition-all p-5",
<div className="flex justify-between items-start mb-2"> listing.status === 'active'
<div> ? "bg-white/[0.02] border-white/[0.08] hover:border-accent/30"
<div className="font-mono font-bold text-white text-lg">{listing.domain}</div> : "bg-white/[0.01] border-white/[0.04]"
<div className="text-xs text-zinc-500 mt-0.5">{listing.title || 'No headline'}</div> )}
</div> >
<div className="text-right"> <div className="flex items-start justify-between gap-4">
<div className="font-mono text-emerald-400 font-bold">{formatPrice(listing.asking_price, listing.currency)}</div> <div className="flex items-center gap-4">
</div>
</div>
<div className="flex justify-between items-center mt-3 pt-3 border-t border-white/5">
<span className={clsx(
"text-[10px] font-bold uppercase px-2 py-0.5 rounded border",
listing.status === 'active' ? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20" :
listing.status === 'draft' ? "bg-zinc-800 text-zinc-400 border-zinc-700" :
"bg-blue-500/10 text-blue-400 border-blue-500/20"
)}>
{listing.status}
</span>
<div className="flex gap-2">
<button onClick={() => handleDelete(listing)} className="p-2 text-zinc-500 hover:text-rose-400"><Trash2 className="w-4 h-4" /></button>
{!listing.is_verified && <button onClick={() => handleStartVerification(listing)} className="p-2 text-amber-400 hover:bg-amber-500/10 rounded"><Shield className="w-4 h-4" /></button>}
</div>
</div>
</div>
{/* Desktop View */}
<div className="hidden md:block col-span-4">
<div className="flex items-center gap-3">
<div className={clsx( <div className={clsx(
"w-10 h-10 rounded-lg flex items-center justify-center text-lg font-bold relative", "w-12 h-12 flex items-center justify-center text-lg font-bold relative",
listing.status === 'active' ? "bg-emerald-500/10 text-emerald-400" : "bg-zinc-800 text-zinc-500" listing.status === 'active' ? "bg-accent/10 text-accent" : "bg-white/5 text-white/40"
)}> )}>
{listing.domain.charAt(0).toUpperCase()} {listing.domain.charAt(0).toUpperCase()}
{/* Featured Badge for Tycoon */}
{isTycoon && listing.status === 'active' && ( {isTycoon && listing.status === 'active' && (
<div className="absolute -top-1 -right-1 w-4 h-4 bg-amber-500 rounded-full flex items-center justify-center"> <div className="absolute -top-1 -right-1 w-4 h-4 bg-amber-400 flex items-center justify-center">
<Sparkles className="w-2.5 h-2.5 text-black" /> <Sparkles className="w-2.5 h-2.5 text-black" />
</div> </div>
)} )}
</div> </div>
<div>
<div className="flex items-center gap-2">
<span className="font-mono font-bold text-white tracking-tight">{listing.domain}</span>
{listing.is_verified && (
<CheckCircle className="w-3.5 h-3.5 text-emerald-400" />
)}
</div>
<div className="text-xs text-zinc-500">{listing.title || 'No headline'}</div>
</div>
</div>
</div>
<div className="hidden md:flex col-span-2 justify-center"> <div>
<div className="flex items-center gap-2 mb-1">
<span className="font-mono font-medium text-white">{listing.domain}</span>
{listing.is_verified && <CheckCircle className="w-3.5 h-3.5 text-accent" />}
<span className={clsx( <span className={clsx(
"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider border", "px-2 py-0.5 text-[9px] font-mono border",
listing.status === 'active' ? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20" : listing.status === 'active' ? "bg-accent/10 text-accent border-accent/20" :
listing.status === 'draft' ? "bg-zinc-800/50 text-zinc-400 border-zinc-700" : listing.status === 'sold' ? "bg-blue-400/10 text-blue-400 border-blue-400/20" :
listing.status === 'sold' ? "bg-blue-500/10 text-blue-400 border-blue-500/20" : "bg-white/5 text-white/40 border-white/10"
"bg-zinc-800/50 text-zinc-400 border-zinc-700"
)}> )}>
<span className={clsx("w-1.5 h-1.5 rounded-full", listing.status === 'active' ? "bg-emerald-400" : listing.status === 'sold' ? "bg-blue-400" : "bg-zinc-500")} />
{listing.status} {listing.status}
</span> </span>
</div> </div>
<div className="text-xs text-white/40">{listing.title || 'No headline'}</div>
<div className="hidden md:block col-span-2 text-right"> </div>
<div className="font-mono font-medium text-white">{formatPrice(listing.asking_price, listing.currency)}</div>
{listing.pounce_score && <div className="text-[10px] text-zinc-500 mt-0.5">Score: {listing.pounce_score}</div>}
</div> </div>
<div className="hidden md:block col-span-1 text-center"> <div className="flex items-center gap-6">
<div className="text-sm text-zinc-400">{listing.view_count}</div> <div className="text-right hidden lg:block">
<div className="font-mono text-white">{formatPrice(listing.asking_price, listing.currency)}</div>
{listing.pounce_score && <div className="text-[10px] text-white/30">Score: {listing.pounce_score}</div>}
</div> </div>
<div className="hidden md:block col-span-1 text-center"> <div className="flex items-center gap-4 text-xs text-white/40 hidden lg:flex">
<span className="flex items-center gap-1">
<Eye className="w-3.5 h-3.5" />
{listing.view_count}
</span>
<button <button
onClick={() => handleViewInquiries(listing)} onClick={() => handleViewInquiries(listing)}
disabled={listing.inquiry_count === 0} disabled={listing.inquiry_count === 0}
className={clsx( className={clsx(
"text-sm font-medium transition-colors", "flex items-center gap-1",
listing.inquiry_count > 0 listing.inquiry_count > 0 ? "text-amber-400 hover:text-amber-300" : "text-white/30"
? "text-amber-400 hover:text-amber-300 cursor-pointer"
: "text-zinc-600 cursor-default"
)} )}
> >
<MessageSquare className="w-3.5 h-3.5" />
{listing.inquiry_count} {listing.inquiry_count}
{listing.inquiry_count > 0 && <span className="ml-1">📩</span>}
</button> </button>
</div> </div>
<div className="hidden md:flex col-span-2 justify-end gap-2"> <div className="flex items-center gap-2">
{!listing.is_verified ? ( {!listing.is_verified ? (
<Tooltip content="Verify ownership to publish">
<button <button
onClick={() => handleStartVerification(listing)} onClick={() => handleStartVerification(listing)}
className="p-2 rounded-lg bg-amber-500/10 text-amber-400 border border-amber-500/20 hover:bg-amber-500/20 transition-all" disabled={verifying}
className="w-8 h-8 flex items-center justify-center bg-amber-400/10 border border-amber-400/20 text-amber-400 hover:bg-amber-400/20 transition-colors"
title="Verify ownership"
> >
<Shield className="w-4 h-4" /> <Shield className="w-4 h-4" />
</button> </button>
</Tooltip>
) : listing.status === 'draft' ? ( ) : listing.status === 'draft' ? (
<Tooltip content="Publish to Marketplace">
<button <button
onClick={() => handlePublish(listing)} onClick={() => handlePublish(listing)}
className="p-2 rounded-lg bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 hover:bg-emerald-500/20 transition-all" className="w-8 h-8 flex items-center justify-center bg-accent/10 border border-accent/20 text-accent hover:bg-accent/20 transition-colors"
title="Publish"
> >
<CheckCircle className="w-4 h-4" /> <CheckCircle className="w-4 h-4" />
</button> </button>
</Tooltip>
) : ( ) : (
<Tooltip content="View public listing">
<Link <Link
href={`/buy/${listing.slug}`} href={`/buy/${listing.slug}`}
target="_blank" target="_blank"
className="p-2 rounded-lg bg-white/5 text-zinc-400 hover:text-white hover:bg-white/10 transition-all" className="w-8 h-8 flex items-center justify-center bg-white/5 border border-white/10 text-white/40 hover:text-white hover:bg-white/10 transition-colors"
title="View listing"
> >
<ExternalLink className="w-4 h-4" /> <ExternalLink className="w-4 h-4" />
</Link> </Link>
</Tooltip>
)} )}
<Tooltip content="Delete listing">
<button <button
onClick={() => handleDelete(listing)} onClick={() => handleDelete(listing)}
className="p-2 rounded-lg text-zinc-600 hover:text-rose-400 hover:bg-rose-500/10 transition-all" className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/30 hover:text-red-400 hover:border-red-400/30 transition-colors"
title="Delete"
> >
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />
</button> </button>
</Tooltip>
</div> </div>
</div>
</div>
</div> </div>
))} ))}
</div> </div>
)} )}
</div> </section>
)} )}
</div> {/* ═══════════════════════════════════════════════════════════════════════ */}
{/* CREATE MODAL */}
{/* Create Modal */} {/* ═══════════════════════════════════════════════════════════════════════ */}
{showCreateModal && ( {showCreateModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-md"> <div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm" onClick={() => setShowCreateModal(false)}>
<div className="w-full max-w-lg bg-[#0A0A0A] border border-white/10 rounded-2xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200"> <div className="w-full max-w-lg bg-[#050505] border border-white/10" onClick={(e) => e.stopPropagation()}>
<div className="p-6 border-b border-white/5"> <div className="p-6 border-b border-white/[0.08]">
<h2 className="text-xl font-bold text-white">Create Listing</h2> <h2 className="font-medium text-white">Create Listing</h2>
<p className="text-sm text-zinc-500">List your domain for sale on the marketplace</p> <p className="text-xs text-white/40">List your domain for sale</p>
</div> </div>
<form onSubmit={handleCreate} className="p-6 space-y-5"> <form onSubmit={handleCreate} className="p-6 space-y-5">
<div> <div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Domain Name</label> <label className="block text-xs text-white/50 mb-2">Domain Name</label>
<input <input
type="text" type="text"
required required
value={newListing.domain} value={newListing.domain}
onChange={(e) => setNewListing({ ...newListing, domain: e.target.value })} onChange={(e) => setNewListing({ ...newListing, domain: e.target.value })}
placeholder="example.com" placeholder="example.com"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 focus:bg-white/10 transition-all font-mono" className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50 font-mono"
/> />
</div> </div>
<div> <div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Headline</label> <label className="block text-xs text-white/50 mb-2">Headline</label>
<input <input
type="text" type="text"
value={newListing.title} value={newListing.title}
onChange={(e) => setNewListing({ ...newListing, title: e.target.value })} onChange={(e) => setNewListing({ ...newListing, title: e.target.value })}
placeholder="Short, catchy title (e.g. Perfect for AI Startups)" placeholder="Short, catchy title"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all" className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50"
/> />
</div> </div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Price (USD)</label> <label className="block text-xs text-white/50 mb-2">Price (USD)</label>
<div className="relative"> <div className="relative">
<DollarSign className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500" /> <DollarSign className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-white/30" />
<input <input
type="number" type="number"
value={newListing.asking_price} value={newListing.asking_price}
onChange={(e) => setNewListing({ ...newListing, asking_price: e.target.value })} onChange={(e) => setNewListing({ ...newListing, asking_price: e.target.value })}
placeholder="Make Offer" placeholder="Make Offer"
className="w-full pl-9 pr-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 transition-all font-mono" className="w-full pl-9 pr-4 py-3 bg-white/5 border border-white/10 text-white placeholder:text-white/25 outline-none focus:border-accent/50 font-mono"
/> />
</div> </div>
</div> </div>
<div> <div>
<label className="block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2">Type</label> <label className="block text-xs text-white/50 mb-2">Type</label>
<select <select
value={newListing.price_type} value={newListing.price_type}
onChange={(e) => setNewListing({ ...newListing, price_type: e.target.value })} onChange={(e) => setNewListing({ ...newListing, price_type: e.target.value })}
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white focus:outline-none focus:border-emerald-500/50 transition-all appearance-none" className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white outline-none focus:border-accent/50 appearance-none"
> >
<option value="negotiable">Negotiable</option> <option value="negotiable" className="bg-black">Negotiable</option>
<option value="fixed">Fixed Price</option> <option value="fixed" className="bg-black">Fixed Price</option>
<option value="make_offer">Make Offer</option> <option value="make_offer" className="bg-black">Make Offer</option>
</select> </select>
</div> </div>
</div> </div>
<label className="flex items-center gap-3 cursor-pointer p-3 rounded-lg border border-white/5 hover:bg-white/5 transition-colors"> <label className="flex items-center gap-3 cursor-pointer p-3 border border-white/[0.06] hover:bg-white/[0.02] transition-colors">
<input <input
type="checkbox" type="checkbox"
checked={newListing.allow_offers} checked={newListing.allow_offers}
onChange={(e) => setNewListing({ ...newListing, allow_offers: e.target.checked })} onChange={(e) => setNewListing({ ...newListing, allow_offers: e.target.checked })}
className="w-5 h-5 rounded border-white/20 bg-black text-emerald-500 focus:ring-emerald-500 focus:ring-offset-0" className="w-4 h-4 border-white/20 bg-black text-accent focus:ring-accent focus:ring-offset-0"
/> />
<span className="text-sm text-zinc-300">Allow buyers to submit offers</span> <span className="text-sm text-white/60">Allow buyers to submit offers</span>
</label> </label>
<div className="flex gap-3 pt-4"> <div className="flex gap-3 pt-4">
<button <button
type="button" type="button"
onClick={() => setShowCreateModal(false)} onClick={() => setShowCreateModal(false)}
className="flex-1 px-4 py-3 border border-white/10 text-zinc-400 rounded-xl hover:bg-white/5 hover:text-white transition-all font-medium" className="flex-1 px-4 py-3 border border-white/10 text-white/60 hover:bg-white/5 hover:text-white transition-all font-medium"
> >
Cancel Cancel
</button> </button>
<button <button
type="submit" type="submit"
disabled={creating} disabled={creating}
className="flex-1 flex items-center justify-center gap-2 px-4 py-3 bg-emerald-500 text-white font-bold rounded-xl hover:bg-emerald-400 transition-all disabled:opacity-50 shadow-lg shadow-emerald-500/20" className="flex-1 flex items-center justify-center gap-2 px-4 py-3 bg-accent text-black font-semibold hover:bg-white transition-all disabled:opacity-50"
> >
{creating ? <Loader2 className="w-5 h-5 animate-spin" /> : <Plus className="w-5 h-5" />} {creating ? <Loader2 className="w-5 h-5 animate-spin" /> : <Plus className="w-5 h-5" />}
{creating ? 'Creating...' : 'Create Listing'} {creating ? 'Creating...' : 'Create'}
</button> </button>
</div> </div>
</form> </form>
@ -732,46 +616,53 @@ export default function MyListingsPage() {
</div> </div>
)} )}
{/* Verify Modal */} {/* ═══════════════════════════════════════════════════════════════════════ */}
{/* VERIFY MODAL */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{showVerifyModal && verificationInfo && selectedListing && ( {showVerifyModal && verificationInfo && selectedListing && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-md"> <div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm" onClick={() => setShowVerifyModal(false)}>
<div className="w-full max-w-xl bg-[#0A0A0A] border border-white/10 rounded-2xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200"> <div className="w-full max-w-xl bg-[#050505] border border-white/10" onClick={(e) => e.stopPropagation()}>
<div className="p-6 border-b border-white/5 bg-white/[0.02]"> <div className="p-6 border-b border-white/[0.08]">
<h2 className="text-xl font-bold text-white mb-2">Verify Ownership</h2> <h2 className="font-medium text-white mb-1">Verify Ownership</h2>
<p className="text-sm text-zinc-400"> <p className="text-xs text-white/40">
Add this DNS TXT record to <strong>{selectedListing.domain}</strong> to prove you own it. Add this DNS TXT record to <strong className="text-white">{selectedListing.domain}</strong>
</p> </p>
</div> </div>
<div className="p-6 space-y-6"> <div className="p-6 space-y-4">
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-4">
<div className="col-span-1"> <div>
<div className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider mb-2">Type</div> <div className="text-[10px] text-white/40 mb-2">Type</div>
<div className="p-3 bg-white/5 border border-white/10 rounded-lg font-mono text-white text-center"> <div className="p-3 bg-white/5 border border-white/10 font-mono text-white text-center">
{verificationInfo.dns_record_type} {verificationInfo.dns_record_type}
</div> </div>
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<div className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider mb-2">Name / Host</div> <div className="text-[10px] text-white/40 mb-2">Name / Host</div>
<div className="p-3 bg-white/5 border border-white/10 rounded-lg font-mono text-white flex justify-between items-center group cursor-pointer" onClick={() => copyToClipboard(verificationInfo.dns_record_name)}> <div
className="p-3 bg-white/5 border border-white/10 font-mono text-white flex justify-between items-center cursor-pointer hover:bg-white/[0.08] transition-colors"
onClick={() => copyToClipboard(verificationInfo.dns_record_name)}
>
<span className="truncate">{verificationInfo.dns_record_name}</span> <span className="truncate">{verificationInfo.dns_record_name}</span>
<Copy className="w-4 h-4 text-zinc-500 group-hover:text-emerald-400 transition-colors" /> {copied ? <Check className="w-4 h-4 text-accent" /> : <Copy className="w-4 h-4 text-white/30" />}
</div> </div>
</div> </div>
</div> </div>
<div> <div>
<div className="text-[10px] font-bold text-zinc-500 uppercase tracking-wider mb-2">Value</div> <div className="text-[10px] text-white/40 mb-2">Value</div>
<div className="p-3 bg-white/5 border border-white/10 rounded-lg font-mono text-sm text-zinc-300 break-all flex justify-between items-start gap-4 group cursor-pointer" onClick={() => copyToClipboard(verificationInfo.dns_record_value)}> <div
className="p-3 bg-white/5 border border-white/10 font-mono text-sm text-white/80 break-all flex justify-between items-start gap-4 cursor-pointer hover:bg-white/[0.08] transition-colors"
onClick={() => copyToClipboard(verificationInfo.dns_record_value)}
>
{verificationInfo.dns_record_value} {verificationInfo.dns_record_value}
<Copy className="w-4 h-4 text-zinc-500 group-hover:text-emerald-400 transition-colors shrink-0 mt-0.5" /> {copied ? <Check className="w-4 h-4 text-accent shrink-0" /> : <Copy className="w-4 h-4 text-white/30 shrink-0" />}
</div> </div>
</div> </div>
<div className="p-4 bg-emerald-500/5 border border-emerald-500/10 rounded-xl"> <div className="p-4 bg-accent/5 border border-accent/10">
<p className="text-xs text-emerald-400/80 leading-relaxed"> <p className="text-xs text-accent/80 leading-relaxed">
<InfoIcon className="w-4 h-4 inline mr-1.5 -mt-0.5" /> After adding the record, it may take up to 24 hours to propagate. Click verify to check.
After adding the record, it may take up to 24 hours to propagate, though typically it's instant. Click verify below to check.
</p> </p>
</div> </div>
</div> </div>
@ -779,14 +670,14 @@ export default function MyListingsPage() {
<div className="flex gap-3 p-6 pt-0"> <div className="flex gap-3 p-6 pt-0">
<button <button
onClick={() => setShowVerifyModal(false)} onClick={() => setShowVerifyModal(false)}
className="flex-1 px-4 py-3 border border-white/10 text-zinc-400 rounded-xl hover:bg-white/5 hover:text-white transition-all font-medium" className="flex-1 px-4 py-3 border border-white/10 text-white/60 hover:bg-white/5 hover:text-white transition-all font-medium"
> >
Close Close
</button> </button>
<button <button
onClick={handleCheckVerification} onClick={handleCheckVerification}
disabled={verifying} disabled={verifying}
className="flex-1 flex items-center justify-center gap-2 px-4 py-3 bg-emerald-500 text-white font-bold rounded-xl hover:bg-emerald-400 transition-all disabled:opacity-50 shadow-lg shadow-emerald-500/20" className="flex-1 flex items-center justify-center gap-2 px-4 py-3 bg-accent text-black font-semibold hover:bg-white transition-all disabled:opacity-50"
> >
{verifying ? <Loader2 className="w-5 h-5 animate-spin" /> : <Shield className="w-5 h-5" />} {verifying ? <Loader2 className="w-5 h-5 animate-spin" /> : <Shield className="w-5 h-5" />}
{verifying ? 'Verifying...' : 'Verify Now'} {verifying ? 'Verifying...' : 'Verify Now'}
@ -796,94 +687,80 @@ export default function MyListingsPage() {
</div> </div>
)} )}
{/* Inquiries Modal */} {/* ═══════════════════════════════════════════════════════════════════════ */}
{/* INQUIRIES MODAL */}
{/* ═══════════════════════════════════════════════════════════════════════ */}
{showInquiriesModal && selectedListing && ( {showInquiriesModal && selectedListing && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-md"> <div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm" onClick={() => setShowInquiriesModal(false)}>
<div className="w-full max-w-2xl bg-[#0A0A0A] border border-white/10 rounded-2xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200 max-h-[85vh] flex flex-col"> <div className="w-full max-w-2xl bg-[#050505] border border-white/10 max-h-[85vh] flex flex-col" onClick={(e) => e.stopPropagation()}>
<div className="p-6 border-b border-white/5 bg-white/[0.02] flex justify-between items-center shrink-0"> <div className="p-6 border-b border-white/[0.08] flex justify-between items-center shrink-0">
<div> <div>
<h2 className="text-xl font-bold text-white">Inquiries</h2> <h2 className="font-medium text-white">Inquiries</h2>
<p className="text-sm text-zinc-400"> <p className="text-xs text-white/40">
{inquiries.length} inquiry{inquiries.length !== 1 ? 'ies' : ''} for <strong className="text-white">{selectedListing.domain}</strong> {inquiries.length} for <strong className="text-white">{selectedListing.domain}</strong>
</p> </p>
</div> </div>
<button onClick={() => setShowInquiriesModal(false)} className="p-2 text-zinc-400 hover:text-white transition-colors"> <button onClick={() => setShowInquiriesModal(false)} className="p-2 text-white/40 hover:text-white transition-colors">
<X className="w-5 h-5" /> <X className="w-5 h-5" />
</button> </button>
</div> </div>
<div className="flex-1 overflow-y-auto p-6 space-y-4"> <div className="flex-1 overflow-y-auto p-6 space-y-3">
{loadingInquiries ? ( {loadingInquiries ? (
<div className="flex items-center justify-center py-12"> <div className="flex items-center justify-center py-12">
<Loader2 className="w-8 h-8 text-emerald-500 animate-spin" /> <Loader2 className="w-6 h-6 text-accent animate-spin" />
</div> </div>
) : inquiries.length === 0 ? ( ) : inquiries.length === 0 ? (
<div className="text-center py-12"> <div className="text-center py-12">
<MessageSquare className="w-12 h-12 text-zinc-700 mx-auto mb-3" /> <MessageSquare className="w-12 h-12 text-white/10 mx-auto mb-3" />
<p className="text-zinc-500">No inquiries yet</p> <p className="text-white/40">No inquiries yet</p>
</div> </div>
) : ( ) : (
inquiries.map((inquiry) => ( inquiries.map((inquiry) => (
<div key={inquiry.id} className="p-4 bg-white/[0.02] border border-white/5 rounded-xl hover:border-white/10 transition-colors"> <div key={inquiry.id} className="p-4 bg-white/[0.02] border border-white/[0.06] hover:border-white/[0.1] transition-colors">
<div className="flex justify-between items-start mb-3"> <div className="flex justify-between items-start mb-3">
<div> <div>
<div className="font-medium text-white">{inquiry.name}</div> <div className="font-medium text-white">{inquiry.name}</div>
<div className="text-sm text-zinc-500">{inquiry.email}</div> <div className="text-xs text-white/40">{inquiry.email}</div>
{inquiry.company && <div className="text-xs text-zinc-600">{inquiry.company}</div>} {inquiry.company && <div className="text-[10px] text-white/30">{inquiry.company}</div>}
</div> </div>
<div className="text-right"> <div className="text-right">
{inquiry.offer_amount && ( {inquiry.offer_amount && (
<div className="text-emerald-400 font-mono font-bold"> <div className="text-accent font-mono font-medium">
${inquiry.offer_amount.toLocaleString()} ${inquiry.offer_amount.toLocaleString()}
</div> </div>
)} )}
<div className="text-[10px] text-zinc-600"> <div className="text-[10px] text-white/30">
{new Date(inquiry.created_at).toLocaleDateString()} {new Date(inquiry.created_at).toLocaleDateString()}
</div> </div>
</div> </div>
</div> </div>
<p className="text-sm text-zinc-300 leading-relaxed mb-3 whitespace-pre-wrap"> <p className="text-sm text-white/60 leading-relaxed mb-3 whitespace-pre-wrap">
{inquiry.message} {inquiry.message}
</p> </p>
<div className="flex items-center justify-between pt-3 border-t border-white/5"> <div className="flex items-center justify-between pt-3 border-t border-white/[0.06]">
<span className={clsx( <span className={clsx(
"text-[10px] font-bold uppercase px-2 py-0.5 rounded border", "text-[9px] font-mono px-2 py-0.5 border",
inquiry.status === 'new' ? "text-amber-400 border-amber-400/20 bg-amber-400/5" : inquiry.status === 'new' ? "text-amber-400 border-amber-400/20 bg-amber-400/5" :
inquiry.status === 'replied' ? "text-emerald-400 border-emerald-400/20 bg-emerald-400/5" : inquiry.status === 'replied' ? "text-accent border-accent/20 bg-accent/5" :
"text-zinc-400 border-zinc-400/20 bg-zinc-400/5" "text-white/40 border-white/10 bg-white/5"
)}> )}>
{inquiry.status} {inquiry.status}
</span> </span>
<a <a
href={`mailto:${inquiry.email}?subject=Re: ${selectedListing.domain}&body=Hi ${inquiry.name},%0A%0AThank you for your interest in ${selectedListing.domain}.%0A%0A`} href={`mailto:${inquiry.email}?subject=Re: ${selectedListing.domain}`}
className="text-xs text-emerald-400 hover:text-emerald-300 flex items-center gap-1 transition-colors" className="text-xs text-accent hover:text-white flex items-center gap-1 transition-colors"
> >
Reply via Email <ArrowRight className="w-3 h-3" /> Reply <ArrowRight className="w-3 h-3" />
</a> </a>
</div> </div>
</div> </div>
)) ))
)} )}
</div> </div>
<div className="p-4 border-t border-white/5 bg-white/[0.02] shrink-0">
<button
onClick={() => setShowInquiriesModal(false)}
className="w-full px-4 py-3 border border-white/10 text-zinc-400 rounded-xl hover:bg-white/5 hover:text-white transition-all font-medium"
>
Close
</button>
</div>
</div> </div>
</div> </div>
)} )}
</div> </CommandCenterLayout>
</TerminalLayout>
)
}
function InfoIcon(props: any) {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
) )
} }