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 */}
<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()}
src={getProfileImageUrl()!}
alt="Profile"
className="w-full h-full object-cover"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = defaultImage;
setImageError(true);
}}
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>

View File

@@ -64,12 +64,10 @@ interface ProfileCardProps {
function ProfileCard({ profile, resolvedAvatarUrl }: ProfileCardProps) {
const [showFullBio, setShowFullBio] = useState(false);
const [showAllTags, setShowAllTags] = useState(false);
const defaultImage = '/assets/demo_images/8f017153a7b4a239a4f0691234ef97dd55092282.jpg';
const getProfileImageUrl = () => {
const getProfileImageUrl = (): string | null => {
if (resolvedAvatarUrl) return resolvedAvatarUrl;
if (profile.avatar?.startsWith('/')) return profile.avatar;
return defaultImage;
return null;
};
// Format member since date
@@ -237,17 +235,29 @@ function ProfileCard({ profile, resolvedAvatarUrl }: ProfileCardProps) {
<div className="bg-white rounded-[20px] p-5 flex gap-5 shadow-[0px_10px_20px_rgba(217,217,217,0.5)]">
{/* Profile Image & View Profile Button */}
<div className="flex-shrink-0 flex flex-col items-center">
<div className="relative w-[200px] h-[200px] rounded-[15px] overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<div className="relative w-[200px] h-[200px] rounded-[15px] overflow-hidden bg-[#e8e8e8]">
{getProfileImageUrl() ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img
src={getProfileImageUrl()}
src={getProfileImageUrl()!}
alt={`${profile.firstName} ${profile.lastName}`}
className="w-full h-full object-cover"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = defaultImage;
target.style.display = 'none';
}}
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<Image
src="/assets/icons/user-placeholder-icon.svg"
alt={`${profile.firstName} ${profile.lastName}`}
width={64}
height={64}
className="opacity-40"
/>
</div>
)}
</div>
<Link href={`/user/profile/${profile.id}`} className="mt-4">
<button className="w-[113px] py-2.5 bg-[#e58625] hover:bg-[#d47720] text-[#00293d] font-fractul font-bold text-[14px] rounded-[15px] transition-colors">
@@ -469,19 +479,15 @@ function ProfilesPageContent() {
const response = await agentsService.searchAgents(params);
// Resolve all avatar presigned URLs in parallel before rendering
// Resolve all avatar URLs via presigned-download-url API
const urlMap: Record<string, string> = {};
const avatarPromises = response.data.map(async (agent) => {
if (agent.avatar && !agent.avatar.startsWith('http') && !agent.avatar.startsWith('/')) {
try {
const presignedUrl = await uploadService.getPresignedDownloadUrl(agent.avatar);
const agentWithTimestamp = agent as unknown as { updatedAt?: string };
const cacheBuster = agentWithTimestamp.updatedAt
? new Date(agentWithTimestamp.updatedAt).getTime()
: Date.now();
urlMap[agent.id] = `${presignedUrl}&_t=${cacheBuster}`;
urlMap[agent.id] = presignedUrl;
} catch {
// Skip failed URLs, card will show default image
// Skip failed URLs, card will show placeholder
}
} else if (agent.avatar) {
urlMap[agent.id] = agent.avatar;