├── client ├── src │ ├── pages │ │ ├── home │ │ │ ├── home.module.css │ │ │ └── Home.jsx │ │ ├── updateBlog │ │ │ ├── updateBlog.module.css │ │ │ └── UpdateBlog.jsx │ │ ├── login │ │ │ ├── login.module.css │ │ │ └── Login.jsx │ │ ├── register │ │ │ ├── register.module.css │ │ │ └── Register.jsx │ │ ├── blogDetails │ │ │ ├── blogDetails.module.css │ │ │ └── BlogDetails.jsx │ │ └── create │ │ │ ├── create.module.css │ │ │ └── Create.jsx │ ├── assets │ │ ├── woman.jpg │ │ ├── mountain1.jpg │ │ ├── mountain2.jpg │ │ └── mountain3.jpg │ ├── App.css │ ├── index.js │ ├── components │ │ ├── footer │ │ │ ├── footer.module.css │ │ │ └── Footer.jsx │ │ ├── newsletter │ │ │ ├── Newsletter.jsx │ │ │ └── newsletter.module.css │ │ ├── navbar │ │ │ ├── Navbar.jsx │ │ │ └── navbar.module.css │ │ ├── categories │ │ │ ├── categories.module.css │ │ │ └── Categories.jsx │ │ └── featuredBlogs │ │ │ ├── featuredBlogs.module.css │ │ │ └── FeaturedBlogs.jsx │ ├── redux │ │ ├── store.js │ │ └── authSlice.js │ ├── App.js │ └── utils │ │ └── fetchApi.js ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── .gitignore ├── package.json └── README.md └── backend ├── .env ├── public └── images │ ├── 0c2d40a6-0ff7-48e5-b7dc-ba0a439ee8b9mountain1.jpg │ ├── 4de39373-744f-4a31-99b9-fdf1233c9122mountain1.jpg │ ├── b1a89afc-f02e-40be-b546-d1e99b5e8ddbmountain1.jpg │ └── f1cdbb3c-53c4-43e7-b220-8360b4ecf9afmountain1.jpg ├── models ├── User.js └── Blog.js ├── package.json ├── middlewares └── verifyToken.js ├── index.js └── controllers ├── authController.js └── blogController.js /client/src/pages/home/home.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/MERN-Blog-App/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/MERN-Blog-App/HEAD/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/MERN-Blog-App/HEAD/client/public/logo512.png -------------------------------------------------------------------------------- /client/src/assets/woman.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/MERN-Blog-App/HEAD/client/src/assets/woman.jpg -------------------------------------------------------------------------------- /client/src/assets/mountain1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/MERN-Blog-App/HEAD/client/src/assets/mountain1.jpg -------------------------------------------------------------------------------- /client/src/assets/mountain2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/MERN-Blog-App/HEAD/client/src/assets/mountain2.jpg -------------------------------------------------------------------------------- /client/src/assets/mountain3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/MERN-Blog-App/HEAD/client/src/assets/mountain3.jpg -------------------------------------------------------------------------------- /backend/.env: -------------------------------------------------------------------------------- 1 | MONGO_URL=mongodb+srv://username123:username123@cluster0.sxbxehv.mongodb.net/?retryWrites=true&w=majority 2 | PORT=5000 3 | JWT_SECRET=secret123321 -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | *{ 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | font-family: 'Roboto', sans-serif; 6 | scroll-behavior: smooth; 7 | user-select: none; 8 | } -------------------------------------------------------------------------------- /backend/public/images/0c2d40a6-0ff7-48e5-b7dc-ba0a439ee8b9mountain1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/MERN-Blog-App/HEAD/backend/public/images/0c2d40a6-0ff7-48e5-b7dc-ba0a439ee8b9mountain1.jpg -------------------------------------------------------------------------------- /backend/public/images/4de39373-744f-4a31-99b9-fdf1233c9122mountain1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/MERN-Blog-App/HEAD/backend/public/images/4de39373-744f-4a31-99b9-fdf1233c9122mountain1.jpg -------------------------------------------------------------------------------- /backend/public/images/b1a89afc-f02e-40be-b546-d1e99b5e8ddbmountain1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/MERN-Blog-App/HEAD/backend/public/images/b1a89afc-f02e-40be-b546-d1e99b5e8ddbmountain1.jpg -------------------------------------------------------------------------------- /backend/public/images/f1cdbb3c-53c4-43e7-b220-8360b4ecf9afmountain1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevMania/MERN-Blog-App/HEAD/backend/public/images/f1cdbb3c-53c4-43e7-b220-8360b4ecf9afmountain1.jpg -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /backend/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const UserSchema = new mongoose.Schema({ 4 | username: { 5 | type: String, 6 | required: true, 7 | unique: true, 8 | }, 9 | email: { 10 | type: String, 11 | required: true, 12 | unique: true, 13 | }, 14 | password: { 15 | type: String, 16 | required: true, 17 | min: 6, 18 | }, 19 | }, {timestamps: true}) 20 | 21 | module.exports = mongoose.model("User", UserSchema) -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcrypt": "^5.1.0", 14 | "cors": "^2.8.5", 15 | "dotenv": "^16.0.3", 16 | "express": "^4.18.2", 17 | "jsonwebtoken": "^9.0.0", 18 | "mongoose": "^6.10.0", 19 | "multer": "^1.4.5-lts.1", 20 | "nodemon": "^2.0.20" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import { BrowserRouter } from 'react-router-dom' 5 | import { PersistGate } from 'redux-persist/integration/react'; 6 | import { store, persistor } from './redux/store'; 7 | import {Provider} from 'react-redux' 8 | 9 | const root = ReactDOM.createRoot(document.getElementById('root')); 10 | root.render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | 20 | -------------------------------------------------------------------------------- /client/src/components/footer/footer.module.css: -------------------------------------------------------------------------------- 1 | footer{ 2 | width: 100%; 3 | height: 300px; 4 | margin-top: 10rem; 5 | } 6 | 7 | .wrapper{ 8 | width: 85%; 9 | height: 100%; 10 | margin: 0 auto; 11 | display: flex; 12 | justify-content: space-between; 13 | align-items: center; 14 | } 15 | 16 | .col{ 17 | display: flex; 18 | flex-direction: column; 19 | gap: 0.5rem; 20 | } 21 | 22 | .col > h2{ 23 | margin-bottom: 15px; 24 | justify-self: flex-start; 25 | margin-left: -5px; 26 | } 27 | 28 | .col > p{ 29 | max-width: 425px; 30 | color: #555; 31 | font-size: 15px; 32 | } -------------------------------------------------------------------------------- /client/src/pages/home/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Categories from '../../components/categories/Categories' 3 | import FeaturedBlogs from '../../components/featuredBlogs/FeaturedBlogs' 4 | import Footer from '../../components/footer/Footer' 5 | import Navbar from '../../components/navbar/Navbar' 6 | import Newsletter from '../../components/newsletter/Newsletter' 7 | import classes from './home.module.css' 8 | 9 | const Home = () => { 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 |
18 | ) 19 | } 20 | 21 | export default Home -------------------------------------------------------------------------------- /client/src/components/newsletter/Newsletter.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classes from './newsletter.module.css' 3 | import {FiSend} from 'react-icons/fi' 4 | 5 | const Newsletter = () => { 6 | return ( 7 |
8 |
9 |
10 |
Want to get the latest updates?
11 |

Send us your email and we will do the rest!

12 |
13 |
14 | 15 | 16 |
17 |
18 |
19 | ) 20 | } 21 | 22 | export default Newsletter -------------------------------------------------------------------------------- /backend/middlewares/verifyToken.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | 3 | const verifyToken = (req, res, next) => { 4 | console.log(req.headers) 5 | if(!req.headers.authorization) return res.status(403).json({msg: "Not authorized. No token"}) 6 | 7 | if(req.headers.authorization && req.headers.authorization.startsWith("Bearer ")){ 8 | const token = req.headers.authorization.split(" ")[1] 9 | jwt.verify(token, process.env.JWT_SECRET, (err, data) => { 10 | if(err) return res.status(403).json({msg: "Wrong or expired token"}) 11 | else { 12 | req.user = data // an object with the user id as its only property -> data = {id: .....} 13 | next() 14 | } 15 | }) 16 | } 17 | } 18 | 19 | module.exports = verifyToken -------------------------------------------------------------------------------- /backend/models/Blog.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const BlogSchema = new mongoose.Schema({ 4 | userId: { 5 | type: mongoose.Types.ObjectId, 6 | ref: 'User', 7 | required: true, 8 | }, 9 | title: { 10 | type: String, 11 | required: true, 12 | min: 4, 13 | }, 14 | desc: { 15 | type: String, 16 | required: true, 17 | min: 12, 18 | }, 19 | photo: { 20 | type: String, 21 | required: true, 22 | }, 23 | category: { 24 | type: String, 25 | required: true, 26 | }, 27 | featured: { 28 | type: Boolean, 29 | default: false, 30 | }, 31 | views: { 32 | type: Number, 33 | default: 0 34 | }, 35 | likes: { 36 | type: [String], 37 | default: [], 38 | } 39 | }, {timestamps: true}) 40 | 41 | module.exports = mongoose.model("Blog", BlogSchema) -------------------------------------------------------------------------------- /client/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { combineReducers, configureStore } from "@reduxjs/toolkit"; 2 | import { 3 | persistStore, 4 | persistReducer, 5 | FLUSH, 6 | REHYDRATE, 7 | PAUSE, 8 | PERSIST, 9 | PURGE, 10 | REGISTER, 11 | } from "redux-persist"; 12 | import storage from "redux-persist/lib/storage"; 13 | import authSlice from "./authSlice"; 14 | 15 | 16 | const persistConfig = { 17 | key: "root", 18 | version: 1, 19 | storage, 20 | }; 21 | 22 | const reducers = combineReducers({ 23 | auth: authSlice, 24 | }) 25 | 26 | const persistedReducer = persistReducer(persistConfig, reducers); 27 | 28 | export const store = configureStore({ 29 | reducer: persistedReducer, 30 | middleware: (getDefaultMiddleware) => 31 | getDefaultMiddleware({ 32 | serializableCheck: { 33 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], 34 | }, 35 | }), 36 | }); 37 | 38 | export let persistor = persistStore(store); -------------------------------------------------------------------------------- /client/src/redux/authSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | const initialState = { 4 | user: null, 5 | token: null 6 | } 7 | 8 | export const authSlice = createSlice({ 9 | name: 'auth', 10 | initialState: initialState, 11 | reducers: { 12 | login(state, action) { 13 | localStorage.clear() 14 | state.user = action.payload.user 15 | state.token = action.payload.token 16 | }, 17 | register(state, action) { 18 | localStorage.clear() 19 | state.user = action.payload.user 20 | state.token = action.payload.token 21 | }, 22 | logout(state) { 23 | state.user = null 24 | state.token = null 25 | localStorage.clear() 26 | } 27 | } 28 | }) 29 | 30 | export const { register, login, logout } = authSlice.actions 31 | 32 | export default authSlice.reducer 33 | 34 | 35 | // variables and function that change the variables, and they are both available in the all components -------------------------------------------------------------------------------- /client/src/pages/updateBlog/updateBlog.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | height: 100%; 3 | width: 100%; 4 | margin-top: 5rem; 5 | } 6 | 7 | .wrapper{ 8 | max-width: 375px; 9 | height: 425px; 10 | margin: 0 auto; 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | border: 1px solid #333; 15 | border-radius: 12px; 16 | } 17 | 18 | .wrapper > h2{ 19 | margin: 3rem 0; 20 | } 21 | 22 | .wrapper > form{ 23 | display: flex; 24 | flex-direction: column; 25 | justify-content: center; 26 | gap: 2.5rem; 27 | } 28 | 29 | .wrapper > form > input{ 30 | outline: none; 31 | border: none; 32 | border-bottom: 1px solid #333; 33 | width: 100%; 34 | padding: 0.25rem; 35 | } 36 | 37 | .wrapper > form > select{ 38 | outline: none; 39 | padding: 0.25rem; 40 | text-transform: capitalize; 41 | width: 100%; 42 | } 43 | 44 | .wrapper > form > button{ 45 | outline: none; 46 | padding: 0.25rem 1.5rem; 47 | background-color: #000; 48 | color: #fff; 49 | } -------------------------------------------------------------------------------- /client/src/components/footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classes from './footer.module.css' 3 | 4 | const Footer = () => { 5 | return ( 6 | 29 | ) 30 | } 31 | 32 | export default Footer -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.9.3", 7 | "@testing-library/jest-dom": "^5.16.5", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-icons": "^4.7.1", 13 | "react-redux": "^8.0.5", 14 | "react-router-dom": "^6.8.1", 15 | "react-scripts": "5.0.1", 16 | "redux-persist": "^6.0.0", 17 | "timeago.js": "^4.0.2", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import { Routes, Route, Navigate } from 'react-router-dom' 3 | import Home from './pages/home/Home'; 4 | import Login from './pages/login/Login'; 5 | import Register from './pages/register/Register'; 6 | import Create from './pages/create/Create'; 7 | import BlogDetails from './pages/blogDetails/BlogDetails'; 8 | import UpdateBlog from './pages/updateBlog/UpdateBlog'; 9 | import { useSelector } from 'react-redux'; 10 | 11 | function App() { 12 | const { user } = useSelector((state) => state.auth) 13 | 14 | return ( 15 |
16 | 17 | : } /> 18 | : } /> 19 | : } /> 20 | : } /> 21 | : } /> 22 | : } /> 23 | 24 |
25 | ); 26 | } 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /client/src/components/newsletter/newsletter.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | height: 100%; 3 | width: 100%; 4 | margin-top: 2.5rem; 5 | } 6 | 7 | .wrapper{ 8 | max-width: 1180px; 9 | height: 100%; 10 | margin: 0 auto; 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | } 15 | 16 | .titles{ 17 | display: flex; 18 | align-items: center; 19 | flex-direction: column; 20 | gap: 0.5rem; 21 | } 22 | 23 | .titles > h5{ 24 | color: #777; 25 | font-size: 18px; 26 | font-weight: 500; 27 | } 28 | 29 | .titles > h2{ 30 | color: #333; 31 | font-size: 28px; 32 | } 33 | 34 | .wrapper > .inputContainer{ 35 | margin-top: 2.5rem; 36 | border: 2px solid #333; 37 | border-radius: 20px; 38 | height: 50px; 39 | width: 360px; 40 | padding: 0.25rem 0.5rem; 41 | display: flex; 42 | justify-content: space-between; 43 | align-items: center; 44 | } 45 | 46 | .wrapper > .inputContainer:focus-within{ 47 | border-color: #777; 48 | } 49 | 50 | .wrapper > .inputContainer > input{ 51 | border: none; 52 | outline: none; 53 | padding-left: 0.5rem; 54 | } 55 | 56 | .wrapper > .inputContainer > .sendIcon{ 57 | margin-right: 0.5rem; 58 | font-size: 20px; 59 | } -------------------------------------------------------------------------------- /client/src/components/navbar/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classes from './navbar.module.css' 3 | import { Link } from 'react-router-dom' 4 | import womanImg from '../../assets/woman.jpg' 5 | import { useState } from 'react' 6 | 7 | const Navbar = () => { 8 | const [showModal, setShowModal] = useState(false) 9 | 10 | return ( 11 |
12 |
13 |
14 | WebDevMania 15 |
16 | 22 |
23 | setShowModal(prev => !prev)} src={womanImg} className={classes.img} /> 24 | {showModal && 25 |
26 | Create 27 | Logout 28 |
29 | } 30 |
31 |
32 |
33 | ) 34 | } 35 | 36 | export default Navbar -------------------------------------------------------------------------------- /client/src/utils/fetchApi.js: -------------------------------------------------------------------------------- 1 | const BASE_URL = "http://localhost:5000" 2 | 3 | export const request = async (url, method, headers = {}, body = {}, isNotStringified = false) => { 4 | let res 5 | let data 6 | switch (method) { 7 | case 'GET': 8 | res = await fetch(BASE_URL + url, { headers }) 9 | data = await res.json() 10 | return data 11 | 12 | case 'POST': 13 | // if we send form data, it is not content-type:application/json, 14 | // hence the bonus param 15 | if (isNotStringified) { 16 | res = await fetch(BASE_URL + url, { headers, method, body }) 17 | data = await res.json() 18 | } else { 19 | res = await fetch(BASE_URL + url, { headers, method, body: JSON.stringify({ ...body }) }) 20 | data = await res.json() 21 | } 22 | return data 23 | 24 | case 'PUT': 25 | res = await fetch(BASE_URL + url, { headers, method, body: JSON.stringify({ ...body }) }) 26 | data = await res.json() 27 | return data 28 | 29 | case 'DELETE': 30 | res = await fetch(BASE_URL + url, { headers, method }) 31 | data = await res.json() 32 | return data 33 | default: 34 | return 35 | } 36 | } -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const mongoose = require("mongoose") 3 | const dotenv = require('dotenv').config() 4 | const cors = require('cors') 5 | const authController = require('./controllers/authController') 6 | const blogController = require('./controllers/blogController') 7 | const multer = require('multer') 8 | const app = express() 9 | 10 | // connect db 11 | mongoose.set('strictQuery', false); 12 | mongoose.connect(process.env.MONGO_URL, () => console.log('MongoDB has been started successfully')) 13 | 14 | // routes 15 | app.use('/images', express.static('public/images')) 16 | 17 | app.use(cors()) 18 | app.use(express.json()) 19 | app.use(express.urlencoded({extended: true})) 20 | app.use('/auth', authController) 21 | app.use('/blog', blogController) 22 | 23 | // multer 24 | const storage = multer.diskStorage({ 25 | destination: function(req, file, cb){ 26 | cb(null, 'public/images') 27 | }, 28 | filename: function(req, file, cb){ 29 | cb(null, req.body.filename) 30 | } 31 | }) 32 | 33 | const upload = multer({ 34 | storage: storage 35 | }) 36 | 37 | app.post('/upload', upload.single("image"), async(req, res) => { 38 | return res.status(200).json({msg: "Successfully uploaded"}) 39 | }) 40 | 41 | // connect server 42 | app.listen(process.env.PORT, () => console.log('Server has been started successfully')) -------------------------------------------------------------------------------- /client/src/pages/login/login.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | height: 100vh; 6 | width: 100vw; 7 | background-image: url('../../assets/mountain1.jpg'); 8 | background-color: rgba(0, 0, 0, 0.2); 9 | background-size: cover; 10 | background-blend-mode: darken; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | } 15 | 16 | .wrapper{ 17 | height: 52.5vh; 18 | width: 25vw; 19 | border-radius: 20px; 20 | display: flex; 21 | flex-direction: column; 22 | background-color: #fff; 23 | } 24 | 25 | .wrapper > h2{ 26 | color: #171097; 27 | text-align: center; 28 | margin: 2.5rem 0; 29 | font-size: 36px; 30 | } 31 | 32 | form{ 33 | display: flex; 34 | flex-direction: column; 35 | align-items: center; 36 | gap: 42px; 37 | } 38 | 39 | form > input{ 40 | width: 50%; 41 | outline: none; 42 | border: none; 43 | border-bottom: 1px solid #333; 44 | } 45 | 46 | form > button{ 47 | outline: none; 48 | background-color: blue; 49 | color: #fff; 50 | border-radius: 12px; 51 | padding: 0.5rem 1.25rem; 52 | font-size: 18px; 53 | cursor: pointer; 54 | border: 1px solid transparent; 55 | margin-top: 1rem; 56 | } 57 | 58 | form>button:hover{ 59 | background-color: #fff; 60 | border-color: blue; 61 | color: blue; 62 | } -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 21 | React App 22 | 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /client/src/pages/login/Login.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useState } from 'react' 3 | import { Link, useNavigate } from 'react-router-dom' 4 | import { request } from '../../utils/fetchApi' 5 | import classes from './login.module.css' 6 | import {useDispatch} from 'react-redux' 7 | import { login } from '../../redux/authSlice' 8 | 9 | const Login = () => { 10 | const [email, setEmail] = useState("") 11 | const [password, setPassword] = useState("") 12 | const dispatch = useDispatch() 13 | const navigate = useNavigate() 14 | 15 | const handleLogin = async(e) => { 16 | e.preventDefault() 17 | 18 | if(email === '' || password === "") return 19 | 20 | try { 21 | const options = { 22 | 'Content-Type': 'application/json' 23 | } 24 | 25 | 26 | const data = await request("/auth/login", 'POST', options, {email, password}) 27 | console.log(data) 28 | dispatch(login(data)) 29 | navigate('/') 30 | } catch (error) { 31 | console.error(error) 32 | } 33 | } 34 | 35 | return ( 36 |
37 |
38 |

Login

39 |
40 | setEmail(e.target.value)} /> 41 | setPassword(e.target.value)} /> 42 | 43 |

