feat: Improve avatar loading experience by displaying placeholders until images are fully loaded across various components.

This commit is contained in:
pradeepkumar
2026-03-13 22:45:11 +05:30
parent b339dd865c
commit 7b5c670159
11 changed files with 225 additions and 80 deletions

View File

@@ -26,6 +26,7 @@ export function CommonHeader() {
const mobileMenuRef = useRef<HTMLDivElement>(null);
const [profileImage, setProfileImage] = useState<string | null>(null);
const [profileName, setProfileName] = useState<string | null>(null);
const [avatarLoaded, setAvatarLoaded] = useState(false);
const [notificationCount, setNotificationCount] = useState(0);
// Close dropdowns when clicking outside
@@ -73,6 +74,7 @@ export function CommonHeader() {
}
if (avatar) {
setAvatarLoaded(false);
try {
const avatarUrl = await uploadService.getPresignedDownloadUrl(avatar);
setProfileImage(avatarUrl);
@@ -150,6 +152,7 @@ export function CommonHeader() {
alt="RE-QuestN"
width={150}
height={40}
priority
className="h-8 md:h-10 w-auto"
/>
</Link>
@@ -198,23 +201,25 @@ export function CommonHeader() {
className="flex items-center gap-2 md:gap-3 hover:opacity-80 transition-opacity cursor-pointer"
>
{/* Avatar */}
<div className="w-[32px] h-[32px] md:w-[35px] md:h-[35px] rounded-full overflow-hidden border-2 border-[#e58625] bg-gray-100 flex-shrink-0">
{userImage ? (
<img
src={userImage}
alt="Profile"
className="w-full h-full object-cover"
/>
) : (
<div className="w-[32px] h-[32px] md:w-[35px] md:h-[35px] rounded-full overflow-hidden border-2 border-[#e58625] bg-gray-100 flex-shrink-0 relative">
{(!userImage || !avatarLoaded) && (
<Image
src="/assets/icons/user-placeholder-icon.svg"
alt="Profile"
width={35}
height={35}
className="object-cover"
className="absolute inset-0 object-cover"
style={{ width: '100%', height: '100%' }}
/>
)}
{userImage && (
<img
src={userImage}
alt="Profile"
className={`w-full h-full object-cover ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
onLoad={() => setAvatarLoaded(true)}
/>
)}
</div>
{/* Greeting Text - hidden on mobile */}
<div className="hidden sm:flex flex-col text-left">
@@ -233,23 +238,25 @@ export function CommonHeader() {
<div className="absolute -top-2 right-6 w-0 h-0 border-l-[8px] border-l-transparent border-r-[8px] border-r-transparent border-b-[8px] border-b-white"></div>
<div className="flex items-center gap-3 px-4 py-4 border-b border-black/10">
<div className="w-[42px] h-[42px] rounded-full overflow-hidden flex-shrink-0 bg-gray-100">
{userImage ? (
<img
src={userImage}
alt="Profile"
className="w-full h-full object-cover"
/>
) : (
<div className="w-[42px] h-[42px] rounded-full overflow-hidden flex-shrink-0 bg-gray-100 relative">
{(!userImage || !avatarLoaded) && (
<Image
src="/assets/icons/user-placeholder-icon.svg"
alt="Profile"
width={42}
height={42}
className="object-cover"
className="absolute inset-0 object-cover"
style={{ width: '100%', height: '100%' }}
/>
)}
{userImage && (
<img
src={userImage}
alt="Profile"
className={`w-full h-full object-cover ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
onLoad={() => setAvatarLoaded(true)}
/>
)}
</div>
<div className="flex flex-col min-w-0 overflow-hidden">
<p className="font-fractul font-medium text-[16px] leading-[20px] text-black">