import { useEffect, useState } from 'react' import { Link, useLocation, useNavigate } from 'react-router-dom' import { useAuth } from '../contexts/AuthContext' import { DATABRICKS_APP_URL, DATABRICKS_WORKSPACE_URL } from '../utils/adminPaths' const TEAL = '#0d9488' const TEAL_DARK = '#0f766e' const INK = '#1c1917' /** Scoped styles for the shared site header. Kept self-contained (scoped under * `.site-header`) so the header renders identically whether it sits inside the * home page's `.v9` wrapper or the global Layout. */ const HEADER_CSS = ` @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700;800;900&family=Source+Sans+3:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap'); .site-header { font-family: 'Source Sans 3', system-ui, sans-serif; } .site-header .font-display { font-family: 'Playfair Display', Georgia, serif; } .site-header .font-mono-x { font-family: 'IBM Plex Mono', monospace; } .site-header .v9-burger { display: none; } .site-header .v9-navlink { position: relative; background: none; border: none; padding: 4px 0; font-size: 14.5px; font-weight: 600; color: #44403c; cursor: pointer; font-family: inherit; transition: color .2s ease; } .site-header .v9-navlink::after { content: ''; position: absolute; left: 0; bottom: -3px; height: 2px; width: 0; background: ${TEAL}; transition: width .25s ease; } .site-header .v9-navlink:hover { color: ${TEAL_DARK}; } .site-header .v9-navlink:hover::after { width: 100%; } .site-header .v9-navlink.active { color: ${TEAL_DARK}; } .site-header .v9-navlink.active::after { width: 100%; } .site-header .v9-nav { display: flex; gap: 22px; margin-left: auto; align-items: center; } @keyframes spin { to { transform: rotate(360deg); } } @media (max-width: 760px) { .site-header .v9-nav { display: none; } .site-header .v9-nav.open { display: flex; flex-direction: column; align-items: stretch; position: absolute; top: 100%; left: 0; right: 0; background: #fff; border-bottom: 1px solid #e7e5e4; padding: 10px 16px 16px; gap: 4px; box-shadow: 0 16px 32px rgba(28,25,23,0.1); } .site-header .v9-burger { display: grid; margin-left: auto; } .site-header .v9-brand-sub { display: none; } .site-header .v9-header-inner { padding-left: 16px !important; padding-right: 16px !important; gap: 12px !important; } } ` /** * The single, shared top navigation header used by both the home page and the * global Layout (search page and every other route). Edit this one place to * change the header everywhere. */ export default function SiteHeader() { const navigate = useNavigate() const location = useLocation() const [menuOpen, setMenuOpen] = useState(false) const [showLoginMenu, setShowLoginMenu] = useState(false) // Which in-page section is currently active, so its nav link stays highlighted // after a click and as the user scrolls through it. const [activeSection, setActiveSection] = useState('') const { user, isAuthenticated, login, logout, isLoading: authLoading } = useAuth() // Scroll-spy: highlight the nav link for whichever section sits in the middle // of the viewport. Only the home page hosts these sections, so clear the // active section on every other route. useEffect(() => { if (location.pathname !== '/') { setActiveSection('') return } const ids = ['how-it-works', 'impact'] const observer = new IntersectionObserver( (entries) => { entries.forEach((e) => { if (e.isIntersecting) setActiveSection(e.target.id) }) }, { rootMargin: '-50% 0px -50% 0px' }, ) // Sections may mount a frame after the header; defer the lookup so we attach. const raf = requestAnimationFrame(() => { ids.forEach((id) => { const el = document.getElementById(id) if (el) observer.observe(el) }) }) return () => { cancelAnimationFrame(raf) observer.disconnect() } }, [location.pathname]) // "How It Works" / "Impact" are in-page sections that only exist on the home // page. On home we smooth-scroll to them; elsewhere we route home with a hash // so HomeV9's hash-scroll effect lands on the right section. const goSection = (id: string) => { setActiveSection(id) // keep the clicked link highlighted immediately if (location.pathname === '/') { document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' }) } else { navigate(`/#${id}`) } } const navItems: { label: string; onClick: () => void; active: boolean }[] = [ { label: 'Search', onClick: () => navigate('/search'), active: location.pathname === '/search' }, { label: 'How It Works', onClick: () => goSection('how-it-works'), active: location.pathname === '/' && activeSection === 'how-it-works' }, { label: 'Impact', onClick: () => goSection('impact'), active: location.pathname === '/' && activeSection === 'impact' }, { label: 'Contact', onClick: () => navigate('/support'), active: location.pathname === '/support' }, ] return (
{ // The logo always returns home and starts at the top. When already // on '/', the pathname doesn't change so the global ScrollToTop // effect won't fire — reset scroll here to cover that case too. setMenuOpen(false) window.scrollTo(0, 0) }} style={{ display: 'flex', alignItems: 'center', gap: 10, flexShrink: 0, textDecoration: 'none', color: INK }} >
C1
Open Navigator
by CommunityOne
) }