feat: property detail FE integration

This commit is contained in:
RizqiSyahrendra 2025-04-23 16:22:30 +07:00
parent 6fbbb73e05
commit e483f8f281
8 changed files with 293 additions and 237 deletions

View File

@ -1,48 +1,41 @@
import CardProperty from "@/components/CardProperty"; import CardProperty from "@/components/CardProperty";
import HeroImage from "@/components/HeroImage"; import HeroImage from "@/components/HeroImage";
import { CardPropertyData } from "@/schema/property"; import { fetchPropertyDetail, fetchPropertySuggestion } from "@/services/payload/property";
import { formatCurrency } from "@/utils/general"; import { RichText } from "@payloadcms/richtext-lexical/react";
import { headers } from "next/headers";
import Image from "next/image";
import { notFound } from "next/navigation";
const similarPropertiesData: CardPropertyData[] = [ export default async function ListingsForRentDetail({ params }: { params: Promise<{ slug: string }> }) {
{ const slug = (await params).slug;
title: "401 Biscayne Boulevard, Miami", const propertyDetail = await fetchPropertyDetail({ slug });
slug: "401-biscayne-boulevard", if (!propertyDetail) return notFound();
images: [
{ url: "/images/featured-properties-01-480x287.jpg", alt: "biscayne boulevard" }, const { data, formattedData } = propertyDetail;
{ url: "/images/featured-properties-01-480x287.jpg", alt: "biscayne boulevard" }, const isEmbedMapUrlValid = !!data?.embed_map_url && data.embed_map_url.includes("www.google.com/maps/embed");
{ url: "/images/featured-properties-01-480x287.jpg", alt: "biscayne boulevard" }, const headersList = await headers();
], const fullUrl = headersList.get("x-full-url");
price: 5000, const shareUrl = {
propertyType: "rent", facebook: `https://www.facebook.com/sharer/sharer.php?u=${fullUrl}`,
posted_at: "", linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${fullUrl}`,
area: "480", twitter: `https://twitter.com/intent/tweet?url=${fullUrl}`,
bathrooms_count: "2", };
bedrooms_count: "2",
}, const similarPropertiesData = await fetchPropertySuggestion();
{
title: "402 Biscayne Boulevard, Miami",
slug: "402-biscayne-boulevard",
images: [{ url: "/images/featured-properties-01-480x287.jpg", alt: "biscayne boulevard" }],
price: 5000,
propertyType: "rent",
posted_at: "",
area: "480",
bathrooms_count: "2",
bedrooms_count: "2",
},
];
export default function ListingsForRentDetail() {
return ( return (
<> <>
<HeroImage title="Lorem Ipsum, Dolor" /> <HeroImage title={data.name} />
<section className="section section-md bg-gray-12"> <section className="section section-md bg-gray-12">
<div className="container"> <div className="container">
<div className="row row-50"> <div className="row row-50">
<div className="col-lg-7 col-xl-8"> <div className="col-lg-7 col-xl-8">
<div className="slick-slider-1"> <div className="slick-slider-1">
<div className="slick-slider-price">$5000\mo</div> <div className="slick-slider-price bg-colorPriceTag/90!">
{formattedData.price}
{data.property_type === "rent" && "/mo"}
</div>
<div <div
className="slick-slider carousel-parent" className="slick-slider carousel-parent"
id="parent-carousel" id="parent-carousel"
@ -55,24 +48,13 @@ export default function ListingsForRentDetail() {
data-child="#child-carousel" data-child="#child-carousel"
data-for="#child-carousel" data-for="#child-carousel"
> >
<div className="item"> {formattedData.images.map((img, idx) => (
<img src="/images/single-property-1-763x443.jpg" alt="" width="763" height="443" /> <div key={idx} className="item">
<div className="bg-colorImgPlaceholder/90 w-full! h-[443px]! relative">
<Image src={img.url} alt={img.alt} fill />
</div> </div>
<div className="item">
<img src="/images/single-property-2-763x443.jpg" alt="" width="763" height="443" />
</div>
<div className="item">
<img src="/images/single-property-3-763x443.jpg" alt="" width="763" height="443" />
</div>
<div className="item">
<img src="/images/single-property-4-763x443.jpg" alt="" width="763" height="443" />
</div>
<div className="item">
<img src="/images/single-property-5-763x443.jpg" alt="" width="763" height="443" />
</div>
<div className="item">
<img src="/images/single-property-6-763x443.jpg" alt="" width="763" height="443" />
</div> </div>
))}
</div> </div>
<div <div
className="slick-slider carousel-child" className="slick-slider carousel-child"
@ -89,62 +71,50 @@ export default function ListingsForRentDetail() {
data-slide-to-scroll="1" data-slide-to-scroll="1"
data-for="#parent-carousel" data-for="#parent-carousel"
> >
<div> {formattedData.images.map((img, idx) => (
<div className="slick-slide-inner bg-[url(/images/single-property-1-763x443.jpg)]"></div> <div key={idx}>
<div className="bg-colorImgPlaceholder/90 w-[135px]! h-[89px]! relative">
<Image src={img.url} alt={img.alt} fill />
</div> </div>
<div>
<div className="slick-slide-inner bg-[url(/images/single-property-1-763x443.jpg)]"></div>
</div>
<div>
<div className="slick-slide-inner bg-[url(/images/single-property-1-763x443.jpg)]"></div>
</div>
<div>
<div className="slick-slide-inner bg-[url(/images/single-property-1-763x443.jpg)]"></div>
</div>
<div>
<div className="slick-slide-inner bg-[url(/images/single-property-1-763x443.jpg)]"></div>
</div>
<div>
<div className="slick-slide-inner bg-[url(/images/single-property-1-763x443.jpg)]"></div>
</div> </div>
))}
</div> </div>
</div> </div>
<div className="features-block"> <div className="features-block">
<div className="features-block-inner"> <div className="features-block-inner">
<div className="features-block-item"> <div className="features-block-item">
<ul className="features-block-list"> <ul className="features-block-list">
{!!data?.aboutGroup?.bathrooms_count && (
<li> <li>
<span className="icon hotel-icon-10"></span> <span className="icon hotel-icon-10"></span>
<span>2 Bathrooms</span> <span>{data.aboutGroup.bathrooms_count} Bathrooms</span>
</li> </li>
)}
{!!data?.aboutGroup?.bedrooms_count && (
<li> <li>
<span className="icon hotel-icon-05"></span> <span className="icon hotel-icon-05"></span>
<span>2 Bedrooms</span> <span>{data.aboutGroup.bedrooms_count} Bedrooms</span>
</li> </li>
)}
{!!data?.aboutGroup?.area && (
<li> <li>
<span className="icon mdi mdi-vector-square"></span> <span className="icon mdi mdi-vector-square"></span>
<span>480 Sq Ft</span> <span>{data.aboutGroup.area} Sq Ft</span>
</li> </li>
)}
</ul> </ul>
</div> </div>
<div className="features-block-item"> <div className="features-block-item">
<a className="link link-1" href="#"> {/* <a className="link link-1" href="#">
<span className="icon mdi mdi-heart-outline"></span>Add to Favorites <span className="icon mdi mdi-heart-outline"></span>Add to Favorites
</a> </a> */}
</div> </div>
</div> </div>
</div> </div>
<p>
Choose this property if you are looking for a modern house near the ocean shore. With 2 bathrooms and 2 <div className="mt-4">
bedrooms as well as a single garage, it is a perfect option for a small family. <RichText data={data.aboutGroup.description} />
</p> </div>
<p>
This home has been completely renovated within the past year and features amazing views and sunsets of
the local lake, solid wood cabinets (and loads of them), granite counters with colored glass backsplash,
sliding glass doors across the entire family room allowing beautiful views of the lake etc. Its
affordable price serves as a great bonus for a family looking for an opportunity to save money on Miami
MyHome.
</p>
<div <div
className="card-group-custom card-group-corporate" className="card-group-custom card-group-corporate"
@ -179,27 +149,19 @@ export default function ListingsForRentDetail() {
<div className="layout-1"> <div className="layout-1">
<dl className="list-terms-inline"> <dl className="list-terms-inline">
<dt>Address:</dt> <dt>Address:</dt>
<dd>Biscayne Blvd</dd> <dd>{data?.addressGroup?.address ?? ""}</dd>
</dl> </dl>
<dl className="list-terms-inline"> <dl className="list-terms-inline">
<dt>State/County:</dt> <dt>State/County:</dt>
<dd>Florida</dd> <dd>{data?.addressGroup?.state_code ?? ""}</dd>
</dl> </dl>
<dl className="list-terms-inline"> <dl className="list-terms-inline">
<dt>City:</dt> <dt>City:</dt>
<dd>Miami</dd> <dd>{data?.addressGroup?.city_code ?? ""}</dd>
</dl> </dl>
<dl className="list-terms-inline"> <dl className="list-terms-inline">
<dt>Zip:</dt> <dt>Zip:</dt>
<dd>8322</dd> <dd>{data?.addressGroup?.zip_code ?? ""}</dd>
</dl>
<dl className="list-terms-inline">
<dt>Country:</dt>
<dd>United States</dd>
</dl>
<dl className="list-terms-inline">
<dt>Area:</dt>
<dd>Lake Worth</dd>
</dl> </dl>
</div> </div>
</div> </div>
@ -237,21 +199,16 @@ export default function ListingsForRentDetail() {
> >
<div className="card-body"> <div className="card-body">
<ul className="list-marked-2 layout-2"> <ul className="list-marked-2 layout-2">
<li>2 Stories</li> {Array.isArray(data.features) &&
<li>Basketball Court</li> data.features.length > 0 &&
<li>Lawn</li> data.features.map((ft, idx) => <li key={idx}>{typeof ft !== "number" && ft.name}</li>)}
<li>Gym</li>
<li>Fireplace</li>
<li>Sprinklers</li>
<li>Private Space</li>
<li>Balcony</li>
<li>Laundry</li>
<li>Ocean View</li>
</ul> </ul>
</div> </div>
</div> </div>
</article> </article>
</div> </div>
{data.property_type === "rent" && (
<div <div
className="card-group-custom card-group-corporate" className="card-group-custom card-group-corporate"
id="accordion0" id="accordion0"
@ -285,38 +242,37 @@ export default function ListingsForRentDetail() {
<div className="layout-1 columns-1!"> <div className="layout-1 columns-1!">
<dl className="list-terms-inline w-full flex justify-between"> <dl className="list-terms-inline w-full flex justify-between">
<dt>Base Rent</dt> <dt>Base Rent</dt>
<dd>{formatCurrency(2700)}</dd> <dd>{formattedData.price}</dd>
</dl> </dl>
</div> </div>
<div className="layout-1 columns-1!">
{formattedData.additionalPrice.map((p, idx) => (
<div className="layout-1 columns-1!" key={idx}>
<dl className="list-terms-inline w-full flex justify-between"> <dl className="list-terms-inline w-full flex justify-between">
<dt>Smart Home</dt> <dt>{p.name}</dt>
<dd>{formatCurrency(20)}</dd> <dd>{p.price}</dd>
</dl>
</div>
<div className="layout-1 columns-1!">
<dl className="list-terms-inline w-full flex justify-between">
<dt>Utility Service</dt>
<dd>{formatCurrency(5)}</dd>
</dl> </dl>
</div> </div>
))}
<div className="layout-1 columns-1! mt-2"> <div className="layout-1 columns-1! mt-2">
<dl className="list-terms-inline w-full flex justify-between"> <dl className="list-terms-inline w-full flex justify-between">
<dd className="font-semibold!">Est. total monthly*</dd> <dd className="font-semibold!">Est. total monthly*</dd>
<dd className="font-semibold!">{formatCurrency(2900)}</dd> <dd className="font-semibold!">{formattedData.totalPrice}</dd>
</dl> </dl>
</div> </div>
</div> </div>
</div> </div>
</article> </article>
</div> </div>
)}
{isEmbedMapUrlValid && (
<div className="block-group-item"> <div className="block-group-item">
<h3>Property Map</h3> <h3>Property Map</h3>
<div className="row row-30"> <div className="row row-30">
<div className="col-12"> <div className="col-12">
<iframe <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" src={data.embed_map_url ?? ""}
width={"100%"} width={"100%"}
height={450} height={450}
style={{ border: 0 }} style={{ border: 0 }}
@ -326,12 +282,14 @@ export default function ListingsForRentDetail() {
</div> </div>
</div> </div>
</div> </div>
)}
<div className="blog-post-solo-footer mt-20"> <div className="blog-post-solo-footer mt-20">
<div className="blog-post-solo-footer-left"> <div className="blog-post-solo-footer-left">
<ul className="blog-post-solo-footer-list"> <ul className="blog-post-solo-footer-list">
<li> <li>
<span className="icon mdi mdi-clock"></span> <span className="icon mdi mdi-clock"></span>
<a href="#">February 10, 2021</a> <a href="#">{formattedData.postedAt}</a>
</li> </li>
</ul> </ul>
</div> </div>
@ -343,16 +301,13 @@ export default function ListingsForRentDetail() {
<li> <li>
<ul className="list-inline-1"> <ul className="list-inline-1">
<li> <li>
<a className="icon link-default fa-facebook" href="#"></a> <a target="_blank" className="icon link-default fa-facebook" href={shareUrl.facebook}></a>
</li> </li>
<li> <li>
<a className="icon link-default fa-twitter" href="#"></a> <a target="_blank" className="icon link-default fa-twitter" href={shareUrl.twitter}></a>
</li> </li>
<li> <li>
<a className="icon link-default fa-google-plus" href="#"></a> <a target="_blank" className="icon link-default fa-linkedin" href={shareUrl.linkedin}></a>
</li>
<li>
<a className="icon link-default fa-pinterest-p" href="#"></a>
</li> </li>
</ul> </ul>
</li> </li>
@ -360,14 +315,16 @@ export default function ListingsForRentDetail() {
</div> </div>
</div> </div>
{similarPropertiesData.formattedData.length > 0 && (
<div className="block-group-item"> <div className="block-group-item">
<h3>Similar Properties</h3> <h3>Other Properties</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{similarPropertiesData.map((p, idx) => ( {similarPropertiesData.formattedData.map((p, idx) => (
<CardProperty key={idx} data={p} /> <CardProperty key={idx} data={p} />
))} ))}
</div> </div>
</div> </div>
)}
</div> </div>
<div className="col-lg-5 col-xl-4"> <div className="col-lg-5 col-xl-4">
<div className="row row-50"> <div className="row row-50">

View File

@ -30,6 +30,7 @@ export default async function ListingsForRent(props: {
const maxArea = sanitizeNumber(searchParams?.max_area); const maxArea = sanitizeNumber(searchParams?.max_area);
const propertiesData = await fetchProperty({ const propertiesData = await fetchProperty({
property_type: "rent",
page, page,
name: searchParams?.name, name: searchParams?.name,
min_price: minPrice, min_price: minPrice,

View File

@ -11,7 +11,7 @@ export const Properties: CollectionConfig = {
}, },
fields: [ fields: [
{ {
name: "propertyType", name: "property_type",
label: "Type", label: "Type",
type: "select", type: "select",
options: [ options: [
@ -56,20 +56,18 @@ export const Properties: CollectionConfig = {
{ {
name: "area", name: "area",
label: "Area (Sqft)", label: "Area (Sqft)",
type: "text", type: "number",
required: true, required: true,
}, },
{ {
name: "bathrooms_count", name: "bathrooms_count",
label: "Total Bathrooms", label: "Total Bathrooms",
type: "text", type: "number",
required: true,
}, },
{ {
name: "bedrooms_count", name: "bedrooms_count",
label: "Total Bedrooms", label: "Total Bedrooms",
type: "text", type: "number",
required: true,
}, },
], ],
}, },
@ -79,8 +77,8 @@ export const Properties: CollectionConfig = {
type: "group", type: "group",
fields: [ fields: [
{ {
name: "country_code", name: "state_code",
label: "Country", label: "State",
type: "text", type: "text",
// admin: { // admin: {
// components: { // components: {
@ -90,11 +88,6 @@ export const Properties: CollectionConfig = {
// }, // },
// }, // },
}, },
{
name: "state_code",
label: "State",
type: "text",
},
{ {
name: "city_code", name: "city_code",
label: "City", label: "City",
@ -124,7 +117,7 @@ export const Properties: CollectionConfig = {
}, },
{ {
name: "base_price", name: "base_price",
label: "Base Price", label: "Price",
type: "number", type: "number",
required: true, required: true,
}, },
@ -147,7 +140,7 @@ export const Properties: CollectionConfig = {
}, },
{ {
name: "embed_map_url", name: "embed_map_url",
label: "Embed Map URL", label: "Embed Google Map URL",
type: "text", type: "text",
}, },
], ],

View File

@ -8,7 +8,7 @@ type CardPropertyProps = {
}; };
export default function CardProperty({ data }: CardPropertyProps) { export default function CardProperty({ data }: CardPropertyProps) {
const href = data?.propertyType === "sell" ? `/listings-for-rent/${data.slug}` : `/listings-for-sell/${data.slug}`; const href = data?.propertyType === "rent" ? `/listings-for-rent/${data.slug}` : `/listings-for-sell/${data.slug}`;
return ( return (
<div> <div>
<article className="product-classic"> <article className="product-classic">
@ -24,7 +24,7 @@ export default function CardProperty({ data }: CardPropertyProps) {
> >
{Array.isArray(data.images) && {Array.isArray(data.images) &&
data.images.map((img, idx) => ( data.images.map((img, idx) => (
<div key={idx} className="w-full h-52 bg-colorImgPlaceholder"> <div key={idx} className="w-full h-52 bg-colorImgPlaceholder/90">
<Image src={img.url} alt={img.alt ?? ""} fill className="object-cover" /> <Image src={img.url} alt={img.alt ?? ""} fill className="object-cover" />
</div> </div>
))} ))}
@ -41,17 +41,19 @@ export default function CardProperty({ data }: CardPropertyProps) {
</h4> </h4>
<div className="product-classic-divider"></div> <div className="product-classic-divider"></div>
<ul className="product-classic-list"> <ul className="product-classic-list">
{!!data.area && (
<li> <li>
<span className="icon mdi mdi-vector-square"></span> <span className="icon mdi mdi-vector-square"></span>
<span>{data.area} Sq Ft</span> <span>{data.area} Sq Ft</span>
</li> </li>
{data.bathrooms_count && ( )}
{!!data.bathrooms_count && (
<li> <li>
<span className="icon hotel-icon-10"></span> <span className="icon hotel-icon-10"></span>
<span>{data.bathrooms_count} Bathrooms</span> <span>{data.bathrooms_count} Bathrooms</span>
</li> </li>
)} )}
{data.bedrooms_count && ( {!!data.bedrooms_count && (
<li> <li>
<span className="icon hotel-icon-05"></span> <span className="icon hotel-icon-05"></span>
<span>{data.bedrooms_count} Bedrooms</span> <span>{data.bedrooms_count} Bedrooms</span>

View File

@ -239,7 +239,7 @@ export interface PropertyFeature {
*/ */
export interface Property { export interface Property {
id: number; id: number;
propertyType: 'rent' | 'sell'; property_type: 'rent' | 'sell';
name: string; name: string;
slug?: string | null; slug?: string | null;
images: (number | Media)[]; images: (number | Media)[];
@ -259,12 +259,11 @@ export interface Property {
}; };
[k: string]: unknown; [k: string]: unknown;
}; };
area: string; area: number;
bathrooms_count: string; bathrooms_count?: number | null;
bedrooms_count: string; bedrooms_count?: number | null;
}; };
addressGroup: { addressGroup: {
country_code?: string | null;
state_code?: string | null; state_code?: string | null;
city_code?: string | null; city_code?: string | null;
zip_code: string; zip_code: string;
@ -457,7 +456,7 @@ export interface PropertyFeaturesSelect<T extends boolean = true> {
* via the `definition` "properties_select". * via the `definition` "properties_select".
*/ */
export interface PropertiesSelect<T extends boolean = true> { export interface PropertiesSelect<T extends boolean = true> {
propertyType?: T; property_type?: T;
name?: T; name?: T;
slug?: T; slug?: T;
images?: T; images?: T;
@ -472,7 +471,6 @@ export interface PropertiesSelect<T extends boolean = true> {
addressGroup?: addressGroup?:
| T | T
| { | {
country_code?: T;
state_code?: T; state_code?: T;
city_code?: T; city_code?: T;
zip_code?: T; zip_code?: T;

View File

@ -6,9 +6,9 @@ export type CardPropertyData = {
/** /**
* in sqft * in sqft
*/ */
area: string; area?: number | null;
bedrooms_count?: string; bedrooms_count?: number | null;
bathrooms_count?: string; bathrooms_count?: number | null;
posted_at: string; posted_at: string;
propertyType: "rent" | "sell"; propertyType: "rent" | "sell";
}; };

View File

@ -6,4 +6,9 @@ export type FetchPropertyParams = {
min_price?: number; min_price?: number;
max_price?: number; max_price?: number;
location?: string; location?: string;
property_type?: "rent" | "sell";
};
export type FetchPropertyDetailParams = {
slug: string;
}; };

View File

@ -1,7 +1,8 @@
import payloadConfig from "@/payload.config"; import payloadConfig from "@/payload.config";
import { CardPropertyData } from "@/schema/property"; import { CardPropertyData } from "@/schema/property";
import { FetchPropertyParams } from "@/schema/services/property"; import { FetchPropertyDetailParams, FetchPropertyParams } from "@/schema/services/property";
import { formatDate } from "@/utils/datetime"; import { formatDate } from "@/utils/datetime";
import { formatCurrency, getRandomNumber } from "@/utils/general";
import { getPayload, Where } from "payload"; import { getPayload, Where } from "payload";
export async function fetchProperty({ export async function fetchProperty({
@ -12,6 +13,7 @@ export async function fetchProperty({
max_price, max_price,
min_area, min_area,
max_area, max_area,
property_type,
}: FetchPropertyParams = {}) { }: FetchPropertyParams = {}) {
const payload = await getPayload({ config: payloadConfig }); const payload = await getPayload({ config: payloadConfig });
@ -19,6 +21,11 @@ export async function fetchProperty({
_status: { equals: "published" }, _status: { equals: "published" },
}; };
if (!!property_type) {
queryCondition["property_type"] = {
equals: property_type,
};
}
if (!!name) { if (!!name) {
queryCondition["name"] = { queryCondition["name"] = {
contains: name, contains: name,
@ -60,11 +67,11 @@ export async function fetchProperty({
const formattedData: CardPropertyData[] = dataQuery.docs.map((item) => { const formattedData: CardPropertyData[] = dataQuery.docs.map((item) => {
return { return {
slug: "", slug: item.slug ?? "",
title: item.name, title: item.name,
price: item.base_price, price: item.base_price,
area: item.aboutGroup.area, area: item.aboutGroup.area,
propertyType: item.propertyType, propertyType: item.property_type,
bathrooms_count: item.aboutGroup.bathrooms_count, bathrooms_count: item.aboutGroup.bathrooms_count,
bedrooms_count: item.aboutGroup.bedrooms_count, bedrooms_count: item.aboutGroup.bedrooms_count,
images: item.images.map((img) => images: item.images.map((img) =>
@ -79,3 +86,96 @@ export async function fetchProperty({
formattedData, formattedData,
}; };
} }
export async function fetchPropertySuggestion() {
const payload = await getPayload({ config: payloadConfig });
const limitPerPage = 2;
const countrQuery = await payload.count({
collection: "properties",
where: { _status: { equals: "published" } },
});
// randomize page
let page = 1;
const totalDocs = countrQuery.totalDocs;
if (totalDocs > limitPerPage) {
const totalPage = Math.ceil(totalDocs / limitPerPage);
page = getRandomNumber(totalPage);
}
const dataQuery = await payload.find({
collection: "properties",
page,
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,
};
}
export async function fetchPropertyDetail({ slug }: FetchPropertyDetailParams) {
const payload = await getPayload({ config: payloadConfig });
const queryCondition: Where = {
_status: { equals: "published" },
slug: { equals: slug },
};
const dataQuery = await payload.find({
collection: "properties",
where: queryCondition,
limit: 1,
pagination: false,
});
if (!dataQuery?.docs?.[0]) return null;
const data = dataQuery?.docs?.[0];
const postedAt = formatDate(data.createdAt);
const images = data.images.map((img) =>
typeof img !== "number" ? { url: img?.url ?? "", alt: img.alt } : { url: "", alt: "" }
);
const formattedBasePrice = formatCurrency(data.base_price);
let additionalPrice: { name: string; price: string }[] = [];
let totalPrice = 0;
if (Array.isArray(data.additional_price)) {
for (const p of data.additional_price) {
additionalPrice.push({
name: p.name,
price: formatCurrency(p.price),
});
totalPrice += p.price;
}
}
const formattedTotalPrice = formatCurrency(data.base_price + totalPrice);
return {
data,
formattedData: {
price: formattedBasePrice,
additionalPrice,
totalPrice: formattedTotalPrice,
images,
postedAt,
},
};
}