Hunt: make Search default tab, reorder tabs
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

- Search is now first/default tab
- Tab order: Search → Drops → Auctions → Trends → Forge
- AnalyzePanel: improved tooltips and explanations
- Better Fast/Cached mode descriptions
- Enhanced Health Score display with explanations
This commit is contained in:
2025-12-18 07:43:29 +01:00
parent 87310f4fa2
commit 4c08c92780
2 changed files with 194 additions and 111 deletions

View File

@ -46,10 +46,10 @@ type HuntTab = 'auctions' | 'drops' | 'search' | 'trends' | 'forge'
// ============================================================================ // ============================================================================
const TABS: Array<{ key: HuntTab; label: string; shortLabel: string; icon: any; color: string }> = [ const TABS: Array<{ key: HuntTab; label: string; shortLabel: string; icon: any; color: string }> = [
{ key: 'auctions', label: 'Auctions', shortLabel: 'Auctions', icon: Gavel, color: 'accent' }, { key: 'search', label: 'Search', shortLabel: 'Search', icon: Search, color: 'accent' },
{ key: 'drops', label: 'Drops', shortLabel: 'Drops', icon: Download, color: 'blue' }, { key: 'drops', label: 'Drops', shortLabel: 'Drops', icon: Download, color: 'blue' },
{ key: 'search', label: 'Search', shortLabel: 'Search', icon: Search, color: 'white' }, { key: 'auctions', label: 'Auctions', shortLabel: 'Auctions', icon: Gavel, color: 'orange' },
{ key: 'trends', label: 'Trends', shortLabel: 'Trends', icon: Flame, color: 'orange' }, { key: 'trends', label: 'Trends', shortLabel: 'Trends', icon: Flame, color: 'rose' },
{ key: 'forge', label: 'Forge', shortLabel: 'Forge', icon: Wand2, color: 'purple' }, { key: 'forge', label: 'Forge', shortLabel: 'Forge', icon: Wand2, color: 'purple' },
] ]
@ -60,7 +60,7 @@ const TABS: Array<{ key: HuntTab; label: string; shortLabel: string; icon: any;
export default function HuntPage() { export default function HuntPage() {
const { user, subscription, logout, checkAuth } = useStore() const { user, subscription, logout, checkAuth } = useStore()
const { toast, showToast, hideToast } = useToast() const { toast, showToast, hideToast } = useToast()
const [tab, setTab] = useState<HuntTab>('auctions') const [tab, setTab] = useState<HuntTab>('search')
// Mobile Menu State // Mobile Menu State
const [menuOpen, setMenuOpen] = useState(false) const [menuOpen, setMenuOpen] = useState(false)
@ -150,6 +150,8 @@ export default function HuntPage() {
? 'border-blue-500/40 bg-blue-500/10 text-blue-400' ? 'border-blue-500/40 bg-blue-500/10 text-blue-400'
: t.color === 'orange' : t.color === 'orange'
? 'border-orange-500/40 bg-orange-500/10 text-orange-400' ? 'border-orange-500/40 bg-orange-500/10 text-orange-400'
: t.color === 'rose'
? 'border-rose-500/40 bg-rose-500/10 text-rose-400'
: t.color === 'purple' : t.color === 'purple'
? 'border-purple-500/40 bg-purple-500/10 text-purple-400' ? 'border-purple-500/40 bg-purple-500/10 text-purple-400'
: 'border-white/40 bg-white/10 text-white' : 'border-white/40 bg-white/10 text-white'
@ -192,6 +194,7 @@ export default function HuntPage() {
blue: { active: 'border-blue-500 bg-blue-500/10 text-blue-400', inactive: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]' }, blue: { active: 'border-blue-500 bg-blue-500/10 text-blue-400', inactive: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]' },
white: { active: 'border-white/40 bg-white/10 text-white', inactive: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]' }, white: { active: 'border-white/40 bg-white/10 text-white', inactive: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]' },
orange: { active: 'border-orange-500 bg-orange-500/10 text-orange-400', inactive: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]' }, orange: { active: 'border-orange-500 bg-orange-500/10 text-orange-400', inactive: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]' },
rose: { active: 'border-rose-500 bg-rose-500/10 text-rose-400', inactive: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]' },
purple: { active: 'border-purple-500 bg-purple-500/10 text-purple-400', inactive: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]' }, purple: { active: 'border-purple-500 bg-purple-500/10 text-purple-400', inactive: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]' },
} }
const classes = colorClasses[t.color] || colorClasses.white const classes = colorClasses[t.color] || colorClasses.white

