├── .gitignore ├── README.md ├── client ├── .babelrc ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── dist │ │ ├── bundle.js │ │ ├── bundle.js.map │ │ ├── styles.css │ │ └── styles.css.map │ ├── img │ │ ├── Dell-6.png │ │ ├── apple_macbook-2.jpg │ │ ├── dell-1.png │ │ ├── dell-2.png │ │ ├── dell-3.jpg │ │ ├── dell-4.png │ │ ├── dell-5.png │ │ ├── loader.gif │ │ ├── logo (copy).png │ │ ├── logo.png │ │ ├── macbook-pro-1.jpg │ │ ├── macbook-pro-3.jpg │ │ ├── macbook-pro-4.jpg │ │ └── success.gif │ └── index.html ├── src │ ├── actions │ │ └── index.ts │ ├── components │ │ ├── Account │ │ │ ├── Account.tsx │ │ │ └── index.ts │ │ ├── AccountModal │ │ │ ├── AccountModal.tsx │ │ │ └── index.ts │ │ ├── Cart │ │ │ ├── Cart.tsx │ │ │ └── index.ts │ │ ├── CheckoutModal │ │ │ ├── CheckoutModal.tsx │ │ │ └── index.ts │ │ ├── FiltersList │ │ │ ├── FiltersList.tsx │ │ │ └── index.ts │ │ ├── Footer │ │ │ ├── Footer.tsx │ │ │ └── index.ts │ │ ├── Header │ │ │ ├── Header.tsx │ │ │ └── index.ts │ │ ├── Homepage │ │ │ ├── Homepage.tsx │ │ │ └── index.ts │ │ ├── LoginModal │ │ │ ├── LoginModal.tsx │ │ │ └── index.ts │ │ ├── NotFound │ │ │ ├── NotFound.tsx │ │ │ └── index.ts │ │ ├── OrderSuccessModal │ │ │ ├── OrderSuccessModal.tsx │ │ │ └── index.ts │ │ ├── Product │ │ │ ├── Product.tsx │ │ │ └── index.ts │ │ ├── ProductDetails │ │ │ ├── ProductDetails.tsx │ │ │ └── index.ts │ │ ├── Products │ │ │ ├── Products.tsx │ │ │ └── index.ts │ │ ├── RegisterModal │ │ │ ├── RegisterModal.tsx │ │ │ └── index.ts │ │ └── ShoppingCart │ │ │ ├── ShoppingCart.tsx │ │ │ └── index.ts │ ├── constants │ │ └── index.ts │ ├── index.tsx │ ├── reducers │ │ ├── cartReducer.ts │ │ ├── catalogReducer.ts │ │ ├── filtersReducer.ts │ │ ├── index.ts │ │ ├── sortReducer.ts │ │ └── userReducer.ts │ ├── sagas │ │ └── index.ts │ ├── selectors │ │ ├── cart.ts │ │ ├── catalog.ts │ │ └── user.ts │ ├── store │ │ └── configureStore.ts │ ├── styles │ │ ├── Account.css │ │ ├── AccountModal.css │ │ ├── Cart.css │ │ ├── CheckoutModal.css │ │ ├── FiltersList.css │ │ ├── Footer.css │ │ ├── Header.css │ │ ├── Homepage.css │ │ ├── LoginModal.css │ │ ├── NotFound.css │ │ ├── OrderSuccessModal.css │ │ ├── Product.css │ │ ├── ProductDetails.css │ │ ├── Products.css │ │ ├── RegisterModal.css │ │ └── ShoppingCart.css │ └── typings │ │ ├── action.ts │ │ ├── filters.ts │ │ ├── modal.ts │ │ └── state │ │ ├── cart.ts │ │ ├── cartProduct.ts │ │ ├── catalog.ts │ │ ├── catalogProduct.ts │ │ ├── filters.ts │ │ ├── index.ts │ │ ├── loggedUser.ts │ │ ├── order.ts │ │ ├── sortBy.ts │ │ ├── state.ts │ │ └── user.ts ├── tsconfig.json └── webpack.config.js ├── config ├── dev.js ├── privates.js └── prod.js ├── middleware └── requireLogin.js ├── models ├── Cart.js ├── Product.js └── User.js ├── package-lock.json ├── package.json ├── routes ├── authRoutes.js ├── cartRoutes.js ├── catalogRoutes.js ├── orderRoutes.js └── userRoutes.js ├── seeds └── products.js ├── server.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | */node_modules 5 | 6 | 7 | # testing 8 | /coverage 9 | 10 | # production 11 | build 12 | 13 | # I AM ALWAYS VERY STRICT ABOUT NOT PUSHING .env file RELATED TO REMOTE REPO, BUT FOR THIS EXERCISE, BECAUSE I MAY NOT DEPLOY THE LIVE APP TO AWS/GCP, I HAVE TO INCLUDE THESE .env files SO OTHERS CAN RUN IT IN LOCAL MACHINES 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | .env 20 | .env.override 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # Ignore docs files 27 | _gh_pages 28 | .ruby-version 29 | 30 | # Numerous always-ignore extensions 31 | *.diff 32 | *.err 33 | *.orig 34 | *.log 35 | *.rej 36 | *.swo 37 | *.swp 38 | *.zip 39 | *.vi 40 | *~ 41 | *.~lock* 42 | .~lock* 43 | 44 | # OS or Editor folders 45 | .DS_Store 46 | ._* 47 | Thumbs.db 48 | .cache 49 | .project 50 | .settings 51 | .tmproj 52 | *.esproj 53 | nbproject 54 | *.sublime-project 55 | *.sublime-workspace 56 | .idea 57 | mockData.js 58 | 59 | # Ignore specific files 60 | src/firebase.js 61 | .prettierignore 62 | mockData.js 63 | 64 | # Komodo 65 | *.komodoproject 66 | .komodotools 67 | 68 | # grunt-html-validation 69 | validation-status.json 70 | validation-report.json 71 | 72 | # Folders to ignore 73 | node_modules 74 | Project-Note-PAUL 75 | 76 | # Ignore all logfiles and tempfiles. 77 | !/log/.keep 78 | /tmp 79 | /.gems 80 | 81 | 82 | #ignore file name ending in "-bkp.js" in its name. So I will have to put "-bkp.js" at all files that is just for my development-time back up . 83 | **/*-bkp.js 84 | 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### A shopping cart built with React, Redux (with Typescript), Material-UI, Node, MongoDB, Express 2 | 3 | #### To install locally 4 | 5 | 1. Clone repo on your local machine: 6 | 7 | ```js 8 | git clone 9 | ``` 10 | 11 | Install server-side dependencies: 12 | 13 | ```js 14 | cd fullstack-shopping-cart 15 | npm install 16 | ``` 17 | 18 | Install client-side dependencies: 19 | 20 | ```js 21 | cd client 22 | npm install 23 | ``` 24 | 25 | In the root of the project create a .env file and replace the MONGODB_URI, and SESSION_SECRET env variable with your own 26 | 27 | Start MongoDB in your local machine 28 | 29 | ```js 30 | sudo service mongod start 31 | ``` 32 | 33 | In server.js after `mongoose.connect` uncomment (if they are commented out) these two lines to seed products in your database: 34 | 35 | ```js 36 | const seedProducts = require('./seeds/products.js'); 37 | seedProducts(); 38 | ``` 39 | 40 | cd into ./client and build the client: 41 | 42 | ```js 43 | npm run build 44 | ``` 45 | 46 | Now navigate back to server root directory and start the server 47 | 48 | ```js 49 | cd .. 50 | npm run start 51 | ``` 52 | 53 | Now navigate to `localhost:5000` and the app is running here -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-class-properties" 4 | ], 5 | "presets": [ 6 | "@babel/env", 7 | "@babel/react" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fullstack-shopping-cart", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "ISC", 8 | "scripts": { 9 | "start:dev": "webpack-dev-server", 10 | "build": "webpack -p" 11 | }, 12 | "dependencies": { 13 | "@material-ui/core": "^3.9.2", 14 | "@material-ui/icons": "^3.0.2", 15 | "axios": "^0.19.2", 16 | "connected-react-router": "^6.2.2", 17 | "history": "^4.7.2", 18 | "material-ui": "^0.20.2", 19 | "moment": "^2.20.1", 20 | "numeral": "^2.0.6", 21 | "react": "^16.7.0", 22 | "react-dom": "^16.7.0", 23 | "react-modal": "^3.1.12", 24 | "react-redux": "^6.0.0", 25 | "react-router-dom": "^4.2.2", 26 | "recompose": "^0.30.0", 27 | "redux": "^3.7.2", 28 | "redux-actions": "^2.6.4", 29 | "redux-saga": "^1.0.0", 30 | "reselect": "^4.0.0", 31 | "terser": "^3.14.1" 32 | }, 33 | "devDependencies": { 34 | "@babel/cli": "^7.4.4", 35 | "@babel/core": "^7.4.5", 36 | "@babel/preset-env": "^7.4.5", 37 | "@babel/preset-react": "^7.0.0", 38 | "@types/material-ui": "^0.21.5", 39 | "@types/numeral": "0.0.25", 40 | "@types/react": "^16.8.1", 41 | "@types/react-dom": "^16.0.11", 42 | "@types/react-modal": "^3.8.0", 43 | "@types/react-redux": "^7.0.1", 44 | "@types/react-router-dom": "^4.3.1", 45 | "@types/recompose": "^0.30.3", 46 | "@types/redux-actions": "^2.3.1", 47 | "autoprefixer": "^9.1.5", 48 | "babel-loader": "^8.0.5", 49 | "babel-plugin-transform-class-properties": "^6.24.1", 50 | "babel-regenerator-runtime": "^6.5.0", 51 | "css-loader": "^0.28.9", 52 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 53 | "file-loader": "^1.1.6", 54 | "image-webpack-loader": "^4.6.0", 55 | "postcss-loader": "^3.0.0", 56 | "style-loader": "^0.20.1", 57 | "ts-loader": "^5.3.3", 58 | "tsconfig-paths-webpack-plugin": "^3.2.0", 59 | "typescript": "^3.3.1", 60 | "webpack": "^4.29.0", 61 | "webpack-cli": "^3.2.1", 62 | "webpack-dev-server": "^3.5.1" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer'); 2 | 3 | module.exports = { 4 | plugins: [autoprefixer] 5 | }; -------------------------------------------------------------------------------- /client/public/dist/styles.css: -------------------------------------------------------------------------------- 1 | .subheader{font-size:16px!important;font-weight:700!important}.listItem{border-top:1px solid #dcdcdc}.checkbox{margin-left:20px}.login-modal{width:90%;max-width:385px;height:360px;margin:auto;margin-top:100px;background:#fff;color:#2e3a4e;border:1px solid #fff;border-radius:5px;outline:none}.login-modal .form{width:90%;max-width:360px;margin:auto;margin-top:10px;text-align:center;font-family:Roboto}.login-modal .form h1{color:#ff8c00}.login-modal .form .btn{margin:20px auto}.login-modal .form p{font-style:italic}.login-modal .form a{color:#ff8c00;cursor:pointer}.account-modal{width:90%;max-width:385px;height:375px;margin:auto;margin-top:100px;background:#fff;border:1px solid #fff;border-radius:5px;outline:none}.account-modal .form{width:70%;margin:auto;margin-top:10px;text-align:center;font-family:Roboto}.account-modal .form h1{color:#ff8c00}.account-modal .form .btn{margin:20px auto}.account-container{min-height:100vh;margin:0 6px;margin-top:-72px;border:1px solid #ffffff00}.account-container .top{display:flex;justify-content:space-between}.account-container .top h1{margin-top:100px;margin-bottom:30px}.loader{margin:20px auto;color:#ff8c00;text-align:center}.account{display:flex;flex-direction:row}.account-info{width:50%;margin-bottom:30px}.account-info .btn{margin-top:20px}.account-history{width:50%;margin-bottom:50px}.account-history h1{margin-top:100px;text-align:center}.account-history .orders table{width:100%;margin:10px auto;border-spacing:0}.orders th{padding:5px;background-color:#ff8c00;color:#fff;text-align:left}.orders td{padding:12px 5px;text-align:left;border-bottom:1px solid #ff8c00}.orders td:nth-child(4){text-align:center}@media (max-width:1000px){.account-container h1{font-size:24px}.account{flex-direction:column}.account-history,.account-info{width:100%}}.header>div{z-index:0!important}.title h1{font-family:Bungee Inline;font-style:italic}.menu{margin-top:5px}.icon-menu{display:none}@media (max-width:750px){.menu{display:none}.icon-menu{display:block}}.ReactModalPortal .ReactModal__Overlay--after-open{background-color:#00000073!important}.checkout-modal{width:90%;max-width:500px;height:auto;margin:auto;margin-top:100px;background:#fff;border:1px solid #fff;border-radius:5px;outline:none}.checkout-modal .order{width:90%;max-width:450px;margin:auto;margin-top:10px;text-align:center;font-family:Roboto}.checkout-modal h1{margin-bottom:60px;color:#ff8c00}.checkout-modal table{width:100%;margin-top:40px;border-spacing:0}.checkout-modal th{padding:5px;background-color:#ff8c00;color:#fff;text-align:left}.checkout-modal td{padding:12px 5px;text-align:left}.checkout-modal .total{text-align:left}.checkout-modal .total span{font-size:26px;font-weight:700;color:#151DFB}.checkout-modal .btns{margin-top:40px;margin-bottom:20px}.checkout-modal .btns .btn{margin:0 20px}@media (max-height:700px){.checkout-modal{height:440px;min-height:auto;overflow:scroll}}.order-success-modal{width:90%;max-width:385px;height:410px;margin:auto;margin-top:100px;background:#fff;border:1px solid #fff;border-radius:5px;outline:none}.order-success-modal .success{width:70%;margin:auto;margin-top:10px;text-align:center;font-family:Roboto}.order-success-modal h1{color:#ff8c00}.order-success-modal img{max-height:100px;margin:30px 0}.order-success-modal .btn{margin-top:20px}.cart-container{min-height:100vh;margin:0 6px;margin-top:-72px;border:1px solid #ffffff00}.cart-container h1{margin-top:100px;margin-bottom:40px}.cart{display:flex;flex-direction:row;justify-content:space-between}.cart-info{display:flex;flex-direction:column;width:20%;height:100%;min-height:auto;background:#f5f5f5}.cart-info p{padding:0 10px}.cart-info .total{font-size:22px;color:#151DFB}.cart-info .btns{text-align:center}.cart-info .btn{width:150px;margin:10px auto}.cart-items{width:78%}.cart-items img{max-width:50px}.cart-items table{width:100%;border-spacing:0}.cart-items th{padding:5px;background-color:#ff8c00;color:#fff;text-align:left}.cart-items th:first-child{width:70px}.cart-items th:last-child{width:15px}.cart-items td{padding:12px 5px;text-align:left;border-bottom:1px solid #ff8c00}.cart-items td:nth-child(4){text-align:center}.cart-items a{color:inherit;text-decoration:none}.cart-items button{padding:1px 5px;background:none;color:#f44336;font-weight:700;border:none;border-radius:50%;cursor:pointer}.cart-items button:hover{background:#f44336;color:#fff}.cart-items h1{text-align:center;margin-top:100px}@media (max-width:1000px){.cart-container h1{margin-top:100px;margin-bottom:30px;font-size:24px}.cart{flex-direction:column}.cart-info{flex-direction:row;width:100%}.cart-info .btns,.cart-info .info{width:50%}.cart-info .btn{margin:10px}.cart-items{width:100%;margin:40px auto 60px}}.register-modal{width:90%;max-width:385px;height:570px;margin:auto;margin-top:100px;background:#fff;border:1px solid #fff;border-radius:5px;outline:none}.register-modal .form{width:90%;max-width:360px;margin:auto;margin-top:10px;text-align:center;font-family:Roboto}.register-modal .form h1{color:#ff8c00}.register-modal .form .btn{margin:20px auto}.register-modal .form p{font-style:italic}.register-modal .form a{color:#ff8c00;cursor:pointer}@media (max-height:700px){.register-modal{height:500px;overflow:scroll}}.product{width:100%;height:auto;margin:10px 0;box-shadow:0 0 7px #d6d6d6;transition:.3s}.product:hover{box-shadow:0 0 7px #b3b3b3}.content img{max-width:170px;max-height:170px;padding:10px}.content{display:flex;flex-direction:row;width:100%}.content-left{width:80%}.content-left h3{margin:10px 0 25px}.content-left div{margin:1.5px 0}.content-right{width:20%}.content-right p{margin:50px 0 0}.content-right h2{margin:0 0 40px;font-size:30px;color:#151DFB}.content-info{width:100%!important;text-align:left!important}@media (max-width:860px){.content{flex-direction:column;height:auto;max-height:auto}.content img{margin:auto}.content-left,.content-right{width:100%;margin:10px 0;text-align:center}.content-left div,.content-left h3{margin:0}.content-info{display:none}}.loader{color:#ff8c00}.loader,.no-products{margin-top:150px;text-align:center}.products{width:78%;margin-top:90px;margin-bottom:10px}.products-handle{display:flex;flex-direction:row;border-bottom:1px solid #81d4fa}.products-handle .products-found{width:20%;margin-top:16px}.products-handle .filters{width:20%;text-align:center}.products-handle .filters .btn{width:150px;margin:5px}.products-handle .set-filters{display:none}.products-handle .products-sort{display:flex;justify-content:flex-end;width:60%}.products-handle .products-sort span{margin-top:15px;margin-right:5px}.products-handle .products-sort .sort-field{margin-top:0}@media (max-width:1115px){.products{width:100%}.products-handle{padding-bottom:10px}.products-handle .products-found{margin-top:42px}.products-handle .set-filters{display:block}.products-handle .products-sort span{margin-top:41px}.products-handle .products-sort .sort-field{margin-top:26px}}@media (max-width:850px){.products-handle{flex-direction:column}.products-handle .filters,.products-handle .products-found,.products-handle .products-sort{width:100%;margin:5px 0}.products-handle .products-sort{justify-content:center}.products-handle .products-sort span{margin-top:13px}.products-handle .products-sort .sort-field{margin-top:0}}.homepage-container{display:flex;justify-content:space-between;min-height:100vh;margin:0 6px;margin-top:-72px;border:1px solid #ffffff00}.filtersList-desktop{width:20%;height:100%;min-height:auto;margin-top:90px;margin-bottom:40px;background-color:#f5f5f5}@media (max-width:1115px){.filtersList-desktop{display:none}}.product-details-container{min-height:100vh;margin:0 6px;margin-top:-72px;border:1px solid #ffffff00}.product-details-container h1{margin-top:100px;margin-bottom:20px}.product-details{display:flex;flex-direction:row}.product-image{width:40%}.product-image img{max-height:364px;margin:20px 0}.product-info{width:60%;text-align:center}.product-info table{width:100%;margin:20px 0}.product-info th{background-color:#ff8c00;color:#fff}.product-info td,.product-info th{padding:3px;text-align:left;border:1px solid #80deea}.price-text{font-size:16px;font-weight:700}.price-num{font-size:30px;font-weight:700;color:#151DFB}.product-handle{display:flex;flex-direction:row;margin:10px 0 20px}.product-handle .left{width:40%}.product-handle .left .btn{margin:10px auto}.product-handle .right{display:flex;flex-direction:row;justify-content:flex-start;width:60%}.right .price{width:200px;margin:5px 0}.right .quantity{width:130px;text-align:center}.right .quantity input{width:30px;height:28px;margin:10px;font-size:17px}.right .btn{width:160px;margin:10px 0;text-align:center}@media (max-width:1000px){.product-details-container h1{font-size:24px}.product-details{flex-direction:column}.product-image{width:100%;margin:30px auto;text-align:center}.product-image img{max-height:300px;margin:auto}.product-info{width:100%}}@media (max-width:900px){.product-handle{flex-direction:column}.product-handle .left{width:100%;order:2}.product-handle .right{width:100%;order:1}}@media (max-width:650px){.product-handle .right{flex-direction:column}.right .btn,.right .price,.right .quantity{width:100%}.left,.right .price{text-align:center}}.footer{width:100%;height:auto;background:#ff8c00;color:#c3e3e7;text-align:center}.footer p{margin:0;padding:3px}.not-found-container{min-height:100vh;margin-top:-72px;border:1px solid #ffffff00}.not-found{width:50%;min-width:330px;margin:auto;margin-top:200px}.not-found h1{font-size:60px;color:#ff8c00}.not-found h3{font-size:45px}button:focus{outline:0}body{margin:0}.container{width:100%;max-width:1200px;margin:auto;font-family:Roboto;color:#2e3a4edc} 2 | /*# sourceMappingURL=styles.css.map*/ -------------------------------------------------------------------------------- /client/public/dist/styles.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"styles.css","sourceRoot":""} -------------------------------------------------------------------------------- /client/public/img/Dell-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/Dell-6.png -------------------------------------------------------------------------------- /client/public/img/apple_macbook-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/apple_macbook-2.jpg -------------------------------------------------------------------------------- /client/public/img/dell-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/dell-1.png -------------------------------------------------------------------------------- /client/public/img/dell-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/dell-2.png -------------------------------------------------------------------------------- /client/public/img/dell-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/dell-3.jpg -------------------------------------------------------------------------------- /client/public/img/dell-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/dell-4.png -------------------------------------------------------------------------------- /client/public/img/dell-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/dell-5.png -------------------------------------------------------------------------------- /client/public/img/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/loader.gif -------------------------------------------------------------------------------- /client/public/img/logo (copy).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/logo (copy).png -------------------------------------------------------------------------------- /client/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/logo.png -------------------------------------------------------------------------------- /client/public/img/macbook-pro-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/macbook-pro-1.jpg -------------------------------------------------------------------------------- /client/public/img/macbook-pro-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/macbook-pro-3.jpg -------------------------------------------------------------------------------- /client/public/img/macbook-pro-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/macbook-pro-4.jpg -------------------------------------------------------------------------------- /client/public/img/success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/react-node-mongodb-material-ui-shopping-cart/8062ec8a6c6550ff602a34e3967ed4088d6ae9b0/client/public/img/success.gif -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | LAPTOP SHOP 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /client/src/actions/index.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions'; 2 | import { 3 | INIT_CATALOG, 4 | INIT_CATALOG_SUCCESS, 5 | INIT_CATALOG_FAIL, 6 | GET_USER, 7 | GET_USER_SUCCESS, 8 | GET_USER_FAIL, 9 | GET_CART, 10 | GET_CART_SUCCESS, 11 | GET_CART_FAIL, 12 | SET_FILTER, 13 | CLEAR_FILTERS, 14 | SET_SORT_BY, 15 | } from '../constants'; 16 | import { filterTypes, filterValues } from '@typings/filters'; 17 | 18 | export const initCatalog = createAction(INIT_CATALOG); 19 | export const initCatalogSuccess = createAction(INIT_CATALOG_SUCCESS); 20 | export const initCatalogFail = createAction(INIT_CATALOG_FAIL); 21 | 22 | export const getUser = createAction(GET_USER); 23 | export const getUserSucces = createAction(GET_USER_SUCCESS); 24 | export const getUserFail = createAction(GET_USER_FAIL); 25 | 26 | export const getCart = createAction(GET_CART); 27 | export const getCartSuccess = createAction(GET_CART_SUCCESS); 28 | export const getCartFail = createAction(GET_CART_FAIL); 29 | 30 | export const setFilter = createAction( 31 | SET_FILTER, 32 | (filterType: filterTypes, filterValue: filterValues) => ({ filterType, filterValue }) 33 | ); 34 | export const clearFilters = createAction(CLEAR_FILTERS); 35 | 36 | export const setSortBy = createAction(SET_SORT_BY); 37 | -------------------------------------------------------------------------------- /client/src/components/Account/Account.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as moment from 'moment'; 3 | import * as numeral from 'numeral'; 4 | import { IUser, IOrder } from '@typings/state/index'; 5 | import IconButton from '@material-ui/core/IconButton'; 6 | import EditIcon from '@material-ui/icons/Edit'; 7 | import Divider from '@material-ui/core/Divider'; 8 | import AccountModal from '../AccountModal'; 9 | import '@styles/Account.css'; 10 | 11 | interface Props { 12 | user: IUser; 13 | getUser: () => any; 14 | } 15 | 16 | interface State { 17 | accountModalOpen: boolean; 18 | } 19 | 20 | class Account extends React.Component { 21 | state = { 22 | accountModalOpen: false 23 | } 24 | 25 | toggleAccountModal = () => { 26 | this.setState((prevState: State) => ({ 27 | accountModalOpen: !prevState.accountModalOpen 28 | })); 29 | } 30 | 31 | componentDidMount() { 32 | this.props.getUser(); 33 | } 34 | 35 | render() { 36 | const { user } = this.props; 37 | 38 | if(!user) { 39 | return ( 40 |
41 |
42 | 43 |

