style: Portfolio table styling like Watchlist/Auctions
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
- Same border wrapper with bg-[#020202] - Same grid gap (gap-6) and padding (px-6 py-4) - Same header styling with accent highlight on active sort - Same action button styling (w-10 h-10 with borders) - Same hover opacity transitions - Same empty state styling - Table body with divide-y divider
This commit is contained in:
@ -1330,42 +1330,52 @@ export default function PortfolioPage() {
|
|||||||
<Loader2 className="w-6 h-6 text-accent animate-spin" />
|
<Loader2 className="w-6 h-6 text-accent animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
) : !filteredDomains.length ? (
|
) : !filteredDomains.length ? (
|
||||||
<div className="text-center py-16 border border-dashed border-white/[0.08]">
|
<div className="text-center py-24 border border-dashed border-white/[0.08] bg-white/[0.01]">
|
||||||
<Briefcase className="w-10 h-10 text-white/10 mx-auto mb-4" />
|
<Briefcase className="w-16 h-16 text-white/5 mx-auto mb-6" />
|
||||||
<p className="text-white/40 text-sm font-mono mb-1">No domains found</p>
|
<p className="text-white/50 text-sm font-mono uppercase tracking-widest font-bold">No domains found</p>
|
||||||
<p className="text-white/25 text-xs font-mono mb-4">Add your first domain to start tracking</p>
|
<p className="text-white/20 text-xs font-mono mt-3 uppercase tracking-wider max-w-sm mx-auto leading-relaxed">
|
||||||
<button
|
Add your first domain to start tracking your portfolio
|
||||||
onClick={() => setShowAddModal(true)}
|
</p>
|
||||||
className="inline-flex items-center gap-2 px-4 py-2 bg-accent text-black text-xs font-bold uppercase"
|
<button
|
||||||
>
|
onClick={() => setShowAddModal(true)}
|
||||||
|
className="mt-6 inline-flex items-center gap-2 px-5 py-3 bg-accent text-black text-xs font-black uppercase tracking-widest hover:bg-white transition-colors"
|
||||||
|
>
|
||||||
<Plus className="w-4 h-4" />Add Domain
|
<Plus className="w-4 h-4" />Add Domain
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-px">
|
<div className="border border-white/[0.08] bg-[#020202] overflow-hidden">
|
||||||
{/* Desktop Table Header */}
|
{/* Desktop Table Header */}
|
||||||
<div className="hidden lg:grid grid-cols-[1.5fr_1fr_100px_100px_100px_100px_90px_60px_140px] gap-3 px-4 py-2.5 text-[10px] font-mono text-white/40 uppercase tracking-wider border-b border-white/[0.08] bg-white/[0.02]">
|
<div className="hidden lg:grid grid-cols-[1fr_140px_90px_90px_100px_90px_80px_60px_160px] gap-6 px-6 py-4 text-[10px] font-mono text-white/40 uppercase tracking-[0.15em] border-b border-white/[0.08] bg-white/[0.02]">
|
||||||
<button onClick={() => handleSort('domain')} className="flex items-center gap-1 hover:text-white/60 text-left">
|
<button onClick={() => handleSort('domain')} className="flex items-center gap-2 hover:text-white transition-colors text-left">
|
||||||
Domain {sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
<span className={clsx(sortField === 'domain' && "text-accent font-bold")}>Domain</span>
|
||||||
|
{sortField === 'domain' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||||
</button>
|
</button>
|
||||||
<div>Registrar / Tags</div>
|
<div>Registrar</div>
|
||||||
<button onClick={() => handleSort('purchased')} className="flex items-center gap-1 justify-end hover:text-white/60">
|
<button onClick={() => handleSort('purchased')} className="flex items-center gap-2 justify-center hover:text-white transition-colors">
|
||||||
Purchased {sortField === 'purchased' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
<span className={clsx(sortField === 'purchased' && "text-accent font-bold")}>Purchased</span>
|
||||||
|
{sortField === 'purchased' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => handleSort('renewal')} className="flex items-center gap-1 justify-end hover:text-white/60">
|
<button onClick={() => handleSort('renewal')} className="flex items-center gap-2 justify-center hover:text-white transition-colors">
|
||||||
Expires {sortField === 'renewal' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
<span className={clsx(sortField === 'renewal' && "text-accent font-bold")}>Expires</span>
|
||||||
|
{sortField === 'renewal' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => handleSort('value')} className="flex items-center gap-1 justify-end hover:text-white/60">
|
<button onClick={() => handleSort('value')} className="flex items-center gap-2 justify-end hover:text-white transition-colors">
|
||||||
Value {sortField === 'value' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
<span className={clsx(sortField === 'value' && "text-accent font-bold")}>Value</span>
|
||||||
|
{sortField === 'value' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => handleSort('roi')} className="flex items-center gap-1 justify-end hover:text-white/60">
|
<button onClick={() => handleSort('roi')} className="flex items-center gap-2 justify-end hover:text-white transition-colors">
|
||||||
ROI {sortField === 'roi' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
|
<span className={clsx(sortField === 'roi' && "text-accent font-bold")}>ROI</span>
|
||||||
|
{sortField === 'roi' && (sortDirection === 'asc' ? <ChevronUp className="w-3 h-3 text-accent" /> : <ChevronDown className="w-3 h-3 text-accent" />)}
|
||||||
</button>
|
</button>
|
||||||
<div className="text-center">Yield</div>
|
<div className="text-center">Yield</div>
|
||||||
<div className="text-center">Health</div>
|
<div className="text-center">Health</div>
|
||||||
<div className="text-right">Actions</div>
|
<div className="text-right">Actions</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Table Body */}
|
||||||
|
<div className="divide-y divide-white/[0.04]">
|
||||||
|
|
||||||
{filteredDomains.map((domain) => {
|
{filteredDomains.map((domain) => {
|
||||||
const daysUntilRenewal = getDaysUntil(domain.renewal_date)
|
const daysUntilRenewal = getDaysUntil(domain.renewal_date)
|
||||||
const isExpiringSoon = daysUntilRenewal !== null && daysUntilRenewal <= 30 && daysUntilRenewal > 0
|
const isExpiringSoon = daysUntilRenewal !== null && daysUntilRenewal <= 30 && daysUntilRenewal > 0
|
||||||
@ -1378,80 +1388,67 @@ export default function PortfolioPage() {
|
|||||||
<div
|
<div
|
||||||
key={domain.id}
|
key={domain.id}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"border border-white/[0.06] bg-[#020202] hover:bg-white/[0.02] transition-all",
|
"group transition-all",
|
||||||
domain.is_sold && "opacity-60"
|
domain.is_sold && "opacity-60"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* DESKTOP ROW */}
|
{/* DESKTOP ROW */}
|
||||||
<div className="hidden lg:grid grid-cols-[1.5fr_1fr_100px_100px_100px_100px_90px_60px_140px] gap-3 px-4 py-3 items-center">
|
<div className="hidden lg:grid grid-cols-[1fr_140px_90px_90px_100px_90px_80px_60px_160px] gap-6 px-6 py-4 items-center group-hover:bg-white/[0.02]">
|
||||||
{/* Domain */}
|
{/* Domain */}
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
<div className="min-w-0">
|
<button
|
||||||
<div className="text-sm font-bold text-white font-mono truncate">{domain.domain}</div>
|
onClick={() => openAnalyze(domain.domain)}
|
||||||
<div className="flex items-center gap-1.5 mt-0.5">
|
className="text-sm font-bold text-white font-mono truncate group-hover:text-accent transition-colors text-left"
|
||||||
{renderStatusBadges(domain)}
|
>
|
||||||
{!domain.is_dns_verified && !domain.is_sold && (
|
{domain.domain}
|
||||||
<button
|
</button>
|
||||||
onClick={() => setVerifyingDomain(domain)}
|
<div className="flex items-center gap-2 text-[9px] font-mono text-white/20 uppercase tracking-wider opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
className="text-[9px] font-mono text-amber-400 hover:text-amber-300 underline"
|
{renderStatusBadges(domain)}
|
||||||
>
|
{!domain.is_dns_verified && !domain.is_sold && (
|
||||||
Verify
|
<button
|
||||||
</button>
|
onClick={() => setVerifyingDomain(domain)}
|
||||||
)}
|
className="text-amber-400 hover:text-amber-300"
|
||||||
</div>
|
>
|
||||||
|
Verify
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Registrar & Tags */}
|
{/* Registrar */}
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="text-xs font-mono text-white/60 truncate">{domain.registrar || '—'}</div>
|
<div className="text-sm font-mono text-white/60 truncate">{domain.registrar || '—'}</div>
|
||||||
{tags.length > 0 && (
|
</div>
|
||||||
<div className="flex items-center gap-1 mt-1 flex-wrap">
|
|
||||||
{tags.slice(0, 2).map(tag => (
|
|
||||||
<span key={tag} className="px-1.5 py-0.5 text-[8px] font-mono text-white/40 bg-white/5 border border-white/10">
|
|
||||||
{tag}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
{tags.length > 2 && (
|
|
||||||
<span className="text-[8px] font-mono text-white/30">+{tags.length - 2}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Purchased */}
|
{/* Purchased */}
|
||||||
<div className="text-right">
|
<div className="text-center">
|
||||||
<div className="text-xs font-mono text-white/60">{formatShortDate(domain.purchase_date)}</div>
|
<div className="text-sm font-mono text-white/50">{formatShortDate(domain.purchase_date)}</div>
|
||||||
<div className="text-[10px] font-mono text-white/30">{formatCurrency(domain.purchase_price)}</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Expires */}
|
{/* Expires */}
|
||||||
<div className="text-right">
|
<div className="text-center">
|
||||||
{domain.is_sold ? (
|
{domain.is_sold ? (
|
||||||
<div className="text-xs font-mono text-white/30">Sold</div>
|
<span className="text-[10px] font-mono text-white/30 uppercase">Sold</span>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<span className={clsx(
|
||||||
<div className={clsx(
|
"text-sm font-mono",
|
||||||
"text-xs font-mono",
|
isExpired ? "text-rose-400 font-bold" : isExpiringSoon ? "text-orange-400 font-bold" : "text-white/50"
|
||||||
isExpired ? "text-rose-400" : isExpiringSoon ? "text-orange-400" : "text-white/60"
|
)}>
|
||||||
)}>
|
{daysUntilRenewal !== null ? `${daysUntilRenewal}d` : '—'}
|
||||||
{daysUntilRenewal !== null ? `${daysUntilRenewal}d` : '—'}
|
</span>
|
||||||
</div>
|
)}
|
||||||
<div className="text-[10px] font-mono text-white/30">{formatShortDate(domain.renewal_date)}</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Value */}
|
{/* Value */}
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-sm font-bold text-accent font-mono">{formatCurrency(domain.estimated_value)}</div>
|
<span className="text-sm font-bold text-accent font-mono">{formatCurrency(domain.estimated_value)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ROI */}
|
{/* ROI */}
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className={clsx("text-sm font-bold font-mono", roiPositive ? "text-accent" : "text-rose-400")}>
|
<span className={clsx("text-sm font-bold font-mono", roiPositive ? "text-accent" : "text-rose-400")}>
|
||||||
{formatROI(domain.roi)}
|
{formatROI(domain.roi)}
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Yield */}
|
{/* Yield */}
|
||||||
@ -1465,48 +1462,35 @@ export default function PortfolioPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center justify-end gap-1">
|
<div className="flex items-center justify-end gap-2 opacity-40 group-hover:opacity-100 transition-all">
|
||||||
<button
|
<button
|
||||||
onClick={() => openAnalyze(domain.domain)}
|
onClick={() => openAnalyze(domain.domain)}
|
||||||
title="Analyze"
|
title="Analyze"
|
||||||
className="p-2 text-white/30 hover:text-accent transition-colors"
|
className="w-10 h-10 flex items-center justify-center text-white/40 hover:text-accent border border-white/10 hover:bg-accent/10 hover:border-accent/20 transition-all"
|
||||||
>
|
>
|
||||||
<Shield className="w-4 h-4" />
|
<Shield className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setEditingDomain(domain)}
|
onClick={() => setEditingDomain(domain)}
|
||||||
title="Edit domain details"
|
title="Edit"
|
||||||
className="p-2 text-white/30 hover:text-accent transition-colors"
|
className="w-10 h-10 flex items-center justify-center text-white/40 hover:text-white border border-white/10 hover:bg-white/5 transition-all"
|
||||||
>
|
>
|
||||||
<Edit3 className="w-4 h-4" />
|
<Edit3 className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
onClick={() => handleRefreshValue(domain.id)}
|
|
||||||
disabled={refreshingId === domain.id}
|
|
||||||
title="Refresh value estimate"
|
|
||||||
className={clsx("p-2 text-white/30 hover:text-accent transition-colors", refreshingId === domain.id && "animate-spin")}
|
|
||||||
>
|
|
||||||
<RefreshCw className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
{domain.is_dns_verified && !domain.is_sold && !listedDomains.has(domain.domain.toLowerCase()) && (
|
|
||||||
<Link
|
|
||||||
href="/terminal/listing"
|
|
||||||
title="List for sale"
|
|
||||||
className="p-2 text-white/30 hover:text-blue-400 transition-colors"
|
|
||||||
>
|
|
||||||
<Tag className="w-4 h-4" />
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDelete(domain.id, domain.domain)}
|
onClick={() => handleDelete(domain.id, domain.domain)}
|
||||||
disabled={deletingId === domain.id}
|
disabled={deletingId === domain.id}
|
||||||
title="Remove from portfolio"
|
title="Remove"
|
||||||
className="p-2 text-white/30 hover:text-rose-400 transition-colors"
|
className="w-10 h-10 flex items-center justify-center text-white/40 hover:text-rose-400 border border-white/10 hover:border-rose-400/20 hover:bg-rose-500/10 transition-all"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4" />
|
{deletingId === domain.id ? (
|
||||||
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* MOBILE ROW */}
|
{/* MOBILE ROW */}
|
||||||
<div className="lg:hidden">
|
<div className="lg:hidden">
|
||||||
@ -1632,8 +1616,9 @@ export default function PortfolioPage() {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user