'use client'; import { useSession } from 'next-auth/react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, Legend } from 'recharts'; import { format } from 'date-fns'; import Link from 'next/link'; export default function PortfolioPage() { const { data: session, status } = useSession(); const router = useRouter(); const [portfolio, setPortfolio] = useState([]); const [summary, setSummary] = useState({ total_value: 0, total_gain_loss: 0, cost_basis: 0 }); const [loading, setLoading] = useState(true); const [showAddModal, setShowAddModal] = useState(false); const [newHolding, setNewHolding] = useState({ ticker: '', shares: '', avg_cost: '' }); useEffect(() => { if (status === 'unauthenticated') { router.push('/auth/signin'); return; } if (session?.user) { fetchPortfolio(session.user.id); } }, [session, status, router]); const fetchPortfolio = async (userId: string) => { try { const [portfolioRes, summaryRes] = await Promise.all([ fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/portfolio/${userId}`), fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/portfolio/${userId}/summary`) ]); const portfolioData = await portfolioRes.json(); const summaryData = await summaryRes.json(); setPortfolio(portfolioData); setSummary(summaryData); } catch (error) { console.error('Error fetching portfolio:', error); } finally { setLoading(false); } }; const handleAddHolding = async (e: React.FormEvent) => { e.preventDefault(); try { await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/portfolio`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: session?.user?.id, ticker: newHolding.ticker.toUpperCase(), shares: parseFloat(newHolding.shares), avg_cost: parseFloat(newHolding.avg_cost) }) }); setShowAddModal(false); setNewHolding({ ticker: '', shares: '', avg_cost: '' }); fetchPortfolio(session?.user?.id); } catch (error) { console.error('Error adding holding:', error); } }; const handleDeleteHolding = async (id: number) => { if (!confirm('Are you sure you want to delete this holding?')) return; try { await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/portfolio/${id}`, { method: 'DELETE' }); fetchPortfolio(session?.user?.id); } catch (error) { console.error('Error deleting holding:', error); } }; if (loading) { return
Loading...
; } const pieData = portfolio.length > 0 ? portfolio.map((p: any) => ({ name: p.ticker, value: p.current_value || (p.shares * p.avg_cost) })) : []; const COLORS = ['#3b82f6', '#8b5cf6', '#10b981', '#f59e0b', '#ef4444', '#ec4899']; return (

Portfolio

Total Value

${summary.total_value?.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) || '$0.00'}

Total Gain/Loss

= 0 ? 'text-green-400' : 'text-red-400'}`}> {summary.total_gain_loss >= 0 ? '+' : ''}${summary.total_gain_loss?.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) || '$0.00'}

Positions

{portfolio.length}

Portfolio Allocation

{pieData.length > 0 ? ( `${entry.name} ($${(entry.value / 1000).toFixed(1)}k)`} > {pieData.map((entry, index) => ( ))} ) : (

No holdings yet

)}

Performance

{portfolio.length > 0 ? ( ({ name: p.ticker, value: p.gain_loss_pct || 0 }))}> ) : (

No performance data yet

)}
{portfolio.map((holding: any) => ( ))}
Ticker Shares Avg Cost Current Price Value Gain/Loss % Actions
{holding.ticker} {holding.shares.toLocaleString()} ${holding.avg_cost.toFixed(2)} ${holding.current_price?.toFixed(2) || 'N/A'} ${holding.current_value?.toFixed(2) || 'N/A'} = 0 ? 'text-green-400' : 'text-red-400'}`}> {holding.gain_loss >= 0 ? '+' : ''}${holding.gain_loss?.toFixed(2) || '0.00'} = 0 ? 'text-green-400' : 'text-red-400'}`}> {holding.gain_loss_pct >= 0 ? '+' : ''}{holding.gain_loss_pct?.toFixed(2) || '0.00'}%
{showAddModal && (

Add Holding

setNewHolding({...newHolding, ticker: e.target.value})} className="w-full bg-slate-700 border border-slate-600 rounded-lg px-4 py-3 text-white" placeholder="AAPL" required />
setNewHolding({...newHolding, shares: e.target.value})} className="w-full bg-slate-700 border border-slate-600 rounded-lg px-4 py-3 text-white" placeholder="100" required />
setNewHolding({...newHolding, avg_cost: e.target.value})} className="w-full bg-slate-700 border border-slate-600 rounded-lg px-4 py-3 text-white" placeholder="150.00" required />
)}
); }