From 56d5a3f1944ef0673666b73028a99a88347d09df Mon Sep 17 00:00:00 2001 From: "yves.gugger" Date: Tue, 9 Dec 2025 09:36:25 +0100 Subject: [PATCH] refactor: Dashboard & Navigation overhaul 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 --- frontend/src/app/dashboard/page.tsx | 203 +++++++++++++----------- frontend/src/components/Header.tsx | 234 ++++++++++++++++++++++++---- 2 files changed, 311 insertions(+), 126 deletions(-) diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index b85206d..9a07a84 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -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() {

Tracked

{domains.length}

-
-

Available

-

{availableCount}

+
+

Available

+

{availableCount}

-
-

Monitoring

-

{domains.filter(d => d.notify_on_available).length}

+
+

Monitoring

+

{domains.filter(d => d.notify_on_available).length}

-
0 - ? "bg-warning/5 border-warning/20 hover:border-warning/40" - : "bg-background-secondary border-border hover:border-foreground/20" - )}> -

0 ? "text-warning/80" : "text-foreground-muted")}>Expiring

-

0 ? "text-warning" : "text-foreground")}>{expiringCount}

+
+

Expiring

+

{expiringCount}

+ {/* Limit Warning */} + {!canAddMore && ( +
+
+ +
+

You've reached your domain limit

+

Upgrade to track more domains and unlock premium features

+
+
+ + + Upgrade Now + +
+ )} + {/* Add Domain */}
@@ -626,25 +631,14 @@ export default function DashboardPage() {

Invested

{formatCurrency(portfolioSummary.total_invested)}

-
= 0 - ? "bg-accent/5 border-accent/20 hover:border-accent/40" - : "bg-danger/5 border-danger/20 hover:border-danger/40" - )}> -

= 0 ? "text-accent/80" : "text-danger/80")}>P/L

+
+

P/L

= 0 ? "text-accent" : "text-danger")}> - {portfolioSummary.unrealized_profit >= 0 ? : } - {formatCurrency(Math.abs(portfolioSummary.unrealized_profit))} + {portfolioSummary.unrealized_profit >= 0 ? '+' : ''}{formatCurrency(portfolioSummary.unrealized_profit)}

-
= 0 - ? "bg-accent/5 border-accent/20 hover:border-accent/40" - : "bg-danger/5 border-danger/20 hover:border-danger/40" - )}> -

= 0 ? "text-accent/80" : "text-danger/80")}>ROI

+
+

ROI

= 0 ? "text-accent" : "text-danger")}> {portfolioSummary.overall_roi >= 0 ? '+' : ''}{portfolioSummary.overall_roi.toFixed(1)}%

@@ -652,9 +646,10 @@ export default function DashboardPage() {
)} - {loadingPortfolio ? ( @@ -668,65 +663,87 @@ export default function DashboardPage() {

Add domains you own to track their value and ROI

) : ( -
- {portfolio.map((domain) => { - const roi = domain.roi - const renewal = formatExpirationDate(domain.renewal_date) - return ( -
-
-
-
-

{domain.domain}

- {domain.status === 'sold' && Sold} -
-
- {domain.purchase_price && {formatCurrency(domain.purchase_price)}} - {domain.estimated_value && {formatCurrency(domain.estimated_value)}} - {renewal && {renewal.text}} - {domain.registrar && {domain.registrar}} -
-
-
- {roi !== null && ( -
= 0 ? "bg-accent/10" : "bg-danger/10")}> -

ROI

-

= 0 ? "text-accent" : "text-danger")}>{roi >= 0 ? '+' : ''}{roi.toFixed(1)}%

+
+ + + + + + + + + + + + + {portfolio.map((domain) => { + const roi = domain.roi + const renewal = formatExpirationDate(domain.renewal_date) + return ( + + + + + + + + + ) + })} + +
DomainPurchasedValueRenewalROIActions
+
+
+
+
+
+ {domain.domain} + {domain.status === 'sold' && Sold} + {domain.registrar &&

{domain.registrar}

} +
- )} -
- {domain.status !== 'sold' && } - - - -
-
- - {domain.notes &&

{domain.notes}

} - - ) - })} +
+ {domain.purchase_price ? formatCurrency(domain.purchase_price) : '—'} + {domain.purchase_date &&

{new Date(domain.purchase_date).toLocaleDateString()}

} +
+ {domain.estimated_value ? formatCurrency(domain.estimated_value) : '—'} + + {renewal ? ( + + {renewal.text} + + ) : } + + {roi !== null ? ( + = 0 ? "text-accent" : "text-danger")}> + {roi >= 0 ? '+' : ''}{roi.toFixed(1)}% + + ) : } + +
+ {domain.status !== 'sold' && ( + + )} + + + +
+
)}
)} - - {/* Quick Links */} -
-
-
- -
-
-

TLD Price Intelligence

-

Track 886+ domain extensions in real-time

-
-
- - Explore Prices - - -
diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 9a2775f..e6eea63 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -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(null) + const notificationsRef = useRef(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 (
@@ -50,51 +75,165 @@ export function Header() { > Auctions - - Plans - + {!isAuthenticated && ( + + Plans + + )}
{/* Right side: Auth Links - vertically centered */} -