Files
frontend/src/components/home/FeaturesSection.tsx

208 lines
9.5 KiB
TypeScript
Raw Normal View History

2026-01-11 22:09:41 +05:30
'use client';
import { useState, useEffect } from 'react';
import Image from 'next/image';
import type { FeaturesContent, FeaturedAgentItem } from '@/types/cms';
import { resolveImageUrl } from '@/services/cms.service';
import { agentsService } from '@/services/agents.service';
import { uploadService } from '@/services/upload.service';
const defaultContent: FeaturesContent = {
title: 'Find Trusted Real Estate Professionals On Demand',
subtitle: 'Quickly connect with the right agents exactly when you need them.',
features: [
{
iconPath: '/assets/icons/hourglass-icon.svg',
title: 'Hire Quickly',
description: 'Connect with experienced local real estate agents who match your needs and preferences. Our simple process helps you get started quickly and move forward without delays or unnecessary complexity.',
},
{
iconPath: '/assets/icons/verified-badge-icon.svg',
title: 'Verified Agents',
description: 'All agents are carefully identity-verified and background-checked to ensure trust and safety. You can confidently work with professionals who meet quality and reliability standards.',
},
{
iconPath: '/assets/icons/star-orange-icon.svg',
title: 'Top Rated',
description: 'Work with highly rated agents known for strong expertise and positive client feedback. Their consistent performance helps you make informed and confident property decisions.',
},
{
iconPath: '/assets/icons/trusted-people-icon.svg',
title: 'Trusted by Thousands',
description: 'Thousands of buyers, sellers, and renters trust our platform to find reliable agents. Our professionals help people navigate property journeys with confidence and ease.',
},
],
featuredAgents: [
{ name: 'Anderson', role: 'Rental & Investment Consultant', rating: '4.9', location: 'New York', experience: '7+ years in the real estate industry.', imageUrl: '/assets/images/agent-anderson.jpg' },
{ name: 'Deepak', role: 'Rental & Investment Consultant', rating: '4.9', location: 'New York', experience: '7+ years in the real estate industry.', imageUrl: '/assets/images/agent-deepak.jpg' },
{ name: 'Daniel', role: 'Rental & Investment Consultant', rating: '4.9', location: 'New York', experience: '7+ years in the real estate industry.', imageUrl: '/assets/images/agent-daniel.jpg' },
],
};
2026-01-11 22:09:41 +05:30
function AgentCard({ agent, className = '' }: { agent: FeaturedAgentItem; className?: string }) {
const [imgLoaded, setImgLoaded] = useState(false);
const [imgSrc, setImgSrc] = useState(agent.imageUrl);
useEffect(() => {
// Only resolve if it's an S3 key (not a URL or local path)
if (agent.imageUrl && !agent.imageUrl.startsWith('http') && !agent.imageUrl.startsWith('/')) {
resolveImageUrl(agent.imageUrl).then((url) => {
if (url) setImgSrc(url);
});
} else {
setImgSrc(agent.imageUrl);
}
}, [agent.imageUrl]);
2026-01-11 22:09:41 +05:30
return (
<div className={`bg-white border border-[#00293d]/10 rounded-[15px] w-[254px] overflow-hidden ${className}`}>
<div className="h-[161px] w-full overflow-hidden rounded-t-[15px] bg-gray-100 relative">
{imgSrc && (
<img
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setImgLoaded(true); }}
src={imgSrc}
alt={agent.name}
className={`w-full h-full object-cover transition-opacity duration-300 ${imgLoaded ? 'opacity-100' : 'opacity-0'}`}
onLoad={() => setImgLoaded(true)}
onError={() => setImgLoaded(true)}
/>
)}
{!imgLoaded && <div className="absolute inset-0 shimmer-loading" />}
</div>
<div className="p-4">
<h4 className="font-fractul font-bold text-[16px] leading-[19px] text-[#00293d]">{agent.name}</h4>
<p className="font-fractul font-normal text-[14px] leading-[17px] text-[#00293d] mt-1">{agent.role}</p>
<div className="flex items-center gap-4 mt-2">
<div className="flex items-center gap-1">
<Image src="/assets/icons/verified-icon.svg" alt="Verified" width={14} height={14} />
<span className="font-serif font-medium text-[14px] leading-[19px] text-[#00293d]">Verified Agent</span>
</div>
{agent.rating && (
<div className="flex items-center gap-1">
<Image src="/assets/icons/star-filled-icon.svg" alt="Rating" width={14} height={14} />
<span className="font-serif font-normal text-[14px] leading-[19px] text-[#00293d]">{agent.rating} Rating</span>
</div>
)}
</div>
{agent.location && (
<div className="flex items-center gap-1 mt-2">
<Image src="/assets/icons/location-filled-icon.svg" alt="Location" width={15} height={15} />
<span className="font-serif font-medium text-[14px] leading-[19px] text-[#00293d]">{agent.location}</span>
</div>
)}
{agent.experience && (
<p className="mt-2 text-[14px] leading-[19px] text-[#00293d]">
<span className="font-serif font-bold">Experience:</span>
<span className="font-serif font-normal"> {agent.experience}</span>
</p>
)}
</div>
</div>
);
}
export function FeaturesSection({ content }: { content?: FeaturesContent }) {
const data = content ?? defaultContent;
const [agents, setAgents] = useState<FeaturedAgentItem[]>([]);
const [activeCard, setActiveCard] = useState(0);
useEffect(() => {
const fetchFeaturedAgents = async () => {
try {
const featured = await agentsService.getFeaturedAgents(3);
if (featured.length > 0) {
// Map API response to FeaturedAgentItem format
const mapped = await Promise.all(
featured.map(async (agent) => {
let imageUrl = '/assets/icons/user-placeholder-icon.svg';
if (agent.avatar) {
if (agent.avatar.startsWith('http') || agent.avatar.startsWith('/')) {
imageUrl = agent.avatar;
} else {
try {
imageUrl = await uploadService.getPresignedDownloadUrl(agent.avatar);
} catch (_e) {
// use placeholder
}
}
}
return {
name: `${agent.firstName || ''} ${agent.lastName || ''}`.trim(),
role: agent.agentType?.name || '',
rating: agent.averageRating ? String(agent.averageRating) : '',
location: agent.city || agent.state || '',
experience: agent.experience ? `${agent.experience}+ years in the real estate industry.` : '',
imageUrl,
};
}),
);
setAgents(mapped);
}
} catch (err) {
console.error('Failed to fetch featured agents:', err);
}
};
fetchFeaturedAgents();
}, []);
return (
<section className="py-16 md:py-20 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex flex-col lg:flex-row gap-12 lg:gap-8">
{/* Left Side - Title & Features */}
<div className="lg:w-1/2">
<div className="mb-8">
<h2 className="font-fractul font-bold text-[32px] md:text-[40px] leading-[1.2] text-[#00293d] mb-4">{data.title}</h2>
<p className="font-fractul font-medium text-[18px] md:text-[20px] leading-[1.4] text-[#00293d]">{data.subtitle}</p>
2026-01-11 22:09:41 +05:30
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{data.features.map((feature, index) => (
<div key={index} className="border border-[#00293d]/10 rounded-[15px] p-5">
<div className="mb-3">
<Image src={feature.iconPath} alt={feature.title} width={35} height={35} />
</div>
<h3 className="font-fractul font-semibold text-[20px] leading-[24px] text-[#00293d] mb-2">{feature.title}</h3>
<p className="font-serif font-normal text-[14px] leading-[19px] text-[#00293d]">{feature.description}</p>
</div>
))}
</div>
</div>
{/* Right Side - Agent Cards (Desktop) */}
{agents.length > 0 && (
<div className="lg:w-1/2 relative min-h-[650px] hidden lg:block">
{agents[0] && <AgentCard agent={agents[0]} className="absolute top-0 left-0 shadow-lg z-10" />}
{agents[1] && <AgentCard agent={agents[1]} className="absolute top-[170px] right-0 shadow-lg z-20" />}
{agents[2] && <AgentCard agent={agents[2]} className="absolute top-[320px] left-[60px] shadow-lg z-30" />}
</div>
)}
{/* Mobile Agent Cards - Tab style with dots */}
{agents.length > 0 && (
<div className="lg:hidden">
<div className="flex justify-center">
<AgentCard agent={agents[activeCard]} className="shadow-lg" />
</div>
{/* Dot indicators */}
<div className="flex justify-center gap-2 mt-4">
{agents.map((_, index) => (
<button
key={index}
onClick={() => setActiveCard(index)}
className={`rounded-full transition-all ${
index === activeCard
? 'w-[10px] h-[10px] bg-[#00293d]'
: 'w-[8px] h-[8px] bg-[#00293d]/30'
}`}
/>
))}
</div>
</div>
)}
2026-01-11 22:09:41 +05:30
</div>
</div>
</section>
);
}