feat: listings for rent FE integration
This commit is contained in:
parent
2dbf9e1ad4
commit
1acb297818
@ -25,6 +25,7 @@
|
||||
"qs-esm": "^7.0.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-select": "^5.10.1",
|
||||
"swiper": "^11.2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -5,7 +5,6 @@ import { formatCurrency } from "@/utils/general";
|
||||
|
||||
const similarPropertiesData: CardPropertyData[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: "401 Biscayne Boulevard, Miami",
|
||||
slug: "401-biscayne-boulevard",
|
||||
images: [
|
||||
@ -16,23 +15,20 @@ const similarPropertiesData: CardPropertyData[] = [
|
||||
price: 5000,
|
||||
propertyType: "rent",
|
||||
posted_at: "",
|
||||
area: 480,
|
||||
bathrooms_count: 2,
|
||||
bedrooms_count: 2,
|
||||
is_available: true,
|
||||
area: "480",
|
||||
bathrooms_count: "2",
|
||||
bedrooms_count: "2",
|
||||
},
|
||||
{
|
||||
id: 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,
|
||||
is_available: true,
|
||||
area: "480",
|
||||
bathrooms_count: "2",
|
||||
bedrooms_count: "2",
|
||||
},
|
||||
];
|
||||
|
||||
@ -367,8 +363,8 @@ export default function ListingsForRentDetail() {
|
||||
<div className="block-group-item">
|
||||
<h3>Similar Properties</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{similarPropertiesData.map((p) => (
|
||||
<CardProperty key={p.id} data={p} />
|
||||
{similarPropertiesData.map((p, idx) => (
|
||||
<CardProperty key={idx} data={p} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,8 +1,12 @@
|
||||
import CardProperty from "@/components/CardProperty";
|
||||
import HeroImage from "@/components/HeroImage";
|
||||
import Pagination from "@/components/Pagination";
|
||||
import { CardPropertyData } from "@/schema/property";
|
||||
import Select from "@/components/Select";
|
||||
import { FetchPropertyParams } from "@/schema/services/property";
|
||||
import { fetchProperty } from "@/services/payload/property";
|
||||
import { getDefaultMetadata } from "@/utils/metadata";
|
||||
import { sanitizeNumber, sanitizePageNumber } from "@/utils/sanitize";
|
||||
import { State } from "country-state-city";
|
||||
import { Metadata } from "next";
|
||||
|
||||
const metaDesc = "Explore the latest properties on the Dynamic Realty.";
|
||||
@ -15,66 +19,28 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const propertiesData: CardPropertyData[] = [
|
||||
{
|
||||
id: 1,
|
||||
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,
|
||||
is_available: true,
|
||||
},
|
||||
{
|
||||
id: 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,
|
||||
is_available: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "403 Biscayne Boulevard, Miami",
|
||||
slug: "403-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,
|
||||
is_available: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "404 Biscayne Boulevard, Miami",
|
||||
slug: "404-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,
|
||||
is_available: true,
|
||||
},
|
||||
];
|
||||
export default async function ListingsForRent(props: {
|
||||
searchParams?: Promise<{ [P in keyof FetchPropertyParams]: string }>;
|
||||
}) {
|
||||
const searchParams = await props?.searchParams;
|
||||
const page = sanitizePageNumber(searchParams?.page);
|
||||
const minPrice = sanitizeNumber(searchParams?.min_price);
|
||||
const maxPrice = sanitizeNumber(searchParams?.max_price);
|
||||
const minArea = sanitizeNumber(searchParams?.min_area);
|
||||
const maxArea = sanitizeNumber(searchParams?.max_area);
|
||||
|
||||
const propertiesData = await fetchProperty({
|
||||
page,
|
||||
name: searchParams?.name,
|
||||
min_price: minPrice,
|
||||
max_price: maxPrice,
|
||||
min_area: minArea,
|
||||
max_area: maxArea,
|
||||
location: searchParams?.location,
|
||||
});
|
||||
const isEmpty = propertiesData.formattedData.length <= 0;
|
||||
const statesData = State.getStatesOfCountry("US").map((st) => ({ value: st.name, label: st.name }));
|
||||
|
||||
export default function ListingsForRent() {
|
||||
return (
|
||||
<>
|
||||
<HeroImage title="Listings For Rent" />
|
||||
@ -85,67 +51,33 @@ export default function ListingsForRent() {
|
||||
<div className="col-lg-7 col-xl-8">
|
||||
<div className="row row-30">
|
||||
<div className="col-12">
|
||||
<ul className="block-info-1">
|
||||
<li>
|
||||
<div className="form-wrap-group-1">
|
||||
<div className="form-wrap">
|
||||
<select
|
||||
className="form-input select-filter"
|
||||
data-style="modern"
|
||||
data-placeholder="Publication Date"
|
||||
data-minimum-results-for-search="Infinity"
|
||||
data-constraints="@Required"
|
||||
>
|
||||
<option label="placeholder"></option>
|
||||
<option value="2">Monday</option>
|
||||
<option value="3">Tuesday</option>
|
||||
<option value="4">Wednesday</option>
|
||||
<option value="5">Thursday</option>
|
||||
<option value="6">Friday</option>
|
||||
<option value="7">Saturday</option>
|
||||
<option value="8">Sunday</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-wrap">
|
||||
<select
|
||||
className="form-input select-filter"
|
||||
data-style="modern"
|
||||
data-placeholder="Price Low to High"
|
||||
data-minimum-results-for-search="Infinity"
|
||||
data-constraints="@Required"
|
||||
>
|
||||
<option label="placeholder"></option>
|
||||
<option value="1">Price Low to High</option>
|
||||
<option value="2">Price High to Low</option>
|
||||
<option value="3">Most Popular</option>
|
||||
<option value="4">Top Rated</option>
|
||||
<option value="5">Best Sellers</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{isEmpty && (
|
||||
<div className="text-center mt-40">
|
||||
<h3 className="text-spacing-20">No Properties Found</h3>
|
||||
<p className="heading-5 mt-3">Looks like we couldn’t find any listings that match your search.</p>
|
||||
</div>
|
||||
)}
|
||||
{!isEmpty && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{propertiesData.formattedData.map((p, idx) => (
|
||||
<CardProperty key={idx} data={p} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{propertiesData.map((p) => (
|
||||
<CardProperty key={p.id} data={p} />
|
||||
))}
|
||||
|
||||
{/* Pagination */}
|
||||
{propertiesData.totalPages > 1 && (
|
||||
<div className="col-12">
|
||||
<Pagination
|
||||
page={propertiesData.page ?? 1}
|
||||
hasNextPage={propertiesData.hasNextPage}
|
||||
hasPreviousPage={propertiesData.hasPrevPage}
|
||||
totalPages={propertiesData.totalPages}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
{/* <ul className="pagination-custom">
|
||||
<li>
|
||||
<a className="active" href="#">
|
||||
1
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">2</a>
|
||||
</li>
|
||||
</ul> */}
|
||||
<Pagination hasNextPage={true} hasPreviousPage={true} totalPages={10} page={3} />
|
||||
</div>
|
||||
)}
|
||||
{/* End Pagination */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-5 col-xl-4">
|
||||
@ -153,125 +85,55 @@ export default function ListingsForRent() {
|
||||
<div className="col-md-6 col-lg-12">
|
||||
<div className="block-info">
|
||||
<h3>Find Your Property</h3>
|
||||
<form
|
||||
className="rd-mailform form-select"
|
||||
data-form-output="form-output-global"
|
||||
data-form-type="contact"
|
||||
method="post"
|
||||
action="bat/rd-mailform.php"
|
||||
>
|
||||
<form className="form-select">
|
||||
<div className="form-wrap form-wrap-validation">
|
||||
<select
|
||||
className="form-input select-filter"
|
||||
data-style="modern"
|
||||
data-placeholder="Choose Location"
|
||||
data-minimum-results-for-search="Infinity"
|
||||
data-constraints="@Required"
|
||||
>
|
||||
<option label="placeholder"></option>
|
||||
<option value="2">Alaska</option>
|
||||
<option value="3">Arizona</option>
|
||||
<option value="4">Arkansas</option>
|
||||
<option value="5">California</option>
|
||||
<option value="6">Colorado</option>
|
||||
<option value="7">Connecticut</option>
|
||||
<option value="8">Delaware</option>
|
||||
<option value="9">Florida</option>
|
||||
</select>
|
||||
<span className="select-arrow"></span>
|
||||
<input className="form-input" placeholder="Name" name="s" defaultValue={searchParams?.name} />
|
||||
</div>
|
||||
<div className="form-wrap form-wrap-validation">
|
||||
<select
|
||||
className="form-input select-filter"
|
||||
data-style="modern"
|
||||
data-placeholder="Property Status"
|
||||
data-minimum-results-for-search="Infinity"
|
||||
data-constraints="@Required"
|
||||
>
|
||||
<option label="placeholder"></option>
|
||||
<option value="2">Low</option>
|
||||
<option value="3">Middle</option>
|
||||
<option value="4">Primary</option>
|
||||
</select>
|
||||
<span className="select-arrow"></span>
|
||||
</div>
|
||||
<div className="form-wrap form-wrap-validation">
|
||||
<select
|
||||
className="form-input select-filter"
|
||||
data-style="modern"
|
||||
data-placeholder="Property Type"
|
||||
data-minimum-results-for-search="Infinity"
|
||||
data-constraints="@Required"
|
||||
>
|
||||
<option label="placeholder"></option>
|
||||
<option value="2">Low</option>
|
||||
<option value="3">Middle</option>
|
||||
<option value="4">Primary</option>
|
||||
</select>
|
||||
<span className="select-arrow"></span>
|
||||
<Select
|
||||
name="location"
|
||||
placeholder="Choose Location"
|
||||
options={statesData}
|
||||
defaultInputValue={searchParams?.location}
|
||||
defaultValue={searchParams?.location}
|
||||
isSearchable
|
||||
isClearable
|
||||
/>
|
||||
</div>
|
||||
<div className="form-wrap-group">
|
||||
<div className="form-wrap form-wrap-validation">
|
||||
<select
|
||||
className="form-input select-filter"
|
||||
data-style="modern"
|
||||
data-placeholder="Min Price"
|
||||
data-minimum-results-for-search="Infinity"
|
||||
data-constraints="@Required"
|
||||
>
|
||||
<option label="placeholder"></option>
|
||||
<option value="2">100 $</option>
|
||||
<option value="3">200 $</option>
|
||||
<option value="4">300 $</option>
|
||||
</select>
|
||||
<span className="select-arrow"></span>
|
||||
<input
|
||||
className="form-input"
|
||||
placeholder="Min Area Sqft"
|
||||
name="min_area"
|
||||
defaultValue={searchParams?.min_area}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-wrap form-wrap-validation">
|
||||
<select
|
||||
className="form-input select-filter"
|
||||
data-style="modern"
|
||||
data-placeholder="Max Price"
|
||||
data-minimum-results-for-search="Infinity"
|
||||
data-constraints="@Required"
|
||||
>
|
||||
<option label="placeholder"></option>
|
||||
<option value="2">1000 $</option>
|
||||
<option value="3">2000 $</option>
|
||||
<option value="4">3000 $</option>
|
||||
</select>
|
||||
<span className="select-arrow"></span>
|
||||
<input
|
||||
className="form-input"
|
||||
placeholder="Max Area Sqft"
|
||||
name="max_area"
|
||||
defaultValue={searchParams?.max_area}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-wrap-group">
|
||||
<div className="form-wrap form-wrap-validation">
|
||||
<select
|
||||
className="form-input select-filter"
|
||||
data-style="modern"
|
||||
data-placeholder="Min Area"
|
||||
data-minimum-results-for-search="Infinity"
|
||||
data-constraints="@Required"
|
||||
>
|
||||
<option label="placeholder"></option>
|
||||
<option value="2">100 Sq Ft</option>
|
||||
<option value="3">200 Sq Ft</option>
|
||||
<option value="4">300 Sq Ft</option>
|
||||
</select>
|
||||
<span className="select-arrow"></span>
|
||||
<input
|
||||
className="form-input"
|
||||
placeholder="Min Price ($)"
|
||||
name="min_price"
|
||||
defaultValue={searchParams?.min_price}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-wrap form-wrap-validation">
|
||||
<select
|
||||
className="form-input select-filter"
|
||||
data-style="modern"
|
||||
data-placeholder="Max Area"
|
||||
data-minimum-results-for-search="Infinity"
|
||||
data-constraints="@Required"
|
||||
>
|
||||
<option label="placeholder"></option>
|
||||
<option value="2">1000 Sq Ft</option>
|
||||
<option value="3">2000 Sq Ft</option>
|
||||
<option value="4">3000 Sq Ft</option>
|
||||
</select>
|
||||
<span className="select-arrow"></span>
|
||||
<input
|
||||
className="form-input"
|
||||
placeholder="Max Price ($)"
|
||||
name="max_price"
|
||||
defaultValue={searchParams?.max_price}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-button">
|
||||
|
@ -4,6 +4,11 @@ import type { CollectionConfig } from "payload";
|
||||
export const Properties: CollectionConfig = {
|
||||
slug: "properties",
|
||||
labels: { plural: "Properties", singular: "Property" },
|
||||
versions: {
|
||||
drafts: {
|
||||
validate: true,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "propertyType",
|
||||
@ -76,14 +81,7 @@ export const Properties: CollectionConfig = {
|
||||
{
|
||||
name: "country_code",
|
||||
label: "Country",
|
||||
type: "select",
|
||||
options: [
|
||||
{
|
||||
label: "United States",
|
||||
value: "US",
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
type: "text",
|
||||
// admin: {
|
||||
// components: {
|
||||
// Field: {
|
||||
@ -95,26 +93,12 @@ export const Properties: CollectionConfig = {
|
||||
{
|
||||
name: "state_code",
|
||||
label: "State",
|
||||
type: "select",
|
||||
options: [
|
||||
{
|
||||
label: "Washington",
|
||||
value: "WA",
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
name: "city_code",
|
||||
label: "City",
|
||||
type: "select",
|
||||
options: [
|
||||
{
|
||||
label: "Davenport",
|
||||
value: "Davenport",
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
type: "text",
|
||||
},
|
||||
{
|
||||
name: "zip_code",
|
||||
|
@ -9,55 +9,55 @@ type CardPropertyProps = {
|
||||
|
||||
export default function CardProperty({ data }: CardPropertyProps) {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<article className="product-classic">
|
||||
<div className="product-classic-media">
|
||||
<div
|
||||
className="owl-carousel"
|
||||
data-items="1"
|
||||
data-nav="true"
|
||||
data-stage-padding="0"
|
||||
data-loop="false"
|
||||
data-margin="0"
|
||||
data-mouse-drag="false"
|
||||
>
|
||||
{Array.isArray(data.images) &&
|
||||
data.images.map((img, idx) => (
|
||||
<Image key={idx} src={img.url} alt={img.alt ?? ""} width="480" height="287" />
|
||||
))}
|
||||
</div>
|
||||
<div className="product-classic-price bg-colorPriceTag/80!">
|
||||
<span>
|
||||
{formatCurrency(data.price)}
|
||||
{data.propertyType === "rent" && `/mo`}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<article className="product-classic">
|
||||
<div className="product-classic-media">
|
||||
<div
|
||||
className="owl-carousel"
|
||||
data-items="1"
|
||||
data-nav="true"
|
||||
data-stage-padding="0"
|
||||
data-loop="false"
|
||||
data-margin="0"
|
||||
data-mouse-drag="false"
|
||||
>
|
||||
{Array.isArray(data.images) &&
|
||||
data.images.map((img, idx) => (
|
||||
<div key={idx} className="w-full h-52 bg-colorImgPlaceholder">
|
||||
<Image src={img.url} alt={img.alt ?? ""} fill className="object-cover" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<h4 className="product-classic-title">
|
||||
<Link href="/listings-for-rent/slug">{data.title}</Link>
|
||||
</h4>
|
||||
<div className="product-classic-divider"></div>
|
||||
<ul className="product-classic-list">
|
||||
<div className="product-classic-price bg-colorPriceTag/90!">
|
||||
<span>
|
||||
{formatCurrency(data.price)}
|
||||
{data.propertyType === "rent" && `/mo`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<h4 className="product-classic-title">
|
||||
<Link href="/listings-for-rent/slug">{data.title}</Link>
|
||||
</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 && (
|
||||
<li>
|
||||
<span className="icon mdi mdi-vector-square"></span>
|
||||
<span>{data.area} Sq Ft</span>
|
||||
<span className="icon hotel-icon-10"></span>
|
||||
<span>{data.bathrooms_count} Bathrooms</span>
|
||||
</li>
|
||||
{data.bathrooms_count && (
|
||||
<li>
|
||||
<span className="icon hotel-icon-10"></span>
|
||||
<span>{data.bathrooms_count} Bathrooms</span>
|
||||
</li>
|
||||
)}
|
||||
{data.bedrooms_count && (
|
||||
<li>
|
||||
<span className="icon hotel-icon-05"></span>
|
||||
<span>{data.bedrooms_count} Bedrooms</span>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{data.bedrooms_count && (
|
||||
<li>
|
||||
<span className="icon hotel-icon-05"></span>
|
||||
<span>{data.bedrooms_count} Bedrooms</span>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
8
src/components/Select.tsx
Normal file
8
src/components/Select.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { ComponentProps } from "react";
|
||||
import ReactSelect from "react-select";
|
||||
|
||||
export default function Select(props: ComponentProps<typeof ReactSelect>) {
|
||||
return <ReactSelect {...props} />;
|
||||
}
|
@ -264,9 +264,9 @@ export interface Property {
|
||||
bedrooms_count: string;
|
||||
};
|
||||
addressGroup: {
|
||||
country_code: 'US';
|
||||
state_code: 'WA';
|
||||
city_code: 'Davenport';
|
||||
country_code?: string | null;
|
||||
state_code?: string | null;
|
||||
city_code?: string | null;
|
||||
zip_code: string;
|
||||
address: string;
|
||||
};
|
||||
@ -282,6 +282,7 @@ export interface Property {
|
||||
embed_map_url?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
@ -489,6 +490,7 @@ export interface PropertiesSelect<T extends boolean = true> {
|
||||
embed_map_url?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
@ -1,16 +1,14 @@
|
||||
export type CardPropertyData = {
|
||||
id: number;
|
||||
slug?: string | null;
|
||||
slug: string;
|
||||
title: string;
|
||||
price: number;
|
||||
images?: { url: string; alt?: string }[];
|
||||
/**
|
||||
* in sqft
|
||||
*/
|
||||
area: number;
|
||||
bedrooms_count?: number;
|
||||
bathrooms_count?: number;
|
||||
area: string;
|
||||
bedrooms_count?: string;
|
||||
bathrooms_count?: string;
|
||||
posted_at: string;
|
||||
propertyType: "rent" | "sell";
|
||||
is_available: boolean;
|
||||
};
|
||||
|
9
src/schema/services/property.ts
Normal file
9
src/schema/services/property.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export type FetchPropertyParams = {
|
||||
page?: number;
|
||||
name?: string;
|
||||
min_area?: number;
|
||||
max_area?: number;
|
||||
min_price?: number;
|
||||
max_price?: number;
|
||||
location?: string;
|
||||
};
|
81
src/services/payload/property.ts
Normal file
81
src/services/payload/property.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import payloadConfig from "@/payload.config";
|
||||
import { CardPropertyData } from "@/schema/property";
|
||||
import { FetchPropertyParams } from "@/schema/services/property";
|
||||
import { formatDate } from "@/utils/datetime";
|
||||
import { getPayload, Where } from "payload";
|
||||
|
||||
export async function fetchProperty({
|
||||
page,
|
||||
name = "",
|
||||
location,
|
||||
min_price,
|
||||
max_price,
|
||||
min_area,
|
||||
max_area,
|
||||
}: FetchPropertyParams = {}) {
|
||||
const payload = await getPayload({ config: payloadConfig });
|
||||
|
||||
const queryCondition: Where = {
|
||||
_status: { equals: "published" },
|
||||
};
|
||||
|
||||
if (!!name) {
|
||||
queryCondition["name"] = {
|
||||
contains: name,
|
||||
};
|
||||
}
|
||||
if (!!min_price) {
|
||||
queryCondition["base_price"] = {
|
||||
greater_than_equal: min_price,
|
||||
};
|
||||
}
|
||||
if (!!max_price) {
|
||||
queryCondition["base_price"] = {
|
||||
less_than_equal: max_price,
|
||||
};
|
||||
}
|
||||
if (!!min_area) {
|
||||
queryCondition["aboutGroup.area"] = {
|
||||
greater_than_equal: min_area,
|
||||
};
|
||||
}
|
||||
if (!!max_area) {
|
||||
queryCondition["aboutGroup.area"] = {
|
||||
less_than_equal: max_area,
|
||||
};
|
||||
}
|
||||
if (!!location) {
|
||||
queryCondition["addressGroup.state_code"] = {
|
||||
equals: location,
|
||||
};
|
||||
}
|
||||
|
||||
const dataQuery = await payload.find({
|
||||
collection: "properties",
|
||||
page,
|
||||
pagination: true,
|
||||
limit: 10,
|
||||
where: queryCondition,
|
||||
});
|
||||
|
||||
const formattedData: CardPropertyData[] = dataQuery.docs.map((item) => {
|
||||
return {
|
||||
slug: "",
|
||||
title: item.name,
|
||||
price: item.base_price,
|
||||
area: item.aboutGroup.area,
|
||||
propertyType: item.propertyType,
|
||||
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,
|
||||
};
|
||||
}
|
@ -1,5 +1,18 @@
|
||||
import { Blog } from "@/payload-types";
|
||||
|
||||
export function sanitizeNumber(input: string | undefined | null): number {
|
||||
if (!input) return 0;
|
||||
|
||||
const sanitized = parseFloat(input.replace(/[^0-9.-]+/g, ""));
|
||||
|
||||
// Check if the result is a valid number and not NaN or Infinity
|
||||
if (isNaN(sanitized) || !isFinite(sanitized)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
export function sanitizePageNumber(page: any, defaultPage = 1): number {
|
||||
const parsedPage = Number(page);
|
||||
|
||||
|
21
yarn.lock
21
yarn.lock
@ -5042,6 +5042,7 @@ __metadata:
|
||||
qs-esm: "npm:^7.0.2"
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
react-select: "npm:^5.10.1"
|
||||
swiper: "npm:^11.2.6"
|
||||
tailwindcss: "npm:^4"
|
||||
typescript: "npm:^5"
|
||||
@ -8817,6 +8818,26 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-select@npm:^5.10.1":
|
||||
version: 5.10.1
|
||||
resolution: "react-select@npm:5.10.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.12.0"
|
||||
"@emotion/cache": "npm:^11.4.0"
|
||||
"@emotion/react": "npm:^11.8.1"
|
||||
"@floating-ui/dom": "npm:^1.0.1"
|
||||
"@types/react-transition-group": "npm:^4.4.0"
|
||||
memoize-one: "npm:^6.0.0"
|
||||
prop-types: "npm:^15.6.0"
|
||||
react-transition-group: "npm:^4.3.0"
|
||||
use-isomorphic-layout-effect: "npm:^1.2.0"
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
checksum: 10c0/0d10a249b96150bd648f2575d59c848b8fac7f4d368a97ae84e4aaba5bbc1035deba4cdc82e49a43904b79ec50494505809618b0e98022b2d51e7629551912ed
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-transition-group@npm:4.4.5, react-transition-group@npm:^4.3.0":
|
||||
version: 4.4.5
|
||||
resolution: "react-transition-group@npm:4.4.5"
|
||||
|
Loading…
x
Reference in New Issue
Block a user