├── Chapter02 └── mern-simplesetup │ ├── .babelrc │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── HelloWorld.js │ └── main.js │ ├── nodemon.json │ ├── package.json │ ├── server │ ├── devBundle.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ ├── webpack.config.server.js │ └── yarn.lock ├── Chapter03 and 04 └── mern-skeleton │ ├── .babelrc │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ └── unicornbike.jpg │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── main.js │ ├── theme.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── Profile.js │ │ ├── Signup.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── nodemon.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ └── user.routes.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ ├── webpack.config.server.js │ └── yarn.lock ├── Chapter05 └── mern-social │ ├── .babelrc │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ ├── profile-pic.png │ │ │ └── unicornbike.jpg │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── main.js │ ├── post │ │ ├── Comments.js │ │ ├── NewPost.js │ │ ├── Newsfeed.js │ │ ├── Post.js │ │ ├── PostList.js │ │ └── api-post.js │ ├── theme.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── FindPeople.js │ │ ├── FollowGrid.js │ │ ├── FollowProfileButton.js │ │ ├── Profile.js │ │ ├── ProfileTabs.js │ │ ├── Signup.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── nodemon.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ ├── post.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ ├── post.model.js │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ ├── post.routes.js │ │ └── user.routes.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ ├── webpack.config.server.js │ └── yarn.lock ├── Chapter06 └── mern-classroom │ ├── .babelrc │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ ├── default.png │ │ │ └── unicornbike.jpg │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── course │ │ ├── Course.js │ │ ├── Courses.js │ │ ├── DeleteCourse.js │ │ ├── EditCourse.js │ │ ├── MyCourses.js │ │ ├── NewCourse.js │ │ ├── NewLesson.js │ │ └── api-course.js │ ├── enrollment │ │ ├── Enroll.js │ │ ├── Enrollment.js │ │ ├── Enrollments.js │ │ └── api-enrollment.js │ ├── main.js │ ├── theme.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── Profile.js │ │ ├── Signup.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── nodemon.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ ├── course.controller.js │ │ ├── enrollment.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ ├── course.model.js │ │ ├── enrollment.model.js │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ ├── course.routes.js │ │ ├── enrollment.routes.js │ │ └── user.routes.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ ├── webpack.config.server.js │ └── yarn.lock ├── Chapter07 and 08 └── mern-marketplace │ ├── .babelrc │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ ├── default.png │ │ │ ├── stripeButton.png │ │ │ └── unicornbike.jpg │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── cart │ │ ├── AddToCart.js │ │ ├── Cart.js │ │ ├── CartItems.js │ │ ├── Checkout.js │ │ ├── PlaceOrder.js │ │ └── cart-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── main.js │ ├── order │ │ ├── MyOrders.js │ │ ├── Order.js │ │ ├── ProductOrderEdit.js │ │ ├── ShopOrders.js │ │ └── api-order.js │ ├── product │ │ ├── Categories.js │ │ ├── DeleteProduct.js │ │ ├── EditProduct.js │ │ ├── MyProducts.js │ │ ├── NewProduct.js │ │ ├── Product.js │ │ ├── Products.js │ │ ├── Search.js │ │ ├── Suggestions.js │ │ └── api-product.js │ ├── shop │ │ ├── DeleteShop.js │ │ ├── EditShop.js │ │ ├── MyShops.js │ │ ├── NewShop.js │ │ ├── Shop.js │ │ ├── Shops.js │ │ └── api-shop.js │ ├── theme.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── Profile.js │ │ ├── Signup.js │ │ ├── StripeConnect.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── nodemon.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ ├── order.controller.js │ │ ├── product.controller.js │ │ ├── shop.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ ├── order.model.js │ │ ├── product.model.js │ │ ├── shop.model.js │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ ├── order.routes.js │ │ ├── product.routes.js │ │ ├── shop.routes.js │ │ └── user.routes.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ ├── webpack.config.server.js │ └── yarn.lock ├── Chapter09 └── mern-marketplace-bidding │ ├── .babelrc │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ ├── default.png │ │ │ ├── stripeButton.png │ │ │ └── unicornbike.jpg │ ├── auction │ │ ├── Auction.js │ │ ├── Auctions.js │ │ ├── Bidding.js │ │ ├── DeleteAuction.js │ │ ├── EditAuction.js │ │ ├── MyAuctions.js │ │ ├── NewAuction.js │ │ ├── OpenAuctions.js │ │ ├── Timer.js │ │ └── api-auction.js │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── cart │ │ ├── AddToCart.js │ │ ├── Cart.js │ │ ├── CartItems.js │ │ ├── Checkout.js │ │ ├── PlaceOrder.js │ │ └── cart-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── main.js │ ├── order │ │ ├── MyOrders.js │ │ ├── Order.js │ │ ├── ProductOrderEdit.js │ │ ├── ShopOrders.js │ │ └── api-order.js │ ├── product │ │ ├── Categories.js │ │ ├── DeleteProduct.js │ │ ├── EditProduct.js │ │ ├── MyProducts.js │ │ ├── NewProduct.js │ │ ├── Product.js │ │ ├── Products.js │ │ ├── Search.js │ │ ├── Suggestions.js │ │ └── api-product.js │ ├── shop │ │ ├── DeleteShop.js │ │ ├── EditShop.js │ │ ├── MyShops.js │ │ ├── NewShop.js │ │ ├── Shop.js │ │ ├── Shops.js │ │ └── api-shop.js │ ├── theme.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── Profile.js │ │ ├── Signup.js │ │ ├── StripeConnect.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── nodemon.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auction.controller.js │ │ ├── auth.controller.js │ │ ├── bidding.controller.js │ │ ├── order.controller.js │ │ ├── product.controller.js │ │ ├── shop.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ ├── auction.model.js │ │ ├── order.model.js │ │ ├── product.model.js │ │ ├── shop.model.js │ │ └── user.model.js │ ├── routes │ │ ├── auction.routes.js │ │ ├── auth.routes.js │ │ ├── order.routes.js │ │ ├── product.routes.js │ │ ├── shop.routes.js │ │ └── user.routes.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ ├── webpack.config.server.js │ └── yarn.lock ├── Chapter10 └── mern-expense-tracker │ ├── .babelrc │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ ├── unicornbike.jpg │ │ │ └── unicorncoin.jpg │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── expense │ │ ├── DeleteExpense.js │ │ ├── ExpenseOverview.js │ │ ├── Expenses.js │ │ ├── NewExpense.js │ │ └── api-expense.js │ ├── main.js │ ├── report │ │ ├── CategoryPie.js │ │ ├── MonthlyScatter.js │ │ ├── Reports.js │ │ └── YearlyBar.js │ ├── theme.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── Profile.js │ │ ├── Signup.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── nodemon.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ ├── expense.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ ├── expense.model.js │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ ├── expense.routes.js │ │ └── user.routes.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ ├── webpack.config.server.js │ └── yarn.lock ├── Chapter11 and 12 └── mern-mediastream │ ├── .babelrc │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ └── unicornbike.jpg │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── main.js │ ├── media │ │ ├── DeleteMedia.js │ │ ├── EditMedia.js │ │ ├── Media.js │ │ ├── MediaList.js │ │ ├── MediaPlayer.js │ │ ├── NewMedia.js │ │ ├── PlayMedia.js │ │ ├── RelatedMedia.js │ │ └── api-media.js │ ├── routeConfig.js │ ├── theme.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── Profile.js │ │ ├── Signup.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── nodemon.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ ├── media.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ ├── media.model.js │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ ├── media.routes.js │ │ └── user.routes.js │ └── server.js │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ ├── webpack.config.server.js │ └── yarn.lock ├── Chapter13 └── MERNVR │ ├── .babelrc │ ├── .flowconfig │ ├── .gitignore │ ├── .watchmanconfig │ ├── README.md │ ├── __tests__ │ └── index-test.js │ ├── client.js │ ├── index.html │ ├── index.js │ ├── package.json │ ├── rn-cli.config.js │ ├── static_assets │ ├── 360_world.jpg │ ├── 360_world_black.jpg │ ├── clog-up.mp3 │ ├── collect.mp3 │ └── happy-bot.mp3 │ └── yarn.lock ├── Chapter14 └── mern-vrgame │ ├── .babelrc │ ├── .gitignore │ ├── LICENSE.md │ ├── README.md │ ├── client │ ├── App.js │ ├── MainRouter.js │ ├── assets │ │ └── images │ │ │ ├── unicornbike.jpg │ │ │ └── unicorncoin.jpg │ ├── auth │ │ ├── PrivateRoute.js │ │ ├── Signin.js │ │ ├── api-auth.js │ │ └── auth-helper.js │ ├── core │ │ ├── Home.js │ │ └── Menu.js │ ├── game │ │ ├── DeleteGame.js │ │ ├── EditGame.js │ │ ├── GameDetail.js │ │ ├── GameForm.js │ │ ├── NewGame.js │ │ ├── VRObjectForm.js │ │ └── api-game.js │ ├── main.js │ ├── theme.js │ └── user │ │ ├── DeleteUser.js │ │ ├── EditProfile.js │ │ ├── Profile.js │ │ ├── Signup.js │ │ ├── Users.js │ │ └── api-user.js │ ├── config │ └── config.js │ ├── dist │ ├── client.bundle.js │ ├── index.bundle.js │ └── static_assets │ │ ├── 360_world.jpg │ │ ├── 360_world_black.jpg │ │ ├── clog-up.mp3 │ │ ├── collect.mp3 │ │ └── happy-bot.mp3 │ ├── nodemon.json │ ├── package.json │ ├── server │ ├── controllers │ │ ├── auth.controller.js │ │ ├── game.controller.js │ │ └── user.controller.js │ ├── devBundle.js │ ├── express.js │ ├── helpers │ │ └── dbErrorHandler.js │ ├── models │ │ ├── game.model.js │ │ └── user.model.js │ ├── routes │ │ ├── auth.routes.js │ │ ├── game.routes.js │ │ └── user.routes.js │ ├── server.js │ └── vr │ │ └── index.html │ ├── template.js │ ├── webpack.config.client.js │ ├── webpack.config.client.production.js │ ├── webpack.config.server.js │ └── yarn.lock ├── LICENSE └── README.md /Chapter02/mern-simplesetup/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", 4 | { 5 | "targets": { 6 | "node": "current" 7 | } 8 | } 9 | ], 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | "react-hot-loader/babel" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | /data/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/README.md: -------------------------------------------------------------------------------- 1 | # MERN - Simple Setup Check 2 | 3 | ### [Live Demo](http://simplesetup2.mernbook.com/ "MERN Simple Setup") 4 | 5 | #### What you need to run this code 6 | 1. Node (13.12.0) 7 | 2. NPM (6.14.4) or Yarn (1.22.4) 8 | 3. MongoDB (4.2.0) 9 | 10 | 11 | #### How to run this code 12 | 1. Clone this repository 13 | 2. Open command line in the cloned folder, 14 | - To install dependencies, run ``` npm install ``` or ``` yarn ``` 15 | - To run the application for development, run ``` npm run development ``` or ``` yarn development ``` 16 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 17 | 18 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/client/HelloWorld.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hot } from 'react-hot-loader' 3 | 4 | const HelloWorld = () => { 5 | return ( 6 |
7 |

Hello World!

