fix(navbar): show user avatar in account button
This commit is contained in:
@@ -21,6 +21,7 @@ Use this guide when the user reports behavior. Start from the symptom row, inspe
|
||||
| Registration fails or registration verification email arrives blank | `src/app/auth/register/page.tsx`, `src/app/api/auth/register/route.ts`, `src/app/api/email/send-register-code/route.ts`, `src/lib/email-service.ts` | `acceptedTerms`, email code, password strength, invite code, duplicate profile, SMTP settings, send logs, and MIME body encoding. Verification emails use HTML plus plain-text multipart content; base64 bodies must be folded to 76-character lines and multipart blank separators must be preserved. Do not use `.filter(Boolean)` on MIME message arrays because it removes required empty separator lines and can make mailbox clients render a blank email despite SMTP accepting it. |
|
||||
| Profile changes disappear after refresh | `src/app/profile/page.tsx`, `src/app/api/profile/route.ts`, `src/lib/auth-store.ts` | PUT writes both `profiles` and `auth.users` where needed; client refreshes returned profile. |
|
||||
| Navbar or gallery shows login username instead of public nickname | `src/lib/auth-store.ts`, `src/app/api/profile/route.ts`, `src/app/api/gallery/route.ts`, `src/lib/user-profile-defaults.ts` | `profiles.nickname` is login username; public UI should use returned `nickname` from `profiles.display_nickname`. Gallery SQL should select `display_nickname` first. |
|
||||
| Navbar user avatar is missing and only shows an initial | `src/components/navbar.tsx`, `src/lib/auth-store.ts`, `src/app/api/profile/route.ts` | The navbar user button should read `AuthUser.avatarUrl`; confirm `/api/profile` or login returns `avatar_url`, `parseApiUser` maps it, and `UserAvatar` only falls back to initials after image load failure. |
|
||||
| Existing users have blank/default avatar after display-profile migration | `src/lib/user-profile-defaults.ts`, `scripts/backfill-user-display-profile.mjs`, `src/app/api/auth/login/route.ts` | Run the backfill script with `LOCAL_DB_URL`; login also lazily fills missing `avatar_url` with a generated SVG data URL. |
|
||||
| Theme does not persist | `src/components/account-theme-sync.tsx`, `src/app/api/profile/theme/route.ts`, `src/lib/profile-preferences.ts` | `preferred_theme` schema, token auth, theme normalization. |
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Use this document to jump directly to code before broad searching.
|
||||
| --- | --- | --- |
|
||||
| Root layout and providers | `src/app/layout.tsx`, `src/components/app-shell.tsx`, `src/app/globals.css` | App shell wires navbar, site config sync, visit tracking, theme/account sync, toaster, full-width page mounting, and transient scrollbar visibility. Keep product content at the original component scale; use centered responsive containers instead of stretching all content to viewport edges. Global scrollbars are hidden by default and briefly show the rounded glass style when wheel/touch scrolling adds `scrollbars-visible` on `<html>`. |
|
||||
| Home page | `src/app/page.tsx` | Landing/dashboard-like public entry. Check site config dependencies when changing brand text. |
|
||||
| Navbar | `src/components/navbar.tsx`, `src/components/site-brand.tsx` | Navigation, brand display, auth-aware links. User-facing nav intentionally excludes the disabled legacy canvas route. |
|
||||
| Navbar | `src/components/navbar.tsx`, `src/components/site-brand.tsx` | Navigation, brand display, auth-aware links. User-facing nav intentionally excludes the disabled legacy canvas route. Logged-in desktop/mobile user buttons should show `AuthUser.avatarUrl` first and fall back to the display nickname initial only when the avatar is missing or fails to load. |
|
||||
| Footer | `src/components/site-footer.tsx` | Uses site config for policy/help/about links and filing text; footer background spans browser width while inner content keeps the original `max-w-7xl` scale. |
|
||||
| Announcement popup | `src/components/announcement-popup.tsx`, `src/app/api/announcements/route.ts`, `src/app/globals.css` | Frontend popup behavior plus backend announcement CRUD. Desktop dialog is intentionally wide (`max-w-5xl`) for long Markdown notices; scrollbar styling is inherited from the global glass scrollbar rules. |
|
||||
| Site config sync | `src/components/site-config-sync.tsx`, `src/lib/site-config.ts`, `src/app/api/site-config/route.ts` | Site name, tab title, logo, favicon, policy Markdown, filing, membership switch. |
|
||||
|
||||
@@ -29,6 +29,31 @@ const navItems = [
|
||||
{ href: '/profile', label: '我的', icon: User },
|
||||
];
|
||||
|
||||
function UserAvatar({ avatarUrl, nickname, size = 'md' }: { avatarUrl?: string | null; nickname: string; size?: 'sm' | 'md' }) {
|
||||
const [imageFailed, setImageFailed] = useState(false);
|
||||
const initial = (nickname || '用').trim().charAt(0).toUpperCase();
|
||||
const sizeClass = size === 'sm' ? 'h-5 w-5 text-[10px]' : 'h-6 w-6 text-xs';
|
||||
|
||||
useEffect(() => {
|
||||
setImageFailed(false);
|
||||
}, [avatarUrl]);
|
||||
|
||||
return (
|
||||
<div className={cn('flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-primary/10 text-primary font-bold', sizeClass)}>
|
||||
{avatarUrl && !imageFailed ? (
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt={nickname || '用户头像'}
|
||||
className="h-full w-full object-cover"
|
||||
onError={() => setImageFailed(true)}
|
||||
/>
|
||||
) : (
|
||||
initial
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Navbar() {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
@@ -138,9 +163,7 @@ export function Navbar() {
|
||||
<>
|
||||
<Link href="/profile">
|
||||
<Button variant="ghost" size="sm" className="gap-2">
|
||||
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-primary/10 text-primary text-xs font-bold">
|
||||
{user.nickname.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<UserAvatar avatarUrl={user.avatarUrl} nickname={user.nickname} />
|
||||
{user.nickname}
|
||||
{user.role === 'admin' && (
|
||||
<Shield className="h-3.5 w-3.5 text-primary" />
|
||||
@@ -215,9 +238,9 @@ export function Navbar() {
|
||||
<>
|
||||
<Link href="/profile" className="flex-1" onClick={() => setMobileOpen(false)}>
|
||||
<Button variant="outline" className="w-full" size="sm">
|
||||
<div className="flex h-5 w-5 items-center justify-center rounded-full bg-primary/10 text-primary text-xs font-bold mr-2">
|
||||
{user.nickname.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<span className="mr-2">
|
||||
<UserAvatar avatarUrl={user.avatarUrl} nickname={user.nickname} size="sm" />
|
||||
</span>
|
||||
{user.nickname}
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user