feat: Replace hardcoded default profile image with a dynamic placeholder and remove cache-busting from presigned URLs.

This commit is contained in:
pradeepkumar
2026-03-07 22:19:10 +05:30
parent 1e165d395c
commit 1baad05e79
2 changed files with 54 additions and 51 deletions

View File

@@ -86,8 +86,6 @@ export default function AgentProfileView() {
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus | null>(null);
const [connectionRequestId, setConnectionRequestId] = useState<string | null>(null);
const defaultImage = '/assets/demo_images/8f017153a7b4a239a4f0691234ef97dd55092282.jpg';
// Helper to check if avatar is an S3 key (not a full URL or local path)
const isS3Key = (avatar: string | null | undefined): boolean => {
if (!avatar) return false;
@@ -149,20 +147,11 @@ export default function AgentProfileView() {
if (profile.avatar && isS3Key(profile.avatar)) {
try {
const presignedUrl = await uploadService.getPresignedDownloadUrl(profile.avatar);
// Add cache-busting parameter to force browser to fetch fresh image
// This prevents showing old cached image when avatar is updated
const profileWithTimestamp = profile as unknown as { updatedAt?: string };
const cacheBuster = profileWithTimestamp.updatedAt
? new Date(profileWithTimestamp.updatedAt).getTime()
: Date.now();
const urlWithCacheBuster = `${presignedUrl}&_t=${cacheBuster}`;
setAvatarUrl(urlWithCacheBuster);
setAvatarUrl(presignedUrl);
} catch (avatarErr) {
console.error('Failed to get avatar URL:', avatarErr);
// Don't fail the whole page, just use default image
}
} else if (profile.avatar) {
// It's already a full URL or local path
setAvatarUrl(profile.avatar);
}
} catch (err) {
@@ -213,9 +202,9 @@ export default function AgentProfileView() {
};
const getProfileImageUrl = () => {
// If image failed to load, return default
// If image failed to load, return null
if (imageError) {
return defaultImage;
return null;
}
// If we have a presigned URL from S3, use it
@@ -225,7 +214,7 @@ export default function AgentProfileView() {
// Check for null, undefined, or empty string
if (!agentProfile?.avatar || agentProfile.avatar.trim() === '') {
return defaultImage;
return null;
}
// For relative paths (local assets), return as-is
@@ -233,8 +222,8 @@ export default function AgentProfileView() {
return agentProfile.avatar;
}
// Default fallback
return defaultImage;
// No image available
return null;
};
// Format member since date
@@ -293,18 +282,26 @@ export default function AgentProfileView() {
<div className="w-full lg:w-[280px] flex-shrink-0 space-y-4 flex flex-col items-center lg:items-start">
{/* 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">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={getProfileImageUrl()}
alt="Profile"
className="w-full h-full object-cover"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = defaultImage;
setImageError(true);
}}
/>
<div className="w-[200px] h-[200px] lg:w-[260px] lg:h-[260px] rounded-[15px] overflow-hidden bg-[#e8e8e8]">
{getProfileImageUrl() ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img
src={getProfileImageUrl()!}
alt="Profile"
className="w-full h-full object-cover"
onError={() => setImageError(true)}
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<Image
src="/assets/icons/user-placeholder-icon.svg"
alt="Profile"
width={80}
height={80}
className="opacity-40"
/>
</div>
)}
{/* Gradient Overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-black/20 to-transparent pointer-events-none" />
</div>