feat: Implement CMS integration for the About page, including new content types and dynamic data loading with default fallbacks.
This commit is contained in:
@@ -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',
|
||||||
|
headline: 'Connecting You with Trusted Agents.',
|
||||||
|
description:
|
||||||
|
'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',
|
||||||
|
bannerOverlayText: 'Building the future of property.',
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultStats: AboutStatsContent = {
|
||||||
|
stats: [
|
||||||
|
{ value: '15k+', label: 'Verified Agents' },
|
||||||
|
{ value: '98%', label: 'Customer Satisfaction' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultFeatures: AboutFeaturesContent = {
|
||||||
|
badge: 'Why Choose Us',
|
||||||
|
features: [
|
||||||
{
|
{
|
||||||
icon: '/assets/icons/verified-badge-orange.svg',
|
iconPath: '/assets/icons/verified-badge-orange.svg',
|
||||||
title: 'Verified Agents',
|
title: 'Verified Agents',
|
||||||
description: 'Every agent is vetted and verified for credentials, experience, and customer feedback.',
|
description:
|
||||||
|
'Every agent is vetted and verified for credentials, experience, and customer feedback.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '/assets/icons/shield-verified-icon.svg',
|
iconPath: '/assets/icons/shield-verified-icon.svg',
|
||||||
title: 'Total Transparency',
|
title: 'Total Transparency',
|
||||||
description: 'Access honest reviews, transaction history, and verified credentials before connecting.',
|
description:
|
||||||
|
'Access honest reviews, transaction history, and verified credentials before connecting.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: '/assets/icons/home-icon.svg',
|
iconPath: '/assets/icons/home-icon.svg',
|
||||||
title: 'Simple Journey',
|
title: 'Simple Journey',
|
||||||
description: 'From search to settlement, we make finding and working with agents effortless.',
|
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,64 +150,59 @@ 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 */}
|
||||||
|
{stats.stats.length > 0 && (
|
||||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<div className="flex items-center justify-center gap-16 lg:gap-32">
|
<div className="flex items-center justify-center gap-16 lg:gap-32">
|
||||||
<div className="text-center">
|
{stats.stats.map((stat, index) => (
|
||||||
|
<div key={index} className="text-center">
|
||||||
<p className="font-fractul font-bold text-[28px] lg:text-[32px] text-[#e58625]">
|
<p className="font-fractul font-bold text-[28px] lg:text-[32px] text-[#e58625]">
|
||||||
15k+
|
{stat.value}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-serif text-[14px] text-[#00293d]">
|
<p className="font-serif text-[14px] text-[#00293d]">
|
||||||
Verified Agents
|
{stat.label}
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Why Choose Us Section */}
|
{/* Why Choose Us Section */}
|
||||||
|
{features.features.length > 0 && (
|
||||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 lg:py-20">
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 lg:py-20">
|
||||||
{/* Section Header */}
|
{/* Section Header */}
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<span className="bg-[#e58625]/10 text-[#e58625] font-fractul font-semibold text-[14px] px-4 py-2 rounded-full">
|
<span className="bg-[#e58625]/10 text-[#e58625] font-fractul font-semibold text-[14px] px-4 py-2 rounded-full">
|
||||||
Why Choose Us
|
{features.badge}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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}
|
||||||
@@ -163,32 +218,30 @@ export default function AboutPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Team Section */}
|
{/* Team Section */}
|
||||||
|
{team.members.length > 0 && (
|
||||||
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-16 lg:pb-20">
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-16 lg:pb-20">
|
||||||
{/* Section Header */}
|
{/* Section Header */}
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<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">
|
||||||
Meet the minds behind the platform.
|
{team.title}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="font-serif text-[14px] text-[#00293d] max-w-[500px] mx-auto">
|
<p className="font-serif text-[14px] text-[#00293d] max-w-[500px] mx-auto">
|
||||||
Our team is passionate about making real estate connections easier and more transparent for everyone.
|
{team.subtitle}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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
|
|
||||||
src={member.image}
|
|
||||||
alt={member.name}
|
alt={member.name}
|
||||||
width={180}
|
|
||||||
height={180}
|
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -202,6 +255,7 @@ export default function AboutPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user