SoftTraderFrontend/components/widgets/StockCard.tsx
2026-03-20 11:10:13 -04:00

84 lines
2.6 KiB
TypeScript

'use client'
import { useEffect, useState } from 'react'
import { getLiveQuote, formatMarketValue, type MappedQuote } from '@/lib/stockApi'
interface StockCardProps {
symbol?: string
}
export function StockCard({ symbol = 'AAPL' }: StockCardProps) {
const [quote, setQuote] = useState<MappedQuote | null>(null)
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(true)
setError(null)
getLiveQuote(symbol)
.then(setQuote)
.catch((err) => setError(err.message))
.finally(() => setLoading(false))
}, [symbol])
if (loading) {
return (
<div className="border border-border bg-card p-6 space-y-4">
<div className="text-muted-foreground text-sm">Loading {symbol}...</div>
</div>
)
}
if (error || !quote) {
return (
<div className="border border-border bg-card p-6 space-y-4">
<div className="font-bold text-foreground">{symbol}</div>
<div className="text-negative text-sm">{error ?? 'No data available'}</div>
</div>
)
}
const changeColor =
quote.priceChange > 0
? 'text-positive'
: quote.priceChange < 0
? 'text-negative'
: 'text-foreground'
const changeSign = quote.priceChange > 0 ? '+' : ''
return (
<div className="border border-border bg-card p-6 space-y-4">
<div className="text-2xl font-bold text-foreground">{quote.symbol}</div>
<div>
<div className="text-3xl font-bold text-foreground">
${quote.lastPrice.toFixed(2)}
</div>
<div className={`text-lg font-semibold ${changeColor}`}>
{changeSign}${quote.priceChange.toFixed(2)} ({changeSign}{quote.percentChange.toFixed(2)}%)
</div>
</div>
<div className="space-y-1 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Open</span>
<span className="font-medium">
{formatMarketValue(quote.openPrice, quote.marketOpen, (v) => `$${v.toFixed(2)}`)}
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Prev Close</span>
<span className="font-medium">${quote.previousClose.toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Volume</span>
<span className="font-medium">
{formatMarketValue(quote.volume, quote.marketOpen, (v) => v.toLocaleString())}
</span>
</div>
</div>
</div>
)
}