View File

@ -56,7 +56,8 @@ function getSectionConfig(key: string) {
color: 'text-blue-400', color: 'text-blue-400',
bg: 'bg-blue-500/10', bg: 'bg-blue-500/10',
border: 'border-blue-500/20', border: 'border-blue-500/20',
description: 'Age, backlinks, and trust signals' description: 'Domain age, backlink profile, and trust signals',
tooltip: 'Authority measures how established and trusted the domain is. Older domains with quality backlinks rank better.'
} }
case 'market': case 'market':
return { return {
@ -64,7 +65,8 @@ function getSectionConfig(key: string) {
color: 'text-emerald-400', color: 'text-emerald-400',
bg: 'bg-emerald-500/10', bg: 'bg-emerald-500/10',
border: 'border-emerald-500/20', border: 'border-emerald-500/20',
description: 'Search volume, CPC, and TLD availability' description: 'Search demand, ad value, and TLD availability',
tooltip: 'Market data shows commercial potential. High search volume + CPC = strong buyer intent.'
} }
case 'risk': case 'risk':
return { return {
@ -72,7 +74,8 @@ function getSectionConfig(key: string) {
color: 'text-amber-400', color: 'text-amber-400',
bg: 'bg-amber-500/10', bg: 'bg-amber-500/10',
border: 'border-amber-500/20', border: 'border-amber-500/20',
description: 'Trademark, blacklist, and archive checks' description: 'Trademark conflicts, blacklists, and history',
tooltip: 'Risk checks help avoid legal issues and spam penalties. Always clear before buying.'
} }
case 'value': case 'value':
return { return {
@ -80,7 +83,8 @@ function getSectionConfig(key: string) {
color: 'text-violet-400', color: 'text-violet-400',
bg: 'bg-violet-500/10', bg: 'bg-violet-500/10',
border: 'border-violet-500/20', border: 'border-violet-500/20',
description: 'Estimated value and comparable sales' description: 'Estimated worth and recent comparable sales',
tooltip: 'Value estimation based on length, keywords, extension, and actual market sales.'
} }
case 'vision': case 'vision':
return { return {
@ -88,7 +92,8 @@ function getSectionConfig(key: string) {
color: 'text-accent', color: 'text-accent',
bg: 'bg-accent/10', bg: 'bg-accent/10',
border: 'border-accent/20', border: 'border-accent/20',
description: 'AI-powered business concept and buyer analysis' description: 'AI business concepts and ideal buyer profiles',
tooltip: 'AI-powered analysis suggesting business uses and potential buyers for this domain.'
} }
default: default:
return { return {
@ -96,7 +101,8 @@ function getSectionConfig(key: string) {
color: 'text-white/50', color: 'text-white/50',
bg: 'bg-white/5', bg: 'bg-white/5',
border: 'border-white/10', border: 'border-white/10',
description: '' description: '',
tooltip: ''
} }
} }
} }
@ -125,18 +131,40 @@ function isMatrix(item: AnalyzeItem) {
function getItemTooltip(key: string): string { function getItemTooltip(key: string): string {
const tooltips: Record<string, string> = { const tooltips: Record<string, string> = {
domain_age: 'How long the domain has been registered. Older = more authority.', // Authority
backlinks: 'Number of external websites linking to this domain.', domain_age: 'Registration age of the domain. Older domains typically have more authority and trust in search engines. 5+ years is excellent.',
trust_flow: 'Quality score of backlinks (0-100). Higher = more trusted.', age: 'Registration age of the domain. Older domains typically have more authority and trust in search engines. 5+ years is excellent.',
radio_test: 'How easy is the domain to spell when heard verbally.', backlinks: 'Number of external websites linking to this domain. More backlinks = higher authority. Quality matters more than quantity.',
search_volume: 'Monthly Google searches for the main keyword.', trust_flow: 'Majestic Trust Flow score (0-100). Measures the quality of backlinks. Higher = more trusted by search engines.',
cpc: 'Cost-per-click for ads on this keyword. Higher = more commercial value.', citation_flow: 'Majestic Citation Flow score (0-100). Measures the quantity of backlinks regardless of quality.',
tld_matrix: 'Availability of this name across popular TLDs.', radio_test: 'Pronounceability test. Can someone spell the domain correctly after hearing it once? Important for word-of-mouth.',
trademark: 'Potential trademark conflicts. Clear = safe to use.', syllables: 'Number of syllables. Fewer is better - 2-3 syllables is ideal for brandability.',
blacklist: 'Whether the domain appears on spam/malware lists.',
archive: 'Wayback Machine history. Shows previous usage.', // Market
estimated_value: 'AI-estimated market value based on comparables.', search_volume: 'Monthly Google searches for the main keyword. Higher = more organic traffic potential.',
comps: 'Similar domains that have sold recently.', cpc: 'Google Ads Cost-Per-Click. Higher CPC = more commercial intent. $5+ indicates strong buyer intent.',
tld_matrix: 'Availability across popular TLDs (.com, .net, .org etc). Green = available for registration.',
competition: 'SEO competition level. Lower = easier to rank. "Low" is ideal for new sites.',
// Risk
trademark: 'USPTO trademark database check. "Clear" means no conflicts found. Always verify before buying.',
blacklist: 'Spam and malware blacklist check. "Clean" means domain is not flagged by security services.',
archive: 'Wayback Machine first capture date. Shows domain history and previous content.',
spam_score: 'Moz Spam Score (0-100). Lower = cleaner history. Above 30% is concerning.',
// Value
estimated_value: 'AI-estimated market value based on comparable sales, length, keywords, and extension.',
comps: 'Recently sold domains with similar characteristics. Used to determine market value.',
price_range: 'Suggested listing price range based on market analysis.',
// DNS
dns_records: 'Active DNS records. Shows if domain is currently configured.',
nameservers: 'Current nameservers. Indicates where domain is hosted.',
mx_records: 'Mail exchange records. Shows if email is configured.',
// General
length: 'Character count. Shorter is generally more valuable. Under 8 characters is premium.',
extension: 'Top-level domain (.com, .io, etc). .com is most valuable, followed by ccTLDs and new gTLDs.',
} }
return tooltips[key] || '' return tooltips[key] || ''
} }
@ -329,66 +357,95 @@ export function AnalyzePanel() {
{/* Score Bar */} {/* Score Bar */}
{overallScore && !loading && ( {overallScore && !loading && (
<div className="px-4 pb-4"> <div className="px-4 pb-4">
<div className="flex items-center gap-4 p-3 bg-white/[0.02] border border-white/[0.08]"> <div className="p-4 bg-white/[0.02] border border-white/[0.08]">
<div className={clsx( <div className="flex items-center gap-4">
"text-3xl font-bold font-mono", <div
className={clsx(
"w-16 h-16 flex items-center justify-center border-2",
overallScore.score >= 70 ? "border-accent bg-accent/10" :
overallScore.score >= 40 ? "border-amber-400 bg-amber-400/10" : "border-rose-500 bg-rose-500/10"
)}
title={`Health Score: ${overallScore.score}/100. ${overallScore.score >= 70 ? 'Excellent - safe to buy' : overallScore.score >= 40 ? 'Moderate - review warnings' : 'Poor - significant issues'}`}
>
<span className={clsx(
"text-2xl font-bold font-mono",
overallScore.score >= 70 ? "text-accent" : overallScore.score >= 40 ? "text-amber-400" : "text-rose-400" overallScore.score >= 70 ? "text-accent" : overallScore.score >= 40 ? "text-amber-400" : "text-rose-400"
)}> )}>
{overallScore.score} {overallScore.score}
</span>
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider mb-1.5">Health Score</div> <div className="flex items-center justify-between mb-2">
<div className="h-2 bg-white/[0.05] overflow-hidden flex"> <div className="text-xs font-bold text-white uppercase tracking-wider">Health Score</div>
<div className="text-[10px] font-mono text-white/40">
{overallScore.score >= 70 ? '✓ Good to buy' : overallScore.score >= 40 ? '⚠ Review issues' : '⛔ High risk'}
</div>
</div>
<div className="h-2.5 bg-white/[0.05] overflow-hidden flex mb-2">
<div <div
className="h-full bg-accent transition-all" className="h-full bg-accent transition-all"
style={{ width: `${(overallScore.pass / overallScore.total) * 100}%` }} style={{ width: `${(overallScore.pass / overallScore.total) * 100}%` }}
title={`${overallScore.pass} passed checks`}
/> />
<div <div
className="h-full bg-amber-400 transition-all" className="h-full bg-amber-400 transition-all"
style={{ width: `${(overallScore.warn / overallScore.total) * 100}%` }} style={{ width: `${(overallScore.warn / overallScore.total) * 100}%` }}
title={`${overallScore.warn} warnings`}
/> />
<div <div
className="h-full bg-rose-500 transition-all" className="h-full bg-rose-500 transition-all"
style={{ width: `${(overallScore.fail / overallScore.total) * 100}%` }} style={{ width: `${(overallScore.fail / overallScore.total) * 100}%` }}
title={`${overallScore.fail} failed checks`}
/> />
</div> </div>
<div className="flex items-center gap-4 text-xs font-mono">
<span className="text-accent flex items-center gap-1.5" title="Checks passed - no issues found">
<CheckCircle2 className="w-3.5 h-3.5" /> {overallScore.pass} passed
</span>
<span className="text-amber-400 flex items-center gap-1.5" title="Warnings - review before buying">
<AlertTriangle className="w-3.5 h-3.5" /> {overallScore.warn} warnings
</span>
<span className="text-rose-400 flex items-center gap-1.5" title="Failed checks - potential problems">
<XCircle className="w-3.5 h-3.5" /> {overallScore.fail} failed
</span>
</div>
</div> </div>
<div className="flex items-center gap-3 text-xs font-mono shrink-0">
<span className="text-accent flex items-center gap-1" title="Passed checks">
<CheckCircle2 className="w-3.5 h-3.5" /> {overallScore.pass}
</span>
<span className="text-amber-400 flex items-center gap-1" title="Warnings">
<AlertTriangle className="w-3.5 h-3.5" /> {overallScore.warn}
</span>
<span className="text-rose-400 flex items-center gap-1" title="Failed checks">
<XCircle className="w-3.5 h-3.5" /> {overallScore.fail}
</span>
</div> </div>
</div> </div>
</div> </div>
)} )}
{/* Controls */} {/* Controls with Explanations */}
<div className="px-4 pb-3 flex items-center gap-2"> <div className="px-4 pb-3">
<div className="flex items-center gap-2 flex-wrap">
<button <button
onClick={() => setFastMode(!fastMode)} onClick={() => setFastMode(!fastMode)}
className={clsx( className={clsx(
"flex items-center gap-1.5 px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider border transition-all", "flex items-center gap-1.5 px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider border transition-all group relative",
fastMode fastMode
? "border-accent/30 bg-accent/10 text-accent" ? "border-accent/30 bg-accent/10 text-accent"
: "border-white/[0.08] text-white/40 hover:text-white hover:bg-white/[0.05]" : "border-white/[0.08] text-white/40 hover:text-white hover:bg-white/[0.05]"
)} )}
title="Fast mode uses DNS-only checks for speed" title="Fast Mode: DNS-only checks (~2 sec). Skips WHOIS/RDAP for speed. Fewer details but instant results."
> >
<Zap className="w-3.5 h-3.5" /> <Zap className="w-3.5 h-3.5" />
Fast Fast Mode
</button> </button>
{data?.cached && ( {data?.cached && (
<span className="text-[10px] font-mono text-white/30 px-2 py-1.5 border border-white/[0.08] bg-white/[0.02] flex items-center gap-1.5"> <span
className="text-[10px] font-mono text-white/30 px-2 py-1.5 border border-white/[0.08] bg-white/[0.02] flex items-center gap-1.5 cursor-help"
title="Cached Result: Data from previous analysis (saves time). Click refresh ↻ for live data."
>
<Clock className="w-3 h-3" /> <Clock className="w-3 h-3" />
Cached Cached Result
</span> </span>
)} )}
{fastMode && (
<span className="text-[10px] font-mono text-white/30 italic">
DNS-only, limited details
</span>
)}
</div>
</div> </div>
</div> </div>
@ -431,29 +488,37 @@ export function AnalyzePanel() {
<button <button
onClick={() => toggleSection(section.key)} onClick={() => toggleSection(section.key)}
className={clsx( className={clsx(
"w-full px-4 py-3 flex items-center justify-between transition-colors", "w-full px-4 py-3 flex items-center justify-between transition-colors group",
config.bg, "hover:brightness-110" config.bg, "hover:brightness-110"
)} )}
title={(config as any).tooltip || ''}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className={clsx("w-8 h-8 flex items-center justify-center border", config.border)}>
<SectionIcon className={clsx("w-4 h-4", config.color)} /> <SectionIcon className={clsx("w-4 h-4", config.color)} />
</div>
<div className="text-left"> <div className="text-left">
<div className="flex items-center gap-2">
<span className={clsx("text-xs font-bold uppercase tracking-wider", config.color)}> <span className={clsx("text-xs font-bold uppercase tracking-wider", config.color)}>
{section.title} {section.title}
</span> </span>
<div className="text-[10px] font-mono text-white/30 mt-0.5"> <span title={(config as any).tooltip || ''}>
<Info className="w-3 h-3 text-white/20 opacity-0 group-hover:opacity-100 transition-opacity" />
</span>
</div>
<div className="text-[10px] font-mono text-white/40 mt-0.5">
{config.description} {config.description}
</div> </div>
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{section.key !== 'vision' && ( {section.key !== 'vision' && section.items.length > 0 && (
<span className="text-[10px] font-mono text-white/30"> <span className="text-[10px] font-mono text-white/40 px-2 py-0.5 bg-white/5 border border-white/[0.06]">
{section.items.length} {section.items.length} checks
</span> </span>
)} )}
{section.key === 'vision' && ( {section.key === 'vision' && (
<span className="text-[10px] font-mono text-accent px-1.5 py-0.5 bg-accent/10">AI</span> <span className="text-[10px] font-mono text-accent px-2 py-0.5 bg-accent/10 border border-accent/20">AI Powered</span>
)} )}
<ChevronRight className={clsx( <ChevronRight className={clsx(
"w-4 h-4 text-white/30 transition-transform", "w-4 h-4 text-white/30 transition-transform",
@ -479,12 +544,13 @@ export function AnalyzePanel() {
return ( return (
<div <div
key={item.key} key={item.key}
className="px-4 py-3 hover:bg-white/[0.02] transition-colors group" className="px-4 py-3.5 hover:bg-white/[0.02] transition-colors group"
title={tooltip || undefined}
> >
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
{/* Status Indicator */} {/* Status Indicator */}
<div className={clsx( <div className={clsx(
"w-8 h-8 flex items-center justify-center shrink-0 border", "w-9 h-9 flex items-center justify-center shrink-0 border",
statusStyle.bg, statusStyle.border statusStyle.bg, statusStyle.border
)}> )}>
{StatusIcon && <StatusIcon className={clsx("w-4 h-4", statusStyle.text)} />} {StatusIcon && <StatusIcon className={clsx("w-4 h-4", statusStyle.text)} />}
@ -492,20 +558,25 @@ export function AnalyzePanel() {
{/* Content */} {/* Content */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2 mb-1"> <div className="flex items-center justify-between gap-2 mb-1.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm font-medium text-white"> <span className="text-sm font-semibold text-white">
{item.label} {item.label}
</span> </span>
{tooltip && ( {tooltip && (
<span title={tooltip}> <span
<Info className="w-3.5 h-3.5 text-white/20 opacity-0 group-hover:opacity-100 transition-opacity cursor-help" /> title={tooltip}
className="cursor-help"
>
<Info className="w-3.5 h-3.5 text-white/20 group-hover:text-white/40 transition-colors" />
</span> </span>
)} )}
</div> </div>
<span className="text-[9px] font-mono text-white/30 uppercase"> {item.source && (
<span className="text-[9px] font-mono text-white/30 uppercase px-1.5 py-0.5 bg-white/[0.03] border border-white/[0.06]">
{item.source} {item.source}
</span> </span>
)}
</div> </div>
{/* Value */} {/* Value */}
@ -521,7 +592,7 @@ export function AnalyzePanel() {
? "border-accent/20 bg-accent/5 text-accent" ? "border-accent/20 bg-accent/5 text-accent"
: "border-white/[0.06] bg-white/[0.02] text-white/40" : "border-white/[0.06] bg-white/[0.02] text-white/40"
)} )}
title={row.status === 'available' ? 'Available' : 'Taken'} title={row.status === 'available' ? 'Available for registration' : '✗ Already registered'}
> >
<span className="truncate">.{String(row.domain).split('.').pop()}</span> <span className="truncate">.{String(row.domain).split('.').pop()}</span>
{row.status === 'available' && <Check className="w-3 h-3 shrink-0 ml-1" />} {row.status === 'available' && <Check className="w-3 h-3 shrink-0 ml-1" />}
@ -530,21 +601,30 @@ export function AnalyzePanel() {
</div> </div>
) : ( ) : (
<div className={clsx( <div className={clsx(
"text-sm font-mono", "text-base font-mono font-medium",
item.status === 'pass' ? "text-white/70" : item.status === 'pass' ? "text-accent" :
item.status === 'warn' ? "text-amber-300" : item.status === 'warn' ? "text-amber-400" :
item.status === 'fail' ? "text-rose-300" : "text-white/40" item.status === 'fail' ? "text-rose-400" : "text-white/60"
)}> )}>
{formatValue(item.value)} {formatValue(item.value)}
</div> </div>
)} )}
</div> </div>
{/* Inline Explanation for important items */}
{tooltip && item.status !== 'pass' && (
<div className="mt-1.5 text-[10px] font-mono text-white/30 leading-relaxed">
{item.status === 'warn' && '⚠️ '}
{item.status === 'fail' && '⛔ '}
{tooltip.split('.')[0]}.
</div>
)}
{/* Details Toggle */} {/* Details Toggle */}
{item.details && Object.keys(item.details).length > 0 && ( {item.details && Object.keys(item.details).length > 0 && (
<details className="mt-2"> <details className="mt-2">
<summary className="text-[10px] font-mono text-white/30 cursor-pointer hover:text-white/50 select-none"> <summary className="text-[10px] font-mono text-white/30 cursor-pointer hover:text-white/50 select-none">
Raw data Show raw data
</summary> </summary>
<pre className="mt-2 text-[10px] font-mono text-white/40 bg-black/50 border border-white/[0.06] p-3 overflow-x-auto max-h-32"> <pre className="mt-2 text-[10px] font-mono text-white/40 bg-black/50 border border-white/[0.06] p-3 overflow-x-auto max-h-32">
{JSON.stringify(item.details, null, 2)} {JSON.stringify(item.details, null, 2)}