diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index 94d540d..ab61950 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -24,7 +24,6 @@ import { Zap, Crown, TrendingUp, - TrendingDown, Briefcase, Eye, DollarSign, @@ -34,13 +33,9 @@ import { Sparkles, BarChart3, CreditCard, - Target, - Crosshair, - Shield, - Activity, + Globe, ArrowUpRight, ArrowDownRight, - Globe, } from 'lucide-react' import clsx from 'clsx' import Link from 'next/link' @@ -67,10 +62,7 @@ export default function DashboardPage() { refreshDomain, } = useStore() - // Tab state const [activeTab, setActiveTab] = useState('watchlist') - - // Watchlist state const [newDomain, setNewDomain] = useState('') const [adding, setAdding] = useState(false) const [refreshingId, setRefreshingId] = useState(null) @@ -78,8 +70,6 @@ export default function DashboardPage() { const [selectedDomainId, setSelectedDomainId] = useState(null) const [domainHistory, setDomainHistory] = useState(null) const [loadingHistory, setLoadingHistory] = useState(false) - - // Portfolio state const [portfolio, setPortfolio] = useState([]) const [portfolioSummary, setPortfolioSummary] = useState(null) const [loadingPortfolio, setLoadingPortfolio] = useState(false) @@ -88,8 +78,6 @@ export default function DashboardPage() { const [valuationResult, setValuationResult] = useState(null) const [valuatingDomain, setValuatingDomain] = useState('') const [refreshingPortfolioId, setRefreshingPortfolioId] = useState(null) - - // Portfolio form state const [portfolioForm, setPortfolioForm] = useState({ domain: '', purchase_price: '', @@ -100,8 +88,6 @@ export default function DashboardPage() { notes: '', }) const [addingPortfolio, setAddingPortfolio] = useState(false) - - // Edit Portfolio Modal state const [showEditPortfolioModal, setShowEditPortfolioModal] = useState(false) const [editingPortfolioDomain, setEditingPortfolioDomain] = useState(null) const [editPortfolioForm, setEditPortfolioForm] = useState({ @@ -113,8 +99,6 @@ export default function DashboardPage() { notes: '', }) const [savingEdit, setSavingEdit] = useState(false) - - // Sell Domain Modal state const [showSellModal, setShowSellModal] = useState(false) const [sellingDomain, setSellingDomain] = useState(null) const [sellForm, setSellForm] = useState({ @@ -122,8 +106,6 @@ export default function DashboardPage() { sale_price: '', }) const [processingSale, setProcessingSale] = useState(false) - - // Notification toggle state const [togglingNotifyId, setTogglingNotifyId] = useState(null) useEffect(() => { @@ -142,7 +124,6 @@ export default function DashboardPage() { } }, [isAuthenticated, activeTab]) - // Open Stripe billing portal const handleOpenBillingPortal = async () => { try { const { portal_url } = await api.createPortalSession() @@ -171,10 +152,8 @@ export default function DashboardPage() { const handleAddDomain = async (e: React.FormEvent) => { e.preventDefault() if (!newDomain.trim()) return - setAdding(true) setError(null) - try { await addDomain(newDomain) setNewDomain('') @@ -204,7 +183,6 @@ export default function DashboardPage() { setError('Check history requires Trader or Tycoon plan') return } - setSelectedDomainId(domainId) setLoadingHistory(true) try { @@ -220,7 +198,6 @@ export default function DashboardPage() { const handleAddPortfolioDomain = async (e: React.FormEvent) => { e.preventDefault() if (!portfolioForm.domain.trim()) return - setAddingPortfolio(true) try { await api.addPortfolioDomain({ @@ -232,15 +209,7 @@ export default function DashboardPage() { renewal_cost: portfolioForm.renewal_cost ? parseFloat(portfolioForm.renewal_cost) : undefined, notes: portfolioForm.notes || undefined, }) - setPortfolioForm({ - domain: '', - purchase_price: '', - purchase_date: '', - registrar: '', - renewal_date: '', - renewal_cost: '', - notes: '', - }) + setPortfolioForm({ domain: '', purchase_price: '', purchase_date: '', registrar: '', renewal_date: '', renewal_cost: '', notes: '' }) setShowAddPortfolioModal(false) loadPortfolio() } catch (err) { @@ -286,12 +255,10 @@ export default function DashboardPage() { } } - // Toggle domain notification const handleToggleNotify = async (domainId: number, currentNotify: boolean) => { setTogglingNotifyId(domainId) try { await api.updateDomainNotify(domainId, !currentNotify) - // Refresh domains list const { fetchDomains } = useStore.getState() await fetchDomains() } catch (err) { @@ -301,7 +268,6 @@ export default function DashboardPage() { } } - // Open Edit Portfolio Modal const handleOpenEditPortfolio = (domain: PortfolioDomain) => { setEditingPortfolioDomain(domain) setEditPortfolioForm({ @@ -315,11 +281,9 @@ export default function DashboardPage() { setShowEditPortfolioModal(true) } - // Save Edit Portfolio const handleSaveEditPortfolio = async (e: React.FormEvent) => { e.preventDefault() if (!editingPortfolioDomain) return - setSavingEdit(true) try { await api.updatePortfolioDomain(editingPortfolioDomain.id, { @@ -340,21 +304,15 @@ export default function DashboardPage() { } } - // Open Sell Modal const handleOpenSellModal = (domain: PortfolioDomain) => { setSellingDomain(domain) - setSellForm({ - sale_date: new Date().toISOString().split('T')[0], - sale_price: '', - }) + setSellForm({ sale_date: new Date().toISOString().split('T')[0], sale_price: '' }) setShowSellModal(true) } - // Process Sale const handleSellDomain = async (e: React.FormEvent) => { e.preventDefault() if (!sellingDomain || !sellForm.sale_price) return - setProcessingSale(true) try { await api.markDomainSold(sellingDomain.id, sellForm.sale_date, parseFloat(sellForm.sale_price)) @@ -369,14 +327,9 @@ export default function DashboardPage() { } const formatDate = (dateStr: string | null) => { - if (!dateStr) return 'Not checked yet' + if (!dateStr) return 'Not checked' const date = new Date(dateStr) - return date.toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }) + return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) } const formatExpirationDate = (dateStr: string | null) => { @@ -384,7 +337,6 @@ export default function DashboardPage() { const date = new Date(dateStr) const now = new Date() const daysUntil = Math.ceil((date.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)) - if (daysUntil < 0) return { text: 'Expired', urgent: true } if (daysUntil <= 7) return { text: `${daysUntil}d`, urgent: true } if (daysUntil <= 30) return { text: `${daysUntil}d`, urgent: false } @@ -393,35 +345,21 @@ export default function DashboardPage() { const formatCurrency = (value: number | null) => { if (value === null) return '—' - return new Intl.NumberFormat('en-US', { - style: 'currency', - currency: 'USD', - minimumFractionDigits: 0, - maximumFractionDigits: 0, - }).format(value) + return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(value) } if (isLoading) { return (
-
-
-

Loading your command center...

-
+
) } - if (!isAuthenticated) { - return null - } - - const canAddMore = subscription - ? subscription.domains_used < subscription.domain_limit - : true + if (!isAuthenticated) return null + const canAddMore = subscription ? subscription.domains_used < subscription.domain_limit : true const availableCount = domains.filter(d => d.is_available).length - const takenCount = domains.filter(d => !d.is_available).length const expiringCount = domains.filter(d => { if (!d.expiration_date) return false const daysUntil = Math.ceil((new Date(d.expiration_date).getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24)) @@ -433,342 +371,197 @@ export default function DashboardPage() { const isEnterprise = tierName === 'Enterprise' || tierName === 'Tycoon' return ( -
- {/* Background elements */} -
-
-
-
- +
-
-
- {/* Hero Section */} -
-
-
-
-
- -
- - {isEnterprise && } - {tierName} - -
-

- Command Center -

-

- Your domains. Your intel. Your edge. -

-
- -
- {isProOrHigher ? ( - - ) : ( - - - Upgrade - - )} -
+
+
+ {/* Header */} +
+
+

+ Command Center +

+

+ Your domains. Your intel. Your edge. +

+
+
+ + {isEnterprise && } + {tierName} + + {isProOrHigher ? ( + + ) : ( + + + Upgrade + + )}
- {/* Tab Navigation */} -
-
- - -
+ {/* Tabs */} +
+ +
{error && ( -
+

{error}

- +
)} {/* Watchlist Tab */} {activeTab === 'watchlist' && ( -
- {/* Stats Grid */} -
-
-
-
- -
- Total -
-

{domains.length}

-

targets tracked

+
+ {/* Stats Row */} +
+
+

Tracked

+

{domains.length}

- -
-
-
- -
- Hunt Ready -
-

{availableCount}

-

available now

+
+

Available

+

{availableCount}

- -
-
-
- -
- Claimed -
-

{takenCount}

-

registered

+
+

Registered

+

{domains.length - availableCount}

- -
0 - ? "bg-gradient-to-br from-warning/10 to-warning/5 border-warning/20 hover:border-warning/30" - : "bg-gradient-to-br from-background-secondary/80 to-background-secondary/40 border-border hover:border-border-hover" - )}> -
-
0 ? "bg-warning/10" : "bg-foreground/5" - )}> - 0 ? "text-warning" : "text-foreground-muted")} /> -
- 0 ? "text-warning/70" : "text-foreground-subtle")}>Expiring -
-

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

