Fix: Intel TLD pagination, Market page pagination + toggle tracking
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:29:24 +01:00
parent 2e507f5484
commit eedd61cd79
2 changed files with 182 additions and 31 deletions

View File

@ -92,33 +92,50 @@ export default function IntelPage() {
setLoading(true)
setError(null)
try {
const response = await api.getTldOverview(500, 0, 'popularity')
console.log('TLD API Response:', response)
// Fetch multiple pages to get more TLDs (API limit is 100 per request)
const allTlds: TLDData[] = []
let totalRecords = 0
if (!response || !response.tlds) {
for (let offset = 0; offset < 500; offset += 100) {
const response = await api.getTldOverview(100, offset, 'popularity')
if (!response || !response.tlds || response.tlds.length === 0) {
break
}
totalRecords = response.total || 0
const mapped: TLDData[] = response.tlds.map((tld: any) => ({
tld: tld.tld || '',
min_price: tld.min_registration_price || 0,
avg_price: tld.avg_registration_price || 0,
max_price: tld.max_registration_price || 0,
min_renewal_price: tld.min_renewal_price || 0,
avg_renewal_price: tld.avg_renewal_price || 0,
price_change_7d: tld.price_change_7d || 0,
price_change_1y: tld.price_change_1y || 0,
price_change_3y: tld.price_change_3y || 0,
risk_level: tld.risk_level || 'low',
risk_reason: tld.risk_reason || '',
popularity_rank: tld.popularity_rank,
type: tld.type,
}))
allTlds.push(...mapped)
// Stop if we got less than requested (no more data)
if (response.tlds.length < 100) break
}
if (allTlds.length === 0) {
setError('No TLD data available')
setTldData([])
setTotal(0)
return
}
const mapped: TLDData[] = (response.tlds || []).map((tld: any) => ({
tld: tld.tld || '',
min_price: tld.min_registration_price || 0,
avg_price: tld.avg_registration_price || 0,
max_price: tld.max_registration_price || 0,
min_renewal_price: tld.min_renewal_price || 0,
avg_renewal_price: tld.avg_renewal_price || 0,
price_change_7d: tld.price_change_7d || 0,
price_change_1y: tld.price_change_1y || 0,
price_change_3y: tld.price_change_3y || 0,
risk_level: tld.risk_level || 'low',
risk_reason: tld.risk_reason || '',
popularity_rank: tld.popularity_rank,
type: tld.type,
}))
setTldData(mapped)
setTotal(response.total || mapped.length)
setTldData(allTlds)
setTotal(totalRecords || allTlds.length)
} catch (err: any) {
console.error('Failed to load TLD data:', err)
setError(err.message || 'Failed to load TLD data')

View File

@ -10,6 +10,8 @@ import {
Diamond,
Zap,
ChevronDown,
ChevronLeft,
ChevronRight,
Plus,
Check,
TrendingUp,
@ -17,6 +19,7 @@ import {
Clock,
Search,
Eye,
EyeOff,
ShieldCheck,
Store,
Gavel,
@ -123,8 +126,13 @@ export default function MarketPage() {
const [trackedDomains, setTrackedDomains] = useState<Set<string>>(new Set())
const [trackingInProgress, setTrackingInProgress] = useState<string | null>(null)
// Pagination
const [page, setPage] = useState(1)
const [totalPages, setTotalPages] = useState(1)
const ITEMS_PER_PAGE = 50
const loadData = useCallback(async () => {
const loadData = useCallback(async (currentPage = 1) => {
setLoading(true)
try {
const result = await api.getMarketFeed({
@ -136,7 +144,8 @@ export default function MarketPage() {
sortBy: sortField === 'score' ? 'score' :
sortField === 'price' ? (sortDirection === 'asc' ? 'price_asc' : 'price_desc') :
sortField === 'time' ? 'time' : 'newest',
limit: 100
limit: ITEMS_PER_PAGE,
offset: (currentPage - 1) * ITEMS_PER_PAGE
})
setItems(result.items || [])
@ -146,6 +155,7 @@ export default function MarketPage() {
auctionCount: result.auction_count,
highScore: (result.items || []).filter(i => i.pounce_score >= 80).length
})
setTotalPages(Math.ceil((result.total || 0) / ITEMS_PER_PAGE))
} catch (error) {
console.error('Failed to load market data:', error)
setItems([])
@ -154,7 +164,15 @@ export default function MarketPage() {
}
}, [sourceFilter, searchQuery, priceRange, sortField, sortDirection, tldFilter])
useEffect(() => { loadData() }, [loadData])
useEffect(() => {
setPage(1)
loadData(1)
}, [loadData])
const handlePageChange = useCallback((newPage: number) => {
setPage(newPage)
loadData(newPage)
}, [loadData])
const handleRefresh = useCallback(async () => {
setRefreshing(true)
@ -171,12 +189,42 @@ export default function MarketPage() {
}
}, [sortField])
// Load user's tracked domains on mount
useEffect(() => {
const loadTrackedDomains = async () => {
try {
const result = await api.getDomains(1, 100)
const domainSet = new Set(result.items.map(d => d.domain))
setTrackedDomains(domainSet)
} catch (error) {
console.error('Failed to load tracked domains:', error)
}
}
loadTrackedDomains()
}, [])
const handleTrack = useCallback(async (domain: string) => {
if (trackedDomains.has(domain) || trackingInProgress) return
if (trackingInProgress) return
setTrackingInProgress(domain)
try {
await api.addDomain(domain)
setTrackedDomains(prev => new Set([...Array.from(prev), domain]))
if (trackedDomains.has(domain)) {
// Find and delete the domain
const result = await api.getDomains(1, 100)
const domainToDelete = result.items.find(d => d.domain === domain)
if (domainToDelete) {
await api.deleteDomain(domainToDelete.id)
setTrackedDomains(prev => {
const next = new Set(Array.from(prev))
next.delete(domain)
return next
})
}
} else {
// Add the domain
await api.addDomain(domain)
setTrackedDomains(prev => new Set([...Array.from(prev), domain]))
}
} catch (error) {
console.error(error)
} finally {
@ -425,6 +473,34 @@ export default function MarketPage() {
<span>Score: {item.pounce_score}</span>
<span>{displayTime || 'Instant'}</span>
</div>
{/* Mobile Actions */}
<div className="flex items-center gap-2 mt-3 pt-3 border-t border-white/[0.05]">
<button
onClick={() => handleTrack(item.domain)}
disabled={isTracking}
className={clsx(
"flex-1 h-8 flex items-center justify-center gap-2 text-xs font-medium border transition-colors",
isTracked
? "bg-accent/10 text-accent border-accent/20"
: "text-white/50 border-white/10 hover:text-white"
)}
>
{isTracking ? <Loader2 className="w-3 h-3 animate-spin" /> : isTracked ? <Eye className="w-3 h-3" /> : <EyeOff className="w-3 h-3" />}
{isTracked ? 'Tracking' : 'Track'}
</button>
<a
href={item.url}
target={isPounce ? "_self" : "_blank"}
rel={isPounce ? undefined : "noopener noreferrer"}
className={clsx(
"flex-1 h-8 flex items-center justify-center gap-1.5 text-xs font-semibold",
isPounce ? "bg-accent text-black" : "bg-white/10 text-white"
)}
>
{isPounce ? 'Buy' : 'Bid'}
{!isPounce && <ExternalLink className="w-3 h-3" />}
</a>
</div>
</div>
{/* Desktop */}
@ -504,20 +580,21 @@ export default function MarketPage() {
<div className="flex items-center justify-end gap-2 opacity-50 group-hover:opacity-100 transition-opacity">
<button
onClick={() => handleTrack(item.domain)}
disabled={isTracked || isTracking}
disabled={isTracking}
title={isTracked ? "Stop tracking" : "Start tracking"}
className={clsx(
"w-7 h-7 flex items-center justify-center border transition-colors",
isTracked
? "bg-accent/10 text-accent border-accent/20"
: "text-white/30 border-white/10 hover:text-white hover:bg-white/5"
? "bg-accent/10 text-accent border-accent/20 hover:bg-red-500/10 hover:text-red-400 hover:border-red-500/20"
: "text-white/30 border-white/10 hover:text-accent hover:bg-accent/10 hover:border-accent/20"
)}
>
{isTracking ? (
<Loader2 className="w-3.5 h-3.5 animate-spin" />
) : isTracked ? (
<Check className="w-3.5 h-3.5" />
) : (
<Eye className="w-3.5 h-3.5" />
) : (
<EyeOff className="w-3.5 h-3.5" />
)}
</button>
@ -542,6 +619,63 @@ export default function MarketPage() {
})}
</div>
)}
{/* Pagination */}
{!loading && totalPages > 1 && (
<div className="flex items-center justify-between pt-6">
<div className="text-xs text-white/40 font-mono">
Page {page} of {totalPages} · {stats.total} domains
</div>
<div className="flex items-center gap-2">
<button
onClick={() => handlePageChange(page - 1)}
disabled={page === 1}
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
<ChevronLeft className="w-4 h-4" />
</button>
{/* Page Numbers */}
<div className="flex items-center gap-1">
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
let pageNum: number
if (totalPages <= 5) {
pageNum = i + 1
} else if (page <= 3) {
pageNum = i + 1
} else if (page >= totalPages - 2) {
pageNum = totalPages - 4 + i
} else {
pageNum = page - 2 + i
}
return (
<button
key={pageNum}
onClick={() => handlePageChange(pageNum)}
className={clsx(
"w-8 h-8 flex items-center justify-center text-xs font-mono border transition-colors",
page === pageNum
? "bg-accent text-black border-accent"
: "border-white/10 text-white/50 hover:text-white hover:bg-white/5"
)}
>
{pageNum}
</button>
)
})}
</div>
<button
onClick={() => handlePageChange(page + 1)}
disabled={page === totalPages}
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/50 hover:text-white hover:bg-white/5 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
>
<ChevronRight className="w-4 h-4" />
</button>
</div>
</div>
)}
</section>
</CommandCenterLayout>
)