8 |
9 | ) 10 | } 11 | 12 | export default hot(module)(HelloWorld) 13 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import HelloWorld from './HelloWorld' 4 | 5 | 6 | render(, document.getElementById('root')) 7 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mern-simplesetup", 3 | "version": "2.0.0", 4 | "description": "A quick check for your MERN stack installation", 5 | "author": "Shama Hoque", 6 | "license": "MIT", 7 | "keywords": [ 8 | "react", 9 | "node", 10 | "express", 11 | "mongodb", 12 | "mern" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/shamahoque/mern-simplesetup.git" 17 | }, 18 | "homepage": "https://github.com/shamahoque/mern-simplesetup", 19 | "main": "template.js", 20 | "scripts": { 21 | "development": "nodemon", 22 | "build": "webpack --config webpack.config.client.production.js && webpack --mode=production --config webpack.config.server.js", 23 | "start": "NODE_ENV=production node ./dist/server.generated.js" 24 | }, 25 | "engines": { 26 | "node": "13.12.0", 27 | "npm": "6.14.4" 28 | }, 29 | "dependencies": { 30 | "@hot-loader/react-dom": "16.13.0", 31 | "express": "4.17.1", 32 | "mongodb": "3.5.5", 33 | "react": "16.13.1", 34 | "react-dom": "16.13.1", 35 | "react-hot-loader": "4.12.20" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "7.9.0", 39 | "@babel/preset-env": "7.9.0", 40 | "@babel/preset-react": "7.9.4", 41 | "babel-loader": "8.1.0", 42 | "nodemon": "2.0.2", 43 | "webpack": "4.42.1", 44 | "webpack-cli": "3.3.11", 45 | "webpack-dev-middleware": "3.7.2", 46 | "webpack-hot-middleware": "2.25.0", 47 | "webpack-node-externals": "1.7.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack' 2 | import webpackMiddleware from 'webpack-dev-middleware' 3 | import webpackHotMiddleware from 'webpack-hot-middleware' 4 | import webpackConfig from './../webpack.config.client.js' 5 | 6 | const compile = (app) => { 7 | if(process.env.NODE_ENV == "development"){ 8 | const compiler = webpack(webpackConfig) 9 | const middleware = webpackMiddleware(compiler, { 10 | publicPath: webpackConfig.output.publicPath 11 | }) 12 | app.use(middleware) 13 | app.use(webpackHotMiddleware(compiler)) 14 | } 15 | } 16 | 17 | export default { 18 | compile 19 | } 20 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/server/server.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import express from 'express' 3 | import { MongoClient } from 'mongodb' 4 | import template from './../template' 5 | //comment out before building for production 6 | import devBundle from './devBundle' 7 | 8 | const app = express() 9 | //comment out before building for production 10 | devBundle.compile(app) 11 | 12 | const CURRENT_WORKING_DIR = process.cwd() 13 | app.use('/dist', express.static(path.join(CURRENT_WORKING_DIR, 'dist'))) 14 | 15 | app.get('/', (req, res) => { 16 | res.status(200).send(template()) 17 | }) 18 | 19 | let port = process.env.PORT || 3000 20 | app.listen(port, function onStart(err) { 21 | if (err) { 22 | console.log(err) 23 | } 24 | console.info('Server started on port %s.', port) 25 | }) 26 | 27 | // Database Connection URL 28 | const url = process.env.MONGODB_URI || 'mongodb://localhost:27017/mernSimpleSetup' 29 | // Use connect method to connect to the server 30 | MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true },(err, db)=>{ 31 | console.log("Connected successfully to mongodb server") 32 | db.close() 33 | }) 34 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/template.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | return ` 3 | 4 | 5 | 6 | MERN Kickstart 7 | 8 | 9 |
10 | 11 | 12 | ` 13 | } 14 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'webpack-hot-middleware/client?reload=true', 11 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 12 | ], 13 | output: { 14 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 15 | filename: 'bundle.js', 16 | publicPath: '/dist/' 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.jsx?$/, 22 | exclude: /node_modules/, 23 | use: [ 24 | 'babel-loader' 25 | ] 26 | } 27 | ] 28 | }, 29 | plugins: [ 30 | new webpack.HotModuleReplacementPlugin(), 31 | new webpack.NoEmitOnErrorsPlugin() 32 | ], 33 | resolve: { 34 | alias: { 35 | 'react-dom': '@hot-loader/react-dom' 36 | } 37 | } 38 | } 39 | 40 | module.exports = config 41 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CURRENT_WORKING_DIR = process.cwd() 3 | 4 | const config = { 5 | mode: "production", 6 | entry: [ 7 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 8 | ], 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 11 | filename: 'bundle.js', 12 | publicPath: "/dist/" 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | use: [ 20 | 'babel-loader' 21 | ] 22 | } 23 | ] 24 | } 25 | } 26 | 27 | module.exports = config 28 | -------------------------------------------------------------------------------- /Chapter02/mern-simplesetup/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const nodeExternals = require('webpack-node-externals') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "server", 7 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 8 | target: "node", 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 11 | filename: "server.generated.js", 12 | publicPath: '/dist/', 13 | libraryTarget: "commonjs2" 14 | }, 15 | externals: [nodeExternals()], 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | use: [ 'babel-loader' ] 22 | } 23 | ] 24 | } 25 | 26 | } 27 | 28 | module.exports = config 29 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", 4 | { 5 | "targets": { 6 | "node": "current" 7 | } 8 | } 9 | ], 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | "react-hot-loader/babel" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | /data/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/README.md: -------------------------------------------------------------------------------- 1 | # MERN Skeleton 2 | 3 | A skeleton application with basic user CRUD and auth features - developed using React, Node, Express and MongoDB. 4 | 5 | ![MERN Skeleton](https://mernbook.s3.amazonaws.com/git+/skeleton2.png "MERN Skeleton") 6 | 7 | ### [Live Demo](http://skeleton2.mernbook.com/ "MERN Skeleton") 8 | 9 | #### What you need to run this code 10 | 1. Node (13.12.0) 11 | 2. NPM (6.14.4) or Yarn (1.22.4) 12 | 3. MongoDB (4.2.0) 13 | 14 | #### How to run this code 15 | 1. Make sure MongoDB is running on your system 16 | 2. Clone this repository 17 | 3. Open command line in the cloned folder, 18 | - To install dependencies, run ``` npm install ``` or ``` yarn ``` 19 | - To run the application for development, run ``` npm run development ``` or ``` yarn development ``` 20 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 21 | ---- 22 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import { ThemeProvider } from '@material-ui/styles' 5 | import theme from './theme' 6 | import { hot } from 'react-hot-loader' 7 | 8 | const App = () => { 9 | React.useEffect(() => { 10 | const jssStyles = document.querySelector('#jss-server-side') 11 | if (jssStyles) { 12 | jssStyles.parentNode.removeChild(jssStyles) 13 | } 14 | }, []) 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | )} 22 | 23 | export default hot(module)(App) 24 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/MainRouter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Route, Switch} from 'react-router-dom' 3 | import Home from './core/Home' 4 | import Users from './user/Users' 5 | import Signup from './user/Signup' 6 | import Signin from './auth/Signin' 7 | import EditProfile from './user/EditProfile' 8 | import Profile from './user/Profile' 9 | import PrivateRoute from './auth/PrivateRoute' 10 | import Menu from './core/Menu' 11 | 12 | const MainRouter = () => { 13 | return (
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
) 24 | } 25 | 26 | export default MainRouter 27 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/assets/images/unicornbike.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter03 and 04/mern-skeleton/client/assets/images/unicornbike.jpg -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = async (user) => { 2 | try { 3 | let response = await fetch('/auth/signin/', { 4 | method: 'POST', 5 | headers: { 6 | 'Accept': 'application/json', 7 | 'Content-Type': 'application/json' 8 | }, 9 | credentials: 'include', 10 | body: JSON.stringify(user) 11 | }) 12 | return await response.json() 13 | } catch(err) { 14 | console.log(err) 15 | } 16 | } 17 | 18 | const signout = async () => { 19 | try { 20 | let response = await fetch('/auth/signout/', { method: 'GET' }) 21 | return await response.json() 22 | } catch(err) { 23 | console.log(err) 24 | } 25 | } 26 | 27 | export { 28 | signin, 29 | signout 30 | } -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | clearJWT(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | } 27 | } 28 | 29 | export default auth 30 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/client/theme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from '@material-ui/core/styles' 2 | import { pink } from '@material-ui/core/colors' 3 | 4 | const theme = createMuiTheme({ 5 | typography: { 6 | useNextVariants: true, 7 | }, 8 | palette: { 9 | primary: { 10 | light: '#5c67a3', 11 | main: '#3f4771', 12 | dark: '#2e355b', 13 | contrastText: '#fff', 14 | }, 15 | secondary: { 16 | light: '#ff79b0', 17 | main: '#ff4081', 18 | dark: '#c60055', 19 | contrastText: '#000', 20 | }, 21 | openTitle: '#3f4771', 22 | protectedTitle: pink['400'], 23 | type: 'light' 24 | } 25 | }) 26 | 27 | export default theme -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject' 10 | } 11 | 12 | export default config 13 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/:userId') 12 | .get(authCtrl.requireSignin, userCtrl.read) 13 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 14 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 15 | 16 | router.param('userId', userCtrl.userByID) 17 | 18 | export default router 19 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${config.mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | 10 | MERN Skeleton 11 | 12 | 13 | 19 | 20 | 21 |
${markup}
22 | 23 | 24 | 25 | ` 26 | } 27 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'webpack-hot-middleware/client?reload=true', 11 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 12 | ], 13 | output: { 14 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 15 | filename: 'bundle.js', 16 | publicPath: '/dist/' 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.jsx?$/, 22 | exclude: /node_modules/, 23 | use: [ 24 | 'babel-loader' 25 | ] 26 | }, 27 | { 28 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 29 | use: 'file-loader' 30 | } 31 | ] 32 | }, 33 | plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ], 37 | resolve: { 38 | alias: { 39 | 'react-dom': '@hot-loader/react-dom' 40 | } 41 | } 42 | } 43 | 44 | module.exports = config 45 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CURRENT_WORKING_DIR = process.cwd() 3 | 4 | const config = { 5 | mode: "production", 6 | entry: [ 7 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 8 | ], 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 11 | filename: 'bundle.js', 12 | publicPath: "/dist/" 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | use: [ 20 | 'babel-loader' 21 | ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter03 and 04/mern-skeleton/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const nodeExternals = require('webpack-node-externals') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "server", 7 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 8 | target: "node", 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 11 | filename: "server.generated.js", 12 | publicPath: '/dist/', 13 | libraryTarget: "commonjs2" 14 | }, 15 | externals: [nodeExternals()], 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | use: [ 'babel-loader' ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter05/mern-social/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", 4 | { 5 | "targets": { 6 | "node": "current" 7 | } 8 | } 9 | ], 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | "react-hot-loader/babel" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Chapter05/mern-social/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | /data/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /Chapter05/mern-social/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter05/mern-social/README.md: -------------------------------------------------------------------------------- 1 | # MERN Social 2 | 3 | A simple social media application with users, posts, likes and comments - developed using React, Node, Express and MongoDB. 4 | 5 | ![MERN Social](https://s3.amazonaws.com/mernbook/git+/social.png "MERN Social") 6 | 7 | ### [Live Demo](http://social2.mernbook.com/ "MERN Social") 8 | 9 | #### What you need to run this code 10 | 1. Node (13.12.0) 11 | 2. NPM (6.14.4) or Yarn (1.22.4) 12 | 3. MongoDB (4.2.0) 13 | 14 | #### How to run this code 15 | 1. Make sure MongoDB is running on your system 16 | 2. Clone this repository 17 | 3. Open command line in the cloned folder, 18 | - To install dependencies, run ``` npm install ``` or ``` yarn ``` 19 | - To run the application for development, run ``` npm run development ``` or ``` yarn development ``` 20 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 21 | ---- -------------------------------------------------------------------------------- /Chapter05/mern-social/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import { ThemeProvider } from '@material-ui/styles' 5 | import theme from './theme' 6 | import { hot } from 'react-hot-loader' 7 | 8 | const App = () => { 9 | React.useEffect(() => { 10 | const jssStyles = document.querySelector('#jss-server-side') 11 | if (jssStyles) { 12 | jssStyles.parentNode.removeChild(jssStyles) 13 | } 14 | }, []) 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | )} 22 | 23 | export default hot(module)(App) 24 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/MainRouter.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Route, Switch} from 'react-router-dom' 3 | import Home from './core/Home' 4 | import Users from './user/Users' 5 | import Signup from './user/Signup' 6 | import Signin from './auth/Signin' 7 | import EditProfile from './user/EditProfile' 8 | import Profile from './user/Profile' 9 | import PrivateRoute from './auth/PrivateRoute' 10 | import Menu from './core/Menu' 11 | 12 | const MainRouter = () => { 13 | return (
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
) 24 | } 25 | 26 | export default MainRouter 27 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/assets/images/profile-pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter05/mern-social/client/assets/images/profile-pic.png -------------------------------------------------------------------------------- /Chapter05/mern-social/client/assets/images/unicornbike.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter05/mern-social/client/assets/images/unicornbike.jpg -------------------------------------------------------------------------------- /Chapter05/mern-social/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = async (user) => { 2 | try { 3 | let response = await fetch('/auth/signin/', { 4 | method: 'POST', 5 | headers: { 6 | 'Accept': 'application/json', 7 | 'Content-Type': 'application/json' 8 | }, 9 | credentials: 'include', 10 | body: JSON.stringify(user) 11 | }) 12 | return await response.json() 13 | } catch(err) { 14 | console.log(err) 15 | } 16 | } 17 | 18 | const signout = async () => { 19 | try { 20 | let response = await fetch('/auth/signout/', { method: 'GET' }) 21 | return await response.json() 22 | } catch(err) { 23 | console.log(err) 24 | } 25 | } 26 | 27 | export { 28 | signin, 29 | signout 30 | } 31 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | clearJWT(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | } 27 | } 28 | 29 | export default auth 30 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/post/PostList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Post from './Post' 4 | 5 | export default function PostList (props) { 6 | return ( 7 |
8 | {props.posts.map((item, i) => { 9 | return 10 | }) 11 | } 12 |
13 | ) 14 | } 15 | PostList.propTypes = { 16 | posts: PropTypes.array.isRequired, 17 | removeUpdate: PropTypes.func.isRequired 18 | } 19 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/theme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from '@material-ui/core/styles' 2 | import { teal, orange } from '@material-ui/core/colors' 3 | 4 | const theme = createMuiTheme({ 5 | palette: { 6 | primary: { 7 | light: '#52c7b8', 8 | main: '#009688', 9 | dark: '#00675b', 10 | contrastText: '#fff', 11 | }, 12 | secondary: { 13 | light: '#ffd95b', 14 | main: '#ffa726', 15 | dark: '#c77800', 16 | contrastText: '#000', 17 | }, 18 | openTitle: teal['700'], 19 | protectedTitle: orange['700'], 20 | type: 'light' 21 | } 22 | }) 23 | 24 | export default theme -------------------------------------------------------------------------------- /Chapter05/mern-social/client/user/FollowGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {makeStyles} from '@material-ui/core/styles' 3 | import PropTypes from 'prop-types' 4 | import Avatar from '@material-ui/core/Avatar' 5 | import Typography from '@material-ui/core/Typography' 6 | import {Link} from 'react-router-dom' 7 | import GridList from '@material-ui/core/GridList' 8 | import GridListTile from '@material-ui/core/GridListTile' 9 | 10 | const useStyles = makeStyles(theme => ({ 11 | root: { 12 | paddingTop: theme.spacing(2), 13 | display: 'flex', 14 | flexWrap: 'wrap', 15 | justifyContent: 'space-around', 16 | overflow: 'hidden', 17 | background: theme.palette.background.paper, 18 | }, 19 | bigAvatar: { 20 | width: 60, 21 | height: 60, 22 | margin: 'auto' 23 | }, 24 | gridList: { 25 | width: 500, 26 | height: 220, 27 | }, 28 | tileText: { 29 | textAlign: 'center', 30 | marginTop: 10 31 | } 32 | })) 33 | export default function FollowGrid (props) { 34 | const classes = useStyles() 35 | return (
36 | 37 | {props.people.map((person, i) => { 38 | return 39 | 40 | 41 | {person.name} 42 | 43 | 44 | })} 45 | 46 |
) 47 | } 48 | 49 | FollowGrid.propTypes = { 50 | people: PropTypes.array.isRequired 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Chapter05/mern-social/client/user/FollowProfileButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Button from '@material-ui/core/Button' 4 | import {unfollow, follow} from './api-user.js' 5 | 6 | export default function FollowProfileButton (props) { 7 | const followClick = () => { 8 | props.onButtonClick(follow) 9 | } 10 | const unfollowClick = () => { 11 | props.onButtonClick(unfollow) 12 | } 13 | return (
14 | { props.following 15 | ? () 16 | : () 17 | } 18 |
) 19 | } 20 | FollowProfileButton.propTypes = { 21 | following: PropTypes.bool.isRequired, 22 | onButtonClick: PropTypes.func.isRequired 23 | } 24 | -------------------------------------------------------------------------------- /Chapter05/mern-social/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject' 10 | } 11 | 12 | export default config 13 | -------------------------------------------------------------------------------- /Chapter05/mern-social/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/models/post.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | const PostSchema = new mongoose.Schema({ 3 | text: { 4 | type: String, 5 | required: 'Text is required' 6 | }, 7 | photo: { 8 | data: Buffer, 9 | contentType: String 10 | }, 11 | likes: [{type: mongoose.Schema.ObjectId, ref: 'User'}], 12 | comments: [{ 13 | text: String, 14 | created: { type: Date, default: Date.now }, 15 | postedBy: { type: mongoose.Schema.ObjectId, ref: 'User'} 16 | }], 17 | postedBy: {type: mongoose.Schema.ObjectId, ref: 'User'}, 18 | created: { 19 | type: Date, 20 | default: Date.now 21 | } 22 | }) 23 | 24 | export default mongoose.model('Post', PostSchema) 25 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/routes/post.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import postCtrl from '../controllers/post.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/posts/new/:userId') 9 | .post(authCtrl.requireSignin, postCtrl.create) 10 | 11 | router.route('/api/posts/photo/:postId') 12 | .get(postCtrl.photo) 13 | 14 | router.route('/api/posts/by/:userId') 15 | .get(authCtrl.requireSignin, postCtrl.listByUser) 16 | 17 | router.route('/api/posts/feed/:userId') 18 | .get(authCtrl.requireSignin, postCtrl.listNewsFeed) 19 | 20 | router.route('/api/posts/like') 21 | .put(authCtrl.requireSignin, postCtrl.like) 22 | router.route('/api/posts/unlike') 23 | .put(authCtrl.requireSignin, postCtrl.unlike) 24 | 25 | router.route('/api/posts/comment') 26 | .put(authCtrl.requireSignin, postCtrl.comment) 27 | router.route('/api/posts/uncomment') 28 | .put(authCtrl.requireSignin, postCtrl.uncomment) 29 | 30 | router.route('/api/posts/:postId') 31 | .delete(authCtrl.requireSignin, postCtrl.isPoster, postCtrl.remove) 32 | 33 | router.param('userId', userCtrl.userByID) 34 | router.param('postId', postCtrl.postByID) 35 | 36 | export default router 37 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/photo/:userId') 12 | .get(userCtrl.photo, userCtrl.defaultPhoto) 13 | router.route('/api/users/defaultphoto') 14 | .get(userCtrl.defaultPhoto) 15 | 16 | router.route('/api/users/follow') 17 | .put(authCtrl.requireSignin, userCtrl.addFollowing, userCtrl.addFollower) 18 | router.route('/api/users/unfollow') 19 | .put(authCtrl.requireSignin, userCtrl.removeFollowing, userCtrl.removeFollower) 20 | 21 | router.route('/api/users/findpeople/:userId') 22 | .get(authCtrl.requireSignin, userCtrl.findPeople) 23 | 24 | router.route('/api/users/:userId') 25 | .get(authCtrl.requireSignin, userCtrl.read) 26 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 27 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 28 | 29 | router.param('userId', userCtrl.userByID) 30 | 31 | export default router 32 | -------------------------------------------------------------------------------- /Chapter05/mern-social/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, useFindAndModify: false }) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${config.mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter05/mern-social/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | 10 | MERN Social 11 | 12 | 13 | 18 | 19 | 20 |
${markup}
21 | 22 | 23 | 24 | ` 25 | } 26 | -------------------------------------------------------------------------------- /Chapter05/mern-social/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'webpack-hot-middleware/client?reload=true', 11 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 12 | ], 13 | output: { 14 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 15 | filename: 'bundle.js', 16 | publicPath: '/dist/' 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.jsx?$/, 22 | exclude: /node_modules/, 23 | use: [ 24 | 'babel-loader' 25 | ] 26 | }, 27 | { 28 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 29 | use: 'file-loader' 30 | } 31 | ] 32 | }, 33 | plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ], 37 | resolve: { 38 | alias: { 39 | 'react-dom': '@hot-loader/react-dom' 40 | } 41 | } 42 | } 43 | 44 | module.exports = config 45 | -------------------------------------------------------------------------------- /Chapter05/mern-social/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CURRENT_WORKING_DIR = process.cwd() 3 | 4 | const config = { 5 | mode: "production", 6 | entry: [ 7 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 8 | ], 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 11 | filename: 'bundle.js', 12 | publicPath: "/dist/" 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | use: [ 20 | 'babel-loader' 21 | ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter05/mern-social/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const nodeExternals = require('webpack-node-externals') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "server", 7 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 8 | target: "node", 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 11 | filename: "server.generated.js", 12 | publicPath: '/dist/', 13 | libraryTarget: "commonjs2" 14 | }, 15 | externals: [nodeExternals()], 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | use: [ 'babel-loader' ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", 4 | { 5 | "targets": { 6 | "node": "current" 7 | } 8 | } 9 | ], 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | "react-hot-loader/babel" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | /data/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/README.md: -------------------------------------------------------------------------------- 1 | # MERN Classroom 2 | 3 | A simple web-based classroom application that allows instructors to add courses with lessons, while students can enroll in these courses and track their progress. - developed using React, Node, Express and MongoDB. 4 | 5 | ![MERN Skeleton](https://mernbook.s3.amazonaws.com/git+/classroom.png "MERN Skeleton") 6 | 7 | ### [Live Demo](http://classroom.mernbook.com/ "MERN Classroom") 8 | 9 | #### What you need to run this code 10 | 1. Node (13.12.0) 11 | 2. NPM (6.14.4) or Yarn (1.22.4) 12 | 3. MongoDB (4.2.0) 13 | 14 | #### How to run this code 15 | 1. Make sure MongoDB is running on your system 16 | 2. Clone this repository 17 | 3. Open command line in the cloned folder, 18 | - To install dependencies, run ``` npm install ``` or ``` yarn ``` 19 | - To run the application for development, run ``` npm run development ``` or ``` yarn development ``` 20 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 21 | ---- -------------------------------------------------------------------------------- /Chapter06/mern-classroom/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import { ThemeProvider } from '@material-ui/styles' 5 | import theme from './theme' 6 | import { hot } from 'react-hot-loader' 7 | 8 | const App = () => { 9 | React.useEffect(() => { 10 | const jssStyles = document.querySelector('#jss-server-side') 11 | if (jssStyles) { 12 | jssStyles.parentNode.removeChild(jssStyles) 13 | } 14 | }, []) 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | )} 22 | 23 | export default hot(module)(App) 24 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/client/MainRouter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Route, Switch} from 'react-router-dom' 3 | import Home from './core/Home' 4 | import Users from './user/Users' 5 | import Signup from './user/Signup' 6 | import Signin from './auth/Signin' 7 | import EditProfile from './user/EditProfile' 8 | import Profile from './user/Profile' 9 | import PrivateRoute from './auth/PrivateRoute' 10 | import Menu from './core/Menu' 11 | import NewCourse from './course/NewCourse' 12 | //import Courses from './course/Courses' 13 | import Course from './course/Course' 14 | import EditCourse from './course/EditCourse' 15 | import MyCourses from './course/MyCourses' 16 | import Enrollment from './enrollment/Enrollment' 17 | 18 | const MainRouter = () => { 19 | return (
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
) 38 | } 39 | 40 | export default MainRouter 41 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/client/assets/images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter06/mern-classroom/client/assets/images/default.png -------------------------------------------------------------------------------- /Chapter06/mern-classroom/client/assets/images/unicornbike.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter06/mern-classroom/client/assets/images/unicornbike.jpg -------------------------------------------------------------------------------- /Chapter06/mern-classroom/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = async (user) => { 2 | try { 3 | let response = await fetch('/auth/signin/', { 4 | method: 'POST', 5 | headers: { 6 | 'Accept': 'application/json', 7 | 'Content-Type': 'application/json' 8 | }, 9 | credentials: 'include', 10 | body: JSON.stringify(user) 11 | }) 12 | return await response.json() 13 | } catch(err) { 14 | console.log(err) 15 | } 16 | } 17 | 18 | const signout = async () => { 19 | try { 20 | let response = await fetch('/auth/signout/', { method: 'GET' }) 21 | return await response.json() 22 | } catch(err) { 23 | console.log(err) 24 | } 25 | } 26 | 27 | export { 28 | signin, 29 | signout 30 | } -------------------------------------------------------------------------------- /Chapter06/mern-classroom/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | clearJWT(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | }, 27 | updateUser(user, cb) { 28 | if(typeof window !== "undefined"){ 29 | if(sessionStorage.getItem('jwt')){ 30 | let auth = JSON.parse(sessionStorage.getItem('jwt')) 31 | auth.user = user 32 | sessionStorage.setItem('jwt', JSON.stringify(auth)) 33 | cb() 34 | } 35 | } 36 | } 37 | } 38 | 39 | export default auth 40 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/client/enrollment/Enroll.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react' 2 | import PropTypes from 'prop-types' 3 | import Button from '@material-ui/core/Button' 4 | import {makeStyles} from '@material-ui/core/styles' 5 | import {create} from './api-enrollment' 6 | import auth from './../auth/auth-helper' 7 | import {Redirect} from 'react-router-dom' 8 | 9 | const useStyles = makeStyles(theme => ({ 10 | form: { 11 | minWidth: 500 12 | } 13 | })) 14 | 15 | export default function Enroll(props) { 16 | const classes = useStyles() 17 | const [values, setValues] = useState({ 18 | enrollmentId: '', 19 | error: '', 20 | redirect: false 21 | }) 22 | const jwt = auth.isAuthenticated() 23 | const clickEnroll = () => { 24 | create({ 25 | courseId: props.courseId 26 | }, { 27 | t: jwt.token 28 | }).then((data) => { 29 | if (data && data.error) { 30 | setValues({...values, error: data.error}) 31 | } else { 32 | setValues({...values, enrollmentId: data._id, redirect: true}) 33 | } 34 | }) 35 | } 36 | 37 | if(values.redirect){ 38 | return () 39 | } 40 | 41 | return ( 42 | 43 | ) 44 | } 45 | 46 | Enroll.propTypes = { 47 | courseId: PropTypes.string.isRequired 48 | } 49 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/client/theme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from '@material-ui/core/styles' 2 | 3 | const theme = createMuiTheme({ 4 | typography: { 5 | useNextVariants: true, 6 | }, 7 | palette: { 8 | primary: { 9 | light: '#8e8e8e', 10 | main: '#616161', 11 | dark: '#373737', 12 | contrastText: '#fffde7', 13 | }, 14 | secondary: { 15 | light: '#ffad42', 16 | main: '#f57c00', 17 | dark: '#bb4d00', 18 | contrastText: '#fffde7', 19 | }, 20 | openTitle: '#455a64', 21 | protectedTitle: '#f57c00', 22 | type: 'light' 23 | } 24 | }) 25 | 26 | export default theme -------------------------------------------------------------------------------- /Chapter06/mern-classroom/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject' 10 | } 11 | 12 | export default config 13 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/server/models/course.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | const LessonSchema = new mongoose.Schema({ 4 | title: String, 5 | content: String, 6 | resource_url: String 7 | }) 8 | const Lesson = mongoose.model('Lesson', LessonSchema) 9 | const CourseSchema = new mongoose.Schema({ 10 | name: { 11 | type: String, 12 | trim: true, 13 | required: 'Name is required' 14 | }, 15 | image: { 16 | data: Buffer, 17 | contentType: String 18 | }, 19 | description: { 20 | type: String, 21 | trim: true 22 | }, 23 | category: { 24 | type: String, 25 | required: 'Category is required' 26 | }, 27 | updated: Date, 28 | created: { 29 | type: Date, 30 | default: Date.now 31 | }, 32 | instructor: {type: mongoose.Schema.ObjectId, ref: 'User'}, 33 | published: { 34 | type: Boolean, 35 | default: false 36 | }, 37 | lessons: [LessonSchema] 38 | }) 39 | 40 | export default mongoose.model('Course', CourseSchema) 41 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/server/models/enrollment.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | const EnrollmentSchema = new mongoose.Schema({ 4 | course: {type: mongoose.Schema.ObjectId, ref: 'Course'}, 5 | updated: Date, 6 | enrolled: { 7 | type: Date, 8 | default: Date.now 9 | }, 10 | student: {type: mongoose.Schema.ObjectId, ref: 'User'}, 11 | lessonStatus: [{ 12 | lesson: {type: mongoose.Schema.ObjectId, ref: 'Lesson'}, 13 | complete: Boolean}], 14 | completed: Date 15 | }) 16 | 17 | export default mongoose.model('Enrollment', EnrollmentSchema) 18 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/server/routes/course.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import courseCtrl from '../controllers/course.controller' 3 | import userCtrl from '../controllers/user.controller' 4 | import authCtrl from '../controllers/auth.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/courses/published') 9 | .get(courseCtrl.listPublished) 10 | 11 | router.route('/api/courses/by/:userId') 12 | .post(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.isEducator, courseCtrl.create) 13 | .get(authCtrl.requireSignin, authCtrl.hasAuthorization, courseCtrl.listByInstructor) 14 | 15 | router.route('/api/courses/photo/:courseId') 16 | .get(courseCtrl.photo, courseCtrl.defaultPhoto) 17 | 18 | router.route('/api/courses/defaultphoto') 19 | .get(courseCtrl.defaultPhoto) 20 | 21 | router.route('/api/courses/:courseId/lesson/new') 22 | .put(authCtrl.requireSignin, courseCtrl.isInstructor, courseCtrl.newLesson) 23 | 24 | router.route('/api/courses/:courseId') 25 | .get(courseCtrl.read) 26 | .put(authCtrl.requireSignin, courseCtrl.isInstructor, courseCtrl.update) 27 | .delete(authCtrl.requireSignin, courseCtrl.isInstructor, courseCtrl.remove) 28 | 29 | router.param('courseId', courseCtrl.courseByID) 30 | router.param('userId', userCtrl.userByID) 31 | 32 | export default router 33 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/server/routes/enrollment.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import enrollmentCtrl from '../controllers/enrollment.controller' 3 | import courseCtrl from '../controllers/course.controller' 4 | import authCtrl from '../controllers/auth.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/enrollment/enrolled') 9 | .get(authCtrl.requireSignin, enrollmentCtrl.listEnrolled) 10 | 11 | router.route('/api/enrollment/new/:courseId') 12 | .post(authCtrl.requireSignin, enrollmentCtrl.findEnrollment, enrollmentCtrl.create) 13 | 14 | router.route('/api/enrollment/stats/:courseId') 15 | .get(enrollmentCtrl.enrollmentStats) 16 | 17 | router.route('/api/enrollment/complete/:enrollmentId') 18 | .put(authCtrl.requireSignin, enrollmentCtrl.isStudent, enrollmentCtrl.complete) 19 | 20 | router.route('/api/enrollment/:enrollmentId') 21 | .get(authCtrl.requireSignin, enrollmentCtrl.isStudent, enrollmentCtrl.read) 22 | .delete(authCtrl.requireSignin, enrollmentCtrl.isStudent, enrollmentCtrl.remove) 23 | 24 | router.param('courseId', courseCtrl.courseByID) 25 | router.param('enrollmentId', enrollmentCtrl.enrollmentByID) 26 | 27 | export default router 28 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/:userId') 12 | .get(authCtrl.requireSignin, userCtrl.read) 13 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 14 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 15 | 16 | router.param('userId', userCtrl.userByID) 17 | 18 | export default router 19 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${config.mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | 10 | MERN Classroom 11 | 12 | 13 | 19 | 20 | 21 |
${markup}
22 | 23 | 24 | 25 | ` 26 | } 27 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'webpack-hot-middleware/client?reload=true', 11 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 12 | ], 13 | output: { 14 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 15 | filename: 'bundle.js', 16 | publicPath: '/dist/' 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.jsx?$/, 22 | exclude: /node_modules/, 23 | use: [ 24 | 'babel-loader' 25 | ] 26 | }, 27 | { 28 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 29 | use: 'file-loader' 30 | } 31 | ] 32 | }, 33 | plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ], 37 | resolve: { 38 | alias: { 39 | 'react-dom': '@hot-loader/react-dom' 40 | } 41 | } 42 | } 43 | 44 | module.exports = config 45 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CURRENT_WORKING_DIR = process.cwd() 3 | 4 | const config = { 5 | mode: "production", 6 | entry: [ 7 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 8 | ], 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 11 | filename: 'bundle.js', 12 | publicPath: "/dist/" 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | use: [ 20 | 'babel-loader' 21 | ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter06/mern-classroom/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const nodeExternals = require('webpack-node-externals') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "server", 7 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 8 | target: "node", 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 11 | filename: "server.generated.js", 12 | publicPath: '/dist/', 13 | libraryTarget: "commonjs2" 14 | }, 15 | externals: [nodeExternals()], 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | use: [ 'babel-loader' ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", 4 | { 5 | "targets": { 6 | "node": "current" 7 | } 8 | } 9 | ], 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | "react-hot-loader/babel" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | /data/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/README.md: -------------------------------------------------------------------------------- 1 | # MERN Marketplace 2 | 3 | An online marketplace application with seller accounts, product search and suggestions, shopping cart, order management, payment processing with Stripe, and live auction with Socket.io - developed using React, Node, Express and MongoDB. 4 | 5 | ![MERN Marketplace](https://s3.amazonaws.com/mernbook/git+/marketplace.png "MERN Marketplace") 6 | 7 | ### [Live Demo](http://marketplace2.mernbook.com/ "MERN Marketplace") 8 | 9 | #### What you need to run this code 10 | 1. Node (13.12.0) 11 | 2. NPM (6.14.4) or Yarn (1.22.4) 12 | 3. MongoDB (4.2.0) 13 | 4. Stripe account with test data 14 | 15 | #### How to run this code 16 | 1. Make sure MongoDB is running on your system 17 | 2. Clone this repository 18 | 3. Update config/config.js with your test values for Stripe API keys and Stripe Connect Client ID 19 | 4. Open command line in the cloned folder, 20 | - To install dependencies, run ``` npm install ``` or ``` yarn ``` 21 | - To run the application for development, run ``` npm run development ``` or ``` yarn development ``` 22 | 5. Open [localhost:3000](http://localhost:3000/) in the browser 23 | ---- -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import { ThemeProvider } from '@material-ui/styles' 5 | import theme from './theme' 6 | import { hot } from 'react-hot-loader' 7 | 8 | const App = () => { 9 | React.useEffect(() => { 10 | const jssStyles = document.querySelector('#jss-server-side') 11 | if (jssStyles) { 12 | jssStyles.parentNode.removeChild(jssStyles) 13 | } 14 | }, []) 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | )} 22 | 23 | export default hot(module)(App) 24 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/client/assets/images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter07 and 08/mern-marketplace/client/assets/images/default.png -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/client/assets/images/stripeButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter07 and 08/mern-marketplace/client/assets/images/stripeButton.png -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/client/assets/images/unicornbike.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter07 and 08/mern-marketplace/client/assets/images/unicornbike.jpg -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = async (user) => { 2 | try { 3 | let response = await fetch('/auth/signin/', { 4 | method: 'POST', 5 | headers: { 6 | 'Accept': 'application/json', 7 | 'Content-Type': 'application/json' 8 | }, 9 | credentials: 'include', 10 | body: JSON.stringify(user) 11 | }) 12 | return await response.json() 13 | } catch(err) { 14 | console.log(err) 15 | } 16 | } 17 | 18 | const signout = async () => { 19 | try { 20 | let response = await fetch('/auth/signout/', { method: 'GET' }) 21 | return await response.json() 22 | } catch(err) { 23 | console.log(err) 24 | } 25 | } 26 | 27 | export { 28 | signin, 29 | signout 30 | } -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | clearJWT(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | }, 27 | updateUser(user, cb) { 28 | if(typeof window !== "undefined"){ 29 | if(sessionStorage.getItem('jwt')){ 30 | let auth = JSON.parse(sessionStorage.getItem('jwt')) 31 | auth.user = user 32 | sessionStorage.setItem('jwt', JSON.stringify(auth)) 33 | cb() 34 | } 35 | } 36 | } 37 | } 38 | 39 | export default auth 40 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/client/cart/AddToCart.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react' 2 | import {makeStyles} from '@material-ui/core/styles' 3 | import PropTypes from 'prop-types' 4 | import IconButton from '@material-ui/core/IconButton' 5 | import AddCartIcon from '@material-ui/icons/AddShoppingCart' 6 | import DisabledCartIcon from '@material-ui/icons/RemoveShoppingCart' 7 | import cart from './cart-helper.js' 8 | import { Redirect } from 'react-router-dom' 9 | 10 | const useStyles = makeStyles(theme => ({ 11 | iconButton: { 12 | width: '28px', 13 | height: '28px' 14 | }, 15 | disabledIconButton: { 16 | color: '#7f7563', 17 | width: '28px', 18 | height: '28px' 19 | } 20 | })) 21 | 22 | export default function AddToCart(props) { 23 | const classes = useStyles() 24 | const [redirect, setRedirect] = useState(false) 25 | 26 | const addToCart = () => { 27 | cart.addItem(props.item, () => { 28 | setRedirect({redirect:true}) 29 | }) 30 | } 31 | if (redirect) { 32 | return () 33 | } 34 | return ( 35 | {props.item.quantity >= 0 ? 36 | 37 | 38 | : 39 | 40 | 41 | } 42 | ) 43 | } 44 | 45 | AddToCart.propTypes = { 46 | item: PropTypes.object.isRequired, 47 | cartStyle: PropTypes.string 48 | } -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/client/cart/Cart.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react' 2 | import Grid from '@material-ui/core/Grid' 3 | import {makeStyles} from '@material-ui/core/styles' 4 | import CartItems from './CartItems' 5 | import {StripeProvider} from 'react-stripe-elements' 6 | import config from './../../config/config' 7 | import Checkout from './Checkout' 8 | 9 | const useStyles = makeStyles(theme => ({ 10 | root: { 11 | flexGrow: 1, 12 | margin: 30, 13 | } 14 | })) 15 | 16 | export default function Cart () { 17 | const classes = useStyles() 18 | const [checkout, setCheckout] = useState(false) 19 | 20 | const showCheckout = val => { 21 | setCheckout(val) 22 | } 23 | 24 | return (
25 | 26 | 27 | 29 | 30 | {checkout && 31 | 32 | 33 | 34 | 35 | } 36 | 37 |
) 38 | } 39 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/client/theme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from '@material-ui/core/styles' 2 | import { blueGrey, lightGreen } from '@material-ui/core/colors' 3 | 4 | const theme = createMuiTheme({ 5 | palette: { 6 | primary: { 7 | light: '#8eacbb', 8 | main: '#607d8b', 9 | dark: '#34515e', 10 | contrastText: '#fff', 11 | }, 12 | secondary: { 13 | light: '#e7ff8c', 14 | main: '#b2ff59', 15 | dark: '#7ecb20', 16 | contrastText: '#000', 17 | }, 18 | openTitle: blueGrey['400'], 19 | protectedTitle: lightGreen['400'], 20 | type: 'light' 21 | } 22 | }) 23 | 24 | export default theme -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject', 10 | stripe_connect_test_client_id: 'YOUR_stripe_connect_test_client', 11 | stripe_test_secret_key: 'YOUR_stripe_test_secret_key', 12 | stripe_test_api_key: 'YOUR_stripe_test_api_key' 13 | } 14 | 15 | export default config 16 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/server/models/order.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | const CartItemSchema = new mongoose.Schema({ 3 | product: {type: mongoose.Schema.ObjectId, ref: 'Product'}, 4 | quantity: Number, 5 | shop: {type: mongoose.Schema.ObjectId, ref: 'Shop'}, 6 | status: {type: String, 7 | default: 'Not processed', 8 | enum: ['Not processed' , 'Processing', 'Shipped', 'Delivered', 'Cancelled']} 9 | }) 10 | const CartItem = mongoose.model('CartItem', CartItemSchema) 11 | const OrderSchema = new mongoose.Schema({ 12 | products: [CartItemSchema], 13 | customer_name: { 14 | type: String, 15 | trim: true, 16 | required: 'Name is required' 17 | }, 18 | customer_email: { 19 | type: String, 20 | trim: true, 21 | match: [/.+\@.+\..+/, 'Please fill a valid email address'], 22 | required: 'Email is required' 23 | }, 24 | delivery_address: { 25 | street: {type: String, required: 'Street is required'}, 26 | city: {type: String, required: 'City is required'}, 27 | state: {type: String}, 28 | zipcode: {type: String, required: 'Zip Code is required'}, 29 | country: {type: String, required: 'Country is required'} 30 | }, 31 | payment_id: {}, 32 | updated: Date, 33 | created: { 34 | type: Date, 35 | default: Date.now 36 | }, 37 | user: {type: mongoose.Schema.ObjectId, ref: 'User'} 38 | }) 39 | 40 | const Order = mongoose.model('Order', OrderSchema) 41 | 42 | export {Order, CartItem} 43 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/server/models/product.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | const ProductSchema = new mongoose.Schema({ 3 | name: { 4 | type: String, 5 | trim: true, 6 | required: 'Name is required' 7 | }, 8 | image: { 9 | data: Buffer, 10 | contentType: String 11 | }, 12 | description: { 13 | type: String, 14 | trim: true 15 | }, 16 | category: { 17 | type: String 18 | }, 19 | quantity: { 20 | type: Number, 21 | required: "Quantity is required" 22 | }, 23 | price: { 24 | type: Number, 25 | required: "Price is required" 26 | }, 27 | updated: Date, 28 | created: { 29 | type: Date, 30 | default: Date.now 31 | }, 32 | shop: {type: mongoose.Schema.ObjectId, ref: 'Shop'} 33 | }) 34 | 35 | export default mongoose.model('Product', ProductSchema) 36 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/server/models/shop.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | const ShopSchema = new mongoose.Schema({ 3 | name: { 4 | type: String, 5 | trim: true, 6 | required: 'Name is required' 7 | }, 8 | image: { 9 | data: Buffer, 10 | contentType: String 11 | }, 12 | description: { 13 | type: String, 14 | trim: true 15 | }, 16 | updated: Date, 17 | created: { 18 | type: Date, 19 | default: Date.now 20 | }, 21 | owner: {type: mongoose.Schema.ObjectId, ref: 'User'} 22 | }) 23 | 24 | export default mongoose.model('Shop', ShopSchema) 25 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/server/routes/order.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import orderCtrl from '../controllers/order.controller' 3 | import productCtrl from '../controllers/product.controller' 4 | import authCtrl from '../controllers/auth.controller' 5 | import shopCtrl from '../controllers/shop.controller' 6 | import userCtrl from '../controllers/user.controller' 7 | 8 | const router = express.Router() 9 | 10 | router.route('/api/orders/:userId') 11 | .post(authCtrl.requireSignin, userCtrl.stripeCustomer, productCtrl.decreaseQuantity, orderCtrl.create) 12 | 13 | router.route('/api/orders/shop/:shopId') 14 | .get(authCtrl.requireSignin, shopCtrl.isOwner, orderCtrl.listByShop) 15 | 16 | router.route('/api/orders/user/:userId') 17 | .get(authCtrl.requireSignin, orderCtrl.listByUser) 18 | 19 | router.route('/api/order/status_values') 20 | .get(orderCtrl.getStatusValues) 21 | 22 | router.route('/api/order/:shopId/cancel/:productId') 23 | .put(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.increaseQuantity, orderCtrl.update) 24 | 25 | router.route('/api/order/:orderId/charge/:userId/:shopId') 26 | .put(authCtrl.requireSignin, shopCtrl.isOwner, userCtrl.createCharge, orderCtrl.update) 27 | 28 | router.route('/api/order/status/:shopId') 29 | .put(authCtrl.requireSignin, shopCtrl.isOwner, orderCtrl.update) 30 | 31 | router.route('/api/order/:orderId') 32 | .get(orderCtrl.read) 33 | 34 | router.param('userId', userCtrl.userByID) 35 | router.param('shopId', shopCtrl.shopByID) 36 | router.param('productId', productCtrl.productByID) 37 | router.param('orderId', orderCtrl.orderByID) 38 | 39 | export default router 40 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/server/routes/product.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import productCtrl from '../controllers/product.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import shopCtrl from '../controllers/shop.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/products/by/:shopId') 9 | .post(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.create) 10 | .get(productCtrl.listByShop) 11 | 12 | router.route('/api/products/latest') 13 | .get(productCtrl.listLatest) 14 | 15 | router.route('/api/products/related/:productId') 16 | .get(productCtrl.listRelated) 17 | 18 | router.route('/api/products/categories') 19 | .get(productCtrl.listCategories) 20 | 21 | router.route('/api/products') 22 | .get(productCtrl.list) 23 | 24 | router.route('/api/products/:productId') 25 | .get(productCtrl.read) 26 | 27 | router.route('/api/product/image/:productId') 28 | .get(productCtrl.photo, productCtrl.defaultPhoto) 29 | router.route('/api/product/defaultphoto') 30 | .get(productCtrl.defaultPhoto) 31 | 32 | router.route('/api/product/:shopId/:productId') 33 | .put(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.update) 34 | .delete(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.remove) 35 | 36 | router.param('shopId', shopCtrl.shopByID) 37 | router.param('productId', productCtrl.productByID) 38 | 39 | export default router 40 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/server/routes/shop.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import shopCtrl from '../controllers/shop.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/shops') 9 | .get(shopCtrl.list) 10 | 11 | router.route('/api/shop/:shopId') 12 | .get(shopCtrl.read) 13 | 14 | router.route('/api/shops/by/:userId') 15 | .post(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.isSeller, shopCtrl.create) 16 | .get(authCtrl.requireSignin, authCtrl.hasAuthorization, shopCtrl.listByOwner) 17 | 18 | router.route('/api/shops/:shopId') 19 | .put(authCtrl.requireSignin, shopCtrl.isOwner, shopCtrl.update) 20 | .delete(authCtrl.requireSignin, shopCtrl.isOwner, shopCtrl.remove) 21 | 22 | router.route('/api/shops/logo/:shopId') 23 | .get(shopCtrl.photo, shopCtrl.defaultPhoto) 24 | 25 | router.route('/api/shops/defaultphoto') 26 | .get(shopCtrl.defaultPhoto) 27 | 28 | router.param('shopId', shopCtrl.shopByID) 29 | router.param('userId', userCtrl.userByID) 30 | 31 | export default router 32 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/:userId') 12 | .get(authCtrl.requireSignin, userCtrl.read) 13 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 14 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 15 | router.route('/api/stripe_auth/:userId') 16 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.stripe_auth, userCtrl.update) 17 | 18 | router.param('userId', userCtrl.userByID) 19 | 20 | export default router 21 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, useFindAndModify: true }) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${config.mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | MERN Marketplace 7 | 8 | 9 | 14 | 15 | 16 |
${markup}
17 | 18 | 19 | 20 | 21 | ` 22 | } 23 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'react-hot-loader/patch', 11 | 'webpack-hot-middleware/client?reload=true', 12 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 13 | ], 14 | output: { 15 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 16 | filename: 'bundle.js', 17 | publicPath: '/dist/' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | use: [ 25 | 'babel-loader' 26 | ] 27 | }, 28 | { 29 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 30 | use: 'file-loader' 31 | } 32 | ] 33 | }, plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ] 37 | } 38 | 39 | module.exports = config 40 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CURRENT_WORKING_DIR = process.cwd() 3 | 4 | const config = { 5 | mode: "production", 6 | entry: [ 7 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 8 | ], 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 11 | filename: 'bundle.js', 12 | publicPath: "/dist/" 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | use: [ 20 | 'babel-loader' 21 | ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter07 and 08/mern-marketplace/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const nodeExternals = require('webpack-node-externals') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "server", 7 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 8 | target: "node", 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 11 | filename: "server.generated.js", 12 | publicPath: '/dist/', 13 | libraryTarget: "commonjs2" 14 | }, 15 | externals: [nodeExternals()], 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | use: [ 'babel-loader' ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", 4 | { 5 | "targets": { 6 | "node": "current" 7 | } 8 | } 9 | ], 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | "react-hot-loader/babel" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | /data/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/README.md: -------------------------------------------------------------------------------- 1 | # MERN Marketplace 2 | 3 | An online marketplace application with seller accounts, product search and suggestions, shopping cart, order management, payment processing with Stripe, and live auction with Socket.io - developed using React, Node, Express and MongoDB. 4 | 5 | ![MERN Marketplace](https://mernbook.s3.amazonaws.com/git+/marketplace-bidding.png "MERN Marketplace") 6 | 7 | ### [Live Demo](http://marketplace2.mernbook.com/ "MERN Marketplace") 8 | 9 | #### What you need to run this code 10 | 1. Node (13.12.0) 11 | 2. NPM (6.14.4) or Yarn (1.22.4) 12 | 3. MongoDB (4.2.0) 13 | 4. Stripe account with test data 14 | 15 | #### How to run this code 16 | 1. Make sure MongoDB is running on your system 17 | 2. Clone this repository 18 | 3. Update config/config.js with your test values for Stripe API keys and Stripe Connect Client ID 19 | 4. Open command line in the cloned folder, 20 | - To install dependencies, run ``` npm install ``` or ``` yarn ``` 21 | - To run the application for development, run ``` npm run development ``` or ``` yarn development ``` 22 | 5. Open [localhost:3000](http://localhost:3000/) in the browser 23 | ---- -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import { ThemeProvider } from '@material-ui/styles' 5 | import theme from './theme' 6 | import { hot } from 'react-hot-loader' 7 | 8 | const App = () => { 9 | React.useEffect(() => { 10 | const jssStyles = document.querySelector('#jss-server-side') 11 | if (jssStyles) { 12 | jssStyles.parentNode.removeChild(jssStyles) 13 | } 14 | }, []) 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | )} 22 | 23 | export default hot(module)(App) 24 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/client/assets/images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter09/mern-marketplace-bidding/client/assets/images/default.png -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/client/assets/images/stripeButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter09/mern-marketplace-bidding/client/assets/images/stripeButton.png -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/client/assets/images/unicornbike.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter09/mern-marketplace-bidding/client/assets/images/unicornbike.jpg -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = async (user) => { 2 | try { 3 | let response = await fetch('/auth/signin/', { 4 | method: 'POST', 5 | headers: { 6 | 'Accept': 'application/json', 7 | 'Content-Type': 'application/json' 8 | }, 9 | credentials: 'include', 10 | body: JSON.stringify(user) 11 | }) 12 | return await response.json() 13 | } catch(err) { 14 | console.log(err) 15 | } 16 | } 17 | 18 | const signout = async () => { 19 | try { 20 | let response = await fetch('/auth/signout/', { method: 'GET' }) 21 | return await response.json() 22 | } catch(err) { 23 | console.log(err) 24 | } 25 | } 26 | 27 | export { 28 | signin, 29 | signout 30 | } -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | clearJWT(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | }, 27 | updateUser(user, cb) { 28 | if(typeof window !== "undefined"){ 29 | if(sessionStorage.getItem('jwt')){ 30 | let auth = JSON.parse(sessionStorage.getItem('jwt')) 31 | auth.user = user 32 | sessionStorage.setItem('jwt', JSON.stringify(auth)) 33 | cb() 34 | } 35 | } 36 | } 37 | } 38 | 39 | export default auth 40 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/client/cart/AddToCart.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react' 2 | import {makeStyles} from '@material-ui/core/styles' 3 | import PropTypes from 'prop-types' 4 | import IconButton from '@material-ui/core/IconButton' 5 | import AddCartIcon from '@material-ui/icons/AddShoppingCart' 6 | import DisabledCartIcon from '@material-ui/icons/RemoveShoppingCart' 7 | import cart from './cart-helper.js' 8 | import { Redirect } from 'react-router-dom' 9 | 10 | const useStyles = makeStyles(theme => ({ 11 | iconButton: { 12 | width: '28px', 13 | height: '28px' 14 | }, 15 | disabledIconButton: { 16 | color: '#7f7563', 17 | width: '28px', 18 | height: '28px' 19 | } 20 | })) 21 | 22 | export default function AddToCart(props) { 23 | const classes = useStyles() 24 | const [redirect, setRedirect] = useState(false) 25 | 26 | const addToCart = () => { 27 | cart.addItem(props.item, () => { 28 | setRedirect({redirect:true}) 29 | }) 30 | } 31 | if (redirect) { 32 | return () 33 | } 34 | return ( 35 | {props.item.quantity >= 0 ? 36 | 37 | 38 | : 39 | 40 | 41 | } 42 | ) 43 | } 44 | 45 | AddToCart.propTypes = { 46 | item: PropTypes.object.isRequired, 47 | cartStyle: PropTypes.string 48 | } -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/client/cart/Cart.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react' 2 | import Grid from '@material-ui/core/Grid' 3 | import {makeStyles} from '@material-ui/core/styles' 4 | import CartItems from './CartItems' 5 | import {StripeProvider} from 'react-stripe-elements' 6 | import config from './../../config/config' 7 | import Checkout from './Checkout' 8 | 9 | const useStyles = makeStyles(theme => ({ 10 | root: { 11 | flexGrow: 1, 12 | margin: 30, 13 | } 14 | })) 15 | 16 | export default function Cart () { 17 | const classes = useStyles() 18 | const [checkout, setCheckout] = useState(false) 19 | 20 | const showCheckout = val => { 21 | setCheckout(val) 22 | } 23 | 24 | return (
25 | 26 | 27 | 29 | 30 | {checkout && 31 | 32 | 33 | 34 | 35 | } 36 | 37 |
) 38 | } 39 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/client/theme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from '@material-ui/core/styles' 2 | import { blueGrey, lightGreen } from '@material-ui/core/colors' 3 | 4 | const theme = createMuiTheme({ 5 | palette: { 6 | primary: { 7 | light: '#8eacbb', 8 | main: '#607d8b', 9 | dark: '#34515e', 10 | contrastText: '#fff', 11 | }, 12 | secondary: { 13 | light: '#e7ff8c', 14 | main: '#b2ff59', 15 | dark: '#7ecb20', 16 | contrastText: '#000', 17 | }, 18 | openTitle: blueGrey['400'], 19 | protectedTitle: lightGreen['400'], 20 | type: 'light' 21 | } 22 | }) 23 | 24 | export default theme -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject', 10 | stripe_connect_test_client_id: 'YOUR_stripe_connect_test_client', 11 | stripe_test_secret_key: 'YOUR_stripe_test_secret_key', 12 | stripe_test_api_key: 'YOUR_stripe_test_api_key' 13 | } 14 | 15 | export default config 16 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/controllers/bidding.controller.js: -------------------------------------------------------------------------------- 1 | import Auction from '../models/auction.model' 2 | 3 | export default (server) => { 4 | const io = require('socket.io').listen(server) 5 | io.on('connection', function(socket){ 6 | socket.on('join auction room', data => { 7 | socket.join(data.room) 8 | }) 9 | socket.on('leave auction room', data => { 10 | socket.leave(data.room) 11 | }) 12 | socket.on('new bid', data => { 13 | bid(data.bidInfo, data.room) 14 | }) 15 | }) 16 | const bid = async (bid, auction) => { 17 | try { 18 | let result = await Auction.findOneAndUpdate({_id:auction, $or: [{'bids.0.bid':{$lt:bid.bid}},{bids:{$eq:[]}} ]}, {$push: {bids: {$each:[bid], $position: 0}}}, {new: true}) 19 | .populate('bids.bidder', '_id name') 20 | .populate('seller', '_id name') 21 | .exec() 22 | io 23 | .to(auction) 24 | .emit('new bid', result) 25 | } catch(err) { 26 | console.log(err) 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/models/auction.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | const AuctionSchema = new mongoose.Schema({ 3 | itemName: { 4 | type: String, 5 | trim: true, 6 | required: 'Item name is required' 7 | }, 8 | description: { 9 | type: String, 10 | trim: true 11 | }, 12 | image: { 13 | data: Buffer, 14 | contentType: String 15 | }, 16 | updated: Date, 17 | created: { 18 | type: Date, 19 | default: Date.now 20 | }, 21 | bidStart: { 22 | type: Date, 23 | default: Date.now 24 | }, 25 | bidEnd: { 26 | type: Date, 27 | required: "Auction end time is required" 28 | }, 29 | seller: { 30 | type: mongoose.Schema.ObjectId, 31 | ref: 'User' 32 | }, 33 | startingBid: { type: Number, default: 0 }, 34 | bids: [{ 35 | bidder: {type: mongoose.Schema.ObjectId, ref: 'User'}, 36 | bid: Number, 37 | time: Date 38 | }] 39 | }) 40 | 41 | export default mongoose.model('Auction', AuctionSchema) 42 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/models/order.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | const CartItemSchema = new mongoose.Schema({ 3 | product: {type: mongoose.Schema.ObjectId, ref: 'Product'}, 4 | quantity: Number, 5 | shop: {type: mongoose.Schema.ObjectId, ref: 'Shop'}, 6 | status: {type: String, 7 | default: 'Not processed', 8 | enum: ['Not processed' , 'Processing', 'Shipped', 'Delivered', 'Cancelled']} 9 | }) 10 | const CartItem = mongoose.model('CartItem', CartItemSchema) 11 | const OrderSchema = new mongoose.Schema({ 12 | products: [CartItemSchema], 13 | customer_name: { 14 | type: String, 15 | trim: true, 16 | required: 'Name is required' 17 | }, 18 | customer_email: { 19 | type: String, 20 | trim: true, 21 | match: [/.+\@.+\..+/, 'Please fill a valid email address'], 22 | required: 'Email is required' 23 | }, 24 | delivery_address: { 25 | street: {type: String, required: 'Street is required'}, 26 | city: {type: String, required: 'City is required'}, 27 | state: {type: String}, 28 | zipcode: {type: String, required: 'Zip Code is required'}, 29 | country: {type: String, required: 'Country is required'} 30 | }, 31 | payment_id: {}, 32 | updated: Date, 33 | created: { 34 | type: Date, 35 | default: Date.now 36 | }, 37 | user: {type: mongoose.Schema.ObjectId, ref: 'User'} 38 | }) 39 | 40 | const Order = mongoose.model('Order', OrderSchema) 41 | 42 | export {Order, CartItem} 43 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/models/product.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | const ProductSchema = new mongoose.Schema({ 3 | name: { 4 | type: String, 5 | trim: true, 6 | required: 'Name is required' 7 | }, 8 | image: { 9 | data: Buffer, 10 | contentType: String 11 | }, 12 | description: { 13 | type: String, 14 | trim: true 15 | }, 16 | category: { 17 | type: String 18 | }, 19 | quantity: { 20 | type: Number, 21 | required: "Quantity is required" 22 | }, 23 | price: { 24 | type: Number, 25 | required: "Price is required" 26 | }, 27 | updated: Date, 28 | created: { 29 | type: Date, 30 | default: Date.now 31 | }, 32 | shop: {type: mongoose.Schema.ObjectId, ref: 'Shop'} 33 | }) 34 | 35 | export default mongoose.model('Product', ProductSchema) 36 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/models/shop.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | const ShopSchema = new mongoose.Schema({ 3 | name: { 4 | type: String, 5 | trim: true, 6 | required: 'Name is required' 7 | }, 8 | image: { 9 | data: Buffer, 10 | contentType: String 11 | }, 12 | description: { 13 | type: String, 14 | trim: true 15 | }, 16 | updated: Date, 17 | created: { 18 | type: Date, 19 | default: Date.now 20 | }, 21 | owner: {type: mongoose.Schema.ObjectId, ref: 'User'} 22 | }) 23 | 24 | export default mongoose.model('Shop', ShopSchema) 25 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/routes/auction.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import auctionCtrl from '../controllers/auction.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/auctions') 9 | .get(auctionCtrl.listOpen) 10 | 11 | router.route('/api/auctions/bid/:userId') 12 | .get(auctionCtrl.listByBidder) 13 | 14 | router.route('/api/auction/:auctionId') 15 | .get(auctionCtrl.read) 16 | 17 | router.route('/api/auctions/by/:userId') 18 | .post(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.isSeller, auctionCtrl.create) 19 | .get(authCtrl.requireSignin, authCtrl.hasAuthorization, auctionCtrl.listBySeller) 20 | 21 | router.route('/api/auctions/:auctionId') 22 | .put(authCtrl.requireSignin, auctionCtrl.isSeller, auctionCtrl.update) 23 | .delete(authCtrl.requireSignin, auctionCtrl.isSeller, auctionCtrl.remove) 24 | 25 | router.route('/api/auctions/image/:auctionId') 26 | .get(auctionCtrl.photo, auctionCtrl.defaultPhoto) 27 | 28 | router.route('/api/auctions/defaultphoto') 29 | .get(auctionCtrl.defaultPhoto) 30 | 31 | router.param('auctionId', auctionCtrl.auctionByID) 32 | router.param('userId', userCtrl.userByID) 33 | 34 | export default router 35 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/routes/order.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import orderCtrl from '../controllers/order.controller' 3 | import productCtrl from '../controllers/product.controller' 4 | import authCtrl from '../controllers/auth.controller' 5 | import shopCtrl from '../controllers/shop.controller' 6 | import userCtrl from '../controllers/user.controller' 7 | 8 | const router = express.Router() 9 | 10 | router.route('/api/orders/:userId') 11 | .post(authCtrl.requireSignin, userCtrl.stripeCustomer, productCtrl.decreaseQuantity, orderCtrl.create) 12 | 13 | router.route('/api/orders/shop/:shopId') 14 | .get(authCtrl.requireSignin, shopCtrl.isOwner, orderCtrl.listByShop) 15 | 16 | router.route('/api/orders/user/:userId') 17 | .get(authCtrl.requireSignin, orderCtrl.listByUser) 18 | 19 | router.route('/api/order/status_values') 20 | .get(orderCtrl.getStatusValues) 21 | 22 | router.route('/api/order/:shopId/cancel/:productId') 23 | .put(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.increaseQuantity, orderCtrl.update) 24 | 25 | router.route('/api/order/:orderId/charge/:userId/:shopId') 26 | .put(authCtrl.requireSignin, shopCtrl.isOwner, userCtrl.createCharge, orderCtrl.update) 27 | 28 | router.route('/api/order/status/:shopId') 29 | .put(authCtrl.requireSignin, shopCtrl.isOwner, orderCtrl.update) 30 | 31 | router.route('/api/order/:orderId') 32 | .get(orderCtrl.read) 33 | 34 | router.param('userId', userCtrl.userByID) 35 | router.param('shopId', shopCtrl.shopByID) 36 | router.param('productId', productCtrl.productByID) 37 | router.param('orderId', orderCtrl.orderByID) 38 | 39 | export default router 40 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/routes/product.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import productCtrl from '../controllers/product.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import shopCtrl from '../controllers/shop.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/products/by/:shopId') 9 | .post(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.create) 10 | .get(productCtrl.listByShop) 11 | 12 | router.route('/api/products/latest') 13 | .get(productCtrl.listLatest) 14 | 15 | router.route('/api/products/related/:productId') 16 | .get(productCtrl.listRelated) 17 | 18 | router.route('/api/products/categories') 19 | .get(productCtrl.listCategories) 20 | 21 | router.route('/api/products') 22 | .get(productCtrl.list) 23 | 24 | router.route('/api/products/:productId') 25 | .get(productCtrl.read) 26 | 27 | router.route('/api/product/image/:productId') 28 | .get(productCtrl.photo, productCtrl.defaultPhoto) 29 | router.route('/api/product/defaultphoto') 30 | .get(productCtrl.defaultPhoto) 31 | 32 | router.route('/api/product/:shopId/:productId') 33 | .put(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.update) 34 | .delete(authCtrl.requireSignin, shopCtrl.isOwner, productCtrl.remove) 35 | 36 | router.param('shopId', shopCtrl.shopByID) 37 | router.param('productId', productCtrl.productByID) 38 | 39 | export default router 40 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/routes/shop.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import shopCtrl from '../controllers/shop.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/shops') 9 | .get(shopCtrl.list) 10 | 11 | router.route('/api/shop/:shopId') 12 | .get(shopCtrl.read) 13 | 14 | router.route('/api/shops/by/:userId') 15 | .post(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.isSeller, shopCtrl.create) 16 | .get(authCtrl.requireSignin, authCtrl.hasAuthorization, shopCtrl.listByOwner) 17 | 18 | router.route('/api/shops/:shopId') 19 | .put(authCtrl.requireSignin, shopCtrl.isOwner, shopCtrl.update) 20 | .delete(authCtrl.requireSignin, shopCtrl.isOwner, shopCtrl.remove) 21 | 22 | router.route('/api/shops/logo/:shopId') 23 | .get(shopCtrl.photo, shopCtrl.defaultPhoto) 24 | 25 | router.route('/api/shops/defaultphoto') 26 | .get(shopCtrl.defaultPhoto) 27 | 28 | router.param('shopId', shopCtrl.shopByID) 29 | router.param('userId', userCtrl.userByID) 30 | 31 | export default router 32 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/:userId') 12 | .get(authCtrl.requireSignin, userCtrl.read) 13 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 14 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 15 | router.route('/api/stripe_auth/:userId') 16 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.stripe_auth, userCtrl.update) 17 | 18 | router.param('userId', userCtrl.userByID) 19 | 20 | export default router 21 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | import bidding from './controllers/bidding.controller' 5 | 6 | // Connection URL 7 | mongoose.Promise = global.Promise 8 | mongoose.connect(config.mongoUri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, useFindAndModify: true }) 9 | mongoose.connection.on('error', () => { 10 | throw new Error(`unable to connect to database: ${config.mongoUri}`) 11 | }) 12 | 13 | const server = app.listen(config.port, (err) => { 14 | if (err) { 15 | console.log(err) 16 | } 17 | console.info('Server started on port %s.', config.port) 18 | }) 19 | 20 | bidding(server) 21 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | MERN Marketplace 7 | 8 | 9 | 14 | 15 | 16 |
${markup}
17 | 18 | 19 | 20 | 21 | ` 22 | } 23 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'react-hot-loader/patch', 11 | 'webpack-hot-middleware/client?reload=true', 12 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 13 | ], 14 | output: { 15 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 16 | filename: 'bundle.js', 17 | publicPath: '/dist/' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | use: [ 25 | 'babel-loader' 26 | ] 27 | }, 28 | { 29 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 30 | use: 'file-loader' 31 | } 32 | ] 33 | }, plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ] 37 | } 38 | 39 | module.exports = config 40 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CURRENT_WORKING_DIR = process.cwd() 3 | 4 | const config = { 5 | mode: "production", 6 | entry: [ 7 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 8 | ], 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 11 | filename: 'bundle.js', 12 | publicPath: "/dist/" 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | use: [ 20 | 'babel-loader' 21 | ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter09/mern-marketplace-bidding/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const nodeExternals = require('webpack-node-externals') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "server", 7 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 8 | target: "node", 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 11 | filename: "server.generated.js", 12 | publicPath: '/dist/', 13 | libraryTarget: "commonjs2" 14 | }, 15 | externals: [nodeExternals()], 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | use: [ 'babel-loader' ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", 4 | { 5 | "targets": { 6 | "node": "current" 7 | } 8 | } 9 | ], 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | "react-hot-loader/babel" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | /data/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/README.md: -------------------------------------------------------------------------------- 1 | # MERN Expense Tracker 2 | 3 | An expense tracking application with data visualization - developed using React, Node, Express, MongoDB and Victory. 4 | 5 | ![MERN Expense Tracker](https://mernbook.s3.amazonaws.com/git+/expensetracker.png "MERN Expense Tracker") 6 | 7 | ![MERN Expense Tracker Graphs](https://mernbook.s3.amazonaws.com/git+/graphs.png "MERN Expense Tracker Graphs") 8 | 9 | ### [Live Demo](http://expensetracker.mernbook.com/ "MERN Expense Tracker") 10 | 11 | #### What you need to run this code 12 | 1. Node (13.12.0) 13 | 2. NPM (6.14.4) or Yarn (1.22.4) 14 | 3. MongoDB (4.2.0) 15 | 16 | #### How to run this code 17 | 1. Make sure MongoDB is running on your system 18 | 2. Clone this repository 19 | 3. Open command line in the cloned folder, 20 | - To install dependencies, run ``` npm install ``` or ``` yarn ``` 21 | - To run the application for development, run ``` npm run development ``` or ``` yarn development ``` 22 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 23 | ---- -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import { ThemeProvider } from '@material-ui/styles' 5 | import theme from './theme' 6 | import { hot } from 'react-hot-loader' 7 | 8 | const App = () => { 9 | React.useEffect(() => { 10 | const jssStyles = document.querySelector('#jss-server-side') 11 | if (jssStyles) { 12 | jssStyles.parentNode.removeChild(jssStyles) 13 | } 14 | }, []) 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | )} 22 | 23 | export default hot(module)(App) 24 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/client/MainRouter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Route, Switch} from 'react-router-dom' 3 | import Home from './core/Home' 4 | import Users from './user/Users' 5 | import Signup from './user/Signup' 6 | import Signin from './auth/Signin' 7 | import EditProfile from './user/EditProfile' 8 | import Profile from './user/Profile' 9 | import PrivateRoute from './auth/PrivateRoute' 10 | import Menu from './core/Menu' 11 | import NewExpense from './expense/NewExpense' 12 | import Expenses from './expense/Expenses' 13 | import Reports from './report/Reports' 14 | 15 | const MainRouter = () => { 16 | return (
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
) 30 | } 31 | 32 | export default MainRouter 33 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/client/assets/images/unicornbike.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter10/mern-expense-tracker/client/assets/images/unicornbike.jpg -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/client/assets/images/unicorncoin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter10/mern-expense-tracker/client/assets/images/unicorncoin.jpg -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = async (user) => { 2 | try { 3 | let response = await fetch('/auth/signin/', { 4 | method: 'POST', 5 | headers: { 6 | 'Accept': 'application/json', 7 | 'Content-Type': 'application/json' 8 | }, 9 | credentials: 'include', 10 | body: JSON.stringify(user) 11 | }) 12 | return await response.json() 13 | } catch(err) { 14 | console.log(err) 15 | } 16 | } 17 | 18 | const signout = async () => { 19 | try { 20 | let response = await fetch('/auth/signout/', { method: 'GET' }) 21 | return await response.json() 22 | } catch(err) { 23 | console.log(err) 24 | } 25 | } 26 | 27 | export { 28 | signin, 29 | signout 30 | } -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | clearJWT(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | } 27 | } 28 | 29 | export default auth 30 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/client/report/Reports.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | import Divider from '@material-ui/core/Divider' 4 | import CategoryPie from './CategoryPie' 5 | import YearlyBar from './YearlyBar' 6 | import MonthlyScatter from './MonthlyScatter' 7 | 8 | const useStyles = makeStyles(theme => ({ 9 | root: { 10 | width: '90%', 11 | maxWidth: '800px', 12 | margin: 'auto', 13 | marginTop: 40, 14 | marginBottom: 40 15 | }, 16 | separator: { 17 | marginBottom: 36 18 | } 19 | })) 20 | 21 | export default function Reports() { 22 | const classes = useStyles() 23 | return ( 24 |
25 | 26 | 27 | 28 | 29 | 30 |
31 | ) 32 | } -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/client/theme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from '@material-ui/core/styles' 2 | 3 | const theme = createMuiTheme({ 4 | typography: { 5 | useNextVariants: true, 6 | }, 7 | palette: { 8 | primary: { 9 | light: '#4f83cc', 10 | main: '#01579b', 11 | dark: '#002f6c', 12 | contrastText: '#fff', 13 | }, 14 | secondary: { 15 | light: '#9fffe0', 16 | main: '#69f0ae', 17 | dark: '#2bbd7e', 18 | contrastText: '#000', 19 | }, 20 | openTitle: '#002f6c', 21 | protectedTitle: '#2bbd7e', 22 | type: 'light' 23 | } 24 | }) 25 | 26 | export default theme -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject' 10 | } 11 | 12 | export default config 13 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/server/models/expense.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | const ExpenseSchema = new mongoose.Schema({ 3 | title: { 4 | type: String, 5 | trim: true, 6 | required: 'Title is required' 7 | }, 8 | category: { 9 | type: String, 10 | trim: true, 11 | required: 'Category is required' 12 | }, 13 | amount: { 14 | type: Number, 15 | min: 0, 16 | required: 'Amount is required' 17 | }, 18 | incurred_on: { 19 | type: Date, 20 | default: Date.now 21 | }, 22 | notes: { 23 | type: String, 24 | trim: true 25 | }, 26 | updated: Date, 27 | created: { 28 | type: Date, 29 | default: Date.now 30 | }, 31 | recorded_by: {type: mongoose.Schema.ObjectId, ref: 'User'} 32 | }) 33 | 34 | export default mongoose.model('Expense', ExpenseSchema) 35 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/server/routes/expense.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import expenseCtrl from '../controllers/expense.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/expenses/current/preview') 8 | .get(authCtrl.requireSignin, expenseCtrl.currentMonthPreview) 9 | 10 | router.route('/api/expenses/by/category') 11 | .get(authCtrl.requireSignin, expenseCtrl.expenseByCategory) 12 | 13 | router.route('/api/expenses/plot') 14 | .get(authCtrl.requireSignin, expenseCtrl.plotExpenses) 15 | 16 | router.route('/api/expenses/category/averages') 17 | .get(authCtrl.requireSignin, expenseCtrl.averageCategories) 18 | 19 | router.route('/api/expenses/yearly') 20 | .get(authCtrl.requireSignin, expenseCtrl.yearlyExpenses) 21 | 22 | router.route('/api/expenses') 23 | .post(authCtrl.requireSignin, expenseCtrl.create) 24 | .get(authCtrl.requireSignin, expenseCtrl.listByUser) 25 | 26 | router.route('/api/expenses/:expenseId') 27 | // .get(authCtrl.requireSignin, expenseCtrl.read) 28 | .put(authCtrl.requireSignin, expenseCtrl.hasAuthorization, expenseCtrl.update) 29 | .delete(authCtrl.requireSignin, expenseCtrl.hasAuthorization, expenseCtrl.remove) 30 | 31 | router.param('expenseId', expenseCtrl.expenseByID) 32 | 33 | export default router 34 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/:userId') 12 | .get(authCtrl.requireSignin, userCtrl.read) 13 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 14 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 15 | 16 | router.param('userId', userCtrl.userByID) 17 | 18 | export default router 19 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${config.mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | 10 | MERN Expense Tracker 11 | 12 | 13 | 19 | 20 | 21 |
${markup}
22 | 23 | 24 | 25 | ` 26 | } 27 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'webpack-hot-middleware/client?reload=true', 11 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 12 | ], 13 | output: { 14 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 15 | filename: 'bundle.js', 16 | publicPath: '/dist/' 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.jsx?$/, 22 | exclude: /node_modules/, 23 | use: [ 24 | 'babel-loader' 25 | ] 26 | }, 27 | { 28 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 29 | use: 'file-loader' 30 | } 31 | ] 32 | }, 33 | plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ], 37 | resolve: { 38 | alias: { 39 | 'react-dom': '@hot-loader/react-dom' 40 | } 41 | } 42 | } 43 | 44 | module.exports = config 45 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CURRENT_WORKING_DIR = process.cwd() 3 | 4 | const config = { 5 | mode: "production", 6 | entry: [ 7 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 8 | ], 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 11 | filename: 'bundle.js', 12 | publicPath: "/dist/" 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | use: [ 20 | 'babel-loader' 21 | ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter10/mern-expense-tracker/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const nodeExternals = require('webpack-node-externals') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "server", 7 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 8 | target: "node", 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 11 | filename: "server.generated.js", 12 | publicPath: '/dist/', 13 | libraryTarget: "commonjs2" 14 | }, 15 | externals: [nodeExternals()], 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | use: [ 'babel-loader' ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", 4 | { 5 | "targets": { 6 | "node": "current" 7 | } 8 | } 9 | ], 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | "react-hot-loader/babel" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | /data/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/README.md: -------------------------------------------------------------------------------- 1 | # MERN Mediastream 2 | 3 | A media streaming application with media upload and stream features - developed using React, Node, Express and MongoDB. 4 | 5 | ![MERN Mediastream](https://s3.amazonaws.com/mernbook/git+/mediastream.png "MERN Mediastream") 6 | 7 | ### [Live Demo](http://mediastream2.mernbook.com/ "MERN Mediastream") 8 | 9 | #### What you need to run this code 10 | 1. Node (13.12.0) 11 | 2. NPM (6.14.4) or Yarn (1.22.4) 12 | 3. MongoDB (4.2.0) 13 | 14 | #### How to run this code 15 | 1. Clone this repository 16 | 2. Open command line in the cloned folder, 17 | - To install dependencies, run ``` npm install ``` or ``` yarn ``` 18 | - To run the application for development, run ``` npm run development ``` or ``` yarn development ``` 19 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 20 | ---- 21 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import { ThemeProvider } from '@material-ui/styles' 5 | import theme from './theme' 6 | import { hot } from 'react-hot-loader' 7 | 8 | const App = () => { 9 | React.useEffect(() => { 10 | const jssStyles = document.querySelector('#jss-server-side') 11 | if (jssStyles) { 12 | jssStyles.parentNode.removeChild(jssStyles) 13 | } 14 | }, []) 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | )} 22 | 23 | export default hot(module)(App) 24 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/client/MainRouter.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Route, Switch} from 'react-router-dom' 3 | import Home from './core/Home' 4 | import Users from './user/Users' 5 | import Signup from './user/Signup' 6 | import Signin from './auth/Signin' 7 | import EditProfile from './user/EditProfile' 8 | import Profile from './user/Profile' 9 | import PrivateRoute from './auth/PrivateRoute' 10 | import Menu from './core/Menu' 11 | import NewMedia from './media/NewMedia' 12 | import PlayMedia from './media/PlayMedia' 13 | import EditMedia from './media/EditMedia' 14 | 15 | const MainRouter = ({data}) => { 16 | return (
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ( 29 | 30 | )} /> 31 | 32 |
) 33 | } 34 | 35 | export default MainRouter 36 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/client/assets/images/unicornbike.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter11 and 12/mern-mediastream/client/assets/images/unicornbike.jpg -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = async (user) => { 2 | try { 3 | let response = await fetch('/auth/signin/', { 4 | method: 'POST', 5 | headers: { 6 | 'Accept': 'application/json', 7 | 'Content-Type': 'application/json' 8 | }, 9 | credentials: 'include', 10 | body: JSON.stringify(user) 11 | }) 12 | return await response.json() 13 | } catch(err) { 14 | console.log(err) 15 | } 16 | } 17 | 18 | const signout = async () => { 19 | try { 20 | let response = await fetch('/auth/signout/', { method: 'GET' }) 21 | return await response.json() 22 | } catch(err) { 23 | console.log(err) 24 | } 25 | } 26 | 27 | export { 28 | signin, 29 | signout 30 | } 31 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | clearJWT(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | } 27 | } 28 | 29 | export default auth -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/client/core/Home.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | import Card from '@material-ui/core/Card' 4 | import Typography from '@material-ui/core/Typography' 5 | import MediaList from '../media/MediaList' 6 | import {listPopular} from '../media/api-media.js' 7 | 8 | const useStyles = makeStyles(theme => ({ 9 | card: { 10 | margin: `${theme.spacing(5)}px 30px` 11 | }, 12 | title: { 13 | padding:`${theme.spacing(3)}px ${theme.spacing(2.5)}px 0px`, 14 | color: theme.palette.text.secondary, 15 | fontSize: '1em' 16 | }, 17 | media: { 18 | minHeight: 330 19 | } 20 | })) 21 | 22 | export default function Home(){ 23 | const classes = useStyles() 24 | const [media, setMedia] = useState([]) 25 | 26 | useEffect(() => { 27 | const abortController = new AbortController() 28 | const signal = abortController.signal 29 | listPopular(signal).then((data) => { 30 | if (data.error) { 31 | console.log(data.error) 32 | } else { 33 | setMedia(data) 34 | } 35 | }) 36 | return function cleanup(){ 37 | abortController.abort() 38 | } 39 | }, []) 40 | return ( 41 | 42 | 43 | Popular Videos 44 | 45 | 46 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/client/routeConfig.js: -------------------------------------------------------------------------------- 1 | import PlayMedia from './media/PlayMedia' 2 | import { read } from './media/api-media.js' 3 | 4 | const routes = [ 5 | { 6 | path: '/media/:mediaId', 7 | component: PlayMedia, 8 | loadData: (params) => read(params) 9 | } 10 | 11 | ] 12 | export default routes 13 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/client/theme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from '@material-ui/core/styles' 2 | import { red, brown } from '@material-ui/core/colors' 3 | 4 | const theme = createMuiTheme({ 5 | typography: { 6 | useNextVariants: true, 7 | }, 8 | palette: { 9 | primary: { 10 | light: '#f05545', 11 | main: '#b71c1c', 12 | dark: '#7f0000', 13 | contrastText: '#fff', 14 | }, 15 | secondary: { 16 | light: '#fbfffc', 17 | main: '#c8e6c9', 18 | dark: '#97b498', 19 | contrastText: '#37474f', 20 | }, 21 | openTitle: red['500'], 22 | protectedTitle: brown['300'], 23 | type: 'light' 24 | }, 25 | }) 26 | 27 | export default theme -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject', 10 | serverUrl: process.env.serverUrl || 'http://localhost:3000' 11 | } 12 | 13 | export default config 14 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/server/models/media.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | const MediaSchema = new mongoose.Schema({ 3 | title: { 4 | type: String, 5 | required: 'title is required' 6 | }, 7 | description: String, 8 | genre: String, 9 | views: {type: Number, default: 0}, 10 | postedBy: {type: mongoose.Schema.ObjectId, ref: 'User'}, 11 | created: { 12 | type: Date, 13 | default: Date.now 14 | }, 15 | updated: { 16 | type: Date 17 | } 18 | }) 19 | 20 | export default mongoose.model('Media', MediaSchema) 21 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/server/routes/media.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import mediaCtrl from '../controllers/media.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/media/new/:userId') 9 | .post(authCtrl.requireSignin, mediaCtrl.create) 10 | 11 | router.route('/api/media/video/:mediaId') 12 | .get(mediaCtrl.video) 13 | 14 | router.route('/api/media/popular') 15 | .get(mediaCtrl.listPopular) 16 | 17 | router.route('/api/media/related/:mediaId') 18 | .get(mediaCtrl.listRelated) 19 | 20 | router.route('/api/media/by/:userId') 21 | .get(mediaCtrl.listByUser) 22 | 23 | router.route('/api/media/:mediaId') 24 | .get( mediaCtrl.incrementViews, mediaCtrl.read) 25 | .put(authCtrl.requireSignin, mediaCtrl.isPoster, mediaCtrl.update) 26 | .delete(authCtrl.requireSignin, mediaCtrl.isPoster, mediaCtrl.remove) 27 | 28 | router.param('userId', userCtrl.userByID) 29 | router.param('mediaId', mediaCtrl.mediaByID) 30 | 31 | export default router 32 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/:userId') 12 | .get(authCtrl.requireSignin, userCtrl.read) 13 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 14 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 15 | 16 | router.param('userId', userCtrl.userByID) 17 | 18 | export default router 19 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, useFindAndModify: false }) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${config.mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | MERN Mediastream 7 | 8 | 9 | 14 | 15 | 16 |
${markup}
17 | 18 | 19 | 20 | ` 21 | } 22 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'react-hot-loader/patch', 11 | 'webpack-hot-middleware/client?reload=true', 12 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 13 | ], 14 | output: { 15 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 16 | filename: 'bundle.js', 17 | publicPath: '/dist/' 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | use: [ 25 | 'babel-loader' 26 | ] 27 | }, 28 | { 29 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 30 | use: 'file-loader' 31 | } 32 | ] 33 | }, plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ] 37 | } 38 | 39 | module.exports = config 40 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CURRENT_WORKING_DIR = process.cwd() 3 | 4 | const config = { 5 | mode: "production", 6 | entry: [ 7 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 8 | ], 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 11 | filename: 'bundle.js', 12 | publicPath: "/dist/" 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | use: [ 20 | 'babel-loader' 21 | ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter11 and 12/mern-mediastream/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const nodeExternals = require('webpack-node-externals') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "server", 7 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 8 | target: "node", 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 11 | filename: "server.generated.js", 12 | publicPath: '/dist/', 13 | libraryTarget: "commonjs2" 14 | }, 15 | externals: [nodeExternals()], 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | use: [ 'babel-loader' ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter13/MERNVR/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } 4 | -------------------------------------------------------------------------------- /Chapter13/MERNVR/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/.* 3 | 4 | [include] 5 | 6 | [libs] 7 | 8 | [options] 9 | emoji=true 10 | module.system=haste 11 | -------------------------------------------------------------------------------- /Chapter13/MERNVR/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | *.log 4 | *.js.meta 5 | node_modules/ 6 | build/ 7 | -------------------------------------------------------------------------------- /Chapter13/MERNVR/.watchmanconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter13/MERNVR/.watchmanconfig -------------------------------------------------------------------------------- /Chapter13/MERNVR/README.md: -------------------------------------------------------------------------------- 1 | # MERNVR (React 360) 2 | 3 | A web-based VR game developed using React 360. 4 | 5 | ![MERN VR Game](https://s3.amazonaws.com/mernbook/git+/mernvr360.gif "MERN VR Game") 6 | 7 | #### What you need to run this code 8 | 1. Node (13.7.0) 9 | 10 | #### How to run this code 11 | 1. Clone this repository 12 | 2. Open command line in the cloned folder, 13 | - To install dependencies, run ``` yarn ``` 14 | - To run the application for development, run ``` yarn start ``` 15 | 3. Open [localhost:8081/index.html](http://localhost:8081/index.html) in the browser 16 | 17 | ### Integration with a MERN stack web application 18 | Check out [MERN VR Game](https://github.com/shamahoque/mern-vrgame) - A dynamic version of this VR game where game details are retrieved from the server. 19 | 20 | ---- -------------------------------------------------------------------------------- /Chapter13/MERNVR/__tests__/index-test.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import 'react-360'; 3 | import React from 'react'; 4 | import Index from '../index.js'; 5 | 6 | // Note: test renderer must be required after react-native. 7 | import renderer from 'react-test-renderer'; 8 | 9 | it('renders correctly', () => { 10 | const tree = renderer.create( 11 | 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /Chapter13/MERNVR/client.js: -------------------------------------------------------------------------------- 1 | // This file contains the boilerplate to execute your React app. 2 | // If you want to modify your application's content, start in "index.js" 3 | 4 | import {ReactInstance} from 'react-360-web'; 5 | 6 | function init(bundle, parent, options = {}) { 7 | const r360 = new ReactInstance(bundle, parent, { 8 | // Add custom options here 9 | fullScreen: true, 10 | ...options, 11 | }); 12 | 13 | // Render your app content to the default cylinder surface 14 | r360.renderToSurface( 15 | r360.createRoot('MERNVR', { /* initial props */ }), 16 | r360.getDefaultLocation() 17 | ); 18 | 19 | // Load the initial environment 20 | r360.compositor.setBackground(r360.getAssetURL('360_world_black.jpg')) 21 | } 22 | 23 | window.React360 = {init}; 24 | -------------------------------------------------------------------------------- /Chapter13/MERNVR/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | MERNVR 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Chapter13/MERNVR/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MERNVR", 3 | "version": "2.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-360/scripts/packager.js", 7 | "bundle": "node node_modules/react-360/scripts/bundle.js", 8 | "open": "node -e \"require('xopen')('http://localhost:8081/index.html')\"", 9 | "devtools": "react-devtools", 10 | "test": "jest" 11 | }, 12 | "dependencies": { 13 | "react": "16.3.2", 14 | "react-native": "~0.55.4", 15 | "three": "^0.87.0", 16 | "react-360": "~1.1.0", 17 | "react-360-web": "~1.1.0" 18 | }, 19 | "devDependencies": { 20 | "babel-jest": "^19.0.0", 21 | "babel-preset-react-native": "^1.9.1", 22 | "jest": "^19.0.2", 23 | "react-devtools": "^2.5.2", 24 | "react-test-renderer": "16.0.0", 25 | "xopen": "1.0.0" 26 | }, 27 | "jest": { 28 | "preset": "react-360" 29 | } 30 | } -------------------------------------------------------------------------------- /Chapter13/MERNVR/rn-cli.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var blacklist = require('metro/src/blacklist'); 5 | 6 | var config = { 7 | getProjectRoots() { 8 | return getRoots(); 9 | }, 10 | 11 | getBlacklistRE() { 12 | return blacklist([ 13 | ]); 14 | }, 15 | 16 | getAssetExts() { 17 | return ['obj', 'mtl']; 18 | }, 19 | 20 | getPlatforms() { 21 | return ['vr']; 22 | }, 23 | 24 | getProvidesModuleNodeModules() { 25 | return ['react-native', 'react-360']; 26 | }, 27 | }; 28 | 29 | function getRoots() { 30 | var root = process.env.REACT_NATIVE_APP_ROOT; 31 | if (root) { 32 | return [path.resolve(root)]; 33 | } 34 | return [path.resolve(__dirname)]; 35 | } 36 | 37 | module.exports = config; -------------------------------------------------------------------------------- /Chapter13/MERNVR/static_assets/360_world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter13/MERNVR/static_assets/360_world.jpg -------------------------------------------------------------------------------- /Chapter13/MERNVR/static_assets/360_world_black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter13/MERNVR/static_assets/360_world_black.jpg -------------------------------------------------------------------------------- /Chapter13/MERNVR/static_assets/clog-up.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter13/MERNVR/static_assets/clog-up.mp3 -------------------------------------------------------------------------------- /Chapter13/MERNVR/static_assets/collect.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter13/MERNVR/static_assets/collect.mp3 -------------------------------------------------------------------------------- /Chapter13/MERNVR/static_assets/happy-bot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter13/MERNVR/static_assets/happy-bot.mp3 -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", 4 | { 5 | "targets": { 6 | "node": "current" 7 | } 8 | } 9 | ], 10 | "@babel/preset-react" 11 | ], 12 | "plugins": [ 13 | "react-hot-loader/babel" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/bundle.js 3 | /dist/server.generated.js 4 | /data/ 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shama Hoque 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/README.md: -------------------------------------------------------------------------------- 1 | # MERN VR Game 2 | 3 | A web-based game application with VR features - developed using React, React 360, Node, Express and MongoDB. 4 | 5 | ![MERN VR Game](https://s3.amazonaws.com/mernbook/git+/vrgame.png "MERN VR Game") 6 | 7 | ### [Live Demo](http://vrgame2.mernbook.com/ "MERN VR Game") 8 | 9 | #### What you need to run this code 10 | 1. Node (13.12.0) 11 | 2. NPM (6.14.4) or Yarn (1.22.4) 12 | 3. MongoDB (4.2.0) 13 | 14 | #### How to run this code 15 | 1. Make sure MongoDB is running on your system 16 | 2. Clone this repository 17 | 3. Open command line in the cloned folder, 18 | - To install dependencies, run ``` npm install ``` or ``` yarn ``` 19 | - To run the application for development, run ``` npm run development ``` or ``` yarn development ``` 20 | 4. Open [localhost:3000](http://localhost:3000/) in the browser 21 | 22 | ### React 360 23 | The VR game is developed using React 360 and integrated into this application. 24 | 25 | The React 360 code for the game is [available here](https://github.com/shamahoque/MERNVR). 26 | 27 | ---- -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainRouter from './MainRouter' 3 | import {BrowserRouter} from 'react-router-dom' 4 | import { ThemeProvider } from '@material-ui/styles' 5 | import theme from './theme' 6 | import { hot } from 'react-hot-loader' 7 | 8 | const App = () => { 9 | React.useEffect(() => { 10 | const jssStyles = document.querySelector('#jss-server-side') 11 | if (jssStyles) { 12 | jssStyles.parentNode.removeChild(jssStyles) 13 | } 14 | }, []) 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | )} 22 | 23 | export default hot(module)(App) 24 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/MainRouter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Route, Switch} from 'react-router-dom' 3 | import Home from './core/Home' 4 | import Users from './user/Users' 5 | import Signup from './user/Signup' 6 | import Signin from './auth/Signin' 7 | import EditProfile from './user/EditProfile' 8 | import Profile from './user/Profile' 9 | import PrivateRoute from './auth/PrivateRoute' 10 | import Menu from './core/Menu' 11 | import NewGame from './game/NewGame' 12 | import EditGame from './game/EditGame' 13 | 14 | const MainRouter = () => { 15 | return (
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
) 29 | } 30 | 31 | export default MainRouter -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/assets/images/unicornbike.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter14/mern-vrgame/client/assets/images/unicornbike.jpg -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/assets/images/unicorncoin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter14/mern-vrgame/client/assets/images/unicorncoin.jpg -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/auth/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Route, Redirect } from 'react-router-dom' 3 | import auth from './auth-helper' 4 | 5 | const PrivateRoute = ({ component: Component, ...rest }) => ( 6 | ( 7 | auth.isAuthenticated() ? ( 8 | 9 | ) : ( 10 | 14 | ) 15 | )}/> 16 | ) 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/auth/api-auth.js: -------------------------------------------------------------------------------- 1 | const signin = async (user) => { 2 | try { 3 | let response = await fetch('/auth/signin/', { 4 | method: 'POST', 5 | headers: { 6 | 'Accept': 'application/json', 7 | 'Content-Type': 'application/json' 8 | }, 9 | credentials: 'include', 10 | body: JSON.stringify(user) 11 | }) 12 | return await response.json() 13 | } catch(err) { 14 | console.log(err) 15 | } 16 | } 17 | 18 | const signout = async () => { 19 | try { 20 | let response = await fetch('/auth/signout/', { method: 'GET' }) 21 | return await response.json() 22 | } catch(err) { 23 | console.log(err) 24 | } 25 | } 26 | 27 | export { 28 | signin, 29 | signout 30 | } -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/auth/auth-helper.js: -------------------------------------------------------------------------------- 1 | import { signout } from './api-auth.js' 2 | 3 | const auth = { 4 | isAuthenticated() { 5 | if (typeof window == "undefined") 6 | return false 7 | 8 | if (sessionStorage.getItem('jwt')) 9 | return JSON.parse(sessionStorage.getItem('jwt')) 10 | else 11 | return false 12 | }, 13 | authenticate(jwt, cb) { 14 | if (typeof window !== "undefined") 15 | sessionStorage.setItem('jwt', JSON.stringify(jwt)) 16 | cb() 17 | }, 18 | clearJWT(cb) { 19 | if (typeof window !== "undefined") 20 | sessionStorage.removeItem('jwt') 21 | cb() 22 | //optional 23 | signout().then((data) => { 24 | document.cookie = "t=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" 25 | }) 26 | } 27 | } 28 | 29 | export default auth 30 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/core/Home.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react' 2 | import { makeStyles } from '@material-ui/core/styles' 3 | import {list} from '../game/api-game.js' 4 | import GameDetail from '../game/GameDetail' 5 | 6 | const useStyles = makeStyles(theme => ({ 7 | root: { 8 | flexGrow: 1, 9 | margin: '10px 24px', 10 | } 11 | })) 12 | 13 | export default function Home(){ 14 | const classes = useStyles() 15 | const [games, setGames] = useState([]) 16 | useEffect(() => { 17 | const abortController = new AbortController() 18 | const signal = abortController.signal 19 | 20 | list(signal).then((data) => { 21 | if (data.error) { 22 | console.log(data.error) 23 | } else { 24 | setGames(data) 25 | } 26 | }) 27 | return function cleanup(){ 28 | abortController.abort() 29 | } 30 | }, []) 31 | 32 | const updateGames = (game) => { 33 | const updatedGames = [...games] 34 | const index = updatedGames.indexOf(game) 35 | updatedGames.splice(index, 1) 36 | setGames(updatedGames) 37 | } 38 | return ( 39 |
40 | {games.map((game, i) => { 41 | return 42 | })} 43 |
44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/game/EditGame.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react' 2 | import auth from './../auth/auth-helper' 3 | import {update} from './api-game.js' 4 | import {Redirect} from 'react-router-dom' 5 | import GameForm from './GameForm' 6 | 7 | export default function EditGame({ match }) { 8 | const [redirect, setRedirect] = useState(false) 9 | const [error, setError]= useState('') 10 | 11 | const clickSubmit = game => event => { 12 | const jwt = auth.isAuthenticated() 13 | update({ 14 | gameId: match.params.gameId 15 | }, { 16 | t: jwt.token 17 | }, game).then((data) => { 18 | if (data.error) { 19 | setError(data.error) 20 | } else { 21 | setError('') 22 | setRedirect(true) 23 | } 24 | }) 25 | } 26 | 27 | if (redirect) { 28 | return () 29 | } 30 | return ( 31 | 32 | ) 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/game/NewGame.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react' 2 | import auth from './../auth/auth-helper' 3 | import PropTypes from 'prop-types' 4 | import {create} from './api-game.js' 5 | import {Redirect} from 'react-router-dom' 6 | import GameForm from './GameForm' 7 | 8 | export default function NewGame(){ 9 | const [redirect, setRedirect] = useState(false) 10 | const [error, setError]= useState('') 11 | const clickSubmit = game => event => { 12 | const jwt = auth.isAuthenticated() 13 | create({ 14 | userId: jwt.user._id 15 | }, { 16 | t: jwt.token 17 | }, game).then((data) => { 18 | if (data.error) { 19 | setError(data.error) 20 | } else { 21 | setError('') 22 | setRedirect(true) 23 | } 24 | }) 25 | } 26 | if (redirect) { 27 | return () 28 | } 29 | return ( 30 | 31 | ) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { hydrate } from 'react-dom' 3 | import App from './App' 4 | 5 | hydrate(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/client/theme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from '@material-ui/core/styles' 2 | 3 | const theme = createMuiTheme({ 4 | typography: { 5 | useNextVariants: true, 6 | }, 7 | palette: { 8 | primary: { 9 | light: '#484848', 10 | main: '#212121', 11 | dark: '#000000', 12 | contrastText: '#fff', 13 | }, 14 | secondary: { 15 | light: '#ffff6e', 16 | main: '#cddc39', 17 | dark: '#99aa00', 18 | contrastText: '#000', 19 | }, 20 | openTitle: '#484848', 21 | protectedTitle: '#7da453', 22 | type: 'light' 23 | } 24 | }) 25 | 26 | export default theme -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 3000, 4 | jwtSecret: process.env.JWT_SECRET || "YOUR_secret_key", 5 | mongoUri: process.env.MONGODB_URI || 6 | process.env.MONGO_HOST || 7 | 'mongodb://' + (process.env.IP || 'localhost') + ':' + 8 | (process.env.MONGO_PORT || '27017') + 9 | '/mernproject' 10 | } 11 | 12 | export default config 13 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/dist/static_assets/360_world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter14/mern-vrgame/dist/static_assets/360_world.jpg -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/dist/static_assets/360_world_black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter14/mern-vrgame/dist/static_assets/360_world_black.jpg -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/dist/static_assets/clog-up.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter14/mern-vrgame/dist/static_assets/clog-up.mp3 -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/dist/static_assets/collect.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter14/mern-vrgame/dist/static_assets/collect.mp3 -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/dist/static_assets/happy-bot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Full-Stack-React-Projects-Second-Edition/c4007202bd767682b9122cb196b80cd4952658f2/Chapter14/mern-vrgame/dist/static_assets/happy-bot.mp3 -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "watch": [ 4 | "./server" 5 | ], 6 | "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js" 7 | } 8 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/server/devBundle.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import webpack from 'webpack' 3 | import webpackMiddleware from 'webpack-dev-middleware' 4 | import webpackHotMiddleware from 'webpack-hot-middleware' 5 | import webpackConfig from './../webpack.config.client.js' 6 | 7 | const compile = (app) => { 8 | if(config.env === "development"){ 9 | const compiler = webpack(webpackConfig) 10 | const middleware = webpackMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath 12 | }) 13 | app.use(middleware) 14 | app.use(webpackHotMiddleware(compiler)) 15 | } 16 | } 17 | 18 | export default { 19 | compile 20 | } 21 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/server/helpers/dbErrorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | const getUniqueErrorMessage = (err) => { 7 | let output 8 | try { 9 | let fieldName = err.message.substring(err.message.lastIndexOf('.$') + 2, err.message.lastIndexOf('_1')) 10 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists' 11 | } catch (ex) { 12 | output = 'Unique field already exists' 13 | } 14 | 15 | return output 16 | } 17 | 18 | /** 19 | * Get the error message from error object 20 | */ 21 | const getErrorMessage = (err) => { 22 | let message = '' 23 | 24 | if (err.code) { 25 | switch (err.code) { 26 | case 11000: 27 | case 11001: 28 | message = getUniqueErrorMessage(err) 29 | break 30 | default: 31 | message = 'Something went wrong' 32 | } 33 | } else { 34 | for (let errName in err.errors) { 35 | if (err.errors[errName].message) message = err.errors[errName].message 36 | } 37 | } 38 | 39 | return message 40 | } 41 | 42 | export default {getErrorMessage} 43 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/server/routes/auth.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import authCtrl from '../controllers/auth.controller' 3 | 4 | const router = express.Router() 5 | 6 | router.route('/auth/signin') 7 | .post(authCtrl.signin) 8 | router.route('/auth/signout') 9 | .get(authCtrl.signout) 10 | 11 | export default router 12 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/server/routes/game.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | import gameCtrl from '../controllers/game.controller' 5 | 6 | const router = express.Router() 7 | 8 | router.route('/api/games') 9 | .get(gameCtrl.list) 10 | 11 | router.route('/api/games/by/:userId') 12 | .post(authCtrl.requireSignin, authCtrl.hasAuthorization, gameCtrl.create) 13 | .get(gameCtrl.listByMaker) 14 | 15 | router.route('/api/game/:gameId') 16 | .get(gameCtrl.read) 17 | 18 | router.route('/api/games/:gameId') 19 | .put(authCtrl.requireSignin, gameCtrl.isMaker, gameCtrl.update) 20 | .delete(authCtrl.requireSignin, gameCtrl.isMaker, gameCtrl.remove) 21 | 22 | router.route('/game/play') 23 | .get(gameCtrl.playGame) 24 | 25 | router.param('gameId', gameCtrl.gameByID) 26 | router.param('userId', userCtrl.userByID) 27 | 28 | export default router 29 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/server/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import userCtrl from '../controllers/user.controller' 3 | import authCtrl from '../controllers/auth.controller' 4 | 5 | const router = express.Router() 6 | 7 | router.route('/api/users') 8 | .get(userCtrl.list) 9 | .post(userCtrl.create) 10 | 11 | router.route('/api/users/:userId') 12 | .get(authCtrl.requireSignin, userCtrl.read) 13 | .put(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.update) 14 | .delete(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.remove) 15 | 16 | router.param('userId', userCtrl.userByID) 17 | 18 | export default router 19 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/server/server.js: -------------------------------------------------------------------------------- 1 | import config from './../config/config' 2 | import app from './express' 3 | import mongoose from 'mongoose' 4 | 5 | // Connection URL 6 | mongoose.Promise = global.Promise 7 | mongoose.connect(config.mongoUri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }) 8 | mongoose.connection.on('error', () => { 9 | throw new Error(`unable to connect to database: ${config.mongoUri}`) 10 | }) 11 | 12 | app.listen(config.port, (err) => { 13 | if (err) { 14 | console.log(err) 15 | } 16 | console.info('Server started on port %s.', config.port) 17 | }) 18 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/server/vr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | MERNVR 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/template.js: -------------------------------------------------------------------------------- 1 | export default ({markup, css}) => { 2 | return ` 3 | 4 | 5 | 6 | MERN VR Game 7 | 8 | 9 | 14 | 15 | 16 |
${markup}
17 | 18 | 19 | 20 | ` 21 | } 22 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/webpack.config.client.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "browser", 7 | mode: "development", 8 | devtool: 'eval-source-map', 9 | entry: [ 10 | 'webpack-hot-middleware/client?reload=true', 11 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 12 | ], 13 | output: { 14 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 15 | filename: 'bundle.js', 16 | publicPath: '/dist/' 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.jsx?$/, 22 | exclude: /node_modules/, 23 | use: [ 24 | 'babel-loader' 25 | ] 26 | }, 27 | { 28 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 29 | use: 'file-loader' 30 | } 31 | ] 32 | }, 33 | plugins: [ 34 | new webpack.HotModuleReplacementPlugin(), 35 | new webpack.NoEmitOnErrorsPlugin() 36 | ], 37 | resolve: { 38 | alias: { 39 | 'react-dom': '@hot-loader/react-dom' 40 | } 41 | } 42 | } 43 | 44 | module.exports = config 45 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/webpack.config.client.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CURRENT_WORKING_DIR = process.cwd() 3 | 4 | const config = { 5 | mode: "production", 6 | entry: [ 7 | path.join(CURRENT_WORKING_DIR, 'client/main.js') 8 | ], 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist'), 11 | filename: 'bundle.js', 12 | publicPath: "/dist/" 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.jsx?$/, 18 | exclude: /node_modules/, 19 | use: [ 20 | 'babel-loader' 21 | ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /Chapter14/mern-vrgame/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const nodeExternals = require('webpack-node-externals') 3 | const CURRENT_WORKING_DIR = process.cwd() 4 | 5 | const config = { 6 | name: "server", 7 | entry: [ path.join(CURRENT_WORKING_DIR , './server/server.js') ], 8 | target: "node", 9 | output: { 10 | path: path.join(CURRENT_WORKING_DIR , '/dist/'), 11 | filename: "server.generated.js", 12 | publicPath: '/dist/', 13 | libraryTarget: "commonjs2" 14 | }, 15 | externals: [nodeExternals()], 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.js$/, 20 | exclude: /node_modules/, 21 | use: [ 'babel-loader' ] 22 | }, 23 | { 24 | test: /\.(ttf|eot|svg|gif|jpg|png)(\?[\s\S]+)?$/, 25 | use: 'file-loader' 26 | } 27 | ] 28 | } 29 | } 30 | 31 | module.exports = config 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | --------------------------------------------------------------------------------