├── README.md ├── View Source Code ├── README.md ├── instruction.md ├── package-lock.json ├── package.json ├── public │ ├── index.html │ ├── manifest.json │ └── robots.txt └── src │ ├── App.js │ ├── index.css │ └── index.js ├── instruction.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── server ├── controllers │ ├── authController.js │ ├── postController.js │ └── userController.js ├── dbConfig │ └── index.js ├── index.js ├── middleware │ ├── authMiddleware.js │ └── errorMiddleware.js ├── models │ ├── PasswordReset.js │ ├── commentModel.js │ ├── emailVerification.js │ ├── friendRequest.js │ ├── postModel.js │ └── userModel.js ├── package-lock.json ├── package.json ├── routes │ ├── authRoutes.js │ ├── index.js │ ├── postRoutes.js │ └── userRoutes.js ├── utils │ ├── index.js │ └── sendEmail.js └── views │ └── build │ ├── asset-manifest.json │ ├── index.html │ ├── manifest.json │ ├── robots.txt │ └── static │ ├── css │ ├── main.2609a62c.css │ └── main.2609a62c.css.map │ └── js │ ├── main.4e9a0992.js │ ├── main.4e9a0992.js.LICENSE.txt │ ├── main.4e9a0992.js.map │ ├── main.5b2b5904.js │ ├── main.5b2b5904.js.LICENSE.txt │ └── main.5b2b5904.js.map ├── src ├── App.js ├── assets │ ├── data.js │ ├── img.jpeg │ ├── index.js │ └── userprofile.png ├── components │ ├── CustomButton.jsx │ ├── EditProfile.jsx │ ├── FriendsCard.jsx │ ├── Loading.jsx │ ├── PostCard.jsx │ ├── ProfileCard.jsx │ ├── TextInput.jsx │ ├── TopBar.jsx │ └── index.js ├── index.css ├── index.js ├── pages │ ├── Home.jsx │ ├── Login.jsx │ ├── Profile.jsx │ ├── Register.jsx │ ├── ResetPassword.jsx │ └── index.js └── redux │ ├── postSlice.js │ ├── reducer.js │ ├── store.js │ ├── theme.js │ └── userSlice.js └── tailwind.config.js /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 | 72 | 73 | 74 | # INTRUCTIONS 75 | 76 | # SERVER OR BACKEND 77 | Firstly move to the server directory eg: cd server 78 | 79 | 1. Create a .env file 80 | The .env file will contain the following: 81 | i. MONGODB_URL = database connection string 82 | ii. JWT_SECRET_KEY = your secreate key 83 | iii. PORT = 8800 84 | iv. AUTH_EMAIL= email address 85 | v. AUTH_PASSWORD=email access password 86 | vi. APP_URL = http://localhost:8800/api-v1 87 | 88 | Note: I used hotmail account to send verification email, so you can just create one 89 | because hotmail account is reliable in product and has no configuration. 90 | 91 | Alos, chnage API_URL when you deploy your app else use localhost with the appropriate port number 92 | 93 | 2. Run npm install to install the packages 94 | 3. Run npm start to start the server 95 | 96 | 97 | CLINET SIDE 98 | 99 | The client or frontend also has .env filde in the root folder. 100 | Create an environment variable of name REACT_APP_CLOUDINARY_ID. 101 | This will store the cloudinary cloud name 102 | 103 | 104 | 105 | For Support, Contact: 106 | Email: codewavewithasante@gmail.com 107 | Telegram: https://t.me/Codewave_with_asante 108 | -------------------------------------------------------------------------------- /View Source Code/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 | -------------------------------------------------------------------------------- /View Source Code/instruction.md: -------------------------------------------------------------------------------- 1 | Full-Stack Social Media Application using ReactJs, and Tailwind CSS for the front end and NodeJs, ExpressJs and MongoDb for backend. 2 | This App is fully responsive. This project includes for frontend (UI Design) and Backend (Server). 3 | 4 | # Functionalities: 5 | 1. #User Authentication and Authorisation 6 | 2. #Email Verification 7 | 3. #Password reset 8 | 4. #Create Post 9 | 5. #Advance Comment system (comments with sub coments) 10 | 6. #Like post and comments 11 | 7. #Delete post 12 | 8. #Friend Request (send request, accept or deby) 13 | and others..... 14 | 15 | 16 | # Getting Started 17 | 18 | # SERVER OR BACKEND 19 | Firstly move to the server directory eg: cd server 20 | 21 | 1. Create a `.env` file 22 | The .env file will contain the following: 23 | i. MONGODB_URL = `database connection string` 24 | ii. JWT_SECRET_KEY = `your secreate key` 25 | iii. PORT = `8800` 26 | iv. AUTH_EMAIL= `email address` 27 | v. AUTH_PASSWORD= `email access password` 28 | vi. APP_URL = `http://localhost:8800` 29 | 30 | Note: I used hotmail account to send verification email, so you can just create one because hotmail account is reliable in product and has no configuration. 31 | 32 | Alos, change `API_URL` when you deploy your app else use localhost with the appropriate `port number` 33 | 34 | 2. Run `npm install` to install the packages 35 | 3. Run `npm start` to start the server 36 | 37 | # VIEWS FILE 38 | In the view are the static html files to be use for `email verfication` and `password reset`. 39 | 40 | 1. This folder is a React App 41 | 2. navigate in the folder and install it dependencies 42 | 3. make changes to suit your preference and run build 43 | 4. copy the build folder into the view folder in the server folder 44 | 45 | **Override the existing one.** 46 | NOTE: During deployment make sure you change the various links in the view file and build it again and replace the files in the view folder of the server folder. 47 | 48 | 49 | # CLINET SIDE 50 | 51 | The client or frontend also has .env filde in the root folder. 52 | Create an environment variable of name `REACT_APP_CLOUDINARY_ID`. 53 | This will store the cloudinary cloud name 54 | 55 | This side also has and env file with `REACT_APP_CLOUDINARY_ID` 56 | -------------------------------------------------------------------------------- /View Source Code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "views", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.17.0", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^2.1.4" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /View Source Code/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | ShareFun 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /View Source Code/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 | -------------------------------------------------------------------------------- /View Source Code/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /View Source Code/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | 3 | function App() { 4 | const urlParams = new URLSearchParams(window.location.search); 5 | const messageParam = urlParams.get("message"); 6 | const statusParam = urlParams.get("status"); 7 | const typeParam = urlParams.get("type"); 8 | const userId = urlParams.get("id"); 9 | 10 | const [message, setMessage] = useState(null); 11 | const [icon, setIcon] = useState(null); 12 | const [errorMessage, setErrorMessage] = useState(null); 13 | 14 | //frontend url - change it when deployed 15 | const url = `http://localhost:3000/login`; 16 | // const url = `${process.env.REACT_APP_URL}/login`; 17 | 18 | const handleSubmit = async (e) => { 19 | e.preventDefault(); 20 | setErrorMessage(null); 21 | 22 | const password = e.target[0].value; 23 | const confirmPassword = e.target[1].value; 24 | 25 | if (password !== confirmPassword) { 26 | window.alert("Passwords do not match. Please try again."); 27 | return; 28 | } 29 | 30 | // server url - change it when deployed 31 | const apiUrl = `http://localhost:8800/api-v1/users/reset-password`; 32 | // const apiUrl = `${process.env.REACT_APP_URL}/users/reset-password`; 33 | 34 | try { 35 | const response = await fetch(apiUrl, { 36 | method: "POST", 37 | headers: { 38 | "Content-Type": "application/json", 39 | }, 40 | body: JSON.stringify({ userId, password }), 41 | }); 42 | 43 | if (response.ok) { 44 | window.alert("Password reset successful!"); 45 | window.location.replace(url); 46 | setErrorMessage({ 47 | status: "ok", 48 | msg: "Password reset successful!", 49 | }); 50 | } else { 51 | setErrorMessage({ 52 | status: "failed", 53 | msg: "Password reset failed. Please try again.", 54 | }); 55 | } 56 | } catch (error) { 57 | setErrorMessage({ 58 | status: "failed", 59 | msg: "An error occurred. Please try again later.", 60 | }); 61 | console.log("An error occurred:", error); 62 | } 63 | }; 64 | 65 | const EmailVerification = () => { 66 | return ( 67 |
68 |
69 | {icon} 70 |
71 |
75 | {message} 76 |
77 | {statusParam === "success" && ( 78 | 79 | Login 80 | 81 | )} 82 |
83 | ); 84 | }; 85 | 86 | const PasswordReset = () => { 87 | return ( 88 |
89 |
Password Reset
90 |
91 | 98 | 105 | {errorMessage?.status !== "ok" && ( 106 | 109 | )} 110 |
111 | {errorMessage?.status === "ok" && ( 112 | 113 | Login 114 | 115 | )} 116 |
120 | {errorMessage?.msg} 121 |
122 |
123 | ); 124 | }; 125 | 126 | useEffect(() => { 127 | if (!typeParam) { 128 | if (statusParam === "success") { 129 | setIcon("✔️"); 130 | setMessage(messageParam); 131 | } else if (statusParam === "error") { 132 | setIcon("❌"); 133 | setMessage(messageParam); 134 | } else { 135 | setIcon("❓"); 136 | setMessage("Something went wrong. Try again"); 137 | } 138 | } else { 139 | } 140 | }, [typeParam, messageParam, statusParam]); 141 | 142 | return ( 143 |
144 | {typeParam ? : } 145 |
146 | ); 147 | } 148 | 149 | export default App; 150 | -------------------------------------------------------------------------------- /View Source Code/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 | text-align: center; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | height: 100vh; 14 | margin: 0; 15 | background-color: rgba(0, 0, 0, 0.2); 16 | } 17 | 18 | .card { 19 | 20 | border-radius: 10px; 21 | padding: 20px; 22 | background-color: white; 23 | box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2); 24 | } 25 | #resetForm{ 26 | display: flex; 27 | flex-direction: column; 28 | } 29 | #statusMessage{ 30 | font-size: 18px !important; 31 | font-weight: 500; 32 | margin-top: 15px; 33 | } 34 | .icon { 35 | font-size: 64px; 36 | margin-bottom: 20px; 37 | } 38 | 39 | .success { 40 | color: green; 41 | 42 | } 43 | 44 | .error { 45 | color: red; 46 | 47 | } 48 | 49 | .showBtn { 50 | display: flex; 51 | text-align: center; 52 | align-items: center; 53 | justify-content: center; 54 | border-radius: 5px; 55 | padding: 10px; 56 | background-color: rgba(1, 116, 210, 0.971); 57 | color: white; 58 | margin-top: 10px; 59 | text-decoration: none; 60 | } 61 | .title { 62 | font-size: 24px; 63 | margin-bottom: 20px; 64 | } 65 | 66 | .input-field { 67 | width: 90%; 68 | padding: 14px; 69 | margin-bottom: 20px; 70 | border: 1px solid #ccc; 71 | border-radius: 5px; 72 | font-size: 16px; 73 | 74 | } 75 | 76 | .message { 77 | font-size: 14px; 78 | margin-top: 10px; 79 | } 80 | 81 | .success { 82 | color: green; 83 | } 84 | 85 | .error { 86 | color: red; 87 | } 88 | 89 | .submit-button { 90 | font-size: 16px;; 91 | background-color: #007bff; 92 | color: white; 93 | border: none; 94 | border-radius: 5px; 95 | padding: 15px 20px; 96 | cursor: pointer; 97 | transition: background-color 0.3s ease; 98 | } 99 | 100 | .submit-button:hover { 101 | background-color: #0056b3; 102 | } 103 | 104 | -------------------------------------------------------------------------------- /View Source Code/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 | 6 | const root = ReactDOM.createRoot(document.getElementById("root")); 7 | root.render( 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /instruction.md: -------------------------------------------------------------------------------- 1 | INTRUCTIONS 2 | 3 | SERVER OR BACKEND 4 | Firstly move to the server directory eg: cd server 5 | 6 | 1. Create a .env file 7 | The .env file will contain the following: 8 | i. MONGODB_URL = database connection string 9 | ii. JWT_SECRET_KEY = your secreate key 10 | iii. PORT = 8800 11 | iv. AUTH_EMAIL= email address 12 | v. AUTH_PASSWORD=email access password 13 | vi. APP_URL = http://localhost:8800/api-v1 14 | 15 | Note: I used hotmail account to send verification email, so you can just create one 16 | because hotmail account is reliable in product and has no configuration. 17 | 18 | Alos, chnage API_URL when you deploy your app else use localhost with the appropriate port number 19 | 20 | 2. Run npm install to install the packages 21 | 3. Run npm start to start the server 22 | 23 | 24 | CLINET SIDE 25 | 26 | The client or frontend also has .env filde in the root folder. 27 | Create an environment variable of name REACT_APP_CLOUDINARY_ID. 28 | This will store the cloudinary cloud name 29 | 30 | 31 | 32 | For Support, Contact: 33 | Email: codewavewithasante@gmail.com 34 | Telegram: https://t.me/Codewave_with_asante 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.9.5", 7 | "@testing-library/jest-dom": "^5.17.0", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "axios": "^1.4.0", 11 | "dotenv": "^16.3.1", 12 | "moment": "^2.29.4", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-hook-form": "^7.45.4", 16 | "react-icons": "^4.10.1", 17 | "react-redux": "^8.1.2", 18 | "react-router-dom": "^6.15.0", 19 | "react-scripts": "5.0.1", 20 | "web-vitals": "^2.1.4" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "tailwindcss": "^3.3.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeWaveWithAsante/FullStackSocialMedia/e1170e017257f69e18dea7e2294c03fba3cbc79a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | ShareFun 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeWaveWithAsante/FullStackSocialMedia/e1170e017257f69e18dea7e2294c03fba3cbc79a/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeWaveWithAsante/FullStackSocialMedia/e1170e017257f69e18dea7e2294c03fba3cbc79a/public/logo512.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/controllers/authController.js: -------------------------------------------------------------------------------- 1 | import Users from "../models/userModel.js"; 2 | import { compareString, createJWT, hashString } from "../utils/index.js"; 3 | import { sendVerificationEmail } from "../utils/sendEmail.js"; 4 | 5 | export const register = async (req, res, next) => { 6 | const { firstName, lastName, email, password } = req.body; 7 | 8 | //validate fileds 9 | if (!(firstName || lastName || email || password)) { 10 | next("Provide Required Fields!"); 11 | return; 12 | } 13 | 14 | try { 15 | const userExist = await Users.findOne({ email }); 16 | 17 | if (userExist) { 18 | next("Email Address already exists"); 19 | return; 20 | } 21 | 22 | const hashedPassword = await hashString(password); 23 | 24 | const user = await Users.create({ 25 | firstName, 26 | lastName, 27 | email, 28 | password: hashedPassword, 29 | }); 30 | 31 | //send email verification to user 32 | sendVerificationEmail(user, res); 33 | } catch (error) { 34 | console.log(error); 35 | res.status(404).json({ message: error.message }); 36 | } 37 | }; 38 | 39 | export const login = async (req, res, next) => { 40 | const { email, password } = req.body; 41 | 42 | try { 43 | //validation 44 | if (!email || !password) { 45 | next("Please Provide User Credentials"); 46 | return; 47 | } 48 | 49 | // find user by email 50 | const user = await Users.findOne({ email }).select("+password").populate({ 51 | path: "friends", 52 | select: "firstName lastName location profileUrl -password", 53 | }); 54 | 55 | if (!user) { 56 | next("Invalid email or password"); 57 | return; 58 | } 59 | 60 | if (!user?.verified) { 61 | next( 62 | "User email is not verified. Check your email account and verify your email" 63 | ); 64 | return; 65 | } 66 | 67 | // compare password 68 | const isMatch = await compareString(password, user?.password); 69 | 70 | if (!isMatch) { 71 | next("Invalid email or password"); 72 | return; 73 | } 74 | 75 | user.password = undefined; 76 | 77 | const token = createJWT(user?._id); 78 | 79 | res.status(201).json({ 80 | success: true, 81 | message: "Login successfully", 82 | user, 83 | token, 84 | }); 85 | } catch (error) { 86 | console.log(error); 87 | res.status(404).json({ message: error.message }); 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /server/controllers/postController.js: -------------------------------------------------------------------------------- 1 | import Comments from "../models/commentModel.js"; 2 | import Posts from "../models/postModel.js"; 3 | import Users from "../models/userModel.js"; 4 | 5 | export const createPost = async (req, res, next) => { 6 | try { 7 | const { userId } = req.body.user; 8 | const { description, image } = req.body; 9 | 10 | if (!description) { 11 | next("You must provide a description"); 12 | return; 13 | } 14 | 15 | const post = await Posts.create({ 16 | userId, 17 | description, 18 | image, 19 | }); 20 | 21 | res.status(200).json({ 22 | sucess: true, 23 | message: "Post created successfully", 24 | data: post, 25 | }); 26 | } catch (error) { 27 | console.log(error); 28 | res.status(404).json({ message: error.message }); 29 | } 30 | }; 31 | 32 | export const getPosts = async (req, res, next) => { 33 | try { 34 | const { userId } = req.body.user; 35 | const { search } = req.body; 36 | 37 | const user = await Users.findById(userId); 38 | const friends = user?.friends?.toString().split(",") ?? []; 39 | friends.push(userId); 40 | 41 | const searchPostQuery = { 42 | $or: [ 43 | { 44 | description: { $regex: search, $options: "i" }, 45 | }, 46 | ], 47 | }; 48 | 49 | const posts = await Posts.find(search ? searchPostQuery : {}) 50 | .populate({ 51 | path: "userId", 52 | select: "firstName lastName location profileUrl -password", 53 | }) 54 | .sort({ _id: -1 }); 55 | 56 | const friendsPosts = posts?.filter((post) => { 57 | return friends.includes(post?.userId?._id.toString()); 58 | }); 59 | 60 | const otherPosts = posts?.filter( 61 | (post) => !friends.includes(post?.userId?._id.toString()) 62 | ); 63 | 64 | let postsRes = null; 65 | 66 | if (friendsPosts?.length > 0) { 67 | postsRes = search ? friendsPosts : [...friendsPosts, ...otherPosts]; 68 | } else { 69 | postsRes = posts; 70 | } 71 | 72 | res.status(200).json({ 73 | sucess: true, 74 | message: "successfully", 75 | data: postsRes, 76 | }); 77 | } catch (error) { 78 | console.log(error); 79 | res.status(404).json({ message: error.message }); 80 | } 81 | }; 82 | 83 | export const getPost = async (req, res, next) => { 84 | try { 85 | const { id } = req.params; 86 | 87 | const post = await Posts.findById(id).populate({ 88 | path: "userId", 89 | select: "firstName lastName location profileUrl -password", 90 | }); 91 | // .populate({ 92 | // path: "comments", 93 | // populate: { 94 | // path: "userId", 95 | // select: "firstName lastName location profileUrl -password", 96 | // }, 97 | // options: { 98 | // sort: "-_id", 99 | // }, 100 | // }) 101 | // .populate({ 102 | // path: "comments", 103 | // populate: { 104 | // path: "replies.userId", 105 | // select: "firstName lastName location profileUrl -password", 106 | // }, 107 | // }); 108 | 109 | res.status(200).json({ 110 | sucess: true, 111 | message: "successfully", 112 | data: post, 113 | }); 114 | } catch (error) { 115 | console.log(error); 116 | res.status(404).json({ message: error.message }); 117 | } 118 | }; 119 | 120 | export const getUserPost = async (req, res, next) => { 121 | try { 122 | const { id } = req.params; 123 | 124 | const post = await Posts.find({ userId: id }) 125 | .populate({ 126 | path: "userId", 127 | select: "firstName lastName location profileUrl -password", 128 | }) 129 | .sort({ _id: -1 }); 130 | 131 | res.status(200).json({ 132 | sucess: true, 133 | message: "successfully", 134 | data: post, 135 | }); 136 | } catch (error) { 137 | console.log(error); 138 | res.status(404).json({ message: error.message }); 139 | } 140 | }; 141 | 142 | export const getComments = async (req, res, next) => { 143 | try { 144 | const { postId } = req.params; 145 | 146 | const postComments = await Comments.find({ postId }) 147 | .populate({ 148 | path: "userId", 149 | select: "firstName lastName location profileUrl -password", 150 | }) 151 | .populate({ 152 | path: "replies.userId", 153 | select: "firstName lastName location profileUrl -password", 154 | }) 155 | .sort({ _id: -1 }); 156 | 157 | res.status(200).json({ 158 | sucess: true, 159 | message: "successfully", 160 | data: postComments, 161 | }); 162 | } catch (error) { 163 | console.log(error); 164 | res.status(404).json({ message: error.message }); 165 | } 166 | }; 167 | 168 | export const likePost = async (req, res, next) => { 169 | try { 170 | const { userId } = req.body.user; 171 | const { id } = req.params; 172 | 173 | const post = await Posts.findById(id); 174 | 175 | const index = post.likes.findIndex((pid) => pid === String(userId)); 176 | 177 | if (index === -1) { 178 | post.likes.push(userId); 179 | } else { 180 | post.likes = post.likes.filter((pid) => pid !== String(userId)); 181 | } 182 | 183 | const newPost = await Posts.findByIdAndUpdate(id, post, { 184 | new: true, 185 | }); 186 | 187 | res.status(200).json({ 188 | sucess: true, 189 | message: "successfully", 190 | data: newPost, 191 | }); 192 | } catch (error) { 193 | console.log(error); 194 | res.status(404).json({ message: error.message }); 195 | } 196 | }; 197 | 198 | export const likePostComment = async (req, res, next) => { 199 | const { userId } = req.body.user; 200 | const { id, rid } = req.params; 201 | 202 | try { 203 | if (rid === undefined || rid === null || rid === `false`) { 204 | const comment = await Comments.findById(id); 205 | 206 | const index = comment.likes.findIndex((el) => el === String(userId)); 207 | 208 | if (index === -1) { 209 | comment.likes.push(userId); 210 | } else { 211 | comment.likes = comment.likes.filter((i) => i !== String(userId)); 212 | } 213 | 214 | const updated = await Comments.findByIdAndUpdate(id, comment, { 215 | new: true, 216 | }); 217 | 218 | res.status(201).json(updated); 219 | } else { 220 | const replyComments = await Comments.findOne( 221 | { _id: id }, 222 | { 223 | replies: { 224 | $elemMatch: { 225 | _id: rid, 226 | }, 227 | }, 228 | } 229 | ); 230 | 231 | const index = replyComments?.replies[0]?.likes.findIndex( 232 | (i) => i === String(userId) 233 | ); 234 | 235 | if (index === -1) { 236 | replyComments.replies[0].likes.push(userId); 237 | } else { 238 | replyComments.replies[0].likes = replyComments.replies[0]?.likes.filter( 239 | (i) => i !== String(userId) 240 | ); 241 | } 242 | 243 | const query = { _id: id, "replies._id": rid }; 244 | 245 | const updated = { 246 | $set: { 247 | "replies.$.likes": replyComments.replies[0].likes, 248 | }, 249 | }; 250 | 251 | const result = await Comments.updateOne(query, updated, { new: true }); 252 | 253 | res.status(201).json(result); 254 | } 255 | } catch (error) { 256 | console.log(error); 257 | res.status(404).json({ message: error.message }); 258 | } 259 | }; 260 | 261 | export const commentPost = async (req, res, next) => { 262 | try { 263 | const { comment, from } = req.body; 264 | const { userId } = req.body.user; 265 | const { id } = req.params; 266 | 267 | if (comment === null) { 268 | return res.status(404).json({ message: "Comment is required." }); 269 | } 270 | 271 | const newComment = new Comments({ comment, from, userId, postId: id }); 272 | 273 | await newComment.save(); 274 | 275 | //updating the post with the comments id 276 | const post = await Posts.findById(id); 277 | 278 | post.comments.push(newComment._id); 279 | 280 | const updatedPost = await Posts.findByIdAndUpdate(id, post, { 281 | new: true, 282 | }); 283 | 284 | res.status(201).json(newComment); 285 | } catch (error) { 286 | console.log(error); 287 | res.status(404).json({ message: error.message }); 288 | } 289 | }; 290 | 291 | export const replyPostComment = async (req, res, next) => { 292 | const { userId } = req.body.user; 293 | const { comment, replyAt, from } = req.body; 294 | const { id } = req.params; 295 | 296 | if (comment === null) { 297 | return res.status(404).json({ message: "Comment is required." }); 298 | } 299 | 300 | try { 301 | const commentInfo = await Comments.findById(id); 302 | 303 | commentInfo.replies.push({ 304 | comment, 305 | replyAt, 306 | from, 307 | userId, 308 | created_At: Date.now(), 309 | }); 310 | 311 | commentInfo.save(); 312 | 313 | res.status(200).json(commentInfo); 314 | } catch (error) { 315 | console.log(error); 316 | res.status(404).json({ message: error.message }); 317 | } 318 | }; 319 | 320 | export const deletePost = async (req, res, next) => { 321 | try { 322 | const { id } = req.params; 323 | 324 | await Posts.findByIdAndDelete(id); 325 | 326 | res.status(200).json({ 327 | success: true, 328 | message: "Deleted successfully", 329 | }); 330 | } catch (error) { 331 | console.log(error); 332 | res.status(404).json({ message: error.message }); 333 | } 334 | }; 335 | -------------------------------------------------------------------------------- /server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import Verification from "../models/emailVerification.js"; 3 | import Users from "../models/userModel.js"; 4 | import { compareString, createJWT, hashString } from "../utils/index.js"; 5 | import PasswordReset from "../models/PasswordReset.js"; 6 | import { resetPasswordLink } from "../utils/sendEmail.js"; 7 | import FriendRequest from "../models/friendRequest.js"; 8 | 9 | export const verifyEmail = async (req, res) => { 10 | const { userId, token } = req.params; 11 | 12 | try { 13 | const result = await Verification.findOne({ userId }); 14 | 15 | if (result) { 16 | const { expiresAt, token: hashedToken } = result; 17 | 18 | // token has expires 19 | if (expiresAt < Date.now()) { 20 | Verification.findOneAndDelete({ userId }) 21 | .then(() => { 22 | Users.findOneAndDelete({ _id: userId }) 23 | .then(() => { 24 | const message = "Verification token has expired."; 25 | res.redirect(`/users/verified?status=error&message=${message}`); 26 | }) 27 | .catch((err) => { 28 | res.redirect(`/users/verified?status=error&message=`); 29 | }); 30 | }) 31 | .catch((error) => { 32 | console.log(error); 33 | res.redirect(`/users/verified?message=`); 34 | }); 35 | } else { 36 | //token valid 37 | compareString(token, hashedToken) 38 | .then((isMatch) => { 39 | if (isMatch) { 40 | Users.findOneAndUpdate({ _id: userId }, { verified: true }) 41 | .then(() => { 42 | Verification.findOneAndDelete({ userId }).then(() => { 43 | const message = "Email verified successfully"; 44 | res.redirect( 45 | `/users/verified?status=success&message=${message}` 46 | ); 47 | }); 48 | }) 49 | .catch((err) => { 50 | console.log(err); 51 | const message = "Verification failed or link is invalid"; 52 | res.redirect( 53 | `/users/verified?status=error&message=${message}` 54 | ); 55 | }); 56 | } else { 57 | // invalid token 58 | const message = "Verification failed or link is invalid"; 59 | res.redirect(`/users/verified?status=error&message=${message}`); 60 | } 61 | }) 62 | .catch((err) => { 63 | console.log(err); 64 | res.redirect(`/users/verified?message=`); 65 | }); 66 | } 67 | } else { 68 | const message = "Invalid verification link. Try again later."; 69 | res.redirect(`/users/verified?status=error&message=${message}`); 70 | } 71 | } catch (error) { 72 | console.log(err); 73 | res.redirect(`/users/verified?message=`); 74 | } 75 | }; 76 | 77 | export const requestPasswordReset = async (req, res) => { 78 | try { 79 | const { email } = req.body; 80 | 81 | const user = await Users.findOne({ email }); 82 | 83 | if (!user) { 84 | return res.status(404).json({ 85 | status: "FAILED", 86 | message: "Email address not found.", 87 | }); 88 | } 89 | 90 | const existingRequest = await PasswordReset.findOne({ email }); 91 | if (existingRequest) { 92 | if (existingRequest.expiresAt > Date.now()) { 93 | return res.status(201).json({ 94 | status: "PENDING", 95 | message: "Reset password link has already been sent tp your email.", 96 | }); 97 | } 98 | await PasswordReset.findOneAndDelete({ email }); 99 | } 100 | await resetPasswordLink(user, res); 101 | } catch (error) { 102 | console.log(error); 103 | res.status(404).json({ message: error.message }); 104 | } 105 | }; 106 | 107 | export const resetPassword = async (req, res) => { 108 | const { userId, token } = req.params; 109 | 110 | try { 111 | // find record 112 | const user = await Users.findById(userId); 113 | 114 | if (!user) { 115 | const message = "Invalid password reset link. Try again"; 116 | res.redirect(`/users/resetpassword?status=error&message=${message}`); 117 | } 118 | 119 | const resetPassword = await PasswordReset.findOne({ userId }); 120 | 121 | if (!resetPassword) { 122 | const message = "Invalid password reset link. Try again"; 123 | return res.redirect( 124 | `/users/resetpassword?status=error&message=${message}` 125 | ); 126 | } 127 | 128 | const { expiresAt, token: resetToken } = resetPassword; 129 | 130 | if (expiresAt < Date.now()) { 131 | const message = "Reset Password link has expired. Please try again"; 132 | res.redirect(`/users/resetpassword?status=error&message=${message}`); 133 | } else { 134 | const isMatch = await compareString(token, resetToken); 135 | 136 | if (!isMatch) { 137 | const message = "Invalid reset password link. Please try again"; 138 | res.redirect(`/users/resetpassword?status=error&message=${message}`); 139 | } else { 140 | res.redirect(`/users/resetpassword?type=reset&id=${userId}`); 141 | } 142 | } 143 | } catch (error) { 144 | console.log(error); 145 | res.status(404).json({ message: error.message }); 146 | } 147 | }; 148 | 149 | export const changePassword = async (req, res, next) => { 150 | try { 151 | const { userId, password } = req.body; 152 | 153 | const hashedpassword = await hashString(password); 154 | 155 | const user = await Users.findByIdAndUpdate( 156 | { _id: userId }, 157 | { password: hashedpassword } 158 | ); 159 | 160 | if (user) { 161 | await PasswordReset.findOneAndDelete({ userId }); 162 | 163 | res.status(200).json({ 164 | ok: true, 165 | }); 166 | } 167 | } catch (error) { 168 | console.log(error); 169 | res.status(404).json({ message: error.message }); 170 | } 171 | }; 172 | 173 | export const getUser = async (req, res, next) => { 174 | try { 175 | const { userId } = req.body.user; 176 | const { id } = req.params; 177 | 178 | const user = await Users.findById(id ?? userId).populate({ 179 | path: "friends", 180 | select: "-password", 181 | }); 182 | 183 | if (!user) { 184 | return res.status(200).send({ 185 | message: "User Not Found", 186 | success: false, 187 | }); 188 | } 189 | 190 | user.password = undefined; 191 | 192 | res.status(200).json({ 193 | success: true, 194 | user: user, 195 | }); 196 | } catch (error) { 197 | console.log(error); 198 | res.status(500).json({ 199 | message: "auth error", 200 | success: false, 201 | error: error.message, 202 | }); 203 | } 204 | }; 205 | 206 | export const updateUser = async (req, res, next) => { 207 | try { 208 | const { firstName, lastName, location, profileUrl, profession } = req.body; 209 | 210 | if (!(firstName || lastName || contact || profession || location)) { 211 | next("Please provide all required fields"); 212 | return; 213 | } 214 | 215 | const { userId } = req.body.user; 216 | 217 | const updateUser = { 218 | firstName, 219 | lastName, 220 | location, 221 | profileUrl, 222 | profession, 223 | _id: userId, 224 | }; 225 | const user = await Users.findByIdAndUpdate(userId, updateUser, { 226 | new: true, 227 | }); 228 | 229 | await user.populate({ path: "friends", select: "-password" }); 230 | const token = createJWT(user?._id); 231 | 232 | user.password = undefined; 233 | 234 | res.status(200).json({ 235 | sucess: true, 236 | message: "User updated successfully", 237 | user, 238 | token, 239 | }); 240 | } catch (error) { 241 | console.log(error); 242 | res.status(404).json({ message: error.message }); 243 | } 244 | }; 245 | 246 | export const friendRequest = async (req, res, next) => { 247 | try { 248 | const { userId } = req.body.user; 249 | 250 | const { requestTo } = req.body; 251 | 252 | const requestExist = await FriendRequest.findOne({ 253 | requestFrom: userId, 254 | requestTo, 255 | }); 256 | 257 | if (requestExist) { 258 | next("Friend Request already sent."); 259 | return; 260 | } 261 | 262 | const accountExist = await FriendRequest.findOne({ 263 | requestFrom: requestTo, 264 | requestTo: userId, 265 | }); 266 | 267 | if (accountExist) { 268 | next("Friend Request already sent."); 269 | return; 270 | } 271 | 272 | const newRes = await FriendRequest.create({ 273 | requestTo, 274 | requestFrom: userId, 275 | }); 276 | 277 | res.status(201).json({ 278 | success: true, 279 | message: "Friend Request sent successfully", 280 | }); 281 | } catch (error) { 282 | console.log(error); 283 | res.status(500).json({ 284 | message: "auth error", 285 | success: false, 286 | error: error.message, 287 | }); 288 | } 289 | }; 290 | 291 | export const getFriendRequest = async (req, res) => { 292 | try { 293 | const { userId } = req.body.user; 294 | 295 | const request = await FriendRequest.find({ 296 | requestTo: userId, 297 | requestStatus: "Pending", 298 | }) 299 | .populate({ 300 | path: "requestFrom", 301 | select: "firstName lastName profileUrl profession -password", 302 | }) 303 | .limit(10) 304 | .sort({ 305 | _id: -1, 306 | }); 307 | 308 | res.status(200).json({ 309 | success: true, 310 | data: request, 311 | }); 312 | } catch (error) { 313 | console.log(error); 314 | res.status(500).json({ 315 | message: "auth error", 316 | success: false, 317 | error: error.message, 318 | }); 319 | } 320 | }; 321 | 322 | export const acceptRequest = async (req, res, next) => { 323 | try { 324 | const id = req.body.user.userId; 325 | 326 | const { rid, status } = req.body; 327 | 328 | const requestExist = await FriendRequest.findById(rid); 329 | 330 | if (!requestExist) { 331 | next("No Friend Request Found."); 332 | return; 333 | } 334 | 335 | const newRes = await FriendRequest.findByIdAndUpdate( 336 | { _id: rid }, 337 | { requestStatus: status } 338 | ); 339 | 340 | if (status === "Accepted") { 341 | const user = await Users.findById(id); 342 | 343 | user.friends.push(newRes?.requestFrom); 344 | 345 | await user.save(); 346 | 347 | const friend = await Users.findById(newRes?.requestFrom); 348 | 349 | friend.friends.push(newRes?.requestTo); 350 | 351 | await friend.save(); 352 | } 353 | 354 | res.status(201).json({ 355 | success: true, 356 | message: "Friend Request " + status, 357 | }); 358 | } catch (error) { 359 | console.log(error); 360 | res.status(500).json({ 361 | message: "auth error", 362 | success: false, 363 | error: error.message, 364 | }); 365 | } 366 | }; 367 | 368 | export const profileViews = async (req, res, next) => { 369 | try { 370 | const { userId } = req.body.user; 371 | const { id } = req.body; 372 | 373 | const user = await Users.findById(id); 374 | 375 | user.views.push(userId); 376 | 377 | await user.save(); 378 | 379 | res.status(201).json({ 380 | success: true, 381 | message: "Successfully", 382 | }); 383 | } catch (error) { 384 | console.log(error); 385 | res.status(500).json({ 386 | message: "auth error", 387 | success: false, 388 | error: error.message, 389 | }); 390 | } 391 | }; 392 | 393 | export const suggestedFriends = async (req, res) => { 394 | try { 395 | const { userId } = req.body.user; 396 | 397 | let queryObject = {}; 398 | 399 | queryObject._id = { $ne: userId }; 400 | 401 | queryObject.friends = { $nin: userId }; 402 | 403 | let queryResult = Users.find(queryObject) 404 | .limit(15) 405 | .select("firstName lastName profileUrl profession -password"); 406 | 407 | const suggestedFriends = await queryResult; 408 | 409 | res.status(200).json({ 410 | success: true, 411 | data: suggestedFriends, 412 | }); 413 | } catch (error) { 414 | console.log(error); 415 | res.status(404).json({ message: error.message }); 416 | } 417 | }; 418 | -------------------------------------------------------------------------------- /server/dbConfig/index.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const dbConnection = async () => { 4 | try { 5 | const connection = await mongoose.connect(process.env.MONGODB_URL, { 6 | useNewUrlParser: true, 7 | useUnifiedTopology: true, 8 | }); 9 | 10 | console.log("DB Connected Successfully"); 11 | } catch (error) { 12 | console.log("DB Error: " + error); 13 | } 14 | }; 15 | 16 | export default dbConnection; 17 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import dotenv from "dotenv"; 3 | import cors from "cors"; 4 | import morgan from "morgan"; 5 | import bodyParser from "body-parser"; 6 | import path from "path"; 7 | //securty packges 8 | import helmet from "helmet"; 9 | import dbConnection from "./dbConfig/index.js"; 10 | import errorMiddleware from "./middleware/errorMiddleware.js"; 11 | import router from "./routes/index.js"; 12 | 13 | const __dirname = path.resolve(path.dirname("")); 14 | 15 | dotenv.config(); 16 | 17 | const app = express(); 18 | 19 | app.use(express.static(path.join(__dirname, "views/build"))); 20 | 21 | const PORT = process.env.PORT || 8800; 22 | 23 | dbConnection(); 24 | 25 | app.use(helmet()); 26 | app.use(cors()); 27 | app.use(bodyParser.json()); 28 | app.use(bodyParser.urlencoded({ extended: true })); 29 | app.use(express.json({ limit: "10mb" })); 30 | app.use(express.urlencoded({ extended: true })); 31 | 32 | app.use(morgan("dev")); 33 | app.use(router); 34 | 35 | //error middleware 36 | app.use(errorMiddleware); 37 | 38 | app.listen(PORT, () => { 39 | console.log(`Server running on port: ${PORT}`); 40 | }); 41 | -------------------------------------------------------------------------------- /server/middleware/authMiddleware.js: -------------------------------------------------------------------------------- 1 | import JWT from "jsonwebtoken"; 2 | 3 | const userAuth = async (req, res, next) => { 4 | const authHeader = req?.headers?.authorization; 5 | 6 | if (!authHeader || !authHeader?.startsWith("Bearer")) { 7 | next("Authentication== failed"); 8 | } 9 | 10 | const token = authHeader?.split(" ")[1]; 11 | 12 | try { 13 | const userToken = JWT.verify(token, process.env.JWT_SECRET_KEY); 14 | 15 | req.body.user = { 16 | userId: userToken.userId, 17 | }; 18 | 19 | next(); 20 | } catch (error) { 21 | console.log(error); 22 | next("Authentication failed"); 23 | } 24 | }; 25 | 26 | export default userAuth; 27 | -------------------------------------------------------------------------------- /server/middleware/errorMiddleware.js: -------------------------------------------------------------------------------- 1 | // ERROR MIDDLEWARE | NEXT FUNCTION 2 | 3 | const errorMiddleware = (err, req, res, next) => { 4 | const defaultError = { 5 | statusCode: 404, 6 | success: "failed", 7 | message: err, 8 | }; 9 | 10 | if (err?.name === "ValidationError") { 11 | defaultError.statusCode = 404; 12 | 13 | defaultError.message = Object.values(err, errors) 14 | .map((el) => el.message) 15 | .join(","); 16 | } 17 | 18 | //duplicate error 19 | 20 | if (err.code && err.code === 11000) { 21 | defaultError.statusCode = 404; 22 | defaultError.message = `${Object.values( 23 | err.keyValue 24 | )} field has to be unique!`; 25 | } 26 | 27 | res.status(defaultError.statusCode).json({ 28 | success: defaultError.success, 29 | message: defaultError.message, 30 | }); 31 | }; 32 | 33 | export default errorMiddleware; 34 | -------------------------------------------------------------------------------- /server/models/PasswordReset.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | const passwordResetSchema = Schema({ 4 | userId: { type: String, unique: true }, 5 | email: { type: String, unique: true }, 6 | token: String, 7 | createdAt: Date, 8 | expiresAt: Date, 9 | }); 10 | 11 | const PasswordReset = mongoose.model("PasswordReset", passwordResetSchema); 12 | 13 | export default PasswordReset; 14 | -------------------------------------------------------------------------------- /server/models/commentModel.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | const commentSchema = new mongoose.Schema( 4 | { 5 | userId: { type: Schema.Types.ObjectId, ref: "Users" }, 6 | postId: { type: Schema.Types.ObjectId, ref: "Posts" }, 7 | comment: { type: String, required: true }, 8 | from: { type: String, required: true }, 9 | replies: [ 10 | { 11 | rid: { type: mongoose.Schema.Types.ObjectId }, 12 | userId: { type: Schema.Types.ObjectId, ref: "Users" }, 13 | from: { type: String }, 14 | replyAt: { type: String }, 15 | comment: { type: String }, 16 | created_At: { type: Date, default: Date.now() }, 17 | updated_At: { type: Date, default: Date.now() }, 18 | likes: [{ type: String }], 19 | }, 20 | ], 21 | likes: [{ type: String }], 22 | }, 23 | { timestamps: true } 24 | ); 25 | 26 | const Comments = mongoose.model("Comments", commentSchema); 27 | 28 | export default Comments; 29 | -------------------------------------------------------------------------------- /server/models/emailVerification.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | const emailVerificationSchema = Schema({ 4 | userId: String, 5 | token: String, 6 | createdAt: Date, 7 | expiresAt: Date, 8 | }); 9 | 10 | const Verification = mongoose.model("Verification", emailVerificationSchema); 11 | 12 | export default Verification; 13 | -------------------------------------------------------------------------------- /server/models/friendRequest.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | const requestSchema = Schema( 4 | { 5 | requestTo: { type: Schema.Types.ObjectId, ref: "Users" }, 6 | requestFrom: { type: Schema.Types.ObjectId, ref: "Users" }, 7 | requestStatus: { type: String, default: "Pending" }, 8 | }, 9 | { timestamps: true } 10 | ); 11 | 12 | const FriendRequest = mongoose.model("FriendRequest", requestSchema); 13 | 14 | export default FriendRequest; 15 | -------------------------------------------------------------------------------- /server/models/postModel.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | //schema 4 | const postSchema = new mongoose.Schema( 5 | { 6 | userId: { type: Schema.Types.ObjectId, ref: "Users" }, 7 | description: { type: String, required: true }, 8 | image: { type: String }, 9 | likes: [{ type: String }], 10 | comments: [{ type: Schema.Types.ObjectId, ref: "Comments" }], 11 | }, 12 | { timestamps: true } 13 | ); 14 | 15 | const Posts = mongoose.model("Posts", postSchema); 16 | 17 | export default Posts; 18 | -------------------------------------------------------------------------------- /server/models/userModel.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | //schema 4 | const userSchema = new mongoose.Schema( 5 | { 6 | firstName: { 7 | type: String, 8 | required: [true, "First Name is Required!"], 9 | }, 10 | lastName: { 11 | type: String, 12 | required: [true, "Last Name is Required!"], 13 | }, 14 | email: { 15 | type: String, 16 | required: [true, " Email is Required!"], 17 | unique: true, 18 | }, 19 | password: { 20 | type: String, 21 | required: [true, "Password is Required!"], 22 | minlength: [6, "Password length should be greater than 6 character"], 23 | select: true, 24 | }, 25 | location: { type: String }, 26 | profileUrl: { type: String }, 27 | profession: { type: String }, 28 | friends: [{ type: Schema.Types.ObjectId, ref: "Users" }], 29 | views: [{ type: String }], 30 | verified: { type: Boolean, default: false }, 31 | }, 32 | { timestamps: true } 33 | ); 34 | 35 | const Users = mongoose.model("Users", userSchema); 36 | 37 | export default Users; 38 | -------------------------------------------------------------------------------- /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": "nodemon index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "bcryptjs": "^2.4.3", 16 | "body-parser": "^1.20.2", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.3.1", 19 | "express": "^4.18.2", 20 | "helmet": "^7.0.0", 21 | "jsonwebtoken": "^9.0.2", 22 | "mongoose": "^7.5.0", 23 | "morgan": "^1.10.0", 24 | "nodemailer": "^6.9.4", 25 | "nodemon": "^3.0.1", 26 | "uuid": "^9.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/routes/authRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { login, register } from "../controllers/authController.js"; 3 | 4 | const router = express.Router(); 5 | 6 | router.post("/register", register); 7 | router.post("/login", login); 8 | 9 | export default router; 10 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import authRoute from "./authRoutes.js"; 3 | import userRoute from "./userRoutes.js"; 4 | import postRoute from "./postRoutes.js"; 5 | 6 | const router = express.Router(); 7 | 8 | router.use(`/auth`, authRoute); //auth/register 9 | router.use(`/users`, userRoute); 10 | router.use(`/posts`, postRoute); 11 | 12 | export default router; 13 | -------------------------------------------------------------------------------- /server/routes/postRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import userAuth from "../middleware/authMiddleware.js"; 3 | import { 4 | commentPost, 5 | createPost, 6 | deletePost, 7 | getComments, 8 | getPost, 9 | getPosts, 10 | getUserPost, 11 | likePost, 12 | likePostComment, 13 | replyPostComment, 14 | } from "../controllers/postController.js"; 15 | 16 | const router = express.Router(); 17 | 18 | // crete post 19 | router.post("/create-post", userAuth, createPost); 20 | // get posts 21 | router.post("/", userAuth, getPosts); 22 | router.post("/:id", userAuth, getPost); 23 | 24 | router.post("/get-user-post/:id", userAuth, getUserPost); 25 | 26 | // get comments 27 | router.get("/comments/:postId", getComments); 28 | 29 | //like and comment on posts 30 | router.post("/like/:id", userAuth, likePost); 31 | router.post("/like-comment/:id/:rid?", userAuth, likePostComment); 32 | router.post("/comment/:id", userAuth, commentPost); 33 | router.post("/reply-comment/:id", userAuth, replyPostComment); 34 | 35 | //delete post 36 | router.delete("/:id", userAuth, deletePost); 37 | 38 | export default router; 39 | -------------------------------------------------------------------------------- /server/routes/userRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import path from "path"; 3 | import { 4 | acceptRequest, 5 | changePassword, 6 | friendRequest, 7 | getFriendRequest, 8 | getUser, 9 | profileViews, 10 | requestPasswordReset, 11 | resetPassword, 12 | suggestedFriends, 13 | updateUser, 14 | verifyEmail, 15 | } from "../controllers/userController.js"; 16 | import userAuth from "../middleware/authMiddleware.js"; 17 | 18 | const router = express.Router(); 19 | const __dirname = path.resolve(path.dirname("")); 20 | 21 | router.get("/verify/:userId/:token", verifyEmail); 22 | // PASSWORD RESET 23 | router.post("/request-passwordreset", requestPasswordReset); 24 | router.get("/reset-password/:userId/:token", resetPassword); 25 | router.post("/reset-password", changePassword); 26 | 27 | // user routes 28 | router.post("/get-user/:id?", userAuth, getUser); 29 | router.put("/update-user", userAuth, updateUser); 30 | 31 | // friend request 32 | router.post("/friend-request", userAuth, friendRequest); 33 | router.post("/get-friend-request", userAuth, getFriendRequest); 34 | 35 | // accept / deny friend request 36 | router.post("/accept-request", userAuth, acceptRequest); 37 | 38 | // view profile 39 | router.post("/profile-view", userAuth, profileViews); 40 | 41 | //suggested friends 42 | router.post("/suggested-friends", userAuth, suggestedFriends); 43 | 44 | router.get("/verified", (req, res) => { 45 | res.sendFile(path.join(__dirname, "./views/build", "index.html")); 46 | }); 47 | 48 | router.get("/resetpassword", (req, res) => { 49 | res.sendFile(path.join(__dirname, "./views/build", "index.html")); 50 | }); 51 | 52 | export default router; 53 | -------------------------------------------------------------------------------- /server/utils/index.js: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | import JWT from "jsonwebtoken"; 3 | 4 | export const hashString = async (useValue) => { 5 | const salt = await bcrypt.genSalt(10); 6 | 7 | const hashedpassword = await bcrypt.hash(useValue, salt); 8 | return hashedpassword; 9 | }; 10 | 11 | export const compareString = async (userPassword, password) => { 12 | const isMatch = await bcrypt.compare(userPassword, password); 13 | return isMatch; 14 | }; 15 | 16 | //JSON WEBTOKEN 17 | export function createJWT(id) { 18 | return JWT.sign({ userId: id }, process.env.JWT_SECRET_KEY, { 19 | expiresIn: "1d", 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /server/utils/sendEmail.js: -------------------------------------------------------------------------------- 1 | import nodemailer from "nodemailer"; 2 | import dotenv from "dotenv"; 3 | import { v4 as uuidv4 } from "uuid"; 4 | import { hashString } from "./index.js"; 5 | import Verification from "../models/emailVerification.js"; 6 | import PasswordReset from "../models/PasswordReset.js"; 7 | 8 | dotenv.config(); 9 | 10 | const { AUTH_EMAIL, AUTH_PASSWORD, APP_URL } = process.env; 11 | 12 | let transporter = nodemailer.createTransport({ 13 | host: "smtp-mail.outlook.com", 14 | auth: { 15 | user: AUTH_EMAIL, 16 | pass: AUTH_PASSWORD, 17 | }, 18 | }); 19 | 20 | export const sendVerificationEmail = async (user, res) => { 21 | const { _id, email, lastName } = user; 22 | 23 | const token = _id + uuidv4(); 24 | 25 | const link = APP_URL + "users/verify/" + _id + "/" + token; 26 | 27 | // mail options 28 | const mailOptions = { 29 | from: AUTH_EMAIL, 30 | to: email, 31 | subject: "Email Verification", 32 | html: `
34 |

