From 0447896812ac710ee25b91d1746ac94e91de055d Mon Sep 17 00:00:00 2001 From: "yves.gugger" Date: Wed, 10 Dec 2025 08:37:29 +0100 Subject: [PATCH] Major navigation overhaul: Add Command Center with Sidebar - New Sidebar component with collapsible navigation - New CommandCenterLayout for logged-in users - Separate routes: /watchlist, /portfolio, /market, /intelligence - Dashboard with Activity Feed and Market Pulse - Traffic light status indicators for domain status - Updated Header for public/logged-in state separation - Settings page uses new Command Center layout --- frontend/src/app/dashboard/page.tsx | 1366 ++++------------- frontend/src/app/intelligence/page.tsx | 257 ++++ frontend/src/app/market/page.tsx | 252 +++ frontend/src/app/portfolio/page.tsx | 529 +++++++ frontend/src/app/settings/page.tsx | 40 +- frontend/src/app/watchlist/page.tsx | 494 ++++++ .../src/components/CommandCenterLayout.tsx | 238 +++ frontend/src/components/Header.tsx | 267 +--- frontend/src/components/Sidebar.tsx | 280 ++++ 9 files changed, 2440 insertions(+), 1283 deletions(-) create mode 100644 frontend/src/app/intelligence/page.tsx create mode 100644 frontend/src/app/market/page.tsx create mode 100644 frontend/src/app/portfolio/page.tsx create mode 100644 frontend/src/app/watchlist/page.tsx create mode 100644 frontend/src/components/CommandCenterLayout.tsx create mode 100644 frontend/src/components/Sidebar.tsx diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index b8932c3..f50d14c 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -1,117 +1,65 @@ 'use client' -import { useEffect, useState, Suspense } from 'react' +import { useEffect, useState } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import { useStore } from '@/lib/store' -import { api, PortfolioDomain, PortfolioSummary, DomainValuation } from '@/lib/api' -import { Header } from '@/components/Header' -import { Footer } from '@/components/Footer' +import { api } from '@/lib/api' +import { CommandCenterLayout } from '@/components/CommandCenterLayout' import { Toast, useToast } from '@/components/Toast' import { - Plus, - Trash2, - RefreshCw, - Loader2, + Eye, + Briefcase, + TrendingUp, + Gavel, Clock, - AlertCircle, - Calendar, - History, Bell, - BellOff, - Check, - X, + ArrowRight, + ExternalLink, + Sparkles, + ChevronRight, + Search, + Plus, Zap, Crown, - Briefcase, - Eye, - DollarSign, - Tag, - Edit2, - Sparkles, - CreditCard, - Globe, + Activity, } from 'lucide-react' import clsx from 'clsx' import Link from 'next/link' -type TabType = 'watchlist' | 'portfolio' - -interface DomainHistory { - id: number - status: string - is_available: boolean - checked_at: string +interface HotAuction { + domain: string + current_bid: number + time_remaining: string + platform: string + affiliate_url?: string } -function DashboardContent() { +interface TrendingTld { + tld: string + current_price: number + price_change: number + reason: string +} + +export default function DashboardPage() { const router = useRouter() const searchParams = useSearchParams() - const { - isAuthenticated, - isLoading, - checkAuth, - domains, - subscription, - addDomain, - deleteDomain, - refreshDomain, + const { + isAuthenticated, + isLoading, + checkAuth, + user, + domains, + subscription } = useStore() - - const { toast, showToast, hideToast } = useToast() - const [activeTab, setActiveTab] = useState('watchlist') - const [newDomain, setNewDomain] = useState('') - const [adding, setAdding] = useState(false) - const [refreshingId, setRefreshingId] = useState(null) - const [error, setError] = useState(null) - // Check for upgrade success - useEffect(() => { - if (searchParams.get('upgraded') === 'true') { - showToast('Welcome to your upgraded plan! 🎉', 'success') - // Clean up URL - window.history.replaceState({}, '', '/dashboard') - } - }, [searchParams]) - const [selectedDomainId, setSelectedDomainId] = useState(null) - const [domainHistory, setDomainHistory] = useState(null) - const [loadingHistory, setLoadingHistory] = useState(false) - const [portfolio, setPortfolio] = useState([]) - const [portfolioSummary, setPortfolioSummary] = useState(null) - const [loadingPortfolio, setLoadingPortfolio] = useState(false) - const [showAddPortfolioModal, setShowAddPortfolioModal] = useState(false) - const [showValuationModal, setShowValuationModal] = useState(false) - const [valuationResult, setValuationResult] = useState(null) - const [valuatingDomain, setValuatingDomain] = useState('') - const [refreshingPortfolioId, setRefreshingPortfolioId] = useState(null) - const [portfolioForm, setPortfolioForm] = useState({ - domain: '', - purchase_price: '', - purchase_date: '', - registrar: '', - renewal_date: '', - renewal_cost: '', - notes: '', - }) - const [addingPortfolio, setAddingPortfolio] = useState(false) - const [showEditPortfolioModal, setShowEditPortfolioModal] = useState(false) - const [editingPortfolioDomain, setEditingPortfolioDomain] = useState(null) - const [editPortfolioForm, setEditPortfolioForm] = useState({ - purchase_price: '', - purchase_date: '', - registrar: '', - renewal_date: '', - renewal_cost: '', - notes: '', - }) - const [savingEdit, setSavingEdit] = useState(false) - const [showSellModal, setShowSellModal] = useState(false) - const [sellingDomain, setSellingDomain] = useState(null) - const [sellForm, setSellForm] = useState({ - sale_date: new Date().toISOString().split('T')[0], - sale_price: '', - }) - const [processingSale, setProcessingSale] = useState(false) - const [togglingNotifyId, setTogglingNotifyId] = useState(null) + const { toast, showToast, hideToast } = useToast() + const [hotAuctions, setHotAuctions] = useState([]) + const [trendingTlds, setTrendingTlds] = useState([]) + const [loadingAuctions, setLoadingAuctions] = useState(true) + const [loadingTlds, setLoadingTlds] = useState(true) + const [quickDomain, setQuickDomain] = useState('') + const [addingDomain, setAddingDomain] = useState(false) useEffect(() => { checkAuth() @@ -123,983 +71,349 @@ function DashboardContent() { } }, [isLoading, isAuthenticated, router]) - // Load portfolio data on mount (so we can show count in tab) + // Check for upgrade success + useEffect(() => { + if (searchParams.get('upgraded') === 'true') { + showToast('Welcome to your upgraded plan! 🎉', 'success') + window.history.replaceState({}, '', '/dashboard') + } + }, [searchParams]) + + // Load dashboard data useEffect(() => { if (isAuthenticated) { - loadPortfolio() + loadDashboardData() } }, [isAuthenticated]) - const handleOpenBillingPortal = async () => { + const loadDashboardData = async () => { try { - const { portal_url } = await api.createPortalSession() - window.location.href = portal_url - } catch (err: any) { - setError(err.message || 'Failed to open billing portal') - } - } - - const loadPortfolio = async () => { - setLoadingPortfolio(true) - try { - const [portfolioData, summaryData] = await Promise.all([ - api.getPortfolio(), - api.getPortfolioSummary(), + const [auctions, trending] = await Promise.all([ + api.getEndingSoonAuctions(5).catch(() => []), + api.getTrendingTlds().catch(() => ({ trending: [] })) ]) - setPortfolio(portfolioData) - setPortfolioSummary(summaryData) - } catch (err) { - console.error('Failed to load portfolio:', err) + setHotAuctions(auctions.slice(0, 5)) + setTrendingTlds(trending.trending?.slice(0, 4) || []) + } catch (error) { + console.error('Failed to load dashboard data:', error) } finally { - setLoadingPortfolio(false) + setLoadingAuctions(false) + setLoadingTlds(false) } } - const handleAddDomain = async (e: React.FormEvent) => { + const handleQuickAdd = async (e: React.FormEvent) => { e.preventDefault() - if (!newDomain.trim()) return - setAdding(true) - setError(null) + if (!quickDomain.trim()) return + + setAddingDomain(true) try { - await addDomain(newDomain) - showToast(`Added ${newDomain} to watchlist`, 'success') - setNewDomain('') - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to add domain') + const store = useStore.getState() + await store.addDomain(quickDomain.trim()) + setQuickDomain('') + showToast(`Added ${quickDomain.trim()} to watchlist`, 'success') + } catch (err: any) { + showToast(err.message || 'Failed to add domain', 'error') } finally { - setAdding(false) + setAddingDomain(false) } } - const handleRefresh = async (id: number) => { - setRefreshingId(id) - try { - await refreshDomain(id) - } finally { - setRefreshingId(null) - } - } - - const handleDelete = async (id: number) => { - if (!confirm('Remove this domain from your watchlist?')) return - await deleteDomain(id) - } - - const loadDomainHistory = async (domainId: number) => { - if (!subscription?.features?.expiration_tracking) { - setError('Check history requires Trader or Tycoon plan') - return - } - setSelectedDomainId(domainId) - setLoadingHistory(true) - try { - const result = await api.getDomainHistory(domainId, 30) - setDomainHistory(result.history) - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load history') - } finally { - setLoadingHistory(false) - } - } - - const handleAddPortfolioDomain = async (e: React.FormEvent) => { - e.preventDefault() - if (!portfolioForm.domain.trim()) return - setAddingPortfolio(true) - try { - await api.addPortfolioDomain({ - domain: portfolioForm.domain, - purchase_price: portfolioForm.purchase_price ? parseFloat(portfolioForm.purchase_price) : undefined, - purchase_date: portfolioForm.purchase_date || undefined, - registrar: portfolioForm.registrar || undefined, - renewal_date: portfolioForm.renewal_date || undefined, - 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: '' }) - setShowAddPortfolioModal(false) - showToast(`Added ${portfolioForm.domain} to portfolio`, 'success') - loadPortfolio() - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to add domain to portfolio') - } finally { - setAddingPortfolio(false) - } - } - - const handleDeletePortfolioDomain = async (id: number) => { - if (!confirm('Remove this domain from your portfolio?')) return - try { - await api.deletePortfolioDomain(id) - showToast('Domain removed from portfolio', 'success') - loadPortfolio() - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to delete domain') - } - } - - const handleRefreshPortfolioValue = async (id: number) => { - setRefreshingPortfolioId(id) - try { - await api.refreshDomainValue(id) - loadPortfolio() - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to refresh value') - } finally { - setRefreshingPortfolioId(null) - } - } - - const handleGetValuation = async (domain: string) => { - setValuatingDomain(domain) - setShowValuationModal(true) - try { - const result = await api.getDomainValuation(domain) - setValuationResult(result) - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to get valuation') - setShowValuationModal(false) - } finally { - setValuatingDomain('') - } - } - - const handleToggleNotify = async (domainId: number, currentNotify: boolean) => { - setTogglingNotifyId(domainId) - try { - await api.updateDomainNotify(domainId, !currentNotify) - const { fetchDomains } = useStore.getState() - await fetchDomains() - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to update notification setting') - } finally { - setTogglingNotifyId(null) - } - } - - const handleOpenEditPortfolio = (domain: PortfolioDomain) => { - setEditingPortfolioDomain(domain) - setEditPortfolioForm({ - purchase_price: domain.purchase_price?.toString() || '', - purchase_date: domain.purchase_date || '', - registrar: domain.registrar || '', - renewal_date: domain.renewal_date || '', - renewal_cost: domain.renewal_cost?.toString() || '', - notes: domain.notes || '', - }) - setShowEditPortfolioModal(true) - } - - const handleSaveEditPortfolio = async (e: React.FormEvent) => { - e.preventDefault() - if (!editingPortfolioDomain) return - setSavingEdit(true) - try { - await api.updatePortfolioDomain(editingPortfolioDomain.id, { - purchase_price: editPortfolioForm.purchase_price ? parseFloat(editPortfolioForm.purchase_price) : undefined, - purchase_date: editPortfolioForm.purchase_date || undefined, - registrar: editPortfolioForm.registrar || undefined, - renewal_date: editPortfolioForm.renewal_date || undefined, - renewal_cost: editPortfolioForm.renewal_cost ? parseFloat(editPortfolioForm.renewal_cost) : undefined, - notes: editPortfolioForm.notes || undefined, - }) - setShowEditPortfolioModal(false) - setEditingPortfolioDomain(null) - showToast('Domain updated', 'success') - loadPortfolio() - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to update domain') - } finally { - setSavingEdit(false) - } - } - - const handleOpenSellModal = (domain: PortfolioDomain) => { - setSellingDomain(domain) - setSellForm({ sale_date: new Date().toISOString().split('T')[0], sale_price: '' }) - setShowSellModal(true) - } - - 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)) - setShowSellModal(false) - const soldDomainName = sellingDomain.domain - setSellingDomain(null) - showToast(`Marked ${soldDomainName} as sold 🎉`, 'success') - loadPortfolio() - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to mark domain as sold') - } finally { - setProcessingSale(false) - } - } - - const formatDate = (dateStr: string | null) => { - 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' }) - } - - const formatExpirationDate = (dateStr: string | null) => { - if (!dateStr) return null - 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 } - return { text: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), urgent: false } - } - - 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) - } - - if (isLoading) { + if (isLoading || !isAuthenticated) { return (
- +
) } - if (!isAuthenticated) return null - - const canAddMore = subscription ? subscription.domains_used < subscription.domain_limit : true - const availableCount = 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 - + // Calculate stats + const availableDomains = domains?.filter(d => d.is_available) || [] + const totalDomains = domains?.length || 0 const tierName = subscription?.tier_name || subscription?.tier || 'Scout' - const isProOrHigher = tierName === 'Professional' || tierName === 'Enterprise' || tierName === 'Trader' || tierName === 'Tycoon' - const isEnterprise = tierName === 'Enterprise' || tierName === 'Tycoon' - - // Feature flags based on subscription - const hasPortfolio = (subscription?.portfolio_limit ?? 0) !== 0 || subscription?.features?.domain_valuation - const hasDomainValuation = subscription?.features?.domain_valuation ?? false - const hasExpirationTracking = subscription?.features?.expiration_tracking ?? false - const hasHistory = (subscription?.history_days ?? 0) > 0 + const TierIcon = tierName === 'Tycoon' ? Crown : tierName === 'Trader' ? TrendingUp : Zap return ( -
- {/* Background Effects - matching landing page */} -
-
-
-
-
- -
+ + {toast && } -
-
- {/* Header */} -
-
- Command Center -

- Your hunting ground. -

-

- Your domains. Your intel. Your edge. -

-
-
- - {isEnterprise && } - {tierName} - - {isProOrHigher ? ( - - ) : ( - - - Upgrade - - )} -
-
- - {/* Tabs - Landing Page Style */} -
+
+ {/* Quick Add */} +
+

+ + Quick Add to Watchlist +

+
+ setQuickDomain(e.target.value)} + placeholder="Enter domain to track (e.g., dream.com)" + className="flex-1 h-12 px-4 bg-background border border-border rounded-xl + text-foreground placeholder:text-foreground-subtle + focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent" + /> - {hasPortfolio ? ( - - ) : ( - - - Portfolio - Pro + +
+ + {/* Stats Overview */} +
+ +
+
+ +
+ +
+

{totalDomains}

+

Domains Watched

+ + + 0 + ? "bg-accent/10 border-accent/20 hover:border-accent/40" + : "bg-background-secondary/50 border-border hover:border-foreground/20" + )} + > +
+
0 ? "bg-accent/20" : "bg-foreground/5" + )}> + 0 ? "text-accent" : "text-foreground-muted" + )} /> +
+ {availableDomains.length > 0 && ( + + Action! + + )} +
+

