├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── .gitignore ├── API Design.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Model Design.md ├── README.md ├── client ├── .env ├── .env.template ├── .gitignore ├── LICENSE.md ├── README.md ├── package.json ├── postcss.config.js ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── assets │ │ ├── avatars │ │ │ ├── 668412781664e859963a7068.135434950.jpeg │ │ │ ├── 66b4024cbaf1712da9855432.14.webp │ │ │ ├── ava.png │ │ │ ├── avatar-10.jpg │ │ │ ├── avatar-12.jpg │ │ │ ├── avatar-14.jpg │ │ │ ├── avatar-17.jpg │ │ │ ├── avatar.jpg │ │ │ └── default.jpg │ │ └── images │ │ │ ├── background_outrun.png │ │ │ ├── default.jpg │ │ │ └── logo.png │ ├── default.jpg │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── index.html │ ├── intro.png │ ├── logo.jpg │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── components │ │ ├── CalendarView │ │ │ ├── index.jsx │ │ │ └── util.js │ │ ├── Cards │ │ │ └── TitleCard.jsx │ │ ├── General │ │ │ ├── Avatar.jsx │ │ │ ├── Button.jsx │ │ │ ├── ErrorText.jsx │ │ │ ├── Input.jsx │ │ │ ├── InputText.jsx │ │ │ └── Logo.jsx │ │ ├── Input │ │ │ ├── BigInputText.js │ │ │ ├── Input.js │ │ │ ├── InputText.js │ │ │ ├── Inputdisabled.js │ │ │ ├── SearchBar.js │ │ │ ├── SelectBox.js │ │ │ ├── SelectBoxBig.js │ │ │ ├── SelectBoxSmall.js │ │ │ ├── StanSearchBar.js │ │ │ ├── TextAreaInput.js │ │ │ └── ToogleInput.js │ │ ├── Typography │ │ │ ├── ErrorText.jsx │ │ │ ├── HelperText.jsx │ │ │ ├── Subtitle.jsx │ │ │ └── Title.jsx │ │ ├── containers │ │ │ ├── Header.jsx │ │ │ ├── Layout.jsx │ │ │ ├── LeftSidebar.jsx │ │ │ ├── ModalLayout.jsx │ │ │ ├── PageContent.jsx │ │ │ ├── RightSidebar.jsx │ │ │ ├── SidebarSubmenu.jsx │ │ │ └── SuspenseContent.jsx │ │ └── features │ │ │ ├── charts │ │ │ ├── components │ │ │ │ ├── BarChart.jsx │ │ │ │ ├── DoughnutChart.jsx │ │ │ │ ├── LineChart.jsx │ │ │ │ ├── PieChart.jsx │ │ │ │ ├── ScatterChart.jsx │ │ │ │ └── StackBarChart.jsx │ │ │ └── index.jsx │ │ │ ├── common │ │ │ ├── components │ │ │ │ ├── ConfirmationModalBody.js │ │ │ │ └── NotificationBodyRightDrawer.js │ │ │ ├── headerSlice.js │ │ │ ├── modalSlice.js │ │ │ └── rightDrawerSlice.js │ │ │ ├── dashboard │ │ │ ├── components │ │ │ │ ├── AmountStats.js │ │ │ │ ├── ArticleCard.js │ │ │ │ ├── BarChart.js │ │ │ │ ├── DashboardStats.js │ │ │ │ ├── DashboardTopBar.js │ │ │ │ ├── DoughnutChart.js │ │ │ │ ├── LineChart.js │ │ │ │ ├── PageStats.js │ │ │ │ ├── Toolbar.js │ │ │ │ └── UserChannels.js │ │ │ └── index.js │ │ │ ├── integration │ │ │ └── index.js │ │ │ ├── leads │ │ │ ├── components │ │ │ │ └── AddLeadModalBody.js │ │ │ ├── index.js │ │ │ └── leadSlice.js │ │ │ ├── profile │ │ │ ├── components │ │ │ │ ├── ProfileInfo.jsx │ │ │ │ └── Repositories.js │ │ │ └── index.jsx │ │ │ ├── settings │ │ │ ├── changepassword │ │ │ │ └── index.js │ │ │ ├── profilesettings │ │ │ │ └── index.js │ │ │ └── team │ │ │ │ └── index.js │ │ │ ├── transactions │ │ │ └── index.js │ │ │ └── user │ │ │ ├── ForgotPassword.js │ │ │ ├── LandingIntro.js │ │ │ ├── Login.js │ │ │ └── components │ │ │ └── TemplatePointers.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── pages │ │ ├── Admin │ │ │ ├── Category │ │ │ │ ├── components │ │ │ │ │ ├── InputCategory.jsx │ │ │ │ │ └── TableCategory.jsx │ │ │ │ └── index.jsx │ │ │ └── UserManage │ │ │ │ ├── EditAccount │ │ │ │ ├── EditManage │ │ │ │ │ └── index.jsx │ │ │ │ └── index.jsx │ │ │ │ ├── UserManagePanel │ │ │ │ ├── TopSideButtons │ │ │ │ │ └── index.jsx │ │ │ │ └── index.jsx │ │ │ │ └── index.jsx │ │ ├── Article │ │ │ ├── AllArticle.jsx │ │ │ ├── AnswerArticle.jsx │ │ │ ├── Draft.jsx │ │ │ ├── FavouriteArticle.jsx │ │ │ ├── MyArticle.jsx │ │ │ ├── NewArticle.jsx │ │ │ └── ShowArticle.jsx │ │ ├── ForgotPassword │ │ │ └── index.jsx │ │ ├── Settings │ │ │ ├── ChangePassword.jsx │ │ │ ├── ProfileSettings.jsx │ │ │ └── Team.jsx │ │ ├── Signin │ │ │ └── index.jsx │ │ ├── Signup │ │ │ └── index.jsx │ │ └── protected │ │ │ ├── 404.jsx │ │ │ ├── Blank.jsx │ │ │ ├── Charts.jsx │ │ │ ├── Dashboard.jsx │ │ │ ├── Integration.jsx │ │ │ ├── Leads.jsx │ │ │ ├── Transactions.jsx │ │ │ └── Welcome.jsx │ ├── redux │ │ ├── adminSlice.js │ │ ├── articleSlice.js │ │ ├── auth.js │ │ ├── authSlice.js │ │ ├── init.js │ │ └── store.js │ ├── reportWebVitals.js │ ├── routes │ │ ├── index.js │ │ └── sidebar.js │ ├── setupTests.js │ └── utils │ │ ├── dummyData.js │ │ ├── globalConstantUtil.js │ │ └── requestServer.js └── tailwind.config.js ├── package-lock.json ├── package.json └── server ├── .env ├── .eslintrc.json ├── .gitignore ├── Procfile ├── README.md ├── app.js ├── package-lock.json ├── package.json ├── public └── favicon.ico ├── socket ├── constants.js ├── events │ ├── authorizationEvents.js │ ├── chatEvents.js │ ├── loggedusersEvent.js │ └── notificationEvents.js ├── global.js ├── index.js ├── init │ ├── authorizationInit.js │ ├── chatInit.js │ ├── loggedusersInit.js │ └── notificationInit.js └── utils │ ├── authorizationUtils.js │ ├── chatUtils.js │ ├── index.js │ ├── loggedusersUtils.js │ └── notificationUtils.js ├── socketApp.js └── src ├── configs ├── config.js ├── errorHandler.js ├── passport.js └── socket.js ├── controllers ├── adminControllers │ ├── categoryController.js │ └── userController.js ├── articleController.js └── authController.js ├── db └── connect.js ├── middleware └── authorization.js ├── models ├── adminModel │ └── categoryModel.js ├── articleModel.js └── userModel.js └── routes ├── adminRoutes ├── categoryRoutes.js ├── index.js └── userRoutes.js ├── articleRoutes.js ├── authRoutes.js └── index.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | -------------------------------------------------------------------------------- /API Design.md: -------------------------------------------------------------------------------- 1 | # API Design 2 | 3 | ## Auth 4 | 5 | post: /api/signup 6 | 7 | post: /api/signin 8 | 9 | ## Articles 10 | 11 | post /api/article/create 12 | 13 | put /api/article/:id 14 | 15 | delete /api/article/:id 16 | 17 | get /api/article/home 18 | 19 | get /api/article/:id 20 | 21 | put /api/article/comment/:id 22 | 23 | put /api/article/favorite/:id 24 | 25 | - sort by latest 26 | - sort by cnt of comments 27 | - sort by cnt of favourites 28 | - search by title 29 | 30 | ## Admin 31 | 32 | ### User 33 | 34 | - Manage 35 | 36 | get /api/admin/user/all 37 | 38 | put /api/admin/user/role/:id 39 | 40 | delete /api/admin/user/:id 41 | 42 | - Profile 43 | 44 | get /api/admin/user/:id 45 | 46 | put /api/admin/user/:id 47 | 48 | put /api/admin/user/password/:id 49 | 50 | put /api/admin/user/avatar/:id 51 | 52 | ### Category 53 | 54 | post /api/admin/category/ 55 | 56 | put /api/admin/category/:id 57 | 58 | delete /api/admin/category/:id 59 | 60 | get /api/admin/category/all 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Finalgoal231 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Model Design.md: -------------------------------------------------------------------------------- 1 | -Auth 2 | 3 | username:{ 4 | type:string; 5 | required:true; 6 | unique: true; 7 | $regex:[a-z][0-9][_]; 8 | }, 9 | name:{ 10 | type:string; 11 | required:true 12 | }, 13 | bio:{ 14 | type:string; 15 | }, 16 | avatar:{ 17 | type: string;/*avatar's path */ 18 | defaule:"public/default.jpg"; 19 | } 20 | role:{ 21 | type:string; 22 | default:"guest"; 23 | }, 24 | follow:[{ 25 | type:mongoose.Schema.ObjectId, 26 | }], 27 | category:[{ 28 | type:mongoose.Schema.ObjectId, 29 | }] 30 | 31 | -Article 32 | from:{ 33 | type:string; 34 | required:true; 35 | }, 36 | title:{ 37 | type:string; 38 | required:true; 39 | }, 40 | category:{ 41 | type:string; 42 | required:true 43 | }, 44 | tags:[{ 45 | type:string; 46 | }], 47 | content:{ 48 | type: string; 49 | }, 50 | draft:{ 51 | type:number; 52 | default:0; 53 | }, 54 | favouritor: [{ 55 | type:String; 56 | default:0; 57 | }]a, 58 | comment:[{ 59 | from:{ 60 | type:string; 61 | required:true; 62 | }, 63 | title:{ 64 | type:string; 65 | required:true; 66 | }, 67 | category:{ 68 | type:string; 69 | required:true 70 | }, 71 | tags:[{ 72 | type:string; 73 | }], 74 | content:{ 75 | type: string; 76 | }, 77 | 78 | draft:{ 79 | type:number; 80 | default:0; 81 | }, 82 | }], 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /client/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_BASE_URL=http://localhost:4000 2 | PORT = 3001 3 | -------------------------------------------------------------------------------- /client/.env.template: -------------------------------------------------------------------------------- 1 | REACT_APP_BASE_URL=https://reqres.in/ 2 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dashwind 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Logo](https://ik.imagekit.io/vu5t8xb15vzcx/tr:h-100/android-chrome-512x512_EiumvYoXeA.png?ik-sdk-version=javascript-1.4.3&updatedAt=1669548997842) 3 | 4 | # Daisy UI Admin Dashboard Template - DashWind 5 | [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/) 6 | 7 | This is a free admin dashboard template that uses **Daisy UI** and React js. It has **fully customizable and themable CSS** CSS and is powered by Tailwind CSS utility classes. Additionally, it comes with **redux toolkit** and other libraries already set up. 8 | 9 | 10 | ## Preview 11 | 12 | 🚀 [Live preview](https://tailwind-dashboard-template-dashwind.vercel.app/) 13 | 14 | 15 | ![App Screenshot](https://ik.imagekit.io/vu5t8xb15vzcx/tr:h-600/Screenshot_2023-05-09_at_12.57.37_PM_z94SiShUDS.png?updatedAt=1683617550144) 16 | 17 | 18 | 19 | ## Features 20 | 21 | - **Light/dark** mode toggle 22 | - Token based user **authentication** 23 | - **Submenu support** in sidebar 24 | - Store management using **redux toolkit** 25 | - **Daisy UI** components and **Tailwind** support 26 | - **Right and left sidebar**, Universal loader, notifications 27 | - **Calendar**, global modal, **chart js 2** and other components 28 | 29 | 30 | ## Installation 31 | 32 | Go to project directory and run (make sure you have node installed first) 33 | 34 | ```bash 35 | npm install 36 | npm start 37 | ``` 38 | 39 | ## Core Libraries Used 40 | 41 | - [React JS v18.2.0](https://reactjs.org/) 42 | - [React Router v6.4.3](https://reactrouter.com/en/main) 43 | - [Tailwind CSS v3.2.4](https://tailwindcss.com/) 44 | - [Daisy UI v2.41.0](https://daisyui.com/) 45 | - [HeroIcons](https://heroicons.com/) 46 | - [Redux toolkit v1.9](https://redux-toolkit.js.org/) 47 | - [React ChartJS 2 v5](https://react-chartjs-2.js.org/) 48 | 49 | ## Documentation 50 | 51 | [Documentation](https://tailwind-dashboard-template-dashwind.vercel.app/documentation) 52 | 53 | ## Page Examples 54 | 55 | | | | 56 | :-------------------------:|:-------------------------: 57 | ![Dark Mode](https://ik.imagekit.io/vu5t8xb15vzcx/tr:h-600/Screenshot_2023-05-09_at_12.57.37_PM_z94SiShUDS.png?updatedAt=1683617550144) | ![Transations Screenshot](https://ik.imagekit.io/vu5t8xb15vzcx/Screenshot_2023-05-09_at_1.01.54_PM_YiG__JTFu.png?updatedAt=1683619541458) 58 | ![Leads Screenshot](https://ik.imagekit.io/vu5t8xb15vzcx/Screenshot_2023-05-09_at_1.34.56_PM_cdSamaaCmA.png?updatedAt=1683619597855) | ![Setting Screenshot](https://ik.imagekit.io/vu5t8xb15vzcx/Screenshot_2023-01-20_at_12.43.25_PM_xZBThuZdU.png?ik-sdk-version=javascript-1.4.3&updatedAt=1674198832089) 59 | ![Calendar Screenshot](https://ik.imagekit.io/vu5t8xb15vzcx/Screenshot_2023-05-09_at_1.02.03_PM_pObZm43gl.png?updatedAt=1683617549958) | ![Register Screenshot](https://ik.imagekit.io/vu5t8xb15vzcx/Screenshot_2023-01-20_at_12.43.00_PM_1fkpMRG90.png?ik-sdk-version=javascript-1.4.3&updatedAt=1674198831908) 60 | 61 | 62 | 63 | 64 | 65 | ## Preview 66 | 67 | 🚀 [Live preview](https://tailwind-dashboard-template-dashwind.vercel.app/) 68 | 69 | 70 | ## Roadmap 71 | 72 | - Addition of users, chat/ inbox page 73 | - Calendar improments 74 | - Seperate templates based on business functions like CRM, Sales, Project Management 75 | 76 | 77 | ## Contributing 78 | 79 | Contributions are always welcome! 80 | 81 | ## License 82 | 83 | [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/) 84 | 85 | 86 | ## Feedback 87 | 88 | If you have any feedback, please reach out [here](https://forms.gle/8G7PsvQp8X1Swcf29) 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "admin-dashboard-template-dashwind", 3 | "version": "1.0.0", 4 | "description": "Admin Dashboard template built with create-react-app, tailwind css and daisy UI. Template uses rich tailwind css utility classes and have components of daisy UI, also have redux toolkit implemented for store management.", 5 | "scripts": { 6 | "start": "react-scripts start", 7 | "build": "react-scripts build", 8 | "test": "react-scripts test", 9 | "eject": "react-scripts eject" 10 | }, 11 | "dependencies": { 12 | "@heroicons/react": "^2.0.13", 13 | "@reduxjs/toolkit": "^1.9.0", 14 | "@testing-library/jest-dom": "^5.16.5", 15 | "@testing-library/react": "^13.4.0", 16 | "@testing-library/user-event": "^13.5.0", 17 | "axios": "^1.6.2", 18 | "capitalize-the-first-letter": "^1.0.8", 19 | "chart.js": "^4.0.1", 20 | "daisyui": "^2.41.0", 21 | "dayjs": "^1.11.7", 22 | "moment": "^2.29.4", 23 | "react": "^18.2.0", 24 | "react-chartjs-2": "^5.0.1", 25 | "react-dom": "^18.2.0", 26 | "react-icons": "^5.2.1", 27 | "react-notifications": "^1.7.4", 28 | "react-redux": "^8.0.5", 29 | "react-router-dom": "^6.4.3", 30 | "react-scripts": "5.0.1", 31 | "react-tailwindcss-datepicker": "^1.6.0", 32 | "socket.io": "^4.7.2", 33 | "socketio": "^1.0.0", 34 | "theme-change": "^2.2.0", 35 | "web-vitals": "^2.1.4" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "git+https://github.com/srobbin01/tailwind-dashboard-template-dashwind" 40 | }, 41 | "keywords": [ 42 | "reactjs", 43 | "tailwind-css", 44 | "starter-kit", 45 | "saas-starter-kit", 46 | "reduxt-toolkit-dashboard-template", 47 | "daisyui-template", 48 | "dashboard-template", 49 | "react-router", 50 | "react-charts" 51 | ], 52 | "author": "srobbin01", 53 | "license": "ISC", 54 | "bugs": { 55 | "url": "https://github.com/srobbin01/tailwind-dashboard-template-dashwind/issues" 56 | }, 57 | "homepage": "", 58 | "eslintConfig": { 59 | "extends": [ 60 | "react-app", 61 | "react-app/jest" 62 | ] 63 | }, 64 | "browserslist": { 65 | "production": [ 66 | ">0.2%", 67 | "not dead", 68 | "not op_mini all" 69 | ], 70 | "development": [ 71 | "last 1 chrome version", 72 | "last 1 firefox version", 73 | "last 1 safari version" 74 | ] 75 | }, 76 | "devDependencies": { 77 | "@tailwindcss/typography": "^0.5.8", 78 | "autoprefixer": "^10.4.13", 79 | "postcss": "^8.4.19", 80 | "tailwindcss": "^3.2.4" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /client/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /client/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /client/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/apple-touch-icon.png -------------------------------------------------------------------------------- /client/public/assets/avatars/668412781664e859963a7068.135434950.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/avatars/668412781664e859963a7068.135434950.jpeg -------------------------------------------------------------------------------- /client/public/assets/avatars/66b4024cbaf1712da9855432.14.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/avatars/66b4024cbaf1712da9855432.14.webp -------------------------------------------------------------------------------- /client/public/assets/avatars/ava.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/avatars/ava.png -------------------------------------------------------------------------------- /client/public/assets/avatars/avatar-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/avatars/avatar-10.jpg -------------------------------------------------------------------------------- /client/public/assets/avatars/avatar-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/avatars/avatar-12.jpg -------------------------------------------------------------------------------- /client/public/assets/avatars/avatar-14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/avatars/avatar-14.jpg -------------------------------------------------------------------------------- /client/public/assets/avatars/avatar-17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/avatars/avatar-17.jpg -------------------------------------------------------------------------------- /client/public/assets/avatars/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/avatars/avatar.jpg -------------------------------------------------------------------------------- /client/public/assets/avatars/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/avatars/default.jpg -------------------------------------------------------------------------------- /client/public/assets/images/background_outrun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/images/background_outrun.png -------------------------------------------------------------------------------- /client/public/assets/images/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/images/default.jpg -------------------------------------------------------------------------------- /client/public/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/assets/images/logo.png -------------------------------------------------------------------------------- /client/public/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/default.jpg -------------------------------------------------------------------------------- /client/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/favicon-16x16.png -------------------------------------------------------------------------------- /client/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/favicon-32x32.png -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Daisy UI Admin Dashboard Template - DashWind 28 | 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /client/public/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/intro.png -------------------------------------------------------------------------------- /client/public/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/logo.jpg -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/public/logo512.png -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "DashWind", 3 | "name": "DashWind", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | 36 | to { 37 | transform: rotate(360deg); 38 | } 39 | } 40 | 41 | .signHead { 42 | text-shadow: 5px 5px 2px gray; 43 | color: lightgray; 44 | } -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { lazy, useEffect } from "react"; 2 | import "./App.css"; 3 | import { 4 | BrowserRouter as Router, 5 | Route, 6 | Routes, 7 | } from "react-router-dom"; 8 | import { themeChange } from "theme-change"; 9 | import checkAuth from "./redux/auth"; 10 | import initializeApp from "./redux/init"; 11 | import { useDispatch } from "react-redux"; 12 | import { setAuth } from "./redux/authSlice"; 13 | const Layout = lazy(() => import("./components/containers/Layout")); 14 | const Singin = lazy(() => import("./pages/Signin")); 15 | const ForgotPassword = lazy(() => import("./pages/ForgotPassword")); 16 | const Signup = lazy(() => import("./pages/Signup")); 17 | 18 | // Initializing different libraries 19 | initializeApp(); 20 | 21 | // Check for login and initialize axios 22 | const token = checkAuth(); 23 | 24 | function App() { 25 | const dispatch = useDispatch(); 26 | if (token) { 27 | const payload = { 28 | isAuthenicated: true, 29 | user: JSON.parse(localStorage.user), 30 | }; 31 | dispatch(setAuth(payload)); 32 | } 33 | useEffect(() => { 34 | themeChange(false); 35 | }, []); 36 | 37 | return ( 38 | <> 39 | 40 | 41 | } /> 42 | } /> 43 | } /> 44 | } /> 45 | {/* } /> */} 46 | 47 | 48 | 49 | ); 50 | } 51 | 52 | export default App; 53 | -------------------------------------------------------------------------------- /client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /client/src/components/CalendarView/util.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | CALENDAR_EVENT_STYLE: { 3 | BLUE: "bg-blue-200 dark:bg-blue-600 dark:text-blue-100", 4 | GREEN: "bg-green-200 dark:bg-green-600 dark:text-green-100", 5 | PURPLE: "bg-purple-200 dark:bg-purple-600 dark:text-purple-100", 6 | ORANGE: "bg-orange-200 dark:bg-orange-600 dark:text-orange-100", 7 | PINK: "bg-pink-200 dark:bg-pink-600 dark:text-pink-100", 8 | MORE: "hover:underline cursor-pointer font-medium ", 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /client/src/components/Cards/TitleCard.jsx: -------------------------------------------------------------------------------- 1 | import Subtitle from "../Typography/Subtitle"; 2 | 3 | function TitleCard({ title, children, topMargin, TopSideButtons }) { 4 | return ( 5 |
10 | {/* Title for Card */} 11 | 12 | {title} 13 | 14 | {/* Top side button, show only if present */} 15 | {TopSideButtons && ( 16 |
{TopSideButtons}
17 | )} 18 |
19 | 20 |
21 | 22 | {/** Card Body */} 23 |
{children}
24 |
25 | ); 26 | } 27 | 28 | export default TitleCard; 29 | -------------------------------------------------------------------------------- /client/src/components/General/Avatar.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { updateAvatar } from "../../redux/authSlice"; 4 | import { BiSolidEditAlt } from "react-icons/bi"; 5 | 6 | function ProfileAvatar() { 7 | const [avatar, setAvatar] = useState(); 8 | const { user } = useSelector((state) => state.auth); 9 | 10 | const dispatch = useDispatch(); 11 | 12 | const changeAvatar = (e) => { 13 | setAvatar(e.target.files[0]); 14 | }; 15 | 16 | const handleSubmit = () => { 17 | dispatch(updateAvatar({ id: user._id, avatar: avatar })); 18 | }; 19 | 20 | return ( 21 |
22 |
23 | avatar 28 | 33 |
34 |
35 | 43 |
44 |
45 | ); 46 | } 47 | 48 | export default ProfileAvatar; 49 | -------------------------------------------------------------------------------- /client/src/components/General/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const Button = (prop) => { 4 | return ( 5 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /client/src/components/General/ErrorText.jsx: -------------------------------------------------------------------------------- 1 | function ErrorText({ children, Style }) { 2 | return ( 3 |

{children}

4 | ); 5 | } 6 | 7 | export default ErrorText; -------------------------------------------------------------------------------- /client/src/components/General/Input.jsx: -------------------------------------------------------------------------------- 1 | export const Input = (prop) => { 2 | return ( 3 |
4 | 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /client/src/components/General/InputText.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | 4 | function InputText({ labelTitle, lableStyle, type, containerStyle, defaultValue, updateFormValue, updateType, autoFocus, placeholder }) { 5 | const [value, setValue] = useState(defaultValue); 6 | 7 | const updateInputValue = (val) => { 8 | setValue(val); 9 | updateFormValue({ updateType, value: val }); 10 | } 11 | 12 | return ( 13 |
14 | 17 | updateInputValue(e.target.value)} 19 | className="h-[50px] rounded-lg px-[20px] w-full outline-none " /> 20 |
21 | ); 22 | } 23 | 24 | export default InputText; -------------------------------------------------------------------------------- /client/src/components/General/Logo.jsx: -------------------------------------------------------------------------------- 1 | function Logo() { 2 | return ( 3 |
4 |
5 |
6 |

Blog Website

7 | 12 |
13 |
14 |
15 | ); 16 | } 17 | 18 | export default Logo; 19 | -------------------------------------------------------------------------------- /client/src/components/Input/BigInputText.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | function BigInputText({ 4 | name, 5 | labelTitle, 6 | labelStyle, 7 | type, 8 | value, 9 | containerStyle, 10 | placeholder, 11 | setHandleKeyDown, 12 | onChange, 13 | }) { 14 | return ( 15 |
16 | 21 | 30 |
31 | ); 32 | } 33 | 34 | export default BigInputText; 35 | -------------------------------------------------------------------------------- /client/src/components/Input/Input.js: -------------------------------------------------------------------------------- 1 | function Input({ name, type, placeholder, setHandleKeyDown, onChange, className }) { 2 | return ( 3 | 11 | ); 12 | } 13 | 14 | Input.propTypes = {}; 15 | 16 | export default Input; 17 | -------------------------------------------------------------------------------- /client/src/components/Input/InputText.js: -------------------------------------------------------------------------------- 1 | function InputText({ 2 | name, 3 | labelTitle, 4 | labelStyle, 5 | type, 6 | containerStyle, 7 | placeholder, 8 | value, 9 | setHandleKeyDown, 10 | onChange, 11 | }) { 12 | return ( 13 |
14 | 17 | 26 |
27 | ); 28 | } 29 | 30 | export default InputText; 31 | -------------------------------------------------------------------------------- /client/src/components/Input/Inputdisabled.js: -------------------------------------------------------------------------------- 1 | function InputDisabled({ 2 | name, 3 | labelTitle, 4 | labelStyle, 5 | type, 6 | value, 7 | containerStyle, 8 | placeholder, 9 | setHandleKeyDown, 10 | onChange, 11 | }) { 12 | return ( 13 |
14 | 19 | 29 |
30 | ); 31 | } 32 | 33 | export default InputDisabled; 34 | -------------------------------------------------------------------------------- /client/src/components/Input/SearchBar.js: -------------------------------------------------------------------------------- 1 | function SearchBar({ searchText, onChange, className }) { 2 | return ( 3 |
4 |
5 | 12 |
13 |
14 | ); 15 | } 16 | 17 | export default SearchBar; 18 | -------------------------------------------------------------------------------- /client/src/components/Input/SelectBox.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import capitalize from "capitalize-the-first-letter"; 3 | import React, { useState, useEffect } from "react"; 4 | import InformationCircleIcon from "@heroicons/react/24/outline/InformationCircleIcon"; 5 | 6 | function SelectBox(props) { 7 | const { 8 | labelTitle, 9 | labelDescription, 10 | defaultValue, 11 | containerStyle, 12 | placeholder, 13 | labelStyle, 14 | options, 15 | updateType, 16 | updateFormValue, 17 | } = props; 18 | 19 | const [value, setValue] = useState(defaultValue || ""); 20 | 21 | const updateValue = (newValue) => { 22 | updateFormValue({ updateType, value: newValue }); 23 | setValue(newValue); 24 | }; 25 | 26 | return ( 27 |
28 | 38 | 39 | 51 |
52 | ); 53 | } 54 | 55 | export default SelectBox; 56 | -------------------------------------------------------------------------------- /client/src/components/Input/SelectBoxBig.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function SelectBoxBig(props) { 4 | const { options } = props; 5 | return ( 6 |
7 | 8 | 22 |
23 | ); 24 | } 25 | 26 | export default SelectBoxBig; 27 | -------------------------------------------------------------------------------- /client/src/components/Input/SelectBoxSmall.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function SelectBoxSmall(props) { 4 | const { options } = props; 5 | return ( 6 |
7 | 23 |
24 | ); 25 | } 26 | 27 | export default SelectBoxSmall; 28 | -------------------------------------------------------------------------------- /client/src/components/Input/StanSearchBar.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import React, { useEffect } from 'react' 4 | 5 | function StanSearchBar({searchText, styleClass, placeholderText, setSearchText}) { 6 | 7 | 8 | 9 | const updateSearchInput = (value) => { 10 | setSearchText(value) 11 | } 12 | 13 | return ( 14 |
15 |
16 | updateSearchInput(e.target.value)} className="input input-sm input-bordered w-full max-w-xs" /> 17 |
18 |
19 | ) 20 | } 21 | 22 | export default StanSearchBar 23 | -------------------------------------------------------------------------------- /client/src/components/Input/TextAreaInput.js: -------------------------------------------------------------------------------- 1 | 2 | function TextAreaInput({ labelTitle, name, value, onChange, placeholder, containerStyle, labelStyle, className }) { 3 | return ( 4 |
5 | 8 | 15 |
16 | ); 17 | } 18 | 19 | export default TextAreaInput; 20 | -------------------------------------------------------------------------------- /client/src/components/Input/ToogleInput.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | function ToogleInput({ 4 | labelTitle, 5 | labelStyle, 6 | type, 7 | containerStyle, 8 | defaultValue, 9 | placeholder, 10 | updateFormValue, 11 | updateType, 12 | }) { 13 | const [value, setValue] = useState(defaultValue); 14 | 15 | const updateToogleValue = () => { 16 | setValue(!value); 17 | updateFormValue({ updateType, value: !value }); 18 | }; 19 | 20 | return ( 21 |
22 | 26 |
27 | ); 28 | } 29 | 30 | export default ToogleInput; 31 | -------------------------------------------------------------------------------- /client/src/components/Typography/ErrorText.jsx: -------------------------------------------------------------------------------- 1 | function ErrorText({styleClass, children}){ 2 | return( 3 |

{children}

4 | ) 5 | } 6 | 7 | export default ErrorText -------------------------------------------------------------------------------- /client/src/components/Typography/HelperText.jsx: -------------------------------------------------------------------------------- 1 | function HelperText({className, children}){ 2 | return( 3 |
{children}
4 | ) 5 | } 6 | 7 | export default HelperText -------------------------------------------------------------------------------- /client/src/components/Typography/Subtitle.jsx: -------------------------------------------------------------------------------- 1 | function Subtitle({ styleClass, children, onClick }) { 2 | return
{children}
; 3 | } 4 | 5 | export default Subtitle; 6 | -------------------------------------------------------------------------------- /client/src/components/Typography/Title.jsx: -------------------------------------------------------------------------------- 1 | function Title({className, children}){ 2 | return( 3 |

{children}

4 | ) 5 | } 6 | 7 | export default Title -------------------------------------------------------------------------------- /client/src/components/containers/Layout.jsx: -------------------------------------------------------------------------------- 1 | import PageContent from "./PageContent"; 2 | import LeftSidebar from "./LeftSidebar"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import RightSidebar from "./RightSidebar"; 5 | import { useEffect } from "react"; 6 | import { removeNotificationMessage } from "../features/common/headerSlice"; 7 | import { 8 | NotificationContainer, 9 | NotificationManager, 10 | } from "react-notifications"; 11 | import "react-notifications/lib/notifications.css"; 12 | import ModalLayout from "./ModalLayout"; 13 | // import io from "socket.io-client"; 14 | 15 | export const socketEmit = (type, data) => socket.emit(type, { data }); 16 | 17 | var socket; 18 | 19 | function Layout() { 20 | // const { user } = useSelector((state) => state.auth); 21 | const dispatch = useDispatch(); 22 | const { newNotificationMessage, newNotificationStatus } = useSelector( 23 | (state) => state.header 24 | ); 25 | 26 | // useEffect(() => { 27 | // socket = io(process.env.REACT_APP_BASE_URL); 28 | // socket.on("connect", function () { 29 | // socket.emit("id", { userId: user.username }); 30 | // socket.on("like", function (data) { 31 | // NotificationManager.success(data.msg, "Success"); 32 | // }); 33 | // socket.on("comment", function (data) { 34 | // NotificationManager.success(data.msg, "Success"); 35 | // }); 36 | // }); 37 | // }, [user.username]); 38 | 39 | useEffect(() => { 40 | if (newNotificationMessage !== "") { 41 | if (newNotificationStatus === 1) 42 | NotificationManager.success(newNotificationMessage, "Success"); 43 | if (newNotificationStatus === 0) 44 | NotificationManager.error(newNotificationMessage, "Error"); 45 | dispatch(removeNotificationMessage()); 46 | } 47 | }, [dispatch, newNotificationMessage, newNotificationStatus]); 48 | 49 | return ( 50 | <> 51 | {/* Left drawer - containing page content and side bar (always open) */} 52 |
53 | 58 | 59 | 60 |
61 | 62 | {/* Right drawer - containing secondary content like notifications list etc.. */} 63 | 64 | 65 | {/** Notification layout container */} 66 | 67 | 68 | {/* Modal layout container */} 69 | 70 | 71 | ); 72 | } 73 | 74 | export default Layout; 75 | -------------------------------------------------------------------------------- /client/src/components/containers/LeftSidebar.jsx: -------------------------------------------------------------------------------- 1 | import { adminRoutes, userRoutes } from "../../routes/sidebar"; 2 | import { NavLink, Routes, Link, useLocation } from "react-router-dom"; 3 | import SidebarSubmenu from "./SidebarSubmenu"; 4 | import XMarkIcon from "@heroicons/react/24/outline/XMarkIcon"; 5 | import { useDispatch } from "react-redux"; 6 | 7 | function LeftSidebar() { 8 | const location = useLocation(); 9 | 10 | const dispatch = useDispatch(); 11 | const user = JSON.parse(localStorage.getItem("user")); 12 | const close = (e) => { 13 | document.getElementById("left-sidebar-drawer").click(); 14 | }; 15 | 16 | return ( 17 |
18 | 19 |
    20 | 26 |
  • 27 | 28 | DashWind Logo 29 | Blog Website 30 | 31 |
  • 32 | {user.role === "admin" 33 | ? adminRoutes.map((route, k) => { 34 | return ( 35 |
  • 36 | {route.submenu ? ( 37 | 38 | ) : ( 39 | `${isActive ? "font-semibold bg-base-200 " : "font-normal"}`} 43 | > 44 | {route.icon} {route.name} 45 | {location.pathname === route.path ? ( 46 | 50 | ) : null} 51 | 52 | )} 53 |
  • 54 | ); 55 | }) 56 | : userRoutes.map((route, k) => { 57 | return ( 58 |
  • 59 | {route.submenu ? ( 60 | 61 | ) : ( 62 | `${isActive ? "font-semibold bg-base-200 " : "font-normal"}`} 66 | > 67 | {route.icon} {route.name} 68 | {location.pathname === route.path ? ( 69 | 73 | ) : null} 74 | 75 | )} 76 |
  • 77 | ); 78 | })} 79 |
