feat(chat): show user avatar in profile action

Render the stored user avatar in the Chat Header when available while preserving the existing Profile icon fallback, navigation, accessibility label, and analytics.
This commit is contained in:
2026-07-21 18:36:45 +08:00
parent 9fff5b16fd
commit 7f271ef2e0
2 changed files with 57 additions and 18 deletions
+27 -17
View File
@@ -2,17 +2,17 @@
/**
* ChatHeader 顶部栏
*
* 图标:lucide-react <Lock />(游客 banner+ <UserRound />Profile
* tree-shakablecurrentColor 继承父 color
* Profile 优先展示用户头像;头像缺失时回退到 lucide-react <UserRound />
*/
import type { ReactNode } from "react";
import { Lock, UserRound } from "lucide-react";
import { BackButton } from "@/app/_components";
import { BackButton, UserMessageAvatar } from "@/app/_components";
import { useActiveCharacterRoutes } from "@/providers/character-provider";
import { buildGlobalPageUrl } from "@/router/global-route-context";
import { ROUTES } from "@/router/routes";
import { useAppNavigator } from "@/router/use-app-navigator";
import { useUserSelector } from "@/stores/user/user-context";
import { BrowserHintOverlay } from "./browser-hint-overlay";
@@ -29,6 +29,10 @@ export function ChatHeader({
}: ChatHeaderProps) {
const navigator = useAppNavigator();
const characterRoutes = useActiveCharacterRoutes();
const avatarUrl = useUserSelector((state) => state.context.avatarUrl);
const handleOpenProfile = () => {
navigator.push(buildGlobalPageUrl(ROUTES.profile, characterRoutes.chat));
};
return (
<header className="flex shrink-0 flex-col items-stretch bg-transparent p-0">
@@ -72,20 +76,26 @@ export function ChatHeader({
{showBrowserHint ? <BrowserHintOverlay /> : null}
</div>
<button
type="button"
data-analytics-key="chat.open_profile"
data-analytics-label="Open profile"
className="flex size-(--responsive-icon-button-size,42px) cursor-pointer items-center justify-center rounded-full border border-[rgba(255,255,255,0.12)] bg-[rgba(13,11,20,0.7)] text-(--color-text-primary,#fff) shadow-[0_10px_24px_rgba(0,0,0,0.2)] backdrop-blur-md transition-[background,transform] duration-150 hover:bg-[rgba(28,24,39,0.82)] active:scale-96 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
onClick={() =>
navigator.push(
buildGlobalPageUrl(ROUTES.profile, characterRoutes.chat),
)
}
aria-label="Profile"
>
<UserRound size={24} aria-hidden="true" />
</button>
{avatarUrl ? (
<UserMessageAvatar
avatarUrl={avatarUrl}
size="var(--responsive-icon-button-size, 42px)"
actionLabel="Profile"
analyticsKey="chat.open_profile"
onClick={handleOpenProfile}
/>
) : (
<button
type="button"
data-analytics-key="chat.open_profile"
data-analytics-label="Open profile"
className="flex size-(--responsive-icon-button-size,42px) cursor-pointer items-center justify-center rounded-full border border-[rgba(255,255,255,0.12)] bg-[rgba(13,11,20,0.7)] text-(--color-text-primary,#fff) shadow-[0_10px_24px_rgba(0,0,0,0.2)] backdrop-blur-md transition-[background,transform] duration-150 hover:bg-[rgba(28,24,39,0.82)] active:scale-96 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
onClick={handleOpenProfile}
aria-label="Profile"
>
<UserRound size={24} aria-hidden="true" />
</button>
)}
</>
) : null}
</div>