From f5c5f3fd78dbb3d54fd4466a6e301736d65364e5 Mon Sep 17 00:00:00 2001 From: RizqiSyahrendra Date: Wed, 23 Apr 2025 17:39:05 +0700 Subject: [PATCH] fix: property detail filter and metadata integration --- .../(main)/listings-for-rent/[slug]/page.tsx | 188 ++++++------------ src/app/(main)/listings-for-rent/page.tsx | 68 +------ src/collections/Properties.ts | 23 +++ src/components/layouts/Header.tsx | 2 +- .../{ => properties}/CardProperty.tsx | 0 src/components/properties/FilterProperty.tsx | 79 ++++++++ src/payload-types.ts | 4 + src/utils/sanitize.ts | 6 +- 8 files changed, 171 insertions(+), 199 deletions(-) rename src/components/{ => properties}/CardProperty.tsx (100%) create mode 100644 src/components/properties/FilterProperty.tsx diff --git a/src/app/(main)/listings-for-rent/[slug]/page.tsx b/src/app/(main)/listings-for-rent/[slug]/page.tsx index ee5b825..03ecd66 100644 --- a/src/app/(main)/listings-for-rent/[slug]/page.tsx +++ b/src/app/(main)/listings-for-rent/[slug]/page.tsx @@ -1,10 +1,65 @@ -import CardProperty from "@/components/CardProperty"; +import CardProperty from "@/components/properties/CardProperty"; import HeroImage from "@/components/HeroImage"; 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"; +import FilterProperty from "@/components/properties/FilterProperty"; +import { getDefaultMetadata } from "@/utils/metadata"; +import { sanitizeBlogContentIntoStringPreview } from "@/utils/sanitize"; +import { Metadata } from "next"; + +export async function generateMetadata(props: { params: Promise<{ slug: string }> }): Promise { + const metadata = await getDefaultMetadata(); + const params = await props.params; + + let title = "Page"; + let description = "Page"; + let publishedAt = ""; + let updatedAt = ""; + let imgUrl = ""; + let createdByName = ""; + + const property = await fetchPropertyDetail({ slug: params.slug }); + if (!!property) { + // check for property data + title = `${!!property.data?.name ? property.data?.name : ""} - ${metadata.openGraph?.siteName}`; + description = sanitizeBlogContentIntoStringPreview(property.data.aboutGroup.description, 50); + imgUrl = property.formattedData.images.length > 0 ? property.formattedData.images[0].url : ""; + publishedAt = property.data.createdAt; + updatedAt = property.data.updatedAt; + if (!!property?.data?.createdBy && typeof property.data.createdBy !== "number") { + createdByName = property.data.createdBy?.name ?? ""; + } + } + + metadata.title = title; + metadata.description = description; + if (!!metadata.openGraph) { + // @ts-ignore + metadata.openGraph.type = "article"; + metadata.openGraph.title = title; + metadata.openGraph.description = description; + metadata.openGraph.images = !!imgUrl ? [imgUrl] : undefined; + } + metadata.twitter = { + card: "summary_large_image", + title: title, + description: description, + images: !!imgUrl ? [imgUrl] : undefined, + }; + metadata.other = { + "article:published_time": publishedAt, + "article:modified_time": updatedAt, + "twitter:label1": "Written by", + "twitter:data1": !!createdByName ? createdByName : "Admin", + "twitter:label2": "Est. reading time", + "twitter:data2": "3 minutes", + }; + + return metadata; +} export default async function ListingsForRentDetail({ params }: { params: Promise<{ slug: string }> }) { const slug = (await params).slug; @@ -329,136 +384,7 @@ export default async function ListingsForRentDetail({ params }: { params: Promis
-
-

Find Your Property

-
-
- - -
-
- - -
-
- - -
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- -
-
-
+
diff --git a/src/app/(main)/listings-for-rent/page.tsx b/src/app/(main)/listings-for-rent/page.tsx index 30c8702..d7c17fe 100644 --- a/src/app/(main)/listings-for-rent/page.tsx +++ b/src/app/(main)/listings-for-rent/page.tsx @@ -1,12 +1,11 @@ -import CardProperty from "@/components/CardProperty"; import HeroImage from "@/components/HeroImage"; import Pagination from "@/components/Pagination"; -import Select from "@/components/Select"; +import CardProperty from "@/components/properties/CardProperty"; +import FilterProperty from "@/components/properties/FilterProperty"; 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."; @@ -40,7 +39,6 @@ export default async function ListingsForRent(props: { location: searchParams?.location, }); const isEmpty = propertiesData.formattedData.length <= 0; - const statesData = State.getStatesOfCountry("US").map((st) => ({ value: st.name, label: st.name })); return ( <> @@ -81,69 +79,11 @@ export default async function ListingsForRent(props: { {/* End Pagination */}
+
-
-

