refactor: Consistent styling across all Command Center pages

CHANGES:
- All pages now use PageContainer (max-w-7xl) for consistent width
- Unified StatCard component usage across all pages
- All tables now use PremiumTable with consistent styling
- Consistent filter/tab design across pages
- Updated SectionHeader with compact mode option
- Consistent card styling with backdrop blur effects
- Unified form input styling across all pages
- Settings page redesigned with consistent card layouts

PAGES UPDATED:
- Dashboard: PageContainer, SectionHeader, StatCard integration
- Watchlist: Full redesign with PremiumTable
- Portfolio: Full redesign with PremiumTable
- Settings: Consistent card styling and layout
- Auctions & Intelligence: Already using consistent components
This commit is contained in:
yves.gugger
2025-12-10 10:34:23 +01:00
parent b66c3b360d
commit 20f43dbc8d
5 changed files with 1012 additions and 1290 deletions

View File

@ -5,6 +5,7 @@ import { useSearchParams } from 'next/navigation'
import { useStore } from '@/lib/store'
import { api } from '@/lib/api'
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
import { PremiumTable, StatCard, PageContainer, Badge, SectionHeader } from '@/components/PremiumTable'
import { Toast, useToast } from '@/components/Toast'
import {
Eye,
@ -13,7 +14,6 @@ import {
Gavel,
Clock,
Bell,
ArrowRight,
ExternalLink,
Sparkles,
ChevronRight,
@ -59,9 +59,6 @@ export default function DashboardPage() {
const [quickDomain, setQuickDomain] = useState('')
const [addingDomain, setAddingDomain] = useState(false)
// Note: checkAuth is called in CommandCenterLayout, no need to duplicate here
// Auth redirect is also handled by CommandCenterLayout
// Check for upgrade success
useEffect(() => {
if (searchParams.get('upgraded') === 'true') {
@ -131,37 +128,39 @@ export default function DashboardPage() {
>
{toast && <Toast message={toast.message} type={toast.type} onClose={hideToast} />}
<div className="max-w-7xl mx-auto space-y-8">
<PageContainer>
{/* Quick Add */}
<div className="relative p-6 bg-gradient-to-r from-accent/10 via-accent/5 to-transparent border border-accent/20 rounded-2xl overflow-hidden">
<div className="relative p-5 sm:p-6 bg-gradient-to-r from-accent/10 via-accent/5 to-transparent border border-accent/20 rounded-2xl overflow-hidden">
<div className="absolute top-0 right-0 w-64 h-64 bg-accent/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
<div className="relative">
<h2 className="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
<h2 className="text-base font-semibold text-foreground mb-4 flex items-center gap-2">
<div className="w-8 h-8 bg-accent/20 rounded-lg flex items-center justify-center">
<Search className="w-4 h-4 text-accent" />
</div>
Quick Add to Watchlist
</h2>
<form onSubmit={handleQuickAdd} className="flex gap-3">
<input
type="text"
value={quickDomain}
onChange={(e) => setQuickDomain(e.target.value)}
placeholder="Enter domain to track (e.g., dream.com)"
className="flex-1 h-12 px-5 bg-background/80 backdrop-blur-sm border border-border/50 rounded-xl
text-foreground placeholder:text-foreground-subtle
focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/30
shadow-[0_2px_8px_-2px_rgba(0,0,0,0.1)]"
/>
<form onSubmit={handleQuickAdd} className="flex flex-col sm:flex-row gap-3">
<div className="relative flex-1">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-subtle" />
<input
type="text"
value={quickDomain}
onChange={(e) => setQuickDomain(e.target.value)}
placeholder="Enter domain to track (e.g., dream.com)"
className="w-full h-11 pl-11 pr-4 bg-background/80 backdrop-blur-sm border border-border/50 rounded-xl
text-sm text-foreground placeholder:text-foreground-subtle
focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/30"
/>
</div>
<button
type="submit"
disabled={addingDomain || !quickDomain.trim()}
className="flex items-center gap-2 h-12 px-6 bg-gradient-to-r from-accent to-accent/80 text-background rounded-xl
className="flex items-center justify-center gap-2 h-11 px-6 bg-gradient-to-r from-accent to-accent/80 text-background rounded-xl
font-medium hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)] transition-all
disabled:opacity-50 disabled:cursor-not-allowed"
>
<Plus className="w-4 h-4" />
Add
<span>Add</span>
</button>
</form>
</div>
@ -169,269 +168,219 @@ export default function DashboardPage() {
{/* Stats Overview */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<Link
href="/watchlist"
className="group relative p-5 bg-gradient-to-br from-background-secondary/60 to-background-secondary/30 border border-border/50 rounded-2xl
hover:border-accent/30 transition-all duration-300 overflow-hidden"
>
<div className="absolute inset-0 bg-accent/5 opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="relative">
<div className="flex items-start justify-between mb-4">
<div className="w-11 h-11 bg-foreground/5 border border-border/30 rounded-xl flex items-center justify-center group-hover:bg-accent/10 group-hover:border-accent/20 transition-all">
<Eye className="w-5 h-5 text-foreground-muted group-hover:text-accent transition-colors" />
</div>
<ChevronRight className="w-5 h-5 text-foreground-subtle group-hover:text-accent
group-hover:translate-x-0.5 transition-all" />
</div>
<p className="text-3xl font-display text-foreground mb-1">{totalDomains}</p>
<p className="text-sm text-foreground-muted">Domains Watched</p>
</div>
<Link href="/watchlist" className="group">
<StatCard
title="Domains Watched"
value={totalDomains}
icon={Eye}
/>
</Link>
<Link
href="/watchlist?filter=available"
className={clsx(
"group relative p-5 border rounded-2xl transition-all duration-300 overflow-hidden",
availableDomains.length > 0
? "bg-gradient-to-br from-accent/15 to-accent/5 border-accent/30 hover:border-accent/50 shadow-[0_0_30px_-10px_rgba(16,185,129,0.2)]"
: "bg-gradient-to-br from-background-secondary/60 to-background-secondary/30 border-border/50 hover:border-accent/30"
)}
>
{availableDomains.length > 0 && <div className="absolute inset-0 bg-accent/5 animate-pulse" />}
<div className="relative">
<div className="flex items-start justify-between mb-4">
<div className={clsx(
"w-11 h-11 rounded-xl flex items-center justify-center border transition-all",
availableDomains.length > 0 ? "bg-accent/20 border-accent/30" : "bg-foreground/5 border-border/30"
)}>
<Sparkles className={clsx(
"w-5 h-5",
availableDomains.length > 0 ? "text-accent" : "text-foreground-muted"
)} />
</div>
{availableDomains.length > 0 && (
<span className="px-2.5 py-1 bg-accent text-background text-xs font-bold rounded-lg shadow-[0_0_10px_rgba(16,185,129,0.3)]">
POUNCE!
</span>
)}
</div>
<p className={clsx(
"text-3xl font-display mb-1",
availableDomains.length > 0 ? "text-accent" : "text-foreground"
)}>
{availableDomains.length}
</p>
<p className="text-sm text-foreground-muted">Available Now</p>
</div>
<Link href="/watchlist?filter=available" className="group">
<StatCard
title="Available Now"
value={availableDomains.length}
icon={Sparkles}
accent={availableDomains.length > 0}
/>
</Link>
<Link
href="/portfolio"
className="group relative p-5 bg-gradient-to-br from-background-secondary/60 to-background-secondary/30 border border-border/50 rounded-2xl
hover:border-accent/30 transition-all duration-300 overflow-hidden"
>
<div className="absolute inset-0 bg-accent/5 opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="relative">
<div className="flex items-start justify-between mb-4">
<div className="w-11 h-11 bg-foreground/5 border border-border/30 rounded-xl flex items-center justify-center group-hover:bg-accent/10 group-hover:border-accent/20 transition-all">
<Briefcase className="w-5 h-5 text-foreground-muted group-hover:text-accent transition-colors" />
</div>
<ChevronRight className="w-5 h-5 text-foreground-subtle group-hover:text-accent
group-hover:translate-x-0.5 transition-all" />
</div>
<p className="text-3xl font-display text-foreground mb-1">0</p>
<p className="text-sm text-foreground-muted">Portfolio Domains</p>
</div>
<Link href="/portfolio" className="group">
<StatCard
title="Portfolio"
value={0}
icon={Briefcase}
/>
</Link>
<div className="relative p-5 bg-gradient-to-br from-background-secondary/60 to-background-secondary/30 border border-border/50 rounded-2xl overflow-hidden">
<div className="absolute top-0 right-0 w-24 h-24 bg-accent/10 rounded-full blur-2xl -translate-y-1/2 translate-x-1/2" />
<div className="relative">
<div className="flex items-start justify-between mb-4">
<div className="w-11 h-11 bg-accent/10 border border-accent/20 rounded-xl flex items-center justify-center">
<TierIcon className="w-5 h-5 text-accent" />
</div>
</div>
<p className="text-3xl font-display text-foreground mb-1">{tierName}</p>
<p className="text-sm text-foreground-muted">
{subscription?.domains_used || 0}/{subscription?.domain_limit || 5} slots used
</p>
</div>
</div>
<StatCard
title="Plan"
value={tierName}
subtitle={`${subscription?.domains_used || 0}/${subscription?.domain_limit || 5} slots`}
icon={TierIcon}
/>
</div>
{/* Activity Feed + Market Pulse */}
<div className="grid lg:grid-cols-2 gap-6">
{/* Activity Feed */}
<div className="p-6 bg-gradient-to-br from-background-secondary/60 to-background-secondary/30 border border-border/50 rounded-2xl">
<div className="flex items-center justify-between mb-6">
<h2 className="text-lg font-semibold text-foreground flex items-center gap-3">
<div className="w-9 h-9 bg-accent/10 border border-accent/20 rounded-lg flex items-center justify-center">
<Activity className="w-4 h-4 text-accent" />
</div>
Activity Feed
</h2>
<Link href="/watchlist" className="text-sm text-accent hover:text-accent/80 transition-colors">
View all
</Link>
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm">
<div className="p-5 border-b border-border/30">
<SectionHeader
title="Activity Feed"
icon={Activity}
compact
action={
<Link href="/watchlist" className="text-sm text-accent hover:text-accent/80 transition-colors">
View all
</Link>
}
/>
</div>
{availableDomains.length > 0 ? (
<div className="space-y-3">
{availableDomains.slice(0, 4).map((domain) => (
<div
key={domain.id}
className="flex items-center gap-4 p-3 bg-accent/5 border border-accent/20 rounded-xl"
>
<div className="relative">
<span className="w-3 h-3 bg-accent rounded-full block" />
<span className="absolute inset-0 bg-accent rounded-full animate-ping opacity-50" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-foreground truncate">{domain.name}</p>
<p className="text-xs text-accent">Available for registration!</p>
</div>
<a
href={`https://www.namecheap.com/domains/registration/results/?domain=${domain.name}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-medium text-accent hover:underline flex items-center gap-1"
<div className="p-5">
{availableDomains.length > 0 ? (
<div className="space-y-3">
{availableDomains.slice(0, 4).map((domain) => (
<div
key={domain.id}
className="flex items-center gap-4 p-3 bg-accent/5 border border-accent/20 rounded-xl"
>
Register <ExternalLink className="w-3 h-3" />
</a>
</div>
))}
{availableDomains.length > 4 && (
<p className="text-center text-sm text-foreground-muted">
+{availableDomains.length - 4} more available
<div className="relative">
<span className="w-3 h-3 bg-accent rounded-full block" />
<span className="absolute inset-0 bg-accent rounded-full animate-ping opacity-50" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-foreground truncate">{domain.name}</p>
<p className="text-xs text-accent">Available for registration!</p>
</div>
<a
href={`https://www.namecheap.com/domains/registration/results/?domain=${domain.name}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-medium text-accent hover:underline flex items-center gap-1"
>
Register <ExternalLink className="w-3 h-3" />
</a>
</div>
))}
{availableDomains.length > 4 && (
<p className="text-center text-sm text-foreground-muted">
+{availableDomains.length - 4} more available
</p>
)}
</div>
) : totalDomains > 0 ? (
<div className="text-center py-8">
<Bell className="w-10 h-10 text-foreground-subtle mx-auto mb-3" />
<p className="text-foreground-muted">All domains are still registered</p>
<p className="text-sm text-foreground-subtle mt-1">
We're monitoring {totalDomains} domains for you
</p>
)}
</div>
) : totalDomains > 0 ? (
<div className="text-center py-8">
<Bell className="w-10 h-10 text-foreground-subtle mx-auto mb-3" />
<p className="text-foreground-muted">All domains are still registered</p>
<p className="text-sm text-foreground-subtle mt-1">
We're monitoring {totalDomains} domains for you
</p>
</div>
) : (
<div className="text-center py-8">
<Plus className="w-10 h-10 text-foreground-subtle mx-auto mb-3" />
<p className="text-foreground-muted">No domains tracked yet</p>
<p className="text-sm text-foreground-subtle mt-1">
Add a domain above to start monitoring
</p>
</div>
)}
</div>
) : (
<div className="text-center py-8">
<Plus className="w-10 h-10 text-foreground-subtle mx-auto mb-3" />
<p className="text-foreground-muted">No domains tracked yet</p>
<p className="text-sm text-foreground-subtle mt-1">
Add a domain above to start monitoring
</p>
</div>
)}
</div>
</div>
{/* Market Pulse */}
<div className="p-6 bg-gradient-to-br from-background-secondary/60 to-background-secondary/30 border border-border/50 rounded-2xl">
<div className="flex items-center justify-between mb-6">
<h2 className="text-lg font-semibold text-foreground flex items-center gap-3">
<div className="w-9 h-9 bg-accent/10 border border-accent/20 rounded-lg flex items-center justify-center">
<Gavel className="w-4 h-4 text-accent" />
</div>
Market Pulse
</h2>
<Link href="/market" className="text-sm text-accent hover:text-accent/80 transition-colors">
View all →
</Link>
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm">
<div className="p-5 border-b border-border/30">
<SectionHeader
title="Market Pulse"
icon={Gavel}
compact
action={
<Link href="/auctions" className="text-sm text-accent hover:text-accent/80 transition-colors">
View all →
</Link>
}
/>
</div>
<div className="p-5">
{loadingAuctions ? (
<div className="space-y-3">
{[...Array(4)].map((_, i) => (
<div key={i} className="h-14 bg-foreground/5 rounded-xl animate-pulse" />
))}
</div>
) : hotAuctions.length > 0 ? (
<div className="space-y-3">
{hotAuctions.map((auction, idx) => (
<a
key={`${auction.domain}-${idx}`}
href={auction.affiliate_url || '#'}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-4 p-3 bg-foreground/5 rounded-xl
hover:bg-foreground/10 transition-colors group"
>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-foreground truncate">{auction.domain}</p>
<p className="text-xs text-foreground-muted flex items-center gap-2">
<Clock className="w-3 h-3" />
{auction.time_remaining}
<span className="text-foreground-subtle">• {auction.platform}</span>
</p>
</div>
<div className="text-right">
<p className="text-sm font-semibold text-foreground">${auction.current_bid}</p>
<p className="text-xs text-foreground-subtle">current bid</p>
</div>
<ExternalLink className="w-4 h-4 text-foreground-subtle group-hover:text-foreground" />
</a>
))}
</div>
) : (
<div className="text-center py-8">
<Gavel className="w-10 h-10 text-foreground-subtle mx-auto mb-3" />
<p className="text-foreground-muted">No auctions ending soon</p>
</div>
)}
</div>
{loadingAuctions ? (
<div className="space-y-3">
{[...Array(4)].map((_, i) => (
<div key={i} className="h-14 bg-foreground/5 rounded-xl animate-pulse" />
))}
</div>
) : hotAuctions.length > 0 ? (
<div className="space-y-3">
{hotAuctions.map((auction, idx) => (
<a
key={`${auction.domain}-${idx}`}
href={auction.affiliate_url || '#'}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-4 p-3 bg-foreground/5 rounded-xl
hover:bg-foreground/10 transition-colors group"
>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-foreground truncate">{auction.domain}</p>
<p className="text-xs text-foreground-muted flex items-center gap-2">
<Clock className="w-3 h-3" />
{auction.time_remaining}
<span className="text-foreground-subtle">• {auction.platform}</span>
</p>
</div>
<div className="text-right">
<p className="text-sm font-semibold text-foreground">${auction.current_bid}</p>
<p className="text-xs text-foreground-subtle">current bid</p>
</div>
<ExternalLink className="w-4 h-4 text-foreground-subtle group-hover:text-foreground" />
</a>
))}
</div>
) : (
<div className="text-center py-8">
<Gavel className="w-10 h-10 text-foreground-subtle mx-auto mb-3" />
<p className="text-foreground-muted">No auctions ending soon</p>
</div>
)}
</div>
</div>
{/* Trending TLDs */}
<div className="p-6 bg-gradient-to-br from-background-secondary/60 to-background-secondary/30 border border-border/50 rounded-2xl">
<div className="flex items-center justify-between mb-6">
<h2 className="text-lg font-semibold text-foreground flex items-center gap-3">
<div className="w-9 h-9 bg-accent/10 border border-accent/20 rounded-lg flex items-center justify-center">
<TrendingUp className="w-4 h-4 text-accent" />
</div>
Trending TLDs
</h2>
<Link href="/intelligence" className="text-sm text-accent hover:text-accent/80 transition-colors">
View all →
</Link>
</div>
{loadingTlds ? (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{[...Array(4)].map((_, i) => (
<div key={i} className="h-24 bg-foreground/5 rounded-xl animate-pulse" />
))}
</div>
) : (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{trendingTlds.map((tld) => (
<Link
key={tld.tld}
href={`/tld-pricing/${tld.tld}`}
className="group relative p-4 bg-background/50 border border-border/50 rounded-xl
hover:border-accent/30 transition-all duration-300 overflow-hidden"
>
<div className="absolute inset-0 bg-accent/5 opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="relative">
<div className="flex items-center justify-between mb-3">
<span className="font-mono text-2xl font-semibold text-foreground group-hover:text-accent transition-colors">.{tld.tld}</span>
<span className={clsx(
"text-xs font-bold px-2.5 py-1 rounded-lg border",
(tld.price_change || 0) > 0
? "text-orange-400 bg-orange-400/10 border-orange-400/20"
: "text-accent bg-accent/10 border-accent/20"
)}>
{(tld.price_change || 0) > 0 ? '+' : ''}{(tld.price_change || 0).toFixed(1)}%
</span>
</div>
<p className="text-sm text-foreground-muted truncate">{tld.reason}</p>
</div>
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm">
<div className="p-5 border-b border-border/30">
<SectionHeader
title="Trending TLDs"
icon={TrendingUp}
compact
action={
<Link href="/intelligence" className="text-sm text-accent hover:text-accent/80 transition-colors">
View all →
</Link>
))}
</div>
)}
}
/>
</div>
<div className="p-5">
{loadingTlds ? (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{[...Array(4)].map((_, i) => (
<div key={i} className="h-24 bg-foreground/5 rounded-xl animate-pulse" />
))}
</div>
) : trendingTlds.length > 0 ? (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{trendingTlds.map((tld) => (
<Link
key={tld.tld}
href={`/tld-pricing/${tld.tld}`}
className="group relative p-4 bg-foreground/5 border border-border/30 rounded-xl
hover:border-accent/30 transition-all duration-300 overflow-hidden"
>
<div className="absolute inset-0 bg-accent/5 opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="relative">
<div className="flex items-center justify-between mb-3">
<span className="font-mono text-2xl font-semibold text-foreground group-hover:text-accent transition-colors">.{tld.tld}</span>
<span className={clsx(
"text-xs font-bold px-2.5 py-1 rounded-lg border",
(tld.price_change || 0) > 0
? "text-orange-400 bg-orange-400/10 border-orange-400/20"
: "text-accent bg-accent/10 border-accent/20"
)}>
{(tld.price_change || 0) > 0 ? '+' : ''}{(tld.price_change || 0).toFixed(1)}%
</span>
</div>
<p className="text-sm text-foreground-muted truncate">{tld.reason}</p>
</div>
</Link>
))}
</div>
) : (
<div className="text-center py-8">
<TrendingUp className="w-10 h-10 text-foreground-subtle mx-auto mb-3" />
<p className="text-foreground-muted">No trending TLDs available</p>
</div>
)}
</div>
</div>
</div>
</PageContainer>
</CommandCenterLayout>
)
}

View File

@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'
import { useStore } from '@/lib/store'
import { api, PortfolioDomain, PortfolioSummary, DomainValuation } from '@/lib/api'
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
import { PremiumTable, StatCard, PageContainer, TableActionButton } from '@/components/PremiumTable'
import { Toast, useToast } from '@/components/Toast'
import {
Plus,
@ -15,12 +16,12 @@ import {
RefreshCw,
Loader2,
TrendingUp,
TrendingDown,
Tag,
ExternalLink,
Sparkles,
ArrowUpRight,
X,
Briefcase,
PiggyBank,
ShoppingCart,
} from 'lucide-react'
import clsx from 'clsx'
import Link from 'next/link'
@ -230,53 +231,31 @@ export default function PortfolioPage() {
<button
onClick={() => setShowAddModal(true)}
disabled={!canAddMore}
className="flex items-center gap-2 h-9 px-4 bg-accent text-background rounded-lg
font-medium text-sm hover:bg-accent-hover transition-colors
disabled:opacity-50 disabled:cursor-not-allowed"
className="flex items-center gap-2 h-9 px-4 bg-gradient-to-r from-accent to-accent/80 text-background
rounded-lg font-medium text-sm hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)]
transition-all disabled:opacity-50 disabled:cursor-not-allowed"
>
<Plus className="w-4 h-4" />
Add Domain
<span className="hidden sm:inline">Add Domain</span>
</button>
}
>
{toast && <Toast message={toast.message} type={toast.type} onClose={hideToast} />}
<div className="max-w-7xl mx-auto space-y-6">
<PageContainer>
{/* Summary Stats */}
{summary && (
<div className="grid grid-cols-2 lg:grid-cols-5 gap-4">
<div className="p-5 bg-background-secondary/50 border border-border rounded-xl">
<p className="text-sm text-foreground-muted mb-1">Total Domains</p>
<p className="text-2xl font-display text-foreground">{summary.total_domains}</p>
</div>
<div className="p-5 bg-background-secondary/50 border border-border rounded-xl">
<p className="text-sm text-foreground-muted mb-1">Total Invested</p>
<p className="text-2xl font-display text-foreground">${summary.total_invested?.toLocaleString() || 0}</p>
</div>
<div className="p-5 bg-background-secondary/50 border border-border rounded-xl">
<p className="text-sm text-foreground-muted mb-1">Est. Value</p>
<p className="text-2xl font-display text-foreground">${summary.total_value?.toLocaleString() || 0}</p>
</div>
<div className={clsx(
"p-5 border rounded-xl",
(summary.total_profit || 0) >= 0
? "bg-accent/5 border-accent/20"
: "bg-red-500/5 border-red-500/20"
)}>
<p className="text-sm text-foreground-muted mb-1">Profit/Loss</p>
<p className={clsx(
"text-2xl font-display",
(summary.total_profit || 0) >= 0 ? "text-accent" : "text-red-400"
)}>
{(summary.total_profit || 0) >= 0 ? '+' : ''}${summary.total_profit?.toLocaleString() || 0}
</p>
</div>
<div className="p-5 bg-background-secondary/50 border border-border rounded-xl">
<p className="text-sm text-foreground-muted mb-1">Sold</p>
<p className="text-2xl font-display text-foreground">{summary.sold_domains || 0}</p>
</div>
</div>
)}
<div className="grid grid-cols-2 lg:grid-cols-5 gap-4">
<StatCard title="Total Domains" value={summary?.total_domains || 0} icon={Briefcase} />
<StatCard title="Total Invested" value={`$${(summary?.total_invested || 0).toLocaleString()}`} icon={DollarSign} />
<StatCard title="Est. Value" value={`$${(summary?.total_value || 0).toLocaleString()}`} icon={TrendingUp} />
<StatCard
title="Profit/Loss"
value={`${(summary?.total_profit || 0) >= 0 ? '+' : ''}$${(summary?.total_profit || 0).toLocaleString()}`}
icon={PiggyBank}
accent={(summary?.total_profit || 0) >= 0}
/>
<StatCard title="Sold" value={summary?.sold_domains || 0} icon={ShoppingCart} />
</div>
{!canAddMore && (
<div className="flex items-center justify-between p-4 bg-amber-500/10 border border-amber-500/20 rounded-xl">
@ -292,176 +271,177 @@ export default function PortfolioPage() {
</div>
)}
{/* Domain List */}
{loading ? (
<div className="space-y-3">
{[...Array(3)].map((_, i) => (
<div key={i} className="h-24 bg-background-secondary/50 border border-border rounded-xl animate-pulse" />
))}
</div>
) : portfolio.length === 0 ? (
<div className="text-center py-16 bg-background-secondary/30 border border-border rounded-xl">
<div className="w-16 h-16 bg-foreground/5 rounded-2xl flex items-center justify-center mx-auto mb-4">
<Plus className="w-8 h-8 text-foreground-subtle" />
</div>
<p className="text-foreground-muted mb-2">Your portfolio is empty</p>
<p className="text-sm text-foreground-subtle mb-4">Add your first domain to start tracking investments</p>
<button
onClick={() => setShowAddModal(true)}
className="inline-flex items-center gap-2 px-4 py-2 bg-accent text-background rounded-lg font-medium"
>
<Plus className="w-4 h-4" />
Add Domain
</button>
</div>
) : (
<div className="space-y-3">
{portfolio.map((domain) => (
<div
key={domain.id}
className="group p-5 bg-background-secondary/50 border border-border rounded-xl
hover:border-foreground/20 transition-all"
>
<div className="flex flex-col lg:flex-row lg:items-center gap-4">
{/* Domain Info */}
<div className="flex-1 min-w-0">
<h3 className="text-lg font-semibold text-foreground mb-2">{domain.domain}</h3>
<div className="flex flex-wrap gap-4 text-sm text-foreground-muted">
{domain.purchase_price && (
<span className="flex items-center gap-1.5">
<DollarSign className="w-3.5 h-3.5" />
Bought: ${domain.purchase_price}
</span>
)}
{domain.registrar && (
<span className="flex items-center gap-1.5">
<Building className="w-3.5 h-3.5" />
{domain.registrar}
</span>
)}
{domain.renewal_date && (
<span className="flex items-center gap-1.5">
<Calendar className="w-3.5 h-3.5" />
Renews: {new Date(domain.renewal_date).toLocaleDateString()}
</span>
)}
</div>
</div>
{/* Valuation */}
{domain.current_valuation && (
<div className="text-right">
<p className="text-xl font-semibold text-foreground">
${domain.current_valuation.toLocaleString()}
</p>
<p className="text-xs text-foreground-subtle">Est. Value</p>
</div>
{/* Portfolio Table */}
<PremiumTable
data={portfolio}
keyExtractor={(d) => d.id}
loading={loading}
emptyIcon={<Briefcase className="w-12 h-12 text-foreground-subtle" />}
emptyTitle="Your portfolio is empty"
emptyDescription="Add your first domain to start tracking investments"
columns={[
{
key: 'domain',
header: 'Domain',
render: (domain) => (
<div>
<span className="font-mono font-medium text-foreground">{domain.domain}</span>
{domain.registrar && (
<p className="text-xs text-foreground-muted flex items-center gap-1 mt-0.5">
<Building className="w-3 h-3" /> {domain.registrar}
</p>
)}
{/* Actions */}
<div className="flex items-center gap-2">
<button
onClick={() => handleValuate(domain)}
className="p-2 text-foreground-muted hover:text-accent hover:bg-accent/10 rounded-lg transition-colors"
title="Get valuation"
>
<Sparkles className="w-4 h-4" />
</button>
<button
onClick={() => handleRefresh(domain)}
disabled={refreshingId === domain.id}
className="p-2 text-foreground-muted hover:bg-foreground/5 rounded-lg transition-colors"
title="Refresh valuation"
>
<RefreshCw className={clsx("w-4 h-4", refreshingId === domain.id && "animate-spin")} />
</button>
<button
onClick={() => openEditModal(domain)}
className="p-2 text-foreground-muted hover:bg-foreground/5 rounded-lg transition-colors"
title="Edit"
>
<Edit2 className="w-4 h-4" />
</button>
<button
onClick={() => openSellModal(domain)}
className="px-3 py-2 text-sm font-medium text-accent hover:bg-accent/10 rounded-lg transition-colors"
>
Sell
</button>
<button
onClick={() => handleDelete(domain)}
className="p-2 text-foreground-muted hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-colors"
title="Remove"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
</div>
))}
</div>
)}
</div>
),
},
{
key: 'purchase',
header: 'Purchase',
hideOnMobile: true,
render: (domain) => (
<div>
{domain.purchase_price && (
<span className="font-medium text-foreground">${domain.purchase_price.toLocaleString()}</span>
)}
{domain.purchase_date && (
<p className="text-xs text-foreground-muted flex items-center gap-1 mt-0.5">
<Calendar className="w-3 h-3" /> {new Date(domain.purchase_date).toLocaleDateString()}
</p>
)}
</div>
),
},
{
key: 'valuation',
header: 'Est. Value',
align: 'right',
render: (domain) => (
domain.current_valuation ? (
<span className="font-semibold text-foreground">${domain.current_valuation.toLocaleString()}</span>
) : (
<span className="text-foreground-muted">—</span>
)
),
},
{
key: 'renewal',
header: 'Renewal',
hideOnMobile: true,
hideOnTablet: true,
render: (domain) => (
domain.renewal_date ? (
<span className="text-sm text-foreground-muted">
{new Date(domain.renewal_date).toLocaleDateString()}
</span>
) : (
<span className="text-foreground-subtle">—</span>
)
),
},
{
key: 'actions',
header: '',
align: 'right',
render: (domain) => (
<div className="flex items-center gap-1 justify-end">
<TableActionButton
icon={Sparkles}
onClick={() => handleValuate(domain)}
title="Get valuation"
/>
<TableActionButton
icon={RefreshCw}
onClick={() => handleRefresh(domain)}
loading={refreshingId === domain.id}
title="Refresh valuation"
/>
<TableActionButton
icon={Edit2}
onClick={() => openEditModal(domain)}
title="Edit"
/>
<button
onClick={(e) => { e.stopPropagation(); openSellModal(domain) }}
className="px-3 py-2 text-xs font-medium text-accent hover:bg-accent/10 rounded-lg transition-colors"
>
Sell
</button>
<TableActionButton
icon={Trash2}
onClick={() => handleDelete(domain)}
variant="danger"
title="Remove"
/>
</div>
),
},
]}
/>
</PageContainer>
{/* Add Modal */}
{showAddModal && (
<Modal title="Add Domain to Portfolio" onClose={() => setShowAddModal(false)}>
<form onSubmit={handleAddDomain} className="space-y-4">
<div>
<label className="block text-sm text-foreground-muted mb-1">Domain *</label>
<label className="block text-sm text-foreground-muted mb-1.5">Domain *</label>
<input
type="text"
value={addForm.domain}
onChange={(e) => setAddForm({ ...addForm, domain: e.target.value })}
placeholder="example.com"
className="w-full h-10 px-3 bg-background border border-border rounded-lg text-foreground"
className="w-full h-11 px-4 bg-background border border-border/50 rounded-xl text-foreground
focus:outline-none focus:border-accent/50 transition-all"
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-foreground-muted mb-1">Purchase Price</label>
<label className="block text-sm text-foreground-muted mb-1.5">Purchase Price</label>
<input
type="number"
value={addForm.purchase_price}
onChange={(e) => setAddForm({ ...addForm, purchase_price: e.target.value })}
placeholder="100"
className="w-full h-10 px-3 bg-background border border-border rounded-lg text-foreground"
className="w-full h-11 px-4 bg-background border border-border/50 rounded-xl text-foreground
focus:outline-none focus:border-accent/50"
/>
</div>
<div>
<label className="block text-sm text-foreground-muted mb-1">Purchase Date</label>
<label className="block text-sm text-foreground-muted mb-1.5">Purchase Date</label>
<input
type="date"
value={addForm.purchase_date}
onChange={(e) => setAddForm({ ...addForm, purchase_date: e.target.value })}
className="w-full h-10 px-3 bg-background border border-border rounded-lg text-foreground"
className="w-full h-11 px-4 bg-background border border-border/50 rounded-xl text-foreground
focus:outline-none focus:border-accent/50"
/>
</div>
</div>
<div>
<label className="block text-sm text-foreground-muted mb-1">Registrar</label>
<label className="block text-sm text-foreground-muted mb-1.5">Registrar</label>
<input
type="text"
value={addForm.registrar}
onChange={(e) => setAddForm({ ...addForm, registrar: e.target.value })}
placeholder="Namecheap"
className="w-full h-10 px-3 bg-background border border-border rounded-lg text-foreground"
className="w-full h-11 px-4 bg-background border border-border/50 rounded-xl text-foreground
focus:outline-none focus:border-accent/50"
/>
</div>
<div className="flex justify-end gap-3">
<div className="flex justify-end gap-3 pt-2">
<button
type="button"
onClick={() => setShowAddModal(false)}
className="px-4 py-2 text-foreground-muted hover:text-foreground"
className="px-4 py-2.5 text-foreground-muted hover:text-foreground transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={addingDomain || !addForm.domain.trim()}
className="flex items-center gap-2 px-4 py-2 bg-accent text-background rounded-lg font-medium
disabled:opacity-50"
className="flex items-center gap-2 px-5 py-2.5 bg-gradient-to-r from-accent to-accent/80
text-background rounded-xl font-medium disabled:opacity-50 transition-all"
>
{addingDomain && <Loader2 className="w-4 h-4 animate-spin" />}
Add Domain
@ -471,12 +451,110 @@ export default function PortfolioPage() {
</Modal>
)}
{/* Edit Modal */}
{showEditModal && selectedDomain && (
<Modal title={`Edit ${selectedDomain.domain}`} onClose={() => setShowEditModal(false)}>
<form onSubmit={handleEditDomain} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-foreground-muted mb-1.5">Purchase Price</label>
<input
type="number"
value={editForm.purchase_price}
onChange={(e) => setEditForm({ ...editForm, purchase_price: e.target.value })}
className="w-full h-11 px-4 bg-background border border-border/50 rounded-xl text-foreground
focus:outline-none focus:border-accent/50"
/>
</div>
<div>
<label className="block text-sm text-foreground-muted mb-1.5">Registrar</label>
<input
type="text"
value={editForm.registrar}
onChange={(e) => setEditForm({ ...editForm, registrar: e.target.value })}
className="w-full h-11 px-4 bg-background border border-border/50 rounded-xl text-foreground
focus:outline-none focus:border-accent/50"
/>
</div>
</div>
<div className="flex justify-end gap-3 pt-2">
<button
type="button"
onClick={() => setShowEditModal(false)}
className="px-4 py-2.5 text-foreground-muted hover:text-foreground transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={savingEdit}
className="flex items-center gap-2 px-5 py-2.5 bg-gradient-to-r from-accent to-accent/80
text-background rounded-xl font-medium disabled:opacity-50 transition-all"
>
{savingEdit && <Loader2 className="w-4 h-4 animate-spin" />}
Save Changes
</button>
</div>
</form>
</Modal>
)}
{/* Sell Modal */}
{showSellModal && selectedDomain && (
<Modal title={`Sell ${selectedDomain.domain}`} onClose={() => setShowSellModal(false)}>
<form onSubmit={handleSellDomain} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-foreground-muted mb-1.5">Sale Price *</label>
<input
type="number"
value={sellForm.sale_price}
onChange={(e) => setSellForm({ ...sellForm, sale_price: e.target.value })}
placeholder="1000"
className="w-full h-11 px-4 bg-background border border-border/50 rounded-xl text-foreground
focus:outline-none focus:border-accent/50"
required
/>
</div>
<div>
<label className="block text-sm text-foreground-muted mb-1.5">Sale Date</label>
<input
type="date"
value={sellForm.sale_date}
onChange={(e) => setSellForm({ ...sellForm, sale_date: e.target.value })}
className="w-full h-11 px-4 bg-background border border-border/50 rounded-xl text-foreground
focus:outline-none focus:border-accent/50"
/>
</div>
</div>
<div className="flex justify-end gap-3 pt-2">
<button
type="button"
onClick={() => setShowSellModal(false)}
className="px-4 py-2.5 text-foreground-muted hover:text-foreground transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={processingSale || !sellForm.sale_price}
className="flex items-center gap-2 px-5 py-2.5 bg-gradient-to-r from-accent to-accent/80
text-background rounded-xl font-medium disabled:opacity-50 transition-all"
>
{processingSale && <Loader2 className="w-4 h-4 animate-spin" />}
Mark as Sold
</button>
</div>
</form>
</Modal>
)}
{/* Valuation Modal */}
{showValuationModal && (
<Modal title="Domain Valuation" onClose={() => { setShowValuationModal(false); setValuation(null); }}>
{valuatingDomain ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="w-6 h-6 animate-spin text-accent" />
<div className="flex items-center justify-center py-12">
<Loader2 className="w-8 h-8 animate-spin text-accent" />
</div>
) : valuation ? (
<div className="space-y-4">
@ -485,11 +563,11 @@ export default function PortfolioPage() {
<p className="text-sm text-foreground-muted mt-1">Estimated Value</p>
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<div className="flex justify-between p-3 bg-foreground/5 rounded-lg">
<span className="text-foreground-muted">Confidence</span>
<span className="text-foreground capitalize">{valuation.confidence}</span>
<span className="text-foreground capitalize font-medium">{valuation.confidence}</span>
</div>
<div className="flex justify-between">
<div className="flex justify-between p-3 bg-foreground/5 rounded-lg">
<span className="text-foreground-muted">Formula</span>
<span className="text-foreground font-mono text-xs">{valuation.valuation_formula}</span>
</div>
@ -502,7 +580,7 @@ export default function PortfolioPage() {
)
}
// Simple Modal Component
// Modal Component
function Modal({ title, children, onClose }: { title: string; children: React.ReactNode; onClose: () => void }) {
return (
<div
@ -510,20 +588,19 @@ function Modal({ title, children, onClose }: { title: string; children: React.Re
onClick={onClose}
>
<div
className="w-full max-w-md bg-background-secondary border border-border rounded-2xl shadow-2xl"
className="w-full max-w-md bg-background-secondary border border-border/50 rounded-2xl shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between p-4 border-b border-border">
<div className="flex items-center justify-between p-5 border-b border-border/50">
<h3 className="text-lg font-semibold text-foreground">{title}</h3>
<button onClick={onClose} className="text-foreground-muted hover:text-foreground">
<button onClick={onClose} className="p-1 text-foreground-muted hover:text-foreground transition-colors">
<X className="w-5 h-5" />
</button>
</div>
<div className="p-4">
<div className="p-5">
{children}
</div>
</div>
</div>
)
}

View File

@ -3,6 +3,7 @@
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
import { PageContainer } from '@/components/PremiumTable'
import { useStore } from '@/lib/store'
import { api, PriceAlert } from '@/lib/api'
import {
@ -41,7 +42,7 @@ export default function SettingsPage() {
email: '',
})
// Notification preferences (local state - would be persisted via API in production)
// Notification preferences
const [notificationPrefs, setNotificationPrefs] = useState({
domain_availability: true,
price_alerts: true,
@ -99,7 +100,6 @@ export default function SettingsPage() {
try {
await api.updateMe({ name: profileForm.name || undefined })
// Update store with new user info
const { checkAuth } = useStore.getState()
await checkAuth()
setSuccess('Profile updated successfully')
@ -116,7 +116,6 @@ export default function SettingsPage() {
setSuccess(null)
try {
// Store in localStorage for now (would be API in production)
localStorage.setItem('notification_prefs', JSON.stringify(notificationPrefs))
setSuccess('Notification preferences saved')
} catch (err) {
@ -126,7 +125,6 @@ export default function SettingsPage() {
}
}
// Load notification preferences from localStorage
useEffect(() => {
const saved = localStorage.getItem('notification_prefs')
if (saved) {
@ -184,536 +182,382 @@ export default function SettingsPage() {
title="Settings"
subtitle="Manage your account"
>
<PageContainer>
{/* Messages */}
{error && (
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-xl flex items-center gap-3">
<AlertCircle className="w-5 h-5 text-red-400 shrink-0" />
<p className="text-sm text-red-400 flex-1">{error}</p>
<button onClick={() => setError(null)} className="text-red-400 hover:text-red-300">
<Trash2 className="w-4 h-4" />
</button>
</div>
)}
<main className="max-w-5xl mx-auto">
<div className="space-y-8">
{success && (
<div className="p-4 bg-accent/10 border border-accent/20 rounded-xl flex items-center gap-3">
<Check className="w-5 h-5 text-accent shrink-0" />
<p className="text-sm text-accent flex-1">{success}</p>
<button onClick={() => setSuccess(null)} className="text-accent hover:text-accent/80">
<Trash2 className="w-4 h-4" />
</button>
</div>
)}
{/* Messages */}
{error && (
<div className="mb-6 p-4 bg-danger/5 border border-danger/20 rounded-2xl flex items-center gap-3">
<AlertCircle className="w-5 h-5 text-danger shrink-0" />
<p className="text-body-sm text-danger flex-1">{error}</p>
<button onClick={() => setError(null)} className="text-danger hover:text-danger/80">
<Trash2 className="w-4 h-4" />
</button>
</div>
)}
<div className="flex flex-col lg:flex-row gap-6">
{/* Sidebar */}
<div className="lg:w-72 shrink-0 space-y-5">
{/* Mobile: Horizontal scroll tabs */}
<nav className="lg:hidden flex gap-2 overflow-x-auto pb-2 -mx-4 px-4 scrollbar-hide">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={clsx(
"flex items-center gap-2.5 px-5 py-3 text-sm font-medium rounded-xl whitespace-nowrap transition-all",
activeTab === tab.id
? "bg-gradient-to-r from-accent to-accent/80 text-background shadow-lg shadow-accent/20"
: "bg-background-secondary/50 text-foreground-muted hover:text-foreground border border-border/50"
)}
>
<tab.icon className="w-4 h-4" />
{tab.label}
</button>
))}
</nav>
{/* Desktop: Vertical tabs */}
<nav className="hidden lg:block p-2 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 border border-border/40 rounded-2xl backdrop-blur-sm">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={clsx(
"w-full flex items-center gap-3 px-5 py-3.5 text-sm font-medium rounded-xl transition-all",
activeTab === tab.id
? "bg-gradient-to-r from-accent to-accent/80 text-background shadow-lg shadow-accent/20"
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5"
)}
>
<tab.icon className="w-4 h-4" />
{tab.label}
</button>
))}
</nav>
{success && (
<div className="mb-6 p-4 bg-accent/5 border border-accent/20 rounded-2xl flex items-center gap-3">
<Check className="w-5 h-5 text-accent shrink-0" />
<p className="text-body-sm text-accent flex-1">{success}</p>
<button onClick={() => setSuccess(null)} className="text-accent hover:text-accent/80">
<Trash2 className="w-4 h-4" />
</button>
</div>
)}
<div className="flex flex-col lg:flex-row gap-8 animate-slide-up">
{/* Sidebar - Horizontal scroll on mobile, vertical on desktop */}
<div className="lg:w-72 shrink-0">
{/* Mobile: Horizontal scroll tabs */}
<nav className="lg:hidden flex gap-2 overflow-x-auto pb-2 -mx-4 px-4 scrollbar-hide">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={clsx(
"flex items-center gap-2.5 px-5 py-3 text-sm font-medium rounded-xl whitespace-nowrap transition-all duration-300",
activeTab === tab.id
? "bg-accent text-background shadow-lg shadow-accent/20"
: "bg-background-secondary/50 text-foreground-muted hover:text-foreground border border-border hover:border-accent/30"
)}
>
<tab.icon className="w-4 h-4" />
{tab.label}
</button>
))}
</nav>
{/* Desktop: Vertical tabs */}
<nav className="hidden lg:block p-2 bg-background-secondary/50 border border-border rounded-2xl">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={clsx(
"w-full flex items-center gap-3 px-5 py-3.5 text-sm font-medium rounded-xl transition-all duration-300",
activeTab === tab.id
? "bg-accent text-background shadow-lg shadow-accent/20"
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5"
)}
>
<tab.icon className="w-4 h-4" />
{tab.label}
</button>
))}
</nav>
{/* Plan info - hidden on mobile, shown in content area instead */}
<div className="hidden lg:block mt-5 p-6 bg-accent/5 border border-accent/20 rounded-2xl">
<div className="flex items-center gap-2 mb-3">
{isProOrHigher ? <Crown className="w-5 h-5 text-accent" /> : <Zap className="w-5 h-5 text-accent" />}
<span className="text-sm font-semibold text-foreground">{tierName} Plan</span>
</div>
<p className="text-xs text-foreground-muted mb-4">
{subscription?.domains_used || 0} / {subscription?.domain_limit || 5} domains tracked
</p>
{!isProOrHigher && (
<Link
href="/pricing"
className="flex items-center justify-center gap-2 w-full py-2.5 bg-accent text-background text-ui-sm font-medium rounded-xl hover:bg-accent-hover transition-all"
>
Upgrade
<ChevronRight className="w-3.5 h-3.5" />
</Link>
)}
{/* Plan info */}
<div className="hidden lg:block p-5 bg-accent/5 border border-accent/20 rounded-2xl">
<div className="flex items-center gap-2 mb-3">
{isProOrHigher ? <Crown className="w-5 h-5 text-accent" /> : <Zap className="w-5 h-5 text-accent" />}
<span className="text-sm font-semibold text-foreground">{tierName} Plan</span>
</div>
</div>
{/* Content */}
<div className="flex-1 min-w-0">
{/* Profile Tab */}
{activeTab === 'profile' && (
<div className="p-6 sm:p-8 bg-background-secondary/30 border border-border rounded-2xl">
<h2 className="text-body-lg font-medium text-foreground mb-6">Profile Information</h2>
<form onSubmit={handleSaveProfile} className="space-y-5">
<div>
<label className="block text-ui-sm text-foreground-muted mb-2">Name</label>
<input
type="text"
value={profileForm.name}
onChange={(e) => setProfileForm({ ...profileForm, name: e.target.value })}
placeholder="Your name"
className="w-full px-4 py-3.5 bg-background border border-border rounded-xl text-body text-foreground
placeholder:text-foreground-subtle focus:outline-none focus:ring-1 focus:ring-accent/50 focus:border-accent/50 transition-all"
/>
</div>
<div>
<label className="block text-ui-sm text-foreground-muted mb-2">Email</label>
<input
type="email"
value={profileForm.email}
disabled
className="w-full px-4 py-3.5 bg-background-tertiary border border-border rounded-xl text-body text-foreground-muted cursor-not-allowed"
/>
<p className="text-ui-xs text-foreground-subtle mt-1.5">Email cannot be changed</p>
</div>
<button
type="submit"
disabled={saving}
className="px-6 py-3.5 bg-foreground text-background text-ui font-medium rounded-xl
hover:bg-foreground/90 disabled:opacity-50 transition-all flex items-center gap-2 shadow-lg shadow-foreground/10"
>
{saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
Save Changes
</button>
</form>
</div>
)}
{/* Notifications Tab */}
{activeTab === 'notifications' && (
<div className="space-y-6">
<div className="p-6 sm:p-8 bg-background-secondary/30 border border-border rounded-2xl">
<h2 className="text-body-lg font-medium text-foreground mb-5">Email Preferences</h2>
<div className="space-y-3">
<label className="flex items-center justify-between p-4 bg-background border border-border rounded-xl cursor-pointer hover:border-foreground/20 transition-colors">
<div>
<p className="text-body-sm font-medium text-foreground">Domain Availability</p>
<p className="text-body-xs text-foreground-muted">Get notified when watched domains become available</p>
</div>
<input
type="checkbox"
checked={notificationPrefs.domain_availability}
onChange={(e) => setNotificationPrefs({ ...notificationPrefs, domain_availability: e.target.checked })}
className="w-5 h-5 accent-accent cursor-pointer"
/>
</label>
<label className="flex items-center justify-between p-4 bg-background border border-border rounded-xl cursor-pointer hover:border-foreground/20 transition-colors">
<div>
<p className="text-body-sm font-medium text-foreground">Price Alerts</p>
<p className="text-body-xs text-foreground-muted">Get notified when TLD prices change</p>
</div>
<input
type="checkbox"
checked={notificationPrefs.price_alerts}
onChange={(e) => setNotificationPrefs({ ...notificationPrefs, price_alerts: e.target.checked })}
className="w-5 h-5 accent-accent cursor-pointer"
/>
</label>
<label className="flex items-center justify-between p-4 bg-background border border-border rounded-xl cursor-pointer hover:border-foreground/20 transition-colors">
<div>
<p className="text-body-sm font-medium text-foreground">Weekly Digest</p>
<p className="text-body-xs text-foreground-muted">Receive a weekly summary of your portfolio</p>
</div>
<input
type="checkbox"
checked={notificationPrefs.weekly_digest}
onChange={(e) => setNotificationPrefs({ ...notificationPrefs, weekly_digest: e.target.checked })}
className="w-5 h-5 accent-accent cursor-pointer"
/>
</label>
</div>
<button
onClick={handleSaveNotifications}
disabled={savingNotifications}
className="mt-5 px-6 py-3 bg-foreground text-background text-ui font-medium rounded-xl
hover:bg-foreground/90 disabled:opacity-50 transition-all flex items-center gap-2"
>
{savingNotifications ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
Save Preferences
</button>
</div>
{/* Active Price Alerts */}
<div className="p-6 sm:p-8 bg-background-secondary/30 border border-border rounded-2xl">
<h2 className="text-body-lg font-medium text-foreground mb-5">Active Price Alerts</h2>
{loadingAlerts ? (
<div className="py-10 flex items-center justify-center">
<Loader2 className="w-6 h-6 animate-spin text-accent" />
</div>
) : priceAlerts.length === 0 ? (
<div className="py-12 text-center border border-dashed border-border/50 rounded-xl bg-background/30">
<Bell className="w-10 h-10 text-foreground-subtle mx-auto mb-4" />
<p className="text-body text-foreground-muted mb-3">No price alerts set</p>
<Link
href="/tld-pricing"
className="text-accent hover:text-accent-hover text-body-sm font-medium"
>
Browse TLD prices
</Link>
</div>
) : (
<div className="space-y-2">
{priceAlerts.map((alert) => (
<div
key={alert.id}
className="flex items-center justify-between p-4 bg-background border border-border rounded-xl hover:border-foreground/20 transition-colors"
>
<div className="flex items-center gap-3">
<div className="relative">
<div className={clsx(
"w-2.5 h-2.5 rounded-full",
alert.is_active ? "bg-accent" : "bg-foreground-subtle"
)} />
{alert.is_active && (
<span className="absolute inset-0 rounded-full bg-accent animate-ping opacity-40" />
)}
</div>
<div>
<Link
href={`/tld-pricing/${alert.tld}`}
className="text-body-sm font-mono font-medium text-foreground hover:text-accent transition-colors"
>
.{alert.tld}
</Link>
<p className="text-body-xs text-foreground-muted">
Alert on {alert.threshold_percent}% change
{alert.target_price && ` or below $${alert.target_price}`}
</p>
</div>
</div>
<button
onClick={() => handleDeletePriceAlert(alert.tld, alert.id)}
disabled={deletingAlertId === alert.id}
className="p-2 text-foreground-subtle hover:text-danger hover:bg-danger/10 rounded-lg transition-all"
>
{deletingAlertId === alert.id ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Trash2 className="w-4 h-4" />
)}
</button>
</div>
))}
</div>
)}
</div>
</div>
)}
{/* Billing Tab */}
{activeTab === 'billing' && (
<div className="space-y-6">
{/* Current Plan */}
<div className="p-6 sm:p-8 bg-background-secondary/30 border border-border rounded-2xl">
<h2 className="text-body-lg font-medium text-foreground mb-6">Your Current Plan</h2>
<div className="p-5 bg-accent/5 border border-accent/20 rounded-xl mb-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
{tierName === 'Tycoon' ? (
<Crown className="w-6 h-6 text-accent" />
) : tierName === 'Trader' ? (
<TrendingUp className="w-6 h-6 text-accent" />
) : (
<Zap className="w-6 h-6 text-accent" />
)}
<div>
<p className="text-xl font-semibold text-foreground">{tierName}</p>
<p className="text-body-sm text-foreground-muted">
{tierName === 'Scout' ? 'Free forever' : tierName === 'Trader' ? '$9/month' : '$29/month'}
</p>
</div>
</div>
<span className={clsx(
"px-3 py-1.5 text-ui-xs font-medium rounded-full",
isProOrHigher ? "bg-accent/10 text-accent" : "bg-foreground/5 text-foreground-muted"
)}>
{isProOrHigher ? 'Active' : 'Free'}
</span>
</div>
{/* Plan Stats */}
<div className="grid grid-cols-3 gap-4 p-4 bg-background/50 rounded-xl mb-4">
<div className="text-center">
<p className="text-2xl font-semibold text-foreground">{subscription?.domain_limit || 5}</p>
<p className="text-xs text-foreground-muted">Domains</p>
</div>
<div className="text-center border-x border-border/50">
<p className="text-2xl font-semibold text-foreground">
{subscription?.check_frequency === 'realtime' ? '10m' :
subscription?.check_frequency === 'hourly' ? '1h' : '24h'}
</p>
<p className="text-xs text-foreground-muted">Check Interval</p>
</div>
<div className="text-center">
<p className="text-2xl font-semibold text-foreground">
{subscription?.portfolio_limit === -1 ? '∞' : subscription?.portfolio_limit || 0}
</p>
<p className="text-xs text-foreground-muted">Portfolio</p>
</div>
</div>
{isProOrHigher ? (
<button
onClick={handleOpenBillingPortal}
className="w-full py-3 bg-background text-foreground text-ui font-medium rounded-xl border border-border
hover:border-foreground/20 transition-all flex items-center justify-center gap-2"
>
<ExternalLink className="w-4 h-4" />
Manage Subscription
</button>
) : (
<Link
href="/pricing"
className="w-full py-3 bg-accent text-background text-ui font-medium rounded-xl
hover:bg-accent-hover transition-all flex items-center justify-center gap-2 shadow-lg shadow-accent/20"
>
<Zap className="w-4 h-4" />
Upgrade Plan
</Link>
)}
</div>
{/* Plan Features */}
<h3 className="text-body-sm font-medium text-foreground mb-3">Your Plan Includes</h3>
<ul className="grid grid-cols-2 gap-2">
<li className="flex items-center gap-2 text-body-sm">
<Check className="w-4 h-4 text-accent" />
<span className="text-foreground">{subscription?.domain_limit || 5} Watchlist Domains</span>
</li>
<li className="flex items-center gap-2 text-body-sm">
<Check className="w-4 h-4 text-accent" />
<span className="text-foreground">
{subscription?.check_frequency === 'realtime' ? '10-minute' :
subscription?.check_frequency === 'hourly' ? 'Hourly' : 'Daily'} Scans
</span>
</li>
<li className="flex items-center gap-2 text-body-sm">
<Check className="w-4 h-4 text-accent" />
<span className="text-foreground">Email Alerts</span>
</li>
<li className="flex items-center gap-2 text-body-sm">
<Check className="w-4 h-4 text-accent" />
<span className="text-foreground">TLD Price Data</span>
</li>
{subscription?.features?.domain_valuation && (
<li className="flex items-center gap-2 text-body-sm">
<Check className="w-4 h-4 text-accent" />
<span className="text-foreground">Domain Valuation</span>
</li>
)}
{(subscription?.portfolio_limit ?? 0) !== 0 && (
<li className="flex items-center gap-2 text-body-sm">
<Check className="w-4 h-4 text-accent" />
<span className="text-foreground">
{subscription?.portfolio_limit === -1 ? 'Unlimited' : subscription?.portfolio_limit} Portfolio
</span>
</li>
)}
{subscription?.features?.expiration_tracking && (
<li className="flex items-center gap-2 text-body-sm">
<Check className="w-4 h-4 text-accent" />
<span className="text-foreground">Expiry Tracking</span>
</li>
)}
{(subscription?.history_days ?? 0) !== 0 && (
<li className="flex items-center gap-2 text-body-sm">
<Check className="w-4 h-4 text-accent" />
<span className="text-foreground">
{subscription?.history_days === -1 ? 'Full' : `${subscription?.history_days}-day`} History
</span>
</li>
)}
</ul>
</div>
{/* Compare All Plans */}
<div className="p-6 sm:p-8 bg-background-secondary/30 border border-border rounded-2xl">
<h2 className="text-body-lg font-medium text-foreground mb-6">Compare All Plans</h2>
<div className="overflow-x-auto -mx-2">
<table className="w-full min-w-[500px]">
<thead>
<tr className="border-b border-border">
<th className="text-left py-3 px-3 text-body-sm font-medium text-foreground-muted">Feature</th>
<th className={clsx(
"text-center py-3 px-3 text-body-sm font-medium",
tierName === 'Scout' ? "text-accent" : "text-foreground-muted"
)}>Scout</th>
<th className={clsx(
"text-center py-3 px-3 text-body-sm font-medium",
tierName === 'Trader' ? "text-accent" : "text-foreground-muted"
)}>Trader</th>
<th className={clsx(
"text-center py-3 px-3 text-body-sm font-medium",
tierName === 'Tycoon' ? "text-accent" : "text-foreground-muted"
)}>Tycoon</th>
</tr>
</thead>
<tbody>
<tr className="border-b border-border/50">
<td className="py-3 px-3 text-body-sm text-foreground">Price</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">Free</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">$9/mo</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">$29/mo</td>
</tr>
<tr className="border-b border-border/50">
<td className="py-3 px-3 text-body-sm text-foreground">Watchlist Domains</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">5</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">50</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">500</td>
</tr>
<tr className="border-b border-border/50">
<td className="py-3 px-3 text-body-sm text-foreground">Scan Frequency</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">Daily</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">Hourly</td>
<td className="py-3 px-3 text-center text-body-sm text-accent font-medium">10 min</td>
</tr>
<tr className="border-b border-border/50">
<td className="py-3 px-3 text-body-sm text-foreground">Portfolio</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground-muted"></td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">25</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">Unlimited</td>
</tr>
<tr className="border-b border-border/50">
<td className="py-3 px-3 text-body-sm text-foreground">Domain Valuation</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground-muted"></td>
<td className="py-3 px-3 text-center"><Check className="w-5 h-5 text-accent mx-auto" /></td>
<td className="py-3 px-3 text-center"><Check className="w-5 h-5 text-accent mx-auto" /></td>
</tr>
<tr className="border-b border-border/50">
<td className="py-3 px-3 text-body-sm text-foreground">Price History</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground-muted"></td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">90 days</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground">Unlimited</td>
</tr>
<tr>
<td className="py-3 px-3 text-body-sm text-foreground">Expiry Tracking</td>
<td className="py-3 px-3 text-center text-body-sm text-foreground-muted"></td>
<td className="py-3 px-3 text-center"><Check className="w-5 h-5 text-accent mx-auto" /></td>
<td className="py-3 px-3 text-center"><Check className="w-5 h-5 text-accent mx-auto" /></td>
</tr>
</tbody>
</table>
</div>
{!isProOrHigher && (
<div className="mt-6 text-center">
<Link
href="/pricing"
className="inline-flex items-center gap-2 px-6 py-3 bg-accent text-background text-ui font-medium rounded-xl
hover:bg-accent-hover transition-all shadow-lg shadow-accent/20"
>
<Zap className="w-4 h-4" />
Upgrade Now
</Link>
</div>
)}
</div>
</div>
)}
{/* Security Tab */}
{activeTab === 'security' && (
<div className="space-y-6">
<div className="p-6 sm:p-8 bg-background-secondary/30 border border-border rounded-2xl">
<h2 className="text-body-lg font-medium text-foreground mb-4">Password</h2>
<p className="text-body-sm text-foreground-muted mb-5">
Change your password or reset it if you've forgotten it.
</p>
<Link
href="/forgot-password"
className="inline-flex items-center gap-2 px-5 py-3 bg-background border border-border text-foreground text-ui font-medium rounded-xl
hover:border-foreground/20 transition-all"
>
<Key className="w-4 h-4" />
Change Password
</Link>
</div>
<div className="p-6 sm:p-8 bg-background-secondary/30 border border-border rounded-2xl">
<h2 className="text-body-lg font-medium text-foreground mb-5">Account Security</h2>
<div className="space-y-3">
<div className="flex items-center justify-between p-4 bg-background border border-border rounded-xl">
<div>
<p className="text-body-sm font-medium text-foreground">Email Verified</p>
<p className="text-body-xs text-foreground-muted">Your email address has been verified</p>
</div>
<div className="w-8 h-8 bg-accent/10 rounded-lg flex items-center justify-center">
<Check className="w-4 h-4 text-accent" />
</div>
</div>
<div className="flex items-center justify-between p-4 bg-background border border-border rounded-xl">
<div>
<p className="text-body-sm font-medium text-foreground">Two-Factor Authentication</p>
<p className="text-body-xs text-foreground-muted">Coming soon</p>
</div>
<span className="text-ui-xs px-2.5 py-1 bg-foreground/5 text-foreground-muted rounded-full">Soon</span>
</div>
</div>
</div>
<div className="p-6 sm:p-8 bg-danger/5 border border-danger/20 rounded-2xl">
<h2 className="text-body-lg font-medium text-danger mb-2">Danger Zone</h2>
<p className="text-body-sm text-foreground-muted mb-5">
Permanently delete your account and all associated data.
</p>
<button
className="px-5 py-3 bg-danger text-white text-ui font-medium rounded-xl hover:bg-danger/90 transition-all"
>
Delete Account
</button>
</div>
</div>
<p className="text-xs text-foreground-muted mb-4">
{subscription?.domains_used || 0} / {subscription?.domain_limit || 5} domains tracked
</p>
{!isProOrHigher && (
<Link
href="/pricing"
className="flex items-center justify-center gap-2 w-full py-2.5 bg-gradient-to-r from-accent to-accent/80 text-background text-sm font-medium rounded-xl hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)] transition-all"
>
Upgrade
<ChevronRight className="w-3.5 h-3.5" />
</Link>
)}
</div>
</div>
{/* Content */}
<div className="flex-1 min-w-0">
{/* Profile Tab */}
{activeTab === 'profile' && (
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
<h2 className="text-lg font-medium text-foreground mb-6">Profile Information</h2>
<form onSubmit={handleSaveProfile} className="space-y-5">
<div>
<label className="block text-sm text-foreground-muted mb-2">Name</label>
<input
type="text"
value={profileForm.name}
onChange={(e) => setProfileForm({ ...profileForm, name: e.target.value })}
placeholder="Your name"
className="w-full h-11 px-4 bg-background border border-border/50 rounded-xl text-foreground
placeholder:text-foreground-subtle focus:outline-none focus:border-accent/50 transition-all"
/>
</div>
<div>
<label className="block text-sm text-foreground-muted mb-2">Email</label>
<input
type="email"
value={profileForm.email}
disabled
className="w-full h-11 px-4 bg-foreground/5 border border-border/50 rounded-xl text-foreground-muted cursor-not-allowed"
/>
<p className="text-xs text-foreground-subtle mt-1.5">Email cannot be changed</p>
</div>
<button
type="submit"
disabled={saving}
className="flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-accent to-accent/80 text-background font-medium rounded-xl
hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)] disabled:opacity-50 transition-all"
>
{saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
Save Changes
</button>
</form>
</div>
)}
{/* Notifications Tab */}
{activeTab === 'notifications' && (
<div className="space-y-6">
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
<h2 className="text-lg font-medium text-foreground mb-5">Email Preferences</h2>
<div className="space-y-3">
{[
{ key: 'domain_availability', label: 'Domain Availability', desc: 'Get notified when watched domains become available' },
{ key: 'price_alerts', label: 'Price Alerts', desc: 'Get notified when TLD prices change' },
{ key: 'weekly_digest', label: 'Weekly Digest', desc: 'Receive a weekly summary of your portfolio' },
].map((item) => (
<label key={item.key} className="flex items-center justify-between p-4 bg-foreground/5 border border-border/30 rounded-xl cursor-pointer hover:border-foreground/20 transition-colors">
<div>
<p className="text-sm font-medium text-foreground">{item.label}</p>
<p className="text-xs text-foreground-muted">{item.desc}</p>
</div>
<input
type="checkbox"
checked={notificationPrefs[item.key as keyof typeof notificationPrefs]}
onChange={(e) => setNotificationPrefs({ ...notificationPrefs, [item.key]: e.target.checked })}
className="w-5 h-5 accent-accent cursor-pointer"
/>
</label>
))}
</div>
<button
onClick={handleSaveNotifications}
disabled={savingNotifications}
className="mt-5 flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-accent to-accent/80 text-background font-medium rounded-xl
hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)] disabled:opacity-50 transition-all"
>
{savingNotifications ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
Save Preferences
</button>
</div>
{/* Active Price Alerts */}
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
<h2 className="text-lg font-medium text-foreground mb-5">Active Price Alerts</h2>
{loadingAlerts ? (
<div className="py-10 flex items-center justify-center">
<Loader2 className="w-6 h-6 animate-spin text-accent" />
</div>
) : priceAlerts.length === 0 ? (
<div className="py-12 text-center border border-dashed border-border/50 rounded-xl bg-foreground/5">
<Bell className="w-10 h-10 text-foreground-subtle mx-auto mb-4" />
<p className="text-foreground-muted mb-3">No price alerts set</p>
<Link href="/intelligence" className="text-accent hover:text-accent/80 text-sm font-medium">
Browse TLD prices
</Link>
</div>
) : (
<div className="space-y-2">
{priceAlerts.map((alert) => (
<div
key={alert.id}
className="flex items-center justify-between p-4 bg-foreground/5 border border-border/30 rounded-xl hover:border-foreground/20 transition-colors"
>
<div className="flex items-center gap-3">
<div className="relative">
<div className={clsx(
"w-2.5 h-2.5 rounded-full",
alert.is_active ? "bg-accent" : "bg-foreground-subtle"
)} />
{alert.is_active && (
<span className="absolute inset-0 rounded-full bg-accent animate-ping opacity-40" />
)}
</div>
<div>
<Link
href={`/tld-pricing/${alert.tld}`}
className="text-sm font-mono font-medium text-foreground hover:text-accent transition-colors"
>
.{alert.tld}
</Link>
<p className="text-xs text-foreground-muted">
Alert on {alert.threshold_percent}% change
{alert.target_price && ` or below $${alert.target_price}`}
</p>
</div>
</div>
<button
onClick={() => handleDeletePriceAlert(alert.tld, alert.id)}
disabled={deletingAlertId === alert.id}
className="p-2 text-foreground-subtle hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-all"
>
{deletingAlertId === alert.id ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Trash2 className="w-4 h-4" />
)}
</button>
</div>
))}
</div>
)}
</div>
</div>
)}
{/* Billing Tab */}
{activeTab === 'billing' && (
<div className="space-y-6">
{/* Current Plan */}
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
<h2 className="text-lg font-medium text-foreground mb-6">Your Current Plan</h2>
<div className="p-5 bg-accent/5 border border-accent/20 rounded-xl mb-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
{tierName === 'Tycoon' ? <Crown className="w-6 h-6 text-accent" /> : tierName === 'Trader' ? <TrendingUp className="w-6 h-6 text-accent" /> : <Zap className="w-6 h-6 text-accent" />}
<div>
<p className="text-xl font-semibold text-foreground">{tierName}</p>
<p className="text-sm text-foreground-muted">
{tierName === 'Scout' ? 'Free forever' : tierName === 'Trader' ? '$9/month' : '$29/month'}
</p>
</div>
</div>
<span className={clsx(
"px-3 py-1.5 text-xs font-medium rounded-full",
isProOrHigher ? "bg-accent/10 text-accent" : "bg-foreground/5 text-foreground-muted"
)}>
{isProOrHigher ? 'Active' : 'Free'}
</span>
</div>
{/* Plan Stats */}
<div className="grid grid-cols-3 gap-4 p-4 bg-background/50 rounded-xl mb-4">
<div className="text-center">
<p className="text-2xl font-semibold text-foreground">{subscription?.domain_limit || 5}</p>
<p className="text-xs text-foreground-muted">Domains</p>
</div>
<div className="text-center border-x border-border/50">
<p className="text-2xl font-semibold text-foreground">
{subscription?.check_frequency === 'realtime' ? '10m' :
subscription?.check_frequency === 'hourly' ? '1h' : '24h'}
</p>
<p className="text-xs text-foreground-muted">Check Interval</p>
</div>
<div className="text-center">
<p className="text-2xl font-semibold text-foreground">
{subscription?.portfolio_limit === -1 ? '∞' : subscription?.portfolio_limit || 0}
</p>
<p className="text-xs text-foreground-muted">Portfolio</p>
</div>
</div>
{isProOrHigher ? (
<button
onClick={handleOpenBillingPortal}
className="w-full flex items-center justify-center gap-2 py-3 bg-background text-foreground font-medium rounded-xl border border-border/50
hover:border-foreground/20 transition-all"
>
<ExternalLink className="w-4 h-4" />
Manage Subscription
</button>
) : (
<Link
href="/pricing"
className="w-full flex items-center justify-center gap-2 py-3 bg-gradient-to-r from-accent to-accent/80 text-background font-medium rounded-xl
hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)] transition-all"
>
<Zap className="w-4 h-4" />
Upgrade Plan
</Link>
)}
</div>
{/* Plan Features */}
<h3 className="text-sm font-medium text-foreground mb-3">Your Plan Includes</h3>
<ul className="grid grid-cols-2 gap-2">
{[
`${subscription?.domain_limit || 5} Watchlist Domains`,
`${subscription?.check_frequency === 'realtime' ? '10-minute' : subscription?.check_frequency === 'hourly' ? 'Hourly' : 'Daily'} Scans`,
'Email Alerts',
'TLD Price Data',
].map((feature) => (
<li key={feature} className="flex items-center gap-2 text-sm">
<Check className="w-4 h-4 text-accent" />
<span className="text-foreground">{feature}</span>
</li>
))}
</ul>
</div>
</div>
)}
{/* Security Tab */}
{activeTab === 'security' && (
<div className="space-y-6">
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
<h2 className="text-lg font-medium text-foreground mb-4">Password</h2>
<p className="text-sm text-foreground-muted mb-5">
Change your password or reset it if you've forgotten it.
</p>
<Link
href="/forgot-password"
className="inline-flex items-center gap-2 px-5 py-3 bg-foreground/5 border border-border/50 text-foreground font-medium rounded-xl
hover:border-foreground/20 transition-all"
>
<Key className="w-4 h-4" />
Change Password
</Link>
</div>
<div className="relative overflow-hidden rounded-2xl border border-border/40 bg-gradient-to-b from-background-secondary/40 to-background-secondary/20 backdrop-blur-sm p-6">
<h2 className="text-lg font-medium text-foreground mb-5">Account Security</h2>
<div className="space-y-3">
<div className="flex items-center justify-between p-4 bg-foreground/5 border border-border/30 rounded-xl">
<div>
<p className="text-sm font-medium text-foreground">Email Verified</p>
<p className="text-xs text-foreground-muted">Your email address has been verified</p>
</div>
<div className="w-8 h-8 bg-accent/10 rounded-lg flex items-center justify-center">
<Check className="w-4 h-4 text-accent" />
</div>
</div>
<div className="flex items-center justify-between p-4 bg-foreground/5 border border-border/30 rounded-xl">
<div>
<p className="text-sm font-medium text-foreground">Two-Factor Authentication</p>
<p className="text-xs text-foreground-muted">Coming soon</p>
</div>
<span className="text-xs px-2.5 py-1 bg-foreground/5 text-foreground-muted rounded-full border border-border/30">Soon</span>
</div>
</div>
</div>
<div className="relative overflow-hidden rounded-2xl border border-red-500/20 bg-red-500/5 p-6">
<h2 className="text-lg font-medium text-red-400 mb-2">Danger Zone</h2>
<p className="text-sm text-foreground-muted mb-5">
Permanently delete your account and all associated data.
</p>
<button
className="px-5 py-3 bg-red-500 text-white font-medium rounded-xl hover:bg-red-500/90 transition-all"
>
Delete Account
</button>
</div>
</div>
)}
</div>
</div>
</main>
</PageContainer>
</CommandCenterLayout>
)
}

View File

@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'
import { useStore } from '@/lib/store'
import { api } from '@/lib/api'
import { CommandCenterLayout } from '@/components/CommandCenterLayout'
import { PremiumTable, Badge, StatCard, PageContainer, TableActionButton } from '@/components/PremiumTable'
import { Toast, useToast } from '@/components/Toast'
import {
Plus,
@ -14,10 +15,11 @@ import {
BellOff,
History,
ExternalLink,
MoreVertical,
Search,
Filter,
Eye,
Sparkles,
ArrowUpRight,
X,
} from 'lucide-react'
import clsx from 'clsx'
import Link from 'next/link'
@ -29,57 +31,6 @@ interface DomainHistory {
checked_at: string
}
// Status indicator component with traffic light system
function StatusIndicator({ domain }: { domain: any }) {
// Determine status based on domain data
let status: 'available' | 'watching' | 'stable' = 'stable'
let label = 'Stable'
let description = 'Domain is registered and active'
if (domain.is_available) {
status = 'available'
label = 'Available'
description = 'Domain is available for registration!'
} else if (domain.status === 'checking' || domain.status === 'pending') {
status = 'watching'
label = 'Watching'
description = 'Monitoring for changes'
}
const colors = {
available: 'bg-accent text-accent',
watching: 'bg-amber-400 text-amber-400',
stable: 'bg-foreground-muted text-foreground-muted',
}
return (
<div className="flex items-center gap-3">
<div className="relative">
<span className={clsx(
"block w-3 h-3 rounded-full",
colors[status].split(' ')[0]
)} />
{status === 'available' && (
<span className={clsx(
"absolute inset-0 rounded-full animate-ping opacity-75",
colors[status].split(' ')[0]
)} />
)}
</div>
<div>
<p className={clsx(
"text-sm font-medium",
status === 'available' ? 'text-accent' :
status === 'watching' ? 'text-amber-400' : 'text-foreground-muted'
)}>
{label}
</p>
<p className="text-xs text-foreground-subtle hidden sm:block">{description}</p>
</div>
</div>
)
}
export default function WatchlistPage() {
const { domains, addDomain, deleteDomain, refreshDomain, subscription } = useStore()
const { toast, showToast, hideToast } = useToast()
@ -97,11 +48,9 @@ export default function WatchlistPage() {
// Filter domains
const filteredDomains = domains?.filter(domain => {
// Search filter
if (searchQuery && !domain.name.toLowerCase().includes(searchQuery.toLowerCase())) {
return false
}
// Status filter
if (filterStatus === 'available' && !domain.is_available) return false
if (filterStatus === 'watching' && domain.is_available) return false
return true
@ -110,6 +59,9 @@ export default function WatchlistPage() {
// Stats
const availableCount = domains?.filter(d => d.is_available).length || 0
const watchingCount = domains?.filter(d => !d.is_available).length || 0
const domainsUsed = domains?.length || 0
const domainLimit = subscription?.domain_limit || 5
const canAddMore = domainsUsed < domainLimit
const handleAddDomain = async (e: React.FormEvent) => {
e.preventDefault()
@ -168,29 +120,6 @@ export default function WatchlistPage() {
}
}
const loadHistory = async (domainId: number) => {
if (selectedDomainId === domainId) {
setSelectedDomainId(null)
setDomainHistory(null)
return
}
setSelectedDomainId(domainId)
setLoadingHistory(true)
try {
const history = await api.getDomainHistory(domainId)
setDomainHistory(history)
} catch (err) {
setDomainHistory([])
} finally {
setLoadingHistory(false)
}
}
const domainLimit = subscription?.domain_limit || 5
const domainsUsed = domains?.length || 0
const canAddMore = domainsUsed < domainLimit
return (
<CommandCenterLayout
title="Watchlist"
@ -198,65 +127,43 @@ export default function WatchlistPage() {
>
{toast && <Toast message={toast.message} type={toast.type} onClose={hideToast} />}
<div className="max-w-6xl mx-auto space-y-6">
<PageContainer>
{/* Stats Cards */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
<div className="p-5 bg-gradient-to-br from-background-secondary/60 to-background-secondary/30 border border-border/50 rounded-2xl">
<p className="text-xs text-foreground-subtle uppercase tracking-wider mb-2">Total Watched</p>
<p className="text-3xl font-display text-foreground">{domainsUsed}</p>
</div>
<div className="relative p-5 bg-gradient-to-br from-accent/15 to-accent/5 border border-accent/30 rounded-2xl overflow-hidden">
<div className="absolute top-0 right-0 w-20 h-20 bg-accent/10 rounded-full blur-2xl" />
<div className="relative">
<p className="text-xs text-foreground-subtle uppercase tracking-wider mb-2">Available</p>
<p className="text-3xl font-display text-accent">{availableCount}</p>
</div>
</div>
<div className="p-5 bg-gradient-to-br from-background-secondary/60 to-background-secondary/30 border border-border/50 rounded-2xl">
<p className="text-xs text-foreground-subtle uppercase tracking-wider mb-2">Watching</p>
<p className="text-3xl font-display text-foreground">{watchingCount}</p>
</div>
<div className="p-5 bg-gradient-to-br from-background-secondary/60 to-background-secondary/30 border border-border/50 rounded-2xl">
<p className="text-xs text-foreground-subtle uppercase tracking-wider mb-2">Limit</p>
<p className="text-3xl font-display text-foreground">{domainLimit === -1 ? '∞' : domainLimit}</p>
</div>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<StatCard title="Total Watched" value={domainsUsed} icon={Eye} />
<StatCard title="Available" value={availableCount} icon={Sparkles} accent />
<StatCard title="Watching" value={watchingCount} subtitle="still registered" icon={RefreshCw} />
<StatCard title="Limit" value={domainLimit === -1 ? '∞' : domainLimit} subtitle="max domains" icon={Bell} />
</div>
{/* Add Domain Form */}
<form onSubmit={handleAddDomain} className="flex gap-3">
<div className="flex-1 relative">
<div className="flex flex-col sm:flex-row gap-3">
<div className="relative flex-1">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-subtle" />
<input
type="text"
value={newDomain}
onChange={(e) => setNewDomain(e.target.value)}
placeholder="Enter domain to track (e.g., dream.com)"
disabled={!canAddMore}
className={clsx(
"w-full h-12 px-5 bg-background-secondary/50 border border-border/50 rounded-xl",
"text-foreground placeholder:text-foreground-subtle",
"focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/30",
"disabled:opacity-50 disabled:cursor-not-allowed",
"shadow-[0_2px_8px_-2px_rgba(0,0,0,0.1)]"
)}
onKeyDown={(e) => e.key === 'Enter' && handleAddDomain(e)}
className="w-full h-11 pl-11 pr-4 bg-background-secondary/50 border border-border/50 rounded-xl
text-sm text-foreground placeholder:text-foreground-subtle
focus:outline-none focus:border-accent/50 transition-all
disabled:opacity-50 disabled:cursor-not-allowed"
/>
</div>
<button
type="submit"
onClick={handleAddDomain}
disabled={adding || !newDomain.trim() || !canAddMore}
className={clsx(
"flex items-center gap-2 h-12 px-6 rounded-xl font-medium transition-all",
"bg-gradient-to-r from-accent to-accent/80 text-background hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)]",
"disabled:opacity-50 disabled:cursor-not-allowed"
)}
className="flex items-center justify-center gap-2 h-11 px-6 bg-gradient-to-r from-accent to-accent/80
text-background rounded-xl font-medium hover:shadow-[0_0_20px_-5px_rgba(16,185,129,0.4)]
disabled:opacity-50 disabled:cursor-not-allowed transition-all"
>
{adding ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Plus className="w-4 h-4" />
)}
Add
{adding ? <Loader2 className="w-4 h-4 animate-spin" /> : <Plus className="w-4 h-4" />}
<span>Add Domain</span>
</button>
</form>
</div>
{!canAddMore && (
<div className="flex items-center justify-between p-4 bg-amber-500/10 border border-amber-500/20 rounded-xl">
@ -274,225 +181,165 @@ export default function WatchlistPage() {
{/* Filters */}
<div className="flex flex-col sm:flex-row gap-3 sm:items-center sm:justify-between">
<div className="flex gap-2">
<button
onClick={() => setFilterStatus('all')}
className={clsx(
"px-4 py-2 text-sm rounded-lg transition-colors",
filterStatus === 'all'
? "bg-foreground/10 text-foreground"
: "text-foreground-muted hover:bg-foreground/5"
)}
>
All ({domainsUsed})
</button>
<button
onClick={() => setFilterStatus('available')}
className={clsx(
"px-4 py-2 text-sm rounded-lg transition-colors flex items-center gap-2",
filterStatus === 'available'
? "bg-accent/10 text-accent"
: "text-foreground-muted hover:bg-foreground/5"
)}
>
<span className="w-2 h-2 rounded-full bg-accent" />
Available ({availableCount})
</button>
<button
onClick={() => setFilterStatus('watching')}
className={clsx(
"px-4 py-2 text-sm rounded-lg transition-colors flex items-center gap-2",
filterStatus === 'watching'
? "bg-foreground/10 text-foreground"
: "text-foreground-muted hover:bg-foreground/5"
)}
>
<span className="w-2 h-2 rounded-full bg-foreground-muted" />
Watching ({watchingCount})
</button>
<div className="flex flex-wrap items-center gap-2 p-1.5 bg-background-secondary/30 border border-border/30 rounded-2xl w-fit">
{[
{ id: 'all' as const, label: 'All', count: domainsUsed },
{ id: 'available' as const, label: 'Available', count: availableCount, color: 'accent' },
{ id: 'watching' as const, label: 'Watching', count: watchingCount },
].map((tab) => (
<button
key={tab.id}
onClick={() => setFilterStatus(tab.id)}
className={clsx(
"flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-xl transition-all",
filterStatus === tab.id
? tab.color === 'accent'
? "bg-gradient-to-r from-accent to-accent/80 text-background shadow-lg shadow-accent/20"
: "bg-foreground/10 text-foreground"
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5"
)}
>
{tab.id === 'available' && <span className="w-2 h-2 rounded-full bg-accent" />}
{tab.id === 'watching' && <span className="w-2 h-2 rounded-full bg-foreground-muted" />}
<span>{tab.label}</span>
<span className={clsx(
"text-xs px-1.5 py-0.5 rounded",
filterStatus === tab.id ? "bg-background/20" : "bg-foreground/10"
)}>{tab.count}</span>
</button>
))}
</div>
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-muted" />
<div className="relative max-w-xs">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-muted" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search domains..."
className="w-full sm:w-64 h-10 pl-9 pr-4 bg-background-secondary border border-border rounded-lg
placeholder="Filter domains..."
className="w-full h-10 pl-10 pr-10 bg-background-secondary/50 border border-border/50 rounded-xl
text-sm text-foreground placeholder:text-foreground-subtle
focus:outline-none focus:border-accent"
focus:outline-none focus:border-accent/50"
/>
{searchQuery && (
<button onClick={() => setSearchQuery('')} className="absolute right-3 top-1/2 -translate-y-1/2 text-foreground-subtle hover:text-foreground">
<X className="w-4 h-4" />
</button>
)}
</div>
</div>
{/* Domain List */}
<div className="space-y-3">
{filteredDomains.length === 0 ? (
<div className="text-center py-16 bg-background-secondary/30 border border-border rounded-xl">
{domainsUsed === 0 ? (
<>
<div className="w-16 h-16 bg-foreground/5 rounded-2xl flex items-center justify-center mx-auto mb-4">
<Plus className="w-8 h-8 text-foreground-subtle" />
</div>
<p className="text-foreground-muted mb-2">Your watchlist is empty</p>
<p className="text-sm text-foreground-subtle">Add a domain above to start tracking</p>
</>
) : (
<>
<Filter className="w-8 h-8 text-foreground-subtle mx-auto mb-4" />
<p className="text-foreground-muted">No domains match your filters</p>
</>
)}
</div>
) : (
filteredDomains.map((domain) => (
<div
key={domain.id}
className={clsx(
"group p-4 sm:p-5 rounded-xl border transition-all duration-200",
domain.is_available
? "bg-accent/5 border-accent/20 hover:border-accent/40"
: "bg-background-secondary/50 border-border hover:border-foreground/20"
)}
>
<div className="flex flex-col sm:flex-row sm:items-center gap-4">
{/* Domain Name + Status */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-lg font-semibold text-foreground truncate">
{domain.name}
</h3>
{domain.is_available && (
<span className="shrink-0 px-2 py-0.5 bg-accent/20 text-accent text-xs font-semibold rounded-full">
GRAB IT!
</span>
)}
</div>
<StatusIndicator domain={domain} />
</div>
{/* Actions */}
<div className="flex items-center gap-2 shrink-0">
{/* Notify Toggle */}
<button
onClick={() => handleToggleNotify(domain.id, domain.notify_on_available)}
disabled={togglingNotifyId === domain.id}
className={clsx(
"p-2 rounded-lg transition-colors",
domain.notify_on_available
? "bg-accent/10 text-accent hover:bg-accent/20"
: "text-foreground-muted hover:bg-foreground/5"
)}
title={domain.notify_on_available ? "Disable alerts" : "Enable alerts"}
>
{togglingNotifyId === domain.id ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : domain.notify_on_available ? (
<Bell className="w-4 h-4" />
) : (
<BellOff className="w-4 h-4" />
)}
</button>
{/* History */}
<button
onClick={() => loadHistory(domain.id)}
className={clsx(
"p-2 rounded-lg transition-colors",
selectedDomainId === domain.id
? "bg-foreground/10 text-foreground"
: "text-foreground-muted hover:bg-foreground/5"
)}
title="View history"
>
<History className="w-4 h-4" />
</button>
{/* Refresh */}
<button
onClick={() => handleRefresh(domain.id)}
disabled={refreshingId === domain.id}
className="p-2 rounded-lg text-foreground-muted hover:bg-foreground/5 transition-colors"
title="Refresh status"
>
<RefreshCw className={clsx(
"w-4 h-4",
refreshingId === domain.id && "animate-spin"
)} />
</button>
{/* Delete */}
<button
onClick={() => handleDelete(domain.id, domain.name)}
disabled={deletingId === domain.id}
className="p-2 rounded-lg text-foreground-muted hover:text-red-400 hover:bg-red-400/10 transition-colors"
title="Remove"
>
{deletingId === domain.id ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Trash2 className="w-4 h-4" />
)}
</button>
{/* External Link (if available) */}
{/* Domain Table */}
<PremiumTable
data={filteredDomains}
keyExtractor={(d) => d.id}
emptyIcon={<Eye className="w-12 h-12 text-foreground-subtle" />}
emptyTitle={domainsUsed === 0 ? "Your watchlist is empty" : "No domains match your filters"}
emptyDescription={domainsUsed === 0 ? "Add a domain above to start tracking" : "Try adjusting your filter criteria"}
columns={[
{
key: 'domain',
header: 'Domain',
render: (domain) => (
<div className="flex items-center gap-3">
<div className="relative">
<span className={clsx(
"block w-3 h-3 rounded-full",
domain.is_available ? "bg-accent" : "bg-foreground-muted/50"
)} />
{domain.is_available && (
<a
href={`https://www.namecheap.com/domains/registration/results/?domain=${domain.name}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 px-4 py-2 bg-accent text-background rounded-lg
font-medium text-sm hover:bg-accent-hover transition-colors"
>
Register
<ExternalLink className="w-3 h-3" />
</a>
<span className="absolute inset-0 rounded-full bg-accent animate-ping opacity-50" />
)}
</div>
<div>
<span className="font-mono font-medium text-foreground">{domain.name}</span>
{domain.is_available && (
<Badge variant="success" size="xs" className="ml-2">AVAILABLE</Badge>
)}
</div>
</div>
{/* History Panel */}
{selectedDomainId === domain.id && (
<div className="mt-4 pt-4 border-t border-border/50">
<h4 className="text-sm font-medium text-foreground-muted mb-3">Status History</h4>
{loadingHistory ? (
<div className="flex items-center gap-2 text-foreground-muted">
<Loader2 className="w-4 h-4 animate-spin" />
<span className="text-sm">Acquiring targets...</span>
</div>
) : domainHistory && domainHistory.length > 0 ? (
<div className="space-y-2">
{domainHistory.slice(0, 5).map((entry) => (
<div
key={entry.id}
className="flex items-center gap-3 text-sm"
>
<span className={clsx(
"w-2 h-2 rounded-full",
entry.is_available ? "bg-accent" : "bg-foreground-muted"
)} />
<span className="text-foreground-muted">
{new Date(entry.checked_at).toLocaleDateString()} at{' '}
{new Date(entry.checked_at).toLocaleTimeString()}
</span>
<span className="text-foreground">
{entry.is_available ? 'Available' : 'Registered'}
</span>
</div>
))}
</div>
) : (
<p className="text-sm text-foreground-subtle">No history available yet</p>
)}
</div>
)}
</div>
))
)}
</div>
</div>
),
},
{
key: 'status',
header: 'Status',
hideOnMobile: true,
render: (domain) => (
<span className={clsx(
"text-sm",
domain.is_available ? "text-accent font-medium" : "text-foreground-muted"
)}>
{domain.is_available ? 'Ready to register!' : 'Monitoring...'}
</span>
),
},
{
key: 'notifications',
header: 'Alerts',
align: 'center',
hideOnMobile: true,
render: (domain) => (
<button
onClick={(e) => {
e.stopPropagation()
handleToggleNotify(domain.id, domain.notify_on_available)
}}
disabled={togglingNotifyId === domain.id}
className={clsx(
"p-2 rounded-lg transition-colors",
domain.notify_on_available
? "bg-accent/10 text-accent hover:bg-accent/20"
: "text-foreground-muted hover:bg-foreground/5"
)}
title={domain.notify_on_available ? "Disable alerts" : "Enable alerts"}
>
{togglingNotifyId === domain.id ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : domain.notify_on_available ? (
<Bell className="w-4 h-4" />
) : (
<BellOff className="w-4 h-4" />
)}
</button>
),
},
{
key: 'actions',
header: '',
align: 'right',
render: (domain) => (
<div className="flex items-center gap-1 justify-end">
<TableActionButton
icon={RefreshCw}
onClick={() => handleRefresh(domain.id)}
loading={refreshingId === domain.id}
title="Refresh status"
/>
<TableActionButton
icon={Trash2}
onClick={() => handleDelete(domain.id, domain.name)}
variant="danger"
loading={deletingId === domain.id}
title="Remove"
/>
{domain.is_available && (
<a
href={`https://www.namecheap.com/domains/registration/results/?domain=${domain.name}`}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="flex items-center gap-1.5 px-3 py-2 bg-accent text-background text-xs font-medium
rounded-lg hover:bg-accent-hover transition-colors ml-1"
>
Register <ExternalLink className="w-3 h-3" />
</a>
)}
</div>
),
},
]}
/>
</PageContainer>
</CommandCenterLayout>
)
}

View File

@ -397,22 +397,27 @@ export function SectionHeader({
subtitle,
icon: Icon,
action,
compact = false,
}: {
title: string
subtitle?: string
icon?: React.ComponentType<{ className?: string }>
action?: ReactNode
compact?: boolean
}) {
return (
<div className="flex items-center justify-between mb-6">
<div className={clsx("flex items-center justify-between", !compact && "mb-6")}>
<div className="flex items-center gap-3">
{Icon && (
<div className="w-10 h-10 bg-accent/10 border border-accent/20 rounded-xl flex items-center justify-center">
<Icon className="w-5 h-5 text-accent" />
<div className={clsx(
"bg-accent/10 border border-accent/20 rounded-xl flex items-center justify-center",
compact ? "w-9 h-9" : "w-10 h-10"
)}>
<Icon className={clsx(compact ? "w-4 h-4" : "w-5 h-5", "text-accent")} />
</div>
)}
<div>
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
<h2 className={clsx(compact ? "text-base" : "text-lg", "font-semibold text-foreground")}>{title}</h2>
{subtitle && <p className="text-sm text-foreground-muted">{subtitle}</p>}
</div>
</div>