diff --git a/backend/app/api/domains.py b/backend/app/api/domains.py
index dba383c..2f9f8ea 100644
--- a/backend/app/api/domains.py
+++ b/backend/app/api/domains.py
@@ -3,6 +3,7 @@ from datetime import datetime
from math import ceil
from fastapi import APIRouter, HTTPException, status, Query
+from pydantic import BaseModel
from sqlalchemy import select, func
from app.api.deps import Database, CurrentUser
@@ -212,10 +213,15 @@ async def refresh_domain(
return domain
+class NotifyUpdate(BaseModel):
+ """Schema for updating notification settings."""
+ notify: bool
+
+
@router.patch("/{domain_id}/notify", response_model=DomainResponse)
async def update_notification_settings(
domain_id: int,
- notify_on_available: bool,
+ data: NotifyUpdate,
current_user: CurrentUser,
db: Database,
):
@@ -234,7 +240,7 @@ async def update_notification_settings(
detail="Domain not found",
)
- domain.notify_on_available = notify_on_available
+ domain.notify_on_available = data.notify
await db.commit()
await db.refresh(domain)
diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx
index c307944..94d540d 100644
--- a/frontend/src/app/dashboard/page.tsx
+++ b/frontend/src/app/dashboard/page.tsx
@@ -18,6 +18,7 @@ import {
History,
ChevronRight,
Bell,
+ BellOff,
Check,
X,
Zap,
@@ -28,13 +29,18 @@ import {
Eye,
DollarSign,
Tag,
- MoreVertical,
Edit2,
ExternalLink,
Sparkles,
BarChart3,
CreditCard,
- Settings,
+ Target,
+ Crosshair,
+ Shield,
+ Activity,
+ ArrowUpRight,
+ ArrowDownRight,
+ Globe,
} from 'lucide-react'
import clsx from 'clsx'
import Link from 'next/link'
@@ -195,7 +201,7 @@ export default function DashboardPage() {
const loadDomainHistory = async (domainId: number) => {
if (!subscription?.features?.expiration_tracking) {
- setError('Check history requires Professional or Enterprise plan')
+ setError('Check history requires Trader or Tycoon plan')
return
}
@@ -380,9 +386,9 @@ export default function DashboardPage() {
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 left`, urgent: true }
- if (daysUntil <= 30) return { text: `${daysUntil}d left`, urgent: false }
- return { text: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }), urgent: false }
+ if (daysUntil <= 7) return { text: `${daysUntil}d`, urgent: true }
+ if (daysUntil <= 30) return { text: `${daysUntil}d`, urgent: false }
+ return { text: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), urgent: false }
}
const formatCurrency = (value: number | null) => {
@@ -397,8 +403,11 @@ export default function DashboardPage() {
if (isLoading) {
return (
-
-
+
+
+
+
Loading your command center...
+
)
}
@@ -412,107 +421,126 @@ export default function DashboardPage() {
: 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))
return daysUntil <= 30 && daysUntil > 0
}).length
- const tierName = subscription?.tier_name || subscription?.tier || 'Starter'
+ const tierName = subscription?.tier_name || subscription?.tier || 'Scout'
const isProOrHigher = tierName === 'Professional' || tierName === 'Enterprise' || tierName === 'Trader' || tierName === 'Tycoon'
const isEnterprise = tierName === 'Enterprise' || tierName === 'Tycoon'
return (
- {/* Ambient glow */}
-
-
+ {/* Background elements */}
+
-
-
- {/* Header with Tabs */}
-
-
+
+
+ {/* Hero Section */}
+
+
-
+
+
+
+
+
+ {isEnterprise && }
+ {tierName}
+
+
+
Command Center
-
+
Your domains. Your intel. Your edge.
-
- {isEnterprise && }
- {tierName} Plan
-
-
- {/* Billing/Upgrade Button */}
{isProOrHigher ? (
) : (
-
+
Upgrade
)}
+
- {/* Tabs */}
-
+ {/* Tab Navigation */}
+
+
{error && (
-
+
{error}
-
@@ -520,62 +548,83 @@ export default function DashboardPage() {
{/* Watchlist Tab */}
{activeTab === 'watchlist' && (
-
- {/* Stats */}
- {isProOrHigher && (
-
-
-
-
-
Tracked
+
+ {/* Stats Grid */}
+
+
+
-
-
-
- Available
-
-
{availableCount}
-
-
-
-
- Expiring
-
-
0 ? "text-warning" : "text-foreground"
- )}>{expiringCount}
-
-
-
-
- Frequency
-
-
- {subscription?.check_frequency || 'Daily'}
-
+
Total
+
{domains.length}
+
targets tracked
- )}
+
+
+
+
{availableCount}
+
available now
+
+
+
+
+
{takenCount}
+
registered
+
+
+
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
+
+
+
{/* Add Domain Form */}
-
@@ -723,61 +776,101 @@ export default function DashboardPage() {
{/* Portfolio Tab */}
{activeTab === 'portfolio' && (
-
+
{/* Portfolio Summary */}
{portfolioSummary && (
-
-
-
-
-
Total Value
+
+
+
-
+
{formatCurrency(portfolioSummary.total_value)}
+
total value
-
-
-
-
Invested
+
+
+
-
+
{formatCurrency(portfolioSummary.total_invested)}
+
invested
-
-
-
-
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"
+ )}>
+ {portfolioSummary.unrealized_profit >= 0 ? (
+
+ ) : (
+
+ )}
+
= 0 ? "text-accent" : "text-danger"
)}>
{portfolioSummary.unrealized_profit >= 0 ? '+' : ''}{formatCurrency(portfolioSummary.unrealized_profit)}
+
= 0 ? "text-accent/70" : "text-danger/70"
+ )}>unrealized P/L
-
-
-
-
ROI
+
+
= 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"
)}>
{portfolioSummary.overall_roi >= 0 ? '+' : ''}{portfolioSummary.overall_roi.toFixed(1)}%
+
= 0 ? "text-accent/70" : "text-danger/70"
+ )}>overall ROI
)}
{/* Add Domain Button */}
-
+
setShowAddPortfolioModal(true)}
- className="flex items-center gap-2 px-5 py-3 bg-foreground text-background text-ui font-medium rounded-xl
- hover:bg-foreground/90 transition-all"
+ className="flex items-center gap-2 px-5 py-3.5 bg-foreground text-background text-ui font-medium rounded-2xl
+ hover:bg-foreground/90 transition-all shadow-lg shadow-foreground/10"
>
Add Domain to Portfolio
@@ -786,50 +879,52 @@ export default function DashboardPage() {
{/* Portfolio List */}
{loadingPortfolio ? (
-
-
+
) : portfolio.length === 0 ? (
-
-
-
+
+
+
-
Your portfolio is empty
-
- Add domains you own to track their value
+
Your portfolio is empty
+
+ Add domains you own to track their value and ROI
) : (
-
- {portfolio.map((domain) => {
+
+ {portfolio.map((domain, index) => {
const roi = domain.roi
const renewal = formatExpirationDate(domain.renewal_date)
return (
-
-
+
+
{domain.domain}
{domain.status === 'sold' && (
- Sold
+ Sold
)}
{domain.purchase_price && (
- Bought: {formatCurrency(domain.purchase_price)}
+ {formatCurrency(domain.purchase_price)}
)}
{domain.estimated_value && (
- Value: {formatCurrency(domain.estimated_value)}
+ {formatCurrency(domain.estimated_value)}
)}
{renewal && (
@@ -838,37 +933,38 @@ export default function DashboardPage() {
renewal.urgent && "text-warning"
)}>
- Renewal: {renewal.text}
+ {renewal.text}
)}
{domain.registrar && (
-
+
{domain.registrar}
)}
-
+
+
{roi !== null && (
= 0 ? "bg-accent-muted" : "bg-danger-muted"
+ "text-right px-4 py-2 rounded-xl",
+ roi >= 0 ? "bg-accent/10" : "bg-danger/10"
)}>
ROI
= 0 ? "text-accent" : "text-danger"
)}>
{roi >= 0 ? '+' : ''}{roi.toFixed(1)}%
)}
-
+
{domain.status !== 'sold' && (
handleOpenSellModal(domain)}
- className="p-2 text-foreground-subtle hover:text-accent hover:bg-accent-muted rounded-lg transition-all"
+ className="p-2.5 text-foreground-subtle hover:text-accent hover:bg-accent/10 rounded-xl transition-all"
title="Mark as sold"
>
@@ -876,22 +972,22 @@ export default function DashboardPage() {
)}
handleOpenEditPortfolio(domain)}
- className="p-2 text-foreground-subtle hover:text-foreground hover:bg-background-tertiary rounded-lg transition-all"
- title="Edit details"
+ className="p-2.5 text-foreground-subtle hover:text-foreground hover:bg-foreground/5 rounded-xl transition-all"
+ title="Edit"
>
handleRefreshPortfolioValue(domain.id)}
disabled={refreshingPortfolioId === domain.id}
- className="p-2 text-foreground-subtle hover:text-foreground hover:bg-background-tertiary rounded-lg transition-all"
+ className="p-2.5 text-foreground-subtle hover:text-foreground hover:bg-foreground/5 rounded-xl transition-all"
title="Refresh value"
>
handleDeletePortfolioDomain(domain.id)}
- className="p-2 text-foreground-subtle hover:text-danger hover:bg-danger-muted rounded-lg transition-all"
+ className="p-2.5 text-foreground-subtle hover:text-danger hover:bg-danger/10 rounded-xl transition-all"
title="Remove"
>
@@ -900,7 +996,7 @@ export default function DashboardPage() {
{domain.notes && (
-
+
{domain.notes}
)}
@@ -912,44 +1008,64 @@ export default function DashboardPage() {
)}
- {/* CTA */}
-
-
-
-
-
-
-
TLD Price Intelligence
-
Track domain extension pricing trends
-
-
+ {/* Quick Links */}
+
- Explore Pricing
-
+
+
+
+
+
+
+
TLD Price Intelligence
+
Track 886+ domain extensions
+
+
+
+
+
+
+
+
+
+
+
+
Smart Pounce Auctions
+
Find undervalued domains
+
+
+
+
+ {/* Modals */}
{/* Add Portfolio Domain Modal */}
{showAddPortfolioModal && (
-
-
-
-
-
Add Domain to Portfolio
+
+
+
+
+
Add to Portfolio
setShowAddPortfolioModal(false)}
- className="p-2 text-foreground-subtle hover:text-foreground hover:bg-background-tertiary rounded-lg transition-all"
+ className="p-2 text-foreground-subtle hover:text-foreground hover:bg-foreground/5 rounded-xl transition-all"
>
-
@@ -983,8 +1099,8 @@ export default function DashboardPage() {
type="date"
value={portfolioForm.purchase_date}
onChange={(e) => 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-border-hover transition-all"
+ 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"
/>
@@ -995,9 +1111,9 @@ export default function DashboardPage() {
type="text"
value={portfolioForm.registrar}
onChange={(e) => setPortfolioForm({ ...portfolioForm, registrar: e.target.value })}
- placeholder="e.g., Porkbun, Namecheap"
- 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-border-hover transition-all"
+ 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"
/>
@@ -1008,8 +1124,8 @@ export default function DashboardPage() {
type="date"
value={portfolioForm.renewal_date}
onChange={(e) => 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-border-hover transition-all"
+ 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"
/>
@@ -1020,9 +1136,9 @@ export default function DashboardPage() {
min="0"
value={portfolioForm.renewal_cost}
onChange={(e) => 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-border-hover transition-all"
+ 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"
/>
@@ -1032,26 +1148,26 @@ export default function DashboardPage() {
-
+
setShowAddPortfolioModal(false)}
- className="flex-1 py-3 bg-background-tertiary text-foreground text-ui font-medium rounded-xl
- hover:bg-background-tertiary/80 transition-all"
+ className="flex-1 py-3.5 bg-foreground/5 text-foreground text-ui font-medium rounded-xl
+ hover:bg-foreground/10 transition-all"
>
Cancel
@@ -1067,35 +1183,37 @@ export default function DashboardPage() {
{/* Valuation Modal */}
{showValuationModal && (
-
-
-
+
+
+
-
Domain Valuation
+ Domain Valuation
{
setShowValuationModal(false)
setValuationResult(null)
}}
- className="p-2 text-foreground-subtle hover:text-foreground hover:bg-background-tertiary rounded-lg transition-all"
+ className="p-2 text-foreground-subtle hover:text-foreground hover:bg-foreground/5 rounded-xl transition-all"
>
{valuatingDomain ? (
-
-
+
+
+
+
Analyzing {valuatingDomain}...
) : valuationResult ? (
-
-
{valuationResult.domain}
-
+
+
{valuationResult.domain}
+
{formatCurrency(valuationResult.estimated_value)}
-
+
{valuationResult.confidence} confidence
@@ -1103,16 +1221,16 @@ export default function DashboardPage() {
{Object.entries(valuationResult.scores).map(([key, value]) => (
key !== 'overall' && (
-
-
{key}
-
-
+
)
@@ -1121,18 +1239,18 @@ export default function DashboardPage() {
{valuationResult.factors.has_numbers && (
- Contains numbers
+ Has numbers
)}
{valuationResult.factors.has_hyphens && (
- Contains hyphens
+ Has hyphens
)}
{valuationResult.factors.is_dictionary_word && (
- Dictionary word
+ Dictionary word
)}
-
- {valuationResult.factors.length} characters
+
+ {valuationResult.factors.length} chars
-
+
.{valuationResult.factors.tld}
@@ -1145,13 +1263,13 @@ export default function DashboardPage() {
{/* Domain History Modal */}
{selectedDomainId && domainHistory && (
-
-
-
+
+
+
-
Check History
-
+
Check History
+
{domains.find(d => d.id === selectedDomainId)?.name}
@@ -1160,28 +1278,29 @@ export default function DashboardPage() {
setSelectedDomainId(null)
setDomainHistory(null)
}}
- className="p-2 text-foreground-subtle hover:text-foreground hover:bg-background-tertiary rounded-lg transition-all"
+ className="p-2 text-foreground-subtle hover:text-foreground hover:bg-foreground/5 rounded-xl transition-all"
>
{loadingHistory ? (
-
-
+
+
) : domainHistory.length === 0 ? (
-
No check history yet
+
No history yet
) : (
- {domainHistory.map((check) => (
+ {domainHistory.map((check, index) => (
)}
- {/* Edit Portfolio Domain Modal */}
+ {/* Edit Portfolio Modal */}
{showEditPortfolioModal && editingPortfolioDomain && (
-
-
-
-
+
+
+
+
-
Edit Domain
-
{editingPortfolioDomain.domain}
+
Edit Domain
+
{editingPortfolioDomain.domain}
{
setShowEditPortfolioModal(false)
setEditingPortfolioDomain(null)
}}
- className="p-2 text-foreground-subtle hover:text-foreground hover:bg-background-tertiary rounded-lg transition-all"
+ className="p-2 text-foreground-subtle hover:text-foreground hover:bg-foreground/5 rounded-xl transition-all"
>
-
@@ -1270,8 +1389,8 @@ export default function DashboardPage() {
type="date"
value={editPortfolioForm.renewal_date}
onChange={(e) => setEditPortfolioForm({ ...editPortfolioForm, 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-border-hover transition-all"
+ 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"
/>
@@ -1282,9 +1401,9 @@ export default function DashboardPage() {
min="0"
value={editPortfolioForm.renewal_cost}
onChange={(e) => setEditPortfolioForm({ ...editPortfolioForm, 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-border-hover transition-all"
+ 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"
/>
@@ -1294,29 +1413,29 @@ export default function DashboardPage() {
-
+
{
setShowEditPortfolioModal(false)
setEditingPortfolioDomain(null)
}}
- className="flex-1 py-3 bg-background-tertiary text-foreground text-ui font-medium rounded-xl
- hover:bg-background transition-all"
+ className="flex-1 py-3.5 bg-foreground/5 text-foreground text-ui font-medium rounded-xl
+ hover:bg-foreground/10 transition-all"
>
Cancel
@@ -1332,28 +1451,27 @@ export default function DashboardPage() {
{/* Sell Domain Modal */}
{showSellModal && sellingDomain && (
-
-
-
+
+
+
-
Mark as Sold
-
{sellingDomain.domain}
+
Mark as Sold
+
{sellingDomain.domain}
{
setShowSellModal(false)
setSellingDomain(null)
}}
- className="p-2 text-foreground-subtle hover:text-foreground hover:bg-background-tertiary rounded-lg transition-all"
+ className="p-2 text-foreground-subtle hover:text-foreground hover:bg-foreground/5 rounded-xl transition-all"
>
- {/* Current value info */}
{sellingDomain.estimated_value && (
-
+
Estimated Value
{formatCurrency(sellingDomain.estimated_value)}
@@ -1367,7 +1485,7 @@ export default function DashboardPage() {
)}
-
- {/* Profit preview */}
{sellForm.sale_price && sellingDomain.purchase_price && (
-
+
= 0 ? "bg-accent/10" : "bg-danger/10"
+ )}>
Profit
)}
-
+
{
setShowSellModal(false)
setSellingDomain(null)
}}
- className="flex-1 py-3 bg-background-tertiary text-foreground text-ui font-medium rounded-xl
- hover:bg-background transition-all"
+ className="flex-1 py-3.5 bg-foreground/5 text-foreground text-ui font-medium rounded-xl
+ hover:bg-foreground/10 transition-all"
>
Cancel