3340fd11ca
- Initialize Prisma with SQLite and Stock model - Create database service layer with singleton client - Add API routes for stock CRUD operations - Integrate database with analyze page to persist ticker entries - Add Playwright tests for stock database functionality
76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
import { useState } from "react";
|
|
|
|
export default function StockViewer() {
|
|
const [symbol, setSymbol] = useState("");
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [indicators, setIndicators] = useState<{
|
|
sma: number;
|
|
ema: number;
|
|
rsi: number;
|
|
macd: number;
|
|
} | null>(null);
|
|
|
|
const fetchIndicators = async () => {
|
|
if (!symbol.trim()) return;
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const res = await fetch(
|
|
`/api/indicators?symbol=${encodeURIComponent(symbol.trim())}`
|
|
);
|
|
const data = await res.json();
|
|
if (!res.ok) throw new Error(data.error || "API error");
|
|
setIndicators(data.indicators);
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : "Failed to fetch indicators.";
|
|
setError(message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="bg-white rounded-xl shadow-lg p-6 border border-gray-200 max-w-lg mx-auto">
|
|
<div className="flex gap-3 mb-6">
|
|
<input
|
|
type="text"
|
|
value={symbol}
|
|
onChange={(e) => setSymbol(e.target.value.toUpperCase())}
|
|
placeholder="Enter stock symbol (e.g. AAPL)"
|
|
className="flex-1 border border-gray-300 rounded-lg px-4 py-2.5 text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
onKeyDown={(e) => e.key === "Enter" && fetchIndicators()}
|
|
/>
|
|
<button
|
|
onClick={fetchIndicators}
|
|
disabled={loading || !symbol.trim()}
|
|
className="bg-blue-600 text-white px-6 py-2.5 rounded-lg font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
>
|
|
{loading ? "Loading…" : "Get Indicators"}
|
|
</button>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
|
|
<p className="text-red-600 text-sm">{error}</p>
|
|
</div>
|
|
)}
|
|
|
|
{indicators && (
|
|
<div className="bg-gray-50 rounded-lg p-4">
|
|
<h3 className="text-xl font-bold text-gray-900 mb-3">
|
|
Results for {symbol.toUpperCase()}
|
|
</h3>
|
|
<div className="space-y-2">
|
|
{Object.entries(indicators).map(([key, value]) => (
|
|
<div key={key} className="flex justify-between py-1.5 border-b border-gray-200 last:border-0">
|
|
<span className="text-gray-600 capitalize">{key}</span>
|
|
<span className="font-mono font-medium text-gray-900">{value.toFixed(2)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |