"use client"; import { useEffect, useRef, useState } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { Building2, Plus } from "lucide-react"; import { useAuth } from "@/auth/clerk"; import { ApiError } from "@/api/mutator"; import { type listMyOrganizationsApiV1OrganizationsMeListGetResponse, useCreateOrganizationApiV1OrganizationsPost, useListMyOrganizationsApiV1OrganizationsMeListGet, useSetActiveOrgApiV1OrganizationsMeActivePatch, } from "@/api/generated/organizations/organizations"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue, } from "@/components/ui/select"; export function OrgSwitcher() { const { isSignedIn } = useAuth(); const queryClient = useQueryClient(); const [createOpen, setCreateOpen] = useState(false); const [orgName, setOrgName] = useState(""); const [orgError, setOrgError] = useState(null); const channelRef = useRef(null); useEffect(() => { if (typeof window === "undefined") return; if (!("BroadcastChannel" in window)) return; const channel = new BroadcastChannel("org-switch"); channelRef.current = channel; return () => { channel.close(); channelRef.current = null; }; }, []); const orgsQuery = useListMyOrganizationsApiV1OrganizationsMeListGet< listMyOrganizationsApiV1OrganizationsMeListGetResponse, ApiError >({ query: { enabled: Boolean(isSignedIn), refetchOnMount: "always", retry: false, }, }); const orgs = orgsQuery.data?.status === 200 ? orgsQuery.data.data : []; const activeOrg = orgs.find((item) => item.is_active) ?? null; const orgValue = activeOrg?.id ?? "personal"; const announceOrgSwitch = (orgId: string) => { if (typeof window === "undefined") return; const payload = JSON.stringify({ orgId, ts: Date.now() }); try { window.localStorage.setItem("openclaw_org_switch", payload); } catch { // Ignore storage failures. } channelRef.current?.postMessage(payload); }; const setActiveOrgMutation = useSetActiveOrgApiV1OrganizationsMeActivePatch({ mutation: { onSuccess: (_result, variables) => { const orgId = variables?.data?.organization_id; if (orgId) { announceOrgSwitch(orgId); } window.location.reload(); }, onError: (err) => { setOrgError(err.message || "Unable to switch organization."); }, }, }); const createOrgMutation = useCreateOrganizationApiV1OrganizationsPost({ mutation: { onSuccess: () => { setOrgName(""); setOrgError(null); setCreateOpen(false); queryClient.invalidateQueries({ queryKey: ["/api/v1/organizations/me/list"], }); if (typeof window !== "undefined") { announceOrgSwitch("new"); } window.location.reload(); }, onError: (err) => { setOrgError(err.message || "Unable to create organization."); }, }, }); const handleOrgChange = (value: string) => { if (value === "__create__") { setOrgError(null); setCreateOpen(true); return; } if (!value || value === orgValue) { return; } setActiveOrgMutation.mutate({ data: { organization_id: value }, }); }; const handleCreateOrg = () => { const trimmed = orgName.trim(); if (!trimmed) { setOrgError("Organization name is required."); return; } createOrgMutation.mutate({ data: { name: trimmed }, }); }; if (!isSignedIn) { return null; } return (
{orgError && !createOpen ? (

{orgError}

) : null} Create a new organization This will switch you to the new organization as soon as it is created.
setOrgName(event.target.value)} /> {orgError ? (

{orgError}

) : null}
); }