fix: home fetching featured properties and header active styling

This commit is contained in:
RizqiSyahrendra 2025-04-24 14:47:06 +07:00
parent 8d2a2c83de
commit d8d7532169
11 changed files with 204 additions and 41 deletions

View File

@ -41,6 +41,9 @@
.rd-nav-link-custom {
@apply text-colorHeader! lg:text-colorHeaderText! lg:hover:text-colorHeaderTextHover!
}
.rd-nav-link-custom.active {
@apply text-colorHeader! lg:text-colorHeaderTextHover! lg:hover:text-colorHeaderTextHover!
}
}
@media (prefers-color-scheme: dark) {

View File

@ -1,27 +1,15 @@
"use client";
import CardProduct from "@/components/CardProduct";
import ContactFormSection from "@/components/ContactFormSection";
import GoogleReviewBox from "@/components/GoogleReviewBox";
import HeroSlider from "@/components/HeroSlider";
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 { useRef } from "react";
import { Suspense } from "react";
export default function Home() {
const formRef = useRef<HTMLDivElement | null>(null);
function scrollToForm() {
formRef.current?.scrollIntoView?.({ behavior: "smooth" });
}
return (
<>
<HeroSlider onClickBook={scrollToForm} />
<div className="lg:hidden" ref={formRef}>
<ContactFormSection />
</div>
<HomeTopSection />
<section className="section section-lg bg-colorSection1">
<div className="container">
@ -131,29 +119,26 @@ export default function Home() {
<div className="layout-4-item">
<ul className="list-inline-bordered heading-7">
<li>
<a href="#">For Rent</a>
<a href="/listings-for-rent">For Rent</a>
</li>
<li>
<a href="#">For Sale</a>
<a href="/listings-for-sale">For Sale</a>
</li>
</ul>
</div>
</div>
<div className="my-10!">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
<CardProduct />
<CardProduct />
<CardProduct />
<CardProduct />
<CardProduct />
<CardProduct />
</div>
<div className="text-center mt-10!">
<a className="button button-primary" href="/">
View all properties
</a>
</div>
<Suspense
fallback={
<>
<div className="mt-5">
<Loader />
</div>
</>
}
>
<ListOfFeaturedProperty />
</Suspense>
</div>
</div>
</section>

View File

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

View File

@ -179,4 +179,7 @@ export const Properties: CollectionConfig = {
group: "Properties",
useAsTitle: "name",
},
access: {
read: () => true,
},
};

View File

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

View File

@ -1,4 +1,16 @@
export default function Header() {
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" : "";
};
return (
<header className="section page-header">
<div className="rd-navbar-wrap">
@ -98,8 +110,8 @@ export default function Header() {
</div>
<div className="rd-navbar-nav-wrap">
<ul className="rd-navbar-nav">
<li className="rd-nav-item active">
<a className="rd-nav-link" href="/">
<li className="rd-nav-item">
<a className={`rd-nav-link rd-nav-link-custom ${headerActive("")}`} href="/">
HOME
</a>
</li>
@ -113,17 +125,23 @@ export default function Header() {
</a>
</li>
<li className="rd-nav-item">
<a className="rd-nav-link rd-nav-link-custom" href="/listings-for-sale">
<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" href="/listings-for-rent">
<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" href="/blog">
<a className={`rd-nav-link rd-nav-link-custom ${headerActive("blog")}`} href="/blog">
BLOGS
</a>
</li>

View File

@ -1,7 +1,6 @@
import { CardPropertyData } from "@/schema/property";
import { formatCurrency } from "@/utils/general";
import Image from "next/image";
import Link from "next/link";
type CardPropertyProps = {
data: CardPropertyData;
@ -37,7 +36,7 @@ export default function CardProperty({ data }: CardPropertyProps) {
</div>
</div>
<h4 className="product-classic-title">
<Link href={href}>{data.title}</Link>
<a href={href}>{data.title}</a>
</h4>
<div className="product-classic-divider"></div>
<ul className="product-classic-list">

View File

@ -0,0 +1,23 @@
import { fetchPropertyLatest } from "@/services/payload/property";
import CardProperty from "./CardProperty";
export default async function ListOfFeaturedProperty() {
const latestProperties = await fetchPropertyLatest();
return (
<>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
{latestProperties.formattedData.map((pr, idx) => (
<CardProperty key={idx} data={pr} />
))}
</div>
{latestProperties.formattedData.length > 0 && (
<div className="text-center mt-10!">
<a className="button button-primary" href="/listings-for-sale">
View all properties
</a>
</div>
)}
</>
);
}

View File

@ -0,0 +1,24 @@
import { CardPropertyData } from "@/schema/property";
import { useState } from "react";
import { fetchLatestPropertyREST } from "../rest/property";
export function useLatestPropertyQuery() {
const [data, setData] = useState<CardPropertyData[]>([]);
const [isFetching, setFetching] = useState(false);
async function _fetch() {
setFetching(true);
const res = await fetchLatestPropertyREST();
setFetching(false);
if (Array.isArray(res?.formattedData)) {
setData(res.formattedData);
}
}
return {
_fetch,
data,
isFetching,
};
}

View File

@ -179,3 +179,33 @@ export async function fetchPropertyDetail({ slug }: FetchPropertyDetailParams) {
},
};
}
export async function fetchPropertyLatest() {
const payload = await getPayload({ config: payloadConfig });
const limitPerPage = 6;
const dataQuery = await payload.find({
collection: "properties",
limit: limitPerPage,
});
const formattedData: CardPropertyData[] = dataQuery.docs.map((item) => {
return {
slug: item.slug ?? "",
title: item.name,
price: item.base_price,
area: item.aboutGroup.area,
propertyType: item.property_type,
bathrooms_count: item.aboutGroup.bathrooms_count,
bedrooms_count: item.aboutGroup.bedrooms_count,
images: item.images.map((img) =>
typeof img !== "number" ? { url: img?.url ?? "", alt: img.alt } : { url: "", alt: "" }
),
posted_at: formatDate(item.createdAt),
};
});
return {
...dataQuery,
formattedData,
};
}

View File

@ -0,0 +1,50 @@
import { Property } from "@/payload-types";
import { CardPropertyData } from "@/schema/property";
import { formatDate } from "@/utils/datetime";
import { PaginatedDocs, Where } from "payload";
import { stringify } from "qs-esm";
export async function fetchLatestPropertyREST() {
const limitPerPage = 6;
const queryCondition: Where = {
_status: { equals: "published" },
};
const queryParams = stringify(
{
page: 1,
pagination: true,
limit: limitPerPage,
where: queryCondition,
},
{ addQueryPrefix: true }
);
const req = await fetch(`/api/properties${queryParams}`);
if (req.ok) {
const resData = (await req.json()) as PaginatedDocs<Property>;
const formattedData: CardPropertyData[] = resData.docs.map((item) => {
return {
slug: item.slug ?? "",
title: item.name,
price: item.base_price,
area: item.aboutGroup.area,
propertyType: item.property_type,
bathrooms_count: item.aboutGroup.bathrooms_count,
bedrooms_count: item.aboutGroup.bedrooms_count,
images: item.images.map((img) =>
typeof img !== "number" ? { url: img?.url ?? "", alt: img.alt } : { url: "", alt: "" }
),
posted_at: formatDate(item.createdAt),
};
});
return {
...resData,
formattedData,
};
} else {
return null;
}
}