fix: Tooltip behavior & Links (INTEL, MARKET, RADAR)

Changes:
- Fixed hover/tooltip issue: Tooltips now only trigger on the specific element, not the entire row.
- Added explicit 'Details' link to TLD table in INTEL view.
- Added clickable TLD badges in INTEL view.
- Standardized Tooltip implementation across all views.
- Ensured consistent 'Award-Winning' style for all interactive elements.
This commit is contained in:
2025-12-11 08:08:28 +01:00
parent cfbc84387a
commit 254c2b2cf0
3 changed files with 83 additions and 70 deletions

View File

@ -31,9 +31,9 @@ import Link from 'next/link'
function Tooltip({ children, content }: { children: React.ReactNode; content: string }) {
return (
<div className="relative flex items-center group">
<div className="relative flex items-center group/tooltip w-fit">
{children}
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-zinc-900 border border-zinc-800 rounded text-[10px] text-zinc-300 whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 shadow-xl">
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-zinc-900 border border-zinc-800 rounded text-[10px] text-zinc-300 whitespace-nowrap opacity-0 group-hover/tooltip:opacity-100 transition-opacity pointer-events-none z-50 shadow-xl">
{content}
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px border-4 border-transparent border-t-zinc-800" />
</div>
@ -340,7 +340,7 @@ export default function IntelPage() {
<div className="col-span-2 text-right"><SortableHeader label="Renewal" field="price" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="right" tooltip="Estimated annual renewal cost" /></div>
<div className="col-span-2 text-center"><SortableHeader label="Trend (1y)" field="change" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="center" /></div>
<div className="col-span-2 text-center"><SortableHeader label="Risk Level" field="risk" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="center" tooltip="Risk of price hikes or restrictions" /></div>
<div className="col-span-2 text-right"><span className="text-[10px] font-bold uppercase tracking-widest text-zinc-600 py-2 block">Provider</span></div>
<div className="col-span-2 text-right"><span className="text-[10px] font-bold uppercase tracking-widest text-zinc-600 py-2 block">Action</span></div>
</div>
<div className="divide-y divide-white/5">
{filteredData.map((tld) => {
@ -351,7 +351,9 @@ export default function IntelPage() {
<div key={tld.tld} className="grid grid-cols-12 gap-4 px-6 py-4 items-center hover:bg-white/[0.04] transition-all group relative">
{/* TLD */}
<div className="col-span-2">
<span className="font-mono font-bold text-white text-lg">.{tld.tld}</span>
<Link href={`/terminal/intel/${tld.tld}`} className="font-mono font-bold text-white text-lg hover:text-emerald-400 transition-colors">
.{tld.tld}
</Link>
</div>
{/* Price */}
@ -396,15 +398,21 @@ export default function IntelPage() {
</Tooltip>
</div>
{/* Provider */}
<div className="col-span-2 text-right">
{tld.cheapest_registrar ? (
<a href={tld.cheapest_registrar_url || '#'} target="_blank" className="text-xs text-zinc-500 hover:text-white transition-colors">
{tld.cheapest_registrar} <ExternalLink className="w-3 h-3 inline ml-0.5" />
</a>
) : (
<span className="text-xs text-zinc-600">-</span>
{/* Action / Provider */}
<div className="col-span-2 flex justify-end items-center gap-3">
{tld.cheapest_registrar && (
<Tooltip content={`Best price at ${tld.cheapest_registrar}`}>
<a href={tld.cheapest_registrar_url || '#'} target="_blank" className="text-xs text-zinc-500 hover:text-white transition-colors truncate max-w-[80px]">
{tld.cheapest_registrar}
</a>
</Tooltip>
)}
<Link
href={`/terminal/intel/${tld.tld}`}
className="w-8 h-8 flex items-center justify-center rounded-lg border border-zinc-800 text-zinc-400 hover:text-white hover:border-zinc-600 hover:bg-white/5 transition-all"
>
<ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
)
@ -417,40 +425,45 @@ export default function IntelPage() {
{filteredData.map((tld) => {
const isTrap = tld.min_renewal_price > tld.min_price * 1.5
return (
<div key={tld.tld} className="bg-zinc-900/40 border border-white/5 rounded-xl p-4 active:bg-zinc-900/60 transition-colors">
<div className="flex justify-between items-start mb-3">
<span className="font-mono font-bold text-white text-xl">.{tld.tld}</span>
<div className={clsx("px-2 py-1 rounded text-[10px] uppercase font-bold",
tld.risk_level === 'low' ? "bg-emerald-500/10 text-emerald-400" :
tld.risk_level === 'medium' ? "bg-amber-500/10 text-amber-400" :
"bg-red-500/10 text-red-400"
)}>
{tld.risk_level} Risk
<Link href={`/terminal/intel/${tld.tld}`} key={tld.tld}>
<div className="bg-zinc-900/40 border border-white/5 rounded-xl p-4 active:bg-zinc-900/60 transition-colors">
<div className="flex justify-between items-start mb-3">
<span className="font-mono font-bold text-white text-xl">.{tld.tld}</span>
<div className={clsx("px-2 py-1 rounded text-[10px] uppercase font-bold",
tld.risk_level === 'low' ? "bg-emerald-500/10 text-emerald-400" :
tld.risk_level === 'medium' ? "bg-amber-500/10 text-amber-400" :
"bg-red-500/10 text-red-400"
)}>
{tld.risk_level} Risk
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4 mb-3">
<div>
<div className="text-[10px] text-zinc-500 uppercase tracking-wider mb-0.5">Register</div>
<div className="font-mono text-lg font-medium text-white">{formatPrice(tld.min_price)}</div>
<div className="grid grid-cols-2 gap-4 mb-3">
<div>
<div className="text-[10px] text-zinc-500 uppercase tracking-wider mb-0.5">Register</div>
<div className="font-mono text-lg font-medium text-white">{formatPrice(tld.min_price)}</div>
</div>
<div className="text-right">
<div className="text-[10px] text-zinc-500 uppercase tracking-wider mb-0.5">Renew</div>
<div className={clsx("font-mono text-lg font-medium", isTrap ? "text-amber-400" : "text-zinc-400")}>
{formatPrice(tld.min_renewal_price)}
</div>
</div>
</div>
<div className="text-right">
<div className="text-[10px] text-zinc-500 uppercase tracking-wider mb-0.5">Renew</div>
<div className={clsx("font-mono text-lg font-medium", isTrap ? "text-amber-400" : "text-zinc-400")}>
{formatPrice(tld.min_renewal_price)}
<div className="pt-3 border-t border-white/5 flex items-center justify-between">
<div className="flex items-center gap-1 text-xs text-zinc-500">
<span>Provider:</span>
<span className="text-white font-medium truncate max-w-[100px]">
{tld.cheapest_registrar || '-'}
</span>
</div>
<div className="flex items-center gap-1 text-emerald-400 text-xs font-bold">
Details <ArrowRight className="w-3 h-3" />
</div>
</div>
</div>
{tld.cheapest_registrar && (
<div className="pt-3 border-t border-white/5 text-center">
<span className="text-xs text-zinc-500">Best price at </span>
<a href={tld.cheapest_registrar_url || '#'} className="text-xs text-white font-medium hover:underline">
{tld.cheapest_registrar}
</a>
</div>
)}
</div>
</Link>
)
})}
</div>

View File

@ -141,9 +141,9 @@ function parseTimeToSeconds(timeStr?: string): number {
// Tooltip Component
function Tooltip({ children, content }: { children: React.ReactNode; content: string }) {
return (
<div className="relative flex items-center group">
<div className="relative flex items-center group/tooltip w-fit">
{children}
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-zinc-900 border border-zinc-800 rounded text-[10px] text-zinc-300 whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 shadow-xl">
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-zinc-900 border border-zinc-800 rounded text-[10px] text-zinc-300 whitespace-nowrap opacity-0 group-hover/tooltip:opacity-100 transition-opacity pointer-events-none z-50 shadow-xl">
{content}
{/* Arrow */}
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px border-4 border-transparent border-t-zinc-800" />
@ -313,7 +313,7 @@ export default function MarketPage() {
// Watchlist
const [trackedDomains, setTrackedDomains] = useState<Set<string>>(new Set())
const [trackingInProgress, setTrackingInProgress] = useState<string | null>(null)
// Load
const loadData = useCallback(async () => {
setLoading(true)
@ -378,7 +378,7 @@ export default function MarketPage() {
if (priceRange === 'high') filtered = filtered.filter(item => item.price >= 1000)
if (searchQuery) filtered = filtered.filter(item => item.domain.toLowerCase().includes(searchQuery.toLowerCase()))
const mult = sortDirection === 'asc' ? 1 : -1
const mult = sortDirection === 'asc' ? 1 : -1
filtered.sort((a, b) => {
switch (sortField) {
case 'domain': return mult * a.domain.localeCompare(b.domain)
@ -386,9 +386,9 @@ export default function MarketPage() {
case 'price': return mult * (a.price - b.price)
case 'time': return mult * (parseTimeToSeconds(a.timeLeft) - parseTimeToSeconds(b.timeLeft))
case 'source': return mult * a.source.localeCompare(b.source)
default: return 0
}
})
default: return 0
}
})
return filtered
}, [auctions, hideSpam, pounceOnly, priceRange, searchQuery, sortField, sortDirection])
@ -486,7 +486,7 @@ export default function MarketPage() {
<div className="col-span-2 text-right"><SortableHeader label="Price" field="price" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="right" tooltip="Current highest bid or buy-now price" /></div>
<div className="col-span-2 text-center"><SortableHeader label="Time" field="time" currentSort={sortField} currentDirection={sortDirection} onSort={handleSort} align="center" tooltip="Time remaining until auction ends" /></div>
<div className="col-span-2 text-right"><span className="text-[10px] font-bold uppercase tracking-widest text-zinc-600 py-2 block">Action</span></div>
</div>
</div>
<div className="divide-y divide-white/5">
{marketItems.map((item) => {
const timeLeftSec = parseTimeToSeconds(item.timeLeft)
@ -528,7 +528,7 @@ export default function MarketPage() {
{item.timeLeft}
</div>
</Tooltip>
</div>
</div>
{/* Actions */}
<div className="col-span-2 flex items-center justify-end opacity-0 group-hover:opacity-100 transition-opacity">
{/* Monitor Button - Distinct Style & Spacing */}
@ -555,7 +555,7 @@ export default function MarketPage() {
</a>
</Tooltip>
</div>
</div>
</div>
)
})}
</div>
@ -566,7 +566,7 @@ export default function MarketPage() {
{marketItems.map((item) => {
const timeLeftSec = parseTimeToSeconds(item.timeLeft)
const isUrgent = timeLeftSec < 3600
return (
return (
<div key={item.id} className="bg-zinc-900/40 border border-white/5 rounded-xl p-4 active:bg-zinc-900/60 transition-colors">
<div className="flex justify-between items-start mb-3">
<div className="flex items-center gap-2">
@ -588,13 +588,13 @@ export default function MarketPage() {
{item.timeLeft}
</div>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<button
<button
onClick={() => handleTrack(item.domain)}
disabled={trackedDomains.has(item.domain)}
className={clsx(
className={clsx(
"flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-medium border transition-all",
trackedDomains.has(item.domain)
? "bg-emerald-500/10 text-emerald-400 border-emerald-500/20"
@ -605,27 +605,27 @@ export default function MarketPage() {
<><Check className="w-4 h-4" /> Tracked</>
) : (
<><Eye className="w-4 h-4" /> Watch</>
)}
</button>
<a
)}
</button>
<a
href={item.affiliateUrl || '#'}
target="_blank"
rel="noopener noreferrer"
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-bold bg-white text-black hover:bg-zinc-200 active:scale-95 transition-all shadow-lg shadow-white/5"
>
>
{item.isPounce ? 'Buy Now' : 'Place Bid'}
<ExternalLink className="w-3 h-3 opacity-50" />
</a>
</div>
</div>
)
})}
</div>
</a>
</div>
</div>
)
})}
</div>
</>
)}
</div>
</div>
</div>
</div>
</TerminalLayout>
)
}

View File

@ -40,9 +40,9 @@ import Link from 'next/link'
function Tooltip({ children, content }: { children: React.ReactNode; content: string }) {
return (
<div className="relative flex items-center group">
<div className="relative flex items-center group/tooltip w-fit">
{children}
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-zinc-900 border border-zinc-800 rounded text-[10px] text-zinc-300 whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 shadow-xl">
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-zinc-900 border border-zinc-800 rounded text-[10px] text-zinc-300 whitespace-nowrap opacity-0 group-hover/tooltip:opacity-100 transition-opacity pointer-events-none z-50 shadow-xl">
{content}
<div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px border-4 border-transparent border-t-zinc-800" />
</div>