├── README.md ├── client ├── .eslintrc.cjs ├── .gitignore ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ └── vite.svg ├── src │ ├── App.jsx │ ├── Assets │ │ └── Images │ │ │ ├── black-bg.png │ │ │ ├── blog.png │ │ │ ├── blog1.png │ │ │ ├── blog2.png │ │ │ ├── blog3.png │ │ │ ├── code.jpg │ │ │ ├── community.png │ │ │ ├── emma.jpg │ │ │ ├── endless-constellation.png │ │ │ ├── img1.png │ │ │ ├── img2.svg │ │ │ ├── img3.png │ │ │ ├── logo.png │ │ │ ├── nobg.png │ │ │ └── notfound.png │ ├── Components │ │ ├── Auth │ │ │ ├── ForgetPassword.jsx │ │ │ ├── Login.jsx │ │ │ ├── ResetPassword.jsx │ │ │ └── SignUp.jsx │ │ ├── Dashboard │ │ │ ├── Dashboard.jsx │ │ │ ├── EditPost.jsx │ │ │ ├── EditProfile.jsx │ │ │ ├── FloatingButton.jsx │ │ │ ├── Posts.jsx │ │ │ ├── ProfilePic.jsx │ │ │ ├── Profiles.jsx │ │ │ ├── ReadPostPage.jsx │ │ │ ├── SearchBox.jsx │ │ │ ├── SideMenu.jsx │ │ │ ├── Subscriber.jsx │ │ │ ├── UserPost.jsx │ │ │ └── Write.jsx │ │ ├── Drawer.jsx │ │ ├── Footer.jsx │ │ ├── Home.jsx │ │ ├── Navbar.jsx │ │ ├── NewHome.jsx │ │ ├── NotFound.jsx │ │ ├── TimeAgo.jsx │ │ ├── TopMenu.jsx │ │ ├── UseGet.jsx │ │ ├── index.html │ │ └── redux │ │ │ ├── AccessTokenSlice.jsx │ │ │ ├── MyPostSlice.jsx │ │ │ ├── PendingSlice.jsx │ │ │ ├── PostSlice.jsx │ │ │ ├── UserDataSlice.jsx │ │ │ ├── UserPostSlice.jsx │ │ │ ├── UserSlice.jsx │ │ │ └── store.jsx │ ├── color.txt │ ├── index.css │ └── main.jsx ├── tailwind.config.js ├── vercel.json └── vite.config.js └── server ├── .env.sample ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── nest-cli.json ├── package-lock.json ├── package.json ├── public └── avatars │ └── 402a5f16-9272-416c-a3f8-fa4ae236833b.webp ├── src ├── app.module.ts ├── config │ ├── database.config.ts │ ├── index.ts │ └── jwt.config.ts ├── main.ts └── modules │ ├── auth │ ├── auth.controller.ts │ ├── auth.module.ts │ ├── auth.service.ts │ ├── dto │ │ ├── login-user.dto.ts │ │ ├── password-reset.dto.ts │ │ ├── resend-code.dto.ts │ │ └── verifyUserData.dto.ts │ ├── guards │ │ ├── jwt-guard.guard.ts │ │ ├── local-auth.guard.ts │ │ └── roles.guard.ts │ └── strategies │ │ ├── jwt.strategy.ts │ │ └── local.strategy.ts │ ├── category │ ├── category.controller.ts │ ├── category.module.ts │ ├── category.service.ts │ ├── dto │ │ ├── create-category.dto.ts │ │ └── update-category.dto.ts │ └── entities │ │ └── category.entity.ts │ ├── comment │ ├── comment.controller.ts │ ├── comment.module.ts │ ├── comment.service.ts │ ├── dto │ │ ├── create-comment.dto.ts │ │ └── update-comment.dto.ts │ ├── entities │ │ └── comment.entity.ts │ └── interface │ │ └── comment.interface.ts │ ├── common │ ├── decorators │ │ └── role.decorator.ts │ ├── enum │ │ ├── getPost.enum.ts │ │ ├── role.enum.ts │ │ └── upload-folder.enum.ts │ ├── interface │ │ └── index.interface.ts │ ├── utils..ts │ └── validators │ │ └── image-pipe.pipe.ts │ ├── otp │ ├── dto │ │ └── verify-otp.ts │ ├── entities │ │ └── otp.entity.ts │ ├── otp.controller.ts │ ├── otp.module.ts │ └── otp.service.ts │ ├── post │ ├── dto │ │ ├── create-post.dto.ts │ │ └── update-post.dto.ts │ ├── entities │ │ └── post.entity.ts │ ├── post.controller.ts │ ├── post.module.ts │ ├── post.service.ts │ └── slug.provider.ts │ ├── search │ ├── search.controller.ts │ ├── search.dto.ts │ ├── search.module.ts │ └── search.service.ts │ ├── uploads │ ├── uploads.controller.ts │ ├── uploads.module.ts │ └── uploads.service.ts │ └── user │ ├── dto │ ├── create-user.dto.ts │ ├── login-user.dto.ts │ ├── update-user-role.dto.ts │ ├── update-user-sensitive.dto.ts │ └── update-user.dto.ts │ ├── entities │ └── user.entity.ts │ ├── user.controller.ts │ ├── user.module.ts │ └── user.service.ts ├── test ├── app.e2e-spec.ts └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 | # Blog Website With TypeScript, NestJS, PostgreSQL, TypeORM, and SwaggerJS 2 | 3 | This is a blog Post Website Application created with with NodeJS and React, also used some technologies along side with it, which are: 4 | 5 | - Typescript: for type checking in javascript 6 | - NestJS: a nodejs framework which was built on top express a nodejs light weight framework. 7 | - PostgreSQL: the RDBMS that was used to store the blog post, comments and all in the database. 8 | - TypeORM: the ORM which is used to connect the database. 9 | - SwaggerJS: for the API documentation. 10 | 11 | ## Database Design 12 | 13 | - [Database Design For The Blog](https://drawsql.app/teams/oluwatosin/diagrams/blog-database-design/embed) 14 | 15 | ## Getting Started 16 | 17 | ``` 18 | $ git clone https://github.com/dkrest1/My-Blog.git 19 | $ cd server 20 | ``` 21 | 22 | ## Create a `.env` file and put in the right credentials 23 | 24 | ``` 25 | $ cp .env.sample .env 26 | ``` 27 | 28 | ## Installation 29 | 30 | ``` 31 | $ npm install 32 | ``` 33 | 34 | ### API Documentation 35 | 36 | - http://localhost:3000/api#/ 37 | -------------------------------------------------------------------------------- /client/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, es2020: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:react/recommended', 6 | 'plugin:react/jsx-runtime', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 10 | settings: { react: { version: '18.2' } }, 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': 'warn', 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /client/.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 | .env 12 | dist 13 | dist-ssr 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Blog App 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@fortawesome/fontawesome-svg-core": "^6.4.0", 14 | "@fortawesome/free-regular-svg-icons": "^6.4.0", 15 | "@fortawesome/free-solid-svg-icons": "^6.4.0", 16 | "@fortawesome/react-fontawesome": "^0.2.0", 17 | "@heroicons/react": "^2.0.18", 18 | "@material-tailwind/react": "^2.0.0", 19 | "@reduxjs/toolkit": "^1.9.5", 20 | "ahooks": "^3.7.7", 21 | "axios": "^1.4.0", 22 | "classnames": "^2.3.2", 23 | "date-fns": "^2.30.0", 24 | "draft-js": "^0.11.7", 25 | "draftjs-utils": "^0.10.2", 26 | "form-data": "^4.0.0", 27 | "html-to-draftjs": "^1.5.0", 28 | "immutable": "^4.3.0", 29 | "js-cookie": "^3.0.5", 30 | "react": "^18.2.0", 31 | "react-dom": "^18.2.0", 32 | "react-loading": "^2.0.3", 33 | "react-redux": "^8.0.5", 34 | "react-router-dom": "^6.11.1", 35 | "react-toastify": "^9.1.3", 36 | "react-use": "^17.4.0", 37 | "react-use-click-away": "^1.0.10", 38 | "redux": "^4.2.1" 39 | }, 40 | "devDependencies": { 41 | "@types/react": "^18.0.28", 42 | "@types/react-dom": "^18.0.11", 43 | "@vitejs/plugin-react": "^4.0.0", 44 | "autoprefixer": "^10.4.14", 45 | "eslint": "^8.38.0", 46 | "eslint-plugin-react": "^7.32.2", 47 | "eslint-plugin-react-hooks": "^4.6.0", 48 | "eslint-plugin-react-refresh": "^0.3.4", 49 | "postcss": "^8.4.23", 50 | "tailwindcss": "^3.3.2", 51 | "vite": "^4.3.5" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/App.jsx: -------------------------------------------------------------------------------- 1 | import {BrowserRouter, Routes, Route} from 'react-router-dom'; 2 | import { useState } from 'react'; 3 | import { Login } from './Components/Auth/Login'; 4 | import Home from './Components/Home'; 5 | import SignUpForm from './Components/Auth/SignUp'; 6 | import Dashboard from './Components/Dashboard/Dashboard'; 7 | import Footer from './Components/Footer'; 8 | import Profiles from './Components/Dashboard/Profiles'; 9 | import WritingPage from './Components/Dashboard/Write'; 10 | import Navbar from './Components/Navbar'; 11 | import { useSelector, useDispatch } from 'react-redux'; 12 | import ReadPostPage from './Components/Dashboard/ReadPostPage'; 13 | import { useEffect } from 'react'; 14 | import axios from 'axios'; 15 | import { token } from './Components/redux/AccessTokenSlice'; 16 | import { user, getUser } from './Components/redux/UserDataSlice'; 17 | import { EditPost } from './Components/Dashboard/EditPost'; 18 | import { ForgetPassword } from './Components/Auth/ForgetPassword'; 19 | import { ResetPassword } from './Components/Auth/ResetPassword'; 20 | import ReactLoading from 'react-loading' 21 | import { pending, getPending } from './Components/redux/PendingSlice'; 22 | import Cookies from 'js-cookie'; 23 | import NotFound from './Components/NotFound'; 24 | import NewHome from './Components/NewHome'; 25 | 26 | function App() { 27 | const accessToken = useSelector(token) 28 | const userData=useSelector(user) 29 | const dispatch = useDispatch() 30 | const initialState = localStorage.getItem("profilePic") || null; 31 | const [selectedFile, setSelectedFile] = useState(initialState); 32 | const isPending= useSelector(pending) 33 | useEffect(()=>{ 34 | if(!userData){ 35 | const headers={ 36 | Authorization: `Bearer ${accessToken}` 37 | } 38 | dispatch(getPending(true)) 39 | axios.get('http://localhost:3000/user/me', {headers}) 40 | .then((response)=>{ 41 | dispatch(getUser(response.data)) 42 | dispatch(getPending(false)) 43 | }) 44 | .catch((error)=>{ 45 | console.log(error) 46 | dispatch(getPending(false)) 47 | }) 48 | } 49 | },[userData]) 50 | useEffect(()=>{ 51 | if(!userData && accessToken){ 52 | Cookies.remove('token') 53 | } 54 | }) 55 | 56 | return ( 57 |
58 | 59 | 60 | {isPending ? ( 61 |
62 | 69 |
70 | ) : ( 71 | 72 | } /> 73 | {/* } /> */} 74 | } /> 75 | } /> 76 | } 79 | /> 80 | 88 | } 89 | /> 90 | } 93 | /> 94 | } /> 95 | } /> 96 | } /> 97 | } /> 98 | }/> 99 | 100 | )} 101 |
104 | ); 105 | } 106 | 107 | export default App; 108 | -------------------------------------------------------------------------------- /client/src/Assets/Images/black-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/black-bg.png -------------------------------------------------------------------------------- /client/src/Assets/Images/blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/blog.png -------------------------------------------------------------------------------- /client/src/Assets/Images/blog1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/blog1.png -------------------------------------------------------------------------------- /client/src/Assets/Images/blog2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/blog2.png -------------------------------------------------------------------------------- /client/src/Assets/Images/blog3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/blog3.png -------------------------------------------------------------------------------- /client/src/Assets/Images/code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/code.jpg -------------------------------------------------------------------------------- /client/src/Assets/Images/community.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/community.png -------------------------------------------------------------------------------- /client/src/Assets/Images/emma.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/emma.jpg -------------------------------------------------------------------------------- /client/src/Assets/Images/endless-constellation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/endless-constellation.png -------------------------------------------------------------------------------- /client/src/Assets/Images/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/img1.png -------------------------------------------------------------------------------- /client/src/Assets/Images/img2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/src/Assets/Images/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/img3.png -------------------------------------------------------------------------------- /client/src/Assets/Images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/logo.png -------------------------------------------------------------------------------- /client/src/Assets/Images/nobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/nobg.png -------------------------------------------------------------------------------- /client/src/Assets/Images/notfound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkrest1/blog-api/9383906dce8efbf09a1d3f963c6d81eb464c6d3e/client/src/Assets/Images/notfound.png -------------------------------------------------------------------------------- /client/src/Components/Auth/ForgetPassword.jsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import React, { useState } from 'react' 3 | import {Button, Dialog, DialogBody, DialogFooter, DialogHeader} from '@material-tailwind/react' 4 | import { ResetPassword } from './ResetPassword' 5 | 6 | export const ForgetPassword = () => { 7 | const [userEmail, setEmail] = useState( 8 | { 9 | email: '' 10 | } 11 | ) 12 | const [error, setError] = useState('') 13 | const [open, setOpen] = useState(false) 14 | 15 | const handleForgetPassword=(event)=>{ 16 | event.preventDefault() 17 | if(userEmail.email===''){ 18 | setError("Please enter your email to recover your password") 19 | }else{ 20 | axios.post('http://localhost:3000/auth/forget/password') 21 | .then((response)=>{ 22 | console.log(response) 23 | if(response.statusText==='OK'){ 24 | setOpen(true) 25 | } 26 | }) 27 | .catch((error)=>{ 28 | console.log(error) 29 | 30 | }) 31 | } 32 | } 33 | // console.log(userEmail) 34 | return ( 35 |
36 |

Forgot Password Recovery

37 |
38 | setOpen(!open)} size='xl'> 39 | Password Reset 40 | 41 | A link to reset password has been sent to your mail. 42 | 43 | 44 | 47 | 48 | 49 |
50 |
51 |
52 | 55 | { 62 | setEmail({email: event.target.value}) 63 | setError('')}} 64 | /> 65 |

