"use client"; import { useState, useEffect, useRef, useCallback } from "react"; export type ProfileInfo = { name: string; stateDir: string; workspaceDir: string | null; isActive: boolean; hasConfig: boolean; }; export type ProfileSwitcherTriggerProps = { isOpen: boolean; onClick: () => void; activeProfile: string; switching: boolean; }; type ProfileSwitcherProps = { onProfileSwitch?: () => void; onCreateWorkspace?: () => void; /** Parent-tracked active profile -- triggers a re-fetch when it changes (e.g. after workspace creation). */ activeProfileHint?: string | null; /** When set, this renders instead of the default button; dropdown still opens below. */ trigger?: (props: ProfileSwitcherTriggerProps) => React.ReactNode; }; export function ProfileSwitcher({ onProfileSwitch, onCreateWorkspace, activeProfileHint, trigger }: ProfileSwitcherProps) { const [profiles, setProfiles] = useState([]); const [activeProfile, setActiveProfile] = useState("default"); const [isOpen, setIsOpen] = useState(false); const [switching, setSwitching] = useState(false); const dropdownRef = useRef(null); const fetchProfiles = useCallback(async () => { try { const res = await fetch("/api/profiles"); const data = await res.json(); setProfiles(data.profiles ?? []); setActiveProfile(data.activeProfile ?? "default"); } catch { // ignore } }, []); useEffect(() => { void fetchProfiles(); }, [fetchProfiles, activeProfileHint]); // Close dropdown on outside click useEffect(() => { function handleClickOutside(e: MouseEvent) { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { setIsOpen(false); } } if (isOpen) { document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); } }, [isOpen]); const handleSwitch = async (profileName: string) => { if (profileName === activeProfile) { setIsOpen(false); return; } setSwitching(true); try { const res = await fetch("/api/profiles/switch", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ profile: profileName }), }); if (res.ok) { const data = await res.json(); setActiveProfile(data.activeProfile ?? "default"); onProfileSwitch?.(); void fetchProfiles(); } } catch { // ignore } finally { setSwitching(false); setIsOpen(false); } }; // Don't show the switcher if there's only one profile and no way to create more const showSwitcher = profiles.length > 0; const handleToggle = () => { if (showSwitcher) { setIsOpen((o) => !o); } }; if (!trigger && !showSwitcher) { return null; } return (
{trigger ? ( trigger({ isOpen, onClick: handleToggle, activeProfile, switching, }) ) : ( )} {showSwitcher && isOpen && (
{/* Header */}
Workspace Profiles
{/* Profile list */}
{profiles.map((p) => { const isCurrent = p.name === activeProfile; return ( ); })}
{/* Create new */}
)}
); }