Improve Portfolio UX and redesign AnalyzePanel
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
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
- Move Portfolio tabs directly under subtitle (above filters line) - Add Health Detail Overlay when clicking health score (like Watchlist) - Redesign AnalyzePanel: wider (600-680px), larger text, better contrast - Improved section headers with colored backgrounds - Larger status indicators and score display - Better spacing and readability throughout
This commit is contained in:
@ -30,6 +30,9 @@ import {
|
||||
Calendar,
|
||||
Edit3,
|
||||
CheckCircle,
|
||||
CheckCircle2,
|
||||
XCircle,
|
||||
AlertTriangle,
|
||||
Copy,
|
||||
Check,
|
||||
Navigation,
|
||||
@ -686,6 +689,9 @@ export default function PortfolioPage() {
|
||||
// Mobile
|
||||
const [navDrawerOpen, setNavDrawerOpen] = useState(false)
|
||||
const [expandedRow, setExpandedRow] = useState<number | null>(null)
|
||||
|
||||
// Health detail overlay
|
||||
const [selectedHealthDomain, setSelectedHealthDomain] = useState<PortfolioDomain | null>(null)
|
||||
|
||||
const tier = subscription?.tier || 'scout'
|
||||
const tierName = subscription?.tier_name || tier
|
||||
@ -949,9 +955,12 @@ export default function PortfolioPage() {
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => handleHealthCheck(domain.domain)}
|
||||
onClick={() => {
|
||||
setSelectedHealthDomain(domain)
|
||||
if (!health) handleHealthCheck(domain.domain)
|
||||
}}
|
||||
disabled={isChecking}
|
||||
title={health ? `${cfg.label} • Score ${health.score}/100` : 'Run health check'}
|
||||
title={health ? `${cfg.label} • Score ${health.score}/100 • Click for details` : 'Run health check'}
|
||||
className={clsx(
|
||||
"flex items-center gap-1 px-2 py-1 text-[10px] font-mono uppercase border transition-colors",
|
||||
cfg.color, cfg.bg, cfg.border,
|
||||
@ -1109,18 +1118,53 @@ export default function PortfolioPage() {
|
||||
|
||||
{/* DESKTOP HEADER */}
|
||||
<section className="hidden lg:block px-10 pt-10 pb-6">
|
||||
<div className="flex items-end justify-between gap-8">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-1.5 h-1.5 bg-accent animate-pulse" />
|
||||
<span className="text-[10px] font-mono tracking-[0.2em] text-accent uppercase">Portfolio Manager</span>
|
||||
<div className="flex items-start justify-between gap-8">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-1.5 h-1.5 bg-accent animate-pulse" />
|
||||
<span className="text-[10px] font-mono tracking-[0.2em] text-accent uppercase">Portfolio Manager</span>
|
||||
</div>
|
||||
<h1 className="font-display text-[2.5rem] leading-[1] tracking-[-0.02em] text-white">
|
||||
My Portfolio
|
||||
</h1>
|
||||
<p className="text-sm text-white/40 font-mono max-w-lg mt-2">
|
||||
Track your domain investments. Add purchase details, monitor values, verify ownership, and list for sale.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* TABS - Directly under subtitle */}
|
||||
<div className="flex gap-2 pt-2">
|
||||
<button
|
||||
onClick={() => setActiveTab('assets')}
|
||||
className={clsx(
|
||||
'flex items-center gap-2 px-4 py-2.5 border transition-all',
|
||||
activeTab === 'assets'
|
||||
? 'border-accent bg-accent/10 text-accent'
|
||||
: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]'
|
||||
)}
|
||||
>
|
||||
<Briefcase className="w-4 h-4" />
|
||||
<span className="text-xs font-bold uppercase tracking-wider">Assets</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('financials')}
|
||||
className={clsx(
|
||||
'flex items-center gap-2 px-4 py-2.5 border transition-all',
|
||||
activeTab === 'financials'
|
||||
? 'border-orange-500 bg-orange-500/10 text-orange-400'
|
||||
: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]'
|
||||
)}
|
||||
>
|
||||
<Wallet className="w-4 h-4" />
|
||||
<span className="text-xs font-bold uppercase tracking-wider">Financials</span>
|
||||
{stats.upcoming30dCost > 0 && (
|
||||
<span className="px-1.5 py-0.5 text-[9px] bg-orange-500/20 text-orange-400 border border-orange-500/30">
|
||||
${Math.round(stats.upcoming30dCost)}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<h1 className="font-display text-[2.5rem] leading-[1] tracking-[-0.02em] text-white">
|
||||
My Portfolio
|
||||
</h1>
|
||||
<p className="text-sm text-white/40 font-mono max-w-lg">
|
||||
Track your domain investments. Add purchase details, monitor values, verify ownership, and list for sale.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-8">
|
||||
@ -1151,41 +1195,8 @@ export default function PortfolioPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* TABS - Matching Hunt page style */}
|
||||
<section className="px-4 lg:px-10 py-4 border-y border-white/[0.08] bg-white/[0.01]">
|
||||
<div className="flex gap-2 mb-4">
|
||||
<button
|
||||
onClick={() => setActiveTab('assets')}
|
||||
className={clsx(
|
||||
'flex items-center gap-2 px-4 py-2.5 border transition-all',
|
||||
activeTab === 'assets'
|
||||
? 'border-accent bg-accent/10 text-accent'
|
||||
: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]'
|
||||
)}
|
||||
>
|
||||
<Briefcase className="w-4 h-4" />
|
||||
<span className="text-xs font-bold uppercase tracking-wider">Assets</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('financials')}
|
||||
className={clsx(
|
||||
'flex items-center gap-2 px-4 py-2.5 border transition-all',
|
||||
activeTab === 'financials'
|
||||
? 'border-orange-500 bg-orange-500/10 text-orange-400'
|
||||
: 'border-white/[0.08] text-white/50 hover:text-white hover:bg-white/[0.02]'
|
||||
)}
|
||||
>
|
||||
<Wallet className="w-4 h-4" />
|
||||
<span className="text-xs font-bold uppercase tracking-wider">Financials</span>
|
||||
{stats.upcoming30dCost > 0 && (
|
||||
<span className="px-1.5 py-0.5 text-[9px] bg-orange-500/20 text-orange-400 border border-orange-500/30">
|
||||
${Math.round(stats.upcoming30dCost)}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Asset Filters - only show when assets tab active */}
|
||||
{/* FILTERS BAR - Only for assets tab */}
|
||||
<section className="hidden lg:block px-10 py-3 border-y border-white/[0.08] bg-white/[0.01]">
|
||||
{activeTab === 'assets' && (
|
||||
<div className="flex items-center gap-2 overflow-x-auto">
|
||||
{[
|
||||
@ -1209,6 +1220,11 @@ export default function PortfolioPage() {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{activeTab === 'financials' && (
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">
|
||||
Financial Overview & Renewal Costs
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* TAB CONTENT */}
|
||||
@ -1744,6 +1760,149 @@ export default function PortfolioPage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* HEALTH DETAIL OVERLAY */}
|
||||
{selectedHealthDomain && (
|
||||
<div
|
||||
className="fixed inset-0 z-[100] bg-black/80 flex items-center justify-center p-4"
|
||||
onClick={() => setSelectedHealthDomain(null)}
|
||||
>
|
||||
<div
|
||||
className="w-full max-w-md bg-[#0A0A0A] border border-white/[0.08]"
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="px-5 py-4 border-b border-white/[0.08] flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider mb-1">Health Check</div>
|
||||
<h3 className="text-lg font-bold text-white font-mono">{selectedHealthDomain.domain}</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSelectedHealthDomain(null)}
|
||||
className="p-2 text-white/40 hover:text-white transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="p-5">
|
||||
{(() => {
|
||||
const key = selectedHealthDomain.domain.toLowerCase()
|
||||
const health = healthByDomain[key]
|
||||
const isLoading = checkingHealth.has(key)
|
||||
const cfg = healthConfig[(health?.status as HealthStatus) || 'unknown']
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Score Badge */}
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<div className={clsx(
|
||||
"px-3 py-1.5 text-sm font-mono font-bold uppercase border",
|
||||
cfg.bg, cfg.color, cfg.border
|
||||
)}>
|
||||
{cfg.label}
|
||||
</div>
|
||||
{health?.score !== undefined && (
|
||||
<span className="text-sm font-mono text-white/60">Score: {health.score}/100</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Checks */}
|
||||
{health ? (
|
||||
<div className="space-y-px mb-5 border border-white/[0.08]">
|
||||
{/* DNS */}
|
||||
<div className="flex items-center justify-between px-4 py-3 bg-[#020202]">
|
||||
<span className="text-sm font-mono text-white/70">DNS Resolution</span>
|
||||
{health.dns?.has_a || health.dns?.has_ns ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-5 h-5 text-accent" />
|
||||
<span className="text-sm font-mono text-accent font-bold">OK</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<XCircle className="w-5 h-5 text-rose-400" />
|
||||
<span className="text-sm font-mono text-rose-400 font-bold">FAIL</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* HTTP */}
|
||||
<div className="flex items-center justify-between px-4 py-3 bg-[#020202]">
|
||||
<span className="text-sm font-mono text-white/70">HTTP Access</span>
|
||||
{health.http?.is_reachable ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-5 h-5 text-accent" />
|
||||
<span className="text-sm font-mono text-accent font-bold">OK</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<XCircle className="w-5 h-5 text-rose-400" />
|
||||
<span className="text-sm font-mono text-rose-400 font-bold">{health.http?.error?.substring(0, 15) || 'FAIL'}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* SSL */}
|
||||
<div className="flex items-center justify-between px-4 py-3 bg-[#020202]">
|
||||
<span className="text-sm font-mono text-white/70">SSL Certificate</span>
|
||||
{health.ssl?.has_certificate ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-5 h-5 text-accent" />
|
||||
<span className="text-sm font-mono text-accent font-bold">VALID</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<XCircle className="w-5 h-5 text-rose-400" />
|
||||
<span className="text-sm font-mono text-rose-400 font-bold">MISSING</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Parked Status */}
|
||||
<div className="flex items-center justify-between px-4 py-3 bg-[#020202]">
|
||||
<span className="text-sm font-mono text-white/70">Not Parked</span>
|
||||
{!health.dns?.is_parked && !health.http?.is_parked ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle2 className="w-5 h-5 text-accent" />
|
||||
<span className="text-sm font-mono text-accent font-bold">OK</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertTriangle className="w-5 h-5 text-amber-400" />
|
||||
<span className="text-sm font-mono text-amber-400 font-bold">PARKED</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-10 text-center text-white/40 text-sm font-mono mb-5 border border-white/[0.08] bg-[#020202]">
|
||||
{isLoading ? 'Running health check...' : 'Click below to run health check'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Run Check Button */}
|
||||
<button
|
||||
onClick={() => handleHealthCheck(selectedHealthDomain.domain)}
|
||||
disabled={isLoading}
|
||||
className="w-full py-3.5 bg-accent text-black text-sm font-bold uppercase tracking-wider hover:bg-white transition-colors flex items-center justify-center gap-2 disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="w-5 h-5" />
|
||||
{health ? 'Refresh Check' : 'Run Check'}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{toast && <Toast message={toast.message} type={toast.type} onClose={hideToast} />}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -20,6 +20,8 @@ import {
|
||||
Eye,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
CheckCircle2,
|
||||
XCircle,
|
||||
} from 'lucide-react'
|
||||
import { api } from '@/lib/api'
|
||||
import { useAnalyzePanelStore } from '@/lib/analyze-store'
|
||||
@ -32,13 +34,13 @@ import type { AnalyzeResponse, AnalyzeSection, AnalyzeItem } from '@/components/
|
||||
function getStatusColor(status: string) {
|
||||
switch (status) {
|
||||
case 'pass':
|
||||
return { bg: 'bg-accent/10', text: 'text-accent', border: 'border-accent/30', icon: Check }
|
||||
return { bg: 'bg-accent/20', text: 'text-accent', border: 'border-accent/40', icon: CheckCircle2 }
|
||||
case 'warn':
|
||||
return { bg: 'bg-amber-400/10', text: 'text-amber-300', border: 'border-amber-400/30', icon: AlertTriangle }
|
||||
return { bg: 'bg-amber-400/20', text: 'text-amber-300', border: 'border-amber-400/40', icon: AlertTriangle }
|
||||
case 'fail':
|
||||
return { bg: 'bg-red-500/10', text: 'text-red-400', border: 'border-red-500/30', icon: X }
|
||||
return { bg: 'bg-red-500/20', text: 'text-red-400', border: 'border-red-500/40', icon: XCircle }
|
||||
default:
|
||||
return { bg: 'bg-white/5', text: 'text-white/40', border: 'border-white/10', icon: null }
|
||||
return { bg: 'bg-white/10', text: 'text-white/50', border: 'border-white/20', icon: null }
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,15 +62,15 @@ function getSectionIcon(key: string) {
|
||||
function getSectionColor(key: string) {
|
||||
switch (key) {
|
||||
case 'authority':
|
||||
return 'text-blue-400'
|
||||
return { text: 'text-blue-400', bg: 'bg-blue-500/10', border: 'border-blue-500/30' }
|
||||
case 'market':
|
||||
return 'text-emerald-400'
|
||||
return { text: 'text-emerald-400', bg: 'bg-emerald-500/10', border: 'border-emerald-500/30' }
|
||||
case 'risk':
|
||||
return 'text-amber-400'
|
||||
return { text: 'text-amber-400', bg: 'bg-amber-500/10', border: 'border-amber-500/30' }
|
||||
case 'value':
|
||||
return 'text-violet-400'
|
||||
return { text: 'text-violet-400', bg: 'bg-violet-500/10', border: 'border-violet-500/30' }
|
||||
default:
|
||||
return 'text-white/60'
|
||||
return { text: 'text-white/60', bg: 'bg-white/5', border: 'border-white/20' }
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,28 +205,28 @@ export function AnalyzePanel() {
|
||||
return (
|
||||
<div className="fixed inset-0 z-[200]">
|
||||
{/* Backdrop */}
|
||||
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={close} />
|
||||
<div className="absolute inset-0 bg-black/85 backdrop-blur-md" onClick={close} />
|
||||
|
||||
{/* Panel */}
|
||||
<div className="absolute right-0 top-0 bottom-0 w-full sm:w-[480px] bg-[#050505] border-l border-white/[0.06] flex flex-col overflow-hidden">
|
||||
{/* Panel - WIDER & MORE READABLE */}
|
||||
<div className="absolute right-0 top-0 bottom-0 w-full sm:w-[600px] lg:w-[680px] bg-[#0A0A0A] border-l border-white/10 flex flex-col overflow-hidden shadow-2xl">
|
||||
|
||||
{/* Header */}
|
||||
<div className="shrink-0 border-b border-white/[0.06]">
|
||||
<div className="shrink-0 border-b border-white/10 bg-[#050505]">
|
||||
{/* Top Bar */}
|
||||
<div className="px-4 py-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-accent/10 border border-accent/20 flex items-center justify-center">
|
||||
<Shield className="w-5 h-5 text-accent" />
|
||||
<div className="px-6 py-5 flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-accent/15 border border-accent/30 flex items-center justify-center">
|
||||
<Shield className="w-6 h-6 text-accent" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs font-mono text-white/40 uppercase tracking-wider">Analyze</div>
|
||||
<div className="text-base font-bold text-white font-mono truncate max-w-[200px]">
|
||||
<div className="text-xs font-mono text-accent uppercase tracking-widest mb-1">Domain Analysis</div>
|
||||
<div className="text-xl font-bold text-white font-mono truncate max-w-[300px]">
|
||||
{headerDomain}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={async () => {
|
||||
const ok = await copyToClipboard(headerDomain)
|
||||
@ -232,48 +234,49 @@ export function AnalyzePanel() {
|
||||
setTimeout(() => setCopied(false), 1500)
|
||||
}}
|
||||
className={clsx(
|
||||
"w-8 h-8 flex items-center justify-center border transition-all",
|
||||
copied ? "border-accent bg-accent/10 text-accent" : "border-white/10 text-white/40 hover:text-white"
|
||||
"w-10 h-10 flex items-center justify-center border transition-all",
|
||||
copied ? "border-accent bg-accent/20 text-accent" : "border-white/20 text-white/50 hover:text-white hover:bg-white/10"
|
||||
)}
|
||||
>
|
||||
{copied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
|
||||
{copied ? <Check className="w-5 h-5" /> : <Copy className="w-5 h-5" />}
|
||||
</button>
|
||||
<a
|
||||
href={`https://${encodeURIComponent(headerDomain)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-white transition-colors"
|
||||
className="w-10 h-10 flex items-center justify-center border border-white/20 text-white/50 hover:text-white hover:bg-white/10 transition-colors"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
<ExternalLink className="w-5 h-5" />
|
||||
</a>
|
||||
<button
|
||||
onClick={refresh}
|
||||
disabled={loading}
|
||||
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-white transition-colors disabled:opacity-50"
|
||||
className="w-10 h-10 flex items-center justify-center border border-white/20 text-white/50 hover:text-white hover:bg-white/10 transition-colors disabled:opacity-50"
|
||||
>
|
||||
<RefreshCw className={clsx('w-4 h-4', loading && 'animate-spin')} />
|
||||
<RefreshCw className={clsx('w-5 h-5', loading && 'animate-spin')} />
|
||||
</button>
|
||||
<button
|
||||
onClick={close}
|
||||
className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40 hover:text-white transition-colors"
|
||||
className="w-10 h-10 flex items-center justify-center border border-white/20 text-white/50 hover:text-white hover:bg-white/10 transition-colors ml-2"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Score Bar */}
|
||||
{/* Score Bar - LARGER */}
|
||||
{overallScore && !loading && (
|
||||
<div className="px-4 pb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="px-6 pb-5">
|
||||
<div className="flex items-center gap-4 p-4 bg-white/[0.03] border border-white/10">
|
||||
<div className={clsx(
|
||||
"text-2xl font-bold font-mono",
|
||||
"text-4xl font-bold font-mono",
|
||||
overallScore.score >= 70 ? "text-accent" : overallScore.score >= 40 ? "text-amber-400" : "text-red-400"
|
||||
)}>
|
||||
{overallScore.score}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="h-1.5 bg-white/5 rounded-full overflow-hidden flex">
|
||||
<div className="text-xs font-mono text-white/50 uppercase tracking-wider mb-2">Overall Score</div>
|
||||
<div className="h-3 bg-white/10 overflow-hidden flex">
|
||||
<div
|
||||
className="h-full bg-accent transition-all"
|
||||
style={{ width: `${(overallScore.pass / overallScore.total) * 100}%` }}
|
||||
@ -288,90 +291,93 @@ export function AnalyzePanel() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-[10px] font-mono">
|
||||
<span className="text-accent">{overallScore.pass} pass</span>
|
||||
<span className="text-amber-400">{overallScore.warn} warn</span>
|
||||
<span className="text-red-400">{overallScore.fail} fail</span>
|
||||
<div className="flex flex-col gap-1 text-sm font-mono">
|
||||
<span className="text-accent flex items-center gap-2"><CheckCircle2 className="w-4 h-4" /> {overallScore.pass}</span>
|
||||
<span className="text-amber-400 flex items-center gap-2"><AlertTriangle className="w-4 h-4" /> {overallScore.warn}</span>
|
||||
<span className="text-red-400 flex items-center gap-2"><XCircle className="w-4 h-4" /> {overallScore.fail}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mode Toggle */}
|
||||
<div className="px-4 pb-3 flex items-center gap-2">
|
||||
<div className="px-6 pb-4 flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => setFastMode(!fastMode)}
|
||||
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-2 px-4 py-2 text-sm font-bold uppercase tracking-wider border transition-all",
|
||||
fastMode
|
||||
? "border-accent/30 bg-accent/10 text-accent"
|
||||
: "border-white/10 text-white/40 hover:text-white"
|
||||
? "border-accent/40 bg-accent/15 text-accent"
|
||||
: "border-white/20 text-white/50 hover:text-white hover:bg-white/10"
|
||||
)}
|
||||
>
|
||||
<Zap className="w-3 h-3" />
|
||||
Fast
|
||||
<Zap className="w-4 h-4" />
|
||||
Fast Mode
|
||||
</button>
|
||||
{data?.cached && (
|
||||
<span className="text-[10px] font-mono text-white/30 px-2 py-1 border border-white/10">
|
||||
Cached
|
||||
<span className="text-sm font-mono text-white/40 px-3 py-2 border border-white/20 bg-white/5">
|
||||
⚡ Cached
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
{/* Body - BETTER SPACING */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<div className="flex items-center justify-center py-24">
|
||||
<div className="text-center">
|
||||
<RefreshCw className="w-6 h-6 text-accent animate-spin mx-auto mb-3" />
|
||||
<div className="text-sm font-mono text-white/40">Analyzing...</div>
|
||||
<RefreshCw className="w-10 h-10 text-accent animate-spin mx-auto mb-4" />
|
||||
<div className="text-base font-mono text-white/50">Analyzing domain...</div>
|
||||
</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="p-4">
|
||||
<div className="border border-red-500/20 bg-red-500/5 p-4">
|
||||
<div className="text-sm font-bold text-red-400 mb-1">Analysis Failed</div>
|
||||
<div className="text-xs font-mono text-white/40">{error}</div>
|
||||
<div className="p-6">
|
||||
<div className="border border-red-500/30 bg-red-500/10 p-6">
|
||||
<div className="text-lg font-bold text-red-400 mb-2">Analysis Failed</div>
|
||||
<div className="text-sm font-mono text-white/60">{error}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : !data ? (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<div className="text-sm font-mono text-white/30">No data</div>
|
||||
<div className="flex items-center justify-center py-24">
|
||||
<div className="text-base font-mono text-white/40">No data available</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-3 space-y-2">
|
||||
<div className="p-6 space-y-4">
|
||||
{visibleSections.map((section) => {
|
||||
const SectionIcon = getSectionIcon(section.key)
|
||||
const sectionColor = getSectionColor(section.key)
|
||||
const sectionStyle = getSectionColor(section.key)
|
||||
const isExpanded = expandedSections[section.key] !== false
|
||||
|
||||
return (
|
||||
<div key={section.key} className="border border-white/[0.06] bg-[#020202] overflow-hidden">
|
||||
{/* Section Header */}
|
||||
<div key={section.key} className={clsx("border overflow-hidden", sectionStyle.border, "bg-[#050505]")}>
|
||||
{/* Section Header - LARGER */}
|
||||
<button
|
||||
onClick={() => toggleSection(section.key)}
|
||||
className="w-full px-3 py-2.5 flex items-center justify-between hover:bg-white/[0.02] transition-colors"
|
||||
className={clsx(
|
||||
"w-full px-5 py-4 flex items-center justify-between transition-colors",
|
||||
sectionStyle.bg, "hover:brightness-110"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<SectionIcon className={clsx("w-4 h-4", sectionColor)} />
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-white/80">
|
||||
<div className="flex items-center gap-3">
|
||||
<SectionIcon className={clsx("w-5 h-5", sectionStyle.text)} />
|
||||
<span className={clsx("text-sm font-bold uppercase tracking-wider", sectionStyle.text)}>
|
||||
{section.title}
|
||||
</span>
|
||||
<span className="text-[10px] font-mono text-white/30">
|
||||
{section.items.length}
|
||||
<span className="text-sm font-mono text-white/40 ml-2">
|
||||
{section.items.length} checks
|
||||
</span>
|
||||
</div>
|
||||
{isExpanded ? (
|
||||
<ChevronUp className="w-4 h-4 text-white/30" />
|
||||
<ChevronUp className="w-5 h-5 text-white/40" />
|
||||
) : (
|
||||
<ChevronDown className="w-4 h-4 text-white/30" />
|
||||
<ChevronDown className="w-5 h-5 text-white/40" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Section Items */}
|
||||
{/* Section Items - BETTER CONTRAST */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-white/[0.04]">
|
||||
<div className="border-t border-white/10">
|
||||
{section.items.map((item) => {
|
||||
const statusStyle = getStatusColor(item.status)
|
||||
const StatusIcon = statusStyle.icon
|
||||
@ -379,53 +385,53 @@ export function AnalyzePanel() {
|
||||
return (
|
||||
<div
|
||||
key={item.key}
|
||||
className="px-3 py-2 border-b border-white/[0.04] last:border-0 hover:bg-white/[0.01] transition-colors"
|
||||
className="px-5 py-4 border-b border-white/[0.06] last:border-0 hover:bg-white/[0.03] transition-colors"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
{/* Status Indicator */}
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Status Indicator - LARGER */}
|
||||
<div className={clsx(
|
||||
"w-6 h-6 flex items-center justify-center shrink-0 mt-0.5",
|
||||
"w-10 h-10 flex items-center justify-center shrink-0",
|
||||
statusStyle.bg, statusStyle.border, "border"
|
||||
)}>
|
||||
{StatusIcon && <StatusIcon className={clsx("w-3 h-3", statusStyle.text)} />}
|
||||
{StatusIcon && <StatusIcon className={clsx("w-5 h-5", statusStyle.text)} />}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
{/* Content - BETTER READABILITY */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-[11px] font-medium text-white/70">
|
||||
<div className="flex items-center justify-between gap-4 mb-2">
|
||||
<span className="text-base font-medium text-white">
|
||||
{item.label}
|
||||
</span>
|
||||
<span className="text-[10px] font-mono text-white/30">
|
||||
<span className="text-xs font-mono text-white/40 uppercase tracking-wider">
|
||||
{item.source}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Value */}
|
||||
<div className="mt-1">
|
||||
{/* Value - LARGER TEXT */}
|
||||
<div>
|
||||
{isMatrix(item) ? (
|
||||
<div className="grid grid-cols-3 gap-1">
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{(item.value as any[]).slice(0, 12).map((row: any) => (
|
||||
<div
|
||||
key={String(row.domain)}
|
||||
className={clsx(
|
||||
"px-2 py-1 text-[10px] font-mono flex items-center justify-between border",
|
||||
"px-3 py-2 text-sm font-mono flex items-center justify-between border",
|
||||
row.status === 'available'
|
||||
? "border-accent/20 bg-accent/5 text-accent"
|
||||
: "border-white/5 bg-white/[0.02] text-white/40"
|
||||
? "border-accent/30 bg-accent/10 text-accent"
|
||||
: "border-white/10 bg-white/[0.03] text-white/50"
|
||||
)}
|
||||
>
|
||||
<span className="truncate">{String(row.domain)}</span>
|
||||
{row.status === 'available' && <Check className="w-2.5 h-2.5 shrink-0" />}
|
||||
{row.status === 'available' && <Check className="w-4 h-4 shrink-0 ml-2" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className={clsx(
|
||||
"text-xs font-mono",
|
||||
item.status === 'pass' ? "text-white/60" :
|
||||
item.status === 'warn' ? "text-amber-300/80" :
|
||||
item.status === 'fail' ? "text-red-300/80" : "text-white/40"
|
||||
"text-base font-mono",
|
||||
item.status === 'pass' ? "text-white/80" :
|
||||
item.status === 'warn' ? "text-amber-300" :
|
||||
item.status === 'fail' ? "text-red-300" : "text-white/50"
|
||||
)}>
|
||||
{formatValue(item.value)}
|
||||
</div>
|
||||
@ -434,11 +440,11 @@ export function AnalyzePanel() {
|
||||
|
||||
{/* Details Toggle */}
|
||||
{item.details && Object.keys(item.details).length > 0 && (
|
||||
<details className="mt-2">
|
||||
<summary className="text-[10px] font-mono text-white/25 cursor-pointer hover:text-white/40 select-none">
|
||||
View details
|
||||
<details className="mt-3">
|
||||
<summary className="text-sm font-mono text-white/40 cursor-pointer hover:text-white/60 select-none">
|
||||
View raw details
|
||||
</summary>
|
||||
<pre className="mt-1.5 text-[9px] font-mono text-white/30 bg-black/40 border border-white/5 p-2 overflow-x-auto rounded">
|
||||
<pre className="mt-2 text-xs font-mono text-white/50 bg-black/50 border border-white/10 p-4 overflow-x-auto">
|
||||
{JSON.stringify(item.details, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
Reference in New Issue
Block a user