{error}

66 |
67 | 69 |
70 | 71 |
72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /client/src/Components/Auth/Login.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { React, useState } from "react"; 3 | import { Link, useNavigate } from "react-router-dom"; 4 | import { ToastContainer, toast } from "react-toastify"; 5 | import { token } from "../redux/AccessTokenSlice"; 6 | import { useSelector, useDispatch } from "react-redux"; 7 | import { getToken } from "../redux/AccessTokenSlice"; 8 | import { user } from "../redux/UserDataSlice"; 9 | import { getUser } from "../redux/UserDataSlice"; 10 | import Cookies from "js-cookie"; 11 | import { useEffect } from "react"; 12 | import { getPending, pending } from "../redux/PendingSlice"; 13 | 14 | export const Login = () => { 15 | const userData = useSelector(user); 16 | const accessToken = useSelector(token); 17 | const isPending = useSelector(pending); 18 | const dispatch = useDispatch(); 19 | 20 | const [formValues, setFormValues] = useState({ 21 | email: "", 22 | password: "", 23 | }); 24 | const navigateTo = useNavigate(); 25 | const notify = (status) => toast(status); 26 | const handleInputChange = (event) => { 27 | const { name, value } = event.target; 28 | setFormValues((prevValues) => ({ ...prevValues, [name]: value })); 29 | }; 30 | const handleLoginSubmit = (event) => { 31 | event.preventDefault(); 32 | dispatch(getPending(true)); 33 | axios 34 | .post("http://localhost:3000/auth/login", formValues) 35 | .then(function (response) { 36 | if (response.statusText === "Created") { 37 | let data = response.data.access_token; 38 | dispatch(getToken(data)); 39 | // setToken(data) 40 | Cookies.set("token", data, { expires: 1 }); 41 | const headers = { 42 | Authorization: `Bearer ${data}`, 43 | }; 44 | axios 45 | .get("http://localhost:3000/user/me", { headers }) 46 | .then(function (response) { 47 | if (response.statusText === "OK") { 48 | let data = response.data; 49 | dispatch(getUser(data)); 50 | let status = "Login Successful!"; 51 | notify(status); 52 | setTimeout(() => { 53 | navigateTo("/"); 54 | }, 2000); 55 | dispatch(getPending(false)); 56 | } 57 | }) 58 | .catch(function (error) { 59 | console.log(error); 60 | let status = error.response.data.message; 61 | notify(status); 62 | dispatch(getPending(false)); 63 | }); 64 | } 65 | dispatch(getPending(false)); 66 | }) 67 | .catch(function (error) { 68 | console.log(error); 69 | let status = error.response.data.message; 70 | notify(status); 71 | dispatch(getPending(false)); 72 | // setIsPending(false) 73 | }); 74 | }; 75 | const handleForgotPassword = (event) => { 76 | event.preventDefault(); 77 | navigateTo("/forgot-password"); 78 | }; 79 | // console.log(userData) 80 | return ( 81 |
82 |
83 |
84 | 85 |

86 | Login 87 |

88 |
89 |
90 | 96 | 103 |
104 |
105 | 111 | 118 | 124 |
125 |
126 | 134 | 135 | 141 | 142 |
143 |
144 |

145 | Don't have an account?{" "} 146 | 147 | Sign Up here 148 | 149 |

150 |
151 |
152 |
153 |
154 |
155 | ); 156 | }; 157 | -------------------------------------------------------------------------------- /client/src/Components/Auth/ResetPassword.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useState } from 'react'; 3 | 4 | export const ResetPassword = () => { 5 | const [formValues, setFormValues] = useState({ 6 | email: "", 7 | password: "", 8 | confirmPassword: "" 9 | }); 10 | const [error, setError] = useState('') 11 | const handleInputChange =(event)=>{ 12 | const {name, value} = event.target 13 | setFormValues((prevValues)=>({...prevValues, [name]:value})) 14 | setError((prevValues)=>({...prevValues, [name]:''})) 15 | } 16 | 17 | const handleResetPassword =(event)=>{ 18 | event.preventDefault() 19 | console.log('clicked') 20 | if(formValues.email===''){ 21 | setError('Please enter your email') 22 | } else if(formValues.password===''){ 23 | setError('please enter new password') 24 | } else if(formValues.confirmPassword===''){ 25 | setError('Please enter new password again') 26 | } 27 | else if(formValues.password !==formValues.confirmPassword){ 28 | setError("Password do not match!") 29 | } 30 | else{ 31 | console.log('good to go!!') 32 | } 33 | } 34 | return ( 35 |
36 |

Login

37 |
38 |
39 | 42 | 49 |

{error}

50 |
51 |
52 | 55 | 62 |

{error}

63 |
64 |
65 | 68 | 79 |

{error}

80 |
81 |
82 | 90 |
91 |
92 |
93 | ) 94 | } 95 | -------------------------------------------------------------------------------- /client/src/Components/Dashboard/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from "react"; 2 | import {useClickAway} from 'react-use' 3 | import { NavLink, useNavigate } from "react-router-dom"; 4 | import { useSelector, useDispatch } from "react-redux"; 5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 6 | import { faSearch, faPencilSquare } from "@fortawesome/free-solid-svg-icons"; 7 | import Posts from "./Posts"; 8 | import { user } from "../redux/UserDataSlice"; 9 | import { token } from "../redux/AccessTokenSlice"; 10 | import { getPosts } from "../redux/PostSlice"; 11 | import { post } from "../redux/PostSlice"; 12 | import FloatingButton from "./FloatingButton"; 13 | import SearchBox from "./SearchBox"; 14 | import UseGet from "../UseGet"; 15 | import { toast, ToastContainer } from "react-toastify"; 16 | 17 | export default function Dashboard() { 18 | const accessToken = useSelector(token) 19 | const postFetched = useSelector(post) 20 | const dispatch = useDispatch() 21 | const [isOpen, setIsOpen] = useState(false); 22 | 23 | const {fetchedData, isPending, setIsPending} = UseGet("http://localhost:3000/post?page=1&limit=0", accessToken) 24 | useEffect(()=>{ 25 | fetchedData &&( 26 | dispatch(getPosts(fetchedData)), 27 | setIsPending(false) 28 | ) 29 | },[fetchedData]) 30 | 31 | const ref = useRef(null); 32 | useClickAway(ref,()=>{ 33 | setIsOpen(false); 34 | }); 35 | 36 | const [searchItem, setsearchItem] = useState('') 37 | const [filtered, setFiltered] = useState([]) 38 | const [searchError, setSearchError] = useState('') 39 | const notify = ()=>toast(searchError) 40 | 41 | const handleInputChange =(event)=>{ 42 | setsearchItem(event.target.value) 43 | } 44 | const handleSearchSubmit =(event)=>{ 45 | event.preventDefault() 46 | if(searchItem===''){ 47 | setFiltered([]) 48 | } else if(filteredItem.length < 1 ){ 49 | setSearchError("No matched post") 50 | notify() 51 | } 52 | else{ 53 | setFiltered(filteredItem) 54 | } 55 | } 56 | let filteredItem 57 | if (searchItem){ 58 | filteredItem = postFetched.filter((obj)=>{ 59 | let searched = obj.title.toLowerCase().includes(searchItem.toLowerCase()) || obj.content.toLowerCase().includes(searchItem.toLowerCase()) || obj.user.firstname.toLowerCase().includes(searchItem.toLowerCase()) || obj.user.lastname.toLowerCase().includes(searchItem.toLowerCase()) 60 | return(searched) 61 | }) 62 | } 63 | 64 | return ( 65 |
66 | {/* {accessToken ? 67 | <> */} 68 |
69 | 70 |
71 |
72 |

Latest Posts

73 |
74 | {/* */} 75 |
76 | 84 |
85 |
86 |
87 | 88 | Post 89 | 90 |
91 |
92 |
93 |
94 | { 95 | filtered.length > 0 ? 96 | 97 | : 98 | 99 | } 100 |
101 |
102 | 103 |
104 |
105 | {/* : gotoLogin()} */} 106 |
107 | ) 108 | } 109 | -------------------------------------------------------------------------------- /client/src/Components/Dashboard/EditPost.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useEffect, useState } from 'react' 3 | import { useParams, useNavigate } from 'react-router-dom' 4 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 5 | import { faPenToSquare, faPlus, } from '@fortawesome/free-solid-svg-icons' 6 | import { user } from '../redux/UserDataSlice' 7 | import { post } from '../redux/PostSlice' 8 | import { nanoid } from '@reduxjs/toolkit' 9 | import axios from 'axios' 10 | import { ToastContainer, toast } from 'react-toastify' 11 | import { token } from '../redux/AccessTokenSlice' 12 | import { useSelector } from 'react-redux' 13 | import { PhotoIcon } from '@heroicons/react/24/solid' 14 | import UseGet from '../UseGet' 15 | import Subscriber from './Subscriber' 16 | 17 | export const EditPost = () => { 18 | const {id} = useParams() 19 | const accessToken = useSelector(token) 20 | let edit = JSON.parse(localStorage.getItem('editPost')) 21 | const userData = useSelector(user) 22 | const navigateTo = useNavigate(); 23 | const [postValues, setPostValues] = useState({ 24 | title: edit ? edit.title : '', 25 | content: edit ? edit.content : '' , 26 | published: false 27 | }) 28 | useEffect(()=>{ 29 | if(!accessToken){ 30 | const gotoLogin =()=>{ 31 | navigateTo('/login') 32 | } 33 | gotoLogin() 34 | } 35 | }) 36 | const handleInputChange =(event)=>{ 37 | const {name, value} = event.target 38 | setPostValues((prevValues)=>({...prevValues, [name]: value})) 39 | } 40 | let status 41 | const notify =()=> toast(status) 42 | 43 | const onPublishPost =(event)=>{ 44 | event.preventDefault() 45 | if(!postValues.title && !postValues.content){ 46 | console.log('Title or Content cannot be empty!') 47 | }else{ 48 | const headers = { 49 | Authorization: `Bearer ${accessToken}`, 50 | 'content-type': 'application/json', 51 | } 52 | axios.patch(`http://localhost:3000/post/${id}`, postValues, {headers}) 53 | .then((response)=>{ 54 | if(response.statusText='OK'){ 55 | status='Post updated successfully!' 56 | notify() 57 | setPostValues({title:'', content:''}) 58 | setTimeout(() => { 59 | navigateTo('/dashboard') 60 | }, 2000); 61 | } 62 | }) 63 | .catch((err)=>{ 64 | console.log(err) 65 | status="Something happened, couldn't update post." 66 | notify() 67 | }) 68 | } 69 | } 70 | const fileInputRef = React.createRef(); 71 | const [storyImage, setStoryImage] = useState(null) 72 | 73 | const handleFileUpload = (event) => { 74 | const file = event.target.files[0]; 75 | const imageFile = URL.createObjectURL(file) 76 | setStoryImage(imageFile) 77 | }; 78 | const hanldeAddImage = () => { 79 | // Trigger click event on the hidden file input element 80 | if (fileInputRef.current) { 81 | fileInputRef.current.click(); 82 | } 83 | }; 84 | const handleRemoveImage =()=>{ 85 | setStoryImage(null) 86 | } 87 | 88 | return ( 89 |
90 |
91 |
92 |
93 |

94 | Edit Post 95 |

96 | 97 |
98 |
99 | 106 |
107 |
108 |
109 |
110 | 116 | 123 |
124 |
125 | 131 |
132 |