echos-ocr/src/components/layout/org-switcher.tsx

139 lines
3.9 KiB
TypeScript
Raw Normal View History

"use client";
import * as React from "react";
import { useRouter } from "next/navigation";
import { Check, ChevronsUpDown, Plus } from "lucide-react";
import { cn } from "@/lib/utils";
import { useUserProfile } from "@/lib/user-profile";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
type OrgListItem = {
id: string;
name: string;
slug: string;
type: string;
role: string;
};
export function OrgSwitcher({ collapsed }: { collapsed: boolean }) {
const { orgId, orgName, switchOrg } = useUserProfile();
const router = useRouter();
const [orgs, setOrgs] = React.useState<OrgListItem[]>([]);
const [loading, setLoading] = React.useState(false);
const fetchOrgs = React.useCallback(async () => {
try {
const res = await fetch("/api/org/list");
if (res.ok) {
const data = await res.json();
setOrgs(data.orgs ?? []);
}
} catch {}
}, []);
const initial = orgName
? orgName
.split(/\s+/)
.map((w: string) => w[0])
.join("")
.slice(0, 2)
.toUpperCase()
: "?";
const trigger = (
<DropdownMenuTrigger
className={cn(
"flex w-full items-center gap-2 rounded-lg px-3 py-2.5 text-left transition-colors hover:bg-sidebar-accent/50",
collapsed && "justify-center px-0"
)}
onClick={fetchOrgs}
>
<div className="flex size-8 shrink-0 items-center justify-center rounded-md bg-primary/10 text-xs font-bold text-primary">
{initial}
</div>
{!collapsed && (
<>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold leading-tight text-sidebar-foreground">
{orgName || "No workspace"}
</p>
</div>
<ChevronsUpDown className="size-4 shrink-0 text-sidebar-foreground/40" />
</>
)}
</DropdownMenuTrigger>
);
return (
<DropdownMenu>
{collapsed ? (
<Tooltip>
<TooltipTrigger render={<div />}>{trigger}</TooltipTrigger>
<TooltipContent side="right" sideOffset={8}>
{orgName || "No workspace"}
</TooltipContent>
</Tooltip>
) : (
trigger
)}
<DropdownMenuContent
side={collapsed ? "right" : "bottom"}
align="start"
sideOffset={collapsed ? 8 : 4}
className="w-64"
>
{orgs.map((org) => (
<DropdownMenuItem
key={org.id}
className="flex items-center gap-2"
disabled={loading}
onClick={async () => {
if (org.id === orgId) return;
setLoading(true);
await switchOrg(org.id);
setLoading(false);
}}
>
<div className="flex size-6 shrink-0 items-center justify-center rounded bg-primary/10 text-[10px] font-bold text-primary">
{org.name
.split(/\s+/)
.map((w) => w[0])
.join("")
.slice(0, 2)
.toUpperCase()}
</div>
<span className="flex-1 truncate">{org.name}</span>
{org.id === orgId && (
<Check className="size-4 shrink-0 text-primary" />
)}
</DropdownMenuItem>
))}
{orgs.length > 0 && <DropdownMenuSeparator />}
<DropdownMenuItem
className="flex items-center gap-2"
onClick={() => router.push("/onboarding")}
>
<div className="flex size-6 items-center justify-center">
<Plus className="size-4 text-muted-foreground" />
</div>
<span>Create new workspace</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}