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
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:
@ -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
|
||||
@ -75,6 +78,7 @@ export default function IntelPage() {
|
||||
|
||||
const [tldData, setTldData] = useState<TLDData[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const [total, setTotal] = useState(0)
|
||||
|
||||
@ -86,27 +90,39 @@ export default function IntelPage() {
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
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) => ({
|
||||
tld: tld.tld,
|
||||
min_price: tld.min_registration_price,
|
||||
avg_price: tld.avg_registration_price,
|
||||
max_price: tld.max_registration_price,
|
||||
min_renewal_price: tld.min_renewal_price,
|
||||
avg_renewal_price: tld.avg_renewal_price,
|
||||
price_change_7d: tld.price_change_7d,
|
||||
price_change_1y: tld.price_change_1y,
|
||||
price_change_3y: tld.price_change_3y,
|
||||
risk_level: tld.risk_level,
|
||||
risk_reason: tld.risk_reason,
|
||||
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 || 0)
|
||||
} catch (error) {
|
||||
console.error('Failed to load TLD data:', error)
|
||||
setTotal(response.total || mapped.length)
|
||||
} catch (err: any) {
|
||||
console.error('Failed to load TLD data:', err)
|
||||
setError(err.message || 'Failed to load TLD data')
|
||||
setTldData([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@ -132,7 +148,7 @@ export default function IntelPage() {
|
||||
}, [sortField, canSeeRenewal, canSee3yTrend])
|
||||
|
||||
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']
|
||||
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) => {
|
||||
switch (sortField) {
|
||||
case 'tld': return mult * a.tld.localeCompare(b.tld)
|
||||
case 'price': return mult * (a.min_price - b.min_price)
|
||||
case 'renewal': return mult * (a.min_renewal_price - b.min_renewal_price)
|
||||
case 'price': return mult * ((a.min_price || 0) - (b.min_price || 0))
|
||||
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 'change3y': return mult * ((a.price_change_3y || 0) - (b.price_change_3y || 0))
|
||||
case 'risk':
|
||||
const riskMap = { low: 1, medium: 2, high: 3 }
|
||||
return mult * (riskMap[a.risk_level] - riskMap[b.risk_level])
|
||||
const riskMap: Record<string, number> = { low: 1, medium: 2, high: 3 }
|
||||
return mult * ((riskMap[a.risk_level] || 1) - (riskMap[b.risk_level] || 1))
|
||||
case 'popularity': return mult * ((a.popularity_rank || 999) - (b.popularity_rank || 999))
|
||||
default: return 0
|
||||
}
|
||||
@ -163,11 +179,12 @@ export default function IntelPage() {
|
||||
}, [tldData, filterType, searchQuery, sortField, sortDirection])
|
||||
|
||||
const stats = useMemo(() => {
|
||||
const lowest = tldData.length > 0 ? Math.min(...tldData.map(t => t.min_price)) : 0
|
||||
const hottest = tldData.reduce((prev, current) => (prev.price_change_1y > current.price_change_1y) ? prev : current, tldData[0] || {})
|
||||
if (!tldData.length) return { lowest: 0, traps: 0, avgRenewal: 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 avgRenewal = tldData.length > 0 ? tldData.reduce((sum, t) => sum + t.min_renewal_price, 0) / tldData.length : 0
|
||||
return { lowest, hottest, traps, avgRenewal }
|
||||
const avgRenewal = tldData.length > 0 ? tldData.reduce((sum, t) => sum + (t.min_renewal_price || 0), 0) / tldData.length : 0
|
||||
return { lowest, traps, avgRenewal }
|
||||
}, [tldData])
|
||||
|
||||
return (
|
||||
@ -284,6 +301,14 @@ export default function IntelPage() {
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<Loader2 className="w-6 h-6 text-accent animate-spin" />
|
||||
</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 ? (
|
||||
<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">
|
||||
@ -339,7 +364,7 @@ export default function IntelPage() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{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 trend3y = tld.price_change_3y || 0
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user