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,
|
Calendar,
|
||||||
Edit3,
|
Edit3,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
|
CheckCircle2,
|
||||||
|
XCircle,
|
||||||
|
AlertTriangle,
|
||||||
Copy,
|
Copy,
|
||||||
Check,
|
Check,
|
||||||
Navigation,
|
Navigation,
|
||||||
@ -687,6 +690,9 @@ export default function PortfolioPage() {
|
|||||||
const [navDrawerOpen, setNavDrawerOpen] = useState(false)
|
const [navDrawerOpen, setNavDrawerOpen] = useState(false)
|
||||||
const [expandedRow, setExpandedRow] = useState<number | null>(null)
|
const [expandedRow, setExpandedRow] = useState<number | null>(null)
|
||||||
|
|
||||||
|
// Health detail overlay
|
||||||
|
const [selectedHealthDomain, setSelectedHealthDomain] = useState<PortfolioDomain | null>(null)
|
||||||
|
|
||||||
const tier = subscription?.tier || 'scout'
|
const tier = subscription?.tier || 'scout'
|
||||||
const tierName = subscription?.tier_name || tier
|
const tierName = subscription?.tier_name || tier
|
||||||
const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap
|
const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap
|
||||||
@ -949,9 +955,12 @@ export default function PortfolioPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleHealthCheck(domain.domain)}
|
onClick={() => {
|
||||||
|
setSelectedHealthDomain(domain)
|
||||||
|
if (!health) handleHealthCheck(domain.domain)
|
||||||
|
}}
|
||||||
disabled={isChecking}
|
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(
|
className={clsx(
|
||||||
"flex items-center gap-1 px-2 py-1 text-[10px] font-mono uppercase border transition-colors",
|
"flex items-center gap-1 px-2 py-1 text-[10px] font-mono uppercase border transition-colors",
|
||||||
cfg.color, cfg.bg, cfg.border,
|
cfg.color, cfg.bg, cfg.border,
|
||||||
@ -1109,51 +1118,23 @@ export default function PortfolioPage() {
|
|||||||
|
|
||||||
{/* DESKTOP HEADER */}
|
{/* DESKTOP HEADER */}
|
||||||
<section className="hidden lg:block px-10 pt-10 pb-6">
|
<section className="hidden lg:block px-10 pt-10 pb-6">
|
||||||
<div className="flex items-end justify-between gap-8">
|
<div className="flex items-start justify-between gap-8">
|
||||||
<div className="space-y-3">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-2">
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<div className="w-1.5 h-1.5 bg-accent animate-pulse" />
|
<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>
|
<span className="text-[10px] font-mono tracking-[0.2em] text-accent uppercase">Portfolio Manager</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-display text-[2.5rem] leading-[1] tracking-[-0.02em] text-white">
|
<h1 className="font-display text-[2.5rem] leading-[1] tracking-[-0.02em] text-white">
|
||||||
My Portfolio
|
My Portfolio
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm text-white/40 font-mono max-w-lg">
|
<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.
|
Track your domain investments. Add purchase details, monitor values, verify ownership, and list for sale.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-8">
|
{/* TABS - Directly under subtitle */}
|
||||||
<div className="text-right" title="Total invested">
|
<div className="flex gap-2 pt-2">
|
||||||
<div className="text-2xl font-bold text-white font-mono">{formatCurrency(summary?.total_invested || 0)}</div>
|
|
||||||
<div className="text-[9px] font-mono text-white/30 uppercase">Invested</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-right" title="Current market value">
|
|
||||||
<div className="text-2xl font-bold text-accent font-mono">{formatCurrency(summary?.total_value || 0)}</div>
|
|
||||||
<div className="text-[9px] font-mono text-white/30 uppercase">Value</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-right" title="Return on investment">
|
|
||||||
<div className={clsx("text-2xl font-bold font-mono", (summary?.overall_roi || 0) >= 0 ? "text-accent" : "text-rose-400")}>
|
|
||||||
{formatROI(summary?.overall_roi || 0)}
|
|
||||||
</div>
|
|
||||||
<div className="text-[9px] font-mono text-white/30 uppercase">ROI</div>
|
|
||||||
</div>
|
|
||||||
<div className="pl-6 border-l border-white/10">
|
|
||||||
<button
|
|
||||||
onClick={() => setShowAddModal(true)}
|
|
||||||
className="flex items-center gap-2 px-5 py-3 bg-accent text-black text-xs font-bold uppercase tracking-wider hover:bg-white transition-colors"
|
|
||||||
>
|
|
||||||
<Plus className="w-4 h-4" />
|
|
||||||
Add Domain
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</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
|
<button
|
||||||
onClick={() => setActiveTab('assets')}
|
onClick={() => setActiveTab('assets')}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
@ -1184,8 +1165,38 @@ export default function PortfolioPage() {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Asset Filters - only show when assets tab active */}
|
<div className="flex items-center gap-8">
|
||||||
|
<div className="text-right" title="Total invested">
|
||||||
|
<div className="text-2xl font-bold text-white font-mono">{formatCurrency(summary?.total_invested || 0)}</div>
|
||||||
|
<div className="text-[9px] font-mono text-white/30 uppercase">Invested</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-right" title="Current market value">
|
||||||
|
<div className="text-2xl font-bold text-accent font-mono">{formatCurrency(summary?.total_value || 0)}</div>
|
||||||
|
<div className="text-[9px] font-mono text-white/30 uppercase">Value</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-right" title="Return on investment">
|
||||||
|
<div className={clsx("text-2xl font-bold font-mono", (summary?.overall_roi || 0) >= 0 ? "text-accent" : "text-rose-400")}>
|
||||||
|
{formatROI(summary?.overall_roi || 0)}
|
||||||
|
</div>
|
||||||
|
<div className="text-[9px] font-mono text-white/30 uppercase">ROI</div>
|
||||||
|
</div>
|
||||||
|
<div className="pl-6 border-l border-white/10">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowAddModal(true)}
|
||||||
|
className="flex items-center gap-2 px-5 py-3 bg-accent text-black text-xs font-bold uppercase tracking-wider hover:bg-white transition-colors"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
Add Domain
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 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' && (
|
{activeTab === 'assets' && (
|
||||||
<div className="flex items-center gap-2 overflow-x-auto">
|
<div className="flex items-center gap-2 overflow-x-auto">
|
||||||
{[
|
{[
|
||||||
@ -1209,6 +1220,11 @@ export default function PortfolioPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{activeTab === 'financials' && (
|
||||||
|
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">
|
||||||
|
Financial Overview & Renewal Costs
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* TAB CONTENT */}
|
{/* 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} />}
|
{toast && <Toast message={toast.message} type={toast.type} onClose={hideToast} />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -20,6 +20,8 @@ import {
|
|||||||
Eye,
|
Eye,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
|
CheckCircle2,
|
||||||
|
XCircle,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { api } from '@/lib/api'
|
import { api } from '@/lib/api'
|
||||||
import { useAnalyzePanelStore } from '@/lib/analyze-store'
|
import { useAnalyzePanelStore } from '@/lib/analyze-store'
|
||||||
@ -32,13 +34,13 @@ import type { AnalyzeResponse, AnalyzeSection, AnalyzeItem } from '@/components/
|
|||||||
function getStatusColor(status: string) {
|
function getStatusColor(status: string) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'pass':
|
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':
|
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':
|
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:
|
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) {
|
function getSectionColor(key: string) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'authority':
|
case 'authority':
|
||||||
return 'text-blue-400'
|
return { text: 'text-blue-400', bg: 'bg-blue-500/10', border: 'border-blue-500/30' }
|
||||||
case 'market':
|
case 'market':
|
||||||
return 'text-emerald-400'
|
return { text: 'text-emerald-400', bg: 'bg-emerald-500/10', border: 'border-emerald-500/30' }
|
||||||
case 'risk':
|
case 'risk':
|
||||||
return 'text-amber-400'
|
return { text: 'text-amber-400', bg: 'bg-amber-500/10', border: 'border-amber-500/30' }
|
||||||
case 'value':
|
case 'value':
|
||||||
return 'text-violet-400'
|
return { text: 'text-violet-400', bg: 'bg-violet-500/10', border: 'border-violet-500/30' }
|
||||||
default:
|
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 (
|
return (
|
||||||
<div className="fixed inset-0 z-[200]">
|
<div className="fixed inset-0 z-[200]">
|
||||||
{/* Backdrop */}
|
{/* 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 */}
|
{/* Panel - WIDER & MORE READABLE */}
|
||||||
<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">
|
<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 */}
|
{/* Header */}
|
||||||
<div className="shrink-0 border-b border-white/[0.06]">
|
<div className="shrink-0 border-b border-white/10 bg-[#050505]">
|
||||||
{/* Top Bar */}
|
{/* Top Bar */}
|
||||||
<div className="px-4 py-3 flex items-center justify-between">
|
<div className="px-6 py-5 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-4">
|
||||||
<div className="w-10 h-10 bg-accent/10 border border-accent/20 flex items-center justify-center">
|
<div className="w-12 h-12 bg-accent/15 border border-accent/30 flex items-center justify-center">
|
||||||
<Shield className="w-5 h-5 text-accent" />
|
<Shield className="w-6 h-6 text-accent" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs font-mono text-white/40 uppercase tracking-wider">Analyze</div>
|
<div className="text-xs font-mono text-accent uppercase tracking-widest mb-1">Domain Analysis</div>
|
||||||
<div className="text-base font-bold text-white font-mono truncate max-w-[200px]">
|
<div className="text-xl font-bold text-white font-mono truncate max-w-[300px]">
|
||||||
{headerDomain}
|
{headerDomain}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const ok = await copyToClipboard(headerDomain)
|
const ok = await copyToClipboard(headerDomain)
|
||||||
@ -232,48 +234,49 @@ export function AnalyzePanel() {
|
|||||||
setTimeout(() => setCopied(false), 1500)
|
setTimeout(() => setCopied(false), 1500)
|
||||||
}}
|
}}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"w-8 h-8 flex items-center justify-center border transition-all",
|
"w-10 h-10 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"
|
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>
|
</button>
|
||||||
<a
|
<a
|
||||||
href={`https://${encodeURIComponent(headerDomain)}`}
|
href={`https://${encodeURIComponent(headerDomain)}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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>
|
</a>
|
||||||
<button
|
<button
|
||||||
onClick={refresh}
|
onClick={refresh}
|
||||||
disabled={loading}
|
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>
|
||||||
<button
|
<button
|
||||||
onClick={close}
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Score Bar */}
|
{/* Score Bar - LARGER */}
|
||||||
{overallScore && !loading && (
|
{overallScore && !loading && (
|
||||||
<div className="px-4 pb-3">
|
<div className="px-6 pb-5">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-4 p-4 bg-white/[0.03] border border-white/10">
|
||||||
<div className={clsx(
|
<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 >= 70 ? "text-accent" : overallScore.score >= 40 ? "text-amber-400" : "text-red-400"
|
||||||
)}>
|
)}>
|
||||||
{overallScore.score}
|
{overallScore.score}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<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
|
<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}%` }}
|
||||||
@ -288,90 +291,93 @@ export function AnalyzePanel() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-[10px] font-mono">
|
<div className="flex flex-col gap-1 text-sm font-mono">
|
||||||
<span className="text-accent">{overallScore.pass} pass</span>
|
<span className="text-accent flex items-center gap-2"><CheckCircle2 className="w-4 h-4" /> {overallScore.pass}</span>
|
||||||
<span className="text-amber-400">{overallScore.warn} warn</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">{overallScore.fail} fail</span>
|
<span className="text-red-400 flex items-center gap-2"><XCircle className="w-4 h-4" /> {overallScore.fail}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Mode Toggle */}
|
{/* 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
|
<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-2 px-4 py-2 text-sm font-bold uppercase tracking-wider border transition-all",
|
||||||
fastMode
|
fastMode
|
||||||
? "border-accent/30 bg-accent/10 text-accent"
|
? "border-accent/40 bg-accent/15 text-accent"
|
||||||
: "border-white/10 text-white/40 hover:text-white"
|
: "border-white/20 text-white/50 hover:text-white hover:bg-white/10"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Zap className="w-3 h-3" />
|
<Zap className="w-4 h-4" />
|
||||||
Fast
|
Fast Mode
|
||||||
</button>
|
</button>
|
||||||
{data?.cached && (
|
{data?.cached && (
|
||||||
<span className="text-[10px] font-mono text-white/30 px-2 py-1 border border-white/10">
|
<span className="text-sm font-mono text-white/40 px-3 py-2 border border-white/20 bg-white/5">
|
||||||
Cached
|
⚡ Cached
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Body */}
|
{/* Body - BETTER SPACING */}
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex items-center justify-center py-20">
|
<div className="flex items-center justify-center py-24">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<RefreshCw className="w-6 h-6 text-accent animate-spin mx-auto mb-3" />
|
<RefreshCw className="w-10 h-10 text-accent animate-spin mx-auto mb-4" />
|
||||||
<div className="text-sm font-mono text-white/40">Analyzing...</div>
|
<div className="text-base font-mono text-white/50">Analyzing domain...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="p-4">
|
<div className="p-6">
|
||||||
<div className="border border-red-500/20 bg-red-500/5 p-4">
|
<div className="border border-red-500/30 bg-red-500/10 p-6">
|
||||||
<div className="text-sm font-bold text-red-400 mb-1">Analysis Failed</div>
|
<div className="text-lg font-bold text-red-400 mb-2">Analysis Failed</div>
|
||||||
<div className="text-xs font-mono text-white/40">{error}</div>
|
<div className="text-sm font-mono text-white/60">{error}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : !data ? (
|
) : !data ? (
|
||||||
<div className="flex items-center justify-center py-20">
|
<div className="flex items-center justify-center py-24">
|
||||||
<div className="text-sm font-mono text-white/30">No data</div>
|
<div className="text-base font-mono text-white/40">No data available</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-3 space-y-2">
|
<div className="p-6 space-y-4">
|
||||||
{visibleSections.map((section) => {
|
{visibleSections.map((section) => {
|
||||||
const SectionIcon = getSectionIcon(section.key)
|
const SectionIcon = getSectionIcon(section.key)
|
||||||
const sectionColor = getSectionColor(section.key)
|
const sectionStyle = getSectionColor(section.key)
|
||||||
const isExpanded = expandedSections[section.key] !== false
|
const isExpanded = expandedSections[section.key] !== false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={section.key} className="border border-white/[0.06] bg-[#020202] overflow-hidden">
|
<div key={section.key} className={clsx("border overflow-hidden", sectionStyle.border, "bg-[#050505]")}>
|
||||||
{/* Section Header */}
|
{/* Section Header - LARGER */}
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleSection(section.key)}
|
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">
|
<div className="flex items-center gap-3">
|
||||||
<SectionIcon className={clsx("w-4 h-4", sectionColor)} />
|
<SectionIcon className={clsx("w-5 h-5", sectionStyle.text)} />
|
||||||
<span className="text-xs font-bold uppercase tracking-wider text-white/80">
|
<span className={clsx("text-sm font-bold uppercase tracking-wider", sectionStyle.text)}>
|
||||||
{section.title}
|
{section.title}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[10px] font-mono text-white/30">
|
<span className="text-sm font-mono text-white/40 ml-2">
|
||||||
{section.items.length}
|
{section.items.length} checks
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{isExpanded ? (
|
{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>
|
</button>
|
||||||
|
|
||||||
{/* Section Items */}
|
{/* Section Items - BETTER CONTRAST */}
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div className="border-t border-white/[0.04]">
|
<div className="border-t border-white/10">
|
||||||
{section.items.map((item) => {
|
{section.items.map((item) => {
|
||||||
const statusStyle = getStatusColor(item.status)
|
const statusStyle = getStatusColor(item.status)
|
||||||
const StatusIcon = statusStyle.icon
|
const StatusIcon = statusStyle.icon
|
||||||
@ -379,53 +385,53 @@ export function AnalyzePanel() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.key}
|
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">
|
<div className="flex items-start gap-4">
|
||||||
{/* Status Indicator */}
|
{/* Status Indicator - LARGER */}
|
||||||
<div className={clsx(
|
<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"
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content - BETTER READABILITY */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-4 mb-2">
|
||||||
<span className="text-[11px] font-medium text-white/70">
|
<span className="text-base font-medium text-white">
|
||||||
{item.label}
|
{item.label}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[10px] font-mono text-white/30">
|
<span className="text-xs font-mono text-white/40 uppercase tracking-wider">
|
||||||
{item.source}
|
{item.source}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Value */}
|
{/* Value - LARGER TEXT */}
|
||||||
<div className="mt-1">
|
<div>
|
||||||
{isMatrix(item) ? (
|
{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) => (
|
{(item.value as any[]).slice(0, 12).map((row: any) => (
|
||||||
<div
|
<div
|
||||||
key={String(row.domain)}
|
key={String(row.domain)}
|
||||||
className={clsx(
|
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'
|
row.status === 'available'
|
||||||
? "border-accent/20 bg-accent/5 text-accent"
|
? "border-accent/30 bg-accent/10 text-accent"
|
||||||
: "border-white/5 bg-white/[0.02] text-white/40"
|
: "border-white/10 bg-white/[0.03] text-white/50"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="truncate">{String(row.domain)}</span>
|
<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>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
"text-xs font-mono",
|
"text-base font-mono",
|
||||||
item.status === 'pass' ? "text-white/60" :
|
item.status === 'pass' ? "text-white/80" :
|
||||||
item.status === 'warn' ? "text-amber-300/80" :
|
item.status === 'warn' ? "text-amber-300" :
|
||||||
item.status === 'fail' ? "text-red-300/80" : "text-white/40"
|
item.status === 'fail' ? "text-red-300" : "text-white/50"
|
||||||
)}>
|
)}>
|
||||||
{formatValue(item.value)}
|
{formatValue(item.value)}
|
||||||
</div>
|
</div>
|
||||||
@ -434,11 +440,11 @@ export function AnalyzePanel() {
|
|||||||
|
|
||||||
{/* 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-3">
|
||||||
<summary className="text-[10px] font-mono text-white/25 cursor-pointer hover:text-white/40 select-none">
|
<summary className="text-sm font-mono text-white/40 cursor-pointer hover:text-white/60 select-none">
|
||||||
View details
|
View raw details
|
||||||
</summary>
|
</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)}
|
{JSON.stringify(item.details, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
Reference in New Issue
Block a user