diff --git a/app/blogs/[slug]/loading.tsx b/app/blogs/[slug]/loading.tsx
new file mode 100644
index 0000000..09dbf97
--- /dev/null
+++ b/app/blogs/[slug]/loading.tsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+const Loading = () => {
+ return
Loading
;
+};
+
+export default Loading;
diff --git a/app/blogs/[slug]/page.tsx b/app/blogs/[slug]/page.tsx
new file mode 100644
index 0000000..62c3094
--- /dev/null
+++ b/app/blogs/[slug]/page.tsx
@@ -0,0 +1,70 @@
+import { getOneBlog } from "@/utils/fetch-blogs";
+import React, { Suspense } from "react";
+import Loading from "./loading";
+import { formatDate } from "@/utils/helper";
+import ImageFallback from "@/components/image-fallback";
+import { Metadata } from "next";
+
+interface BlogProps {
+ params: {
+ slug: string;
+ };
+}
+
+export async function generateMetadata({ params: { slug } }: BlogProps): Promise {
+ const blogData = await getOneBlog(slug);
+ if (!blogData) {
+ return {
+ title: "Bài viết không tồn tại",
+ description: "Bài viết không tồn tại",
+ };
+ }
+ return {
+ title: blogData.title,
+ description: blogData.description,
+ openGraph: {
+ title: blogData.title,
+ description: blogData.description,
+ images: [`/api/og?title=${blogData.title}&image=${blogData.imageUrl}`, blogData.imageUrl],
+ },
+ };
+}
+
+const BlogDetailPage = async ({ params: { slug } }: BlogProps) => {
+ const blogData = await getOneBlog(slug);
+ if (!blogData) {
+ return Blog not found
;
+ }
+
+ const content = (
+
+ );
+
+ return (
+ }>
+
+
+
Ngày cập nhật: {formatDate(blogData.createdAt)}
+
+
{blogData.title}
+
Tác giả: {blogData.normalizedName || "Unknown"}
+
+ {content}
+
+
+
+
+ );
+};
+
+export default BlogDetailPage;
diff --git a/components/image-fallback.tsx b/components/image-fallback.tsx
index 3c27e34..f027ba9 100644
--- a/components/image-fallback.tsx
+++ b/components/image-fallback.tsx
@@ -1,3 +1,4 @@
+"use client";
import Image from "next/image";
import { useEffect, useState } from "react";
diff --git a/package-lock.json b/package-lock.json
index 5c5c410..2ba5895 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
"clsx": "^2.1.1",
"lucide-react": "^0.411.0",
"mini-svg-data-uri": "^1.4.4",
+ "moment": "^2.30.1",
"next": "14.2.5",
"next-themes": "^0.3.0",
"react": "^18",
@@ -5640,6 +5641,15 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/moment": {
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
diff --git a/package.json b/package.json
index 2c9f6af..970958a 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"clsx": "^2.1.1",
"lucide-react": "^0.411.0",
"mini-svg-data-uri": "^1.4.4",
+ "moment": "^2.30.1",
"next": "14.2.5",
"next-themes": "^0.3.0",
"react": "^18",
diff --git a/utils/fetch-blogs.ts b/utils/fetch-blogs.ts
index ce54461..d27d315 100644
--- a/utils/fetch-blogs.ts
+++ b/utils/fetch-blogs.ts
@@ -7,3 +7,13 @@ export async function getListBlog() {
const res = await fetch(`${baseUrl}/Blog/getList?type=${type}`);
return res.json() as Promise;
}
+
+export async function getOneBlog(slug: string) {
+ const res = await fetch(`${baseUrl}/Blog/getOneSlug/${slug}?type=${type}`);
+ return res.json() as Promise;
+}
+
+export async function getImageBlog(slug: string) {
+ const data = await getOneBlog(slug);
+ return data.imageUrl || "";
+}
diff --git a/utils/helper.ts b/utils/helper.ts
new file mode 100644
index 0000000..1ab0f4b
--- /dev/null
+++ b/utils/helper.ts
@@ -0,0 +1,6 @@
+import moment from "moment";
+
+export function formatDate(date: string) {
+ const convertDate = new Date(date);
+ return moment(convertDate).format("DD-MM-YYYY");
+}