From 3b57ad4f37be65f92e0686973b12ac0a55e142e2 Mon Sep 17 00:00:00 2001 From: zvspany Date: Sun, 29 Mar 2026 15:12:21 +0200 Subject: [PATCH] feat(profile): enhance avatar handling and improve layout for profile header --- components/public/profile-header.tsx | 66 +++++++++++++++++++++------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/components/public/profile-header.tsx b/components/public/profile-header.tsx index d9ddc1f..e733ae9 100644 --- a/components/public/profile-header.tsx +++ b/components/public/profile-header.tsx @@ -1,4 +1,7 @@ /* eslint-disable @next/next/no-img-element */ +"use client"; + +import { useMemo, useState } from "react"; type ProfileHeaderProps = { displayName: string; username: string; @@ -7,34 +10,67 @@ type ProfileHeaderProps = { }; export function ProfileHeader({ displayName, username, bio, avatarUrl }: ProfileHeaderProps) { - const words = displayName.trim().split(/\s+/).filter(Boolean); - const initials = - words.length >= 2 - ? `${words[0]?.[0] ?? ""}${words[1]?.[0] ?? ""}`.toUpperCase() - : (words[0]?.slice(0, 2) ?? username.slice(0, 2)).toUpperCase(); + const [avatarFailed, setAvatarFailed] = useState(false); + + const initials = useMemo(() => { + const words = displayName.trim().split(/\s+/).filter(Boolean); + if (words.length >= 2) { + return `${words[0]?.[0] ?? ""}${words[1]?.[0] ?? ""}`.toUpperCase(); + } + if (words.length === 1) { + return (words[0]?.[0] ?? username[0] ?? "?").toUpperCase(); + } + return (username[0] ?? "?").toUpperCase(); + }, [displayName, username]); + + const normalizedAvatarUrl = avatarUrl?.trim() ?? ""; + const hasValidAvatarUrl = useMemo(() => { + if (!normalizedAvatarUrl) { + return false; + } + try { + const url = new URL(normalizedAvatarUrl); + if (url.protocol !== "http:" && url.protocol !== "https:") { + return false; + } + // Reject placeholder-like values such as "https://..." + if (!url.hostname || url.hostname === "..." || !url.hostname.includes(".")) { + return false; + } + return true; + } catch { + return false; + } + }, [normalizedAvatarUrl]); + + const shouldShowAvatar = hasValidAvatarUrl && !avatarFailed; return (
-
- {avatarUrl ? ( +
+ {shouldShowAvatar ? ( {`${displayName} setAvatarFailed(true)} /> ) : ( -
+
{initials}
)} -
-

{displayName}

-

@{username}

+
+

{displayName}

+

@{username}

+ {bio ?

{bio}

: null}
- - {bio ?

{bio}

: null}
); }