-
Financial News
-
Stay updated with the latest market news and analysis
+
+
+
Financial News
+
Live market updates and portfolio insights
+
+
+
+
+
+
+
+
+
+
-
-
- {categories.map(category => (
-
- ))}
-
{isLoading ? (
-
Loading latest news...
+
Loading {activeTab} news...
) : (
- {filteredArticles.map((article, index) => (
-
-
+ {articles.length > 0 ? (
+ articles.map((article, index) => (
+
+ ))
+ ) : (
+
+ {activeTab === 'portfolio' && holdings.length === 0 ? (
+
Add stocks to your portfolio to see personalized news.
+ ) : (
+
No articles found.
+ )}
- ))}
-
- )}
-
- {!isLoading && filteredArticles.length === 0 && (
-
-
No articles found matching your criteria.
-
+ )}
)}
+
+ {isSubscriptionModalOpen && (
+
setIsSubscriptionModalOpen(false)} />
+ )}
);
};
diff --git a/frontend/src/pages/Portfolio/PortfolioPage.css b/frontend/src/pages/Portfolio/PortfolioPage.css
index ac8ca85..10326a9 100644
--- a/frontend/src/pages/Portfolio/PortfolioPage.css
+++ b/frontend/src/pages/Portfolio/PortfolioPage.css
@@ -15,13 +15,10 @@
}
.portfolio-title {
- font-size: 1.5rem;
+ font-size: 2rem;
font-weight: 700;
+ color: #00e6b8;
margin: 0;
- background: linear-gradient(135deg, #00d4aa, #0099cc);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
}
.portfolio-subtitle {
@@ -71,7 +68,7 @@
}
.summary-value.positive {
- color: #00d4aa;
+ color: #00e6b8;
}
.summary-value.negative {
@@ -84,7 +81,7 @@
}
.summary-change.positive {
- color: #00d4aa;
+ color: #00e6b8;
}
.summary-change.negative {
@@ -173,7 +170,7 @@
}
.highlight-content .value.positive {
- color: #00d4aa;
+ color: #00e6b8;
}
.highlight-content .value.negative {
@@ -221,7 +218,7 @@
.updating-indicator {
font-size: 0.8rem;
- color: #00d4aa;
+ color: #00e6b8;
margin-right: 8px;
animation: pulse 1.5s infinite;
}
@@ -288,7 +285,7 @@
}
.gain-loss.positive {
- color: #00d4aa;
+ color: #00e6b8;
}
.gain-loss.negative {
@@ -301,7 +298,7 @@
}
.gain-percent.positive {
- color: #00d4aa;
+ color: #00e6b8;
}
.gain-percent.negative {
@@ -427,7 +424,7 @@
.form-group input:focus {
outline: none;
- border-color: #00d4aa;
+ border-color: #00e6b8;
}
.search-dropdown-pf {
@@ -463,11 +460,26 @@
.item-symbol {
font-weight: 700;
- color: #00d4aa;
+ color: #00e6b8;
margin-right: 8px;
min-width: 50px;
}
+.item-price {
+ font-weight: 600;
+ margin-right: 8px;
+ min-width: 60px;
+ text-align: right;
+}
+
+.item-price.positive {
+ color: #00e6b8;
+}
+
+.item-price.negative {
+ color: #ff4757;
+}
+
.item-name {
flex: 1;
color: #fff;
@@ -483,8 +495,8 @@
}
.selected-preview {
- background: rgba(0, 212, 170, 0.1);
- border: 1px solid #00d4aa;
+ background: rgba(0, 230, 184, 0.1);
+ border: 1px solid #00e6b8;
padding: 12px;
border-radius: 6px;
margin-bottom: 20px;
@@ -514,8 +526,8 @@
.loading-bar {
text-align: center;
padding: 8px;
- background: rgba(0, 212, 170, 0.1);
- color: #00d4aa;
+ background: rgba(0, 230, 184, 0.1);
+ color: #00e6b8;
font-size: 0.9rem;
margin-bottom: 20px;
border-radius: 4px;
diff --git a/frontend/src/pages/Portfolio/PortfolioPage.tsx b/frontend/src/pages/Portfolio/PortfolioPage.tsx
index 7de89cf..7225d34 100644
--- a/frontend/src/pages/Portfolio/PortfolioPage.tsx
+++ b/frontend/src/pages/Portfolio/PortfolioPage.tsx
@@ -299,6 +299,11 @@ const PortfolioPage: React.FC = () => {
handleSelectStock(result)}>
{result.symbol}
{result.shortname || result.longname}
+ {result.price !== undefined && (
+ = 0 ? 'positive' : 'negative'}`}>
+ ${result.price.toFixed(2)}
+
+ )}
{result.exchange}
))}
diff --git a/frontend/src/pages/Profile/ProfilePage.css b/frontend/src/pages/Profile/ProfilePage.css
index 7751bd3..54dacd6 100644
--- a/frontend/src/pages/Profile/ProfilePage.css
+++ b/frontend/src/pages/Profile/ProfilePage.css
@@ -32,10 +32,9 @@
}
.gradient-text {
- font-size: 2.5rem;
+ font-size: 2rem;
font-weight: 700;
- color: #fff;
- /* Minimalist white instead of gradient */
+ color: #00e6b8;
margin-bottom: 8px;
}
@@ -51,8 +50,8 @@
width: 100%;
/* width: 100%; already there, just removing max-width */
/* max-width: 1000px; REMOVED */
- background: #111111;
- /* Near pitch black for the card */
+ background: #1a1a1a;
+ /* Standard card background */
backdrop-filter: none;
/* No blur */
border: 1px solid #333;
@@ -87,7 +86,7 @@
display: flex;
align-items: center;
justify-content: center;
- color: #00d4aa;
+ color: #00e6b8;
font-size: 2.5rem;
font-weight: bold;
border: none;
@@ -114,7 +113,7 @@
}
.subscription-badge.premium {
- background: #00d4aa;
+ background: #00e6b8;
color: #000;
border: none;
}
@@ -235,7 +234,7 @@
}
.glass-input:focus {
- border-color: #00d4aa;
+ border-color: #00e6b8;
box-shadow: none;
}
@@ -265,7 +264,7 @@
.upgrade-link {
background: none;
border: none;
- color: #00d4aa;
+ color: #00e6b8;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
@@ -300,7 +299,7 @@
}
.action-btn.save-btn {
- background: #00d4aa;
+ background: #00e6b8;
color: #000;
}
@@ -374,7 +373,7 @@
}
.tab-btn.active {
- color: #00d4aa;
+ color: #00e6b8;
}
.tab-btn.active::after {
@@ -384,7 +383,7 @@
left: 0;
width: 100%;
height: 2px;
- background: #00d4aa;
+ background: #00e6b8;
}
/* History Table */
@@ -421,8 +420,8 @@
}
.status-badge.success {
- background: rgba(0, 212, 170, 0.1);
- color: #00d4aa;
+ background: rgba(0, 230, 184, 0.1);
+ color: #00e6b8;
}
.download-btn {
diff --git a/frontend/src/services/newsService.ts b/frontend/src/services/newsService.ts
new file mode 100644
index 0000000..b62401a
--- /dev/null
+++ b/frontend/src/services/newsService.ts
@@ -0,0 +1,61 @@
+
+import axios from 'axios';
+
+const LIVE_API_URL = 'http://localhost:3001/api';
+
+export interface NewsArticle {
+ id: string;
+ title: string;
+ description: string;
+ source: string;
+ timestamp: number; // Unix timestamp
+ url: string;
+ imageUrl?: string;
+ stockTicker?: string;
+}
+
+export const newsService = {
+ // Fetch general, symbol-specific, or search query news
+ getNews: async (input: string[] | string): Promise
=> {
+ try {
+ let url = `${LIVE_API_URL}/yahoo/news`;
+
+ if (Array.isArray(input)) {
+ // It's a list of symbols
+ const query = input.length > 0 ? input.join(',') : 'market';
+ url += `?symbols=${query}`;
+ } else {
+ // It's a search term
+ url += `?q=${encodeURIComponent(input)}`;
+ }
+
+ const response = await axios.get(url);
+ return response.data;
+ } catch (error) {
+ console.error("News Service Error:", error);
+ return [];
+ }
+ },
+
+ // Subscribe to email alerts
+ subscribe: async (email: string, symbols: string[]) => {
+ try {
+ const response = await axios.post(`${LIVE_API_URL}/subscribe`, { email, symbols });
+ return response.data;
+ } catch (error) {
+ console.error("Subscription Error:", error);
+ throw error;
+ }
+ },
+
+ // Unsubscribe
+ unsubscribe: async (email: string) => {
+ try {
+ const response = await axios.post(`${LIVE_API_URL}/unsubscribe`, { email });
+ return response.data;
+ } catch (error) {
+ console.error("Unsubscribe Error:", error);
+ throw error;
+ }
+ }
+};
diff --git a/frontend/src/services/yahooFinance.ts b/frontend/src/services/yahooFinance.ts
index 1a99d8c..80ccf5f 100644
--- a/frontend/src/services/yahooFinance.ts
+++ b/frontend/src/services/yahooFinance.ts
@@ -8,6 +8,9 @@ export interface YahooSearchResult {
longname: string;
exchange: string;
quoteType: string;
+ price?: number;
+ change?: number;
+ changePercent?: number;
}
export interface YahooQuote {
@@ -30,15 +33,34 @@ export interface YahooHistoryCandle {
export const yahooFinanceService = {
/**
- * Search for stocks using Yahoo Finance API
+ * Search for stocks using Yahoo Finance API and fetch current prices
*/
searchStocks: async (query: string): Promise => {
try {
const response = await axios.get(`${API_BASE_URL}/search`, {
params: { q: query }
});
- // The Yahoo API structure for search returns data.quotes
- return response.data.quotes || [];
+ // Backend returns { results: [...] }
+ let results = response.data.results || [];
+
+ // Enhance with prices
+ if (results.length > 0) {
+ const symbols = results.map((r: any) => r.symbol);
+ const quotes = await yahooFinanceService.getMultipleQuotes(symbols);
+
+ results = results.map((r: any) => {
+ const quote = quotes.find(q => q.symbol === r.symbol);
+ return {
+ ...r,
+ price: quote ? quote.price : null,
+ change: quote ? (quote.price - quote.previousClose) : null,
+ changePercent: quote ? ((quote.price - quote.previousClose) / quote.previousClose) * 100 : null
+
+ };
+ });
+ }
+
+ return results;
} catch (error) {
console.error('Error searching stocks:', error);
return [];
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts
index c9c5656..89f780c 100644
--- a/frontend/src/types/index.ts
+++ b/frontend/src/types/index.ts
@@ -29,6 +29,8 @@ export interface Stock {
price: number;
change: number;
changePercent: number;
+ exch?: string;
+ type?: string;
}
export interface StockTicker {