Don't have an account? Register

44 |
45 |
46 |
47 | ) 48 | } 49 | 50 | export default Login -------------------------------------------------------------------------------- /client/src/components/navbar/navbar.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | height: 60px; 3 | width: 100%; 4 | position: sticky; 5 | top: 0; 6 | left: 0; 7 | border-bottom: 1px solid #777; 8 | background-color: #000; 9 | z-index: 999; 10 | } 11 | 12 | .wrapper{ 13 | height: 100%; 14 | width: 1180px; 15 | margin: 0 auto; 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | } 20 | 21 | /* left */ 22 | .left{ 23 | font-size: 28px; 24 | font-weight: bold; 25 | } 26 | 27 | .left > a{ 28 | text-decoration: none; 29 | color: #fff; 30 | } 31 | 32 | /* center */ 33 | 34 | .center{ 35 | display: flex; 36 | align-items: center; 37 | list-style: none; 38 | gap: 1.5rem; 39 | } 40 | 41 | .listItem{ 42 | color: #fff; 43 | cursor: pointer; 44 | transition: 150ms all; 45 | } 46 | 47 | .listItem:hover{ 48 | color: #dedbdb; 49 | } 50 | 51 | 52 | /* right */ 53 | .right{ 54 | display: flex; 55 | align-items: center; 56 | gap: 0.75rem; 57 | color: #fff; 58 | } 59 | 60 | 61 | .img{ 62 | width: 40px; 63 | height: 40px; 64 | object-fit: cover; 65 | border-radius: 50%; 66 | cursor: pointer; 67 | } 68 | 69 | .modal{ 70 | height: 75px; 71 | width: 75px; 72 | background-color: #666; 73 | position: absolute; 74 | top: 2.5rem; 75 | right: 9.5rem; 76 | display: flex; 77 | flex-direction: column; 78 | align-items: center; 79 | justify-content: center; 80 | gap: 1rem; 81 | } 82 | 83 | .right > .modal > span{ 84 | cursor: pointer; 85 | } 86 | 87 | .right > .modal > a{ 88 | text-decoration: none; 89 | color: #fff; 90 | } -------------------------------------------------------------------------------- /client/src/pages/register/register.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | height: 100vh; 6 | width: 100vw; 7 | background-image: url('../../assets/mountain2.jpg'); 8 | background-color: rgba(0, 0, 0, 0.3); 9 | background-size: cover; 10 | background-blend-mode: darken; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | } 15 | 16 | .wrapper{ 17 | height: 60vh; 18 | width: 27.5vw; 19 | border-radius: 20px; 20 | display: flex; 21 | flex-direction: column; 22 | background-color: #fff; 23 | } 24 | 25 | .wrapper > h2{ 26 | color: #171097; 27 | text-align: center; 28 | margin: 2.5rem 0; 29 | font-size: 36px; 30 | } 31 | 32 | form{ 33 | display: flex; 34 | flex-direction: column; 35 | align-items: center; 36 | gap: 42px; 37 | } 38 | 39 | form > input{ 40 | width: 50%; 41 | outline: none; 42 | border: none; 43 | border-bottom: 1px solid #333; 44 | } 45 | 46 | form > button{ 47 | outline: none; 48 | background-color: blue; 49 | color: #fff; 50 | border-radius: 12px; 51 | padding: 0.5rem 1.25rem; 52 | font-size: 18px; 53 | cursor: pointer; 54 | border: 1px solid transparent; 55 | } 56 | 57 | form > button:hover{ 58 | background-color: #fff; 59 | border-color: blue; 60 | color: blue; 61 | } 62 | 63 | form > p{ 64 | text-align: center; 65 | color: black; 66 | display: flex; 67 | flex-direction: column; 68 | justify-content: center; 69 | } 70 | 71 | form > p > a{ 72 | text-decoration: none; 73 | font-size: 17px; 74 | color: #888; 75 | margin-top: 0.25rem; 76 | } -------------------------------------------------------------------------------- /backend/controllers/authController.js: -------------------------------------------------------------------------------- 1 | const authController = require('express').Router() 2 | const User = require("../models/User") 3 | const bcrypt = require("bcrypt") 4 | const jwt = require('jsonwebtoken') 5 | 6 | authController.post('/register', async (req, res) => { 7 | try { 8 | const isExisting = await User.findOne({email: req.body.email}) 9 | if(isExisting){ 10 | throw new Error("Already such an account. Try a different email") 11 | } 12 | 13 | const hashedPassword = await bcrypt.hash(req.body.password, 10) 14 | const newUser = await User.create({...req.body, password: hashedPassword}) 15 | 16 | const {password, ...others} = newUser._doc 17 | const token = jwt.sign({id: newUser._id}, process.env.JWT_SECRET, {expiresIn: '5h'}) 18 | 19 | return res.status(201).json({user: others, token}) 20 | } catch (error) { 21 | return res.status(500).json(error) 22 | } 23 | }) 24 | 25 | authController.post('/login', async (req, res) => { 26 | try { 27 | const user = await User.findOne({email: req.body.email}) 28 | if(!user){ 29 | throw new Error("Invalid credentials") 30 | } 31 | 32 | const comparePass = await bcrypt.compare(req.body.password, user.password) 33 | if(!comparePass){ 34 | throw new Error("Invalid credentials") 35 | } 36 | 37 | const {password, ...others} = user._doc 38 | const token = jwt.sign({id: user._id}, process.env.JWT_SECRET, {expiresIn: '5h'}) 39 | 40 | return res.status(200).json({user: others, token}) 41 | } catch (error) { 42 | return res.status(500).json(error) 43 | } 44 | }) 45 | 46 | module.exports = authController -------------------------------------------------------------------------------- /client/src/pages/register/Register.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classes from './register.module.css' 3 | import { Link, useNavigate } from 'react-router-dom' 4 | import { useState } from 'react' 5 | import { request } from '../../utils/fetchApi' 6 | import { register } from '../../redux/authSlice' 7 | import {useDispatch} from 'react-redux' 8 | 9 | const Register = () => { 10 | const [username, setUsername] = useState("") 11 | const [email, setEmail] = useState("") 12 | const [password, setPassword] = useState("") 13 | const dispatch = useDispatch() 14 | const navigate = useNavigate() 15 | 16 | const handleRegister = async (e) => { 17 | e.preventDefault() 18 | 19 | if(username === '' || email === '' || password === '') return 20 | 21 | try { 22 | const options = {'Content-Type': 'application/json'} 23 | 24 | const data = await request('/auth/register', "POST", options, {username, email, password}) 25 | dispatch(register(data)) 26 | navigate("/") 27 | } catch (error) { 28 | console.error(error) 29 | } 30 | } 31 | 32 | return ( 33 |
34 |
35 |

