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:
@@ -58,41 +58,16 @@ function FilterSection({ title, options, selectedOptions, onToggle, showMore }:
|
|||||||
|
|
||||||
interface ProfileCardProps {
|
interface ProfileCardProps {
|
||||||
profile: PublicAgentProfile;
|
profile: PublicAgentProfile;
|
||||||
|
resolvedAvatarUrl?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProfileCard({ profile }: ProfileCardProps) {
|
function ProfileCard({ profile, resolvedAvatarUrl }: ProfileCardProps) {
|
||||||
const [showFullBio, setShowFullBio] = useState(false);
|
const [showFullBio, setShowFullBio] = useState(false);
|
||||||
const [showAllTags, setShowAllTags] = useState(false);
|
const [showAllTags, setShowAllTags] = useState(false);
|
||||||
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
|
|
||||||
const defaultImage = '/assets/demo_images/8f017153a7b4a239a4f0691234ef97dd55092282.jpg';
|
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 = () => {
|
const getProfileImageUrl = () => {
|
||||||
if (avatarUrl) return avatarUrl;
|
if (resolvedAvatarUrl) return resolvedAvatarUrl;
|
||||||
if (profile.avatar?.startsWith('/')) return profile.avatar;
|
if (profile.avatar?.startsWith('/')) return profile.avatar;
|
||||||
return defaultImage;
|
return defaultImage;
|
||||||
};
|
};
|
||||||
@@ -405,6 +380,7 @@ function ProfilesPageContent() {
|
|||||||
|
|
||||||
// State for API data
|
// State for API data
|
||||||
const [agents, setAgents] = useState<PublicAgentProfile[]>([]);
|
const [agents, setAgents] = useState<PublicAgentProfile[]>([]);
|
||||||
|
const [avatarUrls, setAvatarUrls] = useState<Record<string, string>>({});
|
||||||
const [agentTypes, setAgentTypes] = useState<AgentType[]>([]);
|
const [agentTypes, setAgentTypes] = useState<AgentType[]>([]);
|
||||||
const [filterFields, setFilterFields] = useState<FilterField[]>([]);
|
const [filterFields, setFilterFields] = useState<FilterField[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -474,10 +450,9 @@ function ProfilesPageContent() {
|
|||||||
params.agentTypeId = activeTypeId;
|
params.agentTypeId = activeTypeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add location from URL params
|
// Add location search (searches city, state, country, and dynamic field values)
|
||||||
if (locationFromUrl) {
|
if (locationFromUrl) {
|
||||||
// Try to set as city first
|
params.location = locationFromUrl;
|
||||||
params.city = locationFromUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add dynamic profile field filters
|
// Add dynamic profile field filters
|
||||||
@@ -493,6 +468,28 @@ function ProfilesPageContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await agentsService.searchAgents(params);
|
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);
|
setAgents(response.data);
|
||||||
setTotalPages(response.totalPages);
|
setTotalPages(response.totalPages);
|
||||||
setTotalResults(response.total);
|
setTotalResults(response.total);
|
||||||
@@ -703,7 +700,7 @@ function ProfilesPageContent() {
|
|||||||
{!loading && !error && agents.length > 0 && (
|
{!loading && !error && agents.length > 0 && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{agents.map((profile) => (
|
{agents.map((profile) => (
|
||||||
<ProfileCard key={profile.id} profile={profile} />
|
<ProfileCard key={profile.id} profile={profile} resolvedAvatarUrl={avatarUrls[profile.id]} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export interface SaveFieldValuesResponse {
|
|||||||
// Search parameters for public agent search
|
// Search parameters for public agent search
|
||||||
export interface SearchAgentsParams {
|
export interface SearchAgentsParams {
|
||||||
search?: string;
|
search?: string;
|
||||||
|
location?: string;
|
||||||
city?: string;
|
city?: string;
|
||||||
state?: string;
|
state?: string;
|
||||||
country?: string;
|
country?: string;
|
||||||
@@ -187,6 +188,7 @@ class AgentsService {
|
|||||||
const queryParams = new URLSearchParams();
|
const queryParams = new URLSearchParams();
|
||||||
|
|
||||||
if (params.search) queryParams.set('search', params.search);
|
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.city) queryParams.set('city', params.city);
|
||||||
if (params.state) queryParams.set('state', params.state);
|
if (params.state) queryParams.set('state', params.state);
|
||||||
if (params.country) queryParams.set('country', params.country);
|
if (params.country) queryParams.set('country', params.country);
|
||||||
|
|||||||
Reference in New Issue
Block a user