feat: Implement CMS integration for the About page, including new content types and dynamic data loading with default fallbacks.

This commit is contained in:
pradeepkumar
2026-02-24 03:30:22 +05:30
parent b235378815
commit 20857a9d59
2 changed files with 233 additions and 132 deletions

View File

@@ -1,47 +1,107 @@
'use client'; 'use client';
import { useState, useEffect } from 'react';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { CommonHeader } from '@/components/layout/CommonHeader'; import { CommonHeader } from '@/components/layout/CommonHeader';
import { Footer } from '@/components/layout/Footer'; import { Footer } from '@/components/layout/Footer';
import { cmsService } from '@/services/cms.service';
import type {
AboutHeroContent,
AboutStatsContent,
AboutFeaturesContent,
AboutTeamContent,
AboutCtaContent,
CmsContentRecord,
} from '@/types/cms';
const teamMembers = [ // ---------------------------------------------------------------------------
{ // Hardcoded defaults (used when no CMS content is saved)
name: 'Andrew', // ---------------------------------------------------------------------------
role: 'Founder & CEO',
image: '/assets/images/professional-1.jpg',
},
{
name: 'Thomas',
role: 'Co-Founder',
image: '/assets/images/professional-2.jpg',
},
{
name: 'Darren',
role: 'Advisor',
image: '/assets/images/professional-3.jpg',
},
];
const features = [ const defaultHero: AboutHeroContent = {
{ badge: 'Our Mission',
icon: '/assets/icons/verified-badge-orange.svg', headline: 'Connecting You with Trusted Agents.',
title: 'Verified Agents', description:
description: 'Every agent is vetted and verified for credentials, experience, and customer feedback.', 'We believe finding the right home shouldn\u2019t be complicated. Our platform bridges the gap between buyers, sellers, and verified real estate agents for a smooth and transparent experience.',
}, ctaButtonText: 'Find Your Agent',
{ bannerImageUrl: '/assets/images/user_home_top.png',
icon: '/assets/icons/shield-verified-icon.svg', bannerOverlayText: 'Building the future of property.',
title: 'Total Transparency', };
description: 'Access honest reviews, transaction history, and verified credentials before connecting.',
}, const defaultStats: AboutStatsContent = {
{ stats: [
icon: '/assets/icons/home-icon.svg', { value: '15k+', label: 'Verified Agents' },
title: 'Simple Journey', { value: '98%', label: 'Customer Satisfaction' },
description: 'From search to settlement, we make finding and working with agents effortless.', ],
}, };
];
const defaultFeatures: AboutFeaturesContent = {
badge: 'Why Choose Us',
features: [
{
iconPath: '/assets/icons/verified-badge-orange.svg',
title: 'Verified Agents',
description:
'Every agent is vetted and verified for credentials, experience, and customer feedback.',
},
{
iconPath: '/assets/icons/shield-verified-icon.svg',
title: 'Total Transparency',
description:
'Access honest reviews, transaction history, and verified credentials before connecting.',
},
{
iconPath: '/assets/icons/home-icon.svg',
title: 'Simple Journey',
description:
'From search to settlement, we make finding and working with agents effortless.',
},
],
};
const defaultTeam: AboutTeamContent = {
title: 'Meet the minds behind the platform.',
subtitle:
'Our team is passionate about making real estate connections easier and more transparent for everyone.',
members: [
{ name: 'Andrew', role: 'Founder & CEO', imageUrl: '/assets/images/professional-1.jpg' },
{ name: 'Thomas', role: 'Co-Founder', imageUrl: '/assets/images/professional-2.jpg' },
{ name: 'Darren', role: 'Advisor', imageUrl: '/assets/images/professional-3.jpg' },
],
};
const defaultCta: AboutCtaContent = {
title: 'Ready to find an agent?',
description: 'Discover trusted agents and start your property journey with confidence.',
buttonText: 'Start Your Search',
};
// ---------------------------------------------------------------------------
// Page component
// ---------------------------------------------------------------------------
export default function AboutPage() { export default function AboutPage() {
const [hero, setHero] = useState<AboutHeroContent>(defaultHero);
const [stats, setStats] = useState<AboutStatsContent>(defaultStats);
const [features, setFeatures] = useState<AboutFeaturesContent>(defaultFeatures);
const [team, setTeam] = useState<AboutTeamContent>(defaultTeam);
const [cta, setCta] = useState<AboutCtaContent>(defaultCta);
useEffect(() => {
cmsService.getPageContent('about').then((sections) => {
sections.forEach((s: CmsContentRecord) => {
switch (s.sectionKey) {
case 'hero': setHero(s.content as AboutHeroContent); break;
case 'stats': setStats(s.content as AboutStatsContent); break;
case 'features': setFeatures(s.content as AboutFeaturesContent); break;
case 'team': setTeam(s.content as AboutTeamContent); break;
case 'cta': setCta(s.content as AboutCtaContent); break;
}
});
});
}, []);
return ( return (
<div className="min-h-screen bg-white flex flex-col"> <div className="min-h-screen bg-white flex flex-col">
{/* Header */} {/* Header */}
@@ -63,18 +123,18 @@ export default function AboutPage() {
height={19} height={19}
/> />
<span className="font-fractul font-medium text-[14px] text-[#00293d]"> <span className="font-fractul font-medium text-[14px] text-[#00293d]">
Our Mission {hero.badge}
</span> </span>
</div> </div>
{/* Headline */} {/* Headline */}
<h1 className="font-fractul font-bold text-[32px] lg:text-[40px] text-[#00293d] leading-tight mb-6"> <h1 className="font-fractul font-bold text-[32px] lg:text-[40px] text-[#00293d] leading-tight mb-6">
Connecting You with Trusted Agents. {hero.headline}
</h1> </h1>
{/* Description */} {/* Description */}
<p className="font-serif text-[14px] text-[#00293d] leading-relaxed mb-8 max-w-[550px] mx-auto"> <p className="font-serif text-[14px] text-[#00293d] leading-relaxed mb-8 max-w-[550px] mx-auto">
We believe finding the right home shouldn't be complicated. Our platform bridges the gap between buyers, sellers, and verified real estate agents for a smooth and transparent experience. {hero.description}
</p> </p>
{/* CTA Button */} {/* CTA Button */}
@@ -82,7 +142,7 @@ export default function AboutPage() {
href="/agents" href="/agents"
className="inline-flex items-center justify-center w-[160px] h-[46px] border border-[#00293d] rounded-full font-fractul font-medium text-[14px] text-[#00293d] hover:bg-gray-50 transition-colors" className="inline-flex items-center justify-center w-[160px] h-[46px] border border-[#00293d] rounded-full font-fractul font-medium text-[14px] text-[#00293d] hover:bg-gray-50 transition-colors"
> >
Find Your Agent {hero.ctaButtonText}
</Link> </Link>
</div> </div>
</section> </section>
@@ -90,118 +150,112 @@ export default function AboutPage() {
{/* Hero Image with Overlay Text */} {/* Hero Image with Overlay Text */}
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-8"> <section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-8">
<div className="relative rounded-[20px] overflow-hidden"> <div className="relative rounded-[20px] overflow-hidden">
<Image {/* eslint-disable-next-line @next/next/no-img-element */}
src="/assets/images/user_home_top.png" <img
src={hero.bannerImageUrl || defaultHero.bannerImageUrl}
alt="City buildings" alt="City buildings"
width={1200}
height={500}
className="w-full h-auto object-cover" className="w-full h-auto object-cover"
priority
/> />
{/* Overlay Text */} {/* Overlay Text */}
<div className="absolute bottom-6 left-6 lg:bottom-10 lg:left-10"> <div className="absolute bottom-6 left-6 lg:bottom-10 lg:left-10">
<h2 className="font-fractul font-bold text-[20px] lg:text-[24px] text-[#00293d]"> <h2 className="font-fractul font-bold text-[20px] lg:text-[24px] text-[#00293d]">
Building the future of property. {hero.bannerOverlayText}
</h2> </h2>
</div> </div>
</div> </div>
</section> </section>
{/* Stats Section */} {/* Stats Section */}
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> {stats.stats.length > 0 && (
<div className="flex items-center justify-center gap-16 lg:gap-32"> <section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="text-center"> <div className="flex items-center justify-center gap-16 lg:gap-32">
<p className="font-fractul font-bold text-[28px] lg:text-[32px] text-[#e58625]"> {stats.stats.map((stat, index) => (
15k+ <div key={index} className="text-center">
</p> <p className="font-fractul font-bold text-[28px] lg:text-[32px] text-[#e58625]">
<p className="font-serif text-[14px] text-[#00293d]"> {stat.value}
Verified Agents </p>
</p> <p className="font-serif text-[14px] text-[#00293d]">
{stat.label}
</p>
</div>
))}
</div> </div>
<div className="text-center"> </section>
<p className="font-fractul font-bold text-[28px] lg:text-[32px] text-[#e58625]"> )}
98%
</p>
<p className="font-serif text-[14px] text-[#00293d]">
Customer Satisfaction
</p>
</div>
</div>
</section>
{/* Why Choose Us Section */} {/* Why Choose Us Section */}
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 lg:py-20"> {features.features.length > 0 && (
{/* Section Header */} <section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 lg:py-20">
<div className="text-center mb-12"> {/* Section Header */}
<span className="bg-[#e58625]/10 text-[#e58625] font-fractul font-semibold text-[14px] px-4 py-2 rounded-full"> <div className="text-center mb-12">
Why Choose Us <span className="bg-[#e58625]/10 text-[#e58625] font-fractul font-semibold text-[14px] px-4 py-2 rounded-full">
</span> {features.badge}
</div> </span>
</div>
{/* Feature Cards */} {/* Feature Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{features.map((feature, index) => ( {features.features.map((feature, index) => (
<div <div
key={index} key={index}
className="border border-[#00293d]/10 rounded-[15px] p-6 text-center hover:shadow-lg transition-shadow" className="border border-[#00293d]/10 rounded-[15px] p-6 text-center hover:shadow-lg transition-shadow"
> >
<div className="w-[60px] h-[60px] bg-[#e58625]/10 rounded-full flex items-center justify-center mx-auto mb-4"> <div className="w-[60px] h-[60px] bg-[#e58625]/10 rounded-full flex items-center justify-center mx-auto mb-4">
<Image <Image
src={feature.icon} src={feature.iconPath || '/assets/icons/verified-badge-orange.svg'}
alt="" alt=""
width={28} width={28}
height={28} height={28}
/> />
</div>
<h3 className="font-fractul font-bold text-[18px] text-[#00293d] mb-3">
{feature.title}
</h3>
<p className="font-serif text-[14px] text-[#00293d] leading-relaxed">
{feature.description}
</p>
</div> </div>
<h3 className="font-fractul font-bold text-[18px] text-[#00293d] mb-3"> ))}
{feature.title} </div>
</h3> </section>
<p className="font-serif text-[14px] text-[#00293d] leading-relaxed"> )}
{feature.description}
</p>
</div>
))}
</div>
</section>
{/* Team Section */} {/* Team Section */}
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-16 lg:pb-20"> {team.members.length > 0 && (
{/* Section Header */} <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"> {/* Section Header */}
<h2 className="font-fractul font-bold text-[24px] lg:text-[28px] text-[#00293d] mb-3"> <div className="text-center mb-12">
Meet the minds behind the platform. <h2 className="font-fractul font-bold text-[24px] lg:text-[28px] text-[#00293d] mb-3">
</h2> {team.title}
<p className="font-serif text-[14px] text-[#00293d] max-w-[500px] mx-auto"> </h2>
Our team is passionate about making real estate connections easier and more transparent for everyone. <p className="font-serif text-[14px] text-[#00293d] max-w-[500px] mx-auto">
</p> {team.subtitle}
</div> </p>
</div>
{/* Team Cards */} {/* Team Cards */}
<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-4xl mx-auto">
{teamMembers.map((member, index) => ( {team.members.map((member, index) => (
<div <div key={index} className="text-center">
key={index} <div className="w-[180px] h-[180px] rounded-full overflow-hidden mx-auto mb-4 border-4 border-[#e58625]/20 bg-gray-100">
className="text-center" {/* eslint-disable-next-line @next/next/no-img-element */}
> <img
<div className="w-[180px] h-[180px] rounded-full overflow-hidden mx-auto mb-4 border-4 border-[#e58625]/20"> src={member.imageUrl || '/assets/images/professional-1.jpg'}
<Image alt={member.name}
src={member.image} className="w-full h-full object-cover"
alt={member.name} />
width={180} </div>
height={180} <h3 className="font-fractul font-bold text-[18px] text-[#00293d] mb-1">
className="w-full h-full object-cover" {member.name}
/> </h3>
<p className="font-serif text-[14px] text-[#e58625]">
{member.role}
</p>
</div> </div>
<h3 className="font-fractul font-bold text-[18px] text-[#00293d] mb-1"> ))}
{member.name} </div>
</h3> </section>
<p className="font-serif text-[14px] text-[#e58625]"> )}
{member.role}
</p>
</div>
))}
</div>
</section>
{/* CTA Section */} {/* CTA Section */}
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-16"> <section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-16">
@@ -209,16 +263,16 @@ export default function AboutPage() {
<div className="flex flex-col lg:flex-row items-center justify-between gap-6"> <div className="flex flex-col lg:flex-row items-center justify-between gap-6">
<div className="lg:max-w-[400px] text-center lg:text-left"> <div className="lg:max-w-[400px] text-center lg:text-left">
<h2 className="font-fractul font-bold text-[24px] lg:text-[28px] text-[#00293d] mb-3"> <h2 className="font-fractul font-bold text-[24px] lg:text-[28px] text-[#00293d] mb-3">
Ready to find an agent? {cta.title}
</h2> </h2>
<p className="font-serif text-[13px] text-[#00293d] mb-5"> <p className="font-serif text-[13px] text-[#00293d] mb-5">
Discover trusted agents and start your property journey with confidence. {cta.description}
</p> </p>
<Link <Link
href="/agents" href="/agents"
className="inline-flex items-center gap-2 bg-[#e58625] text-white px-5 py-2.5 rounded-full font-fractul font-medium text-[13px] hover:bg-[#d47720] transition-colors" className="inline-flex items-center gap-2 bg-[#e58625] text-white px-5 py-2.5 rounded-full font-fractul font-medium text-[13px] hover:bg-[#d47720] transition-colors"
> >
Start Your Search {cta.buttonText}
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 12H19M19 12L12 5M19 12L12 19" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/> <path d="M5 12H19M19 12L12 5M19 12L12 19" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg> </svg>

View File

@@ -56,6 +56,53 @@ export interface TestimonialsContent {
testimonials: TestimonialItem[]; testimonials: TestimonialItem[];
} }
export interface AboutHeroContent {
badge: string;
headline: string;
description: string;
ctaButtonText: string;
bannerImageUrl: string;
bannerOverlayText: string;
}
export interface AboutStatItem {
value: string;
label: string;
}
export interface AboutStatsContent {
stats: AboutStatItem[];
}
export interface AboutFeatureItem {
iconPath: string;
title: string;
description: string;
}
export interface AboutFeaturesContent {
badge: string;
features: AboutFeatureItem[];
}
export interface AboutTeamMember {
name: string;
role: string;
imageUrl: string;
}
export interface AboutTeamContent {
title: string;
subtitle: string;
members: AboutTeamMember[];
}
export interface AboutCtaContent {
title: string;
description: string;
buttonText: string;
}
export interface CmsContentRecord { export interface CmsContentRecord {
id: string; id: string;
pageSlug: string; pageSlug: string;