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 (
<>
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
- 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 (
-
+
-
- {/* 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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ 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,
+ },
+ };
+};