Please verify your email address

35 |
36 |

Hi ${lastName},

37 |

38 | Please verify your email address so we can know that it's really you. 39 |
40 |

This link expires in 1 hour

41 |
42 | Verify 44 | Email Address 45 |

46 |
47 |
Best Regards
48 |
ShareFun Team
49 |
50 |
`, 51 | }; 52 | 53 | try { 54 | const hashedToken = await hashString(token); 55 | 56 | const newVerifiedEmail = await Verification.create({ 57 | userId: _id, 58 | token: hashedToken, 59 | createdAt: Date.now(), 60 | expiresAt: Date.now() + 3600000, 61 | }); 62 | 63 | if (newVerifiedEmail) { 64 | transporter 65 | .sendMail(mailOptions) 66 | .then(() => { 67 | res.status(201).send({ 68 | success: "PENDING", 69 | message: 70 | "Verification email has been sent to your account. Check your email for further instructions.", 71 | }); 72 | }) 73 | .catch((err) => { 74 | console.log(err); 75 | res.status(404).json({ message: "Something went wrong" }); 76 | }); 77 | } 78 | } catch (error) { 79 | console.log(error); 80 | res.status(404).json({ message: "Something went wrong" }); 81 | } 82 | }; 83 | 84 | export const resetPasswordLink = async (user, res) => { 85 | const { _id, email } = user; 86 | 87 | const token = _id + uuidv4(); 88 | const link = APP_URL + "users/reset-password/" + _id + "/" + token; 89 | 90 | // mail options 91 | const mailOptions = { 92 | from: AUTH_EMAIL, 93 | to: email, 94 | subject: "Password Reset", 95 | html: `

