feat: Optimize agent profile avatar loading by resolving all presigned URLs in parallel at the page level and passing them to individual profile cards.

This commit is contained in:
pradeepkumar
2026-03-04 20:42:46 +05:30
parent 39a2998344
commit 39137b4dfc
2 changed files with 31 additions and 32 deletions

View File

@@ -58,41 +58,16 @@ function FilterSection({ title, options, selectedOptions, onToggle, showMore }:
interface ProfileCardProps {
profile: PublicAgentProfile;
resolvedAvatarUrl?: string | null;
}
function ProfileCard({ profile }: ProfileCardProps) {
function ProfileCard({ profile, resolvedAvatarUrl }: ProfileCardProps) {
const [showFullBio, setShowFullBio] = useState(false);
const [showAllTags, setShowAllTags] = useState(false);
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
const defaultImage = '/assets/demo_images/8f017153a7b4a239a4f0691234ef97dd55092282.jpg';
// Fetch presigned URL for avatar if it's an S3 key
useEffect(() => {
const fetchAvatarUrl = async () => {
if (profile.avatar && !profile.avatar.startsWith('http') && !profile.avatar.startsWith('/')) {
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);
} catch (error) {
console.error('Failed to get avatar URL:', error);
}
} else if (profile.avatar) {
setAvatarUrl(profile.avatar);
}
};
fetchAvatarUrl();
}, [profile.avatar, profile]);
const getProfileImageUrl = () => {
if (avatarUrl) return avatarUrl;
if (resolvedAvatarUrl) return resolvedAvatarUrl;
if (profile.avatar?.startsWith('/')) return profile.avatar;
return defaultImage;
};
@@ -405,6 +380,7 @@ function ProfilesPageContent() {
// State for API data
const [agents, setAgents] = useState<PublicAgentProfile[]>([]);
const [avatarUrls, setAvatarUrls] = useState<Record<string, string>>({});
const [agentTypes, setAgentTypes] = useState<AgentType[]>([]);
const [filterFields, setFilterFields] = useState<FilterField[]>([]);
const [loading, setLoading] = useState(true);
@@ -474,10 +450,9 @@ function ProfilesPageContent() {
params.agentTypeId = activeTypeId;
}
// Add location from URL params
// Add location search (searches city, state, country, and dynamic field values)
if (locationFromUrl) {
// Try to set as city first
params.city = locationFromUrl;
params.location = locationFromUrl;
}
// Add dynamic profile field filters
@@ -493,6 +468,28 @@ function ProfilesPageContent() {
}
const response = await agentsService.searchAgents(params);
// Resolve all avatar presigned URLs in parallel before rendering
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}`;
} catch {
// Skip failed URLs, card will show default image
}
} else if (agent.avatar) {
urlMap[agent.id] = agent.avatar;
}
});
await Promise.all(avatarPromises);
setAvatarUrls(urlMap);
setAgents(response.data);
setTotalPages(response.totalPages);
setTotalResults(response.total);
@@ -703,7 +700,7 @@ function ProfilesPageContent() {
{!loading && !error && agents.length > 0 && (
<div className="space-y-4">
{agents.map((profile) => (
<ProfileCard key={profile.id} profile={profile} />
<ProfileCard key={profile.id} profile={profile} resolvedAvatarUrl={avatarUrls[profile.id]} />
))}
</div>
)}

View File

@@ -72,6 +72,7 @@ export interface SaveFieldValuesResponse {
// Search parameters for public agent search
export interface SearchAgentsParams {
search?: string;
location?: string;
city?: string;
state?: string;
country?: string;
@@ -187,6 +188,7 @@ class AgentsService {
const queryParams = new URLSearchParams();
if (params.search) queryParams.set('search', params.search);
if (params.location) queryParams.set('location', params.location);
if (params.city) queryParams.set('city', params.city);
if (params.state) queryParams.set('state', params.state);
if (params.country) queryParams.set('country', params.country);