'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. 'rcal.online'. Space links become . */ domain?: string; } /** Read the EncryptID token from localStorage (set by token-relay across r*.online) */ function getEncryptIDToken(): string | null { if (typeof window === 'undefined') return null; try { return localStorage.getItem('encryptid_token'); } catch { return null; } } /** Read the username from the EncryptID session in localStorage */ function getSessionUsername(): string | null { if (typeof window === 'undefined') return null; try { const stored = localStorage.getItem('encryptid_session'); if (!stored) return null; const parsed = JSON.parse(stored); const claims = parsed?.claims || parsed; return claims?.eid?.username || claims?.username || null; } catch { return null; } } /** Read the current space_id from the cookie set by middleware */ function getCurrentSpaceId(): string { if (typeof document === 'undefined') return 'default'; const match = document.cookie.match(/(?:^|;\s*)space_id=([^;]*)/); return match ? decodeURIComponent(match[1]) : 'default'; } 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 [username, setUsername] = useState(null); 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'); const currentSpaceId = getCurrentSpaceId(); 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(() => { const token = getEncryptIDToken(); const sessionUsername = getSessionUsername(); if (token) { setIsAuthenticated(true); if (sessionUsername) { setUsername(sessionUsername); } } else { // Fallback: check /api/me fetch('/api/me') .then((r) => r.json()) .then((data) => { if (data.authenticated) { setIsAuthenticated(true); if (data.user?.username) setUsername(data.user.username); } }) .catch(() => {}); } }, []); const loadSpaces = async () => { if (loaded) return; try { // Pass EncryptID token so the proxy can forward it to rSpace const token = getEncryptIDToken(); const headers: Record = {}; if (token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch('/api/spaces', { headers }); if (res.ok) { const data = await res.json(); // Handle both flat array and { spaces: [...] } response formats const raw: Array<{ id?: string; slug?: string; name: string; icon?: string; role?: string }> = Array.isArray(data) ? data : (data.spaces || []); setSpaces(raw.map((s) => ({ slug: s.slug || s.id || '', name: s.name, icon: s.icon, role: s.role, }))); } } 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}`; // Build personal space entry for logged-in user const personalSpace: SpaceInfo | null = isAuthenticated && username ? { slug: username, name: 'Personal', icon: '👤', role: 'owner' } : null; // Deduplicate: remove personal space from fetched list if it already appears const dedupedSpaces = personalSpace ? spaces.filter((s) => s.slug !== personalSpace.slug) : spaces; const mySpaces = dedupedSpaces.filter((s) => s.role); const publicSpaces = dedupedSpaces.filter((s) => !s.role); // Determine what to show in the button const currentLabel = currentSpaceId === 'default' ? (personalSpace ? 'personal' : 'public') : currentSpaceId; return (
{open && (
{!loaded ? (
Loading spaces...
) : !isAuthenticated && spaces.length === 0 ? ( <>
Sign in to see your spaces
) : ( <> {/* Personal space — always first when logged in */} {personalSpace && ( <>
Personal
setOpen(false)} > {personalSpace.icon} {username} owner )} {/* Other spaces the user belongs to */} {mySpaces.length > 0 && ( <> {personalSpace &&
}
Your spaces
{mySpaces.map((s) => ( setOpen(false)} > {s.icon || '🌐'} {s.name} {s.role && ( {s.role} )} ))} )} {/* Public spaces */} {publicSpaces.length > 0 && ( <> {(personalSpace || mySpaces.length > 0) && ); }