80 |
81 | ); 82 | } 83 | 84 | export default LeftSidebar; 85 | -------------------------------------------------------------------------------- /client/src/components/containers/ModalLayout.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { MODAL_BODY_TYPES } from "../../utils/globalConstantUtil"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { closeModal } from "../features/common/modalSlice"; 5 | import AddLeadModalBody from "../features/leads/components/AddLeadModalBody"; 6 | import ConfirmationModalBody from "../features/common/components/ConfirmationModalBody"; 7 | 8 | function ModalLayout() { 9 | const { isOpen, bodyType, size, extraObject, title } = useSelector( 10 | (state) => state.modal 11 | ); 12 | const dispatch = useDispatch(); 13 | 14 | const close = (e) => { 15 | dispatch(closeModal(e)); 16 | }; 17 | 18 | return ( 19 | <> 20 | {/* The button to open modal */} 21 | 22 | {/* Put this part before tag */} 23 |
24 |
25 | 31 |

{title}

32 | 33 | {/* Loading modal body according to different modal type */} 34 | { 35 | { 36 | [MODAL_BODY_TYPES.LEAD_ADD_NEW]: ( 37 | 41 | ), 42 | [MODAL_BODY_TYPES.CONFIRMATION]: ( 43 | 47 | ), 48 | [MODAL_BODY_TYPES.DEFAULT]:
, 49 | }[bodyType] 50 | } 51 |
52 |
53 | 54 | ); 55 | } 56 | 57 | export default ModalLayout; 58 | -------------------------------------------------------------------------------- /client/src/components/containers/PageContent.jsx: -------------------------------------------------------------------------------- 1 | import Header from "./Header"; 2 | import { Route, Routes } from "react-router-dom"; 3 | import routes from "../../routes"; 4 | import { Suspense, lazy } from "react"; 5 | import SuspenseContent from "./SuspenseContent.jsx"; 6 | import { useSelector } from "react-redux"; 7 | import { useEffect, useRef } from "react"; 8 | 9 | const Page404 = lazy(() => import("../../pages/protected/404")); 10 | 11 | function PageContent() { 12 | const mainContentRef = useRef(null); 13 | const { pageTitle } = useSelector((state) => state.header); 14 | 15 | // Scroll back to top on new page load 16 | useEffect(() => { 17 | mainContentRef.current.scroll({ 18 | top: 0, 19 | behavior: "smooth", 20 | }); 21 | }, [pageTitle]); 22 | 23 | return ( 24 |
25 |
26 |
30 | }> 31 | 32 | {routes.map((route, key) => { 33 | return ( 34 | } 39 | /> 40 | ); 41 | })} 42 | {/* Redirecting unknown url to 404 page */} 43 | } /> 44 | 45 | 46 |
47 |
48 |
49 | ); 50 | } 51 | 52 | export default PageContent; 53 | -------------------------------------------------------------------------------- /client/src/components/containers/RightSidebar.jsx: -------------------------------------------------------------------------------- 1 | import XMarkIcon from "@heroicons/react/24/solid/XMarkIcon"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import NotificationBodyRightDrawer from "../features/common/components/NotificationBodyRightDrawer"; 4 | import { closeRightDrawer } from "../features/common/rightDrawerSlice"; 5 | import { RIGHT_DRAWER_TYPES } from "../../utils/globalConstantUtil"; 6 | 7 | function RightSidebar() { 8 | const { isOpen, bodyType, extraObject, header } = useSelector( 9 | (state) => state.rightDrawer 10 | ); 11 | const dispatch = useDispatch(); 12 | 13 | const close = (e) => { 14 | dispatch(closeRightDrawer(e)); 15 | }; 16 | 17 | return ( 18 |
26 |
32 |
33 | {/* Header */} 34 |
35 | 41 | {header} 42 |
43 | 44 | {/* ------------------ Content Start ------------------ */} 45 |
46 |
47 | {/* Loading drawer body according to different drawer type */} 48 | { 49 | { 50 | [RIGHT_DRAWER_TYPES.NOTIFICATION]: ( 51 | 52 | ), 53 | [RIGHT_DRAWER_TYPES.DEFAULT]:
, 54 | }[bodyType] 55 | } 56 |
57 |
58 | {/* ------------------ Content End ------------------ */} 59 |
60 |
61 | 62 |
close()} 65 | >
66 |
67 | ); 68 | } 69 | 70 | export default RightSidebar; 71 | -------------------------------------------------------------------------------- /client/src/components/containers/SidebarSubmenu.jsx: -------------------------------------------------------------------------------- 1 | import ChevronDownIcon from "@heroicons/react/24/outline/ChevronDownIcon"; 2 | import { useEffect, useState } from "react"; 3 | import { Link, useLocation } from "react-router-dom"; 4 | 5 | function SidebarSubmenu({ submenu, name, icon }) { 6 | const location = useLocation(); 7 | const [isExpanded, setIsExpanded] = useState(false); 8 | /** Open Submenu list if path found in routes, this is for directly loading submenu routes first time */ 9 | useEffect(() => { 10 | if ( 11 | submenu.filter((m) => { 12 | return m.path === location.pathname; 13 | })[0] 14 | ) 15 | setIsExpanded(true); 16 | }, []); 17 | 18 | return ( 19 |
20 | {/** Route header */} 21 |
setIsExpanded(!isExpanded)}> 22 | {icon} {name} 23 | 29 |
30 | {/** Submenu list */} 31 |
32 |
    33 | {submenu.map((m, k) => { 34 | return ( 35 |
  • 36 | 37 | {m.icon} {m.name} 38 | {location.pathname === m.path ? ( 39 | 43 | ) : null} 44 | 45 |
  • 46 | ); 47 | })} 48 |
49 |
50 |
51 | ); 52 | } 53 | 54 | export default SidebarSubmenu; 55 | -------------------------------------------------------------------------------- /client/src/components/containers/SuspenseContent.jsx: -------------------------------------------------------------------------------- 1 | function SuspenseContent() { 2 | return ( 3 |
4 | Loading... 5 |
6 | ); 7 | } 8 | 9 | export default SuspenseContent; 10 | -------------------------------------------------------------------------------- /client/src/components/features/charts/components/BarChart.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | CategoryScale, 4 | LinearScale, 5 | BarElement, 6 | Title, 7 | Tooltip, 8 | Legend, 9 | } from "chart.js"; 10 | import { Bar } from "react-chartjs-2"; 11 | import TitleCard from "../../../Cards/TitleCard"; 12 | 13 | ChartJS.register( 14 | CategoryScale, 15 | LinearScale, 16 | BarElement, 17 | Title, 18 | Tooltip, 19 | Legend 20 | ); 21 | 22 | function BarChart() { 23 | const options = { 24 | responsive: true, 25 | plugins: { 26 | legend: { 27 | position: "top", 28 | }, 29 | }, 30 | }; 31 | 32 | const labels = [ 33 | "January", 34 | "February", 35 | "March", 36 | "April", 37 | "May", 38 | "June", 39 | "July", 40 | ]; 41 | 42 | const data = { 43 | labels, 44 | datasets: [ 45 | { 46 | label: "Store 1", 47 | data: labels.map(() => { 48 | return Math.random() * 1000 + 500; 49 | }), 50 | backgroundColor: "rgba(255, 99, 132, 1)", 51 | }, 52 | { 53 | label: "Store 2", 54 | data: labels.map(() => { 55 | return Math.random() * 1000 + 500; 56 | }), 57 | backgroundColor: "rgba(53, 162, 235, 1)", 58 | }, 59 | ], 60 | }; 61 | 62 | return ( 63 | 64 | 65 | 66 | ); 67 | } 68 | 69 | export default BarChart; 70 | -------------------------------------------------------------------------------- /client/src/components/features/charts/components/DoughnutChart.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | Filler, 4 | ArcElement, 5 | Title, 6 | Tooltip, 7 | Legend, 8 | } from "chart.js"; 9 | import { Doughnut } from "react-chartjs-2"; 10 | import TitleCard from "../../../Cards/TitleCard"; 11 | import Subtitle from "../../../Typography/Subtitle"; 12 | 13 | ChartJS.register(ArcElement, Tooltip, Legend, Tooltip, Filler, Legend); 14 | 15 | function DoughnutChart() { 16 | const options = { 17 | responsive: true, 18 | plugins: { 19 | legend: { 20 | position: "top", 21 | }, 22 | }, 23 | }; 24 | 25 | const labels = [ 26 | "Electronics", 27 | "Home Applicances", 28 | "Beauty", 29 | "Furniture", 30 | "Watches", 31 | "Apparel", 32 | ]; 33 | 34 | const data = { 35 | labels, 36 | datasets: [ 37 | { 38 | label: "# of Orders", 39 | data: [122, 219, 30, 51, 82, 13], 40 | backgroundColor: [ 41 | "rgba(255, 99, 132, 0.8)", 42 | "rgba(54, 162, 235, 0.8)", 43 | "rgba(255, 206, 86, 0.8)", 44 | "rgba(75, 192, 192, 0.8)", 45 | "rgba(153, 102, 255, 0.8)", 46 | "rgba(255, 159, 64, 0.8)", 47 | ], 48 | borderColor: [ 49 | "rgba(255, 99, 132, 1)", 50 | "rgba(54, 162, 235, 1)", 51 | "rgba(255, 206, 86, 1)", 52 | "rgba(75, 192, 192, 1)", 53 | "rgba(153, 102, 255, 1)", 54 | "rgba(255, 159, 64, 1)", 55 | ], 56 | borderWidth: 1, 57 | }, 58 | ], 59 | }; 60 | 61 | return ( 62 | 63 | 64 | 65 | ); 66 | } 67 | 68 | export default DoughnutChart; 69 | -------------------------------------------------------------------------------- /client/src/components/features/charts/components/LineChart.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | CategoryScale, 4 | LinearScale, 5 | PointElement, 6 | LineElement, 7 | Title, 8 | Tooltip, 9 | Filler, 10 | Legend, 11 | } from 'chart.js'; 12 | import { Line } from 'react-chartjs-2'; 13 | import TitleCard from '../../../Cards/TitleCard'; 14 | 15 | ChartJS.register( 16 | CategoryScale, 17 | LinearScale, 18 | PointElement, 19 | LineElement, 20 | Title, 21 | Tooltip, 22 | Filler, 23 | Legend 24 | ); 25 | 26 | function LineChart(){ 27 | 28 | const options = { 29 | responsive: true, 30 | plugins: { 31 | legend: { 32 | position: 'top', 33 | }, 34 | }, 35 | }; 36 | 37 | 38 | const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; 39 | 40 | const data = { 41 | labels, 42 | datasets: [ 43 | { 44 | fill: true, 45 | label: 'MAU', 46 | data: labels.map(() => { return Math.random() * 100 + 500 }), 47 | borderColor: 'rgb(53, 162, 235)', 48 | backgroundColor: 'rgba(53, 162, 235, 0.5)', 49 | }, 50 | ], 51 | }; 52 | 53 | 54 | return( 55 | 56 | 57 | 58 | ) 59 | } 60 | 61 | 62 | export default LineChart -------------------------------------------------------------------------------- /client/src/components/features/charts/components/PieChart.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | Filler, 4 | ArcElement, 5 | Title, 6 | Tooltip, 7 | Legend, 8 | } from "chart.js"; 9 | import { Pie } from "react-chartjs-2"; 10 | import TitleCard from "../../../Cards/TitleCard"; 11 | import Subtitle from "../../../Typography/Subtitle"; 12 | 13 | ChartJS.register(ArcElement, Tooltip, Legend, Tooltip, Filler, Legend); 14 | 15 | function PieChart() { 16 | const options = { 17 | responsive: true, 18 | plugins: { 19 | legend: { 20 | position: "top", 21 | }, 22 | }, 23 | }; 24 | 25 | const labels = [ 26 | "India", 27 | "Middle East", 28 | "Europe", 29 | "US", 30 | "Latin America", 31 | "Asia(non-india)", 32 | ]; 33 | 34 | const data = { 35 | labels, 36 | datasets: [ 37 | { 38 | label: "# of Orders", 39 | data: [122, 219, 30, 51, 82, 13], 40 | backgroundColor: [ 41 | "rgba(255, 99, 255, 0.8)", 42 | "rgba(54, 162, 235, 0.8)", 43 | "rgba(255, 206, 255, 0.8)", 44 | "rgba(75, 192, 255, 0.8)", 45 | "rgba(153, 102, 255, 0.8)", 46 | "rgba(255, 159, 255, 0.8)", 47 | ], 48 | borderColor: [ 49 | "rgba(255, 99, 255, 1)", 50 | "rgba(54, 162, 235, 1)", 51 | "rgba(255, 206, 255, 1)", 52 | "rgba(75, 192, 255, 1)", 53 | "rgba(153, 102, 255, 1)", 54 | "rgba(255, 159, 255, 1)", 55 | ], 56 | borderWidth: 1, 57 | }, 58 | ], 59 | }; 60 | 61 | return ( 62 | 63 | 64 | 65 | ); 66 | } 67 | 68 | export default PieChart; 69 | -------------------------------------------------------------------------------- /client/src/components/features/charts/components/ScatterChart.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | Filler, 4 | ArcElement, 5 | Tooltip, 6 | Legend, 7 | } from "chart.js"; 8 | import { Scatter } from "react-chartjs-2"; 9 | import TitleCard from "../../../Cards/TitleCard"; 10 | 11 | ChartJS.register(ArcElement, Tooltip, Legend, Tooltip, Filler, Legend); 12 | 13 | function ScatterChart() { 14 | const options = { 15 | scales: { 16 | y: { 17 | beginAtZero: true, 18 | }, 19 | }, 20 | }; 21 | 22 | const data = { 23 | datasets: [ 24 | { 25 | label: "Orders > 1k", 26 | data: Array.from({ length: 100 }, () => ({ 27 | x: Math.random() * 11, 28 | y: Math.random() * 31, 29 | })), 30 | backgroundColor: "rgba(255, 99, 132, 1)", 31 | }, 32 | { 33 | label: "Orders > 2K", 34 | data: Array.from({ length: 100 }, () => ({ 35 | x: Math.random() * 12, 36 | y: Math.random() * 12, 37 | })), 38 | backgroundColor: "rgba(0, 0, 255, 1)", 39 | }, 40 | ], 41 | }; 42 | 43 | return ( 44 | 45 | 46 | 47 | ); 48 | } 49 | 50 | export default ScatterChart; 51 | -------------------------------------------------------------------------------- /client/src/components/features/charts/components/StackBarChart.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | CategoryScale, 4 | LinearScale, 5 | BarElement, 6 | Title, 7 | Tooltip, 8 | Legend, 9 | } from "chart.js"; 10 | import { Bar } from "react-chartjs-2"; 11 | import TitleCard from "../../../Cards/TitleCard"; 12 | 13 | ChartJS.register( 14 | CategoryScale, 15 | LinearScale, 16 | BarElement, 17 | Title, 18 | Tooltip, 19 | Legend 20 | ); 21 | 22 | function StackBarChart() { 23 | const options = { 24 | responsive: true, 25 | scales: { 26 | x: { 27 | stacked: true, 28 | }, 29 | y: { 30 | stacked: true, 31 | }, 32 | }, 33 | }; 34 | 35 | const labels = [ 36 | "January", 37 | "February", 38 | "March", 39 | "April", 40 | "May", 41 | "June", 42 | "July", 43 | ]; 44 | 45 | const data = { 46 | labels, 47 | datasets: [ 48 | { 49 | label: "Store 1", 50 | data: labels.map(() => { 51 | return Math.random() * 1000 + 500; 52 | }), 53 | backgroundColor: "rgba(255, 99, 132, 1)", 54 | }, 55 | { 56 | label: "Store 2", 57 | data: labels.map(() => { 58 | return Math.random() * 1000 + 500; 59 | }), 60 | backgroundColor: "rgba(53, 162, 235, 1)", 61 | }, 62 | { 63 | label: "Store 3", 64 | data: labels.map(() => { 65 | return Math.random() * 1000 + 500; 66 | }), 67 | backgroundColor: "rgba(235, 162, 235, 1)", 68 | }, 69 | ], 70 | }; 71 | 72 | return ( 73 | 74 | 75 | 76 | ); 77 | } 78 | 79 | export default StackBarChart; 80 | -------------------------------------------------------------------------------- /client/src/components/features/charts/index.jsx: -------------------------------------------------------------------------------- 1 | import LineChart from "./components/LineChart"; 2 | import BarChart from "./components/BarChart"; 3 | import DoughnutChart from "./components/DoughnutChart"; 4 | import PieChart from "./components/PieChart"; 5 | import ScatterChart from "./components/ScatterChart"; 6 | import StackBarChart from "./components/StackBarChart"; 7 | import Datepicker from "react-tailwindcss-datepicker"; 8 | import { useState } from "react"; 9 | 10 | function Charts() { 11 | const [dateValue, setDateValue] = useState({ 12 | startDate: new Date(), 13 | endDate: new Date(), 14 | }); 15 | 16 | const handleDatePickerValueChange = (newValue) => { 17 | console.log("newValue:", newValue); 18 | setDateValue(newValue); 19 | }; 20 | 21 | return ( 22 | <> 23 | 34 | {/** ---------------------- Different charts ------------------------- */} 35 |
36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 |
44 | 45 |
46 | 47 | 48 |
49 | 50 | ); 51 | } 52 | 53 | export default Charts; 54 | -------------------------------------------------------------------------------- /client/src/components/features/common/components/ConfirmationModalBody.js: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from "react-redux"; 2 | import axios from "axios"; 3 | import { CONFIRMATION_MODAL_CLOSE_TYPES, MODAL_CLOSE_TYPES } from "../../../../utils/globalConstantUtil"; 4 | import { deleteLead } from "../../leads/leadSlice"; 5 | import { showNotification } from "../headerSlice"; 6 | 7 | function ConfirmationModalBody({ extraObject, closeModal }) { 8 | const dispatch = useDispatch(); 9 | 10 | const { message, type, _id, index } = extraObject; 11 | 12 | const proceedWithYes = async () => { 13 | if (type === CONFIRMATION_MODAL_CLOSE_TYPES.LEAD_DELETE) { 14 | // positive response, call api or dispatch redux function 15 | dispatch(deleteLead({ index })); 16 | dispatch(showNotification({ message: "Lead Deleted!", status: 1 })); 17 | } 18 | closeModal(); 19 | }; 20 | 21 | return ( 22 | <> 23 |

{message}

24 | 25 |
26 | 29 | 30 | 33 |
34 | 35 | ); 36 | } 37 | 38 | export default ConfirmationModalBody; 39 | -------------------------------------------------------------------------------- /client/src/components/features/common/components/NotificationBodyRightDrawer.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | 3 | function NotificationBodyRightDrawer() { 4 | const state = useSelector((state) => state.article); 5 | console.log(state); 6 | return ( 7 | <> 8 | {[""].map((v, i) => { 9 | return ( 10 |
11 | {v} 12 |
13 | ); 14 | })} 15 | 16 | ); 17 | } 18 | 19 | export default NotificationBodyRightDrawer; 20 | -------------------------------------------------------------------------------- /client/src/components/features/common/headerSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit' 2 | 3 | export const headerSlice = createSlice({ 4 | name: 'header', 5 | initialState: { 6 | pageTitle: "Home", // current page title state management 7 | noOfNotifications : 5, // no of unread notifications 8 | newNotificationMessage : "", // message of notification to be shown 9 | newNotificationStatus : 1, // to check the notification type - success/ error/ info 10 | }, 11 | reducers: { 12 | setPageTitle: (state, action) => { 13 | state.pageTitle = action.payload.title 14 | }, 15 | removeNotificationMessage: (state, action) => { 16 | state.newNotificationMessage = "" 17 | }, 18 | 19 | showNotification: (state, action) => { 20 | state.newNotificationMessage = action.payload.message 21 | state.newNotificationStatus = action.payload.status 22 | }, 23 | } 24 | }) 25 | 26 | export const { setPageTitle, removeNotificationMessage, showNotification } = headerSlice.actions 27 | 28 | export default headerSlice.reducer -------------------------------------------------------------------------------- /client/src/components/features/common/modalSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | export const modalSlice = createSlice({ 4 | name: "modal", 5 | initialState: { 6 | title: "", // current title state management 7 | isOpen: false, // modal state management for opening closing 8 | bodyType: "", // modal content management 9 | size: "", // modal content management 10 | extraObject: {}, 11 | }, 12 | reducers: { 13 | openModal: (state, action) => { 14 | const { title, bodyType, extraObject, size } = action.payload; 15 | state.isOpen = true; 16 | state.bodyType = bodyType; 17 | state.title = title; 18 | state.size = size || "md"; 19 | state.extraObject = extraObject; 20 | }, 21 | 22 | closeModal: (state, action) => { 23 | state.isOpen = false; 24 | state.bodyType = ""; 25 | state.title = ""; 26 | state.extraObject = {}; 27 | }, 28 | }, 29 | }); 30 | 31 | export const { openModal, closeModal } = modalSlice.actions; 32 | 33 | export default modalSlice.reducer; 34 | -------------------------------------------------------------------------------- /client/src/components/features/common/rightDrawerSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | export const rightDrawerSlice = createSlice({ 4 | name: "rightDrawer", 5 | initialState: { 6 | header: "", // current title state management 7 | isOpen: false, // right drawer state management for opening closing 8 | bodyType: "", // right drawer content management 9 | extraObject: {}, 10 | }, 11 | reducers: { 12 | openRightDrawer: (state, action) => { 13 | const { header, bodyType, extraObject } = action.payload; 14 | state.isOpen = true; 15 | state.bodyType = bodyType; 16 | state.header = header; 17 | state.extraObject = extraObject; 18 | }, 19 | 20 | closeRightDrawer: (state, action) => { 21 | state.isOpen = false; 22 | state.bodyType = ""; 23 | state.header = ""; 24 | state.extraObject = {}; 25 | }, 26 | }, 27 | }); 28 | 29 | export const { openRightDrawer, closeRightDrawer } = rightDrawerSlice.actions; 30 | 31 | export default rightDrawerSlice.reducer; 32 | -------------------------------------------------------------------------------- /client/src/components/features/dashboard/components/AmountStats.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function AmountStats() { 4 | return ( 5 |
6 |
7 |
Amount to be Collected
8 |
$25,600
9 |
10 | 11 |
12 |
13 |
14 |
Cash in hand
15 |
$5,600
16 |
17 | 18 |
19 |
20 |
21 | ) 22 | } 23 | 24 | export default AmountStats -------------------------------------------------------------------------------- /client/src/components/features/dashboard/components/ArticleCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Subtitle from "../../../Typography/Subtitle"; 3 | import { AiOutlineCalendar } from "react-icons/ai"; 4 | import UserIcon from "@heroicons/react/24/outline/UserIcon"; 5 | import moment from "moment"; 6 | import { Button } from "../../../General/Button"; 7 | import XMarkIcon from "@heroicons/react/24/solid/XMarkIcon"; 8 | import { BiSolidEditAlt } from "react-icons/bi"; 9 | import { Link } from "react-router-dom"; 10 | const ArticleCard = (prop) => { 11 | // const user = JSON.parse(localStorage.getItem("user")); 12 | return ( 13 |
14 |
19 |
20 | 21 |
22 | avatar 27 |
{prop.favouriteNum}
28 |
29 | 30 |
31 |
32 | 36 | {prop.title} 37 | 38 |
39 | 40 | 41 | {prop.date ? moment(prop.date).format("YYYY-MM-DD") : ""} 42 | 43 | 44 | 45 | 46 | {prop.from} 47 | 48 | 49 |
50 |
51 |
52 |
{prop.content}
53 |
54 |
55 |
56 | {prop.type !== "draft" ? ( 57 | <> 58 |
85 |
86 |
87 | ); 88 | }; 89 | 90 | export default ArticleCard; 91 | -------------------------------------------------------------------------------- /client/src/components/features/dashboard/components/BarChart.js: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | CategoryScale, 4 | LinearScale, 5 | BarElement, 6 | Title, 7 | Tooltip, 8 | Legend, 9 | } from "chart.js"; 10 | import { Bar } from "react-chartjs-2"; 11 | import TitleCard from "../../../Cards/TitleCard"; 12 | 13 | ChartJS.register( 14 | CategoryScale, 15 | LinearScale, 16 | BarElement, 17 | Title, 18 | Tooltip, 19 | Legend 20 | ); 21 | 22 | function BarChart() { 23 | const options = { 24 | responsive: true, 25 | plugins: { 26 | legend: { 27 | position: "top", 28 | }, 29 | }, 30 | }; 31 | 32 | const labels = [ 33 | "January", 34 | "February", 35 | "March", 36 | "April", 37 | "May", 38 | "June", 39 | "July", 40 | ]; 41 | 42 | const data = { 43 | labels, 44 | datasets: [ 45 | { 46 | label: "Store 1", 47 | data: labels.map(() => { 48 | return Math.random() * 1000 + 500; 49 | }), 50 | backgroundColor: "rgba(255, 99, 132, 1)", 51 | }, 52 | { 53 | label: "Store 2", 54 | data: labels.map(() => { 55 | return Math.random() * 1000 + 500; 56 | }), 57 | backgroundColor: "rgba(53, 162, 235, 1)", 58 | }, 59 | ], 60 | }; 61 | 62 | return ( 63 | 64 | 65 | 66 | ); 67 | } 68 | 69 | export default BarChart; 70 | -------------------------------------------------------------------------------- /client/src/components/features/dashboard/components/DashboardStats.js: -------------------------------------------------------------------------------- 1 | function DashboardStats({title, icon, value, description, colorIndex}){ 2 | 3 | const COLORS = ["primary", "primary"] 4 | 5 | const getDescStyle = () => { 6 | if(description.includes("↗︎"))return "font-bold text-green-700 dark:text-green-300" 7 | else if(description.includes("↙"))return "font-bold text-rose-500 dark:text-red-400" 8 | else return "" 9 | } 10 | 11 | return( 12 |
13 |
14 |
{icon}
15 |
{title}
16 |
{value}
17 |
{description}
18 |
19 |
20 | ) 21 | } 22 | 23 | export default DashboardStats -------------------------------------------------------------------------------- /client/src/components/features/dashboard/components/DashboardTopBar.js: -------------------------------------------------------------------------------- 1 | // import SelectBox from "../../../components/Input/SelectBox" 2 | import ArrowDownTrayIcon from '@heroicons/react/24/outline/ArrowDownTrayIcon' 3 | import ShareIcon from '@heroicons/react/24/outline/ShareIcon' 4 | import EnvelopeIcon from '@heroicons/react/24/outline/EnvelopeIcon' 5 | import EllipsisVerticalIcon from '@heroicons/react/24/outline/EllipsisVerticalIcon' 6 | import ArrowPathIcon from '@heroicons/react/24/outline/ArrowPathIcon' 7 | import { useState } from "react" 8 | import Datepicker from "react-tailwindcss-datepicker"; 9 | 10 | 11 | 12 | // const periodOptions = [ 13 | // { name: "Today", value: "TODAY" }, 14 | // { name: "Yesterday", value: "YESTERDAY" }, 15 | // { name: "This Week", value: "THIS_WEEK" }, 16 | // { name: "Last Week", value: "LAST_WEEK" }, 17 | // { name: "This Month", value: "THIS_MONTH" }, 18 | // { name: "Last Month", value: "LAST_MONTH" }, 19 | // ] 20 | 21 | function DashboardTopBar({ updateDashboardPeriod }) { 22 | 23 | const [dateValue, setDateValue] = useState({ 24 | startDate: new Date(), 25 | endDate: new Date() 26 | }); 27 | 28 | const handleDatePickerValueChange = (newValue) => { 29 | console.log("newValue:", newValue); 30 | setDateValue(newValue); 31 | updateDashboardPeriod(newValue) 32 | } 33 | 34 | return ( 35 |
36 |
37 | 48 |
49 |
50 | 51 | 52 |
53 | 54 |
    55 |
  • Email Digests
  • 56 |
  • Download
  • 57 |
