'use client'; import { useState, useEffect } from 'react'; import Link from 'next/link'; import Image from 'next/image'; 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 */}
{/* Shimmer while loading, initials only on error */} {imgError || isPlaceholder ? (
{name?.[0]?.toUpperCase() || '?'}
) : !imgLoaded ? (
) : null} {!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 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 loadProfessionals = async () => { try { // Map CMS data to display format, resolving avatar URLs const mapCmsEntries = async ( entries: TopProfessionalsContent['agents'], ): Promise => { return Promise.all( entries.map(async (entry, index) => { const imageUrl = await resolveAvatarUrl(entry.imageUrl || null); return { id: `cms-${index}`, name: entry.name, subtitle: entry.subtitle || '', location: entry.location || '', experience: entry.experience || '', expertise: entry.expertise || [], imageUrl, isVerified: false, rating: null, slug: '', }; }), ); }; const [mappedAgents, mappedLenders] = await Promise.all([ mapCmsEntries(data.agents || []), mapCmsEntries(data.lenders || []), ]); setAgents(mappedAgents); setLenders(mappedLenders); } catch (err) { console.error('Failed to load top professionals:', err); } finally { setLoading(false); } }; loadProfessionals(); }, [data.agents, data.lenders]); 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 */}
); }