LOADING ACCOUNT DATA...

44 |
45 |
46 | ); 47 | } else { 48 | return ( 49 |
50 |

Your Account

51 |
52 |
53 |
54 |

Info

55 | 59 | 60 | 61 |
62 | 63 |

Username: {user.username}

64 |

E-mail: {user.email}

65 |

Billing Address: {user.address}

66 |

Phone: {user.phone}

67 |
68 |
69 |

Order History

70 | 71 |
72 | {user.orders.length ? 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | {user.orders.map((order: IOrder) => { 85 | return ( 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ); 94 | })} 95 | 96 |
Date CreatedProduct NamePriceQtyTotal
{moment(order.dateCreated).format('ll')}{order.name}{numeral(order.price).format('$0,0.00')}{order.quantity}{numeral(parseInt(order.price) * parseInt(order.quantity)).format('$0,0.00')}
: 97 |

No order history.

98 | } 99 |
100 |
101 |
102 | 107 |
108 | ); 109 | } 110 | } 111 | }; 112 | 113 | export default Account; 114 | -------------------------------------------------------------------------------- /client/src/components/Account/index.ts: -------------------------------------------------------------------------------- 1 | import { compose } from 'recompose'; 2 | import { connect } from 'react-redux'; 3 | import { getUser } from '@actions/index'; 4 | import { selectUser } from '@selectors/user'; 5 | import { IState } from '@typings/state/index'; 6 | import Account from './Account'; 7 | 8 | const mapStateToProps = (state: IState) => ({ 9 | user: selectUser(state) 10 | }); 11 | 12 | const actions = { getUser }; 13 | 14 | export default compose( 15 | connect(mapStateToProps, actions) 16 | )(Account); 17 | -------------------------------------------------------------------------------- /client/src/components/AccountModal/AccountModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as Modal from 'react-modal'; 3 | import TextField from 'material-ui/TextField'; 4 | import RaisedButton from 'material-ui/RaisedButton'; 5 | import { IUser } from '@typings/state/index'; 6 | import '@styles/AccountModal.css'; 7 | 8 | interface Props { 9 | user: IUser; 10 | isOpen: boolean; 11 | onRequestClose: () => void; 12 | } 13 | 14 | interface State { 15 | email: string; 16 | address: string; 17 | phone: string; 18 | } 19 | 20 | class AccountModal extends React.Component { 21 | state = { 22 | email: this.props.user.email, 23 | address: this.props.user.address, 24 | phone: this.props.user.phone 25 | } 26 | 27 | onInputChange = (e: React.FormEvent) => { 28 | const value = e.currentTarget.value; 29 | const key = e.currentTarget.name; 30 | 31 | this.setState((prevState: State) => ({ 32 | ...prevState, 33 | [key]: value 34 | })); 35 | } 36 | 37 | render() { 38 | return ( 39 | 44 |
45 |

Edit Account

46 |
54 |
62 |
70 | 76 | 77 |
78 | ); 79 | } 80 | } 81 | 82 | export default AccountModal; 83 | -------------------------------------------------------------------------------- /client/src/components/AccountModal/index.ts: -------------------------------------------------------------------------------- 1 | import AccountModal from './AccountModal'; 2 | 3 | export default AccountModal; 4 | -------------------------------------------------------------------------------- /client/src/components/Cart/Cart.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import * as numeral from 'numeral'; 5 | import FlatButton from 'material-ui/FlatButton'; 6 | import RaisedButton from 'material-ui/RaisedButton'; 7 | import NavigateNext from 'material-ui/svg-icons/image/navigate-next'; 8 | import RemoveShoppingCart from '@material-ui/icons/RemoveShoppingCart'; 9 | import Dialog from 'material-ui/Dialog'; 10 | import Snackbar from 'material-ui/Snackbar'; 11 | import CheckoutModal from '../CheckoutModal'; 12 | import OrderSuccessModal from '../OrderSuccessModal'; 13 | import { ICart } from '@typings/state/index'; 14 | import { modal } from '@typings/modal'; 15 | import '@styles/Cart.css'; 16 | 17 | interface Props { 18 | cart: ICart; 19 | getCart: () => ICart; 20 | } 21 | 22 | interface State { 23 | activeModal: modal 24 | } 25 | 26 | class Cart extends React.Component { 27 | state = { 28 | activeModal: null 29 | } 30 | 31 | setActiveModal = (modal: modal) => { 32 | this.setState({ activeModal: modal }); 33 | } 34 | 35 | removeItem = async (itemId: string) => { 36 | await axios.put('/api/cart', { 37 | cartId: this.props.cart._id, 38 | itemId: itemId 39 | }); 40 | 41 | this.props.getCart(); 42 | 43 | this.setActiveModal('snackbar'); 44 | setTimeout(() => { 45 | this.setActiveModal(null); 46 | }, 4000); 47 | } 48 | 49 | emptyCart = async () => { 50 | await axios.delete('/api/cart', { params: { id: this.props.cart._id } }); 51 | await this.setState({ activeModal: null }); 52 | await this.props.getCart(); 53 | } 54 | 55 | makeOrder = async () => { 56 | const order = this.props.cart.items.map((item) => { 57 | let order = { 58 | name: item.product.info.name, 59 | price: item.product.info.price, 60 | quantity: item.quantity, 61 | dateCreated: Date.now() 62 | }; 63 | return order; 64 | }); 65 | 66 | await axios.post('/api/order', { order: order }); 67 | await this.emptyCart(); 68 | 69 | this.setActiveModal('orderSuccess'); 70 | } 71 | 72 | componentWillMount() { 73 | this.props.getCart(); 74 | } 75 | 76 | render() { 77 | const { cart } = this.props; 78 | const cartExists = cart.isLoaded && !cart.error && cart.items.length; 79 | 80 | return ( 81 |
82 |

Your Cart

83 |
84 |
85 |
86 |

87 | Number of items: 88 | {cartExists ? cart.items.reduce((acc, item) => acc += item.quantity!, 0) : 0} 89 |

90 |

91 | Total amount: 92 | 93 | {cartExists ? numeral(cart.items.reduce((acc, item) => acc += item.product.info.price * item.quantity!, 0)).format('$0,0.00') : numeral(0).format('$0,0.00')} 94 | 95 |

96 |
97 |
98 | this.setActiveModal('checkout')} 100 | className="btn" 101 | label="Checkout" 102 | labelPosition="before" 103 | icon={} 104 | primary={true} 105 | disabled={!cartExists} 106 | /> 107 | this.setActiveModal('dialog')} 109 | className="btn" 110 | label="Empty cart" 111 | labelPosition="before" 112 | icon={} 113 | secondary={true} 114 | disabled={!cartExists} 115 | /> 116 |
117 | this.setActiveModal} 120 | setActiveModal={this.setActiveModal} 121 | makeOrder={this.makeOrder} 122 | /> 123 | 127 | this.setActiveModal(null)} 134 | />, 135 | , 140 | ]} 141 | modal={true} 142 | open={this.state.activeModal === 'dialog'} 143 | > 144 | All items will be removed. 145 | 146 |
147 |
148 | {cartExists ? 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | {cart.items.map((item) => { 162 | return ( 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | ); 172 | })} 173 | 174 |
Product NamePriceQtyTotal
{item.product.info.name}{numeral(item.product.info.price).format('$0,0.00')}{item.quantity}{numeral(item.product.info.price * item.quantity!).format('$0,0.00')}
: 175 |

No items in the cart.

176 | } 177 | 182 |
183 |
184 |
185 | ) 186 | } 187 | }; 188 | 189 | export default Cart; 190 | -------------------------------------------------------------------------------- /client/src/components/Cart/index.ts: -------------------------------------------------------------------------------- 1 | import { compose } from 'recompose'; 2 | import { connect } from 'react-redux'; 3 | import { getCart } from '@actions/index'; 4 | import { selectCart } from '@selectors/cart'; 5 | import { IState } from '@typings/state/index'; 6 | import Cart from './Cart'; 7 | 8 | const mapStateToProps = (state: IState) => ({ 9 | cart: selectCart(state) 10 | }); 11 | 12 | const actions = { getCart }; 13 | 14 | export default compose( 15 | connect(mapStateToProps, actions) 16 | )(Cart); 17 | -------------------------------------------------------------------------------- /client/src/components/CheckoutModal/CheckoutModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as numeral from 'numeral'; 3 | import * as Modal from 'react-modal'; 4 | import { ICartProduct } from '@typings/state/index'; 5 | import { ModalProps } from '@typings/modal'; 6 | import Button from '@material-ui/core/Button'; 7 | import '@styles/CheckoutModal.css'; 8 | 9 | const CheckoutModal: React.SFC = ({ cart, isOpen, setActiveModal, makeOrder }): JSX.Element => ( 10 | setActiveModal(null)} 14 | > 15 |
16 |

Checkout Information

17 |

18 | Please read the list of items in your order and click "Confirm" to confirm your order. 19 |

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {cart!.length && cart!.map((item: ICartProduct) => { 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | 38 | ) 39 | })} 40 | 41 |
Product NamePriceQuantityTotal
{item.product.info.name}{numeral(item.product.info.price).format('$0,0.00')}{item.quantity}{numeral(item.product.info.price * item.quantity!).format('$0,0.00')}
42 |

43 | TOTAL AMOUNT: 44 | {numeral(cart!.length && cart!.reduce((acc, item) => acc += item.product.info.price * item.quantity!, 0)).format('$0,0.00')} 45 |

46 |
47 | 52 | 57 |
58 |
59 |
60 | ); 61 | 62 | export default CheckoutModal; 63 | -------------------------------------------------------------------------------- /client/src/components/CheckoutModal/index.ts: -------------------------------------------------------------------------------- 1 | import { compose } from 'recompose'; 2 | import { connect } from 'react-redux'; 3 | import { selectItems } from '@selectors/cart'; 4 | import { IState, ICartProduct, } from '@typings/state/index'; 5 | import { ModalProps } from '@typings/modal'; 6 | import CheckoutModal from './CheckoutModal'; 7 | 8 | interface EnhancedProps extends ModalProps { 9 | cart?: ICartProduct[]; 10 | } 11 | 12 | const mapStateToProps = (state: IState) => ({ 13 | cart: selectItems(state) 14 | }); 15 | 16 | export default compose( 17 | connect(mapStateToProps) 18 | )(CheckoutModal); 19 | -------------------------------------------------------------------------------- /client/src/components/FiltersList/FiltersList.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { List, ListItem } from 'material-ui/List'; 3 | import Subheader from 'material-ui/Subheader'; 4 | import Checkbox from 'material-ui/Checkbox'; 5 | import { IFilters } from '@typings/state/index'; 6 | import '@styles/FiltersList.css'; 7 | 8 | interface Props { 9 | filters: IFilters; 10 | setFilter: (name: string, value: string) => void; 11 | } 12 | 13 | class FiltersList extends React.Component { 14 | handleCheck = (e: React.MouseEvent) => { 15 | const { setFilter } = this.props; 16 | 17 | setFilter(e.currentTarget.name, e.currentTarget.value); 18 | }; 19 | 20 | render() { 21 | const { filters: { checked } } = this.props; 22 | 23 | return ( 24 |
25 | 26 | Search by: 27 | , 34 | , 35 | , 36 | ')} onCheck={this.handleCheck} /> 37 | ]} 38 | /> 39 | , 46 | , 47 | , 48 | , 49 | , 50 | ]} 51 | /> 52 | , 59 | , 60 | 61 | ]} 62 | /> 63 | , 70 | 71 | ]} 72 | /> 73 | , 80 | , 81 | , 82 | 83 | ]} 84 | /> 85 | , 92 | , 93 | , 94 | 95 | ]} 96 | /> 97 | , 104 | , 105 | , 106 | , 107 | , 108 | 109 | ]} 110 | /> 111 | , 118 | , 119 | , 120 | , 121 | , 122 | 123 | ]} 124 | /> 125 | , 132 | , 133 | , 134 | 135 | ]} 136 | /> 137 | , 144 | , 145 | 146 | ]} 147 | /> 148 | 149 |
150 | ) 151 | } 152 | }; 153 | 154 | export default FiltersList; 155 | -------------------------------------------------------------------------------- /client/src/components/FiltersList/index.ts: -------------------------------------------------------------------------------- 1 | import { compose } from 'recompose'; 2 | import { connect } from 'react-redux'; 3 | import { selectFilters } from '@selectors/catalog'; 4 | import { setFilter } from '@actions/index'; 5 | import { IState } from '@typings/state/index'; 6 | import FiltersList from './FiltersList'; 7 | 8 | const mapStateToProps = (state: IState) => ({ 9 | filters: selectFilters(state) 10 | }); 11 | 12 | const actions = { setFilter }; 13 | 14 | export default compose( 15 | connect(mapStateToProps, actions) 16 | )(FiltersList); 17 | -------------------------------------------------------------------------------- /client/src/components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import '@styles/Footer.css'; 3 | 4 | const Footer = () => ( 5 |
6 |

Computer Shop © 2020

7 |
8 | ); 9 | 10 | export default Footer; 11 | -------------------------------------------------------------------------------- /client/src/components/Footer/index.ts: -------------------------------------------------------------------------------- 1 | import Footer from './Footer'; 2 | 3 | export default Footer; 4 | -------------------------------------------------------------------------------- /client/src/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import AppBar from 'material-ui/AppBar'; 4 | import FlatButton from 'material-ui/FlatButton'; 5 | import IconMenu from 'material-ui/IconMenu'; 6 | import IconButton from 'material-ui/IconButton'; 7 | import Person from 'material-ui/svg-icons/social/person'; 8 | import Menu from 'material-ui/svg-icons/navigation/menu'; 9 | import Logout from 'material-ui/svg-icons/navigation/subdirectory-arrow-left'; 10 | import ShoppingCart from '@material-ui/icons/ShoppingCart'; 11 | import Input from '@material-ui/icons/Input'; 12 | import { RouteComponentProps } from 'react-router-dom'; 13 | import { ILoggedUser } from '@typings/state/loggedUser'; 14 | import { ICart } from '@typings/state/cart'; 15 | import { modal } from '@typings/modal'; 16 | import LoginModal from '../LoginModal'; 17 | import RegisterModal from '../RegisterModal'; 18 | import '@styles/Header.css'; 19 | 20 | interface Props extends RouteComponentProps { 21 | loggedUser: ILoggedUser; 22 | cart: ICart; 23 | getUser: () => void; 24 | } 25 | 26 | const styles = { 27 | menuBtn: { 28 | color: '#fff' 29 | }, 30 | iconMenuBtn: { 31 | color: '#ff8c00', 32 | minWidth: '168px', 33 | textAlign: 'left' 34 | } 35 | } 36 | 37 | class Header extends React.Component { 38 | state = { 39 | activeModal: null 40 | } 41 | 42 | setActiveModal = (modal: modal) => { 43 | this.setState({ activeModal: modal }); 44 | } 45 | 46 | componentDidMount() { 47 | this.props.getUser(); 48 | } 49 | 50 | render() { 51 | const { 52 | history, 53 | loggedUser 54 | } = this.props; 55 | 56 | return ( 57 |
58 | history.push('/')} 62 | showMenuIconButton={false} 63 | zDepth={0} 64 | iconElementRight={ 65 | loggedUser ? 66 | : 112 | } 116 | onClick={() => this.setActiveModal('login')} 117 | /> 118 | } 119 | /> 120 | this.setActiveModal(null)} 123 | setActiveModal={this.setActiveModal} 124 | /> 125 | this.setActiveModal('register')} 128 | setActiveModal={this.setActiveModal} 129 | /> 130 |
131 | ) 132 | } 133 | }; 134 | 135 | export default Header; 136 | -------------------------------------------------------------------------------- /client/src/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | import { compose } from 'recompose'; 2 | import { connect } from 'react-redux'; 3 | import { getUser } from '@actions/index'; 4 | import { selectUser } from '@selectors/user'; 5 | import { selectCart } from '@selectors/cart'; 6 | import { IState } from '@typings/state/index'; 7 | import Header from './Header'; 8 | 9 | const mapStateToProps = (state: IState) => ({ 10 | loggedUser: selectUser(state), 11 | cart: selectCart(state) 12 | }); 13 | 14 | const actions = { getUser }; 15 | 16 | export default compose( 17 | connect(mapStateToProps, actions) 18 | )(Header); 19 | -------------------------------------------------------------------------------- /client/src/components/Homepage/Homepage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import FiltersList from '../FiltersList'; 3 | import Products from '../Products'; 4 | import '@styles/Homepage.css'; 5 | 6 | const Homepage = () => ( 7 |
8 |
9 | 10 |
11 | 12 |
13 | ); 14 | 15 | export default Homepage; 16 | -------------------------------------------------------------------------------- /client/src/components/Homepage/index.ts: -------------------------------------------------------------------------------- 1 | import Homepage from './Homepage'; 2 | 3 | export default Homepage; 4 | -------------------------------------------------------------------------------- /client/src/components/LoginModal/LoginModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as Modal from 'react-modal'; 3 | import TextField from 'material-ui/TextField'; 4 | import RaisedButton from 'material-ui/RaisedButton'; 5 | import { ModalProps } from '@typings/modal'; 6 | import '@styles/LoginModal.css'; 7 | 8 | const LoginModal: React.SFC = ({ isOpen, onRequestClose, setActiveModal }): JSX.Element => ( 9 | 14 |
15 |

Log In

16 |
22 |
28 | 34 |

Don't have an account yet? setActiveModal('register')}>Register here.

35 |
36 | 37 | ); 38 | 39 | export default LoginModal; 40 | -------------------------------------------------------------------------------- /client/src/components/LoginModal/index.ts: -------------------------------------------------------------------------------- 1 | import LoginModal from './LoginModal'; 2 | 3 | export default LoginModal; 4 | -------------------------------------------------------------------------------- /client/src/components/NotFound/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import '@styles/NotFound.css'; 3 | 4 | const NotFound = () => ( 5 |
6 |
7 |

404

8 |

Page Not Found

9 |
10 |
11 | ); 12 | 13 | export default NotFound; 14 | -------------------------------------------------------------------------------- /client/src/components/NotFound/index.ts: -------------------------------------------------------------------------------- 1 | import NotFound from './NotFound'; 2 | 3 | export default NotFound; 4 | -------------------------------------------------------------------------------- /client/src/components/OrderSuccessModal/OrderSuccessModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as Modal from 'react-modal'; 3 | import RaisedButton from 'material-ui/RaisedButton'; 4 | import { ModalProps } from '@typings/modal'; 5 | import '@styles/OrderSuccessModal.css'; 6 | 7 | const OrderSuccessModal: React.SFC = ({ isOpen, setActiveModal }) => ( 8 | setActiveModal(null)} 12 | > 13 |
14 |

