fix: Smart 'Best' label + tooltips for TLD detail pages
BEST VALUE LOGIC:
- 'Best' badge only shown when:
1. Cheapest registration price AND
2. No renewal trap (renewal <= 1.5x registration)
- New 'Cheap Start' badge for cheapest with renewal trap
Shows warning: 'Cheapest registration but high renewal costs'
TOOLTIPS ADDED:
Stats Cards:
- Buy (1y): 'Lowest first-year registration price...'
- Renew (1y): 'Annual renewal price after first year'
or 'Warning: Renewal is Xx the registration price'
- 1y Change: 'Price change over the last 12 months'
- 3y Change: 'Price change over the last 3 years'
Registrar Table Headers:
- Register: 'First year registration price'
- Renew: 'Annual renewal price'
- Transfer: 'Transfer from another registrar'
Registrar Table Cells:
- Registration price: 'First year: $X.XX'
- Renewal price: 'Annual renewal: $X.XX' or trap warning
- Transfer price: 'Transfer from another registrar: $X.XX'
- AlertTriangle icon: 'Renewal trap: Xx registration price'
- Best badge: 'Best overall value: lowest registration...'
- Cheap Start badge: 'Cheapest registration but high renewal...'
- Visit link: 'Register at {registrar}'
Applied to both:
- /command/pricing/[tld] (Command Center)
- /tld-pricing/[tld] (Public)
This commit is contained in:
@ -406,28 +406,38 @@ export default function CommandTldDetailPage() {
|
||||
|
||||
{/* Stats Grid - All info from table */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<StatCard
|
||||
title="Buy Price (1y)"
|
||||
value={`$${details.pricing.min.toFixed(2)}`}
|
||||
subtitle={`at ${details.cheapest_registrar}`}
|
||||
icon={DollarSign}
|
||||
/>
|
||||
<StatCard
|
||||
title="Renewal (1y)"
|
||||
value={details.min_renewal_price ? `$${details.min_renewal_price.toFixed(2)}` : '—'}
|
||||
subtitle={renewalInfo?.isTrap ? `${renewalInfo.ratio.toFixed(1)}x registration` : 'per year'}
|
||||
icon={RefreshCw}
|
||||
/>
|
||||
<StatCard
|
||||
title="1y Change"
|
||||
value={`${details.price_change_1y > 0 ? '+' : ''}${details.price_change_1y.toFixed(0)}%`}
|
||||
icon={details.price_change_1y > 0 ? TrendingUp : details.price_change_1y < 0 ? TrendingDown : Minus}
|
||||
/>
|
||||
<StatCard
|
||||
title="3y Change"
|
||||
value={`${details.price_change_3y > 0 ? '+' : ''}${details.price_change_3y.toFixed(0)}%`}
|
||||
icon={BarChart3}
|
||||
/>
|
||||
<div title="Lowest first-year registration price across all tracked registrars">
|
||||
<StatCard
|
||||
title="Buy Price (1y)"
|
||||
value={`$${details.pricing.min.toFixed(2)}`}
|
||||
subtitle={`at ${details.cheapest_registrar}`}
|
||||
icon={DollarSign}
|
||||
/>
|
||||
</div>
|
||||
<div title={renewalInfo?.isTrap
|
||||
? `Warning: Renewal is ${renewalInfo.ratio.toFixed(1)}x the registration price`
|
||||
: 'Annual renewal price after first year'}>
|
||||
<StatCard
|
||||
title="Renewal (1y)"
|
||||
value={details.min_renewal_price ? `$${details.min_renewal_price.toFixed(2)}` : '—'}
|
||||
subtitle={renewalInfo?.isTrap ? `${renewalInfo.ratio.toFixed(1)}x registration` : 'per year'}
|
||||
icon={RefreshCw}
|
||||
/>
|
||||
</div>
|
||||
<div title="Price change over the last 12 months">
|
||||
<StatCard
|
||||
title="1y Change"
|
||||
value={`${details.price_change_1y > 0 ? '+' : ''}${details.price_change_1y.toFixed(0)}%`}
|
||||
icon={details.price_change_1y > 0 ? TrendingUp : details.price_change_1y < 0 ? TrendingDown : Minus}
|
||||
/>
|
||||
</div>
|
||||
<div title="Price change over the last 3 years">
|
||||
<StatCard
|
||||
title="3y Change"
|
||||
value={`${details.price_change_3y > 0 ? '+' : ''}${details.price_change_3y.toFixed(0)}%`}
|
||||
icon={BarChart3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Risk Level */}
|
||||
@ -506,55 +516,95 @@ export default function CommandTldDetailPage() {
|
||||
<thead>
|
||||
<tr className="border-b border-border/30">
|
||||
<th className="text-left pb-3 text-sm font-medium text-foreground-muted">Registrar</th>
|
||||
<th className="text-right pb-3 text-sm font-medium text-foreground-muted">Register</th>
|
||||
<th className="text-right pb-3 text-sm font-medium text-foreground-muted">Renew</th>
|
||||
<th className="text-right pb-3 text-sm font-medium text-foreground-muted">Transfer</th>
|
||||
<th className="text-right pb-3 text-sm font-medium text-foreground-muted" title="First year registration price">Register</th>
|
||||
<th className="text-right pb-3 text-sm font-medium text-foreground-muted" title="Annual renewal price">Renew</th>
|
||||
<th className="text-right pb-3 text-sm font-medium text-foreground-muted" title="Transfer from another registrar">Transfer</th>
|
||||
<th className="text-right pb-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border/20">
|
||||
{details.registrars.map((registrar, idx) => (
|
||||
<tr key={registrar.name} className={clsx(idx === 0 && "bg-accent/5")}>
|
||||
<td className="py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-foreground">{registrar.name}</span>
|
||||
{idx === 0 && (
|
||||
<span className="px-2 py-0.5 text-xs bg-accent/10 text-accent rounded-full">Cheapest</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-4 text-right">
|
||||
<span className={clsx(
|
||||
"font-medium tabular-nums",
|
||||
idx === 0 ? "text-accent" : "text-foreground"
|
||||
)}>
|
||||
${registrar.registration_price.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-4 text-right">
|
||||
<div className="flex items-center gap-1 justify-end">
|
||||
<span className="text-foreground-muted tabular-nums">${registrar.renewal_price.toFixed(2)}</span>
|
||||
{registrar.renewal_price / registrar.registration_price > 2 && (
|
||||
<AlertTriangle className="w-3.5 h-3.5 text-amber-400" />
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-4 text-right">
|
||||
<span className="text-foreground-muted tabular-nums">${registrar.transfer_price.toFixed(2)}</span>
|
||||
</td>
|
||||
<td className="py-4 text-right">
|
||||
<a
|
||||
href={getRegistrarUrl(registrar.name)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 text-sm text-accent hover:text-accent/80 transition-colors"
|
||||
>
|
||||
Visit
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{details.registrars.map((registrar, idx) => {
|
||||
const hasRenewalTrap = registrar.renewal_price / registrar.registration_price > 1.5
|
||||
const isBestValue = idx === 0 && !hasRenewalTrap
|
||||
|
||||
return (
|
||||
<tr key={registrar.name} className={clsx(isBestValue && "bg-accent/5")}>
|
||||
<td className="py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-foreground">{registrar.name}</span>
|
||||
{isBestValue && (
|
||||
<span
|
||||
className="px-2 py-0.5 text-xs bg-accent/10 text-accent rounded-full cursor-help"
|
||||
title="Best overall value: lowest registration price without renewal trap"
|
||||
>
|
||||
Best
|
||||
</span>
|
||||
)}
|
||||
{idx === 0 && hasRenewalTrap && (
|
||||
<span
|
||||
className="px-2 py-0.5 text-xs bg-amber-500/10 text-amber-400 rounded-full cursor-help"
|
||||
title="Cheapest registration but high renewal costs"
|
||||
>
|
||||
Cheap Start
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-4 text-right">
|
||||
<span
|
||||
className={clsx(
|
||||
"font-medium tabular-nums cursor-help",
|
||||
isBestValue ? "text-accent" : "text-foreground"
|
||||
)}
|
||||
title={`First year: $${registrar.registration_price.toFixed(2)}`}
|
||||
>
|
||||
${registrar.registration_price.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-4 text-right">
|
||||
<div className="flex items-center gap-1 justify-end">
|
||||
<span
|
||||
className={clsx(
|
||||
"tabular-nums cursor-help",
|
||||
hasRenewalTrap ? "text-amber-400" : "text-foreground-muted"
|
||||
)}
|
||||
title={hasRenewalTrap
|
||||
? `Renewal is ${(registrar.renewal_price / registrar.registration_price).toFixed(1)}x the registration price`
|
||||
: `Annual renewal: $${registrar.renewal_price.toFixed(2)}`}
|
||||
>
|
||||
${registrar.renewal_price.toFixed(2)}
|
||||
</span>
|
||||
{hasRenewalTrap && (
|
||||
<AlertTriangle
|
||||
className="w-3.5 h-3.5 text-amber-400 cursor-help"
|
||||
title={`Renewal trap: ${(registrar.renewal_price / registrar.registration_price).toFixed(1)}x registration price`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-4 text-right">
|
||||
<span
|
||||
className="text-foreground-muted tabular-nums cursor-help"
|
||||
title={`Transfer from another registrar: $${registrar.transfer_price.toFixed(2)}`}
|
||||
>
|
||||
${registrar.transfer_price.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-4 text-right">
|
||||
<a
|
||||
href={getRegistrarUrl(registrar.name)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 text-sm text-accent hover:text-accent/80 transition-colors"
|
||||
title={`Register at ${registrar.name}`}
|
||||
>
|
||||
Visit
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -690,7 +690,10 @@ export default function TldDetailPage() {
|
||||
|
||||
{/* Quick Stats - All data from table */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mt-8">
|
||||
<div className="p-4 bg-background-secondary/50 border border-border/50 rounded-xl">
|
||||
<div
|
||||
className="p-4 bg-background-secondary/50 border border-border/50 rounded-xl cursor-help"
|
||||
title="Lowest first-year registration price across all tracked registrars"
|
||||
>
|
||||
<p className="text-ui-xs text-foreground-subtle uppercase tracking-wider mb-1">Buy (1y)</p>
|
||||
{isAuthenticated ? (
|
||||
<p className="text-body-lg font-medium text-foreground tabular-nums">${details.pricing.min.toFixed(2)}</p>
|
||||
@ -698,7 +701,12 @@ export default function TldDetailPage() {
|
||||
<Shimmer className="h-6 w-16 mt-1" />
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4 bg-background-secondary/50 border border-border/50 rounded-xl">
|
||||
<div
|
||||
className="p-4 bg-background-secondary/50 border border-border/50 rounded-xl cursor-help"
|
||||
title={renewalInfo?.isTrap
|
||||
? `Warning: Renewal is ${renewalInfo.ratio.toFixed(1)}x the registration price`
|
||||
: 'Annual renewal price after first year'}
|
||||
>
|
||||
<p className="text-ui-xs text-foreground-subtle uppercase tracking-wider mb-1">Renew (1y)</p>
|
||||
{isAuthenticated ? (
|
||||
<div className="flex items-center gap-1">
|
||||
@ -706,14 +714,17 @@ export default function TldDetailPage() {
|
||||
${details.min_renewal_price.toFixed(2)}
|
||||
</p>
|
||||
{renewalInfo?.isTrap && (
|
||||
<AlertTriangle className="w-4 h-4 text-amber-400" />
|
||||
<AlertTriangle className="w-4 h-4 text-amber-400" title={`Renewal trap: ${renewalInfo.ratio.toFixed(1)}x registration`} />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Shimmer className="h-6 w-20 mt-1" />
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4 bg-background-secondary/50 border border-border/50 rounded-xl">
|
||||
<div
|
||||
className="p-4 bg-background-secondary/50 border border-border/50 rounded-xl cursor-help"
|
||||
title="Price change over the last 12 months"
|
||||
>
|
||||
<p className="text-ui-xs text-foreground-subtle uppercase tracking-wider mb-1">1y Change</p>
|
||||
{isAuthenticated ? (
|
||||
<p className={clsx(
|
||||
@ -728,7 +739,10 @@ export default function TldDetailPage() {
|
||||
<Shimmer className="h-6 w-14 mt-1" />
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4 bg-background-secondary/50 border border-border/50 rounded-xl">
|
||||
<div
|
||||
className="p-4 bg-background-secondary/50 border border-border/50 rounded-xl cursor-help"
|
||||
title="Price change over the last 3 years"
|
||||
>
|
||||
<p className="text-ui-xs text-foreground-subtle uppercase tracking-wider mb-1">3y Change</p>
|
||||
{isAuthenticated ? (
|
||||
<p className={clsx(
|
||||
@ -969,68 +983,102 @@ export default function TldDetailPage() {
|
||||
<th className="text-left text-ui-xs text-foreground-subtle font-medium uppercase tracking-wider px-5 py-4">
|
||||
Registrar
|
||||
</th>
|
||||
<th className="text-right text-ui-xs text-foreground-subtle font-medium uppercase tracking-wider px-5 py-4">
|
||||
<th className="text-right text-ui-xs text-foreground-subtle font-medium uppercase tracking-wider px-5 py-4 cursor-help" title="First year registration price">
|
||||
Register
|
||||
</th>
|
||||
<th className="text-right text-ui-xs text-foreground-subtle font-medium uppercase tracking-wider px-5 py-4 hidden sm:table-cell">
|
||||
<th className="text-right text-ui-xs text-foreground-subtle font-medium uppercase tracking-wider px-5 py-4 hidden sm:table-cell cursor-help" title="Annual renewal price">
|
||||
Renew
|
||||
</th>
|
||||
<th className="text-right text-ui-xs text-foreground-subtle font-medium uppercase tracking-wider px-5 py-4 hidden sm:table-cell">
|
||||
<th className="text-right text-ui-xs text-foreground-subtle font-medium uppercase tracking-wider px-5 py-4 hidden sm:table-cell cursor-help" title="Transfer from another registrar">
|
||||
Transfer
|
||||
</th>
|
||||
<th className="px-5 py-4 w-24"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border/30">
|
||||
{details.registrars.map((registrar, i) => (
|
||||
<tr key={registrar.name} className={clsx(
|
||||
"transition-colors group",
|
||||
i === 0 && "bg-accent/[0.03]"
|
||||
)}>
|
||||
<td className="px-5 py-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="text-body-sm font-medium text-foreground">{registrar.name}</span>
|
||||
{i === 0 && (
|
||||
<span className="text-ui-xs text-accent bg-accent/10 px-2 py-0.5 rounded-full font-medium">
|
||||
Best
|
||||
</span>
|
||||
{details.registrars.map((registrar, i) => {
|
||||
const hasRenewalTrap = registrar.renewal_price / registrar.registration_price > 1.5
|
||||
const isBestValue = i === 0 && !hasRenewalTrap
|
||||
|
||||
return (
|
||||
<tr key={registrar.name} className={clsx(
|
||||
"transition-colors group",
|
||||
isBestValue && "bg-accent/[0.03]"
|
||||
)}>
|
||||
<td className="px-5 py-4">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="text-body-sm font-medium text-foreground">{registrar.name}</span>
|
||||
{isBestValue && (
|
||||
<span
|
||||
className="text-ui-xs text-accent bg-accent/10 px-2 py-0.5 rounded-full font-medium cursor-help"
|
||||
title="Best overall value: lowest registration price without renewal trap"
|
||||
>
|
||||
Best
|
||||
</span>
|
||||
)}
|
||||
{i === 0 && hasRenewalTrap && (
|
||||
<span
|
||||
className="text-ui-xs text-amber-400 bg-amber-500/10 px-2 py-0.5 rounded-full font-medium cursor-help"
|
||||
title="Cheapest registration but high renewal costs"
|
||||
>
|
||||
Cheap Start
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-5 py-4 text-right">
|
||||
<span
|
||||
className={clsx(
|
||||
"text-body-sm font-medium tabular-nums cursor-help",
|
||||
isBestValue ? "text-accent" : "text-foreground"
|
||||
)}
|
||||
title={`First year: $${registrar.registration_price.toFixed(2)}`}
|
||||
>
|
||||
${registrar.registration_price.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-5 py-4 text-right hidden sm:table-cell">
|
||||
<span
|
||||
className={clsx(
|
||||
"text-body-sm tabular-nums cursor-help",
|
||||
hasRenewalTrap ? "text-amber-400" : "text-foreground-muted"
|
||||
)}
|
||||
title={hasRenewalTrap
|
||||
? `Renewal is ${(registrar.renewal_price / registrar.registration_price).toFixed(1)}x the registration price`
|
||||
: `Annual renewal: $${registrar.renewal_price.toFixed(2)}`}
|
||||
>
|
||||
${registrar.renewal_price.toFixed(2)}
|
||||
</span>
|
||||
{hasRenewalTrap && (
|
||||
<AlertTriangle
|
||||
className="inline-block ml-1.5 w-3.5 h-3.5 text-amber-400 cursor-help"
|
||||
title={`Renewal trap: ${(registrar.renewal_price / registrar.registration_price).toFixed(1)}x registration price`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-5 py-4 text-right">
|
||||
<span className={clsx(
|
||||
"text-body-sm font-medium tabular-nums",
|
||||
i === 0 ? "text-accent" : "text-foreground"
|
||||
)}>
|
||||
${registrar.registration_price.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-5 py-4 text-right hidden sm:table-cell">
|
||||
<span className="text-body-sm text-foreground-muted tabular-nums">
|
||||
${registrar.renewal_price.toFixed(2)}
|
||||
</span>
|
||||
{registrar.renewal_price > registrar.registration_price * 1.5 && (
|
||||
<AlertTriangle className="inline-block ml-1.5 w-3.5 h-3.5 text-amber-400" />
|
||||
)}
|
||||
</td>
|
||||
<td className="px-5 py-4 text-right hidden sm:table-cell">
|
||||
<span className="text-body-sm text-foreground-muted tabular-nums">
|
||||
${registrar.transfer_price.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-5 py-4">
|
||||
<a
|
||||
href={getRegistrarUrl(registrar.name, `example.${tld}`)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1.5 text-ui-sm text-foreground-muted hover:text-accent transition-colors opacity-0 group-hover:opacity-100"
|
||||
>
|
||||
Visit
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</td>
|
||||
<td className="px-5 py-4 text-right hidden sm:table-cell">
|
||||
<span
|
||||
className="text-body-sm text-foreground-muted tabular-nums cursor-help"
|
||||
title={`Transfer from another registrar: $${registrar.transfer_price.toFixed(2)}`}
|
||||
>
|
||||
${registrar.transfer_price.toFixed(2)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-5 py-4">
|
||||
<a
|
||||
href={getRegistrarUrl(registrar.name, `example.${tld}`)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1.5 text-ui-sm text-foreground-muted hover:text-accent transition-colors opacity-0 group-hover:opacity-100"
|
||||
title={`Register at ${registrar.name}`}
|
||||
>
|
||||
Visit
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user