From 44c429aabc62b46336e05f38bdc32222eec16fe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Wed, 24 Mar 2021 09:24:32 +0100 Subject: [PATCH 1/5] added slides and code --- code/01-starting-project/package.json | 38 +++++++ code/01-starting-project/public/favicon.ico | Bin 0 -> 3870 bytes code/01-starting-project/public/index.html | 43 ++++++++ code/01-starting-project/public/logo192.png | Bin 0 -> 5347 bytes code/01-starting-project/public/logo512.png | Bin 0 -> 9664 bytes code/01-starting-project/public/manifest.json | 25 +++++ code/01-starting-project/public/robots.txt | 3 + code/01-starting-project/src/App.js | 14 +++ .../src/components/Cart/Cart.js | 18 ++++ .../src/components/Cart/Cart.module.css | 16 +++ .../src/components/Cart/CartButton.js | 12 +++ .../src/components/Cart/CartButton.module.css | 21 ++++ .../src/components/Cart/CartItem.js | 28 ++++++ .../src/components/Cart/CartItem.module.css | 58 +++++++++++ .../src/components/Layout/Layout.js | 13 +++ .../src/components/Layout/MainHeader.js | 19 ++++ .../components/Layout/MainHeader.module.css | 19 ++++ .../src/components/Shop/ProductItem.js | 23 +++++ .../components/Shop/ProductItem.module.css | 27 +++++ .../src/components/Shop/Products.js | 19 ++++ .../src/components/Shop/Products.module.css | 12 +++ .../src/components/UI/Card.js | 13 +++ .../src/components/UI/Card.module.css | 8 ++ code/01-starting-project/src/index.css | 31 ++++++ code/01-starting-project/src/index.js | 6 ++ .../package.json | 40 ++++++++ .../public/favicon.ico | Bin 0 -> 3870 bytes .../public/index.html | 43 ++++++++ .../public/logo192.png | Bin 0 -> 5347 bytes .../public/logo512.png | Bin 0 -> 9664 bytes .../public/manifest.json | 25 +++++ .../public/robots.txt | 3 + .../02-refresher-practice-finished/src/App.js | 18 ++++ .../src/components/Cart/Cart.js | 31 ++++++ .../src/components/Cart/Cart.module.css | 16 +++ .../src/components/Cart/CartButton.js | 22 ++++ .../src/components/Cart/CartButton.module.css | 21 ++++ .../src/components/Cart/CartItem.js | 47 +++++++++ .../src/components/Cart/CartItem.module.css | 58 +++++++++++ .../src/components/Layout/Layout.js | 13 +++ .../src/components/Layout/MainHeader.js | 19 ++++ .../components/Layout/MainHeader.module.css | 19 ++++ .../src/components/Shop/ProductItem.js | 38 +++++++ .../components/Shop/ProductItem.module.css | 27 +++++ .../src/components/Shop/Products.js | 38 +++++++ .../src/components/Shop/Products.module.css | 12 +++ .../src/components/UI/Card.js | 13 +++ .../src/components/UI/Card.module.css | 8 ++ .../src/index.css | 31 ++++++ .../src/index.js | 13 +++ .../src/store/cart-slice.js | 42 ++++++++ .../src/store/index.js | 10 ++ .../src/store/ui-slice.js | 15 +++ .../package.json | 40 ++++++++ .../public/favicon.ico | Bin 0 -> 3870 bytes .../public/index.html | 43 ++++++++ .../public/logo192.png | Bin 0 -> 5347 bytes .../public/logo512.png | Bin 0 -> 9664 bytes .../public/manifest.json | 25 +++++ .../public/robots.txt | 3 + code/03-using-useeffect-with-redux/src/App.js | 27 +++++ .../src/components/Cart/Cart.js | 31 ++++++ .../src/components/Cart/Cart.module.css | 16 +++ .../src/components/Cart/CartButton.js | 22 ++++ .../src/components/Cart/CartButton.module.css | 21 ++++ .../src/components/Cart/CartItem.js | 47 +++++++++ .../src/components/Cart/CartItem.module.css | 58 +++++++++++ .../src/components/Layout/Layout.js | 13 +++ .../src/components/Layout/MainHeader.js | 19 ++++ .../components/Layout/MainHeader.module.css | 19 ++++ .../src/components/Shop/ProductItem.js | 41 ++++++++ .../components/Shop/ProductItem.module.css | 27 +++++ .../src/components/Shop/Products.js | 38 +++++++ .../src/components/Shop/Products.module.css | 12 +++ .../src/components/UI/Card.js | 13 +++ .../src/components/UI/Card.module.css | 8 ++ .../src/index.css | 31 ++++++ .../src/index.js | 13 +++ .../src/store/cart-slice.js | 46 +++++++++ .../src/store/index.js | 10 ++ .../src/store/ui-slice.js | 15 +++ .../package.json | 40 ++++++++ .../public/favicon.ico | Bin 0 -> 3870 bytes .../public/index.html | 43 ++++++++ .../public/logo192.png | Bin 0 -> 5347 bytes .../public/logo512.png | Bin 0 -> 9664 bytes .../public/manifest.json | 25 +++++ .../public/robots.txt | 3 + .../src/App.js | 81 +++++++++++++++ .../src/components/Cart/Cart.js | 31 ++++++ .../src/components/Cart/Cart.module.css | 16 +++ .../src/components/Cart/CartButton.js | 22 ++++ .../src/components/Cart/CartButton.module.css | 21 ++++ .../src/components/Cart/CartItem.js | 47 +++++++++ .../src/components/Cart/CartItem.module.css | 58 +++++++++++ .../src/components/Layout/Layout.js | 13 +++ .../src/components/Layout/MainHeader.js | 19 ++++ .../components/Layout/MainHeader.module.css | 19 ++++ .../src/components/Shop/ProductItem.js | 41 ++++++++ .../components/Shop/ProductItem.module.css | 27 +++++ .../src/components/Shop/Products.js | 38 +++++++ .../src/components/Shop/Products.module.css | 12 +++ .../src/components/UI/Card.js | 13 +++ .../src/components/UI/Card.module.css | 8 ++ .../src/components/UI/Notification.js | 23 +++++ .../src/components/UI/Notification.module.css | 24 +++++ .../src/index.css | 31 ++++++ .../src/index.js | 13 +++ .../src/store/cart-slice.js | 46 +++++++++ .../src/store/index.js | 10 ++ .../src/store/ui-slice.js | 22 ++++ .../package.json | 40 ++++++++ .../public/favicon.ico | Bin 0 -> 3870 bytes .../public/index.html | 43 ++++++++ .../public/logo192.png | Bin 0 -> 5347 bytes .../public/logo512.png | Bin 0 -> 9664 bytes .../public/manifest.json | 25 +++++ .../public/robots.txt | 3 + .../src/App.js | 44 ++++++++ .../src/components/Cart/Cart.js | 31 ++++++ .../src/components/Cart/Cart.module.css | 16 +++ .../src/components/Cart/CartButton.js | 22 ++++ .../src/components/Cart/CartButton.module.css | 21 ++++ .../src/components/Cart/CartItem.js | 47 +++++++++ .../src/components/Cart/CartItem.module.css | 58 +++++++++++ .../src/components/Layout/Layout.js | 13 +++ .../src/components/Layout/MainHeader.js | 19 ++++ .../components/Layout/MainHeader.module.css | 19 ++++ .../src/components/Shop/ProductItem.js | 41 ++++++++ .../components/Shop/ProductItem.module.css | 27 +++++ .../src/components/Shop/Products.js | 38 +++++++ .../src/components/Shop/Products.module.css | 12 +++ .../src/components/UI/Card.js | 13 +++ .../src/components/UI/Card.module.css | 8 ++ .../src/components/UI/Notification.js | 23 +++++ .../src/components/UI/Notification.module.css | 24 +++++ .../src/index.css | 31 ++++++ .../src/index.js | 13 +++ .../src/store/cart-slice.js | 94 ++++++++++++++++++ .../src/store/index.js | 10 ++ .../src/store/ui-slice.js | 22 ++++ code/06-finished/package.json | 40 ++++++++ code/06-finished/public/favicon.ico | Bin 0 -> 3870 bytes code/06-finished/public/index.html | 43 ++++++++ code/06-finished/public/logo192.png | Bin 0 -> 5347 bytes code/06-finished/public/logo512.png | Bin 0 -> 9664 bytes code/06-finished/public/manifest.json | 25 +++++ code/06-finished/public/robots.txt | 3 + code/06-finished/src/App.js | 50 ++++++++++ code/06-finished/src/components/Cart/Cart.js | 31 ++++++ .../src/components/Cart/Cart.module.css | 16 +++ .../src/components/Cart/CartButton.js | 22 ++++ .../src/components/Cart/CartButton.module.css | 21 ++++ .../src/components/Cart/CartItem.js | 47 +++++++++ .../src/components/Cart/CartItem.module.css | 58 +++++++++++ .../src/components/Layout/Layout.js | 13 +++ .../src/components/Layout/MainHeader.js | 19 ++++ .../components/Layout/MainHeader.module.css | 19 ++++ .../src/components/Shop/ProductItem.js | 41 ++++++++ .../components/Shop/ProductItem.module.css | 27 +++++ .../src/components/Shop/Products.js | 38 +++++++ .../src/components/Shop/Products.module.css | 12 +++ code/06-finished/src/components/UI/Card.js | 13 +++ .../src/components/UI/Card.module.css | 8 ++ .../src/components/UI/Notification.js | 23 +++++ .../src/components/UI/Notification.module.css | 24 +++++ code/06-finished/src/index.css | 31 ++++++ code/06-finished/src/index.js | 13 +++ code/06-finished/src/store/cart-actions.js | 87 ++++++++++++++++ code/06-finished/src/store/cart-slice.js | 50 ++++++++++ code/06-finished/src/store/index.js | 10 ++ code/06-finished/src/store/ui-slice.js | 22 ++++ slides/slides.pdf | Bin 0 -> 61834 bytes 173 files changed, 4035 insertions(+) create mode 100644 code/01-starting-project/package.json create mode 100644 code/01-starting-project/public/favicon.ico create mode 100644 code/01-starting-project/public/index.html create mode 100644 code/01-starting-project/public/logo192.png create mode 100644 code/01-starting-project/public/logo512.png create mode 100644 code/01-starting-project/public/manifest.json create mode 100644 code/01-starting-project/public/robots.txt create mode 100644 code/01-starting-project/src/App.js create mode 100644 code/01-starting-project/src/components/Cart/Cart.js create mode 100644 code/01-starting-project/src/components/Cart/Cart.module.css create mode 100644 code/01-starting-project/src/components/Cart/CartButton.js create mode 100644 code/01-starting-project/src/components/Cart/CartButton.module.css create mode 100644 code/01-starting-project/src/components/Cart/CartItem.js create mode 100644 code/01-starting-project/src/components/Cart/CartItem.module.css create mode 100644 code/01-starting-project/src/components/Layout/Layout.js create mode 100644 code/01-starting-project/src/components/Layout/MainHeader.js create mode 100644 code/01-starting-project/src/components/Layout/MainHeader.module.css create mode 100644 code/01-starting-project/src/components/Shop/ProductItem.js create mode 100644 code/01-starting-project/src/components/Shop/ProductItem.module.css create mode 100644 code/01-starting-project/src/components/Shop/Products.js create mode 100644 code/01-starting-project/src/components/Shop/Products.module.css create mode 100644 code/01-starting-project/src/components/UI/Card.js create mode 100644 code/01-starting-project/src/components/UI/Card.module.css create mode 100644 code/01-starting-project/src/index.css create mode 100644 code/01-starting-project/src/index.js create mode 100644 code/02-refresher-practice-finished/package.json create mode 100644 code/02-refresher-practice-finished/public/favicon.ico create mode 100644 code/02-refresher-practice-finished/public/index.html create mode 100644 code/02-refresher-practice-finished/public/logo192.png create mode 100644 code/02-refresher-practice-finished/public/logo512.png create mode 100644 code/02-refresher-practice-finished/public/manifest.json create mode 100644 code/02-refresher-practice-finished/public/robots.txt create mode 100644 code/02-refresher-practice-finished/src/App.js create mode 100644 code/02-refresher-practice-finished/src/components/Cart/Cart.js create mode 100644 code/02-refresher-practice-finished/src/components/Cart/Cart.module.css create mode 100644 code/02-refresher-practice-finished/src/components/Cart/CartButton.js create mode 100644 code/02-refresher-practice-finished/src/components/Cart/CartButton.module.css create mode 100644 code/02-refresher-practice-finished/src/components/Cart/CartItem.js create mode 100644 code/02-refresher-practice-finished/src/components/Cart/CartItem.module.css create mode 100644 code/02-refresher-practice-finished/src/components/Layout/Layout.js create mode 100644 code/02-refresher-practice-finished/src/components/Layout/MainHeader.js create mode 100644 code/02-refresher-practice-finished/src/components/Layout/MainHeader.module.css create mode 100644 code/02-refresher-practice-finished/src/components/Shop/ProductItem.js create mode 100644 code/02-refresher-practice-finished/src/components/Shop/ProductItem.module.css create mode 100644 code/02-refresher-practice-finished/src/components/Shop/Products.js create mode 100644 code/02-refresher-practice-finished/src/components/Shop/Products.module.css create mode 100644 code/02-refresher-practice-finished/src/components/UI/Card.js create mode 100644 code/02-refresher-practice-finished/src/components/UI/Card.module.css create mode 100644 code/02-refresher-practice-finished/src/index.css create mode 100644 code/02-refresher-practice-finished/src/index.js create mode 100644 code/02-refresher-practice-finished/src/store/cart-slice.js create mode 100644 code/02-refresher-practice-finished/src/store/index.js create mode 100644 code/02-refresher-practice-finished/src/store/ui-slice.js create mode 100644 code/03-using-useeffect-with-redux/package.json create mode 100644 code/03-using-useeffect-with-redux/public/favicon.ico create mode 100644 code/03-using-useeffect-with-redux/public/index.html create mode 100644 code/03-using-useeffect-with-redux/public/logo192.png create mode 100644 code/03-using-useeffect-with-redux/public/logo512.png create mode 100644 code/03-using-useeffect-with-redux/public/manifest.json create mode 100644 code/03-using-useeffect-with-redux/public/robots.txt create mode 100644 code/03-using-useeffect-with-redux/src/App.js create mode 100644 code/03-using-useeffect-with-redux/src/components/Cart/Cart.js create mode 100644 code/03-using-useeffect-with-redux/src/components/Cart/Cart.module.css create mode 100644 code/03-using-useeffect-with-redux/src/components/Cart/CartButton.js create mode 100644 code/03-using-useeffect-with-redux/src/components/Cart/CartButton.module.css create mode 100644 code/03-using-useeffect-with-redux/src/components/Cart/CartItem.js create mode 100644 code/03-using-useeffect-with-redux/src/components/Cart/CartItem.module.css create mode 100644 code/03-using-useeffect-with-redux/src/components/Layout/Layout.js create mode 100644 code/03-using-useeffect-with-redux/src/components/Layout/MainHeader.js create mode 100644 code/03-using-useeffect-with-redux/src/components/Layout/MainHeader.module.css create mode 100644 code/03-using-useeffect-with-redux/src/components/Shop/ProductItem.js create mode 100644 code/03-using-useeffect-with-redux/src/components/Shop/ProductItem.module.css create mode 100644 code/03-using-useeffect-with-redux/src/components/Shop/Products.js create mode 100644 code/03-using-useeffect-with-redux/src/components/Shop/Products.module.css create mode 100644 code/03-using-useeffect-with-redux/src/components/UI/Card.js create mode 100644 code/03-using-useeffect-with-redux/src/components/UI/Card.module.css create mode 100644 code/03-using-useeffect-with-redux/src/index.css create mode 100644 code/03-using-useeffect-with-redux/src/index.js create mode 100644 code/03-using-useeffect-with-redux/src/store/cart-slice.js create mode 100644 code/03-using-useeffect-with-redux/src/store/index.js create mode 100644 code/03-using-useeffect-with-redux/src/store/ui-slice.js create mode 100644 code/04-handling-http-states-feedback-with-redux/package.json create mode 100644 code/04-handling-http-states-feedback-with-redux/public/favicon.ico create mode 100644 code/04-handling-http-states-feedback-with-redux/public/index.html create mode 100644 code/04-handling-http-states-feedback-with-redux/public/logo192.png create mode 100644 code/04-handling-http-states-feedback-with-redux/public/logo512.png create mode 100644 code/04-handling-http-states-feedback-with-redux/public/manifest.json create mode 100644 code/04-handling-http-states-feedback-with-redux/public/robots.txt create mode 100644 code/04-handling-http-states-feedback-with-redux/src/App.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Cart/Cart.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Cart/Cart.module.css create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartButton.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartButton.module.css create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartItem.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartItem.module.css create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Layout/Layout.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Layout/MainHeader.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Layout/MainHeader.module.css create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Shop/ProductItem.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Shop/ProductItem.module.css create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Shop/Products.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/Shop/Products.module.css create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/UI/Card.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/UI/Card.module.css create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/UI/Notification.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/components/UI/Notification.module.css create mode 100644 code/04-handling-http-states-feedback-with-redux/src/index.css create mode 100644 code/04-handling-http-states-feedback-with-redux/src/index.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/store/cart-slice.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/store/index.js create mode 100644 code/04-handling-http-states-feedback-with-redux/src/store/ui-slice.js create mode 100644 code/05-using-an-action-creator-thunk/package.json create mode 100644 code/05-using-an-action-creator-thunk/public/favicon.ico create mode 100644 code/05-using-an-action-creator-thunk/public/index.html create mode 100644 code/05-using-an-action-creator-thunk/public/logo192.png create mode 100644 code/05-using-an-action-creator-thunk/public/logo512.png create mode 100644 code/05-using-an-action-creator-thunk/public/manifest.json create mode 100644 code/05-using-an-action-creator-thunk/public/robots.txt create mode 100644 code/05-using-an-action-creator-thunk/src/App.js create mode 100644 code/05-using-an-action-creator-thunk/src/components/Cart/Cart.js create mode 100644 code/05-using-an-action-creator-thunk/src/components/Cart/Cart.module.css create mode 100644 code/05-using-an-action-creator-thunk/src/components/Cart/CartButton.js create mode 100644 code/05-using-an-action-creator-thunk/src/components/Cart/CartButton.module.css create mode 100644 code/05-using-an-action-creator-thunk/src/components/Cart/CartItem.js create mode 100644 code/05-using-an-action-creator-thunk/src/components/Cart/CartItem.module.css create mode 100644 code/05-using-an-action-creator-thunk/src/components/Layout/Layout.js create mode 100644 code/05-using-an-action-creator-thunk/src/components/Layout/MainHeader.js create mode 100644 code/05-using-an-action-creator-thunk/src/components/Layout/MainHeader.module.css create mode 100644 code/05-using-an-action-creator-thunk/src/components/Shop/ProductItem.js create mode 100644 code/05-using-an-action-creator-thunk/src/components/Shop/ProductItem.module.css create mode 100644 code/05-using-an-action-creator-thunk/src/components/Shop/Products.js create mode 100644 code/05-using-an-action-creator-thunk/src/components/Shop/Products.module.css create mode 100644 code/05-using-an-action-creator-thunk/src/components/UI/Card.js create mode 100644 code/05-using-an-action-creator-thunk/src/components/UI/Card.module.css create mode 100644 code/05-using-an-action-creator-thunk/src/components/UI/Notification.js create mode 100644 code/05-using-an-action-creator-thunk/src/components/UI/Notification.module.css create mode 100644 code/05-using-an-action-creator-thunk/src/index.css create mode 100644 code/05-using-an-action-creator-thunk/src/index.js create mode 100644 code/05-using-an-action-creator-thunk/src/store/cart-slice.js create mode 100644 code/05-using-an-action-creator-thunk/src/store/index.js create mode 100644 code/05-using-an-action-creator-thunk/src/store/ui-slice.js create mode 100644 code/06-finished/package.json create mode 100644 code/06-finished/public/favicon.ico create mode 100644 code/06-finished/public/index.html create mode 100644 code/06-finished/public/logo192.png create mode 100644 code/06-finished/public/logo512.png create mode 100644 code/06-finished/public/manifest.json create mode 100644 code/06-finished/public/robots.txt create mode 100644 code/06-finished/src/App.js create mode 100644 code/06-finished/src/components/Cart/Cart.js create mode 100644 code/06-finished/src/components/Cart/Cart.module.css create mode 100644 code/06-finished/src/components/Cart/CartButton.js create mode 100644 code/06-finished/src/components/Cart/CartButton.module.css create mode 100644 code/06-finished/src/components/Cart/CartItem.js create mode 100644 code/06-finished/src/components/Cart/CartItem.module.css create mode 100644 code/06-finished/src/components/Layout/Layout.js create mode 100644 code/06-finished/src/components/Layout/MainHeader.js create mode 100644 code/06-finished/src/components/Layout/MainHeader.module.css create mode 100644 code/06-finished/src/components/Shop/ProductItem.js create mode 100644 code/06-finished/src/components/Shop/ProductItem.module.css create mode 100644 code/06-finished/src/components/Shop/Products.js create mode 100644 code/06-finished/src/components/Shop/Products.module.css create mode 100644 code/06-finished/src/components/UI/Card.js create mode 100644 code/06-finished/src/components/UI/Card.module.css create mode 100644 code/06-finished/src/components/UI/Notification.js create mode 100644 code/06-finished/src/components/UI/Notification.module.css create mode 100644 code/06-finished/src/index.css create mode 100644 code/06-finished/src/index.js create mode 100644 code/06-finished/src/store/cart-actions.js create mode 100644 code/06-finished/src/store/cart-slice.js create mode 100644 code/06-finished/src/store/index.js create mode 100644 code/06-finished/src/store/ui-slice.js create mode 100644 slides/slides.pdf diff --git a/code/01-starting-project/package.json b/code/01-starting-project/package.json new file mode 100644 index 0000000000..21f2dd39f2 --- /dev/null +++ b/code/01-starting-project/package.json @@ -0,0 +1,38 @@ +{ + "name": "react-complete-guide", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.11.6", + "@testing-library/react": "^11.2.2", + "@testing-library/user-event": "^12.5.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "react-scripts": "4.0.1", + "web-vitals": "^0.2.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/code/01-starting-project/public/favicon.ico b/code/01-starting-project/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/code/01-starting-project/public/index.html b/code/01-starting-project/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/01-starting-project/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/code/01-starting-project/public/logo192.png b/code/01-starting-project/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 zcmZWtbyO6NvR-oO24RV%BvuJ&=?+<7=`LvyB&A_#M7mSDYw1v6DJkiYl9XjT!%$dLEBTQ8R9|wd3008in6lFF3GV-6mLi?MoP_y~}QUnaDCHI#t z7w^m$@6DI)|C8_jrT?q=f8D?0AM?L)Z}xAo^e^W>t$*Y0KlT5=@bBjT9kxb%-KNdk zeOS1tKO#ChhG7%{ApNBzE2ZVNcxbrin#E1TiAw#BlUhXllzhN$qWez5l;h+t^q#Eav8PhR2|T}y5kkflaK`ba-eoE+Z2q@o6P$)=&` z+(8}+-McnNO>e#$Rr{32ngsZIAX>GH??tqgwUuUz6kjns|LjsB37zUEWd|(&O!)DY zQLrq%Y>)Y8G`yYbYCx&aVHi@-vZ3|ebG!f$sTQqMgi0hWRJ^Wc+Ibv!udh_r%2|U) zPi|E^PK?UE!>_4`f`1k4hqqj_$+d!EB_#IYt;f9)fBOumGNyglU(ofY`yHq4Y?B%- zp&G!MRY<~ajTgIHErMe(Z8JG*;D-PJhd@RX@QatggM7+G(Lz8eZ;73)72Hfx5KDOE zkT(m}i2;@X2AT5fW?qVp?@WgN$aT+f_6eo?IsLh;jscNRp|8H}Z9p_UBO^SJXpZew zEK8fz|0Th%(Wr|KZBGTM4yxkA5CFdAj8=QSrT$fKW#tweUFqr0TZ9D~a5lF{)%-tTGMK^2tz(y2v$i%V8XAxIywrZCp=)83p(zIk6@S5AWl|Oa2hF`~~^W zI;KeOSkw1O#TiQ8;U7OPXjZM|KrnN}9arP)m0v$c|L)lF`j_rpG(zW1Qjv$=^|p*f z>)Na{D&>n`jOWMwB^TM}slgTEcjxTlUby89j1)|6ydRfWERn3|7Zd2&e7?!K&5G$x z`5U3uFtn4~SZq|LjFVrz$3iln-+ucY4q$BC{CSm7Xe5c1J<=%Oagztj{ifpaZk_bQ z9Sb-LaQMKp-qJA*bP6DzgE3`}*i1o3GKmo2pn@dj0;He}F=BgINo};6gQF8!n0ULZ zL>kC0nPSFzlcB7p41doao2F7%6IUTi_+!L`MM4o*#Y#0v~WiO8uSeAUNp=vA2KaR&=jNR2iVwG>7t%sG2x_~yXzY)7K& zk3p+O0AFZ1eu^T3s};B%6TpJ6h-Y%B^*zT&SN7C=N;g|#dGIVMSOru3iv^SvO>h4M=t-N1GSLLDqVTcgurco6)3&XpU!FP6Hlrmj}f$ zp95;b)>M~`kxuZF3r~a!rMf4|&1=uMG$;h^g=Kl;H&Np-(pFT9FF@++MMEx3RBsK?AU0fPk-#mdR)Wdkj)`>ZMl#^<80kM87VvsI3r_c@_vX=fdQ`_9-d(xiI z4K;1y1TiPj_RPh*SpDI7U~^QQ?%0&!$Sh#?x_@;ag)P}ZkAik{_WPB4rHyW#%>|Gs zdbhyt=qQPA7`?h2_8T;-E6HI#im9K>au*(j4;kzwMSLgo6u*}-K`$_Gzgu&XE)udQ zmQ72^eZd|vzI)~!20JV-v-T|<4@7ruqrj|o4=JJPlybwMg;M$Ud7>h6g()CT@wXm` zbq=A(t;RJ^{Xxi*Ff~!|3!-l_PS{AyNAU~t{h;(N(PXMEf^R(B+ZVX3 z8y0;0A8hJYp@g+c*`>eTA|3Tgv9U8#BDTO9@a@gVMDxr(fVaEqL1tl?md{v^j8aUv zm&%PX4^|rX|?E4^CkplWWNv*OKM>DxPa z!RJ)U^0-WJMi)Ksc!^ixOtw^egoAZZ2Cg;X7(5xZG7yL_;UJ#yp*ZD-;I^Z9qkP`} zwCTs0*%rIVF1sgLervtnUo&brwz?6?PXRuOCS*JI-WL6GKy7-~yi0giTEMmDs_-UX zo=+nFrW_EfTg>oY72_4Z0*uG>MnXP=c0VpT&*|rvv1iStW;*^={rP1y?Hv+6R6bxFMkxpWkJ>m7Ba{>zc_q zEefC3jsXdyS5??Mz7IET$Kft|EMNJIv7Ny8ZOcKnzf`K5Cd)&`-fTY#W&jnV0l2vt z?Gqhic}l}mCv1yUEy$%DP}4AN;36$=7aNI^*AzV(eYGeJ(Px-j<^gSDp5dBAv2#?; zcMXv#aj>%;MiG^q^$0MSg-(uTl!xm49dH!{X0){Ew7ThWV~Gtj7h%ZD zVN-R-^7Cf0VH!8O)uUHPL2mO2tmE*cecwQv_5CzWeh)ykX8r5Hi`ehYo)d{Jnh&3p z9ndXT$OW51#H5cFKa76c<%nNkP~FU93b5h-|Cb}ScHs@4Q#|}byWg;KDMJ#|l zE=MKD*F@HDBcX@~QJH%56eh~jfPO-uKm}~t7VkHxHT;)4sd+?Wc4* z>CyR*{w@4(gnYRdFq=^(#-ytb^5ESD?x<0Skhb%Pt?npNW1m+Nv`tr9+qN<3H1f<% zZvNEqyK5FgPsQ`QIu9P0x_}wJR~^CotL|n zk?dn;tLRw9jJTur4uWoX6iMm914f0AJfB@C74a;_qRrAP4E7l890P&{v<}>_&GLrW z)klculcg`?zJO~4;BBAa=POU%aN|pmZJn2{hA!d!*lwO%YSIzv8bTJ}=nhC^n}g(ld^rn#kq9Z3)z`k9lvV>y#!F4e{5c$tnr9M{V)0m(Z< z#88vX6-AW7T2UUwW`g<;8I$Jb!R%z@rCcGT)-2k7&x9kZZT66}Ztid~6t0jKb&9mm zpa}LCb`bz`{MzpZR#E*QuBiZXI#<`5qxx=&LMr-UUf~@dRk}YI2hbMsAMWOmDzYtm zjof16D=mc`^B$+_bCG$$@R0t;e?~UkF?7<(vkb70*EQB1rfUWXh$j)R2)+dNAH5%R zEBs^?N;UMdy}V};59Gu#0$q53$}|+q7CIGg_w_WlvE}AdqoS<7DY1LWS9?TrfmcvT zaypmplwn=P4;a8-%l^e?f`OpGb}%(_mFsL&GywhyN(-VROj`4~V~9bGv%UhcA|YW% zs{;nh@aDX11y^HOFXB$a7#Sr3cEtNd4eLm@Y#fc&j)TGvbbMwze zXtekX_wJqxe4NhuW$r}cNy|L{V=t#$%SuWEW)YZTH|!iT79k#?632OFse{+BT_gau zJwQcbH{b}dzKO?^dV&3nTILYlGw{27UJ72ZN){BILd_HV_s$WfI2DC<9LIHFmtyw? zQ;?MuK7g%Ym+4e^W#5}WDLpko%jPOC=aN)3!=8)s#Rnercak&b3ESRX3z{xfKBF8L z5%CGkFmGO@x?_mPGlpEej!3!AMddChabyf~nJNZxx!D&{@xEb!TDyvqSj%Y5@A{}9 zRzoBn0?x}=krh{ok3Nn%e)#~uh;6jpezhA)ySb^b#E>73e*frBFu6IZ^D7Ii&rsiU z%jzygxT-n*joJpY4o&8UXr2s%j^Q{?e-voloX`4DQyEK+DmrZh8A$)iWL#NO9+Y@!sO2f@rI!@jN@>HOA< z?q2l{^%mY*PNx2FoX+A7X3N}(RV$B`g&N=e0uvAvEN1W^{*W?zT1i#fxuw10%~))J zjx#gxoVlXREWZf4hRkgdHx5V_S*;p-y%JtGgQ4}lnA~MBz-AFdxUxU1RIT$`sal|X zPB6sEVRjGbXIP0U+?rT|y5+ev&OMX*5C$n2SBPZr`jqzrmpVrNciR0e*Wm?fK6DY& zl(XQZ60yWXV-|Ps!A{EF;=_z(YAF=T(-MkJXUoX zI{UMQDAV2}Ya?EisdEW;@pE6dt;j0fg5oT2dxCi{wqWJ<)|SR6fxX~5CzblPGr8cb zUBVJ2CQd~3L?7yfTpLNbt)He1D>*KXI^GK%<`bq^cUq$Q@uJifG>p3LU(!H=C)aEL zenk7pVg}0{dKU}&l)Y2Y2eFMdS(JS0}oZUuVaf2+K*YFNGHB`^YGcIpnBlMhO7d4@vV zv(@N}(k#REdul8~fP+^F@ky*wt@~&|(&&meNO>rKDEnB{ykAZ}k>e@lad7to>Ao$B zz<1(L=#J*u4_LB=8w+*{KFK^u00NAmeNN7pr+Pf+N*Zl^dO{LM-hMHyP6N!~`24jd zXYP|Ze;dRXKdF2iJG$U{k=S86l@pytLx}$JFFs8e)*Vi?aVBtGJ3JZUj!~c{(rw5>vuRF$`^p!P8w1B=O!skwkO5yd4_XuG^QVF z`-r5K7(IPSiKQ2|U9+`@Js!g6sfJwAHVd|s?|mnC*q zp|B|z)(8+mxXyxQ{8Pg3F4|tdpgZZSoU4P&9I8)nHo1@)9_9u&NcT^FI)6|hsAZFk zZ+arl&@*>RXBf-OZxhZerOr&dN5LW9@gV=oGFbK*J+m#R-|e6(Loz(;g@T^*oO)0R zN`N=X46b{7yk5FZGr#5&n1!-@j@g02g|X>MOpF3#IjZ_4wg{dX+G9eqS+Es9@6nC7 zD9$NuVJI}6ZlwtUm5cCAiYv0(Yi{%eH+}t)!E^>^KxB5^L~a`4%1~5q6h>d;paC9c zTj0wTCKrhWf+F#5>EgX`sl%POl?oyCq0(w0xoL?L%)|Q7d|Hl92rUYAU#lc**I&^6p=4lNQPa0 znQ|A~i0ip@`B=FW-Q;zh?-wF;Wl5!+q3GXDu-x&}$gUO)NoO7^$BeEIrd~1Dh{Tr` z8s<(Bn@gZ(mkIGnmYh_ehXnq78QL$pNDi)|QcT*|GtS%nz1uKE+E{7jdEBp%h0}%r zD2|KmYGiPa4;md-t_m5YDz#c*oV_FqXd85d@eub?9N61QuYcb3CnVWpM(D-^|CmkL z(F}L&N7qhL2PCq)fRh}XO@U`Yn<?TNGR4L(mF7#4u29{i~@k;pLsgl({YW5`Mo+p=zZn3L*4{JU;++dG9 X@eDJUQo;Ye2mwlRs?y0|+_a0zY+Zo%Dkae}+MySoIppb75o?vUW_?)>@g{U2`ERQIXV zeY$JrWnMZ$QC<=ii4X|@0H8`si75jB(ElJb00HAB%>SlLR{!zO|C9P3zxw_U8?1d8uRZ=({Ga4shyN}3 zAK}WA(ds|``G4jA)9}Bt2Hy0+f3rV1E6b|@?hpGA=PI&r8)ah|)I2s(P5Ic*Ndhn^ z*T&j@gbCTv7+8rpYbR^Ty}1AY)YH;p!m948r#%7x^Z@_-w{pDl|1S4`EM3n_PaXvK z1JF)E3qy$qTj5Xs{jU9k=y%SQ0>8E$;x?p9ayU0bZZeo{5Z@&FKX>}s!0+^>C^D#z z>xsCPvxD3Z=dP}TTOSJhNTPyVt14VCQ9MQFN`rn!c&_p?&4<5_PGm4a;WS&1(!qKE z_H$;dDdiPQ!F_gsN`2>`X}$I=B;={R8%L~`>RyKcS$72ai$!2>d(YkciA^J0@X%G4 z4cu!%Ps~2JuJ8ex`&;Fa0NQOq_nDZ&X;^A=oc1&f#3P1(!5il>6?uK4QpEG8z0Rhu zvBJ+A9RV?z%v?!$=(vcH?*;vRs*+PPbOQ3cdPr5=tOcLqmfx@#hOqX0iN)wTTO21jH<>jpmwRIAGw7`a|sl?9y9zRBh>(_%| zF?h|P7}~RKj?HR+q|4U`CjRmV-$mLW>MScKnNXiv{vD3&2@*u)-6P@h0A`eeZ7}71 zK(w%@R<4lLt`O7fs1E)$5iGb~fPfJ?WxhY7c3Q>T-w#wT&zW522pH-B%r5v#5y^CF zcC30Se|`D2mY$hAlIULL%-PNXgbbpRHgn<&X3N9W!@BUk@9g*P5mz-YnZBb*-$zMM z7Qq}ic0mR8n{^L|=+diODdV}Q!gwr?y+2m=3HWwMq4z)DqYVg0J~^}-%7rMR@S1;9 z7GFj6K}i32X;3*$SmzB&HW{PJ55kT+EI#SsZf}bD7nW^Haf}_gXciYKX{QBxIPSx2Ma? zHQqgzZq!_{&zg{yxqv3xq8YV+`S}F6A>Gtl39_m;K4dA{pP$BW0oIXJ>jEQ!2V3A2 zdpoTxG&V=(?^q?ZTj2ZUpDUdMb)T?E$}CI>r@}PFPWD9@*%V6;4Ag>D#h>!s)=$0R zRXvdkZ%|c}ubej`jl?cS$onl9Tw52rBKT)kgyw~Xy%z62Lr%V6Y=f?2)J|bZJ5(Wx zmji`O;_B+*X@qe-#~`HFP<{8$w@z4@&`q^Q-Zk8JG3>WalhnW1cvnoVw>*R@c&|o8 zZ%w!{Z+MHeZ*OE4v*otkZqz11*s!#s^Gq>+o`8Z5 z^i-qzJLJh9!W-;SmFkR8HEZJWiXk$40i6)7 zZpr=k2lp}SasbM*Nbn3j$sn0;rUI;%EDbi7T1ZI4qL6PNNM2Y%6{LMIKW+FY_yF3) zSKQ2QSujzNMSL2r&bYs`|i2Dnn z=>}c0>a}>|uT!IiMOA~pVT~R@bGlm}Edf}Kq0?*Af6#mW9f9!}RjW7om0c9Qlp;yK z)=XQs(|6GCadQbWIhYF=rf{Y)sj%^Id-ARO0=O^Ad;Ph+ z0?$eE1xhH?{T$QI>0JP75`r)U_$#%K1^BQ8z#uciKf(C701&RyLQWBUp*Q7eyn76} z6JHpC9}R$J#(R0cDCkXoFSp;j6{x{b&0yE@P7{;pCEpKjS(+1RQy38`=&Yxo%F=3y zCPeefABp34U-s?WmU#JJw23dcC{sPPFc2#J$ZgEN%zod}J~8dLm*fx9f6SpO zn^Ww3bt9-r0XaT2a@Wpw;C23XM}7_14#%QpubrIw5aZtP+CqIFmsG4`Cm6rfxl9n5 z7=r2C-+lM2AB9X0T_`?EW&Byv&K?HS4QLoylJ|OAF z`8atBNTzJ&AQ!>sOo$?^0xj~D(;kS$`9zbEGd>f6r`NC3X`tX)sWgWUUOQ7w=$TO&*j;=u%25ay-%>3@81tGe^_z*C7pb9y*Ed^H3t$BIKH2o+olp#$q;)_ zfpjCb_^VFg5fU~K)nf*d*r@BCC>UZ!0&b?AGk_jTPXaSnCuW110wjHPPe^9R^;jo3 zwvzTl)C`Zl5}O2}3lec=hZ*$JnkW#7enKKc)(pM${_$9Hc=Sr_A9Biwe*Y=T?~1CK z6eZ9uPICjy-sMGbZl$yQmpB&`ouS8v{58__t0$JP%i3R&%QR3ianbZqDs<2#5FdN@n5bCn^ZtH992~5k(eA|8|@G9u`wdn7bnpg|@{m z^d6Y`*$Zf2Xr&|g%sai#5}Syvv(>Jnx&EM7-|Jr7!M~zdAyjt*xl;OLhvW-a%H1m0 z*x5*nb=R5u><7lyVpNAR?q@1U59 zO+)QWwL8t zyip?u_nI+K$uh{y)~}qj?(w0&=SE^8`_WMM zTybjG=999h38Yes7}-4*LJ7H)UE8{mE(6;8voE+TYY%33A>S6`G_95^5QHNTo_;Ao ztIQIZ_}49%{8|=O;isBZ?=7kfdF8_@azfoTd+hEJKWE!)$)N%HIe2cplaK`ry#=pV z0q{9w-`i0h@!R8K3GC{ivt{70IWG`EP|(1g7i_Q<>aEAT{5(yD z=!O?kq61VegV+st@XCw475j6vS)_z@efuqQgHQR1T4;|-#OLZNQJPV4k$AX1Uk8Lm z{N*b*ia=I+MB}kWpupJ~>!C@xEN#Wa7V+7{m4j8c?)ChV=D?o~sjT?0C_AQ7B-vxqX30s0I_`2$in86#`mAsT-w?j{&AL@B3$;P z31G4(lV|b}uSDCIrjk+M1R!X7s4Aabn<)zpgT}#gE|mIvV38^ODy@<&yflpCwS#fRf9ZX3lPV_?8@C5)A;T zqmouFLFk;qIs4rA=hh=GL~sCFsXHsqO6_y~*AFt939UYVBSx1s(=Kb&5;j7cSowdE;7()CC2|-i9Zz+_BIw8#ll~-tyH?F3{%`QCsYa*b#s*9iCc`1P1oC26?`g<9))EJ3%xz+O!B3 zZ7$j~To)C@PquR>a1+Dh>-a%IvH_Y7^ys|4o?E%3`I&ADXfC8++hAdZfzIT#%C+Jz z1lU~K_vAm0m8Qk}K$F>|>RPK%<1SI0(G+8q~H zAsjezyP+u!Se4q3GW)`h`NPSRlMoBjCzNPesWJwVTY!o@G8=(6I%4XHGaSiS3MEBK zhgGFv6Jc>L$4jVE!I?TQuwvz_%CyO!bLh94nqK11C2W$*aa2ueGopG8DnBICVUORP zgytv#)49fVXDaR$SukloYC3u7#5H)}1K21=?DKj^U)8G;MS)&Op)g^zR2($<>C*zW z;X7`hLxiIO#J`ANdyAOJle4V%ppa*(+0i3w;8i*BA_;u8gOO6)MY`ueq7stBMJTB; z-a0R>hT*}>z|Gg}@^zDL1MrH+2hsR8 zHc}*9IvuQC^Ju)^#Y{fOr(96rQNPNhxc;mH@W*m206>Lo<*SaaH?~8zg&f&%YiOEG zGiz?*CP>Bci}!WiS=zj#K5I}>DtpregpP_tfZtPa(N<%vo^#WCQ5BTv0vr%Z{)0q+ z)RbfHktUm|lg&U3YM%lMUM(fu}i#kjX9h>GYctkx9Mt_8{@s%!K_EI zScgwy6%_fR?CGJQtmgNAj^h9B#zmaMDWgH55pGuY1Gv7D z;8Psm(vEPiwn#MgJYu4Ty9D|h!?Rj0ddE|&L3S{IP%H4^N!m`60ZwZw^;eg4sk6K{ ziA^`Sbl_4~f&Oo%n;8Ye(tiAdlZKI!Z=|j$5hS|D$bDJ}p{gh$KN&JZYLUjv4h{NY zBJ>X9z!xfDGY z+oh_Z&_e#Q(-}>ssZfm=j$D&4W4FNy&-kAO1~#3Im;F)Nwe{(*75(p=P^VI?X0GFakfh+X-px4a%Uw@fSbmp9hM1_~R>?Z8+ ziy|e9>8V*`OP}4x5JjdWp}7eX;lVxp5qS}0YZek;SNmm7tEeSF*-dI)6U-A%m6YvCgM(}_=k#a6o^%-K4{`B1+}O4x zztDT%hVb;v#?j`lTvlFQ3aV#zkX=7;YFLS$uIzb0E3lozs5`Xy zi~vF+%{z9uLjKvKPhP%x5f~7-Gj+%5N`%^=yk*Qn{`> z;xj&ROY6g`iy2a@{O)V(jk&8#hHACVDXey5a+KDod_Z&}kHM}xt7}Md@pil{2x7E~ zL$k^d2@Ec2XskjrN+IILw;#7((abu;OJii&v3?60x>d_Ma(onIPtcVnX@ELF0aL?T zSmWiL3(dOFkt!x=1O!_0n(cAzZW+3nHJ{2S>tgSK?~cFha^y(l@-Mr2W$%MN{#af8J;V*>hdq!gx=d0h$T7l}>91Wh07)9CTX zh2_ZdQCyFOQ)l(}gft0UZG`Sh2`x-w`5vC2UD}lZs*5 zG76$akzn}Xi))L3oGJ75#pcN=cX3!=57$Ha=hQ2^lwdyU#a}4JJOz6ddR%zae%#4& za)bFj)z=YQela(F#Y|Q#dp}PJghITwXouVaMq$BM?K%cXn9^Y@g43$=O)F&ZlOUom zJiad#dea;-eywBA@e&D6Pdso1?2^(pXiN91?jvcaUyYoKUmvl5G9e$W!okWe*@a<^ z8cQQ6cNSf+UPDx%?_G4aIiybZHHagF{;IcD(dPO!#=u zWfqLcPc^+7Uu#l(Bpxft{*4lv#*u7X9AOzDO z1D9?^jIo}?%iz(_dwLa{ex#T}76ZfN_Z-hwpus9y+4xaUu9cX}&P{XrZVWE{1^0yw zO;YhLEW!pJcbCt3L8~a7>jsaN{V3>tz6_7`&pi%GxZ=V3?3K^U+*ryLSb)8^IblJ0 zSRLNDvIxt)S}g30?s_3NX>F?NKIGrG_zB9@Z>uSW3k2es_H2kU;Rnn%j5qP)!XHKE zPB2mHP~tLCg4K_vH$xv`HbRsJwbZMUV(t=ez;Ec(vyHH)FbfLg`c61I$W_uBB>i^r z&{_P;369-&>23R%qNIULe=1~T$(DA`ev*EWZ6j(B$(te}x1WvmIll21zvygkS%vwG zzkR6Z#RKA2!z!C%M!O>!=Gr0(J0FP=-MN=5t-Ir)of50y10W}j`GtRCsXBakrKtG& zazmITDJMA0C51&BnLY)SY9r)NVTMs);1<=oosS9g31l{4ztjD3#+2H7u_|66b|_*O z;Qk6nalpqdHOjx|K&vUS_6ITgGll;TdaN*ta=M_YtyC)I9Tmr~VaPrH2qb6sd~=AcIxV+%z{E&0@y=DPArw zdV7z(G1hBx7hd{>(cr43^WF%4Y@PXZ?wPpj{OQ#tvc$pABJbvPGvdR`cAtHn)cSEV zrpu}1tJwQ3y!mSmH*uz*x0o|CS<^w%&KJzsj~DU0cLQUxk5B!hWE>aBkjJle8z~;s z-!A=($+}Jq_BTK5^B!`R>!MulZN)F=iXXeUd0w5lUsE5VP*H*oCy(;?S$p*TVvTxwAeWFB$jHyb0593)$zqalVlDX=GcCN1gU0 zlgU)I$LcXZ8Oyc2TZYTPu@-;7<4YYB-``Qa;IDcvydIA$%kHhJKV^m*-zxcvU4viy&Kr5GVM{IT>WRywKQ9;>SEiQD*NqplK-KK4YR`p0@JW)n_{TU3bt0 zim%;(m1=#v2}zTps=?fU5w^(*y)xT%1vtQH&}50ZF!9YxW=&7*W($2kgKyz1mUgfs zfV<*XVVIFnohW=|j+@Kfo!#liQR^x>2yQdrG;2o8WZR+XzU_nG=Ed2rK?ntA;K5B{ z>M8+*A4!Jm^Bg}aW?R?6;@QG@uQ8&oJ{hFixcfEnJ4QH?A4>P=q29oDGW;L;= z9-a0;g%c`C+Ai!UmK$NC*4#;Jp<1=TioL=t^YM)<<%u#hnnfSS`nq63QKGO1L8RzX z@MFDqs1z ztYmxDl@LU)5acvHk)~Z`RW7=aJ_nGD!mOSYD>5Odjn@TK#LY{jf?+piB5AM-CAoT_ z?S-*q7}wyLJzK>N%eMPuFgN)Q_otKP;aqy=D5f!7<=n(lNkYRXVpkB{TAYLYg{|(jtRqYmg$xH zjmq?B(RE4 zQx^~Pt}gxC2~l=K$$-sYy_r$CO(d=+b3H1MB*y_5g6WLaWTXn+TKQ|hNY^>Mp6k*$ zwkovomhu776vQATqT4blf~g;TY(MWCrf^^yfWJvSAB$p5l;jm@o#=!lqw+Lqfq>X= z$6~kxfm7`3q4zUEB;u4qa#BdJxO!;xGm)wwuisj{0y2x{R(IGMrsIzDY9LW>m!Y`= z04sx3IjnYvL<4JqxQ8f7qYd0s2Ig%`ytYPEMKI)s(LD}D@EY>x`VFtqvnADNBdeao zC96X+MxnwKmjpg{U&gP3HE}1=s!lv&D{6(g_lzyF3A`7Jn*&d_kL<;dAFx!UZ>hB8 z5A*%LsAn;VLp>3${0>M?PSQ)9s3}|h2e?TG4_F{}{Cs>#3Q*t$(CUc}M)I}8cPF6% z=+h(Kh^8)}gj(0}#e7O^FQ6`~fd1#8#!}LMuo3A0bN`o}PYsm!Y}sdOz$+Tegc=qT z8x`PH$7lvnhJp{kHWb22l;@7B7|4yL4UOOVM0MP_>P%S1Lnid)+k9{+3D+JFa#Pyf zhVc#&df87APl4W9X)F3pGS>@etfl=_E5tBcVoOfrD4hmVeTY-cj((pkn%n@EgN{0f zwb_^Rk0I#iZuHK!l*lN`ceJn(sI{$Fq6nN& zE<-=0_2WN}m+*ivmIOxB@#~Q-cZ>l136w{#TIJe478`KE7@=a{>SzPHsKLzYAyBQO zAtuuF$-JSDy_S@6GW0MOE~R)b;+0f%_NMrW(+V#c_d&U8Z9+ec4=HmOHw?gdjF(Lu zzra83M_BoO-1b3;9`%&DHfuUY)6YDV21P$C!Rc?mv&{lx#f8oc6?0?x zK08{WP65?#>(vPfA-c=MCY|%*1_<3D4NX zeVTi-JGl2uP_2@0F{G({pxQOXt_d{g_CV6b?jNpfUG9;8yle-^4KHRvZs-_2siata zt+d_T@U$&t*xaD22(fH(W1r$Mo?3dc%Tncm=C6{V9y{v&VT#^1L04vDrLM9qBoZ4@ z6DBN#m57hX7$C(=#$Y5$bJmwA$T8jKD8+6A!-IJwA{WOfs%s}yxUw^?MRZjF$n_KN z6`_bGXcmE#5e4Ym)aQJ)xg3Pg0@k`iGuHe?f(5LtuzSq=nS^5z>vqU0EuZ&75V%Z{ zYyhRLN^)$c6Ds{f7*FBpE;n5iglx5PkHfWrj3`x^j^t z7ntuV`g!9Xg#^3!x)l*}IW=(Tz3>Y5l4uGaB&lz{GDjm2D5S$CExLT`I1#n^lBH7Y zDgpMag@`iETKAI=p<5E#LTkwzVR@=yY|uBVI1HG|8h+d;G-qfuj}-ZR6fN>EfCCW z9~wRQoAPEa#aO?3h?x{YvV*d+NtPkf&4V0k4|L=uj!U{L+oLa(z#&iuhJr3-PjO3R z5s?=nn_5^*^Rawr>>Nr@K(jwkB#JK-=+HqwfdO<+P5byeim)wvqGlP-P|~Nse8=XF zz`?RYB|D6SwS}C+YQv+;}k6$-%D(@+t14BL@vM z2q%q?f6D-A5s$_WY3{^G0F131bbh|g!}#BKw=HQ7mx;Dzg4Z*bTLQSfo{ed{4}NZW zfrRm^Ca$rlE{Ue~uYv>R9{3smwATcdM_6+yWIO z*ZRH~uXE@#p$XTbCt5j7j2=86e{9>HIB6xDzV+vAo&B?KUiMP|ttOElepnl%|DPqL b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/code/01-starting-project/public/manifest.json b/code/01-starting-project/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/01-starting-project/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/code/01-starting-project/public/robots.txt b/code/01-starting-project/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/01-starting-project/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/01-starting-project/src/App.js b/code/01-starting-project/src/App.js new file mode 100644 index 0000000000..425f59acd7 --- /dev/null +++ b/code/01-starting-project/src/App.js @@ -0,0 +1,14 @@ +import Cart from './components/Cart/Cart'; +import Layout from './components/Layout/Layout'; +import Products from './components/Shop/Products'; + +function App() { + return ( + + + + + ); +} + +export default App; diff --git a/code/01-starting-project/src/components/Cart/Cart.js b/code/01-starting-project/src/components/Cart/Cart.js new file mode 100644 index 0000000000..ff9572d1f5 --- /dev/null +++ b/code/01-starting-project/src/components/Cart/Cart.js @@ -0,0 +1,18 @@ +import Card from '../UI/Card'; +import classes from './Cart.module.css'; +import CartItem from './CartItem'; + +const Cart = (props) => { + return ( + +

Your Shopping Cart

+
    + +
+
+ ); +}; + +export default Cart; diff --git a/code/01-starting-project/src/components/Cart/Cart.module.css b/code/01-starting-project/src/components/Cart/Cart.module.css new file mode 100644 index 0000000000..95670ab70b --- /dev/null +++ b/code/01-starting-project/src/components/Cart/Cart.module.css @@ -0,0 +1,16 @@ +.cart { + max-width: 30rem; + background-color: #313131; + color: white; +} + +.cart h2 { + font-size: 1.25rem; + margin: 0.5rem 0; +} + +.cart ul { + list-style: none; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/code/01-starting-project/src/components/Cart/CartButton.js b/code/01-starting-project/src/components/Cart/CartButton.js new file mode 100644 index 0000000000..4af1e8c3a6 --- /dev/null +++ b/code/01-starting-project/src/components/Cart/CartButton.js @@ -0,0 +1,12 @@ +import classes from './CartButton.module.css'; + +const CartButton = (props) => { + return ( + + ); +}; + +export default CartButton; diff --git a/code/01-starting-project/src/components/Cart/CartButton.module.css b/code/01-starting-project/src/components/Cart/CartButton.module.css new file mode 100644 index 0000000000..93445f4596 --- /dev/null +++ b/code/01-starting-project/src/components/Cart/CartButton.module.css @@ -0,0 +1,21 @@ +.button { + background-color: transparent; + border-color: #1ad1b9; + color: #1ad1b9; +} + +.button:hover, +.button:active { + color: white; +} + +.button span { + margin: 0 0.5rem; +} + +.badge { + background-color: #1ad1b9; + border-radius: 30px; + padding: 0.15rem 1.25rem; + color: #1d1d1d; +} \ No newline at end of file diff --git a/code/01-starting-project/src/components/Cart/CartItem.js b/code/01-starting-project/src/components/Cart/CartItem.js new file mode 100644 index 0000000000..22d34df192 --- /dev/null +++ b/code/01-starting-project/src/components/Cart/CartItem.js @@ -0,0 +1,28 @@ +import classes from './CartItem.module.css'; + +const CartItem = (props) => { + const { title, quantity, total, price } = props.item; + + return ( +
  • +
    +

    {title}

    +
    + ${total.toFixed(2)}{' '} + (${price.toFixed(2)}/item) +
    +
    +
    +
    + x {quantity} +
    +
    + + +
    +
    +
  • + ); +}; + +export default CartItem; diff --git a/code/01-starting-project/src/components/Cart/CartItem.module.css b/code/01-starting-project/src/components/Cart/CartItem.module.css new file mode 100644 index 0000000000..e34100d841 --- /dev/null +++ b/code/01-starting-project/src/components/Cart/CartItem.module.css @@ -0,0 +1,58 @@ +.item { + margin: 1rem 0; + background-color: #575757; + padding: 1rem; +} + +.item h3 { + margin: 0 0 0.5rem 0; + font-size: 1.75rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.details { + display: flex; + justify-content: space-between; + align-items: center; +} + +.quantity span { + font-size: 1.5rem; + font-weight: bold; +} + +.price { + font-size: 1.5rem; + font-weight: bold; +} + +.itemprice { + font-weight: normal; + font-size: 1rem; + font-style: italic; +} + +.actions { + display: flex; + justify-content: flex-end; + margin: 0.5rem 0; +} + +.actions button { + background-color: transparent; + border: 1px solid white; + margin-left: 0.5rem; + padding: 0.15rem 1rem; + color: white; +} + +.actions button:hover, +.actions button:active { + background-color: #4b4b4b; + color: white; +} \ No newline at end of file diff --git a/code/01-starting-project/src/components/Layout/Layout.js b/code/01-starting-project/src/components/Layout/Layout.js new file mode 100644 index 0000000000..0ce12a011f --- /dev/null +++ b/code/01-starting-project/src/components/Layout/Layout.js @@ -0,0 +1,13 @@ +import { Fragment } from 'react'; +import MainHeader from './MainHeader'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/01-starting-project/src/components/Layout/MainHeader.js b/code/01-starting-project/src/components/Layout/MainHeader.js new file mode 100644 index 0000000000..38ea37a29b --- /dev/null +++ b/code/01-starting-project/src/components/Layout/MainHeader.js @@ -0,0 +1,19 @@ +import CartButton from '../Cart/CartButton'; +import classes from './MainHeader.module.css'; + +const MainHeader = (props) => { + return ( +
    +

    ReduxCart

    + +
    + ); +}; + +export default MainHeader; diff --git a/code/01-starting-project/src/components/Layout/MainHeader.module.css b/code/01-starting-project/src/components/Layout/MainHeader.module.css new file mode 100644 index 0000000000..e41c98d247 --- /dev/null +++ b/code/01-starting-project/src/components/Layout/MainHeader.module.css @@ -0,0 +1,19 @@ +.header { + width: 100%; + height: 5rem; + padding: 0 10%; + display: flex; + align-items: center; + justify-content: space-between; + background-color: #252424; +} + +.header h1 { + color: white; +} + +.header ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/01-starting-project/src/components/Shop/ProductItem.js b/code/01-starting-project/src/components/Shop/ProductItem.js new file mode 100644 index 0000000000..52fff3065a --- /dev/null +++ b/code/01-starting-project/src/components/Shop/ProductItem.js @@ -0,0 +1,23 @@ +import Card from '../UI/Card'; +import classes from './ProductItem.module.css'; + +const ProductItem = (props) => { + const { title, price, description } = props; + + return ( +
  • + +
    +

    {title}

    +
    ${price.toFixed(2)}
    +
    +

    {description}

    +
    + +
    +
    +
  • + ); +}; + +export default ProductItem; diff --git a/code/01-starting-project/src/components/Shop/ProductItem.module.css b/code/01-starting-project/src/components/Shop/ProductItem.module.css new file mode 100644 index 0000000000..2fcdc5d8a6 --- /dev/null +++ b/code/01-starting-project/src/components/Shop/ProductItem.module.css @@ -0,0 +1,27 @@ +.item h3 { + margin: 0.5rem 0; + font-size: 1.25rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.price { + border-radius: 30px; + padding: 0.15rem 1.5rem; + background-color: #3a3a3a; + color: white; + font-size: 1.5rem; +} + +.item p { + color: #3a3a3a; +} + +.actions { + display: flex; + justify-content: flex-end; +} \ No newline at end of file diff --git a/code/01-starting-project/src/components/Shop/Products.js b/code/01-starting-project/src/components/Shop/Products.js new file mode 100644 index 0000000000..1f6869b296 --- /dev/null +++ b/code/01-starting-project/src/components/Shop/Products.js @@ -0,0 +1,19 @@ +import ProductItem from './ProductItem'; +import classes from './Products.module.css'; + +const Products = (props) => { + return ( +
    +

    Buy your favorite products

    +
      + +
    +
    + ); +}; + +export default Products; diff --git a/code/01-starting-project/src/components/Shop/Products.module.css b/code/01-starting-project/src/components/Shop/Products.module.css new file mode 100644 index 0000000000..d81c97330f --- /dev/null +++ b/code/01-starting-project/src/components/Shop/Products.module.css @@ -0,0 +1,12 @@ +.products h2 { + color: white; + margin: 2rem auto; + text-align: center; + text-transform: uppercase; +} + +.products ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/01-starting-project/src/components/UI/Card.js b/code/01-starting-project/src/components/UI/Card.js new file mode 100644 index 0000000000..849202f21c --- /dev/null +++ b/code/01-starting-project/src/components/UI/Card.js @@ -0,0 +1,13 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return ( +
    + {props.children} +
    + ); +}; + +export default Card; diff --git a/code/01-starting-project/src/components/UI/Card.module.css b/code/01-starting-project/src/components/UI/Card.module.css new file mode 100644 index 0000000000..ac9c6709f4 --- /dev/null +++ b/code/01-starting-project/src/components/UI/Card.module.css @@ -0,0 +1,8 @@ +.card { + margin: 1rem auto; + border-radius: 6px; + background-color: white; + padding: 1rem; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/01-starting-project/src/index.css b/code/01-starting-project/src/index.css new file mode 100644 index 0000000000..3431f5f884 --- /dev/null +++ b/code/01-starting-project/src/index.css @@ -0,0 +1,31 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap'); + +* { + box-sizing: border-box; +} + +html { + font-family: 'Noto Sans JP', sans-serif; +} + +body { + margin: 0; + background-color: #3f3f3f; +} + +button { + font: inherit; + cursor: pointer; + padding: 0.5rem 1.5rem; + border-radius: 6px; + background-color: transparent; + color: #1a8ed1; + border: 1px solid #1a8ed1; +} + +button:hover, +button:active { + background-color: #1ac5d1; + border-color: #1ac5d1; + color: white; +} \ No newline at end of file diff --git a/code/01-starting-project/src/index.js b/code/01-starting-project/src/index.js new file mode 100644 index 0000000000..d59d0ce18d --- /dev/null +++ b/code/01-starting-project/src/index.js @@ -0,0 +1,6 @@ +import ReactDOM from 'react-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render(, document.getElementById('root')); diff --git a/code/02-refresher-practice-finished/package.json b/code/02-refresher-practice-finished/package.json new file mode 100644 index 0000000000..2165b1f063 --- /dev/null +++ b/code/02-refresher-practice-finished/package.json @@ -0,0 +1,40 @@ +{ + "name": "react-complete-guide", + "version": "0.1.0", + "private": true, + "dependencies": { + "@reduxjs/toolkit": "^1.5.0", + "@testing-library/jest-dom": "^5.11.6", + "@testing-library/react": "^11.2.2", + "@testing-library/user-event": "^12.5.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "react-redux": "^7.2.2", + "react-scripts": "4.0.1", + "web-vitals": "^0.2.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/code/02-refresher-practice-finished/public/favicon.ico b/code/02-refresher-practice-finished/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB
    3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/code/02-refresher-practice-finished/public/index.html b/code/02-refresher-practice-finished/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/02-refresher-practice-finished/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/02-refresher-practice-finished/public/logo192.png b/code/02-refresher-practice-finished/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/code/02-refresher-practice-finished/public/manifest.json b/code/02-refresher-practice-finished/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/02-refresher-practice-finished/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/code/02-refresher-practice-finished/public/robots.txt b/code/02-refresher-practice-finished/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/02-refresher-practice-finished/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/02-refresher-practice-finished/src/App.js b/code/02-refresher-practice-finished/src/App.js new file mode 100644 index 0000000000..8531b2d78c --- /dev/null +++ b/code/02-refresher-practice-finished/src/App.js @@ -0,0 +1,18 @@ +import { useSelector } from 'react-redux'; + +import Cart from './components/Cart/Cart'; +import Layout from './components/Layout/Layout'; +import Products from './components/Shop/Products'; + +function App() { + const showCart = useSelector((state) => state.ui.cartIsVisible); + + return ( + + {showCart && } + + + ); +} + +export default App; diff --git a/code/02-refresher-practice-finished/src/components/Cart/Cart.js b/code/02-refresher-practice-finished/src/components/Cart/Cart.js new file mode 100644 index 0000000000..33030d2089 --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Cart/Cart.js @@ -0,0 +1,31 @@ +import { useSelector } from 'react-redux'; + +import Card from '../UI/Card'; +import classes from './Cart.module.css'; +import CartItem from './CartItem'; + +const Cart = (props) => { + const cartItems = useSelector((state) => state.cart.items); + + return ( + +

    Your Shopping Cart

    +
      + {cartItems.map((item) => ( + + ))} +
    +
    + ); +}; + +export default Cart; diff --git a/code/02-refresher-practice-finished/src/components/Cart/Cart.module.css b/code/02-refresher-practice-finished/src/components/Cart/Cart.module.css new file mode 100644 index 0000000000..95670ab70b --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Cart/Cart.module.css @@ -0,0 +1,16 @@ +.cart { + max-width: 30rem; + background-color: #313131; + color: white; +} + +.cart h2 { + font-size: 1.25rem; + margin: 0.5rem 0; +} + +.cart ul { + list-style: none; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/code/02-refresher-practice-finished/src/components/Cart/CartButton.js b/code/02-refresher-practice-finished/src/components/Cart/CartButton.js new file mode 100644 index 0000000000..4e59baac3c --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Cart/CartButton.js @@ -0,0 +1,22 @@ +import { useDispatch, useSelector } from 'react-redux'; + +import { uiActions } from '../../store/ui-slice'; +import classes from './CartButton.module.css'; + +const CartButton = (props) => { + const dispatch = useDispatch(); + const cartQuantity = useSelector((state) => state.cart.totalQuantity); + + const toggleCartHandler = () => { + dispatch(uiActions.toggle()); + }; + + return ( + + ); +}; + +export default CartButton; diff --git a/code/02-refresher-practice-finished/src/components/Cart/CartButton.module.css b/code/02-refresher-practice-finished/src/components/Cart/CartButton.module.css new file mode 100644 index 0000000000..93445f4596 --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Cart/CartButton.module.css @@ -0,0 +1,21 @@ +.button { + background-color: transparent; + border-color: #1ad1b9; + color: #1ad1b9; +} + +.button:hover, +.button:active { + color: white; +} + +.button span { + margin: 0 0.5rem; +} + +.badge { + background-color: #1ad1b9; + border-radius: 30px; + padding: 0.15rem 1.25rem; + color: #1d1d1d; +} \ No newline at end of file diff --git a/code/02-refresher-practice-finished/src/components/Cart/CartItem.js b/code/02-refresher-practice-finished/src/components/Cart/CartItem.js new file mode 100644 index 0000000000..e1c6a8f012 --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Cart/CartItem.js @@ -0,0 +1,47 @@ +import { useDispatch } from 'react-redux'; + +import classes from './CartItem.module.css'; +import { cartActions } from '../../store/cart-slice'; + +const CartItem = (props) => { + const dispatch = useDispatch(); + + const { title, quantity, total, price, id } = props.item; + + const removeItemHandler = () => { + dispatch(cartActions.removeItemFromCart(id)); + }; + + const addItemHandler = () => { + dispatch( + cartActions.addItemToCart({ + id, + title, + price, + }) + ); + }; + + return ( +
  • +
    +

    {title}

    +
    + ${total.toFixed(2)}{' '} + (${price.toFixed(2)}/item) +
    +
    +
    +
    + x {quantity} +
    +
    + + +
    +
    +
  • + ); +}; + +export default CartItem; diff --git a/code/02-refresher-practice-finished/src/components/Cart/CartItem.module.css b/code/02-refresher-practice-finished/src/components/Cart/CartItem.module.css new file mode 100644 index 0000000000..e34100d841 --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Cart/CartItem.module.css @@ -0,0 +1,58 @@ +.item { + margin: 1rem 0; + background-color: #575757; + padding: 1rem; +} + +.item h3 { + margin: 0 0 0.5rem 0; + font-size: 1.75rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.details { + display: flex; + justify-content: space-between; + align-items: center; +} + +.quantity span { + font-size: 1.5rem; + font-weight: bold; +} + +.price { + font-size: 1.5rem; + font-weight: bold; +} + +.itemprice { + font-weight: normal; + font-size: 1rem; + font-style: italic; +} + +.actions { + display: flex; + justify-content: flex-end; + margin: 0.5rem 0; +} + +.actions button { + background-color: transparent; + border: 1px solid white; + margin-left: 0.5rem; + padding: 0.15rem 1rem; + color: white; +} + +.actions button:hover, +.actions button:active { + background-color: #4b4b4b; + color: white; +} \ No newline at end of file diff --git a/code/02-refresher-practice-finished/src/components/Layout/Layout.js b/code/02-refresher-practice-finished/src/components/Layout/Layout.js new file mode 100644 index 0000000000..0ce12a011f --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Layout/Layout.js @@ -0,0 +1,13 @@ +import { Fragment } from 'react'; +import MainHeader from './MainHeader'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/02-refresher-practice-finished/src/components/Layout/MainHeader.js b/code/02-refresher-practice-finished/src/components/Layout/MainHeader.js new file mode 100644 index 0000000000..38ea37a29b --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Layout/MainHeader.js @@ -0,0 +1,19 @@ +import CartButton from '../Cart/CartButton'; +import classes from './MainHeader.module.css'; + +const MainHeader = (props) => { + return ( +
    +

    ReduxCart

    + +
    + ); +}; + +export default MainHeader; diff --git a/code/02-refresher-practice-finished/src/components/Layout/MainHeader.module.css b/code/02-refresher-practice-finished/src/components/Layout/MainHeader.module.css new file mode 100644 index 0000000000..e41c98d247 --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Layout/MainHeader.module.css @@ -0,0 +1,19 @@ +.header { + width: 100%; + height: 5rem; + padding: 0 10%; + display: flex; + align-items: center; + justify-content: space-between; + background-color: #252424; +} + +.header h1 { + color: white; +} + +.header ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/02-refresher-practice-finished/src/components/Shop/ProductItem.js b/code/02-refresher-practice-finished/src/components/Shop/ProductItem.js new file mode 100644 index 0000000000..b60369769b --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Shop/ProductItem.js @@ -0,0 +1,38 @@ +import { useDispatch } from 'react-redux'; + +import { cartActions } from '../../store/cart-slice'; +import Card from '../UI/Card'; +import classes from './ProductItem.module.css'; + +const ProductItem = (props) => { + const dispatch = useDispatch(); + + const { title, price, description, id } = props; + + const addToCartHandler = () => { + dispatch( + cartActions.addItemToCart({ + id, + title, + price, + }) + ); + }; + + return ( +
  • + +
    +

    {title}

    +
    ${price.toFixed(2)}
    +
    +

    {description}

    +
    + +
    +
    +
  • + ); +}; + +export default ProductItem; diff --git a/code/02-refresher-practice-finished/src/components/Shop/ProductItem.module.css b/code/02-refresher-practice-finished/src/components/Shop/ProductItem.module.css new file mode 100644 index 0000000000..2fcdc5d8a6 --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Shop/ProductItem.module.css @@ -0,0 +1,27 @@ +.item h3 { + margin: 0.5rem 0; + font-size: 1.25rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.price { + border-radius: 30px; + padding: 0.15rem 1.5rem; + background-color: #3a3a3a; + color: white; + font-size: 1.5rem; +} + +.item p { + color: #3a3a3a; +} + +.actions { + display: flex; + justify-content: flex-end; +} \ No newline at end of file diff --git a/code/02-refresher-practice-finished/src/components/Shop/Products.js b/code/02-refresher-practice-finished/src/components/Shop/Products.js new file mode 100644 index 0000000000..6b430cc262 --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Shop/Products.js @@ -0,0 +1,38 @@ +import ProductItem from './ProductItem'; +import classes from './Products.module.css'; + +const DUMMY_PRODUCTS = [ + { + id: 'p1', + price: 6, + title: 'My First Book', + description: 'The first book I ever wrote', + }, + { + id: 'p2', + price: 5, + title: 'My Second Book', + description: 'The second book I ever wrote', + }, +]; + +const Products = (props) => { + return ( +
    +

    Buy your favorite products

    +
      + {DUMMY_PRODUCTS.map((product) => ( + + ))} +
    +
    + ); +}; + +export default Products; diff --git a/code/02-refresher-practice-finished/src/components/Shop/Products.module.css b/code/02-refresher-practice-finished/src/components/Shop/Products.module.css new file mode 100644 index 0000000000..d81c97330f --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/Shop/Products.module.css @@ -0,0 +1,12 @@ +.products h2 { + color: white; + margin: 2rem auto; + text-align: center; + text-transform: uppercase; +} + +.products ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/02-refresher-practice-finished/src/components/UI/Card.js b/code/02-refresher-practice-finished/src/components/UI/Card.js new file mode 100644 index 0000000000..849202f21c --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/UI/Card.js @@ -0,0 +1,13 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return ( +
    + {props.children} +
    + ); +}; + +export default Card; diff --git a/code/02-refresher-practice-finished/src/components/UI/Card.module.css b/code/02-refresher-practice-finished/src/components/UI/Card.module.css new file mode 100644 index 0000000000..ac9c6709f4 --- /dev/null +++ b/code/02-refresher-practice-finished/src/components/UI/Card.module.css @@ -0,0 +1,8 @@ +.card { + margin: 1rem auto; + border-radius: 6px; + background-color: white; + padding: 1rem; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/02-refresher-practice-finished/src/index.css b/code/02-refresher-practice-finished/src/index.css new file mode 100644 index 0000000000..3431f5f884 --- /dev/null +++ b/code/02-refresher-practice-finished/src/index.css @@ -0,0 +1,31 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap'); + +* { + box-sizing: border-box; +} + +html { + font-family: 'Noto Sans JP', sans-serif; +} + +body { + margin: 0; + background-color: #3f3f3f; +} + +button { + font: inherit; + cursor: pointer; + padding: 0.5rem 1.5rem; + border-radius: 6px; + background-color: transparent; + color: #1a8ed1; + border: 1px solid #1a8ed1; +} + +button:hover, +button:active { + background-color: #1ac5d1; + border-color: #1ac5d1; + color: white; +} \ No newline at end of file diff --git a/code/02-refresher-practice-finished/src/index.js b/code/02-refresher-practice-finished/src/index.js new file mode 100644 index 0000000000..ac1938ea3e --- /dev/null +++ b/code/02-refresher-practice-finished/src/index.js @@ -0,0 +1,13 @@ +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; + +import store from './store/index'; +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/02-refresher-practice-finished/src/store/cart-slice.js b/code/02-refresher-practice-finished/src/store/cart-slice.js new file mode 100644 index 0000000000..dce632d86c --- /dev/null +++ b/code/02-refresher-practice-finished/src/store/cart-slice.js @@ -0,0 +1,42 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const cartSlice = createSlice({ + name: 'cart', + initialState: { + items: [], + totalQuantity: 0, + }, + reducers: { + addItemToCart(state, action) { + const newItem = action.payload; + const existingItem = state.items.find((item) => item.id === newItem.id); + state.totalQuantity++; + if (!existingItem) { + state.items.push({ + id: newItem.id, + price: newItem.price, + quantity: 1, + totalPrice: newItem.price, + name: newItem.title + }); + } else { + existingItem.quantity++; + existingItem.totalPrice = existingItem.totalPrice + newItem.price; + } + }, + removeItemFromCart(state, action) { + const id = action.payload; + const existingItem = state.items.find(item => item.id === id); + state.totalQuantity--; + if (existingItem.quantity === 1) { + state.items = state.items.filter(item => item.id !== id); + } else { + existingItem.quantity--; + } + }, + }, +}); + +export const cartActions = cartSlice.actions; + +export default cartSlice; \ No newline at end of file diff --git a/code/02-refresher-practice-finished/src/store/index.js b/code/02-refresher-practice-finished/src/store/index.js new file mode 100644 index 0000000000..d663f26de2 --- /dev/null +++ b/code/02-refresher-practice-finished/src/store/index.js @@ -0,0 +1,10 @@ +import { configureStore } from '@reduxjs/toolkit'; + +import uiSlice from './ui-slice'; +import cartSlice from './cart-slice'; + +const store = configureStore({ + reducer: { ui: uiSlice.reducer, cart: cartSlice.reducer }, +}); + +export default store; diff --git a/code/02-refresher-practice-finished/src/store/ui-slice.js b/code/02-refresher-practice-finished/src/store/ui-slice.js new file mode 100644 index 0000000000..4fa43c1b12 --- /dev/null +++ b/code/02-refresher-practice-finished/src/store/ui-slice.js @@ -0,0 +1,15 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const uiSlice = createSlice({ + name: 'ui', + initialState: { cartIsVisible: false }, + reducers: { + toggle(state) { + state.cartIsVisible = !state.cartIsVisible; + } + } +}); + +export const uiActions = uiSlice.actions; + +export default uiSlice; \ No newline at end of file diff --git a/code/03-using-useeffect-with-redux/package.json b/code/03-using-useeffect-with-redux/package.json new file mode 100644 index 0000000000..2165b1f063 --- /dev/null +++ b/code/03-using-useeffect-with-redux/package.json @@ -0,0 +1,40 @@ +{ + "name": "react-complete-guide", + "version": "0.1.0", + "private": true, + "dependencies": { + "@reduxjs/toolkit": "^1.5.0", + "@testing-library/jest-dom": "^5.11.6", + "@testing-library/react": "^11.2.2", + "@testing-library/user-event": "^12.5.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "react-redux": "^7.2.2", + "react-scripts": "4.0.1", + "web-vitals": "^0.2.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/code/03-using-useeffect-with-redux/public/favicon.ico b/code/03-using-useeffect-with-redux/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB
    3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/code/03-using-useeffect-with-redux/public/index.html b/code/03-using-useeffect-with-redux/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/03-using-useeffect-with-redux/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/03-using-useeffect-with-redux/public/logo192.png b/code/03-using-useeffect-with-redux/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/code/03-using-useeffect-with-redux/public/manifest.json b/code/03-using-useeffect-with-redux/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/03-using-useeffect-with-redux/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/code/03-using-useeffect-with-redux/public/robots.txt b/code/03-using-useeffect-with-redux/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/03-using-useeffect-with-redux/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/03-using-useeffect-with-redux/src/App.js b/code/03-using-useeffect-with-redux/src/App.js new file mode 100644 index 0000000000..096f522320 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/App.js @@ -0,0 +1,27 @@ +import { useEffect } from 'react'; +import { useSelector } from 'react-redux'; + +import Cart from './components/Cart/Cart'; +import Layout from './components/Layout/Layout'; +import Products from './components/Shop/Products'; + +function App() { + const showCart = useSelector((state) => state.ui.cartIsVisible); + const cart = useSelector((state) => state.cart); + + useEffect(() => { + fetch('https://react-http-6b4a6.firebaseio.com/cart.json', { + method: 'PUT', + body: JSON.stringify(cart), + }); + }, [cart]); + + return ( + + {showCart && } + + + ); +} + +export default App; diff --git a/code/03-using-useeffect-with-redux/src/components/Cart/Cart.js b/code/03-using-useeffect-with-redux/src/components/Cart/Cart.js new file mode 100644 index 0000000000..33030d2089 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Cart/Cart.js @@ -0,0 +1,31 @@ +import { useSelector } from 'react-redux'; + +import Card from '../UI/Card'; +import classes from './Cart.module.css'; +import CartItem from './CartItem'; + +const Cart = (props) => { + const cartItems = useSelector((state) => state.cart.items); + + return ( + +

    Your Shopping Cart

    +
      + {cartItems.map((item) => ( + + ))} +
    +
    + ); +}; + +export default Cart; diff --git a/code/03-using-useeffect-with-redux/src/components/Cart/Cart.module.css b/code/03-using-useeffect-with-redux/src/components/Cart/Cart.module.css new file mode 100644 index 0000000000..95670ab70b --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Cart/Cart.module.css @@ -0,0 +1,16 @@ +.cart { + max-width: 30rem; + background-color: #313131; + color: white; +} + +.cart h2 { + font-size: 1.25rem; + margin: 0.5rem 0; +} + +.cart ul { + list-style: none; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/code/03-using-useeffect-with-redux/src/components/Cart/CartButton.js b/code/03-using-useeffect-with-redux/src/components/Cart/CartButton.js new file mode 100644 index 0000000000..4e59baac3c --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Cart/CartButton.js @@ -0,0 +1,22 @@ +import { useDispatch, useSelector } from 'react-redux'; + +import { uiActions } from '../../store/ui-slice'; +import classes from './CartButton.module.css'; + +const CartButton = (props) => { + const dispatch = useDispatch(); + const cartQuantity = useSelector((state) => state.cart.totalQuantity); + + const toggleCartHandler = () => { + dispatch(uiActions.toggle()); + }; + + return ( + + ); +}; + +export default CartButton; diff --git a/code/03-using-useeffect-with-redux/src/components/Cart/CartButton.module.css b/code/03-using-useeffect-with-redux/src/components/Cart/CartButton.module.css new file mode 100644 index 0000000000..93445f4596 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Cart/CartButton.module.css @@ -0,0 +1,21 @@ +.button { + background-color: transparent; + border-color: #1ad1b9; + color: #1ad1b9; +} + +.button:hover, +.button:active { + color: white; +} + +.button span { + margin: 0 0.5rem; +} + +.badge { + background-color: #1ad1b9; + border-radius: 30px; + padding: 0.15rem 1.25rem; + color: #1d1d1d; +} \ No newline at end of file diff --git a/code/03-using-useeffect-with-redux/src/components/Cart/CartItem.js b/code/03-using-useeffect-with-redux/src/components/Cart/CartItem.js new file mode 100644 index 0000000000..e1c6a8f012 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Cart/CartItem.js @@ -0,0 +1,47 @@ +import { useDispatch } from 'react-redux'; + +import classes from './CartItem.module.css'; +import { cartActions } from '../../store/cart-slice'; + +const CartItem = (props) => { + const dispatch = useDispatch(); + + const { title, quantity, total, price, id } = props.item; + + const removeItemHandler = () => { + dispatch(cartActions.removeItemFromCart(id)); + }; + + const addItemHandler = () => { + dispatch( + cartActions.addItemToCart({ + id, + title, + price, + }) + ); + }; + + return ( +
  • +
    +

    {title}

    +
    + ${total.toFixed(2)}{' '} + (${price.toFixed(2)}/item) +
    +
    +
    +
    + x {quantity} +
    +
    + + +
    +
    +
  • + ); +}; + +export default CartItem; diff --git a/code/03-using-useeffect-with-redux/src/components/Cart/CartItem.module.css b/code/03-using-useeffect-with-redux/src/components/Cart/CartItem.module.css new file mode 100644 index 0000000000..e34100d841 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Cart/CartItem.module.css @@ -0,0 +1,58 @@ +.item { + margin: 1rem 0; + background-color: #575757; + padding: 1rem; +} + +.item h3 { + margin: 0 0 0.5rem 0; + font-size: 1.75rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.details { + display: flex; + justify-content: space-between; + align-items: center; +} + +.quantity span { + font-size: 1.5rem; + font-weight: bold; +} + +.price { + font-size: 1.5rem; + font-weight: bold; +} + +.itemprice { + font-weight: normal; + font-size: 1rem; + font-style: italic; +} + +.actions { + display: flex; + justify-content: flex-end; + margin: 0.5rem 0; +} + +.actions button { + background-color: transparent; + border: 1px solid white; + margin-left: 0.5rem; + padding: 0.15rem 1rem; + color: white; +} + +.actions button:hover, +.actions button:active { + background-color: #4b4b4b; + color: white; +} \ No newline at end of file diff --git a/code/03-using-useeffect-with-redux/src/components/Layout/Layout.js b/code/03-using-useeffect-with-redux/src/components/Layout/Layout.js new file mode 100644 index 0000000000..0ce12a011f --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Layout/Layout.js @@ -0,0 +1,13 @@ +import { Fragment } from 'react'; +import MainHeader from './MainHeader'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/03-using-useeffect-with-redux/src/components/Layout/MainHeader.js b/code/03-using-useeffect-with-redux/src/components/Layout/MainHeader.js new file mode 100644 index 0000000000..38ea37a29b --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Layout/MainHeader.js @@ -0,0 +1,19 @@ +import CartButton from '../Cart/CartButton'; +import classes from './MainHeader.module.css'; + +const MainHeader = (props) => { + return ( +
    +

    ReduxCart

    + +
    + ); +}; + +export default MainHeader; diff --git a/code/03-using-useeffect-with-redux/src/components/Layout/MainHeader.module.css b/code/03-using-useeffect-with-redux/src/components/Layout/MainHeader.module.css new file mode 100644 index 0000000000..e41c98d247 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Layout/MainHeader.module.css @@ -0,0 +1,19 @@ +.header { + width: 100%; + height: 5rem; + padding: 0 10%; + display: flex; + align-items: center; + justify-content: space-between; + background-color: #252424; +} + +.header h1 { + color: white; +} + +.header ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/03-using-useeffect-with-redux/src/components/Shop/ProductItem.js b/code/03-using-useeffect-with-redux/src/components/Shop/ProductItem.js new file mode 100644 index 0000000000..4a2a4b3f9f --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Shop/ProductItem.js @@ -0,0 +1,41 @@ +import { useDispatch } from 'react-redux'; + +import { cartActions } from '../../store/cart-slice'; +import Card from '../UI/Card'; +import classes from './ProductItem.module.css'; + +const ProductItem = (props) => { + const dispatch = useDispatch(); + + const { title, price, description, id } = props; + + const addToCartHandler = () => { + // and then send Http request + // fetch('firebase-url', { method: 'POST', body: JSON.stringify(newCart) }) + + dispatch( + cartActions.addItemToCart({ + id, + title, + price, + }) + ); + }; + + return ( +
  • + +
    +

    {title}

    +
    ${price.toFixed(2)}
    +
    +

    {description}

    +
    + +
    +
    +
  • + ); +}; + +export default ProductItem; diff --git a/code/03-using-useeffect-with-redux/src/components/Shop/ProductItem.module.css b/code/03-using-useeffect-with-redux/src/components/Shop/ProductItem.module.css new file mode 100644 index 0000000000..2fcdc5d8a6 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Shop/ProductItem.module.css @@ -0,0 +1,27 @@ +.item h3 { + margin: 0.5rem 0; + font-size: 1.25rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.price { + border-radius: 30px; + padding: 0.15rem 1.5rem; + background-color: #3a3a3a; + color: white; + font-size: 1.5rem; +} + +.item p { + color: #3a3a3a; +} + +.actions { + display: flex; + justify-content: flex-end; +} \ No newline at end of file diff --git a/code/03-using-useeffect-with-redux/src/components/Shop/Products.js b/code/03-using-useeffect-with-redux/src/components/Shop/Products.js new file mode 100644 index 0000000000..6b430cc262 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Shop/Products.js @@ -0,0 +1,38 @@ +import ProductItem from './ProductItem'; +import classes from './Products.module.css'; + +const DUMMY_PRODUCTS = [ + { + id: 'p1', + price: 6, + title: 'My First Book', + description: 'The first book I ever wrote', + }, + { + id: 'p2', + price: 5, + title: 'My Second Book', + description: 'The second book I ever wrote', + }, +]; + +const Products = (props) => { + return ( +
    +

    Buy your favorite products

    +
      + {DUMMY_PRODUCTS.map((product) => ( + + ))} +
    +
    + ); +}; + +export default Products; diff --git a/code/03-using-useeffect-with-redux/src/components/Shop/Products.module.css b/code/03-using-useeffect-with-redux/src/components/Shop/Products.module.css new file mode 100644 index 0000000000..d81c97330f --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/Shop/Products.module.css @@ -0,0 +1,12 @@ +.products h2 { + color: white; + margin: 2rem auto; + text-align: center; + text-transform: uppercase; +} + +.products ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/03-using-useeffect-with-redux/src/components/UI/Card.js b/code/03-using-useeffect-with-redux/src/components/UI/Card.js new file mode 100644 index 0000000000..849202f21c --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/UI/Card.js @@ -0,0 +1,13 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return ( +
    + {props.children} +
    + ); +}; + +export default Card; diff --git a/code/03-using-useeffect-with-redux/src/components/UI/Card.module.css b/code/03-using-useeffect-with-redux/src/components/UI/Card.module.css new file mode 100644 index 0000000000..ac9c6709f4 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/components/UI/Card.module.css @@ -0,0 +1,8 @@ +.card { + margin: 1rem auto; + border-radius: 6px; + background-color: white; + padding: 1rem; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/03-using-useeffect-with-redux/src/index.css b/code/03-using-useeffect-with-redux/src/index.css new file mode 100644 index 0000000000..3431f5f884 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/index.css @@ -0,0 +1,31 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap'); + +* { + box-sizing: border-box; +} + +html { + font-family: 'Noto Sans JP', sans-serif; +} + +body { + margin: 0; + background-color: #3f3f3f; +} + +button { + font: inherit; + cursor: pointer; + padding: 0.5rem 1.5rem; + border-radius: 6px; + background-color: transparent; + color: #1a8ed1; + border: 1px solid #1a8ed1; +} + +button:hover, +button:active { + background-color: #1ac5d1; + border-color: #1ac5d1; + color: white; +} \ No newline at end of file diff --git a/code/03-using-useeffect-with-redux/src/index.js b/code/03-using-useeffect-with-redux/src/index.js new file mode 100644 index 0000000000..ac1938ea3e --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/index.js @@ -0,0 +1,13 @@ +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; + +import store from './store/index'; +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/03-using-useeffect-with-redux/src/store/cart-slice.js b/code/03-using-useeffect-with-redux/src/store/cart-slice.js new file mode 100644 index 0000000000..e629fc66ce --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/store/cart-slice.js @@ -0,0 +1,46 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const cartSlice = createSlice({ + name: 'cart', + initialState: { + items: [], + totalQuantity: 0, + }, + reducers: { + replaceCart(state, action) { + state.totalQuantity = action.payload.totalQuantity; + state.items = action.payload.items; + }, + addItemToCart(state, action) { + const newItem = action.payload; + const existingItem = state.items.find((item) => item.id === newItem.id); + state.totalQuantity++; + if (!existingItem) { + state.items.push({ + id: newItem.id, + price: newItem.price, + quantity: 1, + totalPrice: newItem.price, + name: newItem.title, + }); + } else { + existingItem.quantity++; + existingItem.totalPrice = existingItem.totalPrice + newItem.price; + } + }, + removeItemFromCart(state, action) { + const id = action.payload; + const existingItem = state.items.find((item) => item.id === id); + state.totalQuantity--; + if (existingItem.quantity === 1) { + state.items = state.items.filter((item) => item.id !== id); + } else { + existingItem.quantity--; + } + }, + }, +}); + +export const cartActions = cartSlice.actions; + +export default cartSlice; diff --git a/code/03-using-useeffect-with-redux/src/store/index.js b/code/03-using-useeffect-with-redux/src/store/index.js new file mode 100644 index 0000000000..d663f26de2 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/store/index.js @@ -0,0 +1,10 @@ +import { configureStore } from '@reduxjs/toolkit'; + +import uiSlice from './ui-slice'; +import cartSlice from './cart-slice'; + +const store = configureStore({ + reducer: { ui: uiSlice.reducer, cart: cartSlice.reducer }, +}); + +export default store; diff --git a/code/03-using-useeffect-with-redux/src/store/ui-slice.js b/code/03-using-useeffect-with-redux/src/store/ui-slice.js new file mode 100644 index 0000000000..4fa43c1b12 --- /dev/null +++ b/code/03-using-useeffect-with-redux/src/store/ui-slice.js @@ -0,0 +1,15 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const uiSlice = createSlice({ + name: 'ui', + initialState: { cartIsVisible: false }, + reducers: { + toggle(state) { + state.cartIsVisible = !state.cartIsVisible; + } + } +}); + +export const uiActions = uiSlice.actions; + +export default uiSlice; \ No newline at end of file diff --git a/code/04-handling-http-states-feedback-with-redux/package.json b/code/04-handling-http-states-feedback-with-redux/package.json new file mode 100644 index 0000000000..2165b1f063 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/package.json @@ -0,0 +1,40 @@ +{ + "name": "react-complete-guide", + "version": "0.1.0", + "private": true, + "dependencies": { + "@reduxjs/toolkit": "^1.5.0", + "@testing-library/jest-dom": "^5.11.6", + "@testing-library/react": "^11.2.2", + "@testing-library/user-event": "^12.5.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "react-redux": "^7.2.2", + "react-scripts": "4.0.1", + "web-vitals": "^0.2.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/code/04-handling-http-states-feedback-with-redux/public/favicon.ico b/code/04-handling-http-states-feedback-with-redux/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB
    3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/code/04-handling-http-states-feedback-with-redux/public/index.html b/code/04-handling-http-states-feedback-with-redux/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/04-handling-http-states-feedback-with-redux/public/logo192.png b/code/04-handling-http-states-feedback-with-redux/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/code/04-handling-http-states-feedback-with-redux/public/manifest.json b/code/04-handling-http-states-feedback-with-redux/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/code/04-handling-http-states-feedback-with-redux/public/robots.txt b/code/04-handling-http-states-feedback-with-redux/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/04-handling-http-states-feedback-with-redux/src/App.js b/code/04-handling-http-states-feedback-with-redux/src/App.js new file mode 100644 index 0000000000..3778b223a1 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/App.js @@ -0,0 +1,81 @@ +import { Fragment, useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; + +import Cart from './components/Cart/Cart'; +import Layout from './components/Layout/Layout'; +import Products from './components/Shop/Products'; +import { uiActions } from './store/ui-slice'; +import Notification from './components/UI/Notification'; + +let isInitial = true; + +function App() { + const dispatch = useDispatch(); + const showCart = useSelector((state) => state.ui.cartIsVisible); + const cart = useSelector((state) => state.cart); + const notification = useSelector((state) => state.ui.notification); + + useEffect(() => { + const sendCartData = async () => { + dispatch( + uiActions.showNotification({ + status: 'pending', + title: 'Sending...', + message: 'Sending cart data!', + }) + ); + const response = await fetch( + 'https://react-http-6b4a6.firebaseio.com/cart.json', + { + method: 'PUT', + body: JSON.stringify(cart), + } + ); + + if (!response.ok) { + throw new Error('Sending cart data failed.'); + } + + dispatch( + uiActions.showNotification({ + status: 'success', + title: 'Success!', + message: 'Sent cart data successfully!', + }) + ); + }; + + if (isInitial) { + isInitial = false; + return; + } + + sendCartData().catch((error) => { + dispatch( + uiActions.showNotification({ + status: 'error', + title: 'Error!', + message: 'Sending cart data failed!', + }) + ); + }); + }, [cart, dispatch]); + + return ( + + {notification && ( + + )} + + {showCart && } + + + + ); +} + +export default App; diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Cart/Cart.js b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/Cart.js new file mode 100644 index 0000000000..33030d2089 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/Cart.js @@ -0,0 +1,31 @@ +import { useSelector } from 'react-redux'; + +import Card from '../UI/Card'; +import classes from './Cart.module.css'; +import CartItem from './CartItem'; + +const Cart = (props) => { + const cartItems = useSelector((state) => state.cart.items); + + return ( + +

    Your Shopping Cart

    +
      + {cartItems.map((item) => ( + + ))} +
    +
    + ); +}; + +export default Cart; diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Cart/Cart.module.css b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/Cart.module.css new file mode 100644 index 0000000000..95670ab70b --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/Cart.module.css @@ -0,0 +1,16 @@ +.cart { + max-width: 30rem; + background-color: #313131; + color: white; +} + +.cart h2 { + font-size: 1.25rem; + margin: 0.5rem 0; +} + +.cart ul { + list-style: none; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartButton.js b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartButton.js new file mode 100644 index 0000000000..4e59baac3c --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartButton.js @@ -0,0 +1,22 @@ +import { useDispatch, useSelector } from 'react-redux'; + +import { uiActions } from '../../store/ui-slice'; +import classes from './CartButton.module.css'; + +const CartButton = (props) => { + const dispatch = useDispatch(); + const cartQuantity = useSelector((state) => state.cart.totalQuantity); + + const toggleCartHandler = () => { + dispatch(uiActions.toggle()); + }; + + return ( + + ); +}; + +export default CartButton; diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartButton.module.css b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartButton.module.css new file mode 100644 index 0000000000..93445f4596 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartButton.module.css @@ -0,0 +1,21 @@ +.button { + background-color: transparent; + border-color: #1ad1b9; + color: #1ad1b9; +} + +.button:hover, +.button:active { + color: white; +} + +.button span { + margin: 0 0.5rem; +} + +.badge { + background-color: #1ad1b9; + border-radius: 30px; + padding: 0.15rem 1.25rem; + color: #1d1d1d; +} \ No newline at end of file diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartItem.js b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartItem.js new file mode 100644 index 0000000000..e1c6a8f012 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartItem.js @@ -0,0 +1,47 @@ +import { useDispatch } from 'react-redux'; + +import classes from './CartItem.module.css'; +import { cartActions } from '../../store/cart-slice'; + +const CartItem = (props) => { + const dispatch = useDispatch(); + + const { title, quantity, total, price, id } = props.item; + + const removeItemHandler = () => { + dispatch(cartActions.removeItemFromCart(id)); + }; + + const addItemHandler = () => { + dispatch( + cartActions.addItemToCart({ + id, + title, + price, + }) + ); + }; + + return ( +
  • +
    +

    {title}

    +
    + ${total.toFixed(2)}{' '} + (${price.toFixed(2)}/item) +
    +
    +
    +
    + x {quantity} +
    +
    + + +
    +
    +
  • + ); +}; + +export default CartItem; diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartItem.module.css b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartItem.module.css new file mode 100644 index 0000000000..e34100d841 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Cart/CartItem.module.css @@ -0,0 +1,58 @@ +.item { + margin: 1rem 0; + background-color: #575757; + padding: 1rem; +} + +.item h3 { + margin: 0 0 0.5rem 0; + font-size: 1.75rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.details { + display: flex; + justify-content: space-between; + align-items: center; +} + +.quantity span { + font-size: 1.5rem; + font-weight: bold; +} + +.price { + font-size: 1.5rem; + font-weight: bold; +} + +.itemprice { + font-weight: normal; + font-size: 1rem; + font-style: italic; +} + +.actions { + display: flex; + justify-content: flex-end; + margin: 0.5rem 0; +} + +.actions button { + background-color: transparent; + border: 1px solid white; + margin-left: 0.5rem; + padding: 0.15rem 1rem; + color: white; +} + +.actions button:hover, +.actions button:active { + background-color: #4b4b4b; + color: white; +} \ No newline at end of file diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Layout/Layout.js b/code/04-handling-http-states-feedback-with-redux/src/components/Layout/Layout.js new file mode 100644 index 0000000000..0ce12a011f --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Layout/Layout.js @@ -0,0 +1,13 @@ +import { Fragment } from 'react'; +import MainHeader from './MainHeader'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Layout/MainHeader.js b/code/04-handling-http-states-feedback-with-redux/src/components/Layout/MainHeader.js new file mode 100644 index 0000000000..38ea37a29b --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Layout/MainHeader.js @@ -0,0 +1,19 @@ +import CartButton from '../Cart/CartButton'; +import classes from './MainHeader.module.css'; + +const MainHeader = (props) => { + return ( +
    +

    ReduxCart

    + +
    + ); +}; + +export default MainHeader; diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Layout/MainHeader.module.css b/code/04-handling-http-states-feedback-with-redux/src/components/Layout/MainHeader.module.css new file mode 100644 index 0000000000..e41c98d247 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Layout/MainHeader.module.css @@ -0,0 +1,19 @@ +.header { + width: 100%; + height: 5rem; + padding: 0 10%; + display: flex; + align-items: center; + justify-content: space-between; + background-color: #252424; +} + +.header h1 { + color: white; +} + +.header ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Shop/ProductItem.js b/code/04-handling-http-states-feedback-with-redux/src/components/Shop/ProductItem.js new file mode 100644 index 0000000000..4a2a4b3f9f --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Shop/ProductItem.js @@ -0,0 +1,41 @@ +import { useDispatch } from 'react-redux'; + +import { cartActions } from '../../store/cart-slice'; +import Card from '../UI/Card'; +import classes from './ProductItem.module.css'; + +const ProductItem = (props) => { + const dispatch = useDispatch(); + + const { title, price, description, id } = props; + + const addToCartHandler = () => { + // and then send Http request + // fetch('firebase-url', { method: 'POST', body: JSON.stringify(newCart) }) + + dispatch( + cartActions.addItemToCart({ + id, + title, + price, + }) + ); + }; + + return ( +
  • + +
    +

    {title}

    +
    ${price.toFixed(2)}
    +
    +

    {description}

    +
    + +
    +
    +
  • + ); +}; + +export default ProductItem; diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Shop/ProductItem.module.css b/code/04-handling-http-states-feedback-with-redux/src/components/Shop/ProductItem.module.css new file mode 100644 index 0000000000..2fcdc5d8a6 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Shop/ProductItem.module.css @@ -0,0 +1,27 @@ +.item h3 { + margin: 0.5rem 0; + font-size: 1.25rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.price { + border-radius: 30px; + padding: 0.15rem 1.5rem; + background-color: #3a3a3a; + color: white; + font-size: 1.5rem; +} + +.item p { + color: #3a3a3a; +} + +.actions { + display: flex; + justify-content: flex-end; +} \ No newline at end of file diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Shop/Products.js b/code/04-handling-http-states-feedback-with-redux/src/components/Shop/Products.js new file mode 100644 index 0000000000..6b430cc262 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Shop/Products.js @@ -0,0 +1,38 @@ +import ProductItem from './ProductItem'; +import classes from './Products.module.css'; + +const DUMMY_PRODUCTS = [ + { + id: 'p1', + price: 6, + title: 'My First Book', + description: 'The first book I ever wrote', + }, + { + id: 'p2', + price: 5, + title: 'My Second Book', + description: 'The second book I ever wrote', + }, +]; + +const Products = (props) => { + return ( +
    +

    Buy your favorite products

    +
      + {DUMMY_PRODUCTS.map((product) => ( + + ))} +
    +
    + ); +}; + +export default Products; diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/Shop/Products.module.css b/code/04-handling-http-states-feedback-with-redux/src/components/Shop/Products.module.css new file mode 100644 index 0000000000..d81c97330f --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/Shop/Products.module.css @@ -0,0 +1,12 @@ +.products h2 { + color: white; + margin: 2rem auto; + text-align: center; + text-transform: uppercase; +} + +.products ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/UI/Card.js b/code/04-handling-http-states-feedback-with-redux/src/components/UI/Card.js new file mode 100644 index 0000000000..849202f21c --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/UI/Card.js @@ -0,0 +1,13 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return ( +
    + {props.children} +
    + ); +}; + +export default Card; diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/UI/Card.module.css b/code/04-handling-http-states-feedback-with-redux/src/components/UI/Card.module.css new file mode 100644 index 0000000000..ac9c6709f4 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/UI/Card.module.css @@ -0,0 +1,8 @@ +.card { + margin: 1rem auto; + border-radius: 6px; + background-color: white; + padding: 1rem; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/UI/Notification.js b/code/04-handling-http-states-feedback-with-redux/src/components/UI/Notification.js new file mode 100644 index 0000000000..982017fa74 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/UI/Notification.js @@ -0,0 +1,23 @@ +import classes from './Notification.module.css'; + +const Notification = (props) => { + let specialClasses = ''; + + if (props.status === 'error') { + specialClasses = classes.error; + } + if (props.status === 'success') { + specialClasses = classes.success; + } + + const cssClasses = `${classes.notification} ${specialClasses}`; + + return ( +
    +

    {props.title}

    +

    {props.message}

    +
    + ); +}; + +export default Notification; diff --git a/code/04-handling-http-states-feedback-with-redux/src/components/UI/Notification.module.css b/code/04-handling-http-states-feedback-with-redux/src/components/UI/Notification.module.css new file mode 100644 index 0000000000..5decd98ea5 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/components/UI/Notification.module.css @@ -0,0 +1,24 @@ +.notification { + width: 100%; + height: 3rem; + background-color: #1a8ed1; + display: flex; + justify-content: space-between; + padding: 0.5rem 10%; + align-items: center; + color: white; +} + +.notification h2, +.notification p { + font-size: 1rem; + margin: 0; +} + +.error { + background-color: #690000; +} + +.success { + background-color: #1ad1b9; +} \ No newline at end of file diff --git a/code/04-handling-http-states-feedback-with-redux/src/index.css b/code/04-handling-http-states-feedback-with-redux/src/index.css new file mode 100644 index 0000000000..3431f5f884 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/index.css @@ -0,0 +1,31 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap'); + +* { + box-sizing: border-box; +} + +html { + font-family: 'Noto Sans JP', sans-serif; +} + +body { + margin: 0; + background-color: #3f3f3f; +} + +button { + font: inherit; + cursor: pointer; + padding: 0.5rem 1.5rem; + border-radius: 6px; + background-color: transparent; + color: #1a8ed1; + border: 1px solid #1a8ed1; +} + +button:hover, +button:active { + background-color: #1ac5d1; + border-color: #1ac5d1; + color: white; +} \ No newline at end of file diff --git a/code/04-handling-http-states-feedback-with-redux/src/index.js b/code/04-handling-http-states-feedback-with-redux/src/index.js new file mode 100644 index 0000000000..ac1938ea3e --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/index.js @@ -0,0 +1,13 @@ +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; + +import store from './store/index'; +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/04-handling-http-states-feedback-with-redux/src/store/cart-slice.js b/code/04-handling-http-states-feedback-with-redux/src/store/cart-slice.js new file mode 100644 index 0000000000..e629fc66ce --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/store/cart-slice.js @@ -0,0 +1,46 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const cartSlice = createSlice({ + name: 'cart', + initialState: { + items: [], + totalQuantity: 0, + }, + reducers: { + replaceCart(state, action) { + state.totalQuantity = action.payload.totalQuantity; + state.items = action.payload.items; + }, + addItemToCart(state, action) { + const newItem = action.payload; + const existingItem = state.items.find((item) => item.id === newItem.id); + state.totalQuantity++; + if (!existingItem) { + state.items.push({ + id: newItem.id, + price: newItem.price, + quantity: 1, + totalPrice: newItem.price, + name: newItem.title, + }); + } else { + existingItem.quantity++; + existingItem.totalPrice = existingItem.totalPrice + newItem.price; + } + }, + removeItemFromCart(state, action) { + const id = action.payload; + const existingItem = state.items.find((item) => item.id === id); + state.totalQuantity--; + if (existingItem.quantity === 1) { + state.items = state.items.filter((item) => item.id !== id); + } else { + existingItem.quantity--; + } + }, + }, +}); + +export const cartActions = cartSlice.actions; + +export default cartSlice; diff --git a/code/04-handling-http-states-feedback-with-redux/src/store/index.js b/code/04-handling-http-states-feedback-with-redux/src/store/index.js new file mode 100644 index 0000000000..d663f26de2 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/store/index.js @@ -0,0 +1,10 @@ +import { configureStore } from '@reduxjs/toolkit'; + +import uiSlice from './ui-slice'; +import cartSlice from './cart-slice'; + +const store = configureStore({ + reducer: { ui: uiSlice.reducer, cart: cartSlice.reducer }, +}); + +export default store; diff --git a/code/04-handling-http-states-feedback-with-redux/src/store/ui-slice.js b/code/04-handling-http-states-feedback-with-redux/src/store/ui-slice.js new file mode 100644 index 0000000000..a25529a6f0 --- /dev/null +++ b/code/04-handling-http-states-feedback-with-redux/src/store/ui-slice.js @@ -0,0 +1,22 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const uiSlice = createSlice({ + name: 'ui', + initialState: { cartIsVisible: false, notification: null }, + reducers: { + toggle(state) { + state.cartIsVisible = !state.cartIsVisible; + }, + showNotification(state, action) { + state.notification = { + status: action.payload.status, + title: action.payload.title, + message: action.payload.message, + }; + }, + }, +}); + +export const uiActions = uiSlice.actions; + +export default uiSlice; diff --git a/code/05-using-an-action-creator-thunk/package.json b/code/05-using-an-action-creator-thunk/package.json new file mode 100644 index 0000000000..2165b1f063 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/package.json @@ -0,0 +1,40 @@ +{ + "name": "react-complete-guide", + "version": "0.1.0", + "private": true, + "dependencies": { + "@reduxjs/toolkit": "^1.5.0", + "@testing-library/jest-dom": "^5.11.6", + "@testing-library/react": "^11.2.2", + "@testing-library/user-event": "^12.5.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "react-redux": "^7.2.2", + "react-scripts": "4.0.1", + "web-vitals": "^0.2.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/code/05-using-an-action-creator-thunk/public/favicon.ico b/code/05-using-an-action-creator-thunk/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB
    3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/code/05-using-an-action-creator-thunk/public/index.html b/code/05-using-an-action-creator-thunk/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/05-using-an-action-creator-thunk/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/05-using-an-action-creator-thunk/public/logo192.png b/code/05-using-an-action-creator-thunk/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/code/05-using-an-action-creator-thunk/public/manifest.json b/code/05-using-an-action-creator-thunk/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/05-using-an-action-creator-thunk/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/code/05-using-an-action-creator-thunk/public/robots.txt b/code/05-using-an-action-creator-thunk/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/05-using-an-action-creator-thunk/src/App.js b/code/05-using-an-action-creator-thunk/src/App.js new file mode 100644 index 0000000000..2464d31f8a --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/App.js @@ -0,0 +1,44 @@ +import { Fragment, useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; + +import Cart from './components/Cart/Cart'; +import Layout from './components/Layout/Layout'; +import Products from './components/Shop/Products'; +import Notification from './components/UI/Notification'; +import { sendCartData } from './store/cart-slice'; + +let isInitial = true; + +function App() { + const dispatch = useDispatch(); + const showCart = useSelector((state) => state.ui.cartIsVisible); + const cart = useSelector((state) => state.cart); + const notification = useSelector((state) => state.ui.notification); + + useEffect(() => { + if (isInitial) { + isInitial = false; + return; + } + + dispatch(sendCartData(cart)); + }, [cart, dispatch]); + + return ( + + {notification && ( + + )} + + {showCart && } + + + + ); +} + +export default App; diff --git a/code/05-using-an-action-creator-thunk/src/components/Cart/Cart.js b/code/05-using-an-action-creator-thunk/src/components/Cart/Cart.js new file mode 100644 index 0000000000..33030d2089 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Cart/Cart.js @@ -0,0 +1,31 @@ +import { useSelector } from 'react-redux'; + +import Card from '../UI/Card'; +import classes from './Cart.module.css'; +import CartItem from './CartItem'; + +const Cart = (props) => { + const cartItems = useSelector((state) => state.cart.items); + + return ( + +

    Your Shopping Cart

    +
      + {cartItems.map((item) => ( + + ))} +
    +
    + ); +}; + +export default Cart; diff --git a/code/05-using-an-action-creator-thunk/src/components/Cart/Cart.module.css b/code/05-using-an-action-creator-thunk/src/components/Cart/Cart.module.css new file mode 100644 index 0000000000..95670ab70b --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Cart/Cart.module.css @@ -0,0 +1,16 @@ +.cart { + max-width: 30rem; + background-color: #313131; + color: white; +} + +.cart h2 { + font-size: 1.25rem; + margin: 0.5rem 0; +} + +.cart ul { + list-style: none; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/code/05-using-an-action-creator-thunk/src/components/Cart/CartButton.js b/code/05-using-an-action-creator-thunk/src/components/Cart/CartButton.js new file mode 100644 index 0000000000..4e59baac3c --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Cart/CartButton.js @@ -0,0 +1,22 @@ +import { useDispatch, useSelector } from 'react-redux'; + +import { uiActions } from '../../store/ui-slice'; +import classes from './CartButton.module.css'; + +const CartButton = (props) => { + const dispatch = useDispatch(); + const cartQuantity = useSelector((state) => state.cart.totalQuantity); + + const toggleCartHandler = () => { + dispatch(uiActions.toggle()); + }; + + return ( + + ); +}; + +export default CartButton; diff --git a/code/05-using-an-action-creator-thunk/src/components/Cart/CartButton.module.css b/code/05-using-an-action-creator-thunk/src/components/Cart/CartButton.module.css new file mode 100644 index 0000000000..93445f4596 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Cart/CartButton.module.css @@ -0,0 +1,21 @@ +.button { + background-color: transparent; + border-color: #1ad1b9; + color: #1ad1b9; +} + +.button:hover, +.button:active { + color: white; +} + +.button span { + margin: 0 0.5rem; +} + +.badge { + background-color: #1ad1b9; + border-radius: 30px; + padding: 0.15rem 1.25rem; + color: #1d1d1d; +} \ No newline at end of file diff --git a/code/05-using-an-action-creator-thunk/src/components/Cart/CartItem.js b/code/05-using-an-action-creator-thunk/src/components/Cart/CartItem.js new file mode 100644 index 0000000000..e1c6a8f012 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Cart/CartItem.js @@ -0,0 +1,47 @@ +import { useDispatch } from 'react-redux'; + +import classes from './CartItem.module.css'; +import { cartActions } from '../../store/cart-slice'; + +const CartItem = (props) => { + const dispatch = useDispatch(); + + const { title, quantity, total, price, id } = props.item; + + const removeItemHandler = () => { + dispatch(cartActions.removeItemFromCart(id)); + }; + + const addItemHandler = () => { + dispatch( + cartActions.addItemToCart({ + id, + title, + price, + }) + ); + }; + + return ( +
  • +
    +

    {title}

    +
    + ${total.toFixed(2)}{' '} + (${price.toFixed(2)}/item) +
    +
    +
    +
    + x {quantity} +
    +
    + + +
    +
    +
  • + ); +}; + +export default CartItem; diff --git a/code/05-using-an-action-creator-thunk/src/components/Cart/CartItem.module.css b/code/05-using-an-action-creator-thunk/src/components/Cart/CartItem.module.css new file mode 100644 index 0000000000..e34100d841 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Cart/CartItem.module.css @@ -0,0 +1,58 @@ +.item { + margin: 1rem 0; + background-color: #575757; + padding: 1rem; +} + +.item h3 { + margin: 0 0 0.5rem 0; + font-size: 1.75rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.details { + display: flex; + justify-content: space-between; + align-items: center; +} + +.quantity span { + font-size: 1.5rem; + font-weight: bold; +} + +.price { + font-size: 1.5rem; + font-weight: bold; +} + +.itemprice { + font-weight: normal; + font-size: 1rem; + font-style: italic; +} + +.actions { + display: flex; + justify-content: flex-end; + margin: 0.5rem 0; +} + +.actions button { + background-color: transparent; + border: 1px solid white; + margin-left: 0.5rem; + padding: 0.15rem 1rem; + color: white; +} + +.actions button:hover, +.actions button:active { + background-color: #4b4b4b; + color: white; +} \ No newline at end of file diff --git a/code/05-using-an-action-creator-thunk/src/components/Layout/Layout.js b/code/05-using-an-action-creator-thunk/src/components/Layout/Layout.js new file mode 100644 index 0000000000..0ce12a011f --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Layout/Layout.js @@ -0,0 +1,13 @@ +import { Fragment } from 'react'; +import MainHeader from './MainHeader'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/05-using-an-action-creator-thunk/src/components/Layout/MainHeader.js b/code/05-using-an-action-creator-thunk/src/components/Layout/MainHeader.js new file mode 100644 index 0000000000..38ea37a29b --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Layout/MainHeader.js @@ -0,0 +1,19 @@ +import CartButton from '../Cart/CartButton'; +import classes from './MainHeader.module.css'; + +const MainHeader = (props) => { + return ( +
    +

    ReduxCart

    + +
    + ); +}; + +export default MainHeader; diff --git a/code/05-using-an-action-creator-thunk/src/components/Layout/MainHeader.module.css b/code/05-using-an-action-creator-thunk/src/components/Layout/MainHeader.module.css new file mode 100644 index 0000000000..e41c98d247 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Layout/MainHeader.module.css @@ -0,0 +1,19 @@ +.header { + width: 100%; + height: 5rem; + padding: 0 10%; + display: flex; + align-items: center; + justify-content: space-between; + background-color: #252424; +} + +.header h1 { + color: white; +} + +.header ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/05-using-an-action-creator-thunk/src/components/Shop/ProductItem.js b/code/05-using-an-action-creator-thunk/src/components/Shop/ProductItem.js new file mode 100644 index 0000000000..4a2a4b3f9f --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Shop/ProductItem.js @@ -0,0 +1,41 @@ +import { useDispatch } from 'react-redux'; + +import { cartActions } from '../../store/cart-slice'; +import Card from '../UI/Card'; +import classes from './ProductItem.module.css'; + +const ProductItem = (props) => { + const dispatch = useDispatch(); + + const { title, price, description, id } = props; + + const addToCartHandler = () => { + // and then send Http request + // fetch('firebase-url', { method: 'POST', body: JSON.stringify(newCart) }) + + dispatch( + cartActions.addItemToCart({ + id, + title, + price, + }) + ); + }; + + return ( +
  • + +
    +

    {title}

    +
    ${price.toFixed(2)}
    +
    +

    {description}

    +
    + +
    +
    +
  • + ); +}; + +export default ProductItem; diff --git a/code/05-using-an-action-creator-thunk/src/components/Shop/ProductItem.module.css b/code/05-using-an-action-creator-thunk/src/components/Shop/ProductItem.module.css new file mode 100644 index 0000000000..2fcdc5d8a6 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Shop/ProductItem.module.css @@ -0,0 +1,27 @@ +.item h3 { + margin: 0.5rem 0; + font-size: 1.25rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.price { + border-radius: 30px; + padding: 0.15rem 1.5rem; + background-color: #3a3a3a; + color: white; + font-size: 1.5rem; +} + +.item p { + color: #3a3a3a; +} + +.actions { + display: flex; + justify-content: flex-end; +} \ No newline at end of file diff --git a/code/05-using-an-action-creator-thunk/src/components/Shop/Products.js b/code/05-using-an-action-creator-thunk/src/components/Shop/Products.js new file mode 100644 index 0000000000..6b430cc262 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Shop/Products.js @@ -0,0 +1,38 @@ +import ProductItem from './ProductItem'; +import classes from './Products.module.css'; + +const DUMMY_PRODUCTS = [ + { + id: 'p1', + price: 6, + title: 'My First Book', + description: 'The first book I ever wrote', + }, + { + id: 'p2', + price: 5, + title: 'My Second Book', + description: 'The second book I ever wrote', + }, +]; + +const Products = (props) => { + return ( +
    +

    Buy your favorite products

    +
      + {DUMMY_PRODUCTS.map((product) => ( + + ))} +
    +
    + ); +}; + +export default Products; diff --git a/code/05-using-an-action-creator-thunk/src/components/Shop/Products.module.css b/code/05-using-an-action-creator-thunk/src/components/Shop/Products.module.css new file mode 100644 index 0000000000..d81c97330f --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/Shop/Products.module.css @@ -0,0 +1,12 @@ +.products h2 { + color: white; + margin: 2rem auto; + text-align: center; + text-transform: uppercase; +} + +.products ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/05-using-an-action-creator-thunk/src/components/UI/Card.js b/code/05-using-an-action-creator-thunk/src/components/UI/Card.js new file mode 100644 index 0000000000..849202f21c --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/UI/Card.js @@ -0,0 +1,13 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return ( +
    + {props.children} +
    + ); +}; + +export default Card; diff --git a/code/05-using-an-action-creator-thunk/src/components/UI/Card.module.css b/code/05-using-an-action-creator-thunk/src/components/UI/Card.module.css new file mode 100644 index 0000000000..ac9c6709f4 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/UI/Card.module.css @@ -0,0 +1,8 @@ +.card { + margin: 1rem auto; + border-radius: 6px; + background-color: white; + padding: 1rem; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/05-using-an-action-creator-thunk/src/components/UI/Notification.js b/code/05-using-an-action-creator-thunk/src/components/UI/Notification.js new file mode 100644 index 0000000000..982017fa74 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/UI/Notification.js @@ -0,0 +1,23 @@ +import classes from './Notification.module.css'; + +const Notification = (props) => { + let specialClasses = ''; + + if (props.status === 'error') { + specialClasses = classes.error; + } + if (props.status === 'success') { + specialClasses = classes.success; + } + + const cssClasses = `${classes.notification} ${specialClasses}`; + + return ( +
    +

    {props.title}

    +

    {props.message}

    +
    + ); +}; + +export default Notification; diff --git a/code/05-using-an-action-creator-thunk/src/components/UI/Notification.module.css b/code/05-using-an-action-creator-thunk/src/components/UI/Notification.module.css new file mode 100644 index 0000000000..5decd98ea5 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/components/UI/Notification.module.css @@ -0,0 +1,24 @@ +.notification { + width: 100%; + height: 3rem; + background-color: #1a8ed1; + display: flex; + justify-content: space-between; + padding: 0.5rem 10%; + align-items: center; + color: white; +} + +.notification h2, +.notification p { + font-size: 1rem; + margin: 0; +} + +.error { + background-color: #690000; +} + +.success { + background-color: #1ad1b9; +} \ No newline at end of file diff --git a/code/05-using-an-action-creator-thunk/src/index.css b/code/05-using-an-action-creator-thunk/src/index.css new file mode 100644 index 0000000000..3431f5f884 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/index.css @@ -0,0 +1,31 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap'); + +* { + box-sizing: border-box; +} + +html { + font-family: 'Noto Sans JP', sans-serif; +} + +body { + margin: 0; + background-color: #3f3f3f; +} + +button { + font: inherit; + cursor: pointer; + padding: 0.5rem 1.5rem; + border-radius: 6px; + background-color: transparent; + color: #1a8ed1; + border: 1px solid #1a8ed1; +} + +button:hover, +button:active { + background-color: #1ac5d1; + border-color: #1ac5d1; + color: white; +} \ No newline at end of file diff --git a/code/05-using-an-action-creator-thunk/src/index.js b/code/05-using-an-action-creator-thunk/src/index.js new file mode 100644 index 0000000000..ac1938ea3e --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/index.js @@ -0,0 +1,13 @@ +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; + +import store from './store/index'; +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/05-using-an-action-creator-thunk/src/store/cart-slice.js b/code/05-using-an-action-creator-thunk/src/store/cart-slice.js new file mode 100644 index 0000000000..c096bb67d7 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/store/cart-slice.js @@ -0,0 +1,94 @@ +import { createSlice } from '@reduxjs/toolkit'; + +import { uiActions } from './ui-slice'; + +const cartSlice = createSlice({ + name: 'cart', + initialState: { + items: [], + totalQuantity: 0, + }, + reducers: { + replaceCart(state, action) { + state.totalQuantity = action.payload.totalQuantity; + state.items = action.payload.items; + }, + addItemToCart(state, action) { + const newItem = action.payload; + const existingItem = state.items.find((item) => item.id === newItem.id); + state.totalQuantity++; + if (!existingItem) { + state.items.push({ + id: newItem.id, + price: newItem.price, + quantity: 1, + totalPrice: newItem.price, + name: newItem.title, + }); + } else { + existingItem.quantity++; + existingItem.totalPrice = existingItem.totalPrice + newItem.price; + } + }, + removeItemFromCart(state, action) { + const id = action.payload; + const existingItem = state.items.find((item) => item.id === id); + state.totalQuantity--; + if (existingItem.quantity === 1) { + state.items = state.items.filter((item) => item.id !== id); + } else { + existingItem.quantity--; + } + }, + }, +}); + +export const sendCartData = (cart) => { + return async (dispatch) => { + dispatch( + uiActions.showNotification({ + status: 'pending', + title: 'Sending...', + message: 'Sending cart data!', + }) + ); + + const sendRequest = async () => { + const response = await fetch( + 'https://react-http-6b4a6.firebaseio.com/cart.json', + { + method: 'PUT', + body: JSON.stringify(cart), + } + ); + + if (!response.ok) { + throw new Error('Sending cart data failed.'); + } + }; + + try { + await sendRequest(); + + dispatch( + uiActions.showNotification({ + status: 'success', + title: 'Success!', + message: 'Sent cart data successfully!', + }) + ); + } catch (error) { + dispatch( + uiActions.showNotification({ + status: 'error', + title: 'Error!', + message: 'Sending cart data failed!', + }) + ); + } + }; +}; + +export const cartActions = cartSlice.actions; + +export default cartSlice; diff --git a/code/05-using-an-action-creator-thunk/src/store/index.js b/code/05-using-an-action-creator-thunk/src/store/index.js new file mode 100644 index 0000000000..d663f26de2 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/store/index.js @@ -0,0 +1,10 @@ +import { configureStore } from '@reduxjs/toolkit'; + +import uiSlice from './ui-slice'; +import cartSlice from './cart-slice'; + +const store = configureStore({ + reducer: { ui: uiSlice.reducer, cart: cartSlice.reducer }, +}); + +export default store; diff --git a/code/05-using-an-action-creator-thunk/src/store/ui-slice.js b/code/05-using-an-action-creator-thunk/src/store/ui-slice.js new file mode 100644 index 0000000000..a25529a6f0 --- /dev/null +++ b/code/05-using-an-action-creator-thunk/src/store/ui-slice.js @@ -0,0 +1,22 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const uiSlice = createSlice({ + name: 'ui', + initialState: { cartIsVisible: false, notification: null }, + reducers: { + toggle(state) { + state.cartIsVisible = !state.cartIsVisible; + }, + showNotification(state, action) { + state.notification = { + status: action.payload.status, + title: action.payload.title, + message: action.payload.message, + }; + }, + }, +}); + +export const uiActions = uiSlice.actions; + +export default uiSlice; diff --git a/code/06-finished/package.json b/code/06-finished/package.json new file mode 100644 index 0000000000..2165b1f063 --- /dev/null +++ b/code/06-finished/package.json @@ -0,0 +1,40 @@ +{ + "name": "react-complete-guide", + "version": "0.1.0", + "private": true, + "dependencies": { + "@reduxjs/toolkit": "^1.5.0", + "@testing-library/jest-dom": "^5.11.6", + "@testing-library/react": "^11.2.2", + "@testing-library/user-event": "^12.5.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "react-redux": "^7.2.2", + "react-scripts": "4.0.1", + "web-vitals": "^0.2.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/code/06-finished/public/favicon.ico b/code/06-finished/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB
    3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= z!sii`1giyH7EKL_+Wi0ab<)&E_0KD!3Rp2^HNB*K2@PHCs4PWSA32*-^7d{9nH2_E zmC{C*N*)(vEF1_aMamw2A{ZH5aIDqiabnFdJ|y0%aS|64E$`s2ccV~3lR!u<){eS` z#^Mx6o(iP1Ix%4dv`t@!&Za-K@mTm#vadc{0aWDV*_%EiGK7qMC_(`exc>-$Gb9~W!w_^{*pYRm~G zBN{nA;cm^w$VWg1O^^<6vY`1XCD|s_zv*g*5&V#wv&s#h$xlUilPe4U@I&UXZbL z0)%9Uj&@yd03n;!7do+bfixH^FeZ-Ema}s;DQX2gY+7g0s(9;`8GyvPY1*vxiF&|w z>!vA~GA<~JUqH}d;DfBSi^IT*#lrzXl$fNpq0_T1tA+`A$1?(gLb?e#0>UELvljtQ zK+*74m0jn&)5yk8mLBv;=@}c{t0ztT<v;Avck$S6D`Z)^c0(jiwKhQsn|LDRY&w(Fmi91I7H6S;b0XM{e zXp0~(T@k_r-!jkLwd1_Vre^v$G4|kh4}=Gi?$AaJ)3I+^m|Zyj#*?Kp@w(lQdJZf4 z#|IJW5z+S^e9@(6hW6N~{pj8|NO*>1)E=%?nNUAkmv~OY&ZV;m-%?pQ_11)hAr0oAwILrlsGawpxx4D43J&K=n+p3WLnlDsQ$b(9+4 z?mO^hmV^F8MV{4Lx>(Q=aHhQ1){0d*(e&s%G=i5rq3;t{JC zmgbn5Nkl)t@fPH$v;af26lyhH!k+#}_&aBK4baYPbZy$5aFx4}ka&qxl z$=Rh$W;U)>-=S-0=?7FH9dUAd2(q#4TCAHky!$^~;Dz^j|8_wuKc*YzfdAht@Q&ror?91Dm!N03=4=O!a)I*0q~p0g$Fm$pmr$ zb;wD;STDIi$@M%y1>p&_>%?UP($15gou_ue1u0!4(%81;qcIW8NyxFEvXpiJ|H4wz z*mFT(qVx1FKufG11hByuX%lPk4t#WZ{>8ka2efjY`~;AL6vWyQKpJun2nRiZYDij$ zP>4jQXPaP$UC$yIVgGa)jDV;F0l^n(V=HMRB5)20V7&r$jmk{UUIe zVjKroK}JAbD>B`2cwNQ&GDLx8{pg`7hbA~grk|W6LgiZ`8y`{Iq0i>t!3p2}MS6S+ zO_ruKyAElt)rdS>CtF7j{&6rP-#c=7evGMt7B6`7HG|-(WL`bDUAjyn+k$mx$CH;q2Dz4x;cPP$hW=`pFfLO)!jaCL@V2+F)So3}vg|%O*^T1j>C2lx zsURO-zIJC$^$g2byVbRIo^w>UxK}74^TqUiRR#7s_X$e)$6iYG1(PcW7un-va-S&u zHk9-6Zn&>T==A)lM^D~bk{&rFzCi35>UR!ZjQkdSiNX*-;l4z9j*7|q`TBl~Au`5& z+c)*8?#-tgUR$Zd%Q3bs96w6k7q@#tUn`5rj+r@_sAVVLqco|6O{ILX&U-&-cbVa3 zY?ngHR@%l{;`ri%H*0EhBWrGjv!LE4db?HEWb5mu*t@{kv|XwK8?npOshmzf=vZA@ zVSN9sL~!sn?r(AK)Q7Jk2(|M67Uy3I{eRy z_l&Y@A>;vjkWN5I2xvFFTLX0i+`{qz7C_@bo`ZUzDugfq4+>a3?1v%)O+YTd6@Ul7 zAfLfm=nhZ`)P~&v90$&UcF+yXm9sq!qCx3^9gzIcO|Y(js^Fj)Rvq>nQAHI92ap=P z10A4@prk+AGWCb`2)dQYFuR$|H6iDE8p}9a?#nV2}LBCoCf(Xi2@szia7#gY>b|l!-U`c}@ zLdhvQjc!BdLJvYvzzzngnw51yRYCqh4}$oRCy-z|v3Hc*d|?^Wj=l~18*E~*cR_kU z{XsxM1i{V*4GujHQ3DBpl2w4FgFR48Nma@HPgnyKoIEY-MqmMeY=I<%oG~l!f<+FN z1ZY^;10j4M4#HYXP zw5eJpA_y(>uLQ~OucgxDLuf}fVs272FaMxhn4xnDGIyLXnw>Xsd^J8XhcWIwIoQ9} z%FoSJTAGW(SRGwJwb=@pY7r$uQRK3Zd~XbxU)ts!4XsJrCycrWSI?e!IqwqIR8+Jh zlRjZ`UO1I!BtJR_2~7AbkbSm%XQqxEPkz6BTGWx8e}nQ=w7bZ|eVP4?*Tb!$(R)iC z9)&%bS*u(lXqzitAN)Oo=&Ytn>%Hzjc<5liuPi>zC_nw;Z0AE3Y$Jao_Q90R-gl~5 z_xAb2J%eArrC1CN4G$}-zVvCqF1;H;abAu6G*+PDHSYFx@Tdbfox*uEd3}BUyYY-l zTfEsOqsi#f9^FoLO;ChK<554qkri&Av~SIM*{fEYRE?vH7pTAOmu2pz3X?Wn*!ROX ztd54huAk&mFBemMooL33RV-*1f0Q3_(7hl$<#*|WF9P!;r;4_+X~k~uKEqdzZ$5Al zV63XN@)j$FN#cCD;ek1R#l zv%pGrhB~KWgoCj%GT?%{@@o(AJGt*PG#l3i>lhmb_twKH^EYvacVY-6bsCl5*^~L0 zonm@lk2UvvTKr2RS%}T>^~EYqdL1q4nD%0n&Xqr^cK^`J5W;lRRB^R-O8b&HENO||mo0xaD+S=I8RTlIfVgqN@SXDr2&-)we--K7w= zJVU8?Z+7k9dy;s;^gDkQa`0nz6N{T?(A&Iz)2!DEecLyRa&FI!id#5Z7B*O2=PsR0 zEvc|8{NS^)!d)MDX(97Xw}m&kEO@5jqRaDZ!+%`wYOI<23q|&js`&o4xvjP7D_xv@ z5hEwpsp{HezI9!~6O{~)lLR@oF7?J7i>1|5a~UuoN=q&6N}EJPV_GD`&M*v8Y`^2j zKII*d_@Fi$+i*YEW+Hbzn{iQk~yP z>7N{S4)r*!NwQ`(qcN#8SRQsNK6>{)X12nbF`*7#ecO7I)Q$uZsV+xS4E7aUn+U(K baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/code/06-finished/public/index.html b/code/06-finished/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/06-finished/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/06-finished/public/logo192.png b/code/06-finished/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/code/06-finished/public/manifest.json b/code/06-finished/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/06-finished/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/code/06-finished/public/robots.txt b/code/06-finished/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/06-finished/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/06-finished/src/App.js b/code/06-finished/src/App.js new file mode 100644 index 0000000000..b20071be84 --- /dev/null +++ b/code/06-finished/src/App.js @@ -0,0 +1,50 @@ +import { Fragment, useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; + +import Cart from './components/Cart/Cart'; +import Layout from './components/Layout/Layout'; +import Products from './components/Shop/Products'; +import Notification from './components/UI/Notification'; +import { sendCartData, fetchCartData } from './store/cart-actions'; + +let isInitial = true; + +function App() { + const dispatch = useDispatch(); + const showCart = useSelector((state) => state.ui.cartIsVisible); + const cart = useSelector((state) => state.cart); + const notification = useSelector((state) => state.ui.notification); + + useEffect(() => { + dispatch(fetchCartData()); + }, [dispatch]); + + useEffect(() => { + if (isInitial) { + isInitial = false; + return; + } + + if (cart.changed) { + dispatch(sendCartData(cart)); + } + }, [cart, dispatch]); + + return ( + + {notification && ( + + )} + + {showCart && } + + + + ); +} + +export default App; diff --git a/code/06-finished/src/components/Cart/Cart.js b/code/06-finished/src/components/Cart/Cart.js new file mode 100644 index 0000000000..33030d2089 --- /dev/null +++ b/code/06-finished/src/components/Cart/Cart.js @@ -0,0 +1,31 @@ +import { useSelector } from 'react-redux'; + +import Card from '../UI/Card'; +import classes from './Cart.module.css'; +import CartItem from './CartItem'; + +const Cart = (props) => { + const cartItems = useSelector((state) => state.cart.items); + + return ( + +

    Your Shopping Cart

    +
      + {cartItems.map((item) => ( + + ))} +
    +
    + ); +}; + +export default Cart; diff --git a/code/06-finished/src/components/Cart/Cart.module.css b/code/06-finished/src/components/Cart/Cart.module.css new file mode 100644 index 0000000000..95670ab70b --- /dev/null +++ b/code/06-finished/src/components/Cart/Cart.module.css @@ -0,0 +1,16 @@ +.cart { + max-width: 30rem; + background-color: #313131; + color: white; +} + +.cart h2 { + font-size: 1.25rem; + margin: 0.5rem 0; +} + +.cart ul { + list-style: none; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/code/06-finished/src/components/Cart/CartButton.js b/code/06-finished/src/components/Cart/CartButton.js new file mode 100644 index 0000000000..4e59baac3c --- /dev/null +++ b/code/06-finished/src/components/Cart/CartButton.js @@ -0,0 +1,22 @@ +import { useDispatch, useSelector } from 'react-redux'; + +import { uiActions } from '../../store/ui-slice'; +import classes from './CartButton.module.css'; + +const CartButton = (props) => { + const dispatch = useDispatch(); + const cartQuantity = useSelector((state) => state.cart.totalQuantity); + + const toggleCartHandler = () => { + dispatch(uiActions.toggle()); + }; + + return ( + + ); +}; + +export default CartButton; diff --git a/code/06-finished/src/components/Cart/CartButton.module.css b/code/06-finished/src/components/Cart/CartButton.module.css new file mode 100644 index 0000000000..93445f4596 --- /dev/null +++ b/code/06-finished/src/components/Cart/CartButton.module.css @@ -0,0 +1,21 @@ +.button { + background-color: transparent; + border-color: #1ad1b9; + color: #1ad1b9; +} + +.button:hover, +.button:active { + color: white; +} + +.button span { + margin: 0 0.5rem; +} + +.badge { + background-color: #1ad1b9; + border-radius: 30px; + padding: 0.15rem 1.25rem; + color: #1d1d1d; +} \ No newline at end of file diff --git a/code/06-finished/src/components/Cart/CartItem.js b/code/06-finished/src/components/Cart/CartItem.js new file mode 100644 index 0000000000..e1c6a8f012 --- /dev/null +++ b/code/06-finished/src/components/Cart/CartItem.js @@ -0,0 +1,47 @@ +import { useDispatch } from 'react-redux'; + +import classes from './CartItem.module.css'; +import { cartActions } from '../../store/cart-slice'; + +const CartItem = (props) => { + const dispatch = useDispatch(); + + const { title, quantity, total, price, id } = props.item; + + const removeItemHandler = () => { + dispatch(cartActions.removeItemFromCart(id)); + }; + + const addItemHandler = () => { + dispatch( + cartActions.addItemToCart({ + id, + title, + price, + }) + ); + }; + + return ( +
  • +
    +

    {title}

    +
    + ${total.toFixed(2)}{' '} + (${price.toFixed(2)}/item) +
    +
    +
    +
    + x {quantity} +
    +
    + + +
    +
    +
  • + ); +}; + +export default CartItem; diff --git a/code/06-finished/src/components/Cart/CartItem.module.css b/code/06-finished/src/components/Cart/CartItem.module.css new file mode 100644 index 0000000000..e34100d841 --- /dev/null +++ b/code/06-finished/src/components/Cart/CartItem.module.css @@ -0,0 +1,58 @@ +.item { + margin: 1rem 0; + background-color: #575757; + padding: 1rem; +} + +.item h3 { + margin: 0 0 0.5rem 0; + font-size: 1.75rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.details { + display: flex; + justify-content: space-between; + align-items: center; +} + +.quantity span { + font-size: 1.5rem; + font-weight: bold; +} + +.price { + font-size: 1.5rem; + font-weight: bold; +} + +.itemprice { + font-weight: normal; + font-size: 1rem; + font-style: italic; +} + +.actions { + display: flex; + justify-content: flex-end; + margin: 0.5rem 0; +} + +.actions button { + background-color: transparent; + border: 1px solid white; + margin-left: 0.5rem; + padding: 0.15rem 1rem; + color: white; +} + +.actions button:hover, +.actions button:active { + background-color: #4b4b4b; + color: white; +} \ No newline at end of file diff --git a/code/06-finished/src/components/Layout/Layout.js b/code/06-finished/src/components/Layout/Layout.js new file mode 100644 index 0000000000..0ce12a011f --- /dev/null +++ b/code/06-finished/src/components/Layout/Layout.js @@ -0,0 +1,13 @@ +import { Fragment } from 'react'; +import MainHeader from './MainHeader'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/06-finished/src/components/Layout/MainHeader.js b/code/06-finished/src/components/Layout/MainHeader.js new file mode 100644 index 0000000000..38ea37a29b --- /dev/null +++ b/code/06-finished/src/components/Layout/MainHeader.js @@ -0,0 +1,19 @@ +import CartButton from '../Cart/CartButton'; +import classes from './MainHeader.module.css'; + +const MainHeader = (props) => { + return ( +
    +

    ReduxCart

    + +
    + ); +}; + +export default MainHeader; diff --git a/code/06-finished/src/components/Layout/MainHeader.module.css b/code/06-finished/src/components/Layout/MainHeader.module.css new file mode 100644 index 0000000000..e41c98d247 --- /dev/null +++ b/code/06-finished/src/components/Layout/MainHeader.module.css @@ -0,0 +1,19 @@ +.header { + width: 100%; + height: 5rem; + padding: 0 10%; + display: flex; + align-items: center; + justify-content: space-between; + background-color: #252424; +} + +.header h1 { + color: white; +} + +.header ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/06-finished/src/components/Shop/ProductItem.js b/code/06-finished/src/components/Shop/ProductItem.js new file mode 100644 index 0000000000..4a2a4b3f9f --- /dev/null +++ b/code/06-finished/src/components/Shop/ProductItem.js @@ -0,0 +1,41 @@ +import { useDispatch } from 'react-redux'; + +import { cartActions } from '../../store/cart-slice'; +import Card from '../UI/Card'; +import classes from './ProductItem.module.css'; + +const ProductItem = (props) => { + const dispatch = useDispatch(); + + const { title, price, description, id } = props; + + const addToCartHandler = () => { + // and then send Http request + // fetch('firebase-url', { method: 'POST', body: JSON.stringify(newCart) }) + + dispatch( + cartActions.addItemToCart({ + id, + title, + price, + }) + ); + }; + + return ( +
  • + +
    +

    {title}

    +
    ${price.toFixed(2)}
    +
    +

    {description}

    +
    + +
    +
    +
  • + ); +}; + +export default ProductItem; diff --git a/code/06-finished/src/components/Shop/ProductItem.module.css b/code/06-finished/src/components/Shop/ProductItem.module.css new file mode 100644 index 0000000000..2fcdc5d8a6 --- /dev/null +++ b/code/06-finished/src/components/Shop/ProductItem.module.css @@ -0,0 +1,27 @@ +.item h3 { + margin: 0.5rem 0; + font-size: 1.25rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.price { + border-radius: 30px; + padding: 0.15rem 1.5rem; + background-color: #3a3a3a; + color: white; + font-size: 1.5rem; +} + +.item p { + color: #3a3a3a; +} + +.actions { + display: flex; + justify-content: flex-end; +} \ No newline at end of file diff --git a/code/06-finished/src/components/Shop/Products.js b/code/06-finished/src/components/Shop/Products.js new file mode 100644 index 0000000000..6b430cc262 --- /dev/null +++ b/code/06-finished/src/components/Shop/Products.js @@ -0,0 +1,38 @@ +import ProductItem from './ProductItem'; +import classes from './Products.module.css'; + +const DUMMY_PRODUCTS = [ + { + id: 'p1', + price: 6, + title: 'My First Book', + description: 'The first book I ever wrote', + }, + { + id: 'p2', + price: 5, + title: 'My Second Book', + description: 'The second book I ever wrote', + }, +]; + +const Products = (props) => { + return ( +
    +

    Buy your favorite products

    +
      + {DUMMY_PRODUCTS.map((product) => ( + + ))} +
    +
    + ); +}; + +export default Products; diff --git a/code/06-finished/src/components/Shop/Products.module.css b/code/06-finished/src/components/Shop/Products.module.css new file mode 100644 index 0000000000..d81c97330f --- /dev/null +++ b/code/06-finished/src/components/Shop/Products.module.css @@ -0,0 +1,12 @@ +.products h2 { + color: white; + margin: 2rem auto; + text-align: center; + text-transform: uppercase; +} + +.products ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/06-finished/src/components/UI/Card.js b/code/06-finished/src/components/UI/Card.js new file mode 100644 index 0000000000..849202f21c --- /dev/null +++ b/code/06-finished/src/components/UI/Card.js @@ -0,0 +1,13 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return ( +
    + {props.children} +
    + ); +}; + +export default Card; diff --git a/code/06-finished/src/components/UI/Card.module.css b/code/06-finished/src/components/UI/Card.module.css new file mode 100644 index 0000000000..ac9c6709f4 --- /dev/null +++ b/code/06-finished/src/components/UI/Card.module.css @@ -0,0 +1,8 @@ +.card { + margin: 1rem auto; + border-radius: 6px; + background-color: white; + padding: 1rem; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/06-finished/src/components/UI/Notification.js b/code/06-finished/src/components/UI/Notification.js new file mode 100644 index 0000000000..982017fa74 --- /dev/null +++ b/code/06-finished/src/components/UI/Notification.js @@ -0,0 +1,23 @@ +import classes from './Notification.module.css'; + +const Notification = (props) => { + let specialClasses = ''; + + if (props.status === 'error') { + specialClasses = classes.error; + } + if (props.status === 'success') { + specialClasses = classes.success; + } + + const cssClasses = `${classes.notification} ${specialClasses}`; + + return ( +
    +

    {props.title}

    +

    {props.message}

    +
    + ); +}; + +export default Notification; diff --git a/code/06-finished/src/components/UI/Notification.module.css b/code/06-finished/src/components/UI/Notification.module.css new file mode 100644 index 0000000000..5decd98ea5 --- /dev/null +++ b/code/06-finished/src/components/UI/Notification.module.css @@ -0,0 +1,24 @@ +.notification { + width: 100%; + height: 3rem; + background-color: #1a8ed1; + display: flex; + justify-content: space-between; + padding: 0.5rem 10%; + align-items: center; + color: white; +} + +.notification h2, +.notification p { + font-size: 1rem; + margin: 0; +} + +.error { + background-color: #690000; +} + +.success { + background-color: #1ad1b9; +} \ No newline at end of file diff --git a/code/06-finished/src/index.css b/code/06-finished/src/index.css new file mode 100644 index 0000000000..3431f5f884 --- /dev/null +++ b/code/06-finished/src/index.css @@ -0,0 +1,31 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap'); + +* { + box-sizing: border-box; +} + +html { + font-family: 'Noto Sans JP', sans-serif; +} + +body { + margin: 0; + background-color: #3f3f3f; +} + +button { + font: inherit; + cursor: pointer; + padding: 0.5rem 1.5rem; + border-radius: 6px; + background-color: transparent; + color: #1a8ed1; + border: 1px solid #1a8ed1; +} + +button:hover, +button:active { + background-color: #1ac5d1; + border-color: #1ac5d1; + color: white; +} \ No newline at end of file diff --git a/code/06-finished/src/index.js b/code/06-finished/src/index.js new file mode 100644 index 0000000000..ac1938ea3e --- /dev/null +++ b/code/06-finished/src/index.js @@ -0,0 +1,13 @@ +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; + +import store from './store/index'; +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/06-finished/src/store/cart-actions.js b/code/06-finished/src/store/cart-actions.js new file mode 100644 index 0000000000..4459999b4f --- /dev/null +++ b/code/06-finished/src/store/cart-actions.js @@ -0,0 +1,87 @@ +import { uiActions } from './ui-slice'; +import { cartActions } from './cart-slice'; + +export const fetchCartData = () => { + return async (dispatch) => { + const fetchData = async () => { + const response = await fetch( + 'https://react-http-6b4a6.firebaseio.com/cart.json' + ); + + if (!response.ok) { + throw new Error('Could not fetch cart data!'); + } + + const data = await response.json(); + + return data; + }; + + try { + const cartData = await fetchData(); + dispatch( + cartActions.replaceCart({ + items: cartData.items || [], + totalQuantity: cartData.totalQuantity, + }) + ); + } catch (error) { + dispatch( + uiActions.showNotification({ + status: 'error', + title: 'Error!', + message: 'Fetching cart data failed!', + }) + ); + } + }; +}; + +export const sendCartData = (cart) => { + return async (dispatch) => { + dispatch( + uiActions.showNotification({ + status: 'pending', + title: 'Sending...', + message: 'Sending cart data!', + }) + ); + + const sendRequest = async () => { + const response = await fetch( + 'https://react-http-6b4a6.firebaseio.com/cart.json', + { + method: 'PUT', + body: JSON.stringify({ + items: cart.items, + totalQuantity: cart.totalQuantity, + }), + } + ); + + if (!response.ok) { + throw new Error('Sending cart data failed.'); + } + }; + + try { + await sendRequest(); + + dispatch( + uiActions.showNotification({ + status: 'success', + title: 'Success!', + message: 'Sent cart data successfully!', + }) + ); + } catch (error) { + dispatch( + uiActions.showNotification({ + status: 'error', + title: 'Error!', + message: 'Sending cart data failed!', + }) + ); + } + }; +}; diff --git a/code/06-finished/src/store/cart-slice.js b/code/06-finished/src/store/cart-slice.js new file mode 100644 index 0000000000..c4a7abc07e --- /dev/null +++ b/code/06-finished/src/store/cart-slice.js @@ -0,0 +1,50 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const cartSlice = createSlice({ + name: 'cart', + initialState: { + items: [], + totalQuantity: 0, + changed: false, + }, + reducers: { + replaceCart(state, action) { + state.totalQuantity = action.payload.totalQuantity; + state.items = action.payload.items; + }, + addItemToCart(state, action) { + const newItem = action.payload; + const existingItem = state.items.find((item) => item.id === newItem.id); + state.totalQuantity++; + state.changed = true; + if (!existingItem) { + state.items.push({ + id: newItem.id, + price: newItem.price, + quantity: 1, + totalPrice: newItem.price, + name: newItem.title, + }); + } else { + existingItem.quantity++; + existingItem.totalPrice = existingItem.totalPrice + newItem.price; + } + }, + removeItemFromCart(state, action) { + const id = action.payload; + const existingItem = state.items.find((item) => item.id === id); + state.totalQuantity--; + state.changed = true; + if (existingItem.quantity === 1) { + state.items = state.items.filter((item) => item.id !== id); + } else { + existingItem.quantity--; + existingItem.totalPrice = existingItem.totalPrice - existingItem.price; + } + }, + }, +}); + +export const cartActions = cartSlice.actions; + +export default cartSlice; diff --git a/code/06-finished/src/store/index.js b/code/06-finished/src/store/index.js new file mode 100644 index 0000000000..d663f26de2 --- /dev/null +++ b/code/06-finished/src/store/index.js @@ -0,0 +1,10 @@ +import { configureStore } from '@reduxjs/toolkit'; + +import uiSlice from './ui-slice'; +import cartSlice from './cart-slice'; + +const store = configureStore({ + reducer: { ui: uiSlice.reducer, cart: cartSlice.reducer }, +}); + +export default store; diff --git a/code/06-finished/src/store/ui-slice.js b/code/06-finished/src/store/ui-slice.js new file mode 100644 index 0000000000..a25529a6f0 --- /dev/null +++ b/code/06-finished/src/store/ui-slice.js @@ -0,0 +1,22 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const uiSlice = createSlice({ + name: 'ui', + initialState: { cartIsVisible: false, notification: null }, + reducers: { + toggle(state) { + state.cartIsVisible = !state.cartIsVisible; + }, + showNotification(state, action) { + state.notification = { + status: action.payload.status, + title: action.payload.title, + message: action.payload.message, + }; + }, + }, +}); + +export const uiActions = uiSlice.actions; + +export default uiSlice; diff --git a/slides/slides.pdf b/slides/slides.pdf new file mode 100644 index 0000000000000000000000000000000000000000..48bd67b686eb47acdfc53642458f0db9e7f5ec09 GIT binary patch literal 61834 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 Ky_B*v#{U7>t+AH? literal 0 HcmV?d00001 From 8d1a4ab6d21389bc9b140d48c65166146034d380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Wed, 24 Mar 2021 13:56:12 +0100 Subject: [PATCH 2/5] added missing attachments --- code/zz-suboptimal-example-code/package.json | 40 ++++++++++ .../public/favicon.ico | Bin 0 -> 3870 bytes .../public/index.html | 43 +++++++++++ .../public/logo192.png | Bin 0 -> 5347 bytes .../public/logo512.png | Bin 0 -> 9664 bytes .../public/manifest.json | 25 ++++++ .../public/robots.txt | 3 + code/zz-suboptimal-example-code/src/App.js | 18 +++++ .../src/components/Cart/Cart.js | 31 ++++++++ .../src/components/Cart/Cart.module.css | 16 ++++ .../src/components/Cart/CartButton.js | 22 ++++++ .../src/components/Cart/CartButton.module.css | 21 ++++++ .../src/components/Cart/CartItem.js | 47 ++++++++++++ .../src/components/Cart/CartItem.module.css | 58 ++++++++++++++ .../src/components/Layout/Layout.js | 13 ++++ .../src/components/Layout/MainHeader.js | 19 +++++ .../components/Layout/MainHeader.module.css | 19 +++++ .../src/components/Shop/ProductItem.js | 71 ++++++++++++++++++ .../components/Shop/ProductItem.module.css | 27 +++++++ .../src/components/Shop/Products.js | 38 ++++++++++ .../src/components/Shop/Products.module.css | 12 +++ .../src/components/UI/Card.js | 13 ++++ .../src/components/UI/Card.module.css | 8 ++ code/zz-suboptimal-example-code/src/index.css | 31 ++++++++ code/zz-suboptimal-example-code/src/index.js | 13 ++++ .../src/store/cart-slice.js | 46 ++++++++++++ .../src/store/index.js | 10 +++ .../src/store/ui-slice.js | 15 ++++ extra-files/Notification.js | 23 ++++++ extra-files/Notification.module.css | 24 ++++++ 30 files changed, 706 insertions(+) create mode 100644 code/zz-suboptimal-example-code/package.json create mode 100644 code/zz-suboptimal-example-code/public/favicon.ico create mode 100644 code/zz-suboptimal-example-code/public/index.html create mode 100644 code/zz-suboptimal-example-code/public/logo192.png create mode 100644 code/zz-suboptimal-example-code/public/logo512.png create mode 100644 code/zz-suboptimal-example-code/public/manifest.json create mode 100644 code/zz-suboptimal-example-code/public/robots.txt create mode 100644 code/zz-suboptimal-example-code/src/App.js create mode 100644 code/zz-suboptimal-example-code/src/components/Cart/Cart.js create mode 100644 code/zz-suboptimal-example-code/src/components/Cart/Cart.module.css create mode 100644 code/zz-suboptimal-example-code/src/components/Cart/CartButton.js create mode 100644 code/zz-suboptimal-example-code/src/components/Cart/CartButton.module.css create mode 100644 code/zz-suboptimal-example-code/src/components/Cart/CartItem.js create mode 100644 code/zz-suboptimal-example-code/src/components/Cart/CartItem.module.css create mode 100644 code/zz-suboptimal-example-code/src/components/Layout/Layout.js create mode 100644 code/zz-suboptimal-example-code/src/components/Layout/MainHeader.js create mode 100644 code/zz-suboptimal-example-code/src/components/Layout/MainHeader.module.css create mode 100644 code/zz-suboptimal-example-code/src/components/Shop/ProductItem.js create mode 100644 code/zz-suboptimal-example-code/src/components/Shop/ProductItem.module.css create mode 100644 code/zz-suboptimal-example-code/src/components/Shop/Products.js create mode 100644 code/zz-suboptimal-example-code/src/components/Shop/Products.module.css create mode 100644 code/zz-suboptimal-example-code/src/components/UI/Card.js create mode 100644 code/zz-suboptimal-example-code/src/components/UI/Card.module.css create mode 100644 code/zz-suboptimal-example-code/src/index.css create mode 100644 code/zz-suboptimal-example-code/src/index.js create mode 100644 code/zz-suboptimal-example-code/src/store/cart-slice.js create mode 100644 code/zz-suboptimal-example-code/src/store/index.js create mode 100644 code/zz-suboptimal-example-code/src/store/ui-slice.js create mode 100644 extra-files/Notification.js create mode 100644 extra-files/Notification.module.css diff --git a/code/zz-suboptimal-example-code/package.json b/code/zz-suboptimal-example-code/package.json new file mode 100644 index 0000000000..2165b1f063 --- /dev/null +++ b/code/zz-suboptimal-example-code/package.json @@ -0,0 +1,40 @@ +{ + "name": "react-complete-guide", + "version": "0.1.0", + "private": true, + "dependencies": { + "@reduxjs/toolkit": "^1.5.0", + "@testing-library/jest-dom": "^5.11.6", + "@testing-library/react": "^11.2.2", + "@testing-library/user-event": "^12.5.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "react-redux": "^7.2.2", + "react-scripts": "4.0.1", + "web-vitals": "^0.2.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/code/zz-suboptimal-example-code/public/favicon.ico b/code/zz-suboptimal-example-code/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB
    3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/code/zz-suboptimal-example-code/public/index.html b/code/zz-suboptimal-example-code/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/zz-suboptimal-example-code/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/zz-suboptimal-example-code/public/logo192.png b/code/zz-suboptimal-example-code/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/code/zz-suboptimal-example-code/public/manifest.json b/code/zz-suboptimal-example-code/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/zz-suboptimal-example-code/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/code/zz-suboptimal-example-code/public/robots.txt b/code/zz-suboptimal-example-code/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/zz-suboptimal-example-code/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/zz-suboptimal-example-code/src/App.js b/code/zz-suboptimal-example-code/src/App.js new file mode 100644 index 0000000000..8531b2d78c --- /dev/null +++ b/code/zz-suboptimal-example-code/src/App.js @@ -0,0 +1,18 @@ +import { useSelector } from 'react-redux'; + +import Cart from './components/Cart/Cart'; +import Layout from './components/Layout/Layout'; +import Products from './components/Shop/Products'; + +function App() { + const showCart = useSelector((state) => state.ui.cartIsVisible); + + return ( + + {showCart && } + + + ); +} + +export default App; diff --git a/code/zz-suboptimal-example-code/src/components/Cart/Cart.js b/code/zz-suboptimal-example-code/src/components/Cart/Cart.js new file mode 100644 index 0000000000..33030d2089 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Cart/Cart.js @@ -0,0 +1,31 @@ +import { useSelector } from 'react-redux'; + +import Card from '../UI/Card'; +import classes from './Cart.module.css'; +import CartItem from './CartItem'; + +const Cart = (props) => { + const cartItems = useSelector((state) => state.cart.items); + + return ( + +

    Your Shopping Cart

    +
      + {cartItems.map((item) => ( + + ))} +
    +
    + ); +}; + +export default Cart; diff --git a/code/zz-suboptimal-example-code/src/components/Cart/Cart.module.css b/code/zz-suboptimal-example-code/src/components/Cart/Cart.module.css new file mode 100644 index 0000000000..95670ab70b --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Cart/Cart.module.css @@ -0,0 +1,16 @@ +.cart { + max-width: 30rem; + background-color: #313131; + color: white; +} + +.cart h2 { + font-size: 1.25rem; + margin: 0.5rem 0; +} + +.cart ul { + list-style: none; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/code/zz-suboptimal-example-code/src/components/Cart/CartButton.js b/code/zz-suboptimal-example-code/src/components/Cart/CartButton.js new file mode 100644 index 0000000000..4e59baac3c --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Cart/CartButton.js @@ -0,0 +1,22 @@ +import { useDispatch, useSelector } from 'react-redux'; + +import { uiActions } from '../../store/ui-slice'; +import classes from './CartButton.module.css'; + +const CartButton = (props) => { + const dispatch = useDispatch(); + const cartQuantity = useSelector((state) => state.cart.totalQuantity); + + const toggleCartHandler = () => { + dispatch(uiActions.toggle()); + }; + + return ( + + ); +}; + +export default CartButton; diff --git a/code/zz-suboptimal-example-code/src/components/Cart/CartButton.module.css b/code/zz-suboptimal-example-code/src/components/Cart/CartButton.module.css new file mode 100644 index 0000000000..93445f4596 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Cart/CartButton.module.css @@ -0,0 +1,21 @@ +.button { + background-color: transparent; + border-color: #1ad1b9; + color: #1ad1b9; +} + +.button:hover, +.button:active { + color: white; +} + +.button span { + margin: 0 0.5rem; +} + +.badge { + background-color: #1ad1b9; + border-radius: 30px; + padding: 0.15rem 1.25rem; + color: #1d1d1d; +} \ No newline at end of file diff --git a/code/zz-suboptimal-example-code/src/components/Cart/CartItem.js b/code/zz-suboptimal-example-code/src/components/Cart/CartItem.js new file mode 100644 index 0000000000..e1c6a8f012 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Cart/CartItem.js @@ -0,0 +1,47 @@ +import { useDispatch } from 'react-redux'; + +import classes from './CartItem.module.css'; +import { cartActions } from '../../store/cart-slice'; + +const CartItem = (props) => { + const dispatch = useDispatch(); + + const { title, quantity, total, price, id } = props.item; + + const removeItemHandler = () => { + dispatch(cartActions.removeItemFromCart(id)); + }; + + const addItemHandler = () => { + dispatch( + cartActions.addItemToCart({ + id, + title, + price, + }) + ); + }; + + return ( +
  • +
    +

    {title}

    +
    + ${total.toFixed(2)}{' '} + (${price.toFixed(2)}/item) +
    +
    +
    +
    + x {quantity} +
    +
    + + +
    +
    +
  • + ); +}; + +export default CartItem; diff --git a/code/zz-suboptimal-example-code/src/components/Cart/CartItem.module.css b/code/zz-suboptimal-example-code/src/components/Cart/CartItem.module.css new file mode 100644 index 0000000000..e34100d841 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Cart/CartItem.module.css @@ -0,0 +1,58 @@ +.item { + margin: 1rem 0; + background-color: #575757; + padding: 1rem; +} + +.item h3 { + margin: 0 0 0.5rem 0; + font-size: 1.75rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.details { + display: flex; + justify-content: space-between; + align-items: center; +} + +.quantity span { + font-size: 1.5rem; + font-weight: bold; +} + +.price { + font-size: 1.5rem; + font-weight: bold; +} + +.itemprice { + font-weight: normal; + font-size: 1rem; + font-style: italic; +} + +.actions { + display: flex; + justify-content: flex-end; + margin: 0.5rem 0; +} + +.actions button { + background-color: transparent; + border: 1px solid white; + margin-left: 0.5rem; + padding: 0.15rem 1rem; + color: white; +} + +.actions button:hover, +.actions button:active { + background-color: #4b4b4b; + color: white; +} \ No newline at end of file diff --git a/code/zz-suboptimal-example-code/src/components/Layout/Layout.js b/code/zz-suboptimal-example-code/src/components/Layout/Layout.js new file mode 100644 index 0000000000..0ce12a011f --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Layout/Layout.js @@ -0,0 +1,13 @@ +import { Fragment } from 'react'; +import MainHeader from './MainHeader'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/zz-suboptimal-example-code/src/components/Layout/MainHeader.js b/code/zz-suboptimal-example-code/src/components/Layout/MainHeader.js new file mode 100644 index 0000000000..38ea37a29b --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Layout/MainHeader.js @@ -0,0 +1,19 @@ +import CartButton from '../Cart/CartButton'; +import classes from './MainHeader.module.css'; + +const MainHeader = (props) => { + return ( +
    +

    ReduxCart

    + +
    + ); +}; + +export default MainHeader; diff --git a/code/zz-suboptimal-example-code/src/components/Layout/MainHeader.module.css b/code/zz-suboptimal-example-code/src/components/Layout/MainHeader.module.css new file mode 100644 index 0000000000..e41c98d247 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Layout/MainHeader.module.css @@ -0,0 +1,19 @@ +.header { + width: 100%; + height: 5rem; + padding: 0 10%; + display: flex; + align-items: center; + justify-content: space-between; + background-color: #252424; +} + +.header h1 { + color: white; +} + +.header ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/zz-suboptimal-example-code/src/components/Shop/ProductItem.js b/code/zz-suboptimal-example-code/src/components/Shop/ProductItem.js new file mode 100644 index 0000000000..2c593ec625 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Shop/ProductItem.js @@ -0,0 +1,71 @@ +import { useDispatch, useSelector } from 'react-redux'; + +import { cartActions } from '../../store/cart-slice'; +import Card from '../UI/Card'; +import classes from './ProductItem.module.css'; + +const ProductItem = (props) => { + const cart = useSelector((state) => state.cart); + const dispatch = useDispatch(); + + const { title, price, description, id } = props; + + const addToCartHandler = () => { + const newTotalQuantity = cart.totalQuantity + 1; + + const updatedItems = cart.items.slice(); // create copy via slice to avoid mutating original state + const existingItem = updatedItems.find((item) => item.id === id); + if (existingItem) { + const updatedItem = { ...existingItem }; // new object + copy existing properties to avoid state mutation + updatedItem.quantity++; + updatedItem.totalPrice = updatedItem.totalPrice + price; + const existingItemIndex = updatedItems.findIndex( + (item) => item.id === id + ); + updatedItems[existingItemIndex] = updatedItem; + } else { + updatedItems.push({ + id: id, + price: price, + quantity: 1, + totalPrice: price, + name: title, + }); + } + + const newCart = { + totalQuantity: newTotalQuantity, + items: updatedItems, + }; + + dispatch(cartActions.replaceCart(newCart)); + + // and then send Http request + // fetch('firebase-url', { method: 'POST', body: JSON.stringify(newCart) }) + + // dispatch( + // cartActions.addItemToCart({ + // id, + // title, + // price, + // }) + // ); + }; + + return ( +
  • + +
    +

    {title}

    +
    ${price.toFixed(2)}
    +
    +

    {description}

    +
    + +
    +
    +
  • + ); +}; + +export default ProductItem; diff --git a/code/zz-suboptimal-example-code/src/components/Shop/ProductItem.module.css b/code/zz-suboptimal-example-code/src/components/Shop/ProductItem.module.css new file mode 100644 index 0000000000..2fcdc5d8a6 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Shop/ProductItem.module.css @@ -0,0 +1,27 @@ +.item h3 { + margin: 0.5rem 0; + font-size: 1.25rem; +} + +.item header { + display: flex; + justify-content: space-between; + align-items: baseline; +} + +.price { + border-radius: 30px; + padding: 0.15rem 1.5rem; + background-color: #3a3a3a; + color: white; + font-size: 1.5rem; +} + +.item p { + color: #3a3a3a; +} + +.actions { + display: flex; + justify-content: flex-end; +} \ No newline at end of file diff --git a/code/zz-suboptimal-example-code/src/components/Shop/Products.js b/code/zz-suboptimal-example-code/src/components/Shop/Products.js new file mode 100644 index 0000000000..6b430cc262 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Shop/Products.js @@ -0,0 +1,38 @@ +import ProductItem from './ProductItem'; +import classes from './Products.module.css'; + +const DUMMY_PRODUCTS = [ + { + id: 'p1', + price: 6, + title: 'My First Book', + description: 'The first book I ever wrote', + }, + { + id: 'p2', + price: 5, + title: 'My Second Book', + description: 'The second book I ever wrote', + }, +]; + +const Products = (props) => { + return ( +
    +

    Buy your favorite products

    +
      + {DUMMY_PRODUCTS.map((product) => ( + + ))} +
    +
    + ); +}; + +export default Products; diff --git a/code/zz-suboptimal-example-code/src/components/Shop/Products.module.css b/code/zz-suboptimal-example-code/src/components/Shop/Products.module.css new file mode 100644 index 0000000000..d81c97330f --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/Shop/Products.module.css @@ -0,0 +1,12 @@ +.products h2 { + color: white; + margin: 2rem auto; + text-align: center; + text-transform: uppercase; +} + +.products ul { + list-style: none; + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/code/zz-suboptimal-example-code/src/components/UI/Card.js b/code/zz-suboptimal-example-code/src/components/UI/Card.js new file mode 100644 index 0000000000..849202f21c --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/UI/Card.js @@ -0,0 +1,13 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return ( +
    + {props.children} +
    + ); +}; + +export default Card; diff --git a/code/zz-suboptimal-example-code/src/components/UI/Card.module.css b/code/zz-suboptimal-example-code/src/components/UI/Card.module.css new file mode 100644 index 0000000000..ac9c6709f4 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/components/UI/Card.module.css @@ -0,0 +1,8 @@ +.card { + margin: 1rem auto; + border-radius: 6px; + background-color: white; + padding: 1rem; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/zz-suboptimal-example-code/src/index.css b/code/zz-suboptimal-example-code/src/index.css new file mode 100644 index 0000000000..3431f5f884 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/index.css @@ -0,0 +1,31 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap'); + +* { + box-sizing: border-box; +} + +html { + font-family: 'Noto Sans JP', sans-serif; +} + +body { + margin: 0; + background-color: #3f3f3f; +} + +button { + font: inherit; + cursor: pointer; + padding: 0.5rem 1.5rem; + border-radius: 6px; + background-color: transparent; + color: #1a8ed1; + border: 1px solid #1a8ed1; +} + +button:hover, +button:active { + background-color: #1ac5d1; + border-color: #1ac5d1; + color: white; +} \ No newline at end of file diff --git a/code/zz-suboptimal-example-code/src/index.js b/code/zz-suboptimal-example-code/src/index.js new file mode 100644 index 0000000000..ac1938ea3e --- /dev/null +++ b/code/zz-suboptimal-example-code/src/index.js @@ -0,0 +1,13 @@ +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; + +import store from './store/index'; +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/zz-suboptimal-example-code/src/store/cart-slice.js b/code/zz-suboptimal-example-code/src/store/cart-slice.js new file mode 100644 index 0000000000..e629fc66ce --- /dev/null +++ b/code/zz-suboptimal-example-code/src/store/cart-slice.js @@ -0,0 +1,46 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const cartSlice = createSlice({ + name: 'cart', + initialState: { + items: [], + totalQuantity: 0, + }, + reducers: { + replaceCart(state, action) { + state.totalQuantity = action.payload.totalQuantity; + state.items = action.payload.items; + }, + addItemToCart(state, action) { + const newItem = action.payload; + const existingItem = state.items.find((item) => item.id === newItem.id); + state.totalQuantity++; + if (!existingItem) { + state.items.push({ + id: newItem.id, + price: newItem.price, + quantity: 1, + totalPrice: newItem.price, + name: newItem.title, + }); + } else { + existingItem.quantity++; + existingItem.totalPrice = existingItem.totalPrice + newItem.price; + } + }, + removeItemFromCart(state, action) { + const id = action.payload; + const existingItem = state.items.find((item) => item.id === id); + state.totalQuantity--; + if (existingItem.quantity === 1) { + state.items = state.items.filter((item) => item.id !== id); + } else { + existingItem.quantity--; + } + }, + }, +}); + +export const cartActions = cartSlice.actions; + +export default cartSlice; diff --git a/code/zz-suboptimal-example-code/src/store/index.js b/code/zz-suboptimal-example-code/src/store/index.js new file mode 100644 index 0000000000..d663f26de2 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/store/index.js @@ -0,0 +1,10 @@ +import { configureStore } from '@reduxjs/toolkit'; + +import uiSlice from './ui-slice'; +import cartSlice from './cart-slice'; + +const store = configureStore({ + reducer: { ui: uiSlice.reducer, cart: cartSlice.reducer }, +}); + +export default store; diff --git a/code/zz-suboptimal-example-code/src/store/ui-slice.js b/code/zz-suboptimal-example-code/src/store/ui-slice.js new file mode 100644 index 0000000000..4fa43c1b12 --- /dev/null +++ b/code/zz-suboptimal-example-code/src/store/ui-slice.js @@ -0,0 +1,15 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const uiSlice = createSlice({ + name: 'ui', + initialState: { cartIsVisible: false }, + reducers: { + toggle(state) { + state.cartIsVisible = !state.cartIsVisible; + } + } +}); + +export const uiActions = uiSlice.actions; + +export default uiSlice; \ No newline at end of file diff --git a/extra-files/Notification.js b/extra-files/Notification.js new file mode 100644 index 0000000000..982017fa74 --- /dev/null +++ b/extra-files/Notification.js @@ -0,0 +1,23 @@ +import classes from './Notification.module.css'; + +const Notification = (props) => { + let specialClasses = ''; + + if (props.status === 'error') { + specialClasses = classes.error; + } + if (props.status === 'success') { + specialClasses = classes.success; + } + + const cssClasses = `${classes.notification} ${specialClasses}`; + + return ( +
    +

    {props.title}

    +

    {props.message}

    +
    + ); +}; + +export default Notification; diff --git a/extra-files/Notification.module.css b/extra-files/Notification.module.css new file mode 100644 index 0000000000..5decd98ea5 --- /dev/null +++ b/extra-files/Notification.module.css @@ -0,0 +1,24 @@ +.notification { + width: 100%; + height: 3rem; + background-color: #1a8ed1; + display: flex; + justify-content: space-between; + padding: 0.5rem 10%; + align-items: center; + color: white; +} + +.notification h2, +.notification p { + font-size: 1rem; + margin: 0; +} + +.error { + background-color: #690000; +} + +.success { + background-color: #1ad1b9; +} \ No newline at end of file From 340edb0fdf8fc3770af24c7769ff81f677217b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Tue, 24 Aug 2021 19:19:04 +0200 Subject: [PATCH 3/5] updated slides, added missing slides --- slides/slides.pdf | Bin 61834 -> 95343 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/slides/slides.pdf b/slides/slides.pdf index 48bd67b686eb47acdfc53642458f0db9e7f5ec09..9471d04995023142fb03278c41b3c57f037ebd7e 100644 GIT binary patch delta 65344 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 delta 28910 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 Ld@e;zCF1`AVn<73 From 6050b172c6e29b17c22d59d4187c3e1ecae7710b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Fri, 17 Dec 2021 16:28:12 +0100 Subject: [PATCH 4/5] updated to React 18 --- code/01-starting-project/package.json | 4 ++-- code/01-starting-project/src/index.js | 3 ++- code/02-refresher-practice-finished/package.json | 4 ++-- code/02-refresher-practice-finished/src/index.js | 6 +++--- code/03-using-useeffect-with-redux/package.json | 4 ++-- code/03-using-useeffect-with-redux/src/index.js | 6 +++--- .../package.json | 4 ++-- .../src/index.js | 6 +++--- code/05-using-an-action-creator-thunk/package.json | 4 ++-- code/05-using-an-action-creator-thunk/src/index.js | 6 +++--- code/06-finished/package.json | 4 ++-- code/06-finished/src/index.js | 6 +++--- code/zz-suboptimal-example-code/package.json | 4 ++-- code/zz-suboptimal-example-code/src/index.js | 6 +++--- 14 files changed, 34 insertions(+), 33 deletions(-) diff --git a/code/01-starting-project/package.json b/code/01-starting-project/package.json index 21f2dd39f2..b16a8faa39 100644 --- a/code/01-starting-project/package.json +++ b/code/01-starting-project/package.json @@ -6,8 +6,8 @@ "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^11.2.2", "@testing-library/user-event": "^12.5.0", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" }, diff --git a/code/01-starting-project/src/index.js b/code/01-starting-project/src/index.js index d59d0ce18d..e4d0924dc0 100644 --- a/code/01-starting-project/src/index.js +++ b/code/01-starting-project/src/index.js @@ -3,4 +3,5 @@ import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; -ReactDOM.render(, document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(); diff --git a/code/02-refresher-practice-finished/package.json b/code/02-refresher-practice-finished/package.json index 2165b1f063..99c15d93ce 100644 --- a/code/02-refresher-practice-finished/package.json +++ b/code/02-refresher-practice-finished/package.json @@ -7,8 +7,8 @@ "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^11.2.2", "@testing-library/user-event": "^12.5.0", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-redux": "^7.2.2", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/02-refresher-practice-finished/src/index.js b/code/02-refresher-practice-finished/src/index.js index ac1938ea3e..cb38b1f0b2 100644 --- a/code/02-refresher-practice-finished/src/index.js +++ b/code/02-refresher-practice-finished/src/index.js @@ -5,9 +5,9 @@ import store from './store/index'; import './index.css'; import App from './App'; -ReactDOM.render( +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( - , - document.getElementById('root') + ); diff --git a/code/03-using-useeffect-with-redux/package.json b/code/03-using-useeffect-with-redux/package.json index 2165b1f063..99c15d93ce 100644 --- a/code/03-using-useeffect-with-redux/package.json +++ b/code/03-using-useeffect-with-redux/package.json @@ -7,8 +7,8 @@ "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^11.2.2", "@testing-library/user-event": "^12.5.0", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-redux": "^7.2.2", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/03-using-useeffect-with-redux/src/index.js b/code/03-using-useeffect-with-redux/src/index.js index ac1938ea3e..cb38b1f0b2 100644 --- a/code/03-using-useeffect-with-redux/src/index.js +++ b/code/03-using-useeffect-with-redux/src/index.js @@ -5,9 +5,9 @@ import store from './store/index'; import './index.css'; import App from './App'; -ReactDOM.render( +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( - , - document.getElementById('root') + ); diff --git a/code/04-handling-http-states-feedback-with-redux/package.json b/code/04-handling-http-states-feedback-with-redux/package.json index 2165b1f063..99c15d93ce 100644 --- a/code/04-handling-http-states-feedback-with-redux/package.json +++ b/code/04-handling-http-states-feedback-with-redux/package.json @@ -7,8 +7,8 @@ "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^11.2.2", "@testing-library/user-event": "^12.5.0", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-redux": "^7.2.2", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/04-handling-http-states-feedback-with-redux/src/index.js b/code/04-handling-http-states-feedback-with-redux/src/index.js index ac1938ea3e..cb38b1f0b2 100644 --- a/code/04-handling-http-states-feedback-with-redux/src/index.js +++ b/code/04-handling-http-states-feedback-with-redux/src/index.js @@ -5,9 +5,9 @@ import store from './store/index'; import './index.css'; import App from './App'; -ReactDOM.render( +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( - , - document.getElementById('root') + ); diff --git a/code/05-using-an-action-creator-thunk/package.json b/code/05-using-an-action-creator-thunk/package.json index 2165b1f063..99c15d93ce 100644 --- a/code/05-using-an-action-creator-thunk/package.json +++ b/code/05-using-an-action-creator-thunk/package.json @@ -7,8 +7,8 @@ "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^11.2.2", "@testing-library/user-event": "^12.5.0", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-redux": "^7.2.2", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/05-using-an-action-creator-thunk/src/index.js b/code/05-using-an-action-creator-thunk/src/index.js index ac1938ea3e..cb38b1f0b2 100644 --- a/code/05-using-an-action-creator-thunk/src/index.js +++ b/code/05-using-an-action-creator-thunk/src/index.js @@ -5,9 +5,9 @@ import store from './store/index'; import './index.css'; import App from './App'; -ReactDOM.render( +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( - , - document.getElementById('root') + ); diff --git a/code/06-finished/package.json b/code/06-finished/package.json index 2165b1f063..99c15d93ce 100644 --- a/code/06-finished/package.json +++ b/code/06-finished/package.json @@ -7,8 +7,8 @@ "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^11.2.2", "@testing-library/user-event": "^12.5.0", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-redux": "^7.2.2", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/06-finished/src/index.js b/code/06-finished/src/index.js index ac1938ea3e..cb38b1f0b2 100644 --- a/code/06-finished/src/index.js +++ b/code/06-finished/src/index.js @@ -5,9 +5,9 @@ import store from './store/index'; import './index.css'; import App from './App'; -ReactDOM.render( +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( - , - document.getElementById('root') + ); diff --git a/code/zz-suboptimal-example-code/package.json b/code/zz-suboptimal-example-code/package.json index 2165b1f063..99c15d93ce 100644 --- a/code/zz-suboptimal-example-code/package.json +++ b/code/zz-suboptimal-example-code/package.json @@ -7,8 +7,8 @@ "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^11.2.2", "@testing-library/user-event": "^12.5.0", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-redux": "^7.2.2", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/zz-suboptimal-example-code/src/index.js b/code/zz-suboptimal-example-code/src/index.js index ac1938ea3e..cb38b1f0b2 100644 --- a/code/zz-suboptimal-example-code/src/index.js +++ b/code/zz-suboptimal-example-code/src/index.js @@ -5,9 +5,9 @@ import store from './store/index'; import './index.css'; import App from './App'; -ReactDOM.render( +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( - , - document.getElementById('root') + ); From 307a47074dc47cfccebb9d8a56600974cbf38b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= <28806196+maxschwarzmueller@users.noreply.github.com> Date: Fri, 25 Mar 2022 17:48:44 +0100 Subject: [PATCH 5/5] updated code attachments to v18 --- code/01-starting-project/src/index.js | 2 +- code/02-refresher-practice-finished/src/index.js | 2 +- code/03-using-useeffect-with-redux/src/index.js | 2 +- code/04-handling-http-states-feedback-with-redux/src/index.js | 2 +- code/05-using-an-action-creator-thunk/src/index.js | 2 +- code/06-finished/src/index.js | 2 +- code/zz-suboptimal-example-code/src/index.js | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/code/01-starting-project/src/index.js b/code/01-starting-project/src/index.js index e4d0924dc0..778ec1ba20 100644 --- a/code/01-starting-project/src/index.js +++ b/code/01-starting-project/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; diff --git a/code/02-refresher-practice-finished/src/index.js b/code/02-refresher-practice-finished/src/index.js index cb38b1f0b2..c67c8acc68 100644 --- a/code/02-refresher-practice-finished/src/index.js +++ b/code/02-refresher-practice-finished/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; import store from './store/index'; diff --git a/code/03-using-useeffect-with-redux/src/index.js b/code/03-using-useeffect-with-redux/src/index.js index cb38b1f0b2..c67c8acc68 100644 --- a/code/03-using-useeffect-with-redux/src/index.js +++ b/code/03-using-useeffect-with-redux/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; import store from './store/index'; diff --git a/code/04-handling-http-states-feedback-with-redux/src/index.js b/code/04-handling-http-states-feedback-with-redux/src/index.js index cb38b1f0b2..c67c8acc68 100644 --- a/code/04-handling-http-states-feedback-with-redux/src/index.js +++ b/code/04-handling-http-states-feedback-with-redux/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; import store from './store/index'; diff --git a/code/05-using-an-action-creator-thunk/src/index.js b/code/05-using-an-action-creator-thunk/src/index.js index cb38b1f0b2..c67c8acc68 100644 --- a/code/05-using-an-action-creator-thunk/src/index.js +++ b/code/05-using-an-action-creator-thunk/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; import store from './store/index'; diff --git a/code/06-finished/src/index.js b/code/06-finished/src/index.js index cb38b1f0b2..c67c8acc68 100644 --- a/code/06-finished/src/index.js +++ b/code/06-finished/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; import store from './store/index'; diff --git a/code/zz-suboptimal-example-code/src/index.js b/code/zz-suboptimal-example-code/src/index.js index cb38b1f0b2..c67c8acc68 100644 --- a/code/zz-suboptimal-example-code/src/index.js +++ b/code/zz-suboptimal-example-code/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; import store from './store/index';