diff --git a/src/app/page.tsx b/src/app/page.tsx
index cded5b1..a1fe3f5 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -5,7 +5,7 @@ import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { nanoid } from 'nanoid';
import { AuthButton } from '@/components/AuthButton';
-import { AppSwitcher } from '@/components/AppSwitcher';
+import { Header } from '@/components/Header';
import { EcosystemFooter } from '@/components/EcosystemFooter';
import { useAuthStore } from '@/stores/auth';
@@ -125,36 +125,16 @@ export default function HomePage() {
return (
{/* ── Header Nav ─────────────────────────────────────────── */}
-
+
+ Demo
+ Create Map
+ >
+ }
+ />
{/* ── Hero Section ─────────────────────────────────────────── */}
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 0000000..0d4599d
--- /dev/null
+++ b/src/components/Header.tsx
@@ -0,0 +1,60 @@
+'use client';
+
+import { AppSwitcher } from './AppSwitcher';
+import { SpaceSwitcher } from './SpaceSwitcher';
+import { UserMenu } from './UserMenu';
+
+export interface BreadcrumbItem {
+ label: string;
+ href?: string;
+}
+
+interface HeaderProps {
+ /** Which r*App is current (e.g. 'notes', 'vote', 'funds') */
+ current?: string;
+ /** Breadcrumb trail after the switchers */
+ breadcrumbs?: BreadcrumbItem[];
+ /** Right-side actions (rendered between breadcrumbs and UserMenu) */
+ actions?: React.ReactNode;
+ /** Max width class for the inner container */
+ maxWidth?: string;
+}
+
+export function Header({ current = 'notes', breadcrumbs, actions, maxWidth = 'max-w-6xl' }: HeaderProps) {
+ return (
+
+ );
+}
diff --git a/src/components/SpaceSwitcher.tsx b/src/components/SpaceSwitcher.tsx
new file mode 100644
index 0000000..33db21a
--- /dev/null
+++ b/src/components/SpaceSwitcher.tsx
@@ -0,0 +1,152 @@
+'use client';
+
+import { useState, useRef, useEffect } from 'react';
+
+interface SpaceInfo {
+ slug: string;
+ name: string;
+ icon?: string;
+ role?: string;
+}
+
+export function SpaceSwitcher() {
+ const [open, setOpen] = useState(false);
+ const [spaces, setSpaces] = useState([]);
+ const [loaded, setLoaded] = useState(false);
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ const ref = useRef(null);
+
+ useEffect(() => {
+ function handleClick(e: MouseEvent) {
+ if (ref.current && !ref.current.contains(e.target as Node)) {
+ setOpen(false);
+ }
+ }
+ document.addEventListener('click', handleClick);
+ return () => document.removeEventListener('click', handleClick);
+ }, []);
+
+ // Check auth status on mount
+ useEffect(() => {
+ fetch('/api/me')
+ .then((r) => r.json())
+ .then((data) => {
+ if (data.authenticated) setIsAuthenticated(true);
+ })
+ .catch(() => {});
+ }, []);
+
+ const loadSpaces = async () => {
+ if (loaded) return;
+ try {
+ const res = await fetch('/api/spaces');
+ if (res.ok) {
+ const data = await res.json();
+ setSpaces(data.spaces || []);
+ }
+ } catch {
+ // API not available
+ }
+ setLoaded(true);
+ };
+
+ const handleOpen = async () => {
+ const nowOpen = !open;
+ setOpen(nowOpen);
+ if (nowOpen && !loaded) {
+ await loadSpaces();
+ }
+ };
+
+ const mySpaces = spaces.filter((s) => s.role);
+ const publicSpaces = spaces.filter((s) => !s.role);
+
+ return (
+
+
+
+ {open && (
+
+ )}
+
+ );
+}
diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx
new file mode 100644
index 0000000..3a31e75
--- /dev/null
+++ b/src/components/UserMenu.tsx
@@ -0,0 +1,65 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+
+interface UserInfo {
+ username?: string;
+ did?: string;
+}
+
+export function UserMenu() {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ fetch('/api/me')
+ .then((r) => r.json())
+ .then((data) => {
+ if (data.authenticated && data.user) {
+ setUser(data.user);
+ }
+ })
+ .catch(() => {})
+ .finally(() => setLoading(false));
+ }, []);
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (!user) {
+ return (
+
+ Sign In
+
+ );
+ }
+
+ const displayName = user.username || (user.did ? `${user.did.slice(0, 12)}...` : 'User');
+
+ return (
+
+
+
+ {(user.username || 'U')[0].toUpperCase()}
+
+
{displayName}
+
+
+
+ );
+}