├── .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 | Date Created |
77 | Product Name |
78 | Price |
79 | Qty |
80 | Total |
81 |
82 |
83 |
84 | {user.orders.map((order: IOrder) => {
85 | return (
86 |
87 | {moment(order.dateCreated).format('ll')} |
88 | {order.name} |
89 | {numeral(order.price).format('$0,0.00')} |
90 | {order.quantity} |
91 | {numeral(parseInt(order.price) * parseInt(order.quantity)).format('$0,0.00')} |
92 |
93 | );
94 | })}
95 |
96 |
:
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 |
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 |
146 |
147 |
148 | {cartExists ?
149 |
150 |
151 |
152 | |
153 | Product Name |
154 | Price |
155 | Qty |
156 | Total |
157 | |
158 |
159 |
160 |
161 | {cart.items.map((item) => {
162 | return (
163 |
164 |  |
165 | {item.product.info.name} |
166 | {numeral(item.product.info.price).format('$0,0.00')} |
167 | {item.quantity} |
168 | {numeral(item.product.info.price * item.quantity!).format('$0,0.00')} |
169 | |
170 |
171 | );
172 | })}
173 |
174 |
:
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 | Product Name |
24 | Price |
25 | Quantity |
26 | Total |
27 |
28 |
29 |
30 | {cart!.length && cart!.map((item: ICartProduct) => {
31 | return (
32 |
33 | {item.product.info.name} |
34 | {numeral(item.product.info.price).format('$0,0.00')} |
35 | {item.quantity} |
36 | {numeral(item.product.info.price * item.quantity!).format('$0,0.00')} |
37 |
38 | )
39 | })}
40 |
41 |
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 |
67 |
68 |
}
72 | containerElement={
}
73 | />
74 |
}
78 | containerElement={
}
79 | />
80 |
}
84 | containerElement={
}
85 | />
86 |
87 |
88 |
}
90 | anchorOrigin={{ horizontal: 'right', vertical: 'top' }}
91 | targetOrigin={{ horizontal: 'right', vertical: 'top' }}
92 | iconStyle={{ color: '#fff' }}
93 | >
94 | }
97 | containerElement={}
98 | />
99 | }
102 | containerElement={}
103 | />
104 | }
107 | containerElement={}
108 | />
109 |
110 |
111 |
:
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 |
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 |
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 | Model |
67 | {info.name} |
68 |
69 |
70 | Dimensions |
71 | {info.dimensions} |
72 |
73 |
74 | Weight |
75 | {info.weight} |
76 |
77 |
78 | Display Type |
79 | {info.displayType} |
80 |
81 |
82 | Display Size |
83 | {info.displaySize} |
84 |
85 |
86 | Display Resolution |
87 | {info.displayResolution} |
88 |
89 |
90 | OS |
91 | {info.os} |
92 |
93 |
94 | CPU |
95 | {info.cpu} |
96 |
97 |
98 | Internal Memory |
99 | {info.internalMemory} |
100 |
101 |
102 | RAM |
103 | {info.ram} |
104 |
105 |
106 | Camera |
107 | {info.camera} |
108 |
109 |
110 | Batery |
111 | {info.batery} |
112 |
113 |
114 | Color |
115 | {info.color} |
116 |
117 |
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 |
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 |
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 |
--------------------------------------------------------------------------------