Find Your Property

-
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
-
- -
- -
+
diff --git a/src/collections/Properties.ts b/src/collections/Properties.ts index 6632bb1..72d34eb 100644 --- a/src/collections/Properties.ts +++ b/src/collections/Properties.ts @@ -1,4 +1,5 @@ import formatSlug from "@/utils/payload/formatSlug"; +import setAuthor from "@/utils/payload/setAuthor"; import type { CollectionConfig } from "payload"; export const Properties: CollectionConfig = { @@ -143,6 +144,28 @@ export const Properties: CollectionConfig = { label: "Embed Google Map URL", type: "text", }, + { + name: "createdBy", + type: "relationship", + relationTo: "users", + hooks: { + beforeChange: [setAuthor], + }, + admin: { + hidden: true, + }, + }, + { + name: "updatedBy", + type: "relationship", + relationTo: "users", + hooks: { + beforeChange: [setAuthor], + }, + admin: { + hidden: true, + }, + }, ], admin: { hideAPIURL: true, diff --git a/src/components/layouts/Header.tsx b/src/components/layouts/Header.tsx index d8a5e50..ac8c3fd 100644 --- a/src/components/layouts/Header.tsx +++ b/src/components/layouts/Header.tsx @@ -56,7 +56,7 @@ export default function Header() { - Login + Login diff --git a/src/components/CardProperty.tsx b/src/components/properties/CardProperty.tsx similarity index 100% rename from src/components/CardProperty.tsx rename to src/components/properties/CardProperty.tsx diff --git a/src/components/properties/FilterProperty.tsx b/src/components/properties/FilterProperty.tsx new file mode 100644 index 0000000..e2ce402 --- /dev/null +++ b/src/components/properties/FilterProperty.tsx @@ -0,0 +1,79 @@ +import { State } from "country-state-city"; +import Select from "@/components/Select"; +import { FetchPropertyParams } from "@/schema/services/property"; + +type FilterPropertyProps = { + propertyType: "sell" | "rent"; + searchParams?: { + [P in keyof FetchPropertyParams]: string | undefined; + }; +}; + +export default function FilterProperty({ propertyType, searchParams }: FilterPropertyProps) { + const statesData = State.getStatesOfCountry("US").map((st) => ({ value: st.name, label: st.name })); + + return ( + <> +
+

Find Your Property

+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ +
+ + + + ); +} diff --git a/src/payload-types.ts b/src/payload-types.ts index 1929e8e..6670958 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -279,6 +279,8 @@ export interface Property { }[] | null; embed_map_url?: string | null; + createdBy?: (number | null) | User; + updatedBy?: (number | null) | User; updatedAt: string; createdAt: string; _status?: ('draft' | 'published') | null; @@ -486,6 +488,8 @@ export interface PropertiesSelect { id?: T; }; embed_map_url?: T; + createdBy?: T; + updatedBy?: T; updatedAt?: T; createdAt?: T; _status?: T; diff --git a/src/utils/sanitize.ts b/src/utils/sanitize.ts index 01b04b3..8c5a8b0 100644 --- a/src/utils/sanitize.ts +++ b/src/utils/sanitize.ts @@ -23,7 +23,7 @@ export function sanitizePageNumber(page: any, defaultPage = 1): number { return parsedPage; } -export function sanitizeBlogContentIntoStringPreview(data: Blog["content"]) { +export function sanitizeBlogContentIntoStringPreview(data: Blog["content"], limit = 100) { // Find the first paragraph that has children with text const firstParagraph = data.root.children.find( (node) => @@ -40,6 +40,6 @@ export function sanitizeBlogContentIntoStringPreview(data: Blog["content"]) { // @ts-ignore const text = firstParagraph.children?.[0]?.text ?? ""; - // Limit to 100 characters - return `${text.length > 100 ? text.slice(0, 100) : text}...`; + // Limit characters + return `${text.length > limit ? text.slice(0, limit) : text}...`; }