diff --git a/frontend/src/app/terminal/portfolio/page.tsx b/frontend/src/app/terminal/portfolio/page.tsx deleted file mode 100644 index 7c881f4..0000000 --- a/frontend/src/app/terminal/portfolio/page.tsx +++ /dev/null @@ -1,1013 +0,0 @@ -'use client' - -import { useEffect, useState, useCallback } from 'react' -import { useStore } from '@/lib/store' -import { api } from '@/lib/api' -import { TerminalLayout } from '@/components/TerminalLayout' -import { - Plus, - TrendingUp, - TrendingDown, - Wallet, - DollarSign, - Calendar, - RefreshCw, - Trash2, - Edit3, - Loader2, - CheckCircle, - AlertCircle, - X, - Briefcase, - PiggyBank, - Target, - ArrowRight, - MoreHorizontal, - Tag, - Clock, - Sparkles, - Shield -} from 'lucide-react' -import Link from 'next/link' -import clsx from 'clsx' - -// ============================================================================ -// SHARED COMPONENTS -// ============================================================================ - -function StatCard({ - label, - value, - subValue, - icon: Icon, - trend, - color = 'emerald' -}: { - label: string - value: string | number - subValue?: string - icon: any - trend?: 'up' | 'down' | 'neutral' - color?: 'emerald' | 'blue' | 'amber' | 'rose' -}) { - const colors = { - emerald: 'text-emerald-400', - blue: 'text-blue-400', - amber: 'text-amber-400', - rose: 'text-rose-400', - } - - return ( -
-
- -
-
-
- - {label} -
-
- {value} - {subValue && {subValue}} -
- {trend && ( -
- {trend === 'up' ? : trend === 'down' ? : null} - {trend === 'up' ? 'PROFIT' : trend === 'down' ? 'LOSS' : 'NEUTRAL'} -
- )} -
-
- ) -} - -// ============================================================================ -// TYPES -// ============================================================================ - -interface PortfolioDomain { - id: number - domain: string - purchase_date: string | null - purchase_price: number | null - purchase_registrar: string | null - registrar: string | null - renewal_date: string | null - renewal_cost: number | null - auto_renew: boolean - estimated_value: number | null - value_updated_at: string | null - is_sold: boolean - sale_date: string | null - sale_price: number | null - status: string - notes: string | null - tags: string | null - roi: number | null - created_at: string - updated_at: string -} - -interface PortfolioSummary { - total_domains: number - active_domains: number - sold_domains: number - total_invested: number - total_value: number - total_sold_value: number - unrealized_profit: number - realized_profit: number - overall_roi: number -} - -// ============================================================================ -// MAIN PAGE -// ============================================================================ - -export default function PortfolioPage() { - const { subscription } = useStore() - - const [domains, setDomains] = useState([]) - const [summary, setSummary] = useState(null) - const [loading, setLoading] = useState(true) - - // Modals - const [showAddModal, setShowAddModal] = useState(false) - const [showEditModal, setShowEditModal] = useState(false) - const [showSellModal, setShowSellModal] = useState(false) - const [showListModal, setShowListModal] = useState(false) - const [selectedDomain, setSelectedDomain] = useState(null) - const [saving, setSaving] = useState(false) - const [error, setError] = useState(null) - const [success, setSuccess] = useState(null) - - // List for sale form - const [listData, setListData] = useState({ - asking_price: '', - price_type: 'negotiable', - }) - - // Form state - const [formData, setFormData] = useState({ - domain: '', - purchase_date: '', - purchase_price: '', - registrar: '', - renewal_date: '', - renewal_cost: '', - notes: '', - tags: '', - }) - - const [sellData, setSellData] = useState({ - sale_date: new Date().toISOString().split('T')[0], - sale_price: '', - }) - - const loadData = useCallback(async () => { - setLoading(true) - try { - const [domainsData, summaryData] = await Promise.all([ - api.request('/portfolio'), - api.request('/portfolio/summary'), - ]) - setDomains(domainsData) - setSummary(summaryData) - } catch (err: any) { - console.error('Failed to load portfolio:', err) - } finally { - setLoading(false) - } - }, []) - - useEffect(() => { - loadData() - }, [loadData]) - - const handleAdd = async (e: React.FormEvent) => { - e.preventDefault() - setSaving(true) - setError(null) - - try { - await api.request('/portfolio', { - method: 'POST', - body: JSON.stringify({ - domain: formData.domain, - purchase_date: formData.purchase_date || null, - purchase_price: formData.purchase_price ? parseFloat(formData.purchase_price) : null, - registrar: formData.registrar || null, - renewal_date: formData.renewal_date || null, - renewal_cost: formData.renewal_cost ? parseFloat(formData.renewal_cost) : null, - notes: formData.notes || null, - tags: formData.tags || null, - }), - }) - setSuccess('Domain added to portfolio!') - setShowAddModal(false) - setFormData({ domain: '', purchase_date: '', purchase_price: '', registrar: '', renewal_date: '', renewal_cost: '', notes: '', tags: '' }) - loadData() - } catch (err: any) { - setError(err.message) - } finally { - setSaving(false) - } - } - - const handleEdit = async (e: React.FormEvent) => { - e.preventDefault() - if (!selectedDomain) return - setSaving(true) - setError(null) - - try { - await api.request(`/portfolio/${selectedDomain.id}`, { - method: 'PUT', - body: JSON.stringify({ - purchase_date: formData.purchase_date || null, - purchase_price: formData.purchase_price ? parseFloat(formData.purchase_price) : null, - registrar: formData.registrar || null, - renewal_date: formData.renewal_date || null, - renewal_cost: formData.renewal_cost ? parseFloat(formData.renewal_cost) : null, - notes: formData.notes || null, - tags: formData.tags || null, - }), - }) - setSuccess('Domain updated!') - setShowEditModal(false) - loadData() - } catch (err: any) { - setError(err.message) - } finally { - setSaving(false) - } - } - - const handleSell = async (e: React.FormEvent) => { - e.preventDefault() - if (!selectedDomain) return - setSaving(true) - setError(null) - - try { - await api.request(`/portfolio/${selectedDomain.id}/sell`, { - method: 'POST', - body: JSON.stringify({ - sale_date: sellData.sale_date, - sale_price: parseFloat(sellData.sale_price), - }), - }) - setSuccess(`🎉 Congratulations! ${selectedDomain.domain} marked as sold!`) - setShowSellModal(false) - loadData() - } catch (err: any) { - setError(err.message) - } finally { - setSaving(false) - } - } - - const handleDelete = async (domain: PortfolioDomain) => { - if (!confirm(`Remove ${domain.domain} from portfolio?`)) return - - try { - await api.request(`/portfolio/${domain.id}`, { method: 'DELETE' }) - setSuccess('Domain removed from portfolio') - loadData() - } catch (err: any) { - setError(err.message) - } - } - - const handleRefreshValue = async (domain: PortfolioDomain) => { - try { - await api.request(`/portfolio/${domain.id}/refresh-value`, { method: 'POST' }) - setSuccess(`Value refreshed for ${domain.domain}`) - loadData() - } catch (err: any) { - setError(err.message) - } - } - - const openEditModal = (domain: PortfolioDomain) => { - setSelectedDomain(domain) - setFormData({ - domain: domain.domain, - purchase_date: domain.purchase_date?.split('T')[0] || '', - purchase_price: domain.purchase_price?.toString() || '', - registrar: domain.registrar || '', - renewal_date: domain.renewal_date?.split('T')[0] || '', - renewal_cost: domain.renewal_cost?.toString() || '', - notes: domain.notes || '', - tags: domain.tags || '', - }) - setShowEditModal(true) - } - - const openSellModal = (domain: PortfolioDomain) => { - setSelectedDomain(domain) - setSellData({ - sale_date: new Date().toISOString().split('T')[0], - sale_price: domain.estimated_value?.toString() || '', - }) - setShowSellModal(true) - } - - const openListModal = (domain: PortfolioDomain) => { - setSelectedDomain(domain) - setListData({ - asking_price: domain.estimated_value?.toString() || '', - price_type: 'negotiable', - }) - setShowListModal(true) - } - - const handleListForSale = async (e: React.FormEvent) => { - e.preventDefault() - if (!selectedDomain) return - setSaving(true) - setError(null) - - try { - // Create a listing for this domain - await api.request('/listings', { - method: 'POST', - body: JSON.stringify({ - domain: selectedDomain.domain, - asking_price: listData.asking_price ? parseFloat(listData.asking_price) : null, - price_type: listData.price_type, - allow_offers: true, - }), - }) - setSuccess(`${selectedDomain.domain} is now listed for sale! Go to "For Sale" to verify ownership and publish.`) - setShowListModal(false) - } catch (err: any) { - setError(err.message) - } finally { - setSaving(false) - } - } - - const formatCurrency = (value: number | null) => { - if (value === null) return '—' - return new Intl.NumberFormat('en-US', { - style: 'currency', - currency: 'USD', - minimumFractionDigits: 0, - }).format(value) - } - - const formatDate = (date: string | null) => { - if (!date) return '—' - return new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) - } - - // Tier check - const tier = subscription?.tier || 'scout' - const canUsePortfolio = tier !== 'scout' - - return ( - -
- - {/* Ambient Background Glow */} -
-
-
-
- -
- - {/* Header Section */} -
-
-
-
-

