mirror of
https://github.com/ryanamay/twinkle.git
synced 2024-09-20 05:30:35 +00:00
feat: recommendations, ui improvements
This commit is contained in:
parent
7869362aeb
commit
57ea40937d
13 changed files with 346 additions and 72 deletions
|
@ -1,4 +1,5 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import PeersWidget from './PeersWidget';
|
||||
|
||||
interface CompanyProfileProps {
|
||||
ticker: string;
|
||||
|
@ -51,29 +52,30 @@ const CompanyProfileCard = ({ ticker }: CompanyProfileProps) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 border bg-white dark:bg-gray-800 rounded-lg shadow-lg mt-4 flex-col">
|
||||
<h1 className="mb-4 font-bold">About this company</h1>
|
||||
<div className="flex items-center space-x-4 mb-4">
|
||||
<div className="p-6 bg-surface0 dark:bg-surface0 rounded-lg shadow-md">
|
||||
<h1 className="text-xl font-bold text-text dark:text-text">About this company</h1>
|
||||
<div className="flex items-center gap-4 mt-4">
|
||||
<img src={profile.logo} alt={`${profile.name} logo`} className="w-16 h-16 object-cover rounded-lg" />
|
||||
<div>
|
||||
<h2 className="text-xl font-bold">{profile.name}</h2>
|
||||
<p className="text-sm text-gray-500">{profile.ticker} | {profile.finnhubIndustry}</p>
|
||||
<h2 className="text-2xl font-bold text-text dark:text-text">{profile.name}</h2>
|
||||
<p className="text-sm text-text dark:text-subtext1">{profile.ticker} | {profile.finnhubIndustry}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
|
||||
<div>
|
||||
<p><span className="font-semibold">Country:</span> {profile.country}</p>
|
||||
<p><span className="font-semibold">Currency:</span> {profile.currency}</p>
|
||||
<p><span className="font-semibold">Exchange:</span> {profile.exchange}</p>
|
||||
<p><span className="font-semibold">Market Cap:</span> ${profile.marketCapitalization.toFixed(2)}B</p>
|
||||
<p className="text-text dark:text-subtext1"><span className="font-semibold text-text dark:text-subtext1">Country:</span> {profile.country}</p>
|
||||
<p className="text-text dark:text-subtext1"><span className="font-semibold text-text dark:text-subtext1">Currency:</span> {profile.currency}</p>
|
||||
<p className="text-text dark:text-subtext1"><span className="font-semibold text-text dark:text-subtext1">Exchange:</span> {profile.exchange}</p>
|
||||
<p className="text-text dark:text-subtext1"><span className="font-semibold text-text dark:text-subtext1">Market Cap:</span> ${profile.marketCapitalization.toFixed(2)}B</p>
|
||||
</div>
|
||||
<div>
|
||||
<p><span className="font-semibold">IPO Date:</span> {new Date(profile.ipo).toLocaleDateString()}</p>
|
||||
<p><span className="font-semibold">Outstanding Shares:</span> {profile.shareOutstanding.toFixed(2)}M</p>
|
||||
<p><span className="font-semibold">Phone:</span> {profile.phone}</p>
|
||||
<p><span className="font-semibold">Website:</span> <a href={profile.weburl} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">{profile.weburl}</a></p>
|
||||
<p className="text-text dark:text-subtext1"><span className="font-semibold text-text dark:text-subtext1">IPO Date:</span> {new Date(profile.ipo).toLocaleDateString()}</p>
|
||||
<p className="text-text dark:text-subtext1"><span className="font-semibold text-text dark:text-subtext1">Outstanding Shares:</span> {profile.shareOutstanding.toFixed(2)}M</p>
|
||||
<p className="text-text dark:text-subtext1"><span className="font-semibold text-text dark:text-subtext1">Phone:</span> {profile.phone}</p>
|
||||
<p className="text-text dark:text-subtext1"><span className="font-semibold text-text dark:text-subtext1">Website:</span> <a href={profile.weburl} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">{profile.weburl}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<PeersWidget symbol={ticker} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,8 +24,8 @@ const NavigationBar = ({ onSelectSymbol }: NavigationBarProps) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<nav className="bg-crust transition-all text-text border-b-2 border-surface0 px-4">
|
||||
<div className="container mx-auto max-w-7xl flex items-center justify-between py-1">
|
||||
<nav className="w-full g-crust transition-all text-text border-b-2 border-surface0 px-4">
|
||||
<div className="container mx-auto max-w-7xl w-full flex items-center justify-between py-1">
|
||||
<Link className="text-lg font-bold flex flex-row align-middle items-center gap-2" href="/"><IoSparkles /> <span className="md:block hidden">twinkle</span>
|
||||
</Link>
|
||||
<div className="flex-grow max-w-md mx-auto lg:max-w-lg w-full">
|
||||
|
|
61
src/components/PeersWidget.tsx
Normal file
61
src/components/PeersWidget.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface PeersWidgetProps {
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
const PeersWidget = ({ symbol }: PeersWidgetProps) => {
|
||||
const [peers, setPeers] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPeers = async () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const res = await fetch(`/api/peers?symbol=${symbol}`);
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
setPeers(data);
|
||||
} else {
|
||||
setError(data.error || 'Failed to fetch data');
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to fetch data');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (symbol) {
|
||||
fetchPeers();
|
||||
}
|
||||
}, [symbol]);
|
||||
|
||||
if (loading) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <p className="text-red-500">{error}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-surface0 dark:bg-surface0 rounded-lg shadow-md mt-4">
|
||||
<h2 className="text-xl font-bold text-text dark:text-text mb-2">Peers for {symbol}</h2>
|
||||
<ul className="list-none">
|
||||
{peers.map((peer, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="inline-block bg-text dark:bg-text text-white text-sm font-medium py-1 px-2 m-1 rounded-md"
|
||||
>
|
||||
{peer}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PeersWidget;
|
106
src/components/RecommendationTrends.tsx
Normal file
106
src/components/RecommendationTrends.tsx
Normal file
|
@ -0,0 +1,106 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
Legend,
|
||||
} from 'recharts';
|
||||
|
||||
interface RecommendationTrendsWidgetProps {
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
interface RecommendationTrend {
|
||||
period: string;
|
||||
strongBuy: number;
|
||||
buy: number;
|
||||
hold: number;
|
||||
sell: number;
|
||||
strongSell: number;
|
||||
}
|
||||
|
||||
const RecommendationTrendsWidget = ({ symbol }: RecommendationTrendsWidgetProps) => {
|
||||
const [recommendationTrends, setRecommendationTrends] = useState<RecommendationTrend[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchRecommendationTrends = async () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const res = await fetch(`/api/recommendation-trends?symbol=${symbol}`);
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
setRecommendationTrends(data);
|
||||
} else {
|
||||
setError(data.error || 'Failed to fetch data');
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to fetch data');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (symbol) {
|
||||
fetchRecommendationTrends();
|
||||
}
|
||||
}, [symbol]);
|
||||
|
||||
if (loading) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <p className="text-red-500">{error}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 bg-surface0 dark:bg-surface0 rounded-lg shadow-md mt-4">
|
||||
<h2 className="text-xl font-bold text-text dark:text-text mb-4">Recommendation Trends for {symbol}</h2>
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart data={recommendationTrends} margin={{ top: 5, right: 20, left: 20, bottom: 5 }}>
|
||||
<defs>
|
||||
<linearGradient id="colorStrongBuy" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#32a852" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="#32a852" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
<linearGradient id="colorBuy" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#7dc968" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="#7dc968" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
<linearGradient id="colorHold" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#d3a637" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="#d3a637" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
<linearGradient id="colorSell" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#e57373" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="#e57373" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
<linearGradient id="colorStrongSell" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#c62828" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="#c62828" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="var(--color-overlay0)" />
|
||||
<XAxis dataKey="period" tick={{ fill: "var(--color-text)" }} />
|
||||
<YAxis tick={{ fill: "var(--color-text)" }} />
|
||||
<Tooltip contentStyle={{ backgroundColor: "var(--color-surface0)", border: "none" }} />
|
||||
<Legend wrapperStyle={{ color: "var(--color-text)" }}/>
|
||||
<Bar dataKey="strongBuy" fill="url(#colorStrongBuy)" />
|
||||
<Bar dataKey="buy" fill="url(#colorBuy)" />
|
||||
<Bar dataKey="hold" fill="url(#colorHold)" />
|
||||
<Bar dataKey="sell" fill="url(#colorSell)" />
|
||||
<Bar dataKey="strongSell" fill="url(#colorStrongSell)" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RecommendationTrendsWidget;
|
|
@ -177,7 +177,7 @@ const SearchBar = ({ onSelectSymbol }: SearchBarProps) => {
|
|||
? "Searching..."
|
||||
: "Start searching"
|
||||
: `Searching...`
|
||||
: `Showing top hits for '${query}'. (Matched ${totalCount} result${totalCount > 1 ? "s" : ""})`}
|
||||
: `Showing top hits for '${query}'. (Matched ${totalCount} result${totalCount > 1 ? "s" : ""}).`}
|
||||
</span>
|
||||
{loading && (
|
||||
<span
|
||||
|
|
28
src/components/StockGraph.tsx
Normal file
28
src/components/StockGraph.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { LineChart, Line, CartesianGrid, Tooltip, ResponsiveContainer, defs, linearGradient, stop } from 'recharts';
|
||||
import { generateMockHistoricalData } from '../utils/mockHistoricalData';
|
||||
|
||||
interface StockGraphProps {
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
const StockGraph = ({ symbol }: StockGraphProps) => {
|
||||
const data = generateMockHistoricalData(symbol);
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={data} margin={{ top: 5, right: 20, left: 10, bottom: 5 }}>
|
||||
<defs>
|
||||
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="var(--color-text)" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="var(--color-text)" stopOpacity={1} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<Tooltip />
|
||||
<Line type="monotone" dataKey="close" stroke="var(--color-text)" strokeWidth={2} dot={false} fill="url(#fillGradient)" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default StockGraph;
|
|
@ -1,4 +1,5 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import StockGraph from './StockGraph';
|
||||
|
||||
interface StockPriceProps {
|
||||
symbol: string;
|
||||
|
@ -63,42 +64,49 @@ const StockPrice = ({ symbol }: StockPriceProps) => {
|
|||
return () => clearInterval(intervalId);
|
||||
}, [symbol]);
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md mt-6">
|
||||
{symbol && (
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold mb-2">{symbol}</h1>
|
||||
{stockDescription && <p className="text-lg mb-4 text-gray-600">{stockDescription}</p>}
|
||||
const PriceBadge = ({ label, value, isPositive = true }: { label: string, value: number | string, isPositive?: boolean }) => (
|
||||
<span className={`inline-block px-2 py-1 text-xs font-medium ${isPositive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'} rounded-md`}>
|
||||
{label} {value}
|
||||
</span>
|
||||
);
|
||||
|
||||
{stockData !== null && (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg shadow-md">
|
||||
<p className="text-xl font-semibold">Current Price:</p>
|
||||
<p className="text-2xl">${stockData.c}</p>
|
||||
return (
|
||||
<div className="relative bg-surface0 dark:bg-surface0 p-6 rounded-t-lg shadow-md">
|
||||
<div className="absolute inset-0 opacity-20">
|
||||
<StockGraph symbol={symbol} />
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg shadow-md">
|
||||
<p className="text-xl font-semibold">Change:</p>
|
||||
<p className="text-2xl">${stockData.d}</p>
|
||||
{symbol && (
|
||||
<div className="relative z-10">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-3xl font-bold text-text dark:text-text">{symbol}</h1>
|
||||
{stockData && (
|
||||
<div className="flex space-x-2">
|
||||
<PriceBadge label="Change:" value={stockData.d} isPositive={stockData.d >= 0} />
|
||||
<PriceBadge label="Percent Change:" value={`${stockData.dp}%`} isPositive={stockData.dp >= 0} />
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg shadow-md">
|
||||
<p className="text-xl font-semibold">Percent Change:</p>
|
||||
<p className="text-2xl">{stockData.dp}%</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg shadow-md">
|
||||
<p className="text-xl font-semibold">High Price of the Day:</p>
|
||||
<p className="text-2xl">${stockData.h}</p>
|
||||
{stockDescription && <p className="text-lg mb-4 text-text dark:text-subtext1">{stockDescription}</p>}
|
||||
{stockData && (
|
||||
<div className="mt-4">
|
||||
<div className="text-5xl font-bold mb-2 text-subtext0 dark:text-subtext0">${stockData.c}</div>
|
||||
<div className="grid grid-cols-4 gap-4 mt-4">
|
||||
<div className="bg-overlay0 dark:bg-overlay0 p-4 rounded-lg shadow-md">
|
||||
<p className="text-sm font-semibold text-text dark:text-subtext1">High</p>
|
||||
<p className="text-xl text-text dark:text-text">${stockData.h}</p>
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg shadow-md">
|
||||
<p className="text-xl font-semibold">Low Price of the Day:</p>
|
||||
<p className="text-2xl">${stockData.l}</p>
|
||||
<div className="bg-overlay0 dark:bg-overlay0 p-4 rounded-lg shadow-md">
|
||||
<p className="text-sm font-semibold text-text dark:text-subtext1">Low</p>
|
||||
<p className="text-xl text-text dark:text-text">${stockData.l}</p>
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg shadow-md">
|
||||
<p className="text-xl font-semibold">Open Price of the Day:</p>
|
||||
<p className="text-2xl">${stockData.o}</p>
|
||||
<div className="bg-overlay0 dark:bg-overlay0 p-4 rounded-lg shadow-md">
|
||||
<p className="text-sm font-semibold text-text dark:text-subtext1">Open</p>
|
||||
<p className="text-xl text-text dark:text-text">${stockData.o}</p>
|
||||
</div>
|
||||
<div className="bg-overlay0 dark:bg-overlay0 p-4 rounded-lg shadow-md">
|
||||
<p className="text-sm font-semibold text-text dark:text-subtext1">Previous Close</p>
|
||||
<p className="text-xl text-text dark:text-text">${stockData.pc}</p>
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg shadow-md">
|
||||
<p className="text-xl font-semibold">Previous Close Price:</p>
|
||||
<p className="text-2xl">${stockData.pc}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -17,7 +17,6 @@ const Ticker = ({ symbol }: { symbol: string }) => {
|
|||
const [webSocketInitialized, setWebSocketInitialized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const initializePrices = async () => {
|
||||
try {
|
||||
const quote = await fetchQuote(symbol);
|
||||
|
@ -57,8 +56,9 @@ const Ticker = ({ symbol }: { symbol: string }) => {
|
|||
|
||||
socket.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'trade') {
|
||||
setLatestTrade(data.data[0]);
|
||||
if (data.type === 'trade' && data.data.some((trade: Trade) => trade.s === symbol)) {
|
||||
const tradeForSymbol = data.data.find((trade: Trade) => trade.s === symbol);
|
||||
setLatestTrade(tradeForSymbol);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -93,10 +93,12 @@ const Ticker = ({ symbol }: { symbol: string }) => {
|
|||
return (
|
||||
<div className="">
|
||||
{symbol && latestTrade && (
|
||||
<div className="border-b dark:bg-overlay0 bg-overlay0 p-2 px-4 rounded-lg">
|
||||
<div className="border-b dark:bg-overlay0 bg-overlay0 p-2 px-4 rounded-b-lg">
|
||||
<div className="flex flex-row space-x-4 ed-lg text-xs">
|
||||
<strong>LIVE ${latestTrade.s}</strong>
|
||||
<div>Traded {latestTrade.v} @ ${latestTrade.p} on {new Date(latestTrade.t).toLocaleTimeString()}</div>
|
||||
<div>Type: {identifyTradeType(latestTrade)}</div>
|
||||
<div>Conditions: {getTradeConditions(latestTrade)}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
18
src/pages/api/peers.ts
Normal file
18
src/pages/api/peers.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { fetchPeers } from '../../utils/sparkle';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { symbol } = req.query;
|
||||
|
||||
if (!symbol || typeof symbol !== 'string') {
|
||||
res.status(400).json({ error: 'Invalid symbol' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await fetchPeers(symbol);
|
||||
res.status(200).json(data);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: "Error fetching peers" });
|
||||
}
|
||||
}
|
18
src/pages/api/recommendation-trends.ts
Normal file
18
src/pages/api/recommendation-trends.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { fetchRecommendationTrends } from '../../utils/sparkle';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { symbol } = req.query;
|
||||
|
||||
if (!symbol || typeof symbol !== 'string') {
|
||||
res.status(400).json({ error: 'Invalid symbol' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await fetchRecommendationTrends(symbol);
|
||||
res.status(200).json(data);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
|
@ -1,28 +1,37 @@
|
|||
import { useState } from 'react'
|
||||
import NavigationBar from '../components/NavigationBar'
|
||||
import StockPrice from '../components/StockPrice'
|
||||
import Ticker from '@/components/Ticker'
|
||||
import NewsColumn from '@/components/NewsColumn'
|
||||
import CompanyProfileCard from '@/components/CompanyProfileCard'
|
||||
import { useState } from 'react';
|
||||
import NavigationBar from '../components/NavigationBar';
|
||||
import StockPrice from '../components/StockPrice';
|
||||
import Ticker from '../components/Ticker';
|
||||
import NewsColumn from '../components/NewsColumn';
|
||||
import CompanyProfileCard from '../components/CompanyProfileCard';
|
||||
import PeersWidget from '../components/PeersWidget';
|
||||
import RecommendationTrendsWidget from '@/components/RecommendationTrends';
|
||||
|
||||
export default function Home() {
|
||||
const [symbol, setSymbol] = useState('')
|
||||
const [symbol, setSymbol] = useState('');
|
||||
|
||||
const handleSelectSymbol = (selectedSymbol: string) => {
|
||||
setSymbol(selectedSymbol)
|
||||
}
|
||||
setSymbol(selectedSymbol);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col items-center w-full">
|
||||
<NavigationBar onSelectSymbol={handleSelectSymbol} />
|
||||
<div className="container flex flex-row mx-auto max-w-7xl">
|
||||
<div>
|
||||
<Ticker symbol={symbol} />
|
||||
<CompanyProfileCard ticker={symbol} />
|
||||
<div className="gap-8 flex-row flex max-w-7xl w-full p-4">
|
||||
{symbol && (
|
||||
<>
|
||||
<div className="flex flex-col w-full">
|
||||
<StockPrice symbol={symbol} />
|
||||
<NewsColumn />
|
||||
<Ticker symbol={symbol} />
|
||||
<RecommendationTrendsWidget symbol={symbol} /> \
|
||||
</div>
|
||||
<div className="flex flex-col max-w-md">
|
||||
<CompanyProfileCard ticker={symbol} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{/* <NewsColumn /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
16
src/utils/mockhistoricalData.ts
Normal file
16
src/utils/mockhistoricalData.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
export const generateMockHistoricalData = (symbol: string) => {
|
||||
// Mock data for the past 10 days
|
||||
const mockData = [];
|
||||
const now = new Date();
|
||||
|
||||
for (let i = 10; i >= 0; i--) {
|
||||
const date = new Date(now);
|
||||
date.setDate(now.getDate() - i);
|
||||
mockData.push({
|
||||
date: date.toISOString().split('T')[0],
|
||||
close: (Math.random() * 100 + 100).toFixed(2), // Generate a random closing price
|
||||
});
|
||||
}
|
||||
|
||||
return mockData;
|
||||
};
|
|
@ -47,3 +47,9 @@ export const fetchPeers = async (symbol: string) => {
|
|||
const res = await fetch(url);
|
||||
return handleResponse(res);
|
||||
};
|
||||
|
||||
export const fetchRecommendationTrends = async (symbol: string) => {
|
||||
const url = `${SPARKLE_BASE_URL}/api/v1/recommendation-trends?symbol=${symbol}`;
|
||||
const res = await fetch(url);
|
||||
return handleResponse(res);
|
||||
};
|
Loading…
Reference in a new issue