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:
@ -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>
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user