Merge pull request 'dev' (#12) from dev into main

Reviewed-on: #12
This commit is contained in:
RizqiSyahrendra 2025-04-28 20:55:26 +00:00
commit 4d4033fc7d
34 changed files with 909 additions and 621 deletions

View File

@ -18,6 +18,7 @@
"@payloadcms/plugin-form-builder": "^3.35.1",
"@payloadcms/richtext-lexical": "^3.35.1",
"@payloadcms/storage-s3": "^3.35.1",
"clsx": "^2.1.1",
"country-state-city": "^3.2.1",
"dayjs": "^1.11.13",
"graphql": "^16.8.1",
@ -29,7 +30,8 @@
"react-hook-form": "^7.56.1",
"react-select": "^5.10.1",
"react-toastify": "^11.0.5",
"swiper": "^11.2.6"
"swiper": "^11.2.6",
"tailwind-merge": "^3.2.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",

View File

@ -3744,13 +3744,13 @@ html .page .divider-secondary::after {
}
.button-primary, .button-primary:focus {
color: #ffffff;
background-color: #bc986b;
border-color: #bc986b;
color: var(--color-colorBtnPrimaryText);
background-color: var(--color-colorBtnPrimary);
border-color: var(--color-colorBtnPrimary);
}
.button-primary:hover, .button-primary:active {
color: #151515;
color: var(--color-colorBtnPrimaryText);
opacity: 0.8;
}
@ -3759,15 +3759,14 @@ html .page .divider-secondary::after {
}
.button-secondary, .button-secondary:focus {
color: #151515;
background-color: #fdde52;
border-color: #fdde52;
color: var(--color-colorBtnSecondaryText);
background-color: var(--color-colorBtnSecondary);
border-color: var(--color-colorBtnSecondary);
}
.button-secondary:hover, .button-secondary:active {
color: #ffffff;
background-color: #bc986b;
border-color: #bc986b;
color: var(--color-colorBtnSecondaryText);
opacity: 0.8;
}
.button-secondary.button-ujarak::before {
@ -11354,7 +11353,7 @@ html:not(.lt-ie10) .not-animated {
.ui-to-top:hover {
color: #151515;
background: #fdde52;
opacity: 0.8;
text-decoration: none;
}
@ -11699,7 +11698,7 @@ html.tablet .ui-to-top {
}
.rd-navbar-static .rd-menu {
z-index: 15;
z-index: 30;
position: absolute;
display: block;
visibility: hidden;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 MiB

BIN
public/images/home1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 KiB

BIN
public/images/home2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
public/images/home3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

BIN
public/images/home4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
public/images/home5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
public/images/home6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

BIN
public/images/home7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
public/images/logo1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,5 @@
import LoaderFixed from "@/components/loaders/LoaderFixed";
export default function Loading() {
return <LoaderFixed />;
}

View File

@ -0,0 +1,111 @@
import DetailPageBlog from "@/components/blogs/DetailPageBlog";
import DetailPage from "@/components/pages/DetailPage";
import { fetchBlogDetail } from "@/services/payload/blog";
import { fetchPageBySlug } from "@/services/payload/page";
import { getDefaultMetadata } from "@/utils/metadata";
import { Metadata } from "next";
import { headers } from "next/headers";
import { notFound } from "next/navigation";
export async function generateMetadata(props: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const metadata = await getDefaultMetadata();
const params = await props.params;
let title = `Page Not Found - ${metadata.openGraph?.siteName}`;
let description = title;
let publishedAt = "";
let updatedAt = "";
let imgUrl = "";
let createdByName = "";
let canonicalUrl = "";
const blog = await fetchBlogDetail(params.slug);
if (!!blog) {
// check for blog data
title = `${!!blog.data?.meta?.title ? blog.data?.meta?.title : blog.data.title} - ${metadata.openGraph?.siteName}`;
description = `${!!blog.data?.meta?.description ? blog.data?.meta?.description : blog.data.title}`;
imgUrl = blog.img.url;
publishedAt = blog.data.createdAt;
updatedAt = blog.data.updatedAt;
if (!!blog.data?.meta?.canonical_url) {
canonicalUrl = blog.data.meta.canonical_url;
}
if (!!blog?.data?.createdBy && typeof blog.data.createdBy !== "number") {
createdByName = blog.data.createdBy?.name ?? "";
}
} else {
// check for page data when blog is not found
const page = await fetchPageBySlug({ slug: params.slug });
if (!!page) {
title = `${!!page?.data?.meta?.title ? page?.data?.meta?.title : page.data.title} - ${metadata.openGraph?.siteName}`;
description = `${!!page?.data?.meta?.description ? page?.data?.meta?.description : page.data.title}`;
imgUrl = page.heroImg?.url;
publishedAt = page.createdAt;
updatedAt = page.updatedAt;
if (!!page.data?.meta?.canonical_url) {
canonicalUrl = page.data.meta.canonical_url;
}
if (!!page?.data?.createdBy && typeof page?.data?.createdBy !== "number") {
createdByName = page?.data?.createdBy?.name ?? "";
}
}
}
metadata.title = title;
metadata.description = description;
if (!!metadata.openGraph) {
// @ts-ignore
metadata.openGraph.type = "article";
metadata.openGraph.title = title;
metadata.openGraph.description = description;
metadata.openGraph.images = !!imgUrl ? [imgUrl] : undefined;
}
if (!!metadata.alternates && !!canonicalUrl) {
metadata.alternates.canonical = canonicalUrl;
}
metadata.twitter = {
card: "summary_large_image",
title: title,
description: description,
images: !!imgUrl ? [imgUrl] : undefined,
};
metadata.other = {
"article:published_time": publishedAt,
"article:modified_time": updatedAt,
"twitter:label1": "Written by",
"twitter:data1": !!createdByName ? createdByName : "Admin",
"twitter:label2": "Est. reading time",
"twitter:data2": "3 minutes",
};
return metadata;
}
export default async function SinglePage(props: { params: Promise<{ slug: string }> }) {
const params = await props.params;
const headersList = await headers();
const fullUrl = headersList.get("x-full-url");
const shareUrl = {
facebook: `https://www.facebook.com/sharer/sharer.php?u=${fullUrl}`,
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${fullUrl}`,
twitter: `https://twitter.com/intent/tweet?url=${fullUrl}`,
};
const blog = await fetchBlogDetail(params.slug);
if (!!blog) {
return (
<>
<DetailPageBlog data={blog} shareUrl={shareUrl} />
</>
);
}
const page = await fetchPageBySlug({ slug: params.slug });
if (!page) return notFound();
return (
<>
<DetailPage data={page} shareUrl={shareUrl} />
</>
);
}

View File

@ -1,19 +1,16 @@
import ListOfRecentBlog from "@/components/blogs/ListOfRecentBlog";
import HeroImage from "@/components/HeroImage";
import DetailPageBlog from "@/components/blogs/DetailPageBlog";
import { fetchBlogDetail } from "@/services/payload/blog";
import { getDefaultMetadata } from "@/utils/metadata";
import { RichText } from "@payloadcms/richtext-lexical/react";
import { Metadata } from "next";
import { headers } from "next/headers";
import Image from "next/image";
import { notFound } from "next/navigation";
export async function generateMetadata(props: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const metadata = await getDefaultMetadata();
const params = await props.params;
let title = "Page";
let description = "Page";
let title = `Page Not Found - ${metadata.openGraph?.siteName}`;
let description = title;
let publishedAt = "";
let updatedAt = "";
let imgUrl = "";
@ -81,135 +78,7 @@ export default async function BlogDetail(props: { params: Promise<{ slug: string
return (
<>
<HeroImage title={blog.data.title} />
<section className="section section-md bg-colorSection1">
<div className="container">
<div className="row justify-content-lg-center">
<div className="col-lg-8">
<article className="blog-post-solo">
<div className="blog-post-solo-part">
<div className="w-full h-56 md:h-80 bg-colorImgPlaceholder/50 relative">
<Image className="object-cover" src={blog.img.url} alt={blog.img.alt} fill />
</div>
<RichText className="mt-4" data={blog.data.content} />
</div>
<div className="blog-post-solo-footer">
<div className="blog-post-solo-footer-left">
<ul className="blog-post-solo-footer-list">
<li>
<span className="icon mdi mdi-clock"></span>
<a href="#">{blog.createdAt}</a>
</li>
</ul>
</div>
<div className="blog-post-solo-footer-right">
<ul className="blog-post-solo-footer-list-1">
<li>
<span>Share this post</span>
</li>
<li>
<a
target="_blank"
className="icon icon-circle icon-rounded icon-5 fa-facebook"
href={shareUrl.facebook}
></a>
</li>
<li>
<a
target="_blank"
className="icon icon-circle icon-rounded icon-6 fa-twitter"
href={shareUrl.twitter}
></a>
</li>
<li>
<a
target="_blank"
className="icon icon-circle icon-rounded icon-4 fa-linkedin"
href={shareUrl.linkedin}
></a>
</li>
</ul>
</div>
</div>
<ListOfRecentBlog currentBlogId={blog?.data?.id} />
</article>
</div>
{/* Sidebar */}
<div className="col-lg-4">
<div className="pdl-xl-40">
<div className="row row-60">
<div className="col-md-6 col-lg-12">
<form action="/blog" className="form-lg rd-search rd-search-classic">
<div className="form-wrap">
<input
className="form-input"
id="rd-search-form-input"
type="text"
name="s"
autoComplete="off"
placeholder="Search blog..."
/>
</div>
<button className="rd-search-submit" type="submit"></button>
</form>
</div>
<div className="col-md-6 col-lg-12">
<div className="block-info-2">
<div className="block-info-2-title">
<h3>Latest Listings</h3>
</div>
<a className="post-minimal-1" href="#">
<div className="post-minimal-1-left">
<Image src="/images/post-agent-01-212x208.jpg" alt="" width="212" height="208" />
</div>
<div className="post-minimal-1-body">
<div className="post-minimal-1-title">
<span>401 Biscayne Blvd</span>
</div>
<div className="post-minimal-1-text">
<span>$5000\mo</span>
</div>
</div>
</a>
<a className="post-minimal-1" href="#">
<div className="post-minimal-1-left">
<Image src="/images/post-agent-02-212x208.jpg" alt="" width="212" height="208" />
</div>
<div className="post-minimal-1-body">
<div className="post-minimal-1-title">
<span>35 Pond St, New York</span>
</div>
<div className="post-minimal-1-text">
<span>$5550\mo</span>
</div>
</div>
</a>
<a className="post-minimal-1" href="#">
<div className="post-minimal-1-left">
<Image src="/images/post-agent-03-212x208.jpg" alt="" width="212" height="208" />
</div>
<div className="post-minimal-1-body">
<div className="post-minimal-1-title">
<span>182 3rd St, Seattle</span>
</div>
<div className="post-minimal-1-text">
<span>$2520\mo</span>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<DetailPageBlog data={blog} shareUrl={shareUrl} />
</>
);
}

View File

@ -33,8 +33,15 @@
--color-colorContactForm: var(--color-colorExt50);
--color-colorText1: var(--color-colorExt10);
--color-colorText2: var(--color-colorExt20);
--color-colorText3: var(--color-colorExt30);
--color-colorLoaderBackground: var(--color-colorExt20);
--color-colorPriceTag: var(--color-colorExt30);
--color-colorBtnPrimary: var(--color-colorext40);
--color-colorBtnPrimaryText: var(--color-colorExt20);
--color-colorBtnSecondary: var(--color-colorExt50);
--color-colorBtnSecondaryText: var(--color-colorExt20);
--color-colorLinkText1: var(--color-colorExt30);
--color-colorLinkText2: var(--color-colorext40);
}
@layer components {

View File

@ -1,10 +1,5 @@
import GoogleReviewBox from "@/components/GoogleReviewBox";
import HomeTopSection from "@/components/homes/HomeTopSection";
import Loader from "@/components/loaders/Loader";
import ListOfFeaturedProperty from "@/components/properties/ListOfFeaturedProperty";
import Image from "next/image";
import Link from "next/link";
import { Suspense } from "react";
export default function Home() {
return (
@ -13,95 +8,57 @@ export default function Home() {
<section className="section section-lg bg-colorSection1">
<div className="container">
<h2 className="heading-decoration-1">
<span className="heading-inner">Why Dynamic Realty?</span>
</h2>
<div className="row row-30">
<div className="col-md-6 col-lg-3">
<Link href="/">
<div className="col-md-12 col-lg-4">
<div>
<article className="box-modern">
<span className="icon box-modern-icon fl-bigmug-line-search74"></span>
<div className="flex justify-center">
<span className="icon box-modern-icon fa-home"></span>
</div>
<div className="box-modern-main">
<h4 className="box-modern-title">Buy</h4>
<p>
Discover a wide selection of properties available for purchase in prime locations at competitive
prices.
<h4 className="box-modern-title">Tailored Management, Trusted Care</h4>
<p className="text-colorText3">
Committed to providing a personalized structure to each client, with on-site management & a robust
property management
</p>
</div>
</article>
</Link>
</div>
<div className="col-md-6 col-lg-3">
<Link href="/">
<article className="box-modern">
<span className="icon box-modern-icon fl-bigmug-line-label25"></span>
<div className="box-modern-main">
<h4 className="box-modern-title">Sell</h4>
<p>
List your property with ease and reach potential buyers quickly with our expert marketing support.
</p>
</div>
</article>
</Link>
</div>
<div className="col-md-6 col-lg-3">
<Link href="/">
<article className="box-modern">
<span className="icon box-modern-icon fl-bigmug-line-timer35"></span>
<div className="box-modern-main">
<h4 className="box-modern-title">Rent</h4>
<p>Find the perfect rental home or apartment with flexible lease terms and no hidden fees.</p>
</div>
</article>
</Link>
</div>
<div className="col-md-6 col-lg-3">
<Link href="/">
<article className="box-modern">
<span className="icon box-modern-icon fl-bigmug-line-big104"></span>
<div className="box-modern-main">
<h4 className="box-modern-title">Need Help?</h4>
<p>
Have questions? Our support team is here to guide you through every step of your real estate
journey.
</p>
</div>
</article>
</Link>
</div>
</div>
</div>
</section>
<section className="section relative!">
<Image
alt="Experience the Difference in Every Key Turn"
src={"/images/bg-header.webp"}
quality={100}
fill
sizes="100vw"
style={{
objectFit: "cover",
}}
/>
<div className="box-1-cell height-fill context-dark bg-colorHeader/80!">
<div className="cell-inner box-1-outer">
<div className="box-1">
<div className="text-center">
<h2>Experience the Difference in Every Key Turn</h2>
</div>
<div className="mt-4 text-lg">
<p>
Dynamic Realty is a full-service veteran-owned real estate company based in Centerville, GA,
established in 2004. Our services include buying & selling, rentals, & property management. We are
committed to providing a personalized structure to each client, with on-site management & a robust
property management division. We strive to provide a service that meets the needs of all our customers
& are proud to have been helping people with their real estate needs for the past 20 years.
</p>
<p>
At Dynamic Realty, we offer a comprehensive range of realty services to meet the needs of our
customers in Central Georgia. Our services include buying & selling, renting, & property management.
We understand the real estate sector inside out & are committed to providing a professional service to
our clients. We strive to make the process of reaching your real estate goals as easy & stress-free as
possible. Get in touch with us today to find out more.
</p>
</div>
<div className="col-md-12 col-lg-4">
<div>
<article className="box-modern">
<div className="flex justify-center">
<span className="icon box-modern-icon fa-calendar"></span>
</div>
<div className="box-modern-main">
<h4 className="box-modern-title">20 Years of Real Estate Excellence</h4>
<p className="text-colorText3">
We strive to provide a service that meets the needs of all our customers & are proud to have been
helping people with their real estate needs for the past 20 years
</p>
</div>
</article>
</div>
</div>
<div className="col-md-12 col-lg-4">
<div>
<article className="box-modern">
<div className="flex justify-center">
<span className="icon box-modern-icon fa-map"></span>
</div>
<div className="box-modern-main">
<h4 className="box-modern-title">Expert Guidance, Stress-Free Journeys</h4>
<p className="text-colorText3">
We understand the real estate sector inside out & are committed to providing a professional
service to our clients. We strive to make the process of reaching your real estate goals as easy &
stress-free as possible
</p>
</div>
</article>
</div>
</div>
</div>
@ -109,101 +66,6 @@ export default function Home() {
</section>
<GoogleReviewBox />
<section className="section section-lg bg-colorSection2">
<div className="container">
<div className="layout-4">
<h2 className="heading-decoration-1">
<span className="heading-inner">Featured Properties</span>
</h2>
<div className="layout-4-item">
<ul className="list-inline-bordered heading-7">
<li>
<a href="/listings-for-rent">For Rent</a>
</li>
<li>
<a href="/listings-for-sale">For Sale</a>
</li>
</ul>
</div>
</div>
<div className="my-10!">
<Suspense
fallback={
<>
<div className="mt-5">
<Loader />
</div>
</>
}
>
<ListOfFeaturedProperty />
</Suspense>
</div>
</div>
</section>
<section className="section parallax-container" data-parallax-img="/images/bg-02-1920x916.jpg">
<div className="parallax-content section-lg context-dark text-center">
<div className="container">
<div className="row row-30">
<div className="col-6 col-md-3">
<article className="box-counter">
<div className="box-counter-main">
<span>860</span>
</div>
<p className="box-counter-title">Properties on Map</p>
</article>
</div>
<div className="col-6 col-md-3">
<article className="box-counter">
<div className="box-counter-main">
<div className="counter">29</div>
</div>
<p className="box-counter-title">Professional Agents</p>
</article>
</div>
<div className="col-6 col-md-3">
<article className="box-counter">
<div className="box-counter-main">
<div className="counter">10</div>
<span>k</span>
</div>
<p className="box-counter-title">Happy Clients</p>
</article>
</div>
<div className="col-6 col-md-3">
<article className="box-counter">
<div className="box-counter-main">
<div className="counter">15</div>
</div>
<p className="box-counter-title">New Apartments Daily</p>
</article>
</div>
</div>
</div>
</div>
</section>
<section className="section section-lg bg-colorSection1">
<div className="container">
<h2 className="heading-decoration-1">
<span className="heading-inner">Find Us</span>
</h2>
<div className="row row-30">
<div className="col-12">
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3360.4823498818855!2d-83.68565822483802!3d32.61997607372962!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x88f3e6ce99781991%3A0xabfd803ad30f6d12!2s100%20N%20Houston%20Lake%20Blvd%2C%20Centerville%2C%20GA%2031028%2C%20USA!5e0!3m2!1sen!2sid!4v1744883077476!5m2!1sen!2sid"
width={"100%"}
height={450}
style={{ border: 0 }}
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
/>
</div>
</div>
</div>
</section>
</>
);
}

View File

@ -7,7 +7,7 @@ type HeroImageProps = {
export default function HeroImage({ title = "", imgSrc = "/images/breadcrumbs-bg-05-1922x441.jpg" }: HeroImageProps) {
return (
<section className="breadcrumbs-custom bg-image context-dark">
<section className="breadcrumbs-custom context-dark min-h-[300px]!">
<Image
alt="Blog"
src={imgSrc}
@ -18,7 +18,7 @@ export default function HeroImage({ title = "", imgSrc = "/images/breadcrumbs-bg
objectFit: "cover",
}}
/>
<div className="bg-colorHeroOverlay/50 w-full h-full absolute top-0 left-0" />
<div className="bg-colorHeroOverlay/85 w-full h-full absolute top-0 left-0" />
<div className="container relative">
<h2 className="breadcrumbs-custom-title">{title}</h2>
</div>

View File

@ -4,11 +4,6 @@ import Image from "next/image";
import { useState } from "react";
import { Autoplay } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import ContactFormBox from "./ContactFormBox";
type HeroSliderProps = {
onClickBook?: () => void;
};
type HeroSlideItem = {
title: string;
@ -16,28 +11,30 @@ type HeroSlideItem = {
img: string;
};
export default function HeroSlider({ onClickBook }: HeroSliderProps) {
export default function HeroSlider() {
const [slideItems] = useState<HeroSlideItem[]>([
{
title: "Real Estate, Redefined",
description: "Where Southern Charm Meets Boutique Real Estate",
img: "/images/bg-header2.jpg",
title: "Full-Service Veteran-Owned Real Estate Company",
description:
"Dynamic Realty is a full-service veteran-owned real estate company based in Centerville, GA, established in 2004. Our services include buying & selling, rentals, & property management. We strive to provide a service that meets the needs of all our customers & are proud to have been helping people with their real estate needs for the past 20 years.",
img: "/images/bg-header4.jpg",
},
{
title: "Your Vision, Our Expertise",
description: "Discover a Smarter Way to Buy & Sell",
img: "/images/bg-header3.jpg",
title: "Comprehensive Range of Realty Services",
description:
"Our services include buying & selling, renting, & property management. We understand the real estate sector inside out & are committed to providing a professional service to our clients. We strive to make the process of reaching your real estate goals as easy & stress-free as possible.",
img: "/images/bg-header5.jpg",
},
]);
return (
<section className="section flex! flex-row! justify-end! relative! min-h-96! lg:min-h-auto!">
<section className="section flex! flex-row! justify-end! relative! min-h-[900px]! lg:min-h-[600px]!">
<Swiper
className="absolute w-full h-full"
modules={[Autoplay]}
spaceBetween={50}
slidesPerView={1}
autoplay={{ delay: 4000 }}
autoplay={{ delay: 10000 }}
loop
>
{slideItems.map((slide, idx) => (
@ -52,21 +49,29 @@ export default function HeroSlider({ onClickBook }: HeroSliderProps) {
objectFit: "cover",
}}
/>
<section className="section section-lg text-colorText2! bg-colorHeroOverlay/50 w-full h-full z-20">
<section className="section section-lg text-colorText2! bg-colorHeroOverlay/85 w-full h-full z-20">
<div className="container">
<div className="row">
<div className="col-12 col-lg-4 flex justify-center">
<div className="w-[200px] h-[200px] relative mb-4 lg:hidden">
<Image alt="Logo" src={"/images/logo1.png"} quality={100} fill />
</div>
<div className="w-[300px] h-[300px] relative mb-4 hidden lg:block">
<Image alt="Logo" src={"/images/logo1.png"} quality={100} fill />
</div>
</div>
<div className="col-12 col-lg-8 text-wrap space-y-5 md:space-y-8 lg:space-y-12">
<div className="text-4xl lg:text-8xl! font-playfairdisplay text-center! lg:text-left!">
<div className="text-4xl lg:text-6xl! font-playfairdisplay text-center! lg:text-left!">
{slide.title}
</div>
<div className="text-lg lg:text-5xl! font-montserrat text-center! lg:text-left!">
<div className="text-lg lg:text-xl! font-montserrat text-center! lg:text-left!">
{slide.description}
</div>
<div className="context-dark flex flex-row justify-center lg:hidden">
{/* <div className="context-dark flex flex-row justify-center lg:hidden">
<button onClick={onClickBook} className="button button-xs button-primary-outline">
BOOK APPOINTMENT
</button>
</div>
</div> */}
<div className="context-dark flex flex-row justify-center lg:hidden">
<a href="tel:(478) 225 9061" className="button button-xs button-primary-outline">
Call Us
@ -80,7 +85,7 @@ export default function HeroSlider({ onClickBook }: HeroSliderProps) {
))}
</Swiper>
<ContactFormBox />
{/* <ContactFormBox /> */}
</section>
);
}

View File

@ -4,23 +4,9 @@ import { RichText } from "@payloadcms/richtext-lexical/react";
export function ContentBlock(props: any) {
return (
<div className="container relative">
<div className="row">
{/* Content */}
<div className="col-md-10 offset-md-1 col-lg-10 offset-lg-1">
{/* Post */}
<div className="blog-item mb-10">
<div className="blog-item-body">
<div>
{/* @ts-ignore */}
<RichText data={props.content} />
</div>
</div>
</div>
{/* End Post */}
</div>
{/* End Content */}
</div>
<div>
{/* @ts-ignore */}
<RichText data={props.content} />
</div>
);
}

View File

@ -9,7 +9,7 @@ type CardBlogProps = {
};
export default function CardBlog({ data, colorPreset = 1, isDescriptionVisible = true }: CardBlogProps) {
const linkDetail = `/blog/${data.slug}`;
const linkDetail = `/${data.slug}`;
if (colorPreset === 2) {
return (

View File

@ -0,0 +1,151 @@
import { fetchBlogDetail } from "@/services/payload/blog";
import Image from "next/image";
import ListOfRecentBlog from "./ListOfRecentBlog";
import HeroImage from "../HeroImage";
import { RichText } from "@payloadcms/richtext-lexical/react";
type shareUrlDestination = "facebook" | "linkedin" | "twitter";
type DetailPageBlogProps = {
data: Awaited<ReturnType<typeof fetchBlogDetail>>;
shareUrl: Record<shareUrlDestination, string>;
};
export default function DetailPageBlog({ data, shareUrl }: DetailPageBlogProps) {
const blog = data;
if (!blog) return <></>;
return (
<>
<HeroImage title={blog.data.title} />
<section className="section section-md bg-colorSection1">
<div className="container">
<div className="row justify-content-lg-center">
<div className="col-lg-8">
<article className="blog-post-solo">
<div className="blog-post-solo-part">
<div className="w-full h-56 md:h-80 bg-colorImgPlaceholder/50 relative">
<Image className="object-cover" src={blog.img.url} alt={blog.img.alt} fill />
</div>
<RichText className="mt-4" data={blog.data.content} />
</div>
<div className="blog-post-solo-footer">
<div className="blog-post-solo-footer-left">
<ul className="blog-post-solo-footer-list">
<li>
<span className="icon mdi mdi-clock"></span>
<a href="#">{blog.createdAt}</a>
</li>
</ul>
</div>
<div className="blog-post-solo-footer-right">
<ul className="blog-post-solo-footer-list-1">
<li>
<span>Share this post</span>
</li>
<li>
<a
target="_blank"
className="icon icon-circle icon-rounded icon-5 fa-facebook"
href={shareUrl.facebook}
></a>
</li>
<li>
<a
target="_blank"
className="icon icon-circle icon-rounded icon-6 fa-twitter"
href={shareUrl.twitter}
></a>
</li>
<li>
<a
target="_blank"
className="icon icon-circle icon-rounded icon-4 fa-linkedin"
href={shareUrl.linkedin}
></a>
</li>
</ul>
</div>
</div>
<ListOfRecentBlog currentBlogId={blog?.data?.id} />
</article>
</div>
{/* Sidebar */}
<div className="col-lg-4">
<div className="pdl-xl-40">
<div className="row row-60">
<div className="col-md-6 col-lg-12">
<form action="/blog" className="form-lg rd-search rd-search-classic">
<div className="form-wrap">
<input
className="form-input"
id="rd-search-form-input"
type="text"
name="s"
autoComplete="off"
placeholder="Search blog..."
/>
</div>
<button className="rd-search-submit" type="submit"></button>
</form>
</div>
<div className="col-md-6 col-lg-12">
<div className="block-info-2">
<div className="block-info-2-title">
<h3>Latest Listings</h3>
</div>
<a className="post-minimal-1" href="#">
<div className="post-minimal-1-left">
<Image src="/images/post-agent-01-212x208.jpg" alt="" width="212" height="208" />
</div>
<div className="post-minimal-1-body">
<div className="post-minimal-1-title">
<span>401 Biscayne Blvd</span>
</div>
<div className="post-minimal-1-text">
<span>$5000\mo</span>
</div>
</div>
</a>
<a className="post-minimal-1" href="#">
<div className="post-minimal-1-left">
<Image src="/images/post-agent-02-212x208.jpg" alt="" width="212" height="208" />
</div>
<div className="post-minimal-1-body">
<div className="post-minimal-1-title">
<span>35 Pond St, New York</span>
</div>
<div className="post-minimal-1-text">
<span>$5550\mo</span>
</div>
</div>
</a>
<a className="post-minimal-1" href="#">
<div className="post-minimal-1-left">
<Image src="/images/post-agent-03-212x208.jpg" alt="" width="212" height="208" />
</div>
<div className="post-minimal-1-body">
<div className="post-minimal-1-title">
<span>182 3rd St, Seattle</span>
</div>
<div className="post-minimal-1-text">
<span>$2520\mo</span>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</>
);
}

View File

@ -1,23 +1,21 @@
"use client";
import HeroSlider from "@/components/HeroSlider";
import { useRef } from "react";
import ContactFormSection from "../ContactFormSection";
export default function HomeTopSection() {
const formRef = useRef<HTMLDivElement | null>(null);
// const formRef = useRef<HTMLDivElement | null>(null);
function scrollToForm() {
formRef.current?.scrollIntoView?.({ behavior: "smooth" });
}
// function scrollToForm() {
// formRef.current?.scrollIntoView?.({ behavior: "smooth" });
// }
return (
<>
<HeroSlider onClickBook={scrollToForm} />
<HeroSlider />
<div className="lg:hidden" ref={formRef}>
{/* <div className="lg:hidden" ref={formRef}>
<ContactFormSection />
</div>
</div> */}
</>
);
}

View File

@ -1,6 +1,16 @@
import Image from "next/image";
import ListOfFeaturedProperiesFooter from "@/components/properties/ListOfFeaturedPropertiesFooter";
import { headers } from "next/headers";
export default async function Footer() {
const headersList = await headers();
const fullUrl = headersList.get("x-full-url");
const shareUrl = {
facebook: `https://www.facebook.com/sharer/sharer.php?u=${fullUrl}`,
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${fullUrl}`,
twitter: `https://twitter.com/intent/tweet?url=${fullUrl}`,
};
export default function Footer() {
return (
<>
<section className="section section-md bg-colorFooter context-dark">
@ -10,36 +20,7 @@ export default function Footer() {
<h3 className="heading-square font-weight-sbold" data-item=".heading-square-item">
<span className="heading-square-item"></span>Latest Properties
</h3>
<a className="post-minimal" href="single-property.html">
<div className="post-minimal-image">
<Image src="/images/featured-properties-17-480x287.jpg" alt="" width="161" height="136" />
</div>
<div className="post-minimal-body">
<div className="post-minimal-title">
<span className="text-colorFooterText! hover:text-colorFooterTextHover!">
Retail Store Southwest 186th Street
</span>
</div>
<div className="post-minimal-text">
<span>From $120/month</span>
</div>
</div>
</a>
<a className="post-minimal" href="single-property.html">
<div className="post-minimal-image">
<Image src="/images/featured-properties-17-480x287.jpg" alt="" width="161" height="136" />
</div>
<div className="post-minimal-body">
<div className="post-minimal-title">
<span className="text-colorFooterText! hover:text-colorFooterTextHover!">
Apartment Building with Subunits
</span>
</div>
<div className="post-minimal-text">
<span>From $120/month</span>
</div>
</div>
</a>
<ListOfFeaturedProperiesFooter />
</div>
<div className="col-md-6 col-lg-4 col-xl-3 col-bordered">
<h3 className="heading-square font-weight-sbold" data-item=".heading-square-item">
@ -103,16 +84,13 @@ export default function Footer() {
</form> */}
<ul className="list-inline-1 space-x-5">
<li>
<a className="icon fa-facebook text-4xl!" href="#"></a>
<a target="_blank" className="icon fa-facebook text-4xl!" href={shareUrl.facebook}></a>
</li>
<li>
<a className="icon fa-twitter text-4xl!" href="#"></a>
<a target="_blank" className="icon fa-twitter text-4xl!" href={shareUrl.twitter}></a>
</li>
<li>
<a className="icon fa-google-plus text-4xl!" href="#"></a>
</li>
<li>
<a className="icon fa-pinterest-p text-4xl!" href="#"></a>
<a target="_blank" className="icon fa-linkedin text-4xl!" href={shareUrl.linkedin}></a>
</li>
</ul>
</div>
@ -127,15 +105,15 @@ export default function Footer() {
<span>Dynamic Realty</span> <span>&copy;&nbsp;</span>
<span className="copyright-year"></span>
<span>&nbsp;</span>
<a href="privacy-policy.html">Privacy Policy</a>
<a href="/privacy-policy">Privacy Policy</a>
</p>
</div>
<div className="col-sm-6 text-sm-right">
<div className="right-1">
{/* <div className="right-1">
<a href="#">
<span className="icon mdi mdi-plus"></span>Book Appointment
</a>
</div>
</div> */}
</div>
</div>
</div>

View File

@ -1,16 +1,7 @@
import { headers } from "next/headers";
export default async function Header() {
const headerList = await headers();
const fullUrl = headerList.get("x-full-url");
const headerActive = (pathName: string) => {
if (!fullUrl) return "";
const splittedUrl = fullUrl.split("/");
return splittedUrl[3] === pathName ? "active" : "";
};
import { HeaderDropdown, HeaderDropdownGroup, HeaderDropdownMenu } from "./HeaderDropdown";
import HeaderFeaturedHomes from "./HeaderFeaturedHomes";
export default function Header() {
return (
<header className="section page-header">
<div className="rd-navbar-wrap">
@ -103,213 +94,248 @@ export default async function Header() {
srcSet="images/logo2.png 2x"
/>
</a>
{/* <h3 className="text-colorHeaderText! font-montserrat! font-semibold! hidden lg:inline">
Dynamic Realty
</h3> */}
</div>
</div>
<div className="rd-navbar-nav-wrap">
<ul className="rd-navbar-nav">
<li className="rd-nav-item">
<a className={`rd-nav-link rd-nav-link-custom ${headerActive("")}`} href="/">
HOME
<a className="rd-nav-link rd-nav-link-custom" href="#">
Buying / Selling
</a>
<HeaderDropdown>
<HeaderDropdownGroup>
<div className="pr-2">
<form>
<div className="form-wrap">
<input
type="text"
id="search"
className="form-input border-colorHeader!"
placeholder="Search"
/>
</div>
</form>
</div>
<div className="mt-2">
<HeaderFeaturedHomes />
</div>
</HeaderDropdownGroup>
<HeaderDropdownGroup title="Why Dynamic Realty?">
<HeaderDropdownMenu
list={[
{
title:
"Committed to providing a personalized structure to each client, with on-site management & a robust property management",
},
{
title:
"We strive to provide a service that meets the needs of all our customers & are proud to have been helping people with their real estate needs for the past 20 years",
},
{
title:
"We understand the real estate sector inside out & are committed to providing a professional service to our clients. We strive to make the process of reaching your real estate goals as easy & stress-free as possible",
},
]}
/>
</HeaderDropdownGroup>
<HeaderDropdownGroup title="Future Owners">
<HeaderDropdownMenu
list={[
{
title: "Moving In",
child: [
{
title: "What To Be Done Prior",
href: "#",
},
{
title: "Next Steps (keys, utilities, etc)",
href: "#",
},
{
title: "Common Questions",
href: "#",
},
],
},
{
title: "Welcome home",
child: [
{
title: "Payment",
href: "#",
},
{
title: "Utilities",
href: "#",
},
{
title: "Maintenance",
href: "#",
},
],
},
{
title: "Moving Out",
child: [
{
title: "What To Be Done Prior",
href: "#",
},
{
title: "Next Steps (keys, utilities, etc)",
href: "#",
},
{
title: "Common Questions",
href: "#",
},
],
},
]}
/>
</HeaderDropdownGroup>
<HeaderDropdownGroup className="flex! pr-4!">
<div className="flex flex-col self-end!">
<a className="button button-secondary" href="/">
FAQ
</a>
<a className="button button-primary mt-2!" href="/">
LOOKING FOR SELL
</a>
</div>
</HeaderDropdownGroup>
</HeaderDropdown>
</li>
<li className="rd-nav-item">
<a
className="rd-nav-link rd-nav-link-custom"
href="https://dynamicrealtyinc.managebuilding.com/Resident/public/rentals"
target="_blank"
>
RENTAL PORTAL
<a className="rd-nav-link rd-nav-link-custom" href="#">
Leasing
</a>
<HeaderDropdown>
<HeaderDropdownGroup>
<div className="pr-2">
<form>
<div className="form-wrap">
<input
type="text"
id="search"
className="form-input border-colorHeader!"
placeholder="Search"
/>
</div>
</form>
</div>
<div className="mt-2">
<HeaderFeaturedHomes />
</div>
</HeaderDropdownGroup>
<HeaderDropdownGroup title="Future Residents">
<HeaderDropdownMenu
list={[
{
title: "Our Rentals",
href: "#",
},
{
title: "Application Process",
child: [
{
title: "Before You Apply",
href: "#",
},
{
title: "Rental Criteria",
href: "#",
},
{
title: "Before You Apply",
href: "#",
},
],
},
]}
/>
</HeaderDropdownGroup>
<HeaderDropdownGroup title="Current Residents">
<HeaderDropdownMenu
list={[
{
title: "Moving In",
child: [
{
title: "What To Be Done Prior",
href: "#",
},
{
title: "Next Steps (keys, utilities, etc)",
href: "#",
},
{
title: "Common Questions",
href: "#",
},
],
},
{
title: "Welcome home",
child: [
{
title: "Payment",
href: "#",
},
{
title: "Utilities",
href: "#",
},
{
title: "Maintenance",
href: "#",
},
],
},
{
title: "Moving Out",
child: [
{
title: "What To Be Done Prior",
href: "#",
},
{
title: "Next Steps (keys, utilities, etc)",
href: "#",
},
{
title: "Common Questions",
href: "#",
},
],
},
]}
/>
</HeaderDropdownGroup>
<HeaderDropdownGroup className="flex! pr-4!">
<div className="flex flex-col self-end!">
<a className="button button-secondary" href="#">
FAQ
</a>
<a className="button button-primary mt-2!" href="#">
APPLY NOW
</a>
</div>
</HeaderDropdownGroup>
</HeaderDropdown>
</li>
<li className="rd-nav-item">
<a
className={`rd-nav-link rd-nav-link-custom ${headerActive("listings-for-sale")}`}
href="/listings-for-sale"
>
LISTINGS FOR SALE
</a>
</li>
<li className="rd-nav-item">
<a
className={`rd-nav-link rd-nav-link-custom ${headerActive("listings-for-rent")}`}
href="/listings-for-rent"
>
LISTINGS FOR RENT
</a>
</li>
<li className="rd-nav-item">
<a className={`rd-nav-link rd-nav-link-custom ${headerActive("blog")}`} href="/blog">
BLOGS
<a className={`rd-nav-link rd-nav-link-custom`} href="/blog">
Resources
</a>
</li>
<li className="rd-nav-item block lg:hidden!">
<a className="rd-nav-link rd-nav-link-custom" href="/login">
LOGIN
Login
</a>
</li>
{/* <li className="rd-nav-item">
<a className="rd-nav-link" href="#">
Properties
</a>
<ul className="rd-menu rd-navbar-dropdown">
<li className="rd-dropdown-item">
<a className="rd-dropdown-link" href="properties-grid.html">
Properties Grid
</a>
</li>
<li className="rd-dropdown-item">
<a className="rd-dropdown-link" href="properties-grid-2.html">
Properties Grid 2
</a>
</li>
<li className="rd-dropdown-item">
<a className="rd-dropdown-link" href="properties-list.html">
Properties List
</a>
</li>
<li className="rd-dropdown-item">
<a className="rd-dropdown-link" href="submit-property.html">
Submit property
</a>
</li>
<li className="rd-dropdown-item">
<a className="rd-dropdown-link" href="single-property.html">
Single Property
</a>
</li>
</ul>
</li>
<li className="rd-nav-item">
<a className="rd-nav-link" href="about-us.html">
About Us
</a>
</li>
<li className="rd-nav-item">
<a className="rd-nav-link" href="blog.html">
Blog
</a>
<ul className="rd-menu rd-navbar-dropdown">
<li className="rd-dropdown-item">
<a className="rd-dropdown-link" href="blog-post.html">
Blog post
</a>
</li>
</ul>
</li>
<li className="rd-nav-item">
<a className="rd-nav-link" href="#">
Pages
</a>
<ul className="rd-menu rd-navbar-megamenu">
<li className="rd-megamenu-item">
<h6 className="rd-megamenu-title">Pages 1</h6>
<ul className="rd-megamenu-list">
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="agents.html">
Agents
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="agent-single-page.html">
Agent Single Page
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="careers.html">
Careers
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="gallery.html">
Gallery
</a>
</li>
</ul>
</li>
<li className="rd-megamenu-item">
<h6 className="rd-megamenu-title">Pages 2</h6>
<ul className="rd-megamenu-list">
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="search-results.html">
Search results
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="coming-soon.html">
Coming Soon
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="404.html">
404
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="privacy-policy.html">
Privacy Policy
</a>
</li>
</ul>
</li>
<li className="rd-megamenu-item">
<h6 className="rd-megamenu-title">Elements</h6>
<ul className="rd-megamenu-list">
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="typography.html">
Typography
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="buttons.html">
Buttons
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="forms.html">
Forms
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="tabs-and-accordions.html">
Tabs and accordions
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="progress-bars.html">
Progress bars
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="tables.html">
Tables
</a>
</li>
<li className="rd-megamenu-list-item">
<a className="rd-megamenu-list-link" href="grid-system.html">
Grid System
</a>
</li>
</ul>
</li>
</ul>
</li>
<li className="rd-nav-item">
<a className="rd-nav-link" href="contact-us.html">
Contact Us
</a>
</li> */}
</ul>
<div className="rd-navbar-main-item context-dark">
<a className="button button-sm button-primary-outline" href="#">
BOOK APPOINTMENT
</a>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,81 @@
import { createSlug } from "@/utils/general";
import { cn } from "@/utils/style";
import { FC, Fragment, PropsWithChildren } from "react";
type HeaderDropdownProps = {
className?: string;
};
export const HeaderDropdown: FC<PropsWithChildren<HeaderDropdownProps>> = ({ children, className }) => {
return <ul className={cn("rd-menu rd-navbar-megamenu", className)}>{children}</ul>;
};
type HeaderDropdownGroupProps = {
title?: string;
className?: string;
};
export const HeaderDropdownGroup: FC<PropsWithChildren<HeaderDropdownGroupProps>> = ({
children,
title,
className,
}) => {
return (
<li className={cn("rd-megamenu-item", className)}>
{!!title && <h6 className="rd-megamenu-title">{title}</h6>}
{children}
</li>
);
};
type HeaderDropdownMenuItem = { title: string; href?: string };
type HeaderDropdownMenuItemWithChild = { title: string; child: HeaderDropdownMenuItem[] };
type HeaderDropdownMenuProps = {
list?: (HeaderDropdownMenuItem | HeaderDropdownMenuItemWithChild)[];
};
export const HeaderDropdownMenu: FC<HeaderDropdownMenuProps> = ({ list = [] }) => {
return (
<ul className="rd-megamenu-list">
{list.map((item, idx) => {
const collapseId = createSlug(`collapse-${item.title}-${idx}`);
const hasChild = "child" in item;
const hrefClass = hasChild || !!item?.href ? "text-colorLinkText1! hover:text-colorLinkText2!" : "";
return (
<Fragment key={idx}>
{hasChild && (
<li className="rd-megamenu-list-item">
<a
className={`rd-megamenu-list-link cursor-pointer ${hrefClass}`}
data-toggle="collapse"
data-target={`#${collapseId}`}
aria-controls={collapseId}
aria-expanded="false"
>
{item.title}
</a>
<div className="pl-3 mt-2 space-y-2 collapse visible!" id={collapseId}>
{item.child.map((itemChild, itemChildIdx) => (
<a key={itemChildIdx} className={`rd-megamenu-list-link ${hrefClass}`} href={itemChild.href}>
- {itemChild.title}
</a>
))}
</div>
</li>
)}
{!hasChild && (
<li className="rd-megamenu-list-item">
<a className={`rd-megamenu-list-link ${hrefClass}`} href={item.href}>
{item.title}
</a>
</li>
)}
</Fragment>
);
})}
</ul>
);
};

View File

@ -0,0 +1,34 @@
"use client";
import Image from "next/image";
export default function HeaderFeaturedHomes() {
const images = [
"/images/home1.jpg",
"/images/home2.jpg",
"/images/home3.jpg",
"/images/home4.jpg",
"/images/home5.jpg",
"/images/home6.jpg",
];
return (
<div className="p-2">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-1">
{images.map((src, index) => (
<div
key={index}
className="overflow-hidden rounded-lg shadow hover:shadow-lg transition-shadow duration-300 relative h-16"
>
<Image
src={src}
alt={`Gallery image ${index + 1}`}
className="object-cover transform transition-transform duration-300 hover:scale-105 cursor-pointer"
fill
/>
</div>
))}
</div>
</div>
);
}

View File

@ -0,0 +1,74 @@
import { fetchPageBySlug } from "@/services/payload/page";
import HeroImage from "../HeroImage";
import { RenderBlocks } from "../blocks/RenderBlocks";
type shareUrlDestination = "facebook" | "linkedin" | "twitter";
type DetailPageProps = {
data: Awaited<ReturnType<typeof fetchPageBySlug>>;
shareUrl: Record<shareUrlDestination, string>;
};
export default function DetailPage({ data, shareUrl }: DetailPageProps) {
const page = data;
if (!page) return <></>;
return (
<>
<HeroImage imgSrc={page.heroImg?.url} title={page.data.title} />
<section className="section section-md bg-colorSection1">
<div className="container">
<div className="row justify-content-lg-center">
<div className="col-12">
<article className="blog-post-solo">
<div className="blog-post-solo-part">
<RenderBlocks blocks={page.data.layout} />
</div>
<div className="blog-post-solo-footer">
<div className="blog-post-solo-footer-left">
<ul className="blog-post-solo-footer-list">
<li>
<span className="icon mdi mdi-clock"></span>
<a href="#">{page.createdAt}</a>
</li>
</ul>
</div>
<div className="blog-post-solo-footer-right">
<ul className="blog-post-solo-footer-list-1">
<li>
<span>Share this post</span>
</li>
<li>
<a
target="_blank"
className="icon icon-circle icon-rounded icon-5 fa-facebook"
href={shareUrl.facebook}
></a>
</li>
<li>
<a
target="_blank"
className="icon icon-circle icon-rounded icon-6 fa-twitter"
href={shareUrl.twitter}
></a>
</li>
<li>
<a
target="_blank"
className="icon icon-circle icon-rounded icon-4 fa-linkedin"
href={shareUrl.linkedin}
></a>
</li>
</ul>
</div>
</div>
</article>
</div>
</div>
</div>
</section>
</>
);
}

View File

@ -0,0 +1,37 @@
"use client";
import { useLatestPropertyQuery } from "@/services/hooks/property";
import { formatCurrency } from "@/utils/general";
import Image from "next/image";
import { useEffect } from "react";
export default function ListOfFeaturedProperiesFooter() {
const { data, _fetch } = useLatestPropertyQuery();
useEffect(() => {
_fetch();
}, []);
const latestProperties = data ? data.slice(0, 2) : [];
return (
<>
{latestProperties.length > 0 &&
latestProperties.map((property, index) => (
<a key={index} className="post-minimal" href={`single-property/${property.slug}`}>
<div className="post-minimal-image">
<Image src={property.images?.[0]?.url ?? ""} alt={property.title} width={161} height={136} />
</div>
<div className="post-minimal-body">
<div className="post-minimal-title">
<span className="text-colorFooterText! hover:text-colorFooterTextHover!">{property.title}</span>
</div>
<div className="post-minimal-text">
<span>From {formatCurrency(property.price)}/month</span>
</div>
</div>
</a>
))}
</>
);
}

View File

@ -1,3 +1,4 @@
"use client";
import { CardPropertyData } from "@/schema/property";
import { useState } from "react";
import { fetchLatestPropertyREST } from "../rest/property";

View File

@ -0,0 +1,40 @@
import payloadConfig from "@/payload.config";
import { formatDate } from "@/utils/datetime";
import { getPayload } from "payload";
export const fetchPageBySlug = async ({ slug }: { slug: string | undefined }) => {
const payload = await getPayload({ config: payloadConfig });
const result = await payload.find({
collection: "pages",
// draft,
limit: 1,
pagination: false,
// overrideAccess: draft,
where: {
_status: { equals: "published" },
slug: {
equals: slug,
},
},
});
if (!result.docs?.[0]) {
return null;
}
const data = result.docs[0];
const heroImgUrl = typeof data.hero_img !== "number" ? (data?.hero_img?.url ?? "") : "";
const heroImgAlt = typeof data.hero_img !== "number" ? (data?.hero_img?.alt ?? "") : "";
return {
data: data,
createdAt: formatDate(data.createdAt),
updatedAt: formatDate(data.updatedAt),
heroImg: {
url: heroImgUrl,
alt: heroImgAlt,
},
};
};

View File

@ -24,3 +24,10 @@ export function formatCurrency(num: number): string {
//maximumFractionDigits: 0, // Causes 2500.99 to be printed as $2,501
}).format(num);
}
export function createSlug(val: string) {
return val
.replace(/ /g, "-")
.replace(/[^\w-/]+/g, "")
.toLowerCase();
}

6
src/utils/style.ts Normal file
View File

@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@ -5044,6 +5044,7 @@ __metadata:
"@types/node": "npm:^20"
"@types/react": "npm:^19"
"@types/react-dom": "npm:^19"
clsx: "npm:^2.1.1"
country-state-city: "npm:^3.2.1"
dayjs: "npm:^1.11.13"
eslint: "npm:^9"
@ -5061,6 +5062,7 @@ __metadata:
react-select: "npm:^5.10.1"
react-toastify: "npm:^11.0.5"
swiper: "npm:^11.2.6"
tailwind-merge: "npm:^3.2.0"
tailwindcss: "npm:^4"
typescript: "npm:^5"
languageName: unknown
@ -9759,6 +9761,13 @@ __metadata:
languageName: node
linkType: hard
"tailwind-merge@npm:^3.2.0":
version: 3.2.0
resolution: "tailwind-merge@npm:3.2.0"
checksum: 10c0/294f6c2db0df74405bff126107107426c3126a70a1717d78e8d6811db65546c9bb3d61282bdb8d9fbded23f6bc8ec3e8e61031a4f53265f90b7f3dba558f88f4
languageName: node
linkType: hard
"tailwindcss@npm:4.1.4, tailwindcss@npm:^4":
version: 4.1.4
resolution: "tailwindcss@npm:4.1.4"