diff --git a/frontend/src/app/command/portfolio/page.tsx b/frontend/src/app/command/portfolio/page.tsx index 5bf431a..938519a 100644 --- a/frontend/src/app/command/portfolio/page.tsx +++ b/frontend/src/app/command/portfolio/page.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react' import { useStore } from '@/lib/store' import { api, PortfolioDomain, PortfolioSummary, DomainValuation, DomainHealthReport, HealthStatus } from '@/lib/api' import { CommandCenterLayout } from '@/components/CommandCenterLayout' -import { PremiumTable, StatCard, PageContainer, TableActionButton } from '@/components/PremiumTable' +import { PremiumTable, StatCard, PageContainer } from '@/components/PremiumTable' import { Toast, useToast } from '@/components/Toast' import { Plus, @@ -13,19 +13,17 @@ import { DollarSign, Calendar, Building, - RefreshCw, Loader2, - TrendingUp, - Sparkles, ArrowUpRight, X, Briefcase, - PiggyBank, ShoppingCart, Activity, Shield, AlertTriangle, Tag, + MoreVertical, + ExternalLink, } from 'lucide-react' // Health status configuration @@ -67,6 +65,9 @@ export default function PortfolioPage() { const [healthReports, setHealthReports] = useState>({}) const [loadingHealth, setLoadingHealth] = useState>({}) const [selectedHealthDomain, setSelectedHealthDomain] = useState(null) + + // Dropdown menu state + const [openMenuId, setOpenMenuId] = useState(null) const [addForm, setAddForm] = useState({ domain: '', @@ -261,10 +262,15 @@ export default function PortfolioPage() { // Dynamic subtitle const getSubtitle = () => { if (loading) return 'Loading your portfolio...' - if (portfolio.length === 0) return 'Start tracking your domain investments' - const profit = summary?.total_profit || 0 - if (profit > 0) return `${portfolio.length} domains • +$${profit.toLocaleString()} profit` - if (profit < 0) return `${portfolio.length} domains • -$${Math.abs(profit).toLocaleString()} loss` + if (portfolio.length === 0) return 'Start tracking your domains' + const expiringSoon = portfolio.filter(d => { + if (!d.renewal_date) return false + const days = Math.ceil((new Date(d.renewal_date).getTime() - Date.now()) / (1000 * 60 * 60 * 24)) + return days <= 30 && days > 0 + }).length + if (expiringSoon > 0) { + return `${portfolio.length} domain${portfolio.length !== 1 ? 's' : ''} • ${expiringSoon} expiring soon` + } return `Managing ${portfolio.length} domain${portfolio.length !== 1 ? 's' : ''}` } @@ -288,18 +294,25 @@ export default function PortfolioPage() { {toast && } - {/* Summary Stats */} -
+ {/* Summary Stats - Only reliable data */} +
- - = 0 ? '+' : ''}$${(summary?.total_profit || 0).toLocaleString()}`} - icon={PiggyBank} - accent={(summary?.total_profit || 0) >= 0} + title="Expiring Soon" + value={portfolio.filter(d => { + if (!d.renewal_date) return false + const days = Math.ceil((new Date(d.renewal_date).getTime() - Date.now()) / (1000 * 60 * 60 * 24)) + return days <= 30 && days > 0 + }).length} + icon={Calendar} + accent /> - + r.status !== 'healthy').length} + icon={AlertTriangle} + /> +
{!canAddMore && ( @@ -340,48 +353,53 @@ export default function PortfolioPage() { ), }, { - key: 'purchase', - header: 'Purchase', + key: 'added', + header: 'Added', hideOnMobile: true, + hideOnTablet: true, render: (domain) => ( -
- {domain.purchase_price && ( - ${domain.purchase_price.toLocaleString()} - )} - {domain.purchase_date && ( -

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

- )} -
- ), - }, - { - key: 'valuation', - header: 'Est. Value', - align: 'right', - render: (domain) => ( - domain.current_valuation ? ( - ${domain.current_valuation.toLocaleString()} - ) : ( - - ) + + {domain.purchase_date + ? new Date(domain.purchase_date).toLocaleDateString() + : new Date(domain.created_at).toLocaleDateString() + } + ), }, { key: 'renewal', - header: 'Renewal', + header: 'Expires', hideOnMobile: true, - hideOnTablet: true, - render: (domain) => ( - domain.renewal_date ? ( - - {new Date(domain.renewal_date).toLocaleDateString()} - - ) : ( - + render: (domain) => { + if (!domain.renewal_date) { + return + } + const days = Math.ceil((new Date(domain.renewal_date).getTime() - Date.now()) / (1000 * 60 * 60 * 24)) + const isExpiringSoon = days <= 30 && days > 0 + const isExpired = days <= 0 + return ( +
+ + {new Date(domain.renewal_date).toLocaleDateString()} + + {isExpiringSoon && ( + + {days}d + + )} + {isExpired && ( + + EXPIRED + + )} +
) - ), + }, }, { key: 'health', @@ -424,51 +442,77 @@ export default function PortfolioPage() { header: '', align: 'right', render: (domain) => ( -
- handleHealthCheck(domain.domain)} - loading={loadingHealth[domain.domain]} - title="Health check (SSL, DNS, HTTP)" - variant={healthReports[domain.domain] ? 'accent' : 'default'} - /> - handleValuate(domain)} - title="Get valuation" - /> - handleRefresh(domain)} - loading={refreshingId === domain.id} - title="Refresh valuation" - /> - openEditModal(domain)} - title="Edit" - /> - e.stopPropagation()} - className="px-3 py-2 text-xs font-medium text-accent hover:bg-accent/10 rounded-lg transition-colors flex items-center gap-1" - > - - List - +
- handleDelete(domain)} - variant="danger" - title="Remove" - /> + + {openMenuId === domain.id && ( + <> + {/* Backdrop */} +
setOpenMenuId(null)} + /> + {/* Menu */} +
+ + +
+ setOpenMenuId(null)} + className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-accent hover:bg-accent/5 transition-colors" + > + + List for Sale + + setOpenMenuId(null)} + className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-foreground-muted hover:text-foreground hover:bg-foreground/5 transition-colors" + > + + Visit Website + +
+ + +
+ + )}
), },