├── .gitignore ├── README.md ├── authenticate.js ├── client ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── setupProxy.js ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── baseUrl.js │ ├── components │ │ ├── AddBookComponent.js │ │ ├── BookDetailComponent.js │ │ ├── BooksComponent.js │ │ ├── FooterComponent.js │ │ ├── HeaderComponent.js │ │ ├── HistoryComponent.js │ │ ├── HomeComponent.js │ │ ├── IssueComponent.js │ │ ├── LoadingComponent.js │ │ ├── LogComponent.js │ │ ├── MainComponent.js │ │ ├── ProfileComponent.js │ │ ├── ReturnComponent.js │ │ ├── SearchComponent.js │ │ ├── StatsComponent.js │ │ ├── UserDetailComponent.js │ │ └── UserListComponent.js │ ├── images │ │ └── walllpaper.jpg │ ├── index.css │ ├── index.js │ ├── redux │ │ ├── ActionCreators.js │ │ ├── ActionTypes.js │ │ ├── auth.js │ │ ├── books.js │ │ ├── issues.js │ │ ├── store.js │ │ └── users.js │ └── serviceWorker.js └── yarn.lock ├── config └── keys.js ├── models ├── books.js ├── issues.js └── users.js ├── package-lock.json ├── package.json ├── routes ├── api │ ├── bookRouter.js │ ├── issueRouter.js │ └── userRouter.js └── cors.js └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | backup 3 | config 4 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Library Management Web App 2 | 3 | A web app for the management of books, users and the Issue and Return of Books in a library. 4 | 5 | ## User Permissions 6 | 7 | ### Student 8 | 9 | A student can 10 | 11 | * register himself on the app 12 | * view and edit his profile 13 | * change his password 14 | * search for books and view availabilty 15 | * view his issue history 16 | 17 | ### Admin 18 | 19 | An admin can 20 | 21 | * view and edit his profile 22 | * search for books and view availability 23 | * view, Edit or Delete existing books 24 | * add new books 25 | * issue a book to a student 26 | * return a book issued earlier 27 | * view all stats of the library 28 | * view issue log and the profile of all the students 29 | * view the profile of all admins 30 | 31 | ## A note to the viewers 32 | 33 | 1. You can try logging in as an **admin** by entering the following credentials: 34 | 35 | * **username**: *Director* 36 | * **password**: *123pass* 37 | 38 | 2. You can also register yourself as a student and then login to view the options available to a student. 39 | 40 | ## View live App 41 | 42 | Hosted at https://lib-manage.herokuapp.com/ 43 | 44 | ## Tech Stack Used 45 | 46 | ### The MERN Stack 47 | 48 | * [MongoDB](https://docs.mongodb.com/) - Document database - to store data as JSON 49 | * [Express.js](https://devdocs.io/express/) - Back-end web application framework running on top of Node.js 50 | * [React](https://reactjs.org/docs/) - Front-end web app framework used 51 | * [Node.js](https://nodejs.org/en/docs/) - JavaScript runtime environment 52 | 53 | ### Middleware 54 | 55 | * [Redux](https://redux.js.org/basics/usage-with-react) - For flux architecture, and fetching rapidly data 56 | * [Mongoose](https://mongoosejs.com/docs/guide.html) - ODM for MongoDB 57 | 58 | ## Steps followed to setup the project 59 | 60 | ### Setting up server and database 61 | 62 | 1. Initialise a package.json file by entering the following command in terminal, after getting into the project directory : 63 | 64 | ```(bash) 65 | npm init 66 | ``` 67 | 68 | 2. Install npm packages required for backend side : 69 | 70 | ```(bash) 71 | npm i express body-parser mongoose concurrently 72 | npm i -D nodemon 73 | ``` 74 | 75 | 3. Create a file server.js to make use of the express packages 76 | 77 | 4. Modify the package.json by adding the following scripts to it : 78 | 79 | ```(JSON) 80 | "start": "node server.js", 81 | "server": "nodemon server.js", 82 | ``` 83 | 84 | 5. Create an account on MongoDB cloud Atlas, thereafter, creating a database on it and get your MongoURI exported from a file keys.js in a folder config 85 | 86 | 6. Modify server.js to get connected to the database, using the MongoURI and also add the following lines at the end of server.js : 87 | 88 | ```(JavaScript) 89 | const port = process.env.PORT || 5000; 90 | app.listen(port, ()=> console.log(`Server started running on port ${port}`)); 91 | ``` 92 | 93 | 7. Type the following command to get your server running on your localhost and ensure hot-reloading, when the server-side code is modified : 94 | 95 | ```(bash) 96 | npm run server 97 | ``` 98 | 99 | 8. Make Schemas for various collections to be stored in database and export them from a folder models and the REST APIs for various routes in the folder routes. Change the server.js accordingly to make the use of these REST APIs. Ensure that the APIs are working correctly, by making requests using POSTMAN 100 | 101 | 9. Add JWT token based authentication and 'cors' module and use them in server.js. 102 | 103 | ### Setting up the React client 104 | 105 | 1. Create a folder 'client' in the project directory. Ensure that you have create-react-app CLI installed. Enter the following commands in terminal : 106 | 107 | ```(bash) 108 | cd client 109 | create-react-app . 110 | cd .. 111 | ``` 112 | 113 | 2. In the package.json of the server, add the following scripts : 114 | 115 | ```(JSON) 116 | "client-install": "npm install --prefix client", 117 | "client": "npm start --prefix client", 118 | "dev": "concurrently \"npm run server\" \"npm run client\" ", 119 | ``` 120 | 121 | 3. Remove all the additional default setup from client folder like logo, index.css, etc. Then, configure the client to make use of *bootstrap* and *reactstrap* to make the app responsive by using following commands in terminal : 122 | 123 | ```(bash) 124 | cd client 125 | npm i bootstrap reactstrap react-popper font-awesome bootstrap-social 126 | ``` 127 | 128 | Add the following line to index.js : 129 | 130 | ```(JavaScript) 131 | import 'bootstrap/dist/css/bootstrap.min.css 132 | ``` 133 | 134 | 4. Install Redux for maintaining the state : 135 | 136 | ```(Terminal) 137 | npm i redux react-redux redux-thunk 138 | ``` 139 | 140 | 5. Create a redux store, the various actions and reducers required in a folder named redux. Make corresponding changes in the React components to map the actions and state to props 141 | 142 | ### Deployment 143 | 144 | 1. Add the following lines to server.js : 145 | 146 | ```(JavaScript) 147 | // Serve static assets if in production 148 | if (process.env.NODE_ENV === 'production') { 149 | // Set static folder 150 | app.use(express.static('client/build')); 151 | 152 | app.get('*', (req, res) => { 153 | res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html')); 154 | }); 155 | } 156 | ``` 157 | 158 | 2. Add the following script to the package.json of server 159 | 160 | ```(JSON) 161 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client" 162 | ``` 163 | 164 | 3. Install Heroku CLI and make sure you have intialised a git repository in the project directory. Enter the following commands in the terminal : 165 | 166 | ```(bash) 167 | heroku login 168 | heroku create 169 | git add . 170 | git commit -am "Deployed to Heroku" 171 | git push heroku master 172 | ``` 173 | 174 | 4. Open your heroku account and click on **Open App** option in the dashboard. -------------------------------------------------------------------------------- /authenticate.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var LocalStrategy = require('passport-local').Strategy; 3 | var User = require('./models/users'); 4 | var JwtStrategy = require('passport-jwt').Strategy; 5 | var ExtractJwt = require('passport-jwt').ExtractJwt; 6 | var jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens 7 | 8 | var config = require('./config/keys.js'); 9 | 10 | exports.local=passport.use(new LocalStrategy(User.authenticate())); 11 | 12 | // For sessions 13 | passport.serializeUser(User.serializeUser()); 14 | passport.deserializeUser(User.deserializeUser()); 15 | 16 | exports.getToken = function(user) { 17 | return jwt.sign(user, config.secretKey, 18 | {expiresIn: 3600}); 19 | }; 20 | 21 | var opts = {}; 22 | opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); 23 | opts.secretOrKey = config.secretKey; 24 | 25 | exports.jwtPassport = passport.use(new JwtStrategy(opts, 26 | (jwt_payload, done) => { 27 | console.log("JWT payload: ", jwt_payload); 28 | User.findOne({_id: jwt_payload._id}, (err, user) => { 29 | if (err) { 30 | return done(err, false); 31 | } 32 | else if (user) { 33 | return done(null, user); 34 | } 35 | else { 36 | return done(null, false); 37 | } 38 | }); 39 | })); 40 | 41 | exports.verifyUser = passport.authenticate('jwt', {session: false}); 42 | 43 | exports.verifyAdmin = function (req, res, next){ 44 | if(req.user.admin){ 45 | next(); 46 | }else{ 47 | var err = new Error('You are not authorized!'); 48 | err.status = 403; 49 | return next(err); 50 | } 51 | }; -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | 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/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "bootstrap": "^4.3.1", 7 | "font-awesome": "^4.7.0", 8 | "http-proxy-middleware": "^2.0.1", 9 | "lodash": "^4.17.21", 10 | "lodash.template": "^4.5.0", 11 | "react": "^17.0.2", 12 | "react-dom": "^17.0.2", 13 | "react-popper": "^2.2.5", 14 | "react-redux": "^7.2.4", 15 | "react-redux-form": "^1.16.14", 16 | "react-router-dom": "^5.2.1", 17 | "react-scripts": "^4.0.3", 18 | "reactstrap": "^8.9.0", 19 | "redux": "^4.1.1", 20 | "redux-thunk": "^2.3.0" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": "react-app" 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjchirag7/lib_management/6b1aba891aee01d92749b51e0cb9002922b6b4e7/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Central Library 24 | 25 | 26 | 27 |
28 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /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/setupProxy.js: -------------------------------------------------------------------------------- 1 | const proxy = require('http-proxy-middleware'); 2 | module.exports = function(app) { 3 | app.use(proxy('/api', 4 | { target: 'http://localhost:5000/' , 5 | secure: false} 6 | )); 7 | } -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | .row-header{ 2 | margin:0px auto; 3 | padding:0px auto; 4 | } 5 | 6 | body{ 7 | background-color: rgba(1, 11, 20, 0.007); 8 | width: 100vw; 9 | } 10 | 11 | .row-content { 12 | margin:0px auto; 13 | padding: 30px 0px 50px 0px; 14 | } 15 | 16 | .footer{ 17 | background-color: rgb(95, 148, 216); 18 | height: 25px; 19 | margin: auto; 20 | } 21 | 22 | 23 | .home{ 24 | background-image: url('./images/walllpaper.jpg'); 25 | min-height: 100vh; 26 | min-width: 99vw; 27 | } 28 | 29 | .darkbg{ 30 | background-color: black; 31 | opacity: 0.7; 32 | color: white; 33 | size: 4rem; 34 | } 35 | 36 | table, th, td { 37 | border: 1px solid white; 38 | } 39 | 40 | table { 41 | min-width: 40% 42 | } 43 | 44 | .heading{ 45 | margin-top: 70px; 46 | } 47 | 48 | .full{ 49 | min-height: 100vh; 50 | } 51 | 52 | .Option{ 53 | cursor: pointer; 54 | } 55 | 56 | .loading{ 57 | min-height: 100vh; 58 | } -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Main from './components/MainComponent'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import {Provider} from 'react-redux'; 5 | import {ConfigureStore} from './redux/store.js' 6 | 7 | import 'bootstrap/dist/css/bootstrap.min.css'; 8 | import 'font-awesome/css/font-awesome.css'; 9 | import './App.css'; 10 | 11 | const store= ConfigureStore(); 12 | 13 | function App() { 14 | return ( 15 | 16 | 17 |
18 |
19 |
20 |
21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /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/baseUrl.js: -------------------------------------------------------------------------------- 1 | export const baseUrl="https://lib-manage.herokuapp.com/api/"; 2 | // export const baseUrl="http://localhost:5000/api/"; -------------------------------------------------------------------------------- /client/src/components/AddBookComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {Button, Label, Col, Row} from 'reactstrap'; 3 | import { Control, LocalForm, Errors } from 'react-redux-form'; 4 | import Loading from './LoadingComponent'; 5 | 6 | const required = (val) => val && val.length; 7 | const requiredNum = (val) => !!(val); 8 | const maxLength = (len) => (val) => !(val) || (val.length <= len); 9 | const minLength = (len) => (val) => (val) && (val.length >= len); 10 | const maxVal = (len) => (val) => !(val) || (val<= len); 11 | const minVal = (len) => (val) => (val) && (val>= len); 12 | const isNumber = (val) => !isNaN(Number(val)); 13 | 14 | class AddBook extends Component { 15 | 16 | constructor(props){ 17 | super(props); 18 | this.state={ 19 | } 20 | 21 | } 22 | 23 | componentDidMount() { 24 | window.scrollTo(0, 0) 25 | } 26 | 27 | render(){ 28 | let uniqueIsbn=(val) =>(!this.props.books.some((book)=>(book.isbn===val))) 29 | let uniqueName=(val) =>(!this.props.books.some((book)=>(book.name===val))) 30 | 31 | if (this.props.booksLoading) { 32 | return( 33 |
34 |
35 | 36 |
37 |
38 | ); 39 | } 40 | else if (this.props.booksErrMess) { 41 | return( 42 |
43 |
44 |
45 |



46 |

{this.props.booksErrMess}

47 |
48 |
49 |
50 | ); 51 | } 52 | else 53 | return ( 54 |
55 |
56 |
57 |

Add a book

58 |
59 |
60 |
61 | { 62 | this.props.postBook(values.name, values.author, values.description, values.isbn, values.cat, values.floor, values.shelf, values.copies); 63 | }}> 64 | 65 | 66 | 67 | 74 | 84 | 85 | 86 | 87 | 94 | 103 | 104 | 105 | 106 | 107 | 108 | 116 | 128 | 129 | 130 | 131 | 138 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 190 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 212 | 213 | 214 | 215 | 216 | 219 | 220 | 221 | 222 |
223 |
224 |
225 | ); 226 | 227 | } 228 | 229 | } 230 | 231 | export default AddBook; -------------------------------------------------------------------------------- /client/src/components/BookDetailComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {Row,Col, Card, CardText, CardHeader, CardFooter, CardBody,CardTitle } from 'reactstrap'; 3 | import Loading from './LoadingComponent'; 4 | function RenderBook({book,isAdmin,toggleEditModal,changeSelected}) { 5 | if (book != null) 6 | return( 7 | 8 | 9 | {book.name}        10 | {isAdmin?({changeSelected(book._id);toggleEditModal();}}/>):()} 11 | 12 | 13 | - {book.author} 14 | 15 | Category: {book.cat}

16 | ISBN number: {book.isbn}

17 | Descrption:
{book.description}

18 | Location:
Shelf no. {book.shelf} ,
19 | {book.floor===0?' Ground ':book.floor}{(book.floor===1)?'st ':(book.floor===2)?'nd ':(book.floor===3)?'rd ':(book.floor===0)?'':'th '} 20 | Floor

21 | Copies available : {book.copies} 22 |

23 |
24 | 25 | 26 | 27 | Created at : {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit', hour: 'numeric',minute: 'numeric', hour12: true }).format(new Date( Date.parse(book.createdAt)))} 28 | 29 | 30 | Last updated at : {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit',hour: 'numeric',minute: 'numeric', hour12: true}).format(new Date( Date.parse(book.updatedAt)))} 31 | 32 | 33 | 34 |
35 | ); 36 | else 37 | return( 38 |
39 | ); 40 | } 41 | 42 | 43 | class BookDetail extends Component { 44 | 45 | constructor(props){ 46 | super(props); 47 | this.state={ 48 | } 49 | } 50 | componentDidMount() { 51 | window.scrollTo(0, 0) 52 | } 53 | render(){ 54 | if (this.props.isLoading) { 55 | return( 56 |
57 |
58 | 59 |
60 |
61 | ); 62 | } 63 | else if (this.props.errMess) { 64 | return( 65 |
66 |
67 |
68 |



69 |

{this.props.errMess}

70 |
71 |
72 |
73 | ); 74 | } 75 | else 76 | return( 77 | 78 |
79 |
80 |
81 |

82 | 85 | 86 | 87 |
88 |
89 |
90 |
91 | ); 92 | } 93 | 94 | } 95 | 96 | export default BookDetail; -------------------------------------------------------------------------------- /client/src/components/BooksComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import { Table } from 'reactstrap'; 3 | import {Link} from 'react-router-dom'; 4 | import Loading from './LoadingComponent.js'; 5 | 6 | // RenderBook is a functional component 7 | function RenderBook ({book, changeSelected ,isAdmin, toggleDeleteModal,toggleEditModal,i}) { 8 | return ( 9 | 10 | 11 | {i} 12 | 13 | 14 | 15 | {book.name} 16 | 17 | 18 | 19 | {book.isbn} 20 | 21 | 22 | {book.author} 23 | 24 | 25 | {book.copies} 26 | 27 | {isAdmin?({changeSelected(book._id); toggleEditModal(); }} className="Option fa fa-pencil"/> 28 |     {changeSelected(book._id); toggleDeleteModal();}} className="Option fa fa-trash"/> 29 | ):()} 30 | 31 | ); 32 | } 33 | 34 | 35 | class Booklist extends Component { 36 | 37 | constructor(props){ 38 | super(props); 39 | this.state={ 40 | } 41 | this.i=1; 42 | } 43 | 44 | 45 | componentDidMount() { 46 | window.scrollTo(0, 0) 47 | } 48 | 49 | render(){ 50 | const list = this.props.books.map((book) => { 51 | return ( 52 | 53 | 58 | 59 | ); 60 | }); 61 | 62 | if (this.props.booksLoading) { 63 | return( 64 |
65 |
66 | 67 |
68 |
69 | ); 70 | } 71 | else if (this.props.booksErrMess) { 72 | return( 73 |
74 |
75 |
76 |



77 |

{this.props.booksErrMess}

78 |
79 |
80 |
81 | ); 82 | } 83 | else 84 | { 85 | return( 86 | 87 |
88 |
89 |
90 |

List of All books

91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | {this.props.isAdmin?():()} 100 | 101 | 102 | 103 | {list} 104 | 105 |
S.No.Name of BookISBN numberAuthorsCopies availableEdit /
Delete
106 |
107 |
108 |
109 | 110 | ); 111 | } 112 | } 113 | 114 | } 115 | 116 | export default Booklist; -------------------------------------------------------------------------------- /client/src/components/FooterComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | 3 | class Footer extends Component { 4 | 5 | constructor(props){ 6 | super(props); 7 | this.state={ 8 | } 9 | } 10 | 11 | render(){ 12 | return( 13 |

© Copyright 2019 IIT(ISM)

14 | ); 15 | } 16 | 17 | } 18 | 19 | export default Footer; -------------------------------------------------------------------------------- /client/src/components/HeaderComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {Navbar, Form, FormGroup, Label, Input, Nav, NavbarToggler,Collapse,NavItem, NavbarBrand, Modal, ModalBody, ModalHeader, Button} from 'reactstrap'; 3 | import {Dropdown,DropdownItem,DropdownMenu,DropdownToggle} from 'reactstrap'; 4 | import { NavLink,Link } from 'react-router-dom'; 5 | import { Control, LocalForm, Errors } from 'react-redux-form'; 6 | 7 | const required = (val) => val && val.length; 8 | const maxLength = (len) => (val) => !(val) || (val.length <= len); 9 | const minLength = (len) => (val) => (val) && (val.length >= len); 10 | const validEmail = (val) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(val); 11 | 12 | 13 | function Registerer(props){ 14 | if(props.isSignedIn===false) 15 | return ( 16 | 17 |   18 | 21 | 22 | ); 23 | else return( 24 | 25 | 26 | ); 27 | } 28 | 29 | 30 | class Header extends Component{ 31 | 32 | constructor(props){ 33 | super(props); 34 | this.state={ 35 | isNavOpen: false, 36 | isModalOpen: false, 37 | isRegisterOpen: false, 38 | dropdownOpen: false 39 | } 40 | this.toggleModal=this.toggleModal.bind(this); 41 | this.toggleNav=this.toggleNav.bind(this); 42 | this.handleLogin = this.handleLogin.bind(this); 43 | this.handleLogout = this.handleLogout.bind(this); 44 | this.toggleRegister=this.toggleRegister.bind(this); 45 | this.toggle=this.toggle.bind(this); 46 | } 47 | 48 | toggle(){ 49 | this.setState({dropdownOpen: !this.state.dropdownOpen}); 50 | } 51 | toggleNav(){ 52 | if(window.innerWidth<1200){ 53 | this.setState({ 54 | isNavOpen: !this.state.isNavOpen 55 | }); 56 | } 57 | } 58 | 59 | toggleRegister(event){ 60 | this.setState({ 61 | isRegisterOpen: !this.state.isRegisterOpen 62 | }); 63 | 64 | } 65 | 66 | handleLogin(event) { 67 | this.toggleModal(); 68 | this.props.loginUser({username: this.username.value, password: this.password.value}); 69 | event.preventDefault(); 70 | } 71 | 72 | handleLogout() { 73 | this.props.logoutUser(); 74 | } 75 | toggleModal() { 76 | this.setState({ 77 | isModalOpen: !this.state.isModalOpen 78 | }); 79 | } 80 | 81 | render(){ 82 | return ( 83 | 84 | 85 |
86 | 87 | 88 | Central Library 89 | 90 | 91 | 171 | 198 | 199 |
200 |
201 | 202 | 203 | Sign In 204 | 205 | 206 |
207 | 208 | 209 | this.username = input} /> 211 | 212 | 213 | 214 | this.password = input} /> 216 | 217 | 218 |
219 |
220 |
221 | 222 | 223 | Register 224 | 225 | 226 | { 227 | this.toggleRegister(); 228 | this.props.registerUser({ 229 | username: values.username, 230 | password: values.password, 231 | email: values.email, 232 | roll: values.roll, 233 | firstname: values.firstname, 234 | lastname: values.lastname }); 235 | }}> 236 | 237 | 238 | 240 | 242 | 243 | 244 | 245 | 247 | 249 | 250 | 251 | 252 | 254 | 256 | 257 | 258 | 259 | 261 | 263 | 264 | 265 | 266 | 268 | 270 | 271 | 272 | 273 | 275 | 277 | 278 | 279 | 280 | 281 | 282 |
283 | ); 284 | } 285 | } 286 | 287 | export default Header; -------------------------------------------------------------------------------- /client/src/components/HistoryComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import { Table } from 'reactstrap'; 3 | import {Link} from 'react-router-dom'; 4 | import Loading from './LoadingComponent.js'; 5 | 6 | const fineRate=1; 7 | let totalFine=0; 8 | const allowedDays=30; 9 | function RenderIssue ({issue,i}) { 10 | const dates=[]; 11 | const today= new Date(); 12 | dates.push(today); 13 | const issueDate=new Date(Date.parse(issue.createdAt)); 14 | const deadline = new Date( Date.parse(issue.createdAt)); 15 | deadline.setDate(deadline.getDate()+30); 16 | dates.push(deadline); 17 | const returnDate=issue.returned?new Date(Date.parse((issue.updatedAt))):(new Date(Math.min.apply(null,dates))); 18 | let fine=0; 19 | if(((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))>allowedDays) 20 | { 21 | fine=Math.floor((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))*fineRate; 22 | } 23 | totalFine+=fine; 24 | return ( 25 | 26 | 27 | {i} 28 | 29 | 30 | 31 | {issue.book.name} 32 | 33 | 34 | 35 | {issue.book.isbn} 36 | 37 | 38 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(new Date( Date.parse(issue.createdAt)))} 39 | 40 | 41 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(deadline)} 42 | 43 | 44 | { 45 | issue.returned?('Returned on '+(new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(new Date( Date.parse(returnDate))))) 46 | :('Not returned yet') 47 | } 48 | 49 | 50 | { 51 | fine 52 | } 53 | 54 | 55 | ); 56 | } 57 | 58 | class History extends Component { 59 | 60 | constructor(props){ 61 | super(props); 62 | this.state={ 63 | } 64 | this.i=1; 65 | } 66 | componentDidMount() { 67 | window.scrollTo(0, 0) 68 | } 69 | 70 | render(){ 71 | 72 | if (this.props.issues.isLoading) { 73 | return( 74 |
75 |
76 | 77 |
78 |
79 | ); 80 | } 81 | else if (this.props.issues.errMess) { 82 | return( 83 |
84 |
85 |
86 |



87 |

{this.props.errMess}

88 |
89 |
90 |
91 | ); 92 | } 93 | else if(this.props.issues.issues.length===0){ 94 | return ( 95 |
96 |
97 |
98 |



99 |

{'You have not issued any books.'}

100 |

{'Request admin to issue a book'}

101 |
102 |
103 |
104 | ); 105 | } 106 | else { 107 | const list = this.props.issues.issues.map((issue) => { 108 | return ( 109 | 110 | 113 | 114 | ); 115 | }); 116 | 117 | return( 118 | 119 |
120 |
121 |
122 |

Issue History

123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | {list} 137 | 138 |
S.No.Name of BookISBN numberIssue DateReturn DeadlineReturn statusFine (in Rs.)
139 |
140 |
Total Fine due (if all pending books are returned today) : Rs. {totalFine}
141 |
142 |
143 |
144 |
145 | ); 146 | } 147 | } 148 | 149 | } 150 | 151 | export default History; -------------------------------------------------------------------------------- /client/src/components/HomeComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | 3 | class Home extends Component { 4 | 5 | constructor(props){ 6 | super(props); 7 | this.state={ 8 | } 9 | } 10 | 11 | componentDidMount() { 12 | window.scrollTo(0, 0) 13 | } 14 | 15 | render(){ 16 | return( 17 |
18 |


19 |
20 |

Welcome to the Central Library

21 |
22 |
23 | 24 |


25 |


26 |
27 | The Central Library of Indian Institute of Technology (Indian School of Mines) Dhanbad is the heart of the institute providing direct academic and research supports to all departments. 28 | 29 | The Central Library is an automated library in terms of records organisation and management of all its different sections, search and discovery, information retrieval and service delivery. The whole library operations run on LIBSYS integrated library management software. 30 |

31 |
32 |
33 | 34 | 35 | 36 | 37 |
Library Timings
Opening Time
9.00 A.M.
Closing Time 9.00 P.M.
38 |
39 |
40 |
41 |

42 |


43 |
44 | ); 45 | } 46 | 47 | } 48 | 49 | export default Home; -------------------------------------------------------------------------------- /client/src/components/IssueComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import { Button, Form, FormGroup, Label, Input, Col,Row } from 'reactstrap'; 3 | import Loading from './LoadingComponent'; 4 | 5 | 6 | class Issue extends Component { 7 | 8 | constructor(props){ 9 | super(props); 10 | this.state={ 11 | isbn: '', 12 | roll: '' 13 | } 14 | } 15 | 16 | componentDidMount() { 17 | window.scrollTo(0, 0) 18 | } 19 | 20 | render(){ 21 | if (this.props.booksLoading||this.props.usersLoading) { 22 | return( 23 |
24 |
25 | 26 |
27 |
28 | ); 29 | } 30 | else if (this.props.booksErrMess) { 31 | return( 32 |
33 |
34 |
35 |



36 |

{this.props.booksErrMess}

37 |
38 |
39 |
40 | ); 41 | } 42 | else if (this.props.usersErrMess) { 43 | return( 44 |
45 |
46 |
47 |



48 |

{this.props.usersErrMess}

49 |
50 |
51 |
52 | ); 53 | } 54 | else 55 | { 56 | const bookoptions= this.props.books.map((book,index)=>()); 58 | const defaultBook=this.props.books[0]; 59 | // To just get list of the students (not the admins) 60 | let useroptions=this.props.users.filter((user)=>(!user.admin)); 61 | const defaultUser=useroptions[0]; 62 | useroptions= useroptions.map((user,index)=>()) 64 | if(this.state.isbn==='') { 65 | this.setState({isbn: defaultBook.isbn,roll: defaultUser.roll }); 66 | } 67 | return ( 68 |
69 |
70 |
71 |

Issue book

72 |
73 |
74 |
75 |
{ 76 | let bookid=this.props.books.filter((book)=>(book.isbn===this.state.isbn))[0]._id 77 | let studentid=this.props.users.filter((user)=>(user.roll===this.state.roll))[0]._id; 78 | this.props.postIssue(bookid,studentid); 79 | e.preventDefault(); 80 | }}> 81 | 82 | 83 | 84 | {this.setState({isbn: e.target.value})}}> 85 | {bookoptions} 86 | 87 | 88 | 89 | 90 | {this.setState({roll: e.target.value})}}> 92 | {useroptions} 93 | 94 | 95 | 96 | 97 | (book.isbn===this.state.isbn))[0].name} 101 | className="form-control" disabled/> 102 | 103 | 104 | 105 | (book.isbn===this.state.isbn))[0].author} 110 | className="form-control" disabled/> 111 | 112 | 113 | 114 | (user.roll===this.state.roll))[0].firstname+' '+ 119 | this.props.users.filter((user)=>(user.roll===this.state.roll))[0].lastname} 120 | className="form-control" disabled/> 121 | 122 | 123 | 124 | (user.roll===this.state.roll))[0].username} 129 | className="form-control" disabled/> 130 | 131 | 132 | 133 | 136 | 137 | 138 |
139 |
140 |
141 |
142 | ); 143 | 144 | } 145 | } 146 | } 147 | 148 | export default Issue; -------------------------------------------------------------------------------- /client/src/components/LoadingComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Loading = () => { 4 | return( 5 |
6 |

7 |

8 |

9 | 10 |

11 | Loading ....

12 |
13 | ); 14 | }; 15 | 16 | export default Loading; -------------------------------------------------------------------------------- /client/src/components/LogComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import { Table } from 'reactstrap'; 3 | import {Link} from 'react-router-dom'; 4 | import Loading from './LoadingComponent.js'; 5 | 6 | const fineRate=1; 7 | 8 | const allowedDays=30; 9 | function RenderIssue ({issue,i}) { 10 | const dates=[]; 11 | const today= new Date(); 12 | dates.push(today); 13 | const issueDate=new Date(Date.parse(issue.createdAt)); 14 | const deadline = new Date( Date.parse(issue.createdAt)); 15 | deadline.setDate(deadline.getDate()+30); 16 | dates.push(deadline); 17 | const returnDate=issue.returned?new Date(Date.parse((issue.updatedAt))):(new Date(Math.min.apply(null,dates))); 18 | let fine=0; 19 | if(((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))>allowedDays) 20 | { 21 | fine=Math.floor((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))*fineRate; 22 | } 23 | return ( 24 | 25 | 26 | {i} 27 | 28 | 29 | 30 | {issue.student.firstname+' '+issue.student.lastname} 31 | 32 | 33 | 34 | {issue.student.roll} 35 | 36 | 37 | {issue.book==null ? "N/A": 38 | {issue.book.name} 39 | } 40 | 41 | 42 | {issue.book==null ? "N/A":issue.book.isbn} 43 | 44 | 45 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(new Date( Date.parse(issue.createdAt)))} 46 | 47 | 48 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(deadline)} 49 | 50 | 51 | { 52 | issue.returned?('Returned on '+(new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(new Date( Date.parse(returnDate))))) 53 | :('Not returned yet') 54 | } 55 | 56 | 57 | { 58 | fine 59 | } 60 | 61 | 62 | ); 63 | } 64 | 65 | class Log extends Component { 66 | 67 | constructor(props){ 68 | super(props); 69 | this.state={ 70 | } 71 | this.i=1; 72 | } 73 | componentDidMount() { 74 | window.scrollTo(0, 0) 75 | } 76 | 77 | render(){ 78 | 79 | if (this.props.issues.isLoading) { 80 | return( 81 |
82 |
83 | 84 |
85 |
86 | ); 87 | } 88 | else if (this.props.issues.errMess) { 89 | return( 90 |
91 |
92 |
93 |



94 |

{this.props.errMess}

95 |
96 |
97 |
98 | ); 99 | } 100 | else if(this.props.issues.issues.length===0){ 101 | return ( 102 |
103 |
104 |
105 |



106 |

{'You have not issued any books.'}

107 |

{'Request admin to issue a book'}

108 |
109 |
110 |
111 | ); 112 | } 113 | else { 114 | const list = this.props.issues.issues.map((issue) => { 115 | return ( 116 | 117 | 120 | 121 | ); 122 | }); 123 | 124 | return( 125 | 126 |
127 |
128 |
129 |

Issue Log

130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | {list} 146 | 147 |
S.No.Name of StudentRoll No.Name of BookISBN numberIssue DateReturn DeadlineReturn statusFine (in Rs.)
148 |
149 | 150 |
151 |
152 |
153 |
154 | ); 155 | } 156 | } 157 | 158 | } 159 | 160 | export default Log; -------------------------------------------------------------------------------- /client/src/components/MainComponent.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Header from './HeaderComponent.js'; 3 | import Footer from './FooterComponent.js'; 4 | import Home from './HomeComponent.js'; 5 | import Booklist from './BooksComponent.js'; 6 | import Search from './SearchComponent.js'; 7 | import BookDetail from './BookDetailComponent.js'; 8 | import Profile from './ProfileComponent.js'; 9 | import AddBook from './AddBookComponent.js'; 10 | import History from './HistoryComponent.js'; 11 | import Issue from './IssueComponent.js'; 12 | import Return from './ReturnComponent.js'; 13 | import UserDetail from './UserDetailComponent.js'; 14 | import Stats from './StatsComponent.js'; 15 | import Log from './LogComponent.js'; 16 | import UserList from './UserListComponent.js'; 17 | 18 | import {Switch,Route,Redirect, withRouter} from 'react-router-dom'; 19 | import {connect} from 'react-redux'; 20 | import {Modal,ModalBody,ModalHeader,Button, Label, Col, Row} from 'reactstrap'; 21 | import { postBook, fetchBooks, editBook, deleteBook,loginUser, logoutUser, 22 | registerUser, editUser, editPassword, postIssue, returnIssue, fetchIssues, fetchUsers} from '../redux/ActionCreators'; 23 | import { Control, LocalForm, Errors } from 'react-redux-form'; 24 | 25 | const required = (val) => val && val.length; 26 | const requiredNum = (val) => !!(val); 27 | const maxLength = (len) => (val) => !(val) || (val.length <= len); 28 | const minLength = (len) => (val) => (val) && (val.length >= len); 29 | const maxVal = (len) => (val) => !(val) || (val<= len); 30 | const minVal = (len) => (val) => (val) && (val>= len); 31 | const isNumber = (val) => !isNaN(Number(val)); 32 | 33 | const mapStateToProps= (state)=>{ 34 | return{ 35 | books: state.books, 36 | auth: state.auth, 37 | issues: state.issues, 38 | users: state.users 39 | }; 40 | } 41 | 42 | const mapDispatchToProps = dispatch => ({ 43 | fetchBooks: () => { dispatch(fetchBooks())}, 44 | fetchIssues: (student) =>{ dispatch(fetchIssues(student))}, 45 | fetchUsers: () => { dispatch(fetchUsers())}, 46 | postBook: (name, author, description, isbn, cat, floor, shelf, copies) => dispatch(postBook(name, author, description, isbn, cat, floor, shelf, copies)), 47 | editBook: (_id, name, author, description, isbn, cat, floor, shelf, copies) => dispatch(editBook(_id, name, author, description, isbn, cat, floor, shelf, copies)), 48 | deleteBook: (_id) => dispatch(deleteBook(_id)), 49 | loginUser: (creds) => dispatch(loginUser(creds)), 50 | logoutUser: () => dispatch(logoutUser()), 51 | registerUser: (creds) => dispatch(registerUser(creds)), 52 | editUser: (_id, firstname, lastname, roll, email) => dispatch(editUser(_id, firstname, lastname, roll, email)), 53 | editPassword : (_id,username,password) => dispatch(editPassword(_id,username,password)), 54 | postIssue: (bookId,studentId) => (dispatch(postIssue(bookId,studentId))), 55 | returnIssue: (issueId) => (dispatch(returnIssue(issueId))) 56 | }); 57 | 58 | class Main extends Component { 59 | 60 | componentDidMount() { 61 | this.props.fetchBooks(); 62 | if(this.props.auth.isAuthenticated){ 63 | this.props.fetchIssues(!this.props.auth.userinfo.admin); 64 | } 65 | if(this.props.auth.isAuthenticated&&this.props.auth.userinfo.admin){ 66 | this.props.fetchUsers(); 67 | } 68 | } 69 | constructor(props){ 70 | super(props); 71 | this.state={ 72 | isDeleteModalOpen: false, 73 | isEditModalOpen: false, 74 | selectedBook: null 75 | }; 76 | this.toggleDeleteModal=this.toggleDeleteModal.bind(this); 77 | this.toggleEditModal=this.toggleEditModal.bind(this); 78 | this.changeSelected=this.changeSelected.bind(this); 79 | this.handleSubmitEdit = this.handleSubmitEdit.bind(this); 80 | } 81 | 82 | handleSubmitEdit(values) { 83 | this.toggleEditModal(); 84 | this.props.editBook(this.state.selectedBook._id, values.name, values.author, 85 | values.description, values.isbn, values.cat, values.floor, values.shelf, values.copies); 86 | } 87 | 88 | changeSelected(_id){ 89 | this.setState({selectedBook:this.props.books.books.filter((book)=>(book._id===_id))[0]}); 90 | } 91 | 92 | toggleDeleteModal(){ 93 | this.setState({isDeleteModalOpen: !this.state.isDeleteModalOpen}) 94 | } 95 | 96 | toggleEditModal(){ 97 | this.setState({isEditModalOpen: !this.state.isEditModalOpen}); 98 | } 99 | 100 | render(){ 101 | const BookWithId = ({match}) => { 102 | let selectedBook=this.props.books.books.filter((book) => (book._id)===(match.params.bookId))[0] 103 | let notFoundErr=null; 104 | if(selectedBook===undefined){ 105 | notFoundErr=("\n\n Error 404 : Book not found"); 106 | } 107 | return( 108 | 115 | ); 116 | }; 117 | 118 | const UserWithId = ({match}) => { 119 | let selectedUser=this.props.users.users.filter((user) => ((user._id)===(match.params.userId)))[0]; 120 | let notFoundErr=null; 121 | if(selectedUser===undefined){ 122 | notFoundErr=("\n\n Error 404 : User not found"); 123 | } 124 | return( 125 | 129 | ); 130 | }; 131 | 132 | const PrivateRouteCommon = ({ component: Component, ...rest }) => ( 133 | ( 134 | this.props.auth.isAuthenticated 135 | ? 136 | : 140 | )} /> 141 | ); 142 | 143 | const PrivateRouteAdmin = ({ component: Component, ...rest }) => ( 144 | ( 145 | this.props.auth.isAuthenticated&&this.props.auth.userinfo.admin 146 | ? 147 | : 151 | )} /> 152 | ); 153 | 154 | const PrivateRoute = ({ component: Component, ...rest }) => ( 155 | ( 156 | this.props.auth.isAuthenticated&&!this.props.auth.userinfo.admin 157 | ? 158 | : 162 | )} /> 163 | ); 164 | 165 | let uniqueIsbn= (defaultIsbn)=> (val) =>(!this.props.books.books.some((book)=>(book.isbn===val))||(val===defaultIsbn)) 166 | let uniqueName= (defaultName)=>(val) =>(!this.props.books.books.some((book)=>(book.name===val))||(val===defaultName)) 167 | 168 | return ( 169 |
170 |
175 | 176 | } /> 177 | } 187 | /> 188 | 189 | }/> 199 | 200 | 204 | } 205 | /> 206 | 213 | }/> 214 | } 217 | /> 218 | } 222 | /> 223 | } 226 | /> 227 | (!user.admin))} 229 | usersLoading={this.props.users.isLoading} 230 | usersErrMess={this.props.users.errMess} 231 | />} 232 | /> 233 | (user.admin))} 235 | usersLoading={this.props.users.isLoading} 236 | usersErrMess={this.props.users.errMess} 237 | />} 238 | /> 239 | } /> 249 | } /> 254 | 255 | }/> 264 | 265 | 266 |
267 | 268 | 269 | Confirm Deletion 270 | 271 | 272 | Book details :