96 | Password reset link. Please click the link below to reset password. 97 |
98 |

This link expires in 10 minutes

99 |
100 | Reset Password. 101 |

`, 102 | }; 103 | 104 | try { 105 | const hashedToken = await hashString(token); 106 | 107 | const resetEmail = await PasswordReset.create({ 108 | userId: _id, 109 | email: email, 110 | token: hashedToken, 111 | createdAt: Date.now(), 112 | expiresAt: Date.now() + 600000, 113 | }); 114 | 115 | if (resetEmail) { 116 | transporter 117 | .sendMail(mailOptions) 118 | .then(() => { 119 | res.status(201).send({ 120 | success: "PENDING", 121 | message: "Reset Password Link has been sent to your account.", 122 | }); 123 | }) 124 | .catch((err) => { 125 | console.log(err); 126 | res.status(404).json({ message: "Something went wrong" }); 127 | }); 128 | } 129 | } catch (error) { 130 | console.log(error); 131 | res.status(404).json({ message: "Something went wrong" }); 132 | } 133 | }; 134 | -------------------------------------------------------------------------------- /server/views/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.2609a62c.css", 4 | "main.js": "/static/js/main.4e9a0992.js", 5 | "index.html": "/index.html", 6 | "main.2609a62c.css.map": "/static/css/main.2609a62c.css.map", 7 | "main.4e9a0992.js.map": "/static/js/main.4e9a0992.js.map" 8 | }, 9 | "entrypoints": [ 10 | "static/css/main.2609a62c.css", 11 | "static/js/main.4e9a0992.js" 12 | ] 13 | } -------------------------------------------------------------------------------- /server/views/build/index.html: -------------------------------------------------------------------------------- 1 | ShareFun
-------------------------------------------------------------------------------- /server/views/build/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 | -------------------------------------------------------------------------------- /server/views/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/views/build/static/css/main.2609a62c.css: -------------------------------------------------------------------------------- 1 | body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;align-items:center;background-color:rgba(0,0,0,.2);display:flex;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;height:100vh;justify-content:center;margin:0;text-align:center}.card{background-color:#fff;border-radius:10px;box-shadow:0 4px 8px rgba(0,0,0,.2);padding:20px}#resetForm{display:flex;flex-direction:column}#statusMessage{font-size:18px!important;font-weight:500;margin-top:15px}.icon{font-size:64px;margin-bottom:20px}.showBtn{align-items:center;background-color:rgba(1,116,210,.971);border-radius:5px;color:#fff;display:flex;justify-content:center;margin-top:10px;padding:10px;text-align:center;text-decoration:none}.title{font-size:24px;margin-bottom:20px}.input-field{border:1px solid #ccc;border-radius:5px;font-size:16px;margin-bottom:20px;padding:14px;width:90%}.message{font-size:14px;margin-top:10px}.success{color:green}.error{color:red}.submit-button{background-color:#007bff;border:none;border-radius:5px;color:#fff;cursor:pointer;font-size:16px;padding:15px 20px;transition:background-color .3s ease}.submit-button:hover{background-color:#0056b3} 2 | /*# sourceMappingURL=main.2609a62c.css.map*/ -------------------------------------------------------------------------------- /server/views/build/static/css/main.2609a62c.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"static/css/main.2609a62c.css","mappings":"AAAA,KAKE,kCAAmC,CACnC,iCAAkC,CAKxB,kBAAmB,CAGnB,+BAAoC,CALpC,YAAa,CAPvB,mIAEY,CAQF,YAAa,CAFb,sBAAuB,CAGvB,QAAS,CALT,iBAOZ,CAEI,MAIQ,qBAAuB,CAFvB,kBAAmB,CAGnB,mCAA0C,CAF1C,YAGJ,CACR,WACA,YAAa,CACb,qBACA,CACA,eACE,wBAA0B,CAC1B,eAAgB,CAChB,eACF,CACS,MACG,cAAe,CACf,kBACJ,CAYA,SAGI,kBAAmB,CAInB,qCAA0C,CAF1C,iBAAkB,CAGlB,UAAY,CAPZ,YAAa,CAGb,sBAAuB,CAKvB,eAAgB,CAHhB,YAAa,CAJb,iBAAkB,CAQjB,oBACL,CACP,OACW,cAAe,CACf,kBACJ,CAEA,aAII,qBAAsB,CACtB,iBAAkB,CAClB,cAAe,CAHf,kBAAmB,CADnB,YAAa,CADb,SAOJ,CAEA,SACI,cAAe,CACf,eACJ,CAEA,SACI,WACJ,CAEA,OACI,SACJ,CAEA,eAEI,wBAAyB,CAEzB,WAAY,CACZ,iBAAkB,CAFlB,UAAY,CAIZ,cAAe,CANjB,cAAe,CAKb,iBAAkB,CAElB,oCACJ,CAEA,qBACI,wBACJ","sources":["index.css"],"sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n\n text-align: center;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n background-color: rgba(0, 0, 0, 0.2);\n}\n\n .card {\n \n border-radius: 10px;\n padding: 20px;\n background-color: white;\n box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);\n }\n#resetForm{\ndisplay: flex;\nflex-direction: column;\n}\n#statusMessage{\n font-size: 18px !important;\n font-weight: 500;\n margin-top: 15px;\n}\n .icon {\n font-size: 64px;\n margin-bottom: 20px;\n }\n\n .success {\n color: green;\n \n }\n\n .error {\n color: red;\n \n }\n\n .showBtn {\n display: flex;\n text-align: center;\n align-items: center;\n justify-content: center;\n border-radius: 5px;\n padding: 10px;\n background-color: rgba(1, 116, 210, 0.971);\n color: white;\n margin-top: 10px;\n text-decoration: none;\n }\n .title {\n font-size: 24px;\n margin-bottom: 20px;\n }\n\n .input-field {\n width: 90%;\n padding: 14px;\n margin-bottom: 20px;\n border: 1px solid #ccc;\n border-radius: 5px;\n font-size: 16px;\n \n }\n\n .message {\n font-size: 14px;\n margin-top: 10px;\n }\n\n .success {\n color: green;\n }\n\n .error {\n color: red;\n }\n\n .submit-button {\n font-size: 16px;;\n background-color: #007bff;\n color: white;\n border: none;\n border-radius: 5px;\n padding: 15px 20px;\n cursor: pointer;\n transition: background-color 0.3s ease;\n }\n\n .submit-button:hover {\n background-color: #0056b3;\n }\n\n "],"names":[],"sourceRoot":""} -------------------------------------------------------------------------------- /server/views/build/static/js/main.4e9a0992.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 2 | 3 | /** 4 | * @license React 5 | * react-dom.production.min.js 6 | * 7 | * Copyright (c) Facebook, Inc. and its affiliates. 8 | * 9 | * This source code is licensed under the MIT license found in the 10 | * LICENSE file in the root directory of this source tree. 11 | */ 12 | 13 | /** 14 | * @license React 15 | * react-jsx-runtime.production.min.js 16 | * 17 | * Copyright (c) Facebook, Inc. and its affiliates. 18 | * 19 | * This source code is licensed under the MIT license found in the 20 | * LICENSE file in the root directory of this source tree. 21 | */ 22 | 23 | /** 24 | * @license React 25 | * react.production.min.js 26 | * 27 | * Copyright (c) Facebook, Inc. and its affiliates. 28 | * 29 | * This source code is licensed under the MIT license found in the 30 | * LICENSE file in the root directory of this source tree. 31 | */ 32 | 33 | /** 34 | * @license React 35 | * scheduler.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | -------------------------------------------------------------------------------- /server/views/build/static/js/main.5b2b5904.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 2 | 3 | /** 4 | * @license React 5 | * react-dom.production.min.js 6 | * 7 | * Copyright (c) Facebook, Inc. and its affiliates. 8 | * 9 | * This source code is licensed under the MIT license found in the 10 | * LICENSE file in the root directory of this source tree. 11 | */ 12 | 13 | /** 14 | * @license React 15 | * react-jsx-runtime.production.min.js 16 | * 17 | * Copyright (c) Facebook, Inc. and its affiliates. 18 | * 19 | * This source code is licensed under the MIT license found in the 20 | * LICENSE file in the root directory of this source tree. 21 | */ 22 | 23 | /** 24 | * @license React 25 | * react.production.min.js 26 | * 27 | * Copyright (c) Facebook, Inc. and its affiliates. 28 | * 29 | * This source code is licensed under the MIT license found in the 30 | * LICENSE file in the root directory of this source tree. 31 | */ 32 | 33 | /** 34 | * @license React 35 | * scheduler.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { Outlet, Navigate, Route, Routes, useLocation } from "react-router-dom"; 2 | import { useSelector } from "react-redux"; 3 | import { Home, Login, Profile, Register, ResetPassword } from "./pages"; 4 | 5 | function Layout() { 6 | const { user } = useSelector((state) => state.user); 7 | const location = useLocation(); 8 | 9 | return user?.token ? ( 10 | 11 | ) : ( 12 | 13 | ); 14 | } 15 | 16 | function App() { 17 | const { theme } = useSelector((state) => state.theme); 18 | 19 | return ( 20 |
21 | 22 | }> 23 | } /> 24 | } /> 25 | 26 | 27 | } /> 28 | } /> 29 | } /> 30 | 31 |
32 | ); 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /src/assets/data.js: -------------------------------------------------------------------------------- 1 | export const user = { 2 | _id: "64df3c064180b81adfe41d4b", 3 | firstName: "Code", 4 | lastName: "Wave", 5 | email: "codewavewithasante@gmail.com", 6 | friends: [ 7 | { 8 | _id: "64df3aec4180b81adfe41d32", 9 | firstName: "John", 10 | lastName: "Bruce", 11 | email: "john@gmail.com", 12 | friends: ["64df3c064180b81adfe41d4b", "64df39704180b81adfe41d0b"], 13 | views: [], 14 | verified: true, 15 | createdAt: "2023-08-18T09:33:32.519Z", 16 | updatedAt: "2023-08-18T09:49:19.475Z", 17 | __v: 2, 18 | profileUrl: 19 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874470/cld-sample.jpg", 20 | }, 21 | { 22 | _id: "64df39704180b81adfe41d0b", 23 | firstName: "James", 24 | lastName: "Jackson", 25 | email: "james@gmail.com", 26 | friends: ["64df3c064180b81adfe41d4b", "64df3aec4180b81adfe41d32"], 27 | views: [ 28 | "64df39704180b81adfe41d0b", 29 | "64df39704180b81adfe41d0b", 30 | "64df39704180b81adfe41d0b", 31 | "64df39704180b81adfe41d0b", 32 | "64df39704180b81adfe41d0b", 33 | "64df39704180b81adfe41d0b", 34 | ], 35 | verified: true, 36 | createdAt: "2023-08-18T09:27:12.064Z", 37 | updatedAt: "2023-08-21T06:46:26.798Z", 38 | __v: 8, 39 | location: "Mumbai, India", 40 | profession: "Full-Stack Developer", 41 | }, 42 | { 43 | _id: "64df424b4a4c0d47b5369f65", 44 | firstName: "User", 45 | lastName: "One", 46 | email: "user!@gmail.com", 47 | friends: ["64df3c064180b81adfe41d4b"], 48 | views: [], 49 | verified: true, 50 | createdAt: "2023-08-18T10:04:59.677Z", 51 | updatedAt: "2023-08-18T10:09:20.006Z", 52 | __v: 1, 53 | }, 54 | ], 55 | views: [ 56 | "64df39704180b81adfe41d0b", 57 | "64df39704180b81adfe41d0b", 58 | "64df39704180b81adfe41d0b", 59 | "64df39704180b81adfe41d0b", 60 | "64df39704180b81adfe41d0b", 61 | "64df39704180b81adfe41d0b", 62 | "64df39704180b81adfe41d0b", 63 | "64df39704180b81adfe41d0b", 64 | "64df39704180b81adfe41d0b", 65 | "64df39704180b81adfe41d0b", 66 | "64df39704180b81adfe41d0b", 67 | "64df39704180b81adfe41d0b", 68 | "64df39704180b81adfe41d0b", 69 | "64df39704180b81adfe41d0b", 70 | "64df39704180b81adfe41d0b", 71 | "64df39704180b81adfe41d0b", 72 | "64df39704180b81adfe41d0b", 73 | "64df39704180b81adfe41d0b", 74 | "64df39704180b81adfe41d0b", 75 | "64df39704180b81adfe41d0b", 76 | "64df39704180b81adfe41d0b", 77 | "64df39704180b81adfe41d0b", 78 | "64df39704180b81adfe41d0b", 79 | "64df39704180b81adfe41d0b", 80 | "64df39704180b81adfe41d0b", 81 | "64df39704180b81adfe41d0b", 82 | "64df39704180b81adfe41d0b", 83 | "64df39704180b81adfe41d0b", 84 | ], 85 | verified: true, 86 | createdAt: "2023-08-18T09:38:14.179Z", 87 | updatedAt: "2023-08-21T06:46:18.258Z", 88 | profileUrl: 89 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874454/samples/people/boy-snow-hoodie.jpg", 90 | token: "hZWFmZmU3NmMiLCJpYXQiOjE2OTIwMzY5", 91 | }; 92 | 93 | export const friends = [ 94 | { 95 | _id: "64df3aec4180b81adfe41d32", 96 | firstName: "John", 97 | lastName: "Bruce", 98 | email: "john@gmail.com", 99 | profileUrl: 100 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874470/cld-sample.jpg", 101 | }, 102 | { 103 | _id: "64df39704180b81adfe41d0b", 104 | firstName: "James", 105 | lastName: "Jackson", 106 | email: "james@gmail.com", 107 | location: "Mumbai, India", 108 | profession: "Full-Stack Developer", 109 | }, 110 | { 111 | _id: "64df424b4a4c0d47b5369f65", 112 | firstName: "User", 113 | lastName: "One", 114 | email: "user!@gmail.com", 115 | }, 116 | ]; 117 | 118 | export const requests = [ 119 | { 120 | _id: "64df3aec4180b81adfe41d32", 121 | requestFrom: friends[0], 122 | }, 123 | { 124 | _id: "64df39704180b81adfe41d0b", 125 | requestFrom: friends[1], 126 | }, 127 | { 128 | _id: "64df424b4a4c0d47b5369f65", 129 | requestFrom: friends[2], 130 | }, 131 | ]; 132 | 133 | export const suggest = [ 134 | { 135 | _id: "64df3aec4180b81adfe41d32", 136 | ...friends[0], 137 | }, 138 | { 139 | _id: "64df39704180b81adfe41d0b", 140 | ...friends[1], 141 | }, 142 | { 143 | _id: "64df424b4a4c0d47b5369f65", 144 | ...friends[2], 145 | }, 146 | ]; 147 | export const posts = [ 148 | { 149 | _id: "64e2fe620d7868ecff1a6a86", 150 | userId: { 151 | _id: "64df39704180b81adfe41d0b", 152 | firstName: "Chris", 153 | lastName: "Omar", 154 | profileUrl: 155 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874454/samples/people/boy-snow-hoodie.jpg", 156 | location: "New York, USA", 157 | }, 158 | description: "Hello everyone, this is a new video. check it out. thank you", 159 | image: 160 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1692597858/SOCIALMEDIA/hdahstpztt1fvobc13st.png", 161 | likes: ["64df3c064180b81adfe41d4b"], 162 | comments: [], 163 | createdAt: "2023-08-21T06:04:18.297Z", 164 | updatedAt: "2023-08-21T06:04:18.297Z", 165 | __v: 0, 166 | }, 167 | { 168 | _id: "64e1cdd64baffca670364c8c", 169 | userId: { 170 | _id: "64df39704180b81adfe41d0b", 171 | firstName: "Love", 172 | lastName: "Banks", 173 | profileUrl: 174 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874454/samples/people/boy-snow-hoodie.jpg", 175 | location: "Mumbai, India", 176 | }, 177 | description: 178 | "What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, ", 179 | likes: ["64df39704180b81adfe41d0b"], 180 | comments: [], 181 | createdAt: "2023-08-20T08:24:54.330Z", 182 | updatedAt: "2023-08-21T03:23:24.809Z", 183 | __v: 0, 184 | }, 185 | { 186 | _id: "64df437e4a4c0d47b536a002", 187 | userId: { 188 | _id: "64df424b4a4c0d47b5369f65", 189 | firstName: "User", 190 | lastName: "One", 191 | }, 192 | description: 193 | "What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, ", 194 | likes: ["64df424b4a4c0d47b5369f65"], 195 | comments: ["64e2d1c977e497bd3a0bf67b"], 196 | createdAt: "2023-08-18T10:10:06.969Z", 197 | updatedAt: "2023-08-21T02:54:01.806Z", 198 | __v: 0, 199 | image: 200 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874458/samples/imagecon-group.jpg", 201 | }, 202 | { 203 | _id: "64df43714a4c0d47b5369fef", 204 | userId: { 205 | _id: "64df3c064180b81adfe41d4b", 206 | firstName: "Code", 207 | lastName: "Wave", 208 | profileUrl: 209 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874454/samples/people/boy-snow-hoodie.jpg", 210 | }, 211 | description: 212 | "What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, ", 213 | likes: ["64df39704180b81adfe41d0b"], 214 | comments: [], 215 | createdAt: "2023-08-18T10:09:53.009Z", 216 | updatedAt: "2023-08-21T03:35:18.541Z", 217 | __v: 0, 218 | }, 219 | { 220 | _id: "64df42dc4a4c0d47b5369f8a", 221 | userId: { 222 | _id: "64df424b4a4c0d47b5369f65", 223 | firstName: "User", 224 | lastName: "One", 225 | }, 226 | description: 227 | " What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text,", 228 | likes: ["64df424b4a4c0d47b5369f65"], 229 | comments: [], 230 | createdAt: "2023-08-18T10:07:24.023Z", 231 | updatedAt: "2023-08-18T10:11:00.348Z", 232 | __v: 0, 233 | image: 234 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874457/samples/ecommerce/leather-bag-gray.jpg", 235 | }, 236 | { 237 | _id: "64df42b04a4c0d47b5369f77", 238 | userId: { 239 | _id: "64df424b4a4c0d47b5369f65", 240 | firstName: "User", 241 | lastName: "One", 242 | }, 243 | description: 244 | "What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, ", 245 | likes: [], 246 | comments: [], 247 | createdAt: "2023-08-18T10:06:40.339Z", 248 | updatedAt: "2023-08-18T10:06:40.339Z", 249 | __v: 0, 250 | }, 251 | { 252 | _id: "64df41114a4c0d47b5369f02", 253 | userId: { 254 | _id: "64df3aec4180b81adfe41d32", 255 | firstName: "Jomes", 256 | lastName: "Gardener", 257 | profileUrl: 258 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874470/cld-sample.jpg", 259 | }, 260 | description: 261 | " What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).", 262 | likes: ["64df39704180b81adfe41d0b"], 263 | comments: [], 264 | createdAt: "2023-08-18T09:59:45.876Z", 265 | updatedAt: "2023-08-18T10:01:35.333Z", 266 | __v: 0, 267 | }, 268 | { 269 | _id: "64df3ef86c2bd953c7b43193", 270 | userId: { 271 | _id: "64df3c064180b81adfe41d4b", 272 | firstName: "Code", 273 | lastName: "Wave", 274 | profileUrl: 275 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874454/samples/people/boy-snow-hoodie.jpg", 276 | }, 277 | description: 278 | "What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, ", 279 | likes: [ 280 | "64df3aec4180b81adfe41d32", 281 | "64df424b4a4c0d47b5369f65", 282 | "64df39704180b81adfe41d0b", 283 | ], 284 | comments: [ 285 | "64df41304a4c0d47b5369f0d", 286 | "64df41b14a4c0d47b5369f4d", 287 | "64df43e04a4c0d47b536a02a", 288 | ], 289 | createdAt: "2023-08-18T09:50:48.398Z", 290 | updatedAt: "2023-08-21T03:36:36.745Z", 291 | __v: 0, 292 | image: 293 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874455/samples/animals/three-dogs.jpg", 294 | }, 295 | { 296 | _id: "64df3ed06c2bd953c7b43172", 297 | userId: { 298 | _id: "64df39704180b81adfe41d0b", 299 | firstName: "John ", 300 | lastName: "Smith", 301 | profileUrl: 302 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874453/samples/bike.jpg", 303 | location: "Mumbai, India", 304 | }, 305 | description: 306 | " What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. Why do we use it? It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).", 307 | likes: ["64df39704180b81adfe41d0b"], 308 | comments: ["64e2dc8577e497bd3a0bf843"], 309 | createdAt: "2023-08-18T09:50:08.431Z", 310 | updatedAt: "2023-08-21T03:44:36.962Z", 311 | __v: 0, 312 | image: 313 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874453/samples/bike.jpg", 314 | }, 315 | ]; 316 | 317 | export const postComments = [ 318 | { 319 | _id: "64df43e04a4c0d47b536a02a", 320 | userId: { 321 | _id: "64df424b4a4c0d47b5369f65", 322 | firstName: "User", 323 | lastName: "One", 324 | }, 325 | postId: "64df3ef86c2bd953c7b43193", 326 | comment: "hahahah", 327 | from: "User One", 328 | likes: ["64df39704180b81adfe41d0b"], 329 | replies: [], 330 | createdAt: "2023-08-18T10:11:44.091Z", 331 | updatedAt: "2023-08-21T03:37:03.927Z", 332 | __v: 0, 333 | }, 334 | { 335 | _id: "64df41b14a4c0d47b5369f4d", 336 | userId: { 337 | _id: "64df39704180b81adfe41d0b", 338 | firstName: "MTech", 339 | lastName: "Solutions", 340 | profileUrl: 341 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1692299991/SOCIALMEDIA/fvws1unsqytcqketv78w.png", 342 | location: "Mumbai, India", 343 | }, 344 | postId: "64df3ef86c2bd953c7b43193", 345 | comment: "i would like to have them in my house", 346 | from: "MTech Solutions", 347 | likes: ["64df39704180b81adfe41d0b"], 348 | replies: [], 349 | createdAt: "2023-08-18T10:02:25.492Z", 350 | updatedAt: "2023-08-21T03:27:57.602Z", 351 | __v: 0, 352 | }, 353 | { 354 | _id: "64df41304a4c0d47b5369f0d", 355 | userId: { 356 | _id: "64df3aec4180b81adfe41d32", 357 | firstName: "Akwasi", 358 | lastName: "Asante", 359 | profileUrl: 360 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1683874470/cld-sample.jpg", 361 | }, 362 | postId: "64df3ef86c2bd953c7b43193", 363 | comment: "This dogs are too serious!", 364 | from: "Akwasi Asante", 365 | likes: ["64df39704180b81adfe41d0b"], 366 | replies: [ 367 | { 368 | userId: { 369 | _id: "64df39704180b81adfe41d0b", 370 | firstName: "MTech", 371 | lastName: "Solutions", 372 | profileUrl: 373 | "https://res.cloudinary.com/djs3wu5bg/image/upload/v1692299991/SOCIALMEDIA/fvws1unsqytcqketv78w.png", 374 | location: "Mumbai, India", 375 | }, 376 | from: "MTech Solutions", 377 | replyAt: "Akwasi Asante", 378 | comment: "Not easy, hahahah", 379 | created_At: "2023-08-18T10:01:08.771Z", 380 | updated_At: "2023-08-18T09:56:38.344Z", 381 | likes: [], 382 | _id: "64df41644a4c0d47b5369f24", 383 | }, 384 | ], 385 | createdAt: "2023-08-18T10:00:16.352Z", 386 | updatedAt: "2023-08-18T10:01:14.090Z", 387 | __v: 1, 388 | }, 389 | ]; 390 | -------------------------------------------------------------------------------- /src/assets/img.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeWaveWithAsante/FullStackSocialMedia/e1170e017257f69e18dea7e2294c03fba3cbc79a/src/assets/img.jpeg -------------------------------------------------------------------------------- /src/assets/index.js: -------------------------------------------------------------------------------- 1 | import NoProfile from "./userprofile.png"; 2 | import BgImage from "./img.jpeg"; 3 | export { NoProfile, BgImage }; 4 | -------------------------------------------------------------------------------- /src/assets/userprofile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeWaveWithAsante/FullStackSocialMedia/e1170e017257f69e18dea7e2294c03fba3cbc79a/src/assets/userprofile.png -------------------------------------------------------------------------------- /src/components/CustomButton.jsx: -------------------------------------------------------------------------------- 1 | const CustomButton = ({ title, containerStyles, iconRight, type, onClick }) => { 2 | return ( 3 | 12 | ); 13 | }; 14 | 15 | export default CustomButton; 16 | -------------------------------------------------------------------------------- /src/components/EditProfile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useForm } from "react-hook-form"; 3 | import { MdClose } from "react-icons/md"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import TextInput from "./TextInput"; 6 | import Loading from "./Loading"; 7 | import CustomButton from "./CustomButton"; 8 | import { UpdateProfile } from "../redux/userSlice"; 9 | 10 | const EditProfile = () => { 11 | const { user } = useSelector((state) => state.user); 12 | const dispatch = useDispatch(); 13 | const [errMsg, setErrMsg] = useState(""); 14 | const [isSubmitting, setIsSubmitting] = useState(false); 15 | const [picture, setPicture] = useState(null); 16 | 17 | const { 18 | register, 19 | handleSubmit, 20 | formState: { errors }, 21 | } = useForm({ 22 | mode: "onChange", 23 | defaultValues: { ...user }, 24 | }); 25 | 26 | const onSubmit = async (data) => {}; 27 | 28 | const handleClose = () => { 29 | dispatch(UpdateProfile(false)); 30 | }; 31 | const handleSelect = (e) => { 32 | setPicture(e.target.files[0]); 33 | }; 34 | 35 | return ( 36 | <> 37 |
38 |
39 |
40 |
41 |
42 | 43 | ​ 44 |
50 |
51 | 57 | 58 | 61 |
62 |
66 | 77 | 78 | 88 | 89 | 100 | 101 | 111 | 112 | 124 | 125 | {errMsg?.message && ( 126 | 134 | {errMsg?.message} 135 | 136 | )} 137 | 138 |
139 | {isSubmitting ? ( 140 | 141 | ) : ( 142 | 147 | )} 148 |
149 | 150 |
151 |
152 |
153 | 154 | ); 155 | }; 156 | 157 | export default EditProfile; 158 | -------------------------------------------------------------------------------- /src/components/FriendsCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { NoProfile } from "../assets"; 4 | 5 | const FriendsCard = ({ friends }) => { 6 | return ( 7 |
8 |
9 |
10 | Friends 11 | {friends?.length} 12 |
13 | 14 |
15 | {friends?.map((friend) => ( 16 | 21 | {friend?.firstName} 26 |
27 |

28 | {friend?.firstName} {friend?.lastName} 29 |

30 | 31 | {friend?.profession ?? "No Profession"} 32 | 33 |
34 | 35 | ))} 36 |
37 |
38 |
39 | ); 40 | }; 41 | 42 | export default FriendsCard; 43 | -------------------------------------------------------------------------------- /src/components/Loading.jsx: -------------------------------------------------------------------------------- 1 | const Loading = () => { 2 | return ( 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | ); 11 | }; 12 | 13 | export default Loading; 14 | -------------------------------------------------------------------------------- /src/components/PostCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import moment from "moment"; 4 | import { NoProfile } from "../assets"; 5 | import { BiComment, BiLike, BiSolidLike } from "react-icons/bi"; 6 | import { MdOutlineDeleteOutline } from "react-icons/md"; 7 | import { useForm } from "react-hook-form"; 8 | import TextInput from "./TextInput"; 9 | import Loading from "./Loading"; 10 | import CustomButton from "./CustomButton"; 11 | import { postComments } from "../assets/data"; 12 | 13 | const ReplyCard = ({ reply, user, handleLike }) => { 14 | return ( 15 |
16 |
17 | 18 | {reply?.userId?.firstName} 23 | 24 | 25 |
26 | 27 |

28 | {reply?.userId?.firstName} {reply?.userId?.lastName} 29 |

30 | 31 | 32 | {moment(reply?.createdAt).fromNow()} 33 | 34 |
35 |
36 | 37 |
38 |

{reply?.comment}

39 |
40 |

44 | {reply?.likes?.includes(user?._id) ? ( 45 | 46 | ) : ( 47 | 48 | )} 49 | {reply?.likes?.length} Likes 50 |

51 |
52 |
53 |
54 | ); 55 | }; 56 | 57 | const CommentForm = ({ user, id, replyAt, getComments }) => { 58 | const [loading, setLoading] = useState(false); 59 | const [errMsg, setErrMsg] = useState(""); 60 | const { 61 | register, 62 | handleSubmit, 63 | reset, 64 | formState: { errors }, 65 | } = useForm({ 66 | mode: "onChange", 67 | }); 68 | 69 | const onSubmit = async (data) => {}; 70 | 71 | return ( 72 |
76 |
77 | User Image 82 | 83 | 92 |
93 | {errMsg?.message && ( 94 | 102 | {errMsg?.message} 103 | 104 | )} 105 | 106 |
107 | {loading ? ( 108 | 109 | ) : ( 110 | 115 | )} 116 |
117 |
118 | ); 119 | }; 120 | 121 | const PostCard = ({ post, user, deletePost, likePost }) => { 122 | const [showAll, setShowAll] = useState(0); 123 | const [showReply, setShowReply] = useState(0); 124 | const [comments, setComments] = useState([]); 125 | const [loading, setLoading] = useState(false); 126 | const [replyComments, setReplyComments] = useState(0); 127 | const [showComments, setShowComments] = useState(0); 128 | 129 | const getComments = async () => { 130 | setReplyComments(0); 131 | 132 | setComments(postComments); 133 | setLoading(false); 134 | }; 135 | const handleLike = async () => {}; 136 | 137 | return ( 138 |
139 |
140 | 141 | {post?.userId?.firstName} 146 | 147 | 148 |
149 |
150 | 151 |

152 | {post?.userId?.firstName} {post?.userId?.lastName} 153 |

154 | 155 | {post?.userId?.location} 156 |
157 | 158 | 159 | {moment(post?.createdAt ?? "2023-05-25").fromNow()} 160 | 161 |
162 |
163 | 164 |
165 |

166 | {showAll === post?._id 167 | ? post?.description 168 | : post?.description.slice(0, 300)} 169 | 170 | {post?.description?.length > 301 && 171 | (showAll === post?._id ? ( 172 | setShowAll(0)} 175 | > 176 | Show Less 177 | 178 | ) : ( 179 | setShowAll(post?._id)} 182 | > 183 | Show More 184 | 185 | ))} 186 |

187 | 188 | {post?.image && ( 189 | post image 194 | )} 195 |
196 | 197 |
201 |

202 | {post?.likes?.includes(user?._id) ? ( 203 | 204 | ) : ( 205 | 206 | )} 207 | {post?.likes?.length} Likes 208 |

209 | 210 |

{ 213 | setShowComments(showComments === post._id ? null : post._id); 214 | getComments(post?._id); 215 | }} 216 | > 217 | 218 | {post?.comments?.length} Comments 219 |

220 | 221 | {user?._id === post?.userId?._id && ( 222 |
deletePost(post?._id)} 225 | > 226 | 227 | Delete 228 |
229 | )} 230 |
231 | 232 | {/* COMMENTS */} 233 | {showComments === post?._id && ( 234 |
235 | getComments(post?._id)} 239 | /> 240 | 241 | {loading ? ( 242 | 243 | ) : comments?.length > 0 ? ( 244 | comments?.map((comment) => ( 245 |
246 |
247 | 248 | {comment?.userId?.firstName} 253 | 254 |
255 | 256 |

257 | {comment?.userId?.firstName} {comment?.userId?.lastName} 258 |

259 | 260 | 261 | {moment(comment?.createdAt ?? "2023-05-25").fromNow()} 262 | 263 |
264 |
265 | 266 |
267 |

{comment?.comment}

268 | 269 |
270 |

271 | {comment?.likes?.includes(user?._id) ? ( 272 | 273 | ) : ( 274 | 275 | )} 276 | {comment?.likes?.length} Likes 277 |

278 | setReplyComments(comment?._id)} 281 | > 282 | Reply 283 | 284 |
285 | 286 | {replyComments === comment?._id && ( 287 | getComments(post?._id)} 292 | /> 293 | )} 294 |
295 | 296 | {/* REPLIES */} 297 | 298 |
299 | {comment?.replies?.length > 0 && ( 300 |

303 | setShowReply( 304 | showReply === comment?.replies?._id 305 | ? 0 306 | : comment?.replies?._id 307 | ) 308 | } 309 | > 310 | Show Replies ({comment?.replies?.length}) 311 |

312 | )} 313 | 314 | {showReply === comment?.replies?._id && 315 | comment?.replies?.map((reply) => ( 316 | 321 | handleLike( 322 | "/posts/like-comment/" + 323 | comment?._id + 324 | "/" + 325 | reply?._id 326 | ) 327 | } 328 | /> 329 | ))} 330 |
331 |
332 | )) 333 | ) : ( 334 | 335 | No Comments, be first to comment 336 | 337 | )} 338 |
339 | )} 340 |
341 | ); 342 | }; 343 | 344 | export default PostCard; 345 | -------------------------------------------------------------------------------- /src/components/ProfileCard.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { Link } from "react-router-dom"; 4 | import { LiaEditSolid } from "react-icons/lia"; 5 | import { 6 | BsBriefcase, 7 | BsFacebook, 8 | BsInstagram, 9 | BsPersonFillAdd, 10 | } from "react-icons/bs"; 11 | import { FaTwitterSquare } from "react-icons/fa"; 12 | import { CiLocationOn } from "react-icons/ci"; 13 | import moment from "moment"; 14 | 15 | import { NoProfile } from "../assets"; 16 | import { UpdateProfile } from "../redux/userSlice"; 17 | 18 | const ProfileCard = ({ user }) => { 19 | const { user: data, edit } = useSelector((state) => state.user); 20 | const dispatch = useDispatch(); 21 | 22 | return ( 23 |
24 |
25 |
26 | 27 | {user?.email} 32 | 33 |
34 |

35 | {user?.firstName} {user?.lastName} 36 |

37 | 38 | {user?.profession ?? "No Profession"} 39 | 40 |
41 | 42 | 43 |
44 | {user?._id === data?._id ? ( 45 | dispatch(UpdateProfile(true))} 49 | /> 50 | ) : ( 51 | 57 | )} 58 |
59 |
60 | 61 |
62 |
63 | 64 | {user?.location ?? "Add Location"} 65 |
66 | 67 |
68 | 69 | {user?.profession ?? "Add Profession"} 70 |
71 |
72 | 73 |
74 |

75 | {user?.friends?.length} Friends 76 |

77 | 78 |
79 | Who viewed your profile 80 | {user?.views?.length} 81 |
82 | 83 | 84 | {user?.verified ? "Verified Account" : "Not Verified"} 85 | 86 | 87 |
88 | Joined 89 | 90 | {moment(user?.createdAt).fromNow()} 91 | 92 |
93 |
94 | 95 |
96 |

Social Profile

97 | 98 |
99 | 100 | Instagram 101 |
102 |
103 | 104 | Twitter 105 |
106 |
107 | 108 | Facebook 109 |
110 |
111 |
112 |
113 | ); 114 | }; 115 | 116 | export default ProfileCard; 117 | -------------------------------------------------------------------------------- /src/components/TextInput.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const TextInput = React.forwardRef( 4 | ( 5 | { type, placeholder, styles, label, labelStyles, register, name, error }, 6 | ref 7 | ) => { 8 | return ( 9 |
10 | {label && ( 11 |

{label}

12 | )} 13 | 14 |
15 | 24 |
25 | {error && ( 26 | {error} 27 | )} 28 |
29 | ); 30 | } 31 | ); 32 | 33 | export default TextInput; 34 | -------------------------------------------------------------------------------- /src/components/TopBar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TbSocial } from "react-icons/tb"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { Link } from "react-router-dom"; 5 | import TextInput from "./TextInput"; 6 | import CustomButton from "./CustomButton"; 7 | import { useForm } from "react-hook-form"; 8 | import { BsMoon, BsSunFill } from "react-icons/bs"; 9 | import { IoMdNotificationsOutline } from "react-icons/io"; 10 | import { SetTheme } from "../redux/theme"; 11 | import { Logout } from "../redux/userSlice"; 12 | 13 | const TopBar = () => { 14 | const { theme } = useSelector((state) => state.theme); 15 | const { user } = useSelector((state) => state.user); 16 | const dispatch = useDispatch(); 17 | const { 18 | register, 19 | handleSubmit, 20 | formState: { errors }, 21 | } = useForm(); 22 | 23 | const handleTheme = () => { 24 | const themeValue = theme === "light" ? "dark" : "light"; 25 | 26 | dispatch(SetTheme(themeValue)); 27 | }; 28 | 29 | const handleSearch = async (data) => {}; 30 | 31 | return ( 32 |
33 | 34 |
35 | 36 |
37 | 38 | ShareFun 39 | 40 | 41 | 42 |
46 | 51 | 56 | 57 | 58 | {/* ICONS */} 59 |
60 | 63 |
64 | 65 |
66 | 67 |
68 | dispatch(Logout())} 70 | title='Log Out' 71 | containerStyles='text-sm text-ascent-1 px-4 md:px-6 py-1 md:py-2 border border-[#666] rounded-full' 72 | /> 73 |
74 |
75 |
76 | ); 77 | }; 78 | 79 | export default TopBar; 80 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import TextInput from "./TextInput"; 2 | import Loading from "./Loading"; 3 | import CustomButton from "./CustomButton"; 4 | import TopBar from "./TopBar"; 5 | import ProfileCard from "./ProfileCard"; 6 | import FriendsCard from "./FriendsCard"; 7 | import PostCard from "./PostCard"; 8 | import EditProfile from "./EditProfile"; 9 | 10 | export { 11 | Loading, 12 | TextInput, 13 | CustomButton, 14 | TopBar, 15 | ProfileCard, 16 | FriendsCard, 17 | PostCard, 18 | EditProfile, 19 | }; 20 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | ::-webkit-scrollbar { 7 | display: none; 8 | -ms-overflow-style: none; /* IE and Edge */ 9 | scrollbar-width: none; 10 | } 11 | 12 | .topbar{ 13 | position: sticky; 14 | top: 0px; 15 | z-index: 49; 16 | } 17 | 18 | 19 | .dots-container { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | height: 100%; 24 | width: 100%; 25 | margin-bottom: 5px; 26 | } 27 | 28 | .dot { 29 | height: 12px; 30 | width: 12px; 31 | margin-right: 10px; 32 | border-radius: 10px; 33 | background-color: #fff; 34 | animation: pulse 1.5s infinite ease-in-out; 35 | } 36 | 37 | .dot:last-child { 38 | margin-right: 0; 39 | } 40 | 41 | .dot:nth-child(1) { 42 | animation-delay: -0.3s; 43 | } 44 | 45 | .dot:nth-child(2) { 46 | animation-delay: -0.1s; 47 | } 48 | 49 | .dot:nth-child(3) { 50 | animation-delay: 0.1s; 51 | } 52 | 53 | @keyframes pulse { 54 | 0% { 55 | transform: scale(0.8); 56 | background-color: #b3d4fc; 57 | box-shadow: 0 0 0 0 rgba(178, 212, 252, 0.7); 58 | } 59 | 60 | 50% { 61 | transform: scale(1.2); 62 | background-color: #4b79e4; 63 | box-shadow: 0 0 0 10px rgba(178, 212, 252, 0); 64 | } 65 | 66 | 100% { 67 | transform: scale(0.8); 68 | background-color: #2584f8; 69 | box-shadow: 0 0 0 0 rgba(178, 212, 252, 0.7); 70 | } 71 | } 72 | 73 | @layer base { 74 | :root { 75 | --color-bg: 227 227 227; 76 | --color-primary: 255 255 255; 77 | --color-secondary:255 255 255; 78 | --color-ascent1: 0 0 0; 79 | --color-ascent2: 89 91 100; 80 | --color-blue: 6 90 216; 81 | --color-white: 255 255 255; 82 | 83 | } 84 | /* //15 23 42 21 30 49*/ 85 | [data-theme="dark"] { 86 | --color-bg: 12 12 12; 87 | --color-primary: 31 31 31; 88 | --color-secondary: 47 45 48; 89 | --color-ascent1: 255 255 255; 90 | --color-ascent2: 164 161 162; 91 | --color-blue: 6 90 216; 92 | /* //16 176 255; */ 93 | --color-white: 255 255 255; 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { BrowserRouter } from "react-router-dom"; 4 | import { Provider } from "react-redux"; 5 | import "./index.css"; 6 | import App from "./App"; 7 | import { store } from "./redux/store"; 8 | 9 | const root = ReactDOM.createRoot(document.getElementById("root")); 10 | root.render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { 4 | CustomButton, 5 | EditProfile, 6 | FriendsCard, 7 | Loading, 8 | PostCard, 9 | ProfileCard, 10 | TextInput, 11 | TopBar, 12 | } from "../components"; 13 | import { suggest, requests, posts } from "../assets/data"; 14 | import { Link } from "react-router-dom"; 15 | import { NoProfile } from "../assets"; 16 | import { BsFiletypeGif, BsPersonFillAdd } from "react-icons/bs"; 17 | import { BiImages, BiSolidVideo } from "react-icons/bi"; 18 | import { useForm } from "react-hook-form"; 19 | 20 | const Home = () => { 21 | const { user, edit } = useSelector((state) => state.user); 22 | const [friendRequest, setFriendRequest] = useState(requests); 23 | const [suggestedFriends, setSuggestedFriends] = useState(suggest); 24 | const [errMsg, setErrMsg] = useState(""); 25 | const [file, setFile] = useState(null); 26 | const [posting, setPosting] = useState(false); 27 | const [loading, setLoading] = useState(false); 28 | 29 | const { 30 | register, 31 | handleSubmit, 32 | formState: { errors }, 33 | } = useForm(); 34 | 35 | const handlePostSubmit = async (data) => {}; 36 | 37 | return ( 38 | <> 39 |
40 | 41 | 42 |
43 | {/* LEFT */} 44 |
45 | 46 | 47 |
48 | 49 | {/* CENTER */} 50 |
51 |
55 |
56 | User Image 61 | 70 |
71 | {errMsg?.message && ( 72 | 80 | {errMsg?.message} 81 | 82 | )} 83 | 84 |
85 | 100 | 101 | 116 | 117 | 132 | 133 |
134 | {posting ? ( 135 | 136 | ) : ( 137 | 142 | )} 143 |
144 |
145 |
146 | 147 | {loading ? ( 148 | 149 | ) : posts?.length > 0 ? ( 150 | posts?.map((post) => ( 151 | {}} 156 | likePost={() => {}} 157 | /> 158 | )) 159 | ) : ( 160 |
161 |

No Post Available

162 |
163 | )} 164 |
165 | 166 | {/* RIGJT */} 167 |
168 | {/* FRIEND REQUEST */} 169 |
170 |
171 | Friend Request 172 | {friendRequest?.length} 173 |
174 | 175 |
176 | {friendRequest?.map(({ _id, requestFrom: from }) => ( 177 |
178 | 182 | {from?.firstName} 187 |
188 |

189 | {from?.firstName} {from?.lastName} 190 |

191 | 192 | {from?.profession ?? "No Profession"} 193 | 194 |
195 | 196 | 197 |
198 | 202 | 206 |
207 |
208 | ))} 209 |
210 |
211 | 212 | {/* SUGGESTED FRIENDS */} 213 |
214 |
215 | Friend Suggestion 216 |
217 |
218 | {suggestedFriends?.map((friend) => ( 219 |
223 | 228 | {friend?.firstName} 233 |
234 |

235 | {friend?.firstName} {friend?.lastName} 236 |

237 | 238 | {friend?.profession ?? "No Profession"} 239 | 240 |
241 | 242 | 243 |
244 | 250 |
251 |
252 | ))} 253 |
254 |
255 |
256 |
257 |
258 | 259 | {edit && } 260 | 261 | ); 262 | }; 263 | 264 | export default Home; 265 | -------------------------------------------------------------------------------- /src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useDispatch } from "react-redux"; 4 | import { useForm } from "react-hook-form"; 5 | import { TbSocial } from "react-icons/tb"; 6 | import { BsShare } from "react-icons/bs"; 7 | import { AiOutlineInteraction } from "react-icons/ai"; 8 | import { ImConnection } from "react-icons/im"; 9 | import { CustomButton, Loading, TextInput } from "../components"; 10 | import { BgImage } from "../assets"; 11 | 12 | const Login = () => { 13 | const { 14 | register, 15 | handleSubmit, 16 | formState: { errors }, 17 | } = useForm({ 18 | mode: "onChange", 19 | }); 20 | 21 | const onSubmit = async (data) => {}; 22 | 23 | const [errMsg, setErrMsg] = useState(""); 24 | const [isSubmitting, setIsSubmitting] = useState(false); 25 | const dispatch = useDispatch(); 26 | return ( 27 |
28 |
29 | {/* LEFT */} 30 |
31 |
32 |
33 | 34 |
35 | 36 | ShareFun 37 | 38 |
39 | 40 |

41 | Log in to your account 42 |

43 | Welcome back 44 | 45 |
49 | 61 | 62 | 74 | 75 | 79 | Forgot Password ? 80 | 81 | 82 | {errMsg?.message && ( 83 | 90 | {errMsg?.message} 91 | 92 | )} 93 | 94 | {isSubmitting ? ( 95 | 96 | ) : ( 97 | 102 | )} 103 | 104 | 105 |

106 | Don't have an account? 107 | 111 | Create Account 112 | 113 |

114 |
115 | {/* RIGHT */} 116 |
117 |
118 | Bg Image 123 | 124 |
125 | 126 | Share 127 |
128 | 129 |
130 | 131 | Connect 132 |
133 | 134 |
135 | 136 | Interact 137 |
138 |
139 | 140 |
141 |

142 | Connect with friedns & have share for fun 143 |

144 | 145 | Share memories with friends and the world. 146 | 147 |
148 |
149 |
150 |
151 | ); 152 | }; 153 | 154 | export default Login; 155 | -------------------------------------------------------------------------------- /src/pages/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useParams } from "react-router-dom"; 4 | import { 5 | FriendsCard, 6 | Loading, 7 | PostCard, 8 | ProfileCard, 9 | TopBar, 10 | } from "../components"; 11 | import { posts } from "../assets/data"; 12 | 13 | const Profile = () => { 14 | const { id } = useParams(); 15 | const dispatch = useDispatch(); 16 | const { user } = useSelector((state) => state.user); 17 | // const { posts } = useSelector((state) => state.posts); 18 | const [userInfo, setUserInfo] = useState(user); 19 | const [loading, setLoading] = useState(false); 20 | 21 | const handleDelete = () => {}; 22 | const handleLikePost = () => {}; 23 | 24 | return ( 25 | <> 26 |
27 | 28 |
29 | {/* LEFT */} 30 |
31 | 32 | 33 |
34 | 35 |
36 |
37 | 38 | {/* CENTER */} 39 |
40 | {loading ? ( 41 | 42 | ) : posts?.length > 0 ? ( 43 | posts?.map((post) => ( 44 | 51 | )) 52 | ) : ( 53 |
54 |

No Post Available

55 |
56 | )} 57 |
58 | 59 | {/* RIGHT */} 60 |
61 | 62 |
63 |
64 |
65 | 66 | ); 67 | }; 68 | 69 | export default Profile; 70 | -------------------------------------------------------------------------------- /src/pages/Register.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { useDispatch } from "react-redux"; 4 | import { useForm } from "react-hook-form"; 5 | import { TbSocial } from "react-icons/tb"; 6 | import { BsShare } from "react-icons/bs"; 7 | import { AiOutlineInteraction } from "react-icons/ai"; 8 | import { ImConnection } from "react-icons/im"; 9 | import { CustomButton, Loading, TextInput } from "../components"; 10 | import { BgImage } from "../assets"; 11 | 12 | const Register = () => { 13 | const { 14 | register, 15 | handleSubmit, 16 | getValues, 17 | formState: { errors }, 18 | } = useForm({ 19 | mode: "onChange", 20 | }); 21 | 22 | const onSubmit = async (data) => {}; 23 | 24 | const [errMsg, setErrMsg] = useState(""); 25 | const [isSubmitting, setIsSubmitting] = useState(false); 26 | const dispatch = useDispatch(); 27 | 28 | return ( 29 |
30 |
31 | {/* LEFT */} 32 |
33 |
34 |
35 | 36 |
37 | 38 | ShareFun 39 | 40 |
41 | 42 |

43 | Create your account 44 |

45 | 46 |
50 |
51 | 62 | 63 | 73 |
74 | 75 | 86 | 87 |
88 | 99 | 100 | { 107 | const { password } = getValues(); 108 | 109 | if (password != value) { 110 | return "Passwords do no match"; 111 | } 112 | }, 113 | })} 114 | error={ 115 | errors.cPassword && errors.cPassword.type === "validate" 116 | ? errors.cPassword?.message 117 | : "" 118 | } 119 | /> 120 |
121 | 122 | {errMsg?.message && ( 123 | 130 | {errMsg?.message} 131 | 132 | )} 133 | 134 | {isSubmitting ? ( 135 | 136 | ) : ( 137 | 142 | )} 143 | 144 | 145 |

146 | Already has an account?{" "} 147 | 151 | Login 152 | 153 |

154 |
155 | {/* RIGHT */} 156 |
157 |
158 | Bg Image 163 | 164 |
165 | 166 | Share 167 |
168 | 169 |
170 | 171 | Connect 172 |
173 | 174 |
175 | 176 | Interact 177 |
178 |
179 | 180 |
181 |

182 | Connect with friedns & have share for fun 183 |

184 | 185 | Share memories with friends and the world. 186 | 187 |
188 |
189 |
190 |
191 | ); 192 | }; 193 | 194 | export default Register; 195 | -------------------------------------------------------------------------------- /src/pages/ResetPassword.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useForm } from "react-hook-form"; 3 | import { CustomButton, Loading, TextInput } from "../components"; 4 | 5 | const ResetPassword = () => { 6 | const [errMsg, setErrMsg] = useState(""); 7 | const [isSubmitting, setIsSubmitting] = useState(false); 8 | 9 | const { 10 | register, 11 | handleSubmit, 12 | 13 | formState: { errors }, 14 | } = useForm({ 15 | mode: "onChange", 16 | }); 17 | 18 | const onSubmit = async (data) => {}; 19 | 20 | return ( 21 |
22 |
23 |

Email Address

24 | 25 | 26 | Enter email address used during registration 27 | 28 | 29 |
33 | 44 | {errMsg?.message && ( 45 | 53 | {errMsg?.message} 54 | 55 | )} 56 | 57 | {isSubmitting ? ( 58 | 59 | ) : ( 60 | 65 | )} 66 | 67 |
68 |
69 | ); 70 | }; 71 | 72 | export default ResetPassword; 73 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import Login from "./Login"; 2 | import Home from "./Home"; 3 | import Register from "./Register"; 4 | import Profile from "./Profile"; 5 | import ResetPassword from "./ResetPassword"; 6 | 7 | export { Login, Home, Register, Profile, ResetPassword }; 8 | -------------------------------------------------------------------------------- /src/redux/postSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | posts: {}, 5 | }; 6 | 7 | const postSlice = createSlice({ 8 | name: "post", 9 | initialState, 10 | reducers: { 11 | getPosts(state, action) { 12 | state.posts = action.payload; 13 | }, 14 | }, 15 | }); 16 | 17 | export default postSlice.reducer; 18 | 19 | export function SetPosts(post) { 20 | return (dispatch, getState) => { 21 | dispatch(postSlice.actions.getPosts(post)); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/redux/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "@reduxjs/toolkit"; 2 | 3 | import userSlice from "./userSlice"; 4 | import themeSlice from "./theme"; 5 | import postSlice from "./postSlice"; 6 | 7 | const rootReducer = combineReducers({ 8 | user: userSlice, 9 | theme: themeSlice, 10 | posts: postSlice, 11 | }); 12 | 13 | export { rootReducer }; 14 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import { rootReducer } from "./reducer"; 3 | 4 | const store = configureStore({ 5 | reducer: rootReducer, 6 | }); 7 | 8 | const { dispatch } = store; 9 | 10 | export { store, dispatch }; 11 | -------------------------------------------------------------------------------- /src/redux/theme.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | theme: JSON.parse(window?.localStorage.getItem("theme")) ?? "dark", 5 | }; 6 | 7 | const themeSlice = createSlice({ 8 | name: "theme", 9 | initialState, 10 | reducers: { 11 | setTheme(state, action) { 12 | state.theme = action.payload; 13 | localStorage.setItem("theme", JSON.stringify(action.payload)); 14 | }, 15 | }, 16 | }); 17 | 18 | export default themeSlice.reducer; 19 | 20 | export function SetTheme(value) { 21 | return (dispatch) => { 22 | dispatch(themeSlice.actions.setTheme(value)); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/redux/userSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import { user } from "../assets/data"; 3 | 4 | const initialState = { 5 | user: JSON.parse(window?.localStorage.getItem("user")) ?? user, 6 | edit: false, 7 | }; 8 | 9 | const userSlice = createSlice({ 10 | name: "user", 11 | initialState, 12 | reducers: { 13 | login(state, action) { 14 | state.user = action.payload; 15 | localStorage.setItem("user", JSON.stringify(action.payload)); 16 | }, 17 | logout(state) { 18 | state.user = null; 19 | localStorage?.removeItem("user"); 20 | }, 21 | updateProfile(state, action) { 22 | state.edit = action.payload; 23 | }, 24 | }, 25 | }); 26 | export default userSlice.reducer; 27 | 28 | export function UserLogin(user) { 29 | return (dispatch, getState) => { 30 | dispatch(userSlice.actions.login(user)); 31 | }; 32 | } 33 | 34 | export function Logout() { 35 | return (dispatch, getState) => { 36 | dispatch(userSlice.actions.logout()); 37 | }; 38 | } 39 | 40 | export function UpdateProfile(val) { 41 | return (dispatch, getState) => { 42 | dispatch(userSlice.actions.updateProfile(val)); 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{html,js,jsx}"], 4 | theme: { 5 | colors: { 6 | bgColor: "rgb(var(--color-bg) / )", 7 | primary: "rgb(var(--color-primary) / )", 8 | secondary: "rgb(var(--color-secondary) / )", 9 | blue: "rgb(var(--color-blue) / )", 10 | white: "rgb(var(--color-white) / )", 11 | ascent: { 12 | 1: "rgb(var(--color-ascent1) / )", 13 | 2: "rgb(var(--color-ascent2) / )", 14 | }, 15 | }, 16 | screens: { 17 | sm: "640px", 18 | 19 | md: "768px", 20 | 21 | lg: "1024px", 22 | 23 | xl: "1280px", 24 | 25 | "2xl": "1536px", 26 | }, 27 | extend: {}, 28 | }, 29 | plugins: [], 30 | }; 31 | --------------------------------------------------------------------------------