From 7b1b0d6106217f44e690ac1b315717577367d796 Mon Sep 17 00:00:00 2001 From: Tanmoy Parvez Date: Sun, 21 Sep 2025 12:13:01 +0600 Subject: [PATCH 1/3] Next.js Core Concepts --- next.config.ts | 8 + src/actions/create.ts | 34 ++++ .../dashboard/create-blog/page.tsx | 5 +- src/app/(public)/blogs/[blogId]/page.tsx | 45 +++++ src/app/(public)/blogs/loading.tsx | 11 ++ src/app/(public)/blogs/page.tsx | 21 ++- src/app/(public)/page.tsx | 16 +- src/app/api/blogs/[blogId]/route.ts | 11 ++ src/app/api/blogs/route.ts | 154 ++++++++++++++++++ .../modules/Blogs/CreateBlogForm.tsx | 107 ++++++++++++ src/components/shared/Navbar/nav-menu.tsx | 2 +- src/services/PostServices/index.ts | 4 + 12 files changed, 413 insertions(+), 5 deletions(-) create mode 100644 src/actions/create.ts create mode 100644 src/app/(public)/blogs/[blogId]/page.tsx create mode 100644 src/app/(public)/blogs/loading.tsx create mode 100644 src/app/api/blogs/[blogId]/route.ts create mode 100644 src/app/api/blogs/route.ts create mode 100644 src/components/modules/Blogs/CreateBlogForm.tsx create mode 100644 src/services/PostServices/index.ts diff --git a/next.config.ts b/next.config.ts index e9ffa30..e67724c 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,14 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "**", + }, + ], + }, }; export default nextConfig; diff --git a/src/actions/create.ts b/src/actions/create.ts new file mode 100644 index 0000000..6ac5bc0 --- /dev/null +++ b/src/actions/create.ts @@ -0,0 +1,34 @@ +"use server"; + +import { revalidatePath, revalidateTag } from "next/cache"; +import { redirect } from "next/navigation"; + +export const create = async (data: FormData) => { + const blogInfo = Object.fromEntries(data.entries()); + const modifiedData = { + ...blogInfo, + tags: blogInfo.tags + .toString() + .split(",") + .map((tag) => tag.trim()), + authorId: 1, + isFeatured: Boolean(blogInfo.isFeatured), + }; + + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_API}/post`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(modifiedData), + }); + + const result = await res.json(); + + if (result?.id) { + revalidateTag("BLOGS"); + revalidatePath("/blogs"); + redirect("/"); + } + return result; +}; diff --git a/src/app/(dashboard)/dashboard/create-blog/page.tsx b/src/app/(dashboard)/dashboard/create-blog/page.tsx index 5fe61e1..588ccd1 100644 --- a/src/app/(dashboard)/dashboard/create-blog/page.tsx +++ b/src/app/(dashboard)/dashboard/create-blog/page.tsx @@ -1,9 +1,10 @@ +import CreateBlogForm from "@/components/modules/Blogs/CreateBlogForm"; import React from "react"; const CreateBlog = () => { return ( -
-

Create Blog

+
+
); }; diff --git a/src/app/(public)/blogs/[blogId]/page.tsx b/src/app/(public)/blogs/[blogId]/page.tsx new file mode 100644 index 0000000..c05180b --- /dev/null +++ b/src/app/(public)/blogs/[blogId]/page.tsx @@ -0,0 +1,45 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import BlogDetailsCard from "@/components/modules/Blogs/BlogDetailsCard"; +import { getBlogById } from "@/services/PostServices"; +import React from "react"; + +export const generateStaticParams = async () => { + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_API}/post`); + const { data: blogs } = await res.json(); + + return blogs.slice(0, 2).map((blog: any) => ({ + blogId: String(blog.id), + })); +}; + +export const generateMetadata = async ({ + params, +}: { + params: Promise<{ blogId: string }>; +}) => { + const { blogId } = await params; + const blog = await getBlogById(blogId); + + return { + title: blog?.title, + description: blog?.content, + }; +}; + +const BlogDetailsPage = async ({ + params, +}: { + params: Promise<{ blogId: string }>; +}) => { + const { blogId } = await params; + + const blog = await getBlogById(blogId); + + return ( +
+ +
+ ); +}; + +export default BlogDetailsPage; diff --git a/src/app/(public)/blogs/loading.tsx b/src/app/(public)/blogs/loading.tsx new file mode 100644 index 0000000..b9442c0 --- /dev/null +++ b/src/app/(public)/blogs/loading.tsx @@ -0,0 +1,11 @@ +import Loading from "@/components/ui/Loading"; + +const BlogsLoadingPage = () => { + return ( +
+ +
+ ); +}; + +export default BlogsLoadingPage; diff --git a/src/app/(public)/blogs/page.tsx b/src/app/(public)/blogs/page.tsx index 60fc28f..2e09cbd 100644 --- a/src/app/(public)/blogs/page.tsx +++ b/src/app/(public)/blogs/page.tsx @@ -1,7 +1,26 @@ -const AllBlogsPage = () => { +/* eslint-disable @typescript-eslint/no-explicit-any */ +import BlogCard from "@/components/modules/Blogs/BlogCard"; +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "All Blogs | Next Blog", + description: + "Browse all blog posts on web development, Next.js, React, and more. Stay updated with the latest tutorials and articles.", +}; + +const AllBlogsPage = async () => { + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_API}/post`, { + cache: "no-store", + }); + const { data: blogs } = await res.json(); return (

All Blogs

+
+ {blogs.map((blog: any) => ( + + ))} +
); }; diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx index 39eb8c0..425dffd 100644 --- a/src/app/(public)/page.tsx +++ b/src/app/(public)/page.tsx @@ -1,10 +1,24 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import BlogCard from "@/components/modules/Blogs/BlogCard"; import Hero from "@/components/modules/Home/Hero"; -export default function HomePage() { +export default async function HomePage() { + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_API}/post`, { + next: { + tags: ["BLOGS"], + }, + }); + const { data: blogs } = await res.json(); + return (

Featured Posts

+
+ {blogs.slice(0, 3).map((blog: any) => ( + + ))} +
); } diff --git a/src/app/api/blogs/[blogId]/route.ts b/src/app/api/blogs/[blogId]/route.ts new file mode 100644 index 0000000..31ec787 --- /dev/null +++ b/src/app/api/blogs/[blogId]/route.ts @@ -0,0 +1,11 @@ +import { NextResponse } from "next/server"; +import { blogs } from "../route"; + +export async function GET( + request: Request, + { params }: { params: Promise<{ blogId: string }> } +) { + const { blogId } = await params; + const blog = blogs.find((blog) => blog.id === parseInt(blogId)); + return NextResponse.json(blog); +} diff --git a/src/app/api/blogs/route.ts b/src/app/api/blogs/route.ts new file mode 100644 index 0000000..08735bd --- /dev/null +++ b/src/app/api/blogs/route.ts @@ -0,0 +1,154 @@ +import { NextResponse } from "next/server"; + +export const blogs = [ + { + id: 13, + title: "abc", + content: "abc", + thumbnail: "https://teamraft.com/wp-content/uploads/nextjs.jpg", + isFeatured: true, + tags: ["Next.js"], + views: 0, + authorId: 1, + createdAt: "2025-09-21T04:33:08.406Z", + updatedAt: "2025-09-21T04:33:08.406Z", + author: { + id: 1, + name: "Tanmoy Parvez", + email: "tanmoy@gmail.com", + password: "tanmoy1234", + role: "USER", + phone: "01234567890", + picture: null, + status: "ACTIVE", + isVerified: false, + createdAt: "2025-09-19T18:39:44.582Z", + updatedAt: "2025-09-19T18:39:44.582Z", + }, + }, + { + id: 12, + title: "Getting Started with Server Actions", + content: + "Next.js introduces new features for building fast and scalable web applications. Learn how to set up your first project and explore its App Router", + thumbnail: "https://teamraft.com/wp-content/uploads/nextjs.jpg", + isFeatured: true, + tags: ["Next.js", "Actions"], + views: 0, + authorId: 1, + createdAt: "2025-09-21T04:29:49.359Z", + updatedAt: "2025-09-21T04:29:49.359Z", + author: { + id: 1, + name: "Tanmoy Parvez", + email: "tanmoy@gmail.com", + password: "tanmoy1234", + role: "USER", + phone: "01234567890", + picture: null, + status: "ACTIVE", + isVerified: false, + createdAt: "2025-09-19T18:39:44.582Z", + updatedAt: "2025-09-19T18:39:44.582Z", + }, + }, + { + id: 3, + title: "A Beginner’s Guide to Tailwind CSS", + content: + "Tailwind CSS helps you design modern websites quickly with utility-first classes. Learn how to set it up and start styling your first project.", + thumbnail: "https://miro.medium.com/v2/1*tHpUU_Z2pTMt5G1KfY0ulg.jpeg", + isFeatured: true, + tags: ["CSS", "Tailwind", "Frontend"], + views: 78, + authorId: 1, + createdAt: "2025-09-20T13:57:27.855Z", + updatedAt: "2025-09-21T02:19:49.934Z", + author: { + id: 1, + name: "Tanmoy Parvez", + email: "tanmoy@gmail.com", + password: "tanmoy1234", + role: "USER", + phone: "01234567890", + picture: null, + status: "ACTIVE", + isVerified: false, + createdAt: "2025-09-19T18:39:44.582Z", + updatedAt: "2025-09-19T18:39:44.582Z", + }, + }, + { + id: 2, + title: "Understanding Server Components in React", + content: + "React Server Components let you build faster apps by splitting work between the client and server. This guide covers how they work and why they matter.", + thumbnail: + "https://images.ctfassets.net/e5382hct74si/2cwYB0NTfhKcwLjwOoAXS/0f8fc9f08fe8e1e4e79795fee16a1d99/OG_Card_V1.png", + isFeatured: true, + tags: ["React", "Server Components", "Performance"], + views: 18, + authorId: 1, + createdAt: "2025-09-20T13:56:06.685Z", + updatedAt: "2025-09-20T19:46:11.383Z", + author: { + id: 1, + name: "Tanmoy Parvez", + email: "tanmoy@gmail.com", + password: "tanmoy1234", + role: "USER", + phone: "01234567890", + picture: null, + status: "ACTIVE", + isVerified: false, + createdAt: "2025-09-19T18:39:44.582Z", + updatedAt: "2025-09-19T18:39:44.582Z", + }, + }, + { + id: 1, + title: "Getting Started with Next.js", + content: + "Next.js introduces new features for building fast and scalable web applications. Learn how to set up your first project and explore its App Router.", + thumbnail: "https://teamraft.com/wp-content/uploads/nextjs.jpg", + isFeatured: true, + tags: ["Next.js", "React", "Web Development"], + views: 8, + authorId: 1, + createdAt: "2025-09-19T18:44:36.805Z", + updatedAt: "2025-09-20T19:18:11.897Z", + author: { + id: 1, + name: "Tanmoy Parvez", + email: "tanmoy@gmail.com", + password: "tanmoy1234", + role: "USER", + phone: "01234567890", + picture: null, + status: "ACTIVE", + isVerified: false, + createdAt: "2025-09-19T18:39:44.582Z", + updatedAt: "2025-09-19T18:39:44.582Z", + }, + }, +]; + +export async function GET() { + return Response.json(blogs); +} + +export const POST = async (request: Request) => { + const blog = await request.json(); + const newBlog = { + ...blog, + id: blogs.length + 1, + }; + blogs.push(newBlog); + + return new NextResponse(JSON.stringify(newBlog), { + status: 201, + headers: { + "Content-type": "application/json", + }, + }); +}; diff --git a/src/components/modules/Blogs/CreateBlogForm.tsx b/src/components/modules/Blogs/CreateBlogForm.tsx new file mode 100644 index 0000000..0f8cb36 --- /dev/null +++ b/src/components/modules/Blogs/CreateBlogForm.tsx @@ -0,0 +1,107 @@ +"use client"; +import { create } from "@/actions/create"; +import Form from "next/form"; + +import { useState } from "react"; + +export default function CreateBlogForm() { + const [isFeatured, setIsFeatured] = useState("false"); + + return ( +
+

Create Blog

+ + {/* Title */} +
+ + +
+ + {/* Content */} +
+ +