feat(quick-260630-gbh-01): header avatar display with initial fallback

- AuthUser store: add optional hasAvatar? field
- Header: populate hasAvatar from fetchCurrentUser() result
- Header avatar button: shows <img src='/api-proxy/users/me/avatar'> when hasAvatar=true with onError fallback to initial span
- Plain <img> tag used (same-origin /api-proxy rewrite, no next/image remote config needed)
This commit is contained in:
2026-06-30 12:02:02 +02:00
parent 4482a8ce78
commit 5ae498fd88
2 changed files with 15 additions and 2 deletions
+14 -2
View File
@@ -31,6 +31,7 @@ export function Header() {
displayName: u.displayName,
role: u.role,
tenantId: u.tenantId,
hasAvatar: u.hasAvatar,
});
}
});
@@ -53,6 +54,8 @@ export function Header() {
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [dropdownOpen]);
const [avatarError, setAvatarError] = useState(false);
const userInitial = user
? (user.displayName ?? user.username).charAt(0).toUpperCase()
: '?';
@@ -104,10 +107,19 @@ export function Header() {
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setDropdownOpen(!dropdownOpen)}
className="h-8 w-8 rounded-full bg-primary flex items-center justify-center text-xs font-medium text-primary-foreground hover:opacity-90 transition-opacity"
className="h-8 w-8 rounded-full overflow-hidden bg-primary flex items-center justify-center text-xs font-medium text-primary-foreground hover:opacity-90 transition-opacity"
aria-label={tHeader('userMenu')}
>
{userInitial}
{user?.hasAvatar && !avatarError ? (
<img
src="/api-proxy/users/me/avatar"
alt=""
className="h-8 w-8 rounded-full object-cover"
onError={() => setAvatarError(true)}
/>
) : (
<span>{userInitial}</span>
)}
</button>
{/* Dropdown menu */}
+1
View File
@@ -6,6 +6,7 @@ export interface AuthUser {
displayName: string | null;
role: 'SUPER_ADMIN' | 'ADMIN' | 'USER';
tenantId: string;
hasAvatar?: boolean;
}
interface AuthState {