Success!

15 | 16 |
17 |

18 | Your order has been received. The items you've ordered will be sent to your address. 19 |

20 | setActiveModal(null)} 22 | className="btn" 23 | label="OK" 24 | primary={true} 25 | /> 26 |
27 |
28 | ); 29 | 30 | export default OrderSuccessModal; 31 | -------------------------------------------------------------------------------- /client/src/components/OrderSuccessModal/index.ts: -------------------------------------------------------------------------------- 1 | import OrderSuccessModal from './OrderSuccessModal'; 2 | 3 | export default OrderSuccessModal; 4 | -------------------------------------------------------------------------------- /client/src/components/Product/Product.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import * as numeral from 'numeral'; 4 | import RaisedButton from 'material-ui/RaisedButton'; 5 | import NavigateNext from 'material-ui/svg-icons/image/navigate-next'; 6 | import { ICatalogProduct } from '@typings/state/index' 7 | import '@styles/Product.css'; 8 | 9 | interface Props { 10 | key: string; 11 | item: ICatalogProduct; 12 | } 13 | 14 | const Product = ({ item: {info, _id} }: Props) => { 15 | const { 16 | photo, 17 | name, 18 | displaySize, 19 | displayResolution, 20 | cpu, 21 | internalMemory, 22 | ram, 23 | camera, 24 | price 25 | } = info; 26 | 27 | return ( 28 |
29 |
30 | 31 |
32 |

{name}

33 |
34 |
Display size: {displaySize}
35 |
Display resolution: {displayResolution}
36 |
CPU: {cpu}
37 |
Internal memory: {internalMemory}
38 |
RAM: {ram}
39 |
Camera: {camera.length < 50 ? camera : camera.slice(0, 50) + '...'}
40 |
41 |
42 |
43 |
44 |

Price:

45 |

{numeral(price).format('$0,0.00')}

46 |
47 | } 49 | className="btn" 50 | label="See more" 51 | labelPosition="before" 52 | primary={true} 53 | icon={} 54 | /> 55 |
56 |
57 |
58 | ) 59 | }; 60 | 61 | export default Product; 62 | -------------------------------------------------------------------------------- /client/src/components/Product/index.ts: -------------------------------------------------------------------------------- 1 | import Product from './Product'; 2 | 3 | export default Product; 4 | -------------------------------------------------------------------------------- /client/src/components/ProductDetails/ProductDetails.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import * as numeral from 'numeral'; 5 | import RaisedButton from 'material-ui/RaisedButton'; 6 | import Snackbar from 'material-ui/Snackbar'; 7 | import AddShoppingCart from 'material-ui/svg-icons/action/add-shopping-cart'; 8 | import KeyboardArrowLeft from 'material-ui/svg-icons/hardware/keyboard-arrow-left'; 9 | import { IUser, ICatalogProduct } from '@typings/state/index'; 10 | import '@styles/ProductDetails.css'; 11 | 12 | interface Props { 13 | loggedUser: IUser; 14 | product: ICatalogProduct; 15 | } 16 | 17 | interface State { 18 | postData: { 19 | user: string; 20 | product: string; 21 | quantity: number; 22 | }; 23 | snackbarOpen: boolean; 24 | } 25 | 26 | class ProductDetails extends React.Component { 27 | state = { 28 | postData: { 29 | user: this.props.loggedUser._id, 30 | product: this.props.product._id, 31 | quantity: 1 32 | }, 33 | snackbarOpen: false 34 | } 35 | 36 | onQuantityChange = (e: React.ChangeEvent) => { 37 | let value = e.target.value; 38 | 39 | this.setState((prevState: State) => ({ 40 | postData: { ...prevState.postData, quantity: parseInt(value) } 41 | })); 42 | } 43 | 44 | addToCart = async () => { 45 | await axios.post('/api/cart', this.state.postData); 46 | 47 | this.setState({ snackbarOpen: true }); 48 | } 49 | 50 | render() { 51 | const { 52 | loggedUser, 53 | product: { info } 54 | } = this.props; 55 | 56 | return ( 57 |
58 |

{info.name}

59 |
60 |
61 | 62 |
63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 |
Model{info.name}
Dimensions{info.dimensions}
Weight{info.weight}
Display Type{info.displayType}
Display Size{info.displaySize}
Display Resolution{info.displayResolution}
OS{info.os}
CPU{info.cpu}
Internal Memory{info.internalMemory}
RAM{info.ram}
Camera{info.camera}
Batery{info.batery}
Color{info.color}
118 | 124 |
125 |
126 |
127 |
128 | } 130 | className="btn" 131 | label="Back to catalog" 132 | labelPosition="after" 133 | secondary={true} 134 | icon={} 135 | /> 136 |
137 |
138 |
139 | Price: 140 | {numeral(info.price).format('$0,0.00')} 141 |
142 |
143 | Quantity: 144 | 145 |
146 |
147 | } 153 | /> 154 |
155 |
156 |
157 |
158 | ); 159 | } 160 | } 161 | 162 | export default ProductDetails; 163 | -------------------------------------------------------------------------------- /client/src/components/ProductDetails/index.ts: -------------------------------------------------------------------------------- 1 | import { compose } from 'recompose'; 2 | import { connect } from 'react-redux'; 3 | import { selectUser } from '@selectors/user'; 4 | import { selectProduct } from '@selectors/catalog'; 5 | import { IState } from '@typings/state/index' 6 | import ProductDetails from './ProductDetails'; 7 | 8 | const mapStateToProps = (state: IState, ownProps: any) => ({ 9 | loggedUser: selectUser(state), 10 | product: selectProduct(state, ownProps) 11 | }); 12 | 13 | export default compose( 14 | connect(mapStateToProps) 15 | )(ProductDetails); 16 | -------------------------------------------------------------------------------- /client/src/components/Products/Products.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import RaisedButton from 'material-ui/RaisedButton'; 3 | import Drawer from 'material-ui/Drawer'; 4 | import SelectField from 'material-ui/SelectField'; 5 | import MenuItem from 'material-ui/MenuItem'; 6 | import { ICatalogProduct } from '@typings/state/index'; 7 | import FiltersList from '../FiltersList'; 8 | import Product from '../Product'; 9 | import '@styles/Products.css'; 10 | 11 | interface Props { 12 | catalogLoaded: boolean; 13 | catalog: ICatalogProduct[]; 14 | sortBy: string; 15 | initCatalog: () => void; 16 | clearFilters: () => void; 17 | setSortBy: (value: string) => void; 18 | } 19 | 20 | interface State { 21 | drawerOpen: boolean; 22 | value: string; 23 | } 24 | 25 | export class Products extends React.Component { 26 | state = { 27 | drawerOpen: false, 28 | value: this.props.sortBy || 'Name: A-Z' 29 | } 30 | 31 | toggleDrawer = () => { 32 | this.setState({ drawerOpen: !this.state.drawerOpen }); 33 | } 34 | 35 | handleChange = (e: React.ChangeEvent, index: number, value: string) => { 36 | this.props.setSortBy(value); 37 | this.setState({ value }); 38 | } 39 | 40 | componentWillMount() { 41 | this.props.initCatalog(); 42 | } 43 | 44 | render() { 45 | const { 46 | catalogLoaded, 47 | catalog, 48 | clearFilters 49 | } = this.props; 50 | 51 | if(!catalogLoaded) { 52 | return ( 53 |
54 | 55 |

LOADING PRODUCTS...

56 |
57 | ); 58 | } else return ( 59 |
60 |
61 |
62 | Products found: {catalog.length} 63 |
64 |
65 |
66 | 72 |
73 | 79 |
80 |
81 | Sort By: 82 | 87 | 88 | 89 | 90 | 91 | 92 | 98 | 99 | 100 |
101 |
102 | {catalog.length ? 103 | catalog.map((item) => { 104 | return 105 | }) : 106 |

No products found.

} 107 |
108 | ) 109 | } 110 | } 111 | 112 | export default Products; 113 | -------------------------------------------------------------------------------- /client/src/components/Products/index.ts: -------------------------------------------------------------------------------- 1 | import { compose } from 'recompose'; 2 | import { connect } from 'react-redux'; 3 | import { initCatalog, clearFilters, setSortBy } from '@actions/index'; 4 | import { isCatalogLoaded, sortProducts, filterProducts, selectSortBy } from '@selectors/catalog'; 5 | import { IState } from '@typings/state/index'; 6 | import Products from './Products'; 7 | 8 | const mapStateToProps = (state: IState) => ({ 9 | catalogLoaded: isCatalogLoaded(state), 10 | catalog: sortProducts(filterProducts(state), state.sortBy), 11 | sortBy: selectSortBy(state) 12 | }); 13 | 14 | const actions = { 15 | initCatalog, 16 | clearFilters, 17 | setSortBy 18 | }; 19 | 20 | export default compose( 21 | connect(mapStateToProps, actions) 22 | )(Products); 23 | -------------------------------------------------------------------------------- /client/src/components/RegisterModal/RegisterModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as Modal from 'react-modal'; 3 | import TextField from 'material-ui/TextField'; 4 | import RaisedButton from 'material-ui/RaisedButton'; 5 | import { ModalProps } from '@typings/modal'; 6 | import '@styles/RegisterModal.css'; 7 | 8 | const RegisterModal = ({ isOpen, onRequestClose, setActiveModal }: ModalProps) => ( 9 | 14 |
15 |

Register

16 |
22 |
28 |
33 |
38 |
43 | 49 |

Already have an account? setActiveModal('login')}>Login here.

