From d8d7532169e37c31fc741fd7fa1d1bc1365ce989 Mon Sep 17 00:00:00 2001 From: RizqiSyahrendra Date: Thu, 24 Apr 2025 14:47:06 +0700 Subject: [PATCH] fix: home fetching featured properties and header active styling --- src/app/(main)/globals.css | 3 ++ src/app/(main)/page.tsx | 51 +++++++------------ src/app/(main)/property/[slug]/loading.tsx | 5 ++ src/collections/Properties.ts | 3 ++ src/components/homes/HomeTopSection.tsx | 23 +++++++++ src/components/layouts/Header.tsx | 30 ++++++++--- src/components/properties/CardProperty.tsx | 3 +- .../properties/ListOfFeaturedProperty.tsx | 23 +++++++++ src/services/hooks/property.ts | 24 +++++++++ src/services/payload/property.ts | 30 +++++++++++ src/services/rest/property.ts | 50 ++++++++++++++++++ 11 files changed, 204 insertions(+), 41 deletions(-) create mode 100644 src/app/(main)/property/[slug]/loading.tsx create mode 100644 src/components/homes/HomeTopSection.tsx create mode 100644 src/components/properties/ListOfFeaturedProperty.tsx create mode 100644 src/services/hooks/property.ts create mode 100644 src/services/rest/property.ts diff --git a/src/app/(main)/globals.css b/src/app/(main)/globals.css index 29b5ab8..f7699f9 100644 --- a/src/app/(main)/globals.css +++ b/src/app/(main)/globals.css @@ -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) { diff --git a/src/app/(main)/page.tsx b/src/app/(main)/page.tsx index 579f3fd..a7ec3a8 100644 --- a/src/app/(main)/page.tsx +++ b/src/app/(main)/page.tsx @@ -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(null); - - function scrollToForm() { - formRef.current?.scrollIntoView?.({ behavior: "smooth" }); - } - return ( <> - - -
- -
+
@@ -131,29 +119,26 @@ export default function Home() {
-
- - - - - - -
- - + +
+ +
+ + } + > + +
diff --git a/src/app/(main)/property/[slug]/loading.tsx b/src/app/(main)/property/[slug]/loading.tsx new file mode 100644 index 0000000..779bae8 --- /dev/null +++ b/src/app/(main)/property/[slug]/loading.tsx @@ -0,0 +1,5 @@ +import LoaderFixed from "@/components/loaders/LoaderFixed"; + +export default function Loading() { + return ; +} diff --git a/src/collections/Properties.ts b/src/collections/Properties.ts index 1ee7a3f..3e7d0c2 100644 --- a/src/collections/Properties.ts +++ b/src/collections/Properties.ts @@ -179,4 +179,7 @@ export const Properties: CollectionConfig = { group: "Properties", useAsTitle: "name", }, + access: { + read: () => true, + }, }; diff --git a/src/components/homes/HomeTopSection.tsx b/src/components/homes/HomeTopSection.tsx new file mode 100644 index 0000000..2c30b63 --- /dev/null +++ b/src/components/homes/HomeTopSection.tsx @@ -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(null); + + function scrollToForm() { + formRef.current?.scrollIntoView?.({ behavior: "smooth" }); + } + + return ( + <> + + +
+ +
+ + ); +} diff --git a/src/components/layouts/Header.tsx b/src/components/layouts/Header.tsx index 304c89e..fc8378f 100644 --- a/src/components/layouts/Header.tsx +++ b/src/components/layouts/Header.tsx @@ -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 (
@@ -98,8 +110,8 @@ export default function Header() {
    -
  • - +
  • + HOME
  • @@ -113,17 +125,23 @@ export default function Header() {
  • - + LISTINGS FOR SALE
  • - + LISTINGS FOR RENT
  • - + BLOGS
  • diff --git a/src/components/properties/CardProperty.tsx b/src/components/properties/CardProperty.tsx index 7bfce0e..8c4cf2b 100644 --- a/src/components/properties/CardProperty.tsx +++ b/src/components/properties/CardProperty.tsx @@ -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) {

- {data.title} + {data.title}

    diff --git a/src/components/properties/ListOfFeaturedProperty.tsx b/src/components/properties/ListOfFeaturedProperty.tsx new file mode 100644 index 0000000..b2e16b0 --- /dev/null +++ b/src/components/properties/ListOfFeaturedProperty.tsx @@ -0,0 +1,23 @@ +import { fetchPropertyLatest } from "@/services/payload/property"; +import CardProperty from "./CardProperty"; + +export default async function ListOfFeaturedProperty() { + const latestProperties = await fetchPropertyLatest(); + + return ( + <> +
    + {latestProperties.formattedData.map((pr, idx) => ( + + ))} +
    + {latestProperties.formattedData.length > 0 && ( + + )} + + ); +} diff --git a/src/services/hooks/property.ts b/src/services/hooks/property.ts new file mode 100644 index 0000000..39ae8d8 --- /dev/null +++ b/src/services/hooks/property.ts @@ -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([]); + 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, + }; +} diff --git a/src/services/payload/property.ts b/src/services/payload/property.ts index 0f1062a..c4d60ba 100644 --- a/src/services/payload/property.ts +++ b/src/services/payload/property.ts @@ -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, + }; +} diff --git a/src/services/rest/property.ts b/src/services/rest/property.ts new file mode 100644 index 0000000..e0cb3ee --- /dev/null +++ b/src/services/rest/property.ts @@ -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; + 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; + } +}