58 |
59 |
60 |
61 | ) 62 | } 63 | 64 | export default DashboardTopBar -------------------------------------------------------------------------------- /client/src/components/features/dashboard/components/DoughnutChart.js: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | Filler, 4 | ArcElement, 5 | Title, 6 | Tooltip, 7 | Legend, 8 | } from "chart.js"; 9 | import { Doughnut } from "react-chartjs-2"; 10 | import TitleCard from "../../../Cards/TitleCard"; 11 | import Subtitle from "../../../components/Typography/Subtitle"; 12 | 13 | ChartJS.register(ArcElement, Tooltip, Legend, Tooltip, Filler, Legend); 14 | 15 | function DoughnutChart() { 16 | const options = { 17 | responsive: true, 18 | plugins: { 19 | legend: { 20 | position: "top", 21 | }, 22 | }, 23 | }; 24 | 25 | const labels = [ 26 | "Electronics", 27 | "Home Applicances", 28 | "Beauty", 29 | "Furniture", 30 | "Watches", 31 | "Apparel", 32 | ]; 33 | 34 | const data = { 35 | labels, 36 | datasets: [ 37 | { 38 | label: "# of Orders", 39 | data: [122, 219, 30, 51, 82, 13], 40 | backgroundColor: [ 41 | "rgba(255, 99, 132, 0.8)", 42 | "rgba(54, 162, 235, 0.8)", 43 | "rgba(255, 206, 86, 0.8)", 44 | "rgba(75, 192, 192, 0.8)", 45 | "rgba(153, 102, 255, 0.8)", 46 | "rgba(255, 159, 64, 0.8)", 47 | ], 48 | borderColor: [ 49 | "rgba(255, 99, 132, 1)", 50 | "rgba(54, 162, 235, 1)", 51 | "rgba(255, 206, 86, 1)", 52 | "rgba(75, 192, 192, 1)", 53 | "rgba(153, 102, 255, 1)", 54 | "rgba(255, 159, 64, 1)", 55 | ], 56 | borderWidth: 1, 57 | }, 58 | ], 59 | }; 60 | 61 | return ( 62 | 63 | 64 | 65 | ); 66 | } 67 | 68 | export default DoughnutChart; 69 | -------------------------------------------------------------------------------- /client/src/components/features/dashboard/components/LineChart.js: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | CategoryScale, 4 | LinearScale, 5 | PointElement, 6 | LineElement, 7 | Title, 8 | Tooltip, 9 | Filler, 10 | Legend, 11 | } from "chart.js"; 12 | import { Line } from "react-chartjs-2"; 13 | import TitleCard from "../../../Cards/TitleCard"; 14 | 15 | ChartJS.register( 16 | CategoryScale, 17 | LinearScale, 18 | PointElement, 19 | LineElement, 20 | Title, 21 | Tooltip, 22 | Filler, 23 | Legend 24 | ); 25 | 26 | function LineChart() { 27 | const options = { 28 | responsive: true, 29 | plugins: { 30 | legend: { 31 | position: "top", 32 | }, 33 | }, 34 | }; 35 | 36 | const labels = [ 37 | "January", 38 | "February", 39 | "March", 40 | "April", 41 | "May", 42 | "June", 43 | "July", 44 | ]; 45 | 46 | const data = { 47 | labels, 48 | datasets: [ 49 | { 50 | fill: true, 51 | label: "MAU", 52 | data: labels.map(() => { 53 | return Math.random() * 100 + 500; 54 | }), 55 | borderColor: "rgb(53, 162, 235)", 56 | backgroundColor: "rgba(53, 162, 235, 0.5)", 57 | }, 58 | ], 59 | }; 60 | 61 | return ( 62 | 63 | 64 | 65 | ); 66 | } 67 | 68 | export default LineChart; 69 | -------------------------------------------------------------------------------- /client/src/components/features/dashboard/components/PageStats.js: -------------------------------------------------------------------------------- 1 | import HeartIcon from '@heroicons/react/24/outline/HeartIcon' 2 | import BoltIcon from '@heroicons/react/24/outline/BoltIcon' 3 | 4 | 5 | function PageStats({}){ 6 | return( 7 |
8 | 9 |
10 |
11 | 12 |
13 |
Total Likes
14 |
25.6K
15 |
21% more than last month
16 |
17 | 18 |
19 |
20 | 21 |
22 |
Page Views
23 |
2.6M
24 |
14% more than last month
25 |
26 |
27 | ) 28 | } 29 | 30 | export default PageStats -------------------------------------------------------------------------------- /client/src/components/features/dashboard/components/Toolbar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import SearchBar from "../.././../Input/SearchBar"; 3 | import SelectBoxSmall from "../../../Input/SelectBoxSmall"; 4 | import { Button } from "../../../General/Button"; 5 | import { AiOutlinePlus } from "react-icons/ai"; 6 | 7 | const Toolbar = (prop) => { 8 | return ( 9 | <> 10 |
11 |
12 | 13 |
15 | 20 | 25 | 32 |
33 | 34 | ); 35 | }; 36 | 37 | export default Toolbar; 38 | -------------------------------------------------------------------------------- /client/src/components/features/dashboard/components/UserChannels.js: -------------------------------------------------------------------------------- 1 | import TitleCard from "../../../components/Cards/TitleCard"; 2 | 3 | const userSourceData = [ 4 | { source: "Facebook Ads", count: "26,345", conversionPercent: 10.2 }, 5 | { source: "Google Ads", count: "21,341", conversionPercent: 11.7 }, 6 | { source: "Instagram Ads", count: "34,379", conversionPercent: 12.4 }, 7 | { source: "Affiliates", count: "12,359", conversionPercent: 20.9 }, 8 | { source: "Organic", count: "10,345", conversionPercent: 10.3 }, 9 | ]; 10 | 11 | function UserChannels() { 12 | return ( 13 | 14 | {/** Table Data */} 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {userSourceData.map((u, k) => { 27 | return ( 28 | 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | })} 36 | 37 |
SourceNo of UsersConversion
{k + 1}{u.source}{u.count}{`${u.conversionPercent}%`}
38 |
39 |
40 | ); 41 | } 42 | 43 | export default UserChannels; 44 | -------------------------------------------------------------------------------- /client/src/components/features/dashboard/index.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useNavigate, Link } from "react-router-dom"; 4 | import Toolbar from "./components/Toolbar"; 5 | import ArticleCard from "./components/ArticleCard"; 6 | import { 7 | getAArticles, 8 | deleteArticle, 9 | addFavourite, 10 | getHomeArticles, 11 | } from "../../../redux/articleSlice"; 12 | 13 | function Dashboard() { 14 | const dispatch = useDispatch(); 15 | const navigate = useNavigate(); 16 | 17 | const value = useSelector((state) => state.article); 18 | const { user } = useSelector((state) => state.auth); 19 | 20 | useEffect(() => { 21 | dispatch(getHomeArticles(user)); 22 | }, [dispatch, value.isLoading]); 23 | 24 | const setHandleAddArticle = () => { 25 | navigate(`/newArticle/${0}`); 26 | }; 27 | const setHandleCommentArticle = (index) => { 28 | dispatch(getAArticles(index)); 29 | navigate(`/answerArticle/${index}`); 30 | }; 31 | const onFavouriteClick = (index) => { 32 | dispatch(addFavourite({ id: index, from: user._id })); 33 | }; 34 | const setHandleDelete = (index) => { 35 | if (window.confirm("Are you delete this Article?")) { 36 | dispatch(deleteArticle(index)); 37 | } 38 | }; 39 | const setHandleEdit = (index) => { 40 | dispatch(getAArticles(index)); 41 | navigate(`/newArticle/${index}`); 42 | }; 43 | 44 | return ( 45 | <> 46 | {/* */} 47 | {value.articles.map((v, i) => { 48 | return ( 49 |
50 | { 58 | setHandleCommentArticle(v._id); 59 | }} 60 | onFavouriteClick={() => { 61 | onFavouriteClick(v._id); 62 | }} 63 | onDeleteArticle={() => { 64 | setHandleDelete(v._id); 65 | }} 66 | onEditArticle={() => { 67 | setHandleEdit(v._id); 68 | }} 69 | show={false} 70 | /> 71 |
72 | ); 73 | })} 74 | 75 | ); 76 | } 77 | 78 | export default Dashboard; 79 | -------------------------------------------------------------------------------- /client/src/components/features/integration/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import { useDispatch } from "react-redux" 3 | import TitleCard from "../../components/Cards/TitleCard" 4 | import { showNotification } from "../common/headerSlice" 5 | 6 | 7 | const INITIAL_INTEGRATION_LIST = [ 8 | {name : "Slack", icon : "https://cdn-icons-png.flaticon.com/512/2111/2111615.png", isActive : true, description : "Slack is an instant messaging program designed by Slack Technologies and owned by Salesforce."}, 9 | {name : "Facebook", icon : "https://cdn-icons-png.flaticon.com/512/124/124010.png", isActive : false, description : "Meta Platforms, Inc., doing business as Meta and formerly named Facebook, Inc., and TheFacebook."}, 10 | {name : "Linkedin", icon : "https://cdn-icons-png.flaticon.com/512/174/174857.png", isActive : true, description : "LinkedIn is a business and employment-focused social media platform that works through websites and mobile apps."}, 11 | {name : "Google Ads", icon : "https://cdn-icons-png.flaticon.com/512/2301/2301145.png", isActive : false, description : "Google Ads is an online advertising platform developed by Google, where advertisers bid to display brief advertisements, service offerings"}, 12 | {name : "Gmail", icon : "https://cdn-icons-png.flaticon.com/512/5968/5968534.png", isActive : false, description : "Gmail is a free email service provided by Google. As of 2019, it had 1.5 billion active users worldwide."}, 13 | {name : "Salesforce", icon : "https://cdn-icons-png.flaticon.com/512/5968/5968880.png", isActive : false, description : "It provides customer relationship management software and applications focused on sales, customer service, marketing automation."}, 14 | {name : "Hubspot", icon : "https://cdn-icons-png.flaticon.com/512/5968/5968872.png", isActive : false, description : "American developer and marketer of software products for inbound marketing, sales, and customer service."}, 15 | ] 16 | 17 | function Integration(){ 18 | 19 | const dispatch = useDispatch() 20 | 21 | const [integrationList, setIntegrationList] = useState(INITIAL_INTEGRATION_LIST) 22 | 23 | 24 | const updateIntegrationStatus = (index) => { 25 | let integration = integrationList[index] 26 | setIntegrationList(integrationList.map((i, k) => { 27 | if(k===index)return {...i, isActive : !i.isActive} 28 | return i 29 | })) 30 | dispatch(showNotification({message : `${integration.name} ${integration.isActive ? "disabled" : "enabled"}` , status : 1})) 31 | } 32 | 33 | 34 | return( 35 | <> 36 |
37 | { 38 | integrationList.map((i, k) => { 39 | return( 40 | 41 | 42 |

43 | icon 44 | {i.description} 45 |

46 |
47 | updateIntegrationStatus(k)}/> 48 |
49 | 50 |
51 | ) 52 | 53 | }) 54 | } 55 |
56 | 57 | ) 58 | } 59 | 60 | export default Integration -------------------------------------------------------------------------------- /client/src/components/features/leads/components/AddLeadModalBody.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import { useDispatch } from "react-redux" 3 | import InputText from '../../../../components/Input/InputText' 4 | import ErrorText from '../../../../components/Typography/ErrorText' 5 | import { showNotification } from "../../common/headerSlice" 6 | import { addNewLead } from "../leadSlice" 7 | 8 | const INITIAL_LEAD_OBJ = { 9 | first_name : "", 10 | last_name : "", 11 | email : "" 12 | } 13 | 14 | function AddLeadModalBody({closeModal}){ 15 | const dispatch = useDispatch() 16 | const [loading, setLoading] = useState(false) 17 | const [errorMessage, setErrorMessage] = useState("") 18 | const [leadObj, setLeadObj] = useState(INITIAL_LEAD_OBJ) 19 | 20 | 21 | const saveNewLead = () => { 22 | if(leadObj.first_name.trim() === "")return setErrorMessage("First Name is required!") 23 | else if(leadObj.email.trim() === "")return setErrorMessage("Email id is required!") 24 | else{ 25 | let newLeadObj = { 26 | "id": 7, 27 | "email": leadObj.email, 28 | "first_name": leadObj.first_name, 29 | "last_name": leadObj.last_name, 30 | "avatar": "https://reqres.in/img/faces/1-image.jpg" 31 | } 32 | dispatch(addNewLead({newLeadObj})) 33 | dispatch(showNotification({message : "New Lead Added!", status : 1})) 34 | closeModal() 35 | } 36 | } 37 | 38 | const updateFormValue = ({updateType, value}) => { 39 | setErrorMessage("") 40 | setLeadObj({...leadObj, [updateType] : value}) 41 | } 42 | 43 | return( 44 | <> 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {errorMessage} 54 |
55 | 56 | 57 |
58 | 59 | ) 60 | } 61 | 62 | export default AddLeadModalBody -------------------------------------------------------------------------------- /client/src/components/features/leads/leadSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' 2 | import axios from 'axios' 3 | 4 | 5 | 6 | export const getLeadsContent = createAsyncThunk('/leads/content', async () => { 7 | const response = await axios.get('/api/users?page=2', {}) 8 | return response.data; 9 | }) 10 | 11 | export const leadsSlice = createSlice({ 12 | name: 'leads', 13 | initialState: { 14 | isLoading: false, 15 | leads : [] 16 | }, 17 | reducers: { 18 | 19 | 20 | addNewLead: (state, action) => { 21 | let {newLeadObj} = action.payload 22 | state.leads = [...state.leads, newLeadObj] 23 | }, 24 | 25 | deleteLead: (state, action) => { 26 | let {index} = action.payload 27 | state.leads.splice(index, 1) 28 | } 29 | }, 30 | 31 | extraReducers: { 32 | [getLeadsContent.pending]: state => { 33 | state.isLoading = true 34 | }, 35 | [getLeadsContent.fulfilled]: (state, action) => { 36 | state.leads = action.payload.data 37 | state.isLoading = false 38 | }, 39 | [getLeadsContent.rejected]: state => { 40 | state.isLoading = false 41 | }, 42 | } 43 | }) 44 | 45 | export const { addNewLead, deleteLead } = leadsSlice.actions 46 | 47 | export default leadsSlice.reducer -------------------------------------------------------------------------------- /client/src/components/features/profile/components/Repositories.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/client/src/components/features/profile/components/Repositories.js -------------------------------------------------------------------------------- /client/src/components/features/profile/index.jsx: -------------------------------------------------------------------------------- 1 | import ProfileInfo from "./components/ProfileInfo"; 2 | 3 | const Profile = () => { 4 | return ( 5 | <> 6 | 7 | 8 | ); 9 | }; 10 | 11 | export default Profile; 12 | -------------------------------------------------------------------------------- /client/src/components/features/settings/changepassword/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import TitleCard from "../../../Cards/TitleCard"; 3 | import InputText from "../../../Input/InputText"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import ProfileAvatar from "../../../General/Avatar"; 6 | import { NotificationManager } from "react-notifications"; 7 | import { changePassword } from "../../../../redux/authSlice"; 8 | 9 | const ChangePassword = () => { 10 | const dispatch = useDispatch(); 11 | const { user } = useSelector((state) => state.auth); 12 | const [id, setId] = useState(); 13 | const [newData, setNewData] = useState({ 14 | currentPassword: "", 15 | newpassword: "", 16 | }); 17 | const [conPass, setConPass] = useState(""); 18 | 19 | useEffect(() => { 20 | setId(user._id); 21 | }, [user._id]); 22 | 23 | const handlePassword = (e) => { 24 | setNewData({ ...newData, [e.target.name]: e.target.value }); 25 | }; 26 | 27 | const handleConPass = (e) => { 28 | setConPass(e.target.value); 29 | }; 30 | 31 | const setHandleSend = () => { 32 | if (conPass === newData.newpassword) { 33 | dispatch(changePassword({ params: id, payload: newData })); 34 | } else { 35 | NotificationManager.warning( 36 | "Password and confirm password must be equal!!!", 37 | "WARNING" 38 | ); 39 | } 40 | }; 41 | return ( 42 |
43 | 44 |
45 | 46 |
47 | 55 | 63 | 71 |
72 |
73 |
74 |
75 |
76 | 82 |
83 |
84 |
85 |
86 | 87 |
88 |
89 |
90 |
91 |
92 | ); 93 | }; 94 | 95 | export default ChangePassword; 96 | -------------------------------------------------------------------------------- /client/src/components/features/user/LandingIntro.js: -------------------------------------------------------------------------------- 1 | import TemplatePointers from "./components/TemplatePointers" 2 | 3 | 4 | 5 | function LandingIntro(){ 6 | 7 | return( 8 |
9 |
10 |
11 | 12 |

dashwind-logoDashWind

13 | 14 |
Dashwind Admin Template
15 | 16 | {/* Importing pointers component */} 17 | 18 | 19 |
20 | 21 |
22 |
23 | ) 24 | 25 | } 26 | 27 | export default LandingIntro -------------------------------------------------------------------------------- /client/src/components/features/user/Login.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { Link } from 'react-router-dom' 3 | import LandingIntro from './LandingIntro' 4 | import ErrorText from '../../Typography/ErrorText' 5 | import InputText from '../../Input/InputText' 6 | 7 | function Login(){ 8 | 9 | const INITIAL_LOGIN_OBJ = { 10 | password : "", 11 | emailId : "" 12 | } 13 | 14 | const [loading, setLoading] = useState(false) 15 | const [errorMessage, setErrorMessage] = useState("") 16 | const [loginObj, setLoginObj] = useState(INITIAL_LOGIN_OBJ) 17 | 18 | const submitForm = (e) =>{ 19 | e.preventDefault() 20 | setErrorMessage("") 21 | 22 | if(loginObj.emailId.trim() === "")return setErrorMessage("Email Id is required! (use any value)") 23 | if(loginObj.password.trim() === "")return setErrorMessage("Password is required! (use any value)") 24 | else{ 25 | setLoading(true) 26 | // Call API to check user credentials and save token in localstorage 27 | localStorage.setItem("token", "DumyTokenHere") 28 | setLoading(false) 29 | window.location.href = '/app/welcome' 30 | } 31 | } 32 | 33 | const updateFormValue = ({updateType, value}) => { 34 | setErrorMessage("") 35 | setLoginObj({...loginObj, [updateType] : value}) 36 | } 37 | 38 | return( 39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 |

Login

47 |
submitForm(e)}> 48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 |
Forgot Password? 58 |
59 | 60 | {errorMessage} 61 | 62 | 63 |
Don't have an account yet? Register
64 |
65 |
66 |
67 |
68 |
69 | ) 70 | } 71 | 72 | export default Login -------------------------------------------------------------------------------- /client/src/components/features/user/components/TemplatePointers.js: -------------------------------------------------------------------------------- 1 | function TemplatePointers(){ 2 | return( 3 | <> 4 |

Admin Dashboard Starter Kit

5 |

Light/dark mode toggle

6 |

Redux toolkit and other utility libraries configured

7 |

Calendar, Modal, Sidebar components

8 |

✓ User-friendly documentation

9 |

Daisy UI components, Tailwind CSS support

10 | 11 | ) 12 | } 13 | 14 | export default TemplatePointers -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .loading-indicator:before { 6 | content: ''; 7 | background: #00000080; 8 | position: fixed; 9 | width: 100%; 10 | height: 100%; 11 | top: 0; 12 | left: 0; 13 | z-index: 1000; 14 | } 15 | 16 | .loading-indicator:after { 17 | content: ' '; 18 | position: fixed; 19 | top: 40%; 20 | left: 45%; 21 | z-index: 10010; 22 | color: white; 23 | text-align: center; 24 | font-weight: bold; 25 | font-size: 1.2rem; 26 | border: 16px solid #f3f3f3; 27 | /* Light grey */ 28 | border-top: 16px solid #0474bf; 29 | /* Blue */ 30 | border-radius: 50%; 31 | width: 120px; 32 | height: 120px; 33 | animation: spin 2s linear infinite; 34 | } 35 | 36 | .bg-pic { 37 | background-image: url("../public/assets/images/background_outrun.png"); 38 | height: 100vh; 39 | background-size: 100% 100%; 40 | } 41 | 42 | .passBtn { 43 | width: 5rem; 44 | height: 3.125rem; 45 | border-radius: 0.7rem; 46 | border: 1px solid rgb(238, 236, 127); 47 | animation: btnact 1s infinite; 48 | } 49 | 50 | @keyframes btnact { 51 | 0% {} 52 | 53 | 100% { 54 | transform: scale(1.5); 55 | opacity: 0; 56 | } 57 | } -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | import store from "./redux/store"; 7 | import { Provider } from "react-redux"; 8 | import SuspenseContent from "./components/containers/SuspenseContent.jsx"; 9 | 10 | const root = ReactDOM.createRoot(document.getElementById("root")); 11 | root.render( 12 | }> 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | 21 | // If you want to start measuring performance in your app, pass a function 22 | // to log results (for example: reportWebVitals(console.log)) 23 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 24 | reportWebVitals(); 25 | -------------------------------------------------------------------------------- /client/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/pages/Admin/Category/components/InputCategory.jsx: -------------------------------------------------------------------------------- 1 | import TitleCard from "../../../../components/Cards/TitleCard"; 2 | import BigInputText from "../../../../components/Input/BigInputText"; 3 | import { BsPlusLg } from "react-icons/bs"; 4 | import { createCategory, updateCategory } from "../../../../redux/adminSlice"; 5 | import { useCallback, useEffect, useState } from "react"; 6 | import { useDispatch, useSelector } from "react-redux"; 7 | import { BiSolidEditAlt } from "react-icons/bi"; 8 | import { NotificationManager } from "react-notifications"; 9 | import StanSearchBar from "../../../../components/Input/StanSearchBar"; 10 | 11 | function InputCategory({searchVal, setSearchVal}) { 12 | const dispatch = useDispatch(); 13 | const { category } = useSelector((state) => state.admin); 14 | const [searchText, setSearchText] = useState(""); 15 | const [data, setData] = useState({ title: "", _id: "" }); 16 | 17 | useEffect(() => { 18 | setData(category); 19 | }, [category]); 20 | 21 | const create = useCallback(() => { 22 | if (data.title !== "") { 23 | dispatch(createCategory({ title: data.title })) 24 | .then(() => 25 | NotificationManager.success("Create Category Success!", "SUCCESS") 26 | ) 27 | .catch(() => 28 | NotificationManager.error("Create Category Error!", "ERROR") 29 | ); 30 | setData({ title: "", _id: "" }); 31 | } else { 32 | NotificationManager.error("Input Correctly!", "ERROR"); 33 | } 34 | }, [dispatch, data]); 35 | 36 | const edit = useCallback( 37 | (val) => { 38 | dispatch(updateCategory({ title: val.title, _id: val._id })) 39 | .then(() => 40 | NotificationManager.success("Update Category Success!", "SUCCESS") 41 | ) 42 | .catch(() => 43 | NotificationManager.error("Update Category Error!", "ERROR") 44 | ); 45 | setData({ title: "", _id: "" }); 46 | }, 47 | [dispatch] 48 | ); 49 | return ( 50 | 51 | setData({ ...data, title: e.target.value })} 55 | /> 56 | 57 | {category.title === "" ? ( 58 | 66 | ) : ( 67 | 75 | )} 76 | 81 | 82 | ); 83 | } 84 | 85 | export default InputCategory; 86 | -------------------------------------------------------------------------------- /client/src/pages/Admin/Category/components/TableCategory.jsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | import { BiSolidEditAlt, BiSolidTrashAlt } from "react-icons/bi"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { deleteCategory, getCategory } from "../../../../redux/adminSlice"; 5 | import { NotificationManager } from "react-notifications"; 6 | 7 | function TableCategory({setSortIndex}) { 8 | const dispatch = useDispatch(); 9 | const { categories } = useSelector((state) => state.admin); 10 | 11 | const get = useCallback( 12 | (val) => { 13 | dispatch(getCategory({ val })); 14 | }, 15 | [dispatch] 16 | ); 17 | 18 | const del = useCallback( 19 | (id) => { 20 | dispatch(deleteCategory({ _id: id })) 21 | .then(() => 22 | NotificationManager.success("Update Category Success!", "SUCCESS") 23 | ) 24 | .catch(() => 25 | NotificationManager.error("Update Category Error!", "ERROR") 26 | ); 27 | }, 28 | [dispatch] 29 | ); 30 | return ( 31 | <> 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {categories.map((item, k) => { 43 | return ( 44 | 45 | 46 | 51 | 72 | 73 | ); 74 | })} 75 | 76 |
NosetSortIndex("title")}>CategoryAction
{k + 1} 47 |
48 |
{item.title}
49 |
50 |
52 |
53 | 61 | 70 |
71 |
77 |
78 | 79 | ); 80 | } 81 | 82 | export default TableCategory; 83 | -------------------------------------------------------------------------------- /client/src/pages/Admin/Category/index.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import { useEffect, useState } from "react"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import InputCategory from "./components/InputCategory"; 5 | import TableCategory from "./components/TableCategory"; 6 | import { getAllCategory } from "../../../redux/adminSlice"; 7 | 8 | function CategoryPage() { 9 | const [sortIndex, setSortIndex] = useState("createAt"); 10 | const [searchVal, setSearchVal] = useState(""); 11 | const dispatch = useDispatch(); 12 | const { flag } = useSelector((state) => state.admin); 13 | 14 | useEffect(() => { 15 | dispatch(getAllCategory({sortIndex, searchVal})); 16 | }, [flag, sortIndex, searchVal]); 17 | return ( 18 | <> 19 |

Category Page

20 | 21 | 22 | 23 | ); 24 | } 25 | export default CategoryPage; 26 | -------------------------------------------------------------------------------- /client/src/pages/Admin/UserManage/EditAccount/EditManage/index.jsx: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from "react-redux"; 2 | import TitleCard from "../../../../../components/Cards/TitleCard"; 3 | import { useParams } from "react-router-dom"; 4 | import { useEffect, useState } from "react"; 5 | import { getUser, permissionUser } from "../../../../../redux/adminSlice"; 6 | import InputDisabled from "../../../../../components/Input/Inputdisabled"; 7 | import SelectBoxSmall from "../../../../../components/Input/SelectBoxSmall"; 8 | import { NotificationManager } from "react-notifications"; 9 | import { BiSolidEditAlt } from "react-icons/bi"; 10 | 11 | function EditManage() { 12 | const dispatch = useDispatch(); 13 | const { id } = useParams(); 14 | const { user } = useSelector((state) => state.admin); 15 | const [role, setRole] = useState(user.role); 16 | useEffect(() => { 17 | dispatch(getUser(id)); 18 | }, [dispatch, id, user.role]); 19 | 20 | const updateAccount = () => { 21 | dispatch(permissionUser({ params: id, role: role })) 22 | .then(() => 23 | NotificationManager.success("Update Role Success!", "SUCCESS") 24 | ) 25 | .catch(() => NotificationManager.error("Update Role Error!", "ERROR")); 26 | }; 27 | 28 | return ( 29 | <> 30 | 31 |
32 |
33 | Avatar 34 |
35 |
36 |
37 | 38 | 39 | 40 |
41 |
42 | 43 |
44 | setRole(e.target.value)} 49 | /> 50 | 59 | {/* */} 62 |
63 |
64 | 65 | ); 66 | } 67 | 68 | export default EditManage; 69 | -------------------------------------------------------------------------------- /client/src/pages/Admin/UserManage/EditAccount/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { setPageTitle } from "../../../../components/features/common/headerSlice"; 4 | import EditManage from "./EditManage"; 5 | 6 | function EditAccount() { 7 | const dispatch = useDispatch(); 8 | useEffect(() => { 9 | dispatch(setPageTitle({ title: "Edit Account" })); 10 | }, [dispatch]); 11 | return ( 12 | <> 13 |

User Page

14 | 15 | 16 | ); 17 | } 18 | export default EditAccount; 19 | -------------------------------------------------------------------------------- /client/src/pages/Admin/UserManage/UserManagePanel/TopSideButtons/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import FunnelIcon from "@heroicons/react/24/outline/FunnelIcon"; 3 | import XMarkIcon from "@heroicons/react/24/outline/XMarkIcon"; 4 | import StanSearchBar from "../../../../../components/Input/StanSearchBar"; 5 | 6 | const TopSideButtons = ({ removeFilter, applyFilter, applySearch, users }) => { 7 | const [filterParam, setFilterParam] = useState(""); 8 | const [searchText, setSearchText] = useState(""); 9 | const bioFilters = users.map((item, index) => { 10 | return item.bio; 11 | }); 12 | 13 | const showFiltersAndApply = (params) => { 14 | applyFilter(params); 15 | setFilterParam(params); 16 | }; 17 | 18 | const removeAppliedFilter = () => { 19 | removeFilter(); 20 | setFilterParam(""); 21 | setSearchText(""); 22 | }; 23 | 24 | useEffect(() => { 25 | if (searchText === "") { 26 | removeAppliedFilter(); 27 | } else { 28 | applySearch(searchText); 29 | } 30 | }, [searchText, users]); 31 | return ( 32 |
33 | 38 | {filterParam !== "" && ( 39 | 46 | )} 47 |
48 | 52 |
    56 | {bioFilters.map((l, k) => { 57 | return ( 58 |
  • 59 |
    showFiltersAndApply(l)}>{l}
    60 |
  • 61 | ); 62 | })} 63 |
    64 |
  • 65 |
    removeAppliedFilter()}>Remove Filter
    66 |
  • 67 |
