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/package-lock.json b/package-lock.json index 787db4f..fc66c62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,16 +8,22 @@ "name": "next-blog-ui", "version": "0.1.0", "dependencies": { + "@hookform/resolvers": "^5.2.2", "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-slot": "^1.2.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.544.0", "next": "15.5.3", + "next-auth": "^4.24.11", "react": "19.1.0", "react-dom": "19.1.0", - "tailwind-merge": "^3.3.1" + "react-hook-form": "^7.63.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.3.1", + "zod": "^4.1.11" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -45,6 +51,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/core": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", @@ -219,6 +234,18 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@hookform/resolvers": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", + "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -967,6 +994,15 @@ "node": ">=12.4.0" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", @@ -1165,6 +1201,29 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-navigation-menu": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", @@ -1427,6 +1486,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2821,6 +2886,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4566,6 +4640,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4955,6 +5038,24 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-cache/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/lucide-react": { "version": "0.544.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz", @@ -5170,6 +5271,38 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.11", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz", + "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.2", + "next": "^12.2.5 || ^13 || ^14 || ^15", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -5198,6 +5331,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5208,6 +5347,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -5321,6 +5469,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oidc-token-hash": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.1.tgz", + "integrity": "sha512-D7EmwxJV6DsEB6vOFLrBM2OzsVgQzgPWyHlV2OOAVj772n+WTXpudC9e9u5BVKQnYwaD30Ivhi9b+4UeBcGu9g==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5487,6 +5659,28 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.27.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", + "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5497,6 +5691,12 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5561,6 +5761,22 @@ "react": "^19.1.0" } }, + "node_modules/react-hook-form": { + "version": "7.63.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz", + "integrity": "sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6022,6 +6238,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6608,6 +6834,15 @@ } } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6745,6 +6980,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", + "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 688cf9d..ba9cb18 100644 --- a/package.json +++ b/package.json @@ -9,16 +9,22 @@ "lint": "eslint" }, "dependencies": { + "@hookform/resolvers": "^5.2.2", "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-slot": "^1.2.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.544.0", "next": "15.5.3", + "next-auth": "^4.24.11", "react": "19.1.0", "react-dom": "19.1.0", - "tailwind-merge": "^3.3.1" + "react-hook-form": "^7.63.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.3.1", + "zod": "^4.1.11" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/src/actions/auth.ts b/src/actions/auth.ts new file mode 100644 index 0000000..53b3fb3 --- /dev/null +++ b/src/actions/auth.ts @@ -0,0 +1,30 @@ +"use server"; +import { FieldValues } from "react-hook-form"; + +export const register = async (data: FieldValues) => { + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_API}/user`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + if (!res?.ok) { + console.error("User Registration Failed", await res.text()); + } + return await res.json(); +}; + +export const login = async (data: FieldValues) => { + const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_API}/auth/login`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + if (!res?.ok) { + console.error("Login Failed", await res.text()); + } + return await res.json(); +}; diff --git a/src/actions/create.ts b/src/actions/create.ts new file mode 100644 index 0000000..8f076ad --- /dev/null +++ b/src/actions/create.ts @@ -0,0 +1,36 @@ +"use server"; + +import { getUserSession } from "@/helpers/getUserSession"; +import { revalidatePath, revalidateTag } from "next/cache"; +import { redirect } from "next/navigation"; + +export const create = async (data: FormData) => { + const session = await getUserSession(); + const blogInfo = Object.fromEntries(data.entries()); + const modifiedData = { + ...blogInfo, + tags: blogInfo.tags + .toString() + .split(",") + .map((tag) => tag.trim()), + authorId: session?.user?.id, + 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/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index 7a3a338..58a54e5 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -1,9 +1,19 @@ -const DashboardHomePage = () => { +import { getUserSession } from "@/helpers/getUserSession"; + +export default async function DashboardHome() { + const quote = "The secret of getting ahead is getting started. – Mark Twain"; + + const session = await getUserSession(); + return ( -
-

Dashboard Home Page

+
+

+ Welcome, {session?.user?.name}! +

+

+ {session?.user?.email} +

+

{quote}

); -}; - -export default DashboardHomePage; +} diff --git a/src/app/(public)/(auth)/login/page.tsx b/src/app/(public)/(auth)/login/page.tsx new file mode 100644 index 0000000..716760a --- /dev/null +++ b/src/app/(public)/(auth)/login/page.tsx @@ -0,0 +1,7 @@ +import LoginForm from "@/components/modules/Auth/LoginForm"; + +const LoginPage = () => { + return ; +}; + +export default LoginPage; diff --git a/src/app/(public)/(auth)/register/page.tsx b/src/app/(public)/(auth)/register/page.tsx new file mode 100644 index 0000000..f5bdbf1 --- /dev/null +++ b/src/app/(public)/(auth)/register/page.tsx @@ -0,0 +1,7 @@ +import RegisterForm from "@/components/modules/Auth/RegisterForm"; + +const RegisterPage = () => { + return ; +}; + +export default RegisterPage; 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/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..f2dc64f --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,6 @@ +import { authOptions } from "@/helpers/authOptions"; +import NextAuth from "next-auth"; + +const handler = NextAuth(authOptions); + +export { handler as GET, handler as POST }; 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/app/layout.tsx b/src/app/layout.tsx index 948a036..000e9f2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,8 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import AuthProvider from "@/providers/AuthProvider"; +import { Toaster } from "sonner"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -27,7 +29,10 @@ export default function RootLayout({ - {children} + + + {children} + ); diff --git a/src/components/modules/Auth/LoginForm.tsx b/src/components/modules/Auth/LoginForm.tsx new file mode 100644 index 0000000..7a5e609 --- /dev/null +++ b/src/components/modules/Auth/LoginForm.tsx @@ -0,0 +1,161 @@ +"use client"; + +import React from "react"; +import { FieldValues, useForm } from "react-hook-form"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import Link from "next/link"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import Image from "next/image"; +import { signIn } from "next-auth/react"; +import { login } from "@/actions/auth"; +import { toast } from "sonner"; + +// type LoginFormValues = { +// email: string; +// password: string; +// }; + +export default function LoginForm() { + const form = useForm({ + defaultValues: { + email: "", + password: "", + }, + }); + + const onSubmit = async (values: FieldValues) => { + try { + // const res = await login(values); + // if (res?.id) { + // toast.success("User Logged in Successfully"); + // } else { + // toast.error("User Login Failed"); + // } + signIn("credentials", { + ...values, + callbackUrl: "/dashboard", + }); + } catch (err) { + console.error(err); + } + }; + + const handleSocialLogin = (provider: "google" | "github") => { + console.log(`Login with ${provider}`); + }; + + return ( +
+
+
+ +

Login

+ + {/* Email */} + ( + + Email + + + + + + )} + /> + + {/* Password */} + ( + + Password + + + + + + )} + /> + + + +
+
+ or continue with +
+
+ + + {/* Social Login Buttons */} +
+ + + +
+

