'use client'; import { useState, useEffect } from 'react'; import Link from 'next/link'; import Image from 'next/image'; import { agentsService, PublicAgentProfile, AgentType } from '@/services/agents.service'; import { uploadService } from '@/services/upload.service'; import type { TopProfessionalsContent } from '@/types/cms'; interface ProfessionalCardProps { name: string; subtitle: string; location: string; experience: string; expertise: string[]; imageUrl: string; isVerified: boolean; rating: number | null; } function ProfessionalCard({ name, subtitle, location, experience, expertise, imageUrl, isVerified, rating, }: ProfessionalCardProps) { const starCount = rating ? Math.round(rating) : 0; const [imgLoaded, setImgLoaded] = useState(false); const [imgError, setImgError] = useState(false); const placeholder = '/assets/icons/user-placeholder-icon.svg'; const isPlaceholder = imageUrl === placeholder; const INITIAL_TAG_COUNT = 4; return (
{/* Image */}
{/* Initials base layer */}
{name?.[0]?.toUpperCase() || '?'}
{!isPlaceholder && !imgError && ( {name} setImgLoaded(true)} onError={() => setImgError(true)} /> )}
{/* Content */}
{/* Name */}

{name}

{/* Subtitle */}

{subtitle}

{/* Verified Badge */} {isVerified && (
Verified “Verified local agent”
)} {/* Location */} {location && (

Location:

Location {location}
)} {/* Expertise */} {expertise.length > 0 && (

Expertise:

{expertise.slice(0, INITIAL_TAG_COUNT).map((tag, index) => ( {tag} ))} {expertise.length > INITIAL_TAG_COUNT && ( +{expertise.length - INITIAL_TAG_COUNT} more )}
)} {/* Experience */} {experience && (

Experience: {experience}

)} {/* Star Rating */} {starCount > 0 && (
{[...Array(5)].map((_, i) => ( Star ))}
)}
); } // Helper to extract expertise tags from agent profile function getExpertiseTags(profile: PublicAgentProfile): string[] { const tags: string[] = []; const expertiseFieldSlugs = [ 'about_me_expertise', 'expertise_areas', 'specializations', 'areas_of_expertise', 'expertise', ]; if (profile.fieldValues && profile.fieldValues.length > 0) { profile.fieldValues.forEach((fv) => { if (!expertiseFieldSlugs.includes(fv.field.slug)) return; const value = fv.jsonValue; if (Array.isArray(value)) { value.forEach((v) => { if (typeof v === 'string' && v.trim()) { const displayValue = v .split('_') .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(' '); if (!tags.includes(displayValue)) tags.push(displayValue); } }); } else if (typeof value === 'string' && value.trim()) { if (!tags.includes(value)) tags.push(value); } }); } if (profile.specializations && profile.specializations.length > 0) { profile.specializations.forEach((spec) => { if (!tags.includes(spec)) tags.push(spec); }); } return tags; } // Helper to get location string from agent profile function getLocationString(profile: PublicAgentProfile): string { if (profile.fieldValues && profile.fieldValues.length > 0) { const stateField = profile.fieldValues.find(fv => fv.field.slug === 'state'); const cityField = profile.fieldValues.find(fv => fv.field.slug === 'city'); const parts: string[] = []; if (cityField) { const val = cityField.jsonValue; if (Array.isArray(val) && val.length > 0 && typeof val[0] === 'string') { parts.push(val[0].split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ')); } else if (typeof val === 'string') { parts.push(val); } } if (stateField) { const val = stateField.jsonValue; if (Array.isArray(val) && val.length > 0 && typeof val[0] === 'string') { parts.push(val[0].split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ')); } else if (typeof val === 'string') { parts.push(val); } } if (parts.length > 0) return parts.join(', '); } const legacyParts = [profile.city, profile.state].filter(Boolean); if (legacyParts.length > 0) return legacyParts.join(', '); if (profile.serviceAreas && profile.serviceAreas.length > 0) return profile.serviceAreas[0]; return ''; } // Helper to resolve avatar URL async function resolveAvatarUrl(avatar: string | null): Promise { if (!avatar) return '/assets/icons/user-placeholder-icon.svg'; if (avatar.startsWith('http') || avatar.startsWith('/')) return avatar; try { return await uploadService.getPresignedDownloadUrl(avatar); } catch { return '/assets/icons/user-placeholder-icon.svg'; } } interface DisplayProfessional { id: string; name: string; subtitle: string; location: string; experience: string; expertise: string[]; imageUrl: string; isVerified: boolean; rating: number | null; slug: string; } const defaultContent: TopProfessionalsContent = { title: '\u201cMeet top real estate professionals\u201d', ctaText: 'Discover 5,000+ Top Real Estate Agents in Our Network.', ctaButtonText: 'Browse Experts', agents: [], lenders: [], }; export function TopProfessionals({ content }: { content?: TopProfessionalsContent }) { const data = content ?? defaultContent; const [activeTab, setActiveTab] = useState<'agents' | 'lenders'>('agents'); const [agents, setAgents] = useState([]); const [lenders, setLenders] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { const fetchProfessionals = async () => { try { // Get agent types to find Professional and Lender type IDs const agentTypes = await agentsService.getAgentTypes(); const professionalType = agentTypes.find(t => t.name === 'Professional'); const lenderType = agentTypes.find(t => t.name === 'Lender'); // Fetch both in parallel - get top 3 of each sorted by rating const [agentsResponse, lendersResponse] = await Promise.all([ professionalType ? agentsService.searchAgents({ agentTypeId: professionalType.id, limit: 3, sortBy: 'averageRating', sortOrder: 'desc', }) : Promise.resolve({ data: [], total: 0, page: 1, limit: 3, totalPages: 0 }), lenderType ? agentsService.searchAgents({ agentTypeId: lenderType.id, limit: 3, sortBy: 'averageRating', sortOrder: 'desc', }) : Promise.resolve({ data: [], total: 0, page: 1, limit: 3, totalPages: 0 }), ]); // Resolve avatars and map to display format const mapProfiles = async (profiles: PublicAgentProfile[]): Promise => { return Promise.all( profiles.map(async (profile) => { const imageUrl = await resolveAvatarUrl(profile.avatar); const expertise = getExpertiseTags(profile); const location = getLocationString(profile); const experienceYears = profile.experience ? `${profile.experience}+ years in real estate.` : ''; return { id: profile.id, name: `${profile.firstName} ${profile.lastName}`.trim(), subtitle: profile.agentType ? `(${profile.agentType.name})` : '', location, experience: experienceYears, expertise, imageUrl, isVerified: profile.isVerified, rating: profile.rating, slug: profile.slug, }; }) ); }; const [mappedAgents, mappedLenders] = await Promise.all([ mapProfiles(agentsResponse.data), mapProfiles(lendersResponse.data), ]); setAgents(mappedAgents); setLenders(mappedLenders); } catch (err) { console.error('Failed to fetch top professionals:', err); } finally { setLoading(false); } }; fetchProfessionals(); }, []); const displayData = activeTab === 'agents' ? agents : lenders; return (
{/* Section Header */}

{data.title}

{/* Tabs Container */}
{/* Agents Tab */} {/* Divider */}
{/* Lenders Tab */}
{/* Cards Grid - 3 cards + CTA sidebar */}
{loading ? ( // Loading skeleton cards <> {[1, 2, 3].map((i) => (
))} ) : displayData.length > 0 ? ( displayData.map((professional) => ( )) ) : ( // No data message

No {activeTab === 'agents' ? 'agents' : 'lenders'} available yet.

)} {/* CTA Sidebar Card */}
{/* Building Icon */}
Building
{/* Text */}

{data.ctaText}

{/* Browse Experts Button */}
); }