"use client" import { Building2Icon, CheckIcon, ChevronsUpDownIcon, LayoutDashboardIcon, WrenchIcon } from "lucide-react" import Link from "next/link" import { useEffect, useState, type ReactElement } from "react" import { toast } from "sonner" import { useI18n } from "@/i18n/provider" import { fetchPublicConfig } from "@/lib/api/config" import { listMyOrganizations, switchOrganization, type OrganizationItem } from "@/lib/api/organization" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" export type WorkspaceKey = "dashboard" | "workbench" export type WorkspaceOption = { key: WorkspaceKey href: string labelKey: string icon: typeof LayoutDashboardIcon } export const workspaceOptions: WorkspaceOption[] = [ { key: "dashboard", href: "/dashboard", labelKey: "workspace.dashboard", icon: LayoutDashboardIcon, }, { key: "workbench", href: "/workbench", labelKey: "workspace.workbench", icon: WrenchIcon, }, ] type WorkspaceSwitcherProps = { currentWorkspace: WorkspaceKey variant?: "sidebar" | "header" | "rail" className?: string trigger?: ReactElement } export function WorkspaceSwitcher({ currentWorkspace, variant = "header", className, trigger, }: WorkspaceSwitcherProps) { const t = useI18n() const [orgs, setOrgs] = useState([]) const [activeOrgId, setActiveOrgId] = useState(null) const [switching, setSwitching] = useState(false) const [logoUrl, setLogoUrl] = useState("") useEffect(() => { let mounted = true void fetchPublicConfig() .then((config) => { if (mounted) { setLogoUrl(config.companyLogoUrl?.trim() || "") } }) .catch(() => { // Fall back to the bundled logo. }) return () => { mounted = false } }, []) useEffect(() => { let mounted = true listMyOrganizations() .then((res) => { if (mounted && res?.organizations) { setOrgs(res.organizations) setActiveOrgId(res.currentOrganizationId || res.organizations[0]?.id || null) } }) .catch(() => {}) return () => { mounted = false } }, []) const currentOption = workspaceOptions.find((item) => item.key === currentWorkspace) ?? workspaceOptions[0] const currentOrg = orgs.find((o) => o.id === activeOrgId) || orgs[0] const handleSwitchOrg = async (orgId: number) => { if (orgId === activeOrgId || switching) return setSwitching(true) try { await switchOrganization(orgId) setActiveOrgId(orgId) toast.success("Switched organization successfully") window.location.reload() } catch { toast.error("Failed to switch organization") } finally { setSwitching(false) } } const switchIndicatorClassName = "absolute bottom-0.5 right-0.5 size-2.5 rounded-full bg-sidebar text-sidebar-foreground/70" const triggerClassName = cn( "gap-2 text-left", variant === "header" && "h-9 rounded-md border border-border/70 bg-background px-2.5 shadow-xs hover:bg-muted", variant === "sidebar" && "relative data-[slot=sidebar-menu-button]:p-1.5! group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0! group-data-[collapsible=icon]:data-[slot=sidebar-menu-button]:p-0!", variant === "rail" && "relative size-8 rounded-md border-0 bg-transparent p-0 shadow-none hover:bg-sidebar-accent", className ) const triggerContent = variant === "rail" ? ( <> {t("app.brand")} {currentOrg?.name || t("app.brand")} - {t(currentOption.labelKey)} ) : ( <> {t("app.brand")}
{currentOrg?.name || t("app.brand")} {t(currentOption.labelKey)} {currentOrg?.role ? `• ${currentOrg.role}` : ""}
{variant === "sidebar" ? ( ) : null} ) return ( } > {triggerContent} {orgs.length > 0 ? ( <> Organizations / Workspaces {orgs.map((org) => { const isActive = org.id === activeOrgId return ( handleSwitchOrg(org.id)} >
{org.name} {org.role || "Member"} {org.plan ? `• ${org.plan}` : ""}
{isActive ? : null}
) })}
) : null} {t("workspace.switchWorkspace")} {workspaceOptions.map((item) => ( } className="cursor-pointer gap-2" > {t(item.labelKey)} {item.key === currentWorkspace ? ( ) : null} ))}
) }