├── .gitignore ├── README.md ├── client ├── .gitignore ├── README.md ├── build │ ├── asset-manifest.json │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ ├── precache-manifest.7f847caf055cd54d782f3c10f6faeaff.js │ ├── service-worker.js │ └── static │ │ ├── css │ │ ├── 2.c8b95c23.chunk.css │ │ ├── 2.c8b95c23.chunk.css.map │ │ ├── main.a3825a46.chunk.css │ │ └── main.a3825a46.chunk.css.map │ │ ├── js │ │ ├── 2.d1af4b66.chunk.js │ │ ├── 2.d1af4b66.chunk.js.map │ │ ├── main.069a4f16.chunk.js │ │ ├── main.069a4f16.chunk.js.map │ │ ├── runtime~main.a8a9905a.js │ │ └── runtime~main.a8a9905a.js.map │ │ └── media │ │ ├── fontawesome-webfont.674f50d2.eot │ │ ├── fontawesome-webfont.912ec66d.svg │ │ ├── fontawesome-webfont.af7ae505.woff2 │ │ ├── fontawesome-webfont.b06871f2.ttf │ │ └── fontawesome-webfont.fee66e71.woff ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── actions │ ├── authActions.js │ ├── types.js │ └── userActions.js │ ├── components │ ├── auth │ │ ├── Login.js │ │ └── Register.js │ ├── layout │ │ ├── Landing.js │ │ └── NotFound.js │ ├── pages │ │ ├── Dashboard.js │ │ └── Users.js │ ├── partials │ │ ├── Navbar.js │ │ ├── Sidebar.js │ │ ├── UserAddModal.js │ │ └── UserUpdateModal.js │ └── private-route │ │ └── PrivateRoute.js │ ├── index.css │ ├── index.js │ ├── reducers │ ├── authReducers.js │ ├── errorReducers.js │ └── index.js │ ├── store.js │ └── utils │ └── setAuthToken.js ├── config ├── keys.js └── passport.js ├── demo.gif ├── logo.svg ├── models └── User.js ├── package-lock.json ├── package.json ├── routes └── api │ └── users.js ├── server.js └── validation ├── login.js ├── register.js └── updateUser.js /.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 | /.idea 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## MERN-Admin-Panel 2 | 3 | ## Features 4 | * Login-page and Basic layout design done by using Bootstrap 5 | * JWT and Passport for Authentication 6 | * Datatable with Client-side & Server Side Pagination 7 | * Multi-sort 8 | * Filters 9 | * Minimal design 10 | * Fully controllable via optional props and callbacks 11 | 12 | ## Demo 13 | [Click Here](https://raw.githubusercontent.com/androidneha/mern-admin-panel/master/demo.gif) 14 | 15 | 16 | MERN-Admin-Panel 17 | 18 |
19 | 20 | ## Available Script 21 | To start server and client simultaneously 22 | 23 | `npm run dev` 24 | 25 | To Build react application 26 | 27 | cd client and run 28 | 29 | `npm run build` 30 | 31 | 32 | #### Datatable sample usage with static data 33 | 34 | ```js 35 | import React, { Component, Fragment } from 'react'; 36 | import { render} from 'react-dom'; 37 | import ReactDatatable from '@ashvin27/react-datatable'; 38 | 39 | class App extends Component { 40 | constructor(props) { 41 | super(props); 42 | this.columns = [ 43 | { 44 | key: "name", 45 | text: "Name", 46 | className: "name", 47 | align: "left", 48 | sortable: true, 49 | }, 50 | { 51 | key: "address", 52 | text: "Address", 53 | className: "address", 54 | align: "left", 55 | sortable: true 56 | }, 57 | { 58 | key: "postcode", 59 | text: "Postcode", 60 | className: "postcode", 61 | sortable: true 62 | }, 63 | { 64 | key: "rating", 65 | text: "Rating", 66 | className: "rating", 67 | align: "left", 68 | sortable: true 69 | }, 70 | { 71 | key: "type_of_food", 72 | text: "Type of Food", 73 | className: "type_of_food", 74 | sortable: true, 75 | align: "left" 76 | }, 77 | { 78 | key: "action", 79 | text: "Action", 80 | className: "action", 81 | width: 100, 82 | align: "left", 83 | sortable: false, 84 | cell: record => { 85 | return ( 86 | 87 | 93 | 98 | 99 | ); 100 | } 101 | } 102 | ]; 103 | this.config = { 104 | page_size: 10, 105 | length_menu: [ 10, 20, 50 ], 106 | button: { 107 | excel: true, 108 | print: true 109 | } 110 | } 111 | 112 | this.state = { 113 | records: [ 114 | { 115 | "id": "55f14312c7447c3da7051b26", 116 | "address": "228 City Road", 117 | "name": ".CN Chinese", 118 | "postcode": "3JH", 119 | "rating": 5, 120 | "type_of_food": "Chinese" 121 | }, 122 | { 123 | "id": "55f14312c7447c3da7051b27", 124 | "address": "376 Rayleigh Road", 125 | "name": "@ Thai", 126 | "postcode": "5PT", 127 | "rating": 5.5, 128 | "type_of_food": "Thai" 129 | }, 130 | { 131 | "id": "55f14312c7447c3da7051b28", 132 | "address": "30 Greyhound Road Hammersmith", 133 | "name": "@ Thai Restaurant", 134 | "postcode": "8NX", 135 | "rating": 4.5, 136 | "type_of_food": "Thai" 137 | }, 138 | { 139 | "id": "55f14312c7447c3da7051b29", 140 | "address": "30 Greyhound Road Hammersmith", 141 | "name": "@ Thai Restaurant", 142 | "postcode": "8NX", 143 | "rating": 4.5, 144 | "type_of_food": "Thai" 145 | } 146 | ] 147 | } 148 | } 149 | 150 | editRecord(record) { 151 | console.log("Edit Record", record); 152 | } 153 | 154 | deleteRecord(record) { 155 | console.log("Delete Record", record); 156 | } 157 | 158 | render() { 159 | return ( 160 |
161 | 166 |
167 | ) 168 | } 169 | } 170 | 171 | render(, document.getElementById("app")); 172 | ``` 173 | -------------------------------------------------------------------------------- /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 | # testing 8 | /coverage 9 | 10 | # misc 11 | .DS_Store 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /client/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.a3825a46.chunk.css", 4 | "main.js": "/static/js/main.069a4f16.chunk.js", 5 | "main.js.map": "/static/js/main.069a4f16.chunk.js.map", 6 | "runtime~main.js": "/static/js/runtime~main.a8a9905a.js", 7 | "runtime~main.js.map": "/static/js/runtime~main.a8a9905a.js.map", 8 | "static/css/2.c8b95c23.chunk.css": "/static/css/2.c8b95c23.chunk.css", 9 | "static/js/2.d1af4b66.chunk.js": "/static/js/2.d1af4b66.chunk.js", 10 | "static/js/2.d1af4b66.chunk.js.map": "/static/js/2.d1af4b66.chunk.js.map", 11 | "index.html": "/index.html", 12 | "precache-manifest.7f847caf055cd54d782f3c10f6faeaff.js": "/precache-manifest.7f847caf055cd54d782f3c10f6faeaff.js", 13 | "service-worker.js": "/service-worker.js", 14 | "static/css/2.c8b95c23.chunk.css.map": "/static/css/2.c8b95c23.chunk.css.map", 15 | "static/css/main.a3825a46.chunk.css.map": "/static/css/main.a3825a46.chunk.css.map", 16 | "static/media/font-awesome.css": "/static/media/fontawesome-webfont.fee66e71.woff" 17 | } 18 | } -------------------------------------------------------------------------------- /client/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidneha/mern-admin-panel/a3c9f8ddc3a09060f9d0f624275916bd9222bf27/client/build/favicon.ico -------------------------------------------------------------------------------- /client/build/index.html: -------------------------------------------------------------------------------- 1 | Admin Panel
-------------------------------------------------------------------------------- /client/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /client/build/precache-manifest.7f847caf055cd54d782f3c10f6faeaff.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "929594c82362127bc5e257d7c8df89d5", 4 | "url": "/index.html" 5 | }, 6 | { 7 | "revision": "fb7fbb8fb50f996b474e", 8 | "url": "/static/css/2.c8b95c23.chunk.css" 9 | }, 10 | { 11 | "revision": "b0edc77c3bc66da69123", 12 | "url": "/static/css/main.a3825a46.chunk.css" 13 | }, 14 | { 15 | "revision": "fb7fbb8fb50f996b474e", 16 | "url": "/static/js/2.d1af4b66.chunk.js" 17 | }, 18 | { 19 | "revision": "b0edc77c3bc66da69123", 20 | "url": "/static/js/main.069a4f16.chunk.js" 21 | }, 22 | { 23 | "revision": "42ac5946195a7306e2a5", 24 | "url": "/static/js/runtime~main.a8a9905a.js" 25 | }, 26 | { 27 | "revision": "674f50d287a8c48dc19ba404d20fe713", 28 | "url": "/static/media/fontawesome-webfont.674f50d2.eot" 29 | }, 30 | { 31 | "revision": "912ec66d7572ff821749319396470bde", 32 | "url": "/static/media/fontawesome-webfont.912ec66d.svg" 33 | }, 34 | { 35 | "revision": "af7ae505a9eed503f8b8e6982036873e", 36 | "url": "/static/media/fontawesome-webfont.af7ae505.woff2" 37 | }, 38 | { 39 | "revision": "b06871f281fee6b241d60582ae9369b9", 40 | "url": "/static/media/fontawesome-webfont.b06871f2.ttf" 41 | }, 42 | { 43 | "revision": "fee66e712a8a08eef5805a46892932ad", 44 | "url": "/static/media/fontawesome-webfont.fee66e71.woff" 45 | } 46 | ]); -------------------------------------------------------------------------------- /client/build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/precache-manifest.7f847caf055cd54d782f3c10f6faeaff.js" 18 | ); 19 | 20 | self.addEventListener('message', (event) => { 21 | if (event.data && event.data.type === 'SKIP_WAITING') { 22 | self.skipWaiting(); 23 | } 24 | }); 25 | 26 | workbox.core.clientsClaim(); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | 36 | workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), { 37 | 38 | blacklist: [/^\/_/,/\/[^\/]+\.[^\/]+$/], 39 | }); 40 | -------------------------------------------------------------------------------- /client/build/static/css/main.a3825a46.chunk.css: -------------------------------------------------------------------------------- 1 | body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}footer,header,main{padding-left:300px}@media only screen and (max-width:992px){footer,header,main{padding-left:0}}body{overflow-x:hidden}#sidebar-wrapper{min-height:100vh;margin-left:-15rem;-webkit-transition:margin .25s ease-out;transition:margin .25s ease-out}#sidebar-wrapper .sidebar-heading{padding:.875rem 1.25rem;font-size:1.2rem}#sidebar-wrapper .list-group{width:15rem}#page-content-wrapper{min-width:100vw}#wrapper.toggled #sidebar-wrapper{margin-left:0}@media (min-width:768px){#sidebar-wrapper{margin-left:0}#page-content-wrapper{min-width:0;width:100%}#wrapper.toggled #sidebar-wrapper{margin-left:-15rem}}#as-react-datatable-container select{width:70px!important} 2 | /*# sourceMappingURL=main.a3825a46.chunk.css.map */ -------------------------------------------------------------------------------- /client/build/static/css/main.a3825a46.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["index.css","App.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,mIAEU,CACV,kCAAmC,CACnC,iCACF,CAEA,KACE,uEAEF,CAEA,mBACE,kBACF,CAEA,yCACE,mBACE,cACF,CACF,CCtBA,KACI,iBACJ,CAEA,iBACI,gBAAiB,CACjB,kBAAmB,CACnB,uCAAwC,CAGxC,+BACJ,CAEA,kCACI,uBAAyB,CACzB,gBACJ,CAEA,6BACI,WACJ,CAEA,sBACI,eACJ,CAEA,kCACI,aACJ,CAEA,yBACI,iBACI,aACJ,CAEA,sBACI,WAAY,CACZ,UACJ,CAEA,kCACI,kBACJ,CACJ,CAEA,qCACI,oBACJ","file":"main.a3825a46.chunk.css","sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\n monospace;\n}\n\nheader, main, footer {\n padding-left: 300px;\n}\n\n@media only screen and (max-width : 992px) {\n header, main, footer {\n padding-left: 0;\n }\n}\n","body {\r\n overflow-x: hidden;\r\n}\r\n\r\n#sidebar-wrapper {\r\n min-height: 100vh;\r\n margin-left: -15rem;\r\n -webkit-transition: margin .25s ease-out;\r\n -moz-transition: margin .25s ease-out;\r\n -o-transition: margin .25s ease-out;\r\n transition: margin .25s ease-out;\r\n}\r\n\r\n#sidebar-wrapper .sidebar-heading {\r\n padding: 0.875rem 1.25rem;\r\n font-size: 1.2rem;\r\n}\r\n\r\n#sidebar-wrapper .list-group {\r\n width: 15rem;\r\n}\r\n\r\n#page-content-wrapper {\r\n min-width: 100vw;\r\n}\r\n\r\n#wrapper.toggled #sidebar-wrapper {\r\n margin-left: 0;\r\n}\r\n\r\n@media (min-width: 768px) {\r\n #sidebar-wrapper {\r\n margin-left: 0;\r\n }\r\n\r\n #page-content-wrapper {\r\n min-width: 0;\r\n width: 100%;\r\n }\r\n\r\n #wrapper.toggled #sidebar-wrapper {\r\n margin-left: -15rem;\r\n }\r\n}\r\n\r\n#as-react-datatable-container select {\r\n width: 70px !important;\r\n}\r\n"]} -------------------------------------------------------------------------------- /client/build/static/js/main.069a4f16.chunk.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{58:function(e,a,t){"use strict";var n=t(3),r=t(4),s=t(8),l=t(6),o=t(9),c=t(12),i=t(24),m=t(0),d=t.n(m),u=t(7),p=t(16),h=t.n(p),b=function(e){e?h.a.defaults.headers.common.Authorization=e:delete h.a.defaults.headers.common.Authorization},E=t(30),g=t.n(E),v=function(e){return{type:"SET_CURRENT_USER",payload:e}},f=function(){return function(e){localStorage.removeItem("jwtToken"),b(!1),e(v({}))}},N=t(20),w=t(26),y=function(e){function a(){var e,t;Object(n.a)(this,a);for(var r=arguments.length,o=new Array(r),c=0;c0&&void 0!==arguments[0]?arguments[0]:B,a=arguments.length>1?arguments[1]:void 0;switch(a.type){case"USER_ADD":case"USER_UPDATE":return{isAuthenticated:!z(a.payload),user:a.payload};case"SET_CURRENT_USER":return Object(V.a)({},e,{isAuthenticated:!z(a.payload),user:a.payload});case"USER_LOADING":return Object(V.a)({},e,{loading:!0});default:return e}},errors:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:J,a=arguments.length>1?arguments[1]:void 0;switch(a.type){case"GET_ERRORS":return a.payload;default:return e}}}),$=[M.a],q=Object(W.e)(Q,{},Object(W.d)(W.a.apply(void 0,$))),H=(t(92),t(93),t(94),t(96),t(97),t(98),t(29)),K=t(55),X=t.n(K),Y=t(21),Z=t(11),ee=t.n(Z),ae=(t(48),function(e){function a(){var e;return Object(n.a)(this,a),(e=Object(s.a)(this,Object(l.a)(a).call(this))).onChange=function(a){e.setState(Object(U.a)({},a.target.id,a.target.value))},e.onUserAdd=function(a){a.preventDefault();var t={name:e.state.name,email:e.state.email,password:e.state.password,password2:e.state.password2};e.props.addUser(t,e.props.history)},e.state={name:"",email:"",password:"",password2:"",errors:{}},e}return Object(o.a)(a,e),Object(r.a)(a,[{key:"componentWillReceiveProps",value:function(e){e.errors&&this.setState({errors:e.errors}),void 0!==e.auth&&void 0!==e.auth.user&&void 0!==e.auth.user.data&&void 0!==e.auth.user.data.message&&(ee()("#add-user-modal").modal("hide"),Object(Y.b)(e.auth.user.data.message,{position:Y.b.POSITION.TOP_CENTER}))}},{key:"render",value:function(){var e=this.state.errors;return d.a.createElement("div",null,d.a.createElement("div",{className:"modal fade",id:"add-user-modal","data-reset":"true"},d.a.createElement("div",{className:"modal-dialog modal-lg"},d.a.createElement("div",{className:"modal-content"},d.a.createElement("div",{className:"modal-header"},d.a.createElement("h4",{className:"modal-title"},"Add User"),d.a.createElement("button",{type:"button",className:"close","data-dismiss":"modal"},"\xd7")),d.a.createElement("div",{className:"modal-body"},d.a.createElement("form",{noValidate:!0,onSubmit:this.onUserAdd,id:"add-user"},d.a.createElement("div",{className:"row mt-2"},d.a.createElement("div",{className:"col-md-3"},d.a.createElement("label",{htmlFor:"name"},"Name")),d.a.createElement("div",{className:"col-md-9"},d.a.createElement("input",{onChange:this.onChange,value:this.state.name,id:"name",type:"text",error:e.name,className:D()("form-control",{invalid:e.name})}),d.a.createElement("span",{className:"text-danger"},e.name))),d.a.createElement("div",{className:"row mt-2"},d.a.createElement("div",{className:"col-md-3"},d.a.createElement("label",{htmlFor:"email"},"Email")),d.a.createElement("div",{className:"col-md-9"},d.a.createElement("input",{onChange:this.onChange,value:this.state.email,error:e.email,id:"email",type:"email",className:D()("form-control",{invalid:e.email})}),d.a.createElement("span",{className:"text-danger"},e.email))),d.a.createElement("div",{className:"row mt-2"},d.a.createElement("div",{className:"col-md-3"},d.a.createElement("label",{htmlFor:"password"},"Password")),d.a.createElement("div",{className:"col-md-9"},d.a.createElement("input",{autoComplete:"",onChange:this.onChange,value:this.state.password,error:e.password,id:"password",type:"password",className:D()("form-control",{invalid:e.password})}),d.a.createElement("span",{className:"text-danger"},e.password))),d.a.createElement("div",{className:"row mt-2"},d.a.createElement("div",{className:"col-md-3"},d.a.createElement("label",{htmlFor:"password2"},"Confirm Password")),d.a.createElement("div",{className:"col-md-9"},d.a.createElement("input",{autoComplete:"",onChange:this.onChange,value:this.state.password2,id:"password2",type:"password",className:D()("form-control",{invalid:e.password2})}),d.a.createElement("span",{className:"text-danger"},e.password2))))),d.a.createElement("div",{className:"modal-footer"},d.a.createElement("button",{type:"button",className:"btn btn-secondary","data-dismiss":"modal"},"Close"),d.a.createElement("button",{form:"add-user",type:"submit",className:"btn btn-primary"},"Add User"))))))}}]),a}(d.a.Component)),te=Object(u.b)(function(e){return{auth:e.auth,errors:e.errors}},{addUser:function(e,a){return function(a){h.a.post("/api/user-add",e).then(function(e){return a({type:"USER_ADD",payload:e})}).catch(function(e){return a({type:"GET_ERRORS",payload:e.response.data})})}}})(Object(i.g)(ae)),ne=function(e){function a(e){var t;return Object(n.a)(this,a),(t=Object(s.a)(this,Object(l.a)(a).call(this,e))).onChange=function(e){"user-update-name"===e.target.id&&t.setState({name:e.target.value}),"user-update-email"===e.target.id&&t.setState({email:e.target.value}),"user-update-password"===e.target.id&&t.setState({password:e.target.value})},t.onUserUpdate=function(e){e.preventDefault();var a={_id:t.state.id,name:t.state.name,email:t.state.email,password:t.state.password};t.props.updateUser(a)},t.state={id:t.props.record.id,name:t.props.record.name,email:t.props.record.email,password:"",errors:{}},t}return Object(o.a)(a,e),Object(r.a)(a,[{key:"componentWillReceiveProps",value:function(e){e.record&&this.setState({id:e.record.id,name:e.record.name,email:e.record.email}),e.errors&&this.setState({errors:e.errors}),void 0!==e.auth&&void 0!==e.auth.user&&void 0!==e.auth.user.data&&void 0!==e.auth.user.data.message&&e.auth.user.data.success&&(ee()("#update-user-modal").modal("hide"),Object(Y.b)(e.auth.user.data.message,{position:Y.b.POSITION.TOP_CENTER}))}},{key:"render",value:function(){var e=this.state.errors;return d.a.createElement("div",null,d.a.createElement("div",{className:"modal fade",id:"update-user-modal"},d.a.createElement("div",{className:"modal-dialog modal-lg"},d.a.createElement("div",{className:"modal-content"},d.a.createElement("div",{className:"modal-header"},d.a.createElement("h4",{className:"modal-title"},"Update User"),d.a.createElement("button",{type:"button",className:"close","data-dismiss":"modal"},"\xd7")),d.a.createElement("div",{className:"modal-body"},d.a.createElement("form",{noValidate:!0,onSubmit:this.onUserUpdate,id:"update-user"},d.a.createElement("input",{onChange:this.onChange,value:this.state.id,id:"user-update-id",type:"text",className:"d-none"}),d.a.createElement("div",{className:"row mt-2"},d.a.createElement("div",{className:"col-md-3"},d.a.createElement("label",{htmlFor:"name"},"Name")),d.a.createElement("div",{className:"col-md-9"},d.a.createElement("input",{onChange:this.onChange,value:this.state.name,id:"user-update-name",type:"text",error:e.name,className:D()("form-control",{invalid:e.name})}),d.a.createElement("span",{className:"text-danger"},e.name))),d.a.createElement("div",{className:"row mt-2"},d.a.createElement("div",{className:"col-md-3"},d.a.createElement("label",{htmlFor:"email"},"Email")),d.a.createElement("div",{className:"col-md-9"},d.a.createElement("input",{onChange:this.onChange,value:this.state.email,error:e.email,id:"user-update-email",type:"email",className:D()("form-control",{invalid:e.email})}),d.a.createElement("span",{className:"text-danger"},e.email))),d.a.createElement("div",{className:"row mt-2"},d.a.createElement("div",{className:"col-md-3"},d.a.createElement("label",{htmlFor:"password"},"Password")),d.a.createElement("div",{className:"col-md-9"},d.a.createElement("input",{"data-reset-input":!0,autoComplete:"",onChange:this.onChange,error:e.password,id:"user-update-password",type:"password",className:D()("form-control",{invalid:e.password})}),d.a.createElement("span",{className:"text-danger"},e.password))))),d.a.createElement("div",{className:"modal-footer"},d.a.createElement("button",{type:"button",className:"btn btn-secondary","data-dismiss":"modal"},"Close"),d.a.createElement("button",{form:"update-user",type:"submit",className:"btn btn-primary"},"Update User"))))))}}]),a}(d.a.Component),re=Object(u.b)(function(e){return{auth:e.auth,errors:e.errors}},{updateUser:function(e){return function(a){h.a.post("/api/user-update",e).then(function(e){return a({type:"USER_UPDATE",payload:e})}).catch(function(e){return a({type:"GET_ERRORS",payload:e.response.data})})}}})(Object(i.g)(ne)),se=function(e){function a(e){var t;return Object(n.a)(this,a),(t=Object(s.a)(this,Object(l.a)(a).call(this,e))).columns=[{key:"_id",text:"Id",className:"id",align:"left",sortable:!0},{key:"name",text:"Name",className:"name",align:"left",sortable:!0},{key:"email",text:"Email",className:"email",align:"left",sortable:!0},{key:"date",text:"Date",className:"date",align:"left",sortable:!0},{key:"action",text:"Action",className:"action",width:100,align:"left",sortable:!1,cell:function(e){return d.a.createElement(m.Fragment,null,d.a.createElement("button",{"data-toggle":"modal","data-target":"#update-user-modal",className:"btn btn-primary btn-sm",onClick:function(){return t.editRecord(e)},style:{marginRight:"5px"}},d.a.createElement("i",{className:"fa fa-edit"})),d.a.createElement("button",{className:"btn btn-danger btn-sm",onClick:function(){return t.deleteRecord(e)}},d.a.createElement("i",{className:"fa fa-trash"})))}}],t.config={page_size:10,length_menu:[10,20,50],filename:"Users",no_data_text:"No user found!",button:{excel:!0,print:!0,csv:!0},language:{length_menu:"Show _MENU_ result per page",filter:"Filter in records...",info:"Showing _START_ to _END_ of _TOTAL_ records",pagination:{first:"First",previous:"Previous",next:"Next",last:"Last"}},show_length_menu:!0,show_filter:!0,show_pagination:!0,show_info:!0},t.state={records:[]},t.state={currentRecord:{id:"",name:"",email:"",password:"",password2:""}},t.getData=t.getData.bind(Object(H.a)(t)),t}return Object(o.a)(a,e),Object(r.a)(a,[{key:"componentDidMount",value:function(){this.getData()}},{key:"componentWillReceiveProps",value:function(e){this.getData()}},{key:"getData",value:function(){var e=this;h.a.post("/api/user-data").then(function(a){e.setState({records:a.data})}).catch()}},{key:"editRecord",value:function(e){this.setState({currentRecord:e})}},{key:"deleteRecord",value:function(e){h.a.post("/api/user-delete",{_id:e._id}).then(function(e){200===e.status&&Object(Y.b)(e.data.message,{position:Y.b.POSITION.TOP_CENTER})}).catch(),this.getData()}},{key:"pageChange",value:function(e){console.log("OnPageChange",e)}},{key:"render",value:function(){return d.a.createElement("div",null,d.a.createElement(O,null),d.a.createElement("div",{className:"d-flex",id:"wrapper"},d.a.createElement(C,null),d.a.createElement(te,null),d.a.createElement(re,{record:this.state.currentRecord}),d.a.createElement("div",{id:"page-content-wrapper"},d.a.createElement("div",{className:"container-fluid"},d.a.createElement("button",{className:"btn btn-link mt-3",id:"menu-toggle"},d.a.createElement(N.a,{icon:x.faList})),d.a.createElement("button",{className:"btn btn-outline-primary float-right mt-3 mr-2","data-toggle":"modal","data-target":"#add-user-modal"},d.a.createElement(N.a,{icon:w.a})," Add User"),d.a.createElement("h1",{className:"mt-2 text-primary"},"Users List"),d.a.createElement(X.a,{config:this.config,records:this.state.records,columns:this.columns,onPageChange:this.pageChange.bind(this)}))),d.a.createElement(Y.a,null)))}}]),a}(m.Component),le=Object(u.b)(function(e){return{auth:e.auth,records:e.records}})(se);if(localStorage.jwtToken){var oe=localStorage.jwtToken;b(oe);var ce=g()(oe);q.dispatch(v(ce));var ie=Date.now()/1e3;ce.exp {\r\n if (token) {\r\n axios.defaults.headers.common[\"Authorization\"] = token;\r\n } else {\r\n delete axios.defaults.headers.common[\"Authorization\"];\r\n }\r\n};\r\nexport default setAuthToken;","import axios from \"axios\";\r\nimport setAuthToken from \"../utils/setAuthToken\";\r\nimport jwt_decode from \"jwt-decode\";\r\nimport {\r\n GET_ERRORS,\r\n SET_CURRENT_USER,\r\n USER_LOADING\r\n} from \"./types\";\r\n\r\nexport const registerUser = (userData, history) => dispatch => {\r\n axios\r\n .post(\"/api/register\", userData)\r\n .then(res => history.push(\"/login\"))\r\n .catch(err =>\r\n dispatch({\r\n type: GET_ERRORS,\r\n payload: err.response.data\r\n })\r\n );\r\n};\r\n\r\nexport const loginUser = userData => dispatch => {\r\n axios\r\n .post(\"/api/login\", userData)\r\n .then(res => {\r\n const { token } = res.data;\r\n localStorage.setItem(\"jwtToken\", token);\r\n setAuthToken(token);\r\n const decoded = jwt_decode(token);\r\n dispatch(setCurrentUser(decoded));\r\n })\r\n .catch(err =>\r\n dispatch({\r\n type: GET_ERRORS,\r\n payload: err.response.data\r\n })\r\n );\r\n};\r\n\r\nexport const setCurrentUser = decoded => {\r\n return {\r\n type: SET_CURRENT_USER,\r\n payload: decoded\r\n };\r\n};\r\n\r\nexport const setUserLoading = () => {\r\n return {\r\n type: USER_LOADING\r\n };\r\n};\r\n\r\nexport const logoutUser = () => dispatch => {\r\n localStorage.removeItem(\"jwtToken\");\r\n setAuthToken(false);\r\n dispatch(setCurrentUser({}));\r\n};\r\n","export const GET_ERRORS = \"GET_ERRORS\";\r\nexport const USER_LOADING = \"USER_LOADING\";\r\nexport const SET_CURRENT_USER = \"SET_CURRENT_USER\";\r\n\r\nexport const USER_ADD = \"USER_ADD\";\r\nexport const USER_UPDATE = \"USER_UPDATE\";\r\n","import React, { Component } from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport {FontAwesomeIcon} from \"@fortawesome/react-fontawesome\";\r\nimport {faSignOutAlt} from \"@fortawesome/free-solid-svg-icons\";\r\nimport {connect} from \"react-redux\";\r\nimport {logoutUser} from \"../../actions/authActions\";\r\n\r\nclass Navbar extends Component {\r\n\r\n onLogoutClick = e => {\r\n e.preventDefault();\r\n this.props.logoutUser();\r\n };\r\n\r\n render() {\r\n const { user } = this.props.auth;\r\n return (\r\n
\r\n \r\n
\r\n );\r\n }\r\n}\r\n\r\nNavbar.propTypes = {\r\n logoutUser: PropTypes.func.isRequired,\r\n auth: PropTypes.object.isRequired\r\n};\r\n\r\nconst mapStateToProps = state => ({\r\n auth: state.auth\r\n});\r\n\r\nexport default connect(\r\n mapStateToProps,\r\n { logoutUser }\r\n)(Navbar);\r\n","import React, { Component } from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport {FontAwesomeIcon} from \"@fortawesome/react-fontawesome\";\r\nimport {faSignOutAlt} from \"@fortawesome/free-solid-svg-icons\";\r\nimport {connect} from \"react-redux\";\r\nimport {logoutUser} from \"../../actions/authActions\";\r\nimport {Link} from \"react-router-dom\";\r\n\r\nclass Sidebar extends Component {\r\n\r\n onLogoutClick = e => {\r\n e.preventDefault();\r\n this.props.logoutUser();\r\n };\r\n\r\n render() {\r\n //const { user } = this.props.auth;\r\n return (\r\n
\r\n
\r\n Dashboard\r\n Users\r\n Events\r\n \r\n
\r\n
\r\n );\r\n }\r\n}\r\n\r\nSidebar.propTypes = {\r\n logoutUser: PropTypes.func.isRequired,\r\n auth: PropTypes.object.isRequired\r\n};\r\n\r\nconst mapStateToProps = state => ({\r\n auth: state.auth\r\n});\r\n\r\nexport default connect(\r\n mapStateToProps,\r\n { logoutUser }\r\n)(Sidebar);\r\n","import React, { Component } from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { logoutUser } from \"../../actions/authActions\";\r\nimport Navbar from \"../partials/Navbar\";\r\nimport Sidebar from \"../partials/Sidebar\";\r\nimport {FontAwesomeIcon} from \"@fortawesome/react-fontawesome\";\r\nimport {faList} from \"@fortawesome/free-solid-svg-icons/faList\";\r\nimport {Link} from \"react-router-dom\";\r\nimport {faUserAlt} from \"@fortawesome/free-solid-svg-icons/faUserAlt\";\r\n\r\nclass Dashboard extends Component {\r\n\r\n onLogoutClick = e => {\r\n e.preventDefault();\r\n this.props.logoutUser();\r\n };\r\n\r\n render() {\r\n //const { user } = this.props.auth;\r\n return (\r\n
\r\n \r\n
\r\n \r\n
\r\n
\r\n \r\n

Dashboard

\r\n
\r\n
\r\n
\r\n
\r\n
Users
\r\n

With supporting text below as a natural lead-in to\r\n additional content.

\r\n Go to Users\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
Special title treatment
\r\n

With supporting text below as a natural lead-in to\r\n additional content.

\r\n Go somewhere\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
Special title treatment
\r\n

With supporting text below as a natural lead-in to\r\n additional content.

\r\n Go somewhere\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
Special title treatment
\r\n

With supporting text below as a natural lead-in to\r\n additional content.

\r\n Go somewhere\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n );\r\n }\r\n}\r\n\r\nDashboard.propTypes = {\r\n logoutUser: PropTypes.func.isRequired,\r\n auth: PropTypes.object.isRequired\r\n};\r\n\r\nconst mapStateToProps = state => ({\r\n auth: state.auth\r\n});\r\n\r\nexport default connect(\r\n mapStateToProps,\r\n { logoutUser }\r\n)(Dashboard);\r\n","import React, { Component } from \"react\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { loginUser } from \"../../actions/authActions\";\r\nimport classnames from \"classnames\";\r\n\r\nclass Login extends Component {\r\n constructor() {\r\n super();\r\n this.state = {\r\n email: \"\",\r\n password: \"\",\r\n errors: {}\r\n };\r\n }\r\n\r\n componentDidMount() {\r\n if (this.props.auth.isAuthenticated) {\r\n this.props.history.push(\"/dashboard\");\r\n }\r\n };\r\n\r\n componentWillReceiveProps(nextProps) {\r\n if (nextProps.auth.isAuthenticated) {\r\n this.props.history.push(\"/dashboard\");\r\n }\r\n\r\n if (nextProps.errors) {\r\n this.setState({\r\n errors: nextProps.errors\r\n });\r\n }\r\n }\r\n\r\n onChange = e => {\r\n this.setState({ [e.target.id]: e.target.value });\r\n };\r\n\r\n onSubmit = e => {\r\n e.preventDefault();\r\n const userData = {\r\n email: this.state.email,\r\n password: this.state.password\r\n };\r\n this.props.loginUser(userData);\r\n };\r\n\r\n render() {\r\n const { errors } = this.state;\r\n return (\r\n
\r\n
\r\n
\r\n
\r\n

Login

\r\n
\r\n \r\n \r\n {errors.email}\r\n
\r\n \r\n \r\n {errors.password}\r\n

\r\n \r\n Login\r\n \r\n

\r\n \r\n
\r\n
\r\n
\r\n
\r\n );\r\n }\r\n}\r\n\r\nLogin.propTypes = {\r\n loginUser: PropTypes.func.isRequired,\r\n auth: PropTypes.object.isRequired,\r\n errors: PropTypes.object.isRequired\r\n};\r\nconst mapStateToProps = state => ({\r\n auth: state.auth,\r\n errors: state.errors\r\n});\r\nexport default connect(\r\n mapStateToProps,\r\n { loginUser }\r\n)(Login);\r\n","import React, { Component } from \"react\";\r\nclass Landing extends Component {\r\n render() {\r\n return (\r\n
\r\n
\r\n
\r\n

\r\n Not Found\r\n

\r\n
\r\n
\r\n
\r\n );\r\n }\r\n}\r\nexport default Landing;\r\n","import React from \"react\";\r\nimport { Route, Redirect } from \"react-router-dom\";\r\nimport { connect } from \"react-redux\";\r\nimport PropTypes from \"prop-types\";\r\n\r\nconst PrivateRoute = ({ component: Component, auth, ...rest }) => (\r\n \r\n auth.isAuthenticated === true ? (\r\n \r\n ) : (\r\n \r\n )\r\n }\r\n />\r\n);\r\n\r\nPrivateRoute.propTypes = {\r\n auth: PropTypes.object.isRequired\r\n};\r\n\r\nconst mapStateToProps = state => ({\r\n auth: state.auth\r\n});\r\n\r\nexport default connect(mapStateToProps)(PrivateRoute);\r\n","import React, { Component } from \"react\";\r\nimport { Link, withRouter } from \"react-router-dom\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { registerUser } from \"../../actions/authActions\";\r\nimport classnames from \"classnames\";\r\n\r\nclass Register extends Component {\r\n\r\n constructor() {\r\n super();\r\n this.state = {\r\n name: \"\",\r\n email: \"\",\r\n password: \"\",\r\n password2: \"\",\r\n errors: {}\r\n };\r\n }\r\n\r\n componentDidMount() {\r\n if (this.props.auth.isAuthenticated) {\r\n this.props.history.push(\"/dashboard\");\r\n }\r\n }\r\n\r\n componentWillReceiveProps(nextProps) {\r\n if (nextProps.errors) {\r\n this.setState({\r\n errors: nextProps.errors\r\n });\r\n }\r\n }\r\n\r\n onChange = e => {\r\n this.setState({ [e.target.id]: e.target.value });\r\n };\r\n\r\n onSubmit = e => {\r\n e.preventDefault();\r\n const newUser = {\r\n name: this.state.name,\r\n email: this.state.email,\r\n password: this.state.password,\r\n password2: this.state.password2\r\n };\r\n this.props.registerUser(newUser, this.props.history);\r\n };\r\n\r\n render() {\r\n const { errors } = this.state;\r\n return (\r\n
\r\n
\r\n
\r\n \r\n keyboard_backspace Back to\r\n home\r\n \r\n
\r\n

\r\n Register below\r\n

\r\n

\r\n Already have an account? Log in\r\n

\r\n
\r\n
\r\n
\r\n \r\n \r\n {errors.name}\r\n
\r\n
\r\n \r\n \r\n {errors.email}\r\n
\r\n
\r\n \r\n \r\n {errors.password}\r\n
\r\n
\r\n \r\n \r\n {errors.password2}\r\n
\r\n
\r\n \r\n Sign up\r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n );\r\n }\r\n}\r\n\r\nRegister.propTypes = {\r\n registerUser: PropTypes.func.isRequired,\r\n auth: PropTypes.object.isRequired,\r\n errors: PropTypes.object.isRequired\r\n};\r\n\r\nconst mapStateToProps = state => ({\r\n auth: state.auth,\r\n errors: state.errors\r\n});\r\n\r\nexport default connect(\r\n mapStateToProps,\r\n { registerUser }\r\n)(withRouter(Register));","import {\r\n SET_CURRENT_USER,\r\n USER_ADD,\r\n USER_LOADING,\r\n USER_UPDATE\r\n} from \"../actions/types\";\r\nconst isEmpty = require(\"is-empty\");\r\nconst initialState = {\r\n isAuthenticated: false,\r\n user: {},\r\n loading: false,\r\n};\r\nexport default function(state = initialState, action) {\r\n switch (action.type) {\r\n case USER_ADD:\r\n return {\r\n isAuthenticated: !isEmpty(action.payload),\r\n user: action.payload\r\n };\r\n case USER_UPDATE:\r\n return {\r\n isAuthenticated: !isEmpty(action.payload),\r\n user: action.payload,\r\n };\r\n case SET_CURRENT_USER:\r\n return {\r\n ...state,\r\n isAuthenticated: !isEmpty(action.payload),\r\n user: action.payload\r\n };\r\n case USER_LOADING:\r\n return {\r\n ...state,\r\n loading: true\r\n };\r\n default:\r\n return state;\r\n }\r\n}\r\n","import { GET_ERRORS } from \"../actions/types\";\r\nconst initialState = {};\r\n\r\nexport default function(state = initialState, action) {\r\n switch (action.type) {\r\n case GET_ERRORS:\r\n return action.payload;\r\n default:\r\n return state;\r\n }\r\n}\r\n","import { combineReducers } from \"redux\";\r\nimport authReducer from \"./authReducers\";\r\nimport errorReducer from \"./errorReducers\";\r\nexport default combineReducers({\r\n auth: authReducer,\r\n errors: errorReducer\r\n});","import { createStore, applyMiddleware, compose } from \"redux\";\r\nimport thunk from \"redux-thunk\";\r\nimport rootReducer from \"./reducers\";\r\n\r\nconst initialState = {};\r\nconst middleware = [thunk];\r\n\r\nconst store = createStore(\r\n rootReducer,\r\n initialState,\r\n compose(\r\n applyMiddleware(...middleware),\r\n //window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()\r\n )\r\n);\r\nexport default store;\r\n","import React from 'react'\r\nimport classnames from \"classnames\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { addUser } from \"../../actions/userActions\";\r\nimport { withRouter } from \"react-router-dom\";\r\nimport { toast } from 'react-toastify';\r\nimport $ from 'jquery';\r\n\r\nimport 'react-toastify/dist/ReactToastify.css';\r\n\r\nclass UserAddModal extends React.Component {\r\n\r\n constructor() {\r\n super();\r\n this.state = {\r\n name: \"\",\r\n email: \"\",\r\n password: \"\",\r\n password2: \"\",\r\n errors: {},\r\n };\r\n }\r\n\r\n componentWillReceiveProps(nextProps) {\r\n if (nextProps.errors) {\r\n this.setState({\r\n errors: nextProps.errors\r\n });\r\n }\r\n if (nextProps.auth !== undefined\r\n && nextProps.auth.user !== undefined\r\n && nextProps.auth.user.data !== undefined\r\n && nextProps.auth.user.data.message !== undefined) {\r\n $('#add-user-modal').modal('hide');\r\n toast(nextProps.auth.user.data.message, {\r\n position: toast.POSITION.TOP_CENTER\r\n });\r\n }\r\n }\r\n\r\n onChange = e => {\r\n this.setState({ [e.target.id]: e.target.value });\r\n };\r\n\r\n onUserAdd = e => {\r\n e.preventDefault();\r\n const newUser = {\r\n name: this.state.name,\r\n email: this.state.email,\r\n password: this.state.password,\r\n password2: this.state.password2\r\n };\r\n this.props.addUser(newUser, this.props.history);\r\n };\r\n\r\n render() {\r\n const { errors } = this.state;\r\n return (\r\n
\r\n
\r\n
\r\n
\r\n
\r\n

Add User

\r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n {errors.name}\r\n
\r\n
\r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n {errors.email}\r\n
\r\n
\r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n {errors.password}\r\n
\r\n
\r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n {errors.password2}\r\n
\r\n
\r\n
\r\n
\r\n
\r\n \r\n \r\n Add User\r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n )\r\n }\r\n}\r\n\r\nUserAddModal.propTypes = {\r\n addUser: PropTypes.func.isRequired,\r\n auth: PropTypes.object.isRequired,\r\n errors: PropTypes.object.isRequired\r\n};\r\n\r\nconst mapStateToProps = state => ({\r\n auth: state.auth,\r\n errors: state.errors\r\n});\r\n\r\nexport default connect(\r\n mapStateToProps,\r\n { addUser }\r\n)(withRouter(UserAddModal));\r\n","import axios from \"axios\";\r\nimport {\r\n GET_ERRORS,\r\n USER_ADD,\r\n USER_UPDATE\r\n} from \"./types\";\r\n\r\nexport const addUser = (userData, history) => dispatch => {\r\n axios\r\n .post(\"/api/user-add\", userData)\r\n .then(res =>\r\n dispatch({\r\n type: USER_ADD,\r\n payload: res,\r\n })\r\n ).catch(err =>\r\n dispatch({\r\n type: GET_ERRORS,\r\n payload: err.response.data\r\n })\r\n );\r\n};\r\n\r\n\r\nexport const updateUser = (userData) => dispatch => {\r\n axios\r\n .post(\"/api/user-update\", userData)\r\n .then(res =>\r\n dispatch({\r\n type: USER_UPDATE,\r\n payload: res,\r\n })\r\n ).catch(err =>\r\n dispatch({\r\n type: GET_ERRORS,\r\n payload: err.response.data\r\n })\r\n );\r\n};\r\n","import React from 'react'\r\nimport classnames from \"classnames\";\r\nimport PropTypes from \"prop-types\";\r\nimport { connect } from \"react-redux\";\r\nimport { updateUser } from \"../../actions/userActions\";\r\nimport { withRouter } from \"react-router-dom\";\r\nimport { toast } from 'react-toastify';\r\nimport $ from 'jquery';\r\n\r\nimport 'react-toastify/dist/ReactToastify.css';\r\n\r\nclass UserUpdateModal extends React.Component {\r\n\r\n constructor(props) {\r\n super(props);\r\n this.state = {\r\n id: this.props.record.id,\r\n name: this.props.record.name,\r\n email: this.props.record.email,\r\n password: '',\r\n errors: {},\r\n };\r\n }\r\n\r\n componentWillReceiveProps(nextProps) {\r\n if (nextProps.record) {\r\n this.setState({\r\n id: nextProps.record.id,\r\n name: nextProps.record.name,\r\n email: nextProps.record.email,\r\n })\r\n }\r\n if (nextProps.errors) {\r\n this.setState({\r\n errors: nextProps.errors\r\n });\r\n }\r\n if (nextProps.auth !== undefined\r\n && nextProps.auth.user !== undefined\r\n && nextProps.auth.user.data !== undefined\r\n && nextProps.auth.user.data.message !== undefined\r\n && nextProps.auth.user.data.success) {\r\n $('#update-user-modal').modal('hide');\r\n toast(nextProps.auth.user.data.message, {\r\n position: toast.POSITION.TOP_CENTER\r\n });\r\n }\r\n }\r\n\r\n onChange = e => {\r\n if (e.target.id === 'user-update-name') {\r\n this.setState({ name: e.target.value });\r\n }\r\n if (e.target.id === 'user-update-email') {\r\n this.setState({ email: e.target.value });\r\n }\r\n if (e.target.id === 'user-update-password') {\r\n this.setState({ password: e.target.value });\r\n }\r\n };\r\n\r\n onUserUpdate = e => {\r\n e.preventDefault();\r\n const newUser = {\r\n _id: this.state.id,\r\n name: this.state.name,\r\n email: this.state.email,\r\n password: this.state.password\r\n };\r\n this.props.updateUser(newUser);\r\n };\r\n\r\n render() {\r\n const { errors } = this.state;\r\n return (\r\n
\r\n
\r\n
\r\n
\r\n
\r\n

Update User

\r\n \r\n
\r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n {errors.name}\r\n
\r\n
\r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n {errors.email}\r\n
\r\n
\r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n {errors.password}\r\n
\r\n
\r\n \r\n
\r\n
\r\n \r\n \r\n Update User\r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n )\r\n }\r\n}\r\n\r\nUserUpdateModal.propTypes = {\r\n updateUser: PropTypes.func.isRequired,\r\n auth: PropTypes.object.isRequired,\r\n errors: PropTypes.object.isRequired\r\n};\r\n\r\nconst mapStateToProps = state => ({\r\n auth: state.auth,\r\n errors: state.errors\r\n});\r\n\r\nexport default connect(\r\n mapStateToProps,\r\n { updateUser }\r\n)(withRouter(UserUpdateModal));\r\n","import React, { Component, Fragment } from \"react\";\r\nimport Navbar from \"../partials/Navbar\";\r\nimport Sidebar from \"../partials/Sidebar\";\r\nimport {FontAwesomeIcon} from \"@fortawesome/react-fontawesome\";\r\nimport {faList} from \"@fortawesome/free-solid-svg-icons/faList\";\r\nimport ReactDatatable from '@ashvin27/react-datatable';\r\nimport PropTypes from \"prop-types\";\r\nimport {connect} from \"react-redux\";\r\nimport axios from \"axios\";\r\nimport {faPlus} from \"@fortawesome/free-solid-svg-icons\";\r\nimport UserAddModal from \"../partials/UserAddModal\";\r\nimport UserUpdateModal from \"../partials/UserUpdateModal\";\r\nimport { toast, ToastContainer} from \"react-toastify\";\r\n\r\nclass Users extends Component {\r\n\r\n constructor(props) {\r\n super(props);\r\n\r\n this.columns = [\r\n {\r\n key: \"_id\",\r\n text: \"Id\",\r\n className: \"id\",\r\n align: \"left\",\r\n sortable: true,\r\n },\r\n {\r\n key: \"name\",\r\n text: \"Name\",\r\n className: \"name\",\r\n align: \"left\",\r\n sortable: true,\r\n },\r\n {\r\n key: \"email\",\r\n text: \"Email\",\r\n className: \"email\",\r\n align: \"left\",\r\n sortable: true\r\n },\r\n {\r\n key: \"date\",\r\n text: \"Date\",\r\n className: \"date\",\r\n align: \"left\",\r\n sortable: true\r\n },\r\n {\r\n key: \"action\",\r\n text: \"Action\",\r\n className: \"action\",\r\n width: 100,\r\n align: \"left\",\r\n sortable: false,\r\n cell: record => {\r\n return (\r\n \r\n this.editRecord(record)}\r\n style={{marginRight: '5px'}}>\r\n \r\n \r\n this.deleteRecord(record)}>\r\n \r\n \r\n \r\n );\r\n }\r\n }\r\n ];\r\n\r\n this.config = {\r\n page_size: 10,\r\n length_menu: [ 10, 20, 50 ],\r\n filename: \"Users\",\r\n no_data_text: 'No user found!',\r\n button: {\r\n excel: true,\r\n print: true,\r\n csv: true\r\n },\r\n language: {\r\n length_menu: \"Show _MENU_ result per page\",\r\n filter: \"Filter in records...\",\r\n info: \"Showing _START_ to _END_ of _TOTAL_ records\",\r\n pagination: {\r\n first: \"First\",\r\n previous: \"Previous\",\r\n next: \"Next\",\r\n last: \"Last\"\r\n }\r\n },\r\n show_length_menu: true,\r\n show_filter: true,\r\n show_pagination: true,\r\n show_info: true,\r\n };\r\n\r\n this.state = {\r\n records: []\r\n };\r\n\r\n this.state = {\r\n currentRecord: {\r\n id: '',\r\n name: '',\r\n email: '',\r\n password: '',\r\n password2: '',\r\n }\r\n };\r\n\r\n this.getData = this.getData.bind(this);\r\n }\r\n\r\n componentDidMount() {\r\n this.getData()\r\n };\r\n\r\n componentWillReceiveProps(nextProps) {\r\n this.getData()\r\n }\r\n\r\n getData() {\r\n axios\r\n .post(\"/api/user-data\")\r\n .then(res => {\r\n this.setState({ records: res.data})\r\n })\r\n .catch()\r\n }\r\n\r\n editRecord(record) {\r\n this.setState({ currentRecord: record});\r\n }\r\n\r\n deleteRecord(record) {\r\n axios\r\n .post(\"/api/user-delete\", {_id: record._id})\r\n .then(res => {\r\n if (res.status === 200) {\r\n toast(res.data.message, {\r\n position: toast.POSITION.TOP_CENTER,\r\n })\r\n }\r\n })\r\n .catch();\r\n this.getData();\r\n }\r\n\r\n pageChange(pageData) {\r\n console.log(\"OnPageChange\", pageData);\r\n }\r\n\r\n render() {\r\n return (\r\n
\r\n \r\n
\r\n \r\n \r\n \r\n
\r\n
\r\n \r\n \r\n

Users List

\r\n \r\n
\r\n
\r\n \r\n
\r\n
\r\n );\r\n }\r\n\r\n}\r\n\r\nUsers.propTypes = {\r\n auth: PropTypes.object.isRequired,\r\n};\r\n\r\nconst mapStateToProps = state => ({\r\n auth: state.auth,\r\n records: state.records\r\n});\r\n\r\nexport default connect(\r\n mapStateToProps\r\n)(Users);\r\n","import { BrowserRouter as Router, Route, Switch } from \"react-router-dom\";\nimport Dashboard from \"./components/pages/Dashboard\";\nimport React, { Component } from 'react';\nimport Login from \"./components/auth/Login\";\nimport NotFound from \"./components/layout/NotFound\";\nimport { Provider } from \"react-redux\";\nimport PrivateRoute from \"./components/private-route/PrivateRoute\";\nimport Register from \"./components/auth/Register\";\nimport store from \"./store\";\nimport jwt_decode from \"jwt-decode\";\nimport setAuthToken from \"./utils/setAuthToken\";\nimport { setCurrentUser, logoutUser } from \"./actions/authActions\";\n\nimport './App.css';\nimport '../node_modules/bootstrap/dist/css/bootstrap.css';\nimport '../node_modules/bootstrap/dist/js/bootstrap';\nimport '../node_modules/font-awesome/css/font-awesome.css';\nimport '../node_modules/jquery/dist/jquery.min';\nimport '../node_modules/popper.js/dist/popper';\n\nimport User from \"./components/pages/Users\";\n\nif (localStorage.jwtToken) {\n const token = localStorage.jwtToken;\n setAuthToken(token);\n const decoded = jwt_decode(token);\n store.dispatch(setCurrentUser(decoded));\n const currentTime = Date.now() / 1000;\n if (decoded.exp < currentTime) {\n store.dispatch(logoutUser());\n window.location.href = \"./login\";\n }\n}\n\nclass App extends Component {\n render () {\n return (\n \n \n
\n \n \n \n \n \n \n \n \n \n \n
\n
\n
\n );\n }\n}\n\nexport default App;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport $ from \"jquery\";\nwindow.jQuery = $;\nwindow.$ = $;\nglobal.jQuery = $;\n\nReactDOM.render(, document.getElementById('root'));\n\n$(\"#menu-toggle\").click(function() {\n $(\"#wrapper\").toggleClass(\"toggled\");\n});\n\n$('.modal[data-reset=\"true\"]').on('shown.bs.modal', () =>\n $(\"input[name != 'timestamp']\").val(''));\n\n$('.modal').on('shown.bs.modal', () =>\n $('input[data-reset-input=\"true\"]').val(''));\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /client/build/static/js/runtime~main.a8a9905a.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c0.2%", 44 | "not dead", 45 | "not op_mini all" 46 | ], 47 | "development": [ 48 | "last 1 chrome version", 49 | "last 1 firefox version", 50 | "last 1 safari version" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidneha/mern-admin-panel/a3c9f8ddc3a09060f9d0f624275916bd9222bf27/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | Admin Panel 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow-x: hidden; 3 | } 4 | 5 | #sidebar-wrapper { 6 | min-height: 100vh; 7 | margin-left: -15rem; 8 | -webkit-transition: margin .25s ease-out; 9 | -moz-transition: margin .25s ease-out; 10 | -o-transition: margin .25s ease-out; 11 | transition: margin .25s ease-out; 12 | } 13 | 14 | #sidebar-wrapper .sidebar-heading { 15 | padding: 0.875rem 1.25rem; 16 | font-size: 1.2rem; 17 | } 18 | 19 | #sidebar-wrapper .list-group { 20 | width: 15rem; 21 | } 22 | 23 | #page-content-wrapper { 24 | min-width: 100vw; 25 | } 26 | 27 | #wrapper.toggled #sidebar-wrapper { 28 | margin-left: 0; 29 | } 30 | 31 | @media (min-width: 768px) { 32 | #sidebar-wrapper { 33 | margin-left: 0; 34 | } 35 | 36 | #page-content-wrapper { 37 | min-width: 0; 38 | width: 100%; 39 | } 40 | 41 | #wrapper.toggled #sidebar-wrapper { 42 | margin-left: -15rem; 43 | } 44 | } 45 | 46 | #as-react-datatable-container select { 47 | width: 70px !important; 48 | } 49 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; 2 | import Dashboard from "./components/pages/Dashboard"; 3 | import React, { Component } from 'react'; 4 | import Login from "./components/auth/Login"; 5 | import NotFound from "./components/layout/NotFound"; 6 | import { Provider } from "react-redux"; 7 | import PrivateRoute from "./components/private-route/PrivateRoute"; 8 | import Register from "./components/auth/Register"; 9 | import store from "./store"; 10 | import jwt_decode from "jwt-decode"; 11 | import setAuthToken from "./utils/setAuthToken"; 12 | import { setCurrentUser, logoutUser } from "./actions/authActions"; 13 | 14 | import './App.css'; 15 | import '../node_modules/bootstrap/dist/css/bootstrap.css'; 16 | import '../node_modules/bootstrap/dist/js/bootstrap'; 17 | import '../node_modules/font-awesome/css/font-awesome.css'; 18 | import '../node_modules/jquery/dist/jquery.min'; 19 | import '../node_modules/popper.js/dist/popper'; 20 | 21 | import User from "./components/pages/Users"; 22 | 23 | if (localStorage.jwtToken) { 24 | const token = localStorage.jwtToken; 25 | setAuthToken(token); 26 | const decoded = jwt_decode(token); 27 | store.dispatch(setCurrentUser(decoded)); 28 | const currentTime = Date.now() / 1000; 29 | if (decoded.exp < currentTime) { 30 | store.dispatch(logoutUser()); 31 | window.location.href = "./login"; 32 | } 33 | } 34 | 35 | class App extends Component { 36 | render () { 37 | return ( 38 | 39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 |
53 |
54 | ); 55 | } 56 | } 57 | 58 | export default App; 59 | -------------------------------------------------------------------------------- /client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /client/src/actions/authActions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import setAuthToken from "../utils/setAuthToken"; 3 | import jwt_decode from "jwt-decode"; 4 | import { 5 | GET_ERRORS, 6 | SET_CURRENT_USER, 7 | USER_LOADING 8 | } from "./types"; 9 | 10 | export const registerUser = (userData, history) => dispatch => { 11 | axios 12 | .post("/api/register", userData) 13 | .then(res => history.push("/login")) 14 | .catch(err => 15 | dispatch({ 16 | type: GET_ERRORS, 17 | payload: err.response.data 18 | }) 19 | ); 20 | }; 21 | 22 | export const loginUser = userData => dispatch => { 23 | axios 24 | .post("/api/login", userData) 25 | .then(res => { 26 | const { token } = res.data; 27 | localStorage.setItem("jwtToken", token); 28 | setAuthToken(token); 29 | const decoded = jwt_decode(token); 30 | dispatch(setCurrentUser(decoded)); 31 | }) 32 | .catch(err => 33 | dispatch({ 34 | type: GET_ERRORS, 35 | payload: err.response.data 36 | }) 37 | ); 38 | }; 39 | 40 | export const setCurrentUser = decoded => { 41 | return { 42 | type: SET_CURRENT_USER, 43 | payload: decoded 44 | }; 45 | }; 46 | 47 | export const setUserLoading = () => { 48 | return { 49 | type: USER_LOADING 50 | }; 51 | }; 52 | 53 | export const logoutUser = () => dispatch => { 54 | localStorage.removeItem("jwtToken"); 55 | setAuthToken(false); 56 | dispatch(setCurrentUser({})); 57 | }; 58 | -------------------------------------------------------------------------------- /client/src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const GET_ERRORS = "GET_ERRORS"; 2 | export const USER_LOADING = "USER_LOADING"; 3 | export const SET_CURRENT_USER = "SET_CURRENT_USER"; 4 | 5 | export const USER_ADD = "USER_ADD"; 6 | export const USER_UPDATE = "USER_UPDATE"; 7 | -------------------------------------------------------------------------------- /client/src/actions/userActions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { 3 | GET_ERRORS, 4 | USER_ADD, 5 | USER_UPDATE 6 | } from "./types"; 7 | 8 | export const addUser = (userData, history) => dispatch => { 9 | axios 10 | .post("/api/user-add", userData) 11 | .then(res => 12 | dispatch({ 13 | type: USER_ADD, 14 | payload: res, 15 | }) 16 | ).catch(err => 17 | dispatch({ 18 | type: GET_ERRORS, 19 | payload: err.response.data 20 | }) 21 | ); 22 | }; 23 | 24 | 25 | export const updateUser = (userData) => dispatch => { 26 | axios 27 | .post("/api/user-update", userData) 28 | .then(res => 29 | dispatch({ 30 | type: USER_UPDATE, 31 | payload: res, 32 | }) 33 | ).catch(err => 34 | dispatch({ 35 | type: GET_ERRORS, 36 | payload: err.response.data 37 | }) 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /client/src/components/auth/Login.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { connect } from "react-redux"; 4 | import { loginUser } from "../../actions/authActions"; 5 | import classnames from "classnames"; 6 | 7 | class Login extends Component { 8 | constructor() { 9 | super(); 10 | this.state = { 11 | email: "", 12 | password: "", 13 | errors: {} 14 | }; 15 | } 16 | 17 | componentDidMount() { 18 | if (this.props.auth.isAuthenticated) { 19 | this.props.history.push("/dashboard"); 20 | } 21 | }; 22 | 23 | componentWillReceiveProps(nextProps) { 24 | if (nextProps.auth.isAuthenticated) { 25 | this.props.history.push("/dashboard"); 26 | } 27 | 28 | if (nextProps.errors) { 29 | this.setState({ 30 | errors: nextProps.errors 31 | }); 32 | } 33 | } 34 | 35 | onChange = e => { 36 | this.setState({ [e.target.id]: e.target.value }); 37 | }; 38 | 39 | onSubmit = e => { 40 | e.preventDefault(); 41 | const userData = { 42 | email: this.state.email, 43 | password: this.state.password 44 | }; 45 | this.props.loginUser(userData); 46 | }; 47 | 48 | render() { 49 | const { errors } = this.state; 50 | return ( 51 |
52 |
53 |
54 |
55 |

Login

56 |
57 | 58 | 68 | {errors.email} 69 |
70 | 71 | 81 | {errors.password} 82 |

83 | 88 |

89 |
90 |
91 |
92 |
93 |
94 | ); 95 | } 96 | } 97 | 98 | Login.propTypes = { 99 | loginUser: PropTypes.func.isRequired, 100 | auth: PropTypes.object.isRequired, 101 | errors: PropTypes.object.isRequired 102 | }; 103 | const mapStateToProps = state => ({ 104 | auth: state.auth, 105 | errors: state.errors 106 | }); 107 | export default connect( 108 | mapStateToProps, 109 | { loginUser } 110 | )(Login); 111 | -------------------------------------------------------------------------------- /client/src/components/auth/Register.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link, withRouter } from "react-router-dom"; 3 | import PropTypes from "prop-types"; 4 | import { connect } from "react-redux"; 5 | import { registerUser } from "../../actions/authActions"; 6 | import classnames from "classnames"; 7 | 8 | class Register extends Component { 9 | 10 | constructor() { 11 | super(); 12 | this.state = { 13 | name: "", 14 | email: "", 15 | password: "", 16 | password2: "", 17 | errors: {} 18 | }; 19 | } 20 | 21 | componentDidMount() { 22 | if (this.props.auth.isAuthenticated) { 23 | this.props.history.push("/dashboard"); 24 | } 25 | } 26 | 27 | componentWillReceiveProps(nextProps) { 28 | if (nextProps.errors) { 29 | this.setState({ 30 | errors: nextProps.errors 31 | }); 32 | } 33 | } 34 | 35 | onChange = e => { 36 | this.setState({ [e.target.id]: e.target.value }); 37 | }; 38 | 39 | onSubmit = e => { 40 | e.preventDefault(); 41 | const newUser = { 42 | name: this.state.name, 43 | email: this.state.email, 44 | password: this.state.password, 45 | password2: this.state.password2 46 | }; 47 | this.props.registerUser(newUser, this.props.history); 48 | }; 49 | 50 | render() { 51 | const { errors } = this.state; 52 | return ( 53 |
54 |
55 |
56 | 57 | keyboard_backspace Back to 58 | home 59 | 60 |
61 |

62 | Register below 63 |

64 |

65 | Already have an account? Log in 66 |

67 |
68 |
69 |
70 | 80 | 81 | {errors.name} 82 |
83 |
84 | 94 | 95 | {errors.email} 96 |
97 |
98 | 108 | 109 | {errors.password} 110 |
111 |
112 | 121 | 122 | {errors.password2} 123 |
124 |
125 | 136 |
137 |
138 |
139 |
140 |
141 | ); 142 | } 143 | } 144 | 145 | Register.propTypes = { 146 | registerUser: PropTypes.func.isRequired, 147 | auth: PropTypes.object.isRequired, 148 | errors: PropTypes.object.isRequired 149 | }; 150 | 151 | const mapStateToProps = state => ({ 152 | auth: state.auth, 153 | errors: state.errors 154 | }); 155 | 156 | export default connect( 157 | mapStateToProps, 158 | { registerUser } 159 | )(withRouter(Register)); -------------------------------------------------------------------------------- /client/src/components/layout/Landing.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from "react-router-dom"; 3 | class Landing extends Component { 4 | render() { 5 | return ( 6 |
7 |
8 |
9 |

10 | Create a (minimal) full-stack app with user authentication via 11 | passport and JWTs 12 |

13 |
14 |
15 | 22 | Register 23 | 24 |
25 |
26 | 33 | Log In 34 | 35 |
36 |
37 |
38 |
39 | ); 40 | } 41 | } 42 | export default Landing; 43 | -------------------------------------------------------------------------------- /client/src/components/layout/NotFound.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | class Landing extends Component { 3 | render() { 4 | return ( 5 |
6 |
7 |
8 |

9 | Not Found 10 |

11 |
12 |
13 |
14 | ); 15 | } 16 | } 17 | export default Landing; 18 | -------------------------------------------------------------------------------- /client/src/components/pages/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { connect } from "react-redux"; 4 | import { logoutUser } from "../../actions/authActions"; 5 | import Navbar from "../partials/Navbar"; 6 | import Sidebar from "../partials/Sidebar"; 7 | import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; 8 | import {faList} from "@fortawesome/free-solid-svg-icons/faList"; 9 | import {Link} from "react-router-dom"; 10 | import {faUserAlt} from "@fortawesome/free-solid-svg-icons/faUserAlt"; 11 | 12 | class Dashboard extends Component { 13 | 14 | onLogoutClick = e => { 15 | e.preventDefault(); 16 | this.props.logoutUser(); 17 | }; 18 | 19 | render() { 20 | //const { user } = this.props.auth; 21 | return ( 22 |
23 | 24 |
25 | 26 |
27 |
28 | 29 |

Dashboard

30 |
31 |
32 |
33 |
34 |
Users
35 |

With supporting text below as a natural lead-in to 36 | additional content.

37 | Go to Users 38 |
39 |
40 |
41 |
42 |
43 |
44 |
Special title treatment
45 |

With supporting text below as a natural lead-in to 46 | additional content.

47 | Go somewhere 48 |
49 |
50 |
51 |
52 |
53 |
54 |
Special title treatment
55 |

With supporting text below as a natural lead-in to 56 | additional content.

57 | Go somewhere 58 |
59 |
60 |
61 |
62 |
63 |
64 |
Special title treatment
65 |

With supporting text below as a natural lead-in to 66 | additional content.

67 | Go somewhere 68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | ); 77 | } 78 | } 79 | 80 | Dashboard.propTypes = { 81 | logoutUser: PropTypes.func.isRequired, 82 | auth: PropTypes.object.isRequired 83 | }; 84 | 85 | const mapStateToProps = state => ({ 86 | auth: state.auth 87 | }); 88 | 89 | export default connect( 90 | mapStateToProps, 91 | { logoutUser } 92 | )(Dashboard); 93 | -------------------------------------------------------------------------------- /client/src/components/pages/Users.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from "react"; 2 | import Navbar from "../partials/Navbar"; 3 | import Sidebar from "../partials/Sidebar"; 4 | import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; 5 | import {faList} from "@fortawesome/free-solid-svg-icons/faList"; 6 | import ReactDatatable from '@ashvin27/react-datatable'; 7 | import PropTypes from "prop-types"; 8 | import {connect} from "react-redux"; 9 | import axios from "axios"; 10 | import {faPlus} from "@fortawesome/free-solid-svg-icons"; 11 | import UserAddModal from "../partials/UserAddModal"; 12 | import UserUpdateModal from "../partials/UserUpdateModal"; 13 | import { toast, ToastContainer} from "react-toastify"; 14 | 15 | class Users extends Component { 16 | 17 | constructor(props) { 18 | super(props); 19 | 20 | this.columns = [ 21 | { 22 | key: "_id", 23 | text: "Id", 24 | className: "id", 25 | align: "left", 26 | sortable: true, 27 | }, 28 | { 29 | key: "name", 30 | text: "Name", 31 | className: "name", 32 | align: "left", 33 | sortable: true, 34 | }, 35 | { 36 | key: "email", 37 | text: "Email", 38 | className: "email", 39 | align: "left", 40 | sortable: true 41 | }, 42 | { 43 | key: "date", 44 | text: "Date", 45 | className: "date", 46 | align: "left", 47 | sortable: true 48 | }, 49 | { 50 | key: "action", 51 | text: "Action", 52 | className: "action", 53 | width: 100, 54 | align: "left", 55 | sortable: false, 56 | cell: record => { 57 | return ( 58 | 59 | 67 | 72 | 73 | ); 74 | } 75 | } 76 | ]; 77 | 78 | this.config = { 79 | page_size: 10, 80 | length_menu: [ 10, 20, 50 ], 81 | filename: "Users", 82 | no_data_text: 'No user found!', 83 | button: { 84 | excel: true, 85 | print: true, 86 | csv: true 87 | }, 88 | language: { 89 | length_menu: "Show _MENU_ result per page", 90 | filter: "Filter in records...", 91 | info: "Showing _START_ to _END_ of _TOTAL_ records", 92 | pagination: { 93 | first: "First", 94 | previous: "Previous", 95 | next: "Next", 96 | last: "Last" 97 | } 98 | }, 99 | show_length_menu: true, 100 | show_filter: true, 101 | show_pagination: true, 102 | show_info: true, 103 | }; 104 | 105 | this.state = { 106 | records: [] 107 | }; 108 | 109 | this.state = { 110 | currentRecord: { 111 | id: '', 112 | name: '', 113 | email: '', 114 | password: '', 115 | password2: '', 116 | } 117 | }; 118 | 119 | this.getData = this.getData.bind(this); 120 | } 121 | 122 | componentDidMount() { 123 | this.getData() 124 | }; 125 | 126 | componentWillReceiveProps(nextProps) { 127 | this.getData() 128 | } 129 | 130 | getData() { 131 | axios 132 | .post("/api/user-data") 133 | .then(res => { 134 | this.setState({ records: res.data}) 135 | }) 136 | .catch() 137 | } 138 | 139 | editRecord(record) { 140 | this.setState({ currentRecord: record}); 141 | } 142 | 143 | deleteRecord(record) { 144 | axios 145 | .post("/api/user-delete", {_id: record._id}) 146 | .then(res => { 147 | if (res.status === 200) { 148 | toast(res.data.message, { 149 | position: toast.POSITION.TOP_CENTER, 150 | }) 151 | } 152 | }) 153 | .catch(); 154 | this.getData(); 155 | } 156 | 157 | pageChange(pageData) { 158 | console.log("OnPageChange", pageData); 159 | } 160 | 161 | render() { 162 | return ( 163 |
164 | 165 |
166 | 167 | 168 | 169 |
170 |
171 | 172 | 173 |

Users List

174 | 180 |
181 |
182 | 183 |
184 |
185 | ); 186 | } 187 | 188 | } 189 | 190 | Users.propTypes = { 191 | auth: PropTypes.object.isRequired, 192 | }; 193 | 194 | const mapStateToProps = state => ({ 195 | auth: state.auth, 196 | records: state.records 197 | }); 198 | 199 | export default connect( 200 | mapStateToProps 201 | )(Users); 202 | -------------------------------------------------------------------------------- /client/src/components/partials/Navbar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; 4 | import {faSignOutAlt} from "@fortawesome/free-solid-svg-icons"; 5 | import {connect} from "react-redux"; 6 | import {logoutUser} from "../../actions/authActions"; 7 | 8 | class Navbar extends Component { 9 | 10 | onLogoutClick = e => { 11 | e.preventDefault(); 12 | this.props.logoutUser(); 13 | }; 14 | 15 | render() { 16 | const { user } = this.props.auth; 17 | return ( 18 |
19 | 43 |
44 | ); 45 | } 46 | } 47 | 48 | Navbar.propTypes = { 49 | logoutUser: PropTypes.func.isRequired, 50 | auth: PropTypes.object.isRequired 51 | }; 52 | 53 | const mapStateToProps = state => ({ 54 | auth: state.auth 55 | }); 56 | 57 | export default connect( 58 | mapStateToProps, 59 | { logoutUser } 60 | )(Navbar); 61 | -------------------------------------------------------------------------------- /client/src/components/partials/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; 4 | import {faSignOutAlt} from "@fortawesome/free-solid-svg-icons"; 5 | import {connect} from "react-redux"; 6 | import {logoutUser} from "../../actions/authActions"; 7 | import {Link} from "react-router-dom"; 8 | 9 | class Sidebar extends Component { 10 | 11 | onLogoutClick = e => { 12 | e.preventDefault(); 13 | this.props.logoutUser(); 14 | }; 15 | 16 | render() { 17 | //const { user } = this.props.auth; 18 | return ( 19 | 27 | ); 28 | } 29 | } 30 | 31 | Sidebar.propTypes = { 32 | logoutUser: PropTypes.func.isRequired, 33 | auth: PropTypes.object.isRequired 34 | }; 35 | 36 | const mapStateToProps = state => ({ 37 | auth: state.auth 38 | }); 39 | 40 | export default connect( 41 | mapStateToProps, 42 | { logoutUser } 43 | )(Sidebar); 44 | -------------------------------------------------------------------------------- /client/src/components/partials/UserAddModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classnames from "classnames"; 3 | import PropTypes from "prop-types"; 4 | import { connect } from "react-redux"; 5 | import { addUser } from "../../actions/userActions"; 6 | import { withRouter } from "react-router-dom"; 7 | import { toast } from 'react-toastify'; 8 | import $ from 'jquery'; 9 | 10 | import 'react-toastify/dist/ReactToastify.css'; 11 | 12 | class UserAddModal extends React.Component { 13 | 14 | constructor() { 15 | super(); 16 | this.state = { 17 | name: "", 18 | email: "", 19 | password: "", 20 | password2: "", 21 | errors: {}, 22 | }; 23 | } 24 | 25 | componentWillReceiveProps(nextProps) { 26 | if (nextProps.errors) { 27 | this.setState({ 28 | errors: nextProps.errors 29 | }); 30 | } 31 | if (nextProps.auth !== undefined 32 | && nextProps.auth.user !== undefined 33 | && nextProps.auth.user.data !== undefined 34 | && nextProps.auth.user.data.message !== undefined) { 35 | $('#add-user-modal').modal('hide'); 36 | toast(nextProps.auth.user.data.message, { 37 | position: toast.POSITION.TOP_CENTER 38 | }); 39 | } 40 | } 41 | 42 | onChange = e => { 43 | this.setState({ [e.target.id]: e.target.value }); 44 | }; 45 | 46 | onUserAdd = e => { 47 | e.preventDefault(); 48 | const newUser = { 49 | name: this.state.name, 50 | email: this.state.email, 51 | password: this.state.password, 52 | password2: this.state.password2 53 | }; 54 | this.props.addUser(newUser, this.props.history); 55 | }; 56 | 57 | render() { 58 | const { errors } = this.state; 59 | return ( 60 |
61 |
62 |
63 |
64 |
65 |

Add User

66 | 67 |
68 |
69 |
70 |
71 |
72 | 73 |
74 |
75 | 84 | {errors.name} 85 |
86 |
87 |
88 |
89 | 90 |
91 |
92 | 102 | {errors.email} 103 |
104 |
105 |
106 |
107 | 108 |
109 |
110 | 121 | {errors.password} 122 |
123 |
124 |
125 |
126 | 127 |
128 |
129 | 139 | {errors.password2} 140 |
141 |
142 |
143 |
144 |
145 | 146 | 152 |
153 |
154 |
155 |
156 |
157 | ) 158 | } 159 | } 160 | 161 | UserAddModal.propTypes = { 162 | addUser: PropTypes.func.isRequired, 163 | auth: PropTypes.object.isRequired, 164 | errors: PropTypes.object.isRequired 165 | }; 166 | 167 | const mapStateToProps = state => ({ 168 | auth: state.auth, 169 | errors: state.errors 170 | }); 171 | 172 | export default connect( 173 | mapStateToProps, 174 | { addUser } 175 | )(withRouter(UserAddModal)); 176 | -------------------------------------------------------------------------------- /client/src/components/partials/UserUpdateModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classnames from "classnames"; 3 | import PropTypes from "prop-types"; 4 | import { connect } from "react-redux"; 5 | import { updateUser } from "../../actions/userActions"; 6 | import { withRouter } from "react-router-dom"; 7 | import { toast } from 'react-toastify'; 8 | import $ from 'jquery'; 9 | 10 | import 'react-toastify/dist/ReactToastify.css'; 11 | 12 | class UserUpdateModal extends React.Component { 13 | 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | id: this.props.record.id, 18 | name: this.props.record.name, 19 | email: this.props.record.email, 20 | password: '', 21 | errors: {}, 22 | }; 23 | } 24 | 25 | componentWillReceiveProps(nextProps) { 26 | if (nextProps.record) { 27 | this.setState({ 28 | id: nextProps.record.id, 29 | name: nextProps.record.name, 30 | email: nextProps.record.email, 31 | }) 32 | } 33 | if (nextProps.errors) { 34 | this.setState({ 35 | errors: nextProps.errors 36 | }); 37 | } 38 | if (nextProps.auth !== undefined 39 | && nextProps.auth.user !== undefined 40 | && nextProps.auth.user.data !== undefined 41 | && nextProps.auth.user.data.message !== undefined 42 | && nextProps.auth.user.data.success) { 43 | $('#update-user-modal').modal('hide'); 44 | toast(nextProps.auth.user.data.message, { 45 | position: toast.POSITION.TOP_CENTER 46 | }); 47 | } 48 | } 49 | 50 | onChange = e => { 51 | if (e.target.id === 'user-update-name') { 52 | this.setState({ name: e.target.value }); 53 | } 54 | if (e.target.id === 'user-update-email') { 55 | this.setState({ email: e.target.value }); 56 | } 57 | if (e.target.id === 'user-update-password') { 58 | this.setState({ password: e.target.value }); 59 | } 60 | }; 61 | 62 | onUserUpdate = e => { 63 | e.preventDefault(); 64 | const newUser = { 65 | _id: this.state.id, 66 | name: this.state.name, 67 | email: this.state.email, 68 | password: this.state.password 69 | }; 70 | this.props.updateUser(newUser); 71 | }; 72 | 73 | render() { 74 | const { errors } = this.state; 75 | return ( 76 |
77 |
78 |
79 |
80 |
81 |

Update User

82 | 83 |
84 |
85 |
86 | 92 |
93 |
94 | 95 |
96 |
97 | 106 | {errors.name} 107 |
108 |
109 |
110 |
111 | 112 |
113 |
114 | 124 | {errors.email} 125 |
126 |
127 |
128 |
129 | 130 |
131 |
132 | 143 | {errors.password} 144 |
145 |
146 |
147 |
148 |
149 | 150 | 156 |
157 |
158 |
159 |
160 |
161 | ) 162 | } 163 | } 164 | 165 | UserUpdateModal.propTypes = { 166 | updateUser: PropTypes.func.isRequired, 167 | auth: PropTypes.object.isRequired, 168 | errors: PropTypes.object.isRequired 169 | }; 170 | 171 | const mapStateToProps = state => ({ 172 | auth: state.auth, 173 | errors: state.errors 174 | }); 175 | 176 | export default connect( 177 | mapStateToProps, 178 | { updateUser } 179 | )(withRouter(UserUpdateModal)); 180 | -------------------------------------------------------------------------------- /client/src/components/private-route/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, Redirect } from "react-router-dom"; 3 | import { connect } from "react-redux"; 4 | import PropTypes from "prop-types"; 5 | 6 | const PrivateRoute = ({ component: Component, auth, ...rest }) => ( 7 | 10 | auth.isAuthenticated === true ? ( 11 | 12 | ) : ( 13 | 14 | ) 15 | } 16 | /> 17 | ); 18 | 19 | PrivateRoute.propTypes = { 20 | auth: PropTypes.object.isRequired 21 | }; 22 | 23 | const mapStateToProps = state => ({ 24 | auth: state.auth 25 | }); 26 | 27 | export default connect(mapStateToProps)(PrivateRoute); 28 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | 15 | header, main, footer { 16 | padding-left: 300px; 17 | } 18 | 19 | @media only screen and (max-width : 992px) { 20 | header, main, footer { 21 | padding-left: 0; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import $ from "jquery"; 6 | window.jQuery = $; 7 | window.$ = $; 8 | global.jQuery = $; 9 | 10 | ReactDOM.render(, document.getElementById('root')); 11 | 12 | $("#menu-toggle").click(function() { 13 | $("#wrapper").toggleClass("toggled"); 14 | }); 15 | 16 | $('.modal[data-reset="true"]').on('shown.bs.modal', () => 17 | $("input[name != 'timestamp']").val('')); 18 | 19 | $('.modal').on('shown.bs.modal', () => 20 | $('input[data-reset-input="true"]').val('')); 21 | -------------------------------------------------------------------------------- /client/src/reducers/authReducers.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_CURRENT_USER, 3 | USER_ADD, 4 | USER_LOADING, 5 | USER_UPDATE 6 | } from "../actions/types"; 7 | const isEmpty = require("is-empty"); 8 | const initialState = { 9 | isAuthenticated: false, 10 | user: {}, 11 | loading: false, 12 | }; 13 | export default function(state = initialState, action) { 14 | switch (action.type) { 15 | case USER_ADD: 16 | return { 17 | isAuthenticated: !isEmpty(action.payload), 18 | user: action.payload 19 | }; 20 | case USER_UPDATE: 21 | return { 22 | isAuthenticated: !isEmpty(action.payload), 23 | user: action.payload, 24 | }; 25 | case SET_CURRENT_USER: 26 | return { 27 | ...state, 28 | isAuthenticated: !isEmpty(action.payload), 29 | user: action.payload 30 | }; 31 | case USER_LOADING: 32 | return { 33 | ...state, 34 | loading: true 35 | }; 36 | default: 37 | return state; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/src/reducers/errorReducers.js: -------------------------------------------------------------------------------- 1 | import { GET_ERRORS } from "../actions/types"; 2 | const initialState = {}; 3 | 4 | export default function(state = initialState, action) { 5 | switch (action.type) { 6 | case GET_ERRORS: 7 | return action.payload; 8 | default: 9 | return state; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import authReducer from "./authReducers"; 3 | import errorReducer from "./errorReducers"; 4 | export default combineReducers({ 5 | auth: authReducer, 6 | errors: errorReducer 7 | }); -------------------------------------------------------------------------------- /client/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from "redux"; 2 | import thunk from "redux-thunk"; 3 | import rootReducer from "./reducers"; 4 | 5 | const initialState = {}; 6 | const middleware = [thunk]; 7 | 8 | const store = createStore( 9 | rootReducer, 10 | initialState, 11 | compose( 12 | applyMiddleware(...middleware), 13 | //window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 14 | ) 15 | ); 16 | export default store; 17 | -------------------------------------------------------------------------------- /client/src/utils/setAuthToken.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | const setAuthToken = token => { 3 | if (token) { 4 | axios.defaults.headers.common["Authorization"] = token; 5 | } else { 6 | delete axios.defaults.headers.common["Authorization"]; 7 | } 8 | }; 9 | export default setAuthToken; -------------------------------------------------------------------------------- /config/keys.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mongoURI: "mongodb://localhost:27017/mern", 3 | secretOrKey: "FxUum76z" 4 | }; -------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | const JwtStrategy = require("passport-jwt").Strategy; 2 | const ExtractJwt = require("passport-jwt").ExtractJwt; 3 | const mongoose = require("mongoose"); 4 | const User = mongoose.model("users"); 5 | const keys = require("../config/keys"); 6 | const opts = {}; 7 | opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); 8 | opts.secretOrKey = keys.secretOrKey; 9 | module.exports = passport => { 10 | passport.use( 11 | new JwtStrategy(opts, (jwt_payload, done) => { 12 | User.findById(jwt_payload.id) 13 | .then(user => { 14 | if (user) { 15 | return done(null, user); 16 | } 17 | return done(null, false); 18 | }) 19 | .catch(err => console.log(err)); 20 | }) 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidneha/mern-admin-panel/a3c9f8ddc3a09060f9d0f624275916bd9222bf27/demo.gif -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | const UserSchema = new Schema({ 4 | name: { 5 | type: String, 6 | required: true 7 | }, 8 | email: { 9 | type: String, 10 | required: true 11 | }, 12 | password: { 13 | type: String, 14 | required: true 15 | }, 16 | date: { 17 | type: Date, 18 | default: Date.now 19 | } 20 | }); 21 | 22 | UserSchema.virtual('id').get(function(){ 23 | return this._id.toHexString(); 24 | }); 25 | 26 | UserSchema.set('toJSON', { 27 | virtuals: true 28 | }); 29 | 30 | module.exports = User = mongoose.model("users", UserSchema); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "login-registration", 3 | "version": "1.0.0", 4 | "description": "A simple mern application for login-registration system", 5 | "main": "server.js", 6 | "author": "Neha Sharma", 7 | "license": "MIT", 8 | "dependencies": { 9 | "bcryptjs": "^2.4.3", 10 | "body-parser": "^1.19.0", 11 | "concurrently": "^4.1.0", 12 | "express": "^4.17.1", 13 | "is-empty": "^1.2.0", 14 | "jsonwebtoken": "^8.5.1", 15 | "mongoose": "^5.7.5", 16 | "passport": "^0.4.0", 17 | "passport-jwt": "^4.0.0", 18 | "validator": "^11.0.0" 19 | }, 20 | "devDependencies": { 21 | "nodemon": "^1.19.1" 22 | }, 23 | "scripts": { 24 | "client-install": "npm install --prefix client", 25 | "start": "node server.js", 26 | "server": "nodemon server.js", 27 | "client": "npm start --prefix client", 28 | "dev": "concurrently \"npm run server\" \"npm run client\"" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /routes/api/users.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const bcrypt = require('bcryptjs'); 4 | const jwt = require('jsonwebtoken'); 5 | const keys = require('../../config/keys'); 6 | const validateRegisterInput = require('../../validation/register'); 7 | const validateLoginInput = require('../../validation/login'); 8 | const validateUpdateUserInput = require('../../validation/updateUser'); 9 | const User = require('../../models/User'); 10 | 11 | router.post('/user-add', (req, res) => { 12 | const { errors, isValid } = validateRegisterInput(req.body); 13 | if (!isValid) { 14 | return res.status(400).json(errors); 15 | } 16 | User.findOne({ email: req.body.email }).then(user => { 17 | if (user) { 18 | return res.status(400).json({ email: 'Email already exists' }); 19 | } else { 20 | const newUser = new User({ 21 | name: req.body.name, 22 | email: req.body.email, 23 | password: req.body.password 24 | }); 25 | bcrypt.genSalt(10, (err, salt) => { 26 | bcrypt.hash(newUser.password, salt, (err, hash) => { 27 | if (err) throw err; 28 | newUser.password = hash; 29 | newUser 30 | .save() 31 | .then(user => { 32 | return res.status(200).json({message: 'User added successfully. Refreshing data...'}) 33 | }).catch(err => console.log(err)); 34 | }); 35 | }); 36 | } 37 | }); 38 | }); 39 | 40 | router.post('/user-data', (req, res) => { 41 | User.find({}).select(['-password']).then(user => { 42 | if (user) { 43 | return res.status(200).send(user); 44 | } 45 | }); 46 | }); 47 | 48 | router.post('/user-delete', (req, res) => { 49 | User.deleteOne({ _id: req.body._id}).then(user => { 50 | if (user) { 51 | return res.status(200).json({message: 'User deleted successfully. Refreshing data...', success: true}) 52 | } 53 | }); 54 | }); 55 | 56 | router.post('/user-update', (req, res) => { 57 | const { errors, isValid } = validateUpdateUserInput(req.body); 58 | if (!isValid) { 59 | return res.status(400).json(errors); 60 | } 61 | const _id = req.body._id; 62 | User.findOne({ _id }).then(user => { 63 | if (user) { 64 | if (req.body.password !== '') { 65 | bcrypt.genSalt(10, (err, salt) => { 66 | bcrypt.hash(req.body.password, salt, (err, hash) => { 67 | if (err) throw err; 68 | user.password = hash; 69 | }); 70 | }); 71 | } 72 | let update = {'name': req.body.name, 'email': req.body.email, 'password': user.password}; 73 | User.update({ _id: _id}, {$set: update}, function(err, result) { 74 | if (err) { 75 | return res.status(400).json({ message: 'Unable to update user.' }); 76 | } else { 77 | return res.status(200).json({ message: 'User updated successfully. Refreshing data...', success: true }); 78 | } 79 | }); 80 | } else { 81 | return res.status(400).json({ message: 'Now user found to update.' }); 82 | } 83 | }); 84 | }); 85 | 86 | router.post('/login', (req, res) => { 87 | const { errors, isValid } = validateLoginInput(req.body); 88 | if (!isValid) { 89 | return res.status(400).json(errors); 90 | } 91 | const email = req.body.email; 92 | const password = req.body.password; 93 | User.findOne({ email }).then(user => { 94 | if (!user) { 95 | return res.status(404).json({ email: 'Email not found' }); 96 | } 97 | bcrypt.compare(password, user.password).then(isMatch => { 98 | if (isMatch) { 99 | const payload = { 100 | id: user.id, 101 | name: user.name 102 | }; 103 | jwt.sign( 104 | payload, 105 | keys.secretOrKey, 106 | { 107 | expiresIn: 31556926 // 1 year in seconds 108 | }, 109 | (err, token) => { 110 | res.json({ 111 | success: true, 112 | token: 'Bearer ' + token 113 | }); 114 | } 115 | ); 116 | } else { 117 | return res 118 | .status(400) 119 | .json({ password: 'Password incorrect' }); 120 | } 121 | }); 122 | }); 123 | }); 124 | 125 | 126 | module.exports = router; 127 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const mongoose = require('mongoose'); 4 | const bodyParser = require('body-parser'); 5 | const passport = require('passport'); 6 | const users = require('./routes/api/users'); 7 | 8 | require('./config/passport')(passport); 9 | 10 | const app = express(); 11 | 12 | app.use(function(req, res, next) { 13 | res.header("Access-Control-Allow-Origin", "*"); 14 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 15 | next(); 16 | }); 17 | 18 | app.use(bodyParser.urlencoded({extended: false})); 19 | 20 | app.use(bodyParser.json()); 21 | 22 | app.listen(9000); 23 | 24 | const db = require('./config/keys').mongoURI; 25 | 26 | mongoose.connect(db, { useNewUrlParser: true }) 27 | .then(() => 28 | console.log('MongoDB successfully connected.') 29 | ).catch(err => console.log(err)); 30 | 31 | app.use(passport.initialize()); 32 | 33 | app.use('/api', users); 34 | 35 | app.use(express.static(path.join(__dirname, 'client/build'))); 36 | 37 | app.get('*', function (req, res) { 38 | res.sendFile(path.join(__dirname, 'client/build', 'index.html')); 39 | }); 40 | 41 | const port = process.env.PORT || 5000; 42 | 43 | app.listen(port, () => console.log(`Server up and running on port ${port} !`)); 44 | -------------------------------------------------------------------------------- /validation/login.js: -------------------------------------------------------------------------------- 1 | const Validator = require("validator"); 2 | const isEmpty = require("is-empty"); 3 | module.exports = function validateLoginInput(data) { 4 | let errors = {}; 5 | data.email = !isEmpty(data.email) ? data.email : ""; 6 | data.password = !isEmpty(data.password) ? data.password : ""; 7 | if (Validator.isEmpty(data.email)) { 8 | errors.email = "Email field is required"; 9 | } else if (!Validator.isEmail(data.email)) { 10 | errors.email = "Email is invalid"; 11 | } 12 | if (Validator.isEmpty(data.password)) { 13 | errors.password = "Password field is required"; 14 | } 15 | return { 16 | errors, 17 | isValid: isEmpty(errors) 18 | }; 19 | }; -------------------------------------------------------------------------------- /validation/register.js: -------------------------------------------------------------------------------- 1 | const Validator = require("validator"); 2 | const isEmpty = require("is-empty"); 3 | module.exports = function validateRegisterInput(data) { 4 | let errors = {}; 5 | data.name = !isEmpty(data.name) ? data.name : ""; 6 | data.email = !isEmpty(data.email) ? data.email : ""; 7 | data.password = !isEmpty(data.password) ? data.password : ""; 8 | data.password2 = !isEmpty(data.password2) ? data.password2 : ""; 9 | if (Validator.isEmpty(data.name)) { 10 | errors.name = "Name field is required"; 11 | } 12 | if (Validator.isEmpty(data.email)) { 13 | errors.email = "Email field is required"; 14 | } else if (!Validator.isEmail(data.email)) { 15 | errors.email = "Email is invalid"; 16 | } 17 | if (Validator.isEmpty(data.password)) { 18 | errors.password = "Password field is required"; 19 | } 20 | if (Validator.isEmpty(data.password2)) { 21 | errors.password2 = "Confirm password field is required"; 22 | } 23 | if (!Validator.isLength(data.password, { min: 6, max: 30 })) { 24 | errors.password = "Password must be at least 6 characters"; 25 | } 26 | if (!Validator.equals(data.password, data.password2)) { 27 | errors.password2 = "Passwords must match"; 28 | } 29 | return { 30 | errors, 31 | isValid: isEmpty(errors) 32 | }; 33 | }; -------------------------------------------------------------------------------- /validation/updateUser.js: -------------------------------------------------------------------------------- 1 | const Validator = require("validator"); 2 | const isEmpty = require("is-empty"); 3 | module.exports = function validateUpdateUserInput(data) { 4 | let errors = {}; 5 | data.name = !isEmpty(data.name) ? data.name : ""; 6 | data.email = !isEmpty(data.email) ? data.email : ""; 7 | if (Validator.isEmpty(data.name)) { 8 | errors.name = "Name field is required"; 9 | } 10 | if (Validator.isEmpty(data.email)) { 11 | errors.email = "Email field is required"; 12 | } else if (!Validator.isEmail(data.email)) { 13 | errors.email = "Email is invalid"; 14 | } 15 | return { 16 | errors, 17 | isValid: isEmpty(errors) 18 | }; 19 | }; 20 | --------------------------------------------------------------------------------