├── .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 |
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 }\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\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 );\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
\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 )\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 \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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------