Register

36 |
37 | setUsername(e.target.value)}/> 38 | setEmail(e.target.value)}/> 39 | setPassword(e.target.value)}/> 40 | 41 |

Already have an account? Login

42 |
43 |
44 |
45 | ) 46 | } 47 | 48 | export default Register -------------------------------------------------------------------------------- /client/src/pages/blogDetails/blogDetails.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | min-height: calc(100vh - 60px); 3 | margin-top: 3rem; 4 | margin-bottom: 1.5rem; 5 | } 6 | 7 | .wrapper{ 8 | max-width: 1024px; 9 | margin: 0 auto; 10 | display: flex; 11 | flex-direction: column; 12 | } 13 | 14 | .goBack{ 15 | position: absolute; 16 | text-decoration: none; 17 | color: #222; 18 | top: 5rem; 19 | left: 5rem; 20 | display: flex; 21 | align-items: center; 22 | gap: 0.5rem; 23 | } 24 | 25 | .wrapper > img{ 26 | height: 500px; 27 | object-fit: cover; 28 | } 29 | 30 | .titleAndControls{ 31 | margin-top: 2rem; 32 | display: flex; 33 | justify-content: space-between; 34 | align-items: center; 35 | } 36 | 37 | .controls{ 38 | display: flex; 39 | align-items: center; 40 | gap: 1.25rem; 41 | } 42 | 43 | .controls>.edit{ 44 | cursor: pointer; 45 | padding: 0.4rem 0.75rem; 46 | border-radius: 12px; 47 | background-color: green; 48 | color: #fff; 49 | } 50 | 51 | .controls > .delete{ 52 | cursor: pointer; 53 | padding: 0.4rem 0.75rem; 54 | border-radius: 12px; 55 | background-color: #f00; 56 | color: #fff; 57 | } 58 | 59 | .like{ 60 | cursor: pointer; 61 | padding: 0.4rem 0.75rem; 62 | border-radius: 12px; 63 | background-color: blue; 64 | color: #fff; 65 | } 66 | 67 | .title{ 68 | font-size: 30px; 69 | color: #000; 70 | text-transform: capitalize; 71 | } 72 | 73 | .descAndLikesViews{ 74 | margin-top: 0.5rem; 75 | display: flex; 76 | justify-content: space-between; 77 | align-items: center; 78 | } 79 | 80 | .desc{ 81 | margin-top: 2.5rem; 82 | color: #333; 83 | font-size: 20px; 84 | line-height: 28px; 85 | letter-spacing: 0.75px; 86 | display: flex; 87 | flex-direction: column; 88 | } 89 | 90 | .desc > span{ 91 | color: #000; 92 | font-size: 22px; 93 | margin-bottom: 0.5rem; 94 | } 95 | 96 | .likesAndViews{ 97 | display: flex; 98 | flex-direction: column; 99 | font-size: 18px; 100 | align-items: flex-end; 101 | gap: 0.5rem; 102 | } 103 | 104 | .authorAndCreatedAt{ 105 | margin-top: 3.5rem; 106 | display: flex; 107 | justify-content: space-between; 108 | color: #333; 109 | font-size: 18px; 110 | } 111 | 112 | .authorAndCreatedAt > span > span{ 113 | font-size: 20px; 114 | color: #000; 115 | font-weight: bold; 116 | } -------------------------------------------------------------------------------- /client/src/components/categories/categories.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 1200px; 3 | width: 100%; 4 | } 5 | 6 | .wrapper { 7 | max-width: 1180px; 8 | height: 100%; 9 | margin: 0 auto; 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | } 14 | 15 | .wrapper>h3 { 16 | font-size: 28px; 17 | margin: 3rem 0; 18 | margin-top: 7.5rem; 19 | } 20 | 21 | .categoriesAndBlogs { 22 | display: flex; 23 | flex-direction: column; 24 | } 25 | 26 | .categories { 27 | display: flex; 28 | align-items: center; 29 | gap: 3rem; 30 | color: #333; 31 | margin-bottom: 2rem; 32 | } 33 | 34 | 35 | .category { 36 | cursor: pointer; 37 | border-radius: 12px; 38 | padding: 0.25rem 1.5rem; 39 | border: 1px solid #222; 40 | text-transform: capitalize; 41 | } 42 | 43 | .category.active { 44 | background-color: #222; 45 | color: #fff; 46 | } 47 | 48 | .blogs { 49 | display: flex; 50 | flex-wrap: wrap; 51 | align-items: center; 52 | gap: 5rem; 53 | } 54 | 55 | .blog { 56 | width: 550px; 57 | height: 350px; 58 | position: relative; 59 | margin-bottom: 2.5rem; 60 | } 61 | 62 | .blog>a>img { 63 | height: 100%; 64 | width: 100%; 65 | } 66 | 67 | .categoryAndMetadata { 68 | margin-top: 1rem; 69 | display: flex; 70 | align-items: center; 71 | gap: 1.5rem; 72 | margin-bottom: 1.25rem; 73 | } 74 | 75 | .blog .category { 76 | cursor: pointer; 77 | border-radius: 12px; 78 | padding: 0.25rem 1.5rem; 79 | border: 1px solid #222; 80 | text-transform: capitalize; 81 | } 82 | 83 | .metadata { 84 | display: flex; 85 | align-items: center; 86 | gap: 0.25rem; 87 | } 88 | 89 | .blogs>.blog>.blogData { 90 | color: #000; 91 | padding: 0 1rem; 92 | } 93 | 94 | .blogs>.blog>.blogData>h4 { 95 | font-size: 32px; 96 | margin-bottom: 0.75rem; 97 | } 98 | 99 | .blogDesc { 100 | font-size: 18px; 101 | color: #444; 102 | line-height: 29px; 103 | letter-spacing: 0.75px; 104 | margin: 1.25rem 0 1.5rem 0; 105 | } 106 | 107 | /* top right bottom left */ 108 | 109 | .authorAndCreatedAt { 110 | display: flex; 111 | justify-content: space-between; 112 | color: #444; 113 | } 114 | 115 | .authorAndCreatedAt>span>span { 116 | font-size: 17px; 117 | color: #000; 118 | font-weight: bold; 119 | } 120 | 121 | .readMore{ 122 | text-decoration: none; 123 | color: inherit; 124 | display: flex; 125 | gap: 0.5rem; 126 | align-items: center; 127 | margin-top: 1.25rem; 128 | transition: 150ms all ease; 129 | cursor: pointer; 130 | } 131 | 132 | .readMore:hover{ 133 | color: #333; 134 | } 135 | 136 | .noBlogsMessage{ 137 | text-align: center; 138 | font-size: 36px; 139 | font-weight: bold; 140 | color: #222; 141 | } -------------------------------------------------------------------------------- /client/src/components/featuredBlogs/featuredBlogs.module.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | margin-top: 3rem; 3 | height: calc(100vh - 60px); 4 | width: 100%; 5 | } 6 | 7 | .wrapper{ 8 | max-width: 1180px; 9 | margin: 0 auto; 10 | height: 100%; 11 | width: 100%; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | gap: 2rem; 16 | } 17 | 18 | .wrapper > h3{ 19 | margin-bottom: 1.5rem; 20 | font-size: 36px; 21 | color: #222; 22 | font-weight: bold; 23 | } 24 | 25 | .wrapper > .blogs{ 26 | display: flex; 27 | align-items: center; 28 | gap: 5rem; 29 | } 30 | 31 | /* left */ 32 | .left{ 33 | flex: 2; 34 | height: 100%; 35 | } 36 | 37 | .left > .mainBlog{ 38 | height: 100%; 39 | position: relative; 40 | } 41 | 42 | .left > .mainBlog > img{ 43 | height: 100%; 44 | width: 100%; 45 | object-fit: cover; 46 | position: relative; 47 | } 48 | 49 | .categoryAndMetadata{ 50 | display: flex; 51 | align-items: center; 52 | gap: 1.5rem; 53 | margin-bottom: 1.25rem; 54 | } 55 | 56 | .category{ 57 | border: 1px solid #fff; 58 | border-radius: 12px; 59 | padding: 0.25rem 1.25rem; 60 | } 61 | 62 | .metadata{ 63 | display: flex; 64 | align-items: center; 65 | gap: 0.25rem; 66 | } 67 | 68 | .left > .mainBlog > .mainBlogData{ 69 | position: absolute; 70 | bottom: 2.5rem; 71 | color: #fff; 72 | padding: 0 1rem; 73 | } 74 | 75 | .left > .mainBlog > .mainBlogData > h4{ 76 | font-size: 32px; 77 | margin-bottom: 0.75rem; 78 | } 79 | 80 | .blogDesc{ 81 | font-size: 18px; 82 | color: #e2dede; 83 | margin-bottom: 1rem; 84 | } 85 | 86 | .authorAndCreatedAt{ 87 | display: flex; 88 | justify-content: space-between; 89 | } 90 | 91 | .left > .mainBlog > .mainBlogData > .authorAndCreatedAt > span > span{ 92 | font-size: 17px; 93 | color: #efefef; 94 | font-weight: bold; 95 | } 96 | 97 | /* right */ 98 | .right{ 99 | flex: 1; 100 | height: 100%; 101 | display: flex; 102 | flex-direction: column; 103 | gap: 3rem; 104 | } 105 | 106 | .right > .secondaryBlog{ 107 | height: 50%; 108 | width: 100%; 109 | position: relative; 110 | } 111 | 112 | .right > .secondaryBlog > img{ 113 | height: 100%; 114 | width: 100%; 115 | object-fit: cover; 116 | position: relative; 117 | } 118 | 119 | .secondaryBlogData{ 120 | width: 100%; 121 | position: absolute; 122 | bottom: 2.5rem; 123 | padding: 0 1rem; 124 | } 125 | 126 | .secondaryBlogData > h4{ 127 | font-size: 28px; 128 | font-weight: bold; 129 | color: #fff; 130 | } 131 | 132 | .secondaryBlogData > .desc{ 133 | color: #fff; 134 | max-width: 275px; 135 | text-overflow: ellipsis; 136 | white-space: nowrap; 137 | overflow: hidden; 138 | margin: 1.25rem 0; 139 | } 140 | 141 | -------------------------------------------------------------------------------- /client/src/pages/create/create.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: calc(100vh - 60px); 3 | width: 100%; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | padding-top: 5rem; 8 | padding-bottom: 5rem; 9 | } 10 | 11 | .wrapper { 12 | display: flex; 13 | flex-direction: column; 14 | border: 1px solid #444; 15 | border-radius: 22px; 16 | padding: 1.5rem; 17 | } 18 | 19 | .title { 20 | text-align: center; 21 | margin-bottom: 2.5rem; 22 | font-size: 28px; 23 | font-weight: bold; 24 | } 25 | 26 | .inputWrapper { 27 | width: 100%; 28 | display: flex; 29 | align-items: center; 30 | justify-content: flex-end; 31 | margin-bottom: 0.5rem; 32 | } 33 | 34 | .inputWrapper>label { 35 | margin-right: 15px; 36 | width: 33%; 37 | } 38 | 39 | .inputWrapperImg { 40 | width: 400px; 41 | display: flex; 42 | align-items: center; 43 | margin-bottom: 0.5rem; 44 | } 45 | 46 | .inputWrapperImg>label>span { 47 | display: inline-block; 48 | padding: 0.5rem 1rem; 49 | background-color: teal; 50 | color: #fff; 51 | border: 1px solid transparent; 52 | cursor: pointer; 53 | transition: 150ms all; 54 | border-radius: 1rem; 55 | margin: 0.1rem 5rem; 56 | } 57 | 58 | .inputWrapperImg>label>span:hover { 59 | padding: 0.5rem 1rem; 60 | background-color: #fff; 61 | color: teal; 62 | border-color: teal; 63 | } 64 | 65 | .imageName { 66 | position: absolute; 67 | right: 35rem; 68 | display: flex; 69 | align-items: center; 70 | gap: 6px; 71 | font-size: 14px; 72 | } 73 | 74 | .closeIcon { 75 | cursor: pointer; 76 | font-size: 1rem; 77 | } 78 | 79 | .input { 80 | padding: 0.5rem; 81 | width: 77.5%; 82 | border-radius: 8px; 83 | border: 1px solid rgb(170, 165, 165); 84 | } 85 | 86 | .input:focus { 87 | outline: none; 88 | border-color: rgb(99, 92, 92); 89 | } 90 | 91 | .buttonWrapper { 92 | display: flex; 93 | align-items: center; 94 | justify-content: center; 95 | margin-top: 2px; 96 | } 97 | 98 | .submitBtn { 99 | white-space: nowrap; 100 | background-color: #000; 101 | color: #fff; 102 | padding: 0.75rem 1.25rem; 103 | border-radius: 12px; 104 | border: 1px solid transparent; 105 | cursor: pointer; 106 | transition: 150ms all; 107 | margin-top: 1rem; 108 | font-size: 18px; 109 | } 110 | 111 | .submitBtn:hover { 112 | background-color: #fff; 113 | color: #000; 114 | border-color: #000; 115 | } 116 | 117 | .inputWrapperSelect { 118 | width: 100%; 119 | display: flex; 120 | justify-content: space-between; 121 | align-items: center; 122 | } 123 | 124 | .inputWrapperSelect>select { 125 | outline: none; 126 | padding: 0.5rem; 127 | width: 67.5%; 128 | border-radius: 8px; 129 | border: 1px solid rgb(170, 165, 165); 130 | text-transform: capitalize; 131 | } -------------------------------------------------------------------------------- /client/src/pages/updateBlog/UpdateBlog.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useEffect } from 'react' 3 | import { useState } from 'react' 4 | import { useSelector } from 'react-redux' 5 | import { useParams, useNavigate } from 'react-router-dom' 6 | import Footer from '../../components/footer/Footer' 7 | import Navbar from '../../components/navbar/Navbar' 8 | import { request } from '../../utils/fetchApi' 9 | import classes from './updateBlog.module.css' 10 | 11 | const UpdateBlog = () => { 12 | const [blogDetails, setBlogDetails] = useState("") 13 | const [title, setTitle] = useState("") 14 | const [desc, setDesc] = useState("") 15 | const [category, setCategory] = useState("") 16 | const { token } = useSelector((state) => state.auth) 17 | const navigate = useNavigate() 18 | const { id } = useParams() 19 | 20 | 21 | const categories = [ 22 | 'nature', 23 | 'music', 24 | 'travel', 25 | 'design', 26 | 'programming', 27 | 'fun', 28 | 'fashion' 29 | ] 30 | 31 | useEffect(() => { 32 | const fetchBlogDetails = async () => { 33 | try { 34 | const options = {'Authorization': `Bearer ${token}`} 35 | const data = await request(`/blog/find/${id}`, 'GET', options) 36 | setBlogDetails(data) 37 | setTitle(data.title) 38 | setDesc(data.desc) 39 | setCategory(data.category) 40 | 41 | } catch (error) { 42 | console.error(error) 43 | } 44 | } 45 | fetchBlogDetails() 46 | }, [id]) 47 | 48 | 49 | const handleUpdateBlog = async (e) => { 50 | e.preventDefault() 51 | 52 | try { 53 | const options = { 54 | 'Content-Type': 'application/json', 55 | 'Authorization': `Bearer ${token}` 56 | } 57 | await request(`/blog/updateBlog/${id}`, "PUT", options, {title, desc, category}) 58 | navigate(`/blogDetails/${id}`) 59 | } catch (error) { 60 | console.error(error) 61 | } 62 | } 63 | 64 | return ( 65 | <> 66 | 67 |
68 |
69 |

Update Blog

70 |
71 | setTitle(e.target.value)} 76 | /> 77 | setDesc(e.target.value)} 82 | /> 83 | 88 | 89 |
90 |
91 |
92 |