refactor: replace team member section with feature cards and add associated SVG icons
This commit is contained in:
@@ -55,10 +55,14 @@ export default function AboutPage() {
|
||||
}
|
||||
break;
|
||||
case 'team':
|
||||
if (Array.isArray(c.members) && c.members.length > 0) {
|
||||
if (c.title || c.subtitle || c.intro || (Array.isArray(c.cards) && c.cards.length > 0)) {
|
||||
const teamData = c as unknown as AboutTeamContent;
|
||||
for (const member of teamData.members) {
|
||||
member.imageUrl = await resolveImageUrl(member.imageUrl);
|
||||
if (Array.isArray(teamData.cards)) {
|
||||
for (const card of teamData.cards) {
|
||||
card.iconPath = await resolveImageUrl(card.iconPath);
|
||||
}
|
||||
} else {
|
||||
teamData.cards = [];
|
||||
}
|
||||
setTeam(teamData);
|
||||
}
|
||||
@@ -196,24 +200,32 @@ export default function AboutPage() {
|
||||
)}
|
||||
|
||||
{/* Team Section */}
|
||||
{team && team.members.length > 0 && (
|
||||
{team && (team.title || team.subtitle || team.intro || team.cards.length > 0) && (
|
||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-16 lg:pb-20">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="font-fractul font-bold text-[30px] text-[#00293d] mb-3">
|
||||
{team.title}
|
||||
</h2>
|
||||
<p className="font-serif text-[14px] text-[#00293d] max-w-[471px] mx-auto">
|
||||
{team.subtitle}
|
||||
</p>
|
||||
<p className="font-serif text-[14px] text-[#00293d] max-w-[600px] mx-auto mt-4">
|
||||
RE-Quest is a unique platform in real estate where no agent can pay to be pushed to the top. RE-Quest elevates ethical, skilled, service-oriented professionals who genuinely care about their clients with no sponsorship tiers. With RE-Quest you receive:
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-4xl mx-auto">
|
||||
{team.members.map((member, index) => (
|
||||
<TeamCard key={index} member={member} />
|
||||
))}
|
||||
{team.title && (
|
||||
<h2 className="font-fractul font-bold text-[30px] text-[#00293d] mb-3">
|
||||
{team.title}
|
||||
</h2>
|
||||
)}
|
||||
{team.subtitle && (
|
||||
<p className="font-serif text-[14px] text-[#00293d] max-w-[471px] mx-auto">
|
||||
{team.subtitle}
|
||||
</p>
|
||||
)}
|
||||
{team.intro && (
|
||||
<p className="font-serif text-[14px] text-[#00293d] max-w-[600px] mx-auto mt-4 whitespace-pre-line">
|
||||
{team.intro}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{team.cards.length > 0 && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-5xl mx-auto items-stretch">
|
||||
{team.cards.map((card, index) => (
|
||||
<TeamCard key={index} card={card} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
@@ -334,12 +346,14 @@ function AboutSkeleton() {
|
||||
<div className="shimmer-loading h-[36px] w-[350px] max-w-full rounded-lg" />
|
||||
<div className="shimmer-loading h-[14px] w-[400px] max-w-full rounded-lg" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-4xl mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-5xl mx-auto">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="border border-[#00293d]/10 rounded-[15px] p-4 flex flex-col items-center gap-3">
|
||||
<div className="shimmer-loading w-full h-[211px] rounded-[15px]" />
|
||||
<div className="shimmer-loading h-[14px] w-[80px] rounded-lg" />
|
||||
<div className="shimmer-loading h-[12px] w-[100px] rounded-lg" />
|
||||
<div key={i} className="border border-[#00293d]/10 rounded-[15px] p-6 flex flex-col items-center gap-3">
|
||||
<div className="shimmer-loading w-[36px] h-[36px] rounded-full" />
|
||||
<div className="shimmer-loading h-[16px] w-[140px] rounded-lg" />
|
||||
<div className="shimmer-loading h-[12px] w-full rounded-lg" />
|
||||
<div className="shimmer-loading h-[12px] w-[90%] rounded-lg" />
|
||||
<div className="shimmer-loading h-[12px] w-[80%] rounded-lg" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -436,38 +450,40 @@ function FeatureCard({ feature }: { feature: AboutFeaturesContent['features'][nu
|
||||
// Team card — shimmer image while loading
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function TeamCard({ member }: { member: AboutTeamContent['members'][number] }) {
|
||||
const [imgStatus, setImgStatus] = useState<'loading' | 'loaded' | 'error'>(
|
||||
member.imageUrl ? 'loading' : 'error'
|
||||
function TeamCard({ card }: { card: AboutTeamContent['cards'][number] }) {
|
||||
const [iconStatus, setIconStatus] = useState<'loading' | 'loaded' | 'error'>(
|
||||
card.iconPath ? 'loading' : 'error'
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setImgStatus(member.imageUrl ? 'loading' : 'error');
|
||||
}, [member.imageUrl]);
|
||||
setIconStatus(card.iconPath ? 'loading' : 'error');
|
||||
}, [card.iconPath]);
|
||||
|
||||
return (
|
||||
<div className="border border-[#00293d]/10 rounded-[15px] p-4 text-center">
|
||||
<div className="w-full h-[211px] rounded-[15px] overflow-hidden mb-4 border border-[#00293d]/10 bg-gray-50">
|
||||
{imgStatus === 'loading' && (
|
||||
<div className="w-full h-full shimmer-loading rounded-[15px]" />
|
||||
)}
|
||||
{member.imageUrl && imgStatus !== 'error' && (
|
||||
/* eslint-disable-next-line @next/next/no-img-element */
|
||||
<img
|
||||
ref={(el) => { if (el?.complete) setImgStatus(el.naturalWidth > 0 ? 'loaded' : 'error'); }}
|
||||
src={member.imageUrl}
|
||||
alt={member.name}
|
||||
className={`w-full h-full object-cover transition-opacity duration-300 ${imgStatus === 'loaded' ? 'opacity-100' : 'opacity-0'}`}
|
||||
onLoad={() => setImgStatus('loaded')}
|
||||
onError={() => setImgStatus('error')}
|
||||
/>
|
||||
)}
|
||||
<div className="border border-[#00293d]/10 rounded-[15px] p-6 text-center h-full flex flex-col">
|
||||
<div className="flex items-center justify-center mb-4 h-[36px]">
|
||||
<div className="relative w-[36px] h-[36px]">
|
||||
{iconStatus === 'loading' && (
|
||||
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
||||
)}
|
||||
{card.iconPath && iconStatus !== 'error' && (
|
||||
/* eslint-disable-next-line @next/next/no-img-element */
|
||||
<img
|
||||
ref={(el) => { if (el?.complete) setIconStatus(el.naturalWidth > 0 ? 'loaded' : 'error'); }}
|
||||
src={card.iconPath}
|
||||
alt=""
|
||||
className={`w-[36px] h-[36px] object-contain transition-opacity duration-300 ${iconStatus === 'loaded' ? 'opacity-100' : 'opacity-0'}`}
|
||||
onLoad={() => setIconStatus('loaded')}
|
||||
onError={() => setIconStatus('error')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="font-fractul font-bold text-[14px] text-black">
|
||||
{member.name}
|
||||
<h3 className="font-fractul font-bold text-[16px] text-[#00293d] mb-3">
|
||||
{card.title}
|
||||
</h3>
|
||||
<p className="font-fractul font-light text-[14px] text-black">
|
||||
{member.role}
|
||||
<p className="font-serif text-[14px] text-[#00293d] leading-relaxed">
|
||||
{card.description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -95,16 +95,17 @@ export interface AboutFeaturesContent {
|
||||
features: AboutFeatureItem[];
|
||||
}
|
||||
|
||||
export interface AboutTeamMember {
|
||||
name: string;
|
||||
role: string;
|
||||
imageUrl: string;
|
||||
export interface AboutTeamCard {
|
||||
title: string;
|
||||
description: string;
|
||||
iconPath: string;
|
||||
}
|
||||
|
||||
export interface AboutTeamContent {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
members: AboutTeamMember[];
|
||||
intro: string;
|
||||
cards: AboutTeamCard[];
|
||||
}
|
||||
|
||||
export interface AboutCtaContent {
|
||||
|
||||
Reference in New Issue
Block a user