feat: Integrate CMS to dynamically populate home page sections with dedicated content types and a new service.
This commit is contained in:
@@ -1,17 +1,34 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
import { HeroSection } from '@/components/home/HeroSection';
|
import { HeroSection } from '@/components/home/HeroSection';
|
||||||
import { FeaturesSection } from '@/components/home/FeaturesSection';
|
import { FeaturesSection } from '@/components/home/FeaturesSection';
|
||||||
import { TopProfessionals } from '@/components/home/TopProfessionals';
|
import { TopProfessionals } from '@/components/home/TopProfessionals';
|
||||||
import { TestimonialsSection } from '@/components/home/TestimonialsSection';
|
import { TestimonialsSection } from '@/components/home/TestimonialsSection';
|
||||||
|
import { cmsService } from '@/services/cms.service';
|
||||||
|
import type { HeroContent, FeaturesContent, TopProfessionalsContent, TestimonialsContent, CmsContentRecord } from '@/types/cms';
|
||||||
|
|
||||||
export default function UserDashboard() {
|
export default function UserDashboard() {
|
||||||
|
const [cmsData, setCmsData] = useState<Record<string, unknown>>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCms = async () => {
|
||||||
|
const sections = await cmsService.getPageContent('landing');
|
||||||
|
const data: Record<string, unknown> = {};
|
||||||
|
sections.forEach((s: CmsContentRecord) => {
|
||||||
|
data[s.sectionKey] = s.content;
|
||||||
|
});
|
||||||
|
setCmsData(data);
|
||||||
|
};
|
||||||
|
fetchCms();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<HeroSection />
|
<HeroSection content={cmsData.hero as HeroContent | undefined} />
|
||||||
<FeaturesSection />
|
<FeaturesSection content={cmsData.features as FeaturesContent | undefined} />
|
||||||
<TopProfessionals />
|
<TopProfessionals content={cmsData.topProfessionals as TopProfessionalsContent | undefined} />
|
||||||
<TestimonialsSection />
|
<TestimonialsSection content={cmsData.testimonials as TestimonialsContent | undefined} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,34 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import type { FeaturesContent } from '@/types/cms';
|
||||||
|
|
||||||
const features = [
|
const defaultContent: FeaturesContent = {
|
||||||
{
|
title: 'Find Trusted Real Estate Professionals On Demand',
|
||||||
icon: '/assets/icons/hourglass-icon.svg',
|
subtitle: 'Quickly connect with the right agents exactly when you need them.',
|
||||||
title: 'Hire Quickly',
|
features: [
|
||||||
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/hourglass-icon.svg',
|
||||||
{
|
title: 'Hire Quickly',
|
||||||
icon: '/assets/icons/verified-badge-icon.svg',
|
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.',
|
||||||
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/verified-badge-icon.svg',
|
||||||
{
|
title: 'Verified Agents',
|
||||||
icon: '/assets/icons/star-orange-icon.svg',
|
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.',
|
||||||
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/star-orange-icon.svg',
|
||||||
{
|
title: 'Top Rated',
|
||||||
icon: '/assets/icons/trusted-people-icon.svg',
|
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.',
|
||||||
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.',
|
{
|
||||||
},
|
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.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const agents = [
|
const agents = [
|
||||||
{
|
{
|
||||||
@@ -130,7 +135,9 @@ function AgentCard({ agent, className = '' }: { agent: typeof agents[0]; classNa
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FeaturesSection() {
|
export function FeaturesSection({ content }: { content?: FeaturesContent }) {
|
||||||
|
const data = content ?? defaultContent;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-16 md:py-20 bg-white">
|
<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="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
@@ -140,23 +147,23 @@ export function FeaturesSection() {
|
|||||||
{/* Section Header */}
|
{/* Section Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h2 className="font-fractul font-bold text-[32px] md:text-[40px] leading-[1.2] text-[#00293d] mb-4">
|
<h2 className="font-fractul font-bold text-[32px] md:text-[40px] leading-[1.2] text-[#00293d] mb-4">
|
||||||
Find Trusted Real Estate Professionals On Demand
|
{data.title}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="font-fractul font-medium text-[18px] md:text-[20px] leading-[1.4] text-[#00293d]">
|
<p className="font-fractul font-medium text-[18px] md:text-[20px] leading-[1.4] text-[#00293d]">
|
||||||
Quickly connect with the right agents exactly when you need them.
|
{data.subtitle}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Features Grid - 2x2 */}
|
{/* Features Grid - 2x2 */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
{features.map((feature, index) => (
|
{data.features.map((feature, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="border border-[#00293d]/10 rounded-[15px] p-5"
|
className="border border-[#00293d]/10 rounded-[15px] p-5"
|
||||||
>
|
>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<Image
|
<Image
|
||||||
src={feature.icon}
|
src={feature.iconPath}
|
||||||
alt={feature.title}
|
alt={feature.title}
|
||||||
width={35}
|
width={35}
|
||||||
height={35}
|
height={35}
|
||||||
|
|||||||
@@ -4,9 +4,17 @@ import { useState, useEffect } from 'react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { agentsService, AgentType } from '@/services/agents.service';
|
import { agentsService, AgentType } from '@/services/agents.service';
|
||||||
|
import type { HeroContent } from '@/types/cms';
|
||||||
|
|
||||||
|
const defaultHeroContent: HeroContent = {
|
||||||
|
headline: '\u201cDiscover verified, top-rated real estate professionals to guide your buying, selling, or investing journey.\u201d',
|
||||||
|
description: 'Discover verified, top-rated real estate professionals',
|
||||||
|
ctaButtonText: 'See All Agents',
|
||||||
|
helperText: 'Connect with trusted local agents to explore homes, compare options, and make smarter decisions.',
|
||||||
|
};
|
||||||
|
|
||||||
export function HeroSection() {
|
export function HeroSection({ content }: { content?: HeroContent }) {
|
||||||
|
const data = content ?? defaultHeroContent;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [agentTypes, setAgentTypes] = useState<AgentType[]>([]);
|
const [agentTypes, setAgentTypes] = useState<AgentType[]>([]);
|
||||||
const [searchParams, setSearchParams] = useState({
|
const [searchParams, setSearchParams] = useState({
|
||||||
@@ -106,10 +114,10 @@ export function HeroSection() {
|
|||||||
{/* Headline */}
|
{/* Headline */}
|
||||||
<div className="text-center mb-5">
|
<div className="text-center mb-5">
|
||||||
<h1 className="font-fractul font-bold text-[24px] md:text-[28px] lg:text-[30px] leading-[50px] text-[#00293d] max-w-4xl mx-auto">
|
<h1 className="font-fractul font-bold text-[24px] md:text-[28px] lg:text-[30px] leading-[50px] text-[#00293d] max-w-4xl mx-auto">
|
||||||
“Discover verified, top-rated real estate professionals to guide your buying, selling, or investing journey.”
|
{data.headline}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="font-fractul font-bold text-[20px] md:text-[24px] leading-[40px] text-[#00293d] mt-2">
|
<p className="font-fractul font-bold text-[20px] md:text-[24px] leading-[40px] text-[#00293d] mt-2">
|
||||||
Discover verified, top-rated real estate professionals
|
{data.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -245,13 +253,13 @@ export function HeroSection() {
|
|||||||
onClick={handleSeeAllAgents}
|
onClick={handleSeeAllAgents}
|
||||||
className="px-8 py-3 rounded-[15px] bg-[#e58625] hover:bg-[#d47720] text-[#00293d] font-fractul font-bold text-base transition-colors shadow-md hover:shadow-lg"
|
className="px-8 py-3 rounded-[15px] bg-[#e58625] hover:bg-[#d47720] text-[#00293d] font-fractul font-bold text-base transition-colors shadow-md hover:shadow-lg"
|
||||||
>
|
>
|
||||||
See All Agents
|
{data.ctaButtonText}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Helper Text */}
|
{/* Helper Text */}
|
||||||
<p className="text-center font-serif font-normal text-[14px] leading-[19px] text-[#00293D] mt-3 max-w-[419px] mx-auto">
|
<p className="text-center font-serif font-normal text-[14px] leading-[19px] text-[#00293D] mt-3 max-w-[419px] mx-auto">
|
||||||
Connect with trusted local agents to explore homes, compare options, and make smarter decisions.
|
{data.helperText}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import type { TestimonialsContent } from '@/types/cms';
|
||||||
|
|
||||||
const testimonials = [
|
const defaultTestimonials = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
rating: 5,
|
rating: 5,
|
||||||
@@ -86,69 +87,72 @@ function TestimonialCard({ title, content, author, role, rating }: TestimonialCa
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TestimonialsSection() {
|
const defaultContent: TestimonialsContent = {
|
||||||
|
title: "Our Clients' Trust Is Our Foundation",
|
||||||
|
subtitle: 'We help buyers, sellers, and investors close successful real estate deals with confidence in every transaction.',
|
||||||
|
ratingInfo: 'Clients rate our real estate services 4.9 out of 5 on average, based on recent client reviews.',
|
||||||
|
stats: [
|
||||||
|
{
|
||||||
|
iconPath: '/assets/icons/cities-icon.svg',
|
||||||
|
boldText: '25+ Cities &',
|
||||||
|
normalText: 'neighborhoods served',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconPath: '/assets/icons/properties-icon.svg',
|
||||||
|
boldText: '1,000+ Properties',
|
||||||
|
normalText: 'bought and sold for our clients.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
testimonials: defaultTestimonials,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TestimonialsSection({ content }: { content?: TestimonialsContent }) {
|
||||||
|
const data = content ?? defaultContent;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-12 md:py-16 bg-white">
|
<section className="py-12 md:py-16 bg-white">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
{/* Section Header */}
|
{/* Section Header */}
|
||||||
<div className="text-center mb-6">
|
<div className="text-center mb-6">
|
||||||
<h2 className="font-fractul font-bold text-[32px] md:text-[40px] leading-[1.2] text-[#00293d] mb-4">
|
<h2 className="font-fractul font-bold text-[32px] md:text-[40px] leading-[1.2] text-[#00293d] mb-4">
|
||||||
Our Clients' Trust Is Our Foundation
|
{data.title}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="font-fractul font-medium text-[14px] leading-normal text-[#00293d] mb-2">
|
<p className="font-fractul font-medium text-[14px] leading-normal text-[#00293d] mb-2">
|
||||||
We help buyers, sellers, and investors close successful real estate deals with confidence in every transaction.
|
{data.subtitle}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-fractul font-medium text-[14px] leading-normal text-[#00293d]">
|
<p className="font-fractul font-medium text-[14px] leading-normal text-[#00293d]">
|
||||||
Clients rate our real estate services 4.9 out of 5 on average, based on recent client reviews.
|
{data.ratingInfo}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats Section */}
|
{/* Stats Section */}
|
||||||
<div className="flex justify-center items-center gap-8 md:gap-12 mb-10">
|
<div className="flex justify-center items-center gap-8 md:gap-12 mb-10">
|
||||||
{/* Cities Stat */}
|
{data.stats.map((stat, index) => (
|
||||||
<div className="flex items-center gap-3">
|
<div key={index} className="flex items-center gap-3">
|
||||||
<Image
|
<Image
|
||||||
src="/assets/icons/cities-icon.svg"
|
src={stat.iconPath}
|
||||||
alt="Cities"
|
alt=""
|
||||||
width={27}
|
width={27}
|
||||||
height={27}
|
height={27}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<span className="font-serif font-bold text-[14px] leading-normal text-[#00293d]">
|
<span className="font-serif font-bold text-[14px] leading-normal text-[#00293d]">
|
||||||
25+ Cities &
|
{stat.boldText}
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<span className="font-serif font-normal text-[14px] leading-normal text-[#00293d]">
|
<span className="font-serif font-normal text-[14px] leading-normal text-[#00293d]">
|
||||||
neighborhoods served
|
{stat.normalText}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
|
|
||||||
{/* Properties Stat */}
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Image
|
|
||||||
src="/assets/icons/properties-icon.svg"
|
|
||||||
alt="Properties"
|
|
||||||
width={25}
|
|
||||||
height={24}
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<span className="font-serif font-bold text-[14px] leading-normal text-[#00293d]">
|
|
||||||
1,000+ Properties
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
<span className="font-serif font-normal text-[14px] leading-normal text-[#00293d]">
|
|
||||||
bought and sold for our clients.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Testimonials Grid */}
|
{/* Testimonials Grid */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
{testimonials.map((testimonial) => (
|
{data.testimonials.map((testimonial, index) => (
|
||||||
<TestimonialCard
|
<TestimonialCard
|
||||||
key={testimonial.id}
|
key={index}
|
||||||
title={testimonial.title}
|
title={testimonial.title}
|
||||||
content={testimonial.content}
|
content={testimonial.content}
|
||||||
author={testimonial.author}
|
author={testimonial.author}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import type { TopProfessionalsContent } from '@/types/cms';
|
||||||
|
|
||||||
// Sample agents data for tabs
|
// Sample agents data for tabs
|
||||||
const agentsData = [
|
const agentsData = [
|
||||||
@@ -177,9 +178,18 @@ function ProfessionalCard({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TopProfessionals() {
|
const defaultContent: TopProfessionalsContent = {
|
||||||
|
title: '\u201cMeet top real estate professionals\u201d',
|
||||||
|
ctaText: 'Discover 5,000+ Top Real Estate Agents in Our Network.',
|
||||||
|
ctaButtonText: 'Browse Experts',
|
||||||
|
agents: agentsData,
|
||||||
|
lenders: lendersData,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TopProfessionals({ content }: { content?: TopProfessionalsContent }) {
|
||||||
|
const data = content ?? defaultContent;
|
||||||
const [activeTab, setActiveTab] = useState<'agents' | 'lenders'>('agents');
|
const [activeTab, setActiveTab] = useState<'agents' | 'lenders'>('agents');
|
||||||
const displayData = activeTab === 'agents' ? agentsData : lendersData;
|
const displayData = activeTab === 'agents' ? data.agents : data.lenders;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-10 md:py-12 bg-white">
|
<section className="py-10 md:py-12 bg-white">
|
||||||
@@ -187,7 +197,7 @@ export function TopProfessionals() {
|
|||||||
{/* Section Header */}
|
{/* Section Header */}
|
||||||
<div className="text-center mb-10">
|
<div className="text-center mb-10">
|
||||||
<h2 className="font-fractul font-bold text-[32px] md:text-[40px] leading-[1.2] text-[#00293d]">
|
<h2 className="font-fractul font-bold text-[32px] md:text-[40px] leading-[1.2] text-[#00293d]">
|
||||||
“Meet top real estate professionals”
|
{data.title}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -238,9 +248,9 @@ export function TopProfessionals() {
|
|||||||
{/* Cards Grid - 3 cards + CTA sidebar */}
|
{/* Cards Grid - 3 cards + CTA sidebar */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
{/* Professional Cards */}
|
{/* Professional Cards */}
|
||||||
{displayData.map((professional) => (
|
{displayData.map((professional, index) => (
|
||||||
<ProfessionalCard
|
<ProfessionalCard
|
||||||
key={professional.id}
|
key={index}
|
||||||
name={professional.name}
|
name={professional.name}
|
||||||
subtitle={professional.subtitle}
|
subtitle={professional.subtitle}
|
||||||
location={professional.location}
|
location={professional.location}
|
||||||
@@ -269,13 +279,13 @@ export function TopProfessionals() {
|
|||||||
|
|
||||||
{/* Text */}
|
{/* Text */}
|
||||||
<p className="font-fractul font-normal text-[14px] leading-[1.4] text-[#00293d] mb-6">
|
<p className="font-fractul font-normal text-[14px] leading-[1.4] text-[#00293d] mb-6">
|
||||||
Discover 5,000+ Top Real Estate Agents in Our Network.
|
{data.ctaText}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Browse Experts Button */}
|
{/* Browse Experts Button */}
|
||||||
<Link href="/user/profiles">
|
<Link href="/user/profiles">
|
||||||
<button className="bg-[#e58625] hover:bg-[#d47820] text-white font-fractul font-bold text-[16px] leading-[19px] px-8 py-3 rounded-[15px] transition-colors">
|
<button className="bg-[#e58625] hover:bg-[#d47820] text-white font-fractul font-bold text-[16px] leading-[19px] px-8 py-3 rounded-[15px] transition-colors">
|
||||||
Browse Experts
|
{data.ctaButtonText}
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ const publicEndpoints = [
|
|||||||
'/auth/2fa/verify',
|
'/auth/2fa/verify',
|
||||||
'/auth/2fa/verify-backup',
|
'/auth/2fa/verify-backup',
|
||||||
'/agent-types',
|
'/agent-types',
|
||||||
|
'/cms',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Check if URL is a public endpoint
|
// Check if URL is a public endpoint
|
||||||
|
|||||||
24
src/services/cms.service.ts
Normal file
24
src/services/cms.service.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import api, { ApiResponse } from './api';
|
||||||
|
import { CmsContentRecord } from '@/types/cms';
|
||||||
|
|
||||||
|
class CmsService {
|
||||||
|
async getPageContent(pageSlug: string): Promise<CmsContentRecord[]> {
|
||||||
|
try {
|
||||||
|
const response = await api.get<ApiResponse<CmsContentRecord[]>>(`/cms/page/${pageSlug}`);
|
||||||
|
return response.data.data;
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSectionContent<T>(pageSlug: string, sectionKey: string): Promise<T | null> {
|
||||||
|
try {
|
||||||
|
const response = await api.get<ApiResponse<CmsContentRecord>>(`/cms/page/${pageSlug}/${sectionKey}`);
|
||||||
|
return response.data.data.content as T;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cmsService = new CmsService();
|
||||||
@@ -51,3 +51,6 @@ export type {
|
|||||||
// Messaging Services
|
// Messaging Services
|
||||||
export { socketService } from './socket.service';
|
export { socketService } from './socket.service';
|
||||||
export { messagesService } from './messages.service';
|
export { messagesService } from './messages.service';
|
||||||
|
|
||||||
|
// CMS Service
|
||||||
|
export { cmsService } from './cms.service';
|
||||||
|
|||||||
67
src/types/cms.ts
Normal file
67
src/types/cms.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
export interface HeroContent {
|
||||||
|
headline: string;
|
||||||
|
description: string;
|
||||||
|
ctaButtonText: string;
|
||||||
|
helperText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FeatureItem {
|
||||||
|
iconPath: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FeaturesContent {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
features: FeatureItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfessionalItem {
|
||||||
|
name: string;
|
||||||
|
subtitle: string;
|
||||||
|
location: string;
|
||||||
|
experience: string;
|
||||||
|
expertise: string[];
|
||||||
|
imageUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TopProfessionalsContent {
|
||||||
|
title: string;
|
||||||
|
ctaText: string;
|
||||||
|
ctaButtonText: string;
|
||||||
|
agents: ProfessionalItem[];
|
||||||
|
lenders: ProfessionalItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TestimonialItem {
|
||||||
|
rating: number;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
author: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StatItem {
|
||||||
|
iconPath: string;
|
||||||
|
boldText: string;
|
||||||
|
normalText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TestimonialsContent {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
ratingInfo: string;
|
||||||
|
stats: StatItem[];
|
||||||
|
testimonials: TestimonialItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CmsContentRecord {
|
||||||
|
id: string;
|
||||||
|
pageSlug: string;
|
||||||
|
sectionKey: string;
|
||||||
|
content: unknown;
|
||||||
|
isPublished: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user