"use client"; import { usePathname, useRouter } from "next/navigation"; import { Building2, Check, ChevronUp, Ellipsis, Link2, LogOut, Settings } from "lucide-react"; import { FormEvent, useEffect, useRef, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import type { ApiResult } from "@/modules/core/server/api"; import type { AccountOrganizationOption, Organization, PublicAccount } from "@/modules/core/types"; import { UserAvatar } from "@/modules/shared/components/UserAvatar"; import { getWorkspaceHref, getWorkspacePathFromPathname } from "@/modules/shared/lib/workspace-routing"; import { cn } from "@/lib/utils"; type AccountMenuProps = { account: PublicAccount; organization?: Organization; organizations: AccountOrganizationOption[]; }; export function AccountMenu({ account, organization, organizations, }: AccountMenuProps): React.ReactElement { const router = useRouter(); const pathname = usePathname(); const menuRef = useRef(null); const [isOpen, setIsOpen] = useState(false); const [isOrganizationOpen, setIsOrganizationOpen] = useState(false); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isSignOutConfirmOpen, setIsSignOutConfirmOpen] = useState(false); const [isPending, setIsPending] = useState(false); const [message, setMessage] = useState(""); const currentOrganization = organizations.find((item) => item.isActive) ?? organizations[0]; useEffect(() => { if (!isOpen && !isOrganizationOpen) { return; } function handlePointerDown(event: PointerEvent): void { const target = event.target; if (target instanceof Node && !menuRef.current?.contains(target)) { setIsOpen(false); setIsOrganizationOpen(false); } } function handleKeyDown(event: KeyboardEvent): void { if (event.key === "Escape") { setIsOpen(false); setIsOrganizationOpen(false); } } document.addEventListener("pointerdown", handlePointerDown); document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("pointerdown", handlePointerDown); document.removeEventListener("keydown", handleKeyDown); }; }, [isOpen, isOrganizationOpen]); async function handleOrganizationSwitch(organizationId: string): Promise { setMessage(""); setIsPending(true); const response = await fetch("/api/auth/organization", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ organizationId }), }); const result = (await response.json()) as ApiResult<{ organization: Organization | null; organizations: AccountOrganizationOption[]; }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } setIsOrganizationOpen(false); const activeOrganization = result.organization ?? result.organizations.find((item) => item.isActive); const workspacePath = getWorkspacePathFromPathname(pathname); router.replace(getWorkspaceHref(activeOrganization?.slug, workspacePath)); router.refresh(); } async function handleSignOut(): Promise { setIsPending(true); await fetch("/api/auth/logout", { method: "POST" }); router.refresh(); router.replace("/login"); } async function handleProfileSubmit(event: FormEvent): Promise { event.preventDefault(); setMessage(""); setIsPending(true); const formData = new FormData(event.currentTarget); const response = await fetch("/api/account/profile", { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify({ name: String(formData.get("name") ?? ""), avatarUrl: String(formData.get("avatarUrl") ?? ""), }), }); const result = (await response.json()) as ApiResult<{ account: PublicAccount }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } setMessage(result.reason); router.refresh(); } return (
{isOrganizationOpen ? (
{organizations.map((item) => ( ))} {message ?

{message}

: null}
) : null}

{account.name}

{account.email}

{isOpen ? (
) : null} { if (!isPending) { setIsSettingsOpen(false); setMessage(""); } }} open={isSettingsOpen} title="用户设置" >

{organization?.oidcEnabled ? "当前机构已配置 OIDC 登录入口。" : "当前机构暂未启用 OIDC 登录入口。"}

{organization?.oidcEnabled ? "可接入" : "未启用"}
绑定、解绑和重新授权需要接入 OIDC 回调与绑定记录后启用;当前不会展示虚假的绑定账号。
{message ? (

{message}

) : null}
{ if (!isPending) { setIsSignOutConfirmOpen(false); } }} open={isSignOutConfirmOpen} title="确认退出登录" >

确定要退出当前账号吗?

); }