Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { SuccessPage } from '@/components/SuccessPage';
import { VerifyEmail } from '@/components/VerifyEmail';
import { AuthCallback } from '@/components/AuthCallback';
import { DashboardPage } from '@/features/dashboard/DashboardPage';
import { LiveMarketDashboard } from '@/pages/LiveMarketDashboard';
import { AuthGuard } from '@/features/auth/AuthGuard';
import { useUserStore } from '@/stores/userStore';
import { ROUTES } from '@/constants/routes.constants';
Expand Down Expand Up @@ -97,7 +98,7 @@ function AppContent() {
path={ROUTES.LOGIN}
element={
<Login
onLoginSuccess={() => navigate(ROUTES.DASHBOARD)}
onLoginSuccess={() => navigate(ROUTES.EXPLORE)}
onNavigateToSignup={() => navigate(ROUTES.REGISTER)}
onNavigateToForgot={() => navigate(ROUTES.FORGOT_PASSWORD)}
/>
Expand Down Expand Up @@ -131,6 +132,7 @@ function AppContent() {
path={ROUTES.RESET_PASSWORD}
element={<ResetPassword onBackToLogin={() => navigate(ROUTES.LOGIN)} />}
/>
<Route path={ROUTES.EXPLORE} element={<LiveMarketDashboard />} />

{/* Protected Routes */}
<Route
Expand Down
4 changes: 2 additions & 2 deletions src/components/AuthCallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export const AuthCallback: React.FC = () => {
useEffect(() => {
if (isAuthenticated) {
// Clean up fragment from url and navigate to dashboard
window.history.replaceState(null, '', '/#/dashboard');
navigate(ROUTES.DASHBOARD);
window.history.replaceState(null, '', '/#/explore');
navigate(ROUTES.EXPLORE);
} else {
navigate(ROUTES.LOGIN);
}
Expand Down
1 change: 1 addition & 0 deletions src/constants/routes.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const ROUTES = {
VERIFY_EMAIL: '/verify-email',
AUTH_CALLBACK: '/auth/callback',
SUCCESS: '/success',
EXPLORE: '/explore',
DASHBOARD: '/dashboard',
} as const;

Expand Down
15 changes: 10 additions & 5 deletions src/features/globe/GlobeView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useRef } from 'react';
import { useGlobe } from './useGlobe';
import { NATIONS } from './nations.constants';

Check warning on line 3 in src/features/globe/GlobeView.tsx

View workflow job for this annotation

GitHub Actions / Type-check · Lint · Format · Test · Build

eslint(no-unused-vars)

Identifier 'NATIONS' is imported but never used.
import { useNavigate } from 'react-router-dom';
import { ROUTES } from '@/constants/routes.constants';
import '@/styles/components/overlay.css';
import '@/styles/components/country-list.css';

Expand All @@ -16,6 +18,7 @@
*/
export const GlobeView: React.FC<GlobeViewProps> = ({ onEnterApp }) => {
const containerRef = useRef<HTMLDivElement | null>(null);
const navigate = useNavigate();
useGlobe(containerRef);

return (
Expand All @@ -28,7 +31,7 @@
<div className="shooting-star comet-3"></div>
<div className="shooting-star comet-4"></div>
<div className="shooting-star comet-5"></div>

{/* Violet Comets */}
<div className="shooting-star violet-comet comet-violet-1"></div>
<div className="shooting-star violet-comet comet-violet-2"></div>
Expand All @@ -47,7 +50,7 @@
<div className="star star-8"></div>
<div className="star star-9"></div>
<div className="star star-10"></div>

{/* Big stars */}
<div className="big-star big-star-1"></div>
<div className="big-star big-star-2"></div>
Expand All @@ -70,7 +73,7 @@
<span className="logo-icon">x</span>
<span className="brand-text">Discipline Co-pilot</span>
</div>

<div className="nav-search">
<span className="search-icon">🔍</span>
<input type="text" placeholder="Search (Ctrl+K)" />
Expand All @@ -92,7 +95,7 @@
{/* Hero Content */}
<div className="globe-hero">
<h1>Look First Then Leap</h1>

<div className="billing-toggle">
<label className="radio-label">
<input type="radio" name="billing" />
Expand All @@ -106,13 +109,15 @@
</label>
<span className="discount-badge">Save up to 17% 🤑</span>
</div>

<button className="terminal-enter-btn" style={{ marginTop: '32px', padding: '16px 48px', fontSize: '1.1rem', letterSpacing: '2px' }} onClick={() => navigate(ROUTES.EXPLORE)}>Explore</button>
</div>
</section>

{/* ── Section 2: Globe (100vh) ─────────────────────────────────── */}
<section className="globe-section">
<div className="globe-glow"></div>

<div className="globe-wrapper">
<div
ref={containerRef}
Expand Down
131 changes: 131 additions & 0 deletions src/pages/LiveMarketDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import React, { useState, useEffect } from 'react';
import '@/styles/components/live-market.css';

export const LiveMarketDashboard: React.FC = () => {
const [marketData, setMarketData] = useState({ gainers: [], losers: [] });
const [activeTab, setActiveTab] = useState('Stocks');

useEffect(() => {
// 1. Connect to the FastAPI WebSocket Bridge
const ws = new WebSocket("ws://localhost:8000/dashboard/ws/market");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Avoid hardcoding localhost URLs.

The WebSocket connection uses a hardcoded localhost URL, which will fail when the application is deployed to production or accessed from another device. Consider deriving the WebSocket URL from the current window location or using an environment variable.

🛠 Proposed fix

For example, using Vite environment variables:

-        const ws = new WebSocket("ws://localhost:8000/dashboard/ws/market");
+        // Fallback to localhost if the env variable is not set
+        const wsUrl = import.meta.env.VITE_WS_URL || "ws://localhost:8000";
+        const ws = new WebSocket(`${wsUrl}/dashboard/ws/market`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ws = new WebSocket("ws://localhost:8000/dashboard/ws/market");
// Fallback to localhost if the env variable is not set
const wsUrl = import.meta.env.VITE_WS_URL || "ws://localhost:8000";
const ws = new WebSocket(`${wsUrl}/dashboard/ws/market`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/LiveMarketDashboard.tsx` at line 10, Update the WebSocket
initialization in LiveMarketDashboard to derive the endpoint from the current
host/protocol or an approved environment variable instead of hardcoding
localhost, while preserving the existing dashboard WebSocket path and using the
appropriate ws/wss protocol for the deployment context.


// 2. Listen for live updates being pushed from FastAPI
ws.onmessage = (event) => {
try {
const liveData = JSON.parse(event.data);
console.log("Live Update Received:", liveData);
// Instantly update the React state
setMarketData(liveData);
} catch (err) {
console.error("Error parsing websocket message", err);
}
};

// 3. Clean up the connection if the user leaves the page
return () => ws.close();
}, []);

const formatPrice = (price: number) => {
if (!price) return "0.00";
if (price < 0.01) {
return price.toExponential(5);
}
return price.toFixed(2);
};

const getInitials = (symbol: string) => {
return symbol.substring(0, 2).toUpperCase();
};

return (
<div className="market-dashboard">
<div className="top-nav">
<div className="nav-pills">
{['Stocks', 'Crypto', 'Futures', 'Forex', 'Economy', 'Brokers'].map(tab => (
<button
key={tab}
className={`nav-pill ${activeTab === tab ? 'active' : ''}`}
onClick={() => setActiveTab(tab)}
>
{tab}
</button>
))}
</div>
</div>

<div className="market-sections">
{/* Gainers */}
<div className="market-list-section">
<div className="section-header">
<h2>
{activeTab === 'Stocks' ? 'Stock' : activeTab} gainers
<span className="heading-arrow">&gt;</span>
</h2>
</div>

<div className="stock-list">
{marketData.gainers && marketData.gainers.length > 0 ? (
marketData.gainers.map((stock: any) => (
<div className="stock-row" key={stock.symbol}>
<div className="stock-left">
<div className="stock-icon icon-blue">{getInitials(stock.symbol)}</div>
<div className="stock-names">
<div className="stock-name">{stock.symbol.split('.')[0]}</div>
<div className="stock-ticker">{stock.symbol}</div>
</div>
</div>
<div className="stock-middle">
<span className="price">{formatPrice(stock.price)}</span>
<span className="currency">{stock.currency || 'USD'}</span>
</div>
<div className="stock-right">
<div className="pill-gain">+{Math.abs(stock.percent_change).toFixed(2)}%</div>
</div>
</div>
))
) : (
<div className="loading-state">Waiting for data...</div>
)}
</div>
</div>

{/* Losers */}
<div className="market-list-section">
<div className="section-header">
<h2>
{activeTab === 'Stocks' ? 'Stock' : activeTab} losers
<span className="heading-arrow">&gt;</span>
</h2>
</div>

<div className="stock-list">
{marketData.losers && marketData.losers.length > 0 ? (
marketData.losers.map((stock: any) => (
<div className="stock-row" key={stock.symbol}>
<div className="stock-left">
<div className="stock-icon icon-purple">{getInitials(stock.symbol)}</div>
<div className="stock-names">
<div className="stock-name">{stock.symbol.split('.')[0]}</div>
<div className="stock-ticker">{stock.symbol}</div>
</div>
</div>
<div className="stock-middle">
<span className="price">{formatPrice(stock.price)}</span>
<span className="currency">{stock.currency || 'USD'}</span>
</div>
<div className="stock-right">
<div className="pill-loss">
-{Math.abs(stock.percent_change).toFixed(2)}%
</div>
</div>
</div>
))
) : (
<div className="loading-state">Waiting for data...</div>
)}
</div>
</div>
</div>
</div>
);
};
Loading
Loading