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

@@ -1,5 +1,6 @@
'use client';
import { useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
@@ -26,17 +27,33 @@ export function ConnectionCard({
connectedAt,
onMessage,
}: ConnectionCardProps) {
const [avatarLoaded, setAvatarLoaded] = useState(false);
const placeholder = '/assets/icons/user-placeholder-icon.svg';
const isPlaceholder = !avatar || avatar === placeholder;
return (
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 py-4">
{/* Avatar */}
<div className="flex-shrink-0">
<Image
src={avatar}
alt={name}
width={80}
height={80}
className="rounded-full object-cover w-[80px] h-[80px]"
/>
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative">
{(!avatar || !avatarLoaded || isPlaceholder) && (
<Image
src={placeholder}
alt={name}
width={80}
height={80}
className="absolute inset-0 w-full h-full object-cover"
/>
)}
{avatar && !isPlaceholder && (
<img
src={avatar}
alt={name}
className={`absolute inset-0 w-full h-full object-cover rounded-full transition-opacity duration-200 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
onLoad={() => setAvatarLoaded(true)}
/>
)}
</div>
</div>
{/* Content */}

View File

@@ -1,5 +1,6 @@
'use client';
import { useState } from 'react';
import Image from 'next/image';
interface InvitationCardProps {
@@ -40,17 +41,33 @@ export function InvitationCard({
onAccept,
onIgnore,
}: InvitationCardProps) {
const [avatarLoaded, setAvatarLoaded] = useState(false);
const placeholder = '/assets/icons/user-placeholder-icon.svg';
const isPlaceholder = !avatar || avatar === placeholder;
return (
<div className={`flex flex-col sm:flex-row items-start sm:items-center gap-4 py-4 ${isProcessing ? 'opacity-50 pointer-events-none' : ''}`}>
{/* Avatar */}
<div className="flex-shrink-0">
<Image
src={avatar}
alt={name}
width={80}
height={80}
className="rounded-full object-cover w-[80px] h-[80px]"
/>
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative">
{(!avatar || !avatarLoaded || isPlaceholder) && (
<Image
src={placeholder}
alt={name}
width={80}
height={80}
className="absolute inset-0 w-full h-full object-cover"
/>
)}
{avatar && !isPlaceholder && (
<img
src={avatar}
alt={name}
className={`absolute inset-0 w-full h-full object-cover rounded-full transition-opacity duration-200 ${avatarLoaded ? 'opacity-100' : 'opacity-0'}`}
onLoad={() => setAvatarLoaded(true)}
/>
)}
</div>
</div>
{/* Content */}

View File

@@ -111,9 +111,6 @@ body {
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* Selection styling */
::selection {

View File

@@ -130,7 +130,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} ${sourceSerif4.variable} ${fractul.variable} antialiased`}
>