├── frontend
├── src
│ ├── App.css
│ ├── components
│ │ ├── ScrollToTop.jsx
│ │ ├── AdminPrivateRoute.jsx
│ │ ├── PrivateRoute.jsx
│ │ ├── ThemeProvider.jsx
│ │ ├── CallToAction.jsx
│ │ ├── PostCard.jsx
│ │ ├── OAuth.jsx
│ │ ├── Footer.jsx
│ │ ├── DashSidbar.jsx
│ │ ├── Header.jsx
│ │ ├── Comment.jsx
│ │ ├── DashComments.jsx
│ │ ├── DashUsers.jsx
│ │ ├── DashPosts.jsx
│ │ ├── CommentSection.jsx
│ │ ├── DashboardComp.jsx
│ │ └── DashProfile.jsx
│ ├── redux
│ │ ├── reducers
│ │ │ ├── theme
│ │ │ │ └── themeSlice.js
│ │ │ └── user
│ │ │ │ └── userSlice.js
│ │ └── store
│ │ │ └── store.js
│ ├── pages
│ │ ├── Projects.jsx
│ │ ├── Dashboard.jsx
│ │ ├── About.jsx
│ │ ├── PageNotFound.jsx
│ │ ├── Home.jsx
│ │ ├── PostPage.jsx
│ │ ├── SignIn.jsx
│ │ ├── SignUp.jsx
│ │ ├── CreatePost.jsx
│ │ ├── Search.jsx
│ │ └── UpdatePost.jsx
│ ├── index.css
│ ├── main.jsx
│ ├── firebase.js
│ └── App.jsx
├── .env
├── postcss.config.js
├── index.html
├── vite.config.js
├── .gitignore
├── README.md
├── tailwind.config.js
├── .eslintrc.cjs
└── package.json
├── backend
├── utils
│ ├── error.js
│ └── verifyUser.js
├── router
│ ├── auth.routes.js
│ ├── post.routes.js
│ ├── user.routes.js
│ └── comment.routes.js
├── .gitignore
├── config
│ └── config.js
├── model
│ ├── comment.model.js
│ ├── users.model.js
│ └── post.model.js
├── package.json
├── index.js
└── controller
│ ├── auth.controller.js
│ ├── post.controller.js
│ ├── comment.controller.js
│ └── user.controller.js
└── .gitignore
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/.env:
--------------------------------------------------------------------------------
1 | VITE_FIREBASE_API = "AIzaSyDs7gM38dGMGUXMXr6Xou6NOZ3Xtyeq_p8"
2 |
--------------------------------------------------------------------------------
/frontend/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/backend/utils/error.js:
--------------------------------------------------------------------------------
1 | const errorHandler = (statusCode, message) => {
2 | const error = new Error();
3 | error.statusCode = statusCode;
4 | error.message = message;
5 | return error;
6 | };
7 |
8 | module.exports = errorHandler;
9 |
--------------------------------------------------------------------------------
/frontend/src/components/ScrollToTop.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useLocation } from "react-router-dom";
3 |
4 | export default function ScrollToTop() {
5 | const { pathname } = useLocation();
6 | useEffect(() => {
7 | window.scrollTo(0, 0);
8 | }, [pathname]);
9 | return null;
10 | }
11 |
--------------------------------------------------------------------------------
/backend/router/auth.routes.js:
--------------------------------------------------------------------------------
1 | const { signup, signin, googleAuth } = require("../controller/auth.controller");
2 |
3 | const authRoutes = require("express").Router();
4 |
5 | authRoutes.post("/signup", signup);
6 | authRoutes.post("/signin", signin);
7 | authRoutes.post('/google', googleAuth);
8 |
9 | module.exports = authRoutes;
10 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Blog
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/frontend/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | server:{
7 | proxy:{
8 | '/api':{
9 | target : 'http://localhost:8080',
10 | secure:false
11 | }
12 | }
13 | },
14 | plugins: [react()],
15 | })
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | .env
--------------------------------------------------------------------------------
/frontend/src/components/AdminPrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import { Navigate, Outlet } from "react-router-dom";
3 |
4 | export function AdminPrivateRoute() {
5 | const { currentUser } = useSelector((state) => state.user);
6 | return currentUser && currentUser.isAdmin ? (
7 |
8 | ) : (
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/backend/config/config.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 |
4 | const startConnection=(URI)=>{
5 |
6 | mongoose.connect(URI)
7 | .then((res)=>{
8 | console.log(`Server is Connected to ${res.connection.host}`);
9 | })
10 | .catch((err)=>{
11 | console.log(`Error connecting to ${err.message}`);
12 | });
13 |
14 | }
15 |
16 | module.exports = startConnection;
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/theme/themeSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | theme: "light",
5 | };
6 |
7 | const themeSlice = createSlice({
8 | name: "theme",
9 | initialState,
10 | reducers: {
11 | toogletheme: (state) => {
12 | state.theme = state.theme === "light" ? "dark" : "light";
13 | },
14 | },
15 | });
16 |
17 | export const { toogletheme } = themeSlice.actions;
18 | export default themeSlice.reducer;
19 |
--------------------------------------------------------------------------------
/frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | import Flowbite from 'flowbite/plugin'
3 | import tailwindScrollbar from "tailwind-scrollbar";
4 | export default {
5 | content: [
6 | "./index.html",
7 | "./src/**/*.{js,ts,jsx,tsx}",
8 | 'node_modules/flowbite-react/lib/esm/**/*.js',
9 | ],
10 | theme: {
11 | extend: {},
12 | },
13 | plugins: [
14 | // require('flowbite/plugin'),
15 | Flowbite,
16 | tailwindScrollbar,
17 | ],
18 | }
--------------------------------------------------------------------------------
/frontend/src/components/PrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import { Navigate, Outlet } from "react-router-dom";
3 |
4 | export function PrivateRoute() {
5 | const { currentUser } = useSelector((state) => state.user);
6 | return currentUser ? : ;
7 | }
8 |
9 | export function ProtectedRoute() {
10 | const { currentUser } = useSelector((state) => state.user);
11 | return currentUser ? : ;
12 | }
13 |
--------------------------------------------------------------------------------
/backend/router/post.routes.js:
--------------------------------------------------------------------------------
1 | const { create, getPosts, updatePost, deletePost } = require('../controller/post.controller');
2 | const verifyToken = require('../utils/verifyUser');
3 |
4 | const postRoutes = require('express').Router();
5 |
6 |
7 | postRoutes.post('/create', verifyToken, create);
8 | postRoutes.get('/getposts', getPosts);
9 | postRoutes.put('/updatepost/:postId/:userId', verifyToken, updatePost);
10 | postRoutes.delete('/deletepost/:postId/:userId', verifyToken, deletePost);
11 |
12 | module.exports = postRoutes;
--------------------------------------------------------------------------------
/frontend/src/pages/Projects.jsx:
--------------------------------------------------------------------------------
1 | import CallToAction from "../components/CallToAction";
2 |
3 | export default function Projects() {
4 | return (
5 |
6 |
Pojects
7 |
8 | Build fun and engaging projects while learning HTML, CSS, and
9 | JavaScript!
10 |
11 |
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/src/components/ThemeProvider.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import PropTypes from "prop-types";
3 |
4 | export default function ThemeProvider({ children }) {
5 | const { theme } = useSelector((state) => state.theme);
6 | return (
7 |
8 |
9 | {children}
10 |
11 |
12 | );
13 | }
14 |
15 | ThemeProvider.propTypes = {
16 | children: PropTypes.node,
17 | };
18 |
--------------------------------------------------------------------------------
/backend/router/user.routes.js:
--------------------------------------------------------------------------------
1 | const { test, updateUser, deleteUser, signOut, getUsers, getUser } = require("../controller/user.controller");
2 | const verifyToken = require("../utils/verifyUser");
3 |
4 | const userRoutes = require("express").Router();
5 |
6 | userRoutes.get("/", test);
7 | userRoutes.put("/update/:userId", verifyToken, updateUser);
8 | userRoutes.delete("/delete/:userId", verifyToken, deleteUser);
9 | userRoutes.post("/signout", signOut);
10 | userRoutes.get("/getUsers", verifyToken, getUsers);
11 | userRoutes.get('/:userId', getUser);
12 |
13 | module.exports = userRoutes;
14 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react/jsx-no-target-blank': 'off',
16 | 'react-refresh/only-export-components': [
17 | 'warn',
18 | { allowConstantExport: true },
19 | ],
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/backend/model/comment.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const commentSchema = mongoose.Schema(
4 | {
5 | content: {
6 | type: String,
7 | required: true,
8 | },
9 | postId: {
10 | type: String,
11 | required: true,
12 | },
13 | userId: {
14 | type: String,
15 | required: true,
16 | },
17 | likes: {
18 | type: Array,
19 | default: [],
20 | },
21 | numberofLikes: {
22 | type: Number,
23 | default: 0,
24 | },
25 | },
26 | { timestamps: true }
27 | );
28 |
29 | const COMMENT = mongoose.model("COMMENT", commentSchema);
30 |
31 | module.exports = COMMENT;
32 |
--------------------------------------------------------------------------------
/backend/utils/verifyUser.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | const errorHandler = require("./error");
3 |
4 |
5 | const verifyToken = (req , res , next) => {
6 | const token = req.cookies.access_token
7 | if(!token){
8 | return next(errorHandler(401,'Unauthorized access'));
9 | }
10 | try {
11 | jwt.verify(token,process.env.JWT_SECRETKEY, (err, user)=>{
12 | if(err){
13 | return next(errorHandler("Unauthorized Access"));
14 | }
15 | req.user = user;
16 | next()
17 | })
18 | } catch (error) {
19 | next(error)
20 | }
21 | }
22 |
23 | module.exports = verifyToken
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | body {
6 | height: 100vh;
7 | }
8 |
9 | .ql-editor {
10 | font-size: 1.05rem;
11 | }
12 | .post-content p {
13 | margin-bottom: 0.5rem;
14 | }
15 | .post-content h1 {
16 | font-size: 1.5rem;
17 | font-weight: 600;
18 | font-family: sans-serif;
19 | margin: 1.5rem 0;
20 | }
21 | .post-content h2 {
22 | font-size: 1.4rem;
23 | font-family: sans-serif;
24 | margin: 1.5rem 0;
25 | }
26 | .post-content a {
27 | color: rgb(73, 149, 139);
28 | text-decoration: none;
29 | }
30 | .post-content a:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | .dark .post-content a {
35 | color: red;
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.jsx";
4 | import "./index.css";
5 | import { Provider } from "react-redux";
6 | import { store, persistor } from "./redux/store/store.js";
7 | import { PersistGate } from "redux-persist/integration/react";
8 | import ThemeProvider from "./components/ThemeProvider.jsx";
9 |
10 | ReactDOM.createRoot(document.getElementById("root")).render(
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "description": "Nitin's Blog Backend",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node index.js",
9 | "dev": "nodemon start index.js",
10 | "build": "npm install && npm install --prefix ../frontend && npm run build --prefix ../frontend"
11 | },
12 | "author": "Nitin Rajput",
13 | "license": "ISC",
14 | "dependencies": {
15 | "bcrypt": "^5.1.1",
16 | "cookie-parser": "^1.4.6",
17 | "cors": "^2.8.5",
18 | "dotenv": "^16.4.5",
19 | "express": "^4.18.3",
20 | "jsonwebtoken": "^9.0.2",
21 | "mongoose": "^8.2.2",
22 | "nodemon": "^3.1.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/backend/router/comment.routes.js:
--------------------------------------------------------------------------------
1 | const {
2 | createComment,
3 | getPostsComment,
4 | likeComment,
5 | editComment,
6 | deleteComment,
7 | getComments,
8 | } = require("../controller/comment.controller");
9 | const verifyToken = require("../utils/verifyUser");
10 |
11 | const commentRoutes = require("express").Router();
12 |
13 | commentRoutes.post("/create", verifyToken, createComment);
14 | commentRoutes.get("/getPostComment/:postId", getPostsComment);
15 | commentRoutes.put("/likeComment/:commentId", verifyToken, likeComment);
16 | commentRoutes.put("/editComment/:commentId", verifyToken, editComment);
17 | commentRoutes.delete("/deleteComment/:commentId", verifyToken, deleteComment);
18 | commentRoutes.get('/getComments', verifyToken, getComments);
19 |
20 | module.exports = commentRoutes;
21 |
--------------------------------------------------------------------------------
/frontend/src/firebase.js:
--------------------------------------------------------------------------------
1 | // Import the functions you need from the SDKs you need
2 | import { initializeApp } from "firebase/app";
3 | // TODO: Add SDKs for Firebase products that you want to use
4 | // https://firebase.google.com/docs/web/setup#available-libraries
5 |
6 | // Your web app's Firebase configuration
7 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional
8 | const firebaseConfig = {
9 | apiKey: import.meta.env.VITE_FIREBASE_API,
10 | authDomain: "nitin-s-blog.firebaseapp.com",
11 | projectId: "nitin-s-blog",
12 | storageBucket: "nitin-s-blog.appspot.com",
13 | messagingSenderId: "22846366881",
14 | appId: "1:22846366881:web:21063a883f4990a8617ce0",
15 | measurementId: "G-76GXJ4NHCS",
16 | };
17 |
18 | // Initialize Firebase
19 | export const app = initializeApp(firebaseConfig);
20 |
--------------------------------------------------------------------------------
/backend/model/users.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const userSchema = new mongoose.Schema(
4 | {
5 | username: {
6 | type: String,
7 | required: true,
8 | unique: true,
9 | },
10 | email: {
11 | type: String,
12 | required: true,
13 | unique: true,
14 | },
15 | password: {
16 | type: String,
17 | required: true,
18 | },
19 | profilePicture: {
20 | type: String,
21 | default : 'https://w7.pngwing.com/pngs/340/946/png-transparent-avatar-user-computer-icons-software-developer-avatar-child-face-heroes-thumbnail.png',
22 | },
23 | isAdmin: {
24 | type : Boolean,
25 | default : false,
26 | },
27 | },
28 | { timestamps: true }
29 | );
30 |
31 | const USER = mongoose.model("USER", userSchema);
32 |
33 | module.exports = USER;
34 |
--------------------------------------------------------------------------------
/backend/model/post.model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const postSchema = new mongoose.Schema(
4 | {
5 | userId: {
6 | type: String,
7 | required: true,
8 | },
9 | title: {
10 | type: String,
11 | required: true,
12 | },
13 | content: {
14 | type: String,
15 | required: true,
16 | },
17 | image: {
18 | type: String,
19 | default:
20 | "https://tiny-img.com/images/blog/thumb/blog-post-images-best-practices-in-2022.jpg",
21 | },
22 | category: {
23 | type: String,
24 | default: "uncategorized",
25 | },
26 | slug: {
27 | type: String,
28 | required: true,
29 | unique: true,
30 | },
31 | },
32 | { timestamps: true }
33 | );
34 |
35 | const POST = mongoose.model("POST", postSchema);
36 |
37 | module.exports = POST;
38 |
--------------------------------------------------------------------------------
/frontend/src/redux/store/store.js:
--------------------------------------------------------------------------------
1 | import { combineReducers, configureStore } from "@reduxjs/toolkit";
2 | import userSlice from "../reducers/user/userSlice";
3 | import themeSlice from "../reducers/theme/themeSlice";
4 | import { persistReducer } from "redux-persist";
5 | import storage from "redux-persist/lib/storage";
6 | import persistStore from "redux-persist/es/persistStore";
7 |
8 | // Root reducers all the reducers in the store
9 | const rootReducer = combineReducers({
10 | user: userSlice,
11 | theme: themeSlice,
12 | });
13 |
14 | const persistConfig = {
15 | key: "root",
16 | storage,
17 | version: 1,
18 | };
19 |
20 | const persistedReducer = persistReducer(persistConfig, rootReducer);
21 |
22 | export const store = configureStore({
23 | reducer: persistedReducer,
24 | middleware: (getDefaultMiddleware) =>
25 | getDefaultMiddleware({
26 | serializableCheck: false,
27 | }),
28 | });
29 |
30 | export const persistor = persistStore(store);
31 |
--------------------------------------------------------------------------------
/frontend/src/components/CallToAction.jsx:
--------------------------------------------------------------------------------
1 | import { Button } from "flowbite-react";
2 |
3 | export default function CallToAction() {
4 | return (
5 |
6 |
7 |
want to Learn more about JavaScript ?
8 |
9 | Check out these resources to learn more about JavaScript
10 |
11 |
15 |
20 | Learn more
21 |
22 |
23 |
24 |
25 |
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^2.2.2",
14 | "firebase": "^10.9.0",
15 | "flowbite-react": "^0.7.3",
16 | "framer-motion": "^11.3.7",
17 | "moment": "^2.30.1",
18 | "react": "^18.2.0",
19 | "react-circular-progressbar": "^2.1.0",
20 | "react-dom": "^18.2.0",
21 | "react-icons": "^5.0.1",
22 | "react-quill": "^2.0.0",
23 | "react-redux": "^9.1.0",
24 | "react-router-dom": "^6.22.2",
25 | "redux-persist": "^6.0.0"
26 | },
27 | "devDependencies": {
28 | "@tailwindcss/line-clamp": "^0.4.4",
29 | "@types/react": "^18.2.56",
30 | "@types/react-dom": "^18.2.19",
31 | "@vitejs/plugin-react": "^4.2.1",
32 | "autoprefixer": "^10.4.18",
33 | "eslint": "^8.56.0",
34 | "eslint-plugin-react": "^7.33.2",
35 | "eslint-plugin-react-hooks": "^4.6.0",
36 | "eslint-plugin-react-refresh": "^0.4.5",
37 | "postcss": "^8.4.35",
38 | "tailwind-scrollbar": "^3.1.0",
39 | "tailwindcss": "^3.4.1",
40 | "vite": "^5.1.4"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/frontend/src/pages/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useLocation } from "react-router-dom";
3 | import DashSidbar from "../components/DashSidbar";
4 | import DashProfile from "../components/DashProfile";
5 | import DashPosts from "../components/DashPosts";
6 | import DashUsers from "../components/DashUsers";
7 | import DashComments from "../components/DashComments";
8 | import DashboardComp from "../components/DashboardComp";
9 |
10 | export default function Dashboard() {
11 | const location = useLocation();
12 | const [tab, setTab] = useState("");
13 | useEffect(() => {
14 | const urlParms = new URLSearchParams(location.search);
15 | const tabFormUrl = urlParms.get("tab");
16 | if (tabFormUrl) {
17 | setTab(tabFormUrl);
18 | }
19 | }, [location.search]);
20 | return (
21 |
22 | {/* Dashboard SideBar */}
23 |
24 |
25 |
26 | {/* Profile Section */}
27 | {tab === "profile" &&
}
28 | {/* Admin Posts Section */}
29 | {tab === "posts" &&
}
30 | {/* All Users Sections */}
31 | {tab === "users" &&
}
32 | {/* All comments Section */}
33 | {tab === "comments" &&
}
34 | {/* Dashboard Compoent */}
35 | {tab === "dashboard" &&
}
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/frontend/src/components/PostCard.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import PropTypes from "prop-types";
3 |
4 | export default function PostCard({ post }) {
5 | return (
6 |
7 |
8 |
13 |
14 |
15 |
{post.title}
16 |
{post.category}
17 |
21 | Read article
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | PostCard.propTypes = {
29 | post: PropTypes.shape({
30 | _id: PropTypes.string,
31 | title: PropTypes.string,
32 | image: PropTypes.string,
33 | category: PropTypes.string,
34 | createdAt: PropTypes.string,
35 | slug: PropTypes.string,
36 | }),
37 | };
38 |
--------------------------------------------------------------------------------
/frontend/src/pages/About.jsx:
--------------------------------------------------------------------------------
1 | export default function About() {
2 | return (
3 |
4 |
5 |
6 |
7 | About Nitin's Blog
8 |
9 |
10 |
11 | Welcome to Nitin's Blog! This blog was created by Nitin
12 | Rajput as a personal project to share his thoughts and ideas with
13 | the world. Nitin is a passionate developer who loves to write
14 | about technology, coding, and everything in between.
15 |
16 |
17 |
18 | On this blog, you'll find weekly articles and tutorials on
19 | topics such as web development, software engineering, and
20 | programming languages. Nitin is always learning and exploring new
21 | technologies, so be sure to check back often for new content!
22 |
23 |
24 |
25 | We encourage you to leave comments on our posts and engage with
26 | other readers. You can like other people's comments and reply
27 | to them as well. We believe that a community of learners can help
28 | each other grow and improve.
29 |
30 |
31 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/backend/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const connectDB = require("./config/config");
3 | const userRoutes = require("./router/user.routes");
4 | const authRoutes = require("./router/auth.routes");
5 | const postRoutes = require("./router/post.routes");
6 | const cookieParser = require("cookie-parser");
7 | const cors = require("cors");
8 | const commentRoutes = require("./router/comment.routes");
9 | require("dotenv").config();
10 | const path = require("path");
11 |
12 | const PORT = process.env.PORT || 8080;
13 | const app = express();
14 | app.use(express.json());
15 | app.use(cookieParser());
16 | app.use(
17 | cors({
18 | origin: "*",
19 | })
20 | );
21 |
22 | const _dirname = path.resolve();
23 |
24 | // database connection
25 | const startConnection = async () => {
26 | try {
27 | await connectDB(process.env.DB_URI);
28 | app.listen(PORT, () => {
29 | console.log(`Server is Runing on http://localhost:${PORT}`);
30 | });
31 | } catch (err) {
32 | console.log(`Database is showing Error ${err.message}`);
33 | }
34 | };
35 |
36 | startConnection();
37 |
38 | app.use("/api/user", userRoutes);
39 | app.use("/api/auth", authRoutes);
40 | app.use("/api/post", postRoutes);
41 | app.use("/api/comment", commentRoutes);
42 |
43 | app.use(express.static(path.join(__dirname, "../frontend/dist")));
44 | app.get("*", (req, res) => {
45 | res.sendFile(path.join(__dirname, "../frontend", "dist", "index.html"));
46 | });
47 |
48 | //Middleware to handle the error
49 | app.use((err, req, res, next) => {
50 | const statusCode = err.statusCode || 500;
51 | const message = err.message || "Internal Server Error";
52 | res.status(statusCode).json({
53 | success: false,
54 | statusCode,
55 | message,
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/frontend/src/components/OAuth.jsx:
--------------------------------------------------------------------------------
1 | import { Button } from "flowbite-react";
2 | import { AiFillGoogleCircle } from "react-icons/ai";
3 | import { GoogleAuthProvider, getAuth, signInWithPopup } from "firebase/auth";
4 | import { app } from "../firebase";
5 | import { useDispatch } from "react-redux";
6 | import { SignInSuccess } from "../redux/reducers/user/userSlice";
7 | import { useNavigate } from "react-router-dom";
8 |
9 | export default function OAuth() {
10 | const dispatch = useDispatch();
11 | const navigate = useNavigate();
12 | const auth = getAuth(app);
13 | const handleGoogleClick = async () => {
14 | const provider = new GoogleAuthProvider();
15 | provider.setCustomParameters({ prompt: "select_account" });
16 | try {
17 | const resultsFromGoogle = await signInWithPopup(auth, provider);
18 | // console.log(resultsFromGoogle)
19 | const res = await fetch("/api/auth/google", {
20 | method: "POST",
21 | headers: { "Content-Type": "application/json" },
22 | body: JSON.stringify({
23 | name: resultsFromGoogle.user.displayName,
24 | email: resultsFromGoogle.user.email,
25 | googlePhotoUrl: resultsFromGoogle.user.photoURL,
26 | }),
27 | });
28 | const data = await res.json();
29 | console.log("respone data", data);
30 | if (res.ok) {
31 | dispatch(SignInSuccess(data));
32 | navigate("/");
33 | }
34 | } catch (e) {
35 | console.log(e);
36 | }
37 | };
38 | return (
39 |
45 |
46 | Continue with Google
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/user/userSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | currentUser: null,
5 | error: null,
6 | loading: false,
7 | };
8 |
9 | const userSlice = createSlice({
10 | name: "user",
11 | initialState,
12 | reducers: {
13 | SignInStart: (state) => {
14 | state.loading = true;
15 | state.error = null;
16 | },
17 | SignInSuccess: (state, action) => {
18 | state.currentUser = action.payload;
19 | state.loading = false;
20 | state.error = null;
21 | },
22 | SignInFailure: (state, action) => {
23 | state.loading = false;
24 | state.error = action.payload;
25 | },
26 | UpdateStart: (state) => {
27 | state.loading = true;
28 | state.error = null;
29 | },
30 | UpdateSucess: (state, action) => {
31 | state.currentUser = action.payload;
32 | state.loading = false;
33 | state.error = null;
34 | },
35 | UpdateFailure: (state, action) => {
36 | state.loading = false;
37 | state.error = action.payload;
38 | },
39 | deleteUserStart: (state) => {
40 | state.loading = true;
41 | state.error = null;
42 | },
43 | deleteUserSucess: (state) => {
44 | state.currentUser = null;
45 | state.loading = false;
46 | state.error = null;
47 | },
48 | deleteUserFailure: (state, action) => {
49 | state.loading = false;
50 | state.error = action.payload;
51 | },
52 | SignOutSucess: (state) => {
53 | state.currentUser = null;
54 | state.error = null;
55 | state.loading = false;
56 | },
57 | },
58 | });
59 |
60 | export const {
61 | SignInStart,
62 | SignInSuccess,
63 | SignInFailure,
64 | UpdateStart,
65 | UpdateSucess,
66 | UpdateFailure,
67 | deleteUserStart,
68 | deleteUserSucess,
69 | deleteUserFailure,
70 | SignOutSucess,
71 | } = userSlice.actions;
72 |
73 | export default userSlice.reducer;
74 |
--------------------------------------------------------------------------------
/frontend/src/pages/PageNotFound.jsx:
--------------------------------------------------------------------------------
1 | // src/NotFound.js
2 | import { motion } from "framer-motion";
3 |
4 | export const PageNotFound = () => {
5 | return (
6 |
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/frontend/src/pages/Home.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import CallToAction from "../components/CallToAction";
3 | import { useEffect, useState } from "react";
4 | import PostCard from "../components/PostCard";
5 |
6 | export default function Home() {
7 | const [posts, setPosts] = useState([]);
8 |
9 | useEffect(() => {
10 | const fetchPosts = async () => {
11 | const res = await fetch("/api/post/getPosts?limit=12");
12 | const data = await res.json();
13 | setPosts(data.posts);
14 | };
15 | fetchPosts();
16 | }, []);
17 | return (
18 |
19 |
20 |
Welcome to my Blog
21 |
22 | Here you'll find a variety of articles and tutorials on topics
23 | such as web development, software engineering, and programming
24 | languages.
25 |
26 |
30 | View all posts
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | {posts && posts.length > 0 && (
39 |
40 |
Recent Posts
41 |
42 | {posts.map((post) => (
43 |
44 | ))}
45 |
46 |
50 | View all posts
51 |
52 |
53 | )}
54 |
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/frontend/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { BrowserRouter, Route, Routes } from "react-router-dom";
2 | import Home from "./pages/Home";
3 | import About from "./pages/About";
4 | import SignIn from "./pages/SignIn";
5 | import SignUp from "./pages/SignUp";
6 | import Dashboard from "./pages/Dashboard";
7 | import Projects from "./pages/Projects";
8 | import Header from "./components/Header";
9 | import FooterComp from "./components/Footer";
10 | import { PrivateRoute, ProtectedRoute } from "./components/PrivateRoute";
11 | import { AdminPrivateRoute } from "./components/AdminPrivateRoute";
12 | import CreatePost from "./pages/CreatePost";
13 | import UpdatePost from "./pages/UpdatePost";
14 | import PostPage from "./pages/PostPage";
15 | import ScrollToTop from "./components/ScrollToTop";
16 | import Search from "./pages/Search";
17 | import { PageNotFound } from "./pages/PageNotFound";
18 |
19 | export default function App() {
20 | return (
21 | <>
22 |
23 |
24 |
25 |
26 |
27 | } />
28 | } />
29 | } />
30 | {/* Admin Private Routes */}
31 | }>
32 | } />
33 | } />
34 |
35 |
36 | {/* For making protected route */}
37 | }>
38 | } />
39 | } />
40 |
41 | {/* For making protected route */}
42 | }>
43 | } />
44 |
45 |
46 | } />
47 | } />
48 | } />
49 |
50 |
51 |
52 | >
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/frontend/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import { Footer } from "flowbite-react";
2 | import { Link } from "react-router-dom";
3 | import { BsFacebook, BsInstagram, BsLinkedin, BsGithub } from "react-icons/bs";
4 |
5 | export default function FooterComp() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 | Nitin's
14 |
15 | Blog
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 100 JS Project
24 |
25 |
26 | Nitin's Blogs
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Github
35 |
36 | Linkedin
37 |
38 |
39 |
40 |
41 |
42 |
43 | Privacy Policy
44 |
45 | Terms and Conditions
46 |
47 |
48 |
49 |
50 |
51 |
52 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/frontend/src/components/DashSidbar.jsx:
--------------------------------------------------------------------------------
1 | import { Sidebar } from "flowbite-react";
2 | import { Link, useLocation } from "react-router-dom";
3 | import { useEffect, useState } from "react";
4 | import {
5 | HiAnnotation,
6 | HiArrowSmRight,
7 | HiDocumentText,
8 | HiOutlineUserGroup,
9 | HiUser,
10 | } from "react-icons/hi";
11 | import { SignOutSucess } from "../redux/reducers/user/userSlice";
12 | import { useDispatch, useSelector } from "react-redux";
13 |
14 | export default function DashSidbar() {
15 | const location = useLocation();
16 | const dispatch = useDispatch();
17 | const { currentUser } = useSelector((state) => state.user);
18 | const [tab, setTab] = useState("");
19 | useEffect(() => {
20 | const urlParms = new URLSearchParams(location.search);
21 | const tabFormUrl = urlParms.get("tab");
22 | if (tabFormUrl) {
23 | setTab(tabFormUrl);
24 | }
25 | }, [location.search]);
26 | const handleSignOut = async () => {
27 | try {
28 | const res = await fetch("/api/user/signout", {
29 | method: "POST",
30 | });
31 | const data = await res.json();
32 | if (!res.ok) {
33 | console.log(data.message);
34 | } else {
35 | dispatch(SignOutSucess());
36 | }
37 | } catch (error) {
38 | console.log(error.message);
39 | }
40 | };
41 | return (
42 |
43 |
44 |
45 |
46 |
53 | Profile
54 |
55 |
56 | {currentUser.isAdmin && (
57 | <>
58 |
59 |
64 | Dashboard
65 |
66 |
67 |
68 |
73 | Post
74 |
75 |
76 |
77 |
82 | Comments
83 |
84 |
85 |
86 |
91 | Users
92 |
93 |
94 | >
95 | )}
96 |
101 | Sign Out
102 |
103 |
104 |
105 |
106 | );
107 | }
108 |
--------------------------------------------------------------------------------
/frontend/src/pages/PostPage.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Spinner } from "flowbite-react";
2 | import { useEffect, useState } from "react";
3 | import { Link, useParams } from "react-router-dom";
4 | import CallToAction from "../components/CallToAction";
5 | import CommentSection from "../components/CommentSection";
6 | import PostCard from "../components/PostCard";
7 |
8 | export default function PostPage() {
9 | const { postSlug } = useParams();
10 | const [loading, setLoading] = useState(true);
11 | const [post, setPost] = useState(null);
12 | const [recentPosts, setRecentPosts] = useState(null);
13 |
14 | useEffect(() => {
15 | const fetchPost = async () => {
16 | try {
17 | setLoading(true);
18 | const res = await fetch(`/api/post/getposts?slug=${postSlug}`);
19 | const data = await res.json();
20 | if (!res.ok) {
21 | setLoading(false);
22 | return;
23 | }
24 | if (res.ok) {
25 | setPost(data.posts[0]);
26 | setLoading(false);
27 | }
28 | } catch (error) {
29 | setLoading(false);
30 | }
31 | };
32 | if (postSlug) {
33 | fetchPost();
34 | }
35 | }, [postSlug]);
36 |
37 | useEffect(() => {
38 | try {
39 | const fetchRecentPosts = async () => {
40 | const res = await fetch(`/api/post/getposts?limit=3`);
41 | const data = await res.json();
42 | if (res.ok) {
43 | setRecentPosts(data.posts);
44 | }
45 | };
46 | fetchRecentPosts();
47 | } catch (error) {
48 | console.log(error.message);
49 | }
50 | }, []);
51 |
52 | if (loading) {
53 | return (
54 |
55 |
56 |
57 | );
58 | }
59 |
60 | return (
61 | <>
62 | {post && (
63 |
64 |
65 | {post && post.title}
66 |
67 |
71 |
72 | {post && post.category}
73 |
74 |
75 |
80 |
81 | {post && new Date(post.createdAt).toLocaleDateString()}
82 |
83 | {post && (post.content.length / 1000).toFixed(0)} mins read
84 |
85 |
86 |
90 |
91 |
92 |
93 |
94 |
95 | )}
96 |
97 |
Recent articles
98 |
99 | {recentPosts &&
100 | recentPosts.map((post) =>
)}
101 |
102 |
103 | >
104 | );
105 | }
106 |
--------------------------------------------------------------------------------
/backend/controller/auth.controller.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require("bcrypt");
2 | const jwt = require("jsonwebtoken");
3 | const USER = require("../model/users.model");
4 | const errorHandler = require("../utils/error");
5 |
6 | const signup = async (req, res, next) => {
7 | const { username, email, password } = req.body;
8 |
9 | try {
10 | // Check if any required field is missing or empty
11 | if (
12 | !username ||
13 | !email ||
14 | !password ||
15 | username === "" ||
16 | email === "" ||
17 | password === ""
18 | ) {
19 | next(errorHandler(400, "All fields are required"));
20 | }
21 |
22 | // Check if the email is already registered
23 | const existingUser = await USER.findOne({ email });
24 | if (existingUser) {
25 | next(errorHandler(400, "Email already registered"));
26 | }
27 |
28 | // Hash the password
29 | const hashedPassword = await bcrypt.hash(password, 10);
30 |
31 | // Create a new user
32 | const newUser = new USER({
33 | username,
34 | email,
35 | password: hashedPassword,
36 | });
37 |
38 | // Save the new user to the database
39 | await newUser.save();
40 |
41 | res.json({
42 | msg: "User Signup successful",
43 | });
44 | } catch (error) {
45 | next(error);
46 | }
47 | };
48 |
49 | const signin = async (req, res, next) => {
50 | const { email, password } = req.body;
51 | if (!email || !password || password === "" || email === "") {
52 | next(errorHandler(400, "All fields are required"));
53 | }
54 | try {
55 | const validUser = await USER.findOne({ email: email });
56 | if (!validUser) {
57 | return next(errorHandler(400, "invaild email or password"));
58 | }
59 | const vaildPassword = bcrypt.compareSync(password, validUser.password);
60 | if (!vaildPassword) {
61 | return next(errorHandler(400, "invaild email or password"));
62 | }
63 | const token = jwt.sign({ id: validUser._id, isAdmin: validUser.isAdmin }, process.env.JWT_SECRETKEY, {
64 | expiresIn: "30day",
65 | });
66 |
67 | // to remove the password from the response
68 | const { password: pass, ...rest } = validUser._doc;
69 |
70 | res
71 | .status(200)
72 | .cookie("access_token", token, { httpOnly: true })
73 | .json(rest);
74 | } catch (err) {
75 | next(err);
76 | }
77 | };
78 |
79 | const googleAuth = async (req, res, next) => {
80 | const { name, email, googlePhotoUrl } = req.body;
81 | try {
82 | const user = await USER.findOne({ email: email });
83 | if (user) {
84 | const token = jwt.sign({ id: user._id, isAdmin: user.isAdmin }, process.env.JWT_SECRETKEY);
85 | const { password, ...rest } = user._doc;
86 | res
87 | .status(200)
88 | .cookie("access_token", token, { httpOnly: true })
89 | .json(rest);
90 | } else {
91 | const genratedPassword =
92 | Math.random().toString(36).slice(-8) +
93 | Math.random().toString(36).slice(-8);
94 | const hashedPassword = bcrypt.hashSync(genratedPassword, 10);
95 | const newUser = new USER({
96 | username: name.toLowerCase().split(' ').join('') + Math.random().toString(9).slice(-4),
97 | email: email,
98 | password: hashedPassword,
99 | profilePicture: googlePhotoUrl
100 | });
101 | await newUser.save();
102 | const token = jwt.sign({ id: newUser._id, isAdmin: newUser.isAdmin }, process.env.JWT_SECRETKEY);
103 | const { password, ...rest } = newUser._doc;
104 | res
105 | .status(200)
106 | .cookie("access_token", token, { httpOnly: true })
107 | .json(rest);
108 | }
109 | } catch (error) {
110 | next(error);
111 | }
112 | };
113 |
114 | module.exports = { signup, signin, googleAuth };
115 |
--------------------------------------------------------------------------------
/backend/controller/post.controller.js:
--------------------------------------------------------------------------------
1 | const POST = require("../model/post.model");
2 | const errorHandler = require("../utils/error");
3 |
4 | const create = async (req, res, next) => {
5 | if (!req.user.isAdmin) {
6 | return next(
7 | errorHandler(403, "You do not have permission to create a post")
8 | );
9 | }
10 | if (!req.body.title || !req.body.content) {
11 | return next(errorHandler(400, "please provide all required fields"));
12 | }
13 | const slug = req.body.title
14 | .split(" ")
15 | .join("-")
16 | .toLowerCase()
17 | .replace(/[^a-zA-Z0-9-]/g, "");
18 | const newPost = new POST({
19 | userId: req.user.id,
20 | slug: slug,
21 | ...req.body,
22 | });
23 |
24 | try {
25 | const savePost = await newPost.save();
26 | res.status(201).json(savePost);
27 | } catch (error) {
28 | next(error);
29 | }
30 | };
31 |
32 | const getPosts = async (req, res, next) => {
33 | try {
34 | const startIndex = parseInt(req.query.startIndex) || 0;
35 | const limit = parseInt(req.query.limit) || 10;
36 | const sortDirection = req.query.order === "asc" ? 1 : -1;
37 | const posts = await POST.find({
38 | ...(req.query.userId && { userId: req.query.userId }),
39 | ...(req.query.category && { category: req.query.category }),
40 | ...(req.query.slug && { slug: req.query.slug }),
41 | ...(req.query.postId && { _id: req.query.postId }),
42 | ...(req.query.searchTerm && {
43 | $or: [
44 | { title: { $regex: req.query.searchTerm, $options: "i" } },
45 | { content: { $regex: req.query.searchTerm, $options: "i" } },
46 | ],
47 | }),
48 | })
49 | .sort({ updatedAt: sortDirection })
50 | .skip(startIndex)
51 | .limit(limit);
52 |
53 | const totalPosts = await POST.countDocuments();
54 |
55 | const now = new Date();
56 | const oneMonthAgo = new Date(
57 | now.getFullYear(),
58 | now.getMonth() - 1,
59 | now.getDate()
60 | );
61 |
62 | const lastMonthPosts = await POST.countDocuments({
63 | createdAt: { $gte: oneMonthAgo },
64 | });
65 |
66 | res.status(200).json({
67 | posts,
68 | totalPosts,
69 | lastMonthPosts,
70 | });
71 | } catch (error) {
72 | console.log("error :- ", error.message);
73 | next(error);
74 | }
75 | };
76 |
77 | const updatePost = async (req, res, next) => {
78 | if (!req.user.isAdmin) {
79 | return next(
80 | errorHandler(403, "You do not have permission to create a post")
81 | );
82 | }
83 | if (!req.body.title || !req.body.content) {
84 | return next(errorHandler(400, "please provide all required fields"));
85 | }
86 | try {
87 | const updatedPost = await POST.findByIdAndUpdate(
88 | req.params.postId,
89 | {
90 | $set: {
91 | title: req.body.title,
92 | content: req.body.content,
93 | category: req.body.category,
94 | image: req.body.image,
95 | slug: req.body.title
96 | .split(" ")
97 | .join("-")
98 | .toLowerCase()
99 | .replace(/[^a-zA-Z0-9-]/g, ""),
100 | },
101 | },
102 | { new: true }
103 | );
104 |
105 | res.status(200).json(updatedPost);
106 | } catch (error) {
107 | next(error);
108 | }
109 | };
110 |
111 | const deletePost = async (req, res , next) => {
112 | if(!req.user.isAdmin || req.user.id !== req.params.userId){
113 | return next(errorHandler(403, "You do not have permission to delete a post"));
114 | }
115 | try {
116 | await POST.findByIdAndDelete(req.params.postId);
117 | res.status(200).json('Post has been deleted successfully');
118 | } catch (error) {
119 | next(error);
120 | }
121 | }
122 |
123 | module.exports = { create, getPosts, deletePost , updatePost};
124 |
--------------------------------------------------------------------------------
/backend/controller/comment.controller.js:
--------------------------------------------------------------------------------
1 | const COMMENT = require("../model/comment.model");
2 | const errorHandler = require("../utils/error");
3 |
4 | const createComment = async (req, res, next) => {
5 | try {
6 | const { comment, postId, userId } = req.body;
7 | if (userId !== req.user.id) {
8 | return next(
9 | errorHandler(403, " You are not allowed to create a comment")
10 | );
11 | }
12 |
13 | const newComment = new COMMENT({
14 | content: comment,
15 | postId,
16 | userId,
17 | });
18 |
19 | await newComment.save();
20 | res.status(201).json({ newComment });
21 | } catch (error) {
22 | next(error);
23 | }
24 | };
25 |
26 | const getPostsComment = async (req, res, next) => {
27 | try {
28 | const comments = await COMMENT.find({ postId: req.params.postId }).sort({
29 | createAt: -1,
30 | });
31 | res.status(200).json(comments);
32 | } catch (error) {
33 | next(error);
34 | }
35 | };
36 |
37 | const likeComment = async (req, res, next) => {
38 | try {
39 | const comment = await COMMENT.findById(req.params.commentId);
40 | if (!comment) {
41 | return next(errorHandler(404, "Comment not found"));
42 | }
43 | const userIndex = await comment.likes.indexOf(req.user.id);
44 | if (userIndex === -1) {
45 | comment.numberofLikes += 1;
46 | comment.likes.push(req.user.id);
47 | } else {
48 | comment.numberofLikes -= 1;
49 | comment.likes.splice(userIndex, 1);
50 | }
51 | await comment.save();
52 | res.status(200).json(comment);
53 | } catch (error) {
54 | next(error);
55 | }
56 | };
57 |
58 | const editComment = async (req, res, next) => {
59 | try {
60 | const comment = await COMMENT.findById(req.params.commentId);
61 | if (!comment) {
62 | return next(errorHandler(404, "Comment not found"));
63 | }
64 | if (comment.userId !== req.user.id && !req.user.isAdmin) {
65 | return next(
66 | errorHandler(403, "You are not allowed to edit this comment")
67 | );
68 | }
69 | const editedComment = await COMMENT.findByIdAndUpdate(
70 | req.params.commentId,
71 | {
72 | content: req.body.content,
73 | },
74 | { new: true }
75 | );
76 | res.status(200).json(editedComment);
77 | } catch (error) {
78 | next(error);
79 | }
80 | };
81 |
82 | const deleteComment = async (req, res, next) => {
83 | try {
84 | const comment = await COMMENT.findById(req.params.commentId);
85 | if (!comment) {
86 | return next(errorHandler(404, "Comment not found"));
87 | }
88 | if (comment.userId !== req.user.id && !req.user.isAdmin) {
89 | return next(
90 | errorHandler(403, "You are not allowed to edit this comment")
91 | );
92 | }
93 | await COMMENT.findByIdAndDelete(req.params.commentId);
94 | res.status(200).json("Comment has been deleted");
95 | } catch (error) {
96 | next(error);
97 | }
98 | };
99 |
100 | const getComments = async (req, res, next) => {
101 | if (!req.user.isAdmin) {
102 | return next(errorHandler(403, " You are not allowed to get all comments"));
103 | }
104 | try {
105 | const startIndex = parseInt(req.query.startIndex) || 0;
106 | const limit = parseInt(req.query.limit) || 9;
107 | const sortDirection = req.query.sort === "'desc" ? -1 : 1;
108 | const comments = await COMMENT.find()
109 | .sort({ createdAt: sortDirection })
110 | .skip(startIndex)
111 | .limit(limit);
112 |
113 | const totalComments = await COMMENT.countDocuments();
114 | const now = new Date();
115 |
116 | const oneMonth = new Date(
117 | now.getFullYear(),
118 | now.getMonth() - 1,
119 | now.getDate()
120 | );
121 |
122 | const lastMonthComments = await COMMENT.countDocuments({
123 | createdAt: { $gte: oneMonth },
124 | });
125 |
126 | res.status(200).json({ comments, totalComments, lastMonthComments });
127 | } catch (error) {
128 | next(error);
129 | }
130 | };
131 |
132 | module.exports = {
133 | createComment,
134 | getPostsComment,
135 | likeComment,
136 | editComment,
137 | deleteComment,
138 | getComments,
139 | };
140 |
--------------------------------------------------------------------------------
/frontend/src/pages/SignIn.jsx:
--------------------------------------------------------------------------------
1 | import { Alert, Button, Label, Spinner, TextInput } from "flowbite-react";
2 | import { useState } from "react";
3 | import { Link, useNavigate } from "react-router-dom";
4 | import { useDispatch, useSelector } from "react-redux";
5 | import {
6 | SignInStart,
7 | SignInSuccess,
8 | SignInFailure,
9 | } from "../redux/reducers/user/userSlice";
10 | import OAuth from "../components/OAuth";
11 |
12 | export default function SignIn() {
13 | const navigate = useNavigate();
14 | const dispatch = useDispatch();
15 | const { loading, error: errorMessage } = useSelector((state) => state.user);
16 | const [formData, setFormData] = useState({});
17 | const handleChange = (event) => {
18 | setFormData({
19 | ...formData,
20 | [event.target.id]: event.target.value.trim(),
21 | });
22 | };
23 | const handleSubmit = async (event) => {
24 | event.preventDefault();
25 | if (!formData.email || !formData.password) {
26 | return dispatch(SignInFailure("please fill all the required fields"));
27 | }
28 | try {
29 | dispatch(SignInStart());
30 | const res = await fetch("/api/auth/signin", {
31 | method: "POST",
32 | headers: { "Content-Type": "application/json" },
33 | body: JSON.stringify(formData),
34 | });
35 | const data = await res.json();
36 | if (data.success === false) {
37 | dispatch(SignInFailure(data.message));
38 | }
39 | if (res.ok) {
40 | dispatch(SignInSuccess(data));
41 | navigate("/");
42 | }
43 | } catch (err) {
44 | dispatch(SignInFailure(err.message));
45 | }
46 | };
47 | return (
48 |
49 |
50 | {/* left Side Conatnier */}
51 |
52 |
53 |
54 | Nitin's
55 |
56 | Blog
57 |
58 |
59 | Welcome to our blog community! Signing up is your first step towards
60 | unlocking a world of knowledge, creativity, and connection.
61 |
62 |
63 | {/* Right Side Conatnier */}
64 |
65 |
105 |
106 | Don't have an account ?
107 |
108 | Sign Up
109 |
110 |
111 | {errorMessage && (
112 |
113 | {errorMessage}
114 |
115 | )}
116 |
117 |
118 |
119 | );
120 | }
121 |
--------------------------------------------------------------------------------
/backend/controller/user.controller.js:
--------------------------------------------------------------------------------
1 | const errorHandler = require("../utils/error");
2 | const bcrypt = require("bcrypt");
3 | const USER = require("../model/users.model");
4 |
5 | const test = async (req, res) => {
6 | res.json({
7 | message: "test message",
8 | });
9 | };
10 |
11 | const updateUser = async (req, res, next) => {
12 | if (req.user.id !== req.params.userId) {
13 | return next(
14 | errorHandler(403, "You are not allowed to allow to update this user")
15 | );
16 | }
17 | if (req.body.password) {
18 | if (req.body.password.length < 6) {
19 | next(errorHandler(403, "Password must be at least 6 characters"));
20 | }
21 | req.body.password = bcrypt.hashSync(req.body.password, 10);
22 | }
23 | if (req.body.username) {
24 | if (req.body.username.length < 5 || req.body.username.length > 20) {
25 | return next(
26 | errorHandler(403, "Username must be in between 5 to 20 characters")
27 | );
28 | }
29 | if (req.body.username.includes(" ")) {
30 | return next(errorHandler(403, "Username cannot contain spaces"));
31 | }
32 | if (!req.body.username === req.body.username.toLowerCase()) {
33 | return next(errorHandler(403, "Username should be in lowercase"));
34 | }
35 | if (!req.body.username.match(/^[a-zA-Z0-9]+$/)) {
36 | return next(
37 | errorHandler(403, "Username only contains letters and numbers")
38 | );
39 | }
40 | }
41 | try {
42 | const updateUser = await USER.findOneAndUpdate(
43 | { _id: req.params.userId },
44 | {
45 | $set: {
46 | username: req.body.username,
47 | email: req.body.email,
48 | password: req.body.password,
49 | profilePicture: req.body.profilePicture,
50 | },
51 | },
52 | { new: true }
53 | );
54 | const { password, ...rest } = updateUser._doc;
55 | res.status(200).json(rest);
56 | } catch (error) {
57 | next(error);
58 | }
59 | };
60 |
61 | const deleteUser = async (req, res, next) => {
62 | if (!req.user.isAdmin && req.user.id !== req.params.userId) {
63 | return next(
64 | errorHandler(403, "You are not allowed to allow to delete this Account")
65 | );
66 | }
67 | try {
68 | await USER.findByIdAndDelete(req.params.userId);
69 | res.status(200).json("User has been deleted Successfully");
70 | } catch (error) {
71 | next(error);
72 | }
73 | };
74 |
75 | const signOut = async (req, res) => {
76 | try {
77 | res
78 | .clearCookie("access_token")
79 | .status(200)
80 | .json("User has been signed out Successfully");
81 | } catch (error) {
82 | next(error);
83 | }
84 | };
85 |
86 | const getUsers = async (req, res, next) => {
87 | if (!req.user.isAdmin) {
88 | return next(errorHandler(403, "You are not allowed to see all users"));
89 | }
90 | try {
91 | const startIndex = parseInt(req.query.startIndex) || 0;
92 | const limit = parseInt(req.query.limit) || 10;
93 | const sortDirection = req.query.sortDirection === "asc" ? 1 : -1;
94 |
95 | const users = await USER.find()
96 | .skip(startIndex)
97 | .limit(limit)
98 | .sort({ username: sortDirection });
99 |
100 | const userWithPassword = users.map((user) => {
101 | const { password, ...rest } = user._doc;
102 | return rest;
103 | });
104 |
105 | const totalUsers = await USER.countDocuments();
106 |
107 | const now = new Date();
108 |
109 | const oneMonthAgo = new Date(
110 | now.getFullYear(),
111 | now.getMonth() - 1,
112 | now.getDate()
113 | );
114 |
115 | const lastMonthUsers = await USER.countDocuments({
116 | createdAt: { $gte: oneMonthAgo },
117 | });
118 |
119 | res.status(200).json({
120 | users: userWithPassword,
121 | totalUsers,
122 | lastMonthUsers: lastMonthUsers,
123 | });
124 | } catch (error) {
125 | next(error);
126 | }
127 | };
128 |
129 | const getUser = async (req, res, next) => {
130 | try {
131 | const user = await USER.findById(req.params.userId);
132 | const { password, ...rest } = user._doc;
133 | if (!user) {
134 | return next(errorHandler(404, "User not found"));
135 | }
136 | res.status(200).json(rest);
137 | } catch (error) {
138 | next(error);
139 | }
140 | };
141 |
142 | module.exports = { test, updateUser, deleteUser, signOut, getUsers, getUser };
143 |
--------------------------------------------------------------------------------
/frontend/src/pages/SignUp.jsx:
--------------------------------------------------------------------------------
1 | import { Alert, Button, Label, Spinner, TextInput } from "flowbite-react";
2 | import { useState } from "react";
3 | import { Link, useNavigate } from "react-router-dom";
4 | import OAuth from "../components/OAuth";
5 |
6 | export default function SignUp() {
7 | const navigate = useNavigate();
8 | const [formData, setFormData] = useState({});
9 | const [errorMessage, setErrorMessage] = useState(null);
10 | const [loading, setLoading] = useState(false);
11 | const handleChange = (event) => {
12 | setFormData({
13 | ...formData,
14 | [event.target.id]: event.target.value.trim(),
15 | });
16 | };
17 | const handleSubmit = async (event) => {
18 | event.preventDefault();
19 | if (!formData.username || !formData.email || !formData.password) {
20 | return setErrorMessage("please fill all the required fields");
21 | }
22 | try {
23 | setLoading(true);
24 | setErrorMessage(null);
25 | const res = await fetch("/api/auth/signup", {
26 | method: "POST",
27 | headers: { "Content-Type": "application/json" },
28 | body: JSON.stringify(formData),
29 | });
30 | const data = await res.json();
31 | if (data.success === false) {
32 | setLoading(false);
33 | return setErrorMessage(data.message);
34 | }
35 | if (res.ok) {
36 | navigate("/sign-in");
37 | }
38 | setLoading(false);
39 | } catch (err) {
40 | setErrorMessage(err.message);
41 | setLoading(false);
42 | }
43 | };
44 | return (
45 |
46 |
47 | {/* left Side Conatnier */}
48 |
49 |
50 |
51 | Nitin's
52 |
53 | Blog
54 |
55 |
56 | Welcome to our blog community! Signing up is your first step towards
57 | unlocking a world of knowledge, creativity, and connection.
58 |
59 |
60 | {/* Right Side Conatnier */}
61 |
62 |
111 |
112 | have an account ?
113 |
114 | Sign In
115 |
116 |
117 | {errorMessage && (
118 |
119 | {errorMessage}
120 |
121 | )}
122 |
123 |
124 |
125 | );
126 | }
127 |
--------------------------------------------------------------------------------
/frontend/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import { Avatar, Button, Dropdown, Navbar, TextInput } from "flowbite-react";
2 | import { Link, useLocation, useNavigate } from "react-router-dom";
3 | import { AiOutlineSearch } from "react-icons/ai";
4 | import { FaMoon, FaSun } from "react-icons/fa";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { toogletheme } from "../redux/reducers/theme/themeSlice";
7 | import { SignOutSucess } from "../redux/reducers/user/userSlice";
8 | import { useEffect, useState } from "react";
9 |
10 | export default function Header() {
11 | const path = useLocation().pathname;
12 | const location = useLocation();
13 | const navigate = useNavigate();
14 | const dispatch = useDispatch();
15 | const { theme } = useSelector((state) => state.theme);
16 | const { currentUser } = useSelector((state) => state.user);
17 | const [searchTerm, setSearchTerm] = useState("");
18 |
19 | useEffect(() => {
20 | const urlParams = new URLSearchParams(location.search);
21 | const searchTermFromUrl = urlParams.get("searchTerm");
22 | if (searchTermFromUrl) {
23 | setSearchTerm(searchTermFromUrl);
24 | }
25 | }, [location.search]);
26 |
27 | const handleSignOut = async () => {
28 | try {
29 | const res = await fetch("/api/user/signout", {
30 | method: "POST",
31 | });
32 | const data = await res.json();
33 | if (!res.ok) {
34 | console.log(data.message);
35 | } else {
36 | dispatch(SignOutSucess());
37 | }
38 | } catch (error) {
39 | console.log(error.message);
40 | }
41 | };
42 |
43 | const handleSubmit = (e) => {
44 | e.preventDefault();
45 | const urlParams = new URLSearchParams(location.search);
46 | urlParams.set("searchTerm", searchTerm);
47 | const searchQuery = urlParams.toString();
48 | navigate(`/search?${searchQuery}`);
49 | };
50 |
51 | return (
52 |
53 |
57 |
58 | Nitin's
59 |
60 | Blog
61 |
62 |
72 |
73 |
74 |
75 |
76 | dispatch(toogletheme())}
81 | >
82 | {theme === "light" ? : }
83 |
84 | {currentUser ? (
85 |
94 | }
95 | >
96 |
97 | @{currentUser.username}
98 |
99 | {currentUser.email}
100 |
101 |
102 |
103 | Profile
104 |
105 |
106 | Sign Out
107 |
108 | ) : (
109 |
110 |
111 | SignIn
112 |
113 |
114 | )}
115 |
116 |
117 |
118 |
119 | Home
120 |
121 |
122 | About
123 |
124 |
125 | Projects
126 |
127 |
128 |
129 | );
130 | }
131 |
--------------------------------------------------------------------------------
/frontend/src/components/Comment.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import { useEffect, useState } from "react";
3 | import moment from "moment";
4 | import { FaThumbsUp } from "react-icons/fa";
5 | import { useSelector } from "react-redux";
6 | import { Button, Textarea } from "flowbite-react";
7 |
8 | export default function Comment({ comment, onLike, onEdit, onDelte }) {
9 | const [user, setuser] = useState({});
10 | const { currentUser } = useSelector((state) => state.user);
11 | const [editedContent, setEditedContent] = useState(comment.content);
12 | const [isEditing, setIsEditing] = useState(false);
13 |
14 | useEffect(() => {
15 | const getUsers = async () => {
16 | try {
17 | const res = await fetch(`/api/user/${comment.userId}`);
18 | const data = await res.json();
19 | if (res.ok) {
20 | setuser(data);
21 | }
22 | } catch (error) {
23 | console.log(error);
24 | }
25 | };
26 | getUsers();
27 | }, [comment]);
28 |
29 | const handleEdit = async () => {
30 | setIsEditing(true);
31 | setEditedContent(comment.content);
32 | };
33 |
34 | const handleSave = async () => {
35 | try {
36 | const res = await fetch(`/api/comment/editComment/${comment._id}`, {
37 | method: "PUT",
38 | headers: {
39 | "Content-Type": "application/json",
40 | },
41 | body: JSON.stringify({ content: editedContent }),
42 | });
43 |
44 | if (res.ok) {
45 | onEdit(comment, editedContent);
46 | setIsEditing(false);
47 | }
48 | } catch (error) {
49 | console.log(error);
50 | }
51 | };
52 |
53 | return (
54 |
55 |
56 |
61 |
62 |
63 |
64 |
65 | {user ? `@ ${user.username}` : `anonymous user`}
66 |
67 |
68 | {moment(comment.createdAt).fromNow()}
69 |
70 |
71 | {isEditing ? (
72 | <>
73 |
143 |
144 | );
145 | }
146 |
147 | Comment.propTypes = {
148 | comment: PropTypes.shape({
149 | _id: PropTypes.string,
150 | content: PropTypes.string,
151 | userId: PropTypes.string,
152 | postId: PropTypes.string,
153 | createdAt: PropTypes.string,
154 | updatedAt: PropTypes.string,
155 | likes: PropTypes.array,
156 | numberofLikes: PropTypes.number,
157 | }),
158 | onLike: PropTypes.func,
159 | onEdit: PropTypes.func,
160 | onDelte: PropTypes.func,
161 | };
162 |
--------------------------------------------------------------------------------
/frontend/src/components/DashComments.jsx:
--------------------------------------------------------------------------------
1 | import { Modal, Table, Button } from "flowbite-react";
2 | import { useEffect, useState } from "react";
3 | import { useSelector } from "react-redux";
4 | import { HiOutlineExclamationCircle } from "react-icons/hi";
5 |
6 | export default function DashComments() {
7 | const { currentUser } = useSelector((state) => state.user);
8 | const [comments, setComments] = useState([]);
9 | const [showMore, setShowMore] = useState(true);
10 | const [showModal, setShowModal] = useState(false);
11 | const [commentIdToDelete, setCommentIdToDelete] = useState("");
12 | useEffect(() => {
13 | const fetchComments = async () => {
14 | try {
15 | const res = await fetch(`/api/comment/getcomments`);
16 | const data = await res.json();
17 | if (res.ok) {
18 | setComments(data.comments);
19 | if (data.comments.length < 9) {
20 | setShowMore(false);
21 | }
22 | }
23 | } catch (error) {
24 | console.log(error.message);
25 | }
26 | };
27 | if (currentUser.isAdmin) {
28 | fetchComments();
29 | }
30 | }, [currentUser._id, currentUser.isAdmin]);
31 |
32 | const handleShowMore = async () => {
33 | const startIndex = comments.length;
34 | try {
35 | const res = await fetch(
36 | `/api/comment/getcomments?startIndex=${startIndex}`
37 | );
38 | const data = await res.json();
39 | if (res.ok) {
40 | setComments((prev) => [...prev, ...data.comments]);
41 | if (data.comments.length < 9) {
42 | setShowMore(false);
43 | }
44 | }
45 | } catch (error) {
46 | console.log(error.message);
47 | }
48 | };
49 |
50 | const handleDeleteComment = async () => {
51 | setShowModal(false);
52 | try {
53 | const res = await fetch(
54 | `/api/comment/deleteComment/${commentIdToDelete}`,
55 | {
56 | method: "DELETE",
57 | }
58 | );
59 | const data = await res.json();
60 | if (res.ok) {
61 | setComments((prev) =>
62 | prev.filter((comment) => comment._id !== commentIdToDelete)
63 | );
64 | setShowModal(false);
65 | } else {
66 | console.log(data.message);
67 | }
68 | } catch (error) {
69 | console.log(error.message);
70 | }
71 | };
72 |
73 | return (
74 |
75 | {currentUser.isAdmin && comments.length > 0 ? (
76 | <>
77 |
78 |
79 | Date updated
80 | Comment content
81 | Number of likes
82 | PostId
83 | UserId
84 | Delete
85 |
86 | {comments.map((comment) => (
87 |
88 |
89 |
90 | {new Date(comment.updatedAt).toLocaleDateString()}
91 |
92 | {comment.content}
93 | {comment.numberofLikes}
94 | {comment.postId}
95 | {comment.userId}
96 |
97 | {
99 | setShowModal(true);
100 | setCommentIdToDelete(comment._id);
101 | }}
102 | className="font-medium text-red-500 hover:underline cursor-pointer"
103 | >
104 | Delete
105 |
106 |
107 |
108 |
109 | ))}
110 |
111 | {showMore && (
112 |
116 | Show more
117 |
118 | )}
119 | >
120 | ) : (
121 |
You have no comments yet!
122 | )}
123 |
setShowModal(false)}
126 | popup
127 | size="md"
128 | >
129 |
130 |
131 |
132 |
133 |
134 | Are you sure you want to delete this comment?
135 |
136 |
137 |
138 | Yes, I'm sure
139 |
140 | setShowModal(false)}>
141 | No, cancel
142 |
143 |
144 |
145 |
146 |
147 |
148 | );
149 | }
150 |
--------------------------------------------------------------------------------
/frontend/src/pages/CreatePost.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { Alert, Button, FileInput, Select, TextInput } from "flowbite-react";
3 | import ReactQuill from "react-quill";
4 | import "react-quill/dist/quill.snow.css";
5 | import {
6 | getDownloadURL,
7 | getStorage,
8 | ref,
9 | uploadBytesResumable,
10 | } from "firebase/storage";
11 | import { app } from "../firebase";
12 | import { CircularProgressbar } from "react-circular-progressbar";
13 | import "react-circular-progressbar/dist/styles.css";
14 | import { useNavigate } from "react-router-dom";
15 |
16 | export default function CreatePost() {
17 | const [file, setFile] = useState(null);
18 | const [imageUploadProgress, setImageUploadProgress] = useState(null);
19 | const [imageUploadError, setImageUploadError] = useState(null);
20 | const [formData, setFormData] = useState({});
21 | const [publishError, setPublishError] = useState(null);
22 | const navigate = useNavigate();
23 |
24 | const handleUploadImage = async () => {
25 | try {
26 | if (!file) {
27 | setImageUploadError("Please select an Image file");
28 | return;
29 | }
30 | setImageUploadError(null);
31 | const storage = getStorage(app);
32 | const fileName = new Date().getTime() + "-" + file.name;
33 | const storageRef = ref(storage, fileName);
34 | const uploadTask = uploadBytesResumable(storageRef, file);
35 | uploadTask.on(
36 | "state_changed",
37 | (snapshot) => {
38 | const progress =
39 | (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
40 | setImageUploadProgress(progress.toFixed(0));
41 | },
42 | (error) => {
43 | setImageUploadError(error.message);
44 | setImageUploadProgress(null);
45 | },
46 | () => {
47 | getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
48 | setImageUploadProgress(null);
49 | setImageUploadError(null);
50 | setFormData({
51 | ...formData,
52 | image: downloadURL,
53 | });
54 | });
55 | }
56 | );
57 | } catch (error) {
58 | setImageUploadError(error.message);
59 | setImageUploadProgress(null);
60 | console.log(error);
61 | }
62 | };
63 |
64 | const handleSubmit = async (e) => {
65 | e.preventDefault();
66 | try {
67 | const res = await fetch("/api/post/create", {
68 | method: "POST",
69 | headers: {
70 | "Content-Type": "application/json",
71 | },
72 | body: JSON.stringify(formData),
73 | });
74 | const data = await res.json();
75 | if (!res.ok) {
76 | setPublishError(data.message);
77 | return;
78 | }
79 | if (res.ok) {
80 | setPublishError(null);
81 | console.log("Naviagte");
82 | navigate(`/post/${data.slug}`);
83 | }
84 | } catch (error) {
85 | setPublishError("Somthing went wrong, please try again");
86 | }
87 | };
88 |
89 | return (
90 |
91 |
92 | Create a post{" "}
93 |
94 |
170 |
171 | );
172 | }
173 |
--------------------------------------------------------------------------------
/frontend/src/components/DashUsers.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Modal, Table } from "flowbite-react";
2 | import { useEffect, useState } from "react";
3 | import { HiOutlineExclamationCircle } from "react-icons/hi";
4 | import { useSelector } from "react-redux";
5 | import { FaCheck, FaTimes } from "react-icons/fa";
6 |
7 | export default function DashUsers() {
8 | const { currentUser } = useSelector((state) => state.user);
9 | const [users, setUsers] = useState([]);
10 | const [showMore, setShowMore] = useState(true);
11 | const [showModal, setShowModal] = useState(false);
12 | const [userIdToDelete, setUserIdToDelete] = useState("");
13 | useEffect(() => {
14 | const fetchUsers = async () => {
15 | try {
16 | const res = await fetch(`/api/user/getusers`);
17 | const data = await res.json();
18 | if (res.ok) {
19 | setUsers(data.users);
20 | if (data.users.length < 9) {
21 | setShowMore(false);
22 | }
23 | }
24 | } catch (error) {
25 | console.log(error);
26 | }
27 | };
28 | if (currentUser.isAdmin) {
29 | fetchUsers();
30 | }
31 | }, [currentUser._id, currentUser.isAdmin]);
32 |
33 | const handleShowMore = async () => {
34 | const startIndex = users.length;
35 | try {
36 | const res = await fetch(`/api/user/getusers?startIndex=${startIndex}`);
37 | const data = await res.json();
38 | if (res.ok) {
39 | setUsers([...users, ...data.users]);
40 | if (data.users.length < 9) {
41 | setShowMore(false);
42 | }
43 | }
44 | } catch (error) {
45 | console.log(error.message);
46 | }
47 | };
48 |
49 | const handleDeleteUser = async () => {
50 | try {
51 | const res = await fetch(`/api/user/delete/${userIdToDelete}`, {
52 | method: "DELETE",
53 | });
54 | const data = await res.json();
55 | if (res.ok) {
56 | console.log(data);
57 | setUsers(users.filter((user) => user._id !== userIdToDelete));
58 | setShowModal(false);
59 | } else {
60 | console.log(data.message);
61 | }
62 | } catch (error) {
63 | console.log(error.message);
64 | }
65 | };
66 |
67 | return (
68 |
69 | {currentUser.isAdmin && users.length > 0 ? (
70 | <>
71 |
72 |
73 | Date Created
74 | User Image
75 | User Name
76 | Email
77 | Admin
78 | Delete
79 |
80 | {users.map((user, key) => {
81 | return (
82 |
83 |
84 |
85 | {new Date(user.createdAt).toLocaleDateString()}
86 |
87 |
88 |
94 |
95 |
96 | {user.username.charAt(0).toUpperCase() +
97 | user.username.slice(1)}
98 |
99 | {user.email}
100 |
101 | {user.isAdmin ? (
102 |
103 | ) : (
104 |
105 | )}
106 |
107 |
108 | {
110 | setShowModal(true);
111 | setUserIdToDelete(user._id);
112 | }}
113 | className="font-medium text-red-500 hover:underline cursor-pointer"
114 | >
115 | Delete
116 |
117 |
118 |
119 |
120 | );
121 | })}
122 |
123 | {showMore && (
124 |
128 | show more
129 |
130 | )}
131 | >
132 | ) : (
133 |
You Have No Users Yet !
134 | )}
135 |
setShowModal(false)}
138 | popup
139 | size={"md"}
140 | >
141 |
142 |
143 |
144 |
145 |
146 | Are You Sure. You want to delete this User ?
147 |
148 |
149 |
150 | Yes I am Sure
151 |
152 | setShowModal(false)}>
153 | No, Cancel
154 |
155 |
156 |
157 |
158 |
159 |
160 | );
161 | }
162 |
--------------------------------------------------------------------------------
/frontend/src/pages/Search.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Select, TextInput } from "flowbite-react";
2 | import { useEffect, useState } from "react";
3 | import { useLocation, useNavigate } from "react-router-dom";
4 | import PostCard from "../components/PostCard";
5 |
6 | export default function Search() {
7 | const [sidebarData, setSidebarData] = useState({
8 | searchTerm: "",
9 | sort: "desc",
10 | category: "uncategorized",
11 | });
12 |
13 | console.log(sidebarData);
14 | const [posts, setPosts] = useState([]);
15 | const [loading, setLoading] = useState(false);
16 | const [showMore, setShowMore] = useState(false);
17 |
18 | const location = useLocation();
19 |
20 | const navigate = useNavigate();
21 |
22 | useEffect(() => {
23 | const urlParams = new URLSearchParams(location.search);
24 | const searchTermFromUrl = urlParams.get("searchTerm");
25 | const sortFromUrl = urlParams.get("sort");
26 | const categoryFromUrl = urlParams.get("category");
27 | if (searchTermFromUrl || sortFromUrl || categoryFromUrl) {
28 | setSidebarData({
29 | ...sidebarData,
30 | searchTerm: searchTermFromUrl,
31 | sort: sortFromUrl,
32 | category: categoryFromUrl,
33 | });
34 | }
35 |
36 | const fetchPosts = async () => {
37 | setLoading(true);
38 | const searchQuery = urlParams.toString();
39 | const res = await fetch(`/api/post/getposts?${searchQuery}`);
40 | if (!res.ok) {
41 | setLoading(false);
42 | return;
43 | }
44 | if (res.ok) {
45 | const data = await res.json();
46 | setPosts(data.posts);
47 | setLoading(false);
48 | if (data.posts.length === 9) {
49 | setShowMore(true);
50 | } else {
51 | setShowMore(false);
52 | }
53 | }
54 | };
55 | fetchPosts();
56 | }, [location.search]);
57 |
58 | const handleChange = (e) => {
59 | if (e.target.id === "searchTerm") {
60 | setSidebarData({ ...sidebarData, searchTerm: e.target.value });
61 | }
62 | if (e.target.id === "sort") {
63 | const order = e.target.value || "desc";
64 | setSidebarData({ ...sidebarData, sort: order });
65 | }
66 | if (e.target.id === "category") {
67 | const category = e.target.value || "uncategorized";
68 | setSidebarData({ ...sidebarData, category });
69 | }
70 | };
71 |
72 | const handleSubmit = (e) => {
73 | e.preventDefault();
74 | const urlParams = new URLSearchParams(location.search);
75 | urlParams.set("searchTerm", sidebarData.searchTerm);
76 | urlParams.set("sort", sidebarData.sort);
77 | urlParams.set("category", sidebarData.category);
78 | const searchQuery = urlParams.toString();
79 | navigate(`/search?${searchQuery}`);
80 | };
81 |
82 | const handleShowMore = async () => {
83 | const numberOfPosts = posts.length;
84 | const startIndex = numberOfPosts;
85 | const urlParams = new URLSearchParams(location.search);
86 | urlParams.set("startIndex", startIndex);
87 | const searchQuery = urlParams.toString();
88 | const res = await fetch(`/api/post/getposts?${searchQuery}`);
89 | if (!res.ok) {
90 | return;
91 | }
92 | if (res.ok) {
93 | const data = await res.json();
94 | setPosts([...posts, ...data.posts]);
95 | if (data.posts.length === 9) {
96 | setShowMore(true);
97 | } else {
98 | setShowMore(false);
99 | }
100 | }
101 | };
102 |
103 | const handleClearFilters = () => {
104 | setSidebarData({
105 | searchTerm: "",
106 | sort: "desc",
107 | category: "uncategorized",
108 | });
109 | navigate("/search");
110 | };
111 |
112 | return (
113 |
114 |
156 |
157 |
158 | Posts results:
159 |
160 |
161 | {!loading && posts.length === 0 && (
162 |
No posts found.
163 | )}
164 | {loading &&
Loading...
}
165 | {!loading &&
166 | posts &&
167 | posts.map((post) =>
)}
168 | {showMore && (
169 |
173 | Show More
174 |
175 | )}
176 |
177 |
178 |
179 | );
180 | }
181 |
--------------------------------------------------------------------------------
/frontend/src/components/DashPosts.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Modal, Table } from "flowbite-react";
2 | import { useEffect, useState } from "react";
3 | import { HiOutlineExclamationCircle } from "react-icons/hi";
4 | import { useSelector } from "react-redux";
5 | import { Link } from "react-router-dom";
6 |
7 | export default function DashPosts() {
8 | const { currentUser } = useSelector((state) => state.user);
9 | const [userPosts, setUserPosts] = useState([]);
10 | const [showMore, setShowMore] = useState(true);
11 | const [showModal, setShowModal] = useState(false);
12 | const [postIdToDelete, setPostIdToDelete] = useState("");
13 | useEffect(() => {
14 | const fetchPosts = async () => {
15 | try {
16 | const res = await fetch(`/api/post/getposts?userId=${currentUser._id}`);
17 | const data = await res.json();
18 | if (res.ok) {
19 | setUserPosts(data.posts);
20 | if (data.posts.length < 9) {
21 | setShowMore(false);
22 | }
23 | }
24 | } catch (error) {
25 | console.log(error.message);
26 | }
27 | };
28 | if (currentUser.isAdmin) {
29 | fetchPosts();
30 | }
31 | }, [currentUser._id, currentUser.isAdmin]);
32 |
33 | const handleShowMore = async () => {
34 | const startIndex = userPosts.length;
35 | try {
36 | const res = await fetch(
37 | `/api/post/getposts?userId=${currentUser._id}&startIndex=${startIndex}`
38 | );
39 | const data = await res.json();
40 | if (res.ok) {
41 | setUserPosts([...userPosts, ...data.posts]);
42 | if (data.posts.length < 9) {
43 | setShowMore(false);
44 | }
45 | }
46 | } catch (error) {
47 | console.log(error.message);
48 | }
49 | };
50 |
51 | const handleDeletePost = async () => {
52 | setShowModal(false);
53 | try {
54 | const res = await fetch(
55 | `/api/post/deletepost/${postIdToDelete}/${currentUser._id}`,
56 | {
57 | method: "DELETE",
58 | }
59 | );
60 | const data = await res.json();
61 | if (!res.ok) {
62 | console.log(data.message);
63 | } else {
64 | setUserPosts((prev) =>
65 | prev.filter((post) => post._id !== postIdToDelete)
66 | );
67 | }
68 | } catch (error) {
69 | console.log(error.message);
70 | }
71 | };
72 |
73 | return (
74 |
75 | {currentUser.isAdmin && userPosts.length > 0 ? (
76 | <>
77 |
78 |
79 | Date Updated
80 | Post Image
81 | Post Title
82 | Category
83 | Delete
84 |
85 | Edit
86 |
87 |
88 | {userPosts.map((post, key) => {
89 | return (
90 |
91 |
92 |
93 | {new Date(post.updatedAt).toLocaleDateString()}
94 |
95 |
96 |
97 |
103 |
104 |
105 |
106 |
110 | {post.title}
111 |
112 |
113 | {post.category}
114 |
115 | {
117 | setShowModal(true);
118 | setPostIdToDelete(post._id);
119 | }}
120 | className="font-medium text-red-500 hover:underline cursor-pointer"
121 | >
122 | Delete
123 |
124 |
125 |
126 |
130 | Edit
131 |
132 |
133 |
134 |
135 | );
136 | })}
137 |
138 | {showMore && (
139 |
143 | show more
144 |
145 | )}
146 | >
147 | ) : (
148 |
You Have No Post Yet !
149 | )}
150 |
setShowModal(false)}
153 | popup
154 | size={"md"}
155 | >
156 |
157 |
158 |
159 |
160 |
161 | Are You Sure. You want to delete this Post ?
162 |
163 |
164 |
165 | Yes I am Sure
166 |
167 | setShowModal(false)}>
168 | No, Cancel
169 |
170 |
171 |
172 |
173 |
174 |
175 | );
176 | }
177 |
--------------------------------------------------------------------------------
/frontend/src/pages/UpdatePost.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback, useMemo } from "react";
2 | import {
3 | Alert,
4 | Button,
5 | FileInput,
6 | Select,
7 | Spinner,
8 | TextInput,
9 | } from "flowbite-react";
10 | import ReactQuill from "react-quill";
11 | import "react-quill/dist/quill.snow.css";
12 | import {
13 | getDownloadURL,
14 | getStorage,
15 | ref,
16 | uploadBytesResumable,
17 | } from "firebase/storage";
18 | import { app } from "../firebase";
19 | import { CircularProgressbar } from "react-circular-progressbar";
20 | import "react-circular-progressbar/dist/styles.css";
21 | import { useNavigate, useParams } from "react-router-dom";
22 | import { useSelector } from "react-redux";
23 | import { debounce } from "lodash";
24 |
25 | export default function UpdatePost() {
26 | const [file, setFile] = useState(null);
27 | const [imageUploadProgress, setImageUploadProgress] = useState(null);
28 | const [imageUploadError, setImageUploadError] = useState(null);
29 | const [formData, setFormData] = useState({});
30 | const [publishError, setPublishError] = useState(null);
31 | const [loading, setLoading] = useState(true); // Add loading state
32 | const { postId } = useParams();
33 | const navigate = useNavigate();
34 | const { currentUser } = useSelector((state) => state.user);
35 |
36 | useEffect(() => {
37 | setPublishError(null);
38 | const fetchPost = async () => {
39 | try {
40 | setLoading(true); // Start loading
41 | const res = await fetch(`/api/post/getposts?postId=${postId}`);
42 | const data = await res.json();
43 | if (!res.ok) {
44 | setPublishError(data.message);
45 | return;
46 | }
47 | setFormData(data.posts[0]);
48 | } catch (error) {
49 | setPublishError(error.message);
50 | } finally {
51 | setLoading(false); // Stop loading
52 | }
53 | };
54 | fetchPost();
55 | }, [postId]);
56 |
57 | const handleUploadImage = useCallback(async () => {
58 | if (!file) {
59 | setImageUploadError("Please select an Image file");
60 | return;
61 | }
62 |
63 | setImageUploadError(null);
64 | const storage = getStorage(app);
65 | const fileName = `${new Date().getTime()}-${file.name}`;
66 | const storageRef = ref(storage, fileName);
67 | const uploadTask = uploadBytesResumable(storageRef, file);
68 |
69 | uploadTask.on(
70 | "state_changed",
71 | (snapshot) => {
72 | const progress =
73 | (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
74 | setImageUploadProgress(progress.toFixed(0));
75 | },
76 | (error) => {
77 | setImageUploadError(error.message);
78 | setImageUploadProgress(null);
79 | },
80 | async () => {
81 | const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
82 | setImageUploadProgress(null);
83 | setImageUploadError(null);
84 | setFormData((prevData) => ({ ...prevData, image: downloadURL }));
85 | }
86 | );
87 | }, [file]);
88 |
89 | const handleSubmit = async (e) => {
90 | e.preventDefault();
91 | try {
92 | const res = await fetch(
93 | `/api/post/updatepost/${postId}/${currentUser._id}`,
94 | {
95 | method: "PUT",
96 | headers: {
97 | "Content-Type": "application/json",
98 | },
99 | body: JSON.stringify(formData),
100 | }
101 | );
102 | const data = await res.json();
103 | if (!res.ok) {
104 | setPublishError(data.message);
105 | return;
106 | }
107 | navigate(`/post/${data.slug}`);
108 | } catch (error) {
109 | setPublishError("Something went wrong, please try again");
110 | }
111 | };
112 |
113 | const debouncedSetFormData = useMemo(() => debounce(setFormData, 300), []);
114 |
115 | const handleInputChange = (field, value) => {
116 | debouncedSetFormData((prevData) => ({ ...prevData, [field]: value }));
117 | };
118 |
119 | if (loading) {
120 | return (
121 |
122 |
123 |
124 | );
125 | }
126 |
127 | return (
128 |
129 |
Update a post
130 |
206 |
207 | );
208 | }
209 |
--------------------------------------------------------------------------------
/frontend/src/components/CommentSection.jsx:
--------------------------------------------------------------------------------
1 | import { Alert, Button, Modal, Textarea } from "flowbite-react";
2 | import { useEffect, useState } from "react";
3 | import { useSelector } from "react-redux";
4 | import { Link, useNavigate } from "react-router-dom";
5 | import PropTypes from "prop-types";
6 | import Comment from "./Comment";
7 | import { HiOutlineExclamationCircle } from "react-icons/hi";
8 |
9 | export default function CommentSection({ postId }) {
10 | const { currentUser } = useSelector((state) => state.user);
11 | const [comment, setComment] = useState("");
12 | const [commentError, setCommentError] = useState(null);
13 | const [comments, setComments] = useState([]);
14 | const [showModal, setShowModal] = useState(false);
15 | const [commentTodelete, setCommentTodelete] = useState(null);
16 | const navigate = useNavigate();
17 | const handleSubmit = async (e) => {
18 | setCommentError(null);
19 | e.preventDefault();
20 | if (!currentUser) return;
21 | if (!comment) return;
22 | try {
23 | const res = await fetch("/api/comment/create", {
24 | method: "POST",
25 | headers: {
26 | "Content-Type": "application/json",
27 | },
28 | body: JSON.stringify({
29 | comment,
30 | postId,
31 | userId: currentUser._id,
32 | }),
33 | });
34 | const data = await res.json();
35 | if (res.ok) {
36 | setComment("");
37 | setComments([data.newComment, ...comments]);
38 | setCommentError(null);
39 | }
40 | if (!res.ok) {
41 | setCommentError(data);
42 | }
43 | } catch (error) {
44 | setCommentError(error);
45 | }
46 | };
47 |
48 | useEffect(() => {
49 | const fetchPostComment = async () => {
50 | try {
51 | const res = await fetch(`/api/comment/getPostComment/${postId}`);
52 | const data = await res.json();
53 | if (res.ok) {
54 | setComments(data);
55 | }
56 | } catch (error) {
57 | console.log(error);
58 | }
59 | };
60 | if (postId) {
61 | fetchPostComment();
62 | }
63 | }, [postId]);
64 |
65 | const handleLike = async (commentId) => {
66 | try {
67 | if (!currentUser) {
68 | navigate("/sign-in");
69 | return;
70 | }
71 | const res = await fetch(`/api/comment/likeComment/${commentId}`, {
72 | method: "PUT",
73 | headers: {
74 | "Content-Type": "application/json",
75 | },
76 | });
77 | if (res.ok) {
78 | const data = await res.json();
79 | setComments(
80 | comments.map((comment) =>
81 | comment._id === commentId
82 | ? {
83 | ...comment,
84 | likes: data.likes,
85 | numberofLikes: data.likes.length,
86 | }
87 | : comment
88 | )
89 | );
90 | } else {
91 | console.error("Failed to like comment:", res.status, res.statusText);
92 | }
93 | } catch (error) {
94 | console.error("Error liking comment:", error);
95 | }
96 | };
97 |
98 | const handleEdit = async (comment, editedContent) => {
99 | setComments(
100 | comments.map((c) =>
101 | c._id === comment._id ? { ...c, content: editedContent } : c
102 | )
103 | );
104 | };
105 |
106 | const handleDelete = async (commentId) => {
107 | setShowModal(false);
108 | try {
109 | if (!currentUser) {
110 | navigate("/sign-in");
111 | return;
112 | }
113 | const res = await fetch(`/api/comment/deleteComment/${commentId}`, {
114 | method: "DELETE",
115 | });
116 | if (res.ok) {
117 | setComments(comments.filter((comment) => comment._id !== commentId));
118 | }
119 | } catch (error) {
120 | console.log(error);
121 | }
122 | };
123 | return (
124 |
125 | {currentUser ? (
126 |
127 |
Signed in as :
128 |
133 |
137 | @ {currentUser.username}
138 |
139 |
140 | ) : (
141 |
142 | You must be signed in to comment.
143 |
144 | Sign In
145 |
146 |
147 | )}
148 | {currentUser && (
149 | <>
150 |
175 | >
176 | )}
177 | {comments.length === 0 ? (
178 |
No commments yet !
179 | ) : (
180 | <>
181 |
182 |
Comments
183 |
184 |
{comments.length}
185 |
186 |
187 | {comments &&
188 | comments.map((comment) => {
189 | return (
190 |
{
196 | setShowModal(true);
197 | setCommentTodelete(commentId);
198 | }}
199 | />
200 | );
201 | })}
202 | >
203 | )}
204 | setShowModal(false)}
207 | popup
208 | size={"md"}
209 | >
210 |
211 |
212 |
213 |
214 |
215 | Are You Sure. You want to delete this Comment ?
216 |
217 |
218 | handleDelete(commentTodelete)}
221 | >
222 | Yes I am Sure
223 |
224 | setShowModal(false)}>
225 | No, Cancel
226 |
227 |
228 |
229 |
230 |
231 |
232 | );
233 | }
234 |
235 | CommentSection.propTypes = {
236 | postId: PropTypes.string.isRequired,
237 | };
238 |
--------------------------------------------------------------------------------
/frontend/src/components/DashboardComp.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useSelector } from "react-redux";
3 | import {
4 | HiAnnotation,
5 | HiArrowNarrowUp,
6 | HiDocumentText,
7 | HiOutlineUserGroup,
8 | } from "react-icons/hi";
9 | import { Button, Table } from "flowbite-react";
10 | import { Link } from "react-router-dom";
11 |
12 | export default function DashboardComp() {
13 | const [users, setUsers] = useState([]);
14 | const [comments, setComments] = useState([]);
15 | const [posts, setPosts] = useState([]);
16 | const [totalUsers, setTotalUsers] = useState(0);
17 | const [totalPosts, setTotalPosts] = useState(0);
18 | const [totalComments, setTotalComments] = useState(0);
19 | const [lastMonthUsers, setLastMonthUsers] = useState(0);
20 | const [lastMonthPosts, setLastMonthPosts] = useState(0);
21 | const [lastMonthComments, setLastMonthComments] = useState(0);
22 | const { currentUser } = useSelector((state) => state.user);
23 | useEffect(() => {
24 | const fetchUsers = async () => {
25 | try {
26 | const res = await fetch("/api/user/getusers?limit=5");
27 | const data = await res.json();
28 | if (res.ok) {
29 | setUsers(data.users);
30 | setTotalUsers(data.totalUsers);
31 | setLastMonthUsers(data.lastMonthUsers);
32 | }
33 | } catch (error) {
34 | console.log(error.message);
35 | }
36 | };
37 | const fetchPosts = async () => {
38 | try {
39 | const res = await fetch("/api/post/getposts?limit=5");
40 | const data = await res.json();
41 | if (res.ok) {
42 | setPosts(data.posts);
43 | setTotalPosts(data.totalPosts);
44 | setLastMonthPosts(data.lastMonthPosts);
45 | }
46 | } catch (error) {
47 | console.log(error.message);
48 | }
49 | };
50 | const fetchComments = async () => {
51 | try {
52 | const res = await fetch("/api/comment/getcomments?limit=5");
53 | const data = await res.json();
54 | if (res.ok) {
55 | setComments(data.comments);
56 | setTotalComments(data.totalComments);
57 | setLastMonthComments(data.lastMonthComments);
58 | }
59 | } catch (error) {
60 | console.log(error.message);
61 | }
62 | };
63 | if (currentUser.isAdmin) {
64 | fetchUsers();
65 | fetchPosts();
66 | fetchComments();
67 | }
68 | }, [currentUser]);
69 | return (
70 |
71 |
72 |
73 |
74 |
75 |
Total Users
76 |
{totalUsers}
77 |
78 |
79 |
80 |
81 |
82 |
83 | {lastMonthUsers}
84 |
85 |
Last month
86 |
87 |
88 |
89 |
90 |
91 |
92 | Total Comments
93 |
94 |
{totalComments}
95 |
96 |
97 |
98 |
99 |
100 |
101 | {lastMonthComments}
102 |
103 |
Last month
104 |
105 |
106 |
107 |
108 |
109 |
Total Posts
110 |
{totalPosts}
111 |
112 |
113 |
114 |
115 |
116 |
117 | {lastMonthPosts}
118 |
119 |
Last month
120 |
121 |
122 |
123 |
124 |
125 |
126 |
Recent users
127 |
128 | See all
129 |
130 |
131 |
132 |
133 | User image
134 | Username
135 |
136 | {users &&
137 | users.map((user) => (
138 |
139 |
140 |
141 |
146 |
147 | {user.username}
148 |
149 |
150 | ))}
151 |
152 |
153 |
154 |
155 |
Recent comments
156 |
157 | See all
158 |
159 |
160 |
161 |
162 | Comment content
163 | Likes
164 |
165 | {comments &&
166 | comments.map((comment) => (
167 |
168 |
169 |
170 | {comment.content}
171 |
172 | {comment.numberOfLikes}
173 |
174 |
175 | ))}
176 |
177 |
178 |
179 |
180 |
Recent posts
181 |
182 | See all
183 |
184 |
185 |
186 |
187 |
188 | Post image
189 | Post Title
190 | Category
191 |
192 | {posts &&
193 | posts.map((post) => (
194 |
195 |
196 |
197 |
202 |
203 | {post.title}
204 | {post.category}
205 |
206 |
207 | ))}
208 |
209 |
210 |
211 |
212 |
213 | );
214 | }
215 |
--------------------------------------------------------------------------------
/frontend/src/components/DashProfile.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import { Alert, Button, Modal, TextInput } from "flowbite-react";
3 | import { useEffect, useRef, useState } from "react";
4 | import { CircularProgressbar } from "react-circular-progressbar";
5 | import "react-circular-progressbar/dist/styles.css";
6 | import { app } from "../firebase";
7 | import {
8 | getStorage,
9 | getDownloadURL,
10 | ref,
11 | uploadBytesResumable,
12 | } from "firebase/storage";
13 | import {
14 | UpdateStart,
15 | UpdateSucess,
16 | UpdateFailure,
17 | deleteUserStart,
18 | deleteUserSucess,
19 | deleteUserFailure,
20 | SignOutSucess,
21 | } from "../redux/reducers/user/userSlice";
22 | import { useDispatch } from "react-redux";
23 | import { HiOutlineExclamationCircle } from "react-icons/hi";
24 | import { Link } from "react-router-dom";
25 |
26 | export default function DashProfile() {
27 | const { currentUser, error, loading } = useSelector((state) => state.user);
28 | const [imageFile, setImageFile] = useState(null);
29 | const [imageFileUrl, setimageFileUrl] = useState(null);
30 | const [imageFileUploadingProgress, setImageFileUploadingProgress] =
31 | useState(null);
32 | const [imageFileUploadError, setImageFileUploadError] = useState(null);
33 | const [imageFileUploading, setImageFileUploading] = useState(false);
34 | const [UpdateUserSuccess, setUpdateUserSuccess] = useState(null);
35 | const [updateUserError, setUpdateUserError] = useState(null);
36 | const [showModel, setShowModel] = useState(false);
37 | const [formData, setFormData] = useState({});
38 | const filePickerRef = useRef();
39 | const dispatch = useDispatch();
40 | const handleImageChange = (e) => {
41 | const file = e.target.files[0];
42 | if (file) {
43 | setImageFile(e.target.files[0]);
44 | setimageFileUrl(URL.createObjectURL(file));
45 | }
46 | };
47 | useEffect(() => {
48 | if (imageFile) {
49 | uploadImage();
50 | }
51 | // eslint-disable-next-line react-hooks/exhaustive-deps
52 | }, [imageFile]);
53 | const uploadImage = async () => {
54 | // service firebase.storage {
55 | // match /b/{bucket}/o {
56 | // match /{allPaths=**} {
57 | // allow read;
58 | // allow write : if
59 | // request.resource.size <2 * 1024 * 1024 &&
60 | // request.resource.contentType.matches('image/.*')
61 | // }
62 | // }
63 | // }
64 | setImageFileUploading(true);
65 | setImageFileUploadError(null);
66 | const storage = getStorage(app);
67 | const fileName = `${currentUser.username}_${new Date().getDate()}_${
68 | imageFile.name
69 | }`;
70 | const storageRef = ref(storage, fileName);
71 | const uploadTask = uploadBytesResumable(storageRef, imageFile);
72 | uploadTask.on(
73 | "state_changed",
74 | (snapShort) => {
75 | const progress =
76 | (snapShort.bytesTransferred / snapShort.totalBytes) * 100;
77 | setImageFileUploadingProgress(progress.toFixed(0));
78 | },
79 | (error) => {
80 | setImageFileUploadError(
81 | "could not upload image : (File must be less than 2Mb) ",
82 | error
83 | );
84 | setImageFileUploadingProgress(null);
85 | setImageFile(null);
86 | setimageFileUrl(null);
87 | setImageFileUploading(false);
88 | },
89 | () => {
90 | getDownloadURL(uploadTask.snapshot.ref).then((downloadUrl) => {
91 | setimageFileUrl(downloadUrl);
92 | setFormData({ ...formData, profilePicture: downloadUrl });
93 | setImageFileUploading(false);
94 | });
95 | }
96 | );
97 | };
98 | const handleChange = (event) => {
99 | setFormData({ ...formData, [event.target.id]: event.target.value });
100 | };
101 | const handleSubmit = async (event) => {
102 | event.preventDefault();
103 | setUpdateUserError(null);
104 | setUpdateUserSuccess(null);
105 | if (Object.keys(formData).length === 0) {
106 | setUpdateUserError("No changes Made");
107 | return;
108 | }
109 | if (imageFileUploading) {
110 | return;
111 | }
112 | try {
113 | dispatch(UpdateStart());
114 | const res = await fetch(`/api/user/update/${currentUser._id}`, {
115 | method: "PUT",
116 | headers: {
117 | "Content-Type": "application/json",
118 | },
119 | body: JSON.stringify(formData),
120 | });
121 | const data = await res.json();
122 | if (!res.ok) {
123 | dispatch(UpdateFailure(data.message));
124 | setUpdateUserError(data.message);
125 | }
126 | dispatch(UpdateSucess(data));
127 | setUpdateUserSuccess("User Profile Updated SuccessFully");
128 | } catch (error) {
129 | setUpdateUserError(error.message);
130 | dispatch(UpdateFailure(error.message));
131 | }
132 | };
133 | const handleDeleteUser = async () => {
134 | setShowModel(false);
135 | try {
136 | dispatch(deleteUserStart());
137 | const res = await fetch(`/api/user/delete/${currentUser._id}`, {
138 | method: "DELETE",
139 | });
140 | const data = await res.json();
141 | if (!res.ok) {
142 | dispatch(deleteUserFailure(data.message));
143 | } else {
144 | dispatch(deleteUserSucess());
145 | }
146 | } catch (error) {
147 | dispatch(deleteUserFailure(error.message));
148 | }
149 | };
150 | const handleSignOut = async () => {
151 | try {
152 | const res = await fetch("/api/user/signout", {
153 | method: "POST",
154 | });
155 | const data = await res.json();
156 | if (!res.ok) {
157 | console.log(data.message);
158 | } else {
159 | dispatch(SignOutSucess());
160 | }
161 | } catch (error) {
162 | console.log(error.message);
163 | }
164 | };
165 | return (
166 |
167 |
Profile
168 |
169 |
176 | filePickerRef.current.click()}
179 | >
180 | {imageFileUploadingProgress && (
181 |
200 | )}
201 |
210 |
211 | {imageFileUploadError && (
212 | {imageFileUploadError}
213 | )}
214 |
221 |
228 |
235 |
241 | {loading ? "Loading..." : "Update"}
242 |
243 | {currentUser.isAdmin && (
244 |
245 |
250 | Create Post
251 |
252 |
253 | )}
254 |
255 |
256 | setShowModel(true)} className="cursor-pointer">
257 | Delete Account
258 |
259 |
260 | Sign Out
261 |
262 |
263 | {UpdateUserSuccess && (
264 |
265 | {UpdateUserSuccess}
266 |
267 | )}
268 | {updateUserError && (
269 |
270 | {updateUserError}
271 |
272 | )}
273 | {error && (
274 |
275 | {error}
276 |
277 | )}
278 |
setShowModel(false)}
281 | popup
282 | size={"md"}
283 | >
284 |
285 |
286 |
287 |
288 |
289 | Are You Sure. You want to delete Your Account{" "}
290 |
291 |
292 |
293 | Yes I am Sure
294 |
295 | setShowModel(false)}>
296 | No, Cancel
297 |
298 |
299 |
300 |
301 |
302 |
303 | );
304 | }
305 |
--------------------------------------------------------------------------------