Portfolio

-
-

- Track your owned domains, valuations, and ROI. Switch back to your Watchlist anytime. -

-
- -
- {/* View Tabs: Watching vs My Portfolio */} -
- - - Watching - - -
- - {canUsePortfolio && ( - - )} -
-
- - {/* Messages */} - {error && ( -
- -

{error}

- -
- )} - - {success && ( -
- -

{success}

- -
- )} - - {/* Paywall */} - {!canUsePortfolio && ( -
-
-
- -

Unlock Portfolio Management

-

- Track your domain investments, monitor valuations, and calculate ROI. Know exactly how your portfolio is performing. -

- - Upgrade to Trader - -
-
- )} - - {/* Stats Grid */} - {canUsePortfolio && summary && ( -
- 0 ? 'up' : summary.unrealized_profit < 0 ? 'down' : 'neutral'} - /> - - = 0 ? 'emerald' : 'rose'} - trend={summary.unrealized_profit > 0 ? 'up' : summary.unrealized_profit < 0 ? 'down' : 'neutral'} - /> - 0 ? '+' : ''}${summary.overall_roi.toFixed(1)}%`} - subValue={`${summary.sold_domains} sold`} - icon={Target} - color={summary.overall_roi >= 0 ? 'emerald' : 'rose'} - trend={summary.overall_roi > 0 ? 'up' : summary.overall_roi < 0 ? 'down' : 'neutral'} - /> -
- )} - - {/* Domains Table */} - {canUsePortfolio && ( -
- {/* Table Header */} -
-
Domain
-
Cost
-
Value
-
ROI
-
Status
-
Actions
-
- - {loading ? ( -
- -
- ) : domains.length === 0 ? ( -
-
- -
-

No domains in portfolio

-

- Add your first domain to start tracking your investments. -

- -
- ) : ( -
- {domains.map((domain) => ( -
- - {/* Domain */} -
-
-
- {domain.domain.charAt(0).toUpperCase()} -
-
-
{domain.domain}
-
- {domain.registrar || 'No registrar'} - {domain.renewal_date && ( - • Renews {formatDate(domain.renewal_date)} - )} -
-
-
-
- - {/* Cost */} -
-
{formatCurrency(domain.purchase_price)}
- {domain.purchase_date && ( -
{formatDate(domain.purchase_date)}
- )} -
- - {/* Value */} -
-
- {domain.is_sold ? formatCurrency(domain.sale_price) : formatCurrency(domain.estimated_value)} -
- {domain.is_sold ? ( -
Sold {formatDate(domain.sale_date)}
- ) : domain.value_updated_at && ( -
Updated {formatDate(domain.value_updated_at)}
- )} -
- - {/* ROI */} -
- {domain.roi !== null ? ( -
= 0 ? "text-emerald-400" : "text-rose-400" - )}> - {domain.roi > 0 ? '+' : ''}{domain.roi.toFixed(1)}% -
- ) : ( -
- )} -
- - {/* Status */} -
- - {domain.is_sold ? 'Sold' : domain.status} - -
- - {/* Actions */} -
- {!domain.is_sold && ( - <> - - - - - )} - - -
- -
- ))} -
- )} -
- )} - -
- - {/* Add Modal */} - {showAddModal && ( -
-
-
-

Add to Portfolio

-

Track a domain you own

-
- -
-
- - setFormData({ ...formData, domain: e.target.value })} - placeholder="example.com" - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-blue-500/50 transition-all font-mono" - /> -
- -
-
- - setFormData({ ...formData, purchase_date: e.target.value })} - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white focus:outline-none focus:border-blue-500/50 transition-all" - /> -
-
- -
- - setFormData({ ...formData, purchase_price: e.target.value })} - placeholder="0.00" - className="w-full pl-9 pr-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-blue-500/50 transition-all font-mono" - /> -
-
-
- -
-
- - setFormData({ ...formData, registrar: e.target.value })} - placeholder="Namecheap, GoDaddy..." - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-zinc-600 focus:outline-none focus:border-blue-500/50 transition-all" - /> -
-
- - setFormData({ ...formData, renewal_date: e.target.value })} - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white focus:outline-none focus:border-blue-500/50 transition-all" - /> -
-
- -
- -