feat: resolve blank screen, add live github stars badge, and conditional dashboard navbar navigation
Browse files- frontend/src/App.tsx +7 -1
- frontend/src/components/Navbar.tsx +6 -2
- frontend/src/pages/Dashboard.tsx +3 -0
- frontend/src/pages/LandingPage.tsx +103 -16
frontend/src/App.tsx
CHANGED
|
@@ -44,6 +44,7 @@ export default function App() {
|
|
| 44 |
// Auth State
|
| 45 |
const [token, setToken] = useState<string | null>(localStorage.getItem('quantiq_jwt'));
|
| 46 |
const [user, setUser] = useState<any>(null);
|
|
|
|
| 47 |
|
| 48 |
// Dashboard Core State
|
| 49 |
const [watchlist, setWatchlist] = useState<string[]>(['AAPL', 'TSLA', 'TCS.NS', 'RELIANCE.NS']);
|
|
@@ -238,6 +239,7 @@ export default function App() {
|
|
| 238 |
const handleAuthSuccess = (accessToken: string) => {
|
| 239 |
localStorage.setItem('quantiq_jwt', accessToken);
|
| 240 |
setToken(accessToken);
|
|
|
|
| 241 |
};
|
| 242 |
|
| 243 |
const handleGoogleCredentialResponse = async (response: any) => {
|
|
@@ -264,6 +266,7 @@ export default function App() {
|
|
| 264 |
setToken(null);
|
| 265 |
setUser(null);
|
| 266 |
setInsight(null);
|
|
|
|
| 267 |
};
|
| 268 |
|
| 269 |
// 6. Watchlist Operations
|
|
@@ -419,13 +422,15 @@ export default function App() {
|
|
| 419 |
};
|
| 420 |
|
| 421 |
// Auth Router rendering
|
| 422 |
-
if (!token) {
|
| 423 |
return (
|
| 424 |
<LandingPage
|
| 425 |
onGoogleLogin={handleGoogleCredentialResponse}
|
| 426 |
googleClientId={GOOGLE_CLIENT_ID}
|
| 427 |
onAuthSuccess={handleAuthSuccess}
|
| 428 |
apiUrl={API_URL}
|
|
|
|
|
|
|
| 429 |
/>
|
| 430 |
);
|
| 431 |
}
|
|
@@ -451,6 +456,7 @@ export default function App() {
|
|
| 451 |
onOpenRecharge={() => setShowRecharge(true)}
|
| 452 |
onSelectPackage={handlePayment}
|
| 453 |
onLogout={handleLogout}
|
|
|
|
| 454 |
/>
|
| 455 |
);
|
| 456 |
}
|
|
|
|
| 44 |
// Auth State
|
| 45 |
const [token, setToken] = useState<string | null>(localStorage.getItem('quantiq_jwt'));
|
| 46 |
const [user, setUser] = useState<any>(null);
|
| 47 |
+
const [currentView, setCurrentView] = useState<'landing' | 'dashboard'>(localStorage.getItem('quantiq_jwt') ? 'dashboard' : 'landing');
|
| 48 |
|
| 49 |
// Dashboard Core State
|
| 50 |
const [watchlist, setWatchlist] = useState<string[]>(['AAPL', 'TSLA', 'TCS.NS', 'RELIANCE.NS']);
|
|
|
|
| 239 |
const handleAuthSuccess = (accessToken: string) => {
|
| 240 |
localStorage.setItem('quantiq_jwt', accessToken);
|
| 241 |
setToken(accessToken);
|
| 242 |
+
setCurrentView('dashboard');
|
| 243 |
};
|
| 244 |
|
| 245 |
const handleGoogleCredentialResponse = async (response: any) => {
|
|
|
|
| 266 |
setToken(null);
|
| 267 |
setUser(null);
|
| 268 |
setInsight(null);
|
| 269 |
+
setCurrentView('landing');
|
| 270 |
};
|
| 271 |
|
| 272 |
// 6. Watchlist Operations
|
|
|
|
| 422 |
};
|
| 423 |
|
| 424 |
// Auth Router rendering
|
| 425 |
+
if (!token || currentView === 'landing') {
|
| 426 |
return (
|
| 427 |
<LandingPage
|
| 428 |
onGoogleLogin={handleGoogleCredentialResponse}
|
| 429 |
googleClientId={GOOGLE_CLIENT_ID}
|
| 430 |
onAuthSuccess={handleAuthSuccess}
|
| 431 |
apiUrl={API_URL}
|
| 432 |
+
user={user}
|
| 433 |
+
onGoToDashboard={() => setCurrentView('dashboard')}
|
| 434 |
/>
|
| 435 |
);
|
| 436 |
}
|
|
|
|
| 456 |
onOpenRecharge={() => setShowRecharge(true)}
|
| 457 |
onSelectPackage={handlePayment}
|
| 458 |
onLogout={handleLogout}
|
| 459 |
+
onLogoClick={() => setCurrentView('landing')}
|
| 460 |
/>
|
| 461 |
);
|
| 462 |
}
|
frontend/src/components/Navbar.tsx
CHANGED
|
@@ -6,12 +6,16 @@ interface NavbarProps {
|
|
| 6 |
user: any;
|
| 7 |
onRechargeClick: () => void;
|
| 8 |
onLogout: () => void;
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
-
export default function Navbar({ user, onRechargeClick, onLogout }: NavbarProps) {
|
| 12 |
return (
|
| 13 |
<header className="dashboard-header">
|
| 14 |
-
<div
|
|
|
|
|
|
|
|
|
|
| 15 |
<Logo size={32} />
|
| 16 |
<span className="logo-text">QuantIQ</span>
|
| 17 |
</div>
|
|
|
|
| 6 |
user: any;
|
| 7 |
onRechargeClick: () => void;
|
| 8 |
onLogout: () => void;
|
| 9 |
+
onLogoClick?: () => void;
|
| 10 |
}
|
| 11 |
|
| 12 |
+
export default function Navbar({ user, onRechargeClick, onLogout, onLogoClick }: NavbarProps) {
|
| 13 |
return (
|
| 14 |
<header className="dashboard-header">
|
| 15 |
+
<div
|
| 16 |
+
className={`header-left ${onLogoClick ? 'cursor-pointer hover:opacity-85 active:scale-[0.98]' : ''} transition-all`}
|
| 17 |
+
onClick={onLogoClick}
|
| 18 |
+
>
|
| 19 |
<Logo size={32} />
|
| 20 |
<span className="logo-text">QuantIQ</span>
|
| 21 |
</div>
|
frontend/src/pages/Dashboard.tsx
CHANGED
|
@@ -26,6 +26,7 @@ interface DashboardProps {
|
|
| 26 |
onOpenRecharge: () => void;
|
| 27 |
onSelectPackage: (amountInRupees: number) => Promise<void>;
|
| 28 |
onLogout: () => void;
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
export default function Dashboard({
|
|
@@ -48,6 +49,7 @@ export default function Dashboard({
|
|
| 48 |
onOpenRecharge,
|
| 49 |
onSelectPackage,
|
| 50 |
onLogout,
|
|
|
|
| 51 |
}: DashboardProps) {
|
| 52 |
return (
|
| 53 |
<div className="app-container animate-fade">
|
|
@@ -56,6 +58,7 @@ export default function Dashboard({
|
|
| 56 |
user={user}
|
| 57 |
onRechargeClick={onOpenRecharge}
|
| 58 |
onLogout={onLogout}
|
|
|
|
| 59 |
/>
|
| 60 |
|
| 61 |
{/* Main Grid Content */}
|
|
|
|
| 26 |
onOpenRecharge: () => void;
|
| 27 |
onSelectPackage: (amountInRupees: number) => Promise<void>;
|
| 28 |
onLogout: () => void;
|
| 29 |
+
onLogoClick?: () => void;
|
| 30 |
}
|
| 31 |
|
| 32 |
export default function Dashboard({
|
|
|
|
| 49 |
onOpenRecharge,
|
| 50 |
onSelectPackage,
|
| 51 |
onLogout,
|
| 52 |
+
onLogoClick,
|
| 53 |
}: DashboardProps) {
|
| 54 |
return (
|
| 55 |
<div className="app-container animate-fade">
|
|
|
|
| 58 |
user={user}
|
| 59 |
onRechargeClick={onOpenRecharge}
|
| 60 |
onLogout={onLogout}
|
| 61 |
+
onLogoClick={onLogoClick}
|
| 62 |
/>
|
| 63 |
|
| 64 |
{/* Main Grid Content */}
|
frontend/src/pages/LandingPage.tsx
CHANGED
|
@@ -1,18 +1,41 @@
|
|
| 1 |
-
import { useEffect, useRef
|
| 2 |
-
import Logo from '../components/Logo';
|
| 3 |
import { Button } from '../components/ui/button';
|
| 4 |
import { X, ArrowRight, Eye, EyeOff, Lock, Mail, User, Globe } from 'lucide-react';
|
|
|
|
|
|
|
| 5 |
|
| 6 |
interface LandingPageProps {
|
| 7 |
onGoogleLogin: (response: any) => Promise<void>;
|
| 8 |
googleClientId: string;
|
| 9 |
onAuthSuccess: (token: string) => void;
|
| 10 |
apiUrl: string;
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
|
| 13 |
type AuthMode = 'signin' | 'signup';
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
const [showAuthModal, setShowAuthModal] = useState(false);
|
| 17 |
const [showReachUs, setShowReachUs] = useState(false);
|
| 18 |
const [authMode, setAuthMode] = useState<AuthMode>('signin');
|
|
@@ -30,9 +53,33 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
|
|
| 30 |
const [loading, setLoading] = useState(false);
|
| 31 |
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
| 32 |
const [successMsg, setSuccessMsg] = useState<string | null>(null);
|
|
|
|
| 33 |
|
| 34 |
const googleButtonRef = useRef<HTMLDivElement>(null);
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
useEffect(() => {
|
| 37 |
// Append Google Identity Services SDK script
|
| 38 |
const script = document.createElement('script');
|
|
@@ -260,13 +307,43 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
|
|
| 260 |
</div>
|
| 261 |
</nav>
|
| 262 |
|
| 263 |
-
{/* CTA Trigger */}
|
| 264 |
-
<
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
</div>
|
| 271 |
</header>
|
| 272 |
|
|
@@ -292,13 +369,23 @@ export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSucce
|
|
| 292 |
</p>
|
| 293 |
|
| 294 |
{/* Large Hero CTA */}
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
</div>
|
|
|
|
| 302 |
</section>
|
| 303 |
|
| 304 |
{/* THE QUANTIQ STACK INFO SECTION */}
|
|
|
|
| 1 |
+
import { useState, useEffect, useRef } from 'react';
|
|
|
|
| 2 |
import { Button } from '../components/ui/button';
|
| 3 |
import { X, ArrowRight, Eye, EyeOff, Lock, Mail, User, Globe } from 'lucide-react';
|
| 4 |
+
import Logo from '../components/Logo';
|
| 5 |
+
|
| 6 |
|
| 7 |
interface LandingPageProps {
|
| 8 |
onGoogleLogin: (response: any) => Promise<void>;
|
| 9 |
googleClientId: string;
|
| 10 |
onAuthSuccess: (token: string) => void;
|
| 11 |
apiUrl: string;
|
| 12 |
+
user?: any;
|
| 13 |
+
onGoToDashboard?: () => void;
|
| 14 |
}
|
| 15 |
|
| 16 |
type AuthMode = 'signin' | 'signup';
|
| 17 |
|
| 18 |
+
function GithubIcon({ size = 14, className = "" }) {
|
| 19 |
+
return (
|
| 20 |
+
<svg
|
| 21 |
+
xmlns="http://www.w3.org/2000/svg"
|
| 22 |
+
width={size}
|
| 23 |
+
height={size}
|
| 24 |
+
viewBox="0 0 24 24"
|
| 25 |
+
fill="none"
|
| 26 |
+
stroke="currentColor"
|
| 27 |
+
strokeWidth="2"
|
| 28 |
+
strokeLinecap="round"
|
| 29 |
+
strokeLinejoin="round"
|
| 30 |
+
className={className}
|
| 31 |
+
>
|
| 32 |
+
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
| 33 |
+
<path d="M9 18c-4.51 2-5-2-7-2" />
|
| 34 |
+
</svg>
|
| 35 |
+
);
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
export default function LandingPage({ onGoogleLogin, googleClientId, onAuthSuccess, apiUrl, user, onGoToDashboard }: LandingPageProps) {
|
| 39 |
const [showAuthModal, setShowAuthModal] = useState(false);
|
| 40 |
const [showReachUs, setShowReachUs] = useState(false);
|
| 41 |
const [authMode, setAuthMode] = useState<AuthMode>('signin');
|
|
|
|
| 53 |
const [loading, setLoading] = useState(false);
|
| 54 |
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
| 55 |
const [successMsg, setSuccessMsg] = useState<string | null>(null);
|
| 56 |
+
const [githubStars, setGithubStars] = useState<string | null>(null);
|
| 57 |
|
| 58 |
const googleButtonRef = useRef<HTMLDivElement>(null);
|
| 59 |
|
| 60 |
+
// Fetch actual GitHub stargazers count for the repository
|
| 61 |
+
useEffect(() => {
|
| 62 |
+
fetch('https://api.github.com/repos/Edge-Explorer/QuantIQ')
|
| 63 |
+
.then(res => {
|
| 64 |
+
if (!res.ok) throw new Error();
|
| 65 |
+
return res.json();
|
| 66 |
+
})
|
| 67 |
+
.then(data => {
|
| 68 |
+
const count = data.stargazers_count;
|
| 69 |
+
if (typeof count === 'number') {
|
| 70 |
+
if (count >= 1000) {
|
| 71 |
+
setGithubStars(`${(count / 1000).toFixed(1)}K`);
|
| 72 |
+
} else {
|
| 73 |
+
setGithubStars(count.toString());
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
})
|
| 77 |
+
.catch(() => {
|
| 78 |
+
// Fallback default
|
| 79 |
+
setGithubStars('Star');
|
| 80 |
+
});
|
| 81 |
+
}, []);
|
| 82 |
+
|
| 83 |
useEffect(() => {
|
| 84 |
// Append Google Identity Services SDK script
|
| 85 |
const script = document.createElement('script');
|
|
|
|
| 307 |
</div>
|
| 308 |
</nav>
|
| 309 |
|
| 310 |
+
{/* CTA Trigger and Github Badge */}
|
| 311 |
+
<div className="flex items-center gap-4">
|
| 312 |
+
<a
|
| 313 |
+
href="https://github.com/Edge-Explorer/QuantIQ"
|
| 314 |
+
target="_blank"
|
| 315 |
+
rel="noopener noreferrer"
|
| 316 |
+
className="flex items-center gap-2 px-3 py-1.5 text-sm text-foreground/90 hover:text-foreground transition-colors duration-200"
|
| 317 |
+
>
|
| 318 |
+
<GithubIcon size={18} className="text-foreground" />
|
| 319 |
+
<span className="font-medium tracking-tight text-white/95">{githubStars !== null ? githubStars : 'Star'}</span>
|
| 320 |
+
</a>
|
| 321 |
+
|
| 322 |
+
{user ? (
|
| 323 |
+
<div className="flex items-center gap-3">
|
| 324 |
+
<button
|
| 325 |
+
onClick={onGoToDashboard}
|
| 326 |
+
className="bg-[#046A38] text-white hover:bg-[#03522b] transition-all duration-200 px-5 py-2 text-sm font-semibold rounded-[4px] cursor-pointer shadow-md"
|
| 327 |
+
>
|
| 328 |
+
Dashboard
|
| 329 |
+
</button>
|
| 330 |
+
{user.pictureUrl && (
|
| 331 |
+
<img
|
| 332 |
+
src={user.pictureUrl}
|
| 333 |
+
alt="Profile"
|
| 334 |
+
className="w-9 h-9 rounded-full object-cover border border-white/10"
|
| 335 |
+
/>
|
| 336 |
+
)}
|
| 337 |
+
</div>
|
| 338 |
+
) : (
|
| 339 |
+
<Button
|
| 340 |
+
onClick={() => { setAuthMode('signin'); setShowAuthModal(true); }}
|
| 341 |
+
className="liquid-glass rounded-full px-6 py-2.5 text-sm text-foreground hover:scale-[1.03] transition-transform duration-200 cursor-pointer shadow-md"
|
| 342 |
+
>
|
| 343 |
+
Unlock Alpha
|
| 344 |
+
</Button>
|
| 345 |
+
)}
|
| 346 |
+
</div>
|
| 347 |
</div>
|
| 348 |
</header>
|
| 349 |
|
|
|
|
| 369 |
</p>
|
| 370 |
|
| 371 |
{/* Large Hero CTA */}
|
| 372 |
+
{user ? (
|
| 373 |
+
<button
|
| 374 |
+
onClick={onGoToDashboard}
|
| 375 |
+
className="bg-[#046A38] text-white hover:bg-[#03522b] rounded-full px-14 py-5 text-base mt-12 hover:scale-[1.03] cursor-pointer transition-transform duration-200 animate-fade-rise-delay-2 shadow-lg font-semibold"
|
| 376 |
+
>
|
| 377 |
+
Go to Dashboard
|
| 378 |
+
</button>
|
| 379 |
+
) : (
|
| 380 |
+
<button
|
| 381 |
+
onClick={() => { setAuthMode('signin'); setShowAuthModal(true); }}
|
| 382 |
+
className="liquid-glass rounded-full px-14 py-5 text-base text-foreground mt-12 hover:scale-[1.03] cursor-pointer transition-transform duration-200 animate-fade-rise-delay-2 shadow-lg"
|
| 383 |
+
>
|
| 384 |
+
Unlock Alpha
|
| 385 |
+
</button>
|
| 386 |
+
)}
|
| 387 |
</div>
|
| 388 |
+
|
| 389 |
</section>
|
| 390 |
|
| 391 |
{/* THE QUANTIQ STACK INFO SECTION */}
|