-

0 ? "text-warning/70" : "text-foreground-muted")}> - in 30 days -

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

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

+

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

- {/* Add Domain Form */} -
-
-
-
- -
- setNewDomain(e.target.value)} - placeholder="Add domain to hunt (e.g., dream.io)" - disabled={!canAddMore} - className="w-full pl-12 pr-4 py-4 bg-background-secondary/50 border border-border rounded-2xl - text-body text-foreground placeholder:text-foreground-subtle - focus:outline-none focus:border-accent/50 focus:ring-2 focus:ring-accent/10 - disabled:opacity-50 disabled:cursor-not-allowed - transition-all" - /> -
- + {/* Add Domain */} + +
+ + setNewDomain(e.target.value)} + placeholder="Add domain to watchlist..." + disabled={!canAddMore} + className="w-full pl-12 pr-4 py-3 bg-background-secondary border border-border rounded-xl text-body text-foreground placeholder:text-foreground-subtle focus:outline-none focus:border-accent/50 disabled:opacity-50 transition-all" + />
- {!canAddMore && ( -

- - Watchlist limit reached. Upgrade for more. -

- )} + - {/* Domain List */} + {/* Domain Table */} {domains.length === 0 ? ( -
-
- -
-

No targets yet

-

- Add your first domain above to start tracking availability -

+
+ +

No domains yet

+

Add your first domain above

) : ( -
- {domains.map((domain, index) => { - const expiration = formatExpirationDate(domain.expiration_date) - return ( -
-
-
- {/* Status indicator */} -
- - {/* Domain info */} -
-
- - {domain.name} +
+ + + + + + + + + + + + {domains.map((domain) => { + const exp = formatExpirationDate(domain.expiration_date) + return ( + + + + + + + + ) + })} + +
DomainStatusExpirationLast CheckActions
+
+
+ {domain.name} +
+
+ + {domain.is_available ? 'Available' : 'Registered'} + + + {exp ? ( + + {exp.text} - - {domain.is_available ? 'Available' : 'Registered'} - - {expiration && ( - - - {expiration.text} - + ) : } + + + {formatDate(domain.last_checked)} + + +
+ {isProOrHigher && ( + )} + + + +
-
- - {formatDate(domain.last_checked)} -
- - - - {/* Actions */} -
- {isProOrHigher && ( - - )} - - - - -
- - - ) - })} +
)}
@@ -776,230 +569,83 @@ export default function DashboardPage() { {/* Portfolio Tab */} {activeTab === 'portfolio' && ( -
- {/* Portfolio Summary */} +
+ {/* Portfolio Stats */} {portfolioSummary && ( -
-
-
-
- -
-
-

- {formatCurrency(portfolioSummary.total_value)} -

-

total value

+
+
+

Total Value

+

{formatCurrency(portfolioSummary.total_value)}

- -
-
-
- -
-
-

- {formatCurrency(portfolioSummary.total_invested)} -

-

invested

+
+

Invested

+

{formatCurrency(portfolioSummary.total_invested)}

- -
= 0 - ? "bg-gradient-to-br from-accent/10 to-accent/5 border-accent/20" - : "bg-gradient-to-br from-danger/10 to-danger/5 border-danger/20" - )}> -
-
= 0 ? "bg-accent/10" : "bg-danger/10" - )}> - {portfolioSummary.unrealized_profit >= 0 ? ( - - ) : ( - - )} -
-
-

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

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

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

+

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

-

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

- -
= 0 - ? "bg-gradient-to-br from-accent/10 to-accent/5 border-accent/20" - : "bg-gradient-to-br from-danger/10 to-danger/5 border-danger/20" - )}> -
-
= 0 ? "bg-accent/10" : "bg-danger/10" - )}> - = 0 ? "text-accent" : "text-danger" - )} /> -
-
-

