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:
@@ -31,6 +31,7 @@ export function Header() {
|
|||||||
displayName: u.displayName,
|
displayName: u.displayName,
|
||||||
role: u.role,
|
role: u.role,
|
||||||
tenantId: u.tenantId,
|
tenantId: u.tenantId,
|
||||||
|
hasAvatar: u.hasAvatar,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -53,6 +54,8 @@ export function Header() {
|
|||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
}, [dropdownOpen]);
|
}, [dropdownOpen]);
|
||||||
|
|
||||||
|
const [avatarError, setAvatarError] = useState(false);
|
||||||
|
|
||||||
const userInitial = user
|
const userInitial = user
|
||||||
? (user.displayName ?? user.username).charAt(0).toUpperCase()
|
? (user.displayName ?? user.username).charAt(0).toUpperCase()
|
||||||
: '?';
|
: '?';
|
||||||
@@ -104,10 +107,19 @@ export function Header() {
|
|||||||
<div className="relative" ref={dropdownRef}>
|
<div className="relative" ref={dropdownRef}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setDropdownOpen(!dropdownOpen)}
|
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')}
|
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>
|
</button>
|
||||||
|
|
||||||
{/* Dropdown menu */}
|
{/* Dropdown menu */}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export interface AuthUser {
|
|||||||
displayName: string | null;
|
displayName: string | null;
|
||||||
role: 'SUPER_ADMIN' | 'ADMIN' | 'USER';
|
role: 'SUPER_ADMIN' | 'ADMIN' | 'USER';
|
||||||
tenantId: string;
|
tenantId: string;
|
||||||
|
hasAvatar?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
|
|||||||
Reference in New Issue
Block a user