fix: Make Public TLD Pricing table 1:1 identical to Command Center

CHANGES:
1. Sparkline: Now uses exact same SVG polyline implementation
   - Upward trend: orange polyline
   - Downward trend: accent polyline
   - Neutral: horizontal line

2. Risk Badge: Now shows dot + text label (like Command Center)
   - 'Stable', 'Renewal trap', etc. visible on desktop
   - Width changed from 80px → 130px

3. Actions column: Width changed from 40px → 80px

4. Pagination: Simplified to match Command Center styling
   - Removed icons from buttons
   - Uses tabular-nums for page counter

Both tables now render identically with same:
- Column widths
- Sparkline SVGs
- Risk badges with text
- Button styling
This commit is contained in:
yves.gugger
2025-12-10 15:47:12 +01:00
parent 3c0d58f0d3
commit 64785e95ce

View File

@ -8,8 +8,6 @@ import { useStore } from '@/lib/store'
import { api } from '@/lib/api'
import {
TrendingUp,
TrendingDown,
Minus,
ChevronRight,
ChevronLeft,
Search,
@ -55,31 +53,37 @@ interface PaginationData {
has_more: boolean
}
// Sparkline component
// Sparkline component - matching Command Center exactly
function Sparkline({ trend }: { trend: number }) {
const isPositive = trend > 0
const isNegative = trend < 0
// Generate simple sparkline points
const points = Array.from({ length: 8 }, (_, i) => {
const baseY = 50
const variance = isPositive ? -trend * 3 : isNegative ? -trend * 3 : 5
return `${i * 14},${baseY + (Math.random() * variance - variance / 2) * (i / 7)}`
}).join(' ')
const isNeutral = trend === 0
return (
<div className="w-16 h-8 flex items-center">
<svg viewBox="0 0 100 100" className="w-full h-full" preserveAspectRatio="none">
<polyline
points={points}
fill="none"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
className={clsx(
isPositive ? "stroke-orange-400" : isNegative ? "stroke-accent" : "stroke-foreground-subtle"
)}
/>
<div className="flex items-center gap-1">
<svg width="40" height="16" viewBox="0 0 40 16" className="overflow-visible">
{isNeutral ? (
<line x1="0" y1="8" x2="40" y2="8" stroke="currentColor" className="text-foreground-muted" strokeWidth="1.5" />
) : isPositive ? (
<polyline
points="0,14 10,12 20,10 30,6 40,2"
fill="none"
stroke="currentColor"
className="text-orange-400"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
) : (
<polyline
points="0,2 10,6 20,10 30,12 40,14"
fill="none"
stroke="currentColor"
className="text-accent"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
)}
</svg>
</div>
)
@ -90,7 +94,7 @@ export default function TldPricingPage() {
const [tlds, setTlds] = useState<TldData[]>([])
const [trending, setTrending] = useState<TrendingTld[]>([])
const [loading, setLoading] = useState(true)
const [pagination, setPagination] = useState<PaginationData>({ total: 0, limit: 25, offset: 0, has_more: false })
const [pagination, setPagination] = useState<PaginationData>({ total: 0, limit: 50, offset: 0, has_more: false })
// Search & Sort state
const [searchQuery, setSearchQuery] = useState('')
@ -150,6 +154,28 @@ export default function TldPricingPage() {
}
}
// Risk badge - matching Command Center exactly
const getRiskBadge = (tld: TldData) => {
const level = tld.risk_level || 'low'
const reason = tld.risk_reason || 'Stable'
return (
<span className={clsx(
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium",
level === 'high' && "bg-red-500/10 text-red-400",
level === 'medium' && "bg-amber-500/10 text-amber-400",
level === 'low' && "bg-accent/10 text-accent"
)}>
<span className={clsx(
"w-2.5 h-2.5 rounded-full",
level === 'high' && "bg-red-400",
level === 'medium' && "bg-amber-400",
level === 'low' && "bg-accent"
)} />
<span className="hidden sm:inline ml-1">{reason}</span>
</span>
)
}
// Get renewal trap indicator
const getRenewalTrap = (tld: TldData) => {
if (!tld.min_renewal_price || !tld.min_registration_price) return null
@ -348,7 +374,7 @@ export default function TldPricingPage() {
</div>
</div>
{/* TLD Table using PremiumTable */}
{/* TLD Table using PremiumTable - matching Command Center exactly */}
<PremiumTable
data={tlds}
keyExtractor={(tld) => tld.tld}
@ -387,7 +413,7 @@ export default function TldPricingPage() {
render: (tld, idx) => {
const showData = isAuthenticated || (page === 0 && idx === 0)
if (!showData) {
return <div className="w-16 h-8 bg-foreground/5 rounded blur-[3px]" />
return <div className="w-10 h-4 bg-foreground/5 rounded blur-[3px]" />
}
return <Sparkline trend={tld.price_change_1y || 0} />
},
@ -473,30 +499,25 @@ export default function TldPricingPage() {
key: 'risk',
header: 'Risk',
align: 'center',
width: '80px',
hideOnMobile: true,
width: '130px',
render: (tld, idx) => {
const showData = isAuthenticated || (page === 0 && idx === 0)
if (!showData) {
return <span className="w-3 h-3 rounded-full bg-foreground-subtle blur-[3px]" />
return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-foreground/5 blur-[3px]">
<span className="w-2.5 h-2.5 rounded-full bg-foreground-subtle" />
<span className="hidden sm:inline ml-1">Hidden</span>
</span>
)
}
return (
<div className="flex justify-center" title={tld.risk_reason}>
<span className={clsx(
"w-3 h-3 rounded-full",
tld.risk_level === 'high' && "bg-red-400",
tld.risk_level === 'medium' && "bg-amber-400",
tld.risk_level === 'low' && "bg-accent"
)} />
</div>
)
return getRiskBadge(tld)
},
},
{
key: 'action',
key: 'actions',
header: '',
align: 'right',
width: '40px',
width: '80px',
render: () => (
<ChevronRight className="w-5 h-5 text-foreground-subtle group-hover:text-accent transition-colors" />
),
@ -506,29 +527,27 @@ export default function TldPricingPage() {
{/* Pagination */}
{!loading && pagination.total > pagination.limit && (
<div className="flex items-center justify-center gap-4 pt-6">
<div className="flex items-center justify-center gap-4 pt-2">
<button
onClick={() => setPage(Math.max(0, page - 1))}
disabled={page === 0}
className="flex items-center gap-1 px-4 py-2 text-sm font-medium text-foreground-muted hover:text-foreground
className="px-4 py-2 text-sm font-medium text-foreground-muted hover:text-foreground
bg-foreground/5 hover:bg-foreground/10 rounded-lg
disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
<ChevronLeft className="w-4 h-4" />
Previous
</button>
<span className="text-sm text-foreground-muted">
<span className="text-sm text-foreground-muted tabular-nums">
Page {currentPage} of {totalPages}
</span>
<button
onClick={() => setPage(page + 1)}
disabled={!pagination.has_more}
className="flex items-center gap-1 px-4 py-2 text-sm font-medium text-foreground-muted hover:text-foreground
className="px-4 py-2 text-sm font-medium text-foreground-muted hover:text-foreground
bg-foreground/5 hover:bg-foreground/10 rounded-lg
disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Next
<ChevronRight className="w-4 h-4" />
</button>
</div>
)}