= 0 ? "text-accent" : "text-danger" - )}> +

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

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

+

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

-

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

)} - {/* Add Domain Button */} -
- -
+ - {/* Portfolio List */} {loadingPortfolio ? ( -
- -

Loading portfolio...

-
+
) : portfolio.length === 0 ? ( -
-
- -
-

Your portfolio is empty

-

- Add domains you own to track their value and ROI -

+
+ +

Portfolio is empty

+

Add domains you own to track their value

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

- {domain.domain} -

- {domain.status === 'sold' && ( - Sold - )} +
+
+

{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} - - )} +
+ {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" - )}> +
= 0 ? "bg-accent/10" : "bg-danger/10")}>

ROI

-

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

+

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

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

- {domain.notes} -

- )} + {domain.notes &&

{domain.notes}

}
) })} @@ -1009,170 +655,70 @@ export default function DashboardPage() { )} {/* Quick Links */} -
- -
-
-
- -
-
-

TLD Price Intelligence

-

Track 886+ domain extensions

-
-
- +
+
+
+
- - - -
-
-
- -
-
-

Smart Pounce Auctions

-

Find undervalued domains

-
-
- +
+

TLD Price Intelligence

+

Track 886+ domain extensions

+
+ + Explore
{/* Modals */} - {/* Add Portfolio Domain Modal */} {showAddPortfolioModal && ( -
-
-
-
-

Add to Portfolio

- +
+
+
+
+

Add to Portfolio

+
- -
+
- setPortfolioForm({ ...portfolioForm, domain: e.target.value })} - placeholder="example.com" - required - 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:border-accent/50 focus:ring-2 focus:ring-accent/10 transition-all" - /> + setPortfolioForm({ ...portfolioForm, domain: e.target.value })} placeholder="example.com" required className="w-full px-4 py-3 bg-background border border-border rounded-xl text-body text-foreground placeholder:text-foreground-subtle focus:outline-none focus:border-accent/50 transition-all" />
-
- setPortfolioForm({ ...portfolioForm, purchase_price: e.target.value })} - placeholder="0.00" - 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:border-accent/50 focus:ring-2 focus:ring-accent/10 transition-all" - /> + setPortfolioForm({ ...portfolioForm, purchase_price: e.target.value })} placeholder="0.00" className="w-full px-4 py-3 bg-background border border-border rounded-xl text-body text-foreground placeholder:text-foreground-subtle focus:outline-none focus:border-accent/50 transition-all" />
- setPortfolioForm({ ...portfolioForm, purchase_date: e.target.value })} - className="w-full px-4 py-3.5 bg-background border border-border rounded-xl text-body text-foreground - focus:outline-none focus:border-accent/50 focus:ring-2 focus:ring-accent/10 transition-all" - /> + setPortfolioForm({ ...portfolioForm, purchase_date: e.target.value })} className="w-full px-4 py-3 bg-background border border-border rounded-xl text-body text-foreground focus:outline-none focus:border-accent/50 transition-all" />
-
- setPortfolioForm({ ...portfolioForm, registrar: e.target.value })} - placeholder="e.g., Porkbun" - 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:border-accent/50 focus:ring-2 focus:ring-accent/10 transition-all" - /> + setPortfolioForm({ ...portfolioForm, registrar: e.target.value })} placeholder="e.g., Porkbun" className="w-full px-4 py-3 bg-background border border-border rounded-xl text-body text-foreground placeholder:text-foreground-subtle focus:outline-none focus:border-accent/50 transition-all" />
-
- setPortfolioForm({ ...portfolioForm, renewal_date: e.target.value })} - className="w-full px-4 py-3.5 bg-background border border-border rounded-xl text-body text-foreground - focus:outline-none focus:border-accent/50 focus:ring-2 focus:ring-accent/10 transition-all" - /> + setPortfolioForm({ ...portfolioForm, renewal_date: e.target.value })} className="w-full px-4 py-3 bg-background border border-border rounded-xl text-body text-foreground focus:outline-none focus:border-accent/50 transition-all" />
- setPortfolioForm({ ...portfolioForm, renewal_cost: e.target.value })} - placeholder="0.00" - 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:border-accent/50 focus:ring-2 focus:ring-accent/10 transition-all" - /> + setPortfolioForm({ ...portfolioForm, renewal_cost: e.target.value })} placeholder="0.00" className="w-full px-4 py-3 bg-background border border-border rounded-xl text-body text-foreground placeholder:text-foreground-subtle focus:outline-none focus:border-accent/50 transition-all" />
-
-