feat: property detail FE integration
This commit is contained in:
parent
6fbbb73e05
commit
e483f8f281
@ -1,48 +1,41 @@
|
||||
import CardProperty from "@/components/CardProperty";
|
||||
import HeroImage from "@/components/HeroImage";
|
||||
import { CardPropertyData } from "@/schema/property";
|
||||
import { formatCurrency } from "@/utils/general";
|
||||
import { fetchPropertyDetail, fetchPropertySuggestion } from "@/services/payload/property";
|
||||
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[] = [
|
||||
{
|
||||
title: "401 Biscayne Boulevard, Miami",
|
||||
slug: "401-biscayne-boulevard",
|
||||
images: [
|
||||
{ url: "/images/featured-properties-01-480x287.jpg", alt: "biscayne boulevard" },
|
||||
{ url: "/images/featured-properties-01-480x287.jpg", alt: "biscayne boulevard" },
|
||||
{ url: "/images/featured-properties-01-480x287.jpg", alt: "biscayne boulevard" },
|
||||
],
|
||||
price: 5000,
|
||||
propertyType: "rent",
|
||||
posted_at: "",
|
||||
area: "480",
|
||||
bathrooms_count: "2",
|
||||
bedrooms_count: "2",
|
||||
},
|
||||
{
|
||||
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 async function ListingsForRentDetail({ params }: { params: Promise<{ slug: string }> }) {
|
||||
const slug = (await params).slug;
|
||||
const propertyDetail = await fetchPropertyDetail({ slug });
|
||||
if (!propertyDetail) return notFound();
|
||||
|
||||
const { data, formattedData } = propertyDetail;
|
||||
const isEmbedMapUrlValid = !!data?.embed_map_url && data.embed_map_url.includes("www.google.com/maps/embed");
|
||||
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 similarPropertiesData = await fetchPropertySuggestion();
|
||||
|
||||
export default function ListingsForRentDetail() {
|
||||
return (
|
||||
<>
|
||||
<HeroImage title="Lorem Ipsum, Dolor" />
|
||||
<HeroImage title={data.name} />
|
||||
|
||||
<section className="section section-md bg-gray-12">
|
||||
<div className="container">
|
||||
<div className="row row-50">
|
||||
<div className="col-lg-7 col-xl-8">
|
||||
<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
|
||||
className="slick-slider carousel-parent"
|
||||
id="parent-carousel"
|
||||
@ -55,24 +48,13 @@ export default function ListingsForRentDetail() {
|
||||
data-child="#child-carousel"
|
||||
data-for="#child-carousel"
|
||||
>
|
||||
<div className="item">
|
||||
<img src="/images/single-property-1-763x443.jpg" alt="" width="763" height="443" />
|
||||
</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>
|
||||
{formattedData.images.map((img, idx) => (
|
||||
<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>
|
||||
<div
|
||||
className="slick-slider carousel-child"
|
||||
@ -89,62 +71,50 @@ export default function ListingsForRentDetail() {
|
||||
data-slide-to-scroll="1"
|
||||
data-for="#parent-carousel"
|
||||
>
|
||||
<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 className="slick-slide-inner bg-[url(/images/single-property-1-763x443.jpg)]"></div>
|
||||
</div>
|
||||
{formattedData.images.map((img, idx) => (
|
||||
<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>
|
||||
<div className="features-block">
|
||||
<div className="features-block-inner">
|
||||
<div className="features-block-item">
|
||||
<ul className="features-block-list">
|
||||
<li>
|
||||
<span className="icon hotel-icon-10"></span>
|
||||
<span>2 Bathrooms</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="icon hotel-icon-05"></span>
|
||||
<span>2 Bedrooms</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="icon mdi mdi-vector-square"></span>
|
||||
<span>480 Sq Ft</span>
|
||||
</li>
|
||||
{!!data?.aboutGroup?.bathrooms_count && (
|
||||
<li>
|
||||
<span className="icon hotel-icon-10"></span>
|
||||
<span>{data.aboutGroup.bathrooms_count} Bathrooms</span>
|
||||
</li>
|
||||
)}
|
||||
{!!data?.aboutGroup?.bedrooms_count && (
|
||||
<li>
|
||||
<span className="icon hotel-icon-05"></span>
|
||||
<span>{data.aboutGroup.bedrooms_count} Bedrooms</span>
|
||||
</li>
|
||||
)}
|
||||
{!!data?.aboutGroup?.area && (
|
||||
<li>
|
||||
<span className="icon mdi mdi-vector-square"></span>
|
||||
<span>{data.aboutGroup.area} Sq Ft</span>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
<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
|
||||
</a>
|
||||
</a> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
Choose this property if you are looking for a modern house near the ocean shore. With 2 bathrooms and 2
|
||||
bedrooms as well as a single garage, it is a perfect option for a small family.
|
||||
</p>
|
||||
<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 className="mt-4">
|
||||
<RichText data={data.aboutGroup.description} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="card-group-custom card-group-corporate"
|
||||
@ -179,27 +149,19 @@ export default function ListingsForRentDetail() {
|
||||
<div className="layout-1">
|
||||
<dl className="list-terms-inline">
|
||||
<dt>Address:</dt>
|
||||
<dd>Biscayne Blvd</dd>
|
||||
<dd>{data?.addressGroup?.address ?? ""}</dd>
|
||||
</dl>
|
||||
<dl className="list-terms-inline">
|
||||
<dt>State/County:</dt>
|
||||
<dd>Florida</dd>
|
||||
<dd>{data?.addressGroup?.state_code ?? ""}</dd>
|
||||
</dl>
|
||||
<dl className="list-terms-inline">
|
||||
<dt>City:</dt>
|
||||
<dd>Miami</dd>
|
||||
<dd>{data?.addressGroup?.city_code ?? ""}</dd>
|
||||
</dl>
|
||||
<dl className="list-terms-inline">
|
||||
<dt>Zip:</dt>
|
||||
<dd>8322</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>
|
||||
<dd>{data?.addressGroup?.zip_code ?? ""}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@ -237,101 +199,97 @@ export default function ListingsForRentDetail() {
|
||||
>
|
||||
<div className="card-body">
|
||||
<ul className="list-marked-2 layout-2">
|
||||
<li>2 Stories</li>
|
||||
<li>Basketball Court</li>
|
||||
<li>Lawn</li>
|
||||
<li>Gym</li>
|
||||
<li>Fireplace</li>
|
||||
<li>Sprinklers</li>
|
||||
<li>Private Space</li>
|
||||
<li>Balcony</li>
|
||||
<li>Laundry</li>
|
||||
<li>Ocean View</li>
|
||||
{Array.isArray(data.features) &&
|
||||
data.features.length > 0 &&
|
||||
data.features.map((ft, idx) => <li key={idx}>{typeof ft !== "number" && ft.name}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div
|
||||
className="card-group-custom card-group-corporate"
|
||||
id="accordion0"
|
||||
role="tablist"
|
||||
aria-multiselectable="false"
|
||||
>
|
||||
<article className="card card-custom card-corporate">
|
||||
<div className="card-header" id="accordion0-heading-0" role="tab">
|
||||
<div className="card-title">
|
||||
<a
|
||||
className="card-link"
|
||||
role="button"
|
||||
data-toggle="collapse"
|
||||
href="#accordion0-collapse-0"
|
||||
aria-controls="accordion0-collapse-0"
|
||||
aria-expanded="true"
|
||||
>
|
||||
<span>Pricing Detail</span>
|
||||
<div className="card-arrow"></div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="show visible!"
|
||||
id="accordion0-collapse-0"
|
||||
role="tabpanel"
|
||||
aria-labelledby="accordion0-heading-0"
|
||||
data-parent="#accordion0"
|
||||
>
|
||||
<div className="card-body">
|
||||
<div className="layout-1 columns-1!">
|
||||
<dl className="list-terms-inline w-full flex justify-between">
|
||||
<dt>Base Rent</dt>
|
||||
<dd>{formatCurrency(2700)}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div className="layout-1 columns-1!">
|
||||
<dl className="list-terms-inline w-full flex justify-between">
|
||||
<dt>Smart Home</dt>
|
||||
<dd>{formatCurrency(20)}</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>
|
||||
</div>
|
||||
<div className="layout-1 columns-1! mt-2">
|
||||
<dl className="list-terms-inline w-full flex justify-between">
|
||||
<dd className="font-semibold!">Est. total monthly*</dd>
|
||||
<dd className="font-semibold!">{formatCurrency(2900)}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div className="block-group-item">
|
||||
<h3>Property Map</h3>
|
||||
<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"
|
||||
/>
|
||||
{data.property_type === "rent" && (
|
||||
<div
|
||||
className="card-group-custom card-group-corporate"
|
||||
id="accordion0"
|
||||
role="tablist"
|
||||
aria-multiselectable="false"
|
||||
>
|
||||
<article className="card card-custom card-corporate">
|
||||
<div className="card-header" id="accordion0-heading-0" role="tab">
|
||||
<div className="card-title">
|
||||
<a
|
||||
className="card-link"
|
||||
role="button"
|
||||
data-toggle="collapse"
|
||||
href="#accordion0-collapse-0"
|
||||
aria-controls="accordion0-collapse-0"
|
||||
aria-expanded="true"
|
||||
>
|
||||
<span>Pricing Detail</span>
|
||||
<div className="card-arrow"></div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="show visible!"
|
||||
id="accordion0-collapse-0"
|
||||
role="tabpanel"
|
||||
aria-labelledby="accordion0-heading-0"
|
||||
data-parent="#accordion0"
|
||||
>
|
||||
<div className="card-body">
|
||||
<div className="layout-1 columns-1!">
|
||||
<dl className="list-terms-inline w-full flex justify-between">
|
||||
<dt>Base Rent</dt>
|
||||
<dd>{formattedData.price}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{formattedData.additionalPrice.map((p, idx) => (
|
||||
<div className="layout-1 columns-1!" key={idx}>
|
||||
<dl className="list-terms-inline w-full flex justify-between">
|
||||
<dt>{p.name}</dt>
|
||||
<dd>{p.price}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
))}
|
||||
<div className="layout-1 columns-1! mt-2">
|
||||
<dl className="list-terms-inline w-full flex justify-between">
|
||||
<dd className="font-semibold!">Est. total monthly*</dd>
|
||||
<dd className="font-semibold!">{formattedData.totalPrice}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isEmbedMapUrlValid && (
|
||||
<div className="block-group-item">
|
||||
<h3>Property Map</h3>
|
||||
<div className="row row-30">
|
||||
<div className="col-12">
|
||||
<iframe
|
||||
src={data.embed_map_url ?? ""}
|
||||
width={"100%"}
|
||||
height={450}
|
||||
style={{ border: 0 }}
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="blog-post-solo-footer mt-20">
|
||||
<div className="blog-post-solo-footer-left">
|
||||
<ul className="blog-post-solo-footer-list">
|
||||
<li>
|
||||
<span className="icon mdi mdi-clock"></span>
|
||||
<a href="#">February 10, 2021</a>
|
||||
<a href="#">{formattedData.postedAt}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -343,16 +301,13 @@ export default function ListingsForRentDetail() {
|
||||
<li>
|
||||
<ul className="list-inline-1">
|
||||
<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>
|
||||
<a className="icon link-default fa-twitter" href="#"></a>
|
||||
<a target="_blank" className="icon link-default fa-twitter" href={shareUrl.twitter}></a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="icon link-default fa-google-plus" href="#"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="icon link-default fa-pinterest-p" href="#"></a>
|
||||
<a target="_blank" className="icon link-default fa-linkedin" href={shareUrl.linkedin}></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
@ -360,14 +315,16 @@ export default function ListingsForRentDetail() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="block-group-item">
|
||||
<h3>Similar Properties</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{similarPropertiesData.map((p, idx) => (
|
||||
<CardProperty key={idx} data={p} />
|
||||
))}
|
||||
{similarPropertiesData.formattedData.length > 0 && (
|
||||
<div className="block-group-item">
|
||||
<h3>Other Properties</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{similarPropertiesData.formattedData.map((p, idx) => (
|
||||
<CardProperty key={idx} data={p} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-lg-5 col-xl-4">
|
||||
<div className="row row-50">
|
||||
|
@ -30,6 +30,7 @@ export default async function ListingsForRent(props: {
|
||||
const maxArea = sanitizeNumber(searchParams?.max_area);
|
||||
|
||||
const propertiesData = await fetchProperty({
|
||||
property_type: "rent",
|
||||
page,
|
||||
name: searchParams?.name,
|
||||
min_price: minPrice,
|
||||
|
@ -11,7 +11,7 @@ export const Properties: CollectionConfig = {
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "propertyType",
|
||||
name: "property_type",
|
||||
label: "Type",
|
||||
type: "select",
|
||||
options: [
|
||||
@ -56,20 +56,18 @@ export const Properties: CollectionConfig = {
|
||||
{
|
||||
name: "area",
|
||||
label: "Area (Sqft)",
|
||||
type: "text",
|
||||
type: "number",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "bathrooms_count",
|
||||
label: "Total Bathrooms",
|
||||
type: "text",
|
||||
required: true,
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "bedrooms_count",
|
||||
label: "Total Bedrooms",
|
||||
type: "text",
|
||||
required: true,
|
||||
type: "number",
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -79,8 +77,8 @@ export const Properties: CollectionConfig = {
|
||||
type: "group",
|
||||
fields: [
|
||||
{
|
||||
name: "country_code",
|
||||
label: "Country",
|
||||
name: "state_code",
|
||||
label: "State",
|
||||
type: "text",
|
||||
// admin: {
|
||||
// components: {
|
||||
@ -90,11 +88,6 @@ export const Properties: CollectionConfig = {
|
||||
// },
|
||||
// },
|
||||
},
|
||||
{
|
||||
name: "state_code",
|
||||
label: "State",
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
name: "city_code",
|
||||
label: "City",
|
||||
@ -124,7 +117,7 @@ export const Properties: CollectionConfig = {
|
||||
},
|
||||
{
|
||||
name: "base_price",
|
||||
label: "Base Price",
|
||||
label: "Price",
|
||||
type: "number",
|
||||
required: true,
|
||||
},
|
||||
@ -147,7 +140,7 @@ export const Properties: CollectionConfig = {
|
||||
},
|
||||
{
|
||||
name: "embed_map_url",
|
||||
label: "Embed Map URL",
|
||||
label: "Embed Google Map URL",
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
|
@ -8,7 +8,7 @@ type 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 (
|
||||
<div>
|
||||
<article className="product-classic">
|
||||
@ -24,7 +24,7 @@ export default function CardProperty({ data }: CardPropertyProps) {
|
||||
>
|
||||
{Array.isArray(data.images) &&
|
||||
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" />
|
||||
</div>
|
||||
))}
|
||||
@ -41,17 +41,19 @@ export default function CardProperty({ data }: CardPropertyProps) {
|
||||
</h4>
|
||||
<div className="product-classic-divider"></div>
|
||||
<ul className="product-classic-list">
|
||||
<li>
|
||||
<span className="icon mdi mdi-vector-square"></span>
|
||||
<span>{data.area} Sq Ft</span>
|
||||
</li>
|
||||
{data.bathrooms_count && (
|
||||
{!!data.area && (
|
||||
<li>
|
||||
<span className="icon mdi mdi-vector-square"></span>
|
||||
<span>{data.area} Sq Ft</span>
|
||||
</li>
|
||||
)}
|
||||
{!!data.bathrooms_count && (
|
||||
<li>
|
||||
<span className="icon hotel-icon-10"></span>
|
||||
<span>{data.bathrooms_count} Bathrooms</span>
|
||||
</li>
|
||||
)}
|
||||
{data.bedrooms_count && (
|
||||
{!!data.bedrooms_count && (
|
||||
<li>
|
||||
<span className="icon hotel-icon-05"></span>
|
||||
<span>{data.bedrooms_count} Bedrooms</span>
|
||||
|
@ -239,7 +239,7 @@ export interface PropertyFeature {
|
||||
*/
|
||||
export interface Property {
|
||||
id: number;
|
||||
propertyType: 'rent' | 'sell';
|
||||
property_type: 'rent' | 'sell';
|
||||
name: string;
|
||||
slug?: string | null;
|
||||
images: (number | Media)[];
|
||||
@ -259,12 +259,11 @@ export interface Property {
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
area: string;
|
||||
bathrooms_count: string;
|
||||
bedrooms_count: string;
|
||||
area: number;
|
||||
bathrooms_count?: number | null;
|
||||
bedrooms_count?: number | null;
|
||||
};
|
||||
addressGroup: {
|
||||
country_code?: string | null;
|
||||
state_code?: string | null;
|
||||
city_code?: string | null;
|
||||
zip_code: string;
|
||||
@ -457,7 +456,7 @@ export interface PropertyFeaturesSelect<T extends boolean = true> {
|
||||
* via the `definition` "properties_select".
|
||||
*/
|
||||
export interface PropertiesSelect<T extends boolean = true> {
|
||||
propertyType?: T;
|
||||
property_type?: T;
|
||||
name?: T;
|
||||
slug?: T;
|
||||
images?: T;
|
||||
@ -472,7 +471,6 @@ export interface PropertiesSelect<T extends boolean = true> {
|
||||
addressGroup?:
|
||||
| T
|
||||
| {
|
||||
country_code?: T;
|
||||
state_code?: T;
|
||||
city_code?: T;
|
||||
zip_code?: T;
|
||||
|
@ -6,9 +6,9 @@ export type CardPropertyData = {
|
||||
/**
|
||||
* in sqft
|
||||
*/
|
||||
area: string;
|
||||
bedrooms_count?: string;
|
||||
bathrooms_count?: string;
|
||||
area?: number | null;
|
||||
bedrooms_count?: number | null;
|
||||
bathrooms_count?: number | null;
|
||||
posted_at: string;
|
||||
propertyType: "rent" | "sell";
|
||||
};
|
||||
|
@ -6,4 +6,9 @@ export type FetchPropertyParams = {
|
||||
min_price?: number;
|
||||
max_price?: number;
|
||||
location?: string;
|
||||
property_type?: "rent" | "sell";
|
||||
};
|
||||
|
||||
export type FetchPropertyDetailParams = {
|
||||
slug: string;
|
||||
};
|
||||
|
@ -1,7 +1,8 @@
|
||||
import payloadConfig from "@/payload.config";
|
||||
import { CardPropertyData } from "@/schema/property";
|
||||
import { FetchPropertyParams } from "@/schema/services/property";
|
||||
import { FetchPropertyDetailParams, FetchPropertyParams } from "@/schema/services/property";
|
||||
import { formatDate } from "@/utils/datetime";
|
||||
import { formatCurrency, getRandomNumber } from "@/utils/general";
|
||||
import { getPayload, Where } from "payload";
|
||||
|
||||
export async function fetchProperty({
|
||||
@ -12,6 +13,7 @@ export async function fetchProperty({
|
||||
max_price,
|
||||
min_area,
|
||||
max_area,
|
||||
property_type,
|
||||
}: FetchPropertyParams = {}) {
|
||||
const payload = await getPayload({ config: payloadConfig });
|
||||
|
||||
@ -19,6 +21,11 @@ export async function fetchProperty({
|
||||
_status: { equals: "published" },
|
||||
};
|
||||
|
||||
if (!!property_type) {
|
||||
queryCondition["property_type"] = {
|
||||
equals: property_type,
|
||||
};
|
||||
}
|
||||
if (!!name) {
|
||||
queryCondition["name"] = {
|
||||
contains: name,
|
||||
@ -60,11 +67,11 @@ export async function fetchProperty({
|
||||
|
||||
const formattedData: CardPropertyData[] = dataQuery.docs.map((item) => {
|
||||
return {
|
||||
slug: "",
|
||||
slug: item.slug ?? "",
|
||||
title: item.name,
|
||||
price: item.base_price,
|
||||
area: item.aboutGroup.area,
|
||||
propertyType: item.propertyType,
|
||||
propertyType: item.property_type,
|
||||
bathrooms_count: item.aboutGroup.bathrooms_count,
|
||||
bedrooms_count: item.aboutGroup.bedrooms_count,
|
||||
images: item.images.map((img) =>
|
||||
@ -79,3 +86,96 @@ export async function fetchProperty({
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user