From 06057b51dbd408829a34ee147b11f4a1580f0660 Mon Sep 17 00:00:00 2001 From: RizqiSyahrendra Date: Mon, 28 Apr 2025 21:21:22 +0700 Subject: [PATCH] feat: single page payload integration fetching from db --- src/app/(main)/[slug]/loading.tsx | 5 + src/app/(main)/[slug]/page.tsx | 111 +++++++++++++++++ src/app/(main)/blog/[slug]/page.tsx | 139 +--------------------- src/components/HeroImage.tsx | 2 +- src/components/blocks/Content/index.tsx | 20 +--- src/components/blogs/CardBlog.tsx | 2 +- src/components/blogs/DetailPageBlog.tsx | 151 ++++++++++++++++++++++++ src/components/layouts/Footer.tsx | 2 +- src/components/pages/DetailPage.tsx | 74 ++++++++++++ src/services/payload/page.ts | 40 +++++++ 10 files changed, 391 insertions(+), 155 deletions(-) create mode 100644 src/app/(main)/[slug]/loading.tsx create mode 100644 src/app/(main)/[slug]/page.tsx create mode 100644 src/components/blogs/DetailPageBlog.tsx create mode 100644 src/components/pages/DetailPage.tsx create mode 100644 src/services/payload/page.ts diff --git a/src/app/(main)/[slug]/loading.tsx b/src/app/(main)/[slug]/loading.tsx new file mode 100644 index 0000000..779bae8 --- /dev/null +++ b/src/app/(main)/[slug]/loading.tsx @@ -0,0 +1,5 @@ +import LoaderFixed from "@/components/loaders/LoaderFixed"; + +export default function Loading() { + return ; +} diff --git a/src/app/(main)/[slug]/page.tsx b/src/app/(main)/[slug]/page.tsx new file mode 100644 index 0000000..7a098b9 --- /dev/null +++ b/src/app/(main)/[slug]/page.tsx @@ -0,0 +1,111 @@ +import DetailPageBlog from "@/components/blogs/DetailPageBlog"; +import DetailPage from "@/components/pages/DetailPage"; +import { fetchBlogDetail } from "@/services/payload/blog"; +import { fetchPageBySlug } from "@/services/payload/page"; +import { getDefaultMetadata } from "@/utils/metadata"; +import { Metadata } from "next"; +import { headers } from "next/headers"; +import { notFound } from "next/navigation"; + +export async function generateMetadata(props: { params: Promise<{ slug: string }> }): Promise { + const metadata = await getDefaultMetadata(); + const params = await props.params; + + let title = `Page Not Found - ${metadata.openGraph?.siteName}`; + let description = title; + let publishedAt = ""; + let updatedAt = ""; + let imgUrl = ""; + let createdByName = ""; + let canonicalUrl = ""; + + const blog = await fetchBlogDetail(params.slug); + if (!!blog) { + // check for blog data + title = `${!!blog.data?.meta?.title ? blog.data?.meta?.title : blog.data.title} - ${metadata.openGraph?.siteName}`; + description = `${!!blog.data?.meta?.description ? blog.data?.meta?.description : blog.data.title}`; + imgUrl = blog.img.url; + publishedAt = blog.data.createdAt; + updatedAt = blog.data.updatedAt; + if (!!blog.data?.meta?.canonical_url) { + canonicalUrl = blog.data.meta.canonical_url; + } + if (!!blog?.data?.createdBy && typeof blog.data.createdBy !== "number") { + createdByName = blog.data.createdBy?.name ?? ""; + } + } else { + // check for page data when blog is not found + const page = await fetchPageBySlug({ slug: params.slug }); + if (!!page) { + title = `${!!page?.data?.meta?.title ? page?.data?.meta?.title : page.data.title} - ${metadata.openGraph?.siteName}`; + description = `${!!page?.data?.meta?.description ? page?.data?.meta?.description : page.data.title}`; + imgUrl = page.heroImg?.url; + publishedAt = page.createdAt; + updatedAt = page.updatedAt; + if (!!page.data?.meta?.canonical_url) { + canonicalUrl = page.data.meta.canonical_url; + } + if (!!page?.data?.createdBy && typeof page?.data?.createdBy !== "number") { + createdByName = page?.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; + } + if (!!metadata.alternates && !!canonicalUrl) { + metadata.alternates.canonical = canonicalUrl; + } + 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 SinglePage(props: { params: Promise<{ slug: string }> }) { + const params = await props.params; + 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 blog = await fetchBlogDetail(params.slug); + if (!!blog) { + return ( + <> + + + ); + } + + const page = await fetchPageBySlug({ slug: params.slug }); + if (!page) return notFound(); + + return ( + <> + + + ); +} diff --git a/src/app/(main)/blog/[slug]/page.tsx b/src/app/(main)/blog/[slug]/page.tsx index 8287882..9837964 100644 --- a/src/app/(main)/blog/[slug]/page.tsx +++ b/src/app/(main)/blog/[slug]/page.tsx @@ -1,19 +1,16 @@ -import ListOfRecentBlog from "@/components/blogs/ListOfRecentBlog"; -import HeroImage from "@/components/HeroImage"; +import DetailPageBlog from "@/components/blogs/DetailPageBlog"; import { fetchBlogDetail } from "@/services/payload/blog"; import { getDefaultMetadata } from "@/utils/metadata"; -import { RichText } from "@payloadcms/richtext-lexical/react"; import { Metadata } from "next"; import { headers } from "next/headers"; -import Image from "next/image"; import { notFound } from "next/navigation"; 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 title = `Page Not Found - ${metadata.openGraph?.siteName}`; + let description = title; let publishedAt = ""; let updatedAt = ""; let imgUrl = ""; @@ -81,135 +78,7 @@ export default async function BlogDetail(props: { params: Promise<{ slug: string return ( <> - - -
-
-
-
-
-
-
- {blog.img.alt} -
- - -
- -
-
- -
-
-
    -
  • - Share this post -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
- - -
-
- - {/* Sidebar */} - -
-
-
+ ); } diff --git a/src/components/HeroImage.tsx b/src/components/HeroImage.tsx index 5d8de17..a28433e 100644 --- a/src/components/HeroImage.tsx +++ b/src/components/HeroImage.tsx @@ -7,7 +7,7 @@ type HeroImageProps = { export default function HeroImage({ title = "", imgSrc = "/images/breadcrumbs-bg-05-1922x441.jpg" }: HeroImageProps) { return ( -
+
Blog -
- {/* Content */} -
- {/* Post */} -
-
-
- {/* @ts-ignore */} - -
-
-
- {/* End Post */} -
- {/* End Content */} -
+
+ {/* @ts-ignore */} +
); } diff --git a/src/components/blogs/CardBlog.tsx b/src/components/blogs/CardBlog.tsx index 0d41600..7e29085 100644 --- a/src/components/blogs/CardBlog.tsx +++ b/src/components/blogs/CardBlog.tsx @@ -9,7 +9,7 @@ type CardBlogProps = { }; export default function CardBlog({ data, colorPreset = 1, isDescriptionVisible = true }: CardBlogProps) { - const linkDetail = `/blog/${data.slug}`; + const linkDetail = `/${data.slug}`; if (colorPreset === 2) { return ( diff --git a/src/components/blogs/DetailPageBlog.tsx b/src/components/blogs/DetailPageBlog.tsx new file mode 100644 index 0000000..a1d3e48 --- /dev/null +++ b/src/components/blogs/DetailPageBlog.tsx @@ -0,0 +1,151 @@ +import { fetchBlogDetail } from "@/services/payload/blog"; +import Image from "next/image"; +import ListOfRecentBlog from "./ListOfRecentBlog"; +import HeroImage from "../HeroImage"; +import { RichText } from "@payloadcms/richtext-lexical/react"; + +type shareUrlDestination = "facebook" | "linkedin" | "twitter"; + +type DetailPageBlogProps = { + data: Awaited>; + shareUrl: Record; +}; + +export default function DetailPageBlog({ data, shareUrl }: DetailPageBlogProps) { + const blog = data; + if (!blog) return <>; + + return ( + <> + + +
+
+
+
+
+
+
+ {blog.img.alt} +
+ + +
+ +
+
+ +
+
+
    +
  • + Share this post +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+ + +
+
+ + {/* Sidebar */} + +
+
+
+ + ); +} diff --git a/src/components/layouts/Footer.tsx b/src/components/layouts/Footer.tsx index ee0b92a..301d072 100644 --- a/src/components/layouts/Footer.tsx +++ b/src/components/layouts/Footer.tsx @@ -105,7 +105,7 @@ export default async function Footer() { Dynamic Realty ©    - Privacy Policy + Privacy Policy

diff --git a/src/components/pages/DetailPage.tsx b/src/components/pages/DetailPage.tsx new file mode 100644 index 0000000..ff3197e --- /dev/null +++ b/src/components/pages/DetailPage.tsx @@ -0,0 +1,74 @@ +import { fetchPageBySlug } from "@/services/payload/page"; +import HeroImage from "../HeroImage"; +import { RenderBlocks } from "../blocks/RenderBlocks"; + +type shareUrlDestination = "facebook" | "linkedin" | "twitter"; + +type DetailPageProps = { + data: Awaited>; + shareUrl: Record; +}; + +export default function DetailPage({ data, shareUrl }: DetailPageProps) { + const page = data; + if (!page) return <>; + + return ( + <> + + +
+
+
+
+
+
+ +
+ +
+
+ +
+
+
    +
  • + Share this post +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+
+
+
+
+
+ + ); +} diff --git a/src/services/payload/page.ts b/src/services/payload/page.ts new file mode 100644 index 0000000..e73092a --- /dev/null +++ b/src/services/payload/page.ts @@ -0,0 +1,40 @@ +import payloadConfig from "@/payload.config"; +import { formatDate } from "@/utils/datetime"; +import { getPayload } from "payload"; + +export const fetchPageBySlug = async ({ slug }: { slug: string | undefined }) => { + const payload = await getPayload({ config: payloadConfig }); + + const result = await payload.find({ + collection: "pages", + // draft, + limit: 1, + pagination: false, + // overrideAccess: draft, + where: { + _status: { equals: "published" }, + slug: { + equals: slug, + }, + }, + }); + + if (!result.docs?.[0]) { + return null; + } + + const data = result.docs[0]; + + const heroImgUrl = typeof data.hero_img !== "number" ? (data?.hero_img?.url ?? "") : ""; + const heroImgAlt = typeof data.hero_img !== "number" ? (data?.hero_img?.alt ?? "") : ""; + + return { + data: data, + createdAt: formatDate(data.createdAt), + updatedAt: formatDate(data.updatedAt), + heroImg: { + url: heroImgUrl, + alt: heroImgAlt, + }, + }; +};