50 | 51 |
52 | ); 53 | 54 | export default RegisterModal; 55 | -------------------------------------------------------------------------------- /client/src/components/RegisterModal/index.ts: -------------------------------------------------------------------------------- 1 | import RegisterModal from './RegisterModal'; 2 | 3 | export default RegisterModal; 4 | -------------------------------------------------------------------------------- /client/src/components/ShoppingCart/ShoppingCart.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { BrowserRouter, Route, Switch } from 'react-router-dom'; 3 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 4 | import Header from '../Header'; 5 | import Account from '../Account'; 6 | import Cart from '../Cart'; 7 | import Homepage from '../Homepage'; 8 | import ProductDetails from '../ProductDetails'; 9 | import Footer from '../Footer'; 10 | import NotFound from '../NotFound'; 11 | import '@styles/ShoppingCart.css'; 12 | 13 | const ShoppingCart = () => ( 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 | ); 30 | 31 | export default ShoppingCart; 32 | -------------------------------------------------------------------------------- /client/src/components/ShoppingCart/index.ts: -------------------------------------------------------------------------------- 1 | import ShoppingCart from './ShoppingCart'; 2 | 3 | export default ShoppingCart; 4 | -------------------------------------------------------------------------------- /client/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const GET_USER = 'GET_USER'; 2 | export const GET_USER_SUCCESS = 'GET_USER_SUCCESS'; 3 | export const GET_USER_FAIL = 'GET_USER_FAIL'; 4 | 5 | export const INIT_CATALOG = 'INIT_CATALOG'; 6 | export const INIT_CATALOG_SUCCESS = 'INIT_CATALOG_SUCCESS'; 7 | export const INIT_CATALOG_FAIL = 'INIT_CATALOG_FAIL'; 8 | 9 | export const GET_CART = 'GET_CART'; 10 | export const GET_CART_SUCCESS = 'GET_CART_SUCCESS'; 11 | export const GET_CART_FAIL = 'GET_CART_FAIL'; 12 | 13 | export const SET_FILTER = 'SET_FILTER'; 14 | export const CLEAR_FILTERS = 'CLEAR_FILTERS'; 15 | 16 | export const SET_SORT_BY = 'SET_SORT_BY'; 17 | -------------------------------------------------------------------------------- /client/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { ConnectedRouter } from 'connected-react-router'; 5 | import configureStore, { history } from './store/configureStore'; 6 | import ShoppingCart from './components/ShoppingCart'; 7 | 8 | const store = configureStore(); 9 | 10 | ReactDOM.render( 11 | 12 | 13 | 14 | 15 | , 16 | document.getElementById('app_root') 17 | ); 18 | -------------------------------------------------------------------------------- /client/src/reducers/cartReducer.ts: -------------------------------------------------------------------------------- 1 | import { GET_CART, GET_CART_SUCCESS, GET_CART_FAIL } from '../constants'; 2 | import { actionTypes } from '@typings/action'; 3 | import { ICart } from '@typings/state/cart'; 4 | 5 | interface IAction { 6 | type: actionTypes; 7 | payload?: ICart; 8 | } 9 | 10 | export const initState: ICart = { 11 | isLoading: false, 12 | isLoaded: false, 13 | _id: null, 14 | items: [], 15 | error: null 16 | }; 17 | 18 | const cartReducer = (state = initState, action: IAction) => { 19 | switch(action.type) { 20 | case GET_CART: 21 | return { 22 | ...state, 23 | isLoading: true 24 | } 25 | case GET_CART_SUCCESS: 26 | return { 27 | ...state, 28 | isLoading: false, 29 | isLoaded: true, 30 | _id: action.payload ? action.payload._id : null, 31 | items: action.payload ? action.payload.items : [], 32 | error: null 33 | } 34 | case GET_CART_FAIL: 35 | return { 36 | ...state, 37 | isLoaded: true, 38 | items: [], 39 | error: action.payload 40 | } 41 | default: 42 | return state; 43 | } 44 | }; 45 | 46 | export default cartReducer; 47 | -------------------------------------------------------------------------------- /client/src/reducers/catalogReducer.ts: -------------------------------------------------------------------------------- 1 | import { INIT_CATALOG, INIT_CATALOG_SUCCESS, INIT_CATALOG_FAIL } from '../constants'; 2 | import { ICatalog } from '@typings/state/index'; 3 | import { actionTypes } from '@typings/action'; 4 | 5 | interface IAction { 6 | type: actionTypes; 7 | payload: ICatalog; 8 | } 9 | 10 | const initState: ICatalog = { 11 | isLoading: false, 12 | isLoaded: false, 13 | items: [], 14 | error: null 15 | }; 16 | 17 | const catalogReducer = (state = initState, action: IAction) => { 18 | switch (action.type) { 19 | case INIT_CATALOG: 20 | return { 21 | ...state, 22 | isLoading: true 23 | } 24 | case INIT_CATALOG_SUCCESS: 25 | return { 26 | ...state, 27 | isLoading: false, 28 | isLoaded: true, 29 | items: action.payload 30 | } 31 | case INIT_CATALOG_FAIL: 32 | return { 33 | ...state, 34 | isLoaded: true, 35 | error: action.payload 36 | } 37 | default: 38 | return state 39 | } 40 | } 41 | 42 | export default catalogReducer; 43 | -------------------------------------------------------------------------------- /client/src/reducers/filtersReducer.ts: -------------------------------------------------------------------------------- 1 | import { SET_FILTER, CLEAR_FILTERS } from '../constants'; 2 | import { IFilters } from '@typings/state/index'; 3 | import { actionTypes } from '@typings/action'; 4 | import { filterTypes, filterValues } from '@typings/filters'; 5 | 6 | interface IAction { 7 | type: actionTypes; 8 | payload: { 9 | filterType: filterTypes; 10 | filterValue: filterValues; 11 | } 12 | } 13 | 14 | const initState: IFilters = { 15 | filters: { 16 | priceRange: [], 17 | brand: [], 18 | color: [], 19 | os: [], 20 | internalMemory: [], 21 | ram: [], 22 | displaySize: [], 23 | displayResolution: [], 24 | camera: [], 25 | cpu: [] 26 | }, 27 | checked: [] 28 | }; 29 | 30 | const filtersReducer = (state = initState, action: IAction) => { 31 | switch (action.type) { 32 | case SET_FILTER: 33 | const { filterType, filterValue } = action.payload; 34 | const newState = { ...state }; 35 | 36 | if (newState.filters[filterType].includes(filterValue)) { 37 | newState.filters[filterType] = newState.filters[filterType].filter((item: string) => item !== filterValue); 38 | newState.checked = newState.checked.filter((item) => item !== filterValue); 39 | } else { 40 | newState.filters[filterType].push(filterValue); 41 | newState.checked.push(filterValue); 42 | } 43 | 44 | return newState; 45 | case CLEAR_FILTERS: 46 | for (let key in initState.filters) { 47 | initState.filters[key] = []; 48 | } 49 | 50 | initState.checked = []; 51 | 52 | return initState; 53 | default: 54 | return state; 55 | } 56 | }; 57 | 58 | export default filtersReducer; 59 | -------------------------------------------------------------------------------- /client/src/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { connectRouter } from 'connected-react-router'; 3 | import { History } from 'history'; 4 | import userReducer from './userReducer'; 5 | import cartReducer from './cartReducer'; 6 | import catalogReducer from './catalogReducer'; 7 | import filtersReducer from './filtersReducer'; 8 | import sortReducer from './sortReducer'; 9 | 10 | const createRootReducer = (history: History) => combineReducers({ 11 | router: connectRouter(history), 12 | loggedUser: userReducer, 13 | cart: cartReducer, 14 | catalog: catalogReducer, 15 | filters: filtersReducer, 16 | sortBy: sortReducer 17 | }); 18 | 19 | export default createRootReducer; 20 | -------------------------------------------------------------------------------- /client/src/reducers/sortReducer.ts: -------------------------------------------------------------------------------- 1 | import { SET_SORT_BY } from '../constants'; 2 | import { actionTypes } from '@typings/action'; 3 | import { TSortBy } from '@typings/state/sortBy'; 4 | 5 | interface IAction { 6 | type: actionTypes; 7 | payload: TSortBy; 8 | } 9 | 10 | const initState: TSortBy = 'Name: A-Z'; 11 | 12 | const sortReducer = (state = initState, action: IAction) => { 13 | switch (action.type) { 14 | case SET_SORT_BY: 15 | return action.payload; 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | export default sortReducer; 22 | -------------------------------------------------------------------------------- /client/src/reducers/userReducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GET_USER, 3 | GET_USER_SUCCESS, 4 | GET_USER_FAIL 5 | } from '../constants'; 6 | import { ILoggedUser } from '@typings/state/loggedUser'; 7 | import { actionTypes } from '@typings/action'; 8 | 9 | interface IAction { 10 | type: actionTypes; 11 | payload: ILoggedUser; 12 | } 13 | 14 | const initState: ILoggedUser = { 15 | isLoading: false, 16 | isLoaded: false, 17 | user: null, 18 | error: null 19 | } 20 | 21 | const userReducer = (state = initState, action: IAction) => { 22 | switch(action.type) { 23 | case GET_USER: 24 | return { 25 | ...state, 26 | isLoading: true 27 | } 28 | case GET_USER_SUCCESS: 29 | return { 30 | ...state, 31 | isLoading: false, 32 | isLoaded: true, 33 | user: action.payload, 34 | error: null 35 | } 36 | case GET_USER_FAIL: 37 | return { 38 | ...state, 39 | isLoading: false, 40 | isLoaded: true, 41 | user: null, 42 | error: action.payload 43 | } 44 | default: 45 | return state; 46 | } 47 | } 48 | 49 | export default userReducer; 50 | -------------------------------------------------------------------------------- /client/src/sagas/index.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { put, all, takeLatest } from 'redux-saga/effects'; 3 | import { 4 | INIT_CATALOG, 5 | GET_USER, 6 | GET_CART 7 | } from '../constants'; 8 | import * as actions from '../actions'; 9 | 10 | function* initCatalog() { 11 | try { 12 | const catalog = yield axios.get('/api/catalog'); 13 | 14 | yield put(actions.initCatalogSuccess(catalog.data)); 15 | } catch(e) { 16 | yield put(actions.initCatalogFail('COULD NOT GET CATALOG')); 17 | } 18 | } 19 | 20 | function* getUser() { 21 | try { 22 | const user = yield axios.get('/api/user'); 23 | 24 | yield put(actions.getUserSucces(user.data)); 25 | } catch(e) { 26 | yield put(actions.getUserFail('COULD NOT GET USER')); 27 | } 28 | } 29 | 30 | function* getCart() { 31 | try { 32 | const cart = yield axios.get('/api/cart'); 33 | 34 | yield put(actions.getCartSuccess(cart.data)); 35 | } catch(e) { 36 | yield put(actions.getCartFail('COULD NOT GET CART')); 37 | } 38 | } 39 | 40 | export default function*() { 41 | yield all([ 42 | takeLatest(INIT_CATALOG, initCatalog), 43 | takeLatest(GET_USER, getUser), 44 | takeLatest(GET_CART, getCart) 45 | ]); 46 | } 47 | -------------------------------------------------------------------------------- /client/src/selectors/cart.ts: -------------------------------------------------------------------------------- 1 | import { IState, ICart, ICartProduct } from '@typings/state/index'; 2 | 3 | export const selectCart = (state: IState): ICart => state.cart; 4 | export const selectItems = (state: IState): ICartProduct[] => state.cart.items; 5 | -------------------------------------------------------------------------------- /client/src/selectors/catalog.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { IState, ICatalogProduct, TSortBy } from '@typings/state/index'; 3 | 4 | export const isCatalogLoaded = (state: IState) => state.catalog.isLoaded; 5 | export const selectFilters = (state: IState) => state.filters; 6 | export const selectSortBy = (state: IState) => state.sortBy; 7 | export const selectProducts = (state: IState) => state.catalog.items; 8 | export const selectProduct = (state: IState, props: any) => state.catalog.items.find((item) => item._id == props.match.params.id); 9 | 10 | export const sortProducts = (catalog: ICatalogProduct[], sortBy: TSortBy) => { 11 | switch (sortBy) { 12 | case 'Name: A-Z': 13 | return catalog.sort((a, b) => (a.info.name > b.info.name) ? 1 : ((b.info.name > a.info.name) ? -1 : 0)); 14 | case 'Name: Z-A': 15 | return catalog.sort((a, b) => (a.info.name < b.info.name) ? 1 : ((b.info.name < a.info.name) ? -1 : 0)); 16 | case 'Price: Low to High': 17 | return catalog.sort((a, b) => (a.info.price > b.info.price) ? 1 : ((b.info.price > a.info.price) ? -1 : 0)); 18 | case 'Price: High to Low': 19 | return catalog.sort((a, b) => (a.info.price < b.info.price) ? 1 : ((b.info.price < a.info.price) ? -1 : 0)); 20 | default: 21 | return catalog; 22 | } 23 | }; 24 | 25 | export const filterProducts = createSelector( 26 | [selectProducts, selectFilters], 27 | (catalog, { filters }) => { 28 | return catalog.filter((item) => { 29 | const priceRange = filters.priceRange.length ? filters.priceRange.includes(item.tags.priceRange) : item.tags.priceRange; 30 | const brand = filters.brand.length ? filters.brand.includes(item.tags.brand) : item.tags.brand; 31 | const color = filters.color.length ? filters.color.includes(item.tags.color) : item.tags.color; 32 | const os = filters.os.length ? filters.os.includes(item.tags.os) : item.tags.os; 33 | const internalMemory = filters.internalMemory.length ? filters.internalMemory.includes(item.tags.internalMemory) : item.tags.internalMemory; 34 | const ram = filters.ram.length ? filters.ram.includes(item.tags.ram) : item.tags.ram; 35 | const displaySize = filters.displaySize.length ? filters.displaySize.includes(item.tags.displaySize) : item.tags.displaySize; 36 | const displayResolution = filters.displayResolution.length ? filters.displayResolution.includes(item.tags.displayResolution) : item.tags.displayResolution; 37 | const camera = filters.camera.length ? filters.camera.includes(item.tags.camera) : item.tags.camera; 38 | const cpu = filters.cpu.length ? filters.cpu.includes(item.tags.cpu) : item.tags.cpu; 39 | 40 | return priceRange && brand && color && os && internalMemory && ram && displaySize && displayResolution && camera && cpu; 41 | }) 42 | } 43 | ); 44 | -------------------------------------------------------------------------------- /client/src/selectors/user.ts: -------------------------------------------------------------------------------- 1 | import { IState, IUser } from '@typings/state/index'; 2 | 3 | export const selectUser = (state: IState): IUser | null => state.loggedUser.user; 4 | -------------------------------------------------------------------------------- /client/src/store/configureStore.ts: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import { routerMiddleware } from 'connected-react-router'; 3 | import createSagaMiddleware from 'redux-saga'; 4 | import { createBrowserHistory } from 'history'; 5 | import rootSaga from '../sagas'; 6 | import createRootReducer from '../reducers'; 7 | 8 | export const history = createBrowserHistory(); 9 | const composeEnhancers = (window).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 10 | const sagaMiddleware = createSagaMiddleware(); 11 | 12 | const configureStore = () => { 13 | const store = createStore( 14 | createRootReducer(history), 15 | composeEnhancers( 16 | applyMiddleware( 17 | routerMiddleware(history), 18 | sagaMiddleware 19 | ) 20 | ) 21 | ); 22 | 23 | sagaMiddleware.run(rootSaga); 24 | 25 | return store; 26 | }; 27 | 28 | export default configureStore; 29 | -------------------------------------------------------------------------------- /client/src/styles/Account.css: -------------------------------------------------------------------------------- 1 | .account-container { 2 | min-height: 100vh; 3 | margin: 0 6px; 4 | margin-top: -72px; 5 | border: 1px solid #ffffff00; 6 | } 7 | 8 | .account-container .top { 9 | display: flex; 10 | justify-content: space-between; 11 | } 12 | 13 | .account-container .top h1 { 14 | margin-top: 100px; 15 | margin-bottom: 30px; 16 | } 17 | 18 | .loader { 19 | margin: 20px auto; 20 | color: #ff8c00; 21 | text-align: center; 22 | } 23 | 24 | .account { 25 | display: flex; 26 | flex-direction: row; 27 | } 28 | 29 | .account-info { 30 | width: 50%; 31 | margin-bottom: 30px; 32 | } 33 | 34 | .account-info .btn { 35 | margin-top: 20px; 36 | } 37 | 38 | .account-history { 39 | width: 50%; 40 | margin-bottom: 50px; 41 | } 42 | 43 | .account-history h1 { 44 | margin-top: 100px; 45 | text-align: center; 46 | } 47 | 48 | .account-history .orders table { 49 | width: 100%; 50 | margin: 10px auto; 51 | border-spacing: 0; 52 | } 53 | 54 | .orders th { 55 | padding: 5px 5px; 56 | background-color: #ff8c00; 57 | color: white; 58 | text-align: left; 59 | } 60 | 61 | .orders td { 62 | padding: 12px 5px; 63 | text-align: left; 64 | border-bottom: 1px solid #ff8c00; 65 | } 66 | 67 | .orders td:nth-child(4) { 68 | text-align: center; 69 | } 70 | 71 | @media (max-width: 1000px) { 72 | .account-container h1 { 73 | font-size: 24px; 74 | } 75 | 76 | .account { 77 | flex-direction: column; 78 | } 79 | 80 | .account-info { 81 | width: 100%; 82 | } 83 | 84 | .account-history { 85 | width: 100%; 86 | } 87 | } -------------------------------------------------------------------------------- /client/src/styles/AccountModal.css: -------------------------------------------------------------------------------- 1 | .account-modal { 2 | width: 90%; 3 | max-width: 385px; 4 | height: 375px; 5 | margin: auto; 6 | margin-top: 100px; 7 | background: white; 8 | border: 1px solid white; 9 | border-radius: 5px; 10 | outline: none; 11 | } 12 | 13 | .account-modal .form { 14 | width: 70%; 15 | margin: auto; 16 | margin-top: 10px; 17 | text-align: center; 18 | font-family: 'Roboto'; 19 | } 20 | 21 | .account-modal .form h1 { 22 | color: #ff8c00; 23 | } 24 | 25 | .account-modal .form .btn { 26 | margin: 20px auto; 27 | } -------------------------------------------------------------------------------- /client/src/styles/Cart.css: -------------------------------------------------------------------------------- 1 | .cart-container { 2 | min-height: 100vh; 3 | margin: 0 6px; 4 | margin-top: -72px; 5 | border: 1px solid #ffffff00; 6 | } 7 | 8 | .cart-container h1 { 9 | margin-top: 100px; 10 | margin-bottom: 40px; 11 | } 12 | 13 | .cart { 14 | display: flex; 15 | flex-direction: row; 16 | justify-content: space-between; 17 | } 18 | 19 | .cart-info { 20 | display: flex; 21 | flex-direction: column; 22 | width: 20%; 23 | height:100%; 24 | min-height: auto; 25 | background: #f5f5f5; 26 | } 27 | 28 | .cart-info p { 29 | padding: 0 10px; 30 | } 31 | 32 | .cart-info .total { 33 | font-size: 22px; 34 | color: #64DD17; 35 | } 36 | 37 | .cart-info .btns { 38 | text-align: center; 39 | } 40 | 41 | .cart-info .btn { 42 | width: 150px; 43 | margin: 10px auto; 44 | } 45 | 46 | .cart-items { 47 | width: 78%; 48 | } 49 | 50 | .cart-items img { 51 | max-width: 50px; 52 | } 53 | 54 | .cart-items table { 55 | width: 100%; 56 | border-spacing: 0; 57 | } 58 | 59 | .cart-items th { 60 | padding: 5px 5px; 61 | background-color: #ff8c00; 62 | color: white; 63 | text-align: left; 64 | } 65 | 66 | .cart-items th:first-child { 67 | width: 70px; 68 | } 69 | 70 | .cart-items th:last-child { 71 | width: 15px; 72 | } 73 | 74 | .cart-items td { 75 | padding: 12px 5px; 76 | text-align: left; 77 | border-bottom: 1px solid #ff8c00; 78 | } 79 | 80 | .cart-items td:nth-child(4) { 81 | text-align: center; 82 | } 83 | 84 | .cart-items a { 85 | color: inherit; 86 | text-decoration: none; 87 | } 88 | 89 | .cart-items button { 90 | padding: 1px 5px; 91 | background: none; 92 | color: #F44336; 93 | font-weight: bold; 94 | border: none; 95 | border-radius: 50%; 96 | cursor: pointer; 97 | } 98 | 99 | .cart-items button:hover { 100 | background: #F44336; 101 | color: white; 102 | } 103 | 104 | .cart-items h1 { 105 | text-align: center; 106 | margin-top: 100px; 107 | } 108 | 109 | @media (max-width: 1000px) { 110 | .cart-container h1 { 111 | margin-top: 100px; 112 | margin-bottom: 30px; 113 | font-size: 24px; 114 | } 115 | 116 | .cart { 117 | flex-direction: column; 118 | } 119 | 120 | .cart-info { 121 | flex-direction: row; 122 | width: 100%; 123 | } 124 | 125 | .cart-info .info { 126 | width: 50%; 127 | } 128 | 129 | .cart-info .btns { 130 | width: 50%; 131 | } 132 | 133 | .cart-info .btn { 134 | margin: 10px; 135 | } 136 | 137 | .cart-items { 138 | width: 100%; 139 | margin: 40px auto 60px auto; 140 | } 141 | } -------------------------------------------------------------------------------- /client/src/styles/CheckoutModal.css: -------------------------------------------------------------------------------- 1 | .ReactModalPortal .ReactModal__Overlay--after-open { 2 | background-color: #00000073 !important; 3 | } 4 | 5 | .checkout-modal { 6 | width: 90%; 7 | max-width: 500px; 8 | height: auto; 9 | margin: auto; 10 | margin-top: 100px; 11 | background: white; 12 | border: 1px solid white; 13 | border-radius: 5px; 14 | outline: none; 15 | } 16 | 17 | .checkout-modal .order { 18 | width: 90%; 19 | max-width: 450px; 20 | margin: auto; 21 | margin-top: 10px; 22 | text-align: center; 23 | font-family: 'Roboto'; 24 | } 25 | 26 | .checkout-modal h1 { 27 | margin-bottom: 60px; 28 | color: #ff8c00; 29 | } 30 | 31 | .checkout-modal table { 32 | width: 100%; 33 | margin-top: 40px; 34 | border-spacing: 0; 35 | } 36 | 37 | .checkout-modal th { 38 | padding: 5px 5px; 39 | background-color: #ff8c00; 40 | color: white; 41 | text-align: left; 42 | } 43 | 44 | .checkout-modal td { 45 | padding: 12px 5px; 46 | text-align: left; 47 | } 48 | 49 | .checkout-modal .total { 50 | text-align: left; 51 | } 52 | 53 | .checkout-modal .total span { 54 | font-size: 26px; 55 | font-weight: bold; 56 | color: #64DD17; 57 | } 58 | 59 | .checkout-modal .btns { 60 | margin-top: 40px; 61 | margin-bottom: 20px; 62 | } 63 | 64 | .checkout-modal .btns .btn { 65 | margin: 0 20px; 66 | } 67 | 68 | @media (max-height: 700px) { 69 | .checkout-modal { 70 | height: 440px; 71 | min-height: auto; 72 | overflow: scroll; 73 | } 74 | } -------------------------------------------------------------------------------- /client/src/styles/FiltersList.css: -------------------------------------------------------------------------------- 1 | .subheader { 2 | font-size: 16px !important; 3 | font-weight: bold !important; 4 | } 5 | 6 | .listItem { 7 | border-top: 1px solid #dcdcdc; 8 | } 9 | 10 | .checkbox { 11 | margin-left: 20px; 12 | } -------------------------------------------------------------------------------- /client/src/styles/Footer.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | width: 100%; 3 | height: auto; 4 | /* margin-top: -30px; */ 5 | background: #ff8c00; 6 | color: #c3e3e7; 7 | text-align: center; 8 | } 9 | 10 | .footer p { 11 | margin: 0; 12 | padding: 3px; 13 | } -------------------------------------------------------------------------------- /client/src/styles/Header.css: -------------------------------------------------------------------------------- 1 | .header > div { 2 | z-index: 0 !important; 3 | } 4 | 5 | .title h1 { 6 | font-family: 'Bungee Inline'; 7 | font-style: italic; 8 | } 9 | 10 | .menu { 11 | margin-top: 5px; 12 | } 13 | 14 | .icon-menu { 15 | display: none; 16 | } 17 | 18 | @media (max-width: 750px) { 19 | .menu { 20 | display: none; 21 | } 22 | 23 | .icon-menu { 24 | display: block; 25 | } 26 | } -------------------------------------------------------------------------------- /client/src/styles/Homepage.css: -------------------------------------------------------------------------------- 1 | .homepage-container { 2 | display: flex; 3 | justify-content: space-between; 4 | min-height: 100vh; 5 | margin: 0 6px; 6 | margin-top: -72px; 7 | border: 1px solid #ffffff00; 8 | } 9 | 10 | .filtersList-desktop { 11 | width: 20%; 12 | height:100%; 13 | min-height: auto; 14 | margin-top: 90px; 15 | margin-bottom: 40px; 16 | background-color: #f5f5f5; 17 | } 18 | 19 | @media (max-width: 1115px) { 20 | .filtersList-desktop { 21 | display: none; 22 | } 23 | } -------------------------------------------------------------------------------- /client/src/styles/LoginModal.css: -------------------------------------------------------------------------------- 1 | .login-modal { 2 | width: 90%; 3 | max-width: 385px; 4 | height: 360px; 5 | margin: auto; 6 | margin-top: 100px; 7 | background: white; 8 | color: #2e3a4e; 9 | border: 1px solid white; 10 | border-radius: 5px; 11 | outline: none; 12 | } 13 | 14 | .login-modal .form { 15 | width: 90%; 16 | max-width: 360px; 17 | margin: auto; 18 | margin-top: 10px; 19 | text-align: center; 20 | font-family: 'Roboto'; 21 | } 22 | 23 | .login-modal .form h1 { 24 | color: #ff8c00; 25 | } 26 | 27 | .login-modal .form .btn { 28 | margin: 20px auto; 29 | } 30 | 31 | .login-modal .form p { 32 | font-style: italic; 33 | } 34 | 35 | .login-modal .form a { 36 | color: #ff8c00; 37 | cursor: pointer; 38 | } -------------------------------------------------------------------------------- /client/src/styles/NotFound.css: -------------------------------------------------------------------------------- 1 | .not-found-container { 2 | min-height: 100vh; 3 | margin-top: -72px; 4 | border: 1px solid #ffffff00; 5 | } 6 | 7 | .not-found { 8 | width: 50%; 9 | min-width: 330px; 10 | margin: auto; 11 | margin-top: 200px; 12 | } 13 | 14 | .not-found h1 { 15 | font-size: 60px; 16 | color: #ff8c00; 17 | } 18 | 19 | .not-found h3 { 20 | font-size: 45px; 21 | } -------------------------------------------------------------------------------- /client/src/styles/OrderSuccessModal.css: -------------------------------------------------------------------------------- 1 | .order-success-modal { 2 | width: 90%; 3 | max-width: 385px; 4 | height: 410px; 5 | margin: auto; 6 | margin-top: 100px; 7 | background: white; 8 | border: 1px solid white; 9 | border-radius: 5px; 10 | outline: none; 11 | } 12 | 13 | .order-success-modal .success { 14 | width: 70%; 15 | margin: auto; 16 | margin-top: 10px; 17 | text-align: center; 18 | font-family: 'Roboto'; 19 | } 20 | 21 | .order-success-modal h1 { 22 | color: #ff8c00; 23 | } 24 | 25 | .order-success-modal img { 26 | max-height: 100px; 27 | margin: 30px 0; 28 | } 29 | 30 | .order-success-modal .btn { 31 | margin-top: 20px; 32 | } -------------------------------------------------------------------------------- /client/src/styles/Product.css: -------------------------------------------------------------------------------- 1 | .product { 2 | /* display: flex; 3 | flex-direction: row; */ 4 | width: 100%; 5 | /* height: 200px; 6 | max-height: 200px; */ 7 | height: auto; 8 | margin: 10px 0; 9 | box-shadow: 0 0 7px #d6d6d6; 10 | transition: .3s; 11 | } 12 | 13 | .product:hover { 14 | box-shadow: 0 0 7px #b3b3b3; 15 | } 16 | 17 | .content img { 18 | max-width: 170px; 19 | max-height: 170px; 20 | padding: 10px; 21 | } 22 | 23 | .content { 24 | display: flex; 25 | flex-direction: row; 26 | width: 100%; 27 | /* height: 190px; 28 | max-height: 190px; */ 29 | } 30 | 31 | .content-left { 32 | width: 80%; 33 | } 34 | 35 | .content-left h3 { 36 | margin: 10px 0 25px 0; 37 | } 38 | 39 | .content-left div { 40 | margin: 1.5px 0; 41 | } 42 | 43 | .content-right { 44 | width: 20%; 45 | } 46 | 47 | .content-right p { 48 | margin: 50px 0 0 0; 49 | } 50 | 51 | .content-right h2 { 52 | margin: 0 0 40px 0; 53 | font-size: 30px; 54 | color: #64DD17; 55 | } 56 | 57 | .content-info { 58 | width: 100% !important; 59 | text-align: left !important; 60 | } 61 | 62 | @media (max-width: 860px) { 63 | .content { 64 | flex-direction: column; 65 | } 66 | 67 | .content { 68 | height: auto; 69 | max-height: auto; 70 | } 71 | 72 | .content img { 73 | margin: auto; 74 | } 75 | 76 | .content-left, .content-right { 77 | width: 100%; 78 | margin: 10px 0; 79 | text-align: center; 80 | } 81 | 82 | .content-left h3, .content-left div { 83 | margin: 0; 84 | } 85 | 86 | .content-info { 87 | display: none; 88 | } 89 | } -------------------------------------------------------------------------------- /client/src/styles/ProductDetails.css: -------------------------------------------------------------------------------- 1 | .product-details-container { 2 | min-height: 100vh; 3 | margin: 0 6px; 4 | margin-top: -72px; 5 | border: 1px solid #ffffff00; 6 | } 7 | 8 | .product-details-container h1 { 9 | margin-top: 100px; 10 | margin-bottom: 20px; 11 | } 12 | 13 | .product-details { 14 | display: flex; 15 | flex-direction: row; 16 | } 17 | 18 | .product-image { 19 | width: 40%; 20 | } 21 | 22 | .product-image img { 23 | max-height: 364px; 24 | margin: 20px 0; 25 | } 26 | 27 | .product-info { 28 | width: 60%; 29 | text-align: center; 30 | } 31 | 32 | .product-info table { 33 | width: 100%; 34 | margin: 20px 0; 35 | } 36 | 37 | .product-info th { 38 | background-color: #ff8c00; 39 | color: white; 40 | } 41 | 42 | .product-info th, .product-info td { 43 | padding: 3px; 44 | text-align: left; 45 | border: 1px solid #ff8c00; 46 | } 47 | 48 | .price-text { 49 | font-size: 16px; 50 | font-weight: bold; 51 | } 52 | 53 | .price-num { 54 | font-size: 30px; 55 | font-weight: bold; 56 | color: #64DD17; 57 | } 58 | 59 | .product-handle { 60 | display: flex; 61 | flex-direction: row; 62 | margin: 10px 0 20px 0; 63 | } 64 | 65 | .product-handle .left { 66 | width: 40%; 67 | } 68 | 69 | .product-handle .left .btn { 70 | margin: 10px auto; 71 | } 72 | 73 | .product-handle .right { 74 | display: flex; 75 | flex-direction: row; 76 | justify-content: flex-start; 77 | width: 60%; 78 | } 79 | 80 | .right .price { 81 | width: 200px; 82 | margin: 5px 0; 83 | } 84 | 85 | .right .quantity { 86 | width: 130px; 87 | text-align: center; 88 | } 89 | 90 | .right .quantity input { 91 | width: 30px; 92 | height: 28px; 93 | margin: 10px; 94 | font-size: 17px; 95 | } 96 | 97 | .right .btn { 98 | width: 160px; 99 | margin: 10px 0; 100 | text-align: center; 101 | } 102 | 103 | @media (max-width: 1000px) { 104 | .product-details-container h1 { 105 | font-size: 24px; 106 | } 107 | 108 | .product-details { 109 | flex-direction: column; 110 | } 111 | 112 | .product-image { 113 | width: 100%; 114 | margin: 30px auto; 115 | text-align: center; 116 | } 117 | 118 | .product-image img { 119 | max-height: 300px; 120 | margin: auto; 121 | } 122 | 123 | .product-info { 124 | width: 100%; 125 | } 126 | } 127 | 128 | @media (max-width: 900px) { 129 | .product-handle { 130 | flex-direction: column; 131 | } 132 | 133 | .product-handle .left { 134 | width: 100%; 135 | order: 2; 136 | } 137 | 138 | .product-handle .right { 139 | width: 100%; 140 | order: 1; 141 | } 142 | } 143 | 144 | @media (max-width: 650px) { 145 | .product-handle .right { 146 | flex-direction: column; 147 | } 148 | 149 | .right .price, .right .quantity, .right .btn { 150 | width: 100%; 151 | } 152 | 153 | .right .price, .left { 154 | text-align: center; 155 | } 156 | } -------------------------------------------------------------------------------- /client/src/styles/Products.css: -------------------------------------------------------------------------------- 1 | .loader { 2 | margin-top: 150px; 3 | color: #ff8c00; 4 | text-align: center; 5 | } 6 | 7 | .no-products { 8 | margin-top: 150px; 9 | text-align: center; 10 | } 11 | 12 | .products { 13 | width: 78%; 14 | margin-top: 90px; 15 | margin-bottom: 10px; 16 | } 17 | 18 | .products-handle { 19 | display: flex; 20 | flex-direction: row; 21 | border-bottom: 1px solid #81D4FA; 22 | } 23 | 24 | .products-handle .products-found { 25 | width: 20%; 26 | margin-top: 16px; 27 | } 28 | 29 | .products-handle .filters { 30 | width: 20%; 31 | text-align: center; 32 | } 33 | 34 | .products-handle .filters .btn { 35 | width: 150px; 36 | margin: 5px; 37 | } 38 | 39 | .products-handle .set-filters { 40 | display: none; 41 | } 42 | 43 | .products-handle .products-sort { 44 | display: flex; 45 | justify-content: flex-end; 46 | width: 60%; 47 | } 48 | 49 | .products-handle .products-sort span { 50 | margin-top: 15px; 51 | margin-right: 5px; 52 | } 53 | 54 | .products-handle .products-sort .sort-field { 55 | margin-top: 0; 56 | } 57 | 58 | @media (max-width: 1115px) { 59 | .products { 60 | width: 100%; 61 | } 62 | 63 | .products-handle { 64 | padding-bottom: 10px; 65 | } 66 | 67 | .products-handle .products-found { 68 | margin-top: 42px; 69 | } 70 | 71 | .products-handle .set-filters { 72 | display: block; 73 | } 74 | 75 | .products-handle .products-sort span { 76 | margin-top: 41px; 77 | } 78 | 79 | .products-handle .products-sort .sort-field { 80 | margin-top: 26px; 81 | } 82 | } 83 | 84 | @media (max-width: 850px) { 85 | .products-handle { 86 | flex-direction: column; 87 | } 88 | 89 | .products-handle .products-found, .products-handle .filters, .products-handle .products-sort { 90 | width: 100%; 91 | margin: 5px 0; 92 | } 93 | 94 | .products-handle .products-sort { 95 | justify-content: center; 96 | } 97 | 98 | .products-handle .products-sort span { 99 | margin-top: 13px; 100 | } 101 | 102 | .products-handle .products-sort .sort-field { 103 | margin-top: 0; 104 | } 105 | } -------------------------------------------------------------------------------- /client/src/styles/RegisterModal.css: -------------------------------------------------------------------------------- 1 | .register-modal { 2 | width: 90%; 3 | max-width: 385px; 4 | height: 570px; 5 | margin: auto; 6 | margin-top: 100px; 7 | background: white; 8 | border: 1px solid white; 9 | border-radius: 5px; 10 | outline: none; 11 | } 12 | 13 | .register-modal .form { 14 | width: 90%; 15 | max-width: 360px; 16 | margin: auto; 17 | margin-top: 10px; 18 | text-align: center; 19 | font-family: 'Roboto'; 20 | } 21 | 22 | .register-modal .form h1 { 23 | color: #ff8c00; 24 | } 25 | 26 | .register-modal .form .btn { 27 | margin: 20px auto; 28 | } 29 | 30 | .register-modal .form p { 31 | font-style: italic; 32 | } 33 | 34 | .register-modal .form a { 35 | color: #ff8c00; 36 | cursor: pointer; 37 | } 38 | 39 | @media (max-height: 700px) { 40 | .register-modal { 41 | height: 500px; 42 | overflow: scroll; 43 | } 44 | } -------------------------------------------------------------------------------- /client/src/styles/ShoppingCart.css: -------------------------------------------------------------------------------- 1 | button:focus { 2 | outline: 0; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | } 8 | 9 | .container { 10 | width: 100%; 11 | max-width: 1200px; 12 | margin: auto; 13 | font-family: 'Roboto'; 14 | color: #2e3a4edc; 15 | } -------------------------------------------------------------------------------- /client/src/typings/action.ts: -------------------------------------------------------------------------------- 1 | export type actionTypes = 2 | 'GET_USER' | 'GET_USER_SUCCESS' | 'GET_USER_FAIL' | 3 | 'INIT_CATALOG' | 'INIT_CATALOG_SUCCESS' | 'INIT_CATALOG_FAIL' | 4 | 'GET_CART' | 'GET_CART_SUCCESS' | 'GET_CART_FAIL' | 5 | 'SET_FILTER' | 'CLEAR_FILTERS' | 6 | 'SET_SORT_BY'; 7 | -------------------------------------------------------------------------------- /client/src/typings/filters.ts: -------------------------------------------------------------------------------- 1 | export type filterTypes = 2 | 'priceRange' | 3 | 'brand' | 4 | 'color' | 5 | 'os' | 6 | 'internalMemory' | 7 | 'ram' | 8 | 'displaySize' | 9 | 'displayResolution' | 10 | 'camera' | 11 | 'cpu'; 12 | 13 | export type filterValues = 14 | '<250' | '250-500' | '500-750' | '750>' | 15 | 'samsung' | 'apple' | 'huawei' | 'lg' | 'htc' | 16 | 'black' | 'white' | 'grey' | 17 | 'android' | 'ios' | 18 | '16' | '64' | '128' | '256' | 19 | '1' | '3' | '4' | '6' | 20 | '4.5' | '5.1' | '5.5' | '5.8' | '6.0' | '6.3' | 21 | '540x960' | '1080x1920' | '1125x2436' | '1440x2560' | '1440x2880' | '1440x2960' | 22 | '8' | '12' | '13' | '16' | 23 | 'quad_core' | 'hexa_core' | 'octa_core'; 24 | -------------------------------------------------------------------------------- /client/src/typings/modal.ts: -------------------------------------------------------------------------------- 1 | import { ICartProduct } from './state/index'; 2 | 3 | export type modal = 4 | null | 5 | 'snackbar' | 6 | 'checkout' | 7 | 'orderSuccess' | 8 | 'dialog' | 9 | 'login' | 10 | 'register'; 11 | 12 | export interface ModalProps { 13 | isOpen: boolean; 14 | setActiveModal: (modal: modal) => void; 15 | onRequestClose?: (event: MouseEvent) => void; 16 | cart?: ICartProduct[]; 17 | makeOrder?: () => void; 18 | } 19 | -------------------------------------------------------------------------------- /client/src/typings/state/cart.ts: -------------------------------------------------------------------------------- 1 | import { ICartProduct } from './cartProduct'; 2 | 3 | export interface ICart { 4 | isLoading: boolean; 5 | isLoaded: boolean; 6 | _id: string | null; 7 | items: ICartProduct[]; 8 | error: string | null; 9 | } 10 | -------------------------------------------------------------------------------- /client/src/typings/state/cartProduct.ts: -------------------------------------------------------------------------------- 1 | import { ICatalogProduct } from './catalogProduct'; 2 | 3 | export interface ICartProduct { 4 | product: ICatalogProduct; 5 | _id: string; 6 | quantity?: number; 7 | } 8 | -------------------------------------------------------------------------------- /client/src/typings/state/catalog.ts: -------------------------------------------------------------------------------- 1 | import { ICatalogProduct } from './catalogProduct'; 2 | 3 | export interface ICatalog { 4 | isLoading: boolean; 5 | isLoaded: boolean; 6 | items: ICatalogProduct[]; 7 | error: string | null; 8 | } 9 | -------------------------------------------------------------------------------- /client/src/typings/state/catalogProduct.ts: -------------------------------------------------------------------------------- 1 | export interface ICatalogProduct { 2 | info: { 3 | name: string; 4 | dimensions: string; 5 | weight: string; 6 | displayType: string; 7 | displaySize: string; 8 | displayResolution: string; 9 | os: string; 10 | cpu: string; 11 | internalMemory: string; 12 | ram: string; 13 | camera: string; 14 | batery: string; 15 | color: string; 16 | price: number; 17 | photo: string; 18 | }; 19 | tags: { 20 | priceRange: string; 21 | brand: string; 22 | color: string; 23 | os: string; 24 | internalMemory: string; 25 | ram: string; 26 | displaySize: string; 27 | displayResolution: string; 28 | camera: string; 29 | cpu: string; 30 | }; 31 | _id: string; 32 | } 33 | -------------------------------------------------------------------------------- /client/src/typings/state/filters.ts: -------------------------------------------------------------------------------- 1 | export interface IFilters { 2 | filters: { 3 | priceRange: string[]; 4 | brand: string[]; 5 | color: string[]; 6 | os: string[]; 7 | internalMemory: string[]; 8 | ram: string[]; 9 | displaySize: string[]; 10 | displayResolution: string[]; 11 | camera: string[]; 12 | cpu: string[]; 13 | } 14 | checked: string[]; 15 | } 16 | -------------------------------------------------------------------------------- /client/src/typings/state/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cart'; 2 | export * from './cartProduct'; 3 | export * from './catalogProduct'; 4 | export * from './catalog'; 5 | export * from './filters'; 6 | export * from './loggedUser'; 7 | export * from './order'; 8 | export * from './state'; 9 | export * from './user'; 10 | export * from './sortBy'; 11 | -------------------------------------------------------------------------------- /client/src/typings/state/loggedUser.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from './user'; 2 | 3 | export interface ILoggedUser { 4 | isLoading: boolean; 5 | isLoaded: boolean; 6 | user: IUser | null; 7 | error: string | null; 8 | } 9 | -------------------------------------------------------------------------------- /client/src/typings/state/order.ts: -------------------------------------------------------------------------------- 1 | export interface IOrder { 2 | name: string; 3 | price: string; 4 | quantity: string; 5 | dateCreated: string; 6 | } 7 | -------------------------------------------------------------------------------- /client/src/typings/state/sortBy.ts: -------------------------------------------------------------------------------- 1 | export type TSortBy = 2 | 'Name: A-Z' | 3 | 'Name: Z-A' | 4 | 'Price: Low to High' | 5 | 'Price: High to Low'; 6 | -------------------------------------------------------------------------------- /client/src/typings/state/state.ts: -------------------------------------------------------------------------------- 1 | import { RouterState} from 'connected-react-router'; 2 | import { ILoggedUser } from './loggedUser'; 3 | import { ICart } from './cart'; 4 | import { ICatalog } from './catalog'; 5 | import { IFilters } from './filters'; 6 | import { TSortBy } from './sortBy'; 7 | 8 | export interface IState { 9 | router: RouterState; 10 | loggedUser: ILoggedUser; 11 | cart: ICart; 12 | catalog: ICatalog; 13 | filters: IFilters; 14 | sortBy: TSortBy; 15 | } 16 | -------------------------------------------------------------------------------- /client/src/typings/state/user.ts: -------------------------------------------------------------------------------- 1 | import { IOrder } from './order'; 2 | 3 | export interface IUser { 4 | orders: IOrder[]; 5 | username: string; 6 | email: string; 7 | address: string; 8 | phone: string; 9 | _id: string; 10 | } 11 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "public/dist", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": ["es2017","dom"], 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "jsx": "react", 11 | "moduleResolution": "node", 12 | "rootDir": ".", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "noUnusedLocals": true, 20 | "paths": { 21 | "@actions/*": ["src/actions/*"], 22 | "@selectors/*": ["src/selectors/*"], 23 | "@styles/*": ["src/styles/*"], 24 | "@typings/*": ["src/typings/*"] 25 | } 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "dist", 30 | "webpack", 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /client/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: ['babel-regenerator-runtime', './src/index.tsx'], 7 | output: { 8 | path: path.resolve(__dirname, 'public', 'dist'), 9 | filename: 'bundle.js' 10 | }, 11 | resolve: { 12 | extensions: ['.ts', '.tsx', '.js'], 13 | plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })], 14 | alias: { 15 | '@actions': path.resolve(__dirname, 'src/actions'), 16 | '@selectors': path.resolve(__dirname, 'src/selectors'), 17 | '@styles': path.resolve(__dirname, 'src/styles'), 18 | '@typings': path.resolve(__dirname, 'src/typings') 19 | } 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.tsx?$/, 25 | loader: 'ts-loader' 26 | }, 27 | { 28 | test: /\.js$/, 29 | exclude: /node_modules/, 30 | loader: 'babel-loader' 31 | }, 32 | { 33 | test: /\.css$/, 34 | use: ExtractTextPlugin.extract({ 35 | fallback: 'style-loader', 36 | use: ['css-loader', 'postcss-loader'] 37 | }) 38 | }, 39 | { 40 | test: /\.(gif|png|jpe?g|svg)$/i, 41 | use: [ 42 | 'file-loader', 43 | { 44 | loader: 'image-webpack-loader', 45 | options: { 46 | bypassOnDebug: true, 47 | } 48 | } 49 | ] 50 | } 51 | ] 52 | }, 53 | plugins: [ 54 | new ExtractTextPlugin('styles.css') 55 | ], 56 | devtool: 'source-map', 57 | devServer: { 58 | contentBase: path.join(__dirname, 'public'), 59 | historyApiFallback: true, 60 | publicPath: '/dist', 61 | proxy: [{ 62 | context: ['/auth/*', '/api/*'], 63 | target: 'http://localhost:5000', 64 | }] 65 | } 66 | } -------------------------------------------------------------------------------- /config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | MONGODB_URI: process.env.MONGODB_URI, 3 | sessionSecret: process.env.SESSION_SECRET 4 | }; -------------------------------------------------------------------------------- /config/privates.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./prod'); 3 | } else { 4 | module.exports = require('./dev'); 5 | } -------------------------------------------------------------------------------- /config/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | MONGODB_URI: process.env.MONGODB_URI, 3 | sessionSecret: process.env.SESSION_SECRET 4 | }; 5 | -------------------------------------------------------------------------------- /middleware/requireLogin.js: -------------------------------------------------------------------------------- 1 | module.exports = (req, res, next) => { 2 | if(!req.user) { 3 | return res.status(401).send({ error: 'You must log in!' }); 4 | } 5 | next(); 6 | }; -------------------------------------------------------------------------------- /models/Cart.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const cartSchema = new mongoose.Schema({ 4 | user: { 5 | type: mongoose.Schema.Types.ObjectId, 6 | ref: 'User' 7 | }, 8 | items: [ 9 | { 10 | product: { 11 | type: mongoose.Schema.Types.ObjectId, 12 | ref: 'Product' 13 | }, 14 | quantity: Number 15 | } 16 | ] 17 | }); 18 | 19 | module.exports = mongoose.model('Cart', cartSchema); -------------------------------------------------------------------------------- /models/Product.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const productSchema = new mongoose.Schema({ 4 | info: { 5 | name: String, 6 | dimensions: String, 7 | weight: String, 8 | displayType: String, 9 | displaySize: String, 10 | displayResolution: String, 11 | os: String, 12 | cpu: String, 13 | internalMemory: String, 14 | ram: String, 15 | camera: String, 16 | batery: String, 17 | color: String, 18 | price: Number, 19 | photo: String 20 | }, 21 | tags: { 22 | priceRange: String, 23 | brand: String, 24 | color: String, 25 | os: String, 26 | internalMemory: String, 27 | ram: String, 28 | displaySize: String, 29 | displayResolution: String, 30 | camera: String, 31 | cpu: String 32 | } 33 | }); 34 | 35 | module.exports = mongoose.model('Product', productSchema); -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const passportLocalMongoose = require('passport-local-mongoose'); 3 | 4 | const userSchema = new mongoose.Schema({ 5 | username: String, 6 | password: String, 7 | email: String, 8 | address: String, 9 | phone: String, 10 | orders: [] 11 | }); 12 | 13 | userSchema.plugin(passportLocalMongoose); 14 | 15 | module.exports = mongoose.model('User', userSchema); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fullstack-shopping-cart", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "ansi-styles": { 17 | "version": "3.2.1", 18 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 19 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 20 | "requires": { 21 | "color-convert": "^1.9.0" 22 | } 23 | }, 24 | "array-flatten": { 25 | "version": "1.1.1", 26 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 27 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 28 | }, 29 | "bluebird": { 30 | "version": "3.5.1", 31 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 32 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 33 | }, 34 | "body-parser": { 35 | "version": "1.19.0", 36 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 37 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 38 | "requires": { 39 | "bytes": "3.1.0", 40 | "content-type": "~1.0.4", 41 | "debug": "2.6.9", 42 | "depd": "~1.1.2", 43 | "http-errors": "1.7.2", 44 | "iconv-lite": "0.4.24", 45 | "on-finished": "~2.3.0", 46 | "qs": "6.7.0", 47 | "raw-body": "2.4.0", 48 | "type-is": "~1.6.17" 49 | } 50 | }, 51 | "bson": { 52 | "version": "1.1.3", 53 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.3.tgz", 54 | "integrity": "sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg==" 55 | }, 56 | "bytes": { 57 | "version": "3.1.0", 58 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 59 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 60 | }, 61 | "chalk": { 62 | "version": "2.4.2", 63 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 64 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 65 | "requires": { 66 | "ansi-styles": "^3.2.1", 67 | "escape-string-regexp": "^1.0.5", 68 | "supports-color": "^5.3.0" 69 | }, 70 | "dependencies": { 71 | "supports-color": { 72 | "version": "5.5.0", 73 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 74 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 75 | "requires": { 76 | "has-flag": "^3.0.0" 77 | } 78 | } 79 | } 80 | }, 81 | "color-convert": { 82 | "version": "1.9.3", 83 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 84 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 85 | "requires": { 86 | "color-name": "1.1.3" 87 | } 88 | }, 89 | "color-name": { 90 | "version": "1.1.3", 91 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 92 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 93 | }, 94 | "commander": { 95 | "version": "2.6.0", 96 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.6.0.tgz", 97 | "integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=" 98 | }, 99 | "concurrently": { 100 | "version": "3.6.1", 101 | "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-3.6.1.tgz", 102 | "integrity": "sha512-/+ugz+gwFSEfTGUxn0KHkY+19XPRTXR8+7oUK/HxgiN1n7FjeJmkrbSiXAJfyQ0zORgJYPaenmymwon51YXH9Q==", 103 | "requires": { 104 | "chalk": "^2.4.1", 105 | "commander": "2.6.0", 106 | "date-fns": "^1.23.0", 107 | "lodash": "^4.5.1", 108 | "read-pkg": "^3.0.0", 109 | "rx": "2.3.24", 110 | "spawn-command": "^0.0.2-1", 111 | "supports-color": "^3.2.3", 112 | "tree-kill": "^1.1.0" 113 | } 114 | }, 115 | "content-disposition": { 116 | "version": "0.5.3", 117 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 118 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 119 | "requires": { 120 | "safe-buffer": "5.1.2" 121 | } 122 | }, 123 | "content-type": { 124 | "version": "1.0.4", 125 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 126 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 127 | }, 128 | "cookie": { 129 | "version": "0.4.0", 130 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 131 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 132 | }, 133 | "cookie-signature": { 134 | "version": "1.0.6", 135 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 136 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 137 | }, 138 | "date-fns": { 139 | "version": "1.30.1", 140 | "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", 141 | "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" 142 | }, 143 | "debug": { 144 | "version": "2.6.9", 145 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 146 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 147 | "requires": { 148 | "ms": "2.0.0" 149 | } 150 | }, 151 | "depd": { 152 | "version": "1.1.2", 153 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 154 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 155 | }, 156 | "destroy": { 157 | "version": "1.0.4", 158 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 159 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 160 | }, 161 | "dotenv": { 162 | "version": "8.2.0", 163 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 164 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 165 | }, 166 | "ee-first": { 167 | "version": "1.1.1", 168 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 169 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 170 | }, 171 | "encodeurl": { 172 | "version": "1.0.2", 173 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 174 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 175 | }, 176 | "error-ex": { 177 | "version": "1.3.2", 178 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 179 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 180 | "requires": { 181 | "is-arrayish": "^0.2.1" 182 | } 183 | }, 184 | "escape-html": { 185 | "version": "1.0.3", 186 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 187 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 188 | }, 189 | "escape-string-regexp": { 190 | "version": "1.0.5", 191 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 192 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 193 | }, 194 | "etag": { 195 | "version": "1.8.1", 196 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 197 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 198 | }, 199 | "express": { 200 | "version": "4.17.1", 201 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 202 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 203 | "requires": { 204 | "accepts": "~1.3.7", 205 | "array-flatten": "1.1.1", 206 | "body-parser": "1.19.0", 207 | "content-disposition": "0.5.3", 208 | "content-type": "~1.0.4", 209 | "cookie": "0.4.0", 210 | "cookie-signature": "1.0.6", 211 | "debug": "2.6.9", 212 | "depd": "~1.1.2", 213 | "encodeurl": "~1.0.2", 214 | "escape-html": "~1.0.3", 215 | "etag": "~1.8.1", 216 | "finalhandler": "~1.1.2", 217 | "fresh": "0.5.2", 218 | "merge-descriptors": "1.0.1", 219 | "methods": "~1.1.2", 220 | "on-finished": "~2.3.0", 221 | "parseurl": "~1.3.3", 222 | "path-to-regexp": "0.1.7", 223 | "proxy-addr": "~2.0.5", 224 | "qs": "6.7.0", 225 | "range-parser": "~1.2.1", 226 | "safe-buffer": "5.1.2", 227 | "send": "0.17.1", 228 | "serve-static": "1.14.1", 229 | "setprototypeof": "1.1.1", 230 | "statuses": "~1.5.0", 231 | "type-is": "~1.6.18", 232 | "utils-merge": "1.0.1", 233 | "vary": "~1.1.2" 234 | }, 235 | "dependencies": { 236 | "body-parser": { 237 | "version": "1.19.0", 238 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 239 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 240 | "requires": { 241 | "bytes": "3.1.0", 242 | "content-type": "~1.0.4", 243 | "debug": "2.6.9", 244 | "depd": "~1.1.2", 245 | "http-errors": "1.7.2", 246 | "iconv-lite": "0.4.24", 247 | "on-finished": "~2.3.0", 248 | "qs": "6.7.0", 249 | "raw-body": "2.4.0", 250 | "type-is": "~1.6.17" 251 | } 252 | } 253 | } 254 | }, 255 | "express-session": { 256 | "version": "1.17.0", 257 | "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz", 258 | "integrity": "sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg==", 259 | "requires": { 260 | "cookie": "0.4.0", 261 | "cookie-signature": "1.0.6", 262 | "debug": "2.6.9", 263 | "depd": "~2.0.0", 264 | "on-headers": "~1.0.2", 265 | "parseurl": "~1.3.3", 266 | "safe-buffer": "5.2.0", 267 | "uid-safe": "~2.1.5" 268 | }, 269 | "dependencies": { 270 | "depd": { 271 | "version": "2.0.0", 272 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 273 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 274 | }, 275 | "safe-buffer": { 276 | "version": "5.2.0", 277 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 278 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 279 | } 280 | } 281 | }, 282 | "finalhandler": { 283 | "version": "1.1.2", 284 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 285 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 286 | "requires": { 287 | "debug": "2.6.9", 288 | "encodeurl": "~1.0.2", 289 | "escape-html": "~1.0.3", 290 | "on-finished": "~2.3.0", 291 | "parseurl": "~1.3.3", 292 | "statuses": "~1.5.0", 293 | "unpipe": "~1.0.0" 294 | } 295 | }, 296 | "forwarded": { 297 | "version": "0.1.2", 298 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 299 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 300 | }, 301 | "fresh": { 302 | "version": "0.5.2", 303 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 304 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 305 | }, 306 | "generaterr": { 307 | "version": "1.5.0", 308 | "resolved": "https://registry.npmjs.org/generaterr/-/generaterr-1.5.0.tgz", 309 | "integrity": "sha1-sM62zFFk3yoGEzjMNAqGFTlcUvw=" 310 | }, 311 | "graceful-fs": { 312 | "version": "4.2.3", 313 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", 314 | "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" 315 | }, 316 | "has-flag": { 317 | "version": "3.0.0", 318 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 319 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 320 | }, 321 | "hosted-git-info": { 322 | "version": "2.8.5", 323 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", 324 | "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==" 325 | }, 326 | "http-errors": { 327 | "version": "1.7.2", 328 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 329 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 330 | "requires": { 331 | "depd": "~1.1.2", 332 | "inherits": "2.0.3", 333 | "setprototypeof": "1.1.1", 334 | "statuses": ">= 1.5.0 < 2", 335 | "toidentifier": "1.0.0" 336 | } 337 | }, 338 | "iconv-lite": { 339 | "version": "0.4.24", 340 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 341 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 342 | "requires": { 343 | "safer-buffer": ">= 2.1.2 < 3" 344 | } 345 | }, 346 | "inherits": { 347 | "version": "2.0.3", 348 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 349 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 350 | }, 351 | "ipaddr.js": { 352 | "version": "1.9.0", 353 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", 354 | "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" 355 | }, 356 | "is-arrayish": { 357 | "version": "0.2.1", 358 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 359 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" 360 | }, 361 | "json-parse-better-errors": { 362 | "version": "1.0.2", 363 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 364 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" 365 | }, 366 | "kareem": { 367 | "version": "2.3.1", 368 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", 369 | "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" 370 | }, 371 | "load-json-file": { 372 | "version": "4.0.0", 373 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", 374 | "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", 375 | "requires": { 376 | "graceful-fs": "^4.1.2", 377 | "parse-json": "^4.0.0", 378 | "pify": "^3.0.0", 379 | "strip-bom": "^3.0.0" 380 | } 381 | }, 382 | "lodash": { 383 | "version": "4.17.19", 384 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", 385 | "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" 386 | }, 387 | "media-typer": { 388 | "version": "0.3.0", 389 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 390 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 391 | }, 392 | "memory-pager": { 393 | "version": "1.5.0", 394 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 395 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 396 | "optional": true 397 | }, 398 | "merge-descriptors": { 399 | "version": "1.0.1", 400 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 401 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 402 | }, 403 | "methods": { 404 | "version": "1.1.2", 405 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 406 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 407 | }, 408 | "mime": { 409 | "version": "1.6.0", 410 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 411 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 412 | }, 413 | "mime-db": { 414 | "version": "1.43.0", 415 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", 416 | "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" 417 | }, 418 | "mime-types": { 419 | "version": "2.1.26", 420 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", 421 | "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", 422 | "requires": { 423 | "mime-db": "1.43.0" 424 | } 425 | }, 426 | "mongodb": { 427 | "version": "3.4.1", 428 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.4.1.tgz", 429 | "integrity": "sha512-juqt5/Z42J4DcE7tG7UdVaTKmUC6zinF4yioPfpeOSNBieWSK6qCY+0tfGQcHLKrauWPDdMZVROHJOa8q2pWsA==", 430 | "requires": { 431 | "bson": "^1.1.1", 432 | "require_optional": "^1.0.1", 433 | "safe-buffer": "^5.1.2", 434 | "saslprep": "^1.0.0" 435 | } 436 | }, 437 | "mongoose": { 438 | "version": "5.8.11", 439 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.8.11.tgz", 440 | "integrity": "sha512-Yz0leNEJsAtNtMTxTDEadacLWt58gaVeBVL3c1Z3vaBoc159aJqlf+T8jaL9mAdBxKndF5YWhh6Q719xac7cjA==", 441 | "requires": { 442 | "bson": "~1.1.1", 443 | "kareem": "2.3.1", 444 | "mongodb": "3.4.1", 445 | "mongoose-legacy-pluralize": "1.0.2", 446 | "mpath": "0.6.0", 447 | "mquery": "3.2.2", 448 | "ms": "2.1.2", 449 | "regexp-clone": "1.0.0", 450 | "safe-buffer": "5.1.2", 451 | "sift": "7.0.1", 452 | "sliced": "1.0.1" 453 | }, 454 | "dependencies": { 455 | "ms": { 456 | "version": "2.1.2", 457 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 458 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 459 | } 460 | } 461 | }, 462 | "mongoose-legacy-pluralize": { 463 | "version": "1.0.2", 464 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 465 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 466 | }, 467 | "mpath": { 468 | "version": "0.6.0", 469 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.6.0.tgz", 470 | "integrity": "sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw==" 471 | }, 472 | "mquery": { 473 | "version": "3.2.2", 474 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", 475 | "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", 476 | "requires": { 477 | "bluebird": "3.5.1", 478 | "debug": "3.1.0", 479 | "regexp-clone": "^1.0.0", 480 | "safe-buffer": "5.1.2", 481 | "sliced": "1.0.1" 482 | }, 483 | "dependencies": { 484 | "debug": { 485 | "version": "3.1.0", 486 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 487 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 488 | "requires": { 489 | "ms": "2.0.0" 490 | } 491 | } 492 | } 493 | }, 494 | "ms": { 495 | "version": "2.0.0", 496 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 497 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 498 | }, 499 | "negotiator": { 500 | "version": "0.6.2", 501 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 502 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 503 | }, 504 | "normalize-package-data": { 505 | "version": "2.5.0", 506 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 507 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 508 | "requires": { 509 | "hosted-git-info": "^2.1.4", 510 | "resolve": "^1.10.0", 511 | "semver": "2 || 3 || 4 || 5", 512 | "validate-npm-package-license": "^3.0.1" 513 | } 514 | }, 515 | "on-finished": { 516 | "version": "2.3.0", 517 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 518 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 519 | "requires": { 520 | "ee-first": "1.1.1" 521 | } 522 | }, 523 | "on-headers": { 524 | "version": "1.0.2", 525 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 526 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" 527 | }, 528 | "parse-json": { 529 | "version": "4.0.0", 530 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 531 | "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", 532 | "requires": { 533 | "error-ex": "^1.3.1", 534 | "json-parse-better-errors": "^1.0.1" 535 | } 536 | }, 537 | "parseurl": { 538 | "version": "1.3.3", 539 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 540 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 541 | }, 542 | "passport": { 543 | "version": "0.4.1", 544 | "resolved": "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz", 545 | "integrity": "sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==", 546 | "requires": { 547 | "passport-strategy": "1.x.x", 548 | "pause": "0.0.1" 549 | } 550 | }, 551 | "passport-local": { 552 | "version": "1.0.0", 553 | "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", 554 | "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", 555 | "requires": { 556 | "passport-strategy": "1.x.x" 557 | } 558 | }, 559 | "passport-local-mongoose": { 560 | "version": "4.5.0", 561 | "resolved": "https://registry.npmjs.org/passport-local-mongoose/-/passport-local-mongoose-4.5.0.tgz", 562 | "integrity": "sha512-fd5dUdTcN37DA+CCL/7kGQ57ACs6NXyuNmWjrTj4kvTOFa7SA+msEYPYwpgwbc3Nc+4RUeMtEQW4fPMx7nDObQ==", 563 | "requires": { 564 | "debug": "^3.1.0", 565 | "generaterr": "^1.5.0", 566 | "passport-local": "^1.0.0", 567 | "scmp": "^2.0.0", 568 | "semver": "^5.4.1" 569 | }, 570 | "dependencies": { 571 | "debug": { 572 | "version": "3.2.6", 573 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 574 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 575 | "requires": { 576 | "ms": "^2.1.1" 577 | } 578 | }, 579 | "ms": { 580 | "version": "2.1.2", 581 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 582 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 583 | }, 584 | "passport-local": { 585 | "version": "1.0.0", 586 | "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", 587 | "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", 588 | "requires": { 589 | "passport-strategy": "1.x.x" 590 | } 591 | } 592 | } 593 | }, 594 | "passport-strategy": { 595 | "version": "1.0.0", 596 | "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", 597 | "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" 598 | }, 599 | "path-parse": { 600 | "version": "1.0.6", 601 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 602 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" 603 | }, 604 | "path-to-regexp": { 605 | "version": "0.1.7", 606 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 607 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 608 | }, 609 | "path-type": { 610 | "version": "3.0.0", 611 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", 612 | "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", 613 | "requires": { 614 | "pify": "^3.0.0" 615 | } 616 | }, 617 | "pause": { 618 | "version": "0.0.1", 619 | "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", 620 | "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" 621 | }, 622 | "pify": { 623 | "version": "3.0.0", 624 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 625 | "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" 626 | }, 627 | "proxy-addr": { 628 | "version": "2.0.5", 629 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", 630 | "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", 631 | "requires": { 632 | "forwarded": "~0.1.2", 633 | "ipaddr.js": "1.9.0" 634 | } 635 | }, 636 | "qs": { 637 | "version": "6.7.0", 638 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 639 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 640 | }, 641 | "random-bytes": { 642 | "version": "1.0.0", 643 | "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", 644 | "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" 645 | }, 646 | "range-parser": { 647 | "version": "1.2.1", 648 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 649 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 650 | }, 651 | "raw-body": { 652 | "version": "2.4.0", 653 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 654 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 655 | "requires": { 656 | "bytes": "3.1.0", 657 | "http-errors": "1.7.2", 658 | "iconv-lite": "0.4.24", 659 | "unpipe": "1.0.0" 660 | } 661 | }, 662 | "read-pkg": { 663 | "version": "3.0.0", 664 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", 665 | "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", 666 | "requires": { 667 | "load-json-file": "^4.0.0", 668 | "normalize-package-data": "^2.3.2", 669 | "path-type": "^3.0.0" 670 | } 671 | }, 672 | "regexp-clone": { 673 | "version": "1.0.0", 674 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", 675 | "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" 676 | }, 677 | "require_optional": { 678 | "version": "1.0.1", 679 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 680 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 681 | "requires": { 682 | "resolve-from": "^2.0.0", 683 | "semver": "^5.1.0" 684 | } 685 | }, 686 | "resolve": { 687 | "version": "1.15.1", 688 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", 689 | "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", 690 | "requires": { 691 | "path-parse": "^1.0.6" 692 | } 693 | }, 694 | "resolve-from": { 695 | "version": "2.0.0", 696 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 697 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 698 | }, 699 | "rx": { 700 | "version": "2.3.24", 701 | "resolved": "https://registry.npmjs.org/rx/-/rx-2.3.24.tgz", 702 | "integrity": "sha1-FPlQpCF9fjXapxu8vljv9o6ksrc=" 703 | }, 704 | "safe-buffer": { 705 | "version": "5.1.2", 706 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 707 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 708 | }, 709 | "safer-buffer": { 710 | "version": "2.1.2", 711 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 712 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 713 | }, 714 | "saslprep": { 715 | "version": "1.0.3", 716 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 717 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 718 | "optional": true, 719 | "requires": { 720 | "sparse-bitfield": "^3.0.3" 721 | } 722 | }, 723 | "scmp": { 724 | "version": "2.1.0", 725 | "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", 726 | "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" 727 | }, 728 | "semver": { 729 | "version": "5.7.1", 730 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 731 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 732 | }, 733 | "send": { 734 | "version": "0.17.1", 735 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 736 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 737 | "requires": { 738 | "debug": "2.6.9", 739 | "depd": "~1.1.2", 740 | "destroy": "~1.0.4", 741 | "encodeurl": "~1.0.2", 742 | "escape-html": "~1.0.3", 743 | "etag": "~1.8.1", 744 | "fresh": "0.5.2", 745 | "http-errors": "~1.7.2", 746 | "mime": "1.6.0", 747 | "ms": "2.1.1", 748 | "on-finished": "~2.3.0", 749 | "range-parser": "~1.2.1", 750 | "statuses": "~1.5.0" 751 | }, 752 | "dependencies": { 753 | "ms": { 754 | "version": "2.1.1", 755 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 756 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 757 | } 758 | } 759 | }, 760 | "serve-static": { 761 | "version": "1.14.1", 762 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 763 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 764 | "requires": { 765 | "encodeurl": "~1.0.2", 766 | "escape-html": "~1.0.3", 767 | "parseurl": "~1.3.3", 768 | "send": "0.17.1" 769 | } 770 | }, 771 | "setprototypeof": { 772 | "version": "1.1.1", 773 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 774 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 775 | }, 776 | "sift": { 777 | "version": "7.0.1", 778 | "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", 779 | "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" 780 | }, 781 | "sliced": { 782 | "version": "1.0.1", 783 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 784 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 785 | }, 786 | "sparse-bitfield": { 787 | "version": "3.0.3", 788 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 789 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 790 | "optional": true, 791 | "requires": { 792 | "memory-pager": "^1.0.2" 793 | } 794 | }, 795 | "spawn-command": { 796 | "version": "0.0.2-1", 797 | "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", 798 | "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=" 799 | }, 800 | "spdx-correct": { 801 | "version": "3.1.0", 802 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", 803 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", 804 | "requires": { 805 | "spdx-expression-parse": "^3.0.0", 806 | "spdx-license-ids": "^3.0.0" 807 | } 808 | }, 809 | "spdx-exceptions": { 810 | "version": "2.2.0", 811 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", 812 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" 813 | }, 814 | "spdx-expression-parse": { 815 | "version": "3.0.0", 816 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", 817 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", 818 | "requires": { 819 | "spdx-exceptions": "^2.1.0", 820 | "spdx-license-ids": "^3.0.0" 821 | } 822 | }, 823 | "spdx-license-ids": { 824 | "version": "3.0.5", 825 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", 826 | "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" 827 | }, 828 | "statuses": { 829 | "version": "1.5.0", 830 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 831 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 832 | }, 833 | "strip-bom": { 834 | "version": "3.0.0", 835 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 836 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" 837 | }, 838 | "supports-color": { 839 | "version": "3.2.3", 840 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", 841 | "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", 842 | "requires": { 843 | "has-flag": "^1.0.0" 844 | }, 845 | "dependencies": { 846 | "has-flag": { 847 | "version": "1.0.0", 848 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", 849 | "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" 850 | } 851 | } 852 | }, 853 | "toidentifier": { 854 | "version": "1.0.0", 855 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 856 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 857 | }, 858 | "tree-kill": { 859 | "version": "1.2.2", 860 | "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", 861 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" 862 | }, 863 | "type-is": { 864 | "version": "1.6.18", 865 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 866 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 867 | "requires": { 868 | "media-typer": "0.3.0", 869 | "mime-types": "~2.1.24" 870 | } 871 | }, 872 | "uid-safe": { 873 | "version": "2.1.5", 874 | "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", 875 | "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", 876 | "requires": { 877 | "random-bytes": "~1.0.0" 878 | } 879 | }, 880 | "unpipe": { 881 | "version": "1.0.0", 882 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 883 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 884 | }, 885 | "utils-merge": { 886 | "version": "1.0.1", 887 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 888 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 889 | }, 890 | "validate-npm-package-license": { 891 | "version": "3.0.4", 892 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 893 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 894 | "requires": { 895 | "spdx-correct": "^3.0.0", 896 | "spdx-expression-parse": "^3.0.0" 897 | } 898 | }, 899 | "vary": { 900 | "version": "1.1.2", 901 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 902 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 903 | } 904 | } 905 | } 906 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fullstack-shopping-cart", 3 | "version": "1.0.0", 4 | "description": "Laptop shopping cart", 5 | "main": "server.js", 6 | "author": "Ivan Jakimovski", 7 | "license": "ISC", 8 | "engines": { 9 | "node": "10.15.3" 10 | }, 11 | "scripts": { 12 | "start:dev": "nodemon server.js", 13 | "start:client": "npm run start:dev --prefix client", 14 | "dev": "concurrently \"npm run start:dev\" \"npm run start:client\"", 15 | "install:client": "npm install --prefix client", 16 | "build": "npm run build --prefix client", 17 | "start": "node server.js", 18 | "heroku-postbuild": "npm run install:client && npm run build" 19 | }, 20 | "dependencies": { 21 | "body-parser": "^1.19.0", 22 | "concurrently": "^3.5.1", 23 | "dotenv": "^8.2.0", 24 | "express": "^4.16.4", 25 | "express-session": "^1.15.6", 26 | "mongoose": "^5.5.12", 27 | "passport": "^0.4.0", 28 | "passport-local": "^1.0.0", 29 | "passport-local-mongoose": "^4.5.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /routes/authRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const passport = require('passport'); 3 | const User = require('../models/User'); 4 | 5 | const router = express.Router(); 6 | 7 | router.post('/register', (req, res) => { 8 | const newUser = { 9 | username: req.body.username, 10 | email: req.body.email, 11 | address: req.body.address, 12 | phone: req.body.phone, 13 | orders: [] 14 | }; 15 | 16 | User.register(newUser, req.body.password, () => { 17 | passport.authenticate('local')(req, res, () => res.redirect('/')); 18 | }); 19 | }); 20 | 21 | router.post('/login', passport.authenticate('local', { 22 | successRedirect: '/', 23 | failureRedirect: '/' 24 | }), (req, res) => { 25 | res.redirect('/'); 26 | }); 27 | 28 | router.get('/logout', (req, res) => { 29 | req.logout(); 30 | res.redirect('/'); 31 | }); 32 | 33 | module.exports = router; -------------------------------------------------------------------------------- /routes/cartRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const Cart = require('../models/Cart'); 4 | const requireLogin = require('../middleware/requireLogin'); 5 | 6 | const router = express.Router(); 7 | const jsonParser = bodyParser.json(); 8 | 9 | router.post('/', requireLogin, jsonParser, (req, res) => { 10 | const user = req.body.user; 11 | const item = { 12 | product: req.body.product, 13 | quantity: req.body.quantity 14 | }; 15 | 16 | Cart.findOne({ user: user }) 17 | .then((foundCart) => { 18 | if (foundCart) { 19 | let products = foundCart.items.map((item) => item.product + ''); 20 | if (products.includes(item.product)) { 21 | Cart.findOneAndUpdate({ 22 | user: user, 23 | items: { 24 | $elemMatch: { product: item.product } 25 | } 26 | }, 27 | { 28 | $inc: { 'items.$.quantity': item.quantity } 29 | }) 30 | .exec() 31 | .then(() => res.end()); 32 | } else { 33 | foundCart.items.push(item); 34 | foundCart.save().then(() => res.end()); 35 | } 36 | } else { 37 | Cart.create({ 38 | user: user, 39 | items: [item] 40 | }) 41 | .then(() => res.end()); 42 | } 43 | }); 44 | }); 45 | 46 | router.get('/', requireLogin, (req, res) => { 47 | Cart.findOne({ user: req.user.id }) 48 | .populate('items.product') 49 | .exec((err, cart) => { 50 | if (!cart) { 51 | return res.send(null); 52 | } 53 | 54 | res.send(cart); 55 | }); 56 | }); 57 | 58 | router.put('/', requireLogin, jsonParser, (req, res) => { 59 | Cart.findById(req.body.cartId) 60 | .then((foundCart) => { 61 | foundCart.items = foundCart.items.filter((item) => item._id != req.body.itemId); 62 | foundCart.save(() => res.end()); 63 | }); 64 | }); 65 | 66 | router.delete('/', requireLogin, (req, res) => { 67 | Cart.findByIdAndRemove(req.query.id) 68 | .then(() => res.end()) 69 | .catch((err) => res.send(err)); 70 | }); 71 | 72 | module.exports = router; -------------------------------------------------------------------------------- /routes/catalogRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const mongoose = require('mongoose'); 3 | const Product = require('../models/Product'); 4 | 5 | const router = express.Router(); 6 | 7 | router.get('/', (req, res) => { 8 | Product.find({}) 9 | .then((foundProduct) => { 10 | res.send(foundProduct); 11 | }); 12 | }); 13 | 14 | module.exports = router; -------------------------------------------------------------------------------- /routes/orderRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const mongoose = require('mongoose'); 4 | const User = require('../models/User'); 5 | const requireLogin = require('../middleware/requireLogin'); 6 | 7 | const router = express.Router(); 8 | const jsonParser = bodyParser.json(); 9 | 10 | router.post('/', requireLogin, jsonParser, (req, res) => { 11 | User.findById(req.user) 12 | .then((foundUser) => { 13 | foundUser.orders = foundUser.orders.concat(req.body.order); 14 | foundUser.save(() => res.end()); 15 | }); 16 | }); 17 | 18 | module.exports = router; -------------------------------------------------------------------------------- /routes/userRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const mongoose = require('mongoose'); 4 | const User = require('../models/User'); 5 | 6 | const router = express.Router(); 7 | const jsonParser = bodyParser.json(); 8 | 9 | router.get('/', (req, res) => { 10 | res.send(req.user); 11 | }); 12 | 13 | router.post('/', (req, res) => { 14 | User.findById(req.user) 15 | .then((foundUser) => { 16 | foundUser.email = req.body.email; 17 | foundUser.address = req.body.address; 18 | foundUser.phone = req.body.phone; 19 | foundUser.save() 20 | .then(() => res.redirect('/account')); 21 | }); 22 | }); 23 | 24 | module.exports = router; -------------------------------------------------------------------------------- /seeds/products.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Product = require('../models/Product'); 3 | 4 | const products = [ 5 | { 6 | info: { 7 | name: 'Apple MacBook Pro 2020', 8 | dimensions: '158.4 x 78.1 x 7.5 mm', 9 | weight: '1202 g', 10 | displayType: 'LED-backlit IPS LCD, capacitive touchscreen, 16M colors', 11 | displaySize: '13.5"', 12 | displayResolution: '1080 x 1920 pixels', 13 | os: 'ios', 14 | cpu: 'Hexa-core (2x Monsoon + 4x Mistral)', 15 | internalMemory: '256 GB', 16 | ram: '8 GB', 17 | camera: 'Dual: 12 MP (f/1.8, 28mm, OIS) + 12 MP (f/2.8, 57mm)', 18 | batery: 'Non-removable Li-Ion 2691 mAh battery (10.28 Wh)', 19 | color: 'White', 20 | price: 1345, 21 | photo: '/img/macbook-pro-1.jpg' 22 | }, 23 | tags: { 24 | priceRange: '500-750', 25 | brand: 'apple', 26 | color: 'white', 27 | os: 'ios', 28 | internalMemory: '256', 29 | ram: '3', 30 | displaySize: '5.5', 31 | displayResolution: '1080x1920', 32 | camera: '12', 33 | cpu: 'hexa_core' 34 | } 35 | }, 36 | { 37 | info: { 38 | name: 'Apple MacBook Pro 2019', 39 | dimensions: '1143.6 x 700.9 x 5.7 mm', 40 | weight: '1874 g', 41 | displayType: 'Super AMOLED capacitive touchscreen, 16M colors', 42 | displaySize: '5.8"', 43 | displayResolution: '1125 x 2436 pixels', 44 | os: 'ios', 45 | cpu: 'Hexa-core 2.39 GHz (2x Monsoon + 4x Mistral)', 46 | internalMemory: '256 GB', 47 | ram: '8 GB', 48 | camera: 'Dual: 12 MP (f/1.8, 28mm) + 12 MP (f/2.4, 52mm)', 49 | batery: 'Non-removable Li-Ion 2716 mAh battery (10.35 Wh)', 50 | color: 'Black', 51 | price: 1950, 52 | photo: '/img/apple_macbook-2.jpg' 53 | }, 54 | tags: { 55 | priceRange: '750>', 56 | brand: 'apple', 57 | color: 'black', 58 | os: 'ios', 59 | internalMemory: '256', 60 | ram: '3', 61 | displaySize: '5.8', 62 | displayResolution: '1125x2436', 63 | camera: '12', 64 | cpu: 'hexa_core' 65 | } 66 | }, 67 | { 68 | info: { 69 | name: 'Macbook Pro 17inch', 70 | dimensions: '153.9 x 75.9 x 7.9 mm', 71 | weight: '169 g', 72 | displayType: 'Super LCD5 capacitive touchscreen, 16M colors', 73 | displaySize: '5.5"', 74 | displayResolution: '1440 x 2560 pixels', 75 | os: 'Android 7.1 (Nougat)', 76 | cpu: 'Octa-core (4x2.45 GHz Kryo & 4x1.9 GHz Kryo)', 77 | internalMemory: '128 GB', 78 | ram: '6 GB', 79 | camera: '12 MP (f/1.7, 1.4 µm, Dual Pixel PDAF, 5-axis OIS)', 80 | batery: 'Non-removable Li-Ion 3000 mAh battery', 81 | color: 'Ice White', 82 | price: 450, 83 | photo: '/img/macbook-pro-3.jpg' 84 | }, 85 | tags: { 86 | priceRange: '250-500', 87 | brand: 'htc', 88 | color: 'white', 89 | os: 'ios', 90 | internalMemory: '128', 91 | ram: '6', 92 | displaySize: '5.5', 93 | displayResolution: '1440x2560', 94 | camera: '12', 95 | cpu: 'octa_core' 96 | } 97 | }, 98 | { 99 | info: { 100 | name: 'MacBook Pro 15.5inch', 101 | dimensions: '154.2 x 74.5 x 7.9 mm', 102 | weight: '178 g', 103 | displayType: 'AMOLED capacitive touchscreen, 16M colors', 104 | displaySize: '6.0"', 105 | displayResolution: '1080 x 1920 pixels', 106 | os: 'Android 8.0 (Oreo)', 107 | cpu: 'Octa-core (4x2.4 GHz Cortex-A73 & 4x1.8 GHz Cortex-A53)', 108 | internalMemory: '128 GB', 109 | ram: '6 GB', 110 | camera: 'Dual: 12 MP (f/1.6, 27mm, OIS) +20 MP (f/1.6, 27mm)', 111 | batery: 'Non-removable Li-Po 4000 mAh battery', 112 | color: 'Titanium Gray', 113 | price: 800, 114 | photo: '/img/macbook-pro-4.jpg' 115 | }, 116 | tags: { 117 | priceRange: '750>', 118 | brand: 'huawei', 119 | color: 'grey', 120 | os: 'ios', 121 | internalMemory: '128', 122 | ram: '6', 123 | displaySize: '6.0', 124 | displayResolution: '1080x1920', 125 | camera: '12', 126 | cpu: 'octa_core' 127 | } 128 | }, 129 | { 130 | info: { 131 | name: 'Dell 16GB', 132 | dimensions: '145.3 x 69.3 x 7 mm', 133 | weight: '145 g', 134 | displayType: 'IPS-NEO LCD capacitive touchscreen, 16M colors', 135 | displaySize: '5.1"', 136 | displayResolution: '1080 x 1920 pixels', 137 | os: 'Android 7.0 (Nougat)', 138 | cpu: 'Octa-core (4x2.4 GHz Cortex-A73 & 4x1.8 GHz Cortex-A53)', 139 | internalMemory: '64 GB', 140 | ram: '4 GB', 141 | camera: 'Dual: 12 MP (f/2.2, PDAF, OIS) + 20 MP', 142 | batery: 'Non-removable Li-Ion 3200 mAh battery', 143 | color: 'Mystic Silver', 144 | price: 680, 145 | photo: '/img/dell-1.png' 146 | }, 147 | tags: { 148 | priceRange: '500-750', 149 | brand: 'huawei', 150 | color: 'grey', 151 | os: 'android', 152 | internalMemory: '64', 153 | ram: '4', 154 | displaySize: '5.1', 155 | displayResolution: '1080x1920', 156 | camera: '12', 157 | cpu: 'octa_core' 158 | } 159 | }, 160 | { 161 | info: { 162 | name: 'Dell i5', 163 | dimensions: '148.9 x 71.9 x 7.9 mm', 164 | weight: '163 g', 165 | displayType: 'IPS LCD capacitive touchscreen, 16M colors', 166 | displaySize: '5.8"', 167 | displayResolution: '1440 x 2880 pixels', 168 | os: 'Android 7.0 (Nougat)', 169 | cpu: 'Quad-core (2x2.35 GHz Kryo & 2x1.6 GHz Kryo)', 170 | internalMemory: '128 GB', 171 | ram: '4 GB', 172 | camera: 'Dual: 13 MP (f/1.8, 1/3", 1.12 µm, 3-axis OIS, PDAF) + 13 MP (f/2.4, no AF)', 173 | batery: 'Non-removable Li-Po 3300 mAh battery', 174 | color: 'Ice Platinum', 175 | price: 800, 176 | photo: '/img/dell-2.png' 177 | }, 178 | tags: { 179 | priceRange: '750>', 180 | brand: 'lg', 181 | color: 'grey', 182 | os: 'android', 183 | internalMemory: '128', 184 | ram: '4', 185 | displaySize: '5.8', 186 | displayResolution: '1440x2880', 187 | camera: '13', 188 | cpu: 'quad_core' 189 | } 190 | }, 191 | { 192 | info: { 193 | name: 'Dell i7 16GB', 194 | dimensions: '151.7 x 75.4 x 7.3 mm', 195 | weight: '158 g', 196 | displayType: 'P-OLED capacitive touchscreen, 16M colors', 197 | displaySize: '6.0"', 198 | displayResolution: '1440 x 2880 pixels', 199 | os: 'Android 7.1.2 (Nougat)', 200 | cpu: 'Octa-core (4x2.45 GHz Kryo & 4x1.9 GHz Kryo)', 201 | internalMemory: '64 GB', 202 | ram: '4 GB', 203 | camera: 'Dual: 16 MP (f/1.6, 1 µm, 3-axis OIS, PDAF) + 13 MP (f/1.9, no AF)', 204 | batery: 'Non-removable Li-Po 3300 mAh battery', 205 | color: 'Aurora Black', 206 | price: 800, 207 | photo: '/img/dell-3.jpg' 208 | }, 209 | tags: { 210 | priceRange: '750>', 211 | brand: 'lg', 212 | color: 'black', 213 | os: 'android', 214 | internalMemory: '64', 215 | ram: '4', 216 | displaySize: '6.0', 217 | displayResolution: '1440x2880', 218 | camera: '16', 219 | cpu: 'octa_core' 220 | } 221 | }, 222 | { 223 | info: { 224 | name: 'Dell 17inch', 225 | dimensions: '130.1 x 65.5 x 6.9 mm', 226 | weight: '110.3 g', 227 | displayType: 'Super AMOLED capacitive touchscreen, 16M colors', 228 | displaySize: '4.5"', 229 | displayResolution: '540 x 960 pixels', 230 | os: 'Android 4.4.4 (KitKat)', 231 | cpu: 'Quad-core 1.2 GHz Cortex-A53', 232 | internalMemory: '16 GB', 233 | ram: '1 GB', 234 | camera: '8 MP (f/2.4, 31mm), autofocus, LED flash', 235 | batery: 'Non-removable Li-Ion 1900 mAh battery', 236 | color: 'Silver', 237 | price: 150, 238 | photo: '/img/dell-4.png' 239 | }, 240 | tags: { 241 | priceRange: '<250', 242 | brand: 'samsung', 243 | color: 'grey', 244 | os: 'android', 245 | internalMemory: '16', 246 | ram: '1', 247 | displaySize: '4.5', 248 | displayResolution: '540x960', 249 | camera: '8', 250 | cpu: 'quad_core' 251 | } 252 | }, 253 | { 254 | info: { 255 | name: 'Dell Touchscreen', 256 | dimensions: '162.5 x 74.8 x 8.6 mm', 257 | weight: '195.3 g', 258 | displayType: 'Super AMOLED capacitive touchscreen, 16M colors', 259 | displaySize: '6.3"', 260 | displayResolution: '1440 x 2960 pixels', 261 | os: 'Android 7.1.1 (Nougat)', 262 | cpu: 'Octa-core (4x2.3 GHz & 4x1.7 GHz) - EMEA', 263 | internalMemory: '256 GB', 264 | ram: '6 GB', 265 | camera: 'Dual: 12 MP (f/1.7, 26mm, 1/2.5", 1.4 µm) + 12MP (f/2.4, 52mm, 1/3.6", 1 µm)', 266 | batery: 'Non-removable Li-Ion 3300 mAh battery', 267 | color: 'Midnight Black', 268 | price: 800, 269 | photo: '/img/dell-5.png' 270 | }, 271 | tags: { 272 | priceRange: '750>', 273 | brand: 'samsung', 274 | color: 'black', 275 | os: 'android', 276 | internalMemory: '256', 277 | ram: '6', 278 | displaySize: '6.3', 279 | displayResolution: '1440x2960', 280 | camera: '12', 281 | cpu: 'octa_core' 282 | } 283 | }, 284 | { 285 | info: { 286 | name: 'Dell Inspiron ', 287 | dimensions: '148.9 x 68.1 x 8 mm', 288 | weight: '155 g', 289 | displayType: 'Super AMOLED capacitive touchscreen, 16M colors', 290 | displaySize: '5.8"', 291 | displayResolution: '1440 x 2960 pixels', 292 | os: 'Android 7.0 (Nougat)', 293 | cpu: 'Octa-core (4x2.3 GHz & 4x1.7 GHz) - EMEA', 294 | internalMemory: '64 GB', 295 | ram: '4 GB', 296 | camera: '12 MP (f/1.7, 26mm, 1/2.5", 1.4 µm, Dual Pixel PDAF), phase detection autofocus, OIS', 297 | batery: 'Non-removable Li-Ion 3000 mAh battery', 298 | color: 'Midnight Black', 299 | price: 720, 300 | photo: '/img/Dell-6.png' 301 | }, 302 | tags: { 303 | priceRange: '500-750', 304 | brand: 'samsung', 305 | color: 'black', 306 | os: 'android', 307 | internalMemory: '64', 308 | ram: '4', 309 | displaySize: '5.8', 310 | displayResolution: '1440x2960', 311 | camera: '12', 312 | cpu: 'octa_core' 313 | } 314 | } 315 | ]; 316 | 317 | const seedProducts = () => { 318 | Product.remove({}, (err) => { 319 | if(err) { 320 | console.log(err); 321 | } 322 | console.log('PRODUCTS REMOVED'); 323 | products.forEach((product) => { 324 | Product.create(product, (err, createdProduct) => { 325 | if(err) { 326 | console.log(err); 327 | } else { 328 | console.log('PRODUCT CREATED'); 329 | createdProduct.save(); 330 | } 331 | }) 332 | }) 333 | }) 334 | } 335 | 336 | module.exports = seedProducts; -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const express = require("express"); 3 | const path = require("path"); 4 | const bodyParser = require("body-parser"); 5 | const expressSession = require("express-session"); 6 | const passport = require("passport"); 7 | const LocalStrategy = require("passport-local"); 8 | const mongoose = require("mongoose"); 9 | const authRoutes = require("./routes/authRoutes"); 10 | const catalogRoutes = require("./routes/catalogRoutes"); 11 | const userRoutes = require("./routes/userRoutes"); 12 | const cartRoutes = require("./routes/cartRoutes"); 13 | const orderRoutes = require("./routes/orderRoutes"); 14 | const User = require("./models/User"); 15 | const privates = require("./config/privates"); 16 | 17 | 18 | const publicPath = path.join(__dirname, "client", "public"); 19 | const port = process.env.PORT || 5000; 20 | 21 | const app = express(); 22 | mongoose.connect(privates.MONGODB_URI); 23 | // Below 2 lines are Uncomment to seed products to the database, but when database is live comment-out these 2 lines 24 | const seedProducts = require("./seeds/products.js"); 25 | seedProducts(); 26 | 27 | const urlencodedParser = bodyParser.urlencoded({ extended: true }); 28 | 29 | app.use(express.static(publicPath)); 30 | app.use(urlencodedParser); 31 | app.use( 32 | expressSession({ 33 | secret: privates.sessionSecret, 34 | resave: false, 35 | saveUninitialized: false 36 | }) 37 | ); 38 | app.use(passport.initialize()); 39 | app.use(passport.session()); 40 | passport.use(new LocalStrategy(User.authenticate())); 41 | passport.serializeUser(User.serializeUser()); 42 | passport.deserializeUser(User.deserializeUser()); 43 | 44 | app.use("/auth", authRoutes); 45 | app.use("/api/catalog", catalogRoutes); 46 | app.use("/api/user", userRoutes); 47 | app.use("/api/cart", cartRoutes); 48 | app.use("/api/order", orderRoutes); 49 | 50 | app.get("*", (req, res) => { 51 | res.sendFile(path.join(publicPath, "index.html")); 52 | }); 53 | 54 | app.listen(port, () => console.log("SERVER NOW RUNNING...")); 55 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.7: 6 | version "1.3.7" 7 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 8 | integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== 9 | dependencies: 10 | mime-types "~2.1.24" 11 | negotiator "0.6.2" 12 | 13 | ansi-styles@^3.2.1: 14 | version "3.2.1" 15 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 16 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 17 | dependencies: 18 | color-convert "^1.9.0" 19 | 20 | array-flatten@1.1.1: 21 | version "1.1.1" 22 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 23 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 24 | 25 | bluebird@3.5.1: 26 | version "3.5.1" 27 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" 28 | integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA== 29 | 30 | body-parser@1.19.0, body-parser@^1.19.0: 31 | version "1.19.0" 32 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" 33 | integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== 34 | dependencies: 35 | bytes "3.1.0" 36 | content-type "~1.0.4" 37 | debug "2.6.9" 38 | depd "~1.1.2" 39 | http-errors "1.7.2" 40 | iconv-lite "0.4.24" 41 | on-finished "~2.3.0" 42 | qs "6.7.0" 43 | raw-body "2.4.0" 44 | type-is "~1.6.17" 45 | 46 | bson@^1.1.1, bson@~1.1.1: 47 | version "1.1.3" 48 | resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.3.tgz#aa82cb91f9a453aaa060d6209d0675114a8154d3" 49 | integrity sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg== 50 | 51 | bytes@3.1.0: 52 | version "3.1.0" 53 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" 54 | integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== 55 | 56 | chalk@^2.4.1: 57 | version "2.4.2" 58 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 59 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 60 | dependencies: 61 | ansi-styles "^3.2.1" 62 | escape-string-regexp "^1.0.5" 63 | supports-color "^5.3.0" 64 | 65 | color-convert@^1.9.0: 66 | version "1.9.3" 67 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 68 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 69 | dependencies: 70 | color-name "1.1.3" 71 | 72 | color-name@1.1.3: 73 | version "1.1.3" 74 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 75 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 76 | 77 | commander@2.6.0: 78 | version "2.6.0" 79 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" 80 | integrity sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0= 81 | 82 | concurrently@^3.5.1: 83 | version "3.6.1" 84 | resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.6.1.tgz#2f95baec5c4051294dfbb55b57a3b98a3e2b45ec" 85 | integrity sha512-/+ugz+gwFSEfTGUxn0KHkY+19XPRTXR8+7oUK/HxgiN1n7FjeJmkrbSiXAJfyQ0zORgJYPaenmymwon51YXH9Q== 86 | dependencies: 87 | chalk "^2.4.1" 88 | commander "2.6.0" 89 | date-fns "^1.23.0" 90 | lodash "^4.5.1" 91 | read-pkg "^3.0.0" 92 | rx "2.3.24" 93 | spawn-command "^0.0.2-1" 94 | supports-color "^3.2.3" 95 | tree-kill "^1.1.0" 96 | 97 | content-disposition@0.5.3: 98 | version "0.5.3" 99 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" 100 | integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== 101 | dependencies: 102 | safe-buffer "5.1.2" 103 | 104 | content-type@~1.0.4: 105 | version "1.0.4" 106 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 107 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 108 | 109 | cookie-signature@1.0.6: 110 | version "1.0.6" 111 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 112 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 113 | 114 | cookie@0.4.0: 115 | version "0.4.0" 116 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" 117 | integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== 118 | 119 | date-fns@^1.23.0: 120 | version "1.30.1" 121 | resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" 122 | integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== 123 | 124 | debug@2.6.9: 125 | version "2.6.9" 126 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 127 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 128 | dependencies: 129 | ms "2.0.0" 130 | 131 | debug@3.1.0: 132 | version "3.1.0" 133 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 134 | integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== 135 | dependencies: 136 | ms "2.0.0" 137 | 138 | debug@^3.1.0: 139 | version "3.2.6" 140 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 141 | integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== 142 | dependencies: 143 | ms "^2.1.1" 144 | 145 | depd@~1.1.2: 146 | version "1.1.2" 147 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 148 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 149 | 150 | depd@~2.0.0: 151 | version "2.0.0" 152 | resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" 153 | integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== 154 | 155 | destroy@~1.0.4: 156 | version "1.0.4" 157 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 158 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 159 | 160 | dotenv@^8.2.0: 161 | version "8.2.0" 162 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" 163 | integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== 164 | 165 | ee-first@1.1.1: 166 | version "1.1.1" 167 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 168 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 169 | 170 | encodeurl@~1.0.2: 171 | version "1.0.2" 172 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 173 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 174 | 175 | error-ex@^1.3.1: 176 | version "1.3.2" 177 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" 178 | integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== 179 | dependencies: 180 | is-arrayish "^0.2.1" 181 | 182 | escape-html@~1.0.3: 183 | version "1.0.3" 184 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 185 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 186 | 187 | escape-string-regexp@^1.0.5: 188 | version "1.0.5" 189 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 190 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 191 | 192 | etag@~1.8.1: 193 | version "1.8.1" 194 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 195 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 196 | 197 | express-session@^1.15.6: 198 | version "1.17.0" 199 | resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.0.tgz#9b50dbb5e8a03c3537368138f072736150b7f9b3" 200 | integrity sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg== 201 | dependencies: 202 | cookie "0.4.0" 203 | cookie-signature "1.0.6" 204 | debug "2.6.9" 205 | depd "~2.0.0" 206 | on-headers "~1.0.2" 207 | parseurl "~1.3.3" 208 | safe-buffer "5.2.0" 209 | uid-safe "~2.1.5" 210 | 211 | express@^4.16.4: 212 | version "4.17.1" 213 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" 214 | integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== 215 | dependencies: 216 | accepts "~1.3.7" 217 | array-flatten "1.1.1" 218 | body-parser "1.19.0" 219 | content-disposition "0.5.3" 220 | content-type "~1.0.4" 221 | cookie "0.4.0" 222 | cookie-signature "1.0.6" 223 | debug "2.6.9" 224 | depd "~1.1.2" 225 | encodeurl "~1.0.2" 226 | escape-html "~1.0.3" 227 | etag "~1.8.1" 228 | finalhandler "~1.1.2" 229 | fresh "0.5.2" 230 | merge-descriptors "1.0.1" 231 | methods "~1.1.2" 232 | on-finished "~2.3.0" 233 | parseurl "~1.3.3" 234 | path-to-regexp "0.1.7" 235 | proxy-addr "~2.0.5" 236 | qs "6.7.0" 237 | range-parser "~1.2.1" 238 | safe-buffer "5.1.2" 239 | send "0.17.1" 240 | serve-static "1.14.1" 241 | setprototypeof "1.1.1" 242 | statuses "~1.5.0" 243 | type-is "~1.6.18" 244 | utils-merge "1.0.1" 245 | vary "~1.1.2" 246 | 247 | finalhandler@~1.1.2: 248 | version "1.1.2" 249 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" 250 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== 251 | dependencies: 252 | debug "2.6.9" 253 | encodeurl "~1.0.2" 254 | escape-html "~1.0.3" 255 | on-finished "~2.3.0" 256 | parseurl "~1.3.3" 257 | statuses "~1.5.0" 258 | unpipe "~1.0.0" 259 | 260 | forwarded@~0.1.2: 261 | version "0.1.2" 262 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 263 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 264 | 265 | fresh@0.5.2: 266 | version "0.5.2" 267 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 268 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 269 | 270 | generaterr@^1.5.0: 271 | version "1.5.0" 272 | resolved "https://registry.yarnpkg.com/generaterr/-/generaterr-1.5.0.tgz#b0ceb6cc5164df2a061338cc340a8615395c52fc" 273 | integrity sha1-sM62zFFk3yoGEzjMNAqGFTlcUvw= 274 | 275 | graceful-fs@^4.1.2: 276 | version "4.2.3" 277 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" 278 | integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== 279 | 280 | has-flag@^1.0.0: 281 | version "1.0.0" 282 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" 283 | integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= 284 | 285 | has-flag@^3.0.0: 286 | version "3.0.0" 287 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 288 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 289 | 290 | hosted-git-info@^2.1.4: 291 | version "2.8.5" 292 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c" 293 | integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg== 294 | 295 | http-errors@1.7.2: 296 | version "1.7.2" 297 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" 298 | integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== 299 | dependencies: 300 | depd "~1.1.2" 301 | inherits "2.0.3" 302 | setprototypeof "1.1.1" 303 | statuses ">= 1.5.0 < 2" 304 | toidentifier "1.0.0" 305 | 306 | http-errors@~1.7.2: 307 | version "1.7.3" 308 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" 309 | integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== 310 | dependencies: 311 | depd "~1.1.2" 312 | inherits "2.0.4" 313 | setprototypeof "1.1.1" 314 | statuses ">= 1.5.0 < 2" 315 | toidentifier "1.0.0" 316 | 317 | iconv-lite@0.4.24: 318 | version "0.4.24" 319 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 320 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 321 | dependencies: 322 | safer-buffer ">= 2.1.2 < 3" 323 | 324 | inherits@2.0.3: 325 | version "2.0.3" 326 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 327 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 328 | 329 | inherits@2.0.4: 330 | version "2.0.4" 331 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 332 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 333 | 334 | ipaddr.js@1.9.0: 335 | version "1.9.0" 336 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" 337 | integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== 338 | 339 | is-arrayish@^0.2.1: 340 | version "0.2.1" 341 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 342 | integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= 343 | 344 | json-parse-better-errors@^1.0.1: 345 | version "1.0.2" 346 | resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" 347 | integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== 348 | 349 | kareem@2.3.1: 350 | version "2.3.1" 351 | resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.1.tgz#def12d9c941017fabfb00f873af95e9c99e1be87" 352 | integrity sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw== 353 | 354 | load-json-file@^4.0.0: 355 | version "4.0.0" 356 | resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" 357 | integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= 358 | dependencies: 359 | graceful-fs "^4.1.2" 360 | parse-json "^4.0.0" 361 | pify "^3.0.0" 362 | strip-bom "^3.0.0" 363 | 364 | lodash@^4.5.1: 365 | version "4.17.19" 366 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" 367 | integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== 368 | 369 | media-typer@0.3.0: 370 | version "0.3.0" 371 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 372 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 373 | 374 | memory-pager@^1.0.2: 375 | version "1.5.0" 376 | resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" 377 | integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== 378 | 379 | merge-descriptors@1.0.1: 380 | version "1.0.1" 381 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 382 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 383 | 384 | methods@~1.1.2: 385 | version "1.1.2" 386 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 387 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 388 | 389 | mime-db@1.43.0: 390 | version "1.43.0" 391 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" 392 | integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== 393 | 394 | mime-types@~2.1.24: 395 | version "2.1.26" 396 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" 397 | integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== 398 | dependencies: 399 | mime-db "1.43.0" 400 | 401 | mime@1.6.0: 402 | version "1.6.0" 403 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 404 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 405 | 406 | mongodb@3.4.1: 407 | version "3.4.1" 408 | resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.4.1.tgz#0d15e57e0ea0fc85b7a4fb9291b374c2e71652dc" 409 | integrity sha512-juqt5/Z42J4DcE7tG7UdVaTKmUC6zinF4yioPfpeOSNBieWSK6qCY+0tfGQcHLKrauWPDdMZVROHJOa8q2pWsA== 410 | dependencies: 411 | bson "^1.1.1" 412 | require_optional "^1.0.1" 413 | safe-buffer "^5.1.2" 414 | optionalDependencies: 415 | saslprep "^1.0.0" 416 | 417 | mongoose-legacy-pluralize@1.0.2: 418 | version "1.0.2" 419 | resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4" 420 | integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ== 421 | 422 | mongoose@^5.5.12: 423 | version "5.8.11" 424 | resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.8.11.tgz#4b3813e55d33928dcc4a2c5cf4b8a76041192861" 425 | integrity sha512-Yz0leNEJsAtNtMTxTDEadacLWt58gaVeBVL3c1Z3vaBoc159aJqlf+T8jaL9mAdBxKndF5YWhh6Q719xac7cjA== 426 | dependencies: 427 | bson "~1.1.1" 428 | kareem "2.3.1" 429 | mongodb "3.4.1" 430 | mongoose-legacy-pluralize "1.0.2" 431 | mpath "0.6.0" 432 | mquery "3.2.2" 433 | ms "2.1.2" 434 | regexp-clone "1.0.0" 435 | safe-buffer "5.1.2" 436 | sift "7.0.1" 437 | sliced "1.0.1" 438 | 439 | mpath@0.6.0: 440 | version "0.6.0" 441 | resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.6.0.tgz#aa922029fca4f0f641f360e74c5c1b6a4c47078e" 442 | integrity sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw== 443 | 444 | mquery@3.2.2: 445 | version "3.2.2" 446 | resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.2.tgz#e1383a3951852ce23e37f619a9b350f1fb3664e7" 447 | integrity sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q== 448 | dependencies: 449 | bluebird "3.5.1" 450 | debug "3.1.0" 451 | regexp-clone "^1.0.0" 452 | safe-buffer "5.1.2" 453 | sliced "1.0.1" 454 | 455 | ms@2.0.0: 456 | version "2.0.0" 457 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 458 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 459 | 460 | ms@2.1.1: 461 | version "2.1.1" 462 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 463 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 464 | 465 | ms@2.1.2, ms@^2.1.1: 466 | version "2.1.2" 467 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 468 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 469 | 470 | negotiator@0.6.2: 471 | version "0.6.2" 472 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 473 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== 474 | 475 | normalize-package-data@^2.3.2: 476 | version "2.5.0" 477 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" 478 | integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== 479 | dependencies: 480 | hosted-git-info "^2.1.4" 481 | resolve "^1.10.0" 482 | semver "2 || 3 || 4 || 5" 483 | validate-npm-package-license "^3.0.1" 484 | 485 | on-finished@~2.3.0: 486 | version "2.3.0" 487 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 488 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 489 | dependencies: 490 | ee-first "1.1.1" 491 | 492 | on-headers@~1.0.2: 493 | version "1.0.2" 494 | resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" 495 | integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== 496 | 497 | parse-json@^4.0.0: 498 | version "4.0.0" 499 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" 500 | integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= 501 | dependencies: 502 | error-ex "^1.3.1" 503 | json-parse-better-errors "^1.0.1" 504 | 505 | parseurl@~1.3.3: 506 | version "1.3.3" 507 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 508 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 509 | 510 | passport-local-mongoose@^4.5.0: 511 | version "4.5.0" 512 | resolved "https://registry.yarnpkg.com/passport-local-mongoose/-/passport-local-mongoose-4.5.0.tgz#1c43afd758371e005c5aeca780d6120b077e6065" 513 | integrity sha512-fd5dUdTcN37DA+CCL/7kGQ57ACs6NXyuNmWjrTj4kvTOFa7SA+msEYPYwpgwbc3Nc+4RUeMtEQW4fPMx7nDObQ== 514 | dependencies: 515 | debug "^3.1.0" 516 | generaterr "^1.5.0" 517 | passport-local "^1.0.0" 518 | scmp "^2.0.0" 519 | semver "^5.4.1" 520 | 521 | passport-local@^1.0.0: 522 | version "1.0.0" 523 | resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee" 524 | integrity sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4= 525 | dependencies: 526 | passport-strategy "1.x.x" 527 | 528 | passport-strategy@1.x.x: 529 | version "1.0.0" 530 | resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" 531 | integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= 532 | 533 | passport@^0.4.0: 534 | version "0.4.1" 535 | resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" 536 | integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg== 537 | dependencies: 538 | passport-strategy "1.x.x" 539 | pause "0.0.1" 540 | 541 | path-parse@^1.0.6: 542 | version "1.0.6" 543 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" 544 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== 545 | 546 | path-to-regexp@0.1.7: 547 | version "0.1.7" 548 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 549 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 550 | 551 | path-type@^3.0.0: 552 | version "3.0.0" 553 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" 554 | integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== 555 | dependencies: 556 | pify "^3.0.0" 557 | 558 | pause@0.0.1: 559 | version "0.0.1" 560 | resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" 561 | integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= 562 | 563 | pify@^3.0.0: 564 | version "3.0.0" 565 | resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" 566 | integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= 567 | 568 | proxy-addr@~2.0.5: 569 | version "2.0.5" 570 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" 571 | integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ== 572 | dependencies: 573 | forwarded "~0.1.2" 574 | ipaddr.js "1.9.0" 575 | 576 | qs@6.7.0: 577 | version "6.7.0" 578 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" 579 | integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== 580 | 581 | random-bytes@~1.0.0: 582 | version "1.0.0" 583 | resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" 584 | integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= 585 | 586 | range-parser@~1.2.1: 587 | version "1.2.1" 588 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 589 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 590 | 591 | raw-body@2.4.0: 592 | version "2.4.0" 593 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" 594 | integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== 595 | dependencies: 596 | bytes "3.1.0" 597 | http-errors "1.7.2" 598 | iconv-lite "0.4.24" 599 | unpipe "1.0.0" 600 | 601 | read-pkg@^3.0.0: 602 | version "3.0.0" 603 | resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" 604 | integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= 605 | dependencies: 606 | load-json-file "^4.0.0" 607 | normalize-package-data "^2.3.2" 608 | path-type "^3.0.0" 609 | 610 | regexp-clone@1.0.0, regexp-clone@^1.0.0: 611 | version "1.0.0" 612 | resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63" 613 | integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw== 614 | 615 | require_optional@^1.0.1: 616 | version "1.0.1" 617 | resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e" 618 | integrity sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g== 619 | dependencies: 620 | resolve-from "^2.0.0" 621 | semver "^5.1.0" 622 | 623 | resolve-from@^2.0.0: 624 | version "2.0.0" 625 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" 626 | integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c= 627 | 628 | resolve@^1.10.0: 629 | version "1.15.1" 630 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" 631 | integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== 632 | dependencies: 633 | path-parse "^1.0.6" 634 | 635 | rx@2.3.24: 636 | version "2.3.24" 637 | resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7" 638 | integrity sha1-FPlQpCF9fjXapxu8vljv9o6ksrc= 639 | 640 | safe-buffer@5.1.2: 641 | version "5.1.2" 642 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 643 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 644 | 645 | safe-buffer@5.2.0, safe-buffer@^5.1.2: 646 | version "5.2.0" 647 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" 648 | integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== 649 | 650 | "safer-buffer@>= 2.1.2 < 3": 651 | version "2.1.2" 652 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 653 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 654 | 655 | saslprep@^1.0.0: 656 | version "1.0.3" 657 | resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" 658 | integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== 659 | dependencies: 660 | sparse-bitfield "^3.0.3" 661 | 662 | scmp@^2.0.0: 663 | version "2.1.0" 664 | resolved "https://registry.yarnpkg.com/scmp/-/scmp-2.1.0.tgz#37b8e197c425bdeb570ab91cc356b311a11f9c9a" 665 | integrity sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q== 666 | 667 | "semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.4.1: 668 | version "5.7.1" 669 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 670 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 671 | 672 | send@0.17.1: 673 | version "0.17.1" 674 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" 675 | integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== 676 | dependencies: 677 | debug "2.6.9" 678 | depd "~1.1.2" 679 | destroy "~1.0.4" 680 | encodeurl "~1.0.2" 681 | escape-html "~1.0.3" 682 | etag "~1.8.1" 683 | fresh "0.5.2" 684 | http-errors "~1.7.2" 685 | mime "1.6.0" 686 | ms "2.1.1" 687 | on-finished "~2.3.0" 688 | range-parser "~1.2.1" 689 | statuses "~1.5.0" 690 | 691 | serve-static@1.14.1: 692 | version "1.14.1" 693 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" 694 | integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== 695 | dependencies: 696 | encodeurl "~1.0.2" 697 | escape-html "~1.0.3" 698 | parseurl "~1.3.3" 699 | send "0.17.1" 700 | 701 | setprototypeof@1.1.1: 702 | version "1.1.1" 703 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" 704 | integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== 705 | 706 | sift@7.0.1: 707 | version "7.0.1" 708 | resolved "https://registry.yarnpkg.com/sift/-/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08" 709 | integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g== 710 | 711 | sliced@1.0.1: 712 | version "1.0.1" 713 | resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" 714 | integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E= 715 | 716 | sparse-bitfield@^3.0.3: 717 | version "3.0.3" 718 | resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" 719 | integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE= 720 | dependencies: 721 | memory-pager "^1.0.2" 722 | 723 | spawn-command@^0.0.2-1: 724 | version "0.0.2-1" 725 | resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" 726 | integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= 727 | 728 | spdx-correct@^3.0.0: 729 | version "3.1.0" 730 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" 731 | integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== 732 | dependencies: 733 | spdx-expression-parse "^3.0.0" 734 | spdx-license-ids "^3.0.0" 735 | 736 | spdx-exceptions@^2.1.0: 737 | version "2.2.0" 738 | resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" 739 | integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== 740 | 741 | spdx-expression-parse@^3.0.0: 742 | version "3.0.0" 743 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" 744 | integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== 745 | dependencies: 746 | spdx-exceptions "^2.1.0" 747 | spdx-license-ids "^3.0.0" 748 | 749 | spdx-license-ids@^3.0.0: 750 | version "3.0.5" 751 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" 752 | integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== 753 | 754 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0: 755 | version "1.5.0" 756 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 757 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 758 | 759 | strip-bom@^3.0.0: 760 | version "3.0.0" 761 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 762 | integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= 763 | 764 | supports-color@^3.2.3: 765 | version "3.2.3" 766 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" 767 | integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= 768 | dependencies: 769 | has-flag "^1.0.0" 770 | 771 | supports-color@^5.3.0: 772 | version "5.5.0" 773 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 774 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 775 | dependencies: 776 | has-flag "^3.0.0" 777 | 778 | toidentifier@1.0.0: 779 | version "1.0.0" 780 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" 781 | integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== 782 | 783 | tree-kill@^1.1.0: 784 | version "1.2.2" 785 | resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" 786 | integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== 787 | 788 | type-is@~1.6.17, type-is@~1.6.18: 789 | version "1.6.18" 790 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 791 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 792 | dependencies: 793 | media-typer "0.3.0" 794 | mime-types "~2.1.24" 795 | 796 | uid-safe@~2.1.5: 797 | version "2.1.5" 798 | resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" 799 | integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== 800 | dependencies: 801 | random-bytes "~1.0.0" 802 | 803 | unpipe@1.0.0, unpipe@~1.0.0: 804 | version "1.0.0" 805 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 806 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 807 | 808 | utils-merge@1.0.1: 809 | version "1.0.1" 810 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 811 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 812 | 813 | validate-npm-package-license@^3.0.1: 814 | version "3.0.4" 815 | resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" 816 | integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== 817 | dependencies: 818 | spdx-correct "^3.0.0" 819 | spdx-expression-parse "^3.0.0" 820 | 821 | vary@~1.1.2: 822 | version "1.1.2" 823 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 824 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 825 | --------------------------------------------------------------------------------