273 | Name : {this.state.selectedBook?this.state.selectedBook.name:''}
274 | Authors : {this.state.selectedBook?this.state.selectedBook.author:''}
275 | ISBN Number : {this.state.selectedBook?this.state.selectedBook.isbn:''}
276 | Available Copies : {this.state.selectedBook?this.state.selectedBook.copies:''}

277 | Are you sure you wish to delete this book ?

278 | {' '} 281 | 284 |
285 |
286 | {this.state.selectedBook?( 287 | 288 | 289 | Edit a book 290 | 291 | 292 | this.handleSubmitEdit(values)}> 293 | 294 | 295 | 296 | 304 | 314 | 315 | 316 | 317 | 318 | 319 | 326 | 335 | 336 | 337 | 338 | 339 | 340 | 348 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 389 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 425 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 446 | 447 | 448 | 449 | 450 | 453 | 454 | 455 | 456 | 457 | 458 | ):()} 459 | 460 |
461 | ); 462 | } 463 | } 464 | 465 | export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Main)); 466 | 467 | -------------------------------------------------------------------------------- /client/src/components/ProfileComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {Card,CardBody,CardHeader,Label,CardText,Button,Modal,ModalBody,ModalHeader,FormGroup} from 'reactstrap'; 3 | import { Control, LocalForm, Errors } from 'react-redux-form'; 4 | 5 | const required = (val) => val && val.length; 6 | const maxLength = (len) => (val) => !(val) || (val.length <= len); 7 | const minLength = (len) => (val) => (val) && (val.length >= len); 8 | const validEmail = (val) => /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(val); 9 | const matchcreds = (original) => (val) => (val===original); 10 | 11 | class Profile extends Component { 12 | 13 | constructor(props){ 14 | super(props); 15 | this.state={ 16 | isEditModalOpen: false, 17 | isPasswordModalOpen: false 18 | } 19 | this.toggleEditModal=this.toggleEditModal.bind(this); 20 | this.togglePasswordModal=this.togglePasswordModal.bind(this); 21 | } 22 | 23 | componentDidMount() { 24 | window.scrollTo(0, 0) 25 | } 26 | 27 | togglePasswordModal(){ 28 | this.setState({ 29 | isPasswordModalOpen: !this.state.isPasswordModalOpen 30 | }); 31 | } 32 | 33 | toggleEditModal(){ 34 | this.setState({isEditModalOpen: !this.state.isEditModalOpen}); 35 | } 36 | 37 | 38 | render(){ 39 | if(this.props.auth.userinfo===null){ 40 | return ( 41 |
42 | Failed to fetch. Please reload the page 43 |
44 | ) 45 | } 46 | return( 47 | 48 |
49 |
50 | 51 | 52 | 53 |

My Profile

54 | 55 | 56 |
First Name : {' '+this.props.auth.userinfo.firstname}
57 |
Last Name : {' '+this.props.auth.userinfo.lastname}
58 |
{(this.props.auth.userinfo.admin)?'Admin Id : ':'Roll No.'} : {' '+this.props.auth.userinfo.roll}
59 |
Email : {' '+this.props.auth.userinfo.email}
60 |
61 | 62 | 63 | {' '} 64 | {this.props.auth.userinfo.admin?
: } 65 | 66 | 67 | 68 |
69 | 70 | 71 | Edit Profile 72 | 73 | 74 | { 75 | this.toggleEditModal(); 76 | this.props.editUser(this.props.auth.userinfo._id, values.firstname, 77 | values.lastname, values.roll, values.email); 78 | }}> 79 | 80 | 81 | 85 | 87 | 88 | 89 | 90 | 93 | 95 | 96 | 97 | 98 | 101 | 103 | 104 | 105 | 106 | 109 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | Change Password 121 | 122 | 123 | { 124 | if(values.newpassword===values.confirm){ 125 | this.togglePasswordModal(); 126 | this.props.editPassword(this.props.auth.userinfo._id, this.props.auth.user.username, 127 | values.newpassword); 128 | } 129 | else { 130 | alert("Your passwords didn't match. Please try again"); 131 | } 132 | }}> 133 | 134 | 135 | 138 | 141 | 142 | 143 | 144 | 145 | 148 | 151 | 152 | 153 | 154 | 155 | 159 | 162 | 163 | 164 | 165 | 166 | 167 | 168 |
169 | ); 170 | } 171 | 172 | } 173 | 174 | export default Profile; -------------------------------------------------------------------------------- /client/src/components/ReturnComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import { Table,Button } from 'reactstrap'; 3 | import {Link} from 'react-router-dom'; 4 | import Loading from './LoadingComponent.js'; 5 | 6 | const fineRate=1; 7 | let totalFine=0; 8 | const allowedDays=30; 9 | function RenderIssue ({issue,i,returnBook}) { 10 | const dates=[]; 11 | const today= new Date(); 12 | dates.push(today); 13 | const issueDate=new Date(Date.parse(issue.createdAt)); 14 | const deadline = new Date( Date.parse(issue.createdAt)); 15 | deadline.setDate(deadline.getDate()+30); 16 | dates.push(deadline); 17 | const returnDate=issue.returned?new Date(Date.parse((issue.updatedAt))):(new Date(Math.min.apply(null,dates))); 18 | let fine=0; 19 | if(((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))>allowedDays) 20 | { 21 | fine=Math.floor((returnDate.getTime()-issueDate.getTime())/(1000 * 60 * 60 * 24))*fineRate; 22 | } 23 | totalFine+=fine; 24 | return ( 25 | 26 | 27 | {i} 28 | 29 | 30 | 31 | {issue.student.firstname+' '+issue.student.lastname} 32 | 33 | 34 | 35 | {issue.student.roll} 36 | 37 | 38 | {issue.book==null ? "N/A": 39 | {issue.book.name} 40 | } 41 | 42 | 43 | {issue.book==null ? "N/A":issue.book.isbn} 44 | 45 | 46 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(new Date( Date.parse(issue.createdAt)))} 47 | 48 | 49 | {new Intl.DateTimeFormat('en-US',{year: 'numeric', month: 'short', day: '2-digit'}).format(deadline)} 50 | 51 | 52 | { 53 | fine 54 | } 55 | 56 | 57 | 60 | 61 | 62 | ); 63 | } 64 | 65 | class Return extends Component { 66 | 67 | constructor(props){ 68 | super(props); 69 | this.state={ 70 | } 71 | this.i=1; 72 | } 73 | 74 | componentDidMount() { 75 | window.scrollTo(0, 0) 76 | } 77 | 78 | render(){ 79 | console.log(this.props.issues); 80 | if (this.props.issues.isLoading) { 81 | return( 82 |
83 |
84 | 85 |
86 |
87 | ); 88 | } 89 | else if (this.props.issues.errMess) { 90 | return( 91 |
92 |
93 |
94 |



95 |

{this.props.errMess}

96 |
97 |
98 |
99 | ); 100 | } 101 | else if(this.props.issues.issues.length===0){ 102 | return ( 103 |
104 |
105 |
106 |



107 |

{'All books have been returned.'}

108 |
109 |
110 |
111 | ); 112 | } 113 | else { 114 | const dueIssues = this.props.issues.issues.filter((issue)=>(!issue.returned)); 115 | const list = dueIssues.map((issue) => { 116 | return ( 117 | 118 | 122 | 123 | ); 124 | }); 125 | 126 | return( 127 | 128 |
129 |
130 |
131 |

List of books not returned

132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | {list} 148 | 149 |
S.No.Name of StudentRoll No.Name of BookISBN numberIssue DateReturn DeadlineFine (in Rs.)Return book
150 |
151 |
Total Fine due (if all pending books are returned today) : Rs. {totalFine}
152 |
153 |
154 |
155 |
156 | ); 157 | } 158 | } 159 | 160 | } 161 | 162 | export default Return; -------------------------------------------------------------------------------- /client/src/components/SearchComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import { Form,FormGroup,Row,Col,Label, Input, Badge, ListGroupItem, ListGroup } from 'reactstrap'; 3 | import Loading from './LoadingComponent.js'; 4 | import {Link} from 'react-router-dom'; 5 | 6 | class Search extends Component { 7 | 8 | constructor(props){ 9 | super(props); 10 | this.state={ 11 | name: '', 12 | author: '', 13 | cat:['Romance','Technology','Computer Science','Management','Electronics','Physics','Chemistry','Mathematics','Fiction','Philosophy','Language','Arts','Other'] 14 | } 15 | this.onChange=this.onChange.bind(this); 16 | } 17 | 18 | onChange(e) { 19 | const options = this.state.cat; 20 | let pos=-1; 21 | 22 | if (e.target.checked) { 23 | options.push(e.target.value) 24 | } else { 25 | let i=0; 26 | for(; i { 48 | let category_matched=this.state.cat.some((category)=>(category===book.cat)); 49 | if(((book.name).search(nameRegex)!==-1)&&((book.author).search(authorRegex)!==-1)&&category_matched) 50 | return ( 51 | 52 | 53 | {`${book.name} `} 54 | 55 | {book.cat} 56 | {this.props.isAdmin?( 57 |    {this.props.changeSelected(book._id); this.props.toggleEditModal(); }} className="Option fa fa-pencil"/> 58 | {' '}    {this.props.changeSelected(book._id); this.props.toggleDeleteModal();}} className="Option fa fa-trash"/> 59 | 60 | ):()} 61 | 62 |

Authors : {` ${book.author}`}

63 |

{book.copies} Copies available

64 |

Location : {` Floor- ${book.floor}, Shelf- ${book.shelf}`}

65 |
); 66 | else return (); 67 | }); 68 | if (this.props.booksLoading) { 69 | return( 70 |
71 |
72 | 73 |
74 |
75 | ); 76 | } 77 | else if (this.props.booksErrMess) { 78 | return( 79 |
80 |
81 |
82 |



83 |

{this.props.booksErrMess}

84 |
85 |
86 |
87 | ); 88 | } 89 | else 90 | { 91 | return( 92 | 93 |
94 |
95 |
96 |

Search your book here :

97 |
98 | 99 | 100 | 101 | 102 | {this.setState({name: e.target.value});}} placeholder="Enter name of the book" /> 103 | 104 | 105 | 106 | 107 | 108 | {this.setState({author: e.target.value});}} placeholder="Enter name of author" /> 109 | 110 | 111 | 112 | 113 | 114 | Category : {' '} 115 | 116 | 117 | 118 | 119 | 120 | 124 | 125 | 126 | 130 | 131 | 132 | 133 | 137 | 138 | 139 | 143 | 144 | 145 | 146 | 147 | 151 | 152 | 153 | 157 | 158 | 159 | 160 | 164 | 165 | 166 | 170 | 171 | 172 | 173 | 174 | 178 | 179 | 180 | 184 | 185 | 186 | 187 | 191 | 192 | 193 | 197 | 198 | 199 | 200 | 201 | 205 | 206 | 207 | 208 |
209 |
210 |
211 |
212 |
213 |
214 | 215 | {list} 216 | 217 |
218 |
219 |
220 |
221 | 222 | ); 223 | } 224 | } 225 | 226 | } 227 | 228 | export default Search; -------------------------------------------------------------------------------- /client/src/components/StatsComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import { Card, CardText, CardBody, CardLink, 3 | CardTitle, Button } from 'reactstrap'; 4 | import {Link} from 'react-router-dom'; 5 | import Loading from './LoadingComponent.js'; 6 | 7 | class Stats extends Component { 8 | 9 | constructor(props){ 10 | super(props); 11 | this.state={ 12 | } 13 | this.i=1; 14 | } 15 | componentDidMount() { 16 | window.scrollTo(0, 0) 17 | } 18 | 19 | render(){ 20 | if (this.props.issues.isLoading||this.props.booksLoading||this.props.usersLoading) { 21 | return( 22 |
23 |
24 | 25 |
26 |
27 | ); 28 | } 29 | else if (this.props.issues.errMess) { 30 | return( 31 |
32 |
33 |
34 |



35 |

{this.props.issues.errMess}

36 |
37 |
38 |
39 | ); 40 | } 41 | else if (this.props.usersErrMess) { 42 | return( 43 |
44 |
45 |
46 |



47 |

{this.props.usersErrMess}

48 |
49 |
50 |
51 | ); 52 | } 53 | else if (this.props.booksErrMess) { 54 | return( 55 |
56 |
57 |
58 |



59 |

{this.props.booksErrMess}

60 |
61 |
62 |
63 | ); 64 | } 65 | else { 66 | 67 | 68 | return( 69 | 70 |
71 |
72 |
73 |

Stats

74 |
75 |
76 |
77 |
78 | 79 | 80 | 81 |

{this.props.books.length}

82 |
83 | Different books available 84 | 85 | 88 | 89 |
90 |
91 |
92 | 93 |
94 | 95 | 96 | 97 |

{this.props.issues.issues.length}

98 |
99 | Books Issued 100 | 101 | 104 | 105 |
106 |
107 |
108 |
109 | 110 | 111 | 112 |

113 | {this.props.issues.issues.filter((issue)=>(!issue.returned)).length} 114 |

115 |
116 | Books not yet returned 117 | 118 | 121 | 122 |
123 |
124 |
125 |
126 | 127 | 128 | 129 |

{this.props.users.filter((user)=>(!user.admin)).length}

130 |
131 | Students registered 132 | 133 | 136 | 137 |
138 |
139 |
140 |
141 | 142 | 143 | 144 |

{this.props.users.filter((user)=>(user.admin)).length}

145 |
146 | Administrators in-charge 147 | 148 | 151 | 152 |
153 |
154 |
155 |
156 | 157 |
158 |
159 | ); 160 | } 161 | } 162 | 163 | } 164 | 165 | export default Stats; -------------------------------------------------------------------------------- /client/src/components/UserDetailComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import {Card,CardBody,CardHeader,CardText} from 'reactstrap'; 3 | import Loading from './LoadingComponent'; 4 | 5 | class UserDetail extends Component { 6 | 7 | constructor(props){ 8 | super(props); 9 | this.state={ 10 | } 11 | } 12 | 13 | componentDidMount() { 14 | window.scrollTo(0, 0) 15 | } 16 | 17 | 18 | render(){ 19 | if (this.props.isLoading) { 20 | return( 21 |
22 |
23 | 24 |
25 |
26 | ); 27 | } 28 | else if (this.props.errMess) { 29 | return( 30 |
31 |
32 |
33 |



34 |

{this.props.errMess}

35 |
36 |
37 |
38 | ); 39 | } 40 | else 41 | return( 42 | 43 |
44 |
45 | 46 | 47 | 48 |

User Details

49 | 50 | 51 |
First Name : {' '+this.props.user.firstname}
52 |
Last Name : {' '+this.props.user.lastname}
53 |
{(this.props.user.admin)?'Admin Id : ':'Roll No.'} : {' '+this.props.user.roll}
54 |
Email : {' '+this.props.user.email}
55 |
Username : {' '+this.props.user.username}
56 |
57 |
58 |
59 |
60 |
61 | ); 62 | } 63 | 64 | } 65 | 66 | export default UserDetail; -------------------------------------------------------------------------------- /client/src/components/UserListComponent.js: -------------------------------------------------------------------------------- 1 | import React,{Component} from 'react'; 2 | import { Table } from 'reactstrap'; 3 | import {Link} from 'react-router-dom'; 4 | import Loading from './LoadingComponent.js'; 5 | 6 | function RenderUser ({user,i}) { 7 | return ( 8 | 9 | 10 | {i} 11 | 12 | 13 | 14 | {user.firstname+' '+ user.lastname} 15 | 16 | 17 | 18 | {user.roll} 19 | 20 | 21 | {user.username} 22 | 23 | 24 | {user.email} 25 | 26 | 27 | ); 28 | } 29 | 30 | class UserList extends Component { 31 | 32 | constructor(props){ 33 | super(props); 34 | this.state={ 35 | } 36 | this.i=1; 37 | } 38 | componentDidMount() { 39 | window.scrollTo(0, 0) 40 | } 41 | 42 | render(){ 43 | 44 | if (this.props.usersLoading) { 45 | return( 46 |
47 |
48 | 49 |
50 |
51 | ); 52 | } 53 | else if (this.props.usersErrMess) { 54 | return( 55 |
56 |
57 |
58 |



59 |

{this.props.errMess}

60 |
61 |
62 |
63 | ); 64 | } 65 | else if(this.props.users.length===0){ 66 | return ( 67 |
68 |
69 |
70 |



71 |

{'No users found'}

72 |
73 |
74 |
75 | ); 76 | } 77 | else { 78 | const list = this.props.users.map((user) => { 79 | return ( 80 | 81 | 84 | 85 | ); 86 | }); 87 | 88 | return( 89 | 90 |
91 |
92 |
93 |

List of {this.props.users[0].admin?' admins in-charge': ' students registered'}

94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | {list} 106 | 107 |
S.No.Name of {this.props.users[0].admin?' admin': ' student'}{this.props.users[0].admin?' Admin Id': ' Roll No.'}UsernameEmail
108 |
109 |
110 |
111 |
112 |
113 | ); 114 | } 115 | } 116 | 117 | } 118 | 119 | export default UserList; -------------------------------------------------------------------------------- /client/src/images/walllpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cjchirag7/lib_management/6b1aba891aee01d92749b51e0cb9002922b6b4e7/client/src/images/walllpaper.jpg -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /client/src/redux/ActionCreators.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from './ActionTypes'; 2 | import {baseUrl} from '../baseUrl' 3 | 4 | export const addBook = (book) => ({ 5 | type: ActionTypes.ADD_BOOK, 6 | payload: book 7 | }); 8 | 9 | export const postBook = (name, author, description, isbn, cat, floor, shelf, copies) => (dispatch) => { 10 | const newBook = { 11 | name: name, author: author, 12 | description: description, isbn: isbn, 13 | cat: cat, floor: floor, 14 | shelf: shelf, copies: copies 15 | }; 16 | const bearer = 'Bearer ' + localStorage.getItem('token'); 17 | return fetch(baseUrl + 'books', { 18 | method: "POST", 19 | body: JSON.stringify(newBook), 20 | headers: { 21 | "Content-Type": "application/json", 22 | 'Authorization': bearer 23 | } 24 | // , credentials: "same-origin" 25 | }) 26 | .then(response => { 27 | if (response.ok) { 28 | return response; 29 | } else { 30 | var error = new Error('Error ' + response.status + ': ' + response.statusText); 31 | error.response = response; 32 | throw error; 33 | } 34 | }, 35 | error => { 36 | throw error; 37 | }) 38 | .then(response => response.json()) 39 | .then(response => { alert('Book added successfully'); 40 | return dispatch(addBook(response));}) 41 | .catch(error => { alert('Your book could not be added\nError: '+error.message); }); 42 | }; 43 | 44 | export const editBook = (_id, name, author, description, isbn, cat, floor, shelf, copies) => (dispatch) => { 45 | 46 | const newBook = { 47 | name: name, author: author, 48 | description: description, isbn: isbn, 49 | cat: cat, floor: floor, 50 | shelf: shelf, copies: copies 51 | }; 52 | const bearer = 'Bearer ' + localStorage.getItem('token'); 53 | return fetch(baseUrl + 'books/' + _id, { 54 | method: "PUT" 55 | // , credentials: 'same-origin' 56 | , body: JSON.stringify(newBook), 57 | headers: { 58 | "Content-Type": "application/json", 59 | 'Authorization': bearer 60 | } }) 61 | .then(response => { 62 | if (response.ok) { 63 | return response; 64 | } else { 65 | var error = new Error('Error ' + response.status + ': ' + response.statusText); 66 | error.response = response; 67 | throw error; 68 | } 69 | }, 70 | error => { 71 | throw error; 72 | }) 73 | .then(response => response.json()) 74 | .then(response => (dispatch(editBookdispatch(response)))) 75 | .catch(error => { 76 | alert('Your book could not be edited\nError: '+error.message); }); 77 | }; 78 | 79 | export const editPassword = (_id,username,password) => (dispatch) => { 80 | const bearer = 'Bearer ' + localStorage.getItem('token'); 81 | return fetch(baseUrl + 'users/password/' + _id, { 82 | method: "PUT" 83 | // , credentials: 'same-origin' 84 | , body: JSON.stringify({password: password}), 85 | headers: { 86 | "Content-Type": "application/json", 87 | 'Authorization': bearer 88 | } }) 89 | .then(response => { 90 | if (response.ok) { 91 | return response; 92 | } else { 93 | var error = new Error('Error ' + response.status + ': ' + response.statusText+'\n '); 94 | error.response = response; 95 | throw error; 96 | } 97 | }, 98 | error => { 99 | throw error; 100 | }) 101 | .then(response => response.json()) 102 | .then(response => { 103 | let newCreds={username: username, password: password}; 104 | localStorage.removeItem('creds'); 105 | localStorage.setItem('creds', JSON.stringify(newCreds)); 106 | alert('Password changed successfully'); 107 | return dispatch(editPasswordDispatch(newCreds));}) 108 | .catch(error => { 109 | alert('Your password could not be changed\nError: '+error.message); }); 110 | } 111 | 112 | export const editUser = (_id, firstname, lastname, roll, email) => (dispatch) => { 113 | 114 | const newUser = { 115 | firstname: firstname, 116 | lastname: lastname, 117 | roll: roll, 118 | email: email }; 119 | const bearer = 'Bearer ' + localStorage.getItem('token'); 120 | return fetch(baseUrl + 'users/' + _id, { 121 | method: "PUT" 122 | // , credentials: 'same-origin' 123 | , body: JSON.stringify(newUser), 124 | headers: { 125 | "Content-Type": "application/json", 126 | 'Authorization': bearer 127 | } }) 128 | .then(response => { 129 | if (response.ok) { 130 | return response; 131 | } else { 132 | var error = new Error('Error ' + response.status + ': ' + response.statusText); 133 | error.response = response; 134 | throw error; 135 | } 136 | }, 137 | error => { 138 | throw error; 139 | }) 140 | .then(response => response.json()) 141 | .then(response => { 142 | localStorage.removeItem('userinfo'); 143 | localStorage.setItem('userinfo', JSON.stringify(response)); 144 | return dispatch(editUserdispatch(response));}) 145 | .catch(error => { 146 | alert('Your profile could not be edited\nError: '+error.message+'\n May be someone has already registered with that Roll No. or Email'); }); 147 | }; 148 | 149 | export const deleteBook = (_id) => (dispatch) => { 150 | 151 | const bearer = 'Bearer ' + localStorage.getItem('token'); 152 | return fetch(baseUrl + 'books/' + _id, { 153 | method: "DELETE" 154 | // , credentials: "same-origin" 155 | , headers: { 156 | 'Authorization': bearer 157 | } 158 | }) 159 | .then(response => { 160 | if (response.ok) { 161 | return response; 162 | } else { 163 | var error = new Error('Error ' + response.status + ': ' + response.statusText); 164 | error.response = response; 165 | throw error; 166 | } 167 | }, 168 | error => { 169 | throw error; 170 | }) 171 | .then(response => response.json()) 172 | .then(response => dispatch(deleteBookdispatch(response))) 173 | .catch(error => {alert('Your book could not be deleted\nError: '+error.message); }); 174 | }; 175 | 176 | export const fetchBooks = () => (dispatch) => { 177 | dispatch(booksLoading(true)); 178 | return fetch(baseUrl+'books') 179 | .then(response => { 180 | if (response.ok) { 181 | return response; 182 | } else { 183 | var error = new Error('Error ' + response.status + ': ' + response.statusText); 184 | error.response = response; 185 | throw error; 186 | } 187 | }, 188 | error => { 189 | var errmess = new Error(error.message); 190 | throw errmess; 191 | }) 192 | .then(response => response.json()) 193 | .then(books => dispatch(addBooks(books))) 194 | .catch(error => dispatch(booksFailed(error.message))); 195 | } 196 | 197 | 198 | export const fetchUsers = () => (dispatch) => { 199 | const bearer = 'Bearer ' + localStorage.getItem('token'); 200 | dispatch(usersLoading(true)); 201 | return fetch(baseUrl+'users',{ 202 | headers: { 203 | 'Authorization': bearer 204 | } 205 | }) 206 | .then(response => { 207 | if (response.ok) { 208 | return response; 209 | } else { 210 | var error = new Error('Error ' + response.status + ': ' + response.statusText); 211 | error.response = response; 212 | throw error; 213 | } 214 | }, 215 | error => { 216 | var errmess = new Error(error.message); 217 | throw errmess; 218 | }) 219 | .then(response => response.json()) 220 | .then(users => dispatch(addUsers(users))) 221 | .catch(error => dispatch(usersFailed(error.message))); 222 | } 223 | 224 | 225 | export const booksLoading = () => ({ 226 | type: ActionTypes.BOOKS_LOADING 227 | }); 228 | 229 | export const booksFailed = (errmess) => ({ 230 | type: ActionTypes.BOOKS_FAILED, 231 | payload: errmess 232 | }); 233 | 234 | export const addBooks = (books) => ({ 235 | type: ActionTypes.ADD_BOOKS, 236 | payload: books 237 | }); 238 | 239 | export const addUsers = (users) => ({ 240 | type: ActionTypes.ADD_USERS, 241 | payload: users 242 | }); 243 | 244 | export const usersLoading = () => ({ 245 | type: ActionTypes.USERS_LOADING 246 | }); 247 | 248 | export const editBookdispatch = (books) => ({ 249 | type: ActionTypes.EDIT_BOOK, 250 | payload: books 251 | }); 252 | 253 | export const returnBookdispatch = (issue) => ({ 254 | type: ActionTypes.RETURN_ISSUE, 255 | payload: issue 256 | }); 257 | 258 | export const editUserdispatch = (USER) => ({ 259 | type: ActionTypes.EDIT_USER, 260 | payload: USER 261 | }); 262 | 263 | export const editPasswordDispatch = (CREDS) => ({ 264 | type: ActionTypes.EDIT_PASSWORD, 265 | payload: CREDS 266 | }) 267 | 268 | export const deleteBookdispatch = (resp) => ({ 269 | type: ActionTypes.DELETE_BOOK, 270 | payload: resp 271 | }); 272 | 273 | export const requestLogin = (creds) => { 274 | return { 275 | type: ActionTypes.LOGIN_REQUEST, 276 | creds 277 | } 278 | } 279 | 280 | export const receiveLogin = (response) => { 281 | return { 282 | type: ActionTypes.LOGIN_SUCCESS, 283 | token: response.token, 284 | userinfo: response.userinfo 285 | } 286 | } 287 | 288 | export const loginError = (message) => { 289 | return { 290 | type: ActionTypes.LOGIN_FAILURE, 291 | message 292 | } 293 | } 294 | 295 | export const loginUser = (creds) => (dispatch) => { 296 | 297 | dispatch(requestLogin(creds)); 298 | return fetch(baseUrl + 'users/login', { 299 | method: 'POST', 300 | headers: { 301 | 'Content-Type':'application/json' 302 | }, 303 | body: JSON.stringify(creds) 304 | }) 305 | .then(response => { 306 | if (response.ok) { 307 | return response; 308 | } else { 309 | var error = new Error('Error ' + response.status + ': ' + response.statusText); 310 | error.response = response; 311 | throw error; 312 | } 313 | }, 314 | error => { 315 | throw error; 316 | }) 317 | .then(response => response.json()) 318 | .then(response => { 319 | if (response.success) { 320 | // If login was successful, set the token in local storage 321 | localStorage.setItem('token', response.token); 322 | localStorage.setItem('creds', JSON.stringify(creds)); 323 | localStorage.setItem('userinfo', JSON.stringify(response.userinfo)); 324 | dispatch(fetchIssues(!response.userinfo.admin)); 325 | if(response.userinfo.admin) { 326 | dispatch(fetchUsers()) 327 | } 328 | setTimeout(()=>{ 329 | logoutUser(); 330 | alert('Your JWT token has expired. \nPlease log in again to continue.'); 331 | },3600*1000); 332 | // Dispatch the success action 333 | dispatch(receiveLogin(response)); 334 | 335 | } 336 | else { 337 | var error = new Error('Error ' + response.status); 338 | error.response = response; 339 | throw error; 340 | } 341 | }) 342 | .catch(error => { 343 | alert(error.message+'\n'+"Username and password didn't match"); 344 | return dispatch(loginError(error.message));}) 345 | }; 346 | 347 | export const registerUser = (creds) => (dispatch) => { 348 | 349 | 350 | return fetch(baseUrl + 'users/signup', { 351 | method: 'POST', 352 | headers: { 353 | 'Content-Type':'application/json' 354 | }, 355 | body: JSON.stringify(creds) 356 | }) 357 | .then(response => { 358 | if (response.ok) { 359 | return response; 360 | } else { 361 | var error = new Error('Error ' + response.status + ': ' + response.statusText); 362 | error.response = response; 363 | throw error; 364 | } 365 | }, 366 | error => { 367 | throw error; 368 | }) 369 | .then(response => response.json()) 370 | .then(response => { 371 | if (response.success) { 372 | // If Registration was successful, alert the user 373 | alert('Registration Successful'); 374 | } 375 | else { 376 | var error = new Error('Error ' + response.status); 377 | error.response = response; 378 | throw error; 379 | } 380 | }) 381 | .catch(error => alert(error.message+'\n'+ 382 | 'May be someone has already registered with that username, email or Roll No.\nTry Entering a new username,email or Roll No. ')) 383 | }; 384 | 385 | export const addIssue = (issue) => ({ 386 | type: ActionTypes.ADD_ISSUE, 387 | payload: issue 388 | }); 389 | 390 | export const postIssue = (bookId,studentId) => (dispatch) => { 391 | const newIssue = { 392 | book: bookId, 393 | student: studentId 394 | }; 395 | const bearer = 'Bearer ' + localStorage.getItem('token'); 396 | return fetch(baseUrl + 'issues', { 397 | method: "POST", 398 | body: JSON.stringify(newIssue), 399 | headers: { 400 | "Content-Type": "application/json", 401 | 'Authorization': bearer 402 | } 403 | // , credentials: "same-origin" 404 | }) 405 | .then(response => { 406 | if (response.ok) { 407 | return response; 408 | } else { 409 | var error = new Error('Error ' + response.status + ': ' + response.statusText); 410 | error.response = response; 411 | throw error; 412 | } 413 | }, 414 | error => { 415 | throw error; 416 | }) 417 | .then(response => response.json()) 418 | .then(response => { alert('Book issued successfully'); 419 | return dispatch(addIssue(response));}) 420 | .catch(error => { 421 | alert('Book could not be issued\nError: '+error.message+'\n'+ 422 | 'May be the student has already issued 3 books and not returned. Please return them first. \n'+ 423 | 'or the book may not available. You can wait for some days, until the book is returned to library.'); }); 424 | }; 425 | 426 | export const returnIssue = (issueId) => (dispatch) => { 427 | const bearer = 'Bearer ' + localStorage.getItem('token'); 428 | return fetch(baseUrl + 'issues/' + issueId, { 429 | method: "PUT" 430 | // , credentials: 'same-origin' 431 | , headers: { 432 | "Content-Type": "application/json", 433 | 'Authorization': bearer 434 | } }) 435 | .then(response => { 436 | if (response.ok) { 437 | return response; 438 | } else { 439 | var error = new Error('Error ' + response.status + ': ' + response.statusText); 440 | error.response = response; 441 | throw error; 442 | } 443 | }, 444 | error => { 445 | throw error; 446 | }) 447 | .then(response => response.json()) 448 | .then(response => { 449 | alert('Book returned successfully'); 450 | return dispatch(returnBookdispatch(response));}) 451 | .catch(error => { 452 | alert('The book could not be returned\nError: '+error.message); }); 453 | }; 454 | 455 | export const fetchIssues = (student) => (dispatch) => { 456 | let issueUrl; 457 | const bearer = 'Bearer ' + localStorage.getItem('token'); 458 | if(student) { 459 | issueUrl='issues/student'; 460 | } 461 | else { 462 | issueUrl='issues'; 463 | } 464 | dispatch(issuesLoading(true)); 465 | return fetch(baseUrl+issueUrl,{ 466 | headers: { 467 | 'Authorization': bearer 468 | } 469 | }) 470 | .then(response => { 471 | if (response.ok) { 472 | return response; 473 | } else { 474 | var error = new Error('Error ' + response.status + ': ' + response.statusText); 475 | error.response = response; 476 | throw error; 477 | } 478 | }, 479 | error => { 480 | var errmess = new Error(error.message); 481 | throw errmess; 482 | }) 483 | .then(response => response.json()) 484 | .then(issues => dispatch(addIssues(issues))) 485 | .catch(error => dispatch(issuesFailed(error.message))); 486 | } 487 | 488 | 489 | 490 | export const issuesLoading = () => ({ 491 | type: ActionTypes.ISSUES_LOADING 492 | }); 493 | 494 | export const issuesFailed = (errmess) => ({ 495 | type: ActionTypes.ISSUES_FAILED, 496 | payload: errmess 497 | }); 498 | 499 | export const addIssues = (issues) => ({ 500 | type: ActionTypes.ADD_ISSUES, 501 | payload: issues 502 | }); 503 | 504 | export const usersFailed = (errmess) => ({ 505 | type: ActionTypes.USERS_FAILED, 506 | payload: errmess 507 | }); 508 | 509 | 510 | export const requestLogout = () => { 511 | return { 512 | type: ActionTypes.LOGOUT_REQUEST 513 | } 514 | } 515 | 516 | export const receiveLogout = () => { 517 | return { 518 | type: ActionTypes.LOGOUT_SUCCESS 519 | } 520 | } 521 | 522 | 523 | export const logoutUser = () => (dispatch) => { 524 | dispatch(requestLogout()) 525 | localStorage.removeItem('token'); 526 | localStorage.removeItem('creds'); 527 | localStorage.removeItem('userinfo'); 528 | dispatch(receiveLogout()) 529 | } 530 | -------------------------------------------------------------------------------- /client/src/redux/ActionTypes.js: -------------------------------------------------------------------------------- 1 | export const BOOKS_LOADING = 'BOOKS_LOADING'; 2 | export const BOOKS_FAILED = 'BOOKS_FAILED'; 3 | export const ADD_BOOKS = 'ADD_BOOKS'; 4 | export const ADD_BOOK = 'ADD_BOOK'; 5 | export const EDIT_BOOK = 'EDIT_BOOK'; 6 | export const DELETE_BOOK = 'DELETE_BOOK'; 7 | export const LOGIN_REQUEST = 'LOGIN_REQUEST'; 8 | export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; 9 | export const LOGIN_FAILURE = 'LOGIN_FAILURE'; 10 | export const LOGOUT_REQUEST = 'LOGOUT_REQUEST'; 11 | export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; 12 | export const LOGOUT_FAILURE = 'LOGOUT_FAILURE'; 13 | export const EDIT_USER = 'EDIT_USER'; 14 | export const EDIT_PASSWORD = 'EDIT_PASSWORD'; 15 | export const ISSUES_LOADING = 'ISSUES_LOADING'; 16 | export const ISSUES_FAILED = 'ISSUES_FAILED'; 17 | export const ADD_ISSUES = 'ADD_ISSUES'; 18 | export const ADD_ISSUE = 'ADD_ISSUE'; 19 | export const RETURN_ISSUE = 'RETURN_ISSUE'; 20 | export const USERS_LOADING = 'USERS_LOADING'; 21 | export const USERS_FAILED = 'USERS_FAILED'; 22 | export const ADD_USERS = 'ADD_USERS'; -------------------------------------------------------------------------------- /client/src/redux/auth.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from './ActionTypes'; 2 | 3 | // The auth reducer. The starting state sets authentication 4 | // based on a token being in local storage. In a real app, 5 | // we would also want a util to check if the token is expired. 6 | const Auth = (state = { 7 | isLoading: false, 8 | isAuthenticated: localStorage.getItem('token') ? true : false, 9 | token: localStorage.getItem('token'), 10 | user: localStorage.getItem('creds') ? JSON.parse(localStorage.getItem('creds')) : null, 11 | userinfo: localStorage.getItem('userinfo') ? JSON.parse(localStorage.getItem('userinfo')) : null, 12 | errMess: null 13 | }, action) => { 14 | switch (action.type) { 15 | case ActionTypes.LOGIN_REQUEST: 16 | return {...state, 17 | isLoading: true, 18 | isAuthenticated: false, 19 | user: action.creds 20 | }; 21 | case ActionTypes.LOGIN_SUCCESS: 22 | return {...state, 23 | isLoading: false, 24 | isAuthenticated: true, 25 | errMess: '', 26 | token: action.token, 27 | userinfo: action.userinfo 28 | }; 29 | case ActionTypes.LOGIN_FAILURE: 30 | return {...state, 31 | isLoading: false, 32 | isAuthenticated: false, 33 | errMess: action.message 34 | }; 35 | case ActionTypes.LOGOUT_REQUEST: 36 | return {...state, 37 | isLoading: true, 38 | isAuthenticated: true 39 | }; 40 | 41 | case ActionTypes.LOGOUT_SUCCESS: 42 | return {...state, 43 | isLoading: false, 44 | isAuthenticated: false, 45 | token: '', 46 | user: null, 47 | userinfo: null 48 | }; 49 | 50 | case ActionTypes.EDIT_USER: 51 | return {...state, 52 | userinfo: action.payload}; 53 | 54 | case ActionTypes.EDIT_PASSWORD: 55 | return { 56 | ...state, 57 | user: action.payload 58 | } 59 | default: 60 | return state 61 | } 62 | } 63 | 64 | export default Auth; -------------------------------------------------------------------------------- /client/src/redux/books.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from './ActionTypes'; 2 | 3 | const Books = (state = { isLoading: true, 4 | errMess: null, 5 | books:[]}, action) => { 6 | switch (action.type) { 7 | case ActionTypes.ADD_BOOKS: 8 | return {...state, isLoading: false, errMess: null, books: action.payload}; 9 | 10 | case ActionTypes.BOOKS_LOADING: 11 | return {...state, isLoading: true, errMess: null, books: []} 12 | 13 | case ActionTypes.BOOKS_FAILED: 14 | return {...state, isLoading: false, errMess: action.payload}; 15 | 16 | case ActionTypes.ADD_BOOK: 17 | var book = action.payload; 18 | return { ...state, books: state.books.concat(book)}; 19 | 20 | case ActionTypes.EDIT_BOOK: 21 | var newbook = action.payload; 22 | return { ...state, books: state.books.map((book)=> 23 | { 24 | if(book._id===newbook._id) 25 | { 26 | return newbook; 27 | } 28 | else { 29 | return book; 30 | } 31 | }) 32 | } 33 | 34 | case ActionTypes.DELETE_BOOK: 35 | var resp = action.payload; 36 | return { ...state, books: state.books.filter((book)=>{ 37 | return book._id!==resp._id}) } 38 | 39 | default: 40 | return state; 41 | } 42 | }; 43 | export default Books; -------------------------------------------------------------------------------- /client/src/redux/issues.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from './ActionTypes'; 2 | 3 | const Issues = (state = { isLoading: true, 4 | errMess: null, 5 | issues:[]}, action) => { 6 | switch (action.type) { 7 | case ActionTypes.ADD_ISSUES: 8 | return {...state, isLoading: false, errMess: null, issues: action.payload}; 9 | 10 | case ActionTypes.ISSUES_LOADING: 11 | return {...state, isLoading: true, errMess: null, issues: []} 12 | 13 | case ActionTypes.ISSUES_FAILED: 14 | return {...state, isLoading: false, errMess: action.payload}; 15 | 16 | case ActionTypes.ADD_ISSUE: 17 | var issue = action.payload; 18 | return { ...state, issues: state.issues.concat(issue)}; 19 | 20 | case ActionTypes.RETURN_ISSUE: 21 | var newissue = action.payload; 22 | return { ...state, issues: state.issues.map((issue)=> 23 | { 24 | if(issue._id===newissue._id) 25 | { 26 | return newissue; 27 | } 28 | else { 29 | return issue; 30 | } 31 | }) 32 | } 33 | 34 | default: 35 | return state; 36 | } 37 | }; 38 | export default Issues; -------------------------------------------------------------------------------- /client/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import Books from './books.js'; 4 | import Auth from './auth.js'; 5 | import Issues from './issues.js'; 6 | import Users from './users.js' 7 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 8 | 9 | export const ConfigureStore = ()=>{ 10 | const store=createStore( 11 | combineReducers({ 12 | books: Books, 13 | auth: Auth, 14 | issues: Issues, 15 | users: Users 16 | }), 17 | composeEnhancers(applyMiddleware(thunk)) 18 | ); 19 | return store; 20 | } -------------------------------------------------------------------------------- /client/src/redux/users.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from './ActionTypes'; 2 | 3 | const Users = (state = { isLoading: true, 4 | errMess: null, 5 | users:[]}, action) => { 6 | switch (action.type) { 7 | case ActionTypes.ADD_USERS: 8 | return {...state, isLoading: false, errMess: null, users: action.payload}; 9 | 10 | case ActionTypes.USERS_LOADING: 11 | return {...state, isLoading: true, errMess: null, users: []} 12 | 13 | case ActionTypes.USERS_FAILED: 14 | return {...state, isLoading: false, errMess: action.payload}; 15 | 16 | default: 17 | return state; 18 | } 19 | }; 20 | export default Users; -------------------------------------------------------------------------------- /client/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /config/keys.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | mongoURI: process.env.MONGO_URI, 3 | secretKey: process.env.SECRET_KEY 4 | } 5 | -------------------------------------------------------------------------------- /models/books.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const bookSchema = new Schema({ 5 | name: { 6 | type: String, 7 | minlength: 3, 8 | required: true, 9 | unique: true 10 | }, 11 | author: { 12 | type: String, 13 | required: true 14 | }, 15 | description: { 16 | type: String, 17 | default: '----Not available----' 18 | }, 19 | isbn: { 20 | type: String, 21 | minlength: 10, 22 | maxlength: 13, 23 | required: true, 24 | unique: true 25 | }, 26 | cat: { 27 | type: String, 28 | enum: ['Romance','Technology','Computer Science','Management','Electronics','Physics','Chemistry','Mathematics','Fiction','Philosophy','Language','Arts','Other'], 29 | required: true 30 | }, 31 | copies: { 32 | type: Number, 33 | min: 1, 34 | max: 1000, 35 | required: true 36 | }, 37 | shelf: { 38 | type: Number, 39 | min: 1, 40 | max: 100, 41 | required: true 42 | }, 43 | floor: { 44 | type: Number, 45 | min: 0, 46 | max: 8 47 | } 48 | }, { 49 | timestamps: true 50 | }); 51 | var Books = mongoose.model('Book',bookSchema); 52 | 53 | module.exports=Books; -------------------------------------------------------------------------------- /models/issues.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const issueSchema = new Schema({ 5 | student: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: 'User', 8 | required: true 9 | }, 10 | book: { 11 | type: mongoose.Schema.Types.ObjectId, 12 | ref: 'Book', 13 | required: true 14 | }, 15 | returned: { 16 | type: Boolean, 17 | default: false 18 | } 19 | }, { 20 | timestamps: true 21 | }); 22 | var Issues = mongoose.model('Issue',issueSchema); 23 | 24 | module.exports=Issues; -------------------------------------------------------------------------------- /models/users.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | var passportLocalMongoose = require('passport-local-mongoose'); 4 | 5 | var User = new Schema({ 6 | firstname: { 7 | type: String, 8 | required: true, 9 | }, 10 | lastname: { 11 | type: String, 12 | required: true, 13 | }, 14 | email:{ 15 | type: String, 16 | required: true, 17 | unique: true 18 | }, 19 | roll:{ 20 | type: String, 21 | required: true, 22 | unique: true 23 | }, 24 | admin: { 25 | type: Boolean, 26 | default: false 27 | } 28 | }); 29 | 30 | User.plugin(passportLocalMongoose); 31 | module.exports = mongoose.model('User', User); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lib_management", 3 | "version": "1.0.0", 4 | "description": "Library Management Website using MERN stack", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "client-install": "npm install --prefix client", 9 | "start": "node server.js", 10 | "server": "nodemon server.js", 11 | "client": "npm start --prefix client", 12 | "dev": "concurrently \"npm run server\" \"npm run client\" ", 13 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix client && npm run build --prefix client" 14 | }, 15 | "author": "Chirag Jain", 16 | "license": "ISC", 17 | "dependencies": { 18 | "body-parser": "^1.19.0", 19 | "concurrently": "^6.2.1", 20 | "cors": "^2.8.5", 21 | "express": "^4.17.1", 22 | "font-awesome": "^4.7.0", 23 | "jsonwebtoken": "^8.5.1", 24 | "mongoose": "^6.0.4", 25 | "passport": "^0.4.1", 26 | "passport-jwt": "^4.0.0", 27 | "passport-local-mongoose": "^6.1.0" 28 | }, 29 | "devDependencies": { 30 | "nodemon": "^2.0.12" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /routes/api/bookRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const mongoose=require('mongoose'); 4 | const bookRouter = express.Router(); 5 | const authenticate=require('../../authenticate'); 6 | const cors = require('../cors'); 7 | const Books=require('../../models/books'); 8 | bookRouter.use(bodyParser.json()); 9 | 10 | bookRouter.route('/') 11 | .options(cors.corsWithOptions, (req, res) => { res.sendStatus(200); }) 12 | .get(cors.corsWithOptions,(req,res,next) => { 13 | Books.find(req.query) 14 | .sort({name: 'asc'}) 15 | .then((books)=>{ 16 | res.statusCode=200; 17 | res.setHeader('Content-Type','application/json'); 18 | res.json(books); 19 | },(err)=>(next(err))) 20 | .catch((err)=>(next(err))) 21 | }) 22 | .post(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 23 | Books.create(req.body) 24 | .then((book)=>{ 25 | res.statusCode=200; 26 | res.setHeader('Content-Type','application/json'); 27 | res.json(book); 28 | },(err)=>(next(err))) 29 | .catch((err)=>(next(err))) 30 | }) 31 | .put(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 32 | res.statusCode = 403; 33 | res.end('PUT operation not supported on /books'); 34 | }) 35 | .delete(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 36 | res.statusCode = 403; 37 | res.end('DELETE operation not supported on /books'); 38 | 39 | /* Books.remove({}) 40 | .then((resp) => { 41 | console.log("Removed All Books"); 42 | res.statusCode = 200; 43 | res.setHeader('Content-Type', 'application/json'); 44 | res.json(resp); 45 | }, (err) => next(err)) 46 | .catch((err) => next(err));*/ 47 | }); 48 | 49 | bookRouter.route('/:bookId') 50 | .options(cors.corsWithOptions, (req, res) => { res.sendStatus(200); 51 | res.setHeader('Access-Control-Allow-Credentials', 'true')}) 52 | .get(cors.corsWithOptions,(req,res,next) => { 53 | Books.findById(req.params.bookId) 54 | .then((book)=>{ 55 | res.statusCode=200; 56 | res.setHeader('Content-Type','application/json'); 57 | res.json(book); 58 | },(err)=>(next(err))) 59 | .catch((err)=>(next(err))); 60 | }) 61 | 62 | .post(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 63 | res.statusCode = 403; 64 | res.end('POST operation not supported on /books/'+ req.params.bookId); 65 | }) 66 | 67 | .put(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 68 | Books.findByIdAndUpdate(req.params.bookId,{ 69 | $set: req.body 70 | },{new: true}) 71 | .then((book) => { 72 | res.statusCode = 200; 73 | res.setHeader('Content-Type', 'application/json'); 74 | res.json(book); 75 | }, (err) => next(err)) 76 | .catch((err) => res.status(400).json({success: false})); 77 | }) 78 | 79 | .delete(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 80 | Books.findByIdAndRemove(req.params.bookId) 81 | .then((resp) => { 82 | res.statusCode = 200; 83 | res.setHeader('Content-Type', 'application/json'); 84 | res.json({_id: req.params.bookId,success: true}); 85 | }, (err) => next(err)) 86 | .catch((err) => res.status(400).json({success: false})); 87 | }); 88 | 89 | module.exports = bookRouter; -------------------------------------------------------------------------------- /routes/api/issueRouter.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const issueRouter = express.Router(); 4 | const mongoose=require('mongoose'); 5 | 6 | var Issue = require('../../models/issues'); 7 | var Books = require('../../models/books'); 8 | var Users = require('../../models/users'); 9 | 10 | var passport = require('passport'); 11 | var authenticate = require('../../authenticate'); 12 | 13 | const cors = require('../cors'); 14 | 15 | issueRouter.use(bodyParser.json()); 16 | 17 | issueRouter.route('/') 18 | .options(cors.corsWithOptions, (req, res) => { res.sendStatus(200); }) 19 | .get(cors.corsWithOptions, authenticate.verifyUser 20 | ,authenticate.verifyAdmin, 21 | function(req, res, next) { 22 | Issue.find({}) 23 | .populate('student') 24 | .populate('book') 25 | .then((issues)=>{ 26 | res.statusCode=200; 27 | res.setHeader('Content-Type','application/json'); 28 | res.json(issues); 29 | },(err)=>(next(err))) 30 | .catch((err)=>(next(err))) 31 | } 32 | ) 33 | 34 | .post(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 35 | Books.findById(req.body.book) 36 | .then((requiredBook)=>{ 37 | Users.findById(req.body.student) 38 | .then((requiredUser)=>{ 39 | 40 | if(!requiredBook){ 41 | 42 | err = new Error("Book doesn't exist"); 43 | err.status = 400; 44 | return next(err); 45 | } 46 | else if(!requiredUser){ 47 | err = new Error("Student doesn't exist"); 48 | err.status = 400; 49 | return next(err); 50 | } 51 | else if(requiredBook._id&&requiredUser._id) { 52 | Issue.find({ 53 | student: req.body.student 54 | }) 55 | .then((issues)=>{ 56 | notReturned=issues.filter((issue)=>(!issue.returned)); 57 | if(notReturned&¬Returned.length>=3){ 58 | err = new Error(`The student has already issued 3 books. Please return them first`); 59 | err.status = 400; 60 | return next(err); 61 | } 62 | else{ 63 | if(requiredBook.copies>0){ 64 | Issue.create(req.body, function(err, issue) { 65 | if (err) return next(err) 66 | Issue.findById(issue._id) 67 | .populate('student') 68 | .populate('book') 69 | .exec(function(err, issue) { 70 | if (err) return next(err) 71 | Books.findByIdAndUpdate(req.body.book,{ 72 | $set: {copies: (requiredBook.copies-1)} 73 | },{new: true}) 74 | .then((book) => { 75 | res.statusCode=200; 76 | res.setHeader('Content-Type','application/json'); 77 | res.json(issue); 78 | 79 | }, (err) => next(err)) 80 | .catch((err) => res.status(400).json({success: false})); 81 | 82 | })}) 83 | } 84 | else { 85 | console.log(requiredBook); 86 | err = new Error(`The book is not available. You can wait for some days, until the book is returned to library.`); 87 | err.status = 400; 88 | return next(err); 89 | } 90 | } 91 | }) 92 | .catch((err)=>(next(err))) ; 93 | } 94 | 95 | 96 | 97 | 98 | },(err)=>(next(err))) 99 | .catch((err)=>(next(err))) 100 | 101 | },(err)=>(next(err))) 102 | .catch((err)=>(next(err))) 103 | 104 | }) 105 | 106 | .put(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 107 | res.statusCode = 403; 108 | res.end('PUT operation not supported on /issues'); 109 | }) 110 | .delete(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 111 | //res.statusCode = 403; 112 | //res.end('DELETE operation not supported on /issues'); 113 | 114 | Issue.remove({}) 115 | .then((resp) => { 116 | console.log("Removed All Issue"); 117 | res.statusCode = 200; 118 | res.setHeader('Content-Type', 'application/json'); 119 | res.json(resp); 120 | }, (err) => next(err)) 121 | .catch((err) => next(err)); 122 | 123 | }) 124 | issueRouter.route('/student/') 125 | .options(cors.corsWithOptions, (req, res) => { res.sendStatus(200); }) 126 | .get(cors.corsWithOptions,authenticate.verifyUser,(req,res,next) => { 127 | console.log("\n\n\n Object ID =====" +req.user._id); 128 | Issue.find({student: req.user._id}) 129 | .populate('student') 130 | .populate('book') 131 | .then((issue)=>{ 132 | res.statusCode=200; 133 | res.setHeader('Content-Type','application/json'); 134 | res.json(issue); 135 | 136 | },(err)=>(next(err))) 137 | .catch((err)=>(next(err))) 138 | }) 139 | 140 | 141 | issueRouter.route('/:issueId') 142 | .options(cors.corsWithOptions, (req, res) => { res.sendStatus(200); }) 143 | .get(cors.corsWithOptions,authenticate.verifyUser,(req,res,next) => { 144 | Issue.findById(req.params.issueId) 145 | .populate('student') 146 | .populate('book') 147 | .then((issue)=>{ 148 | if(issue&&(issue.student._id===req.user._id||req.user.admin)) 149 | { res.statusCode=200; 150 | res.setHeader('Content-Type','application/json'); 151 | res.json(issue); 152 | } 153 | else if(!issue){ 154 | err = new Error(`Issue not found`); 155 | err.status = 404; 156 | return next(err); 157 | 158 | } 159 | else{ 160 | err = new Error(`Unauthorised`); 161 | err.status = 401; 162 | return next(err); 163 | 164 | } 165 | },(err)=>(next(err))) 166 | .catch((err)=>(next(err))) 167 | }) 168 | 169 | .post(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 170 | res.statusCode = 403; 171 | res.end('POST operation not supported on /issues/'+ req.params.issueId); 172 | }) 173 | 174 | .delete(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 175 | res.statusCode = 403; 176 | res.end('POST operation not supported on /issues/'+ req.params.issueId); 177 | }) 178 | 179 | .put(cors.corsWithOptions,authenticate.verifyUser,authenticate.verifyAdmin,(req, res, next) => { 180 | Issue.findById(req.params.issueId) 181 | .then((issue)=>{ 182 | 183 | Books.findById(issue.book) 184 | .then((requiredBook)=>{ 185 | Issue.findByIdAndUpdate(req.params.issueId,{ 186 | $set: {returned: true} 187 | },{new: true}) 188 | .populate('student') 189 | .populate('book') 190 | .then((issue) => { 191 | Books.findByIdAndUpdate(issue.book,{ 192 | $set: {copies: (requiredBook.copies+1)} 193 | },{new: true}) 194 | .then((book) => { 195 | res.statusCode = 200; 196 | res.setHeader('Content-Type', 'application/json'); 197 | res.json(issue); 198 | }, (err) => next(err)) 199 | .catch((err) => res.status(400).json({success: false,message: "Book not updated"})); 200 | 201 | }, (err) => next(err)) 202 | .catch((err) => res.status(400).json({success: false,message: "Issue not Updated"})); 203 | 204 | }, (err) => next(err)) 205 | .catch((err) => res.status(400).json({success: false,message: "Book not found"})); 206 | 207 | 208 | }, (err) => next(err)) 209 | .catch((err) => res.status(400).json({success: false,message: "Issue not found"})) 210 | }) 211 | 212 | 213 | module.exports = issueRouter; -------------------------------------------------------------------------------- /routes/api/userRouter.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | const bodyParser = require('body-parser'); 4 | var User = require('../../models/users'); 5 | var passport = require('passport'); 6 | var authenticate = require('../../authenticate'); 7 | 8 | const cors = require('../cors'); 9 | 10 | router.use(bodyParser.json()); 11 | 12 | /* GET users listing. */ 13 | router.options('*', cors.corsWithOptions, (req, res) => { res.sendStatus(200); 14 | res.setHeader('Access-Control-Allow-Credentials', 'true');} ) 15 | router.get('/', cors.corsWithOptions, authenticate.verifyUser 16 | ,authenticate.verifyAdmin, 17 | function(req, res, next) { 18 | User.find({}) 19 | .then((users)=>{ 20 | res.statusCode=200; 21 | res.setHeader('Content-Type','application/json'); 22 | res.json(users); 23 | },(err)=>(next(err))) 24 | .catch((err)=>(next(err))) 25 | } 26 | ); 27 | 28 | router.put('/:userId',cors.corsWithOptions,authenticate.verifyUser, 29 | function(req,res,next){ 30 | User.findByIdAndUpdate(req.params.userId,{ 31 | $set: req.body 32 | },{new: true}) 33 | .then((user) => { 34 | res.statusCode = 200; 35 | res.setHeader('Content-Type', 'application/json'); 36 | res.json(user); 37 | }, (err) => next(err)) 38 | .catch((err) => res.status(400).json({success: false})); 39 | }) 40 | 41 | // For change of password 42 | router.put('/password/:userId',cors.corsWithOptions,authenticate.verifyUser, 43 | function(req,res,next){ 44 | User.findById(req.params.userId) 45 | .then((user) => { 46 | if(user&&!user.admin){ 47 | user.setPassword(req.body.password, function(){ 48 | 49 | user.save(); 50 | res.status(200).json({message: 'password changed successfully'}); 51 | }); 52 | } 53 | else if(!user){ 54 | res.status(500).json({message: "User doesn't exist"}); 55 | } 56 | else{ 57 | res.status(400).json({message: "Password of an admin can't be changed this way.\nContact the webmaster"}); 58 | } 59 | }, (err) => next(err)) 60 | .catch((err) => res.status(400).json({message: 'Internal Server Error'})); 61 | }) 62 | 63 | 64 | router.post('/signup',cors.corsWithOptions, (req, res, next) => { 65 | User.register(new User({username: req.body.username, 66 | firstname : req.body.firstname, 67 | lastname : req.body.lastname, 68 | email : req.body.email, 69 | roll : req.body.roll }), 70 | req.body.password, (err, user) => { 71 | if(err) { 72 | res.statusCode = 500; 73 | res.setHeader('Content-Type', 'application/json'); 74 | res.json({err: err}); 75 | } 76 | else { 77 | 78 | user.save((err, user) => { 79 | if (err) { 80 | res.statusCode = 500; 81 | res.setHeader('Content-Type', 'application/json'); 82 | res.json({err: err}); 83 | return ; 84 | } 85 | passport.authenticate('local')(req, res, () => { 86 | res.statusCode = 200; 87 | res.setHeader('Content-Type', 'application/json'); 88 | res.json({success: true, status: 'Registration Successful!'}); 89 | }); 90 | }); 91 | } 92 | }); 93 | }); 94 | 95 | router.post('/login',cors.corsWithOptions, passport.authenticate('local'), (req, res,next) => { 96 | passport.authenticate('local', (err, user, info) => { 97 | if (err) 98 | return next(err); 99 | 100 | if (!user) { 101 | res.statusCode = 401; 102 | res.setHeader('Content-Type', 'application/json'); 103 | res.json({success: false, status: 'Login Unsuccessful!', err: info}); 104 | } 105 | req.logIn(user, (err) => { 106 | if (err) { 107 | res.statusCode = 401; 108 | res.setHeader('Content-Type', 'application/json'); 109 | res.json({success: false, status: 'Login Unsuccessful!', err: 'Could not log in user!'}); 110 | } 111 | 112 | var token = authenticate.getToken({_id: req.user._id}); 113 | res.statusCode = 200; 114 | res.setHeader('Content-Type', 'application/json'); 115 | res.json({success: true, status: 'Login Successful!', token: token, userinfo: req.user}); 116 | }); 117 | }) (req, res, next); // function call IIFE 118 | }); 119 | 120 | router.get('/logout',cors.cors, (req, res) => { 121 | if (req.session) { 122 | req.session.destroy(); 123 | res.clearCookie('session-id'); 124 | res.redirect('/'); 125 | } 126 | else { 127 | var err = new Error('You are not logged in!'); 128 | err.status = 403; 129 | next(err); 130 | } 131 | }); 132 | 133 | router.get('/checkJWTtoken', cors.corsWithOptions, (req, res) => { 134 | passport.authenticate('jwt', {session: false}, (err, user, info) => { 135 | if (err) 136 | return next(err); 137 | 138 | if (!user) { 139 | res.statusCode = 401; 140 | res.setHeader('Content-Type', 'application/json'); 141 | return res.json({status: 'JWT invalid!', success: false, err: info}); 142 | } 143 | else { 144 | res.statusCode = 200; 145 | res.setHeader('Content-Type', 'application/json'); 146 | return res.json({status: 'JWT valid!', success: true, user: user}); 147 | 148 | } 149 | }) (req, res); 150 | }); 151 | 152 | module.exports = router; 153 | -------------------------------------------------------------------------------- /routes/cors.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const app = express(); 4 | 5 | const whitelist = ['http://localhost:3000', 'http://localhost:5000','https://lib-manage.herokuapp.com/','https://lib-manage.herokuapp.com:3000','https://lib-manage.herokuapp.com:5000']; 6 | var corsOptionsDelegate = (req, callback) => { 7 | var corsOptions; 8 | console.log(req.header('Origin')); 9 | if(whitelist.indexOf(req.header('Origin')) !== -1) { 10 | corsOptions = { origin: true }; 11 | } 12 | else { 13 | corsOptions = { origin: false }; 14 | } 15 | callback(null, corsOptions); 16 | }; 17 | 18 | exports.cors = cors(); 19 | exports.corsWithOptions = cors(corsOptionsDelegate); -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express=require('express'); 2 | const mongoose=require('mongoose'); 3 | const bodyParser=require('body-parser'); 4 | const path=require('path'); 5 | var passport = require('passport'); 6 | var authenticate = require('./authenticate'); 7 | 8 | 9 | // Loading routers 10 | const bookRouter = require('./routes/api/bookRouter'); 11 | const userRouter = require('./routes/api/userRouter'); 12 | const issueRouter = require('./routes/api/issueRouter'); 13 | const app= express(); 14 | 15 | app.use(function(req, res, next) { 16 | 17 | res.header("Access-Control-Allow-Origin", "*"); 18 | 19 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 20 | 21 | next(); 22 | 23 | }); 24 | 25 | // Bodyparser Middleware 26 | app.use(bodyParser.json()); 27 | 28 | // DB config 29 | const mongoURI = require('./config/keys').mongoURI; 30 | 31 | // Connect to mongo 32 | mongoose.connect(mongoURI) 33 | .then(()=> {console.log("MongoDB Connected");}) 34 | .catch(err => console.log(err)); 35 | 36 | app.use(passport.initialize()); 37 | 38 | // Use routes 39 | app.use('/api/books',bookRouter); 40 | app.use('/api/users',userRouter); 41 | app.use('/api/issues',issueRouter); 42 | 43 | // Serve static assets if in production 44 | if (process.env.NODE_ENV === 'production') { 45 | // Set static folder 46 | app.use(express.static('client/build')); 47 | 48 | app.get('*', (req, res) => { 49 | res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html')); 50 | }); 51 | } 52 | 53 | const port = process.env.PORT || 5000; 54 | 55 | app.listen(port, ()=> console.log(`Server started running on port ${port}`)); --------------------------------------------------------------------------------