import { useEffect } from 'react' import { useQuery } from '@tanstack/react-query' import { Routes, Route, Navigate, useParams, useLocation } from 'react-router-dom' import { tracer } from './instrumentation' import Layout from './components/Layout' import ScrollToTop from './components/ScrollToTop' import Home from './pages/Home' import HomeV9 from './pages/HomeV9' import PolicyQuestionsPage from './pages/PolicyQuestionsPage' import PolicyQuestionPage from './pages/PolicyQuestionPage' import BrowseTopics from './pages/BrowseTopics' import BrowseCauses from './pages/BrowseCauses' import MoneyTalk from './pages/MoneyTalk' import Dashboard from './pages/Dashboard' import Analytics from './pages/Analytics' import Heatmap from './pages/Heatmap' import Documents from './pages/Documents' import Opportunities from './pages/Opportunities' import Nonprofits from './pages/Nonprofits' import Efile990Viewer from './pages/Efile990Viewer' import NonprofitsHF from './pages/NonprofitsHF' import Settings from './pages/Settings' import PeopleFinder from './pages/PeopleFinder' import PersonDetail from './pages/PersonDetail' import DebateFinder from './pages/DebateGrader' import Profile from './pages/Profile' import FeedSetup from './pages/FeedSetup' import Explore from './pages/Explore' import Events from './pages/Events' import Services from './pages/Services' import Developers from './pages/Developers' import Support from './pages/Support' import JurisdictionMappingQualityPage from './pages/JurisdictionMappingQualityPage' import LighthouseReportPage from './pages/LighthouseReportPage' import BatchJobStatusPage from './pages/BatchJobStatusPage' import Hackathons from './pages/Hackathons' import OpenSource from './pages/OpenSource' import AdvocacyTopics from './pages/AdvocacyTopics' import FactChecking from './pages/FactChecking' import UnifiedSearch from './pages/UnifiedSearch' import JurisdictionsSearch from './pages/JurisdictionsSearch' import PolicyMap from './pages/PolicyMap' import DecisionsMapPage from './pages/DecisionsMapPage' import CensusMapPage from './pages/CensusMapPage' import CensusDrilldownMapPage from './pages/CensusDrilldownMapPage' import DataExplorerLayout from './components/DataExplorerLayout' import AdminLayout from './components/AdminLayout' import DataExplorerScorecardPage from './pages/DataExplorerScorecardPage' import BillDetail from './pages/BillDetail' import GrantDetail from './pages/GrantDetail' import DecisionDetail from './pages/DecisionDetail' import MeetingDetail from './pages/MeetingDetail' import MeetingDocuments from './pages/MeetingDocuments' import MeetingCompare from './pages/MeetingCompare' import EventBillDetail from './pages/EventBillDetail' import NotFound from './pages/NotFound' import { DATA_EXPLORER_MAP_BASE } from './utils/dataExplorerPaths' /** Old route-based drill-down (`/map/state/13/...`, `/map/place/13/...`) is now in-page; * redirect those bookmarks to the unified `/map/us/{vintage}/{metric}` entry. */ function LegacyStatePlaceRedirect() { const { vintage, metric } = useParams<{ vintage: string; metric: string }>() const { search } = useLocation() const v = vintage ?? '2024' const m = metric ?? 'median_household_income' return } /** `/map-v2/...` was the staging URL while the new page lived alongside the old one. */ function MapV2AliasRedirect() { const { pathname, search } = useLocation() const next = pathname.replace(/^\/data-explorer\/map-v2/, DATA_EXPLORER_MAP_BASE) return } /** Old bookmarked URLs used `/census-map/county/...`; national view is now state-level under Data explorer. */ function CensusCountyAliasRedirect() { const { vintage, metric } = useParams<{ vintage: string; metric: string }>() const { search } = useLocation() const v = vintage ?? '2024' const m = metric ?? 'median_household_income' return } function DataExplorerMapDefaultRedirect() { const { data, isError, isPending } = useQuery({ queryKey: ['data-explorer-map-root-redirect'], queryFn: async (): Promise => { const rm = await fetch('/data/census-map/manifest.json') if (!rm.ok) throw new Error('manifest') const manifest = (await rm.json()) as { vintage?: string vintages?: string[] metrics?: { slug: string }[] } const mv = Array.isArray(manifest.vintages) && manifest.vintages.length > 0 ? manifest.vintages : manifest.vintage ? [manifest.vintage] : [] let vintages = mv const rt = await fetch('/data/census-map/state_trends.json') if (rt.ok) { const t = (await rt.json()) as { vintages?: string[] } if (t.vintages?.length) vintages = t.vintages } const v = vintages.length ? vintages[vintages.length - 1]! : (manifest.vintage ?? '2024') const metric = manifest.metrics?.[0]?.slug ?? 'median_household_income' return `${DATA_EXPLORER_MAP_BASE}/us/${v}/${metric}` }, }) if (isPending) return
Loading map…
if (isError) return return } /** Rewrites `/census-map/...` bookmarks to `/data-explorer/map/...`. */ function LegacyCensusMapRedirect() { const { pathname, search } = useLocation() if (pathname === '/census-map' || pathname === '/census-map/') { return } const path = pathname.replace(/^\/census-map/, DATA_EXPLORER_MAP_BASE) return } /** * Emits a short `route.change` span on every navigation. Rendered inside the * Router so `useLocation()` is available. Attribute is the path only (no query * string) to keep cardinality bounded and avoid leaking search terms. */ function RouteChangeTracer() { const location = useLocation() useEffect(() => { const span = tracer.startSpan('route.change', { attributes: { 'route.path': location.pathname }, }) span.end() }, [location.pathname]) return null } function App() { return ( <> {/* v9 prototype homepage (single-column; has its own header) */} } /> {/* Previous homepage, kept reachable for comparison/rollback */} } /> {/* All other pages with sidebar layout */} }> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> }> } /> } /> } /> {/* Lighthouse report + batch jobs moved to the Admin area; keep old URLs working. */} } /> } /> } /> } /> } /> } /> {/* Legacy staging URLs while the drilldown lived alongside the old page. Anyone with a bookmark to /map-v2/... lands on the canonical /map/... now. */} } /> {/* Old CensusMapPage retained at `/map-legacy/...` for one-click rollback if needed. */} } /> } /> } /> {/* Admin area — operational tools, reachable from the logged-in profile menu. */} }> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* 404 Page - Catch all unmatched routes */} } /> ) } export default App