refactor: Dashboard & Navigation overhaul
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
DASHBOARD CHANGES: - Stats cards now all neutral (no colored backgrounds) - Added limit warning with upgrade CTA when domain limit reached - Removed TLD Price Intelligence section - Portfolio now uses table layout like Watchlist - Consistent styling across both tabs NAVIGATION OVERHAUL: - New notification center with dropdown - Shows available domains with pulsing indicator - Empty state when no notifications - Link to manage notifications - New user dropdown menu - Shows user info and plan tier - Domain usage (X/Y domains) - Quick links to Dashboard, TLD Pricing, Settings - Clean logout option - Dashboard button more prominent - Plans link hidden when logged in - Click-outside closes dropdowns MOBILE: - User info shown at top of mobile menu - Plan tier and domain usage visible - All navigation items accessible
This commit is contained in:
@ -13,29 +13,22 @@ import {
|
||||
Loader2,
|
||||
Clock,
|
||||
AlertCircle,
|
||||
Search,
|
||||
Calendar,
|
||||
History,
|
||||
ChevronRight,
|
||||
Bell,
|
||||
BellOff,
|
||||
Check,
|
||||
X,
|
||||
Zap,
|
||||
Crown,
|
||||
TrendingUp,
|
||||
Briefcase,
|
||||
Eye,
|
||||
DollarSign,
|
||||
Tag,
|
||||
Edit2,
|
||||
ExternalLink,
|
||||
Sparkles,
|
||||
BarChart3,
|
||||
CreditCard,
|
||||
Globe,
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
} from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
import Link from 'next/link'
|
||||
@ -465,25 +458,37 @@ export default function DashboardPage() {
|
||||
<p className="text-ui-xs text-foreground-muted uppercase tracking-wider mb-2">Tracked</p>
|
||||
<p className="text-3xl font-display text-foreground">{domains.length}</p>
|
||||
</div>
|
||||
<div className="p-5 bg-accent/5 border border-accent/20 rounded-2xl hover:border-accent/40 transition-colors">
|
||||
<p className="text-ui-xs text-accent/80 uppercase tracking-wider mb-2">Available</p>
|
||||
<p className="text-3xl font-display text-accent">{availableCount}</p>
|
||||
<div className="p-5 bg-background-secondary border border-border rounded-2xl hover:border-foreground/20 transition-colors">
|
||||
<p className="text-ui-xs text-foreground-muted uppercase tracking-wider mb-2">Available</p>
|
||||
<p className="text-3xl font-display text-foreground">{availableCount}</p>
|
||||
</div>
|
||||
<div className="p-5 bg-accent/5 border border-accent/20 rounded-2xl hover:border-accent/40 transition-colors">
|
||||
<p className="text-ui-xs text-accent/80 uppercase tracking-wider mb-2">Monitoring</p>
|
||||
<p className="text-3xl font-display text-accent">{domains.filter(d => d.notify_on_available).length}</p>
|
||||
<div className="p-5 bg-background-secondary border border-border rounded-2xl hover:border-foreground/20 transition-colors">
|
||||
<p className="text-ui-xs text-foreground-muted uppercase tracking-wider mb-2">Monitoring</p>
|
||||
<p className="text-3xl font-display text-foreground">{domains.filter(d => d.notify_on_available).length}</p>
|
||||
</div>
|
||||
<div className={clsx(
|
||||
"p-5 border rounded-2xl transition-colors",
|
||||
expiringCount > 0
|
||||
? "bg-warning/5 border-warning/20 hover:border-warning/40"
|
||||
: "bg-background-secondary border-border hover:border-foreground/20"
|
||||
)}>
|
||||
<p className={clsx("text-ui-xs uppercase tracking-wider mb-2", expiringCount > 0 ? "text-warning/80" : "text-foreground-muted")}>Expiring</p>
|
||||
<p className={clsx("text-3xl font-display", expiringCount > 0 ? "text-warning" : "text-foreground")}>{expiringCount}</p>
|
||||
<div className="p-5 bg-background-secondary border border-border rounded-2xl hover:border-foreground/20 transition-colors">
|
||||
<p className="text-ui-xs text-foreground-muted uppercase tracking-wider mb-2">Expiring</p>
|
||||
<p className="text-3xl font-display text-foreground">{expiringCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Limit Warning */}
|
||||
{!canAddMore && (
|
||||
<div className="p-4 bg-warning/5 border border-warning/20 rounded-xl flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-warning shrink-0" />
|
||||
<div>
|
||||
<p className="text-body-sm font-medium text-foreground">You've reached your domain limit</p>
|
||||
<p className="text-body-xs text-foreground-muted">Upgrade to track more domains and unlock premium features</p>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/pricing" className="flex items-center justify-center gap-2 px-4 py-2 bg-accent text-background text-ui-sm font-medium rounded-lg hover:bg-accent-hover transition-all whitespace-nowrap">
|
||||
<Zap className="w-4 h-4" />
|
||||
Upgrade Now
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add Domain */}
|
||||
<form onSubmit={handleAddDomain} className="flex gap-3">
|
||||
<div className="flex-1 relative">
|
||||
@ -626,25 +631,14 @@ export default function DashboardPage() {
|
||||
<p className="text-ui-xs text-foreground-muted uppercase tracking-wider mb-2">Invested</p>
|
||||
<p className="text-2xl sm:text-3xl font-display text-foreground">{formatCurrency(portfolioSummary.total_invested)}</p>
|
||||
</div>
|
||||
<div className={clsx(
|
||||
"p-5 border rounded-2xl transition-colors",
|
||||
portfolioSummary.unrealized_profit >= 0
|
||||
? "bg-accent/5 border-accent/20 hover:border-accent/40"
|
||||
: "bg-danger/5 border-danger/20 hover:border-danger/40"
|
||||
)}>
|
||||
<p className={clsx("text-ui-xs uppercase tracking-wider mb-2", portfolioSummary.unrealized_profit >= 0 ? "text-accent/80" : "text-danger/80")}>P/L</p>
|
||||
<div className="p-5 bg-background-secondary border border-border rounded-2xl hover:border-foreground/20 transition-colors">
|
||||
<p className="text-ui-xs text-foreground-muted uppercase tracking-wider mb-2">P/L</p>
|
||||
<p className={clsx("text-2xl sm:text-3xl font-display flex items-center gap-1", portfolioSummary.unrealized_profit >= 0 ? "text-accent" : "text-danger")}>
|
||||
{portfolioSummary.unrealized_profit >= 0 ? <ArrowUpRight className="w-5 h-5" /> : <ArrowDownRight className="w-5 h-5" />}
|
||||
{formatCurrency(Math.abs(portfolioSummary.unrealized_profit))}
|
||||
{portfolioSummary.unrealized_profit >= 0 ? '+' : ''}{formatCurrency(portfolioSummary.unrealized_profit)}
|
||||
</p>
|
||||
</div>
|
||||
<div className={clsx(
|
||||
"p-5 border rounded-2xl transition-colors",
|
||||
portfolioSummary.overall_roi >= 0
|
||||
? "bg-accent/5 border-accent/20 hover:border-accent/40"
|
||||
: "bg-danger/5 border-danger/20 hover:border-danger/40"
|
||||
)}>
|
||||
<p className={clsx("text-ui-xs uppercase tracking-wider mb-2", portfolioSummary.overall_roi >= 0 ? "text-accent/80" : "text-danger/80")}>ROI</p>
|
||||
<div className="p-5 bg-background-secondary border border-border rounded-2xl hover:border-foreground/20 transition-colors">
|
||||
<p className="text-ui-xs text-foreground-muted uppercase tracking-wider mb-2">ROI</p>
|
||||
<p className={clsx("text-2xl sm:text-3xl font-display", portfolioSummary.overall_roi >= 0 ? "text-accent" : "text-danger")}>
|
||||
{portfolioSummary.overall_roi >= 0 ? '+' : ''}{portfolioSummary.overall_roi.toFixed(1)}%
|
||||
</p>
|
||||
@ -652,9 +646,10 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button onClick={() => setShowAddPortfolioModal(true)} className="flex items-center gap-2.5 px-6 py-3.5 bg-foreground text-background text-ui font-medium rounded-xl hover:bg-foreground/90 transition-all shadow-lg shadow-foreground/10">
|
||||
{/* Add Domain */}
|
||||
<button onClick={() => setShowAddPortfolioModal(true)} className="flex items-center gap-2 px-5 py-3 bg-foreground text-background text-ui-sm font-medium rounded-xl hover:bg-foreground/90 transition-all">
|
||||
<Plus className="w-4 h-4" />
|
||||
Add Domain to Portfolio
|
||||
Add Domain
|
||||
</button>
|
||||
|
||||
{loadingPortfolio ? (
|
||||
@ -668,65 +663,87 @@ export default function DashboardPage() {
|
||||
<p className="text-body-sm text-foreground-subtle">Add domains you own to track their value and ROI</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{portfolio.map((domain) => {
|
||||
const roi = domain.roi
|
||||
const renewal = formatExpirationDate(domain.renewal_date)
|
||||
return (
|
||||
<div key={domain.id} className="group p-4 bg-background-secondary border border-border rounded-xl hover:border-border-hover transition-all">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="font-mono text-body font-medium text-foreground">{domain.domain}</h3>
|
||||
{domain.status === 'sold' && <span className="text-ui-xs px-2 py-0.5 bg-accent/10 text-accent rounded-full">Sold</span>}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-3 text-body-sm text-foreground-muted">
|
||||
{domain.purchase_price && <span className="flex items-center gap-1"><Tag className="w-3.5 h-3.5" />{formatCurrency(domain.purchase_price)}</span>}
|
||||
{domain.estimated_value && <span className="flex items-center gap-1"><Sparkles className="w-3.5 h-3.5" />{formatCurrency(domain.estimated_value)}</span>}
|
||||
{renewal && <span className={clsx("flex items-center gap-1", renewal.urgent && "text-warning")}><Calendar className="w-3.5 h-3.5" />{renewal.text}</span>}
|
||||
{domain.registrar && <span className="flex items-center gap-1"><Globe className="w-3.5 h-3.5" />{domain.registrar}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{roi !== null && (
|
||||
<div className={clsx("text-right px-3 py-1 rounded-lg", roi >= 0 ? "bg-accent/10" : "bg-danger/10")}>
|
||||
<p className="text-ui-xs text-foreground-muted">ROI</p>
|
||||
<p className={clsx("text-body font-medium", roi >= 0 ? "text-accent" : "text-danger")}>{roi >= 0 ? '+' : ''}{roi.toFixed(1)}%</p>
|
||||
<div className="border border-border rounded-2xl overflow-hidden bg-background-secondary/30">
|
||||
<table className="w-full">
|
||||
<thead className="bg-background-secondary/80 backdrop-blur-sm">
|
||||
<tr className="border-b border-border">
|
||||
<th className="text-left text-ui-xs font-medium text-foreground-muted uppercase tracking-wider px-5 py-4">Domain</th>
|
||||
<th className="text-left text-ui-xs font-medium text-foreground-muted uppercase tracking-wider px-5 py-4 hidden sm:table-cell">Purchased</th>
|
||||
<th className="text-left text-ui-xs font-medium text-foreground-muted uppercase tracking-wider px-5 py-4 hidden md:table-cell">Value</th>
|
||||
<th className="text-left text-ui-xs font-medium text-foreground-muted uppercase tracking-wider px-5 py-4 hidden lg:table-cell">Renewal</th>
|
||||
<th className="text-right text-ui-xs font-medium text-foreground-muted uppercase tracking-wider px-5 py-4">ROI</th>
|
||||
<th className="text-right text-ui-xs font-medium text-foreground-muted uppercase tracking-wider px-5 py-4">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border/50">
|
||||
{portfolio.map((domain) => {
|
||||
const roi = domain.roi
|
||||
const renewal = formatExpirationDate(domain.renewal_date)
|
||||
return (
|
||||
<tr key={domain.id} className="group hover:bg-background-secondary/60 transition-all duration-200">
|
||||
<td className="px-5 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative">
|
||||
<div className={clsx(
|
||||
"w-2.5 h-2.5 rounded-full",
|
||||
domain.status === 'sold' ? "bg-accent" : "bg-foreground-subtle"
|
||||
)} />
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-mono text-body-sm text-foreground">{domain.domain}</span>
|
||||
{domain.status === 'sold' && <span className="ml-2 text-ui-xs px-1.5 py-0.5 bg-accent/10 text-accent rounded">Sold</span>}
|
||||
{domain.registrar && <p className="text-body-xs text-foreground-subtle">{domain.registrar}</p>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-1 opacity-60 group-hover:opacity-100 transition-opacity">
|
||||
{domain.status !== 'sold' && <button onClick={() => handleOpenSellModal(domain)} className="p-2 text-foreground-subtle hover:text-accent hover:bg-accent/10 rounded-lg transition-all" title="Sell"><DollarSign className="w-4 h-4" /></button>}
|
||||
<button onClick={() => handleOpenEditPortfolio(domain)} className="p-2 text-foreground-subtle hover:text-foreground hover:bg-foreground/5 rounded-lg transition-all" title="Edit"><Edit2 className="w-4 h-4" /></button>
|
||||
<button onClick={() => handleRefreshPortfolioValue(domain.id)} disabled={refreshingPortfolioId === domain.id} className="p-2 text-foreground-subtle hover:text-foreground hover:bg-foreground/5 rounded-lg transition-all" title="Refresh"><RefreshCw className={clsx("w-4 h-4", refreshingPortfolioId === domain.id && "animate-spin")} /></button>
|
||||
<button onClick={() => handleDeletePortfolioDomain(domain.id)} className="p-2 text-foreground-subtle hover:text-danger hover:bg-danger/10 rounded-lg transition-all" title="Remove"><Trash2 className="w-4 h-4" /></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{domain.notes && <p className="mt-3 pt-3 border-t border-border text-body-sm text-foreground-subtle">{domain.notes}</p>}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</td>
|
||||
<td className="px-5 py-4 hidden sm:table-cell">
|
||||
<span className="text-body-sm text-foreground">{domain.purchase_price ? formatCurrency(domain.purchase_price) : '—'}</span>
|
||||
{domain.purchase_date && <p className="text-body-xs text-foreground-subtle">{new Date(domain.purchase_date).toLocaleDateString()}</p>}
|
||||
</td>
|
||||
<td className="px-5 py-4 hidden md:table-cell">
|
||||
<span className="text-body-sm text-foreground">{domain.estimated_value ? formatCurrency(domain.estimated_value) : '—'}</span>
|
||||
</td>
|
||||
<td className="px-5 py-4 hidden lg:table-cell">
|
||||
{renewal ? (
|
||||
<span className={clsx("text-body-sm flex items-center gap-1.5", renewal.urgent ? "text-warning font-medium" : "text-foreground-subtle")}>
|
||||
<Calendar className="w-3.5 h-3.5" />{renewal.text}
|
||||
</span>
|
||||
) : <span className="text-foreground-subtle/50">—</span>}
|
||||
</td>
|
||||
<td className="px-5 py-4 text-right">
|
||||
{roi !== null ? (
|
||||
<span className={clsx("text-body-sm font-medium", roi >= 0 ? "text-accent" : "text-danger")}>
|
||||
{roi >= 0 ? '+' : ''}{roi.toFixed(1)}%
|
||||
</span>
|
||||
) : <span className="text-foreground-subtle/50">—</span>}
|
||||
</td>
|
||||
<td className="px-5 py-4">
|
||||
<div className="flex items-center justify-end gap-0.5 opacity-50 group-hover:opacity-100 transition-opacity">
|
||||
{domain.status !== 'sold' && (
|
||||
<button onClick={() => handleOpenSellModal(domain)} className="p-2 text-foreground-subtle hover:text-accent hover:bg-accent/10 rounded-lg transition-all" title="Mark as Sold">
|
||||
<DollarSign className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => handleOpenEditPortfolio(domain)} className="p-2 text-foreground-subtle hover:text-foreground hover:bg-foreground/10 rounded-lg transition-all" title="Edit">
|
||||
<Edit2 className="w-4 h-4" />
|
||||
</button>
|
||||
<button onClick={() => handleRefreshPortfolioValue(domain.id)} disabled={refreshingPortfolioId === domain.id} className="p-2 text-foreground-subtle hover:text-foreground hover:bg-foreground/10 rounded-lg transition-all" title="Refresh Value">
|
||||
<RefreshCw className={clsx("w-4 h-4", refreshingPortfolioId === domain.id && "animate-spin")} />
|
||||
</button>
|
||||
<button onClick={() => handleDeletePortfolioDomain(domain.id)} className="p-2 text-foreground-subtle hover:text-danger hover:bg-danger/10 rounded-lg transition-all" title="Remove">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quick Links */}
|
||||
<div className="mt-12 p-6 bg-accent/5 border border-accent/20 rounded-2xl flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-accent/10 rounded-xl flex items-center justify-center">
|
||||
<TrendingUp className="w-6 h-6 text-accent" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-body font-medium text-foreground">TLD Price Intelligence</p>
|
||||
<p className="text-body-sm text-foreground-muted">Track 886+ domain extensions in real-time</p>
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/tld-pricing" className="text-ui font-medium text-accent hover:text-accent-hover transition-colors flex items-center gap-1.5 group">
|
||||
Explore Prices
|
||||
<ChevronRight className="w-4 h-4 group-hover:translate-x-0.5 transition-transform" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
@ -2,12 +2,37 @@
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useStore } from '@/lib/store'
|
||||
import { LogOut, LayoutDashboard, Menu, X, Settings } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { LogOut, LayoutDashboard, Menu, X, Settings, Bell, User, ChevronDown, TrendingUp, Briefcase, Eye } from 'lucide-react'
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export function Header() {
|
||||
const { isAuthenticated, user, logout } = useStore()
|
||||
const { isAuthenticated, user, logout, domains, subscription } = useStore()
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||
const [userMenuOpen, setUserMenuOpen] = useState(false)
|
||||
const [notificationsOpen, setNotificationsOpen] = useState(false)
|
||||
const userMenuRef = useRef<HTMLDivElement>(null)
|
||||
const notificationsRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Close dropdowns when clicking outside
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (userMenuRef.current && !userMenuRef.current.contains(event.target as Node)) {
|
||||
setUserMenuOpen(false)
|
||||
}
|
||||
if (notificationsRef.current && !notificationsRef.current.contains(event.target as Node)) {
|
||||
setNotificationsOpen(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [])
|
||||
|
||||
// Count notifications (available domains, etc.)
|
||||
const availableDomains = domains?.filter(d => d.is_available) || []
|
||||
const hasNotifications = availableDomains.length > 0
|
||||
|
||||
const tierName = subscription?.tier_name || subscription?.tier || 'Scout'
|
||||
|
||||
return (
|
||||
<header className="fixed top-0 left-0 right-0 z-50 bg-background/80 backdrop-blur-xl border-b border-border-subtle">
|
||||
@ -50,51 +75,165 @@ export function Header() {
|
||||
>
|
||||
Auctions
|
||||
</Link>
|
||||
<Link
|
||||
href="/pricing"
|
||||
className="flex items-center h-9 px-3 text-[0.8125rem] text-foreground-muted hover:text-foreground
|
||||
hover:bg-background-secondary rounded-lg transition-all duration-300"
|
||||
>
|
||||
Plans
|
||||
</Link>
|
||||
{!isAuthenticated && (
|
||||
<Link
|
||||
href="/pricing"
|
||||
className="flex items-center h-9 px-3 text-[0.8125rem] text-foreground-muted hover:text-foreground
|
||||
hover:bg-background-secondary rounded-lg transition-all duration-300"
|
||||
>
|
||||
Plans
|
||||
</Link>
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Right side: Auth Links - vertically centered */}
|
||||
<nav className="hidden sm:flex items-center h-full gap-1">
|
||||
<nav className="hidden sm:flex items-center h-full gap-2">
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
{/* Dashboard Link */}
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="flex items-center gap-2 h-9 px-4 text-[0.8125rem] text-foreground-muted
|
||||
hover:text-foreground hover:bg-background-secondary rounded-lg
|
||||
transition-all duration-300"
|
||||
className="flex items-center gap-2 h-9 px-4 text-[0.8125rem] font-medium text-foreground
|
||||
bg-foreground/5 hover:bg-foreground/10 rounded-lg transition-all duration-300"
|
||||
>
|
||||
<LayoutDashboard className="w-4 h-4" />
|
||||
<span>Dashboard</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/settings"
|
||||
className="flex items-center justify-center w-9 h-9 text-foreground-subtle hover:text-foreground hover:bg-background-secondary rounded-lg transition-all duration-300"
|
||||
title="Settings"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center h-full gap-3 ml-2 pl-4 border-l border-border">
|
||||
<span className="text-[0.8125rem] text-foreground-subtle hidden md:flex items-center">
|
||||
{user?.email}
|
||||
</span>
|
||||
|
||||
|
||||
{/* Notifications */}
|
||||
<div ref={notificationsRef} className="relative">
|
||||
<button
|
||||
onClick={logout}
|
||||
className="flex items-center justify-center w-9 h-9 text-foreground-subtle hover:text-foreground hover:bg-background-secondary
|
||||
rounded-lg transition-all duration-300"
|
||||
title="Sign out"
|
||||
onClick={() => setNotificationsOpen(!notificationsOpen)}
|
||||
className={clsx(
|
||||
"relative flex items-center justify-center w-9 h-9 rounded-lg transition-all duration-300",
|
||||
notificationsOpen ? "bg-foreground/10 text-foreground" : "text-foreground-muted hover:text-foreground hover:bg-foreground/5"
|
||||
)}
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
<Bell className="w-4 h-4" />
|
||||
{hasNotifications && (
|
||||
<span className="absolute top-1.5 right-1.5 w-2 h-2 bg-accent rounded-full">
|
||||
<span className="absolute inset-0 rounded-full bg-accent animate-ping opacity-50" />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Notifications Dropdown */}
|
||||
{notificationsOpen && (
|
||||
<div className="absolute right-0 top-full mt-2 w-80 bg-background-secondary border border-border rounded-xl shadow-2xl overflow-hidden">
|
||||
<div className="p-4 border-b border-border">
|
||||
<h3 className="text-body-sm font-medium text-foreground">Notifications</h3>
|
||||
</div>
|
||||
<div className="max-h-80 overflow-y-auto">
|
||||
{availableDomains.length > 0 ? (
|
||||
<div className="p-2">
|
||||
{availableDomains.map((domain) => (
|
||||
<Link
|
||||
key={domain.id}
|
||||
href="/dashboard"
|
||||
onClick={() => setNotificationsOpen(false)}
|
||||
className="flex items-start gap-3 p-3 hover:bg-foreground/5 rounded-lg transition-colors"
|
||||
>
|
||||
<div className="w-8 h-8 bg-accent/10 rounded-lg flex items-center justify-center shrink-0">
|
||||
<Eye className="w-4 h-4 text-accent" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-body-sm font-medium text-foreground truncate">{domain.name}</p>
|
||||
<p className="text-body-xs text-accent">Domain is available!</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-8 text-center">
|
||||
<Bell className="w-8 h-8 text-foreground-subtle mx-auto mb-3" />
|
||||
<p className="text-body-sm text-foreground-muted">No notifications</p>
|
||||
<p className="text-body-xs text-foreground-subtle mt-1">We'll notify you when domains become available</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Link
|
||||
href="/settings"
|
||||
onClick={() => setNotificationsOpen(false)}
|
||||
className="block p-3 text-center text-body-xs text-foreground-muted hover:text-foreground hover:bg-foreground/5 border-t border-border transition-colors"
|
||||
>
|
||||
Manage notifications
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* User Menu */}
|
||||
<div ref={userMenuRef} className="relative">
|
||||
<button
|
||||
onClick={() => setUserMenuOpen(!userMenuOpen)}
|
||||
className={clsx(
|
||||
"flex items-center gap-2 h-9 pl-3 pr-2 rounded-lg transition-all duration-300",
|
||||
userMenuOpen ? "bg-foreground/10" : "hover:bg-foreground/5"
|
||||
)}
|
||||
>
|
||||
<div className="w-6 h-6 bg-accent/10 rounded-full flex items-center justify-center">
|
||||
<User className="w-3.5 h-3.5 text-accent" />
|
||||
</div>
|
||||
<ChevronDown className={clsx("w-3.5 h-3.5 text-foreground-muted transition-transform", userMenuOpen && "rotate-180")} />
|
||||
</button>
|
||||
|
||||
{/* User Dropdown */}
|
||||
{userMenuOpen && (
|
||||
<div className="absolute right-0 top-full mt-2 w-64 bg-background-secondary border border-border rounded-xl shadow-2xl overflow-hidden">
|
||||
{/* User Info */}
|
||||
<div className="p-4 border-b border-border">
|
||||
<p className="text-body-sm font-medium text-foreground truncate">{user?.name || user?.email}</p>
|
||||
<p className="text-body-xs text-foreground-muted truncate">{user?.email}</p>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<span className="text-ui-xs px-2 py-0.5 bg-foreground/5 text-foreground-muted rounded-full">{tierName}</span>
|
||||
<span className="text-ui-xs text-foreground-subtle">{subscription?.domains_used || 0}/{subscription?.domain_limit || 5} domains</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Menu Items */}
|
||||
<div className="p-2">
|
||||
<Link
|
||||
href="/dashboard"
|
||||
onClick={() => setUserMenuOpen(false)}
|
||||
className="flex items-center gap-3 px-3 py-2.5 text-body-sm text-foreground-muted hover:text-foreground hover:bg-foreground/5 rounded-lg transition-colors"
|
||||
>
|
||||
<LayoutDashboard className="w-4 h-4" />
|
||||
Command Center
|
||||
</Link>
|
||||
<Link
|
||||
href="/tld-pricing"
|
||||
onClick={() => setUserMenuOpen(false)}
|
||||
className="flex items-center gap-3 px-3 py-2.5 text-body-sm text-foreground-muted hover:text-foreground hover:bg-foreground/5 rounded-lg transition-colors"
|
||||
>
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
TLD Pricing
|
||||
</Link>
|
||||
<Link
|
||||
href="/settings"
|
||||
onClick={() => setUserMenuOpen(false)}
|
||||
className="flex items-center gap-3 px-3 py-2.5 text-body-sm text-foreground-muted hover:text-foreground hover:bg-foreground/5 rounded-lg transition-colors"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
Settings
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Logout */}
|
||||
<div className="p-2 border-t border-border">
|
||||
<button
|
||||
onClick={() => {
|
||||
logout()
|
||||
setUserMenuOpen(false)
|
||||
}}
|
||||
className="flex items-center gap-3 w-full px-3 py-2.5 text-body-sm text-foreground-muted hover:text-foreground hover:bg-foreground/5 rounded-lg transition-colors"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
@ -132,6 +271,15 @@ export function Header() {
|
||||
<nav className="px-4 py-4 space-y-2">
|
||||
{isAuthenticated ? (
|
||||
<>
|
||||
{/* User Info on Mobile */}
|
||||
<div className="px-4 py-3 mb-2 border-b border-border">
|
||||
<p className="text-body-sm font-medium text-foreground truncate">{user?.name || user?.email}</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<span className="text-ui-xs px-2 py-0.5 bg-foreground/5 text-foreground-muted rounded-full">{tierName}</span>
|
||||
<span className="text-ui-xs text-foreground-subtle">{subscription?.domains_used || 0}/{subscription?.domain_limit || 5} domains</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="flex items-center gap-3 px-4 py-3 text-body-sm text-foreground-muted
|
||||
@ -142,6 +290,26 @@ export function Header() {
|
||||
<LayoutDashboard className="w-5 h-5" />
|
||||
<span>Dashboard</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/tld-pricing"
|
||||
className="flex items-center gap-3 px-4 py-3 text-body-sm text-foreground-muted
|
||||
hover:text-foreground hover:bg-background-secondary rounded-xl
|
||||
transition-all duration-300"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
<TrendingUp className="w-5 h-5" />
|
||||
<span>TLD Pricing</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/auctions"
|
||||
className="flex items-center gap-3 px-4 py-3 text-body-sm text-foreground-muted
|
||||
hover:text-foreground hover:bg-background-secondary rounded-xl
|
||||
transition-all duration-300"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
<Briefcase className="w-5 h-5" />
|
||||
<span>Auctions</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/settings"
|
||||
className="flex items-center gap-3 px-4 py-3 text-body-sm text-foreground-muted
|
||||
|
||||
Reference in New Issue
Block a user