From 817061855d377b6c1fa9a598770c47e0175b660b Mon Sep 17 00:00:00 2001 From: atmihaa_06 Date: Sat, 30 May 2026 08:06:03 +0530 Subject: [PATCH 1/4] fix(auth): implement toast notifications and resolve connection issues #12 --- package-lock.json | 28 ++++++++++- package.json | 1 + src/App.tsx | 11 +++- src/lib/api.ts | 112 ++++++++++++++++++----------------------- src/pages/AuthPage.tsx | 38 +++++++------- src/pages/NotFound.tsx | 20 ++++++++ 6 files changed, 127 insertions(+), 83 deletions(-) create mode 100644 src/pages/NotFound.tsx diff --git a/package-lock.json b/package-lock.json index 4b8563c..f6a332c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,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" @@ -2170,7 +2171,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/debug": { @@ -2632,6 +2632,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.19.tgz", + "integrity": "sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3452,6 +3461,23 @@ "react": "^19.2.4" } }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-leaflet": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz", diff --git a/package.json b/package.json index 1a3bce8..df4b751 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/App.tsx b/src/App.tsx index 339d66b..651f57e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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'; @@ -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'; // Importing the new 404 component export default function App() { return ( + {/* Toast provider for global error notifications */} + + {/* Fires a $pageview event to PostHog on every SPA route change */} + }> } /> @@ -23,8 +29,11 @@ export default function App() { } /> } /> } /> + + {/* Catch-all route for broken links/404s */} + } /> ); -} +} \ No newline at end of file diff --git a/src/lib/api.ts b/src/lib/api.ts index 3ce7b8e..481c67a 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,3 +1,4 @@ +import toast from 'react-hot-toast'; import type { ScanResult, HistoryScan, @@ -36,62 +37,61 @@ function authHeaders(): Record { return token ? { Authorization: `Bearer ${token}` } : {}; } -// ── Core fetch wrapper ──────────────────────────────────────────────────────── +// ── Shared Error Handling Logic ────────────────────────────────────────────── -async function apiFetch(path: string, options: RequestInit = {}): Promise { - const res = await fetch(`${API_BASE}${path}`, { - ...options, - headers: { - 'Content-Type': 'application/json', - ...authHeaders(), - ...(options.headers as Record || {}), - }, - }); - - if (!res.ok) { +async function handleResponse(res: Response): Promise { + if (res.ok) return res; + + // Handle 5xx errors (Server Side) + if (res.status >= 500) { + toast.error("Server error. Please try again later."); + } else { + // Handle 4xx errors const err = await res.json().catch(() => ({ detail: res.statusText })); throw new Error((err as { detail?: string }).detail || `HTTP ${res.status}`); } - - return res.json() as Promise; + + throw new Error(`HTTP ${res.status}`); } -// ── Response envelopes ──────────────────────────────────────────────────────── - -export interface ScanResponse { - success: boolean; - scan: ScanResult; -} +async function apiFetch(path: string, options: RequestInit = {}): Promise { + try { + const res = await fetch(`${API_BASE}${path}`, { + ...options, + headers: { + 'Content-Type': 'application/json', + ...authHeaders(), + ...(options.headers as Record || {}), + }, + }); -export interface HistoryResponse { - success: boolean; - count: number; - stats: HistoryStats; - scans: HistoryScan[]; + const validRes = await handleResponse(res); + return validRes.json() as Promise; + } catch (error) { + // Catch network-level drops (e.g., ERR_CONNECTION_REFUSED) + if (error instanceof TypeError) { + toast.error("Unable to connect to the server. Please check your internet connection."); + } + console.error("API Error:", error); + throw error; + } } -export interface MarketsResponse { - success: boolean; - markets: Market[]; -} +// ── Response envelopes ──────────────────────────────────────────────────────── -export interface GradcamResponse { - gradcam_image: string; // base64 data-URI - predicted_class: string; - class_index: number; // 0 | 1 | 2 - mode: 'real' | 'demo'; -} +export interface ScanResponse { success: boolean; scan: ScanResult; } +export interface HistoryResponse { success: boolean; count: number; stats: HistoryStats; scans: HistoryScan[]; } +export interface MarketsResponse { success: boolean; markets: Market[]; } +export interface GradcamResponse { gradcam_image: string; predicted_class: string; class_index: number; mode: 'real' | 'demo'; } // ── API surface ─────────────────────────────────────────────────────────────── export const api = { - // Auth loginUrl: (): string => `${API_BASE}/api/v1/auth/login/google`, - getMe: (): Promise => - apiFetch('/api/v1/auth/me'), + getMe: (): Promise => apiFetch('/api/v1/auth/me'), - // Scans + // Scans - Using native fetch with shared handleResponse to accommodate FormData submitScan: async (blob: Blob): Promise => { const form = new FormData(); form.append('image', blob, 'scan.jpg'); @@ -102,24 +102,16 @@ export const api = { body: form, }); - if (!res.ok) { - const err = await res.json().catch(() => ({ detail: res.statusText })); - throw new Error((err as { detail?: string }).detail || `HTTP ${res.status}`); - } - - return res.json() as Promise; + const validRes = await handleResponse(res); + return validRes.json() as Promise; }, - getLatestScan: (): Promise => - apiFetch('/api/v1/scans/latest'), - - getScan: (id: string): Promise => - apiFetch(`/api/v1/scans/${id}`), - - getScanHistory: (limit = 20, offset = 0): Promise => + getLatestScan: (): Promise => apiFetch('/api/v1/scans/latest'), + getScan: (id: string): Promise => apiFetch(`/api/v1/scans/${id}`), + getScanHistory: (limit = 20, offset = 0): Promise => apiFetch(`/api/v1/scans/history?limit=${limit}&offset=${offset}`), - // Grad-CAM + // Grad-CAM - Using native fetch with shared handleResponse getGradcam: async (blob: Blob): Promise => { const form = new FormData(); form.append('image', blob, 'gradcam_input.jpg'); @@ -130,15 +122,9 @@ export const api = { body: form, }); - if (!res.ok) { - const err = await res.json().catch(() => ({ detail: res.statusText })); - throw new Error((err as { detail?: string }).detail || `HTTP ${res.status}`); - } - - return res.json() as Promise; + const validRes = await handleResponse(res); + return validRes.json() as Promise; }, - // Map - getMarkets: (): Promise => - apiFetch('/api/v1/maps/markets'), -}; + getMarkets: (): Promise => apiFetch('/api/v1/maps/markets'), +}; \ No newline at end of file diff --git a/src/pages/AuthPage.tsx b/src/pages/AuthPage.tsx index e826578..616e2ec 100644 --- a/src/pages/AuthPage.tsx +++ b/src/pages/AuthPage.tsx @@ -21,33 +21,43 @@ export default function AuthPage() { const error = params.get('error'); if (error) { - Promise.resolve().then(() => { - setStatus('error'); - setErrorMsg('Authentication failed. Please try again.'); - }); + setStatus('error'); + setErrorMsg('Authentication failed. Please try again.'); window.history.replaceState({}, '', '/auth'); return; } if (accessToken) { - Promise.resolve().then(() => setStatus('processing')); + setStatus('processing'); setToken(accessToken); window.history.replaceState({}, '', '/auth'); navigate('/mode', { replace: true }); return; } - // If already authenticated, skip straight to mode select if (isAuthenticated()) { navigate('/mode', { replace: true }); } }, [navigate, posthog]); const handleGoogleLogin = () => { - window.location.href = api.loginUrl(); + try { + setStatus('processing'); + const loginUrl = api.loginUrl(); + + if (!loginUrl) { + throw new Error("Login URL configuration missing"); + } + + // Force full browser navigation for OAuth + window.location.href = loginUrl; + } catch (err) { + setStatus('error'); + setErrorMsg('Could not initiate Google Login. Please check your network connection.'); + console.error("Auth initiation failed:", err); + } }; - /** Dev-only: skip OAuth entirely, store the bypass token, go straight to /mode */ const handleDevLogin = () => { setToken(DEV_BYPASS_TOKEN); posthog?.identify('dev-user', { email: 'dev@local' }); @@ -55,7 +65,7 @@ export default function AuthPage() { }; const terminalMessages = (() => { - if (status === 'processing') return ['AUTH_SUCCESS', 'REDIRECTING...']; + if (status === 'processing') return ['AUTH_INITIATED', 'REDIRECTING_TO_OAUTH...']; if (status === 'error') return ['AUTH_ERROR', 'RETRY_REQUIRED']; return ['AUTHENTICATION', 'PROTOCOL: OAUTH-SECURE']; })(); @@ -110,7 +120,6 @@ export default function AuthPage() { {status === 'processing' ? 'AUTHENTICATING...' : 'CONTINUE_WITH_GOOGLE'} - {/* DEV ONLY — shown only when VITE_DEV_MODE=true in .env.local */} {IS_DEV_MODE && ( + + {loggedIn ? ( +
+ + + {isDropdownOpen && ( +
+ 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 + +
)} - - {profile?.full_name ? profile.full_name.split(' ')[0] : 'SESSION'} - - - - {isDropdownOpen && ( -
- 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 - - -
- )} -
- ) : ( - - SIGN_IN / SIGN_UP - - )} + + ) : ( + + SIGN_IN / SIGN_UP + + )} + {/* Logout Toast Notification */} @@ -146,4 +156,4 @@ export default function Navbar() { ); -} +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index 2902d04..fd7f927 100644 --- a/src/index.css +++ b/src/index.css @@ -6,9 +6,13 @@ Brutalist. Zero-radius. High-contrast neon. ========================================================= */ -/* ── THEME TOKENS (from Stitch: NEON NEURAL) ── */ +/* ── THEME TOKENS ── */ @theme { - --color-bg: #131313; + /* Mapping tokens to CSS variables */ + --color-bg: var(--bg); + --color-on-surface: var(--on-surface); + --color-heading: var(--heading-color); + --color-bg-void: #000000; --color-surface: #131313; --color-surface-lowest: #0e0e0e; @@ -26,7 +30,6 @@ --color-secondary: #b5d25e; --color-secondary-container: #5d7602; - --color-on-surface: #e2e2e2; --color-on-surface-variant: #c4c9ac; --color-on-primary: #283500; @@ -43,6 +46,19 @@ --font-mono: 'Space Mono', 'Space Grotesk', ui-monospace, monospace; } +/* ── DYNAMIC THEME VARIABLES ── */ +:root { + --bg: #131313; + --on-surface: #e2e2e2; + --heading-color: #ffffff; +} + +:root.light { + --bg: #ffffff; + --on-surface: #121212; + --heading-color: #121212; +} + /* ── GLOBAL RESET: ZERO RADIUS ── */ *, *::before, @@ -55,6 +71,7 @@ html { font-family: var(--font-body); background-color: var(--color-bg); color: var(--color-on-surface); + transition: background-color 0.3s, color 0.3s; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; @@ -80,7 +97,7 @@ h1, h2, h3, h4, h5, h6 { font-family: var(--font-display); font-weight: 700; letter-spacing: -0.03em; - color: var(--color-tertiary); + color: var(--color-heading); margin: 0; } @@ -160,68 +177,36 @@ h1, h2, h3, h4, h5, h6 { background: linear-gradient(135deg, #ffffff 0%, #c3f400 100%); } -/* ── SCAN LINE ANIMATION ── */ +/* ── ANIMATIONS ── */ @keyframes scan-line { 0% { transform: translateY(-100%); } 100% { transform: translateY(100%); } } +.scan-line { animation: scan-line 2.5s ease-in-out infinite; } -.scan-line { - animation: scan-line 2.5s ease-in-out infinite; -} - -/* ── PULSE GLOW ── */ @keyframes pulse-glow { - 0%, 100% { - box-shadow: 0 0 12px rgba(195, 244, 0, 0.1); - } - 50% { - box-shadow: 0 0 28px rgba(195, 244, 0, 0.25); - } + 0%, 100% { box-shadow: 0 0 12px rgba(195, 244, 0, 0.1); } + 50% { box-shadow: 0 0 28px rgba(195, 244, 0, 0.25); } } +.pulse-glow { animation: pulse-glow 2.5s ease-in-out infinite; } -.pulse-glow { - animation: pulse-glow 2.5s ease-in-out infinite; -} - -/* ── CONCENTRIC PULSE (Map Markers) ── */ @keyframes concentric-pulse { - 0% { - transform: scale(1); - opacity: 0.6; - } - 100% { - transform: scale(2.5); - opacity: 0; - } + 0% { transform: scale(1); opacity: 0.6; } + 100% { transform: scale(2.5); opacity: 0; } } -/* ── DATA STREAM ── */ @keyframes data-stream { 0% { opacity: 0.3; } 50% { opacity: 1; } 100% { opacity: 0.3; } } +.data-stream { animation: data-stream 1.5s ease-in-out infinite; } -.data-stream { - animation: data-stream 1.5s ease-in-out infinite; -} - -/* ── FADE IN UP ── */ @keyframes fade-in-up { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.animate-in { - animation: fade-in-up 0.6s ease-out forwards; + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } } +.animate-in { animation: fade-in-up 0.6s ease-out forwards; } /* ── VIEWFINDER CORNER BRACKETS ── */ .viewfinder-corner { @@ -230,36 +215,16 @@ h1, h2, h3, h4, h5, h6 { height: 28px; border-color: var(--color-secondary); } -.viewfinder-corner.top-left { - top: 0; left: 0; - border-top: 2px solid; - border-left: 2px solid; -} -.viewfinder-corner.top-right { - top: 0; right: 0; - border-top: 2px solid; - border-right: 2px solid; -} -.viewfinder-corner.bottom-left { - bottom: 0; left: 0; - border-bottom: 2px solid; - border-left: 2px solid; -} -.viewfinder-corner.bottom-right { - bottom: 0; right: 0; - border-bottom: 2px solid; - border-right: 2px solid; -} +.viewfinder-corner.top-left { top: 0; left: 0; border-top: 2px solid; border-left: 2px solid; } +.viewfinder-corner.top-right { top: 0; right: 0; border-top: 2px solid; border-right: 2px solid; } +.viewfinder-corner.bottom-left { bottom: 0; left: 0; border-bottom: 2px solid; border-left: 2px solid; } +.viewfinder-corner.bottom-right { bottom: 0; right: 0; border-bottom: 2px solid; border-right: 2px solid; } /* ── FRESHNESS BAR ── */ -.freshness-bar-fresh { - border-left: 4px solid var(--color-secondary); -} -.freshness-bar-spoiled { - border-left: 4px solid var(--color-error); -} +.freshness-bar-fresh { border-left: 4px solid var(--color-secondary); } +.freshness-bar-spoiled { border-left: 4px solid var(--color-error); } -/* ── INPUT FIELDS (bottom-border only) ── */ +/* ── INPUT FIELDS ── */ input[type="text"], input[type="email"], input[type="password"], @@ -293,7 +258,6 @@ input::placeholder { letter-spacing: 0.05em; } -/* ── UTILITY: No Scrollbar ── */ .no-scrollbar::-webkit-scrollbar { display: none; } .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } @@ -316,10 +280,5 @@ input::placeholder { border-top: none !important; border-left: none !important; } -.brutalist-popup .leaflet-popup-content { - margin: 0 !important; -} -.custom-leaflet-icon { - background: transparent !important; - border: none !important; -} \ No newline at end of file +.brutalist-popup .leaflet-popup-content { margin: 0 !important; } +.custom-leaflet-icon { background: transparent !important; border: none !important; } \ No newline at end of file diff --git a/src/lib/theme.ts b/src/lib/theme.ts new file mode 100644 index 0000000..9690aea --- /dev/null +++ b/src/lib/theme.ts @@ -0,0 +1,13 @@ +// src/lib/theme.ts +export const toggleTheme = () => { + const isLight = document.documentElement.classList.toggle('light'); + localStorage.setItem('theme', isLight ? 'light' : 'dark'); +}; + +export const initTheme = () => { + const savedTheme = localStorage.getItem('theme'); + // Apply saved preference, or default to dark mode + if (savedTheme === 'light') { + document.documentElement.classList.add('light'); + } +}; \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index 792d000..fac91d0 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,6 +4,10 @@ import posthog from 'posthog-js' import { PostHogProvider } from 'posthog-js/react' import './index.css' import App from './App.tsx' +import { initTheme } from './lib/theme'; + +// Initialize theme before rendering the app to prevent flicker +initTheme(); // PostHog is only initialized when the key is present. // Contributors running locally without the key will have it silently disabled. @@ -28,4 +32,4 @@ createRoot(document.getElementById('root')!).render( , -) +) \ No newline at end of file From 82e72c62b147f9c39917fc78f3f5bcd01663e514 Mon Sep 17 00:00:00 2001 From: atmihaa-06 Date: Mon, 1 Jun 2026 15:33:51 +0530 Subject: [PATCH 4/4] fix: address all AI review comments for theme, accessibility, and API error handling --- package-lock.json | 40 +++++++++++++++++---------------------- src/components/Navbar.tsx | 2 +- src/index.css | 19 ++++++++++++++++++- src/lib/api.ts | 14 ++++++++------ src/lib/theme.ts | 33 ++++++++++++++++++++++++++------ 5 files changed, 71 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index f6a332c..fba0c7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -278,27 +279,6 @@ "node": ">=6.9.0" } }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -593,6 +573,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -1492,6 +1473,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1558,6 +1540,7 @@ "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", @@ -1840,6 +1823,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1958,6 +1942,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -2171,7 +2156,8 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/debug": { "version": "4.4.3", @@ -2272,6 +2258,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2841,7 +2828,8 @@ "version": "1.9.4", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/levn": { "version": "0.4.1", @@ -3324,6 +3312,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3445,6 +3434,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3454,6 +3444,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3803,6 +3794,7 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3887,6 +3879,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -4074,6 +4067,7 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 8d89095..5b30fb5 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -94,12 +94,12 @@ export default function Navbar() {
{/* Theme Toggle Button */} - {loggedIn ? (