0 ? "text-accent" : "text-foreground" + )}> + {availableDomains.length} +

+

Available Now

+ + + +
+
+ +
+ +
+

0

+

Portfolio Domains

+ + +
+
+
+ +
+
+

{tierName}

+

+ {subscription?.domains_used || 0}/{subscription?.domain_limit || 5} slots used +

+
+
+ + {/* Activity Feed + Market Pulse */} +
+ {/* Activity Feed */} +
+
+

+ + Activity Feed +

+ + View all +
+ + {availableDomains.length > 0 ? ( +
+ {availableDomains.slice(0, 4).map((domain) => ( +
+
+ + +
+
+

{domain.name}

+

Available for registration!

+
+ + Register + +
+ ))} + {availableDomains.length > 4 && ( +

+ +{availableDomains.length - 4} more available +

+ )} +
+ ) : totalDomains > 0 ? ( +
+ +

All domains are still registered

+

+ We're monitoring {totalDomains} domains for you +

+
+ ) : ( +
+ +

No domains tracked yet

+

+ Add a domain above to start monitoring +

+
)}
- {error && ( -
- -

{error}

- + {/* Market Pulse */} +
+
+

+ + Market Pulse +

+ + View all +
- )} - {/* Watchlist Tab */} - {activeTab === 'watchlist' && ( -
- {/* Stats Row - Landing Page Style */} -
-
-
-
-
- -
-

Tracked

-

{domains.length}

-
-
-
-
-
-
- -
-

Available

-

{availableCount}

-
-
-
-
-
-
- -
-

Monitoring

-

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

-
-
- {hasExpirationTracking && ( -
-
-
-
- -
-

Expiring

-

{expiringCount}

-
-
- )} + {loadingAuctions ? ( +
+ {[...Array(4)].map((_, i) => ( +
+ ))}
- - {/* Limit Warning */} - {!canAddMore && ( -
-
- -
-

You've reached your domain limit

-

Upgrade to track more domains and unlock premium features

+ ) : hotAuctions.length > 0 ? ( + - - - Upgrade Now - -
- )} +
+

${auction.current_bid}

+

current bid

+
+ +
+ ))} +
+ ) : ( +
+ +

No auctions ending soon

+
+ )} +
+
- {/* 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" - /> -
- -
+ {/* Trending TLDs */} +
+
+

