From fe531940bb28a37d055b50b7155b3e39b212dc40 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Mon, 23 Mar 2026 11:24:49 +0530 Subject: [PATCH] feat: Implement dynamic featured agent cards with image loading and CMS content integration in FeaturesSection. --- src/components/home/FeaturesSection.tsx | 225 +++++++++--------------- src/types/cms.ts | 10 ++ 2 files changed, 95 insertions(+), 140 deletions(-) diff --git a/src/components/home/FeaturesSection.tsx b/src/components/home/FeaturesSection.tsx index 686f2e8..894c536 100644 --- a/src/components/home/FeaturesSection.tsx +++ b/src/components/home/FeaturesSection.tsx @@ -1,7 +1,9 @@ 'use client'; +import { useState, useEffect } from 'react'; import Image from 'next/image'; -import type { FeaturesContent } from '@/types/cms'; +import type { FeaturesContent, FeaturedAgentItem } from '@/types/cms'; +import { resolveImageUrl } from '@/services/cms.service'; const defaultContent: FeaturesContent = { title: 'Find Trusted Real Estate Professionals On Demand', @@ -28,108 +30,70 @@ const defaultContent: FeaturesContent = { 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' }, + ], }; -const agents = [ - { - name: 'Anderson', - role: 'Rental & Investment Consultant', - rating: '4.9', - location: 'New York', - experience: '7+ years in the real estate industry.', - image: '/assets/images/agent-anderson.jpg', - position: 'top-left', - }, - { - name: 'Deepak', - role: 'Rental & Investment Consultant', - rating: '4.9', - location: 'New York', - experience: '7+ years in the real estate industry.', - image: '/assets/images/agent-deepak.jpg', - position: 'top-right', - }, - { - name: 'Daniel', - role: 'Rental & Investment Consultant', - rating: '4.9', - location: 'New York', - experience: '7+ years in the real estate industry.', - image: '/assets/images/agent-daniel.jpg', - position: 'bottom-center', - }, -]; +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]); -function AgentCard({ agent, className = '' }: { agent: typeof agents[0]; className?: string }) { return (
- {/* Agent Image */} -
- {agent.name} +
+ {imgSrc && ( + { 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 &&
}
- - {/* Agent Info */}
- {/* Name */} -

- {agent.name} -

- - {/* Role */} -

- {agent.role} -

- - {/* Verified & Rating */} +

{agent.name}

+

{agent.role}

- Verified - - Verified Agent - + Verified + Verified Agent
-
- Rating - - {agent.rating} Rating - + {agent.rating && ( +
+ Rating + {agent.rating} Rating +
+ )} +
+ {agent.location && ( +
+ Location + {agent.location}
-
- - {/* Location */} -
- Location - - {agent.location} - -
- - {/* Experience */} -

- Experience: - {agent.experience} -

+ )} + {agent.experience && ( +

+ Experience: + {agent.experience} +

+ )}
); @@ -137,6 +101,8 @@ function AgentCard({ agent, className = '' }: { agent: typeof agents[0]; classNa export function FeaturesSection({ content }: { content?: FeaturesContent }) { const data = content ?? defaultContent; + const agents = data.featuredAgents?.length ? data.featuredAgents : defaultContent.featuredAgents!; + const [activeCard, setActiveCard] = useState(0); return (
@@ -144,68 +110,47 @@ export function FeaturesSection({ content }: { content?: FeaturesContent }) {
{/* Left Side - Title & Features */}
- {/* Section Header */}
-

- {data.title} -

-

- {data.subtitle} -

+

{data.title}

+

{data.subtitle}

- - {/* Features Grid - 2x2 */}
{data.features.map((feature, index) => ( -
+
- {feature.title} + {feature.title}
-

- {feature.title} -

-

- {feature.description} -

+

{feature.title}

+

{feature.description}

))}
- {/* Right Side - Agent Cards */} + {/* Right Side - Agent Cards (Desktop) */}
- {/* Anderson - Top Left */} - - - {/* Deepak - Top Right, offset down */} - - - {/* Daniel - Bottom Left, overlapping */} - + {agents[0] && } + {agents[1] && } + {agents[2] && }
- {/* Mobile Agent Cards - Horizontal Scroll */} -
-
- {agents.map((agent, index) => ( - + {/* Mobile Agent Cards - Tab style with dots */} +
+
+ +
+ {/* Dot indicators */} +
+ {agents.map((_, index) => ( +
diff --git a/src/types/cms.ts b/src/types/cms.ts index 4d690a5..4cf5e05 100644 --- a/src/types/cms.ts +++ b/src/types/cms.ts @@ -11,10 +11,20 @@ export interface FeatureItem { description: string; } +export interface FeaturedAgentItem { + name: string; + role: string; + rating: string; + location: string; + experience: string; + imageUrl: string; +} + export interface FeaturesContent { title: string; subtitle: string; features: FeatureItem[]; + featuredAgents?: FeaturedAgentItem[]; } export interface ProfessionalItem {