feat: Pounce listings in acquire table, yield remove button, portfolio shows yield status
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
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:
@ -96,7 +96,7 @@ export default function AboutPage() {
|
|||||||
</li>
|
</li>
|
||||||
<li className="flex justify-between border-b border-white/5 pb-2">
|
<li className="flex justify-between border-b border-white/5 pb-2">
|
||||||
<span>Refresh Rate</span>
|
<span>Refresh Rate</span>
|
||||||
<span className="text-white">Real-time</span>
|
<span className="text-white">Live</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex justify-between">
|
<li className="flex justify-between">
|
||||||
<span>Data Source</span>
|
<span>Data Source</span>
|
||||||
@ -114,7 +114,7 @@ export default function AboutPage() {
|
|||||||
{[
|
{[
|
||||||
{ icon: Target, title: 'Precision', desc: 'Accurate data. No guesswork. Every check counts.' },
|
{ icon: Target, title: 'Precision', desc: 'Accurate data. No guesswork. Every check counts.' },
|
||||||
{ icon: Shield, title: 'Privacy', desc: 'Your strategy stays yours. We never share or sell data.' },
|
{ icon: Shield, title: 'Privacy', desc: 'Your strategy stays yours. We never share or sell data.' },
|
||||||
{ icon: Zap, title: 'Speed', desc: 'Real-time intel. You see it first.' },
|
{ icon: Zap, title: 'Speed', desc: 'Fast intel. You move first.' },
|
||||||
{ icon: Users, title: 'Transparency', desc: 'Clear pricing. No surprises. Ever.' },
|
{ icon: Users, title: 'Transparency', desc: 'Clear pricing. No surprises. Ever.' },
|
||||||
].map((value, i) => (
|
].map((value, i) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -63,6 +63,7 @@ interface Auction {
|
|||||||
age_years: number | null
|
age_years: number | null
|
||||||
tld: string
|
tld: string
|
||||||
affiliate_url: string
|
affiliate_url: string
|
||||||
|
is_pounce?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type TabType = 'all' | 'ending' | 'hot'
|
type TabType = 'all' | 'ending' | 'hot'
|
||||||
@ -206,10 +207,32 @@ export default function AcquirePage() {
|
|||||||
return endMs > (nowMs - 2000) // 2s grace
|
return endMs > (nowMs - 2000) // 2s grace
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isAuthenticated) return activeAuctions
|
// Convert Pounce Direct items to Auction format and mark them
|
||||||
return activeAuctions.filter(isVanityDomain)
|
const pounceAuctions: Auction[] = pounceItems.map(item => ({
|
||||||
|
domain: item.domain,
|
||||||
|
platform: 'Pounce',
|
||||||
|
platform_url: item.url,
|
||||||
|
current_bid: item.price,
|
||||||
|
currency: item.currency,
|
||||||
|
num_bids: 0,
|
||||||
|
end_time: '',
|
||||||
|
time_remaining: '',
|
||||||
|
buy_now_price: item.price,
|
||||||
|
reserve_met: null,
|
||||||
|
traffic: null,
|
||||||
|
age_years: null,
|
||||||
|
tld: item.tld,
|
||||||
|
affiliate_url: item.url,
|
||||||
|
is_pounce: true, // Special flag
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Apply auth filter
|
||||||
|
const filteredAuctions = isAuthenticated ? activeAuctions : activeAuctions.filter(isVanityDomain)
|
||||||
|
|
||||||
|
// Pounce Direct always on top
|
||||||
|
return [...pounceAuctions, ...filteredAuctions]
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [activeTab, allAuctions, endingSoon, hotAuctions, isAuthenticated, tick])
|
}, [activeTab, allAuctions, endingSoon, hotAuctions, pounceItems, isAuthenticated, tick])
|
||||||
|
|
||||||
const filteredAuctions = displayAuctions.filter(auction => {
|
const filteredAuctions = displayAuctions.filter(auction => {
|
||||||
if (searchQuery && !auction.domain.toLowerCase().includes(searchQuery.toLowerCase())) return false
|
if (searchQuery && !auction.domain.toLowerCase().includes(searchQuery.toLowerCase())) return false
|
||||||
@ -407,27 +430,49 @@ export default function AcquirePage() {
|
|||||||
href={auction.affiliate_url}
|
href={auction.affiliate_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="block p-3 bg-[#0A0A0A] border border-white/[0.08] active:bg-white/[0.03] transition-all"
|
className={clsx(
|
||||||
|
"block p-3 border active:bg-white/[0.03] transition-all",
|
||||||
|
auction.is_pounce
|
||||||
|
? "bg-accent/[0.03] border-accent/20"
|
||||||
|
: "bg-[#0A0A0A] border-white/[0.08]"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-sm font-bold text-white font-mono truncate">
|
<div className="flex items-center gap-2">
|
||||||
|
{auction.is_pounce && (
|
||||||
|
<span className="flex items-center gap-0.5 px-1 py-0.5 bg-accent/10 border border-accent/20 text-[8px] font-bold text-accent uppercase shrink-0">
|
||||||
|
<Diamond className="w-2.5 h-2.5" /> Direct
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="text-sm font-bold text-white font-mono truncate">
|
||||||
{auction.domain}
|
{auction.domain}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 mt-1 text-[10px] font-mono text-white/30">
|
<div className="flex items-center gap-2 mt-1 text-[10px] font-mono text-white/30">
|
||||||
|
{auction.is_pounce ? (
|
||||||
|
<span className="flex items-center gap-1 text-accent">
|
||||||
|
<ShieldCheck className="w-3 h-3" /> Verified • Instant
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<span className="uppercase">{auction.platform}</span>
|
<span className="uppercase">{auction.platform}</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
<span className={getTimeColor(auction.end_time)}>
|
<span className={getTimeColor(auction.end_time)}>
|
||||||
<Clock className="w-3 h-3 inline mr-1" />
|
<Clock className="w-3 h-3 inline mr-1" />
|
||||||
{calcTimeRemaining(auction.end_time)}
|
{calcTimeRemaining(auction.end_time)}
|
||||||
</span>
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right shrink-0">
|
<div className="text-right shrink-0">
|
||||||
<div className="text-sm font-bold text-accent font-mono">
|
<div className="text-sm font-bold text-accent font-mono">
|
||||||
{formatCurrency(auction.current_bid)}
|
{formatCurrency(auction.current_bid)}
|
||||||
</div>
|
</div>
|
||||||
{auction.num_bids > 0 && (
|
{auction.is_pounce ? (
|
||||||
|
<div className="text-[10px] text-accent/60 font-mono">Buy Now</div>
|
||||||
|
) : auction.num_bids > 0 && (
|
||||||
<div className="text-[10px] text-white/30 font-mono">
|
<div className="text-[10px] text-white/30 font-mono">
|
||||||
{auction.num_bids} bids
|
{auction.num_bids} bids
|
||||||
</div>
|
</div>
|
||||||
@ -501,47 +546,6 @@ export default function AcquirePage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Featured Direct Listings */}
|
|
||||||
{pounceItems.length > 0 && (
|
|
||||||
<div className="mb-16">
|
|
||||||
<div className="flex items-center justify-between gap-4 mb-6">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="flex items-center gap-2 px-3 py-1 bg-accent/10 border border-accent/20">
|
|
||||||
<Diamond className="w-4 h-4 text-accent" />
|
|
||||||
<span className="text-xs font-bold uppercase tracking-widest text-accent">Direct Listings</span>
|
|
||||||
</div>
|
|
||||||
<span className="text-[10px] font-mono text-white/30">// 0% COMMISSION</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-4">
|
|
||||||
{pounceItems.slice(0, 3).map((item) => (
|
|
||||||
<Link
|
|
||||||
key={item.id}
|
|
||||||
href={item.url}
|
|
||||||
className="group border border-white/10 bg-[#050505] hover:border-accent/50 transition-all p-6"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2 mb-4">
|
|
||||||
<span className="w-1.5 h-1.5 bg-accent animate-pulse" />
|
|
||||||
<span className="text-[10px] font-bold uppercase tracking-widest text-white/40">Available</span>
|
|
||||||
</div>
|
|
||||||
<h3 className="font-mono text-xl text-white font-medium mb-4 truncate group-hover:text-accent transition-colors">
|
|
||||||
{item.domain}
|
|
||||||
</h3>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="font-mono text-lg text-accent">{formatCurrency(item.price)}</span>
|
|
||||||
{item.verified && (
|
|
||||||
<span className="flex items-center gap-1 text-[10px] text-accent">
|
|
||||||
<ShieldCheck className="w-3 h-3" /> Verified
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Search & Filters Bar */}
|
{/* Search & Filters Bar */}
|
||||||
<div className="mb-6 sticky top-20 z-30 backdrop-blur-xl bg-[#020202]/90 border-y border-white/[0.08] py-4 -mx-6 px-6">
|
<div className="mb-6 sticky top-20 z-30 backdrop-blur-xl bg-[#020202]/90 border-y border-white/[0.08] py-4 -mx-6 px-6">
|
||||||
<div className="flex gap-4 justify-between items-center">
|
<div className="flex gap-4 justify-between items-center">
|
||||||
@ -618,22 +622,47 @@ export default function AcquirePage() {
|
|||||||
href={auction.affiliate_url}
|
href={auction.affiliate_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="grid grid-cols-[1fr_100px_120px_100px_80px] gap-4 items-center px-6 py-4 border-b border-white/[0.03] hover:bg-white/[0.02] transition-all group"
|
className={clsx(
|
||||||
|
"grid grid-cols-[1fr_100px_120px_100px_80px] gap-4 items-center px-6 py-4 border-b transition-all group",
|
||||||
|
auction.is_pounce
|
||||||
|
? "border-accent/20 bg-accent/[0.03] hover:bg-accent/[0.06]"
|
||||||
|
: "border-white/[0.03] hover:bg-white/[0.02]"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="font-mono text-base text-white group-hover:text-accent transition-colors truncate">
|
<div className="flex items-center gap-3">
|
||||||
|
{auction.is_pounce && (
|
||||||
|
<div className="flex items-center gap-1 px-1.5 py-0.5 bg-accent/10 border border-accent/20 shrink-0">
|
||||||
|
<Diamond className="w-3 h-3 text-accent" />
|
||||||
|
<span className="text-[8px] font-bold text-accent uppercase">Direct</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<span className="font-mono text-base text-white group-hover:text-accent transition-colors truncate">
|
||||||
{auction.domain}
|
{auction.domain}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center text-xs font-mono text-white/40 uppercase">
|
<div className="text-center text-xs font-mono text-white/40 uppercase">
|
||||||
{auction.platform}
|
{auction.is_pounce ? (
|
||||||
|
<span className="flex items-center justify-center gap-1 text-accent">
|
||||||
|
<ShieldCheck className="w-3 h-3" /> Verified
|
||||||
|
</span>
|
||||||
|
) : auction.platform}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right font-mono text-base text-accent">
|
<div className="text-right font-mono text-base text-accent">
|
||||||
{formatCurrency(auction.current_bid)}
|
{formatCurrency(auction.current_bid)}
|
||||||
|
{auction.is_pounce && (
|
||||||
|
<div className="text-[9px] text-accent/60 font-mono">Buy Now</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx("text-center text-xs font-mono", getTimeColor(auction.end_time))}>
|
<div className={clsx("text-center text-xs font-mono", auction.is_pounce ? "text-accent" : getTimeColor(auction.end_time))}>
|
||||||
{calcTimeRemaining(auction.end_time)}
|
{auction.is_pounce ? 'Instant' : calcTimeRemaining(auction.end_time)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<div className="w-8 h-8 border border-white/10 flex items-center justify-center text-white/30 group-hover:bg-white group-hover:text-black transition-all">
|
<div className={clsx(
|
||||||
|
"w-8 h-8 border flex items-center justify-center transition-all",
|
||||||
|
auction.is_pounce
|
||||||
|
? "border-accent/30 text-accent group-hover:bg-accent group-hover:text-black"
|
||||||
|
: "border-white/10 text-white/30 group-hover:bg-white group-hover:text-black"
|
||||||
|
)}>
|
||||||
<ExternalLink className="w-4 h-4" />
|
<ExternalLink className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -149,7 +149,7 @@ export async function GET(request: NextRequest) {
|
|||||||
letterSpacing: '0.02em',
|
letterSpacing: '0.02em',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Domain Intelligence • Real-time Market Data
|
Domain Intelligence • Pricing Intel
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export async function generateTLDMetadata(tld: string, price?: number, trend?: n
|
|||||||
const trendText = trend ? (trend > 0 ? `+${trend.toFixed(1)}%` : `${trend.toFixed(1)}%`) : ''
|
const trendText = trend ? (trend > 0 ? `+${trend.toFixed(1)}%` : `${trend.toFixed(1)}%`) : ''
|
||||||
|
|
||||||
const title = `.${tldUpper} Domain Pricing & Market Analysis ${new Date().getFullYear()}`
|
const title = `.${tldUpper} Domain Pricing & Market Analysis ${new Date().getFullYear()}`
|
||||||
const description = `Complete .${tldUpper} domain pricing intelligence${price ? ` starting at $${price.toFixed(2)}` : ''}${trendText ? ` (${trendText} trend)` : ''}. Compare registration, renewal, and transfer costs across major registrars. Real-time market data and price alerts.`
|
const description = `Complete .${tldUpper} domain pricing intelligence${price ? ` starting at $${price.toFixed(2)}` : ''}${trendText ? ` (${trendText} trend)` : ''}. Compare registration, renewal, and transfer costs across major registrars. Updated daily with market data and price alerts.`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
|
|||||||
@ -321,7 +321,7 @@ export default function HomePage() {
|
|||||||
{
|
{
|
||||||
module: '01',
|
module: '01',
|
||||||
title: 'Intelligence',
|
title: 'Intelligence',
|
||||||
desc: '"Identify Targets." We scan 886+ TLDs in real-time to uncover hidden opportunities.',
|
desc: '"Identify Targets." We scan 886+ TLDs to uncover pricing traps, trends, and opportunities.',
|
||||||
features: [
|
features: [
|
||||||
{ icon: Scan, title: 'Global Scan', desc: 'Zone file analysis' },
|
{ icon: Scan, title: 'Global Scan', desc: 'Zone file analysis' },
|
||||||
{ icon: Target, title: 'Valuation AI', desc: 'Instant fair-market value' },
|
{ icon: Target, title: 'Valuation AI', desc: 'Instant fair-market value' },
|
||||||
|
|||||||
@ -43,7 +43,7 @@ function GitHubIcon({ className }: { className?: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const benefits = [
|
const benefits = [
|
||||||
{ text: 'Real-time Market Feed', icon: Zap },
|
{ text: 'Live Market Feed', icon: Zap },
|
||||||
{ text: 'Daily Scan Reports', icon: Shield },
|
{ text: 'Daily Scan Reports', icon: Shield },
|
||||||
{ text: 'Yield Intel Access', icon: TrendingUp },
|
{ text: 'Yield Intel Access', icon: TrendingUp },
|
||||||
]
|
]
|
||||||
|
|||||||
@ -94,6 +94,9 @@ export default function PortfolioPage() {
|
|||||||
const [menuOpen, setMenuOpen] = useState(false)
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
const [navDrawerOpen, setNavDrawerOpen] = useState(false)
|
const [navDrawerOpen, setNavDrawerOpen] = useState(false)
|
||||||
|
|
||||||
|
// Yield domains - to show which are in Yield
|
||||||
|
const [yieldDomains, setYieldDomains] = useState<Set<string>>(new Set())
|
||||||
|
|
||||||
const tier = subscription?.tier || 'scout'
|
const tier = subscription?.tier || 'scout'
|
||||||
const isScout = tier === 'scout'
|
const isScout = tier === 'scout'
|
||||||
|
|
||||||
@ -102,12 +105,15 @@ export default function PortfolioPage() {
|
|||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const [domainsData, summaryData] = await Promise.all([
|
const [domainsData, summaryData, yieldData] = await Promise.all([
|
||||||
api.getPortfolio(),
|
api.getPortfolio(),
|
||||||
api.getPortfolioSummary()
|
api.getPortfolioSummary(),
|
||||||
|
api.getYieldDomains().catch(() => ({ domains: [] }))
|
||||||
])
|
])
|
||||||
setDomains(domainsData)
|
setDomains(domainsData)
|
||||||
setSummary(summaryData)
|
setSummary(summaryData)
|
||||||
|
// Create a set of domain names that are in Yield
|
||||||
|
setYieldDomains(new Set((yieldData.domains || []).map((d: any) => d.domain.toLowerCase())))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load portfolio:', err)
|
console.error('Failed to load portfolio:', err)
|
||||||
} finally {
|
} finally {
|
||||||
@ -408,6 +414,11 @@ export default function PortfolioPage() {
|
|||||||
<span className="flex items-center gap-0.5 text-[9px] font-mono text-accent bg-accent/10 px-1 py-0.5 border border-accent/20">
|
<span className="flex items-center gap-0.5 text-[9px] font-mono text-accent bg-accent/10 px-1 py-0.5 border border-accent/20">
|
||||||
<ShieldCheck className="w-2.5 h-2.5" /> Verified
|
<ShieldCheck className="w-2.5 h-2.5" /> Verified
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
|
{yieldDomains.has(domain.domain.toLowerCase()) && (
|
||||||
|
<span className="flex items-center gap-0.5 text-[9px] font-mono text-amber-400 bg-amber-400/10 px-1 py-0.5 border border-amber-400/20">
|
||||||
|
<Coins className="w-2.5 h-2.5" /> Yield
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -506,13 +517,20 @@ export default function PortfolioPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status */}
|
{/* Status */}
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center gap-1">
|
||||||
{domain.is_sold ? (
|
{domain.is_sold ? (
|
||||||
<span className="px-2 py-1 text-[9px] font-mono uppercase bg-white/[0.02] text-white/30 border border-white/[0.06]">Sold</span>
|
<span className="px-2 py-1 text-[9px] font-mono uppercase bg-white/[0.02] text-white/30 border border-white/[0.06]">Sold</span>
|
||||||
) : domain.is_dns_verified ? (
|
) : domain.is_dns_verified ? (
|
||||||
<span className="flex items-center gap-1 px-2 py-1 text-[9px] font-mono uppercase bg-accent/10 text-accent border border-accent/20">
|
<>
|
||||||
<ShieldCheck className="w-3 h-3" /> Verified
|
<span className="flex items-center gap-0.5 px-1.5 py-0.5 text-[9px] font-mono uppercase bg-accent/10 text-accent border border-accent/20">
|
||||||
|
<ShieldCheck className="w-2.5 h-2.5" />
|
||||||
</span>
|
</span>
|
||||||
|
{yieldDomains.has(domain.domain.toLowerCase()) && (
|
||||||
|
<span className="flex items-center gap-0.5 px-1.5 py-0.5 text-[9px] font-mono uppercase bg-amber-400/10 text-amber-400 border border-amber-400/20">
|
||||||
|
<Coins className="w-2.5 h-2.5" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={() => setVerifyingDomain(domain)}
|
onClick={() => setVerifyingDomain(domain)}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
TrendingUp, DollarSign, Zap, Plus, CheckCircle2, Clock, AlertCircle,
|
TrendingUp, DollarSign, Zap, Plus, CheckCircle2, Clock, AlertCircle,
|
||||||
MousePointer, Target, Wallet, RefreshCw, ChevronRight, Copy, Check,
|
MousePointer, Target, Wallet, RefreshCw, ChevronRight, Copy, Check,
|
||||||
XCircle, Sparkles, Loader2, Eye, Gavel, Menu, Settings, Shield, LogOut,
|
XCircle, Sparkles, Loader2, Eye, Gavel, Menu, Settings, Shield, LogOut,
|
||||||
Crown, Coins, Tag, X, Briefcase
|
Crown, Coins, Tag, X, Briefcase, Trash2
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { api, YieldDomain, YieldTransaction } from '@/lib/api'
|
import { api, YieldDomain, YieldTransaction } from '@/lib/api'
|
||||||
import { useStore } from '@/lib/store'
|
import { useStore } from '@/lib/store'
|
||||||
@ -148,6 +148,7 @@ export default function YieldPage() {
|
|||||||
const [showActivateModal, setShowActivateModal] = useState(false)
|
const [showActivateModal, setShowActivateModal] = useState(false)
|
||||||
const [refreshing, setRefreshing] = useState(false)
|
const [refreshing, setRefreshing] = useState(false)
|
||||||
const [menuOpen, setMenuOpen] = useState(false)
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
|
const [deletingId, setDeletingId] = useState<number | null>(null)
|
||||||
|
|
||||||
useEffect(() => { checkAuth() }, [checkAuth])
|
useEffect(() => { checkAuth() }, [checkAuth])
|
||||||
|
|
||||||
@ -159,6 +160,19 @@ export default function YieldPage() {
|
|||||||
finally { setLoading(false); setRefreshing(false) }
|
finally { setLoading(false); setRefreshing(false) }
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleDeleteYield = useCallback(async (domainId: number, domainName: string) => {
|
||||||
|
if (!confirm(`Remove ${domainName} from Yield? This will stop all revenue tracking.`)) return
|
||||||
|
setDeletingId(domainId)
|
||||||
|
try {
|
||||||
|
await api.deleteYieldDomain(domainId)
|
||||||
|
fetchDashboard()
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to remove yield domain:', err)
|
||||||
|
} finally {
|
||||||
|
setDeletingId(null)
|
||||||
|
}
|
||||||
|
}, [fetchDashboard])
|
||||||
|
|
||||||
useEffect(() => { fetchDashboard() }, [fetchDashboard])
|
useEffect(() => { fetchDashboard() }, [fetchDashboard])
|
||||||
|
|
||||||
const stats = dashboard?.stats
|
const stats = dashboard?.stats
|
||||||
@ -285,13 +299,14 @@ export default function YieldPage() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
|
<div className="space-y-px bg-white/[0.04] border border-white/[0.08]">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="hidden lg:grid grid-cols-[1fr_80px_120px_80px_80px_80px] gap-4 px-3 py-2 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08]">
|
<div className="hidden lg:grid grid-cols-[1fr_80px_120px_80px_80px_80px_60px] gap-4 px-3 py-2 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08]">
|
||||||
<div>Domain</div>
|
<div>Domain</div>
|
||||||
<div className="text-center">Status</div>
|
<div className="text-center">Status</div>
|
||||||
<div>Intent</div>
|
<div>Intent</div>
|
||||||
<div className="text-right">Clicks</div>
|
<div className="text-right">Clicks</div>
|
||||||
<div className="text-right">Conv.</div>
|
<div className="text-right">Conv.</div>
|
||||||
<div className="text-right">Revenue</div>
|
<div className="text-right">Revenue</div>
|
||||||
|
<div className="text-right">Action</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{dashboard.domains.map((domain: YieldDomain) => (
|
{dashboard.domains.map((domain: YieldDomain) => (
|
||||||
@ -307,14 +322,27 @@ export default function YieldPage() {
|
|||||||
</div>
|
</div>
|
||||||
<StatusBadge status={domain.status} />
|
<StatusBadge status={domain.status} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-[10px] font-mono text-white/40">
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex gap-4 text-[10px] font-mono text-white/40">
|
||||||
<span>{domain.total_clicks} clicks</span>
|
<span>{domain.total_clicks} clicks</span>
|
||||||
<span className="text-accent font-bold">${domain.total_revenue}</span>
|
<span className="text-accent font-bold">${domain.total_revenue}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDeleteYield(domain.id, domain.domain)}
|
||||||
|
disabled={deletingId === domain.id}
|
||||||
|
className="p-1.5 text-white/30 hover:text-rose-400 disabled:opacity-50 transition-colors"
|
||||||
|
>
|
||||||
|
{deletingId === domain.id ? (
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Desktop */}
|
{/* Desktop */}
|
||||||
<div className="hidden lg:grid grid-cols-[1fr_80px_120px_80px_80px_80px] gap-4 items-center px-3 py-3">
|
<div className="hidden lg:grid grid-cols-[1fr_80px_120px_80px_80px_80px_60px] gap-4 items-center px-3 py-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-8 h-8 bg-white/[0.02] border border-white/[0.06] flex items-center justify-center text-accent text-xs font-bold font-mono">
|
<div className="w-8 h-8 bg-white/[0.02] border border-white/[0.06] flex items-center justify-center text-accent text-xs font-bold font-mono">
|
||||||
{domain.domain.charAt(0).toUpperCase()}
|
{domain.domain.charAt(0).toUpperCase()}
|
||||||
@ -326,6 +354,20 @@ export default function YieldPage() {
|
|||||||
<div className="text-right text-xs font-mono text-white/60">{domain.total_clicks}</div>
|
<div className="text-right text-xs font-mono text-white/60">{domain.total_clicks}</div>
|
||||||
<div className="text-right text-xs font-mono text-white/60">{domain.total_conversions}</div>
|
<div className="text-right text-xs font-mono text-white/60">{domain.total_conversions}</div>
|
||||||
<div className="text-right text-sm font-bold font-mono text-accent">${domain.total_revenue}</div>
|
<div className="text-right text-sm font-bold font-mono text-accent">${domain.total_revenue}</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={() => handleDeleteYield(domain.id, domain.domain)}
|
||||||
|
disabled={deletingId === domain.id}
|
||||||
|
className="p-1.5 text-white/30 hover:text-rose-400 disabled:opacity-50 transition-colors"
|
||||||
|
title="Remove from Yield"
|
||||||
|
>
|
||||||
|
{deletingId === domain.id ? (
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -421,7 +421,7 @@ export default function YieldPage() {
|
|||||||
{ icon: PieChart, title: 'Revenue Share', desc: 'Industry-leading 70/30 split. We only make money when you do.' },
|
{ icon: PieChart, title: 'Revenue Share', desc: 'Industry-leading 70/30 split. We only make money when you do.' },
|
||||||
{ icon: Target, title: '20+ Verticals', desc: 'Finance, Insurance, Travel, Health, B2B Services, and more.' },
|
{ icon: Target, title: '20+ Verticals', desc: 'Finance, Insurance, Travel, Health, B2B Services, and more.' },
|
||||||
{ icon: Shield, title: 'Swiss Partners', desc: 'Direct API integrations with premium Swiss & EU brands.' },
|
{ icon: Shield, title: 'Swiss Partners', desc: 'Direct API integrations with premium Swiss & EU brands.' },
|
||||||
{ icon: BarChart3, title: 'Live Analytics', desc: 'Real-time dashboard showing every click, lead, and conversion.' },
|
{ icon: BarChart3, title: 'Live Analytics', desc: 'Live dashboard showing every click, lead, and conversion.' },
|
||||||
{ icon: RefreshCw, title: 'Auto-Optimization', desc: 'AI automatically routes to the highest-paying partner for each visitor.' },
|
{ icon: RefreshCw, title: 'Auto-Optimization', desc: 'AI automatically routes to the highest-paying partner for each visitor.' },
|
||||||
{ icon: Zap, title: 'Instant DNS', desc: 'Zero downtime. Verify ownership and start earning in < 5 minutes.' }
|
{ icon: Zap, title: 'Instant DNS', desc: 'Zero downtime. Verify ownership and start earning in < 5 minutes.' }
|
||||||
].map((feat, i) => (
|
].map((feat, i) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user