68 |
69 |
70 | ); 71 | }; 72 | 73 | export default TopSideButtons; 74 | -------------------------------------------------------------------------------- /client/src/pages/Admin/UserManage/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import UserManagePanel from "./UserManagePanel"; 4 | import { allUser } from "../../../redux/adminSlice"; 5 | 6 | function UserManage() { 7 | const [sortIndex, setSortIndex] = useState("createAt"); 8 | 9 | const dispatch = useDispatch(); 10 | useEffect(() => { 11 | dispatch(allUser(sortIndex)); 12 | }, [dispatch, sortIndex]); 13 | return ( 14 | <> 15 |

User Page

16 | 17 | 18 | ); 19 | } 20 | export default UserManage; 21 | -------------------------------------------------------------------------------- /client/src/pages/Article/Draft.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useNavigate, Link } from "react-router-dom"; 4 | import Toolbar from "../../components/features/dashboard/components/Toolbar"; 5 | import ArticleCard from "../../components/features/dashboard/components/ArticleCard"; 6 | import { 7 | getAArticles, 8 | deleteArticle, 9 | updateArticle, 10 | getDraftArticles, 11 | } from "../../redux/articleSlice"; 12 | import { 13 | showNotification, 14 | setPageTitle, 15 | } from "../../components/features/common/headerSlice"; 16 | // import { setIsLoading } from "../../redux/articleSlice"; 17 | 18 | function DraftArticle() { 19 | useEffect(() => { 20 | dispatch(setPageTitle({ title: "Draft Article" })); 21 | }, []); 22 | const dispatch = useDispatch(); 23 | const navigate = useNavigate(); 24 | 25 | const { user } = useSelector((state) => state.auth); 26 | 27 | const value = useSelector((state) => state.article); 28 | const setHandleAddArticle = () => { 29 | navigate(`/newArticle/${0}`); 30 | }; 31 | const setHandleSendArticle = (data) => { 32 | dispatch(updateArticle({ ...data, complete: true })); 33 | }; 34 | const setHandleDelete = (index) => { 35 | if (window.confirm("Are you delete this Article?")) { 36 | dispatch(deleteArticle(index)); 37 | } 38 | }; 39 | const setHandleEdit = (index) => { 40 | dispatch(getAArticles(index)); 41 | navigate(`/newArticle/${index}`); 42 | }; 43 | useEffect(() => { 44 | dispatch(getDraftArticles({ from: user._id })); 45 | if (value.isLoading) 46 | dispatch(showNotification({ message: value.message, status: 1 })); 47 | }, [dispatch, value.isLoading, value.message]); 48 | 49 | return ( 50 | <> 51 | 52 | {value.articles.length ? ( 53 | value.articles.map((v, i) => { 54 | return ( 55 |
56 | { 65 | setHandleDelete(v._id); 66 | }} 67 | onEditArticle={() => { 68 | setHandleEdit(v._id); 69 | }} 70 | onSendClick={() => { 71 | setHandleSendArticle(v); 72 | }} 73 | /> 74 |
75 | ); 76 | }) 77 | ) : ( 78 |
No Data
79 | )} 80 | 81 | ); 82 | } 83 | 84 | export default DraftArticle; 85 | -------------------------------------------------------------------------------- /client/src/pages/Article/FavouriteArticle.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useNavigate, Link } from "react-router-dom"; 4 | import Toolbar from "../../components/features/dashboard/components/Toolbar"; 5 | import ArticleCard from "../../components/features/dashboard/components/ArticleCard"; 6 | import { 7 | getAllArticles, 8 | getAArticles, 9 | deleteArticle, 10 | addFavourite, 11 | getFavoriteArticles, 12 | } from "../../redux/articleSlice"; 13 | import { 14 | showNotification, 15 | setPageTitle, 16 | } from "../../components/features/common/headerSlice"; 17 | // import { setIsLoading } from "../../redux/articleSlice"; 18 | 19 | function FavouriteArticle() { 20 | useEffect(() => { 21 | dispatch(setPageTitle({ title: "FavouriteArticle" })); 22 | }, []); 23 | const dispatch = useDispatch(); 24 | const navigate = useNavigate(); 25 | const { user } = useSelector((state) => state.auth); 26 | const value = useSelector((state) => state.article); 27 | const setHandleAddArticle = () => { 28 | navigate(`/newArticle/${0}`); 29 | }; 30 | const onFavouriteArticle = (index) => { 31 | dispatch(addFavourite({ id: index, from: user._id })); 32 | }; 33 | const setHandleCommentArticle = (index) => { 34 | dispatch(getAArticles(index)); 35 | navigate(`/answerArticle/${index}`); 36 | }; 37 | const setHandleDelete = (index) => { 38 | if (window.confirm("Are you delete this Article?")) { 39 | dispatch(deleteArticle(index)); 40 | } 41 | }; 42 | const setHandleEdit = (index) => { 43 | dispatch(getAArticles(index)); 44 | navigate(`/newArticle/${index}`); 45 | }; 46 | useEffect(() => { 47 | dispatch(getFavoriteArticles({ favorite: user._id })); 48 | if (value.isLoading) 49 | dispatch(showNotification({ message: value.message, status: 1 })); 50 | }, [dispatch, value.isLoading, value.message, user._id]); 51 | 52 | return ( 53 | <> 54 | 55 | {value.articles.length ? ( 56 | value.articles.map((v, i) => { 57 | return ( 58 |
59 | { 67 | onFavouriteArticle(v._id); 68 | }} 69 | onDeleteArticle={() => { 70 | setHandleDelete(v._id); 71 | }} 72 | onEditArticle={() => { 73 | setHandleEdit(v._id); 74 | }} 75 | onCommentClick={() => { 76 | setHandleCommentArticle(v._id); 77 | }} 78 | /> 79 |
80 | ); 81 | }) 82 | ) : ( 83 |
No Data
84 | )} 85 | 86 | ); 87 | } 88 | 89 | export default FavouriteArticle; 90 | -------------------------------------------------------------------------------- /client/src/pages/Article/MyArticle.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useNavigate } from "react-router-dom"; 4 | import Toolbar from "../../components/features/dashboard/components/Toolbar"; 5 | import ArticleCard from "../../components/features/dashboard/components/ArticleCard"; 6 | import { 7 | getAArticles, 8 | deleteArticle, 9 | addFavourite, 10 | getMyArticles, 11 | } from "../../redux/articleSlice"; 12 | import { 13 | showNotification, 14 | setPageTitle, 15 | } from "../../components/features/common/headerSlice"; 16 | 17 | function MyArticle(props) { 18 | const dispatch = useDispatch(); 19 | const navigate = useNavigate(); 20 | const { user } = useSelector((state) => state.auth); 21 | const value = useSelector((state) => state.article); 22 | useEffect(() => { 23 | dispatch(setPageTitle({ title: "My Article" })); 24 | }, [dispatch]); 25 | const setHandleAddArticle = () => { 26 | navigate(`/newArticle/${0}`); 27 | }; 28 | const onFavouriteArticle = (index) => { 29 | dispatch(addFavourite({ id: index, from: user._id })); 30 | }; 31 | const setHandleCommentArticle = (index) => { 32 | dispatch(getAArticles(index)); 33 | navigate(`/answerArticle/${index}`); 34 | }; 35 | const setHandleDelete = (index) => { 36 | if (window.confirm("Are you delete this Article?")) { 37 | dispatch(deleteArticle(index)); 38 | } 39 | }; 40 | const setHandleEdit = (index) => { 41 | dispatch(getAArticles(index)); 42 | navigate(`/newArticle/${index}`); 43 | }; 44 | useEffect(() => { 45 | dispatch(getMyArticles({ from: props.id || user._id })); 46 | if (value.isLoading) 47 | dispatch(showNotification({ message: value.message, status: 1 })); 48 | }, [dispatch, props.id, user._id, value.isLoading, value.message]); 49 | 50 | return ( 51 | <> 52 | 53 | {value.articles.length ? ( 54 | value.articles.map((v, i) => { 55 | return ( 56 |
57 | { 66 | onFavouriteArticle(v._id); 67 | }} 68 | onDeleteArticle={() => { 69 | setHandleDelete(v._id); 70 | }} 71 | onEditArticle={() => { 72 | setHandleEdit(v._id); 73 | }} 74 | onCommentClick={() => { 75 | setHandleCommentArticle(v._id); 76 | }} 77 | /> 78 |
79 | ); 80 | }) 81 | ) : ( 82 |
No Data
83 | )} 84 | 85 | ); 86 | } 87 | 88 | export default MyArticle; 89 | -------------------------------------------------------------------------------- /client/src/pages/ForgotPassword/index.jsx: -------------------------------------------------------------------------------- 1 | import ForgotPassword from "../../components/features/user/ForgotPassword"; 2 | 3 | function ExternalPage() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | 11 | export default ExternalPage; 12 | -------------------------------------------------------------------------------- /client/src/pages/Settings/ChangePassword.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { setPageTitle } from "../../components/features/common/headerSlice"; 4 | import ChangePassword from "../../components/features/settings/changepassword"; 5 | 6 | function InternalPage() { 7 | const dispatch = useDispatch(); 8 | 9 | useEffect(() => { 10 | dispatch(setPageTitle({ title: "Settings" })); 11 | }, [dispatch]); 12 | 13 | return ; 14 | } 15 | 16 | export default InternalPage; 17 | -------------------------------------------------------------------------------- /client/src/pages/Settings/ProfileSettings.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { setPageTitle } from "../../components/features/common/headerSlice"; 4 | import ProfileSettings from "../../components/features/settings/profilesettings"; 5 | import { getAllCategory } from "../../redux/adminSlice"; 6 | 7 | function InternalPage() { 8 | const dispatch = useDispatch(); 9 | 10 | useEffect(() => { 11 | dispatch(setPageTitle({ title: "Settings" })); 12 | dispatch(getAllCategory()) 13 | }, [dispatch]); 14 | 15 | return ; 16 | } 17 | 18 | export default InternalPage; 19 | -------------------------------------------------------------------------------- /client/src/pages/Settings/Team.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { setPageTitle } from '../../components/features/common/headerSlice' 4 | import Team from '../../components/features/settings/team' 5 | 6 | function InternalPage(){ 7 | const dispatch = useDispatch() 8 | 9 | useEffect(() => { 10 | dispatch(setPageTitle({ title : "Team Members"})) 11 | }, []) 12 | 13 | 14 | return( 15 | 16 | ) 17 | } 18 | 19 | export default InternalPage -------------------------------------------------------------------------------- /client/src/pages/protected/404.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { setPageTitle } from '../../components/features/common/headerSlice' 4 | import FaceFrownIcon from '@heroicons/react/24/solid/FaceFrownIcon' 5 | 6 | function InternalPage(){ 7 | 8 | const dispatch = useDispatch() 9 | 10 | useEffect(() => { 11 | dispatch(setPageTitle({ title : ""})) 12 | }, []) 13 | 14 | return( 15 |
16 |
17 |
18 | 19 |

404 - Not Found

20 |
21 |
22 |
23 | ) 24 | } 25 | 26 | export default InternalPage -------------------------------------------------------------------------------- /client/src/pages/protected/Blank.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { setPageTitle } from '../../features/common/headerSlice' 4 | 5 | import DocumentIcon from '@heroicons/react/24/solid/DocumentIcon' 6 | 7 | function InternalPage(){ 8 | 9 | const dispatch = useDispatch() 10 | 11 | useEffect(() => { 12 | dispatch(setPageTitle({ title : "Page Title"})) 13 | }, []) 14 | 15 | return( 16 |
17 |
18 |
19 | 20 |

Blank Page

21 |
22 |
23 |
24 | ) 25 | } 26 | 27 | export default InternalPage -------------------------------------------------------------------------------- /client/src/pages/protected/Charts.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import Charts from "../../components/features/charts"; 4 | import { setPageTitle } from "../../components/features/common/headerSlice"; 5 | 6 | function InternalPage() { 7 | const dispatch = useDispatch(); 8 | 9 | useEffect(() => { 10 | dispatch(setPageTitle({ title: "Analytics" })); 11 | }, []); 12 | 13 | return ; 14 | } 15 | 16 | export default InternalPage; 17 | -------------------------------------------------------------------------------- /client/src/pages/protected/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { setPageTitle } from "../../components/features/common/headerSlice"; 4 | import Dashboard from "../../components/features/dashboard/index"; 5 | 6 | function InternalPage() { 7 | const dispatch = useDispatch(); 8 | 9 | useEffect(() => { 10 | dispatch(setPageTitle({ title: "Dashboard" })); 11 | }, []); 12 | 13 | return ; 14 | } 15 | 16 | export default InternalPage; 17 | -------------------------------------------------------------------------------- /client/src/pages/protected/Integration.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { setPageTitle } from '../../features/common/headerSlice' 4 | import Integration from '../../features/integration' 5 | 6 | function InternalPage(){ 7 | 8 | const dispatch = useDispatch() 9 | 10 | useEffect(() => { 11 | dispatch(setPageTitle({ title : "Integrations"})) 12 | }, []) 13 | 14 | return( 15 | 16 | ) 17 | } 18 | 19 | export default InternalPage -------------------------------------------------------------------------------- /client/src/pages/protected/Leads.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { setPageTitle } from '../../features/common/headerSlice' 4 | import Leads from '../../features/leads' 5 | 6 | function InternalPage(){ 7 | const dispatch = useDispatch() 8 | 9 | useEffect(() => { 10 | dispatch(setPageTitle({ title : "Leads"})) 11 | }, []) 12 | 13 | 14 | return( 15 | 16 | ) 17 | } 18 | 19 | export default InternalPage -------------------------------------------------------------------------------- /client/src/pages/protected/Transactions.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { setPageTitle } from '../../features/common/headerSlice' 4 | import Transactions from '../../features/transactions' 5 | 6 | function InternalPage(){ 7 | const dispatch = useDispatch() 8 | 9 | useEffect(() => { 10 | dispatch(setPageTitle({ title : "Transactions"})) 11 | }, []) 12 | 13 | 14 | return( 15 | 16 | ) 17 | } 18 | 19 | export default InternalPage -------------------------------------------------------------------------------- /client/src/pages/protected/Welcome.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'react-redux' 3 | import { setPageTitle } from '../../components/features/common/headerSlice' 4 | import {Link} from 'react-router-dom' 5 | import TemplatePointers from '../../components/features/user/components/TemplatePointers' 6 | 7 | function InternalPage(){ 8 | 9 | const dispatch = useDispatch() 10 | 11 | useEffect(() => { 12 | dispatch(setPageTitle({ title : ""})) 13 | }, []) 14 | 15 | return( 16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 |
24 | ) 25 | } 26 | 27 | export default InternalPage -------------------------------------------------------------------------------- /client/src/redux/adminSlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; 2 | 3 | import axios from "axios"; 4 | 5 | const base_url = process.env.REACT_APP_BASE_URL + "/api/admin"; 6 | 7 | export const allUser = createAsyncThunk("allUser", async (sortIndex) => { 8 | console.log(sortIndex) 9 | const res = await axios.get(base_url + "/user/all", {params: {sortIndex}}); 10 | return res.data; 11 | }); 12 | export const getUser = createAsyncThunk("getUser", async (params) => { 13 | const res = await axios.get(base_url + `/user/${params}`); 14 | return res.data; 15 | }); 16 | export const permissionUser = createAsyncThunk("roleUser", async (data) => { 17 | const res = await axios.put(base_url + `/user/role/${data.params}`, { 18 | role: data.role, 19 | }); 20 | return res.data; 21 | }); 22 | export const getAllCategory = createAsyncThunk("getAllCategory", async (data) => { 23 | const res = await axios.get(base_url + "/category/all", {params: data}); 24 | return res.data; 25 | }); 26 | export const createCategory = createAsyncThunk("addCategory", async (data) => { 27 | const res = await axios.post(base_url + "/category/", data); 28 | return res.data; 29 | }); 30 | export const updateCategory = createAsyncThunk("editCategory", async (data) => { 31 | const res = await axios.put(base_url + `/category/${data._id}`, data); 32 | return res.data; 33 | }); 34 | export const deleteCategory = createAsyncThunk("delCategory", async (data) => { 35 | const res = await axios.delete(base_url + `/category/${data._id}`); 36 | return res.data; 37 | }); 38 | 39 | export const adminSlice = createSlice({ 40 | name: "admin", 41 | initialState: { 42 | users: [], 43 | user: {}, 44 | selectUser: {}, 45 | categories: [], 46 | category: { 47 | title: "", 48 | _id: "", 49 | }, 50 | flag: false, 51 | }, 52 | reducers: { 53 | getCategory: (state, action) => { 54 | state.category.title = action.payload.val.title; 55 | state.category._id = action.payload.val._id; 56 | }, 57 | }, 58 | extraReducers: { 59 | [allUser.fulfilled]: (state, action) => { 60 | state.users = action.payload.users; 61 | }, 62 | [getUser.fulfilled]: (state, action) => { 63 | state.user = action.payload.user; 64 | }, 65 | [permissionUser.fulfilled]: (state, action) => { 66 | state.user = action.payload; 67 | }, 68 | [getAllCategory.fulfilled]: (state, action) => { 69 | state.categories = action.payload.result; 70 | state.flag = true; 71 | }, 72 | [createCategory.fulfilled]: (state) => { 73 | state.flag = false; 74 | }, 75 | [updateCategory.fulfilled]: (state) => { 76 | state.category.title = ""; 77 | state.flag = false; 78 | }, 79 | [deleteCategory.fulfilled]: (state) => { 80 | state.category.title = ""; 81 | state.flag = false; 82 | }, 83 | }, 84 | }); 85 | 86 | export const { getCategory } = adminSlice.actions; 87 | 88 | export default adminSlice.reducer; 89 | -------------------------------------------------------------------------------- /client/src/redux/auth.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const checkAuth = (dispatch) => { 4 | // const dispatch = useDispatch(); 5 | /* Getting token value stored in localstorage, if token is not present we will open login page 6 | for all internal dashboard routes */ 7 | const TOKEN = localStorage.getItem("token"); 8 | const PUBLIC_ROUTES = [ 9 | "signin", 10 | "forgot-password", 11 | "signup", 12 | "documentation", 13 | ]; 14 | 15 | const isPublicPage = PUBLIC_ROUTES.some((r) => 16 | window.location.href.includes(r) 17 | ); 18 | 19 | if (!TOKEN && !isPublicPage) { 20 | window.location.href = "/signin"; 21 | return; 22 | } else { 23 | axios.defaults.headers.common[ 24 | "Authorization" 25 | ] = `Bearer ${TOKEN}`; 26 | axios.interceptors.request.use( 27 | function (config) { 28 | // UPDATE: Add this code to show global loading indicator 29 | document.body.classList.add("loading-indicator"); 30 | return config; 31 | }, 32 | function (error) { 33 | return Promise.reject(error); 34 | } 35 | ); 36 | axios.interceptors.response.use( 37 | function (response) { 38 | // UPDATE: Add this code to hide global loading indicator 39 | document.body.classList.remove("loading-indicator"); 40 | return response; 41 | }, 42 | function (error) { 43 | document.body.classList.remove("loading-indicator"); 44 | return Promise.reject(error); 45 | } 46 | ); 47 | return TOKEN; 48 | } 49 | }; 50 | 51 | export default checkAuth; 52 | -------------------------------------------------------------------------------- /client/src/redux/init.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const initializeApp = () => { 4 | // Setting base URL for all API request via axios 5 | axios.defaults.baseURL = process.env.REACT_APP_BASE_URL; 6 | 7 | if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") { 8 | // dev code 9 | } else { 10 | // Prod build code 11 | 12 | // Removing console.log from prod 13 | console.log = () => {}; 14 | 15 | // init analytics here 16 | } 17 | }; 18 | 19 | export default initializeApp; 20 | -------------------------------------------------------------------------------- /client/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import headerSlice from "../components/features/common/headerSlice"; 3 | import modalSlice from "../components/features/common/modalSlice"; 4 | import rightDrawerSlice from "../components/features/common/rightDrawerSlice"; 5 | import leadsSlice from "../components/features/leads/leadSlice"; 6 | import authSlice from "./authSlice"; 7 | import adminSlice from "./adminSlice"; 8 | import articleSlice from "./articleSlice"; 9 | 10 | const combinedReducer = { 11 | header: headerSlice, 12 | rightDrawer: rightDrawerSlice, 13 | modal: modalSlice, 14 | lead: leadsSlice, 15 | auth: authSlice, 16 | admin: adminSlice, 17 | article: articleSlice, 18 | }; 19 | 20 | export default configureStore({ 21 | reducer: combinedReducer, 22 | }); 23 | -------------------------------------------------------------------------------- /client/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /client/src/routes/index.js: -------------------------------------------------------------------------------- 1 | // All components mapping with path for internal routes 2 | 3 | import { lazy } from "react"; 4 | 5 | const Dashboard = lazy(() => import("../pages/protected/Dashboard")); 6 | const Charts = lazy(() => import("../pages/protected/Charts")); 7 | const Welcome = lazy(() => import("../pages/protected/Welcome")); 8 | const Page404 = lazy(() => import("../pages/protected/404")); 9 | const Team = lazy(() => import("../pages/Settings/Team")); 10 | const ProfileSettings = lazy(() => import("../pages/Settings/ProfileSettings")); 11 | const Cagegory = lazy(() => import("../pages/Admin/Category")); 12 | const UserManage = lazy(() => import("../pages/Admin/UserManage")); 13 | const Profile = lazy(() => import("../components/features/profile")); 14 | const ChangePassword = lazy(() => import("../pages/Settings/ChangePassword")); 15 | const EditAccount = lazy(() => import("../pages/Admin/UserManage/EditAccount")); 16 | const AllArticle = lazy(() => import("../pages/Article/AllArticle")); 17 | const MyArticle = lazy(() => import("../pages/Article/MyArticle")); 18 | const FavouriteArticle = lazy(() => import("../pages/Article/FavouriteArticle")); 19 | const NewArticle = lazy(() => import("../pages/Article/NewArticle")); 20 | const Draft = lazy(() => import("../pages/Article/Draft")); 21 | const AnswerArticle = lazy(() => import("../pages/Article/AnswerArticle")); 22 | const ShowArticle = lazy(() => import("../pages/Article/ShowArticle")); 23 | 24 | const routes = [ 25 | { 26 | path: "/", 27 | component: Dashboard, 28 | }, 29 | { 30 | path: "/welcome", 31 | component: Welcome, 32 | }, 33 | { 34 | path: "/charts", 35 | component: Charts, 36 | }, 37 | { 38 | path: "/admin/user", 39 | component: UserManage, 40 | }, 41 | { 42 | path: "/admin/profile/:id", 43 | component: Profile, 44 | }, 45 | { 46 | path: "/admin/user/edit/:id", 47 | component: EditAccount, 48 | }, 49 | { 50 | path: "/admin/category", 51 | component: Cagegory, 52 | }, 53 | { 54 | path: "/allarticle", 55 | component: AllArticle, 56 | }, 57 | { 58 | path: "/myArticle", 59 | component: MyArticle, 60 | }, 61 | { 62 | path: "/favouriteArticle", 63 | component: FavouriteArticle, 64 | }, 65 | { 66 | path: "/draft", 67 | component: Draft, 68 | }, 69 | { 70 | path: "/newArticle/:id", 71 | component: NewArticle, 72 | }, 73 | { 74 | path: "/answerArticle/:id", 75 | component: AnswerArticle, 76 | }, 77 | { 78 | path: "/showArticle/:id", 79 | component: ShowArticle, 80 | }, 81 | { 82 | path: "/settings-team", 83 | component: Team, 84 | }, 85 | { 86 | path: "/settings-profile", 87 | component: ProfileSettings, 88 | }, 89 | { 90 | path: "/settings-password", 91 | component: ChangePassword, 92 | }, 93 | { 94 | path: "/404", 95 | component: Page404, 96 | }, 97 | ]; 98 | 99 | export default routes; 100 | -------------------------------------------------------------------------------- /client/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /client/src/utils/globalConstantUtil.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = Object.freeze({ 3 | MODAL_BODY_TYPES : { 4 | USER_DETAIL : "USER_DETAIL", 5 | LEAD_ADD_NEW : "LEAD_ADD_NEW", 6 | CONFIRMATION : "CONFIRMATION", 7 | DEFAULT : "", 8 | }, 9 | 10 | RIGHT_DRAWER_TYPES : { 11 | NOTIFICATION : "NOTIFICATION", 12 | CALENDAR_EVENTS : "CALENDAR_EVENTS", 13 | }, 14 | 15 | CONFIRMATION_MODAL_CLOSE_TYPES : { 16 | LEAD_DELETE : "LEAD_DELETE", 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /client/src/utils/requestServer.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | // import store from '../redux/store'; 3 | // import { signout } from '../redux/authSlice'; 4 | 5 | 6 | axios.interceptors.response.use(function (response) { 7 | // Any status code that lie within the range of 2xx cause this function to trigger 8 | // Do something with response data 9 | return response; 10 | }, function (error) { 11 | // Any status codes that falls outside the range of 2xx cause this function to trigger 12 | // Do something with response error 13 | // if (error.response.status === 401) return store.dispatch(signout()); 14 | if (error.response.status === 401) { 15 | localStorage.clear(); 16 | return window.location.assign("/"); 17 | }; 18 | return Promise.reject(error); 19 | }); 20 | 21 | export const requestServer = async(type, url, data)=>{ 22 | switch (type) { 23 | case 'post': 24 | const resPost = await axios.post(process.env.REACT_APP_BASE_URL + url, data) 25 | return resPost 26 | case 'get': 27 | const resGet = await axios.get(process.env.REACT_APP_BASE_URL + url) 28 | return resGet 29 | case 'delete': 30 | const resDel = await axios.delete(process.env.REACT_APP_BASE_URL + url) 31 | return resDel 32 | case 'put': 33 | const resPut = await axios.put(process.env.REACT_APP_BASE_URL + url, data) 34 | return resPut 35 | default: 36 | break; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./src/**/*.{js,jsx,ts,tsx}", 5 | "./node_modules/react-tailwindcss-datepicker/dist/index.esm.js", 6 | ], 7 | darkMode: ["class", '[data-theme="dark"]'], 8 | theme: { 9 | extend: {}, 10 | }, 11 | plugins: [require("@tailwindcss/typography"), require("daisyui")], 12 | daisyui: { 13 | themes: ["light", "dark"], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "finalgoal", 3 | "version": "1.0.0", 4 | "description": "Admin Dashboard template built with create-react-app, tailwind css and daisy UI. Template uses rich tailwind css utility classes and have components of daisy UI, also have redux toolkit implemented for store management.", 5 | "scripts": { 6 | "start": "concurrently \"npm run dev:frontend\"", 7 | "start:frontend": "npm -w ./client run start", 8 | "start:backend": "npm -w ./server run start", 9 | "build:frontend": "npm -w ./client run build", 10 | "dev": "concurrently \"npm run dev:frontend\" \"npm run dev:backend\"", 11 | "dev:frontend": "npm -w ./client run start", 12 | "dev:backend": "npm -w ./server run start", 13 | "test": "npx playwright test", 14 | "test:ui": "npx playwright test --ui", 15 | "postinstall": "npx patch-package" 16 | }, 17 | "workspaces": [ 18 | "./*" 19 | ], 20 | "dependencies": { 21 | "concurrently": "^8.2.2", 22 | "patch-package": "^8.0.0" 23 | }, 24 | "devDependencies": { 25 | "@playwright/test": "^1.42.1", 26 | "@types/node": "^20.11.25", 27 | "dotenv": "^16.4.5", 28 | "prettier": "^3.2.5", 29 | "tsup": "^8.0.1", 30 | "tsx": "^4.7.0", 31 | "typescript": "^5.3.3" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/finalgoal231/finalgoal" 36 | }, 37 | "keywords": [ 38 | "reactjs", 39 | "tailwind-css", 40 | "starter-kit", 41 | "saas-starter-kit", 42 | "reduxt-toolkit-dashboard-template", 43 | "daisyui-template", 44 | "dashboard-template", 45 | "react-router", 46 | "react-charts" 47 | ], 48 | "author": "shinevue", 49 | "license": "ISC", 50 | "bugs": { 51 | "url": "https://github.com/finalgoal231/finalgoal/issues" 52 | }, 53 | "homepage": "", 54 | "eslintConfig": { 55 | "extends": [ 56 | "react-app", 57 | "react-app/jest" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /server/.env: -------------------------------------------------------------------------------- 1 | # APP ENVIRONMENT VARIABLES 2 | APP_NAME = 3 | APP_PORT = 4000 4 | APP_BASE_URL = 5 | APP_API_PREFIX = 6 | 7 | # DB ENVIRONMENT VARIABLES 8 | MONGO_URI = 9 | -------------------------------------------------------------------------------- /server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["prettier", "airbnb-base"], 3 | "parserOptions": { 4 | "ecmaVersion": 12 5 | }, 6 | "env": { 7 | "commonjs": true, 8 | "node": true 9 | }, 10 | "rules": { 11 | "no-console": 0, 12 | "indent": 0, 13 | "linebreak-style": 0, 14 | "prettier/prettier": [ 15 | "error", 16 | { 17 | "trailingComma": "es6", 18 | "singleQuote": false, 19 | "printWidth": 100, 20 | "tabWidth": 2, 21 | "semi": true 22 | } 23 | ] 24 | }, 25 | "plugins": ["prettier"] 26 | } 27 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin-debug/ 3 | bin-release/ 4 | [Oo]bj/ 5 | [Bb]in/ 6 | 7 | # Other files and folders 8 | .settings/ 9 | 10 | # Executables 11 | *.swf 12 | *.air 13 | *.ipa 14 | *.apk 15 | 16 | # Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` 17 | # should NOT be excluded as they contain compiler settings and other important 18 | # information for Eclipse / Flash Builder. 19 | 20 | # ignore node_modules 21 | /node_modules 22 | 23 | # environment variables 24 | -------------------------------------------------------------------------------- /server/Procfile: -------------------------------------------------------------------------------- 1 | web: NODE_ENV=production node app.js -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Facebook][facebook-shield]][facebook-url] 4 | [![Instagram][instagram-shield]][instagram-url] 5 | [![Twitter][twitter-shield]][twitter-url] 6 | [![LinkedIn][linkedin-shield]][linkedin-url] 7 | [![Github][github-shield]][github-url] 8 | 9 | # MERN Stack TODO Application 10 | 11 | 12 | 13 | ## API Documentation 14 | 15 | ```sh 16 | |---------------------------------------------------------------------------------| 17 | | METHOD: URL: // DESCRIPTION | 18 | |---------------------------------------------------------------------------------| 19 | | GET: http:localhost:5000/ // defaults welcome routes | 20 | | GET: http:localhost:5000/api/v1/todos-all // get all todos | 21 | | GET: http:localhost:5000/api/v1/todo/:id // get a single todo | 22 | | POST: http:localhost:5000/api/v1/todo/new // create a new todo | 23 | | POST: http:localhost:5000/api/v1/todos-many // create many todos | 24 | | PUT: http:localhost:5000/api/v1/todo/:id // update a todo | 25 | | DELETE: http:localhost:5000/api/v1/todo/:id // delete a todo | 26 | |---------------------------------------------------------------------------------| 27 | ``` 28 | 29 | 30 | 31 | ## Installing Packages 32 | 33 | ```sh 34 | npm install express // node framework 35 | npm install serve-favicon // api router favicon sets 36 | npm install mongoose // mongoDB database schema-based solution to model your application data 37 | npm install dotenv // environment variables 38 | npm install corss // corss-origin-allow-all 39 | ``` 40 | 41 | 42 | 43 | ## VS-Code Extensions 44 | 45 | Install the below extensions: 46 | 47 | - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 48 | - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 49 | 50 | ## Linting Setup 51 | 52 | In order to lint and format your code automatically according to popular airbnb style guide, I recommend you to follow the instructions as described in video. References are as below. 53 | 54 | ### Install Dev Dependencies 55 | 56 | ```sh 57 | npm install -D eslint prettier 58 | npx install-peerdeps --dev eslint-config-airbnb-base 59 | npm install -D eslint-config-prettier eslint-plugin-prettier 60 | ``` 61 | 62 | 63 | 64 | [facebook-url]: https://www.facebook.com/SamiurRahmanMukul 65 | [instagram-url]: https://www.instagram.com/samiur_rahman_mukul 66 | [twitter-url]: https://www.twitter.com/SamiurRahMukul 67 | [linkedin-url]: https://www.linkedin.com/in/SamiurRahmanMukul 68 | [github-url]: https://www.github.com/SamiurRahmanMukul 69 | 70 | 71 | 72 | [facebook-shield]: https://img.shields.io/badge/-Facebook-black.svg?style=flat-square&logo=facebook&color=555&logoColor=white 73 | [instagram-shield]: https://img.shields.io/badge/-Instagram-black.svg?style=flat-square&logo=instagram&color=555&logoColor=white 74 | [twitter-shield]: https://img.shields.io/badge/-Twitter-black.svg?style=flat-square&logo=twitter&color=555&logoColor=white 75 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555 76 | [github-shield]: https://img.shields.io/badge/-Github-black.svg?style=flat-square&logo=github&color=555&logoColor=white 77 | 78 | HAPPY PROGRAMMING & DEVELOPING 🤣 79 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | // imports modules & dependencies 2 | const express = require("express"); 3 | const env = require("dotenv"); 4 | const favicon = require("serve-favicon"); 5 | var path = require("path"); 6 | var cors = require("cors"); 7 | const passport = require("passport"); 8 | // imports routes, middleware, and configs 9 | const routes = require("./src/routes"); 10 | const { notFoundRoute, errorHandler } = require("./src/configs/errorHandler"); 11 | 12 | // import fileUpload 13 | const fileUpload = require("express-fileupload"); 14 | 15 | // loads environment variables from .env file 16 | env.config(); 17 | 18 | // initializes express app 19 | const app = express(); 20 | const server = require("http").createServer(app); 21 | 22 | // application database connection establishment 23 | const connectDatabase = require("./src/db/connect"); 24 | connectDatabase(); 25 | 26 | // corss-origin-allow-all 27 | app.use(cors()); 28 | 29 | // sets favicon in routes 30 | app.use(favicon(path.join(__dirname, "public", "favicon.ico"))); 31 | 32 | // sets static folder 33 | app.use(express.static(path.join(__dirname, "public"))); 34 | 35 | // parse requests of content-type - application/json 36 | app.use(express.json()); 37 | 38 | // parse requests of content-type - application/x-www-form-urlencoded 39 | app.use(express.urlencoded({ extended: true })); 40 | 41 | app.use(passport.initialize()); 42 | 43 | app.use( 44 | fileUpload({ 45 | limits: { fileSize: 3 * 1024 * 1024 }, 46 | }) 47 | ); 48 | 49 | require("./src/configs/passport")(passport); 50 | 51 | // sets default route 52 | app.get("/", (req, res) => { 53 | res 54 | .status(200) 55 | .json({ message: "Welcome to App Node.js application backend." }); 56 | }); 57 | 58 | // api routes 59 | app.use("/api", routes); 60 | 61 | // 404 - not found error handler 62 | app.use(notFoundRoute); 63 | 64 | // error handler 65 | app.use(errorHandler); 66 | 67 | const authCtr = require("./src/controllers/authController"); 68 | authCtr.defaultAdmin(); 69 | 70 | // app listens to defined port 71 | server.listen(process.env.APP_PORT, () => { 72 | console.log( 73 | `Our Team-App backend server running on: + ${process.env.APP_PORT}` 74 | ); 75 | }); 76 | 77 | // const socketApp = require('./socketApp'); 78 | // socketApp.socketServer(server) 79 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-app-backend", 3 | "version": "1.0.0", 4 | "description": "todo-app-backend development with node.js, express.js & mongoDB", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "nodemon app.js", 8 | "prod": "NODE_ENV=production node app.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/SamiurRahmanMukul/Complete-MERN-TODO-Application.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/SamiurRahmanMukul/Complete-MERN-TODO-Application/issues" 17 | }, 18 | "homepage": "https://github.com/SamiurRahmanMukul/Complete-MERN-TODO-Application/todo-backend#readme", 19 | "keywords": [ 20 | "TODO-APP", 21 | "MERN", 22 | "MongoDB", 23 | "NodeJS", 24 | "ExpressJS", 25 | "ReactJS", 26 | "Mongoose", 27 | "Express", 28 | "MongoDB" 29 | ], 30 | "author": "Samiur Rahman Mukul", 31 | "license": "ISC", 32 | "dependencies": { 33 | "bcryptjs": "^2.4.3", 34 | "cors": "^2.8.5", 35 | "dotenv": "^16.0.1", 36 | "express": "^4.18.1", 37 | "express-fileupload": "^1.5.0", 38 | "jsonwebtoken": "^9.0.2", 39 | "mongoose": "^6.3.5", 40 | "nodemon": "^3.1.3", 41 | "passport": "^0.7.0", 42 | "passport-jwt": "^4.0.1", 43 | "serve-favicon": "^2.5.0", 44 | "socket.io": "^4.7.5" 45 | }, 46 | "devDependencies": { 47 | "eslint": "^8.2.0", 48 | "eslint-config-airbnb-base": "^15.0.0", 49 | "eslint-config-prettier": "^8.5.0", 50 | "eslint-plugin-import": "^2.25.2", 51 | "eslint-plugin-prettier": "^4.0.0", 52 | "prettier": "^2.6.2" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /server/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hotshinydev/finalGoal/2f919660f4c1ff58225d6e6093924f5cb19be10a/server/public/favicon.ico -------------------------------------------------------------------------------- /server/socket/events/authorizationEvents.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | const _ = require('lodash'); 3 | 4 | const utils = require('../utils'); 5 | const authUtils = require('../utils/authorizationUtils'); 6 | const chatUtils = require('../utils/chatUtils'); 7 | const loggedusersUtils = require('../utils/loggedusersUtils'); 8 | const CONSTS = require('../constants'); 9 | const config = require('../../_main/config'); 10 | 11 | exports.authorization = (io, socket, data) => { 12 | const token = utils.parseToken(data.token); 13 | if ( !token ) { 14 | socket.disconnect(CONSTS.AUTHORIZATION_CONSTS.S2C_AUTH_UNAUTHORIZED); 15 | return; 16 | } 17 | jwt.verify(token, config.secret, null, (err, decoded) => { 18 | if (err) { 19 | socket.disconnect(CONSTS.AUTHORIZATION_CONSTS.S2C_AUTH_UNAUTHORIZED); 20 | return; 21 | } 22 | socket.emit(CONSTS.AUTHORIZATION_CONSTS.S2C_AUTH_AUTHORIZED); 23 | decoded._id = decoded.id 24 | authUtils.registerConnected(io, socket, decoded); 25 | authUtils.emitUserlistInfos(io); 26 | loggedusersUtils.emitLoggeduserInfo(io); 27 | }) 28 | } 29 | 30 | exports.registerPageUrl = (io, socket, data) => { 31 | if ( io.connectedUsers && io.connectedUsers.sockets && io.connectedUsers.sockets[socket.id] ) { 32 | // console.log('here', data, 'socketId=', socket.id) 33 | const origin = ( ( _.get( socket, 'handshake.headers.origin' ) || '' ).split( ':' ) || [] )[0]; 34 | io.connectedUsers.sockets[socket.id].lastConnected = ( new Date() ).getTime(); 35 | io.connectedUsers.sockets[socket.id].pageUrl = data.pageUrl; 36 | io.connectedUsers.sockets[socket.id].origin = origin || ''; 37 | loggedusersUtils.registerLog( io, socket, {url: data.pageUrl} ); 38 | loggedusersUtils.emitLoggeduserInfo( io ); 39 | // console.log(io.connectedUsers.sockets) 40 | } 41 | } 42 | 43 | exports.disconnect = (io, socket) => { 44 | // const userId = chatUtils.getUserIdFromSocket(io, socket); 45 | authUtils.removeConnected(io, socket); 46 | authUtils.emitUserlistInfos(io); 47 | loggedusersUtils.emitLoggeduserInfo(io); 48 | } -------------------------------------------------------------------------------- /server/socket/events/loggedusersEvent.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const CONSTS = require('../constants'); 3 | const loggedusersUtils = require('../utils/loggedusersUtils'); 4 | 5 | exports.authorization = (io, socket, data) => { 6 | const userId = data.userId; 7 | const userInfo = _.get( io, `connectedUsers.userlist.${userId}` ); 8 | // console.log('authorization', data, userInfo) 9 | if ( !userInfo ) { 10 | socket.emit( CONSTS.LOGGEDUSERS_CONSTS.S2C_LOGGEDUSERS_UNAUTHORIZED, {} ); 11 | return; 12 | } 13 | _.set( io, `connectedUsers.sockets.${socket.id}.info.type`, CONSTS.SOCKET_TYPES.LOGGEDUSERS ); 14 | 15 | loggedusersUtils.emitLoggeduserInfo( io ); 16 | } 17 | 18 | exports.disconnect = (io, socket, data) => { 19 | loggedusersUtils.removeConnected(io, socket); 20 | loggedusersUtils.emitLoggeduserInfo(io); 21 | } 22 | 23 | exports.registerAction = (io, socket, data) => { 24 | const actionData = { 25 | ...data, 26 | action: data.action || '', 27 | url: data.url || '', 28 | }; 29 | _.map( actionData, ( item, index ) => { 30 | _.set( io, `connectedUsers.sockets.${socket.id}.info.${index}`, item || null ); 31 | }) 32 | // const action = data.action || ''; 33 | // const url = data.url || ''; 34 | // _.set( io, `connectedUsers.sockets.${socket.id}.info.action`, action ); 35 | // _.set( io, `connectedUsers.sockets.${socket.id}.info.url`, url ); 36 | 37 | loggedusersUtils.registerLog( io, socket, actionData ); 38 | loggedusersUtils.emitLoggeduserInfo( io ); 39 | } -------------------------------------------------------------------------------- /server/socket/events/notificationEvents.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const mongoose = require('mongoose'); 3 | const async = require('async'); 4 | const rp = require('request-promise'); 5 | const Notification = require('../../models/dashboard/NotificationModel'); 6 | const notificationUtils = require('../utils/notificationUtils'); 7 | const loggedusersUtils = require('../utils/loggedusersUtils'); 8 | const utils = require('../utils'); 9 | const config = require('../../_main/socket/config'); 10 | 11 | exports.createNotification = ( io, socket, data ) => { 12 | let uids = data.uids || []; 13 | const pageId = data.pageId; 14 | const notificationData = { 15 | type: data.type, 16 | data: data.data, 17 | objId: data.objId, 18 | } 19 | async.waterfall([ 20 | function (callback) { 21 | if ( pageId ) { 22 | const url = `${config.mainApi}/account/user/fetchbypageid?pageId=${pageId}`; 23 | rp({ 24 | uri: url, 25 | method: 'GET', 26 | json: true, 27 | }) 28 | .then(function(result){ 29 | let uids = [], myUid = utils.getUserIdFromSocket( io, socket ) || ''; 30 | _.map( result.uids, uid => { 31 | if ( uid !== myUid ) uids.push( uid ); 32 | }) 33 | callback( null, uids ) 34 | }) 35 | .catch(function(err){ 36 | }); 37 | // callback(null, ['6087690338809b17c41a5786']) 38 | } else { 39 | callback( null, uids ); 40 | } 41 | }, 42 | function(uids, callback) { 43 | let count = 0; 44 | async.whilst( 45 | function test() { 46 | return count < uids.length 47 | }, 48 | function (aNext) { 49 | const uid = uids[count]; 50 | const notification = new Notification({ 51 | uid, 52 | ...notificationData, 53 | }); 54 | notification.save(() => { 55 | count++; 56 | aNext(); 57 | }) 58 | }, 59 | function (err) { 60 | callback( null, uids ) 61 | } 62 | ); 63 | }, 64 | ], (err, uids) => { 65 | if ( err ) { 66 | } else { 67 | notificationUtils.broadcastNotifications( io, uids ); 68 | } 69 | }); 70 | } 71 | 72 | exports.readNotification = ( io, socket, data ) => { 73 | const ids = []; 74 | _.map( data.ids, id => { 75 | let objID = mongoose.Types.ObjectId( id ); 76 | if ( String( objID ) === id ) { 77 | ids.push( id ); 78 | } 79 | }) 80 | Notification.remove( 81 | {_id: {$in: ids}}, 82 | (err, doc) => { 83 | if ( err ) { 84 | console.log('remove notification err', err); 85 | return; 86 | } 87 | const uid = utils.getUserIdFromSocket( io, socket ); 88 | if ( uid ) { 89 | notificationUtils.broadcastNotifications( io, [uid] ); 90 | } 91 | } 92 | ) 93 | } 94 | 95 | exports.readNotificationByObjIds = ( io, socket, data ) => { 96 | let ids = []; 97 | const uid = data.uid || utils.getUserIdFromSocket( io, socket ) || ''; 98 | if ( !uid ) return; 99 | _.map( data.ids, id => { 100 | let objID = mongoose.Types.ObjectId( id ); 101 | if ( String( objID ) === id ) { 102 | ids.push( id ); 103 | } 104 | }) 105 | Notification.remove( 106 | uid === 'all'? {objId: {$in: ids}} : {objId: {$in: ids}, uid: uid}, 107 | (err, doc) => { 108 | if ( err ) { 109 | console.log('remove notification by obj err', err); 110 | return; 111 | } 112 | if ( uid ) { 113 | const uids = uid=== 'all'? loggedusersUtils.getLoggedUsersId( io ) : [uid]; 114 | notificationUtils.broadcastNotifications( io, uids ); 115 | } 116 | } 117 | ) 118 | } -------------------------------------------------------------------------------- /server/socket/global.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | relax: { 3 | roomUniqueIndex: 0, 4 | rooms: {}, 5 | savedRoomData: {}, 6 | }, 7 | } -------------------------------------------------------------------------------- /server/socket/index.js: -------------------------------------------------------------------------------- 1 | const CONSTS = require('./constants'); 2 | const chatUtils = require('./utils/chatUtils'); 3 | const authUtils = require('./utils/authorizationUtils'); 4 | const chatInit = require('./init/chatInit'); 5 | const authInit = require('./init/authorizationInit'); 6 | const loggedusersInit = require('./init/loggedusersInit'); 7 | const notificationInit = require('./init/notificationInit'); 8 | 9 | exports = module.exports = function (io) { 10 | // Set socket.io listeners. 11 | setInterval(() => { 12 | authUtils.emitUserlistInfos(io); 13 | }, 60 * 1000); // 1min 14 | 15 | io.of(CONSTS.SOCKET).on('connection', (socket) => { 16 | 17 | authInit(io, socket); 18 | 19 | 20 | chatInit(io, socket); 21 | 22 | 23 | loggedusersInit(io, socket); 24 | 25 | 26 | notificationInit(io, socket); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /server/socket/init/authorizationInit.js: -------------------------------------------------------------------------------- 1 | const CONSTS = require('../constants'); 2 | const authEvent = require('../events/authorizationEvents'); 3 | 4 | module.exports = function (io, socket) { 5 | socket.on(CONSTS.AUTHORIZATION_CONSTS.C2S_AUTH_AUTHORIZATION, data => authEvent.authorization(io, socket, data)); 6 | socket.on(CONSTS.AUTHORIZATION_CONSTS.C2S_AUTH_REGISTERPAGEURL, data => authEvent.registerPageUrl(io, socket, data)); 7 | socket.on('disconnect', data => authEvent.disconnect(io, socket, data)); 8 | } -------------------------------------------------------------------------------- /server/socket/init/chatInit.js: -------------------------------------------------------------------------------- 1 | const CONSTS = require('../constants'); 2 | const chatEvents = require('../events/chatEvents'); 3 | 4 | module.exports = function (io, socket) { 5 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_DISCONNECT, data => chatEvents.disconnect(io, socket, data)); 6 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_AUTHORIZATION, data => chatEvents.authorization(io, socket, data)); 7 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_GROUP_NEW, data => chatEvents.groupNew(io, socket, data)); 8 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_GROUP_EDIT, data => chatEvents.groupEdit(io, socket, data)); 9 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_GROUP_ARCHIVE, data => chatEvents.groupArchive(io, socket, data)); 10 | 11 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_USER_ENTER, data => chatEvents.roomEnter(io, socket, data)); 12 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_USER_LEAVE, data => chatEvents.roomLeave(io, socket, data)); 13 | 14 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_MESSAGE_NEW, data => chatEvents.roomMessageNew(io, socket, data)); 15 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_MESSAGE_FILES, data => chatEvents.roomMessageFiles(io, socket, data)); 16 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_MESSAGE_EDIT, data => chatEvents.roomMessageEdit(io, socket, data)); 17 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_MESSAGE_DELETE, data => chatEvents.roomMessageDelete(io, socket, data)); 18 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_MESSAGE_RECOMMEND, data => chatEvents.roomMessageRecommend(io, socket, data)); 19 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_MESSAGE_RECORD, data => chatEvents.roomMessageRecord(io, socket, data)); 20 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_MESSAGE_FILE_DOWNLOAD, data => chatEvents.roomMessageFileDownload(io, socket, data)); 21 | 22 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_USER_TYPING, data => chatEvents.roomUserTyping(io, socket, data)); 23 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_USER_STOPTYPING, data => chatEvents.roomUserStopTyping(io, socket, data)); 24 | 25 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_CREATE, data => chatEvents.roomCreate(io, socket, data)); 26 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_EDIT, data => chatEvents.roomEdit(io, socket, data)); 27 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_ARCHIVE, data => chatEvents.roomArchive(io, socket, data)); 28 | socket.on(CONSTS.CHAT_CONSTS.C2S_CHAT_ROOM_LOAD_MOREMESSAGES, data => chatEvents.roomLoadMoreMessages(io, socket, data)); 29 | } -------------------------------------------------------------------------------- /server/socket/init/loggedusersInit.js: -------------------------------------------------------------------------------- 1 | const CONSTS = require('../constants'); 2 | const loggedusersEvent = require('../events/loggedusersEvent'); 3 | 4 | module.exports = function (io, socket) { 5 | socket.on(CONSTS.LOGGEDUSERS_CONSTS.C2S_LOGGEDUSERS_DISCONNECT, data => loggedusersEvent.disconnect(io, socket, data)); 6 | socket.on(CONSTS.LOGGEDUSERS_CONSTS.C2S_LOGGEDUSERS_CONNECT, data => loggedusersEvent.authorization(io, socket, data)); 7 | socket.on(CONSTS.LOGGEDUSERS_CONSTS.C2S_LOGGEDUSERS_REGISTER_ACTION, data => loggedusersEvent.registerAction(io, socket, data)); 8 | } -------------------------------------------------------------------------------- /server/socket/init/notificationInit.js: -------------------------------------------------------------------------------- 1 | const CONSTS = require('../constants'); 2 | const notificationEvent = require('../events/notificationEvents'); 3 | 4 | module.exports = function (io, socket) { 5 | socket.on(CONSTS.NOTIFICATION_CONSTS.C2S_NOTIFICATION_CREATE, data => notificationEvent.createNotification(io, socket, data)); 6 | socket.on(CONSTS.NOTIFICATION_CONSTS.C2S_NOTIFICATION_READ, data => notificationEvent.readNotification(io, socket, data)); 7 | socket.on(CONSTS.NOTIFICATION_CONSTS.C2S_NOTIFICATION_READ_BY_OBJID, data => notificationEvent.readNotificationByObjIds(io, socket, data)); 8 | } -------------------------------------------------------------------------------- /server/socket/utils/authorizationUtils.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const utils = require('./'); 3 | const CONSTS = require('../constants'); 4 | const notificationUtils = require('../utils/notificationUtils'); 5 | 6 | exports.registerConnected = (io, socket, userInfo) => { 7 | if (!io || !socket) 8 | return; 9 | if (!io.connectedUsers) { 10 | io.connectedUsers = { 11 | sockets: {}, 12 | userlist: {} 13 | }; 14 | } 15 | if (!io.userlistInfos) { 16 | io.userlistInfos = []; 17 | } 18 | 19 | _.set(io, `connectedUsers.userlist.${userInfo._id}.userInfo`, userInfo); 20 | _.set(io, `connectedUsers.userlist.${userInfo._id}.sockets.${socket.id}`, true); 21 | 22 | const existSocket = _.get(io, `connectedUsers.sockets.${socket.id}`) || {}; 23 | _.set(io, `connectedUsers.sockets.${socket.id}`, { 24 | user: userInfo._id, 25 | socket: socket, 26 | lastConnected: (new Date()).getTime(), 27 | idleMin: 0, 28 | ip: userInfo.ip, 29 | info: existSocket.info || {}, 30 | pageUrl: existSocket.pageUrl || null, 31 | origin: existSocket.origin || '', 32 | }); 33 | notificationUtils.broadcastNotifications(io, [userInfo._id]); 34 | } 35 | 36 | exports.removeConnected = (io, socket) => { 37 | if (!socket.id) 38 | return; 39 | const userId = _.get(io, `connectedUsers.sockets.${socket.id}.user`); 40 | if (io.connectedUsers && io.connectedUsers.sockets) { 41 | delete io.connectedUsers.sockets[socket.id]; 42 | } 43 | _.set(io, `connectedUsers.userlist.${userId}.sockets.${socket.id}`, false); 44 | // remove this user from `userlist` when his all sockets had disconnected 45 | const sockets = _.get(io, `connectedUsers.userlist.${userId}.sockets`); 46 | let isSocketOpened = false; 47 | _.map(sockets, socketStatus => { 48 | if (socketStatus) isSocketOpened = true; 49 | }); 50 | if (!isSocketOpened) { 51 | delete io.connectedUsers.userlist[userId]; 52 | } 53 | } 54 | 55 | const getUserlistInfos = (io) => { 56 | if (!io || !io.connectedUsers || !io.connectedUsers.sockets) 57 | return null; 58 | const currentTime = (new Date()).getTime(); 59 | let resultArray = []; 60 | _.map(io.connectedUsers.sockets, user => { 61 | const idleDuration = Math.floor((currentTime - user.lastConnected) / (60 * 1000)); // 1min 62 | let idleMin = 0; 63 | if (idleDuration >= 30) { 64 | idleMin = 30; 65 | } else if (idleDuration >= 15) { 66 | idleMin = 15; 67 | } else if (idleDuration >= 10) { 68 | idleMin = 10; 69 | } else if (idleDuration >= 5) { 70 | idleMin = 5; 71 | } else if (idleDuration >= 1) { 72 | idleMin = 1; 73 | } 74 | resultArray.push({ 75 | userInfo: io.connectedUsers.userlist[user.user], 76 | ip: user.ip, 77 | isActive: true, 78 | idleMin: idleMin 79 | }); 80 | }) 81 | return resultArray; 82 | } 83 | 84 | exports.emitUserlistInfos = (io) => { 85 | if (!io || !io.connectedUsers) 86 | return; 87 | const newUserlistInfos = getUserlistInfos(io); 88 | if (!newUserlistInfos) 89 | return; 90 | if (!utils.compareActivityInfos(io.userlistInfos, newUserlistInfos, 'userInfo._id')) { 91 | return; 92 | } 93 | io.userlistInfos = newUserlistInfos; 94 | io.of(CONSTS.SOCKET).emit(CONSTS.AUTHORIZATION_CONSTS.S2C_AUTH_USERLISTINFO, { 95 | users: newUserlistInfos, 96 | }); 97 | } -------------------------------------------------------------------------------- /server/socket/utils/chatUtils.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const utils = require('./'); 3 | const CONSTS = require('../constants'); 4 | const lang = require('../../_main/_lang/lang'); 5 | const config = require('../../_main/config'); 6 | 7 | exports.registerConnected = (io, socket, userInfo) => { 8 | if (!io || !socket) 9 | return; 10 | if (!io.connectedUsers) { 11 | io.connectedUsers = {}; 12 | } 13 | if (!io.activityInfos) { 14 | io.activityInfos = []; 15 | } 16 | } 17 | 18 | exports.removeConnected = (io, socket) => { 19 | if (!socket.id) 20 | return; 21 | _.set(io, `connectedUsers.sockets.${socket.id}.info`, {}); 22 | } 23 | 24 | exports.checkAuthorized = (io, socket) => { 25 | if (io.connectedUsers && io.connectedUsers.sockets && io.connectedUsers.sockets[socket.id]) { 26 | io.connectedUsers.sockets[socket.id].lastConnected = (new Date()).getTime(); 27 | return true; 28 | } 29 | return false; 30 | } 31 | 32 | exports.getUserIdFromSocket = (io, socket) => { 33 | return _.get(io, `connectedUsers.sockets.${socket.id}.user`) || null; 34 | } 35 | 36 | exports.enterRoom = (io, socket, roomId) => { 37 | _.set(io, `connectedUsers.sockets.${socket.id}.info.roomId`, roomId); 38 | } 39 | 40 | exports.leaveRoom = (io, socket) => { 41 | _.set(io, `connectedUsers.sockets.${socket.id}.info.roomId`, null); 42 | } 43 | 44 | exports.getRoomActiveUsers = (io, socket, roomId) => { 45 | let result = []; 46 | _.map(io.connectedUsers.sockets, socket => { 47 | const crrRoomId = _.get(socket, 'info.roomId') || ''; 48 | if (crrRoomId === roomId) { 49 | result.push(socket.user); 50 | } 51 | }) 52 | return result; 53 | } 54 | 55 | exports.getRoomUsersFromReceived = (users, creatorId) => { 56 | let result = []; 57 | result.push(creatorId); 58 | _.map(users, uId => { 59 | if (uId != creatorId) { 60 | result.push(uId); 61 | } 62 | }) 63 | return result; 64 | } 65 | 66 | exports.getGroupAddedUsers = (users, newUsers) => { 67 | let retUsers = []; 68 | _.map(newUsers, newUserId => { 69 | if (users.indexOf(newUserId) < 0) { 70 | retUsers.push(newUserId); 71 | } 72 | }) 73 | return retUsers; 74 | } 75 | 76 | exports.getGroupRemovedUsers = (users, newUsers) => { 77 | let retUsers = []; 78 | _.map(users, userId => { 79 | if (newUsers.indexOf(userId) < 0) { 80 | retUsers.push(userId); 81 | } 82 | }) 83 | return retUsers; 84 | } 85 | 86 | exports.emitWithFilteredByRoom = (io, roomUserIds, key, data) => { 87 | _.map(io.connectedUsers.sockets, socket => { 88 | const crrSocketType = _.get(socket, 'info.type') || ''; 89 | if (crrSocketType === CONSTS.SOCKET_TYPES.CHAT) { 90 | if (roomUserIds.indexOf(socket.user) >= 0 || socket.user == 'admin') { 91 | socket.socket.emit(key, data); 92 | } 93 | } 94 | }) 95 | } -------------------------------------------------------------------------------- /server/socket/utils/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const lang = require('../../_main/_lang/lang'); 3 | const CONSTS = require('../constants'); 4 | 5 | exports.getUserIdFromSocket = ( io, socket ) => { 6 | return _.get( io, `connectedUsers.sockets.${socket.id}.user` ) || ''; 7 | } 8 | 9 | exports.compareActivityInfos = (src1, src2, orderKey) => { 10 | if (!src1 || !src2) { 11 | return true; 12 | } 13 | if (src1.length != src2.length) 14 | return true; 15 | const ordered1 = orderKey? _.orderBy(src1, user=>_.get( user, orderKey ), 'asc') : src1; 16 | const ordered2 = orderKey? _.orderBy(src2, user=>_.get( user, orderKey ), 'asc') : src2; 17 | const str1 = JSON.stringify(ordered1); 18 | const str2 = JSON.stringify(ordered2); 19 | // console.log('compare', str1, '----', str2); 20 | if (str1 != str2) 21 | return true; 22 | return false; 23 | } 24 | 25 | exports.parseToken = (token) => { 26 | var re = /(\S+)\s+(\S+)/; 27 | if (typeof token !== 'string') { 28 | return null; 29 | } 30 | var matches = token.match(re); 31 | return matches && matches[2]; 32 | } 33 | 34 | exports.sendError = (socket, type, detail, err) => { 35 | if (socket) { 36 | console.log('send error', type, detail, err); 37 | socket.emit(CONSTS.CHAT_CONSTS.S2C_CHAT_ERROR, { 38 | type, 39 | detail, 40 | err 41 | }); 42 | } 43 | } 44 | 45 | exports.validate = (socket, eventType, data, fields, types) => { 46 | let errorMsg = []; 47 | let check = false; 48 | for(let i=0; i< fields.length; i++){ 49 | const value = data[fields[i]]; 50 | const type = types[i]; 51 | if( (typeof (value) === 'undefined') || (value === null) ) { 52 | errorMsg.push(fields[i] + ' : ' + lang('require_param')); check = true; 53 | } else { 54 | switch(type){ 55 | case 'id': 56 | if( !value.match(/^[0-9a-fA-F]{24}$/)) { 57 | errorMsg.push(fields[i] + ' : ' + lang('invalid_id')); check = true; 58 | } 59 | break; 60 | case 'string': 61 | if (!value || value.trim().length == 0) { 62 | errorMsg.push(fields[i] + ' : ' + lang('require_string')); check = true; 63 | } 64 | break; 65 | case 'number': 66 | if (typeof(value) !== 'number') { 67 | errorMsg.push(fields[i] + ' : ' + lang('invalid_number')); check = true; 68 | } 69 | break; 70 | case 'boolean': 71 | if (typeof(value) !== 'boolean') { 72 | errorMsg.push(fields[i] + ' : ' + lang('invalid_boolean')); check = true; 73 | } 74 | break; 75 | case 'date': 76 | if (value.trim().length != 10 || value.trim().split('-').length != 3) { 77 | errorMsg.push(fields[i] + ' : ' + lang('invalid_date')); check = true; 78 | } 79 | break; 80 | case 'array': 81 | if ( typeof(value) === 'object' && value.length == 0) { 82 | errorMsg.push(fields[i] + ' : ' + lang('require_array')); check = true; 83 | } 84 | break; 85 | default: break; 86 | } 87 | } 88 | } 89 | if(check) { // if error exists 90 | this.sendError(socket, eventType, 'Parameter error', errorMsg); 91 | return false; 92 | } else { 93 | return true; 94 | } 95 | } -------------------------------------------------------------------------------- /server/socket/utils/loggedusersUtils.js: -------------------------------------------------------------------------------- 1 | const rp = require('request-promise'); 2 | const _ = require('lodash'); 3 | const CONSTS = require('../constants'); 4 | const Log = require('../../models/user/systemManagement/LogModel'); 5 | 6 | const diffLogSaveTime = 5000; 7 | 8 | let lastLogSavedInfo = { 9 | ip: '', 10 | uid: '', 11 | url: '', 12 | action: '', 13 | time: 0 14 | }; 15 | 16 | const getLoggedusersInfo = ( io ) => { 17 | if (!io || !io.connectedUsers || !io.connectedUsers.sockets) 18 | return null; 19 | const currentTime = (new Date()).getTime(); 20 | let resultArray = []; 21 | _.map(io.connectedUsers.sockets, socket => { 22 | const idleDuration = Math.floor((currentTime - socket.lastConnected) / (60 * 1000)); // 1min 23 | let idleMin = 0; 24 | if (idleDuration >= 30) { 25 | idleMin = 30; 26 | } else if (idleDuration >= 15) { 27 | idleMin = 15; 28 | } else if (idleDuration >= 10) { 29 | idleMin = 10; 30 | } else if (idleDuration >= 5) { 31 | idleMin = 5; 32 | } else if (idleDuration >= 1) { 33 | idleMin = 1; 34 | } 35 | const userId = socket.user || 'unknown'; 36 | // console.log('get logged user info', socket.socket.id, socket.pageUrl) 37 | resultArray.push({ 38 | userInfo: _.get( io, `connectedUsers.userlist.${userId}.userInfo` ), 39 | idleMin: idleMin, 40 | ip: socket.ip, 41 | pageUrl: socket.pageUrl, 42 | info: socket.info, 43 | origin: socket.origin, 44 | }); 45 | }) 46 | return resultArray; 47 | } 48 | 49 | exports.getLoggedUsersId = ( io ) => { 50 | if (!io || !io.connectedUsers || !io.connectedUsers.userlist) 51 | return []; 52 | let resultArray = []; 53 | _.map( io.connectedUsers.userlist, ( user, index ) => { 54 | resultArray.push( index ); 55 | }); 56 | return resultArray; 57 | } 58 | 59 | exports.emitLoggeduserInfo = ( io ) => { 60 | const data = getLoggedusersInfo( io ); 61 | _.map( io.connectedUsers.sockets, socket => { 62 | // const crrSocketType = _.get( socket, 'info.type' ) || ''; 63 | // if ( crrSocketType === CONSTS.SOCKET_TYPES.LOGGEDUSERS && socket.socket && socket.socket.emit ) { 64 | // // console.log('emit logged user info. data=', data) 65 | // socket.socket.emit( CONSTS.LOGGEDUSERS_CONSTS.S2C_LOGGEDUSERS_USERS, data ); 66 | // } 67 | if ( socket.socket ) { 68 | socket.socket.emit( CONSTS.LOGGEDUSERS_CONSTS.S2C_LOGGEDUSERS_USERS, data ); 69 | } 70 | }) 71 | } 72 | 73 | exports.removeConnected = (io, socket) => { 74 | if (!socket.id) 75 | return; 76 | // delete io.connectedUsers[socket.id]; 77 | _.set( io, `connectedUsers.sockets.${socket.id}.info`, {} ); 78 | } 79 | 80 | exports.registerLog = ( io, socket, data ) => { 81 | const ip = _.get( io, `connectedUsers.sockets.${socket.id}.ip` ) || ''; 82 | const uid = _.get( io, `connectedUsers.sockets.${socket.id}.user` ) || 'unknown'; 83 | const url = data.url || ''; 84 | const action = data.action || ''; 85 | const did = ''; 86 | 87 | const crrLogSaveTime = { 88 | time: new Date(), 89 | ip, 90 | uid, 91 | url, 92 | action 93 | }; 94 | 95 | if ( lastLogSavedInfo.ip === crrLogSaveTime.ip && 96 | lastLogSavedInfo.uid === crrLogSaveTime.uid && 97 | lastLogSavedInfo.url === crrLogSaveTime.url && 98 | lastLogSavedInfo.action === crrLogSaveTime.action && 99 | crrLogSaveTime.time - lastLogSavedInfo.time < diffLogSaveTime 100 | ) { 101 | lastLogSavedInfo = crrLogSaveTime; 102 | } else { 103 | const log = new Log({ ip, uid, url, action, did }); 104 | 105 | log.save((err, created) => { 106 | lastLogSavedInfo = crrLogSaveTime; 107 | }); 108 | } 109 | } -------------------------------------------------------------------------------- /server/socketApp.js: -------------------------------------------------------------------------------- 1 | const socketIo = require('socket.io'); 2 | 3 | var io; 4 | var nameList = {} 5 | 6 | exports.socketServer = (server) => { 7 | io = socketIo.listen(server) 8 | io.sockets.on('connection', function (socket) { 9 | console.log('socket is joined'); 10 | socket.on('id', function (data) { 11 | nameList[data.userId] = socket 12 | socket.on('like',function (data) { 13 | for (const key in nameList) { 14 | if (key === data.data.author) { 15 | nameList[key].emit('like', {msg: data.data.msg}) 16 | } 17 | } 18 | }) 19 | socket.on('comment', function (data) { 20 | for (const key in nameList) { 21 | if (key === data.data.author) { 22 | nameList[key].emit('comment', {msg: data.data.msg}) 23 | } 24 | } 25 | }) 26 | }) 27 | socket.on('disconnect',function () { 28 | console.log('socket is disconnected'); 29 | }) 30 | }) 31 | } -------------------------------------------------------------------------------- /server/src/configs/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | MONGOURI: "mongodb://127.0.0.1:27017", 3 | secretOrKey: "secret", 4 | adminData: { 5 | name: "admin", 6 | username: "admin", 7 | password: "admin", 8 | role: "admin", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /server/src/configs/errorHandler.js: -------------------------------------------------------------------------------- 1 | // 404 - not found error handler 2 | exports.notFoundRoute = (req, res, next) => { 3 | res.status(404).json({ message: "Sorry! Your request page was not found." }); 4 | }; 5 | 6 | exports.errorHandler = (err, req, res, next) => { 7 | if (res.headersSent) { 8 | return next("Something went wrong."); 9 | } else { 10 | if (err.message) { 11 | res.status(500).json({ message: err.message }); 12 | } else { 13 | res.status(500).json({ message: "There was an error." }); 14 | } 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /server/src/configs/passport.js: -------------------------------------------------------------------------------- 1 | const JwtStrategy = require("passport-jwt").Strategy; 2 | const ExtractJwt = require("passport-jwt").ExtractJwt; 3 | const User = require("../models/userModel"); 4 | const config = require("./config"); 5 | const opts = {}; 6 | opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); 7 | opts.secretOrKey = config.secretOrKey; 8 | 9 | module.exports = (passport) => { 10 | passport.use( 11 | new JwtStrategy(opts, (jwt_payload, done) => { 12 | User.findOne({ username: jwt_payload.username }) 13 | .then((user) => { 14 | if (user) return done(null, user); 15 | return done(null, false); 16 | }) 17 | .catch((err) => { 18 | console.log(err); 19 | }); 20 | }) 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /server/src/configs/socket.js: -------------------------------------------------------------------------------- 1 | const io = require("socket.io"); 2 | const User = require("../models/userModel"); 3 | // const { setSocketMsg } = require("../../../client/src/redux/articleSlice"); 4 | 5 | let userarr = []; 6 | 7 | exports.initialize = (server) => { 8 | const sio = io.listen(server); 9 | console.log("socket server is running"); 10 | sio.on("connection", function (socket) { 11 | socket.emit("connection-success", "successfully connected to socket server"); 12 | socket.on("createArticle", function (msg) { 13 | socket.emit("createArticle-resend", msg); 14 | }); 15 | socket.on("signin", function (username, msg) { 16 | socket.username = username; 17 | userarr.push(socket); 18 | socket.to(username).emit("signin-resend", msg); 19 | }); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /server/src/controllers/adminControllers/categoryController.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const Category = require("../../models/adminModel/categoryModel"); 4 | 5 | // make a controller for create a category 6 | exports.createCategory = (req, res) => { 7 | const newCategory = new Category(req.body); 8 | newCategory 9 | .save() 10 | .then(() => { 11 | res.status(201).json({ msg: "create category successfully." }); 12 | }) 13 | .catch(() => { 14 | res.status(500).json({ msg: "Can't do action article" }); 15 | }); 16 | }; 17 | 18 | // make a controller for update a category 19 | exports.updateCategory = (req, res) => { 20 | let id = req.params.id; 21 | let data = req.body; 22 | Category.findByIdAndUpdate(id, data) 23 | .then(() => { 24 | res.status(201).json({ msg: "Updated successfully." }); 25 | }) 26 | .catch(() => { 27 | res.status(500).json({ msg: "Can't do action article" }); 28 | }); 29 | }; 30 | 31 | // make a controller for deletea category 32 | exports.deleteCategory = (req, res) => { 33 | let id = req.params.id; 34 | Category.findById(id) 35 | .then((category) => { 36 | category.delected = new Date(); 37 | category 38 | .save() 39 | .then(() => { 40 | res.status(201).json({ msg: "Category deleted successfully" }); 41 | }) 42 | .catch(() => { 43 | res.status(400).json({ msg: "Invalide category." }); 44 | }); 45 | }) 46 | .catch((err) => { 47 | res.status(400).json({ msg: "Invalide category." }); 48 | }); 49 | }; 50 | 51 | // make a controller for get all category 52 | exports.getAllCategory = (req, res) => { 53 | const { sortIndex, searchVal } = req.query; 54 | const query = { 55 | delected: null, 56 | // title: { $regex: new RegExp(searchVal, "i") }, 57 | }; 58 | console.log(query); 59 | Category.find(query) 60 | .sort({ [sortIndex]: -1 }) 61 | .then((result) => { 62 | res.status(201).json({ result: result }); 63 | }) 64 | .catch((err) => { 65 | res.status(400).json({ err: err }); 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /server/src/controllers/authController.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const bcrypt = require("bcryptjs"); 3 | 4 | const config = require("../configs/config"); 5 | const User = require("../models/userModel"); 6 | 7 | // make a controller for signup 8 | exports.signup = async (req, res) => { 9 | try { 10 | const isNew = await User.exists({ username: req.body.username }); 11 | if (!isNew) { 12 | const newUser = new User(req.body); 13 | newUser.password = await newUser.hide_pwd(req.body.password); 14 | newUser 15 | .save() 16 | .then(() => { 17 | res.status(201).json({ 18 | msg: "Create a new user successfully.", 19 | }); 20 | }) 21 | .catch(() => { 22 | res.status(500).json({ 23 | msg: "Server Error", 24 | }); 25 | }); 26 | } else { 27 | res.status(400).json({ 28 | msg: "This user is exist.", 29 | }); 30 | } 31 | } catch (error) { 32 | res.status(500).json({ 33 | msg: "DB connection Error", 34 | }); 35 | } 36 | }; 37 | 38 | // make a controller for signin 39 | exports.signin = async (req, res) => { 40 | try { 41 | let { username, password } = req.body; 42 | const user = await User.findOne({ username: username }); 43 | if (!user) { 44 | return res 45 | .status(404) 46 | .json({ msg: `Not found user with id: ${username}` }); 47 | } else { 48 | if (user.show_pwd(password, user.password)) { 49 | const { username, password } = user; 50 | jwt.sign( 51 | { username, password }, 52 | config.secretOrKey, 53 | { expiresIn: 3600 }, 54 | (err, token) => { 55 | res.status(202).json({ 56 | msg: "Login Success!", 57 | token: token, 58 | user: user, 59 | status: true, 60 | }); 61 | } 62 | ); 63 | } else { 64 | res.status(401).json({ 65 | msg: "Password is incorrect.", 66 | user: user, 67 | }); 68 | } 69 | } 70 | } catch (error) { 71 | res.status(500).json({ error: "Server error" }); 72 | } 73 | }; 74 | 75 | // make a controller for admin 76 | exports.defaultAdmin = async (req, res) => { 77 | const adminData = config.adminData; 78 | const AdminUser = new User(adminData); 79 | AdminUser.password = await AdminUser.hide_pwd(adminData.password); 80 | 81 | User.findOne({ username: adminData.username }) 82 | .then(user => { 83 | console.log("Admin user already exist! -- " + user.username); 84 | }) 85 | .catch(() => { 86 | AdminUser.save() 87 | .then(() => console.log("---Create admin successfully.---")) 88 | .catch(() => { 89 | console.log("---Create admin successfully.---"); 90 | }); 91 | }) 92 | }; 93 | -------------------------------------------------------------------------------- /server/src/db/connect.js: -------------------------------------------------------------------------------- 1 | // external modules import 2 | const mongoose = require('mongoose'); 3 | 4 | const connectionString = 'mongodb://localhost:27017/team231'; 5 | 6 | const connectDatabase = async () => { 7 | try { 8 | await mongoose 9 | .connect(connectionString, { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true, 12 | }) 13 | .then(() => { 14 | console.log('Connected to MongoDB database successfully.'); 15 | }) 16 | .catch(error => { 17 | console.log('Error connecting to MongoDB: ', error.message); 18 | }); 19 | } catch (error) { 20 | console.log('Database connection error: ', error.message); 21 | } 22 | }; 23 | 24 | module.exports = connectDatabase; 25 | -------------------------------------------------------------------------------- /server/src/middleware/authorization.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const config = require("../configs/config"); 3 | 4 | module.exports.requireUser = (req, res, next) => { 5 | const token = req.header("authorization").replace("bareer ", ""); 6 | 7 | jwt.verify(token, config.secretOrKey, function (err, decoded) { 8 | if (err) res.status(419).json(err); 9 | else { 10 | req.user = decoded; 11 | next(); 12 | } 13 | }); 14 | }; 15 | 16 | module.exports.requireSeller = (req, res, next) => { 17 | const token = req.header("authorization").replace("bareer ", ""); 18 | 19 | jwt.verify(token, config.secretOrKey, function (err, decoded) { 20 | if (err) res.status(419).json(err); 21 | else { 22 | if (decoded.role === "seller" || decoded.role === "admin") { 23 | req.user = decoded; 24 | next(); 25 | } else { 26 | res.status(419).json({ token: "Invalid Seller token!" }); 27 | } 28 | } 29 | }); 30 | }; 31 | 32 | module.exports.requireAdmin = (req, res, next) => { 33 | const token = req.header("authorization").replace("bareer ", ""); 34 | jwt.verify(token, config.secretOrKey, function (err, decoded) { 35 | if (err) res.status(419).json(err); 36 | else { 37 | if (decoded.role === "admin") { 38 | req.user = decoded; 39 | next(); 40 | } else { 41 | res.status(419).json({ token: "Invalid Admin token!" }); 42 | } 43 | } 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /server/src/models/adminModel/categoryModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const categorySchema = mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: true, 7 | }, 8 | delected: { 9 | type: Date, 10 | default: null, 11 | }, 12 | createdAt: { 13 | type: Date, 14 | default: Date.now, 15 | }, 16 | }); 17 | module.exports = mongoose.model('Category', categorySchema); 18 | -------------------------------------------------------------------------------- /server/src/models/articleModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const articleSchema = new mongoose.Schema({ 4 | from: { 5 | type: mongoose.Schema.ObjectId, 6 | ref: "User", 7 | required: true, 8 | }, 9 | title: { 10 | type: String, 11 | require: true, 12 | }, 13 | content: { 14 | type: String, 15 | required: true, 16 | }, 17 | favorite: [ 18 | { 19 | user: { 20 | type: mongoose.Schema.ObjectId, 21 | ref: "User", 22 | required: true, 23 | }, 24 | }, 25 | ], 26 | category: { type: String }, 27 | tags: [ 28 | { 29 | type: String, 30 | }, 31 | ], 32 | delected: { 33 | type: Date, 34 | default: null, 35 | }, 36 | complete: { 37 | type: Boolean, 38 | default: false, 39 | }, 40 | parent: { type: mongoose.Schema.ObjectId, ref: "Article", default: null }, 41 | comment: [ 42 | { 43 | ans: { 44 | type: mongoose.Schema.ObjectId, 45 | ref: "Article", 46 | required: true, 47 | }, 48 | }, 49 | ], 50 | createdAt: { 51 | type: Date, 52 | default: Date.now, 53 | }, 54 | }); 55 | 56 | module.exports = mongoose.model("Article", articleSchema); 57 | -------------------------------------------------------------------------------- /server/src/models/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const bcrypt = require("bcryptjs"); 3 | const userSchema = new mongoose.Schema({ 4 | username: { 5 | type: String, 6 | unique: [true, "Username already exist!"], 7 | required: [true, "Must be provided username"], 8 | }, 9 | name: { 10 | type: String, 11 | require: true, 12 | }, 13 | bio: { 14 | type: String, 15 | }, 16 | password: { 17 | type: String, 18 | require: true, 19 | }, 20 | role: { 21 | type: String, 22 | default: "user", 23 | }, 24 | avatar: { 25 | type: String, 26 | default: "/assets/avatars/default.jpg", 27 | }, 28 | followers: [ 29 | { 30 | user: { 31 | type: mongoose.Schema.ObjectId, 32 | ref: "User", 33 | required: true, 34 | }, 35 | }, 36 | ], 37 | following: [ 38 | { 39 | user: { 40 | type: mongoose.Schema.ObjectId, 41 | ref: "User", 42 | required: true, 43 | }, 44 | }, 45 | ], 46 | category: [String], 47 | delected: { 48 | type: Date, 49 | default: null, 50 | }, 51 | complete: { 52 | type: Boolean, 53 | default: true, 54 | }, 55 | createdAt: { 56 | type: Date, 57 | default: Date.now, 58 | }, 59 | }); 60 | 61 | userSchema.methods.hide_pwd = (password) => { 62 | let salt = bcrypt.genSaltSync(10); 63 | return bcrypt.hashSync(password, salt); 64 | }; 65 | 66 | userSchema.methods.show_pwd = (reqPwd, dbPwd) => { 67 | return bcrypt.compareSync(reqPwd, dbPwd); 68 | }; 69 | 70 | module.exports = mongoose.model("User", userSchema); 71 | -------------------------------------------------------------------------------- /server/src/routes/adminRoutes/categoryRoutes.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | 3 | // init middleware 4 | 5 | // init controller 6 | const categoryCtrl = require("../../controllers/adminControllers/categoryController"); 7 | 8 | const passport = require("passport"); 9 | const middleware = passport.authenticate("jwt", { session: false }); 10 | 11 | // Admin - Category 12 | router.post("/", middleware, categoryCtrl.createCategory); 13 | router.put("/:id", middleware, categoryCtrl.updateCategory); 14 | router.delete("/:id", middleware, categoryCtrl.deleteCategory); 15 | router.get("/all", middleware, categoryCtrl.getAllCategory); 16 | 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /server/src/routes/adminRoutes/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | 3 | //init Routes 4 | 5 | const userRoutes = require("./userRoutes"); 6 | const categoryRoutes = require("./categoryRoutes"); 7 | // email routes // 8 | 9 | //routes container 10 | const router = express.Router(); 11 | 12 | router.use("/user", userRoutes); 13 | router.use("/category", categoryRoutes); 14 | // email routes con// 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /server/src/routes/adminRoutes/userRoutes.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | 3 | // init middleware 4 | const { requireAdmin } = require("../../middleware/authorization"); 5 | 6 | // init controller 7 | const userManageCtrl = require("../../controllers/adminControllers/userController"); 8 | 9 | const passport = require("passport"); 10 | const middleware = passport.authenticate("jwt", { session: false }); 11 | 12 | // Admin - Users 13 | router.get("/all", userManageCtrl.allUser); 14 | router.put("/role/:id", middleware, userManageCtrl.permissionUser); 15 | router.delete("/:id", middleware, userManageCtrl.delUser); 16 | 17 | // User - Profile 18 | router.get("/:id", userManageCtrl.getUser); 19 | router.put("/:id", middleware, userManageCtrl.changeInfo); 20 | router.put("/password/:id", middleware, userManageCtrl.changePassword); 21 | router.put("/avatar/:id", userManageCtrl.changeAvatar); 22 | router.put("/follow/:id", userManageCtrl.addFollower); 23 | 24 | module.exports = router; 25 | -------------------------------------------------------------------------------- /server/src/routes/articleRoutes.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | 3 | // init controller 4 | const articleCtrl = require("../controllers/articleController"); 5 | 6 | const passport = require("passport"); 7 | const middleware = passport.authenticate("jwt", { session: false }); 8 | 9 | // routers // 10 | router.post("/create", middleware, articleCtrl.createArticle); 11 | router.put("/:id", middleware, articleCtrl.updateArticle); 12 | router.delete("/:id", middleware, articleCtrl.deleteArticle); 13 | router.get("/home", middleware, articleCtrl.getHomeArticles); 14 | router.get("/all", middleware, articleCtrl.getAllArticles); 15 | router.get("/my", middleware, articleCtrl.getMyArticles); 16 | router.get("/favorite", middleware, articleCtrl.getFavoriteArticles); 17 | router.get("/draft", middleware, articleCtrl.getDraftArticles); 18 | router.get("/:id", middleware, articleCtrl.getAArticle); 19 | router.put("/comment/:id", middleware, articleCtrl.addComment); 20 | router.put("/favorite/:id", middleware, articleCtrl.addFavorite); 21 | 22 | module.exports = router; 23 | -------------------------------------------------------------------------------- /server/src/routes/authRoutes.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | 3 | // init controller 4 | const authCtrl = require("../controllers/authController"); 5 | 6 | // routers // 7 | router.post("/signup", authCtrl.signup); 8 | router.post("/signin", authCtrl.signin); 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /server/src/routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | 3 | //init Routes 4 | 5 | const authRoutes = require("./authRoutes"); 6 | const articleRoutes = require("./articleRoutes"); 7 | const adminRoutes = require("./adminRoutes"); 8 | // email routes // 9 | 10 | //routes container 11 | const router = express.Router(); 12 | 13 | router.use("/auth", authRoutes); 14 | router.use("/article", articleRoutes); 15 | router.use("/admin", adminRoutes); 16 | // email routes con// 17 | 18 | module.exports = router; 19 | --------------------------------------------------------------------------------