feat: implement avatar loading shimmers and restrict privacy settings visibility by user role

This commit is contained in:
pradeepkumar
2026-03-28 17:01:37 +05:30
parent 391d717987
commit d0d43eb69b
10 changed files with 151 additions and 161 deletions

View File

@@ -75,6 +75,7 @@ export default function AgentDashboard() {
const [error, setError] = useState<string | null>(null);
const [isPhotoModalOpen, setIsPhotoModalOpen] = useState(false);
const [imageError, setImageError] = useState(false);
const [imageLoaded, setImageLoaded] = useState(false);
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
const [isAvailable, setIsAvailable] = useState(true);
const [isTogglingAvailability, setIsTogglingAvailability] = useState(false);
@@ -334,16 +335,21 @@ export default function AgentDashboard() {
{/* Profile Image */}
<div className="relative w-[200px] lg:w-[260px]">
<div className="w-[200px] h-[200px] lg:w-[260px] lg:h-[260px] rounded-[15px] overflow-hidden relative">
{/* Initials base layer */}
<div className="absolute inset-0 bg-[#c4d9d4] flex items-center justify-center rounded-[15px]">
<span className="font-bold text-[#00293d] text-[80px]">{agentProfile?.firstName?.[0]?.toUpperCase() || '?'}</span>
</div>
{/* Shimmer while loading, initials only on error */}
{imageError ? (
<div className="absolute inset-0 bg-[#c4d9d4] flex items-center justify-center rounded-[15px]">
<span className="font-bold text-[#00293d] text-[80px]">{agentProfile?.firstName?.[0]?.toUpperCase() || '?'}</span>
</div>
) : !imageLoaded ? (
<div className="absolute inset-0 shimmer-loading rounded-[15px]" />
) : null}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={getProfileImageUrl()}
alt="Profile"
className="absolute inset-0 w-full h-full object-cover"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${imageLoaded ? 'opacity-100' : 'opacity-0'}`}
onLoad={() => setImageLoaded(true)}
onError={() => setImageError(true)}
/>
{/* Gradient Overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-black/20 to-transparent pointer-events-none" />

View File

@@ -37,10 +37,14 @@ export function ConnectionCard({
{/* Avatar */}
<div className="flex-shrink-0">
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative bg-[#e8e8e8]">
{/* Initials base layer */}
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
<span className="font-bold text-[#00293d] text-[32px]">{name?.[0]?.toUpperCase() || '?'}</span>
</div>
{/* Shimmer while loading, initials only on error */}
{avatarError || isPlaceholder ? (
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
<span className="font-bold text-[#00293d] text-[32px]">{name?.[0]?.toUpperCase() || '?'}</span>
</div>
) : !avatarLoaded ? (
<div className="absolute inset-0 rounded-full shimmer-loading" />
) : null}
{avatar && !isPlaceholder && !avatarError && (
<img
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}

View File

@@ -51,10 +51,14 @@ export function InvitationCard({
{/* Avatar */}
<div className="flex-shrink-0">
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative bg-[#e8e8e8]">
{/* Initials base layer */}
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
<span className="font-bold text-[#00293d] text-[32px]">{name?.[0]?.toUpperCase() || '?'}</span>
</div>
{/* Shimmer while loading, initials only on error */}
{avatarError || isPlaceholder ? (
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
<span className="font-bold text-[#00293d] text-[32px]">{name?.[0]?.toUpperCase() || '?'}</span>
</div>
) : !avatarLoaded ? (
<div className="absolute inset-0 rounded-full shimmer-loading" />
) : null}
{avatar && !isPlaceholder && !avatarError && (
<img
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}

View File

@@ -286,15 +286,16 @@ export default function AgentProfileView() {
{/* Profile Image */}
<div className="relative w-[200px] lg:w-[260px]">
<div className="w-[200px] h-[200px] lg:w-[260px] lg:h-[260px] rounded-[15px] overflow-hidden bg-[#e8e8e8] relative">
{/* Initials base layer */}
<div className="absolute inset-0 bg-[#c4d9d4] flex items-center justify-center rounded-[15px]">
<span className="font-bold text-[#00293d] text-[80px]">{agentProfile?.firstName?.[0]?.toUpperCase() || '?'}</span>
</div>
{/* Shimmer while loading, initials only on error/no image */}
{imageError || !getProfileImageUrl() ? (
<div className="absolute inset-0 bg-[#c4d9d4] flex items-center justify-center rounded-[15px]">
<span className="font-bold text-[#00293d] text-[80px]">{agentProfile?.firstName?.[0]?.toUpperCase() || '?'}</span>
</div>
) : !imageLoaded ? (
<div className="absolute inset-0 shimmer-loading rounded-[15px]" />
) : null}
{getProfileImageUrl() && (
<>
{!imageLoaded && (
<div className="absolute inset-0 shimmer-loading rounded-[15px]" />
)}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
ref={(el) => { if (el?.complete) { if (el.naturalWidth > 0) setImageLoaded(true); else setImageError(true); } }}