├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── manifest.json
└── robots.txt
├── src
├── App.js
├── assets
│ └── images
│ │ └── burger-logo.png
├── axios-orders.js
├── components
│ ├── Burger
│ │ ├── BuildControls
│ │ │ ├── BuildControl
│ │ │ │ ├── BuildControl.jsx
│ │ │ │ └── BuildControl.module.css
│ │ │ ├── BuildControls.jsx
│ │ │ └── BuildControls.module.css
│ │ ├── Burger.jsx
│ │ ├── Burger.module.css
│ │ ├── BurgerIngredient
│ │ │ ├── BurgerIngredient.jsx
│ │ │ └── BurgerIngredient.module.css
│ │ └── OrderSummary
│ │ │ └── OrderSummary.jsx
│ ├── Logo
│ │ ├── Logo.jsx
│ │ └── Logo.module.css
│ ├── Navigation
│ │ ├── NavigationItems
│ │ │ ├── NavigationItem
│ │ │ │ ├── NavigationItem.jsx
│ │ │ │ └── NavigationItem.module.css
│ │ │ ├── NavigationItems.jsx
│ │ │ └── NavigationItems.module.css
│ │ ├── SideDrawer
│ │ │ ├── DrawerToggle
│ │ │ │ ├── DrawerToggle.jsx
│ │ │ │ └── DrawerToggle.module.css
│ │ │ ├── SideDrawer.jsx
│ │ │ └── SideDrawer.module.css
│ │ └── Toolbar
│ │ │ ├── Toolbar.jsx
│ │ │ └── Toolbar.module.css
│ ├── Order
│ │ ├── CheckoutSummary
│ │ │ ├── CheckoutSummary.jsx
│ │ │ └── CheckoutSummary.module.css
│ │ ├── Order.jsx
│ │ └── Order.module.css
│ └── UI
│ │ ├── Backdrop
│ │ ├── Backdrop.jsx
│ │ └── Backdrop.module.css
│ │ ├── Button
│ │ ├── Button.jsx
│ │ └── Button.module.css
│ │ ├── Input
│ │ ├── Input.jsx
│ │ └── Input.module.css
│ │ ├── Modal
│ │ ├── Modal.jsx
│ │ └── Modal.module.css
│ │ └── Spinner
│ │ ├── Spinner.jsx
│ │ └── Spinner.module.css
├── containers
│ ├── BurgerBuilder
│ │ └── BurgerBuilder.jsx
│ ├── Checkout
│ │ ├── Checkout.jsx
│ │ └── ContactData
│ │ │ ├── ContactData.jsx
│ │ │ └── ContactData.module.css
│ └── Orders
│ │ ├── Orders.jsx
│ │ └── Orders.module.css
├── hoc
│ ├── Auxiliary
│ │ └── Auxiliary.js
│ ├── Layout
│ │ ├── Layout.jsx
│ │ └── Layout.module.css
│ └── withErrorHandler
│ │ └── withErrorHandler.jsx
├── index.css
├── index.js
├── reportWebVitals.js
├── setupTests.js
└── store
│ ├── actions
│ ├── actionTypes.js
│ ├── burgerBuilder.js
│ ├── index.js
│ └── order.js
│ ├── reducers
│ ├── burgerBuilder.js
│ └── order.js
│ └── utility.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "burger-builder",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "axios": "^0.21.1",
10 | "prop-types": "^15.7.2",
11 | "react": "^17.0.1",
12 | "react-dom": "^17.0.1",
13 | "react-redux": "^7.2.4",
14 | "react-router-dom": "^5.2.0",
15 | "react-scripts": "4.0.2",
16 | "redux": "^4.1.0",
17 | "redux-thunk": "^2.3.0",
18 | "web-vitals": "^1.0.1"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": [
28 | "react-app",
29 | "react-app/jest"
30 | ]
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pooridev/burger-builder/c25f1f6e5b5ff11edd10d54ac472e72c9b95eba5/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 | Burger Builder
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Layout from './hoc/Layout/Layout';
3 | import BurgerBuilder from './containers/BurgerBuilder/BurgerBuilder';
4 | import Checkout from './containers/Checkout/Checkout';
5 | import { Route, Switch } from 'react-router';
6 | import Orders from './containers/Orders/Orders';
7 |
8 | class App extends Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 | }
23 |
24 | export default App;
25 |
--------------------------------------------------------------------------------
/src/assets/images/burger-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pooridev/burger-builder/c25f1f6e5b5ff11edd10d54ac472e72c9b95eba5/src/assets/images/burger-logo.png
--------------------------------------------------------------------------------
/src/axios-orders.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | const instance = axios.create({
4 | baseURL: 'https://burger-builder-ff829-default-rtdb.firebaseio.com/'
5 | });
6 |
7 | export default instance;
8 |
--------------------------------------------------------------------------------
/src/components/Burger/BuildControls/BuildControl/BuildControl.jsx:
--------------------------------------------------------------------------------
1 | import classes from './BuildControl.module.css';
2 | import PropTypes from 'prop-types';
3 | const buildControl = props => (
4 |
5 |
{props.label}
6 |
12 |
15 |
16 | );
17 |
18 | buildControl.propTypes = {
19 | label: PropTypes.string,
20 | added: PropTypes.func,
21 | disabled: PropTypes.bool,
22 | removed: PropTypes.func
23 | }
24 |
25 | export default buildControl;
26 |
--------------------------------------------------------------------------------
/src/components/Burger/BuildControls/BuildControl/BuildControl.module.css:
--------------------------------------------------------------------------------
1 | .BuildControl {
2 | display: flex;
3 | justify-content: space-between;
4 | align-items: center;
5 | margin: 5px 0;
6 | }
7 |
8 | .BuildControl button {
9 | display: block;
10 | font: inherit;
11 | padding: 5px;
12 | margin: 0 5px;
13 | width: 80px;
14 | border: 1px solid #aa6817;
15 | cursor: pointer;
16 | outline: none;
17 | }
18 |
19 | .BuildControl button:disabled {
20 | background-color: #ac9980;
21 | border: 1px solid #7e7365;
22 | color: #ccc;
23 | cursor: default;
24 | }
25 |
26 | .BuildControl button:hover:disabled {
27 | background-color: #ac9980;
28 | color: #ccc;
29 | cursor: not-allowed;
30 | }
31 |
32 | .Label {
33 | padding: 10px;
34 | font-weight: bold;
35 | width: 80px;
36 | }
37 |
38 | .BuildControl .Less {
39 | background-color: #d39952;
40 | color: white;
41 | }
42 |
43 | .BuildControl .More {
44 | background-color: #8f5e1e;
45 | color: white;
46 | }
47 |
48 | .BuildControl .Less:hover,
49 | .BuildControl .Less:active {
50 | background-color: #daa972;
51 | color: white;
52 | }
53 |
54 | .BuildControl .More:hover,
55 | .BuildControl .More:active {
56 | background-color: #99703f;
57 | color: white;
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/Burger/BuildControls/BuildControls.jsx:
--------------------------------------------------------------------------------
1 | import BuildControl from './BuildControl/BuildControl';
2 | import PropTypes from 'prop-types';
3 | import classes from './BuildControls.module.css';
4 | const controls = [
5 | { label: 'Cheese', type: 'cheese' },
6 | { label: 'Salad', type: 'salad' },
7 | { label: 'Bacon', type: 'bacon' },
8 | { label: 'Meat', type: 'meat' }
9 | ];
10 |
11 | const buildControls = props => (
12 |
13 |
14 | Current price: {props.totalPrice.toFixed(2)}
15 |
16 | {controls.map(control => (
17 |
props.addedIngredient(control.type)}
21 | removed={() => props.removedIngredient(control.type)}
22 | disabled={props.disabledInfo[control.type]}
23 | />
24 | ))}
25 |
31 |
32 | );
33 | buildControls.propTypes = {
34 | price: PropTypes.number,
35 | purchasable: PropTypes.bool,
36 | ordered: PropTypes.func,
37 | addedIngredient: PropTypes.func,
38 | removedIngredient: PropTypes.func,
39 | disabledInfo: PropTypes.object
40 | };
41 |
42 | export default buildControls;
43 |
--------------------------------------------------------------------------------
/src/components/Burger/BuildControls/BuildControls.module.css:
--------------------------------------------------------------------------------
1 | .BuildControls {
2 | width: 100%;
3 | background-color: #cf8f2e;
4 | display: flex;
5 | flex-flow: column;
6 | align-items: center;
7 | box-shadow: 0 2px 1px #ccc;
8 | margin: auto;
9 | padding: 10px 0;
10 | }
11 |
12 | .OrderButton {
13 | background-color: #dad735;
14 | outline: none;
15 | cursor: pointer;
16 | border: 1px solid #966909;
17 | color: #966909;
18 | font-family: inherit;
19 | font-size: 1.2em;
20 | padding: 15px 30px;
21 | box-shadow: 2px 2px 2px #966909;
22 | }
23 |
24 | .OrderButton:hover,
25 | .OrderButton:active {
26 | background-color: #a0db41;
27 | border: 1px solid #966909;
28 | color: #966909;
29 | }
30 |
31 | .OrderButton:disabled {
32 | background-color: #c7c6c6;
33 | cursor: not-allowed;
34 | border: 1px solid #ccc;
35 | color: #888888;
36 | }
37 |
38 | .OrderButton:not(:disabled) {
39 | animation: enable 0.3s linear;
40 | }
41 |
42 | @keyframes enable {
43 | 0% {
44 | transform: scale(1);
45 | }
46 | 60% {
47 | transform: scale(1.1);
48 | }
49 | 100% {
50 | transform: scale(1);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/components/Burger/Burger.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BurgerIngredient from './BurgerIngredient/BurgerIngredient';
3 | import PropTypes from 'prop-types';
4 | import classes from './Burger.module.css';
5 |
6 | const burger = props => {
7 | // props
8 | const { ingredients } = props;
9 |
10 | let transformedIngredients = Object.keys(ingredients)
11 | .map(igKey => {
12 | return [...Array(ingredients[igKey])].map((_, index) => {
13 | return ;
14 | });
15 | })
16 | .reduce((arr, el) => {
17 | return arr.concat(el);
18 | }, []);
19 |
20 | if (transformedIngredients.length === 0) {
21 | transformedIngredients = Pls start adding ingredients !
;
22 | }
23 | return (
24 |
25 |
26 | {transformedIngredients}
27 |
28 |
29 | );
30 | };
31 |
32 | burger.propTypes = {
33 | ingredients: PropTypes.object
34 | };
35 |
36 | export default burger;
37 |
--------------------------------------------------------------------------------
/src/components/Burger/Burger.module.css:
--------------------------------------------------------------------------------
1 | .Burger {
2 | width: 100%;
3 | margin: auto;
4 | height: 250px;
5 | text-align: center;
6 | overflow: auto;
7 | font-weight: bold;
8 | font-size: 1.2rem;
9 | }
10 |
11 | @media (min-width: 500px) and (min-height: 400px) {
12 | .Burger {
13 | width: 350px;
14 | height: 340px;
15 | }
16 | }
17 |
18 | @media (min-width: 500px) and (min-height: 401px) {
19 | .Burger {
20 | width: 470px;
21 | height: 519px;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Burger/BurgerIngredient/BurgerIngredient.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classes from './BurgerIngredient.module.css';
3 |
4 | import PropTypes from 'prop-types';
5 | const BurgerIngredient = props => {
6 | let ingredient = null;
7 |
8 |
9 | switch (props.type) {
10 | case 'bread-bottom':
11 | ingredient = ;
12 | break;
13 |
14 | case 'bread-top':
15 | ingredient = (
16 |
20 | );
21 | break;
22 | case 'meat':
23 | ingredient = ;
24 | break;
25 | case 'cheese':
26 | ingredient = ;
27 | break;
28 | case 'bacon':
29 | ingredient = ;
30 | break;
31 | case 'salad':
32 | ingredient = ;
33 | break;
34 | default:
35 | ingredient = null;
36 | }
37 | return ingredient;
38 | };
39 |
40 | BurgerIngredient.propTypes = {
41 | type: PropTypes.string.isRequired
42 | };
43 |
44 | export default BurgerIngredient;
45 |
--------------------------------------------------------------------------------
/src/components/Burger/BurgerIngredient/BurgerIngredient.module.css:
--------------------------------------------------------------------------------
1 | .BreadBottom {
2 | height: 13%;
3 | width: 80%;
4 | background: linear-gradient(#f08e4a, #e27b36);
5 | border-radius: 0 0 30px 30px;
6 | box-shadow: inset -15px 0 #c15711;
7 | margin: 2% auto;
8 | }
9 |
10 | .BreadTop {
11 | height: 20%;
12 | width: 80%;
13 | background: linear-gradient(#bc581e, #e27b36);
14 | border-radius: 50% 50% 0 0;
15 | box-shadow: inset -15px 0 #c15711;
16 | margin: 2% auto;
17 | position: relative;
18 | }
19 |
20 | .Seeds1 {
21 | width: 10%;
22 | height: 15%;
23 | position: absolute;
24 | background-color: white;
25 | left: 30%;
26 | top: 50%;
27 | border-radius: 40%;
28 | transform: rotate(-20deg);
29 | box-shadow: inset -2px -3px #c9c9c9;
30 | }
31 |
32 | .Seeds1:after {
33 | content: '';
34 | width: 100%;
35 | height: 100%;
36 | position: absolute;
37 | background-color: white;
38 | left: -170%;
39 | top: -260%;
40 | border-radius: 40%;
41 | transform: rotate(60deg);
42 | box-shadow: inset -1px 2px #c9c9c9;
43 | }
44 |
45 | .Seeds1:before {
46 | content: '';
47 | width: 100%;
48 | height: 100%;
49 | position: absolute;
50 | background-color: white;
51 | left: 180%;
52 | top: -50%;
53 | border-radius: 40%;
54 | transform: rotate(60deg);
55 | box-shadow: inset -1px -3px #c9c9c9;
56 | }
57 |
58 | .Seeds2 {
59 | width: 10%;
60 | height: 15%;
61 | position: absolute;
62 | background-color: white;
63 | left: 64%;
64 | top: 50%;
65 | border-radius: 40%;
66 | transform: rotate(10deg);
67 | box-shadow: inset -3px 0 #c9c9c9;
68 | }
69 |
70 | .Seeds2:before {
71 | content: '';
72 | width: 100%;
73 | height: 100%;
74 | position: absolute;
75 | background-color: white;
76 | left: 150%;
77 | top: -130%;
78 | border-radius: 40%;
79 | transform: rotate(90deg);
80 | box-shadow: inset 1px 3px #c9c9c9;
81 | }
82 |
83 | .Meat {
84 | width: 80%;
85 | height: 8%;
86 | background: linear-gradient(#7f3608, #702e05);
87 | margin: 2% auto;
88 | border-radius: 15px;
89 | }
90 |
91 | .Cheese {
92 | width: 90%;
93 | height: 4.5%;
94 | margin: 2% auto;
95 | background: linear-gradient(#f4d004, #d6bb22);
96 | border-radius: 20px;
97 | }
98 |
99 | .Salad {
100 | width: 85%;
101 | height: 7%;
102 | margin: 2% auto;
103 | background: linear-gradient(#228c1d, #91ce50);
104 | border-radius: 20px;
105 | }
106 |
107 | .Bacon {
108 | width: 80%;
109 | height: 3%;
110 | background: linear-gradient(#bf3813, #c45e38);
111 | margin: 2% auto;
112 | }
113 |
--------------------------------------------------------------------------------
/src/components/Burger/OrderSummary/OrderSummary.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Aux from '../../../hoc/Auxiliary/Auxiliary';
3 | import PropTypes from 'prop-types';
4 | import Button from '../../UI/Button/Button';
5 |
6 | class OrderSummary extends Component {
7 | render() {
8 | const ingredientSummary = Object.keys(this.props.ingredients).map(igKey => {
9 | return (
10 |
11 | {igKey} :{' '}
12 | {this.props.ingredients[igKey]}
13 |
14 | );
15 | });
16 | return (
17 |
18 | Your Order
19 | A delicious burger with following ingredients:
20 |
21 |
22 | Total Price: {this.props.price.toFixed(2)}
23 |
24 | Continue to checkout ?
25 |
28 |
31 |
32 | );
33 | }
34 | }
35 |
36 | OrderSummary.propTypes = {
37 | ingredients: PropTypes.object,
38 | purchaseContinued: PropTypes.func,
39 | purchaseCanceled: PropTypes.func,
40 | price: PropTypes.number
41 | };
42 |
43 | export default OrderSummary;
44 |
--------------------------------------------------------------------------------
/src/components/Logo/Logo.jsx:
--------------------------------------------------------------------------------
1 | import classes from './Logo.module.css';
2 | import burgerLogo from '../../assets/images/burger-logo.png';
3 |
4 | const logo = props => (
5 |
6 |

7 |
8 | );
9 |
10 | export default logo;
11 |
--------------------------------------------------------------------------------
/src/components/Logo/Logo.module.css:
--------------------------------------------------------------------------------
1 | .Logo {
2 | height: 100%;
3 | padding: 8px;
4 | border-radius: 5px;
5 | pointer-events: none;
6 | background: #ffffff;
7 | box-sizing: border-box;
8 | }
9 |
10 | .Logo img {
11 | height: 100%;
12 | }
--------------------------------------------------------------------------------
/src/components/Navigation/NavigationItems/NavigationItem/NavigationItem.jsx:
--------------------------------------------------------------------------------
1 | import classes from './NavigationItem.module.css';
2 | import PropTypes from 'prop-types';
3 | import { NavLink } from 'react-router-dom';
4 |
5 | const navigationItem = props => (
6 |
7 |
8 | {props.children}
9 |
10 |
11 | );
12 |
13 | navigationItem.propTypes = {
14 | link: PropTypes.string,
15 | active: PropTypes.bool
16 | };
17 |
18 | export default navigationItem;
19 |
--------------------------------------------------------------------------------
/src/components/Navigation/NavigationItems/NavigationItem/NavigationItem.module.css:
--------------------------------------------------------------------------------
1 | .NavigationItem {
2 | margin: 10px 0;
3 | box-sizing: border-box;
4 | display: block;
5 | width: 100%;
6 | }
7 |
8 | .NavigationItem a {
9 | color: #8f5c2c;
10 | text-decoration: none;
11 | width: 100%;
12 | box-sizing: border-box;
13 | display: block;
14 | }
15 |
16 | .NavigationItem a:hover,
17 | .NavigationItem a:active,
18 | .NavigationItem a.Active {
19 | color: #40a4c8;
20 | }
21 |
22 | @media (min-width: 500px) {
23 | .NavigationItem {
24 | margin: 0;
25 | display: flex;
26 | height: 100%;
27 | width: auto;
28 | align-items: center;
29 | }
30 |
31 | .NavigationItem a {
32 | color: white;
33 | height: 100%;
34 | padding: 16px 10px;
35 | border-bottom: 4px solid transparent;
36 | }
37 |
38 | .NavigationItem a:hover,
39 | .NavigationItem a:active,
40 | .NavigationItem a.Active {
41 | background-color: #8f5c2c;
42 | border-bottom: 4px solid #40a4c8;
43 | color: white;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/components/Navigation/NavigationItems/NavigationItems.jsx:
--------------------------------------------------------------------------------
1 | import classes from './NavigationItems.module.css';
2 | import NavigationItem from './NavigationItem/NavigationItem';
3 |
4 | const navigationItems = props => (
5 |
6 |
7 | Burger Builder
8 |
9 | Orders
10 |
11 | );
12 | export default navigationItems;
--------------------------------------------------------------------------------
/src/components/Navigation/NavigationItems/NavigationItems.module.css:
--------------------------------------------------------------------------------
1 | .NavigationItems {
2 | margin: 0;
3 | padding: 0;
4 | list-style: none;
5 | display: flex;
6 | flex-flow: column;
7 | align-items: center;
8 | height: 100%;
9 | }
10 |
11 | @media (min-width: 500px) {
12 | .NavigationItems {
13 | flex-flow: row;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/components/Navigation/SideDrawer/DrawerToggle/DrawerToggle.jsx:
--------------------------------------------------------------------------------
1 | import classes from './DrawerToggle.module.css';
2 | import PropTypes from 'prop-types';
3 | const drawerToggle = props => (
4 |
9 | );
10 |
11 | drawerToggle.propTypes = {
12 | clicked: PropTypes.func
13 | };
14 |
15 | export default drawerToggle;
16 |
--------------------------------------------------------------------------------
/src/components/Navigation/SideDrawer/DrawerToggle/DrawerToggle.module.css:
--------------------------------------------------------------------------------
1 | .DrawerToggle {
2 | width: 40px;
3 | height: 100%;
4 | display: flex;
5 | flex-flow: column;
6 | justify-content: space-around;
7 | align-items: center;
8 | padding: 10px 0;
9 | box-sizing: border-box;
10 | cursor: pointer;
11 | }
12 |
13 | .DrawerToggle div {
14 | width: 90%;
15 | height: 3px;
16 | background-color: white;
17 | }
18 |
19 | @media (min-width: 500px) {
20 | .DrawerToggle {
21 | display: none;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Navigation/SideDrawer/SideDrawer.jsx:
--------------------------------------------------------------------------------
1 | import NavigationItems from '../NavigationItems/NavigationItems';
2 | import Logo from '../../Logo/Logo';
3 | import PropTypes from 'prop-types';
4 | import classes from './SideDrawer.module.css';
5 | import Backdrop from '../../UI/Backdrop/Backdrop';
6 | import Aux from '../../../hoc/Auxiliary/Auxiliary';
7 |
8 | const SideDrawer = props => {
9 | let attachedClassed = [classes.SideDrawer, classes.Close];
10 | if (props.open) {
11 | attachedClassed = [classes.SideDrawer, classes.Open];
12 | }
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 | );
26 | };
27 |
28 | SideDrawer.propTypes = {
29 | open: PropTypes.bool,
30 | closed: PropTypes.func
31 | };
32 |
33 | export default SideDrawer;
34 |
--------------------------------------------------------------------------------
/src/components/Navigation/SideDrawer/SideDrawer.module.css:
--------------------------------------------------------------------------------
1 | .SideDrawer {
2 | height: 100%;
3 | width: 280px;
4 | max-width: 70%;
5 | position: fixed;
6 | top: 0;
7 | left: 0;
8 | z-index: 200;
9 | background: #fff;
10 | padding: 32px 16px;
11 | box-sizing: border-box;
12 | transition: transform 0.3s ease-out;
13 | }
14 | @media (min-width: 500px) {
15 | .SideDrawer {
16 | display: none;
17 | }
18 | }
19 | .Open {
20 | transform: translateX(0);
21 | }
22 | .Close {
23 | transform: translateX(-100%);
24 | }
25 | .Logo {
26 | height: 11%;
27 | }
--------------------------------------------------------------------------------
/src/components/Navigation/Toolbar/Toolbar.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import Logo from '../../Logo/Logo';
3 | import NavigationItems from '../NavigationItems/NavigationItems';
4 | import DrawerToggle from '../SideDrawer/DrawerToggle/DrawerToggle';
5 | import classes from './Toolbar.module.css';
6 |
7 | const toolbar = props => (
8 |
9 |
10 | {/* MENU
*/}
11 |
12 |
13 |
14 |
17 |
18 | );
19 |
20 | toolbar.propTypes = {
21 | drawerToggleClicked: PropTypes.func
22 | };
23 |
24 | export default toolbar;
25 |
--------------------------------------------------------------------------------
/src/components/Navigation/Toolbar/Toolbar.module.css:
--------------------------------------------------------------------------------
1 | .Toolbar {
2 | top: 0;
3 | left: 0;
4 | z-index: 90;
5 | width: 100%;
6 | height: 56px;
7 | display: flex;
8 | position: fixed;
9 | padding: 0 20px;
10 | background: #703b09;
11 | align-items: center;
12 | box-sizing: border-box;
13 | justify-content: space-between;
14 | }
15 |
16 | .Toolbar nav {
17 | height: 100%;
18 | }
19 | .Logo {
20 | height: 80%;
21 | }
22 | @media (max-width: 499px) {
23 | .DesktopOnly {
24 | display: none;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/Order/CheckoutSummary/CheckoutSummary.jsx:
--------------------------------------------------------------------------------
1 | import classes from './CheckoutSummary.module.css';
2 | import Burger from '../../Burger/Burger';
3 | import Button from '../../UI/Button/Button';
4 |
5 | const checkoutSummary = props => {
6 | return (
7 |
8 |
We hope it tastes well
9 |
10 |
11 |
12 |
15 |
18 |
19 | );
20 | };
21 |
22 | export default checkoutSummary;
23 |
--------------------------------------------------------------------------------
/src/components/Order/CheckoutSummary/CheckoutSummary.module.css:
--------------------------------------------------------------------------------
1 | .CheckoutSummary {
2 | text-align: center;
3 | margin: auto;
4 | }
--------------------------------------------------------------------------------
/src/components/Order/Order.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import classes from './Order.module.css';
4 |
5 | const Order = props => {
6 | const ingredients = [];
7 |
8 | for (let ingredientName in props.ingredients) {
9 | ingredients.push({
10 | name: ingredientName,
11 | amount: props.ingredients[ingredientName]
12 | });
13 | }
14 |
15 | const ingredientOutput = ingredients.map(ig => {
16 | return (
17 |
26 | {ig.name} ({ig.amount})
27 |
28 | );
29 | });
30 |
31 | return (
32 |
33 |
Ingredients: {ingredientOutput}
34 |
35 | Price: USD {Number.parseFloat(props.price).toFixed(2)}
36 |
37 |
38 | );
39 | };
40 |
41 | export default Order;
42 |
--------------------------------------------------------------------------------
/src/components/Order/Order.module.css:
--------------------------------------------------------------------------------
1 | .Order {
2 | width: 80%;
3 | border: 1px solid #eee;
4 | box-shadow: 0 2px 3px #ccc;
5 | margin: 10px auto;
6 | padding: 10px;
7 | box-sizing: border-box;
8 | }
--------------------------------------------------------------------------------
/src/components/UI/Backdrop/Backdrop.jsx:
--------------------------------------------------------------------------------
1 | import classes from './Backdrop.module.css';
2 | import PropTypes from 'prop-types';
3 | const backdrop = props =>
4 | props.show ? (
5 |
6 | ) : null;
7 |
8 | backdrop.propTypes = {
9 | clicked: PropTypes.func
10 | };
11 | export default backdrop;
--------------------------------------------------------------------------------
/src/components/UI/Backdrop/Backdrop.module.css:
--------------------------------------------------------------------------------
1 | .Backdrop {
2 | top: 0;
3 | left: 0;
4 | width: 100%;
5 | height: 100%;
6 | z-index: 100;
7 | position: fixed;
8 | background-color: rgba(0, 0, 0, 0.5);
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/UI/Button/Button.jsx:
--------------------------------------------------------------------------------
1 | import classes from './Button.module.css';
2 | import PropTypes from 'prop-types';
3 |
4 | const button = ({ btnType, clicked, children, disabled }) => (
5 |
11 | );
12 | button.propTypes = {
13 | btnType: PropTypes.string,
14 | clicked: PropTypes.func
15 | };
16 | export default button;
17 |
--------------------------------------------------------------------------------
/src/components/UI/Button/Button.module.css:
--------------------------------------------------------------------------------
1 | .Button {
2 | background-color: transparent;
3 | border: none;
4 | color: white;
5 | outline: none;
6 | cursor: pointer;
7 | font: inherit;
8 | padding: 10px;
9 | margin: 10px;
10 | font-weight: bold;
11 | }
12 |
13 | .Button:first-of-type {
14 | margin-left: 0;
15 | padding-left: 0;
16 | }
17 |
18 | .Button:disabled {
19 | color: #ccc;
20 | cursor: not-allowed;
21 | }
22 |
23 | .Success {
24 | color: #5c9210;
25 | }
26 |
27 | .Danger {
28 | color: #944317;
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/UI/Input/Input.jsx:
--------------------------------------------------------------------------------
1 | import classes from './Input.module.css';
2 |
3 | const Input = props => {
4 | const {
5 | label,
6 | elementType,
7 | elementConfig,
8 | value,
9 | changed,
10 | inValid,
11 | shouldValidate,
12 | touched
13 | } = props;
14 | let inputElement = null;
15 | const inputClasses = [classes.InputElement];
16 |
17 | if (inValid && shouldValidate && touched) {
18 | inputClasses.push(classes.InValid);
19 | }
20 |
21 | switch (elementType) {
22 | case 'input':
23 | inputElement = (
24 |
30 | );
31 | break;
32 | case 'textarea':
33 | inputElement = (
34 |
40 | );
41 | break;
42 | case 'select':
43 | inputElement = (
44 |
54 | );
55 | break;
56 | default:
57 | inputElement = (
58 |
64 | );
65 | break;
66 | }
67 |
68 | return (
69 |
70 |
71 | {inputElement}
72 |
73 | );
74 | };
75 |
76 | export default Input;
77 |
--------------------------------------------------------------------------------
/src/components/UI/Input/Input.module.css:
--------------------------------------------------------------------------------
1 | .Input {
2 | width: 100%;
3 | padding: 10px;
4 | }
5 |
6 | .Label {
7 | font-weight: bold;
8 | display: block;
9 | margin-bottom: 8px;
10 | }
11 |
12 | .InputElement {
13 | outline: none;
14 | border: 1px solid #ccc;
15 | background: #fff;
16 | font: inherit;
17 | padding: 6px 10px;
18 | display: block;
19 | width: 100%;
20 | }
21 |
22 | .InputElement:focus {
23 | outline: none;
24 | border: 1px solid #f08e4a;
25 | }
26 |
27 | .InValid {
28 | border: 1px solid #e44545;
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/UI/Modal/Modal.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import classes from './Modal.module.css';
3 | import PropTypes from 'prop-types';
4 | import Backdrop from '../Backdrop/Backdrop';
5 | import Aux from '../../../hoc/Auxiliary/Auxiliary';
6 | class Modal extends Component {
7 | shouldComponentUpdate(nextProps) {
8 | return (
9 | nextProps.show !== this.props.show ||
10 | nextProps.children !== this.props.children
11 | );
12 | }
13 | render() {
14 | return (
15 |
16 |
17 |
23 | {this.props.children}
24 |
25 |
26 | );
27 | }
28 | }
29 |
30 | Modal.propTypes = {
31 | modalClosed: PropTypes.func,
32 | show: PropTypes.bool
33 | };
34 | export default Modal;
35 |
--------------------------------------------------------------------------------
/src/components/UI/Modal/Modal.module.css:
--------------------------------------------------------------------------------
1 | .Modal {
2 | position: fixed;
3 | z-index: 500;
4 | background-color: white;
5 | width: 70%;
6 | border: 1px solid #ccc;
7 | box-shadow: 1px 1px 1px black;
8 | padding: 16px;
9 | left: 15%;
10 | top: 30%;
11 | box-sizing: border-box;
12 | transition: all 0.3s ease-out;
13 | }
14 |
15 | @media (min-width: 600px) {
16 | .Modal {
17 | width: 500px;
18 | left: calc(50% - 250px);
19 | }
20 | }
--------------------------------------------------------------------------------
/src/components/UI/Spinner/Spinner.jsx:
--------------------------------------------------------------------------------
1 | import classes from './Spinner.module.css';
2 |
3 | const spinner = () => (
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 | export default spinner;
17 |
--------------------------------------------------------------------------------
/src/components/UI/Spinner/Spinner.module.css:
--------------------------------------------------------------------------------
1 | .Loader {
2 | position: relative;
3 | width: 80px;
4 | height: 80px;
5 | margin: 0 auto;
6 | }
7 | .Loader div {
8 | position: absolute;
9 | width: 16px;
10 | height: 16px;
11 | border-radius: 50%;
12 | background: #d07b13;
13 | animation: Loader 1.2s linear infinite;
14 | }
15 | .Loader div:nth-child(1) {
16 | top: 8px;
17 | left: 8px;
18 | animation-delay: 0s;
19 | }
20 | .Loader div:nth-child(2) {
21 | top: 8px;
22 | left: 32px;
23 | animation-delay: -0.4s;
24 | }
25 | .Loader div:nth-child(3) {
26 | top: 8px;
27 | left: 56px;
28 | animation-delay: -0.8s;
29 | }
30 | .Loader div:nth-child(4) {
31 | top: 32px;
32 | left: 8px;
33 | animation-delay: -0.4s;
34 | }
35 | .Loader div:nth-child(5) {
36 | top: 32px;
37 | left: 32px;
38 | animation-delay: -0.8s;
39 | }
40 | .Loader div:nth-child(6) {
41 | top: 32px;
42 | left: 56px;
43 | animation-delay: -1.2s;
44 | }
45 | .Loader div:nth-child(7) {
46 | top: 56px;
47 | left: 8px;
48 | animation-delay: -0.8s;
49 | }
50 | .Loader div:nth-child(8) {
51 | top: 56px;
52 | left: 32px;
53 | animation-delay: -1.2s;
54 | }
55 | .Loader div:nth-child(9) {
56 | top: 56px;
57 | left: 56px;
58 | animation-delay: -1.6s;
59 | }
60 | @keyframes Loader {
61 | 0%, 100% {
62 | opacity: 1;
63 | }
64 | 50% {
65 | opacity: 0.5;
66 | }
67 | }
--------------------------------------------------------------------------------
/src/containers/BurgerBuilder/BurgerBuilder.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Burger from '../../components/Burger/Burger';
3 | import Aux from '../../hoc/Auxiliary/Auxiliary';
4 | import BuildControls from '../../components/Burger/BuildControls/BuildControls';
5 | import OrderSummary from '../../components/Burger/OrderSummary/OrderSummary';
6 | import Modal from '../../components/UI/Modal/Modal';
7 | import axios from '../../axios-orders';
8 | import Spinner from '../../components/UI/Spinner/Spinner';
9 | import withErrorHandler from '../../hoc/withErrorHandler/withErrorHandler';
10 | import { connect } from 'react-redux';
11 | import * as actions from '../../store/actions/index';
12 |
13 | class BurgerBuilder extends Component {
14 | state = {
15 | purchasable: false,
16 | purchasing: false
17 | };
18 |
19 | updatePurchaseState(ingredients) {
20 | const sum = Object.keys(ingredients)
21 | .map(igKey => {
22 | return ingredients[igKey];
23 | })
24 | .reduce((sum, el) => {
25 | return sum + el;
26 | }, 0);
27 | return sum > 0;
28 | }
29 |
30 | purchaseHandler = () => {
31 | this.setState({ purchasing: true });
32 | };
33 |
34 | purchaseCancelHandler = () => {
35 | this.setState({ purchasing: false });
36 | };
37 |
38 | purchaseContinueHandler = () => {
39 | this.props.onInitPurchase();
40 | this.props.history.push('/checkout');
41 | };
42 |
43 | componentDidMount() {
44 | this.props.onSetIngredients();
45 | }
46 |
47 | render() {
48 | const ingredientsInfo = { ...this.props.ingredients };
49 |
50 | for (let key in ingredientsInfo) {
51 | ingredientsInfo[key] = ingredientsInfo[key] <= 0;
52 | }
53 | let burger = this.props.error ? (
54 | Ingredients can't be loaded
55 | ) : (
56 |
57 | );
58 | let orderSummary = null;
59 |
60 | if (this.props.ingredients) {
61 | burger = (
62 |
63 |
64 |
73 |
74 | );
75 | orderSummary = (
76 |
82 | );
83 | }
84 | return (
85 |
86 |
89 | {orderSummary}
90 |
91 | {burger}
92 |
93 | );
94 | }
95 | }
96 | const mapStateToProps = state => {
97 | return {
98 | ingredients: state.burgerBuilder.ingredients,
99 | totalPrice: state.burgerBuilder.totalPrice,
100 | error: state.burgerBuilder.error
101 | };
102 | };
103 |
104 | const mapDispatchToProps = dispatch => {
105 | return {
106 | onIngredienAdded: ingName => dispatch(actions.addIngredient(ingName)),
107 | onIngredienRemoved: ingName => dispatch(actions.removeIngredient(ingName)),
108 | onSetIngredients: () => dispatch(actions.initIngredients()),
109 | onInitPurchase: () => dispatch(actions.purchaseInit())
110 | };
111 | };
112 |
113 | export default connect(
114 | mapStateToProps,
115 | mapDispatchToProps
116 | )(withErrorHandler(BurgerBuilder, axios));
117 |
--------------------------------------------------------------------------------
/src/containers/Checkout/Checkout.jsx:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import { Route, Redirect } from 'react-router-dom';
3 | import CheckoutSummary from '../../components/Order/CheckoutSummary/CheckoutSummary';
4 | import ContactData from './ContactData/ContactData';
5 | import { connect } from 'react-redux';
6 | class Checkout extends Component {
7 | checkoutCancelledHandler = () => {
8 | this.props.history.goBack();
9 | };
10 |
11 | checkoutContinuedHandler = () => {
12 | this.props.history.replace('/checkout/contact-data');
13 | };
14 |
15 | render() {
16 | let summary = ;
17 | const purchasedRedirect = this.props.purchased && ;
18 | /* will redirect to home page if
19 | * the ingredients were not availbable
20 | */
21 | if (this.props.ingredients) {
22 | summary = (
23 |
24 |
29 |
33 | {purchasedRedirect}
34 |
35 | );
36 | }
37 |
38 | return summary;
39 | }
40 | }
41 | const mapStateToProps = state => {
42 | return {
43 | ingredients: state.burgerBuilder.ingredients,
44 | purchased: state.order.purchased
45 | };
46 | };
47 |
48 | export default connect(mapStateToProps)(Checkout);
49 |
--------------------------------------------------------------------------------
/src/containers/Checkout/ContactData/ContactData.jsx:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Button from '../../../components/UI/Button/Button';
3 | import classes from './ContactData.module.css';
4 | import Spinner from '../../../components/UI/Spinner/Spinner';
5 | import { connect } from 'react-redux';
6 | import Input from './../../../components/UI/Input/Input';
7 | import * as orderActions from '../../../store/actions/index';
8 | import withErrorHandler from './../../../hoc/withErrorHandler/withErrorHandler';
9 | import axios from '../../../axios-orders';
10 |
11 | class ContactData extends Component {
12 | state = {
13 | orderForm: {
14 | name: {
15 | elementType: 'input',
16 | elementConfig: {
17 | type: 'text',
18 | placeholder: 'Your Name'
19 | },
20 | value: '',
21 | validation: {
22 | required: true
23 | },
24 | valid: false,
25 | touched: false
26 | },
27 | street: {
28 | elementType: 'input',
29 | elementConfig: {
30 | type: 'text',
31 | placeholder: 'Street'
32 | },
33 | value: '',
34 | validation: {
35 | required: true
36 | },
37 | valid: false,
38 | touched: false
39 | },
40 | zipCode: {
41 | elementType: 'input',
42 | elementConfig: {
43 | type: 'text',
44 | placeholder: 'ZIP Code'
45 | },
46 | value: '',
47 | validation: {
48 | required: true,
49 | minLength: 5,
50 | maxlength: 5
51 | },
52 | valid: false,
53 | touched: false
54 | },
55 | country: {
56 | elementType: 'input',
57 | elementConfig: {
58 | type: 'text',
59 | placeholder: 'Country'
60 | },
61 | value: '',
62 | validation: {
63 | required: true
64 | },
65 | valid: false,
66 | touched: false
67 | },
68 | email: {
69 | elementType: 'input',
70 | elementConfig: {
71 | type: 'email',
72 | placeholder: 'Your E-Mail'
73 | },
74 | value: '',
75 | validation: {
76 | required: true
77 | },
78 | valid: false,
79 | touched: false
80 | },
81 | deliveryMethod: {
82 | elementType: 'select',
83 | elementConfig: {
84 | options: [
85 | { value: 'fastest', displayValue: 'Fastest' },
86 | { value: 'cheapest', displayValue: 'Cheapest' }
87 | ]
88 | },
89 | validation: {},
90 | value: 'fastest',
91 | valid: true
92 | }
93 | },
94 | loading: false,
95 | isFormValid: false
96 | };
97 |
98 | orderHandler = e => {
99 | e.preventDefault();
100 | const formData = {};
101 | for (let key in this.state.orderForm) {
102 | formData[key] = this.state.orderForm[key].value;
103 | }
104 | const order = {
105 | ingredients: this.props.ingredients,
106 | price: this.props.totalPrice,
107 | contactData: formData
108 | };
109 |
110 | this.props.onOrderBuilder(order);
111 | };
112 |
113 | checkValidaty = (value, rules) => {
114 | let isValid = true;
115 |
116 | if (rules.required) {
117 | isValid = value.trim() !== '';
118 | }
119 |
120 | if (rules.minLength && rules.maxlength) {
121 | isValid =
122 | value.length >= rules.minLength && value.length <= rules.maxlength;
123 | }
124 | return isValid;
125 | };
126 |
127 | inputChangedHandler = (event, id) => {
128 | const updatedOrderForm = {
129 | ...this.state.orderForm
130 | };
131 | const updatedFormElement = {
132 | ...updatedOrderForm[id]
133 | };
134 |
135 | updatedFormElement.value = event.target.value;
136 | updatedFormElement.valid = this.checkValidaty(
137 | updatedFormElement.value,
138 | updatedFormElement.validation
139 | );
140 | updatedFormElement.touched = true;
141 | updatedOrderForm[id] = updatedFormElement;
142 |
143 | let formIsVaid = true;
144 |
145 | for (let key in updatedOrderForm) {
146 | formIsVaid = updatedOrderForm[key].valid && formIsVaid;
147 | }
148 |
149 | this.setState({ orderForm: updatedOrderForm, isFormValid: formIsVaid });
150 | };
151 | render() {
152 | const formElements = [];
153 | for (let key in this.state.orderForm) {
154 | formElements.push({
155 | id: key,
156 | config: this.state.orderForm[key]
157 | });
158 | }
159 |
160 | let form = (
161 |
178 | );
179 | if (this.props.loading) {
180 | form = ;
181 | }
182 | return (
183 |
184 |
Enter your Contact Data
185 | {form}
186 |
187 | );
188 | }
189 | }
190 |
191 | const mapStateToProps = state => {
192 | return {
193 | ingredients: state.burgerBuilder.ingredients,
194 | totalPrice: state.burgerBuilder.totalPrice,
195 | loading: state.order.loading
196 | };
197 | };
198 |
199 | const mapDispatchToProps = dispatch => {
200 | return {
201 | onOrderBuilder: orderData =>
202 | dispatch(orderActions.purchaseBurger(orderData))
203 | };
204 | };
205 |
206 | export default connect(
207 | mapStateToProps,
208 | mapDispatchToProps
209 | )(withErrorHandler(ContactData, axios));
210 |
--------------------------------------------------------------------------------
/src/containers/Checkout/ContactData/ContactData.module.css:
--------------------------------------------------------------------------------
1 | .ContactData {
2 | margin: 2rem auto;
3 | width: 80%;
4 | text-align: center;
5 | box-shadow: 0 2px 3px #ccc;
6 | border: 1px solid #eee;
7 | box-sizing: border-box;
8 | }
9 |
10 | .Input {
11 | display: block;
12 | }
13 |
14 | @media (min-width: 600px) {
15 | .ContactData {
16 | width: 500px;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/containers/Orders/Orders.jsx:
--------------------------------------------------------------------------------
1 | import axios from '../../axios-orders';
2 | import { Component } from 'react';
3 | import Order from '../../components/Order/Order';
4 | import Spinner from '../../components/UI/Spinner/Spinner';
5 | import withErrorHandler from '../../hoc/withErrorHandler/withErrorHandler';
6 | import { fetchOrders } from '../../store/actions';
7 | import { connect } from 'react-redux';
8 | class Orders extends Component {
9 | state = {
10 | orders: [],
11 | loading: true
12 | };
13 |
14 | componentDidMount() {
15 | this.props.onFetchOrders();
16 | }
17 |
18 | render() {
19 | let orders = this.props.orders.map(order => (
20 |
25 | ));
26 |
27 | if (this.props.loading) {
28 | orders = ;
29 | }
30 |
31 | return {orders}
;
32 | }
33 | }
34 |
35 | const mapDispatchToProps = dispatch => {
36 | return {
37 | onFetchOrders: () => dispatch(fetchOrders())
38 | };
39 | };
40 |
41 | const mapStateToProps = state => {
42 | return {
43 | orders: state.order.orders,
44 | loading: state.order.loading
45 | };
46 | };
47 |
48 | export default connect(
49 | mapStateToProps,
50 | mapDispatchToProps
51 | )(withErrorHandler(Orders, axios));
52 |
--------------------------------------------------------------------------------
/src/containers/Orders/Orders.module.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pooridev/burger-builder/c25f1f6e5b5ff11edd10d54ac472e72c9b95eba5/src/containers/Orders/Orders.module.css
--------------------------------------------------------------------------------
/src/hoc/Auxiliary/Auxiliary.js:
--------------------------------------------------------------------------------
1 | const aux = props => props.children;
2 | export default aux;
3 |
--------------------------------------------------------------------------------
/src/hoc/Layout/Layout.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Aux from '../Auxiliary/Auxiliary';
3 | import SideDrawer from '../../components/Navigation/SideDrawer/SideDrawer';
4 | import Toolbar from '../../components/Navigation/Toolbar/Toolbar';
5 | import classes from './Layout.module.css';
6 |
7 | class Layout extends Component {
8 | state = {
9 | showSideDrawer: false
10 | };
11 |
12 | sideDrawerClosedHandler = () => {
13 | this.setState({ showSideDrawer: false });
14 | };
15 | sideDrawerOpenHandler = () => {
16 | this.setState(prevState => {
17 | return { showSideDrawer: !prevState.showSideDrawer };
18 | });
19 | };
20 |
21 | render() {
22 | return (
23 |
24 |
25 |
29 | {this.props.children}
30 |
31 | );
32 | }
33 | }
34 |
35 | export default Layout;
36 |
--------------------------------------------------------------------------------
/src/hoc/Layout/Layout.module.css:
--------------------------------------------------------------------------------
1 | .Content {
2 | margin: 67px 0 0;
3 | }
--------------------------------------------------------------------------------
/src/hoc/withErrorHandler/withErrorHandler.jsx:
--------------------------------------------------------------------------------
1 | import { Component } from 'react';
2 | import Modal from '../../components/UI/Modal/Modal';
3 | import Aux from '../Auxiliary/Auxiliary';
4 |
5 | const withErrorHandler = (WrappedComponent, axios) => {
6 | return class extends Component {
7 | state = {
8 | error: null
9 | };
10 |
11 | componentWillMount() {
12 | this.reqInterceptor = axios.interceptors.request.use(req => {
13 | this.setState({ error: null });
14 | return req;
15 | });
16 | this.resInterceptor = axios.interceptors.response.use(
17 | res => res,
18 | error => {
19 | this.setState({ error: error });
20 | }
21 | );
22 | }
23 |
24 | componentWillUnmount() {
25 | axios.interceptors.request.eject(this.reqInterceptor);
26 | axios.interceptors.response.eject(this.resInterceptor);
27 | }
28 |
29 | errorConfirmedHandler = () => {
30 | this.setState({ error: null });
31 | };
32 |
33 | render() {
34 | return (
35 |
36 |
39 | {this.state.error ? this.state.error.message : null}
40 |
41 |
42 |
43 | );
44 | }
45 | };
46 | };
47 |
48 | export default withErrorHandler;
49 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 | body {
5 | margin: 0;
6 | font-family: 'Open Sans', sans-serif;
7 | }
8 |
9 | .center {
10 | margin: auto;
11 | text-align: center;
12 | }
13 |
14 | ::-webkit-scrollbar {
15 | background-color: rgb(230, 230, 230);
16 | border-radius: 5px;
17 | width: 4px;
18 | }
19 | ::-webkit-scrollbar-thumb {
20 | background-color: #b1b1b1;
21 | border-radius: 5px;
22 | }
23 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { applyMiddleware, compose, createStore, combineReducers } from 'redux';
4 | import { Provider } from 'react-redux';
5 | import thunk from 'redux-thunk';
6 | import './index.css';
7 | import App from './App';
8 |
9 | import burgerBuilderReducer from './store/reducers/burgerBuilder';
10 | import orderReducer from './store/reducers/order';
11 |
12 | import { BrowserRouter as Router } from 'react-router-dom';
13 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
14 |
15 | const rootReducer = combineReducers({
16 | burgerBuilder: burgerBuilderReducer,
17 | order: orderReducer
18 | });
19 |
20 | const store = createStore(
21 | rootReducer,
22 | composeEnhancers(applyMiddleware(thunk))
23 | );
24 |
25 | const app = (
26 |
27 |
28 |
29 |
30 |
31 | );
32 |
33 | ReactDOM.render(app, document.getElementById('root'));
34 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/store/actions/actionTypes.js:
--------------------------------------------------------------------------------
1 | export const ADD_INGREDIENT = 'ADD_INGREDIENT';
2 | export const REMOVE_INGREDIENT = 'REMOVE_INGREDIENT';
3 | export const SET_INGREDIENTS = 'SET_INGREDIENTS';
4 | export const FETCH_INGREDIENTS_FAILED = 'FETCH_INGREDIENTS_FAILED';
5 |
6 | export const PURCHASE_BURGER_SUCCESS = 'PURCHASE_BURGER_SUCCESS';
7 | export const PURCHASE_BURGER_START = 'PURCHASE_BURGER_START';
8 | export const PURCHASE_BURGER_FAIL = 'PURCHASE_BURGER_FAIL';
9 | export const PURCHASE_INIT = 'PURCHASE_INIT';
10 |
11 | export const FETCH_ORDERS_START = 'FETCH_ORDERS_START';
12 | export const FETCH_ORDERS_SUCCESS = 'FETCH_ORDERS_SUCCESS';
13 | export const FETCH_ORDERS_FAIL = 'FETCH_ORDERS_FAIL';
14 |
--------------------------------------------------------------------------------
/src/store/actions/burgerBuilder.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import * as actionTypes from './actionTypes';
3 |
4 | export const addIngredient = name => {
5 | return {
6 | type: actionTypes.ADD_INGREDIENT,
7 | ingredientName: name
8 | };
9 | };
10 |
11 | export const removeIngredient = name => {
12 | return {
13 | type: actionTypes.REMOVE_INGREDIENT,
14 | ingredientName: name
15 | };
16 | };
17 |
18 | export const setIngredients = ingredients => {
19 | return {
20 | type: actionTypes.SET_INGREDIENTS,
21 | ingredients: ingredients
22 | };
23 | };
24 |
25 | export const fetchIngredientsFailed = () => {
26 | return {
27 | type: actionTypes.FETCH_INGREDIENTS_FAILED
28 | };
29 | };
30 |
31 | export const initIngredients = () => {
32 | return dispatch => {
33 | axios
34 | .get(
35 | 'https://burger-builder-ff829-default-rtdb.firebaseio.com/ingredients.json'
36 | )
37 | .then(response => {
38 | dispatch(setIngredients(response.data));
39 | })
40 | .catch(err => dispatch(fetchIngredientsFailed()));
41 | };
42 | };
43 |
--------------------------------------------------------------------------------
/src/store/actions/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | addIngredient,
3 | initIngredients,
4 | removeIngredient,
5 | } from './burgerBuilder';
6 |
7 | export {
8 | fetchOrders,
9 | purchaseBurger,
10 | purchaseInit
11 | } from './order';
12 |
--------------------------------------------------------------------------------
/src/store/actions/order.js:
--------------------------------------------------------------------------------
1 | import axios from '../../axios-orders';
2 | import * as actionTypes from './actionTypes';
3 |
4 | export const purchaseBurgerSuccess = (id, orderData) => {
5 | return {
6 | type: actionTypes.PURCHASE_BURGER_SUCCESS,
7 | orderId: id,
8 | orderData: orderData
9 | };
10 | };
11 |
12 | export const purchaseBurgerFail = error => {
13 | return {
14 | type: actionTypes.PURCHASE_BURGER_FAIL,
15 | error: error
16 | };
17 | };
18 |
19 | export const purchaseBurgerStart = () => {
20 | return {
21 | type: actionTypes.PURCHASE_BURGER_START
22 | };
23 | };
24 |
25 | export const purchaseBurger = orderData => {
26 | return dispatch => {
27 | dispatch(purchaseBurgerStart());
28 | axios
29 | .post('/orders.json', orderData)
30 | .then(res => {
31 | dispatch(purchaseBurgerSuccess(res.data.name, orderData));
32 | })
33 | .catch(err => dispatch(purchaseBurgerFail(err)));
34 | };
35 | };
36 |
37 | export const purchaseInit = () => {
38 | return {
39 | type: actionTypes.PURCHASE_INIT
40 | };
41 | };
42 |
43 | export const fetchOrdersSuccess = orders => {
44 | return {
45 | type: actionTypes.FETCH_ORDERS_SUCCESS,
46 | orders: orders
47 | };
48 | };
49 |
50 | export const fetchOrdersFail = error => {
51 | return {
52 | type: actionTypes.FETCH_ORDERS_FAIL,
53 | error: error
54 | };
55 | };
56 |
57 | export const fetchOrdersStart = () => {
58 | return {
59 | type: actionTypes.FETCH_ORDERS_START
60 | };
61 | };
62 |
63 | export const fetchOrders = () => {
64 | return dispatch => {
65 | dispatch(fetchOrdersStart());
66 | axios
67 | .get('/orders.json')
68 | .then(res => {
69 | const fetchedOrders = [];
70 | for (let key in res.data) {
71 | fetchedOrders.push({
72 | ...res.data[key],
73 | id: key
74 | });
75 | }
76 | dispatch(fetchOrdersSuccess(fetchedOrders));
77 | })
78 | .catch(err => {
79 | dispatch(fetchOrdersFail(err));
80 | });
81 | };
82 | };
83 |
--------------------------------------------------------------------------------
/src/store/reducers/burgerBuilder.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from '../actions/actionTypes';
2 | import { updateObject } from '../utility';
3 |
4 | const initialState = {
5 | ingredients: null,
6 | totalPrice: 4,
7 | error: false
8 | };
9 |
10 | const INGREDIENT_PRICE = {
11 | salad: 1,
12 | cheese: 0.7,
13 | bacon: 0.9,
14 | meat: 1.5
15 | };
16 | /******** Add Ingredient handler ********/
17 | const addIngredient = (state, action) => {
18 | const updatedIngredient = {
19 | [action.ingredientName]: state.ingredients[action.ingredientName] + 1
20 | };
21 | const updatedIngredients = updateObject(state.ingredients, updatedIngredient);
22 | const updatedState = {
23 | ingredients: updatedIngredients,
24 | totalPrice: state.totalPrice + INGREDIENT_PRICE[action.ingredientName]
25 | };
26 |
27 | return updatedState;
28 | };
29 |
30 | /**** remove Ingredient handler ***/
31 | const removeIngredient = (state, action) => {
32 | const updatedIngredient = {
33 | [action.ingredientName]: state.ingredients[action.ingredientName] - 1
34 | };
35 | const updatedIngredients = updateObject(state.ingredients, updatedIngredient);
36 | const updatedState = {
37 | ingredients: updatedIngredients,
38 | totalPrice: state.totalPrice + INGREDIENT_PRICE[action.ingredientName]
39 | };
40 |
41 | return updatedState;
42 | };
43 |
44 | /** save the fetched Ingredients
45 | ** from the firebase database
46 | */
47 | const setIngredients = (state, action) => {
48 | return updateObject(state, {
49 | ingredients: action.ingredients,
50 | error: false,
51 | totalPrice: 0
52 | });
53 | };
54 |
55 | /** set error to 'true' when fething
56 | * process failed
57 | */
58 | const fetchIngredientsFailed = state => {
59 | return updateObject(state, { error: true });
60 | };
61 |
62 | const reducer = (state = initialState, action) => {
63 | switch (action.type) {
64 | case actionTypes.ADD_INGREDIENT:
65 | return addIngredient(state, action);
66 | case actionTypes.REMOVE_INGREDIENT:
67 | return removeIngredient(state, action);
68 | case actionTypes.SET_INGREDIENTS:
69 | return setIngredients(state, action);
70 | case actionTypes.FETCH_INGREDIENTS_FAILED:
71 | return fetchIngredientsFailed(state);
72 | default:
73 | return state;
74 | }
75 | };
76 |
77 | export default reducer;
78 |
--------------------------------------------------------------------------------
/src/store/reducers/order.js:
--------------------------------------------------------------------------------
1 | import * as actionTypes from '../actions/actionTypes';
2 | import { updateObject } from './../utility';
3 |
4 | const initialState = {
5 | orders: [],
6 | loading: false,
7 | purchased: false
8 | };
9 |
10 | const purchaseInit = state => {
11 | return updateObject(state, { purchased: false });
12 | };
13 |
14 | const purchaseBurgerStart = state => {
15 | return updateObject(state, { loading: true });
16 | };
17 |
18 | const purchaseBurgerSuccess = (state, action) => {
19 | const newOrder = updateObject(action.orders, { id: action.orderId });
20 | return updateObject(state, {
21 | loading: false,
22 | orders: state.orders.concat(newOrder),
23 | purchased: true
24 | });
25 | };
26 |
27 | const purchaseBurgerFailed = state => {
28 | return updateObject(state, { loading: false });
29 | };
30 |
31 | const fetchOrdersStart = state => {
32 | return updateObject(state, { loading: true });
33 | };
34 |
35 | const fetchOrdersSuccess = (state, action) => {
36 | return updateObject(state, {
37 | orders: action.orders,
38 | loading: false,
39 | error: false
40 | });
41 | };
42 |
43 | const fetchOrdersFailed = (state, action) => {
44 | return updateObject(state, { loading: false, error: action.error });
45 | };
46 |
47 | const orderReducer = (state = initialState, action) => {
48 | switch (action.type) {
49 | case actionTypes.PURCHASE_INIT:
50 | return purchaseInit(state);
51 |
52 | case actionTypes.PURCHASE_BURGER_START:
53 | return purchaseBurgerStart(state);
54 |
55 | case actionTypes.PURCHASE_BURGER_SUCCESS:
56 | return purchaseBurgerSuccess(state, action);
57 |
58 | case actionTypes.PURCHASE_BURGER_FAIL:
59 | return purchaseBurgerFailed(state);
60 |
61 | case actionTypes.FETCH_ORDERS_START:
62 | return fetchOrdersStart(state);
63 |
64 | case actionTypes.FETCH_ORDERS_SUCCESS:
65 | return fetchOrdersSuccess(state, action);
66 |
67 | case actionTypes.FETCH_ORDERS_FAIL:
68 | return fetchOrdersFailed(state, action);
69 |
70 | default:
71 | return state;
72 | }
73 | };
74 |
75 | export default orderReducer;
76 |
--------------------------------------------------------------------------------
/src/store/utility.js:
--------------------------------------------------------------------------------
1 | export const updateObject = (oldObject, updatedProperties) => {
2 | return {
3 | ...oldObject,
4 | ...updatedProperties
5 | };
6 | };
7 |
--------------------------------------------------------------------------------