├── .DS_Store ├── README.md ├── Screenshots ├── Homepage.png └── Question.png ├── client ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── AllRoutes.jsx │ ├── App.css │ ├── App.js │ ├── Pages │ ├── AskQuestion │ │ ├── AskQuestion.css │ │ └── AskQuestion.jsx │ ├── Auth │ │ ├── AboutAuth.jsx │ │ ├── Auth.css │ │ └── Auth.jsx │ ├── Home │ │ └── Home.jsx │ ├── Questions │ │ ├── DisplayAnswer.jsx │ │ ├── DisplayQuestion.jsx │ │ ├── Questions.css │ │ ├── Questions.jsx │ │ └── QuestionsDetails.jsx │ ├── Tags │ │ ├── Tags.css │ │ ├── Tags.jsx │ │ ├── TagsList.jsx │ │ └── tagList.js │ ├── UserProfile │ │ ├── EditProfileForm.jsx │ │ ├── ProfileBio.jsx │ │ ├── UserProfile.jsx │ │ └── UsersProfile.css │ └── Users │ │ ├── User.jsx │ │ ├── Users.css │ │ ├── Users.jsx │ │ └── UsersList.jsx │ ├── actions │ ├── auth.js │ ├── currentUser.js │ ├── question.js │ └── users.js │ ├── api │ └── index.js │ ├── assets │ ├── Globe.svg │ ├── bars-solid.svg │ ├── blacklogo.svg │ ├── comment-alt-solid.svg │ ├── icon.png │ ├── logo.png │ ├── pen-solid.svg │ ├── search-solid.svg │ ├── sort-down.svg │ └── sort-up.svg │ ├── components │ ├── Avatar │ │ └── Avatar.jsx │ ├── HomeMainbar │ │ ├── HomeMainbar.css │ │ ├── HomeMainbar.jsx │ │ ├── QuestionList.jsx │ │ └── Questions.jsx │ ├── LeftSidebar │ │ ├── LeftSidebar.css │ │ └── LeftSidebar.jsx │ ├── Navbar │ │ ├── Navbar.css │ │ └── Navbar.jsx │ └── RightSidebar │ │ ├── RightSidebar.css │ │ ├── RightSidebar.jsx │ │ ├── Widget.jsx │ │ └── WidgetTags.jsx │ ├── index.css │ ├── index.js │ └── reducers │ ├── auth.js │ ├── currentUser.js │ ├── index.js │ ├── questions.js │ └── users.js └── server ├── .DS_Store ├── .env.example ├── .gitignore ├── connectMongoDb.js ├── controllers ├── Answers.js ├── Questions.js ├── auth.js └── users.js ├── index.js ├── middleware └── auth.js ├── models ├── Questions.js └── auth.js ├── package-lock.json ├── package.json ├── routes ├── Answers.js ├── Questions.js └── users.js └── vercel.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Manoj-Athi/Stack-overflow/4cffe2048c4b21cdb908a00bb65c70cd20a79312/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stack Overflow Clone 2 | 3 | This website is a question forum and made to look like Stack Overflow. 4 | 5 | ## Technologies used: 6 | 7 | - React js 8 | - Node js 9 | - Express js 10 | - MongoDb 11 | - Redux 12 | - Json web token and more 13 | 14 | ## How to use? 15 | 16 | Fork and clone the repo and follow the below steps: 17 | 18 | - Install Node.js 19 | - Open client and server directories in VS code 20 | - Install Dependencies using the command 21 | 22 | ``` 23 | npm install 24 | ``` 25 | 26 | - Start App using the command 27 | 28 | ``` 29 | npm start 30 | ``` 31 | 32 | ## Pics of the application 33 | 34 | 35 | 36 | 37 | ## Live Link for the website: 38 | 39 | [Click here](https://stack-overflow-manoj.netlify.app/) 40 | 41 | ## Contributions 42 | 43 | Contributions are always welcomed. Anyone can contribute to this project. Contributors will be given credits. 44 | 45 | ## Credits 46 | 47 | This website uses icons from font awesome. 48 | 49 | ## Reminder for Null Class Students 50 | 51 | If you are a Null Class student the repo is updated from the old one. If you need the old repo you can checkout the branch I have created. 52 | 53 | Made with ❤️ by [@Manoj](https://twitter.com/Manoj_Athi) 54 | -------------------------------------------------------------------------------- /Screenshots/Homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Manoj-Athi/Stack-overflow/4cffe2048c4b21cdb908a00bb65c70cd20a79312/Screenshots/Homepage.png -------------------------------------------------------------------------------- /Screenshots/Question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Manoj-Athi/Stack-overflow/4cffe2048c4b21cdb908a00bb65c70cd20a79312/Screenshots/Question.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client2", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@fortawesome/fontawesome-svg-core": "^6.3.0", 7 | "@fortawesome/free-solid-svg-icons": "^6.3.0", 8 | "@fortawesome/react-fontawesome": "^0.2.0", 9 | "@testing-library/jest-dom": "^5.16.5", 10 | "@testing-library/react": "^13.4.0", 11 | "@testing-library/user-event": "^13.5.0", 12 | "axios": "^1.3.3", 13 | "jwt-decode": "^3.1.2", 14 | "moment": "^2.29.4", 15 | "react": "^18.2.0", 16 | "react-copy-to-clipboard": "^5.1.0", 17 | "react-dom": "^18.2.0", 18 | "react-redux": "^8.0.5", 19 | "react-router-dom": "^6.8.1", 20 | "react-scripts": "5.0.1", 21 | "redux": "^4.2.1", 22 | "redux-thunk": "^2.4.2", 23 | "web-vitals": "^2.1.4" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": [ 33 | "react-app", 34 | "react-app/jest" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Manoj-Athi/Stack-overflow/4cffe2048c4b21cdb908a00bb65c70cd20a79312/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Manoj-Athi/Stack-overflow/4cffe2048c4b21cdb908a00bb65c70cd20a79312/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Manoj-Athi/Stack-overflow/4cffe2048c4b21cdb908a00bb65c70cd20a79312/client/public/logo512.png -------------------------------------------------------------------------------- /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/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/src/AllRoutes.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Routes, Route } from "react-router-dom"; 3 | 4 | import Home from "./Pages/Home/Home"; 5 | import Auth from "./Pages/Auth/Auth"; 6 | import Questions from "./Pages/Questions/Questions"; 7 | import AskQuestion from "./Pages/AskQuestion/AskQuestion"; 8 | import DisplayQuestion from "./Pages/Questions/DisplayQuestion"; 9 | import Tags from "./Pages/Tags/Tags"; 10 | import Users from "./Pages/Users/Users"; 11 | import UserProfile from "./Pages/UserProfile/UserProfile"; 12 | 13 | const AllRoutes = ({ slideIn, handleSlideIn }) => { 14 | return ( 15 | 16 | } 19 | /> 20 | } /> 21 | } /> 22 | } 25 | /> 26 | 30 | } 31 | /> 32 | } 35 | /> 36 | } 39 | /> 40 | 44 | } 45 | /> 46 | 47 | ); 48 | }; 49 | 50 | export default AllRoutes; 51 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | .home-container-1 { 2 | min-height: 100vh; 3 | max-width: 1250px; 4 | width: 100%; 5 | display: flex; 6 | justify-content: space-between; 7 | margin: 0% auto; 8 | } 9 | 10 | .home-container-2 { 11 | max-width: 1100px; 12 | width: calc(100% - 164px); 13 | padding: 24px; 14 | box-sizing: border-box; 15 | } 16 | 17 | @media screen and (max-width: 760px) { 18 | .home-container-2 { 19 | max-width: 100%; 20 | width: 100%; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter as Router } from "react-router-dom"; 2 | import { useEffect, useState } from "react"; 3 | import { useDispatch } from "react-redux"; 4 | import "./App.css"; 5 | import Navbar from "./components/Navbar/Navbar"; 6 | import AllRoutes from "./AllRoutes"; 7 | import { fetchAllQuestions } from "./actions/question"; 8 | import { fetchAllUsers } from "./actions/users"; 9 | 10 | function App() { 11 | const dispatch = useDispatch(); 12 | 13 | useEffect(() => { 14 | dispatch(fetchAllQuestions()); 15 | dispatch(fetchAllUsers()); 16 | }, [dispatch]); 17 | 18 | const [slideIn, setSlideIn] = useState(true); 19 | 20 | useEffect(() => { 21 | if (window.innerWidth <= 760) { 22 | setSlideIn(false); 23 | } 24 | }, []); 25 | 26 | const handleSlideIn = () => { 27 | if (window.innerWidth <= 760) { 28 | setSlideIn((state) => !state); 29 | } 30 | }; 31 | 32 | return ( 33 |
34 | 35 | 36 | 37 | 38 |
39 | ); 40 | } 41 | 42 | export default App; 43 | -------------------------------------------------------------------------------- /client/src/Pages/AskQuestion/AskQuestion.css: -------------------------------------------------------------------------------- 1 | .ask-question { 2 | min-height: 80vh; 3 | background-color: #f1f2f3; 4 | } 5 | 6 | .ask-ques-container { 7 | margin: auto; 8 | padding: 0px 20px 20px 20px; 9 | max-width: 1200px; 10 | } 11 | 12 | .ask-ques-container h1 { 13 | padding: 80px 0px 20px 0px; 14 | } 15 | 16 | .ask-ques-container form .ask-form-container { 17 | padding: 20px; 18 | background-color: white; 19 | border-radius: 3px; 20 | box-shadow: 0 10px 25px rgb(0 0 0 / 5%), 0 20px 48px rgb(0 0 0 / 5%), 21 | 0 1px 4px rgb(0 0 0 / 10%); 22 | } 23 | 24 | .ask-form-container label h4 { 25 | margin-bottom: 0%; 26 | } 27 | 28 | .ask-form-container label p { 29 | margin: 0%; 30 | font-size: 13px; 31 | padding: 3px 0px; 32 | } 33 | 34 | .ask-form-container label input, 35 | .ask-form-container label textarea { 36 | padding: 10px; 37 | border: solid 1px #0000003e; 38 | font-family: "Roboto", sans-serif; 39 | width: calc(100% - 20px); 40 | resize: none; 41 | } 42 | 43 | .review-btn { 44 | margin: 50px 0px; 45 | padding: 10px; 46 | background-color: #009dff; 47 | border: solid 1px #009dff; 48 | color: white; 49 | border-radius: 4px; 50 | cursor: pointer; 51 | transition: 0.3s; 52 | } 53 | .review-btn:hover { 54 | background-color: #0086d8; 55 | } 56 | -------------------------------------------------------------------------------- /client/src/Pages/AskQuestion/AskQuestion.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useNavigate } from "react-router-dom"; 4 | 5 | import "./AskQuestion.css"; 6 | import { askQuestion } from "../../actions/question"; 7 | 8 | const AskQuestion = () => { 9 | const [questionTitle, setQuestionTitle] = useState(""); 10 | const [questionBody, setQuestionBody] = useState(""); 11 | const [questionTags, setQuestionTags] = useState(""); 12 | 13 | const dispatch = useDispatch(); 14 | const User = useSelector((state) => state.currentUserReducer); 15 | const navigate = useNavigate(); 16 | 17 | const handleSubmit = (e) => { 18 | e.preventDefault(); 19 | if (User) { 20 | if (questionTitle && questionBody && questionTags) { 21 | dispatch( 22 | askQuestion( 23 | { 24 | questionTitle, 25 | questionBody, 26 | questionTags, 27 | userPosted: User.result.name, 28 | }, 29 | navigate 30 | ) 31 | ); 32 | } else alert("Please enter all the fields"); 33 | } else alert("Login to ask question"); 34 | }; 35 | 36 | const handleEnter = (e) => { 37 | if (e.key === "Enter") { 38 | setQuestionBody(questionBody + "\n"); 39 | } 40 | }; 41 | return ( 42 |
43 |
44 |

Ask a public Question

45 |
46 |
47 | 62 | 79 | 91 |
92 | 97 |
98 |
99 |
100 | ); 101 | }; 102 | 103 | export default AskQuestion; 104 | -------------------------------------------------------------------------------- /client/src/Pages/Auth/AboutAuth.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const AboutAuth = () => { 4 | return ( 5 |
6 |

Join the Stack Overflow community

7 |

Get unstuck — ask a question

8 |

Unlock new privileges like voting and commenting

9 |

Save your favorite tags, filters, and jobs

10 |

Earn reputation and badges

11 |

12 | Collaborate and share knowledge with a private group for 13 |

14 |

15 | Get Stack Overflow for Teams free for up to 50 users. 16 |

17 |
18 | ); 19 | }; 20 | 21 | export default AboutAuth; 22 | -------------------------------------------------------------------------------- /client/src/Pages/Auth/Auth.css: -------------------------------------------------------------------------------- 1 | .auth-section { 2 | min-height: 100vh; 3 | margin: 0% auto; 4 | background-color: #f1f2f3; 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | } 9 | 10 | .auth-container-1 { 11 | padding: 20px; 12 | margin-right: 30px; 13 | } 14 | 15 | .login-logo { 16 | padding: 20px 30px; 17 | } 18 | 19 | .auth-container-2 { 20 | min-width: 20%; 21 | display: flex; 22 | flex-direction: column; 23 | justify-content: center; 24 | align-items: center; 25 | } 26 | 27 | .auth-container-2 form { 28 | width: 100%; 29 | padding: 20px; 30 | background-color: white; 31 | border-radius: 10px; 32 | display: flex; 33 | flex-direction: column; 34 | justify-content: space-evenly; 35 | box-shadow: 0 10px 25px rgb(0 0 0 / 5%), 0 20px 48px rgb(0 0 0 / 5%), 36 | 0 1px 4px rgb(0 0 0 / 10%); 37 | } 38 | 39 | .auth-container-2 form p { 40 | word-wrap: break-word; 41 | } 42 | 43 | .auth-container-2 form label input { 44 | padding: 10px; 45 | width: calc(100% - 30px); 46 | border: solid 1px #0000003e; 47 | font-size: 13px; 48 | } 49 | 50 | .auth-container-2 form label:nth-child(1) h4, 51 | .auth-container-2 form label:nth-child(2) h4, 52 | .auth-container-2 form label:nth-child(3) h4 { 53 | margin-bottom: 5px; 54 | margin-top: 10px; 55 | } 56 | 57 | .auth-container-2 form label:nth-child(4) { 58 | display: flex; 59 | } 60 | 61 | .auth-container-2 form label:nth-child(4) input { 62 | width: 15%; 63 | margin: 13px 0px; 64 | } 65 | 66 | .auth-btn { 67 | margin-top: 10px; 68 | padding: 10px 5px; 69 | background-color: #009dff; 70 | border: solid 1px #009dff; 71 | color: white; 72 | border-radius: 5px; 73 | cursor: pointer; 74 | transition: 0.2s; 75 | font-size: 13px; 76 | font-weight: 500; 77 | } 78 | 79 | .auth-btn:hover { 80 | background-color: #018ce3; 81 | } 82 | 83 | .handle-switch-btn { 84 | background-color: transparent; 85 | color: #007ac6; 86 | border: none; 87 | font-size: 13px; 88 | cursor: pointer; 89 | } 90 | 91 | @media screen and (max-width: 820px) { 92 | .auth-container-1 { 93 | display: none; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /client/src/Pages/Auth/Auth.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { useNavigate } from "react-router-dom"; 4 | 5 | import "./Auth.css"; 6 | import icon from "../../assets/icon.png"; 7 | import AboutAuth from "./AboutAuth"; 8 | import { signup, login } from "../../actions/auth"; 9 | const Auth = () => { 10 | const [isSignup, setIsSignup] = useState(false); 11 | const [name, setName] = useState(""); 12 | const [email, setEmail] = useState(""); 13 | const [password, setPassword] = useState(""); 14 | 15 | const dispatch = useDispatch(); 16 | const navigate = useNavigate(); 17 | 18 | const handleSwitch = () => { 19 | setIsSignup(!isSignup); 20 | setName(""); 21 | setEmail(""); 22 | setPassword(""); 23 | }; 24 | 25 | const handleSubmit = (e) => { 26 | e.preventDefault(); 27 | if (!email && !password) { 28 | alert("Enter email and password"); 29 | } 30 | if (isSignup) { 31 | if (!name) { 32 | alert("Enter a name to continue"); 33 | } 34 | dispatch(signup({ name, email, password }, navigate)); 35 | } else { 36 | dispatch(login({ email, password }, navigate)); 37 | } 38 | }; 39 | 40 | return ( 41 |
42 | {isSignup && } 43 |
44 | stack overflow 45 |
46 | {isSignup && ( 47 | 59 | )} 60 | 72 | 91 | 94 |
95 |

96 | {isSignup ? "Already have an account?" : "Don't have an account?"} 97 | 104 |

105 |
106 |
107 | ); 108 | }; 109 | 110 | export default Auth; 111 | -------------------------------------------------------------------------------- /client/src/Pages/Home/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "../../App.css"; 4 | import LeftSidebar from "../../components/LeftSidebar/LeftSidebar"; 5 | import RightSidebar from "../../components/RightSidebar/RightSidebar"; 6 | import HomeMainbar from "../../components/HomeMainbar/HomeMainbar"; 7 | 8 | const Home = ({ slideIn }) => { 9 | return ( 10 |
11 | 12 |
13 | 14 | 15 |
16 |
17 | ); 18 | }; 19 | 20 | export default Home; 21 | -------------------------------------------------------------------------------- /client/src/Pages/Questions/DisplayAnswer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import moment from "moment"; 3 | import { Link, useParams } from "react-router-dom"; 4 | import { useSelector, useDispatch } from "react-redux"; 5 | 6 | import Avatar from "../../components/Avatar/Avatar"; 7 | import { deleteAnswer } from "../../actions/question"; 8 | 9 | const DisplayAnswer = ({ question, handleShare }) => { 10 | const User = useSelector((state) => state.currentUserReducer); 11 | const { id } = useParams(); 12 | const dispatch = useDispatch(); 13 | const handleDelete = (answerId, noOfAnswers) => { 14 | dispatch(deleteAnswer(id, answerId, noOfAnswers - 1)); 15 | }; 16 | return ( 17 |
18 | {question.answer.map((ans) => ( 19 |
20 |

{ans.answerBody}

21 |
22 |
23 | 26 | {User?.result?._id === ans?.userId && ( 27 | 33 | )} 34 |
35 |
36 |

answered {moment(ans.answeredOn).fromNow()}

37 | 42 | 48 | {ans.userAnswered.charAt(0).toUpperCase()} 49 | 50 |
{ans.userAnswered}
51 | 52 |
53 |
54 |
55 | ))} 56 |
57 | ); 58 | }; 59 | 60 | export default DisplayAnswer; 61 | -------------------------------------------------------------------------------- /client/src/Pages/Questions/DisplayQuestion.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import LeftSidebar from "../../components/LeftSidebar/LeftSidebar"; 3 | import RightSidebar from "../../components/RightSidebar/RightSidebar"; 4 | import QuestionsDetails from "./QuestionsDetails"; 5 | 6 | const DisplayQuestion = ({ slideIn, handleSlideIn }) => { 7 | return ( 8 |
9 | 10 |
11 | 12 | 13 |
14 |
15 | ); 16 | }; 17 | 18 | export default DisplayQuestion; 19 | -------------------------------------------------------------------------------- /client/src/Pages/Questions/Questions.css: -------------------------------------------------------------------------------- 1 | .question-details-page { 2 | width: calc(100% - 300px - 24px); 3 | float: left; 4 | margin: 25px 0px; 5 | padding: 0px; 6 | } 7 | 8 | .question-details-container { 9 | margin-bottom: 20px; 10 | padding-bottom: 20px; 11 | border-bottom: solid 1px rgba(0, 0, 0, 0.112); 12 | } 13 | .question-details-container-2 { 14 | display: flex; 15 | } 16 | .question-votes { 17 | padding: 5px 20px 5px 10px; 18 | } 19 | .question-votes p { 20 | margin: 0%; 21 | font-size: 25px; 22 | text-align: center; 23 | } 24 | .votes-icon { 25 | font-size: 40px; 26 | cursor: pointer; 27 | color: rgb(206, 203, 203); 28 | } 29 | .votes-icon:active { 30 | color: #ef8236; 31 | } 32 | .question-details-container .question-body { 33 | line-height: 22px; 34 | white-space: pre-line; 35 | } 36 | 37 | .question-details-container .question-details-tags { 38 | display: flex; 39 | align-items: center; 40 | justify-content: flex-start; 41 | } 42 | 43 | .question-details-container .question-details-tags p, 44 | .post-ans-container p .ans-tags { 45 | padding: 5px 5px; 46 | margin: 3px; 47 | font-size: 13px; 48 | border-radius: 2px; 49 | background-color: #e1ecf4; 50 | color: #39739d; 51 | text-decoration: none; 52 | line-height: 22px; 53 | } 54 | .question-actions-user { 55 | width: 100%; 56 | display: flex; 57 | align-items: center; 58 | justify-content: space-between; 59 | } 60 | 61 | .question-actions-user button, 62 | .edit-question-btn { 63 | background-color: transparent; 64 | border: none; 65 | padding: 5px 0px; 66 | margin: 0px 10px 0px 0px; 67 | text-decoration: none; 68 | color: #939292; 69 | cursor: pointer; 70 | font-size: 14px; 71 | transition: 0.3s; 72 | } 73 | .question-actions-user button:active { 74 | border-bottom: solid 2px black; 75 | } 76 | .question-actions-user div:nth-child(2) p, 77 | .question-actions-user div:nth-child(2) .user-link { 78 | text-decoration: none; 79 | font-size: 14px; 80 | margin: 0%; 81 | } 82 | .user-link { 83 | display: flex; 84 | align-items: center; 85 | } 86 | .user-link div { 87 | padding-left: 10px; 88 | } 89 | /* post answer container */ 90 | 91 | .post-ans-container form textarea { 92 | padding: 10px; 93 | border: solid 1px rgba(0, 0, 0, 0.3); 94 | font-family: "Roboto", sans-serif; 95 | width: calc(100% - 20px); 96 | resize: vertical; 97 | } 98 | 99 | .post-ans-container form .post-ans-btn { 100 | margin: 20px 0px; 101 | padding: 10px 10px; 102 | background-color: #009dff; 103 | color: white; 104 | border: solid 1px #009dff; 105 | border-radius: 4px; 106 | cursor: pointer; 107 | transition: 0.5s all; 108 | } 109 | 110 | .post-ans-container form .post-ans-btn:hover { 111 | background-color: #0086d8; 112 | } 113 | 114 | /* Display answer container */ 115 | 116 | .display-ans { 117 | padding-bottom: 20px; 118 | border-bottom: solid 1px rgba(0, 0, 0, 0.112); 119 | } 120 | 121 | .display-ans p { 122 | font-size: 14px; 123 | line-height: 18px; 124 | white-space: pre-line; 125 | } 126 | 127 | @media screen and (max-width: 1020px) { 128 | .question-details-page { 129 | width: calc(100% - 24px); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /client/src/Pages/Questions/Questions.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "../../App.css"; 4 | import LeftSidebar from "../../components/LeftSidebar/LeftSidebar"; 5 | import RightSidebar from "../../components/RightSidebar/RightSidebar"; 6 | import HomeMainbar from "../../components/HomeMainbar/HomeMainbar"; 7 | 8 | const Questions = ({ slideIn, handleSlideIn }) => { 9 | return ( 10 |
11 | 12 |
13 | 14 | 15 |
16 |
17 | ); 18 | }; 19 | 20 | export default Questions; 21 | -------------------------------------------------------------------------------- /client/src/Pages/Questions/QuestionsDetails.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useParams, Link, useNavigate, useLocation } from "react-router-dom"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import moment from "moment"; 5 | import copy from "copy-to-clipboard"; 6 | 7 | import upvote from "../../assets/sort-up.svg"; 8 | import downvote from "../../assets/sort-down.svg"; 9 | import "./Questions.css"; 10 | import Avatar from "../../components/Avatar/Avatar"; 11 | import DisplayAnswer from "./DisplayAnswer"; 12 | import { 13 | postAnswer, 14 | deleteQuestion, 15 | voteQuestion, 16 | } from "../../actions/question"; 17 | 18 | const QuestionsDetails = () => { 19 | const { id } = useParams(); 20 | const questionsList = useSelector((state) => state.questionsReducer); 21 | 22 | const [Answer, setAnswer] = useState(""); 23 | const Navigate = useNavigate(); 24 | const dispatch = useDispatch(); 25 | const User = useSelector((state) => state.currentUserReducer); 26 | const location = useLocation(); 27 | const url = "http://localhost:3000"; 28 | 29 | const handlePostAns = (e, answerLength) => { 30 | e.preventDefault(); 31 | if (User === null) { 32 | alert("Login or Signup to answer a question"); 33 | Navigate("/Auth"); 34 | } else { 35 | if (Answer === "") { 36 | alert("Enter an answer before submitting"); 37 | } else { 38 | dispatch( 39 | postAnswer({ 40 | id, 41 | noOfAnswers: answerLength + 1, 42 | answerBody: Answer, 43 | userAnswered: User.result.name, 44 | }) 45 | ); 46 | setAnswer(""); 47 | } 48 | } 49 | }; 50 | 51 | const handleShare = () => { 52 | copy(url + location.pathname); 53 | alert("Copied url : " + url + location.pathname); 54 | }; 55 | 56 | const handleDelete = () => { 57 | dispatch(deleteQuestion(id, Navigate)); 58 | }; 59 | 60 | const handleUpVote = () => { 61 | if (User === null) { 62 | alert("Login or Signup to up vote a question"); 63 | Navigate("/Auth"); 64 | } else { 65 | dispatch(voteQuestion(id, "upVote")); 66 | } 67 | }; 68 | 69 | const handleDownVote = () => { 70 | if (User === null) { 71 | alert("Login or Signup to down vote a question"); 72 | Navigate("/Auth"); 73 | } else { 74 | dispatch(voteQuestion(id, "downVote")); 75 | } 76 | }; 77 | 78 | return ( 79 |
80 | {questionsList.data === null ? ( 81 |

Loading...

82 | ) : ( 83 | <> 84 | {questionsList.data 85 | .filter((question) => question._id === id) 86 | .map((question) => ( 87 |
88 |
89 |

{question.questionTitle}

90 |
91 |
92 | 99 |

{question.upVote.length - question.downVote.length}

100 | 107 |
108 |
109 |

{question.questionBody}

110 |
111 | {question.questionTags.map((tag) => ( 112 |

{tag}

113 | ))} 114 |
115 |
116 |
117 | 120 | {User?.result?._id === question?.userId && ( 121 | 124 | )} 125 |
126 |
127 |

asked {moment(question.askedOn).fromNow()}

128 | 133 | 139 | {question.userPosted.charAt(0).toUpperCase()} 140 | 141 |
{question.userPosted}
142 | 143 |
144 |
145 |
146 |
147 |
148 | {question.noOfAnswers !== 0 && ( 149 |
150 |

{question.noOfAnswers} Answers

151 | 156 |
157 | )} 158 |
159 |

Your Answer

160 |
{ 162 | handlePostAns(e, question.answer.length); 163 | }} 164 | > 165 | 173 |
174 | 179 |
180 |

181 | Browse other Question tagged 182 | {question.questionTags.map((tag) => ( 183 | 184 | {" "} 185 | {tag}{" "} 186 | 187 | ))}{" "} 188 | or 189 | 193 | {" "} 194 | ask your own question. 195 | 196 |

197 |
198 |
199 | ))} 200 | 201 | )} 202 |
203 | ); 204 | }; 205 | 206 | export default QuestionsDetails; 207 | -------------------------------------------------------------------------------- /client/src/Pages/Tags/Tags.css: -------------------------------------------------------------------------------- 1 | .tags-h1 { 2 | margin-top: 50px; 3 | font-weight: 400; 4 | } 5 | 6 | .tags-p { 7 | margin: 0px; 8 | font-size: 15px; 9 | } 10 | 11 | .tags-list-container { 12 | padding: 30px 0px; 13 | display: grid; 14 | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); 15 | gap: 10px; 16 | } 17 | 18 | .tag { 19 | padding: 10px; 20 | border: solid 1px #d2d2d2; 21 | border-radius: 2px; 22 | } 23 | 24 | .tag h5 { 25 | display: inline-block; 26 | margin: 10px 0px; 27 | padding: 5px 5px; 28 | background-color: #e1ecf4; 29 | color: #39739d; 30 | } 31 | 32 | .tag p { 33 | font-size: 14px; 34 | color: #323232; 35 | line-height: 17px; 36 | } 37 | -------------------------------------------------------------------------------- /client/src/Pages/Tags/Tags.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import LeftSidebar from "../../components/LeftSidebar/LeftSidebar"; 4 | import TagsList from "./TagsList"; 5 | import "./Tags.css"; 6 | import { tagsList } from "./tagList"; 7 | 8 | const Tags = ({ slideIn, handleSlideIn }) => { 9 | return ( 10 |
11 | 12 |
13 |

Tags

14 |

15 | A tag is a keyword or label that categorizes your question with other, 16 | similar questions. 17 |

18 |

19 | Using the right tags makes it easier for others to find and answer 20 | your question. 21 |

22 |
23 | {tagsList.map((tag, index) => ( 24 | 25 | ))} 26 |
27 |
28 |
29 | ); 30 | }; 31 | 32 | export default Tags; 33 | -------------------------------------------------------------------------------- /client/src/Pages/Tags/TagsList.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Tags.css"; 3 | 4 | const TagsList = ({ tag }) => { 5 | return ( 6 |
7 |
{tag.tagName}
8 |

