Skip to content
Open
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
68 changes: 44 additions & 24 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"posthog-js": "^1.376.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-hot-toast": "^2.6.0",
"react-leaflet": "^5.0.0",
"react-router-dom": "^7.14.0",
"tailwindcss": "^4.2.2"
Expand Down
11 changes: 10 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Toaster } from 'react-hot-toast';
import Layout from './components/Layout';
import LandingPage from './pages/LandingPage';
import AuthPage from './pages/AuthPage';
Expand All @@ -8,12 +9,17 @@ import AnalysisDashboard from './pages/AnalysisDashboard';
import MarketMapPage from './pages/MarketMapPage';
import ResultsPage from './pages/ResultsPage';
import PostHogPageView from './components/PostHogPageView';
import NotFound from './pages/NotFound';

export default function App() {
return (
<BrowserRouter>
{/* Toast provider for global error notifications */}
<Toaster position="bottom-right" />

{/* Fires a $pageview event to PostHog on every SPA route change */}
<PostHogPageView />

<Routes>
<Route element={<Layout />}>
<Route path="/" element={<LandingPage />} />
Expand All @@ -23,8 +29,11 @@ export default function App() {
<Route path="/analysis" element={<AnalysisDashboard />} />
<Route path="/map" element={<MarketMapPage />} />
<Route path="/results" element={<ResultsPage />} />

{/* Catch-all route for broken links/404s */}
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</BrowserRouter>
);
}
}
3 changes: 2 additions & 1 deletion src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Outlet } from 'react-router-dom';
import Navbar from './Navbar';
import BottomNav from './BottomNav';
import Footer from './Footer';
import { toggleTheme } from '../lib/theme';

export default function Layout() {
return (
Expand Down Expand Up @@ -31,4 +32,4 @@ export default function Layout() {
<BottomNav />
</div>
);
}
}
100 changes: 55 additions & 45 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useState, useRef, useEffect } from 'react';
import { usePostHog } from 'posthog-js/react';
import { api, clearToken, isAuthenticated } from '../lib/api';
import type { UserProfile } from '../lib/types';
import { toggleTheme } from '../lib/theme'; // Import the toggle function

export default function Navbar() {
const location = useLocation();
Expand Down Expand Up @@ -45,7 +46,6 @@ export default function Navbar() {
const handleLogout = () => {
setIsDropdownOpen(false);
clearToken();
// Reset PostHog session so next user on this device isn't tracked as this user
posthog?.reset();
navigate('/');
setShowToast(true);
Expand Down Expand Up @@ -90,51 +90,61 @@ export default function Navbar() {
))}
</div>

{/* Auth Button & Modal */}
{loggedIn ? (
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
className="flex items-center gap-3 bg-surface-low border border-outline-variant/30 text-on-surface px-3 py-1.5 transition-all duration-200 hover:bg-surface-mid ghost-border"
>
{profile?.avatar_url ? (
<img src={profile.avatar_url} referrerPolicy="no-referrer" alt="Profile" className="w-7 h-7 rounded-full object-cover grayscale-[0.5] contrast-125 border border-neon/30" />
) : (
<div className="w-7 h-7 bg-surface-highest flex items-center justify-center text-neon text-xs font-bold font-[family-name:var(--font-display)]">
{profile?.full_name?.charAt(0) || 'U'}
{/* Auth Button & Theme Toggle */}
<div className="flex items-center gap-4">
{/* Theme Toggle Button */}
<button
type="button"
onClick={toggleTheme}
className="font-[family-name:var(--font-mono)] text-[10px] tracking-widest text-on-surface-variant hover:text-neon transition-colors duration-200 border border-outline-variant/30 px-3 py-1"
>
THEME
</button>
Comment on lines +96 to +102
{loggedIn ? (
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
className="flex items-center gap-3 bg-surface-low border border-outline-variant/30 text-on-surface px-3 py-1.5 transition-all duration-200 hover:bg-surface-mid ghost-border"
>
{profile?.avatar_url ? (
<img src={profile.avatar_url} referrerPolicy="no-referrer" alt="Profile" className="w-7 h-7 object-cover grayscale-[0.5] contrast-125 border border-neon/30" />
) : (
<div className="w-7 h-7 bg-surface-highest flex items-center justify-center text-neon text-xs font-bold font-[family-name:var(--font-display)]">
{profile?.full_name?.charAt(0) || 'U'}
</div>
)}
<span className="text-sm font-[family-name:var(--font-mono)] tracking-wider mr-1 uppercase">
{profile?.full_name ? profile.full_name.split(' ')[0] : 'SESSION'}
</span>
</button>

{isDropdownOpen && (
<div className="absolute right-0 top-full mt-2 w-48 bg-surface-low border border-outline-variant/30 shadow-2xl p-2 flex flex-col gap-1 z-50">
<Link
to="/results"
onClick={() => setIsDropdownOpen(false)}
className="px-4 py-3 text-sm font-[family-name:var(--font-display)] font-bold text-on-surface-variant hover:text-neon hover:bg-surface-high no-underline transition-colors duration-200 block"
>
RESULTS
</Link>
<button
onClick={handleLogout}
className="px-4 py-3 text-sm font-[family-name:var(--font-display)] font-bold text-error text-left hover:bg-error/10 transition-colors duration-200 block w-full"
>
TERMINATE_SESSION (LOGOUT)
</button>
</div>
)}
<span className="text-sm font-[family-name:var(--font-mono)] tracking-wider mr-1 uppercase">
{profile?.full_name ? profile.full_name.split(' ')[0] : 'SESSION'}
</span>
</button>

{isDropdownOpen && (
<div className="absolute right-0 top-full mt-2 w-48 bg-surface-low border border-outline-variant/30 shadow-2xl p-2 flex flex-col gap-1 z-50">
<Link
to="/results"
onClick={() => setIsDropdownOpen(false)}
className="px-4 py-3 text-sm font-[family-name:var(--font-display)] font-bold text-on-surface-variant hover:text-neon hover:bg-surface-high no-underline transition-colors duration-200 block"
>
RESULTS
</Link>
<button
onClick={handleLogout}
className="px-4 py-3 text-sm font-[family-name:var(--font-display)] font-bold text-error text-left hover:bg-error/10 transition-colors duration-200 block w-full"
>
TERMINATE_SESSION (LOGOUT)
</button>
</div>
)}
</div>
) : (
<Link
to="/auth"
className="flex items-center gap-2 bg-neon text-on-primary px-3 py-1.5 md:px-5 md:py-2.5 font-[family-name:var(--font-display)] font-bold text-xs md:text-sm tracking-wide no-underline transition-all duration-200 hover:bg-neon-dim"
>
SIGN_IN / SIGN_UP
</Link>
)}
</div>
) : (
<Link
to="/auth"
className="flex items-center gap-2 bg-neon text-on-primary px-3 py-1.5 md:px-5 md:py-2.5 font-[family-name:var(--font-display)] font-bold text-xs md:text-sm tracking-wide no-underline transition-all duration-200 hover:bg-neon-dim"
>
SIGN_IN / SIGN_UP
</Link>
)}
</div>
</div>

{/* Logout Toast Notification */}
Expand All @@ -146,4 +156,4 @@ export default function Navbar() {
</div>
</nav>
);
}
}
Loading
Loading