fix: home fetching featured properties and header active styling
This commit is contained in:
parent
8d2a2c83de
commit
d8d7532169
@ -41,6 +41,9 @@
|
|||||||
.rd-nav-link-custom {
|
.rd-nav-link-custom {
|
||||||
@apply text-colorHeader! lg:text-colorHeaderText! lg:hover:text-colorHeaderTextHover!
|
@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) {
|
@media (prefers-color-scheme: dark) {
|
||||||
|
@ -1,27 +1,15 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import CardProduct from "@/components/CardProduct";
|
|
||||||
import ContactFormSection from "@/components/ContactFormSection";
|
|
||||||
import GoogleReviewBox from "@/components/GoogleReviewBox";
|
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 Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRef } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const formRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
function scrollToForm() {
|
|
||||||
formRef.current?.scrollIntoView?.({ behavior: "smooth" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeroSlider onClickBook={scrollToForm} />
|
<HomeTopSection />
|
||||||
|
|
||||||
<div className="lg:hidden" ref={formRef}>
|
|
||||||
<ContactFormSection />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section className="section section-lg bg-colorSection1">
|
<section className="section section-lg bg-colorSection1">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
@ -131,29 +119,26 @@ export default function Home() {
|
|||||||
<div className="layout-4-item">
|
<div className="layout-4-item">
|
||||||
<ul className="list-inline-bordered heading-7">
|
<ul className="list-inline-bordered heading-7">
|
||||||
<li>
|
<li>
|
||||||
<a href="#">For Rent</a>
|
<a href="/listings-for-rent">For Rent</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#">For Sale</a>
|
<a href="/listings-for-sale">For Sale</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="my-10!">
|
<div className="my-10!">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
|
<Suspense
|
||||||
<CardProduct />
|
fallback={
|
||||||
<CardProduct />
|
<>
|
||||||
<CardProduct />
|
<div className="mt-5">
|
||||||
<CardProduct />
|
<Loader />
|
||||||
<CardProduct />
|
</div>
|
||||||
<CardProduct />
|
</>
|
||||||
</div>
|
}
|
||||||
|
>
|
||||||
<div className="text-center mt-10!">
|
<ListOfFeaturedProperty />
|
||||||
<a className="button button-primary" href="/">
|
</Suspense>
|
||||||
View all properties
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
5
src/app/(main)/property/[slug]/loading.tsx
Normal file
5
src/app/(main)/property/[slug]/loading.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import LoaderFixed from "@/components/loaders/LoaderFixed";
|
||||||
|
|
||||||
|
export default function Loading() {
|
||||||
|
return <LoaderFixed />;
|
||||||
|
}
|
@ -179,4 +179,7 @@ export const Properties: CollectionConfig = {
|
|||||||
group: "Properties",
|
group: "Properties",
|
||||||
useAsTitle: "name",
|
useAsTitle: "name",
|
||||||
},
|
},
|
||||||
|
access: {
|
||||||
|
read: () => true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
23
src/components/homes/HomeTopSection.tsx
Normal file
23
src/components/homes/HomeTopSection.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -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 (
|
return (
|
||||||
<header className="section page-header">
|
<header className="section page-header">
|
||||||
<div className="rd-navbar-wrap">
|
<div className="rd-navbar-wrap">
|
||||||
@ -98,8 +110,8 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="rd-navbar-nav-wrap">
|
<div className="rd-navbar-nav-wrap">
|
||||||
<ul className="rd-navbar-nav">
|
<ul className="rd-navbar-nav">
|
||||||
<li className="rd-nav-item active">
|
<li className="rd-nav-item">
|
||||||
<a className="rd-nav-link" href="/">
|
<a className={`rd-nav-link rd-nav-link-custom ${headerActive("")}`} href="/">
|
||||||
HOME
|
HOME
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -113,17 +125,23 @@ export default function Header() {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li className="rd-nav-item">
|
<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
|
LISTINGS FOR SALE
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li className="rd-nav-item">
|
<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
|
LISTINGS FOR RENT
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li className="rd-nav-item">
|
<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
|
BLOGS
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { CardPropertyData } from "@/schema/property";
|
import { CardPropertyData } from "@/schema/property";
|
||||||
import { formatCurrency } from "@/utils/general";
|
import { formatCurrency } from "@/utils/general";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
type CardPropertyProps = {
|
type CardPropertyProps = {
|
||||||
data: CardPropertyData;
|
data: CardPropertyData;
|
||||||
@ -37,7 +36,7 @@ export default function CardProperty({ data }: CardPropertyProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h4 className="product-classic-title">
|
<h4 className="product-classic-title">
|
||||||
<Link href={href}>{data.title}</Link>
|
<a href={href}>{data.title}</a>
|
||||||
</h4>
|
</h4>
|
||||||
<div className="product-classic-divider"></div>
|
<div className="product-classic-divider"></div>
|
||||||
<ul className="product-classic-list">
|
<ul className="product-classic-list">
|
||||||
|
23
src/components/properties/ListOfFeaturedProperty.tsx
Normal file
23
src/components/properties/ListOfFeaturedProperty.tsx
Normal 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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
24
src/services/hooks/property.ts
Normal file
24
src/services/hooks/property.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
50
src/services/rest/property.ts
Normal file
50
src/services/rest/property.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user