+ Don’t have an account?{" "} + + Register + +

+
+
+ ); +} diff --git a/src/components/modules/Auth/RegisterForm.tsx b/src/components/modules/Auth/RegisterForm.tsx new file mode 100644 index 0000000..c3abf9a --- /dev/null +++ b/src/components/modules/Auth/RegisterForm.tsx @@ -0,0 +1,136 @@ +"use client"; + +import React from "react"; +import { FieldValues, useForm } from "react-hook-form"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import Link from "next/link"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { register } from "@/actions/auth"; +import { toast } from "sonner"; +import { useRouter } from "next/navigation"; + +// type RegisterFormValues = { +// name: string; +// email: string; +// phone: string; +// password: string; +// }; + +export default function RegisterForm() { + const form = useForm({ + defaultValues: { + name: "", + email: "", + phone: "", + password: "", + }, + }); + const router = useRouter(); + const onSubmit = async (values: FieldValues) => { + try { + const res = await register(values); + if (res?.id) { + toast.success("User Registered Successfully"); + router.push("/login"); + } + } catch (err) { + console.error(err); + } + }; + + return ( +
+
+ +

Register Now

+ {/* Name */} + ( + + Name + + + + + + )} + /> + {/* Email */} + ( + + Email + + + + + + )} + /> + {/* Phone */} + ( + + Phone + + + + + + )} + /> + {/* Password */} + ( + + Password + + + + + + )} + /> + + + +

+ Already have an account?{" "} + + Login + +

+ + +
+ ); +} 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 */} +
+ +