{tag.tagDesc}

9 |
10 | ); 11 | }; 12 | 13 | export default TagsList; 14 | -------------------------------------------------------------------------------- /client/src/Pages/Tags/tagList.js: -------------------------------------------------------------------------------- 1 | export const tagsList = [ 2 | { 3 | tagName: "javascript", 4 | tagDesc: 5 | "For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Please include all relevant tags on your question;", 6 | }, 7 | { 8 | tagName: "python", 9 | tagDesc: 10 | "Python is a multi-paradigm, dynamically typed, multipurpose programming language. It is designed to be quick to learn, understand, and use, and enforces a clean and uniform syntax.", 11 | }, 12 | { 13 | tagName: "c#", 14 | tagDesc: 15 | "C# (pronounced 'see sharp') is a high level, statically typed, multi-paradigm programming language developed by Microsoft", 16 | }, 17 | { 18 | tagName: "java", 19 | tagDesc: 20 | "Java is a high-level object oriented programming language. Use this tag when you're having problems using or understanding the language itself. ", 21 | }, 22 | { 23 | tagName: "php", 24 | tagDesc: 25 | "PHP is a widely used, open source, general-purpose, multi-paradigm, dynamically typed and interpreted scripting language originally designed for server-side web development", 26 | }, 27 | { 28 | tagName: "html", 29 | tagDesc: 30 | "HTML (HyperText Markup Language) is the markup language for creating web pages and other information to be displayed in a web browser.", 31 | }, 32 | { 33 | tagName: "android", 34 | tagDesc: 35 | "Android is Google's mobile operating system, used for programming or developing digital devices (Smartphones, Tablets, Automobiles, TVs, Wear, Glass, IoT).", 36 | }, 37 | { 38 | tagName: "css", 39 | tagDesc: 40 | "CSS is a representation style sheet language used for describing the look and formatting of HTML, XML documents and SVG elements including colors, layout, fonts, and animations", 41 | }, 42 | { 43 | tagName: "Reactjs", 44 | tagDesc: 45 | "React is a JavaScript library for building user interfaces. It uses a declarative, component-based paradigm and aims to be both efficient and flexible.", 46 | }, 47 | { 48 | tagName: "node.js", 49 | tagDesc: 50 | "Node.js is an event-based, non-blocking, asynchronous I/O runtime that uses Google's V8 JavaScript engine and libuv library. ", 51 | }, 52 | ]; 53 | -------------------------------------------------------------------------------- /client/src/Pages/UserProfile/EditProfileForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { updateProfile } from "../../actions/users"; 4 | 5 | const EditProfileForm = ({ currentUser, setSwitch }) => { 6 | const [name, setName] = useState(currentUser?.result?.name); 7 | const [about, setAbout] = useState(currentUser?.result?.about); 8 | const [tags, setTags] = useState([]); 9 | const dispatch = useDispatch(); 10 | console.log(tags); 11 | const handleSubmit = (e) => { 12 | e.preventDefault(); 13 | if (tags[0] === "" || tags.length === 0) { 14 | alert("Update tags field"); 15 | } else { 16 | dispatch(updateProfile(currentUser?.result?._id, { name, about, tags })); 17 | } 18 | setSwitch(false); 19 | }; 20 | 21 | return ( 22 |
23 |

Edit Your Profile

24 |

Public information

25 |
26 | 34 | 44 | 53 |
54 | 55 | 62 |
63 |
64 | ); 65 | }; 66 | 67 | export default EditProfileForm; 68 | -------------------------------------------------------------------------------- /client/src/Pages/UserProfile/ProfileBio.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ProfileBio = ({ currentProfile }) => { 4 | return ( 5 |
6 |
7 | {currentProfile?.tags.length !== 0 ? ( 8 | <> 9 |

Tags watched

10 | {currentProfile?.tags.map((tag) => ( 11 |

{tag}

12 | ))} 13 | 14 | ) : ( 15 |

0 tags watched

16 | )} 17 |
18 |
19 | {currentProfile?.about ? ( 20 | <> 21 |

About

22 |

{currentProfile?.about}

23 | 24 | ) : ( 25 |

No bio found

26 | )} 27 |
28 |
29 | ); 30 | }; 31 | 32 | export default ProfileBio; 33 | -------------------------------------------------------------------------------- /client/src/Pages/UserProfile/UserProfile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { useParams } from "react-router"; 4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 5 | import { faBirthdayCake, faPen } from "@fortawesome/free-solid-svg-icons"; 6 | import moment from "moment"; 7 | 8 | import LeftSidebar from "../../components/LeftSidebar/LeftSidebar"; 9 | import Avatar from "../../components/Avatar/Avatar"; 10 | import EditProfileForm from "./EditProfileForm"; 11 | import ProfileBio from "./ProfileBio"; 12 | import "./UsersProfile.css"; 13 | 14 | const UserProfile = ({ slideIn, handleSlideIn }) => { 15 | const { id } = useParams(); 16 | const users = useSelector((state) => state.usersReducer); 17 | const currentProfile = users.filter((user) => user._id === id)[0]; 18 | const currentUser = useSelector((state) => state.currentUserReducer); 19 | const [Switch, setSwitch] = useState(false); 20 | 21 | return ( 22 |
23 | 24 |
25 |
26 |
27 |
28 | 35 | {currentProfile?.name.charAt(0).toUpperCase()} 36 | 37 |
38 |

{currentProfile?.name}

39 |

40 | Joined{" "} 41 | {moment(currentProfile?.joinedOn).fromNow()} 42 |

43 |
44 |
45 | {currentUser?.result._id === id && ( 46 | 53 | )} 54 |
55 | <> 56 | {Switch ? ( 57 | 61 | ) : ( 62 | 63 | )} 64 | 65 |
66 |
67 |
68 | ); 69 | }; 70 | 71 | export default UserProfile; 72 | -------------------------------------------------------------------------------- /client/src/Pages/UserProfile/UsersProfile.css: -------------------------------------------------------------------------------- 1 | .user-details-container { 2 | width: 100%; 3 | margin-top: 50px; 4 | display: flex; 5 | align-items: flex-start; 6 | justify-content: space-between; 7 | } 8 | 9 | .user-details { 10 | display: flex; 11 | align-items: flex-start; 12 | } 13 | 14 | .user-name { 15 | padding-left: 20px; 16 | } 17 | .user-name p { 18 | color: #7e7e7e; 19 | } 20 | .edit-profile-btn { 21 | padding: 8px 10px; 22 | border: solid 1px #7e7e7e; 23 | border-radius: 2px; 24 | background-color: white; 25 | cursor: pointer; 26 | transition: 0.3s; 27 | } 28 | .edit-profile-btn:hover { 29 | background-color: #f5f9fc; 30 | } 31 | .edit-profile-title { 32 | padding: 20px 0px; 33 | border-bottom: solid 1px #dbd9d9; 34 | } 35 | .edit-profile-title-2 { 36 | color: grey; 37 | font-weight: 400; 38 | } 39 | .edit-profile-form { 40 | padding: 20px; 41 | border: solid 1px #dbd9d9; 42 | border-radius: 5px; 43 | } 44 | .edit-profile-form label h3 { 45 | margin: 0%; 46 | padding: 3px 0px; 47 | } 48 | .edit-profile-form label p { 49 | margin: 0%; 50 | padding: 3px 0px; 51 | } 52 | .edit-profile-form label input, 53 | .edit-profile-form label textarea { 54 | padding: 5px; 55 | margin-bottom: 20px; 56 | border: solid 1px #dbd9d9; 57 | width: 50%; 58 | } 59 | 60 | .user-submit-btn { 61 | padding: 14px 10px; 62 | background-color: #0a95ff; 63 | color: white; 64 | border: none; 65 | border-radius: 5px; 66 | transition: 0.2s; 67 | cursor: pointer; 68 | } 69 | 70 | .user-submit-btn:hover { 71 | background-color: #0074cc; 72 | } 73 | 74 | .user-cancel-btn { 75 | padding: 14px 10px; 76 | color: #0a95ff; 77 | background-color: transparent; 78 | border: none; 79 | margin-left: 10px; 80 | cursor: pointer; 81 | } 82 | -------------------------------------------------------------------------------- /client/src/Pages/Users/User.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import "./Users.css"; 5 | 6 | const User = ({ user }) => { 7 | return ( 8 | 9 |

{user.name.charAt(0).toUpperCase()}

10 |
{user.name}
11 | 12 | ); 13 | }; 14 | 15 | export default User; 16 | -------------------------------------------------------------------------------- /client/src/Pages/Users/Users.css: -------------------------------------------------------------------------------- 1 | .user-list-container { 2 | padding: 30px 0px; 3 | display: grid; 4 | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); 5 | gap: 30px; 6 | } 7 | 8 | .user-profile-link { 9 | display: flex; 10 | align-items: center; 11 | justify-content: flex-start; 12 | text-decoration: none; 13 | color: black; 14 | } 15 | 16 | .user-profile-link h3 { 17 | padding: 10px 13px; 18 | background-color: #d3d3d3; 19 | border-radius: 50%; 20 | } 21 | 22 | .user-profile-link h5 { 23 | margin: 0px 10px; 24 | } 25 | -------------------------------------------------------------------------------- /client/src/Pages/Users/Users.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "./Users.css"; 4 | import LeftSidebar from "../../components/LeftSidebar/LeftSidebar"; 5 | import UsersList from "./UsersList"; 6 | 7 | const Users = ({ slideIn, handleSlideIn }) => { 8 | return ( 9 |
10 | 11 |
12 |

Users

13 | 14 |
15 |
16 | ); 17 | }; 18 | 19 | export default Users; 20 | -------------------------------------------------------------------------------- /client/src/Pages/Users/UsersList.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | 4 | import User from "./User"; 5 | import "./Users.css"; 6 | 7 | const UsersList = () => { 8 | const users = useSelector((state) => state.usersReducer); 9 | 10 | return ( 11 |
12 | {users.map((user) => ( 13 | 14 | ))} 15 |
16 | ); 17 | }; 18 | 19 | export default UsersList; 20 | -------------------------------------------------------------------------------- /client/src/actions/auth.js: -------------------------------------------------------------------------------- 1 | import * as api from "../api"; 2 | import { setCurrentUser } from "./currentUser"; 3 | import { fetchAllUsers } from "./users"; 4 | 5 | export const signup = (authData, navigate) => async (dispatch) => { 6 | try { 7 | const { data } = await api.signUp(authData); 8 | dispatch({ type: "AUTH", data }); 9 | dispatch(setCurrentUser(JSON.parse(localStorage.getItem("Profile")))); 10 | dispatch(fetchAllUsers()); 11 | navigate("/"); 12 | } catch (error) { 13 | console.log(error); 14 | } 15 | }; 16 | 17 | export const login = (authData, navigate) => async (dispatch) => { 18 | try { 19 | const { data } = await api.logIn(authData); 20 | dispatch({ type: "AUTH", data }); 21 | dispatch(setCurrentUser(JSON.parse(localStorage.getItem("Profile")))); 22 | navigate("/"); 23 | } catch (error) { 24 | console.log(error); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /client/src/actions/currentUser.js: -------------------------------------------------------------------------------- 1 | export const setCurrentUser = (data) => { 2 | return { 3 | type: "FETCH_CURRENT_USER", 4 | payload: data, 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /client/src/actions/question.js: -------------------------------------------------------------------------------- 1 | import * as api from "../api/index"; 2 | 3 | export const askQuestion = (questionData, navigate) => async (dispatch) => { 4 | try { 5 | const { data } = await api.postQuestion(questionData); 6 | dispatch({ type: "POST_QUESTION", payload: data }); 7 | dispatch(fetchAllQuestions()); 8 | navigate("/"); 9 | } catch (error) { 10 | console.log(error); 11 | } 12 | }; 13 | 14 | export const fetchAllQuestions = () => async (disptach) => { 15 | try { 16 | const { data } = await api.getAllQuestions(); 17 | disptach({ type: "FETCH_ALL_QUESTIONS", payload: data }); 18 | } catch (error) { 19 | console.log(error); 20 | } 21 | }; 22 | 23 | export const deleteQuestion = (id, navigate) => async (dispatch) => { 24 | try { 25 | await api.deleteQuestion(id); 26 | dispatch(fetchAllQuestions()); 27 | navigate("/"); 28 | } catch (error) { 29 | console.log(error); 30 | } 31 | }; 32 | 33 | export const voteQuestion = (id, value) => async (dispatch) => { 34 | try { 35 | await api.voteQuestion(id, value); 36 | dispatch(fetchAllQuestions()); 37 | } catch (error) { 38 | console.log(error); 39 | } 40 | }; 41 | 42 | export const postAnswer = (answerData) => async (dispatch) => { 43 | try { 44 | const { id, noOfAnswers, answerBody, userAnswered } = answerData; 45 | const { data } = await api.postAnswer( 46 | id, 47 | noOfAnswers, 48 | answerBody, 49 | userAnswered 50 | ); 51 | dispatch({ type: "POST_ANSWER", payload: data }); 52 | dispatch(fetchAllQuestions()); 53 | } catch (error) { 54 | console.log(error); 55 | } 56 | }; 57 | 58 | export const deleteAnswer = (id, answerId, noOfAnswers) => async (dispatch) => { 59 | try { 60 | await api.deleteAnswer(id, answerId, noOfAnswers); 61 | dispatch(fetchAllQuestions()); 62 | } catch (error) { 63 | console.log(error); 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /client/src/actions/users.js: -------------------------------------------------------------------------------- 1 | import * as api from "../api"; 2 | 3 | export const fetchAllUsers = () => async (dispatch) => { 4 | try { 5 | const { data } = await api.getAllUsers(); 6 | dispatch({ type: "FETCH_USERS", payload: data }); 7 | } catch (error) { 8 | console.log(error); 9 | } 10 | }; 11 | export const updateProfile = (id, updateData) => async (dispatch) => { 12 | try { 13 | const { data } = await api.updateProfile(id, updateData); 14 | dispatch({ type: "UPDATE_CURRENT_USER", payload: data }); 15 | } catch (error) { 16 | console.log(error); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /client/src/api/index.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const API = axios.create({ 4 | baseURL: "https://stack-overflow-eight.vercel.app/", 5 | }); 6 | 7 | API.interceptors.request.use((req) => { 8 | if (localStorage.getItem("Profile")) { 9 | req.headers.authorization = `Bearer ${ 10 | JSON.parse(localStorage.getItem("Profile")).token 11 | }`; 12 | } 13 | return req; 14 | }); 15 | 16 | export const logIn = (authData) => API.post("/user/login", authData); 17 | export const signUp = (authData) => API.post("/user/signup", authData); 18 | 19 | export const postQuestion = (questionData) => 20 | API.post("/questions/Ask", questionData); 21 | export const getAllQuestions = () => API.get("/questions/get"); 22 | export const deleteQuestion = (id) => API.delete(`/questions/delete/${id}`); 23 | export const voteQuestion = (id, value) => 24 | API.patch(`/questions/vote/${id}`, { value }); 25 | 26 | export const postAnswer = (id, noOfAnswers, answerBody, userAnswered) => 27 | API.patch(`/answer/post/${id}`, { noOfAnswers, answerBody, userAnswered }); 28 | export const deleteAnswer = (id, answerId, noOfAnswers) => 29 | API.patch(`/answer/delete/${id}`, { answerId, noOfAnswers }); 30 | 31 | export const getAllUsers = () => API.get("/user/getAllUsers"); 32 | export const updateProfile = (id, updateData) => 33 | API.patch(`/user/update/${id}`, updateData); 34 | -------------------------------------------------------------------------------- /client/src/assets/Globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/bars-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/blacklogo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/comment-alt-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Manoj-Athi/Stack-overflow/4cffe2048c4b21cdb908a00bb65c70cd20a79312/client/src/assets/icon.png -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Manoj-Athi/Stack-overflow/4cffe2048c4b21cdb908a00bb65c70cd20a79312/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/assets/pen-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/search-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/sort-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/sort-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/components/Avatar/Avatar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Avatar = ({ 4 | children, 5 | backgroundColor, 6 | px, 7 | py, 8 | color, 9 | borderRadius, 10 | fontSize, 11 | cursor, 12 | }) => { 13 | const style = { 14 | backgroundColor, 15 | padding: `${py} ${px}`, 16 | color: color || "black", 17 | borderRadius, 18 | fontSize, 19 | textAlign: "center", 20 | cursor: cursor || null, 21 | textDecoration: "none", 22 | }; 23 | 24 | return
{children}
; 25 | }; 26 | 27 | export default Avatar; 28 | -------------------------------------------------------------------------------- /client/src/components/HomeMainbar/HomeMainbar.css: -------------------------------------------------------------------------------- 1 | .main-bar { 2 | width: calc(100% - 300px - 24px); 3 | float: left; 4 | margin: 25px 0px; 5 | padding: 0; 6 | } 7 | 8 | .main-bar-header { 9 | display: flex; 10 | justify-content: space-between; 11 | align-items: center; 12 | } 13 | 14 | .main-bar-header h1 { 15 | font-weight: 400; 16 | } 17 | 18 | .main-bar-header .ask-btn { 19 | padding: 10px 15px; 20 | border-radius: 4px; 21 | background-color: #009dff; 22 | color: white; 23 | border: none; 24 | text-decoration: none; 25 | transition: 0.3s; 26 | } 27 | 28 | .main-bar-header .ask-btn:hover { 29 | background-color: #0086d8; 30 | } 31 | 32 | .display-question-container { 33 | min-height: 80px; 34 | width: 100%; 35 | display: flex; 36 | align-items: center; 37 | background-color: #fdf7e2; 38 | border-bottom: solid 1px #edeff0; 39 | } 40 | 41 | .display-question-container .display-votes-ans { 42 | padding: 10px; 43 | } 44 | 45 | .display-question-container .display-votes-ans p { 46 | margin: 0%; 47 | text-align: center; 48 | } 49 | 50 | .display-question-details { 51 | flex-grow: 1; 52 | padding: 10px; 53 | margin: 0%; 54 | } 55 | 56 | .question-title-link { 57 | text-decoration: none; 58 | color: #037ecb; 59 | transition: 0.3s; 60 | } 61 | 62 | .question-title-link:hover { 63 | color: #009dff; 64 | } 65 | 66 | .display-tags-time .display-tags { 67 | display: flex; 68 | flex-wrap: wrap; 69 | } 70 | 71 | .display-tags-time { 72 | display: flex; 73 | flex-wrap: wrap; 74 | align-items: center; 75 | justify-content: space-between; 76 | } 77 | 78 | .display-tags-time .display-tags p { 79 | margin: 2px; 80 | padding: 4px; 81 | font-size: 13px; 82 | background-color: #edeff0; 83 | color: #39739d; 84 | } 85 | .display-tags-time .display-time { 86 | font-size: 13px; 87 | } 88 | 89 | @media screen and (max-width: 1020px) { 90 | .main-bar { 91 | width: 100%; 92 | } 93 | } 94 | 95 | @media screen and (max-width: 740px) { 96 | .display-question-container .display-votes-ans { 97 | padding: 10px; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /client/src/components/HomeMainbar/HomeMainbar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLocation, useNavigate } from "react-router-dom"; 3 | import { useSelector } from "react-redux"; 4 | import "./HomeMainbar.css"; 5 | import QuestionList from "./QuestionList"; 6 | 7 | const HomeMainbar = () => { 8 | const location = useLocation(); 9 | const user = 1; 10 | const navigate = useNavigate(); 11 | 12 | const questionsList = useSelector((state) => state.questionsReducer); 13 | 14 | const checkAuth = () => { 15 | if (user === null) { 16 | alert("login or signup to ask a question"); 17 | navigate("/Auth"); 18 | } else { 19 | navigate("/AskQuestion"); 20 | } 21 | }; 22 | 23 | return ( 24 |
25 |
26 | {location.pathname === "/" ? ( 27 |

Top Questions

28 | ) : ( 29 |

All Questions

30 | )} 31 | 34 |
35 |
36 | {questionsList.data === null ? ( 37 |

Loading...

38 | ) : ( 39 | <> 40 |

{questionsList.data.length} questions

41 | 42 | 43 | )} 44 |
45 |
46 | ); 47 | }; 48 | 49 | export default HomeMainbar; 50 | -------------------------------------------------------------------------------- /client/src/components/HomeMainbar/QuestionList.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Questions from "./Questions"; 3 | const QuestionList = ({ questionsList }) => { 4 | return ( 5 | <> 6 | {questionsList.map((question) => ( 7 | 8 | ))} 9 | 10 | ); 11 | }; 12 | 13 | export default QuestionList; 14 | -------------------------------------------------------------------------------- /client/src/components/HomeMainbar/Questions.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import moment from "moment"; 4 | 5 | const Questions = ({ question }) => { 6 | return ( 7 |
8 |
9 |

{question.upVote.length - question.downVote.length}

10 |

votes

11 |
12 |
13 |

{question.noOfAnswers}

14 |

answers

15 |
16 |
17 | 18 | {question.questionTitle.length > (window.innerWidth <= 400 ? 70 : 90) 19 | ? question.questionTitle.substring( 20 | 0, 21 | window.innerWidth <= 400 ? 70 : 90 22 | ) + "..." 23 | : question.questionTitle} 24 | 25 |
26 |
27 | {question.questionTags.map((tag) => ( 28 |

{tag}

29 | ))} 30 |
31 |

32 | asked {moment(question.askedOn).fromNow()} {question.userPosted} 33 |

34 |
35 |
36 |
37 | ); 38 | }; 39 | 40 | export default Questions; 41 | -------------------------------------------------------------------------------- /client/src/components/LeftSidebar/LeftSidebar.css: -------------------------------------------------------------------------------- 1 | .left-sidebar { 2 | width: 164px; 3 | box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2); 4 | background-color: white; 5 | height: auto; 6 | min-height: 100vh; 7 | transition: box-shadow ease-in-out 0.1s, transform ease-in-out 0.1s; 8 | box-sizing: border-box; 9 | font-size: 13px; 10 | } 11 | 12 | .nav-btn { 13 | background-color: inherit; 14 | width: 100%; 15 | border: none; 16 | padding: 0%; 17 | } 18 | 19 | .side-nav { 20 | height: auto; 21 | max-width: 100%; 22 | position: sticky; 23 | margin: 50px 0px; 24 | padding: 20px 0px; 25 | } 26 | 27 | .side-nav-div { 28 | padding: 10px 0px; 29 | } 30 | 31 | .side-nav-div div { 32 | padding-left: 10px; 33 | } 34 | 35 | .side-nav-links { 36 | text-decoration: none; 37 | color: #3a3a3a; 38 | display: flex; 39 | align-items: center; 40 | justify-content: flex-start; 41 | padding-left: 10px; 42 | transition: 0.2s; 43 | } 44 | 45 | .side-nav-links:hover { 46 | color: black; 47 | } 48 | 49 | .active { 50 | font-weight: bolder; 51 | color: black; 52 | background-color: rgb(225, 225, 225); 53 | border-right: solid 3px #ef8236; 54 | } 55 | 56 | @media screen and (max-width: 760px) { 57 | .left-sidebar { 58 | position: absolute; 59 | transform: translateX(-100%); 60 | z-index: 10; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /client/src/components/LeftSidebar/LeftSidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./LeftSidebar.css"; 3 | import { NavLink } from "react-router-dom"; 4 | import Globe from "../../assets/Globe.svg"; 5 | 6 | const LeftSidebar = ({ slideIn, handleSlideIn }) => { 7 | const slideInStyle = { 8 | transform: "translateX(0%)", 9 | }; 10 | 11 | const slideOutStyle = { 12 | transform: "translateX(-100%)", 13 | }; 14 | 15 | return ( 16 |
20 | 62 |
63 | ); 64 | }; 65 | 66 | export default LeftSidebar; 67 | -------------------------------------------------------------------------------- /client/src/components/Navbar/Navbar.css: -------------------------------------------------------------------------------- 1 | .main-nav { 2 | min-height: 50px; 3 | width: 100%; 4 | margin: 0% auto; 5 | border-top: solid 3px #ef8236; 6 | box-shadow: 0px 1px 5px #00000033; 7 | position: fixed; 8 | z-index: 15; 9 | top: 0%; 10 | left: 0%; 11 | background-color: #f8f9f9; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | } 16 | 17 | .navbar { 18 | height: 100%; 19 | min-width: 85%; 20 | display: flex; 21 | align-items: center; 22 | justify-content: space-between; 23 | } 24 | 25 | .slide-in-icon { 26 | display: none; 27 | transition: 0.2s; 28 | padding: 5px 6px 3px 6px; 29 | border-radius: 50%; 30 | background-color: inherit; 31 | border: none; 32 | cursor: pointer; 33 | } 34 | 35 | .navbar .navbar-1 { 36 | display: flex; 37 | align-items: center; 38 | flex-grow: 1; 39 | } 40 | 41 | .navbar .navbar-2 { 42 | display: flex; 43 | align-items: center; 44 | } 45 | 46 | .nav-logo { 47 | padding: 5px 25px; 48 | } 49 | 50 | .nav-item { 51 | margin: 0px 3px; 52 | font-size: small; 53 | font-weight: 500; 54 | text-decoration: none; 55 | color: rgb(69, 69, 69); 56 | transition: 0.2s; 57 | } 58 | 59 | .nav-btn { 60 | cursor: pointer; 61 | border-radius: 20px; 62 | padding: 10px 20px; 63 | } 64 | 65 | .nav-item:hover, 66 | .slide-in-icon:hover { 67 | background-color: rgb(226, 226, 226); 68 | } 69 | 70 | .navbar .navbar-1 form { 71 | flex-grow: 1; 72 | padding: 0px 12px; 73 | position: relative; 74 | } 75 | 76 | .navbar .navbar-1 form input { 77 | min-width: 90%; 78 | margin: 0; 79 | padding: 8px 10px 8px 32px; 80 | font-size: 13px; 81 | border: solid 1px #0000003e; 82 | border-radius: 3px; 83 | } 84 | 85 | .navbar .navbar-1 form .search-icon { 86 | position: absolute; 87 | left: 25px; 88 | top: 8px; 89 | } 90 | 91 | .nav-links { 92 | padding: 7px 13px; 93 | border: solid 1px blue; 94 | border-radius: 3px; 95 | background-color: #e7f8fe; 96 | cursor: pointer; 97 | } 98 | 99 | .nav-links:hover { 100 | background-color: #d3e4eb; 101 | } 102 | 103 | @media screen and (max-width: 1120px) { 104 | .navbar .navbar-1 form { 105 | display: none; 106 | } 107 | } 108 | 109 | @media screen and (max-width: 760px) { 110 | .slide-in-icon { 111 | display: block; 112 | } 113 | } 114 | 115 | @media screen and (max-width: 620px) { 116 | .navbar .navbar-1 .res-nav { 117 | display: none; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /client/src/components/Navbar/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Link, useNavigate } from "react-router-dom"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import decode from "jwt-decode"; 5 | 6 | import logo from "../../assets/logo.png"; 7 | import search from "../../assets/search-solid.svg"; 8 | import Avatar from "../../components/Avatar/Avatar"; 9 | import "./Navbar.css"; 10 | import { setCurrentUser } from "../../actions/currentUser"; 11 | import bars from "../../assets/bars-solid.svg"; 12 | 13 | const Navbar = ({ handleSlideIn }) => { 14 | const dispatch = useDispatch(); 15 | var User = useSelector((state) => state.currentUserReducer); 16 | const navigate = useNavigate(); 17 | 18 | const handleLogout = () => { 19 | dispatch({ type: "LOGOUT" }); 20 | navigate("/"); 21 | dispatch(setCurrentUser(null)); 22 | }; 23 | 24 | useEffect(() => { 25 | const token = User?.token; 26 | if (token) { 27 | const decodedToken = decode(token); 28 | if (decodedToken.exp * 1000 < new Date().getTime()) { 29 | handleLogout(); 30 | } 31 | } 32 | dispatch(setCurrentUser(JSON.parse(localStorage.getItem("Profile")))); 33 | }, [User?.token, dispatch]); 34 | 35 | return ( 36 | 88 | ); 89 | }; 90 | 91 | export default Navbar; 92 | -------------------------------------------------------------------------------- /client/src/components/RightSidebar/RightSidebar.css: -------------------------------------------------------------------------------- 1 | .right-sidebar { 2 | float: right; 3 | width: 300px; 4 | margin: 40px 0px 15px 24px; 5 | font-size: 15px; 6 | } 7 | 8 | .widget { 9 | margin-top: 10px; 10 | box-shadow: 3px 3px 10px rgb(0 0 0 / 5%), -3px -3px 10px rgb(0 0 0 / 5%); 11 | } 12 | 13 | .widget h4 { 14 | background-color: #fbf3d5; 15 | margin: 0%; 16 | padding: 15px; 17 | border: solid 1px #f1e5bc; 18 | font-size: 13px; 19 | } 20 | 21 | .right-sidebar-div-1 { 22 | background-color: #fdf7e2; 23 | padding: 15px; 24 | border: solid 1px #f1e5bc; 25 | } 26 | 27 | .right-sidebar-div-1 .right-sidebar-div-2 { 28 | display: flex; 29 | align-items: flex-start; 30 | justify-content: space-evenly; 31 | } 32 | 33 | .right-sidebar-div-1 .right-sidebar-div-2 p { 34 | padding-left: 10px; 35 | margin-top: 0%; 36 | font-size: 13px; 37 | } 38 | 39 | .widget-tags { 40 | margin-top: 40px; 41 | box-shadow: 3px 3px 10px rgb(0 0 0 / 5%), -3px -3px 10px rgb(0 0 0 / 5%); 42 | } 43 | 44 | .widget-tags h4 { 45 | margin: 0%; 46 | padding: 15px; 47 | background-color: #f8f9f9; 48 | border: solid 1px #e3e6e8; 49 | font-size: 13px; 50 | } 51 | 52 | .widget-tags-div { 53 | display: flex; 54 | flex-flow: row wrap; 55 | align-items: center; 56 | justify-content: space-evenly; 57 | border: solid 1px #e3e6e8; 58 | padding: 10px; 59 | } 60 | 61 | .widget-tags-div p { 62 | padding: 5px; 63 | background-color: #e1ecf4; 64 | color: #39739d; 65 | border-radius: 2px; 66 | font-size: 13px; 67 | } 68 | 69 | @media screen and (max-width: 1020px) { 70 | .right-sidebar { 71 | display: none; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /client/src/components/RightSidebar/RightSidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./RightSidebar.css"; 3 | import Widget from "./Widget"; 4 | import WidgetTags from "./WidgetTags"; 5 | 6 | const RightSidebar = () => { 7 | return ( 8 | 12 | ); 13 | }; 14 | 15 | export default RightSidebar; 16 | -------------------------------------------------------------------------------- /client/src/components/RightSidebar/Widget.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./RightSidebar.css"; 3 | import comment from "../../assets/comment-alt-solid.svg"; 4 | import pen from "../../assets/pen-solid.svg"; 5 | import blackLogo from "../../assets/blacklogo.svg"; 6 | 7 | const Widget = () => { 8 | return ( 9 |
10 |

The Overflow Blog

11 |
12 |
13 | pen 14 |

15 | Observability is key to the future of software (and your DevOps 16 | career) 17 |

18 |
19 |
20 | pen 21 |

Podcast 374: How valuable is your screen name?

22 |
23 |
24 |

Featured on Meta

25 |
26 |
27 | pen 28 |

Review queue workflows - Final release....

29 |
30 |
31 | pen 32 |

33 | Please welcome Valued Associates: #958 - V2Blast #959 - SpencerG 34 |

35 |
36 |
37 | pen 38 |

39 | Outdated Answers: accepted answer is now unpinned on Stack Overflow 40 |

41 |
42 |
43 |

Hot Meta Posts

44 |
45 |
46 |

38

47 |

48 | Why was this spam flag declined, yet the question marked as spam? 49 |

50 |
51 |
52 |

20

53 |

54 | What is the best course of action when a user has high enough rep 55 | to... 56 |

57 |
58 |
59 |

14

60 |

Is a link to the "How to ask" help page a useful comment?

61 |
62 |
63 |
64 | ); 65 | }; 66 | 67 | export default Widget; 68 | -------------------------------------------------------------------------------- /client/src/components/RightSidebar/WidgetTags.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const WidgetTags = () => { 4 | const tags = [ 5 | "c", 6 | "css", 7 | "express", 8 | "firebase", 9 | "html", 10 | "java", 11 | "javascript", 12 | "mern", 13 | "mongodb", 14 | "mysql", 15 | "next.js", 16 | "node.js", 17 | "php", 18 | "python", 19 | "reactjs", 20 | ]; 21 | 22 | return ( 23 |
24 |

Watched tags

25 |
26 | {tags.map((tag) => ( 27 |

{tag}

28 | ))} 29 |
30 |
31 | ); 32 | }; 33 | 34 | export default WidgetTags; 35 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import { Provider } from "react-redux"; 6 | import { createStore, applyMiddleware, compose } from "redux"; 7 | import thunk from "redux-thunk"; 8 | import Reducers from "./reducers"; 9 | 10 | const store = createStore(Reducers, compose(applyMiddleware(thunk))); 11 | 12 | const root = ReactDOM.createRoot(document.getElementById("root")); 13 | root.render( 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /client/src/reducers/auth.js: -------------------------------------------------------------------------------- 1 | const authReducer = (state = { data: null }, action) => { 2 | switch (action.type) { 3 | case "AUTH": 4 | localStorage.setItem("Profile", JSON.stringify({ ...action?.data })); 5 | return { ...state, data: action?.data }; 6 | case "LOGOUT": 7 | localStorage.clear(); 8 | return { ...state, data: null }; 9 | default: 10 | return state; 11 | } 12 | }; 13 | 14 | export default authReducer; 15 | -------------------------------------------------------------------------------- /client/src/reducers/currentUser.js: -------------------------------------------------------------------------------- 1 | const currentUserReducer = (state = null, action) => { 2 | switch (action.type) { 3 | case "FETCH_CURRENT_USER": 4 | return action.payload; 5 | default: 6 | return state; 7 | } 8 | }; 9 | 10 | export default currentUserReducer; 11 | -------------------------------------------------------------------------------- /client/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import authReducer from "./auth"; 3 | import currentUserReducer from "./currentUser"; 4 | import questionsReducer from "./questions"; 5 | import usersReducer from "./users"; 6 | 7 | export default combineReducers({ 8 | authReducer, 9 | currentUserReducer, 10 | questionsReducer, 11 | usersReducer, 12 | }); 13 | -------------------------------------------------------------------------------- /client/src/reducers/questions.js: -------------------------------------------------------------------------------- 1 | const questionsReducer = (state = { data: null }, action) => { 2 | switch (action.type) { 3 | case "POST_QUESTION": 4 | return { ...state }; 5 | case "POST_ANSWER": 6 | return { ...state }; 7 | case "FETCH_ALL_QUESTIONS": 8 | return { ...state, data: action.payload }; 9 | default: 10 | return state; 11 | } 12 | }; 13 | export default questionsReducer; 14 | -------------------------------------------------------------------------------- /client/src/reducers/users.js: -------------------------------------------------------------------------------- 1 | const usersReducer = (states = [], action) => { 2 | switch (action.type) { 3 | case "FETCH_USERS": 4 | return action.payload; 5 | case "UPDATE_CURRENT_USER": 6 | return states.map((state) => 7 | state._id === action.payload._id ? action.payload : state 8 | ); 9 | default: 10 | return states; 11 | } 12 | }; 13 | 14 | export default usersReducer; 15 | -------------------------------------------------------------------------------- /server/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Manoj-Athi/Stack-overflow/4cffe2048c4b21cdb908a00bb65c70cd20a79312/server/.DS_Store -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | PORT = "5000" 2 | CONNECTION_URL = "Mongo db url" 3 | JWT_SECRET = "This could be anything like test" -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .env -------------------------------------------------------------------------------- /server/connectMongoDb.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const connectDB = async () => { 4 | try { 5 | const conn = await mongoose.connect(process.env.CONNECTION_URL, { 6 | useNewUrlParser: true, 7 | useUnifiedTopology: true, 8 | }); 9 | console.log(`MongoDB connected: ${conn.connection.host}`); 10 | } catch (error) { 11 | console.error(error); 12 | process.exit(1); 13 | } 14 | }; 15 | 16 | export default connectDB; 17 | -------------------------------------------------------------------------------- /server/controllers/Answers.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import Questions from "../models/Questions.js"; 3 | 4 | export const postAnswer = async (req, res) => { 5 | const { id: _id } = req.params; 6 | const { noOfAnswers, answerBody, userAnswered } = req.body; 7 | const userId = req.userId; 8 | if (!mongoose.Types.ObjectId.isValid(_id)) { 9 | return res.status(404).send("question unavailable..."); 10 | } 11 | 12 | updateNoOfQuestions(_id, noOfAnswers); 13 | try { 14 | const updatedQuestion = await Questions.findByIdAndUpdate(_id, { 15 | $addToSet: { answer: [{ answerBody, userAnswered, userId }] }, 16 | }); 17 | res.status(200).json(updatedQuestion); 18 | } catch (error) { 19 | res.status(400).json("error in updating"); 20 | } 21 | }; 22 | 23 | const updateNoOfQuestions = async (_id, noOfAnswers) => { 24 | try { 25 | await Questions.findByIdAndUpdate(_id, { 26 | $set: { noOfAnswers: noOfAnswers }, 27 | }); 28 | } catch (error) { 29 | console.log(error); 30 | } 31 | }; 32 | 33 | export const deleteAnswer = async (req, res) => { 34 | const { id: _id } = req.params; 35 | const { answerId, noOfAnswers } = req.body; 36 | 37 | if (!mongoose.Types.ObjectId.isValid(_id)) { 38 | return res.status(404).send("Question unavailable..."); 39 | } 40 | if (!mongoose.Types.ObjectId.isValid(answerId)) { 41 | return res.status(404).send("Answer unavailable..."); 42 | } 43 | updateNoOfQuestions(_id, noOfAnswers); 44 | try { 45 | await Questions.updateOne( 46 | { _id }, 47 | { $pull: { answer: { _id: answerId } } } 48 | ); 49 | res.status(200).json({ message: "Successfully deleted..." }); 50 | } catch (error) { 51 | res.status(405).json(error); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /server/controllers/Questions.js: -------------------------------------------------------------------------------- 1 | import Questions from "../models/Questions.js"; 2 | import mongoose from "mongoose"; 3 | 4 | export const AskQuestion = async (req, res) => { 5 | const postQuestionData = req.body; 6 | const userId = req.userId; 7 | const postQuestion = new Questions({ ...postQuestionData, userId }); 8 | try { 9 | await postQuestion.save(); 10 | res.status(200).json("Posted a question successfully"); 11 | } catch (error) { 12 | console.log(error); 13 | res.status(409).json("Couldn't post a new question"); 14 | } 15 | }; 16 | 17 | export const getAllQuestions = async (req, res) => { 18 | try { 19 | const questionList = await Questions.find().sort({ askedOn: -1 }); 20 | res.status(200).json(questionList); 21 | } catch (error) { 22 | res.status(404).json({ message: error.message }); 23 | } 24 | }; 25 | 26 | export const deleteQuestion = async (req, res) => { 27 | const { id: _id } = req.params; 28 | 29 | if (!mongoose.Types.ObjectId.isValid(_id)) { 30 | return res.status(404).send("question unavailable..."); 31 | } 32 | 33 | try { 34 | await Questions.findByIdAndRemove(_id); 35 | res.status(200).json({ message: "successfully deleted..." }); 36 | } catch (error) { 37 | res.status(404).json({ message: error.message }); 38 | } 39 | }; 40 | 41 | export const voteQuestion = async (req, res) => { 42 | const { id: _id } = req.params; 43 | const { value } = req.body; 44 | const userId = req.userId; 45 | 46 | if (!mongoose.Types.ObjectId.isValid(_id)) { 47 | return res.status(404).send("question unavailable..."); 48 | } 49 | 50 | try { 51 | const question = await Questions.findById(_id); 52 | const upIndex = question.upVote.findIndex((id) => id === String(userId)); 53 | const downIndex = question.downVote.findIndex( 54 | (id) => id === String(userId) 55 | ); 56 | 57 | if (value === "upVote") { 58 | if (downIndex !== -1) { 59 | question.downVote = question.downVote.filter( 60 | (id) => id !== String(userId) 61 | ); 62 | } 63 | if (upIndex === -1) { 64 | question.upVote.push(userId); 65 | } else { 66 | question.upVote = question.upVote.filter((id) => id !== String(userId)); 67 | } 68 | } else if (value === "downVote") { 69 | if (upIndex !== -1) { 70 | question.upVote = question.upVote.filter((id) => id !== String(userId)); 71 | } 72 | if (downIndex === -1) { 73 | question.downVote.push(userId); 74 | } else { 75 | question.downVote = question.downVote.filter( 76 | (id) => id !== String(userId) 77 | ); 78 | } 79 | } 80 | await Questions.findByIdAndUpdate(_id, question); 81 | res.status(200).json({ message: "voted successfully..." }); 82 | } catch (error) { 83 | res.status(404).json({ message: "id not found" }); 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /server/controllers/auth.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import bcrypt from "bcryptjs"; 3 | 4 | import users from "../models/auth.js"; 5 | 6 | export const signup = async (req, res) => { 7 | const { name, email, password } = req.body; 8 | try { 9 | const existinguser = await users.findOne({ email }); 10 | if (existinguser) { 11 | return res.status(404).json({ message: "User already Exist." }); 12 | } 13 | 14 | const hashedPassword = await bcrypt.hash(password, 12); 15 | const newUser = await users.create({ 16 | name, 17 | email, 18 | password: hashedPassword, 19 | }); 20 | const token = jwt.sign( 21 | { email: newUser.email, id: newUser._id }, 22 | process.env.JWT_SECRET, 23 | { expiresIn: "1h" } 24 | ); 25 | res.status(200).json({ result: newUser, token }); 26 | } catch (error) { 27 | res.status(500).json("Something went worng..."); 28 | } 29 | }; 30 | 31 | export const login = async (req, res) => { 32 | const { email, password } = req.body; 33 | try { 34 | const existinguser = await users.findOne({ email }); 35 | if (!existinguser) { 36 | return res.status(404).json({ message: "User don't Exist." }); 37 | } 38 | const isPasswordCrt = await bcrypt.compare(password, existinguser.password); 39 | if (!isPasswordCrt) { 40 | return res.status(400).json({ message: "Invalid credentials" }); 41 | } 42 | const token = jwt.sign( 43 | { email: existinguser.email, id: existinguser._id }, 44 | process.env.JWT_SECRET, 45 | { expiresIn: "1h" } 46 | ); 47 | res.status(200).json({ result: existinguser, token }); 48 | } catch (error) { 49 | res.status(500).json("Something went worng..."); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /server/controllers/users.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import users from "../models/auth.js"; 3 | 4 | export const getAllUsers = async (req, res) => { 5 | try { 6 | const allUsers = await users.find(); 7 | const allUserDetails = []; 8 | allUsers.forEach((user) => { 9 | allUserDetails.push({ 10 | _id: user._id, 11 | name: user.name, 12 | about: user.about, 13 | tags: user.tags, 14 | joinedOn: user.joinedOn, 15 | }); 16 | }); 17 | res.status(200).json(allUserDetails); 18 | } catch (error) { 19 | res.status(404).json({ message: error.message }); 20 | } 21 | }; 22 | 23 | export const updateProfile = async (req, res) => { 24 | const { id: _id } = req.params; 25 | const { name, about, tags } = req.body; 26 | 27 | if (!mongoose.Types.ObjectId.isValid(_id)) { 28 | return res.status(404).send("question unavailable..."); 29 | } 30 | 31 | try { 32 | const updatedProfile = await users.findByIdAndUpdate( 33 | _id, 34 | { $set: { name: name, about: about, tags: tags } }, 35 | { new: true } 36 | ); 37 | res.status(200).json(updatedProfile); 38 | } catch (error) { 39 | res.status(405).json({ message: error.message }); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import cors from "cors"; 3 | import dotenv from "dotenv"; 4 | 5 | import userRoutes from "./routes/users.js"; 6 | import questionRoutes from "./routes/Questions.js"; 7 | import answerRoutes from "./routes/Answers.js"; 8 | import connectDB from "./connectMongoDb.js"; 9 | 10 | dotenv.config(); 11 | connectDB(); 12 | const app = express(); 13 | app.use(express.json({ limit: "30mb", extended: true })); 14 | app.use(express.urlencoded({ limit: "30mb", extended: true })); 15 | app.use(cors()); 16 | 17 | // app.use('/',(req, res) => { 18 | // res.send("This is a stack overflow clone API") 19 | // }) 20 | 21 | app.use("/user", userRoutes); 22 | app.use("/questions", questionRoutes); 23 | app.use("/answer", answerRoutes); 24 | 25 | const PORT = process.env.PORT || 5000; 26 | 27 | app.listen(PORT, () => { 28 | console.log(`server running on port ${PORT}`); 29 | }); 30 | -------------------------------------------------------------------------------- /server/middleware/auth.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | const auth = (req, res, next) => { 4 | try { 5 | const token = req.headers.authorization.split(" ")[1]; 6 | 7 | let decodeData = jwt.verify(token, process.env.JWT_SECRET); 8 | req.userId = decodeData?.id; 9 | 10 | next(); 11 | } catch (error) { 12 | console.log(error); 13 | } 14 | }; 15 | 16 | export default auth; 17 | -------------------------------------------------------------------------------- /server/models/Questions.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const QuestionSchema = mongoose.Schema({ 4 | questionTitle: { type: String, required: "Question must have a title" }, 5 | questionBody: { type: String, required: "Question must have a body" }, 6 | questionTags: { type: [String], required: "Question must have a tags" }, 7 | noOfAnswers: { type: Number, default: 0 }, 8 | upVote: { type: [String], default: [] }, 9 | downVote: { type: [String], default: [] }, 10 | userPosted: { type: String, required: "Question must have an author" }, 11 | userId: { type: String }, 12 | askedOn: { type: Date, default: Date.now }, 13 | answer: [ 14 | { 15 | answerBody: String, 16 | userAnswered: String, 17 | userId: String, 18 | answeredOn: { type: Date, default: Date.now }, 19 | }, 20 | ], 21 | }); 22 | 23 | export default mongoose.model("Question", QuestionSchema); 24 | -------------------------------------------------------------------------------- /server/models/auth.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const userSchema = mongoose.Schema({ 4 | name: { type: String, required: true }, 5 | email: { type: String, required: true }, 6 | password: { type: String, required: true }, 7 | about: { type: String }, 8 | tags: { type: [String] }, 9 | joinedOn: { type: Date, default: Date.now }, 10 | }); 11 | 12 | export default mongoose.model("User", userSchema); 13 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node index.js" 9 | }, 10 | "author": "Manoj Kumar", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcryptjs": "^2.4.3", 14 | "cors": "^2.8.5", 15 | "dotenv": "^10.0.0", 16 | "express": "^4.17.1", 17 | "jsonwebtoken": "^8.5.1", 18 | "mongoose": "^6.0.12", 19 | "nodemon": "^2.0.14" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/routes/Answers.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | import { postAnswer, deleteAnswer } from "../controllers/Answers.js"; 4 | import auth from "../middleware/auth.js"; 5 | 6 | const router = express.Router(); 7 | 8 | router.patch("/post/:id", auth, postAnswer); 9 | router.patch("/delete/:id", auth, deleteAnswer); 10 | 11 | export default router; 12 | -------------------------------------------------------------------------------- /server/routes/Questions.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | import { 4 | AskQuestion, 5 | getAllQuestions, 6 | deleteQuestion, 7 | voteQuestion, 8 | } from "../controllers/Questions.js"; 9 | import auth from "../middleware/auth.js"; 10 | 11 | const router = express.Router(); 12 | 13 | router.post("/Ask", auth, AskQuestion); 14 | router.get("/get", getAllQuestions); 15 | router.delete("/delete/:id", auth, deleteQuestion); 16 | router.patch("/vote/:id", auth, voteQuestion); 17 | 18 | export default router; 19 | -------------------------------------------------------------------------------- /server/routes/users.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | import { login, signup } from "../controllers/auth.js"; 4 | import { getAllUsers, updateProfile } from "../controllers/users.js"; 5 | import auth from "../middleware/auth.js"; 6 | 7 | const router = express.Router(); 8 | 9 | router.post("/signup", signup); 10 | router.post("/login", login); 11 | 12 | router.get("/getAllUsers", getAllUsers); 13 | router.patch("/update/:id", auth, updateProfile); 14 | 15 | export default router; 16 | -------------------------------------------------------------------------------- /server/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "server", 4 | "builds": [ 5 | { 6 | "src": "index.js", 7 | "use": "@vercel/node" 8 | } 9 | ], 10 | "routes": [ 11 | { 12 | "src": "/(.*)", 13 | "dest": "index.js" 14 | } 15 | ] 16 | } 17 | --------------------------------------------------------------------------------