From b7308ffabce8a8a326bc86e20fe51f029f13794f Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 24 Feb 2026 23:32:47 -0800 Subject: [PATCH] fix: landing page improvements and SpaceSwitcher domain support - SpaceSwitcher auto-derives domain from window.location - Fix landing page issues (redirect bug, duplicate footers, broken links, missing content) Co-Authored-By: Claude Opus 4.6 --- components/SpaceSwitcher.tsx | 165 +++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 components/SpaceSwitcher.tsx diff --git a/components/SpaceSwitcher.tsx b/components/SpaceSwitcher.tsx new file mode 100644 index 0000000..f5a9450 --- /dev/null +++ b/components/SpaceSwitcher.tsx @@ -0,0 +1,165 @@ +'use client'; + +import { useState, useRef, useEffect } from 'react'; + +interface SpaceInfo { + slug: string; + name: string; + icon?: string; + role?: string; +} + +interface SpaceSwitcherProps { + /** Current app domain, e.g. 'rfunds.online'. Space links become . */ + domain?: string; +} + +export function SpaceSwitcher({ domain }: SpaceSwitcherProps) { + const [open, setOpen] = useState(false); + const [spaces, setSpaces] = useState([]); + const [loaded, setLoaded] = useState(false); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const ref = useRef(null); + + // Derive domain from window.location if not provided + const appDomain = domain || (typeof window !== 'undefined' + ? window.location.hostname.split('.').slice(-2).join('.') + : 'rspace.online'); + + 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(); + } + }; + + /** Build URL for a space: . */ + const spaceUrl = (slug: string) => `https://${slug}.${appDomain}`; + + const mySpaces = spaces.filter((s) => s.role); + const publicSpaces = spaces.filter((s) => !s.role); + + return ( +
+ + + {open && ( +
+ {!loaded ? ( +
Loading spaces...
+ ) : spaces.length === 0 ? ( + <> +
+ {isAuthenticated ? 'No spaces yet' : 'Sign in to see your spaces'} +
+ setOpen(false)} + > + + Create new space + + + ) : ( + <> + {mySpaces.length > 0 && ( + <> +
+ Your spaces +
+ {mySpaces.map((s) => ( + setOpen(false)} + > + {s.icon || '🌐'} + {s.name} + {s.role && ( + + {s.role} + + )} + + ))} + + )} + + {publicSpaces.length > 0 && ( + <> + {mySpaces.length > 0 &&
} +
+ Public spaces +
+ {publicSpaces.map((s) => ( + setOpen(false)} + > + {s.icon || '🌐'} + {s.name} + + ))} + + )} + + + )} +
+ ); +}