516 mechanical replacements across 25 widget files: - spacing xs/sm/md/lg/xl to numeric 1/2/3/4/6 - text-heading-3 to text-lg font-semibold - bg-danger to bg-destructive - border border dedup Fix .eslintrc.tokens.js to use typescript-eslint parser (was importing uninstalled @typescript-eslint/parser). lint:tokens now passes.
57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
"use client";
|
||
|
||
/**
|
||
* user-menu(topbar / top)
|
||
*
|
||
* 通过 useCurrentUser 查询 apollo-router → iam 子图的 me 数据。
|
||
* 展示用户头像 + 下拉菜单(昵称 / 邮箱 / 角色)。
|
||
*
|
||
* 关联:portal-shell spec §5.6 统一 Hook、M8 验收
|
||
*/
|
||
import { useState } from "react";
|
||
import { useCurrentUser } from "@/lib/api/topbar";
|
||
import { useAuth } from "@/providers/AuthProvider";
|
||
import type { PluginProps } from "@/lib/types";
|
||
|
||
export default function UserMenu(_props: PluginProps): React.ReactElement {
|
||
const { user } = useAuth();
|
||
const [open, setOpen] = useState(false);
|
||
|
||
const { data: me } = useCurrentUser();
|
||
|
||
const displayName = me?.name ?? user.name;
|
||
const displayEmail = me?.email ?? user.email;
|
||
const displayRole = me?.role ?? user.role;
|
||
|
||
return (
|
||
<div className="relative">
|
||
<button
|
||
type="button"
|
||
aria-label="用户菜单"
|
||
aria-expanded={open}
|
||
onClick={() => setOpen((v) => !v)}
|
||
className="flex items-center gap-2 rounded-md bg-card px-2 py-1 text-foreground"
|
||
>
|
||
<span
|
||
aria-hidden
|
||
className="flex h-heading-3 w-heading-3 items-center justify-center rounded-full bg-primary text-primary-foreground"
|
||
>
|
||
{displayName.charAt(0).toUpperCase()}
|
||
</span>
|
||
<span className="text-sm">{displayName}</span>
|
||
</button>
|
||
{open ? (
|
||
<ul className="absolute right-0 z-50 mt-2 w-56 rounded-xl border bg-card p-2 shadow-md">
|
||
<li className="border-b border py-1">
|
||
<p className="text-sm text-foreground">{displayName}</p>
|
||
<p className="text-xs text-muted-foreground">{displayEmail}</p>
|
||
</li>
|
||
<li className="py-1 text-sm text-muted-foreground">
|
||
角色:{displayRole}
|
||
</li>
|
||
</ul>
|
||
) : null}
|
||
</div>
|
||
);
|
||
}
|