+ + Trending TLDs +

+ + View all + +
- {/* Domain Table */} - {domains.length === 0 ? ( -
-
- -
-

No domains tracked yet

-

Add your first domain above to start monitoring

-
- ) : ( -
- - - - - - {hasExpirationTracking && ( - - )} - - - - - - {domains.map((domain) => { - const exp = formatExpirationDate(domain.expiration_date) - const isMonitoring = domain.notify_on_available - return ( - - - - {hasExpirationTracking && ( - - )} - - - - ) - })} - -
DomainStatusExpirationLast CheckActions
-
-
-
- {isMonitoring && ( - - )} -
- {domain.name} -
-
- - {domain.is_available ? 'Available' : isMonitoring ? 'Monitoring' : 'Registered'} - - - {exp ? ( - - {exp.text} - - ) : —} - - - {formatDate(domain.last_checked)} - - -
- {hasHistory && ( - - )} - {hasDomainValuation ? ( - - ) : ( - - - - )} - - - -
-
-
- )} + {loadingTlds ? ( +
+ {[...Array(4)].map((_, i) => ( +
+ ))}
- )} - - {/* Portfolio Tab */} - {activeTab === 'portfolio' && ( -
- {/* Portfolio Stats */} - {portfolioSummary && ( -
-
-

Total Value

-

{formatCurrency(portfolioSummary.total_value)}

+ ) : ( +
+ {trendingTlds.map((tld) => ( + +
+ .{tld.tld} + 0 + ? "text-orange-400 bg-orange-400/10" + : "text-accent bg-accent/10" + )}> + {(tld.price_change || 0) > 0 ? '+' : ''}{(tld.price_change || 0).toFixed(1)}% +
-
-

Invested

-

{formatCurrency(portfolioSummary.total_invested)}

-
-
-

P/L

-

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

-
-
-

ROI

-

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

-
-
- )} - - {/* Add Domain */} - - - {loadingPortfolio ? ( -
- ) : portfolio.length === 0 ? ( -
-
- -
-

Your portfolio is empty

-

Add domains you own to track their value and ROI

-
- ) : ( -
- - - - - - - - - - - - - {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.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' && ( - - )} - - - -
-
-
- )} +

{tld.reason}

+ + ))}
)}
-
- - {/* Modals */} - {showAddPortfolioModal && ( -
-
-
-
-

Add to Portfolio

- -
-
-
- - 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 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 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 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 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 bg-background border border-border rounded-xl text-body text-foreground placeholder:text-foreground-subtle focus:outline-none focus:border-accent/50 transition-all" /> -
-
-
- -