From 349ab992a00b070d5874c6fefbba3a29d50c59bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Wed, 24 Mar 2021 09:36:22 +0100 Subject: [PATCH 1/7] added code and slides --- 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 | 9 ++ code/01-starting-project/src/index.css | 15 +++ code/01-starting-project/src/index.js | 6 ++ .../02-defining-and-using-routes/package.json | 39 +++++++ .../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/02-defining-and-using-routes/src/App.js | 22 ++++ .../src/index.css | 15 +++ .../02-defining-and-using-routes/src/index.js | 12 +++ .../src/pages/Products.js | 5 + .../src/pages/Welcome.js | 5 + code/03-working-with-links/package.json | 39 +++++++ code/03-working-with-links/public/favicon.ico | Bin 0 -> 3870 bytes code/03-working-with-links/public/index.html | 43 ++++++++ code/03-working-with-links/public/logo192.png | Bin 0 -> 5347 bytes code/03-working-with-links/public/logo512.png | Bin 0 -> 9664 bytes .../public/manifest.json | 25 +++++ code/03-working-with-links/public/robots.txt | 3 + code/03-working-with-links/src/App.js | 26 +++++ .../src/components/MainHeader.js | 22 ++++ .../src/components/MainHeader.module.css | 37 +++++++ code/03-working-with-links/src/index.css | 26 +++++ code/03-working-with-links/src/index.js | 12 +++ .../src/pages/Products.js | 5 + .../src/pages/Welcome.js | 5 + code/04-using-navlinks/package.json | 39 +++++++ code/04-using-navlinks/public/favicon.ico | Bin 0 -> 3870 bytes code/04-using-navlinks/public/index.html | 43 ++++++++ code/04-using-navlinks/public/logo192.png | Bin 0 -> 5347 bytes code/04-using-navlinks/public/logo512.png | Bin 0 -> 9664 bytes code/04-using-navlinks/public/manifest.json | 25 +++++ code/04-using-navlinks/public/robots.txt | 3 + code/04-using-navlinks/src/App.js | 26 +++++ .../src/components/MainHeader.js | 26 +++++ .../src/components/MainHeader.module.css | 38 +++++++ code/04-using-navlinks/src/index.css | 26 +++++ code/04-using-navlinks/src/index.js | 12 +++ code/04-using-navlinks/src/pages/Products.js | 5 + code/04-using-navlinks/src/pages/Welcome.js | 5 + code/05-extracting-route-params/package.json | 39 +++++++ .../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/05-extracting-route-params/src/App.js | 31 ++++++ .../src/components/MainHeader.js | 26 +++++ .../src/components/MainHeader.module.css | 38 +++++++ code/05-extracting-route-params/src/index.css | 26 +++++ code/05-extracting-route-params/src/index.js | 12 +++ .../src/pages/ProductDetail.js | 16 +++ .../src/pages/Products.js | 14 +++ .../src/pages/Welcome.js | 5 + code/06-using-switch-and-exact/package.json | 39 +++++++ .../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/06-using-switch-and-exact/src/App.js | 33 ++++++ .../src/components/MainHeader.js | 26 +++++ .../src/components/MainHeader.module.css | 38 +++++++ code/06-using-switch-and-exact/src/index.css | 26 +++++ code/06-using-switch-and-exact/src/index.js | 12 +++ .../src/pages/ProductDetail.js | 16 +++ .../src/pages/Products.js | 22 ++++ .../src/pages/Welcome.js | 5 + .../package.json | 39 +++++++ .../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/07-working-with-nested-routes/src/App.js | 33 ++++++ .../src/components/MainHeader.js | 26 +++++ .../src/components/MainHeader.module.css | 38 +++++++ .../src/index.css | 26 +++++ .../src/index.js | 12 +++ .../src/pages/ProductDetail.js | 16 +++ .../src/pages/Products.js | 22 ++++ .../src/pages/Welcome.js | 14 +++ code/08-redirecting-the-user/package.json | 39 +++++++ .../public/favicon.ico | Bin 0 -> 3870 bytes .../08-redirecting-the-user/public/index.html | 43 ++++++++ .../public/logo192.png | Bin 0 -> 5347 bytes .../public/logo512.png | Bin 0 -> 9664 bytes .../public/manifest.json | 25 +++++ .../08-redirecting-the-user/public/robots.txt | 3 + code/08-redirecting-the-user/src/App.js | 36 +++++++ .../src/components/MainHeader.js | 26 +++++ .../src/components/MainHeader.module.css | 38 +++++++ code/08-redirecting-the-user/src/index.css | 26 +++++ code/08-redirecting-the-user/src/index.js | 12 +++ .../src/pages/ProductDetail.js | 16 +++ .../src/pages/Products.js | 22 ++++ .../src/pages/Welcome.js | 14 +++ .../package.json | 39 +++++++ .../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 | 9 ++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.module.css | 5 + .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 14 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 47 +++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 23 +++++ .../components/quotes/QuoteList.module.css | 25 +++++ .../src/index.css | 56 ++++++++++ .../src/index.js | 6 ++ .../package.json | 39 +++++++ .../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 | 26 +++++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.module.css | 5 + .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 14 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 47 +++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 23 +++++ .../components/quotes/QuoteList.module.css | 25 +++++ .../src/index.css | 56 ++++++++++ .../src/index.js | 12 +++ .../src/pages/AllQuotes.js | 5 + .../src/pages/NewQuote.js | 5 + .../src/pages/QuoteDetail.js | 15 +++ code/11-practicing-nested-routes/package.json | 39 +++++++ .../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/11-practicing-nested-routes/src/App.js | 26 +++++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.module.css | 5 + .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 14 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 47 +++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 23 +++++ .../components/quotes/QuoteList.module.css | 25 +++++ .../11-practicing-nested-routes/src/index.css | 56 ++++++++++ code/11-practicing-nested-routes/src/index.js | 12 +++ .../src/pages/AllQuotes.js | 5 + .../src/pages/NewQuote.js | 5 + .../src/pages/QuoteDetail.js | 20 ++++ code/12-adding-a-layout-wrapper/package.json | 39 +++++++ .../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/12-adding-a-layout-wrapper/src/App.js | 29 ++++++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.js | 15 +++ .../src/components/layout/Layout.module.css | 5 + .../src/components/layout/MainNavigation.js | 27 +++++ .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 14 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 47 +++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 23 +++++ .../components/quotes/QuoteList.module.css | 25 +++++ code/12-adding-a-layout-wrapper/src/index.css | 56 ++++++++++ code/12-adding-a-layout-wrapper/src/index.js | 12 +++ .../src/pages/AllQuotes.js | 5 + .../src/pages/NewQuote.js | 5 + .../src/pages/QuoteDetail.js | 20 ++++ .../package.json | 39 +++++++ .../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 | 29 ++++++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.js | 15 +++ .../src/components/layout/Layout.module.css | 5 + .../src/components/layout/MainNavigation.js | 27 +++++ .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 14 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 47 +++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 19 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 23 +++++ .../components/quotes/QuoteList.module.css | 25 +++++ .../src/index.css | 56 ++++++++++ .../src/index.js | 12 +++ .../src/pages/AllQuotes.js | 12 +++ .../src/pages/NewQuote.js | 11 ++ .../src/pages/QuoteDetail.js | 20 ++++ .../package.json | 39 +++++++ .../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 | 29 ++++++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.js | 15 +++ .../src/components/layout/Layout.module.css | 5 + .../src/components/layout/MainNavigation.js | 27 +++++ .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 14 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 47 +++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 23 +++++ .../components/quotes/QuoteList.module.css | 25 +++++ .../src/index.css | 56 ++++++++++ .../src/index.js | 12 +++ .../src/pages/AllQuotes.js | 12 +++ .../src/pages/NewQuote.js | 11 ++ .../src/pages/QuoteDetail.js | 31 ++++++ code/15-adding-a-notfound-page/package.json | 39 +++++++ .../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/15-adding-a-notfound-page/src/App.js | 33 ++++++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.js | 15 +++ .../src/components/layout/Layout.module.css | 5 + .../src/components/layout/MainNavigation.js | 27 +++++ .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 14 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 47 +++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 23 +++++ .../components/quotes/QuoteList.module.css | 25 +++++ code/15-adding-a-notfound-page/src/index.css | 56 ++++++++++ code/15-adding-a-notfound-page/src/index.js | 12 +++ .../src/pages/AllQuotes.js | 12 +++ .../src/pages/NewQuote.js | 11 ++ .../src/pages/NotFound.js | 9 ++ .../src/pages/QuoteDetail.js | 31 ++++++ .../package.json | 39 +++++++ .../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 | 33 ++++++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.js | 15 +++ .../src/components/layout/Layout.module.css | 5 + .../src/components/layout/MainNavigation.js | 27 +++++ .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 14 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 47 +++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 23 +++++ .../components/quotes/QuoteList.module.css | 25 +++++ .../src/index.css | 56 ++++++++++ .../src/index.js | 12 +++ .../src/pages/AllQuotes.js | 12 +++ .../src/pages/NewQuote.js | 17 ++++ .../src/pages/NotFound.js | 9 ++ .../src/pages/QuoteDetail.js | 31 ++++++ .../package.json | 39 +++++++ .../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 | 33 ++++++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.js | 15 +++ .../src/components/layout/Layout.module.css | 5 + .../src/components/layout/MainNavigation.js | 27 +++++ .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 14 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 70 +++++++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 23 +++++ .../components/quotes/QuoteList.module.css | 25 +++++ .../src/index.css | 56 ++++++++++ .../src/index.js | 12 +++ .../src/pages/AllQuotes.js | 12 +++ .../src/pages/NewQuote.js | 17 ++++ .../src/pages/NotFound.js | 9 ++ .../src/pages/QuoteDetail.js | 31 ++++++ .../18-working-with-query-params/package.json | 39 +++++++ .../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/18-working-with-query-params/src/App.js | 33 ++++++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.js | 15 +++ .../src/components/layout/Layout.module.css | 5 + .../src/components/layout/MainNavigation.js | 27 +++++ .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 14 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 70 +++++++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 52 ++++++++++ .../components/quotes/QuoteList.module.css | 25 +++++ .../src/index.css | 56 ++++++++++ .../18-working-with-query-params/src/index.js | 12 +++ .../src/pages/AllQuotes.js | 12 +++ .../src/pages/NewQuote.js | 17 ++++ .../src/pages/NotFound.js | 9 ++ .../src/pages/QuoteDetail.js | 31 ++++++ .../package.json | 39 +++++++ .../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 | 33 ++++++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.js | 15 +++ .../src/components/layout/Layout.module.css | 5 + .../src/components/layout/MainNavigation.js | 27 +++++ .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 14 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 70 +++++++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 55 ++++++++++ .../components/quotes/QuoteList.module.css | 25 +++++ .../src/index.css | 56 ++++++++++ .../src/index.js | 12 +++ .../src/pages/AllQuotes.js | 12 +++ .../src/pages/NewQuote.js | 17 ++++ .../src/pages/NotFound.js | 9 ++ .../src/pages/QuoteDetail.js | 39 +++++++ .../package.json | 39 +++++++ .../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/20-sending-getting-quote-data/src/App.js | 33 ++++++ .../src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 27 +++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 29 ++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.js | 15 +++ .../src/components/layout/Layout.module.css | 5 + .../src/components/layout/MainNavigation.js | 27 +++++ .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 16 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 70 +++++++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 55 ++++++++++ .../components/quotes/QuoteList.module.css | 25 +++++ .../src/hooks/use-http.js | 60 +++++++++++ .../src/index.css | 56 ++++++++++ .../src/index.js | 12 +++ .../src/lib/api.js | 96 ++++++++++++++++++ .../src/pages/AllQuotes.js | 38 +++++++ .../src/pages/NewQuote.js | 25 +++++ .../src/pages/NotFound.js | 9 ++ .../src/pages/QuoteDetail.js | 58 +++++++++++ code/21-finished/package.json | 39 +++++++ code/21-finished/public/favicon.ico | Bin 0 -> 3870 bytes code/21-finished/public/index.html | 43 ++++++++ code/21-finished/public/logo192.png | Bin 0 -> 5347 bytes code/21-finished/public/logo512.png | Bin 0 -> 9664 bytes code/21-finished/public/manifest.json | 25 +++++ code/21-finished/public/robots.txt | 3 + code/21-finished/src/App.js | 33 ++++++ code/21-finished/src/components/UI/Card.js | 7 ++ .../src/components/UI/Card.module.css | 7 ++ .../src/components/UI/LoadingSpinner.js | 7 ++ .../components/UI/LoadingSpinner.module.css | 24 +++++ .../src/components/comments/CommentItem.js | 11 ++ .../comments/CommentItem.module.css | 7 ++ .../src/components/comments/Comments.js | 71 +++++++++++++ .../components/comments/Comments.module.css | 7 ++ .../src/components/comments/CommentsList.js | 14 +++ .../comments/CommentsList.module.css | 5 + .../src/components/comments/NewCommentForm.js | 49 +++++++++ .../comments/NewCommentForm.module.css | 45 ++++++++ .../src/components/layout/Layout.js | 15 +++ .../src/components/layout/Layout.module.css | 5 + .../src/components/layout/MainNavigation.js | 27 +++++ .../layout/MainNavigation.module.css | 37 +++++++ .../src/components/quotes/HighlightedQuote.js | 12 +++ .../quotes/HighlightedQuote.module.css | 20 ++++ .../src/components/quotes/NoQuotesFound.js | 16 +++ .../quotes/NoQuotesFound.module.css | 17 ++++ .../src/components/quotes/QuoteForm.js | 70 +++++++++++++ .../components/quotes/QuoteForm.module.css | 49 +++++++++ .../src/components/quotes/QuoteItem.js | 21 ++++ .../components/quotes/QuoteItem.module.css | 37 +++++++ .../src/components/quotes/QuoteList.js | 55 ++++++++++ .../components/quotes/QuoteList.module.css | 25 +++++ code/21-finished/src/hooks/use-http.js | 60 +++++++++++ code/21-finished/src/index.css | 56 ++++++++++ code/21-finished/src/index.js | 12 +++ code/21-finished/src/lib/api.js | 96 ++++++++++++++++++ code/21-finished/src/pages/AllQuotes.js | 38 +++++++ code/21-finished/src/pages/NewQuote.js | 25 +++++ code/21-finished/src/pages/NotFound.js | 9 ++ code/21-finished/src/pages/QuoteDetail.js | 58 +++++++++++ slides/slides.pdf | Bin 0 -> 73566 bytes 620 files changed, 12937 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/index.css create mode 100644 code/01-starting-project/src/index.js create mode 100644 code/02-defining-and-using-routes/package.json create mode 100644 code/02-defining-and-using-routes/public/favicon.ico create mode 100644 code/02-defining-and-using-routes/public/index.html create mode 100644 code/02-defining-and-using-routes/public/logo192.png create mode 100644 code/02-defining-and-using-routes/public/logo512.png create mode 100644 code/02-defining-and-using-routes/public/manifest.json create mode 100644 code/02-defining-and-using-routes/public/robots.txt create mode 100644 code/02-defining-and-using-routes/src/App.js create mode 100644 code/02-defining-and-using-routes/src/index.css create mode 100644 code/02-defining-and-using-routes/src/index.js create mode 100644 code/02-defining-and-using-routes/src/pages/Products.js create mode 100644 code/02-defining-and-using-routes/src/pages/Welcome.js create mode 100644 code/03-working-with-links/package.json create mode 100644 code/03-working-with-links/public/favicon.ico create mode 100644 code/03-working-with-links/public/index.html create mode 100644 code/03-working-with-links/public/logo192.png create mode 100644 code/03-working-with-links/public/logo512.png create mode 100644 code/03-working-with-links/public/manifest.json create mode 100644 code/03-working-with-links/public/robots.txt create mode 100644 code/03-working-with-links/src/App.js create mode 100644 code/03-working-with-links/src/components/MainHeader.js create mode 100644 code/03-working-with-links/src/components/MainHeader.module.css create mode 100644 code/03-working-with-links/src/index.css create mode 100644 code/03-working-with-links/src/index.js create mode 100644 code/03-working-with-links/src/pages/Products.js create mode 100644 code/03-working-with-links/src/pages/Welcome.js create mode 100644 code/04-using-navlinks/package.json create mode 100644 code/04-using-navlinks/public/favicon.ico create mode 100644 code/04-using-navlinks/public/index.html create mode 100644 code/04-using-navlinks/public/logo192.png create mode 100644 code/04-using-navlinks/public/logo512.png create mode 100644 code/04-using-navlinks/public/manifest.json create mode 100644 code/04-using-navlinks/public/robots.txt create mode 100644 code/04-using-navlinks/src/App.js create mode 100644 code/04-using-navlinks/src/components/MainHeader.js create mode 100644 code/04-using-navlinks/src/components/MainHeader.module.css create mode 100644 code/04-using-navlinks/src/index.css create mode 100644 code/04-using-navlinks/src/index.js create mode 100644 code/04-using-navlinks/src/pages/Products.js create mode 100644 code/04-using-navlinks/src/pages/Welcome.js create mode 100644 code/05-extracting-route-params/package.json create mode 100644 code/05-extracting-route-params/public/favicon.ico create mode 100644 code/05-extracting-route-params/public/index.html create mode 100644 code/05-extracting-route-params/public/logo192.png create mode 100644 code/05-extracting-route-params/public/logo512.png create mode 100644 code/05-extracting-route-params/public/manifest.json create mode 100644 code/05-extracting-route-params/public/robots.txt create mode 100644 code/05-extracting-route-params/src/App.js create mode 100644 code/05-extracting-route-params/src/components/MainHeader.js create mode 100644 code/05-extracting-route-params/src/components/MainHeader.module.css create mode 100644 code/05-extracting-route-params/src/index.css create mode 100644 code/05-extracting-route-params/src/index.js create mode 100644 code/05-extracting-route-params/src/pages/ProductDetail.js create mode 100644 code/05-extracting-route-params/src/pages/Products.js create mode 100644 code/05-extracting-route-params/src/pages/Welcome.js create mode 100644 code/06-using-switch-and-exact/package.json create mode 100644 code/06-using-switch-and-exact/public/favicon.ico create mode 100644 code/06-using-switch-and-exact/public/index.html create mode 100644 code/06-using-switch-and-exact/public/logo192.png create mode 100644 code/06-using-switch-and-exact/public/logo512.png create mode 100644 code/06-using-switch-and-exact/public/manifest.json create mode 100644 code/06-using-switch-and-exact/public/robots.txt create mode 100644 code/06-using-switch-and-exact/src/App.js create mode 100644 code/06-using-switch-and-exact/src/components/MainHeader.js create mode 100644 code/06-using-switch-and-exact/src/components/MainHeader.module.css create mode 100644 code/06-using-switch-and-exact/src/index.css create mode 100644 code/06-using-switch-and-exact/src/index.js create mode 100644 code/06-using-switch-and-exact/src/pages/ProductDetail.js create mode 100644 code/06-using-switch-and-exact/src/pages/Products.js create mode 100644 code/06-using-switch-and-exact/src/pages/Welcome.js create mode 100644 code/07-working-with-nested-routes/package.json create mode 100644 code/07-working-with-nested-routes/public/favicon.ico create mode 100644 code/07-working-with-nested-routes/public/index.html create mode 100644 code/07-working-with-nested-routes/public/logo192.png create mode 100644 code/07-working-with-nested-routes/public/logo512.png create mode 100644 code/07-working-with-nested-routes/public/manifest.json create mode 100644 code/07-working-with-nested-routes/public/robots.txt create mode 100644 code/07-working-with-nested-routes/src/App.js create mode 100644 code/07-working-with-nested-routes/src/components/MainHeader.js create mode 100644 code/07-working-with-nested-routes/src/components/MainHeader.module.css create mode 100644 code/07-working-with-nested-routes/src/index.css create mode 100644 code/07-working-with-nested-routes/src/index.js create mode 100644 code/07-working-with-nested-routes/src/pages/ProductDetail.js create mode 100644 code/07-working-with-nested-routes/src/pages/Products.js create mode 100644 code/07-working-with-nested-routes/src/pages/Welcome.js create mode 100644 code/08-redirecting-the-user/package.json create mode 100644 code/08-redirecting-the-user/public/favicon.ico create mode 100644 code/08-redirecting-the-user/public/index.html create mode 100644 code/08-redirecting-the-user/public/logo192.png create mode 100644 code/08-redirecting-the-user/public/logo512.png create mode 100644 code/08-redirecting-the-user/public/manifest.json create mode 100644 code/08-redirecting-the-user/public/robots.txt create mode 100644 code/08-redirecting-the-user/src/App.js create mode 100644 code/08-redirecting-the-user/src/components/MainHeader.js create mode 100644 code/08-redirecting-the-user/src/components/MainHeader.module.css create mode 100644 code/08-redirecting-the-user/src/index.css create mode 100644 code/08-redirecting-the-user/src/index.js create mode 100644 code/08-redirecting-the-user/src/pages/ProductDetail.js create mode 100644 code/08-redirecting-the-user/src/pages/Products.js create mode 100644 code/08-redirecting-the-user/src/pages/Welcome.js create mode 100644 code/09-time-to-practice-starting-code/package.json create mode 100644 code/09-time-to-practice-starting-code/public/favicon.ico create mode 100644 code/09-time-to-practice-starting-code/public/index.html create mode 100644 code/09-time-to-practice-starting-code/public/logo192.png create mode 100644 code/09-time-to-practice-starting-code/public/logo512.png create mode 100644 code/09-time-to-practice-starting-code/public/manifest.json create mode 100644 code/09-time-to-practice-starting-code/public/robots.txt create mode 100644 code/09-time-to-practice-starting-code/src/App.js create mode 100644 code/09-time-to-practice-starting-code/src/components/UI/Card.js create mode 100644 code/09-time-to-practice-starting-code/src/components/UI/Card.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/UI/LoadingSpinner.js create mode 100644 code/09-time-to-practice-starting-code/src/components/UI/LoadingSpinner.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/comments/CommentItem.js create mode 100644 code/09-time-to-practice-starting-code/src/components/comments/CommentItem.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/comments/Comments.js create mode 100644 code/09-time-to-practice-starting-code/src/components/comments/Comments.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/comments/CommentsList.js create mode 100644 code/09-time-to-practice-starting-code/src/components/comments/CommentsList.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/comments/NewCommentForm.js create mode 100644 code/09-time-to-practice-starting-code/src/components/comments/NewCommentForm.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/layout/Layout.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/layout/MainNavigation.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/quotes/HighlightedQuote.js create mode 100644 code/09-time-to-practice-starting-code/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/quotes/NoQuotesFound.js create mode 100644 code/09-time-to-practice-starting-code/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.js create mode 100644 code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.js create mode 100644 code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.module.css create mode 100644 code/09-time-to-practice-starting-code/src/components/quotes/QuoteList.js create mode 100644 code/09-time-to-practice-starting-code/src/components/quotes/QuoteList.module.css create mode 100644 code/09-time-to-practice-starting-code/src/index.css create mode 100644 code/09-time-to-practice-starting-code/src/index.js create mode 100644 code/10-practice-redirecting-and-extracting-params/package.json create mode 100644 code/10-practice-redirecting-and-extracting-params/public/favicon.ico create mode 100644 code/10-practice-redirecting-and-extracting-params/public/index.html create mode 100644 code/10-practice-redirecting-and-extracting-params/public/logo192.png create mode 100644 code/10-practice-redirecting-and-extracting-params/public/logo512.png create mode 100644 code/10-practice-redirecting-and-extracting-params/public/manifest.json create mode 100644 code/10-practice-redirecting-and-extracting-params/public/robots.txt create mode 100644 code/10-practice-redirecting-and-extracting-params/src/App.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/UI/Card.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/UI/Card.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/UI/LoadingSpinner.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/UI/LoadingSpinner.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentItem.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentItem.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/comments/Comments.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/comments/Comments.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentsList.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentsList.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/comments/NewCommentForm.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/comments/NewCommentForm.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/layout/Layout.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/layout/MainNavigation.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/quotes/HighlightedQuote.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/quotes/NoQuotesFound.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteItem.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteItem.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteList.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteList.module.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/index.css create mode 100644 code/10-practice-redirecting-and-extracting-params/src/index.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/pages/AllQuotes.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/pages/NewQuote.js create mode 100644 code/10-practice-redirecting-and-extracting-params/src/pages/QuoteDetail.js create mode 100644 code/11-practicing-nested-routes/package.json create mode 100644 code/11-practicing-nested-routes/public/favicon.ico create mode 100644 code/11-practicing-nested-routes/public/index.html create mode 100644 code/11-practicing-nested-routes/public/logo192.png create mode 100644 code/11-practicing-nested-routes/public/logo512.png create mode 100644 code/11-practicing-nested-routes/public/manifest.json create mode 100644 code/11-practicing-nested-routes/public/robots.txt create mode 100644 code/11-practicing-nested-routes/src/App.js create mode 100644 code/11-practicing-nested-routes/src/components/UI/Card.js create mode 100644 code/11-practicing-nested-routes/src/components/UI/Card.module.css create mode 100644 code/11-practicing-nested-routes/src/components/UI/LoadingSpinner.js create mode 100644 code/11-practicing-nested-routes/src/components/UI/LoadingSpinner.module.css create mode 100644 code/11-practicing-nested-routes/src/components/comments/CommentItem.js create mode 100644 code/11-practicing-nested-routes/src/components/comments/CommentItem.module.css create mode 100644 code/11-practicing-nested-routes/src/components/comments/Comments.js create mode 100644 code/11-practicing-nested-routes/src/components/comments/Comments.module.css create mode 100644 code/11-practicing-nested-routes/src/components/comments/CommentsList.js create mode 100644 code/11-practicing-nested-routes/src/components/comments/CommentsList.module.css create mode 100644 code/11-practicing-nested-routes/src/components/comments/NewCommentForm.js create mode 100644 code/11-practicing-nested-routes/src/components/comments/NewCommentForm.module.css create mode 100644 code/11-practicing-nested-routes/src/components/layout/Layout.module.css create mode 100644 code/11-practicing-nested-routes/src/components/layout/MainNavigation.module.css create mode 100644 code/11-practicing-nested-routes/src/components/quotes/HighlightedQuote.js create mode 100644 code/11-practicing-nested-routes/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/11-practicing-nested-routes/src/components/quotes/NoQuotesFound.js create mode 100644 code/11-practicing-nested-routes/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/11-practicing-nested-routes/src/components/quotes/QuoteForm.js create mode 100644 code/11-practicing-nested-routes/src/components/quotes/QuoteForm.module.css create mode 100644 code/11-practicing-nested-routes/src/components/quotes/QuoteItem.js create mode 100644 code/11-practicing-nested-routes/src/components/quotes/QuoteItem.module.css create mode 100644 code/11-practicing-nested-routes/src/components/quotes/QuoteList.js create mode 100644 code/11-practicing-nested-routes/src/components/quotes/QuoteList.module.css create mode 100644 code/11-practicing-nested-routes/src/index.css create mode 100644 code/11-practicing-nested-routes/src/index.js create mode 100644 code/11-practicing-nested-routes/src/pages/AllQuotes.js create mode 100644 code/11-practicing-nested-routes/src/pages/NewQuote.js create mode 100644 code/11-practicing-nested-routes/src/pages/QuoteDetail.js create mode 100644 code/12-adding-a-layout-wrapper/package.json create mode 100644 code/12-adding-a-layout-wrapper/public/favicon.ico create mode 100644 code/12-adding-a-layout-wrapper/public/index.html create mode 100644 code/12-adding-a-layout-wrapper/public/logo192.png create mode 100644 code/12-adding-a-layout-wrapper/public/logo512.png create mode 100644 code/12-adding-a-layout-wrapper/public/manifest.json create mode 100644 code/12-adding-a-layout-wrapper/public/robots.txt create mode 100644 code/12-adding-a-layout-wrapper/src/App.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/UI/Card.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/UI/Card.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/UI/LoadingSpinner.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/UI/LoadingSpinner.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/comments/CommentItem.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/comments/CommentItem.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/comments/Comments.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/comments/Comments.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/comments/CommentsList.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/comments/CommentsList.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/comments/NewCommentForm.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/comments/NewCommentForm.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/layout/Layout.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/layout/Layout.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/layout/MainNavigation.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/layout/MainNavigation.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/quotes/HighlightedQuote.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/quotes/NoQuotesFound.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/quotes/QuoteItem.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/quotes/QuoteItem.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/components/quotes/QuoteList.js create mode 100644 code/12-adding-a-layout-wrapper/src/components/quotes/QuoteList.module.css create mode 100644 code/12-adding-a-layout-wrapper/src/index.css create mode 100644 code/12-adding-a-layout-wrapper/src/index.js create mode 100644 code/12-adding-a-layout-wrapper/src/pages/AllQuotes.js create mode 100644 code/12-adding-a-layout-wrapper/src/pages/NewQuote.js create mode 100644 code/12-adding-a-layout-wrapper/src/pages/QuoteDetail.js create mode 100644 code/13-adding-dummy-data-and-more-content/package.json create mode 100644 code/13-adding-dummy-data-and-more-content/public/favicon.ico create mode 100644 code/13-adding-dummy-data-and-more-content/public/index.html create mode 100644 code/13-adding-dummy-data-and-more-content/public/logo192.png create mode 100644 code/13-adding-dummy-data-and-more-content/public/logo512.png create mode 100644 code/13-adding-dummy-data-and-more-content/public/manifest.json create mode 100644 code/13-adding-dummy-data-and-more-content/public/robots.txt create mode 100644 code/13-adding-dummy-data-and-more-content/src/App.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/UI/Card.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/UI/Card.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/UI/LoadingSpinner.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/UI/LoadingSpinner.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/comments/CommentItem.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/comments/CommentItem.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/comments/Comments.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/comments/Comments.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/comments/CommentsList.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/comments/CommentsList.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/comments/NewCommentForm.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/comments/NewCommentForm.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/layout/Layout.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/layout/Layout.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/layout/MainNavigation.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/layout/MainNavigation.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/quotes/HighlightedQuote.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/quotes/NoQuotesFound.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteItem.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteItem.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteList.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteList.module.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/index.css create mode 100644 code/13-adding-dummy-data-and-more-content/src/index.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/pages/AllQuotes.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/pages/NewQuote.js create mode 100644 code/13-adding-dummy-data-and-more-content/src/pages/QuoteDetail.js create mode 100644 code/14-outputting-data-on-details-page/package.json create mode 100644 code/14-outputting-data-on-details-page/public/favicon.ico create mode 100644 code/14-outputting-data-on-details-page/public/index.html create mode 100644 code/14-outputting-data-on-details-page/public/logo192.png create mode 100644 code/14-outputting-data-on-details-page/public/logo512.png create mode 100644 code/14-outputting-data-on-details-page/public/manifest.json create mode 100644 code/14-outputting-data-on-details-page/public/robots.txt create mode 100644 code/14-outputting-data-on-details-page/src/App.js create mode 100644 code/14-outputting-data-on-details-page/src/components/UI/Card.js create mode 100644 code/14-outputting-data-on-details-page/src/components/UI/Card.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/UI/LoadingSpinner.js create mode 100644 code/14-outputting-data-on-details-page/src/components/UI/LoadingSpinner.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/comments/CommentItem.js create mode 100644 code/14-outputting-data-on-details-page/src/components/comments/CommentItem.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/comments/Comments.js create mode 100644 code/14-outputting-data-on-details-page/src/components/comments/Comments.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/comments/CommentsList.js create mode 100644 code/14-outputting-data-on-details-page/src/components/comments/CommentsList.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/comments/NewCommentForm.js create mode 100644 code/14-outputting-data-on-details-page/src/components/comments/NewCommentForm.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/layout/Layout.js create mode 100644 code/14-outputting-data-on-details-page/src/components/layout/Layout.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/layout/MainNavigation.js create mode 100644 code/14-outputting-data-on-details-page/src/components/layout/MainNavigation.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/quotes/HighlightedQuote.js create mode 100644 code/14-outputting-data-on-details-page/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/quotes/NoQuotesFound.js create mode 100644 code/14-outputting-data-on-details-page/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.js create mode 100644 code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/quotes/QuoteItem.js create mode 100644 code/14-outputting-data-on-details-page/src/components/quotes/QuoteItem.module.css create mode 100644 code/14-outputting-data-on-details-page/src/components/quotes/QuoteList.js create mode 100644 code/14-outputting-data-on-details-page/src/components/quotes/QuoteList.module.css create mode 100644 code/14-outputting-data-on-details-page/src/index.css create mode 100644 code/14-outputting-data-on-details-page/src/index.js create mode 100644 code/14-outputting-data-on-details-page/src/pages/AllQuotes.js create mode 100644 code/14-outputting-data-on-details-page/src/pages/NewQuote.js create mode 100644 code/14-outputting-data-on-details-page/src/pages/QuoteDetail.js create mode 100644 code/15-adding-a-notfound-page/package.json create mode 100644 code/15-adding-a-notfound-page/public/favicon.ico create mode 100644 code/15-adding-a-notfound-page/public/index.html create mode 100644 code/15-adding-a-notfound-page/public/logo192.png create mode 100644 code/15-adding-a-notfound-page/public/logo512.png create mode 100644 code/15-adding-a-notfound-page/public/manifest.json create mode 100644 code/15-adding-a-notfound-page/public/robots.txt create mode 100644 code/15-adding-a-notfound-page/src/App.js create mode 100644 code/15-adding-a-notfound-page/src/components/UI/Card.js create mode 100644 code/15-adding-a-notfound-page/src/components/UI/Card.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/UI/LoadingSpinner.js create mode 100644 code/15-adding-a-notfound-page/src/components/UI/LoadingSpinner.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/comments/CommentItem.js create mode 100644 code/15-adding-a-notfound-page/src/components/comments/CommentItem.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/comments/Comments.js create mode 100644 code/15-adding-a-notfound-page/src/components/comments/Comments.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/comments/CommentsList.js create mode 100644 code/15-adding-a-notfound-page/src/components/comments/CommentsList.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/comments/NewCommentForm.js create mode 100644 code/15-adding-a-notfound-page/src/components/comments/NewCommentForm.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/layout/Layout.js create mode 100644 code/15-adding-a-notfound-page/src/components/layout/Layout.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/layout/MainNavigation.js create mode 100644 code/15-adding-a-notfound-page/src/components/layout/MainNavigation.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/quotes/HighlightedQuote.js create mode 100644 code/15-adding-a-notfound-page/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/quotes/NoQuotesFound.js create mode 100644 code/15-adding-a-notfound-page/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.js create mode 100644 code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/quotes/QuoteItem.js create mode 100644 code/15-adding-a-notfound-page/src/components/quotes/QuoteItem.module.css create mode 100644 code/15-adding-a-notfound-page/src/components/quotes/QuoteList.js create mode 100644 code/15-adding-a-notfound-page/src/components/quotes/QuoteList.module.css create mode 100644 code/15-adding-a-notfound-page/src/index.css create mode 100644 code/15-adding-a-notfound-page/src/index.js create mode 100644 code/15-adding-a-notfound-page/src/pages/AllQuotes.js create mode 100644 code/15-adding-a-notfound-page/src/pages/NewQuote.js create mode 100644 code/15-adding-a-notfound-page/src/pages/NotFound.js create mode 100644 code/15-adding-a-notfound-page/src/pages/QuoteDetail.js create mode 100644 code/16-implementing-programmatic-navigation/package.json create mode 100644 code/16-implementing-programmatic-navigation/public/favicon.ico create mode 100644 code/16-implementing-programmatic-navigation/public/index.html create mode 100644 code/16-implementing-programmatic-navigation/public/logo192.png create mode 100644 code/16-implementing-programmatic-navigation/public/logo512.png create mode 100644 code/16-implementing-programmatic-navigation/public/manifest.json create mode 100644 code/16-implementing-programmatic-navigation/public/robots.txt create mode 100644 code/16-implementing-programmatic-navigation/src/App.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/UI/Card.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/UI/Card.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/UI/LoadingSpinner.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/UI/LoadingSpinner.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/comments/CommentItem.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/comments/CommentItem.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/comments/Comments.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/comments/Comments.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/comments/CommentsList.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/comments/CommentsList.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/comments/NewCommentForm.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/comments/NewCommentForm.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/layout/Layout.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/layout/Layout.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/layout/MainNavigation.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/layout/MainNavigation.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/quotes/HighlightedQuote.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/quotes/NoQuotesFound.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/quotes/QuoteItem.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/quotes/QuoteItem.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/components/quotes/QuoteList.js create mode 100644 code/16-implementing-programmatic-navigation/src/components/quotes/QuoteList.module.css create mode 100644 code/16-implementing-programmatic-navigation/src/index.css create mode 100644 code/16-implementing-programmatic-navigation/src/index.js create mode 100644 code/16-implementing-programmatic-navigation/src/pages/AllQuotes.js create mode 100644 code/16-implementing-programmatic-navigation/src/pages/NewQuote.js create mode 100644 code/16-implementing-programmatic-navigation/src/pages/NotFound.js create mode 100644 code/16-implementing-programmatic-navigation/src/pages/QuoteDetail.js create mode 100644 code/17-preventing-unwanted-route-transitions/package.json create mode 100644 code/17-preventing-unwanted-route-transitions/public/favicon.ico create mode 100644 code/17-preventing-unwanted-route-transitions/public/index.html create mode 100644 code/17-preventing-unwanted-route-transitions/public/logo192.png create mode 100644 code/17-preventing-unwanted-route-transitions/public/logo512.png create mode 100644 code/17-preventing-unwanted-route-transitions/public/manifest.json create mode 100644 code/17-preventing-unwanted-route-transitions/public/robots.txt create mode 100644 code/17-preventing-unwanted-route-transitions/src/App.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/UI/Card.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/UI/Card.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/UI/LoadingSpinner.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/UI/LoadingSpinner.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/comments/CommentItem.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/comments/CommentItem.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/comments/Comments.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/comments/Comments.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/comments/CommentsList.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/comments/CommentsList.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/comments/NewCommentForm.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/comments/NewCommentForm.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/layout/Layout.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/layout/Layout.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/layout/MainNavigation.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/layout/MainNavigation.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/quotes/HighlightedQuote.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/quotes/NoQuotesFound.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteItem.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteItem.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteList.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteList.module.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/index.css create mode 100644 code/17-preventing-unwanted-route-transitions/src/index.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/pages/AllQuotes.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/pages/NewQuote.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/pages/NotFound.js create mode 100644 code/17-preventing-unwanted-route-transitions/src/pages/QuoteDetail.js create mode 100644 code/18-working-with-query-params/package.json create mode 100644 code/18-working-with-query-params/public/favicon.ico create mode 100644 code/18-working-with-query-params/public/index.html create mode 100644 code/18-working-with-query-params/public/logo192.png create mode 100644 code/18-working-with-query-params/public/logo512.png create mode 100644 code/18-working-with-query-params/public/manifest.json create mode 100644 code/18-working-with-query-params/public/robots.txt create mode 100644 code/18-working-with-query-params/src/App.js create mode 100644 code/18-working-with-query-params/src/components/UI/Card.js create mode 100644 code/18-working-with-query-params/src/components/UI/Card.module.css create mode 100644 code/18-working-with-query-params/src/components/UI/LoadingSpinner.js create mode 100644 code/18-working-with-query-params/src/components/UI/LoadingSpinner.module.css create mode 100644 code/18-working-with-query-params/src/components/comments/CommentItem.js create mode 100644 code/18-working-with-query-params/src/components/comments/CommentItem.module.css create mode 100644 code/18-working-with-query-params/src/components/comments/Comments.js create mode 100644 code/18-working-with-query-params/src/components/comments/Comments.module.css create mode 100644 code/18-working-with-query-params/src/components/comments/CommentsList.js create mode 100644 code/18-working-with-query-params/src/components/comments/CommentsList.module.css create mode 100644 code/18-working-with-query-params/src/components/comments/NewCommentForm.js create mode 100644 code/18-working-with-query-params/src/components/comments/NewCommentForm.module.css create mode 100644 code/18-working-with-query-params/src/components/layout/Layout.js create mode 100644 code/18-working-with-query-params/src/components/layout/Layout.module.css create mode 100644 code/18-working-with-query-params/src/components/layout/MainNavigation.js create mode 100644 code/18-working-with-query-params/src/components/layout/MainNavigation.module.css create mode 100644 code/18-working-with-query-params/src/components/quotes/HighlightedQuote.js create mode 100644 code/18-working-with-query-params/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/18-working-with-query-params/src/components/quotes/NoQuotesFound.js create mode 100644 code/18-working-with-query-params/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/18-working-with-query-params/src/components/quotes/QuoteForm.js create mode 100644 code/18-working-with-query-params/src/components/quotes/QuoteForm.module.css create mode 100644 code/18-working-with-query-params/src/components/quotes/QuoteItem.js create mode 100644 code/18-working-with-query-params/src/components/quotes/QuoteItem.module.css create mode 100644 code/18-working-with-query-params/src/components/quotes/QuoteList.js create mode 100644 code/18-working-with-query-params/src/components/quotes/QuoteList.module.css create mode 100644 code/18-working-with-query-params/src/index.css create mode 100644 code/18-working-with-query-params/src/index.js create mode 100644 code/18-working-with-query-params/src/pages/AllQuotes.js create mode 100644 code/18-working-with-query-params/src/pages/NewQuote.js create mode 100644 code/18-working-with-query-params/src/pages/NotFound.js create mode 100644 code/18-working-with-query-params/src/pages/QuoteDetail.js create mode 100644 code/19-writing-more-flexible-routing-code/package.json create mode 100644 code/19-writing-more-flexible-routing-code/public/favicon.ico create mode 100644 code/19-writing-more-flexible-routing-code/public/index.html create mode 100644 code/19-writing-more-flexible-routing-code/public/logo192.png create mode 100644 code/19-writing-more-flexible-routing-code/public/logo512.png create mode 100644 code/19-writing-more-flexible-routing-code/public/manifest.json create mode 100644 code/19-writing-more-flexible-routing-code/public/robots.txt create mode 100644 code/19-writing-more-flexible-routing-code/src/App.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/UI/Card.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/UI/Card.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/UI/LoadingSpinner.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/UI/LoadingSpinner.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/comments/CommentItem.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/comments/CommentItem.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/comments/Comments.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/comments/Comments.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/comments/CommentsList.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/comments/CommentsList.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/comments/NewCommentForm.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/comments/NewCommentForm.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/layout/Layout.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/layout/Layout.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/layout/MainNavigation.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/layout/MainNavigation.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/quotes/HighlightedQuote.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/quotes/NoQuotesFound.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteItem.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteItem.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteList.js create mode 100644 code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteList.module.css create mode 100644 code/19-writing-more-flexible-routing-code/src/index.css create mode 100644 code/19-writing-more-flexible-routing-code/src/index.js create mode 100644 code/19-writing-more-flexible-routing-code/src/pages/AllQuotes.js create mode 100644 code/19-writing-more-flexible-routing-code/src/pages/NewQuote.js create mode 100644 code/19-writing-more-flexible-routing-code/src/pages/NotFound.js create mode 100644 code/19-writing-more-flexible-routing-code/src/pages/QuoteDetail.js create mode 100644 code/20-sending-getting-quote-data/package.json create mode 100644 code/20-sending-getting-quote-data/public/favicon.ico create mode 100644 code/20-sending-getting-quote-data/public/index.html create mode 100644 code/20-sending-getting-quote-data/public/logo192.png create mode 100644 code/20-sending-getting-quote-data/public/logo512.png create mode 100644 code/20-sending-getting-quote-data/public/manifest.json create mode 100644 code/20-sending-getting-quote-data/public/robots.txt create mode 100644 code/20-sending-getting-quote-data/src/App.js create mode 100644 code/20-sending-getting-quote-data/src/components/UI/Card.js create mode 100644 code/20-sending-getting-quote-data/src/components/UI/Card.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/UI/LoadingSpinner.js create mode 100644 code/20-sending-getting-quote-data/src/components/UI/LoadingSpinner.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/comments/CommentItem.js create mode 100644 code/20-sending-getting-quote-data/src/components/comments/CommentItem.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/comments/Comments.js create mode 100644 code/20-sending-getting-quote-data/src/components/comments/Comments.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/comments/CommentsList.js create mode 100644 code/20-sending-getting-quote-data/src/components/comments/CommentsList.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/comments/NewCommentForm.js create mode 100644 code/20-sending-getting-quote-data/src/components/comments/NewCommentForm.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/layout/Layout.js create mode 100644 code/20-sending-getting-quote-data/src/components/layout/Layout.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/layout/MainNavigation.js create mode 100644 code/20-sending-getting-quote-data/src/components/layout/MainNavigation.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/quotes/HighlightedQuote.js create mode 100644 code/20-sending-getting-quote-data/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/quotes/NoQuotesFound.js create mode 100644 code/20-sending-getting-quote-data/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.js create mode 100644 code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/quotes/QuoteItem.js create mode 100644 code/20-sending-getting-quote-data/src/components/quotes/QuoteItem.module.css create mode 100644 code/20-sending-getting-quote-data/src/components/quotes/QuoteList.js create mode 100644 code/20-sending-getting-quote-data/src/components/quotes/QuoteList.module.css create mode 100644 code/20-sending-getting-quote-data/src/hooks/use-http.js create mode 100644 code/20-sending-getting-quote-data/src/index.css create mode 100644 code/20-sending-getting-quote-data/src/index.js create mode 100644 code/20-sending-getting-quote-data/src/lib/api.js create mode 100644 code/20-sending-getting-quote-data/src/pages/AllQuotes.js create mode 100644 code/20-sending-getting-quote-data/src/pages/NewQuote.js create mode 100644 code/20-sending-getting-quote-data/src/pages/NotFound.js create mode 100644 code/20-sending-getting-quote-data/src/pages/QuoteDetail.js create mode 100644 code/21-finished/package.json create mode 100644 code/21-finished/public/favicon.ico create mode 100644 code/21-finished/public/index.html create mode 100644 code/21-finished/public/logo192.png create mode 100644 code/21-finished/public/logo512.png create mode 100644 code/21-finished/public/manifest.json create mode 100644 code/21-finished/public/robots.txt create mode 100644 code/21-finished/src/App.js create mode 100644 code/21-finished/src/components/UI/Card.js create mode 100644 code/21-finished/src/components/UI/Card.module.css create mode 100644 code/21-finished/src/components/UI/LoadingSpinner.js create mode 100644 code/21-finished/src/components/UI/LoadingSpinner.module.css create mode 100644 code/21-finished/src/components/comments/CommentItem.js create mode 100644 code/21-finished/src/components/comments/CommentItem.module.css create mode 100644 code/21-finished/src/components/comments/Comments.js create mode 100644 code/21-finished/src/components/comments/Comments.module.css create mode 100644 code/21-finished/src/components/comments/CommentsList.js create mode 100644 code/21-finished/src/components/comments/CommentsList.module.css create mode 100644 code/21-finished/src/components/comments/NewCommentForm.js create mode 100644 code/21-finished/src/components/comments/NewCommentForm.module.css create mode 100644 code/21-finished/src/components/layout/Layout.js create mode 100644 code/21-finished/src/components/layout/Layout.module.css create mode 100644 code/21-finished/src/components/layout/MainNavigation.js create mode 100644 code/21-finished/src/components/layout/MainNavigation.module.css create mode 100644 code/21-finished/src/components/quotes/HighlightedQuote.js create mode 100644 code/21-finished/src/components/quotes/HighlightedQuote.module.css create mode 100644 code/21-finished/src/components/quotes/NoQuotesFound.js create mode 100644 code/21-finished/src/components/quotes/NoQuotesFound.module.css create mode 100644 code/21-finished/src/components/quotes/QuoteForm.js create mode 100644 code/21-finished/src/components/quotes/QuoteForm.module.css create mode 100644 code/21-finished/src/components/quotes/QuoteItem.js create mode 100644 code/21-finished/src/components/quotes/QuoteItem.module.css create mode 100644 code/21-finished/src/components/quotes/QuoteList.js create mode 100644 code/21-finished/src/components/quotes/QuoteList.module.css create mode 100644 code/21-finished/src/hooks/use-http.js create mode 100644 code/21-finished/src/index.css create mode 100644 code/21-finished/src/index.js create mode 100644 code/21-finished/src/lib/api.js create mode 100644 code/21-finished/src/pages/AllQuotes.js create mode 100644 code/21-finished/src/pages/NewQuote.js create mode 100644 code/21-finished/src/pages/NotFound.js create mode 100644 code/21-finished/src/pages/QuoteDetail.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 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 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..4a194e6079 --- /dev/null +++ b/code/01-starting-project/src/App.js @@ -0,0 +1,9 @@ +function App() { + return ( +
+

Let's get started!

+
+ ); +} + +export default App; diff --git a/code/01-starting-project/src/index.css b/code/01-starting-project/src/index.css new file mode 100644 index 0000000000..72399cc5c6 --- /dev/null +++ b/code/01-starting-project/src/index.css @@ -0,0 +1,15 @@ +@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; +} + 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-defining-and-using-routes/package.json b/code/02-defining-and-using-routes/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/02-defining-and-using-routes/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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-defining-and-using-routes/public/favicon.ico b/code/02-defining-and-using-routes/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-defining-and-using-routes/public/index.html b/code/02-defining-and-using-routes/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/02-defining-and-using-routes/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/code/02-defining-and-using-routes/public/logo192.png b/code/02-defining-and-using-routes/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-defining-and-using-routes/public/manifest.json b/code/02-defining-and-using-routes/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/02-defining-and-using-routes/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-defining-and-using-routes/public/robots.txt b/code/02-defining-and-using-routes/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/02-defining-and-using-routes/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/02-defining-and-using-routes/src/App.js b/code/02-defining-and-using-routes/src/App.js new file mode 100644 index 0000000000..86364d8498 --- /dev/null +++ b/code/02-defining-and-using-routes/src/App.js @@ -0,0 +1,22 @@ +import { Route } from 'react-router-dom'; + +import Welcome from './pages/Welcome'; +import Products from './pages/Products'; + +function App() { + return ( +
+ + + + + + +
+ ); +} + +export default App; + +// our-domain.com/welcome => Welcome Component +// our-domain.com/products => Products Component \ No newline at end of file diff --git a/code/02-defining-and-using-routes/src/index.css b/code/02-defining-and-using-routes/src/index.css new file mode 100644 index 0000000000..72399cc5c6 --- /dev/null +++ b/code/02-defining-and-using-routes/src/index.css @@ -0,0 +1,15 @@ +@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; +} + diff --git a/code/02-defining-and-using-routes/src/index.js b/code/02-defining-and-using-routes/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/02-defining-and-using-routes/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/02-defining-and-using-routes/src/pages/Products.js b/code/02-defining-and-using-routes/src/pages/Products.js new file mode 100644 index 0000000000..5755202240 --- /dev/null +++ b/code/02-defining-and-using-routes/src/pages/Products.js @@ -0,0 +1,5 @@ +const Products = () => { + return

The Products Page

; +}; + +export default Products; \ No newline at end of file diff --git a/code/02-defining-and-using-routes/src/pages/Welcome.js b/code/02-defining-and-using-routes/src/pages/Welcome.js new file mode 100644 index 0000000000..1453342bae --- /dev/null +++ b/code/02-defining-and-using-routes/src/pages/Welcome.js @@ -0,0 +1,5 @@ +const Welcome = () => { + return

The Welcome Page

; +}; + +export default Welcome; \ No newline at end of file diff --git a/code/03-working-with-links/package.json b/code/03-working-with-links/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/03-working-with-links/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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-working-with-links/public/favicon.ico b/code/03-working-with-links/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-working-with-links/public/index.html b/code/03-working-with-links/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/03-working-with-links/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/code/03-working-with-links/public/logo192.png b/code/03-working-with-links/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-working-with-links/public/manifest.json b/code/03-working-with-links/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/03-working-with-links/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-working-with-links/public/robots.txt b/code/03-working-with-links/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/03-working-with-links/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/03-working-with-links/src/App.js b/code/03-working-with-links/src/App.js new file mode 100644 index 0000000000..52f9031155 --- /dev/null +++ b/code/03-working-with-links/src/App.js @@ -0,0 +1,26 @@ +import { Route } from 'react-router-dom'; + +import Welcome from './pages/Welcome'; +import Products from './pages/Products'; +import MainHeader from './components/MainHeader'; + +function App() { + return ( +
+ +
+ + + + + + +
+
+ ); +} + +export default App; + +// our-domain.com/welcome => Welcome Component +// our-domain.com/products => Products Component diff --git a/code/03-working-with-links/src/components/MainHeader.js b/code/03-working-with-links/src/components/MainHeader.js new file mode 100644 index 0000000000..66befe0eee --- /dev/null +++ b/code/03-working-with-links/src/components/MainHeader.js @@ -0,0 +1,22 @@ +import { Link } from 'react-router-dom'; + +import classes from './MainHeader.module.css'; + +const MainHeader = () => { + return ( +
+ +
+ ); +}; + +export default MainHeader; diff --git a/code/03-working-with-links/src/components/MainHeader.module.css b/code/03-working-with-links/src/components/MainHeader.module.css new file mode 100644 index 0000000000..ea62a274cf --- /dev/null +++ b/code/03-working-with-links/src/components/MainHeader.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + background-color: #044599; + padding: 0 10%; +} + +.header nav { + height: 100%; +} + +.header ul { + height: 100%; + list-style: none; + display: flex; + padding: 0; + margin: 0; + align-items: center; + justify-content: center; +} + +.header li { + margin: 0 1rem; + width: 5rem; +} + +.header a { + color: white; + text-decoration: none; +} + +.header a:hover, +.header a:active { + color: #95bcf0; + padding-bottom: 0.25rem; + border-bottom: 4px solid #95bcf0; +} \ No newline at end of file diff --git a/code/03-working-with-links/src/index.css b/code/03-working-with-links/src/index.css new file mode 100644 index 0000000000..44a860502a --- /dev/null +++ b/code/03-working-with-links/src/index.css @@ -0,0 +1,26 @@ +@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: #e0e9f5; +} + +main { + margin-top: 7rem; + text-align: center; +} + +h1, +h2, +h3, +p { + color: #042b5f; +} diff --git a/code/03-working-with-links/src/index.js b/code/03-working-with-links/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/03-working-with-links/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/03-working-with-links/src/pages/Products.js b/code/03-working-with-links/src/pages/Products.js new file mode 100644 index 0000000000..5755202240 --- /dev/null +++ b/code/03-working-with-links/src/pages/Products.js @@ -0,0 +1,5 @@ +const Products = () => { + return

The Products Page

; +}; + +export default Products; \ No newline at end of file diff --git a/code/03-working-with-links/src/pages/Welcome.js b/code/03-working-with-links/src/pages/Welcome.js new file mode 100644 index 0000000000..1453342bae --- /dev/null +++ b/code/03-working-with-links/src/pages/Welcome.js @@ -0,0 +1,5 @@ +const Welcome = () => { + return

The Welcome Page

; +}; + +export default Welcome; \ No newline at end of file diff --git a/code/04-using-navlinks/package.json b/code/04-using-navlinks/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/04-using-navlinks/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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-using-navlinks/public/favicon.ico b/code/04-using-navlinks/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-using-navlinks/public/index.html b/code/04-using-navlinks/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/04-using-navlinks/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/code/04-using-navlinks/public/logo192.png b/code/04-using-navlinks/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-using-navlinks/public/manifest.json b/code/04-using-navlinks/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/04-using-navlinks/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-using-navlinks/public/robots.txt b/code/04-using-navlinks/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/04-using-navlinks/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/04-using-navlinks/src/App.js b/code/04-using-navlinks/src/App.js new file mode 100644 index 0000000000..52f9031155 --- /dev/null +++ b/code/04-using-navlinks/src/App.js @@ -0,0 +1,26 @@ +import { Route } from 'react-router-dom'; + +import Welcome from './pages/Welcome'; +import Products from './pages/Products'; +import MainHeader from './components/MainHeader'; + +function App() { + return ( +
+ +
+ + + + + + +
+
+ ); +} + +export default App; + +// our-domain.com/welcome => Welcome Component +// our-domain.com/products => Products Component diff --git a/code/04-using-navlinks/src/components/MainHeader.js b/code/04-using-navlinks/src/components/MainHeader.js new file mode 100644 index 0000000000..5f5b28db4e --- /dev/null +++ b/code/04-using-navlinks/src/components/MainHeader.js @@ -0,0 +1,26 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainHeader.module.css'; + +const MainHeader = () => { + return ( +
+ +
+ ); +}; + +export default MainHeader; diff --git a/code/04-using-navlinks/src/components/MainHeader.module.css b/code/04-using-navlinks/src/components/MainHeader.module.css new file mode 100644 index 0000000000..0d792b4c0d --- /dev/null +++ b/code/04-using-navlinks/src/components/MainHeader.module.css @@ -0,0 +1,38 @@ +.header { + width: 100%; + height: 5rem; + background-color: #044599; + padding: 0 10%; +} + +.header nav { + height: 100%; +} + +.header ul { + height: 100%; + list-style: none; + display: flex; + padding: 0; + margin: 0; + align-items: center; + justify-content: center; +} + +.header li { + margin: 0 1rem; + width: 5rem; +} + +.header a { + color: white; + text-decoration: none; +} + +.header a:hover, +.header a:active, +.header a.active { + color: #95bcf0; + padding-bottom: 0.25rem; + border-bottom: 4px solid #95bcf0; +} \ No newline at end of file diff --git a/code/04-using-navlinks/src/index.css b/code/04-using-navlinks/src/index.css new file mode 100644 index 0000000000..44a860502a --- /dev/null +++ b/code/04-using-navlinks/src/index.css @@ -0,0 +1,26 @@ +@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: #e0e9f5; +} + +main { + margin-top: 7rem; + text-align: center; +} + +h1, +h2, +h3, +p { + color: #042b5f; +} diff --git a/code/04-using-navlinks/src/index.js b/code/04-using-navlinks/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/04-using-navlinks/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/04-using-navlinks/src/pages/Products.js b/code/04-using-navlinks/src/pages/Products.js new file mode 100644 index 0000000000..5755202240 --- /dev/null +++ b/code/04-using-navlinks/src/pages/Products.js @@ -0,0 +1,5 @@ +const Products = () => { + return

The Products Page

; +}; + +export default Products; \ No newline at end of file diff --git a/code/04-using-navlinks/src/pages/Welcome.js b/code/04-using-navlinks/src/pages/Welcome.js new file mode 100644 index 0000000000..1453342bae --- /dev/null +++ b/code/04-using-navlinks/src/pages/Welcome.js @@ -0,0 +1,5 @@ +const Welcome = () => { + return

The Welcome Page

; +}; + +export default Welcome; \ No newline at end of file diff --git a/code/05-extracting-route-params/package.json b/code/05-extracting-route-params/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/05-extracting-route-params/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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-extracting-route-params/public/favicon.ico b/code/05-extracting-route-params/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-extracting-route-params/public/index.html b/code/05-extracting-route-params/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/05-extracting-route-params/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/code/05-extracting-route-params/public/logo192.png b/code/05-extracting-route-params/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-extracting-route-params/public/manifest.json b/code/05-extracting-route-params/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/05-extracting-route-params/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-extracting-route-params/public/robots.txt b/code/05-extracting-route-params/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/05-extracting-route-params/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/05-extracting-route-params/src/App.js b/code/05-extracting-route-params/src/App.js new file mode 100644 index 0000000000..a6fbf09ba1 --- /dev/null +++ b/code/05-extracting-route-params/src/App.js @@ -0,0 +1,31 @@ +import { Route } from 'react-router-dom'; + +import Welcome from './pages/Welcome'; +import Products from './pages/Products'; +import ProductDetail from './pages/ProductDetail'; +import MainHeader from './components/MainHeader'; + +function App() { + return ( +
+ +
+ + + + + + + + + +
+
+ ); +} + +export default App; + +// our-domain.com/welcome => Welcome Component +// our-domain.com/products => Products Component +// our-domain.com/product-detail/a-book diff --git a/code/05-extracting-route-params/src/components/MainHeader.js b/code/05-extracting-route-params/src/components/MainHeader.js new file mode 100644 index 0000000000..5f5b28db4e --- /dev/null +++ b/code/05-extracting-route-params/src/components/MainHeader.js @@ -0,0 +1,26 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainHeader.module.css'; + +const MainHeader = () => { + return ( +
+ +
+ ); +}; + +export default MainHeader; diff --git a/code/05-extracting-route-params/src/components/MainHeader.module.css b/code/05-extracting-route-params/src/components/MainHeader.module.css new file mode 100644 index 0000000000..0d792b4c0d --- /dev/null +++ b/code/05-extracting-route-params/src/components/MainHeader.module.css @@ -0,0 +1,38 @@ +.header { + width: 100%; + height: 5rem; + background-color: #044599; + padding: 0 10%; +} + +.header nav { + height: 100%; +} + +.header ul { + height: 100%; + list-style: none; + display: flex; + padding: 0; + margin: 0; + align-items: center; + justify-content: center; +} + +.header li { + margin: 0 1rem; + width: 5rem; +} + +.header a { + color: white; + text-decoration: none; +} + +.header a:hover, +.header a:active, +.header a.active { + color: #95bcf0; + padding-bottom: 0.25rem; + border-bottom: 4px solid #95bcf0; +} \ No newline at end of file diff --git a/code/05-extracting-route-params/src/index.css b/code/05-extracting-route-params/src/index.css new file mode 100644 index 0000000000..44a860502a --- /dev/null +++ b/code/05-extracting-route-params/src/index.css @@ -0,0 +1,26 @@ +@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: #e0e9f5; +} + +main { + margin-top: 7rem; + text-align: center; +} + +h1, +h2, +h3, +p { + color: #042b5f; +} diff --git a/code/05-extracting-route-params/src/index.js b/code/05-extracting-route-params/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/05-extracting-route-params/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/05-extracting-route-params/src/pages/ProductDetail.js b/code/05-extracting-route-params/src/pages/ProductDetail.js new file mode 100644 index 0000000000..3923681f8d --- /dev/null +++ b/code/05-extracting-route-params/src/pages/ProductDetail.js @@ -0,0 +1,16 @@ +import { useParams } from 'react-router-dom'; + +const ProductDetail = () => { + const params = useParams(); + + console.log(params.productId); + + return ( +
+

Product Detail

+

{params.productId}

+
+ ); +}; + +export default ProductDetail; diff --git a/code/05-extracting-route-params/src/pages/Products.js b/code/05-extracting-route-params/src/pages/Products.js new file mode 100644 index 0000000000..7212d35cb6 --- /dev/null +++ b/code/05-extracting-route-params/src/pages/Products.js @@ -0,0 +1,14 @@ +const Products = () => { + return ( +
+

The Products Page

+
    +
  • A Book
  • +
  • A Carpet
  • +
  • An Online Course
  • +
+
+ ); +}; + +export default Products; diff --git a/code/05-extracting-route-params/src/pages/Welcome.js b/code/05-extracting-route-params/src/pages/Welcome.js new file mode 100644 index 0000000000..1453342bae --- /dev/null +++ b/code/05-extracting-route-params/src/pages/Welcome.js @@ -0,0 +1,5 @@ +const Welcome = () => { + return

The Welcome Page

; +}; + +export default Welcome; \ No newline at end of file diff --git a/code/06-using-switch-and-exact/package.json b/code/06-using-switch-and-exact/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/06-using-switch-and-exact/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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-using-switch-and-exact/public/favicon.ico b/code/06-using-switch-and-exact/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/06-using-switch-and-exact/public/index.html b/code/06-using-switch-and-exact/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/06-using-switch-and-exact/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/code/06-using-switch-and-exact/public/logo192.png b/code/06-using-switch-and-exact/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-using-switch-and-exact/public/manifest.json b/code/06-using-switch-and-exact/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/06-using-switch-and-exact/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-using-switch-and-exact/public/robots.txt b/code/06-using-switch-and-exact/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/06-using-switch-and-exact/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/06-using-switch-and-exact/src/App.js b/code/06-using-switch-and-exact/src/App.js new file mode 100644 index 0000000000..3b6e49c201 --- /dev/null +++ b/code/06-using-switch-and-exact/src/App.js @@ -0,0 +1,33 @@ +import { Route, Switch } from 'react-router-dom'; + +import Welcome from './pages/Welcome'; +import Products from './pages/Products'; +import ProductDetail from './pages/ProductDetail'; +import MainHeader from './components/MainHeader'; + +function App() { + return ( +
+ +
+ + + + + + + + + + + +
+
+ ); +} + +export default App; + +// our-domain.com/welcome => Welcome Component +// our-domain.com/products => Products Component +// our-domain.com/product-detail/a-book diff --git a/code/06-using-switch-and-exact/src/components/MainHeader.js b/code/06-using-switch-and-exact/src/components/MainHeader.js new file mode 100644 index 0000000000..5f5b28db4e --- /dev/null +++ b/code/06-using-switch-and-exact/src/components/MainHeader.js @@ -0,0 +1,26 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainHeader.module.css'; + +const MainHeader = () => { + return ( +
+ +
+ ); +}; + +export default MainHeader; diff --git a/code/06-using-switch-and-exact/src/components/MainHeader.module.css b/code/06-using-switch-and-exact/src/components/MainHeader.module.css new file mode 100644 index 0000000000..0d792b4c0d --- /dev/null +++ b/code/06-using-switch-and-exact/src/components/MainHeader.module.css @@ -0,0 +1,38 @@ +.header { + width: 100%; + height: 5rem; + background-color: #044599; + padding: 0 10%; +} + +.header nav { + height: 100%; +} + +.header ul { + height: 100%; + list-style: none; + display: flex; + padding: 0; + margin: 0; + align-items: center; + justify-content: center; +} + +.header li { + margin: 0 1rem; + width: 5rem; +} + +.header a { + color: white; + text-decoration: none; +} + +.header a:hover, +.header a:active, +.header a.active { + color: #95bcf0; + padding-bottom: 0.25rem; + border-bottom: 4px solid #95bcf0; +} \ No newline at end of file diff --git a/code/06-using-switch-and-exact/src/index.css b/code/06-using-switch-and-exact/src/index.css new file mode 100644 index 0000000000..44a860502a --- /dev/null +++ b/code/06-using-switch-and-exact/src/index.css @@ -0,0 +1,26 @@ +@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: #e0e9f5; +} + +main { + margin-top: 7rem; + text-align: center; +} + +h1, +h2, +h3, +p { + color: #042b5f; +} diff --git a/code/06-using-switch-and-exact/src/index.js b/code/06-using-switch-and-exact/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/06-using-switch-and-exact/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/06-using-switch-and-exact/src/pages/ProductDetail.js b/code/06-using-switch-and-exact/src/pages/ProductDetail.js new file mode 100644 index 0000000000..3923681f8d --- /dev/null +++ b/code/06-using-switch-and-exact/src/pages/ProductDetail.js @@ -0,0 +1,16 @@ +import { useParams } from 'react-router-dom'; + +const ProductDetail = () => { + const params = useParams(); + + console.log(params.productId); + + return ( +
+

Product Detail

+

{params.productId}

+
+ ); +}; + +export default ProductDetail; diff --git a/code/06-using-switch-and-exact/src/pages/Products.js b/code/06-using-switch-and-exact/src/pages/Products.js new file mode 100644 index 0000000000..834f867bfd --- /dev/null +++ b/code/06-using-switch-and-exact/src/pages/Products.js @@ -0,0 +1,22 @@ +import { Link } from 'react-router-dom'; + +const Products = () => { + return ( +
+

The Products Page

+
    +
  • + A Book +
  • +
  • + A Carpet +
  • +
  • + An Online Course +
  • +
+
+ ); +}; + +export default Products; diff --git a/code/06-using-switch-and-exact/src/pages/Welcome.js b/code/06-using-switch-and-exact/src/pages/Welcome.js new file mode 100644 index 0000000000..1453342bae --- /dev/null +++ b/code/06-using-switch-and-exact/src/pages/Welcome.js @@ -0,0 +1,5 @@ +const Welcome = () => { + return

The Welcome Page

; +}; + +export default Welcome; \ No newline at end of file diff --git a/code/07-working-with-nested-routes/package.json b/code/07-working-with-nested-routes/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/07-working-with-nested-routes/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/07-working-with-nested-routes/public/favicon.ico b/code/07-working-with-nested-routes/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/07-working-with-nested-routes/public/index.html b/code/07-working-with-nested-routes/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/07-working-with-nested-routes/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/code/07-working-with-nested-routes/public/logo192.png b/code/07-working-with-nested-routes/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/07-working-with-nested-routes/public/manifest.json b/code/07-working-with-nested-routes/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/07-working-with-nested-routes/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/07-working-with-nested-routes/public/robots.txt b/code/07-working-with-nested-routes/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/07-working-with-nested-routes/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/07-working-with-nested-routes/src/App.js b/code/07-working-with-nested-routes/src/App.js new file mode 100644 index 0000000000..3b6e49c201 --- /dev/null +++ b/code/07-working-with-nested-routes/src/App.js @@ -0,0 +1,33 @@ +import { Route, Switch } from 'react-router-dom'; + +import Welcome from './pages/Welcome'; +import Products from './pages/Products'; +import ProductDetail from './pages/ProductDetail'; +import MainHeader from './components/MainHeader'; + +function App() { + return ( +
+ +
+ + + + + + + + + + + +
+
+ ); +} + +export default App; + +// our-domain.com/welcome => Welcome Component +// our-domain.com/products => Products Component +// our-domain.com/product-detail/a-book diff --git a/code/07-working-with-nested-routes/src/components/MainHeader.js b/code/07-working-with-nested-routes/src/components/MainHeader.js new file mode 100644 index 0000000000..5f5b28db4e --- /dev/null +++ b/code/07-working-with-nested-routes/src/components/MainHeader.js @@ -0,0 +1,26 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainHeader.module.css'; + +const MainHeader = () => { + return ( +
+ +
+ ); +}; + +export default MainHeader; diff --git a/code/07-working-with-nested-routes/src/components/MainHeader.module.css b/code/07-working-with-nested-routes/src/components/MainHeader.module.css new file mode 100644 index 0000000000..0d792b4c0d --- /dev/null +++ b/code/07-working-with-nested-routes/src/components/MainHeader.module.css @@ -0,0 +1,38 @@ +.header { + width: 100%; + height: 5rem; + background-color: #044599; + padding: 0 10%; +} + +.header nav { + height: 100%; +} + +.header ul { + height: 100%; + list-style: none; + display: flex; + padding: 0; + margin: 0; + align-items: center; + justify-content: center; +} + +.header li { + margin: 0 1rem; + width: 5rem; +} + +.header a { + color: white; + text-decoration: none; +} + +.header a:hover, +.header a:active, +.header a.active { + color: #95bcf0; + padding-bottom: 0.25rem; + border-bottom: 4px solid #95bcf0; +} \ No newline at end of file diff --git a/code/07-working-with-nested-routes/src/index.css b/code/07-working-with-nested-routes/src/index.css new file mode 100644 index 0000000000..44a860502a --- /dev/null +++ b/code/07-working-with-nested-routes/src/index.css @@ -0,0 +1,26 @@ +@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: #e0e9f5; +} + +main { + margin-top: 7rem; + text-align: center; +} + +h1, +h2, +h3, +p { + color: #042b5f; +} diff --git a/code/07-working-with-nested-routes/src/index.js b/code/07-working-with-nested-routes/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/07-working-with-nested-routes/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/07-working-with-nested-routes/src/pages/ProductDetail.js b/code/07-working-with-nested-routes/src/pages/ProductDetail.js new file mode 100644 index 0000000000..3923681f8d --- /dev/null +++ b/code/07-working-with-nested-routes/src/pages/ProductDetail.js @@ -0,0 +1,16 @@ +import { useParams } from 'react-router-dom'; + +const ProductDetail = () => { + const params = useParams(); + + console.log(params.productId); + + return ( +
+

Product Detail

+

{params.productId}

+
+ ); +}; + +export default ProductDetail; diff --git a/code/07-working-with-nested-routes/src/pages/Products.js b/code/07-working-with-nested-routes/src/pages/Products.js new file mode 100644 index 0000000000..834f867bfd --- /dev/null +++ b/code/07-working-with-nested-routes/src/pages/Products.js @@ -0,0 +1,22 @@ +import { Link } from 'react-router-dom'; + +const Products = () => { + return ( +
+

The Products Page

+
    +
  • + A Book +
  • +
  • + A Carpet +
  • +
  • + An Online Course +
  • +
+
+ ); +}; + +export default Products; diff --git a/code/07-working-with-nested-routes/src/pages/Welcome.js b/code/07-working-with-nested-routes/src/pages/Welcome.js new file mode 100644 index 0000000000..c5ac2fa18c --- /dev/null +++ b/code/07-working-with-nested-routes/src/pages/Welcome.js @@ -0,0 +1,14 @@ +import { Route } from 'react-router-dom'; + +const Welcome = () => { + return ( +
+

The Welcome Page

+ +

Welcome, new user!

+
+
+ ); +}; + +export default Welcome; diff --git a/code/08-redirecting-the-user/package.json b/code/08-redirecting-the-user/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/08-redirecting-the-user/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/08-redirecting-the-user/public/favicon.ico b/code/08-redirecting-the-user/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/08-redirecting-the-user/public/index.html b/code/08-redirecting-the-user/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/08-redirecting-the-user/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/code/08-redirecting-the-user/public/logo192.png b/code/08-redirecting-the-user/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/08-redirecting-the-user/public/manifest.json b/code/08-redirecting-the-user/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/08-redirecting-the-user/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/08-redirecting-the-user/public/robots.txt b/code/08-redirecting-the-user/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/08-redirecting-the-user/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/08-redirecting-the-user/src/App.js b/code/08-redirecting-the-user/src/App.js new file mode 100644 index 0000000000..b78259ef9c --- /dev/null +++ b/code/08-redirecting-the-user/src/App.js @@ -0,0 +1,36 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import Welcome from './pages/Welcome'; +import Products from './pages/Products'; +import ProductDetail from './pages/ProductDetail'; +import MainHeader from './components/MainHeader'; + +function App() { + return ( +
+ +
+ + + + + + + + + + + + + + +
+
+ ); +} + +export default App; + +// our-domain.com/welcome => Welcome Component +// our-domain.com/products => Products Component +// our-domain.com/product-detail/a-book diff --git a/code/08-redirecting-the-user/src/components/MainHeader.js b/code/08-redirecting-the-user/src/components/MainHeader.js new file mode 100644 index 0000000000..5f5b28db4e --- /dev/null +++ b/code/08-redirecting-the-user/src/components/MainHeader.js @@ -0,0 +1,26 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainHeader.module.css'; + +const MainHeader = () => { + return ( +
+ +
+ ); +}; + +export default MainHeader; diff --git a/code/08-redirecting-the-user/src/components/MainHeader.module.css b/code/08-redirecting-the-user/src/components/MainHeader.module.css new file mode 100644 index 0000000000..0d792b4c0d --- /dev/null +++ b/code/08-redirecting-the-user/src/components/MainHeader.module.css @@ -0,0 +1,38 @@ +.header { + width: 100%; + height: 5rem; + background-color: #044599; + padding: 0 10%; +} + +.header nav { + height: 100%; +} + +.header ul { + height: 100%; + list-style: none; + display: flex; + padding: 0; + margin: 0; + align-items: center; + justify-content: center; +} + +.header li { + margin: 0 1rem; + width: 5rem; +} + +.header a { + color: white; + text-decoration: none; +} + +.header a:hover, +.header a:active, +.header a.active { + color: #95bcf0; + padding-bottom: 0.25rem; + border-bottom: 4px solid #95bcf0; +} \ No newline at end of file diff --git a/code/08-redirecting-the-user/src/index.css b/code/08-redirecting-the-user/src/index.css new file mode 100644 index 0000000000..44a860502a --- /dev/null +++ b/code/08-redirecting-the-user/src/index.css @@ -0,0 +1,26 @@ +@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: #e0e9f5; +} + +main { + margin-top: 7rem; + text-align: center; +} + +h1, +h2, +h3, +p { + color: #042b5f; +} diff --git a/code/08-redirecting-the-user/src/index.js b/code/08-redirecting-the-user/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/08-redirecting-the-user/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/08-redirecting-the-user/src/pages/ProductDetail.js b/code/08-redirecting-the-user/src/pages/ProductDetail.js new file mode 100644 index 0000000000..3923681f8d --- /dev/null +++ b/code/08-redirecting-the-user/src/pages/ProductDetail.js @@ -0,0 +1,16 @@ +import { useParams } from 'react-router-dom'; + +const ProductDetail = () => { + const params = useParams(); + + console.log(params.productId); + + return ( +
+

Product Detail

+

{params.productId}

+
+ ); +}; + +export default ProductDetail; diff --git a/code/08-redirecting-the-user/src/pages/Products.js b/code/08-redirecting-the-user/src/pages/Products.js new file mode 100644 index 0000000000..834f867bfd --- /dev/null +++ b/code/08-redirecting-the-user/src/pages/Products.js @@ -0,0 +1,22 @@ +import { Link } from 'react-router-dom'; + +const Products = () => { + return ( +
+

The Products Page

+
    +
  • + A Book +
  • +
  • + A Carpet +
  • +
  • + An Online Course +
  • +
+
+ ); +}; + +export default Products; diff --git a/code/08-redirecting-the-user/src/pages/Welcome.js b/code/08-redirecting-the-user/src/pages/Welcome.js new file mode 100644 index 0000000000..c5ac2fa18c --- /dev/null +++ b/code/08-redirecting-the-user/src/pages/Welcome.js @@ -0,0 +1,14 @@ +import { Route } from 'react-router-dom'; + +const Welcome = () => { + return ( +
+

The Welcome Page

+ +

Welcome, new user!

+
+
+ ); +}; + +export default Welcome; diff --git a/code/09-time-to-practice-starting-code/package.json b/code/09-time-to-practice-starting-code/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/09-time-to-practice-starting-code/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/09-time-to-practice-starting-code/public/favicon.ico b/code/09-time-to-practice-starting-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/09-time-to-practice-starting-code/public/index.html b/code/09-time-to-practice-starting-code/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/09-time-to-practice-starting-code/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/code/09-time-to-practice-starting-code/public/logo192.png b/code/09-time-to-practice-starting-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/09-time-to-practice-starting-code/public/manifest.json b/code/09-time-to-practice-starting-code/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/09-time-to-practice-starting-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/09-time-to-practice-starting-code/public/robots.txt b/code/09-time-to-practice-starting-code/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/09-time-to-practice-starting-code/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/09-time-to-practice-starting-code/src/App.js b/code/09-time-to-practice-starting-code/src/App.js new file mode 100644 index 0000000000..05d9bcca2e --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/App.js @@ -0,0 +1,9 @@ +function App() { + return ( +
+ +
+ ); +} + +export default App; diff --git a/code/09-time-to-practice-starting-code/src/components/UI/Card.js b/code/09-time-to-practice-starting-code/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
{props.children}
; +}; + +export default Card; diff --git a/code/09-time-to-practice-starting-code/src/components/UI/Card.module.css b/code/09-time-to-practice-starting-code/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/09-time-to-practice-starting-code/src/components/UI/LoadingSpinner.js b/code/09-time-to-practice-starting-code/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
; +} + +export default LoadingSpinner; diff --git a/code/09-time-to-practice-starting-code/src/components/UI/LoadingSpinner.module.css b/code/09-time-to-practice-starting-code/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/09-time-to-practice-starting-code/src/components/comments/CommentItem.js b/code/09-time-to-practice-starting-code/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/09-time-to-practice-starting-code/src/components/comments/CommentItem.module.css b/code/09-time-to-practice-starting-code/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/09-time-to-practice-starting-code/src/components/comments/Comments.js b/code/09-time-to-practice-starting-code/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/09-time-to-practice-starting-code/src/components/comments/Comments.module.css b/code/09-time-to-practice-starting-code/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/09-time-to-practice-starting-code/src/components/comments/CommentsList.js b/code/09-time-to-practice-starting-code/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/09-time-to-practice-starting-code/src/components/comments/CommentsList.module.css b/code/09-time-to-practice-starting-code/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/09-time-to-practice-starting-code/src/components/comments/NewCommentForm.js b/code/09-time-to-practice-starting-code/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/09-time-to-practice-starting-code/src/components/comments/NewCommentForm.module.css b/code/09-time-to-practice-starting-code/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/09-time-to-practice-starting-code/src/components/layout/Layout.module.css b/code/09-time-to-practice-starting-code/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/09-time-to-practice-starting-code/src/components/layout/MainNavigation.module.css b/code/09-time-to-practice-starting-code/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/HighlightedQuote.js b/code/09-time-to-practice-starting-code/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/HighlightedQuote.module.css b/code/09-time-to-practice-starting-code/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/NoQuotesFound.js b/code/09-time-to-practice-starting-code/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..14a83fa67c --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,14 @@ +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/NoQuotesFound.module.css b/code/09-time-to-practice-starting-code/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.js b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..464e2073d7 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.js @@ -0,0 +1,47 @@ +import { useRef } from 'react'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + return ( + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.module.css b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.js b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.module.css b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/QuoteList.js b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..a37943f11c --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteList.js @@ -0,0 +1,23 @@ +import { Fragment } from 'react'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const QuoteList = (props) => { + return ( + +
      + {props.quotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/QuoteList.module.css b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/09-time-to-practice-starting-code/src/index.css b/code/09-time-to-practice-starting-code/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/09-time-to-practice-starting-code/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/09-time-to-practice-starting-code/src/index.js b/code/09-time-to-practice-starting-code/src/index.js new file mode 100644 index 0000000000..d59d0ce18d --- /dev/null +++ b/code/09-time-to-practice-starting-code/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/10-practice-redirecting-and-extracting-params/package.json b/code/10-practice-redirecting-and-extracting-params/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/10-practice-redirecting-and-extracting-params/public/favicon.ico b/code/10-practice-redirecting-and-extracting-params/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/10-practice-redirecting-and-extracting-params/public/index.html b/code/10-practice-redirecting-and-extracting-params/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/10-practice-redirecting-and-extracting-params/public/logo192.png b/code/10-practice-redirecting-and-extracting-params/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/10-practice-redirecting-and-extracting-params/public/manifest.json b/code/10-practice-redirecting-and-extracting-params/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/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/10-practice-redirecting-and-extracting-params/public/robots.txt b/code/10-practice-redirecting-and-extracting-params/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/10-practice-redirecting-and-extracting-params/src/App.js b/code/10-practice-redirecting-and-extracting-params/src/App.js new file mode 100644 index 0000000000..82436c9d54 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/App.js @@ -0,0 +1,26 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; + +function App() { + return ( + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/UI/Card.js b/code/10-practice-redirecting-and-extracting-params/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/UI/Card.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/UI/LoadingSpinner.js b/code/10-practice-redirecting-and-extracting-params/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/UI/LoadingSpinner.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentItem.js b/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentItem.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/comments/Comments.js b/code/10-practice-redirecting-and-extracting-params/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/comments/Comments.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentsList.js b/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentsList.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/comments/NewCommentForm.js b/code/10-practice-redirecting-and-extracting-params/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/comments/NewCommentForm.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/layout/Layout.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/layout/MainNavigation.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/HighlightedQuote.js b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/HighlightedQuote.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/NoQuotesFound.js b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..14a83fa67c --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,14 @@ +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/NoQuotesFound.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.js b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..464e2073d7 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.js @@ -0,0 +1,47 @@ +import { useRef } from 'react'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + return ( + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteItem.js b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteItem.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteList.js b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..a37943f11c --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteList.js @@ -0,0 +1,23 @@ +import { Fragment } from 'react'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const QuoteList = (props) => { + return ( + +
      + {props.quotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteList.module.css b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/10-practice-redirecting-and-extracting-params/src/index.css b/code/10-practice-redirecting-and-extracting-params/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/10-practice-redirecting-and-extracting-params/src/index.js b/code/10-practice-redirecting-and-extracting-params/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/10-practice-redirecting-and-extracting-params/src/pages/AllQuotes.js b/code/10-practice-redirecting-and-extracting-params/src/pages/AllQuotes.js new file mode 100644 index 0000000000..0c50d04a6f --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/pages/AllQuotes.js @@ -0,0 +1,5 @@ +const AllQuotes = () => { + return

    All Quotes Page

    +}; + +export default AllQuotes; \ No newline at end of file diff --git a/code/10-practice-redirecting-and-extracting-params/src/pages/NewQuote.js b/code/10-practice-redirecting-and-extracting-params/src/pages/NewQuote.js new file mode 100644 index 0000000000..0b0d779a5c --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/pages/NewQuote.js @@ -0,0 +1,5 @@ +const NewQuote = () => { + return

    New Quote Page

    +}; + +export default NewQuote; \ No newline at end of file diff --git a/code/10-practice-redirecting-and-extracting-params/src/pages/QuoteDetail.js b/code/10-practice-redirecting-and-extracting-params/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..9c5cd4c72e --- /dev/null +++ b/code/10-practice-redirecting-and-extracting-params/src/pages/QuoteDetail.js @@ -0,0 +1,15 @@ +import { Fragment } from 'react'; +import { useParams } from 'react-router-dom'; + +const QuoteDetail = () => { + const params = useParams(); + + return ( + +

    Quote Detail Page

    +

    {params.quoteId}

    +
    + ); +}; + +export default QuoteDetail; diff --git a/code/11-practicing-nested-routes/package.json b/code/11-practicing-nested-routes/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/11-practicing-nested-routes/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/11-practicing-nested-routes/public/favicon.ico b/code/11-practicing-nested-routes/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/11-practicing-nested-routes/public/index.html b/code/11-practicing-nested-routes/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/11-practicing-nested-routes/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/11-practicing-nested-routes/public/logo192.png b/code/11-practicing-nested-routes/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/11-practicing-nested-routes/public/manifest.json b/code/11-practicing-nested-routes/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/11-practicing-nested-routes/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/11-practicing-nested-routes/public/robots.txt b/code/11-practicing-nested-routes/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/11-practicing-nested-routes/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/11-practicing-nested-routes/src/App.js b/code/11-practicing-nested-routes/src/App.js new file mode 100644 index 0000000000..82436c9d54 --- /dev/null +++ b/code/11-practicing-nested-routes/src/App.js @@ -0,0 +1,26 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; + +function App() { + return ( + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/11-practicing-nested-routes/src/components/UI/Card.js b/code/11-practicing-nested-routes/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/11-practicing-nested-routes/src/components/UI/Card.module.css b/code/11-practicing-nested-routes/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/11-practicing-nested-routes/src/components/UI/LoadingSpinner.js b/code/11-practicing-nested-routes/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/11-practicing-nested-routes/src/components/UI/LoadingSpinner.module.css b/code/11-practicing-nested-routes/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/11-practicing-nested-routes/src/components/comments/CommentItem.js b/code/11-practicing-nested-routes/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/11-practicing-nested-routes/src/components/comments/CommentItem.module.css b/code/11-practicing-nested-routes/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/11-practicing-nested-routes/src/components/comments/Comments.js b/code/11-practicing-nested-routes/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/11-practicing-nested-routes/src/components/comments/Comments.module.css b/code/11-practicing-nested-routes/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/11-practicing-nested-routes/src/components/comments/CommentsList.js b/code/11-practicing-nested-routes/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/11-practicing-nested-routes/src/components/comments/CommentsList.module.css b/code/11-practicing-nested-routes/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/11-practicing-nested-routes/src/components/comments/NewCommentForm.js b/code/11-practicing-nested-routes/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/11-practicing-nested-routes/src/components/comments/NewCommentForm.module.css b/code/11-practicing-nested-routes/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/11-practicing-nested-routes/src/components/layout/Layout.module.css b/code/11-practicing-nested-routes/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/11-practicing-nested-routes/src/components/layout/MainNavigation.module.css b/code/11-practicing-nested-routes/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/11-practicing-nested-routes/src/components/quotes/HighlightedQuote.js b/code/11-practicing-nested-routes/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/11-practicing-nested-routes/src/components/quotes/HighlightedQuote.module.css b/code/11-practicing-nested-routes/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/11-practicing-nested-routes/src/components/quotes/NoQuotesFound.js b/code/11-practicing-nested-routes/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..14a83fa67c --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,14 @@ +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/11-practicing-nested-routes/src/components/quotes/NoQuotesFound.module.css b/code/11-practicing-nested-routes/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/11-practicing-nested-routes/src/components/quotes/QuoteForm.js b/code/11-practicing-nested-routes/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..464e2073d7 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/quotes/QuoteForm.js @@ -0,0 +1,47 @@ +import { useRef } from 'react'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + return ( + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/11-practicing-nested-routes/src/components/quotes/QuoteForm.module.css b/code/11-practicing-nested-routes/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/11-practicing-nested-routes/src/components/quotes/QuoteItem.js b/code/11-practicing-nested-routes/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/11-practicing-nested-routes/src/components/quotes/QuoteItem.module.css b/code/11-practicing-nested-routes/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/11-practicing-nested-routes/src/components/quotes/QuoteList.js b/code/11-practicing-nested-routes/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..a37943f11c --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/quotes/QuoteList.js @@ -0,0 +1,23 @@ +import { Fragment } from 'react'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const QuoteList = (props) => { + return ( + +
      + {props.quotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/11-practicing-nested-routes/src/components/quotes/QuoteList.module.css b/code/11-practicing-nested-routes/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/11-practicing-nested-routes/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/11-practicing-nested-routes/src/index.css b/code/11-practicing-nested-routes/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/11-practicing-nested-routes/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/11-practicing-nested-routes/src/index.js b/code/11-practicing-nested-routes/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/11-practicing-nested-routes/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/11-practicing-nested-routes/src/pages/AllQuotes.js b/code/11-practicing-nested-routes/src/pages/AllQuotes.js new file mode 100644 index 0000000000..0c50d04a6f --- /dev/null +++ b/code/11-practicing-nested-routes/src/pages/AllQuotes.js @@ -0,0 +1,5 @@ +const AllQuotes = () => { + return

    All Quotes Page

    +}; + +export default AllQuotes; \ No newline at end of file diff --git a/code/11-practicing-nested-routes/src/pages/NewQuote.js b/code/11-practicing-nested-routes/src/pages/NewQuote.js new file mode 100644 index 0000000000..0b0d779a5c --- /dev/null +++ b/code/11-practicing-nested-routes/src/pages/NewQuote.js @@ -0,0 +1,5 @@ +const NewQuote = () => { + return

    New Quote Page

    +}; + +export default NewQuote; \ No newline at end of file diff --git a/code/11-practicing-nested-routes/src/pages/QuoteDetail.js b/code/11-practicing-nested-routes/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..5631383a1d --- /dev/null +++ b/code/11-practicing-nested-routes/src/pages/QuoteDetail.js @@ -0,0 +1,20 @@ +import { Fragment } from 'react'; +import { useParams, Route } from 'react-router-dom'; + +import Comments from '../components/comments/Comments'; + +const QuoteDetail = () => { + const params = useParams(); + + return ( + +

    Quote Detail Page

    +

    {params.quoteId}

    + + + +
    + ); +}; + +export default QuoteDetail; diff --git a/code/12-adding-a-layout-wrapper/package.json b/code/12-adding-a-layout-wrapper/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/12-adding-a-layout-wrapper/public/favicon.ico b/code/12-adding-a-layout-wrapper/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/12-adding-a-layout-wrapper/public/index.html b/code/12-adding-a-layout-wrapper/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/12-adding-a-layout-wrapper/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/12-adding-a-layout-wrapper/public/logo192.png b/code/12-adding-a-layout-wrapper/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/12-adding-a-layout-wrapper/public/manifest.json b/code/12-adding-a-layout-wrapper/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/12-adding-a-layout-wrapper/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/12-adding-a-layout-wrapper/public/robots.txt b/code/12-adding-a-layout-wrapper/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/12-adding-a-layout-wrapper/src/App.js b/code/12-adding-a-layout-wrapper/src/App.js new file mode 100644 index 0000000000..5f90130429 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/App.js @@ -0,0 +1,29 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; +import Layout from './components/layout/Layout'; + +function App() { + return ( + + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/12-adding-a-layout-wrapper/src/components/UI/Card.js b/code/12-adding-a-layout-wrapper/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/12-adding-a-layout-wrapper/src/components/UI/Card.module.css b/code/12-adding-a-layout-wrapper/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/12-adding-a-layout-wrapper/src/components/UI/LoadingSpinner.js b/code/12-adding-a-layout-wrapper/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/12-adding-a-layout-wrapper/src/components/UI/LoadingSpinner.module.css b/code/12-adding-a-layout-wrapper/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/12-adding-a-layout-wrapper/src/components/comments/CommentItem.js b/code/12-adding-a-layout-wrapper/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/12-adding-a-layout-wrapper/src/components/comments/CommentItem.module.css b/code/12-adding-a-layout-wrapper/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/12-adding-a-layout-wrapper/src/components/comments/Comments.js b/code/12-adding-a-layout-wrapper/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/12-adding-a-layout-wrapper/src/components/comments/Comments.module.css b/code/12-adding-a-layout-wrapper/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/12-adding-a-layout-wrapper/src/components/comments/CommentsList.js b/code/12-adding-a-layout-wrapper/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/12-adding-a-layout-wrapper/src/components/comments/CommentsList.module.css b/code/12-adding-a-layout-wrapper/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/12-adding-a-layout-wrapper/src/components/comments/NewCommentForm.js b/code/12-adding-a-layout-wrapper/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/12-adding-a-layout-wrapper/src/components/comments/NewCommentForm.module.css b/code/12-adding-a-layout-wrapper/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/12-adding-a-layout-wrapper/src/components/layout/Layout.js b/code/12-adding-a-layout-wrapper/src/components/layout/Layout.js new file mode 100644 index 0000000000..2eff00a625 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/layout/Layout.js @@ -0,0 +1,15 @@ +import { Fragment } from 'react'; + +import classes from './Layout.module.css'; +import MainNavigation from './MainNavigation'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/12-adding-a-layout-wrapper/src/components/layout/Layout.module.css b/code/12-adding-a-layout-wrapper/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/12-adding-a-layout-wrapper/src/components/layout/MainNavigation.js b/code/12-adding-a-layout-wrapper/src/components/layout/MainNavigation.js new file mode 100644 index 0000000000..76d790a1c3 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/layout/MainNavigation.js @@ -0,0 +1,27 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainNavigation.module.css'; + +const MainNavigation = () => { + return ( +
    +
    Great Quotes
    + +
    + ); +}; + +export default MainNavigation; diff --git a/code/12-adding-a-layout-wrapper/src/components/layout/MainNavigation.module.css b/code/12-adding-a-layout-wrapper/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/12-adding-a-layout-wrapper/src/components/quotes/HighlightedQuote.js b/code/12-adding-a-layout-wrapper/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/12-adding-a-layout-wrapper/src/components/quotes/HighlightedQuote.module.css b/code/12-adding-a-layout-wrapper/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/12-adding-a-layout-wrapper/src/components/quotes/NoQuotesFound.js b/code/12-adding-a-layout-wrapper/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..14a83fa67c --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,14 @@ +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/12-adding-a-layout-wrapper/src/components/quotes/NoQuotesFound.module.css b/code/12-adding-a-layout-wrapper/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.js b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..464e2073d7 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.js @@ -0,0 +1,47 @@ +import { useRef } from 'react'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + return ( + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.module.css b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteItem.js b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteItem.module.css b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteList.js b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..a37943f11c --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteList.js @@ -0,0 +1,23 @@ +import { Fragment } from 'react'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const QuoteList = (props) => { + return ( + +
      + {props.quotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteList.module.css b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/12-adding-a-layout-wrapper/src/index.css b/code/12-adding-a-layout-wrapper/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/12-adding-a-layout-wrapper/src/index.js b/code/12-adding-a-layout-wrapper/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/12-adding-a-layout-wrapper/src/pages/AllQuotes.js b/code/12-adding-a-layout-wrapper/src/pages/AllQuotes.js new file mode 100644 index 0000000000..0c50d04a6f --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/pages/AllQuotes.js @@ -0,0 +1,5 @@ +const AllQuotes = () => { + return

    All Quotes Page

    +}; + +export default AllQuotes; \ No newline at end of file diff --git a/code/12-adding-a-layout-wrapper/src/pages/NewQuote.js b/code/12-adding-a-layout-wrapper/src/pages/NewQuote.js new file mode 100644 index 0000000000..0b0d779a5c --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/pages/NewQuote.js @@ -0,0 +1,5 @@ +const NewQuote = () => { + return

    New Quote Page

    +}; + +export default NewQuote; \ No newline at end of file diff --git a/code/12-adding-a-layout-wrapper/src/pages/QuoteDetail.js b/code/12-adding-a-layout-wrapper/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..5631383a1d --- /dev/null +++ b/code/12-adding-a-layout-wrapper/src/pages/QuoteDetail.js @@ -0,0 +1,20 @@ +import { Fragment } from 'react'; +import { useParams, Route } from 'react-router-dom'; + +import Comments from '../components/comments/Comments'; + +const QuoteDetail = () => { + const params = useParams(); + + return ( + +

    Quote Detail Page

    +

    {params.quoteId}

    + + + +
    + ); +}; + +export default QuoteDetail; diff --git a/code/13-adding-dummy-data-and-more-content/package.json b/code/13-adding-dummy-data-and-more-content/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/13-adding-dummy-data-and-more-content/public/favicon.ico b/code/13-adding-dummy-data-and-more-content/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_SjAPB3vGa^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/13-adding-dummy-data-and-more-content/public/index.html b/code/13-adding-dummy-data-and-more-content/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/13-adding-dummy-data-and-more-content/public/logo192.png b/code/13-adding-dummy-data-and-more-content/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/13-adding-dummy-data-and-more-content/public/manifest.json b/code/13-adding-dummy-data-and-more-content/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/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/13-adding-dummy-data-and-more-content/public/robots.txt b/code/13-adding-dummy-data-and-more-content/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/13-adding-dummy-data-and-more-content/src/App.js b/code/13-adding-dummy-data-and-more-content/src/App.js new file mode 100644 index 0000000000..5f90130429 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/App.js @@ -0,0 +1,29 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; +import Layout from './components/layout/Layout'; + +function App() { + return ( + + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/UI/Card.js b/code/13-adding-dummy-data-and-more-content/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/UI/Card.module.css b/code/13-adding-dummy-data-and-more-content/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/13-adding-dummy-data-and-more-content/src/components/UI/LoadingSpinner.js b/code/13-adding-dummy-data-and-more-content/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/UI/LoadingSpinner.module.css b/code/13-adding-dummy-data-and-more-content/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentItem.js b/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentItem.module.css b/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/13-adding-dummy-data-and-more-content/src/components/comments/Comments.js b/code/13-adding-dummy-data-and-more-content/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/comments/Comments.module.css b/code/13-adding-dummy-data-and-more-content/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentsList.js b/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentsList.module.css b/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/13-adding-dummy-data-and-more-content/src/components/comments/NewCommentForm.js b/code/13-adding-dummy-data-and-more-content/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/comments/NewCommentForm.module.css b/code/13-adding-dummy-data-and-more-content/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/13-adding-dummy-data-and-more-content/src/components/layout/Layout.js b/code/13-adding-dummy-data-and-more-content/src/components/layout/Layout.js new file mode 100644 index 0000000000..2eff00a625 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/layout/Layout.js @@ -0,0 +1,15 @@ +import { Fragment } from 'react'; + +import classes from './Layout.module.css'; +import MainNavigation from './MainNavigation'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/layout/Layout.module.css b/code/13-adding-dummy-data-and-more-content/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/13-adding-dummy-data-and-more-content/src/components/layout/MainNavigation.js b/code/13-adding-dummy-data-and-more-content/src/components/layout/MainNavigation.js new file mode 100644 index 0000000000..76d790a1c3 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/layout/MainNavigation.js @@ -0,0 +1,27 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainNavigation.module.css'; + +const MainNavigation = () => { + return ( +
    +
    Great Quotes
    + +
    + ); +}; + +export default MainNavigation; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/layout/MainNavigation.module.css b/code/13-adding-dummy-data-and-more-content/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/13-adding-dummy-data-and-more-content/src/components/quotes/HighlightedQuote.js b/code/13-adding-dummy-data-and-more-content/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/quotes/HighlightedQuote.module.css b/code/13-adding-dummy-data-and-more-content/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/13-adding-dummy-data-and-more-content/src/components/quotes/NoQuotesFound.js b/code/13-adding-dummy-data-and-more-content/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..14a83fa67c --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,14 @@ +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/quotes/NoQuotesFound.module.css b/code/13-adding-dummy-data-and-more-content/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.js b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..464e2073d7 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.js @@ -0,0 +1,47 @@ +import { useRef } from 'react'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + return ( + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.module.css b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteItem.js b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..06cf8b1221 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteItem.js @@ -0,0 +1,19 @@ +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteItem.module.css b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteList.js b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..a37943f11c --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteList.js @@ -0,0 +1,23 @@ +import { Fragment } from 'react'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const QuoteList = (props) => { + return ( + +
      + {props.quotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteList.module.css b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/13-adding-dummy-data-and-more-content/src/index.css b/code/13-adding-dummy-data-and-more-content/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/13-adding-dummy-data-and-more-content/src/index.js b/code/13-adding-dummy-data-and-more-content/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/13-adding-dummy-data-and-more-content/src/pages/AllQuotes.js b/code/13-adding-dummy-data-and-more-content/src/pages/AllQuotes.js new file mode 100644 index 0000000000..6362e57d3f --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/pages/AllQuotes.js @@ -0,0 +1,12 @@ +import QuoteList from '../components/quotes/QuoteList'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const AllQuotes = () => { + return +}; + +export default AllQuotes; \ No newline at end of file diff --git a/code/13-adding-dummy-data-and-more-content/src/pages/NewQuote.js b/code/13-adding-dummy-data-and-more-content/src/pages/NewQuote.js new file mode 100644 index 0000000000..d5785be1fb --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/pages/NewQuote.js @@ -0,0 +1,11 @@ +import QuoteForm from '../components/quotes/QuoteForm'; + +const NewQuote = () => { + const addQuoteHandler = (quoteData) => { + console.log(quoteData); + }; + + return ; +}; + +export default NewQuote; diff --git a/code/13-adding-dummy-data-and-more-content/src/pages/QuoteDetail.js b/code/13-adding-dummy-data-and-more-content/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..5631383a1d --- /dev/null +++ b/code/13-adding-dummy-data-and-more-content/src/pages/QuoteDetail.js @@ -0,0 +1,20 @@ +import { Fragment } from 'react'; +import { useParams, Route } from 'react-router-dom'; + +import Comments from '../components/comments/Comments'; + +const QuoteDetail = () => { + const params = useParams(); + + return ( + +

    Quote Detail Page

    +

    {params.quoteId}

    + + + +
    + ); +}; + +export default QuoteDetail; diff --git a/code/14-outputting-data-on-details-page/package.json b/code/14-outputting-data-on-details-page/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/14-outputting-data-on-details-page/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/14-outputting-data-on-details-page/public/favicon.ico b/code/14-outputting-data-on-details-page/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/14-outputting-data-on-details-page/public/index.html b/code/14-outputting-data-on-details-page/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/14-outputting-data-on-details-page/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/14-outputting-data-on-details-page/public/logo192.png b/code/14-outputting-data-on-details-page/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/14-outputting-data-on-details-page/public/manifest.json b/code/14-outputting-data-on-details-page/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/14-outputting-data-on-details-page/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/14-outputting-data-on-details-page/public/robots.txt b/code/14-outputting-data-on-details-page/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/14-outputting-data-on-details-page/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/14-outputting-data-on-details-page/src/App.js b/code/14-outputting-data-on-details-page/src/App.js new file mode 100644 index 0000000000..5f90130429 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/App.js @@ -0,0 +1,29 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; +import Layout from './components/layout/Layout'; + +function App() { + return ( + + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/14-outputting-data-on-details-page/src/components/UI/Card.js b/code/14-outputting-data-on-details-page/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/14-outputting-data-on-details-page/src/components/UI/Card.module.css b/code/14-outputting-data-on-details-page/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/14-outputting-data-on-details-page/src/components/UI/LoadingSpinner.js b/code/14-outputting-data-on-details-page/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/14-outputting-data-on-details-page/src/components/UI/LoadingSpinner.module.css b/code/14-outputting-data-on-details-page/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/14-outputting-data-on-details-page/src/components/comments/CommentItem.js b/code/14-outputting-data-on-details-page/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/14-outputting-data-on-details-page/src/components/comments/CommentItem.module.css b/code/14-outputting-data-on-details-page/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/14-outputting-data-on-details-page/src/components/comments/Comments.js b/code/14-outputting-data-on-details-page/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/14-outputting-data-on-details-page/src/components/comments/Comments.module.css b/code/14-outputting-data-on-details-page/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/14-outputting-data-on-details-page/src/components/comments/CommentsList.js b/code/14-outputting-data-on-details-page/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/14-outputting-data-on-details-page/src/components/comments/CommentsList.module.css b/code/14-outputting-data-on-details-page/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/14-outputting-data-on-details-page/src/components/comments/NewCommentForm.js b/code/14-outputting-data-on-details-page/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/14-outputting-data-on-details-page/src/components/comments/NewCommentForm.module.css b/code/14-outputting-data-on-details-page/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/14-outputting-data-on-details-page/src/components/layout/Layout.js b/code/14-outputting-data-on-details-page/src/components/layout/Layout.js new file mode 100644 index 0000000000..2eff00a625 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/layout/Layout.js @@ -0,0 +1,15 @@ +import { Fragment } from 'react'; + +import classes from './Layout.module.css'; +import MainNavigation from './MainNavigation'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/14-outputting-data-on-details-page/src/components/layout/Layout.module.css b/code/14-outputting-data-on-details-page/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/14-outputting-data-on-details-page/src/components/layout/MainNavigation.js b/code/14-outputting-data-on-details-page/src/components/layout/MainNavigation.js new file mode 100644 index 0000000000..76d790a1c3 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/layout/MainNavigation.js @@ -0,0 +1,27 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainNavigation.module.css'; + +const MainNavigation = () => { + return ( +
    +
    Great Quotes
    + +
    + ); +}; + +export default MainNavigation; diff --git a/code/14-outputting-data-on-details-page/src/components/layout/MainNavigation.module.css b/code/14-outputting-data-on-details-page/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/14-outputting-data-on-details-page/src/components/quotes/HighlightedQuote.js b/code/14-outputting-data-on-details-page/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/14-outputting-data-on-details-page/src/components/quotes/HighlightedQuote.module.css b/code/14-outputting-data-on-details-page/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/14-outputting-data-on-details-page/src/components/quotes/NoQuotesFound.js b/code/14-outputting-data-on-details-page/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..14a83fa67c --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,14 @@ +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/14-outputting-data-on-details-page/src/components/quotes/NoQuotesFound.module.css b/code/14-outputting-data-on-details-page/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.js b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..464e2073d7 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.js @@ -0,0 +1,47 @@ +import { useRef } from 'react'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + return ( + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.module.css b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/14-outputting-data-on-details-page/src/components/quotes/QuoteItem.js b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/14-outputting-data-on-details-page/src/components/quotes/QuoteItem.module.css b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/14-outputting-data-on-details-page/src/components/quotes/QuoteList.js b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..a37943f11c --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteList.js @@ -0,0 +1,23 @@ +import { Fragment } from 'react'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const QuoteList = (props) => { + return ( + +
      + {props.quotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/14-outputting-data-on-details-page/src/components/quotes/QuoteList.module.css b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/14-outputting-data-on-details-page/src/index.css b/code/14-outputting-data-on-details-page/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/14-outputting-data-on-details-page/src/index.js b/code/14-outputting-data-on-details-page/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/14-outputting-data-on-details-page/src/pages/AllQuotes.js b/code/14-outputting-data-on-details-page/src/pages/AllQuotes.js new file mode 100644 index 0000000000..6362e57d3f --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/pages/AllQuotes.js @@ -0,0 +1,12 @@ +import QuoteList from '../components/quotes/QuoteList'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const AllQuotes = () => { + return +}; + +export default AllQuotes; \ No newline at end of file diff --git a/code/14-outputting-data-on-details-page/src/pages/NewQuote.js b/code/14-outputting-data-on-details-page/src/pages/NewQuote.js new file mode 100644 index 0000000000..d5785be1fb --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/pages/NewQuote.js @@ -0,0 +1,11 @@ +import QuoteForm from '../components/quotes/QuoteForm'; + +const NewQuote = () => { + const addQuoteHandler = (quoteData) => { + console.log(quoteData); + }; + + return ; +}; + +export default NewQuote; diff --git a/code/14-outputting-data-on-details-page/src/pages/QuoteDetail.js b/code/14-outputting-data-on-details-page/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..8381efa2ac --- /dev/null +++ b/code/14-outputting-data-on-details-page/src/pages/QuoteDetail.js @@ -0,0 +1,31 @@ +import { Fragment } from 'react'; +import { useParams, Route } from 'react-router-dom'; + +import HighlightedQuote from '../components/quotes/HighlightedQuote'; +import Comments from '../components/comments/Comments'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const QuoteDetail = () => { + const params = useParams(); + + const quote = DUMMY_QUOTES.find((quote) => quote.id === params.quoteId); + + if (!quote) { + return

    No quote found!

    ; + } + + return ( + + + + + + + ); +}; + +export default QuoteDetail; diff --git a/code/15-adding-a-notfound-page/package.json b/code/15-adding-a-notfound-page/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/15-adding-a-notfound-page/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/15-adding-a-notfound-page/public/favicon.ico b/code/15-adding-a-notfound-page/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/15-adding-a-notfound-page/public/index.html b/code/15-adding-a-notfound-page/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/15-adding-a-notfound-page/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/15-adding-a-notfound-page/public/logo192.png b/code/15-adding-a-notfound-page/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/15-adding-a-notfound-page/public/manifest.json b/code/15-adding-a-notfound-page/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/15-adding-a-notfound-page/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/15-adding-a-notfound-page/public/robots.txt b/code/15-adding-a-notfound-page/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/15-adding-a-notfound-page/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/15-adding-a-notfound-page/src/App.js b/code/15-adding-a-notfound-page/src/App.js new file mode 100644 index 0000000000..97eb33fc84 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/App.js @@ -0,0 +1,33 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; +import NotFound from './pages/NotFound'; +import Layout from './components/layout/Layout'; + +function App() { + return ( + + + + + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/15-adding-a-notfound-page/src/components/UI/Card.js b/code/15-adding-a-notfound-page/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/15-adding-a-notfound-page/src/components/UI/Card.module.css b/code/15-adding-a-notfound-page/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/15-adding-a-notfound-page/src/components/UI/LoadingSpinner.js b/code/15-adding-a-notfound-page/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/15-adding-a-notfound-page/src/components/UI/LoadingSpinner.module.css b/code/15-adding-a-notfound-page/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/15-adding-a-notfound-page/src/components/comments/CommentItem.js b/code/15-adding-a-notfound-page/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/15-adding-a-notfound-page/src/components/comments/CommentItem.module.css b/code/15-adding-a-notfound-page/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/15-adding-a-notfound-page/src/components/comments/Comments.js b/code/15-adding-a-notfound-page/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/15-adding-a-notfound-page/src/components/comments/Comments.module.css b/code/15-adding-a-notfound-page/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/15-adding-a-notfound-page/src/components/comments/CommentsList.js b/code/15-adding-a-notfound-page/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/15-adding-a-notfound-page/src/components/comments/CommentsList.module.css b/code/15-adding-a-notfound-page/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/15-adding-a-notfound-page/src/components/comments/NewCommentForm.js b/code/15-adding-a-notfound-page/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/15-adding-a-notfound-page/src/components/comments/NewCommentForm.module.css b/code/15-adding-a-notfound-page/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/15-adding-a-notfound-page/src/components/layout/Layout.js b/code/15-adding-a-notfound-page/src/components/layout/Layout.js new file mode 100644 index 0000000000..2eff00a625 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/layout/Layout.js @@ -0,0 +1,15 @@ +import { Fragment } from 'react'; + +import classes from './Layout.module.css'; +import MainNavigation from './MainNavigation'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/15-adding-a-notfound-page/src/components/layout/Layout.module.css b/code/15-adding-a-notfound-page/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/15-adding-a-notfound-page/src/components/layout/MainNavigation.js b/code/15-adding-a-notfound-page/src/components/layout/MainNavigation.js new file mode 100644 index 0000000000..76d790a1c3 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/layout/MainNavigation.js @@ -0,0 +1,27 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainNavigation.module.css'; + +const MainNavigation = () => { + return ( +
    +
    Great Quotes
    + +
    + ); +}; + +export default MainNavigation; diff --git a/code/15-adding-a-notfound-page/src/components/layout/MainNavigation.module.css b/code/15-adding-a-notfound-page/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/15-adding-a-notfound-page/src/components/quotes/HighlightedQuote.js b/code/15-adding-a-notfound-page/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/15-adding-a-notfound-page/src/components/quotes/HighlightedQuote.module.css b/code/15-adding-a-notfound-page/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/15-adding-a-notfound-page/src/components/quotes/NoQuotesFound.js b/code/15-adding-a-notfound-page/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..14a83fa67c --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,14 @@ +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/15-adding-a-notfound-page/src/components/quotes/NoQuotesFound.module.css b/code/15-adding-a-notfound-page/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.js b/code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..464e2073d7 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.js @@ -0,0 +1,47 @@ +import { useRef } from 'react'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + return ( + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.module.css b/code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/15-adding-a-notfound-page/src/components/quotes/QuoteItem.js b/code/15-adding-a-notfound-page/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/15-adding-a-notfound-page/src/components/quotes/QuoteItem.module.css b/code/15-adding-a-notfound-page/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/15-adding-a-notfound-page/src/components/quotes/QuoteList.js b/code/15-adding-a-notfound-page/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..a37943f11c --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/quotes/QuoteList.js @@ -0,0 +1,23 @@ +import { Fragment } from 'react'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const QuoteList = (props) => { + return ( + +
      + {props.quotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/15-adding-a-notfound-page/src/components/quotes/QuoteList.module.css b/code/15-adding-a-notfound-page/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/15-adding-a-notfound-page/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/15-adding-a-notfound-page/src/index.css b/code/15-adding-a-notfound-page/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/15-adding-a-notfound-page/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/15-adding-a-notfound-page/src/index.js b/code/15-adding-a-notfound-page/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/15-adding-a-notfound-page/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/15-adding-a-notfound-page/src/pages/AllQuotes.js b/code/15-adding-a-notfound-page/src/pages/AllQuotes.js new file mode 100644 index 0000000000..6362e57d3f --- /dev/null +++ b/code/15-adding-a-notfound-page/src/pages/AllQuotes.js @@ -0,0 +1,12 @@ +import QuoteList from '../components/quotes/QuoteList'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const AllQuotes = () => { + return +}; + +export default AllQuotes; \ No newline at end of file diff --git a/code/15-adding-a-notfound-page/src/pages/NewQuote.js b/code/15-adding-a-notfound-page/src/pages/NewQuote.js new file mode 100644 index 0000000000..d5785be1fb --- /dev/null +++ b/code/15-adding-a-notfound-page/src/pages/NewQuote.js @@ -0,0 +1,11 @@ +import QuoteForm from '../components/quotes/QuoteForm'; + +const NewQuote = () => { + const addQuoteHandler = (quoteData) => { + console.log(quoteData); + }; + + return ; +}; + +export default NewQuote; diff --git a/code/15-adding-a-notfound-page/src/pages/NotFound.js b/code/15-adding-a-notfound-page/src/pages/NotFound.js new file mode 100644 index 0000000000..bb7c85baac --- /dev/null +++ b/code/15-adding-a-notfound-page/src/pages/NotFound.js @@ -0,0 +1,9 @@ +const NotFound = () => { + return ( +
    +

    Page not found!

    +
    + ); +}; + +export default NotFound; diff --git a/code/15-adding-a-notfound-page/src/pages/QuoteDetail.js b/code/15-adding-a-notfound-page/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..8381efa2ac --- /dev/null +++ b/code/15-adding-a-notfound-page/src/pages/QuoteDetail.js @@ -0,0 +1,31 @@ +import { Fragment } from 'react'; +import { useParams, Route } from 'react-router-dom'; + +import HighlightedQuote from '../components/quotes/HighlightedQuote'; +import Comments from '../components/comments/Comments'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const QuoteDetail = () => { + const params = useParams(); + + const quote = DUMMY_QUOTES.find((quote) => quote.id === params.quoteId); + + if (!quote) { + return

    No quote found!

    ; + } + + return ( + + + + + + + ); +}; + +export default QuoteDetail; diff --git a/code/16-implementing-programmatic-navigation/package.json b/code/16-implementing-programmatic-navigation/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/16-implementing-programmatic-navigation/public/favicon.ico b/code/16-implementing-programmatic-navigation/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_SjAPB3vGa^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/16-implementing-programmatic-navigation/public/index.html b/code/16-implementing-programmatic-navigation/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/16-implementing-programmatic-navigation/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/16-implementing-programmatic-navigation/public/logo192.png b/code/16-implementing-programmatic-navigation/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/16-implementing-programmatic-navigation/public/manifest.json b/code/16-implementing-programmatic-navigation/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/16-implementing-programmatic-navigation/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/16-implementing-programmatic-navigation/public/robots.txt b/code/16-implementing-programmatic-navigation/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/16-implementing-programmatic-navigation/src/App.js b/code/16-implementing-programmatic-navigation/src/App.js new file mode 100644 index 0000000000..97eb33fc84 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/App.js @@ -0,0 +1,33 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; +import NotFound from './pages/NotFound'; +import Layout from './components/layout/Layout'; + +function App() { + return ( + + + + + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/16-implementing-programmatic-navigation/src/components/UI/Card.js b/code/16-implementing-programmatic-navigation/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/16-implementing-programmatic-navigation/src/components/UI/Card.module.css b/code/16-implementing-programmatic-navigation/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/16-implementing-programmatic-navigation/src/components/UI/LoadingSpinner.js b/code/16-implementing-programmatic-navigation/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/16-implementing-programmatic-navigation/src/components/UI/LoadingSpinner.module.css b/code/16-implementing-programmatic-navigation/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/16-implementing-programmatic-navigation/src/components/comments/CommentItem.js b/code/16-implementing-programmatic-navigation/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/16-implementing-programmatic-navigation/src/components/comments/CommentItem.module.css b/code/16-implementing-programmatic-navigation/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/16-implementing-programmatic-navigation/src/components/comments/Comments.js b/code/16-implementing-programmatic-navigation/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/16-implementing-programmatic-navigation/src/components/comments/Comments.module.css b/code/16-implementing-programmatic-navigation/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/16-implementing-programmatic-navigation/src/components/comments/CommentsList.js b/code/16-implementing-programmatic-navigation/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/16-implementing-programmatic-navigation/src/components/comments/CommentsList.module.css b/code/16-implementing-programmatic-navigation/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/16-implementing-programmatic-navigation/src/components/comments/NewCommentForm.js b/code/16-implementing-programmatic-navigation/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/16-implementing-programmatic-navigation/src/components/comments/NewCommentForm.module.css b/code/16-implementing-programmatic-navigation/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/16-implementing-programmatic-navigation/src/components/layout/Layout.js b/code/16-implementing-programmatic-navigation/src/components/layout/Layout.js new file mode 100644 index 0000000000..2eff00a625 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/layout/Layout.js @@ -0,0 +1,15 @@ +import { Fragment } from 'react'; + +import classes from './Layout.module.css'; +import MainNavigation from './MainNavigation'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/16-implementing-programmatic-navigation/src/components/layout/Layout.module.css b/code/16-implementing-programmatic-navigation/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/16-implementing-programmatic-navigation/src/components/layout/MainNavigation.js b/code/16-implementing-programmatic-navigation/src/components/layout/MainNavigation.js new file mode 100644 index 0000000000..76d790a1c3 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/layout/MainNavigation.js @@ -0,0 +1,27 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainNavigation.module.css'; + +const MainNavigation = () => { + return ( +
    +
    Great Quotes
    + +
    + ); +}; + +export default MainNavigation; diff --git a/code/16-implementing-programmatic-navigation/src/components/layout/MainNavigation.module.css b/code/16-implementing-programmatic-navigation/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/16-implementing-programmatic-navigation/src/components/quotes/HighlightedQuote.js b/code/16-implementing-programmatic-navigation/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/16-implementing-programmatic-navigation/src/components/quotes/HighlightedQuote.module.css b/code/16-implementing-programmatic-navigation/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/16-implementing-programmatic-navigation/src/components/quotes/NoQuotesFound.js b/code/16-implementing-programmatic-navigation/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..14a83fa67c --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,14 @@ +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/16-implementing-programmatic-navigation/src/components/quotes/NoQuotesFound.module.css b/code/16-implementing-programmatic-navigation/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.js b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..464e2073d7 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.js @@ -0,0 +1,47 @@ +import { useRef } from 'react'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + return ( + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.module.css b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteItem.js b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteItem.module.css b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteList.js b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..a37943f11c --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteList.js @@ -0,0 +1,23 @@ +import { Fragment } from 'react'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const QuoteList = (props) => { + return ( + +
      + {props.quotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteList.module.css b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/16-implementing-programmatic-navigation/src/index.css b/code/16-implementing-programmatic-navigation/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/16-implementing-programmatic-navigation/src/index.js b/code/16-implementing-programmatic-navigation/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/16-implementing-programmatic-navigation/src/pages/AllQuotes.js b/code/16-implementing-programmatic-navigation/src/pages/AllQuotes.js new file mode 100644 index 0000000000..6362e57d3f --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/pages/AllQuotes.js @@ -0,0 +1,12 @@ +import QuoteList from '../components/quotes/QuoteList'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const AllQuotes = () => { + return +}; + +export default AllQuotes; \ No newline at end of file diff --git a/code/16-implementing-programmatic-navigation/src/pages/NewQuote.js b/code/16-implementing-programmatic-navigation/src/pages/NewQuote.js new file mode 100644 index 0000000000..2bf8efb2d2 --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/pages/NewQuote.js @@ -0,0 +1,17 @@ +import { useHistory } from 'react-router-dom'; + +import QuoteForm from '../components/quotes/QuoteForm'; + +const NewQuote = () => { + const history = useHistory(); + + const addQuoteHandler = (quoteData) => { + console.log(quoteData); + + history.push('/quotes'); + }; + + return ; +}; + +export default NewQuote; diff --git a/code/16-implementing-programmatic-navigation/src/pages/NotFound.js b/code/16-implementing-programmatic-navigation/src/pages/NotFound.js new file mode 100644 index 0000000000..bb7c85baac --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/pages/NotFound.js @@ -0,0 +1,9 @@ +const NotFound = () => { + return ( +
    +

    Page not found!

    +
    + ); +}; + +export default NotFound; diff --git a/code/16-implementing-programmatic-navigation/src/pages/QuoteDetail.js b/code/16-implementing-programmatic-navigation/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..8381efa2ac --- /dev/null +++ b/code/16-implementing-programmatic-navigation/src/pages/QuoteDetail.js @@ -0,0 +1,31 @@ +import { Fragment } from 'react'; +import { useParams, Route } from 'react-router-dom'; + +import HighlightedQuote from '../components/quotes/HighlightedQuote'; +import Comments from '../components/comments/Comments'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const QuoteDetail = () => { + const params = useParams(); + + const quote = DUMMY_QUOTES.find((quote) => quote.id === params.quoteId); + + if (!quote) { + return

    No quote found!

    ; + } + + return ( + + + + + + + ); +}; + +export default QuoteDetail; diff --git a/code/17-preventing-unwanted-route-transitions/package.json b/code/17-preventing-unwanted-route-transitions/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/17-preventing-unwanted-route-transitions/public/favicon.ico b/code/17-preventing-unwanted-route-transitions/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/17-preventing-unwanted-route-transitions/public/index.html b/code/17-preventing-unwanted-route-transitions/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/17-preventing-unwanted-route-transitions/public/logo192.png b/code/17-preventing-unwanted-route-transitions/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/17-preventing-unwanted-route-transitions/public/manifest.json b/code/17-preventing-unwanted-route-transitions/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/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/17-preventing-unwanted-route-transitions/public/robots.txt b/code/17-preventing-unwanted-route-transitions/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/17-preventing-unwanted-route-transitions/src/App.js b/code/17-preventing-unwanted-route-transitions/src/App.js new file mode 100644 index 0000000000..97eb33fc84 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/App.js @@ -0,0 +1,33 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; +import NotFound from './pages/NotFound'; +import Layout from './components/layout/Layout'; + +function App() { + return ( + + + + + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/UI/Card.js b/code/17-preventing-unwanted-route-transitions/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/UI/Card.module.css b/code/17-preventing-unwanted-route-transitions/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/17-preventing-unwanted-route-transitions/src/components/UI/LoadingSpinner.js b/code/17-preventing-unwanted-route-transitions/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/UI/LoadingSpinner.module.css b/code/17-preventing-unwanted-route-transitions/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentItem.js b/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentItem.module.css b/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/17-preventing-unwanted-route-transitions/src/components/comments/Comments.js b/code/17-preventing-unwanted-route-transitions/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/comments/Comments.module.css b/code/17-preventing-unwanted-route-transitions/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentsList.js b/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentsList.module.css b/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/17-preventing-unwanted-route-transitions/src/components/comments/NewCommentForm.js b/code/17-preventing-unwanted-route-transitions/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/comments/NewCommentForm.module.css b/code/17-preventing-unwanted-route-transitions/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/17-preventing-unwanted-route-transitions/src/components/layout/Layout.js b/code/17-preventing-unwanted-route-transitions/src/components/layout/Layout.js new file mode 100644 index 0000000000..2eff00a625 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/layout/Layout.js @@ -0,0 +1,15 @@ +import { Fragment } from 'react'; + +import classes from './Layout.module.css'; +import MainNavigation from './MainNavigation'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/layout/Layout.module.css b/code/17-preventing-unwanted-route-transitions/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/17-preventing-unwanted-route-transitions/src/components/layout/MainNavigation.js b/code/17-preventing-unwanted-route-transitions/src/components/layout/MainNavigation.js new file mode 100644 index 0000000000..76d790a1c3 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/layout/MainNavigation.js @@ -0,0 +1,27 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainNavigation.module.css'; + +const MainNavigation = () => { + return ( +
    +
    Great Quotes
    + +
    + ); +}; + +export default MainNavigation; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/layout/MainNavigation.module.css b/code/17-preventing-unwanted-route-transitions/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/17-preventing-unwanted-route-transitions/src/components/quotes/HighlightedQuote.js b/code/17-preventing-unwanted-route-transitions/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/quotes/HighlightedQuote.module.css b/code/17-preventing-unwanted-route-transitions/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/17-preventing-unwanted-route-transitions/src/components/quotes/NoQuotesFound.js b/code/17-preventing-unwanted-route-transitions/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..14a83fa67c --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,14 @@ +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/quotes/NoQuotesFound.module.css b/code/17-preventing-unwanted-route-transitions/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.js b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..823a1e93bc --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.js @@ -0,0 +1,70 @@ +import { Fragment, useRef, useState } from 'react'; +import { Prompt } from 'react-router-dom'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const [isEntering, setIsEntering] = useState(false); + + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + const finishEnteringHandler = () => { + setIsEntering(false); + }; + + const formFocusedHandler = () => { + setIsEntering(true); + }; + + return ( + + + 'Are you sure you want to leave? All your entered data will be lost!' + } + /> + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.module.css b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteItem.js b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteItem.module.css b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteList.js b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..a37943f11c --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteList.js @@ -0,0 +1,23 @@ +import { Fragment } from 'react'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const QuoteList = (props) => { + return ( + +
      + {props.quotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteList.module.css b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/17-preventing-unwanted-route-transitions/src/index.css b/code/17-preventing-unwanted-route-transitions/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/17-preventing-unwanted-route-transitions/src/index.js b/code/17-preventing-unwanted-route-transitions/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/17-preventing-unwanted-route-transitions/src/pages/AllQuotes.js b/code/17-preventing-unwanted-route-transitions/src/pages/AllQuotes.js new file mode 100644 index 0000000000..6362e57d3f --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/pages/AllQuotes.js @@ -0,0 +1,12 @@ +import QuoteList from '../components/quotes/QuoteList'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const AllQuotes = () => { + return +}; + +export default AllQuotes; \ No newline at end of file diff --git a/code/17-preventing-unwanted-route-transitions/src/pages/NewQuote.js b/code/17-preventing-unwanted-route-transitions/src/pages/NewQuote.js new file mode 100644 index 0000000000..2bf8efb2d2 --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/pages/NewQuote.js @@ -0,0 +1,17 @@ +import { useHistory } from 'react-router-dom'; + +import QuoteForm from '../components/quotes/QuoteForm'; + +const NewQuote = () => { + const history = useHistory(); + + const addQuoteHandler = (quoteData) => { + console.log(quoteData); + + history.push('/quotes'); + }; + + return ; +}; + +export default NewQuote; diff --git a/code/17-preventing-unwanted-route-transitions/src/pages/NotFound.js b/code/17-preventing-unwanted-route-transitions/src/pages/NotFound.js new file mode 100644 index 0000000000..bb7c85baac --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/pages/NotFound.js @@ -0,0 +1,9 @@ +const NotFound = () => { + return ( +
    +

    Page not found!

    +
    + ); +}; + +export default NotFound; diff --git a/code/17-preventing-unwanted-route-transitions/src/pages/QuoteDetail.js b/code/17-preventing-unwanted-route-transitions/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..8381efa2ac --- /dev/null +++ b/code/17-preventing-unwanted-route-transitions/src/pages/QuoteDetail.js @@ -0,0 +1,31 @@ +import { Fragment } from 'react'; +import { useParams, Route } from 'react-router-dom'; + +import HighlightedQuote from '../components/quotes/HighlightedQuote'; +import Comments from '../components/comments/Comments'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const QuoteDetail = () => { + const params = useParams(); + + const quote = DUMMY_QUOTES.find((quote) => quote.id === params.quoteId); + + if (!quote) { + return

    No quote found!

    ; + } + + return ( + + + + + + + ); +}; + +export default QuoteDetail; diff --git a/code/18-working-with-query-params/package.json b/code/18-working-with-query-params/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/18-working-with-query-params/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/18-working-with-query-params/public/favicon.ico b/code/18-working-with-query-params/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/18-working-with-query-params/public/index.html b/code/18-working-with-query-params/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/18-working-with-query-params/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/18-working-with-query-params/public/logo192.png b/code/18-working-with-query-params/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/18-working-with-query-params/public/manifest.json b/code/18-working-with-query-params/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/18-working-with-query-params/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/18-working-with-query-params/public/robots.txt b/code/18-working-with-query-params/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/18-working-with-query-params/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/18-working-with-query-params/src/App.js b/code/18-working-with-query-params/src/App.js new file mode 100644 index 0000000000..97eb33fc84 --- /dev/null +++ b/code/18-working-with-query-params/src/App.js @@ -0,0 +1,33 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; +import NotFound from './pages/NotFound'; +import Layout from './components/layout/Layout'; + +function App() { + return ( + + + + + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/18-working-with-query-params/src/components/UI/Card.js b/code/18-working-with-query-params/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/18-working-with-query-params/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/18-working-with-query-params/src/components/UI/Card.module.css b/code/18-working-with-query-params/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/18-working-with-query-params/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/18-working-with-query-params/src/components/UI/LoadingSpinner.js b/code/18-working-with-query-params/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/18-working-with-query-params/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/18-working-with-query-params/src/components/UI/LoadingSpinner.module.css b/code/18-working-with-query-params/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/18-working-with-query-params/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/18-working-with-query-params/src/components/comments/CommentItem.js b/code/18-working-with-query-params/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/18-working-with-query-params/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/18-working-with-query-params/src/components/comments/CommentItem.module.css b/code/18-working-with-query-params/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/18-working-with-query-params/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/18-working-with-query-params/src/components/comments/Comments.js b/code/18-working-with-query-params/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/18-working-with-query-params/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/18-working-with-query-params/src/components/comments/Comments.module.css b/code/18-working-with-query-params/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/18-working-with-query-params/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/18-working-with-query-params/src/components/comments/CommentsList.js b/code/18-working-with-query-params/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/18-working-with-query-params/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/18-working-with-query-params/src/components/comments/CommentsList.module.css b/code/18-working-with-query-params/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/18-working-with-query-params/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/18-working-with-query-params/src/components/comments/NewCommentForm.js b/code/18-working-with-query-params/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/18-working-with-query-params/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/18-working-with-query-params/src/components/comments/NewCommentForm.module.css b/code/18-working-with-query-params/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/18-working-with-query-params/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/18-working-with-query-params/src/components/layout/Layout.js b/code/18-working-with-query-params/src/components/layout/Layout.js new file mode 100644 index 0000000000..2eff00a625 --- /dev/null +++ b/code/18-working-with-query-params/src/components/layout/Layout.js @@ -0,0 +1,15 @@ +import { Fragment } from 'react'; + +import classes from './Layout.module.css'; +import MainNavigation from './MainNavigation'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/18-working-with-query-params/src/components/layout/Layout.module.css b/code/18-working-with-query-params/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/18-working-with-query-params/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/18-working-with-query-params/src/components/layout/MainNavigation.js b/code/18-working-with-query-params/src/components/layout/MainNavigation.js new file mode 100644 index 0000000000..76d790a1c3 --- /dev/null +++ b/code/18-working-with-query-params/src/components/layout/MainNavigation.js @@ -0,0 +1,27 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainNavigation.module.css'; + +const MainNavigation = () => { + return ( +
    +
    Great Quotes
    + +
    + ); +}; + +export default MainNavigation; diff --git a/code/18-working-with-query-params/src/components/layout/MainNavigation.module.css b/code/18-working-with-query-params/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/18-working-with-query-params/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/18-working-with-query-params/src/components/quotes/HighlightedQuote.js b/code/18-working-with-query-params/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/18-working-with-query-params/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/18-working-with-query-params/src/components/quotes/HighlightedQuote.module.css b/code/18-working-with-query-params/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/18-working-with-query-params/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/18-working-with-query-params/src/components/quotes/NoQuotesFound.js b/code/18-working-with-query-params/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..14a83fa67c --- /dev/null +++ b/code/18-working-with-query-params/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,14 @@ +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/18-working-with-query-params/src/components/quotes/NoQuotesFound.module.css b/code/18-working-with-query-params/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/18-working-with-query-params/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/18-working-with-query-params/src/components/quotes/QuoteForm.js b/code/18-working-with-query-params/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..823a1e93bc --- /dev/null +++ b/code/18-working-with-query-params/src/components/quotes/QuoteForm.js @@ -0,0 +1,70 @@ +import { Fragment, useRef, useState } from 'react'; +import { Prompt } from 'react-router-dom'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const [isEntering, setIsEntering] = useState(false); + + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + const finishEnteringHandler = () => { + setIsEntering(false); + }; + + const formFocusedHandler = () => { + setIsEntering(true); + }; + + return ( + + + 'Are you sure you want to leave? All your entered data will be lost!' + } + /> + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/18-working-with-query-params/src/components/quotes/QuoteForm.module.css b/code/18-working-with-query-params/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/18-working-with-query-params/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/18-working-with-query-params/src/components/quotes/QuoteItem.js b/code/18-working-with-query-params/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/18-working-with-query-params/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/18-working-with-query-params/src/components/quotes/QuoteItem.module.css b/code/18-working-with-query-params/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/18-working-with-query-params/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/18-working-with-query-params/src/components/quotes/QuoteList.js b/code/18-working-with-query-params/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..d320fbdf83 --- /dev/null +++ b/code/18-working-with-query-params/src/components/quotes/QuoteList.js @@ -0,0 +1,52 @@ +import { Fragment } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const sortQuotes = (quotes, ascending) => { + return quotes.sort((quoteA, quoteB) => { + if (ascending) { + return quoteA.id > quoteB.id ? 1 : -1; + } else { + return quoteA.id < quoteB.id ? 1 : -1; + } + }); +}; + +const QuoteList = (props) => { + const history = useHistory(); + const location = useLocation(); + + const queryParams = new URLSearchParams(location.search); + + const isSortingAscending = queryParams.get('sort') === 'asc'; + + const sortedQuotes = sortQuotes(props.quotes, isSortingAscending); + + const changeSortingHandler = () => { + history.push('/quotes?sort=' + (isSortingAscending ? 'desc' : 'asc')); + }; + + return ( + +
    + +
    +
      + {sortedQuotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/18-working-with-query-params/src/components/quotes/QuoteList.module.css b/code/18-working-with-query-params/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/18-working-with-query-params/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/18-working-with-query-params/src/index.css b/code/18-working-with-query-params/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/18-working-with-query-params/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/18-working-with-query-params/src/index.js b/code/18-working-with-query-params/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/18-working-with-query-params/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/18-working-with-query-params/src/pages/AllQuotes.js b/code/18-working-with-query-params/src/pages/AllQuotes.js new file mode 100644 index 0000000000..6362e57d3f --- /dev/null +++ b/code/18-working-with-query-params/src/pages/AllQuotes.js @@ -0,0 +1,12 @@ +import QuoteList from '../components/quotes/QuoteList'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const AllQuotes = () => { + return +}; + +export default AllQuotes; \ No newline at end of file diff --git a/code/18-working-with-query-params/src/pages/NewQuote.js b/code/18-working-with-query-params/src/pages/NewQuote.js new file mode 100644 index 0000000000..2bf8efb2d2 --- /dev/null +++ b/code/18-working-with-query-params/src/pages/NewQuote.js @@ -0,0 +1,17 @@ +import { useHistory } from 'react-router-dom'; + +import QuoteForm from '../components/quotes/QuoteForm'; + +const NewQuote = () => { + const history = useHistory(); + + const addQuoteHandler = (quoteData) => { + console.log(quoteData); + + history.push('/quotes'); + }; + + return ; +}; + +export default NewQuote; diff --git a/code/18-working-with-query-params/src/pages/NotFound.js b/code/18-working-with-query-params/src/pages/NotFound.js new file mode 100644 index 0000000000..bb7c85baac --- /dev/null +++ b/code/18-working-with-query-params/src/pages/NotFound.js @@ -0,0 +1,9 @@ +const NotFound = () => { + return ( +
    +

    Page not found!

    +
    + ); +}; + +export default NotFound; diff --git a/code/18-working-with-query-params/src/pages/QuoteDetail.js b/code/18-working-with-query-params/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..8381efa2ac --- /dev/null +++ b/code/18-working-with-query-params/src/pages/QuoteDetail.js @@ -0,0 +1,31 @@ +import { Fragment } from 'react'; +import { useParams, Route } from 'react-router-dom'; + +import HighlightedQuote from '../components/quotes/HighlightedQuote'; +import Comments from '../components/comments/Comments'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const QuoteDetail = () => { + const params = useParams(); + + const quote = DUMMY_QUOTES.find((quote) => quote.id === params.quoteId); + + if (!quote) { + return

    No quote found!

    ; + } + + return ( + + + + + + + ); +}; + +export default QuoteDetail; diff --git a/code/19-writing-more-flexible-routing-code/package.json b/code/19-writing-more-flexible-routing-code/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/19-writing-more-flexible-routing-code/public/favicon.ico b/code/19-writing-more-flexible-routing-code/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/19-writing-more-flexible-routing-code/public/index.html b/code/19-writing-more-flexible-routing-code/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/19-writing-more-flexible-routing-code/public/logo192.png b/code/19-writing-more-flexible-routing-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/19-writing-more-flexible-routing-code/public/manifest.json b/code/19-writing-more-flexible-routing-code/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/19-writing-more-flexible-routing-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/19-writing-more-flexible-routing-code/public/robots.txt b/code/19-writing-more-flexible-routing-code/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/19-writing-more-flexible-routing-code/src/App.js b/code/19-writing-more-flexible-routing-code/src/App.js new file mode 100644 index 0000000000..97eb33fc84 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/App.js @@ -0,0 +1,33 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; +import NotFound from './pages/NotFound'; +import Layout from './components/layout/Layout'; + +function App() { + return ( + + + + + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/19-writing-more-flexible-routing-code/src/components/UI/Card.js b/code/19-writing-more-flexible-routing-code/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/19-writing-more-flexible-routing-code/src/components/UI/Card.module.css b/code/19-writing-more-flexible-routing-code/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/19-writing-more-flexible-routing-code/src/components/UI/LoadingSpinner.js b/code/19-writing-more-flexible-routing-code/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/19-writing-more-flexible-routing-code/src/components/UI/LoadingSpinner.module.css b/code/19-writing-more-flexible-routing-code/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/19-writing-more-flexible-routing-code/src/components/comments/CommentItem.js b/code/19-writing-more-flexible-routing-code/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/19-writing-more-flexible-routing-code/src/components/comments/CommentItem.module.css b/code/19-writing-more-flexible-routing-code/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/19-writing-more-flexible-routing-code/src/components/comments/Comments.js b/code/19-writing-more-flexible-routing-code/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/19-writing-more-flexible-routing-code/src/components/comments/Comments.module.css b/code/19-writing-more-flexible-routing-code/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/19-writing-more-flexible-routing-code/src/components/comments/CommentsList.js b/code/19-writing-more-flexible-routing-code/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/19-writing-more-flexible-routing-code/src/components/comments/CommentsList.module.css b/code/19-writing-more-flexible-routing-code/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/19-writing-more-flexible-routing-code/src/components/comments/NewCommentForm.js b/code/19-writing-more-flexible-routing-code/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/19-writing-more-flexible-routing-code/src/components/comments/NewCommentForm.module.css b/code/19-writing-more-flexible-routing-code/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/19-writing-more-flexible-routing-code/src/components/layout/Layout.js b/code/19-writing-more-flexible-routing-code/src/components/layout/Layout.js new file mode 100644 index 0000000000..2eff00a625 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/layout/Layout.js @@ -0,0 +1,15 @@ +import { Fragment } from 'react'; + +import classes from './Layout.module.css'; +import MainNavigation from './MainNavigation'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/19-writing-more-flexible-routing-code/src/components/layout/Layout.module.css b/code/19-writing-more-flexible-routing-code/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/19-writing-more-flexible-routing-code/src/components/layout/MainNavigation.js b/code/19-writing-more-flexible-routing-code/src/components/layout/MainNavigation.js new file mode 100644 index 0000000000..76d790a1c3 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/layout/MainNavigation.js @@ -0,0 +1,27 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainNavigation.module.css'; + +const MainNavigation = () => { + return ( +
    +
    Great Quotes
    + +
    + ); +}; + +export default MainNavigation; diff --git a/code/19-writing-more-flexible-routing-code/src/components/layout/MainNavigation.module.css b/code/19-writing-more-flexible-routing-code/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/19-writing-more-flexible-routing-code/src/components/quotes/HighlightedQuote.js b/code/19-writing-more-flexible-routing-code/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/19-writing-more-flexible-routing-code/src/components/quotes/HighlightedQuote.module.css b/code/19-writing-more-flexible-routing-code/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/19-writing-more-flexible-routing-code/src/components/quotes/NoQuotesFound.js b/code/19-writing-more-flexible-routing-code/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..14a83fa67c --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,14 @@ +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/19-writing-more-flexible-routing-code/src/components/quotes/NoQuotesFound.module.css b/code/19-writing-more-flexible-routing-code/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.js b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..823a1e93bc --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.js @@ -0,0 +1,70 @@ +import { Fragment, useRef, useState } from 'react'; +import { Prompt } from 'react-router-dom'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const [isEntering, setIsEntering] = useState(false); + + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + const finishEnteringHandler = () => { + setIsEntering(false); + }; + + const formFocusedHandler = () => { + setIsEntering(true); + }; + + return ( + + + 'Are you sure you want to leave? All your entered data will be lost!' + } + /> + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.module.css b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteItem.js b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteItem.module.css b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteList.js b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..05cab9d98b --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteList.js @@ -0,0 +1,55 @@ +import { Fragment } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const sortQuotes = (quotes, ascending) => { + return quotes.sort((quoteA, quoteB) => { + if (ascending) { + return quoteA.id > quoteB.id ? 1 : -1; + } else { + return quoteA.id < quoteB.id ? 1 : -1; + } + }); +}; + +const QuoteList = (props) => { + const history = useHistory(); + const location = useLocation(); + + const queryParams = new URLSearchParams(location.search); + + const isSortingAscending = queryParams.get('sort') === 'asc'; + + const sortedQuotes = sortQuotes(props.quotes, isSortingAscending); + + const changeSortingHandler = () => { + history.push({ + pathname: location.pathname, + search: `?sort=${(isSortingAscending ? 'desc' : 'asc')}` + }); + }; + + return ( + +
    + +
    +
      + {sortedQuotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteList.module.css b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/19-writing-more-flexible-routing-code/src/index.css b/code/19-writing-more-flexible-routing-code/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/19-writing-more-flexible-routing-code/src/index.js b/code/19-writing-more-flexible-routing-code/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/19-writing-more-flexible-routing-code/src/pages/AllQuotes.js b/code/19-writing-more-flexible-routing-code/src/pages/AllQuotes.js new file mode 100644 index 0000000000..6362e57d3f --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/pages/AllQuotes.js @@ -0,0 +1,12 @@ +import QuoteList from '../components/quotes/QuoteList'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const AllQuotes = () => { + return +}; + +export default AllQuotes; \ No newline at end of file diff --git a/code/19-writing-more-flexible-routing-code/src/pages/NewQuote.js b/code/19-writing-more-flexible-routing-code/src/pages/NewQuote.js new file mode 100644 index 0000000000..2bf8efb2d2 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/pages/NewQuote.js @@ -0,0 +1,17 @@ +import { useHistory } from 'react-router-dom'; + +import QuoteForm from '../components/quotes/QuoteForm'; + +const NewQuote = () => { + const history = useHistory(); + + const addQuoteHandler = (quoteData) => { + console.log(quoteData); + + history.push('/quotes'); + }; + + return ; +}; + +export default NewQuote; diff --git a/code/19-writing-more-flexible-routing-code/src/pages/NotFound.js b/code/19-writing-more-flexible-routing-code/src/pages/NotFound.js new file mode 100644 index 0000000000..bb7c85baac --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/pages/NotFound.js @@ -0,0 +1,9 @@ +const NotFound = () => { + return ( +
    +

    Page not found!

    +
    + ); +}; + +export default NotFound; diff --git a/code/19-writing-more-flexible-routing-code/src/pages/QuoteDetail.js b/code/19-writing-more-flexible-routing-code/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..db5ef5d861 --- /dev/null +++ b/code/19-writing-more-flexible-routing-code/src/pages/QuoteDetail.js @@ -0,0 +1,39 @@ +import { Fragment } from 'react'; +import { useParams, Route, Link, useRouteMatch } from 'react-router-dom'; + +import HighlightedQuote from '../components/quotes/HighlightedQuote'; +import Comments from '../components/comments/Comments'; + +const DUMMY_QUOTES = [ + { id: 'q1', author: 'Max', text: 'Learning React is fun!' }, + { id: 'q2', author: 'Maximilian', text: 'Learning React is great!' }, +]; + +const QuoteDetail = () => { + const match = useRouteMatch(); + const params = useParams(); + + const quote = DUMMY_QUOTES.find((quote) => quote.id === params.quoteId); + + if (!quote) { + return

    No quote found!

    ; + } + + return ( + + + +
    + + Load Comments + +
    +
    + + + +
    + ); +}; + +export default QuoteDetail; diff --git a/code/20-sending-getting-quote-data/package.json b/code/20-sending-getting-quote-data/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/20-sending-getting-quote-data/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/20-sending-getting-quote-data/public/favicon.ico b/code/20-sending-getting-quote-data/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_SjAPB3vGa^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/20-sending-getting-quote-data/public/index.html b/code/20-sending-getting-quote-data/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/20-sending-getting-quote-data/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/20-sending-getting-quote-data/public/logo192.png b/code/20-sending-getting-quote-data/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/20-sending-getting-quote-data/public/manifest.json b/code/20-sending-getting-quote-data/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/20-sending-getting-quote-data/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/20-sending-getting-quote-data/public/robots.txt b/code/20-sending-getting-quote-data/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/20-sending-getting-quote-data/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/20-sending-getting-quote-data/src/App.js b/code/20-sending-getting-quote-data/src/App.js new file mode 100644 index 0000000000..97eb33fc84 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/App.js @@ -0,0 +1,33 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; +import NotFound from './pages/NotFound'; +import Layout from './components/layout/Layout'; + +function App() { + return ( + + + + + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/20-sending-getting-quote-data/src/components/UI/Card.js b/code/20-sending-getting-quote-data/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/20-sending-getting-quote-data/src/components/UI/Card.module.css b/code/20-sending-getting-quote-data/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/20-sending-getting-quote-data/src/components/UI/LoadingSpinner.js b/code/20-sending-getting-quote-data/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/20-sending-getting-quote-data/src/components/UI/LoadingSpinner.module.css b/code/20-sending-getting-quote-data/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/20-sending-getting-quote-data/src/components/comments/CommentItem.js b/code/20-sending-getting-quote-data/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/20-sending-getting-quote-data/src/components/comments/CommentItem.module.css b/code/20-sending-getting-quote-data/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/20-sending-getting-quote-data/src/components/comments/Comments.js b/code/20-sending-getting-quote-data/src/components/comments/Comments.js new file mode 100644 index 0000000000..6dbd006177 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/comments/Comments.js @@ -0,0 +1,27 @@ +import { useState } from 'react'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && } +

    Comments...

    +
    + ); +}; + +export default Comments; diff --git a/code/20-sending-getting-quote-data/src/components/comments/Comments.module.css b/code/20-sending-getting-quote-data/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/20-sending-getting-quote-data/src/components/comments/CommentsList.js b/code/20-sending-getting-quote-data/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/20-sending-getting-quote-data/src/components/comments/CommentsList.module.css b/code/20-sending-getting-quote-data/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/20-sending-getting-quote-data/src/components/comments/NewCommentForm.js b/code/20-sending-getting-quote-data/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..2950b1e837 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/comments/NewCommentForm.js @@ -0,0 +1,29 @@ +import { useRef } from 'react'; + +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const submitFormHandler = (event) => { + event.preventDefault(); + + // optional: Could validate here + + // send comment to server + }; + + return ( +
    +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/20-sending-getting-quote-data/src/components/comments/NewCommentForm.module.css b/code/20-sending-getting-quote-data/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/20-sending-getting-quote-data/src/components/layout/Layout.js b/code/20-sending-getting-quote-data/src/components/layout/Layout.js new file mode 100644 index 0000000000..2eff00a625 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/layout/Layout.js @@ -0,0 +1,15 @@ +import { Fragment } from 'react'; + +import classes from './Layout.module.css'; +import MainNavigation from './MainNavigation'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/20-sending-getting-quote-data/src/components/layout/Layout.module.css b/code/20-sending-getting-quote-data/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/20-sending-getting-quote-data/src/components/layout/MainNavigation.js b/code/20-sending-getting-quote-data/src/components/layout/MainNavigation.js new file mode 100644 index 0000000000..76d790a1c3 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/layout/MainNavigation.js @@ -0,0 +1,27 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainNavigation.module.css'; + +const MainNavigation = () => { + return ( +
    +
    Great Quotes
    + +
    + ); +}; + +export default MainNavigation; diff --git a/code/20-sending-getting-quote-data/src/components/layout/MainNavigation.module.css b/code/20-sending-getting-quote-data/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/20-sending-getting-quote-data/src/components/quotes/HighlightedQuote.js b/code/20-sending-getting-quote-data/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/20-sending-getting-quote-data/src/components/quotes/HighlightedQuote.module.css b/code/20-sending-getting-quote-data/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/20-sending-getting-quote-data/src/components/quotes/NoQuotesFound.js b/code/20-sending-getting-quote-data/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..8642a10db4 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,16 @@ +import { Link } from 'react-router-dom'; + +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    +

    No quotes found!

    + + Add a Quote + +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/20-sending-getting-quote-data/src/components/quotes/NoQuotesFound.module.css b/code/20-sending-getting-quote-data/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.js b/code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..823a1e93bc --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.js @@ -0,0 +1,70 @@ +import { Fragment, useRef, useState } from 'react'; +import { Prompt } from 'react-router-dom'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const [isEntering, setIsEntering] = useState(false); + + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + const finishEnteringHandler = () => { + setIsEntering(false); + }; + + const formFocusedHandler = () => { + setIsEntering(true); + }; + + return ( + + + 'Are you sure you want to leave? All your entered data will be lost!' + } + /> + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.module.css b/code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/20-sending-getting-quote-data/src/components/quotes/QuoteItem.js b/code/20-sending-getting-quote-data/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/20-sending-getting-quote-data/src/components/quotes/QuoteItem.module.css b/code/20-sending-getting-quote-data/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/20-sending-getting-quote-data/src/components/quotes/QuoteList.js b/code/20-sending-getting-quote-data/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..05cab9d98b --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/quotes/QuoteList.js @@ -0,0 +1,55 @@ +import { Fragment } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const sortQuotes = (quotes, ascending) => { + return quotes.sort((quoteA, quoteB) => { + if (ascending) { + return quoteA.id > quoteB.id ? 1 : -1; + } else { + return quoteA.id < quoteB.id ? 1 : -1; + } + }); +}; + +const QuoteList = (props) => { + const history = useHistory(); + const location = useLocation(); + + const queryParams = new URLSearchParams(location.search); + + const isSortingAscending = queryParams.get('sort') === 'asc'; + + const sortedQuotes = sortQuotes(props.quotes, isSortingAscending); + + const changeSortingHandler = () => { + history.push({ + pathname: location.pathname, + search: `?sort=${(isSortingAscending ? 'desc' : 'asc')}` + }); + }; + + return ( + +
    + +
    +
      + {sortedQuotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/20-sending-getting-quote-data/src/components/quotes/QuoteList.module.css b/code/20-sending-getting-quote-data/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/20-sending-getting-quote-data/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/20-sending-getting-quote-data/src/hooks/use-http.js b/code/20-sending-getting-quote-data/src/hooks/use-http.js new file mode 100644 index 0000000000..b7c56bc76a --- /dev/null +++ b/code/20-sending-getting-quote-data/src/hooks/use-http.js @@ -0,0 +1,60 @@ +import { useReducer, useCallback } from 'react'; + +function httpReducer(state, action) { + if (action.type === 'SEND') { + return { + data: null, + error: null, + status: 'pending', + }; + } + + if (action.type === 'SUCCESS') { + return { + data: action.responseData, + error: null, + status: 'completed', + }; + } + + if (action.type === 'ERROR') { + return { + data: null, + error: action.errorMessage, + status: 'completed', + }; + } + + return state; +} + +function useHttp(requestFunction, startWithPending = false) { + const [httpState, dispatch] = useReducer(httpReducer, { + status: startWithPending ? 'pending' : null, + data: null, + error: null, + }); + + const sendRequest = useCallback( + async function (requestData) { + dispatch({ type: 'SEND' }); + try { + const responseData = await requestFunction(requestData); + dispatch({ type: 'SUCCESS', responseData }); + } catch (error) { + dispatch({ + type: 'ERROR', + errorMessage: error.message || 'Something went wrong!', + }); + } + }, + [requestFunction] + ); + + return { + sendRequest, + ...httpState, + }; +} + +export default useHttp; diff --git a/code/20-sending-getting-quote-data/src/index.css b/code/20-sending-getting-quote-data/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/20-sending-getting-quote-data/src/index.js b/code/20-sending-getting-quote-data/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/20-sending-getting-quote-data/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/20-sending-getting-quote-data/src/lib/api.js b/code/20-sending-getting-quote-data/src/lib/api.js new file mode 100644 index 0000000000..17d2b1a359 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/lib/api.js @@ -0,0 +1,96 @@ +const FIREBASE_DOMAIN = 'https://react-prep-default-rtdb.firebaseio.com'; + +export async function getAllQuotes() { + const response = await fetch(`${FIREBASE_DOMAIN}/quotes.json`); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Could not fetch quotes.'); + } + + const transformedQuotes = []; + + for (const key in data) { + const quoteObj = { + id: key, + ...data[key], + }; + + transformedQuotes.push(quoteObj); + } + + return transformedQuotes; +} + +export async function getSingleQuote(quoteId) { + const response = await fetch(`${FIREBASE_DOMAIN}/quotes/${quoteId}.json`); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Could not fetch quote.'); + } + + const loadedQuote = { + id: quoteId, + ...data, + }; + + return loadedQuote; +} + +export async function addQuote(quoteData) { + const response = await fetch(`${FIREBASE_DOMAIN}/quotes.json`, { + method: 'POST', + body: JSON.stringify(quoteData), + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Could not create quote.'); + } + + return null; +} + +export async function addComment(commentData, quoteId) { + const response = await fetch(`${FIREBASE_DOMAIN}/comments/${quoteId}.json`, { + method: 'POST', + body: JSON.stringify(commentData), + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Could not add comment.'); + } + + return { commentId: data.name }; +} + +export async function getAllComments(quoteId) { + const response = await fetch(`${FIREBASE_DOMAIN}/comments/${quoteId}.json`); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Could not get comments.'); + } + + const transformedComments = []; + + for (const key in data) { + const commentObj = { + id: key, + ...data[key], + }; + + transformedComments.push(commentObj); + } + + return transformedComments; +} diff --git a/code/20-sending-getting-quote-data/src/pages/AllQuotes.js b/code/20-sending-getting-quote-data/src/pages/AllQuotes.js new file mode 100644 index 0000000000..db087b5c5c --- /dev/null +++ b/code/20-sending-getting-quote-data/src/pages/AllQuotes.js @@ -0,0 +1,38 @@ +import { useEffect } from 'react'; + +import QuoteList from '../components/quotes/QuoteList'; +import LoadingSpinner from '../components/ui/LoadingSpinner'; +import NoQuotesFound from '../components/quotes/NoQuotesFound'; +import useHttp from '../hooks/use-http'; +import { getAllQuotes } from '../lib/api'; + +const AllQuotes = () => { + const { sendRequest, status, data: loadedQuotes, error } = useHttp( + getAllQuotes, + true + ); + + useEffect(() => { + sendRequest(); + }, [sendRequest]); + + if (status === 'pending') { + return ( +
    + +
    + ); + } + + if (error) { + return

    {error}

    ; + } + + if (status === 'completed' && (!loadedQuotes || loadedQuotes.length === 0)) { + return ; + } + + return ; +}; + +export default AllQuotes; diff --git a/code/20-sending-getting-quote-data/src/pages/NewQuote.js b/code/20-sending-getting-quote-data/src/pages/NewQuote.js new file mode 100644 index 0000000000..b571e25788 --- /dev/null +++ b/code/20-sending-getting-quote-data/src/pages/NewQuote.js @@ -0,0 +1,25 @@ +import { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; + +import QuoteForm from '../components/quotes/QuoteForm'; +import useHttp from '../hooks/use-http'; +import { addQuote } from '../lib/api'; + +const NewQuote = () => { + const { sendRequest, status } = useHttp(addQuote); + const history = useHistory(); + + useEffect(() => { + if (status === 'completed') { + history.push('/quotes'); + } + }, [status, history]); + + const addQuoteHandler = (quoteData) => { + sendRequest(quoteData); + }; + + return ; +}; + +export default NewQuote; diff --git a/code/20-sending-getting-quote-data/src/pages/NotFound.js b/code/20-sending-getting-quote-data/src/pages/NotFound.js new file mode 100644 index 0000000000..bb7c85baac --- /dev/null +++ b/code/20-sending-getting-quote-data/src/pages/NotFound.js @@ -0,0 +1,9 @@ +const NotFound = () => { + return ( +
    +

    Page not found!

    +
    + ); +}; + +export default NotFound; diff --git a/code/20-sending-getting-quote-data/src/pages/QuoteDetail.js b/code/20-sending-getting-quote-data/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..9713e6e48e --- /dev/null +++ b/code/20-sending-getting-quote-data/src/pages/QuoteDetail.js @@ -0,0 +1,58 @@ +import { Fragment, useEffect } from 'react'; +import { useParams, Route, Link, useRouteMatch } from 'react-router-dom'; + +import HighlightedQuote from '../components/quotes/HighlightedQuote'; +import Comments from '../components/comments/Comments'; +import useHttp from '../hooks/use-http'; +import { getSingleQuote } from '../lib/api'; +import LoadingSpinner from '../components/ui/LoadingSpinner'; + +const QuoteDetail = () => { + const match = useRouteMatch(); + const params = useParams(); + + const { quoteId } = params; + + const { sendRequest, status, data: loadedQuote, error } = useHttp( + getSingleQuote, + true + ); + + useEffect(() => { + sendRequest(quoteId); + }, [sendRequest, quoteId]); + + if (status === 'pending') { + return ( +
    + +
    + ); + } + + if (error) { + return

    {error}

    ; + } + + if (!loadedQuote.text) { + return

    No quote found!

    ; + } + + return ( + + + +
    + + Load Comments + +
    +
    + + + +
    + ); +}; + +export default QuoteDetail; diff --git a/code/21-finished/package.json b/code/21-finished/package.json new file mode 100644 index 0000000000..3f648c3ac0 --- /dev/null +++ b/code/21-finished/package.json @@ -0,0 +1,39 @@ +{ + "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-router-dom": "^5.2.0", + "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/21-finished/public/favicon.ico b/code/21-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/21-finished/public/index.html b/code/21-finished/public/index.html new file mode 100644 index 0000000000..aa069f27cb --- /dev/null +++ b/code/21-finished/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
    + + + diff --git a/code/21-finished/public/logo192.png b/code/21-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/21-finished/public/manifest.json b/code/21-finished/public/manifest.json new file mode 100644 index 0000000000..080d6c77ac --- /dev/null +++ b/code/21-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/21-finished/public/robots.txt b/code/21-finished/public/robots.txt new file mode 100644 index 0000000000..e9e57dc4d4 --- /dev/null +++ b/code/21-finished/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/code/21-finished/src/App.js b/code/21-finished/src/App.js new file mode 100644 index 0000000000..97eb33fc84 --- /dev/null +++ b/code/21-finished/src/App.js @@ -0,0 +1,33 @@ +import { Route, Switch, Redirect } from 'react-router-dom'; + +import AllQuotes from './pages/AllQuotes'; +import QuoteDetail from './pages/QuoteDetail'; +import NewQuote from './pages/NewQuote'; +import NotFound from './pages/NotFound'; +import Layout from './components/layout/Layout'; + +function App() { + return ( + + + + + + + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/code/21-finished/src/components/UI/Card.js b/code/21-finished/src/components/UI/Card.js new file mode 100644 index 0000000000..03c3af7db2 --- /dev/null +++ b/code/21-finished/src/components/UI/Card.js @@ -0,0 +1,7 @@ +import classes from './Card.module.css'; + +const Card = (props) => { + return
    {props.children}
    ; +}; + +export default Card; diff --git a/code/21-finished/src/components/UI/Card.module.css b/code/21-finished/src/components/UI/Card.module.css new file mode 100644 index 0000000000..dad43b15fb --- /dev/null +++ b/code/21-finished/src/components/UI/Card.module.css @@ -0,0 +1,7 @@ +.card { + padding: 1rem; + margin: 1rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: white; +} diff --git a/code/21-finished/src/components/UI/LoadingSpinner.js b/code/21-finished/src/components/UI/LoadingSpinner.js new file mode 100644 index 0000000000..4b88a52d45 --- /dev/null +++ b/code/21-finished/src/components/UI/LoadingSpinner.js @@ -0,0 +1,7 @@ +import classes from './LoadingSpinner.module.css'; + +const LoadingSpinner = () => { + return
    ; +} + +export default LoadingSpinner; diff --git a/code/21-finished/src/components/UI/LoadingSpinner.module.css b/code/21-finished/src/components/UI/LoadingSpinner.module.css new file mode 100644 index 0000000000..7b38ef62f1 --- /dev/null +++ b/code/21-finished/src/components/UI/LoadingSpinner.module.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 80px; + height: 80px; +} +.spinner:after { + content: ' '; + display: block; + width: 64px; + height: 64px; + margin: 8px; + border-radius: 50%; + border: 6px solid teal; + border-color: teal transparent teal transparent; + animation: spinner 1.2s linear infinite; +} +@keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/code/21-finished/src/components/comments/CommentItem.js b/code/21-finished/src/components/comments/CommentItem.js new file mode 100644 index 0000000000..448654f269 --- /dev/null +++ b/code/21-finished/src/components/comments/CommentItem.js @@ -0,0 +1,11 @@ +import classes from './CommentItem.module.css'; + +const CommentItem = (props) => { + return ( +
  • +

    {props.text}

    +
  • + ); +}; + +export default CommentItem; diff --git a/code/21-finished/src/components/comments/CommentItem.module.css b/code/21-finished/src/components/comments/CommentItem.module.css new file mode 100644 index 0000000000..21b1bef872 --- /dev/null +++ b/code/21-finished/src/components/comments/CommentItem.module.css @@ -0,0 +1,7 @@ +.item { + margin: 1rem 0; + color: #4a5555; + font-size: 1.25rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid teal;; +} \ No newline at end of file diff --git a/code/21-finished/src/components/comments/Comments.js b/code/21-finished/src/components/comments/Comments.js new file mode 100644 index 0000000000..45f7926246 --- /dev/null +++ b/code/21-finished/src/components/comments/Comments.js @@ -0,0 +1,71 @@ +import { useState, useEffect, useCallback } from 'react'; +import { useParams } from 'react-router-dom'; + +import classes from './Comments.module.css'; +import NewCommentForm from './NewCommentForm'; +import useHttp from '../../hooks/use-http'; +import { getAllComments } from '../../lib/api'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import CommentsList from './CommentsList'; + +const Comments = () => { + const [isAddingComment, setIsAddingComment] = useState(false); + const params = useParams(); + + const { quoteId } = params; + + const { sendRequest, status, data: loadedComments } = useHttp(getAllComments); + + useEffect(() => { + sendRequest(quoteId); + }, [quoteId, sendRequest]); + + const startAddCommentHandler = () => { + setIsAddingComment(true); + }; + + const addedCommentHandler = useCallback(() => { + sendRequest(quoteId); + }, [sendRequest, quoteId]); + + let comments; + + if (status === 'pending') { + comments = ( +
    + +
    + ); + } + + if (status === 'completed' && loadedComments && loadedComments.length > 0) { + comments = ; + } + + if ( + status === 'completed' && + (!loadedComments || loadedComments.length === 0) + ) { + comments =

    No comments were added yet!

    ; + } + + return ( +
    +

    User Comments

    + {!isAddingComment && ( + + )} + {isAddingComment && ( + + )} + {comments} +
    + ); +}; + +export default Comments; diff --git a/code/21-finished/src/components/comments/Comments.module.css b/code/21-finished/src/components/comments/Comments.module.css new file mode 100644 index 0000000000..0fad756422 --- /dev/null +++ b/code/21-finished/src/components/comments/Comments.module.css @@ -0,0 +1,7 @@ +.comments { + text-align: center; +} + +.comments > button { + font-size: 1.25rem; +} \ No newline at end of file diff --git a/code/21-finished/src/components/comments/CommentsList.js b/code/21-finished/src/components/comments/CommentsList.js new file mode 100644 index 0000000000..5e800a22e9 --- /dev/null +++ b/code/21-finished/src/components/comments/CommentsList.js @@ -0,0 +1,14 @@ +import CommentItem from './CommentItem'; +import classes from './CommentsList.module.css'; + +const CommentsList = (props) => { + return ( +
      + {props.comments.map((comment) => ( + + ))} +
    + ); +}; + +export default CommentsList; diff --git a/code/21-finished/src/components/comments/CommentsList.module.css b/code/21-finished/src/components/comments/CommentsList.module.css new file mode 100644 index 0000000000..6b7aaac226 --- /dev/null +++ b/code/21-finished/src/components/comments/CommentsList.module.css @@ -0,0 +1,5 @@ +.comments { + list-style: none; + margin: 2.5rem 0; + padding: 0; +} \ No newline at end of file diff --git a/code/21-finished/src/components/comments/NewCommentForm.js b/code/21-finished/src/components/comments/NewCommentForm.js new file mode 100644 index 0000000000..6a96b2b4a1 --- /dev/null +++ b/code/21-finished/src/components/comments/NewCommentForm.js @@ -0,0 +1,49 @@ +import { useRef, useEffect } from 'react'; + +import useHttp from '../../hooks/use-http'; +import { addComment } from '../../lib/api'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './NewCommentForm.module.css'; + +const NewCommentForm = (props) => { + const commentTextRef = useRef(); + + const { sendRequest, status, error } = useHttp(addComment); + + const { onAddedComment } = props; + + useEffect(() => { + if (status === 'completed' && !error) { + onAddedComment(); + } + }, [status, error, onAddedComment]); + + const submitFormHandler = (event) => { + event.preventDefault(); + + const enteredText = commentTextRef.current.value; + + // optional: Could validate here + + sendRequest({ commentData: { text: enteredText }, quoteId: props.quoteId }); + }; + + return ( +
    + {status === 'pending' && ( +
    + +
    + )} +
    + + +
    +
    + +
    +
    + ); +}; + +export default NewCommentForm; diff --git a/code/21-finished/src/components/comments/NewCommentForm.module.css b/code/21-finished/src/components/comments/NewCommentForm.module.css new file mode 100644 index 0000000000..3b2565652d --- /dev/null +++ b/code/21-finished/src/components/comments/NewCommentForm.module.css @@ -0,0 +1,45 @@ +.form { + margin-top: 1rem; + position: relative; + text-align: center; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/21-finished/src/components/layout/Layout.js b/code/21-finished/src/components/layout/Layout.js new file mode 100644 index 0000000000..2eff00a625 --- /dev/null +++ b/code/21-finished/src/components/layout/Layout.js @@ -0,0 +1,15 @@ +import { Fragment } from 'react'; + +import classes from './Layout.module.css'; +import MainNavigation from './MainNavigation'; + +const Layout = (props) => { + return ( + + +
    {props.children}
    +
    + ); +}; + +export default Layout; diff --git a/code/21-finished/src/components/layout/Layout.module.css b/code/21-finished/src/components/layout/Layout.module.css new file mode 100644 index 0000000000..eb13837358 --- /dev/null +++ b/code/21-finished/src/components/layout/Layout.module.css @@ -0,0 +1,5 @@ +.main { + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} \ No newline at end of file diff --git a/code/21-finished/src/components/layout/MainNavigation.js b/code/21-finished/src/components/layout/MainNavigation.js new file mode 100644 index 0000000000..76d790a1c3 --- /dev/null +++ b/code/21-finished/src/components/layout/MainNavigation.js @@ -0,0 +1,27 @@ +import { NavLink } from 'react-router-dom'; + +import classes from './MainNavigation.module.css'; + +const MainNavigation = () => { + return ( +
    +
    Great Quotes
    + +
    + ); +}; + +export default MainNavigation; diff --git a/code/21-finished/src/components/layout/MainNavigation.module.css b/code/21-finished/src/components/layout/MainNavigation.module.css new file mode 100644 index 0000000000..be9d206679 --- /dev/null +++ b/code/21-finished/src/components/layout/MainNavigation.module.css @@ -0,0 +1,37 @@ +.header { + width: 100%; + height: 5rem; + display: flex; + padding: 0 10%; + justify-content: space-between; + align-items: center; + background-color: #008080; +} + +.logo { + font-size: 2rem; + color: white; +} + +.nav ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; +} + +.nav li { + margin-left: 1.5rem; + font-size: 1.25rem; +} + +.nav a { + text-decoration: none; + color: #88dfdf; +} + +.nav a:hover, +.nav a:active, +.nav a.active { + color: #e6fcfc; +} diff --git a/code/21-finished/src/components/quotes/HighlightedQuote.js b/code/21-finished/src/components/quotes/HighlightedQuote.js new file mode 100644 index 0000000000..b6d3445c28 --- /dev/null +++ b/code/21-finished/src/components/quotes/HighlightedQuote.js @@ -0,0 +1,12 @@ +import classes from './HighlightedQuote.module.css'; + +const HighlightedQuote = (props) => { + return ( +
    +

    {props.text}

    +
    {props.author}
    +
    + ); +}; + +export default HighlightedQuote; diff --git a/code/21-finished/src/components/quotes/HighlightedQuote.module.css b/code/21-finished/src/components/quotes/HighlightedQuote.module.css new file mode 100644 index 0000000000..466b463010 --- /dev/null +++ b/code/21-finished/src/components/quotes/HighlightedQuote.module.css @@ -0,0 +1,20 @@ +.quote { + background-color: #162b2b; + color: white; + border-radius: 6px; + padding: 3rem; + margin: 3rem auto; + width: 90%; + max-width: 40rem; +} + +.quote p { + font-size: 2.5rem; +} + +.quote figcaption { + font-style: italic; + font-size: 1.5rem; + text-align: right; + color: #a1e0e0; +} \ No newline at end of file diff --git a/code/21-finished/src/components/quotes/NoQuotesFound.js b/code/21-finished/src/components/quotes/NoQuotesFound.js new file mode 100644 index 0000000000..8642a10db4 --- /dev/null +++ b/code/21-finished/src/components/quotes/NoQuotesFound.js @@ -0,0 +1,16 @@ +import { Link } from 'react-router-dom'; + +import classes from './NoQuotesFound.module.css'; + +const NoQuotesFound = () => { + return ( +
    +

    No quotes found!

    + + Add a Quote + +
    + ); +}; + +export default NoQuotesFound; diff --git a/code/21-finished/src/components/quotes/NoQuotesFound.module.css b/code/21-finished/src/components/quotes/NoQuotesFound.module.css new file mode 100644 index 0000000000..0d48b19f9b --- /dev/null +++ b/code/21-finished/src/components/quotes/NoQuotesFound.module.css @@ -0,0 +1,17 @@ +.noquotes { + height: 20rem; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.noquotes p { + color: #262c2c; + font-size: 3rem; + font-weight: bold; +} + diff --git a/code/21-finished/src/components/quotes/QuoteForm.js b/code/21-finished/src/components/quotes/QuoteForm.js new file mode 100644 index 0000000000..823a1e93bc --- /dev/null +++ b/code/21-finished/src/components/quotes/QuoteForm.js @@ -0,0 +1,70 @@ +import { Fragment, useRef, useState } from 'react'; +import { Prompt } from 'react-router-dom'; + +import Card from '../ui/Card'; +import LoadingSpinner from '../ui/LoadingSpinner'; +import classes from './QuoteForm.module.css'; + +const QuoteForm = (props) => { + const [isEntering, setIsEntering] = useState(false); + + const authorInputRef = useRef(); + const textInputRef = useRef(); + + function submitFormHandler(event) { + event.preventDefault(); + + const enteredAuthor = authorInputRef.current.value; + const enteredText = textInputRef.current.value; + + // optional: Could validate here + + props.onAddQuote({ author: enteredAuthor, text: enteredText }); + } + + const finishEnteringHandler = () => { + setIsEntering(false); + }; + + const formFocusedHandler = () => { + setIsEntering(true); + }; + + return ( + + + 'Are you sure you want to leave? All your entered data will be lost!' + } + /> + +
    + {props.isLoading && ( +
    + +
    + )} + +
    + + +
    +
    + + +
    +
    + +
    +
    +
    +
    + ); +}; + +export default QuoteForm; diff --git a/code/21-finished/src/components/quotes/QuoteForm.module.css b/code/21-finished/src/components/quotes/QuoteForm.module.css new file mode 100644 index 0000000000..ee8d855137 --- /dev/null +++ b/code/21-finished/src/components/quotes/QuoteForm.module.css @@ -0,0 +1,49 @@ +.form { + position: relative; +} + +.loading { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.control { + margin-bottom: 0.5rem; +} + +.control label { + font-weight: bold; + display: block; + margin-bottom: 0.5rem; +} + +.control input, +.control textarea { + font: inherit; + padding: 0.35rem; + border-radius: 4px; + background-color: #f0f0f0; + border: 1px solid #c1d1d1; + display: block; + width: 100%; + font-size: 1.25rem; +} + +.control input:focus, +.control textarea:focus { + background-color: #cbf8f8; + outline-color: teal; +} + +.actions { + text-align: right; +} + +.actions button { + font-size: 1.25rem; +} diff --git a/code/21-finished/src/components/quotes/QuoteItem.js b/code/21-finished/src/components/quotes/QuoteItem.js new file mode 100644 index 0000000000..ee1ccb3ddc --- /dev/null +++ b/code/21-finished/src/components/quotes/QuoteItem.js @@ -0,0 +1,21 @@ +import { Link } from 'react-router-dom'; + +import classes from './QuoteItem.module.css'; + +const QuoteItem = (props) => { + return ( +
  • +
    +
    +

    {props.text}

    +
    +
    {props.author}
    +
    + + View Fullscreen + +
  • + ); +}; + +export default QuoteItem; diff --git a/code/21-finished/src/components/quotes/QuoteItem.module.css b/code/21-finished/src/components/quotes/QuoteItem.module.css new file mode 100644 index 0000000000..74cd09b8b7 --- /dev/null +++ b/code/21-finished/src/components/quotes/QuoteItem.module.css @@ -0,0 +1,37 @@ +.item { + margin: 1rem 0; + padding: 1rem; + display: flex; + justify-content: space-between; + align-items: flex-end; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 6px; + background-color: #c2e7f0; +} + +.item:last-of-type { + border-bottom: none; +} + +.item figure { + margin: 0; + padding: 0; + width: 70%; +} + +.item blockquote { + margin: 0; + text-align: left; + font-size: 1.5rem; + color: #212929; +} + +.item p { + margin: 0; + margin-bottom: 0.25rem; +} + +.item figcaption { + font-style: italic; + color: #566d6d; +} diff --git a/code/21-finished/src/components/quotes/QuoteList.js b/code/21-finished/src/components/quotes/QuoteList.js new file mode 100644 index 0000000000..05cab9d98b --- /dev/null +++ b/code/21-finished/src/components/quotes/QuoteList.js @@ -0,0 +1,55 @@ +import { Fragment } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; + +import QuoteItem from './QuoteItem'; +import classes from './QuoteList.module.css'; + +const sortQuotes = (quotes, ascending) => { + return quotes.sort((quoteA, quoteB) => { + if (ascending) { + return quoteA.id > quoteB.id ? 1 : -1; + } else { + return quoteA.id < quoteB.id ? 1 : -1; + } + }); +}; + +const QuoteList = (props) => { + const history = useHistory(); + const location = useLocation(); + + const queryParams = new URLSearchParams(location.search); + + const isSortingAscending = queryParams.get('sort') === 'asc'; + + const sortedQuotes = sortQuotes(props.quotes, isSortingAscending); + + const changeSortingHandler = () => { + history.push({ + pathname: location.pathname, + search: `?sort=${(isSortingAscending ? 'desc' : 'asc')}` + }); + }; + + return ( + +
    + +
    +
      + {sortedQuotes.map((quote) => ( + + ))} +
    +
    + ); +}; + +export default QuoteList; diff --git a/code/21-finished/src/components/quotes/QuoteList.module.css b/code/21-finished/src/components/quotes/QuoteList.module.css new file mode 100644 index 0000000000..cfb5fbf9ab --- /dev/null +++ b/code/21-finished/src/components/quotes/QuoteList.module.css @@ -0,0 +1,25 @@ +.list { + list-style: none; + margin: 0; + padding: 0; +} + +.sorting { + padding-bottom: 1rem; + border-bottom: 3px solid #b2d4d4; + margin-bottom: 2rem; +} + +.sorting button { + font: inherit; + color: teal; + border: 1px solid teal; + background-color: transparent; + border-radius: 4px; + padding: 0.5rem 1.5rem; + cursor: pointer; +} + +.sorting button:hover { + background-color: #c2fafa; +} \ No newline at end of file diff --git a/code/21-finished/src/hooks/use-http.js b/code/21-finished/src/hooks/use-http.js new file mode 100644 index 0000000000..b7c56bc76a --- /dev/null +++ b/code/21-finished/src/hooks/use-http.js @@ -0,0 +1,60 @@ +import { useReducer, useCallback } from 'react'; + +function httpReducer(state, action) { + if (action.type === 'SEND') { + return { + data: null, + error: null, + status: 'pending', + }; + } + + if (action.type === 'SUCCESS') { + return { + data: action.responseData, + error: null, + status: 'completed', + }; + } + + if (action.type === 'ERROR') { + return { + data: null, + error: action.errorMessage, + status: 'completed', + }; + } + + return state; +} + +function useHttp(requestFunction, startWithPending = false) { + const [httpState, dispatch] = useReducer(httpReducer, { + status: startWithPending ? 'pending' : null, + data: null, + error: null, + }); + + const sendRequest = useCallback( + async function (requestData) { + dispatch({ type: 'SEND' }); + try { + const responseData = await requestFunction(requestData); + dispatch({ type: 'SUCCESS', responseData }); + } catch (error) { + dispatch({ + type: 'ERROR', + errorMessage: error.message || 'Something went wrong!', + }); + } + }, + [requestFunction] + ); + + return { + sendRequest, + ...httpState, + }; +} + +export default useHttp; diff --git a/code/21-finished/src/index.css b/code/21-finished/src/index.css new file mode 100644 index 0000000000..039c6d7fe6 --- /dev/null +++ b/code/21-finished/src/index.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 0; + background-color: #e7f8f8; +} + +.centered { + margin: 3rem auto; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.focused { + font-size: 3rem; + font-weight: bold; + color: white; +} + +.btn { + text-decoration: none; + background-color: teal; + color: white; + border-radius: 4px; + padding: 0.75rem 1.5rem; + border: 1px solid teal; + cursor: pointer; +} + +.btn:hover, +.btn:active { + background-color: #11acac; + border-color: #11acac; +} + +.btn--flat { + cursor: pointer; + font: inherit; + color: teal; + border: none; + background-color: none; + text-decoration: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; +} + +.btn--flat:hover, +.btn--flat:active { + background-color: teal; + color: white; +} \ No newline at end of file diff --git a/code/21-finished/src/index.js b/code/21-finished/src/index.js new file mode 100644 index 0000000000..651888d8fb --- /dev/null +++ b/code/21-finished/src/index.js @@ -0,0 +1,12 @@ +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import './index.css'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/code/21-finished/src/lib/api.js b/code/21-finished/src/lib/api.js new file mode 100644 index 0000000000..2bc84aac81 --- /dev/null +++ b/code/21-finished/src/lib/api.js @@ -0,0 +1,96 @@ +const FIREBASE_DOMAIN = 'https://react-prep-default-rtdb.firebaseio.com'; + +export async function getAllQuotes() { + const response = await fetch(`${FIREBASE_DOMAIN}/quotes.json`); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Could not fetch quotes.'); + } + + const transformedQuotes = []; + + for (const key in data) { + const quoteObj = { + id: key, + ...data[key], + }; + + transformedQuotes.push(quoteObj); + } + + return transformedQuotes; +} + +export async function getSingleQuote(quoteId) { + const response = await fetch(`${FIREBASE_DOMAIN}/quotes/${quoteId}.json`); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Could not fetch quote.'); + } + + const loadedQuote = { + id: quoteId, + ...data, + }; + + return loadedQuote; +} + +export async function addQuote(quoteData) { + const response = await fetch(`${FIREBASE_DOMAIN}/quotes.json`, { + method: 'POST', + body: JSON.stringify(quoteData), + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Could not create quote.'); + } + + return null; +} + +export async function addComment(requestData) { + const response = await fetch(`${FIREBASE_DOMAIN}/comments/${requestData.quoteId}.json`, { + method: 'POST', + body: JSON.stringify(requestData.commentData), + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Could not add comment.'); + } + + return { commentId: data.name }; +} + +export async function getAllComments(quoteId) { + const response = await fetch(`${FIREBASE_DOMAIN}/comments/${quoteId}.json`); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Could not get comments.'); + } + + const transformedComments = []; + + for (const key in data) { + const commentObj = { + id: key, + ...data[key], + }; + + transformedComments.push(commentObj); + } + + return transformedComments; +} diff --git a/code/21-finished/src/pages/AllQuotes.js b/code/21-finished/src/pages/AllQuotes.js new file mode 100644 index 0000000000..db087b5c5c --- /dev/null +++ b/code/21-finished/src/pages/AllQuotes.js @@ -0,0 +1,38 @@ +import { useEffect } from 'react'; + +import QuoteList from '../components/quotes/QuoteList'; +import LoadingSpinner from '../components/ui/LoadingSpinner'; +import NoQuotesFound from '../components/quotes/NoQuotesFound'; +import useHttp from '../hooks/use-http'; +import { getAllQuotes } from '../lib/api'; + +const AllQuotes = () => { + const { sendRequest, status, data: loadedQuotes, error } = useHttp( + getAllQuotes, + true + ); + + useEffect(() => { + sendRequest(); + }, [sendRequest]); + + if (status === 'pending') { + return ( +
    + +
    + ); + } + + if (error) { + return

    {error}

    ; + } + + if (status === 'completed' && (!loadedQuotes || loadedQuotes.length === 0)) { + return ; + } + + return ; +}; + +export default AllQuotes; diff --git a/code/21-finished/src/pages/NewQuote.js b/code/21-finished/src/pages/NewQuote.js new file mode 100644 index 0000000000..b571e25788 --- /dev/null +++ b/code/21-finished/src/pages/NewQuote.js @@ -0,0 +1,25 @@ +import { useEffect } from 'react'; +import { useHistory } from 'react-router-dom'; + +import QuoteForm from '../components/quotes/QuoteForm'; +import useHttp from '../hooks/use-http'; +import { addQuote } from '../lib/api'; + +const NewQuote = () => { + const { sendRequest, status } = useHttp(addQuote); + const history = useHistory(); + + useEffect(() => { + if (status === 'completed') { + history.push('/quotes'); + } + }, [status, history]); + + const addQuoteHandler = (quoteData) => { + sendRequest(quoteData); + }; + + return ; +}; + +export default NewQuote; diff --git a/code/21-finished/src/pages/NotFound.js b/code/21-finished/src/pages/NotFound.js new file mode 100644 index 0000000000..bb7c85baac --- /dev/null +++ b/code/21-finished/src/pages/NotFound.js @@ -0,0 +1,9 @@ +const NotFound = () => { + return ( +
    +

    Page not found!

    +
    + ); +}; + +export default NotFound; diff --git a/code/21-finished/src/pages/QuoteDetail.js b/code/21-finished/src/pages/QuoteDetail.js new file mode 100644 index 0000000000..9713e6e48e --- /dev/null +++ b/code/21-finished/src/pages/QuoteDetail.js @@ -0,0 +1,58 @@ +import { Fragment, useEffect } from 'react'; +import { useParams, Route, Link, useRouteMatch } from 'react-router-dom'; + +import HighlightedQuote from '../components/quotes/HighlightedQuote'; +import Comments from '../components/comments/Comments'; +import useHttp from '../hooks/use-http'; +import { getSingleQuote } from '../lib/api'; +import LoadingSpinner from '../components/ui/LoadingSpinner'; + +const QuoteDetail = () => { + const match = useRouteMatch(); + const params = useParams(); + + const { quoteId } = params; + + const { sendRequest, status, data: loadedQuote, error } = useHttp( + getSingleQuote, + true + ); + + useEffect(() => { + sendRequest(quoteId); + }, [sendRequest, quoteId]); + + if (status === 'pending') { + return ( +
    + +
    + ); + } + + if (error) { + return

    {error}

    ; + } + + if (!loadedQuote.text) { + return

    No quote found!

    ; + } + + return ( + + + +
    + + Load Comments + +
    +
    + + + +
    + ); +}; + +export default QuoteDetail; diff --git a/slides/slides.pdf b/slides/slides.pdf new file mode 100644 index 0000000000000000000000000000000000000000..395967faef8921359e341b5c474ead44918bd554 GIT binary patch literal 73566 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
    {props.author}
    - + View Fullscreen - + ); }; diff --git a/code/21-finished/src/hooks.zip b/code/21-finished/src/hooks.zip new file mode 100644 index 0000000000000000000000000000000000000000..41fb9a916d3d78268dbc5972ff4dee8d638db5ae GIT binary patch literal 814 zcmWIWW@Zs#00FW5iXbopO0Y2~Fl6NCXBX>-hVU}5^O{E|z;I~=Hv=QfS4IW~u-*W$ z85|58C}!+oVPFu1n^9Vvs+&<#QlOVr3^z^yY9yL*Q$r5sEjAF?`&sm|u!~cTL%OrZ_}!(v_Gs>8Y#if8WRh-E-PqQ9bB2iBGValT}U&j#c;#IO-A6qo(vuAISxNYZY?>pDeX#*>++1ujTyz zIDcpFzWet3fA_Ljq0rkLt>sofC6)H9yv@b=`F!Bb)SLZpPOBFrx4n>?n#BHxNwD3h zV+z~eBn+s*ZVPcX8Edr^$*5bDeDe)6*{MWmu+&7b&H8&!2Quk)P50^3zus57r|FP)JnFZ{5 zRti#m8$aDRb9k9ZPx9LDN$Vd-XXP$lvQ2T%zAPo*-V&2na{>hBq$u;5-(%^Y_;rr1 zZJh4EC7Z*RwLY}nniD@sW%Kf;gNJUd{Zam-|J25WSl7krmmly3c(Zf7{j%`dAz%st zC4&HOMkWzv+{p}4%2!GO+WSM<>8=X$3a}Bg+vXWNdvp<;doHbls<3|R zFRS>t!|ohG$zHZ|eMC0h=<#lSIwSeesyjVXAI{b|DUvtin2*~=ou-=^E%x3kc?|Z7 z8>&q0@Ks~Yxp?ODpK4X!S07~flpAhb3t8d)%R%c{nU3*<1)Y~aJbd$keRkESoXv~f zb&nqYxyJdynGJfoTQ>(Px&}r}bNI2)dV>Etn+DIRcN!1K>P(rWmD8|@X=_?^%XD!L zz0|420+%K;`73NV`a1CV0S&bhrO?JZr5+`D?{ji*wO(+weJE#?_;MT1oyOMKTsh6t zxx%S-xsUY!7sV)n2;F~hJF8aMVwd#E@4-Jd#l^bc(kL?a{CC{S1 zA6RsM{g2siUpoKf`oA_(#r%idzeiUW+D1>(6La0bG+RFVMc^XGN4i(k?YwON_sv^c zKDBdK)>4Ui`niwuXB)`<`XYIGT6yp@(XxzlTP%B5?q>+_X6M+bmnEsr#J~VbWC7lc zOd`y-hVU}5^O{E|z;I~=Hv=QfS4IW~u-*W$ z85|58C}!+oVPFu1n^9Vvs+&<#QlOVr3^z^yY9yL*Q$r5sEjAF?`&sm|u!~cTL%OrZ_}!(v_Gs>8Y#if8WRh-E-PqQ9bB2iBGValT}U&j#c;#IO-A6qo(vuAISxNYZY?>pDeX#*>++1ujTyz zIDcpFzWet3fA_Ljq0rkLt>sofC6)H9yv@b=`F!Bb)SLZpPOBFrx4n>?n#BHxNwD3h zV+z~eBn+s*ZVPcX8Edr^$*5bDeDe)6*{MWmu+&7b&H8&!2Quk)P50^3zus57r|FP)JnFZ{5 zRti#m8$aDRb9k9ZPx9LDN$Vd-XXP$lvQ2T%zAPo*-V&2na{>hBq$u;5-(%^Y_;rr1 zZJh4EC7Z*RwLY}nniD@sW%Kf;gNJUd{Zam-|J25WSl7krmmly3c(Zf7{j%`dAz%st zC4&HOMkWzv+{p}4%2!GO+WSM<>8=X$3a}Bg+vXWNdvp<;doHbls<3|R zFRS>t!|ohG$zHZ|eMC0h=<#lSIwSeesyjVXAI{b|DUvtin2*~=ou-=^E%x3kc?|Z7 z8>&q0@Ks~Yxp?ODpK4X!S07~flpAhb3t8d)%R%c{nU3*<1)Y~aJbd$keRkESoXv~f zb&nqYxyJdynGJfoTQ>(Px&}r}bNI2)dV>Etn+DIRcN!1K>P(rWmD8|@X=_?^%XD!L zz0|420+%K;`73NV`a1CV0S&bhrO?JZr5+`D?{ji*wO(+weJE#?_;MT1oyOMKTsh6t zxx%S-xsUY!7sV)n2;F~hJF8aMVwd#E@4-Jd#l^bc(kL?a{CC{S1 zA6RsM{g2siUpoKf`oA_(#r%idzeiUW+D1>(6La0bG+RFVMc^XGN4i(k?YwON_sv^c zKDBdK)>4Ui`niwuXB)`<`XYIGT6yp@(XxzlTP%B5?q>+_X6M+bmnEsr#J~VbWC7lc zOd`y { + return quotes.sort((quoteA, quoteB) => { + if (ascending) { + return quoteA.id > quoteB.id ? 1 : -1; + } else { + return quoteA.id < quoteB.id ? 1 : -1; + } + }); +}; From f95b6cb9e7f488858692d3754f675ba552af30ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Fri, 16 Apr 2021 08:44:54 +0200 Subject: [PATCH 3/7] fixed code: changed /ui/ path to /UI/ --- .../src/components/quotes/QuoteForm.js | 4 ++-- .../src/components/quotes/QuoteForm.js | 4 ++-- .../src/components/quotes/QuoteForm.js | 4 ++-- .../src/components/quotes/QuoteForm.js | 4 ++-- .../src/components/quotes/QuoteForm.js | 4 ++-- .../src/components/quotes/QuoteForm.js | 4 ++-- .../src/components/quotes/QuoteForm.js | 4 ++-- .../src/components/quotes/QuoteForm.js | 4 ++-- .../src/components/quotes/QuoteForm.js | 4 ++-- .../src/components/quotes/QuoteForm.js | 4 ++-- .../src/components/quotes/QuoteForm.js | 4 ++-- .../src/components/quotes/QuoteForm.js | 4 ++-- code/20-sending-getting-quote-data/src/pages/AllQuotes.js | 2 +- code/20-sending-getting-quote-data/src/pages/QuoteDetail.js | 2 +- code/21-finished/src/components/comments/Comments.js | 2 +- code/21-finished/src/components/comments/NewCommentForm.js | 2 +- code/21-finished/src/components/quotes/QuoteForm.js | 4 ++-- code/21-finished/src/pages/AllQuotes.js | 2 +- code/21-finished/src/pages/QuoteDetail.js | 2 +- 19 files changed, 32 insertions(+), 32 deletions(-) diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.js b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.js index 464e2073d7..9a49c7fb7c 100644 --- a/code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.js +++ b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteForm.js @@ -1,7 +1,7 @@ import { useRef } from 'react'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.js b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.js index 464e2073d7..9a49c7fb7c 100644 --- a/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.js +++ b/code/10-practice-redirecting-and-extracting-params/src/components/quotes/QuoteForm.js @@ -1,7 +1,7 @@ import { useRef } from 'react'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/11-practicing-nested-routes/src/components/quotes/QuoteForm.js b/code/11-practicing-nested-routes/src/components/quotes/QuoteForm.js index 464e2073d7..9a49c7fb7c 100644 --- a/code/11-practicing-nested-routes/src/components/quotes/QuoteForm.js +++ b/code/11-practicing-nested-routes/src/components/quotes/QuoteForm.js @@ -1,7 +1,7 @@ import { useRef } from 'react'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.js b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.js index 464e2073d7..9a49c7fb7c 100644 --- a/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.js +++ b/code/12-adding-a-layout-wrapper/src/components/quotes/QuoteForm.js @@ -1,7 +1,7 @@ import { useRef } from 'react'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.js b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.js index 464e2073d7..9a49c7fb7c 100644 --- a/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.js +++ b/code/13-adding-dummy-data-and-more-content/src/components/quotes/QuoteForm.js @@ -1,7 +1,7 @@ import { useRef } from 'react'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.js b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.js index 464e2073d7..9a49c7fb7c 100644 --- a/code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.js +++ b/code/14-outputting-data-on-details-page/src/components/quotes/QuoteForm.js @@ -1,7 +1,7 @@ import { useRef } from 'react'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.js b/code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.js index 464e2073d7..9a49c7fb7c 100644 --- a/code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.js +++ b/code/15-adding-a-notfound-page/src/components/quotes/QuoteForm.js @@ -1,7 +1,7 @@ import { useRef } from 'react'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.js b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.js index 464e2073d7..9a49c7fb7c 100644 --- a/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.js +++ b/code/16-implementing-programmatic-navigation/src/components/quotes/QuoteForm.js @@ -1,7 +1,7 @@ import { useRef } from 'react'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.js b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.js index 823a1e93bc..c50916ab91 100644 --- a/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.js +++ b/code/17-preventing-unwanted-route-transitions/src/components/quotes/QuoteForm.js @@ -1,8 +1,8 @@ import { Fragment, useRef, useState } from 'react'; import { Prompt } from 'react-router-dom'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/18-working-with-query-params/src/components/quotes/QuoteForm.js b/code/18-working-with-query-params/src/components/quotes/QuoteForm.js index 823a1e93bc..c50916ab91 100644 --- a/code/18-working-with-query-params/src/components/quotes/QuoteForm.js +++ b/code/18-working-with-query-params/src/components/quotes/QuoteForm.js @@ -1,8 +1,8 @@ import { Fragment, useRef, useState } from 'react'; import { Prompt } from 'react-router-dom'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.js b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.js index 823a1e93bc..c50916ab91 100644 --- a/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.js +++ b/code/19-writing-more-flexible-routing-code/src/components/quotes/QuoteForm.js @@ -1,8 +1,8 @@ import { Fragment, useRef, useState } from 'react'; import { Prompt } from 'react-router-dom'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.js b/code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.js index 823a1e93bc..c50916ab91 100644 --- a/code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.js +++ b/code/20-sending-getting-quote-data/src/components/quotes/QuoteForm.js @@ -1,8 +1,8 @@ import { Fragment, useRef, useState } from 'react'; import { Prompt } from 'react-router-dom'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/20-sending-getting-quote-data/src/pages/AllQuotes.js b/code/20-sending-getting-quote-data/src/pages/AllQuotes.js index db087b5c5c..ffd486ebc9 100644 --- a/code/20-sending-getting-quote-data/src/pages/AllQuotes.js +++ b/code/20-sending-getting-quote-data/src/pages/AllQuotes.js @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import QuoteList from '../components/quotes/QuoteList'; -import LoadingSpinner from '../components/ui/LoadingSpinner'; +import LoadingSpinner from '../components/UI/LoadingSpinner'; import NoQuotesFound from '../components/quotes/NoQuotesFound'; import useHttp from '../hooks/use-http'; import { getAllQuotes } from '../lib/api'; diff --git a/code/20-sending-getting-quote-data/src/pages/QuoteDetail.js b/code/20-sending-getting-quote-data/src/pages/QuoteDetail.js index 9713e6e48e..3992b44738 100644 --- a/code/20-sending-getting-quote-data/src/pages/QuoteDetail.js +++ b/code/20-sending-getting-quote-data/src/pages/QuoteDetail.js @@ -5,7 +5,7 @@ import HighlightedQuote from '../components/quotes/HighlightedQuote'; import Comments from '../components/comments/Comments'; import useHttp from '../hooks/use-http'; import { getSingleQuote } from '../lib/api'; -import LoadingSpinner from '../components/ui/LoadingSpinner'; +import LoadingSpinner from '../components/UI/LoadingSpinner'; const QuoteDetail = () => { const match = useRouteMatch(); diff --git a/code/21-finished/src/components/comments/Comments.js b/code/21-finished/src/components/comments/Comments.js index 45f7926246..99b9ae2eab 100644 --- a/code/21-finished/src/components/comments/Comments.js +++ b/code/21-finished/src/components/comments/Comments.js @@ -5,7 +5,7 @@ import classes from './Comments.module.css'; import NewCommentForm from './NewCommentForm'; import useHttp from '../../hooks/use-http'; import { getAllComments } from '../../lib/api'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import LoadingSpinner from '../UI/LoadingSpinner'; import CommentsList from './CommentsList'; const Comments = () => { diff --git a/code/21-finished/src/components/comments/NewCommentForm.js b/code/21-finished/src/components/comments/NewCommentForm.js index 6a96b2b4a1..821f8c25a0 100644 --- a/code/21-finished/src/components/comments/NewCommentForm.js +++ b/code/21-finished/src/components/comments/NewCommentForm.js @@ -2,7 +2,7 @@ import { useRef, useEffect } from 'react'; import useHttp from '../../hooks/use-http'; import { addComment } from '../../lib/api'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './NewCommentForm.module.css'; const NewCommentForm = (props) => { diff --git a/code/21-finished/src/components/quotes/QuoteForm.js b/code/21-finished/src/components/quotes/QuoteForm.js index 823a1e93bc..c50916ab91 100644 --- a/code/21-finished/src/components/quotes/QuoteForm.js +++ b/code/21-finished/src/components/quotes/QuoteForm.js @@ -1,8 +1,8 @@ import { Fragment, useRef, useState } from 'react'; import { Prompt } from 'react-router-dom'; -import Card from '../ui/Card'; -import LoadingSpinner from '../ui/LoadingSpinner'; +import Card from '../UI/Card'; +import LoadingSpinner from '../UI/LoadingSpinner'; import classes from './QuoteForm.module.css'; const QuoteForm = (props) => { diff --git a/code/21-finished/src/pages/AllQuotes.js b/code/21-finished/src/pages/AllQuotes.js index db087b5c5c..ffd486ebc9 100644 --- a/code/21-finished/src/pages/AllQuotes.js +++ b/code/21-finished/src/pages/AllQuotes.js @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import QuoteList from '../components/quotes/QuoteList'; -import LoadingSpinner from '../components/ui/LoadingSpinner'; +import LoadingSpinner from '../components/UI/LoadingSpinner'; import NoQuotesFound from '../components/quotes/NoQuotesFound'; import useHttp from '../hooks/use-http'; import { getAllQuotes } from '../lib/api'; diff --git a/code/21-finished/src/pages/QuoteDetail.js b/code/21-finished/src/pages/QuoteDetail.js index 9713e6e48e..3992b44738 100644 --- a/code/21-finished/src/pages/QuoteDetail.js +++ b/code/21-finished/src/pages/QuoteDetail.js @@ -5,7 +5,7 @@ import HighlightedQuote from '../components/quotes/HighlightedQuote'; import Comments from '../components/comments/Comments'; import useHttp from '../hooks/use-http'; import { getSingleQuote } from '../lib/api'; -import LoadingSpinner from '../components/ui/LoadingSpinner'; +import LoadingSpinner from '../components/UI/LoadingSpinner'; const QuoteDetail = () => { const match = useRouteMatch(); From 6e062f27dcd02119947e86b36996340ae69f9809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Fri, 17 Dec 2021 16:55:57 +0100 Subject: [PATCH 4/7] updated to React 18 --- code/01-starting-project/package.json | 4 ++-- code/01-starting-project/src/index.js | 3 ++- code/02-defining-and-using-routes/package.json | 4 ++-- code/02-defining-and-using-routes/src/index.js | 6 +++--- code/03-working-with-links/package.json | 4 ++-- code/03-working-with-links/src/index.js | 6 +++--- code/04-using-navlinks/package.json | 4 ++-- code/04-using-navlinks/src/index.js | 6 +++--- code/05-extracting-route-params/package.json | 4 ++-- code/05-extracting-route-params/src/index.js | 6 +++--- code/06-using-switch-and-exact/package.json | 4 ++-- code/06-using-switch-and-exact/src/index.js | 6 +++--- code/07-working-with-nested-routes/package.json | 4 ++-- code/07-working-with-nested-routes/src/index.js | 6 +++--- code/08-redirecting-the-user/package.json | 4 ++-- code/08-redirecting-the-user/src/index.js | 6 +++--- code/09-time-to-practice-starting-code/package.json | 4 ++-- .../package.json | 4 ++-- .../src/index.js | 6 +++--- code/11-practicing-nested-routes/package.json | 4 ++-- code/11-practicing-nested-routes/src/index.js | 6 +++--- code/12-adding-a-layout-wrapper/package.json | 4 ++-- code/12-adding-a-layout-wrapper/src/index.js | 6 +++--- code/13-adding-dummy-data-and-more-content/package.json | 4 ++-- code/13-adding-dummy-data-and-more-content/src/index.js | 6 +++--- code/14-outputting-data-on-details-page/package.json | 4 ++-- code/14-outputting-data-on-details-page/src/index.js | 6 +++--- code/15-adding-a-notfound-page/package.json | 4 ++-- code/15-adding-a-notfound-page/src/index.js | 6 +++--- code/16-implementing-programmatic-navigation/package.json | 4 ++-- code/16-implementing-programmatic-navigation/src/index.js | 6 +++--- code/17-preventing-unwanted-route-transitions/package.json | 4 ++-- code/17-preventing-unwanted-route-transitions/src/index.js | 6 +++--- code/18-working-with-query-params/package.json | 4 ++-- code/18-working-with-query-params/src/index.js | 6 +++--- code/19-writing-more-flexible-routing-code/package.json | 4 ++-- code/19-writing-more-flexible-routing-code/src/index.js | 6 +++--- code/20-sending-getting-quote-data/package.json | 4 ++-- code/20-sending-getting-quote-data/src/index.js | 6 +++--- code/21-finished/package.json | 4 ++-- code/21-finished/src/index.js | 6 +++--- 41 files changed, 101 insertions(+), 100 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-defining-and-using-routes/package.json b/code/02-defining-and-using-routes/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/02-defining-and-using-routes/package.json +++ b/code/02-defining-and-using-routes/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/02-defining-and-using-routes/src/index.js b/code/02-defining-and-using-routes/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/02-defining-and-using-routes/src/index.js +++ b/code/02-defining-and-using-routes/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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-working-with-links/package.json b/code/03-working-with-links/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/03-working-with-links/package.json +++ b/code/03-working-with-links/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/03-working-with-links/src/index.js b/code/03-working-with-links/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/03-working-with-links/src/index.js +++ b/code/03-working-with-links/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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-using-navlinks/package.json b/code/04-using-navlinks/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/04-using-navlinks/package.json +++ b/code/04-using-navlinks/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/04-using-navlinks/src/index.js b/code/04-using-navlinks/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/04-using-navlinks/src/index.js +++ b/code/04-using-navlinks/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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-extracting-route-params/package.json b/code/05-extracting-route-params/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/05-extracting-route-params/package.json +++ b/code/05-extracting-route-params/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/05-extracting-route-params/src/index.js b/code/05-extracting-route-params/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/05-extracting-route-params/src/index.js +++ b/code/05-extracting-route-params/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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-using-switch-and-exact/package.json b/code/06-using-switch-and-exact/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/06-using-switch-and-exact/package.json +++ b/code/06-using-switch-and-exact/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/06-using-switch-and-exact/src/index.js b/code/06-using-switch-and-exact/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/06-using-switch-and-exact/src/index.js +++ b/code/06-using-switch-and-exact/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/07-working-with-nested-routes/package.json b/code/07-working-with-nested-routes/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/07-working-with-nested-routes/package.json +++ b/code/07-working-with-nested-routes/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/07-working-with-nested-routes/src/index.js b/code/07-working-with-nested-routes/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/07-working-with-nested-routes/src/index.js +++ b/code/07-working-with-nested-routes/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/08-redirecting-the-user/package.json b/code/08-redirecting-the-user/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/08-redirecting-the-user/package.json +++ b/code/08-redirecting-the-user/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/08-redirecting-the-user/src/index.js b/code/08-redirecting-the-user/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/08-redirecting-the-user/src/index.js +++ b/code/08-redirecting-the-user/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/09-time-to-practice-starting-code/package.json b/code/09-time-to-practice-starting-code/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/09-time-to-practice-starting-code/package.json +++ b/code/09-time-to-practice-starting-code/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/10-practice-redirecting-and-extracting-params/package.json b/code/10-practice-redirecting-and-extracting-params/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/10-practice-redirecting-and-extracting-params/package.json +++ b/code/10-practice-redirecting-and-extracting-params/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/10-practice-redirecting-and-extracting-params/src/index.js b/code/10-practice-redirecting-and-extracting-params/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/10-practice-redirecting-and-extracting-params/src/index.js +++ b/code/10-practice-redirecting-and-extracting-params/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/11-practicing-nested-routes/package.json b/code/11-practicing-nested-routes/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/11-practicing-nested-routes/package.json +++ b/code/11-practicing-nested-routes/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/11-practicing-nested-routes/src/index.js b/code/11-practicing-nested-routes/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/11-practicing-nested-routes/src/index.js +++ b/code/11-practicing-nested-routes/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/12-adding-a-layout-wrapper/package.json b/code/12-adding-a-layout-wrapper/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/12-adding-a-layout-wrapper/package.json +++ b/code/12-adding-a-layout-wrapper/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/12-adding-a-layout-wrapper/src/index.js b/code/12-adding-a-layout-wrapper/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/12-adding-a-layout-wrapper/src/index.js +++ b/code/12-adding-a-layout-wrapper/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/13-adding-dummy-data-and-more-content/package.json b/code/13-adding-dummy-data-and-more-content/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/13-adding-dummy-data-and-more-content/package.json +++ b/code/13-adding-dummy-data-and-more-content/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/13-adding-dummy-data-and-more-content/src/index.js b/code/13-adding-dummy-data-and-more-content/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/13-adding-dummy-data-and-more-content/src/index.js +++ b/code/13-adding-dummy-data-and-more-content/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/14-outputting-data-on-details-page/package.json b/code/14-outputting-data-on-details-page/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/14-outputting-data-on-details-page/package.json +++ b/code/14-outputting-data-on-details-page/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/14-outputting-data-on-details-page/src/index.js b/code/14-outputting-data-on-details-page/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/14-outputting-data-on-details-page/src/index.js +++ b/code/14-outputting-data-on-details-page/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/15-adding-a-notfound-page/package.json b/code/15-adding-a-notfound-page/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/15-adding-a-notfound-page/package.json +++ b/code/15-adding-a-notfound-page/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/15-adding-a-notfound-page/src/index.js b/code/15-adding-a-notfound-page/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/15-adding-a-notfound-page/src/index.js +++ b/code/15-adding-a-notfound-page/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/16-implementing-programmatic-navigation/package.json b/code/16-implementing-programmatic-navigation/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/16-implementing-programmatic-navigation/package.json +++ b/code/16-implementing-programmatic-navigation/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/16-implementing-programmatic-navigation/src/index.js b/code/16-implementing-programmatic-navigation/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/16-implementing-programmatic-navigation/src/index.js +++ b/code/16-implementing-programmatic-navigation/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/17-preventing-unwanted-route-transitions/package.json b/code/17-preventing-unwanted-route-transitions/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/17-preventing-unwanted-route-transitions/package.json +++ b/code/17-preventing-unwanted-route-transitions/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/17-preventing-unwanted-route-transitions/src/index.js b/code/17-preventing-unwanted-route-transitions/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/17-preventing-unwanted-route-transitions/src/index.js +++ b/code/17-preventing-unwanted-route-transitions/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/18-working-with-query-params/package.json b/code/18-working-with-query-params/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/18-working-with-query-params/package.json +++ b/code/18-working-with-query-params/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/18-working-with-query-params/src/index.js b/code/18-working-with-query-params/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/18-working-with-query-params/src/index.js +++ b/code/18-working-with-query-params/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/19-writing-more-flexible-routing-code/package.json b/code/19-writing-more-flexible-routing-code/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/19-writing-more-flexible-routing-code/package.json +++ b/code/19-writing-more-flexible-routing-code/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/19-writing-more-flexible-routing-code/src/index.js b/code/19-writing-more-flexible-routing-code/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/19-writing-more-flexible-routing-code/src/index.js +++ b/code/19-writing-more-flexible-routing-code/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/20-sending-getting-quote-data/package.json b/code/20-sending-getting-quote-data/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/20-sending-getting-quote-data/package.json +++ b/code/20-sending-getting-quote-data/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/20-sending-getting-quote-data/src/index.js b/code/20-sending-getting-quote-data/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/20-sending-getting-quote-data/src/index.js +++ b/code/20-sending-getting-quote-data/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; 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/21-finished/package.json b/code/21-finished/package.json index 3f648c3ac0..6cccd46999 100644 --- a/code/21-finished/package.json +++ b/code/21-finished/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-router-dom": "^5.2.0", "react-scripts": "4.0.1", "web-vitals": "^0.2.4" diff --git a/code/21-finished/src/index.js b/code/21-finished/src/index.js index 651888d8fb..c0ef31cc91 100644 --- a/code/21-finished/src/index.js +++ b/code/21-finished/src/index.js @@ -4,9 +4,9 @@ import { BrowserRouter } from 'react-router-dom'; import './index.css'; import App from './App'; -ReactDOM.render( +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( - , - document.getElementById('root') + ); From 3414cdbc6407956e05087dba81f2e9ac1bea967c 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:53 +0100 Subject: [PATCH 5/7] updated code attachments to v18 --- code/01-starting-project/src/index.js | 2 +- code/02-defining-and-using-routes/src/index.js | 2 +- code/03-working-with-links/src/index.js | 2 +- code/04-using-navlinks/src/index.js | 2 +- code/05-extracting-route-params/src/index.js | 2 +- code/06-using-switch-and-exact/src/index.js | 2 +- code/07-working-with-nested-routes/src/index.js | 2 +- code/08-redirecting-the-user/src/index.js | 2 +- code/09-time-to-practice-starting-code/src/index.js | 2 +- code/10-practice-redirecting-and-extracting-params/src/index.js | 2 +- code/11-practicing-nested-routes/src/index.js | 2 +- code/12-adding-a-layout-wrapper/src/index.js | 2 +- code/13-adding-dummy-data-and-more-content/src/index.js | 2 +- code/14-outputting-data-on-details-page/src/index.js | 2 +- code/15-adding-a-notfound-page/src/index.js | 2 +- code/16-implementing-programmatic-navigation/src/index.js | 2 +- code/17-preventing-unwanted-route-transitions/src/index.js | 2 +- code/18-working-with-query-params/src/index.js | 2 +- code/19-writing-more-flexible-routing-code/src/index.js | 2 +- code/20-sending-getting-quote-data/src/index.js | 2 +- code/21-finished/src/index.js | 2 +- 21 files changed, 21 insertions(+), 21 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-defining-and-using-routes/src/index.js b/code/02-defining-and-using-routes/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/02-defining-and-using-routes/src/index.js +++ b/code/02-defining-and-using-routes/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/03-working-with-links/src/index.js b/code/03-working-with-links/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/03-working-with-links/src/index.js +++ b/code/03-working-with-links/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/04-using-navlinks/src/index.js b/code/04-using-navlinks/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/04-using-navlinks/src/index.js +++ b/code/04-using-navlinks/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/05-extracting-route-params/src/index.js b/code/05-extracting-route-params/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/05-extracting-route-params/src/index.js +++ b/code/05-extracting-route-params/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/06-using-switch-and-exact/src/index.js b/code/06-using-switch-and-exact/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/06-using-switch-and-exact/src/index.js +++ b/code/06-using-switch-and-exact/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/07-working-with-nested-routes/src/index.js b/code/07-working-with-nested-routes/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/07-working-with-nested-routes/src/index.js +++ b/code/07-working-with-nested-routes/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/08-redirecting-the-user/src/index.js b/code/08-redirecting-the-user/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/08-redirecting-the-user/src/index.js +++ b/code/08-redirecting-the-user/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/09-time-to-practice-starting-code/src/index.js b/code/09-time-to-practice-starting-code/src/index.js index d59d0ce18d..688e5e0d6c 100644 --- a/code/09-time-to-practice-starting-code/src/index.js +++ b/code/09-time-to-practice-starting-code/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/10-practice-redirecting-and-extracting-params/src/index.js b/code/10-practice-redirecting-and-extracting-params/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/10-practice-redirecting-and-extracting-params/src/index.js +++ b/code/10-practice-redirecting-and-extracting-params/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/11-practicing-nested-routes/src/index.js b/code/11-practicing-nested-routes/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/11-practicing-nested-routes/src/index.js +++ b/code/11-practicing-nested-routes/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/12-adding-a-layout-wrapper/src/index.js b/code/12-adding-a-layout-wrapper/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/12-adding-a-layout-wrapper/src/index.js +++ b/code/12-adding-a-layout-wrapper/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/13-adding-dummy-data-and-more-content/src/index.js b/code/13-adding-dummy-data-and-more-content/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/13-adding-dummy-data-and-more-content/src/index.js +++ b/code/13-adding-dummy-data-and-more-content/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/14-outputting-data-on-details-page/src/index.js b/code/14-outputting-data-on-details-page/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/14-outputting-data-on-details-page/src/index.js +++ b/code/14-outputting-data-on-details-page/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/15-adding-a-notfound-page/src/index.js b/code/15-adding-a-notfound-page/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/15-adding-a-notfound-page/src/index.js +++ b/code/15-adding-a-notfound-page/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/16-implementing-programmatic-navigation/src/index.js b/code/16-implementing-programmatic-navigation/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/16-implementing-programmatic-navigation/src/index.js +++ b/code/16-implementing-programmatic-navigation/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/17-preventing-unwanted-route-transitions/src/index.js b/code/17-preventing-unwanted-route-transitions/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/17-preventing-unwanted-route-transitions/src/index.js +++ b/code/17-preventing-unwanted-route-transitions/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/18-working-with-query-params/src/index.js b/code/18-working-with-query-params/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/18-working-with-query-params/src/index.js +++ b/code/18-working-with-query-params/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/19-writing-more-flexible-routing-code/src/index.js b/code/19-writing-more-flexible-routing-code/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/19-writing-more-flexible-routing-code/src/index.js +++ b/code/19-writing-more-flexible-routing-code/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/20-sending-getting-quote-data/src/index.js b/code/20-sending-getting-quote-data/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/20-sending-getting-quote-data/src/index.js +++ b/code/20-sending-getting-quote-data/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; diff --git a/code/21-finished/src/index.js b/code/21-finished/src/index.js index c0ef31cc91..c2a6402346 100644 --- a/code/21-finished/src/index.js +++ b/code/21-finished/src/index.js @@ -1,4 +1,4 @@ -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; From 14b7b04251210868704ebf017494bd3611925682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= <28806196+maxschwarzmueller@users.noreply.github.com> Date: Mon, 14 Nov 2022 15:24:59 +0100 Subject: [PATCH 6/7] updated react-scripts --- code/01-starting-project/package.json | 2 +- code/02-defining-and-using-routes/package.json | 2 +- code/03-working-with-links/package.json | 2 +- code/04-using-navlinks/package.json | 2 +- code/05-extracting-route-params/package.json | 2 +- code/06-using-switch-and-exact/package.json | 2 +- code/07-working-with-nested-routes/package.json | 2 +- code/08-redirecting-the-user/package.json | 2 +- code/09-time-to-practice-starting-code/package.json | 2 +- code/10-practice-redirecting-and-extracting-params/package.json | 2 +- code/11-practicing-nested-routes/package.json | 2 +- code/12-adding-a-layout-wrapper/package.json | 2 +- code/13-adding-dummy-data-and-more-content/package.json | 2 +- code/14-outputting-data-on-details-page/package.json | 2 +- code/15-adding-a-notfound-page/package.json | 2 +- code/16-implementing-programmatic-navigation/package.json | 2 +- code/17-preventing-unwanted-route-transitions/package.json | 2 +- code/18-working-with-query-params/package.json | 2 +- code/19-writing-more-flexible-routing-code/package.json | 2 +- code/20-sending-getting-quote-data/package.json | 2 +- code/21-finished/package.json | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/code/01-starting-project/package.json b/code/01-starting-project/package.json index b16a8faa39..6c1b63a022 100644 --- a/code/01-starting-project/package.json +++ b/code/01-starting-project/package.json @@ -8,7 +8,7 @@ "@testing-library/user-event": "^12.5.0", "react": "^18.0.0", "react-dom": "^18.0.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/02-defining-and-using-routes/package.json b/code/02-defining-and-using-routes/package.json index 6cccd46999..c0645e836b 100644 --- a/code/02-defining-and-using-routes/package.json +++ b/code/02-defining-and-using-routes/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/03-working-with-links/package.json b/code/03-working-with-links/package.json index 6cccd46999..c0645e836b 100644 --- a/code/03-working-with-links/package.json +++ b/code/03-working-with-links/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/04-using-navlinks/package.json b/code/04-using-navlinks/package.json index 6cccd46999..c0645e836b 100644 --- a/code/04-using-navlinks/package.json +++ b/code/04-using-navlinks/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/05-extracting-route-params/package.json b/code/05-extracting-route-params/package.json index 6cccd46999..c0645e836b 100644 --- a/code/05-extracting-route-params/package.json +++ b/code/05-extracting-route-params/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/06-using-switch-and-exact/package.json b/code/06-using-switch-and-exact/package.json index 6cccd46999..c0645e836b 100644 --- a/code/06-using-switch-and-exact/package.json +++ b/code/06-using-switch-and-exact/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/07-working-with-nested-routes/package.json b/code/07-working-with-nested-routes/package.json index 6cccd46999..c0645e836b 100644 --- a/code/07-working-with-nested-routes/package.json +++ b/code/07-working-with-nested-routes/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/08-redirecting-the-user/package.json b/code/08-redirecting-the-user/package.json index 6cccd46999..c0645e836b 100644 --- a/code/08-redirecting-the-user/package.json +++ b/code/08-redirecting-the-user/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/09-time-to-practice-starting-code/package.json b/code/09-time-to-practice-starting-code/package.json index 6cccd46999..c0645e836b 100644 --- a/code/09-time-to-practice-starting-code/package.json +++ b/code/09-time-to-practice-starting-code/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/10-practice-redirecting-and-extracting-params/package.json b/code/10-practice-redirecting-and-extracting-params/package.json index 6cccd46999..c0645e836b 100644 --- a/code/10-practice-redirecting-and-extracting-params/package.json +++ b/code/10-practice-redirecting-and-extracting-params/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/11-practicing-nested-routes/package.json b/code/11-practicing-nested-routes/package.json index 6cccd46999..c0645e836b 100644 --- a/code/11-practicing-nested-routes/package.json +++ b/code/11-practicing-nested-routes/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/12-adding-a-layout-wrapper/package.json b/code/12-adding-a-layout-wrapper/package.json index 6cccd46999..c0645e836b 100644 --- a/code/12-adding-a-layout-wrapper/package.json +++ b/code/12-adding-a-layout-wrapper/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/13-adding-dummy-data-and-more-content/package.json b/code/13-adding-dummy-data-and-more-content/package.json index 6cccd46999..c0645e836b 100644 --- a/code/13-adding-dummy-data-and-more-content/package.json +++ b/code/13-adding-dummy-data-and-more-content/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/14-outputting-data-on-details-page/package.json b/code/14-outputting-data-on-details-page/package.json index 6cccd46999..c0645e836b 100644 --- a/code/14-outputting-data-on-details-page/package.json +++ b/code/14-outputting-data-on-details-page/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/15-adding-a-notfound-page/package.json b/code/15-adding-a-notfound-page/package.json index 6cccd46999..c0645e836b 100644 --- a/code/15-adding-a-notfound-page/package.json +++ b/code/15-adding-a-notfound-page/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/16-implementing-programmatic-navigation/package.json b/code/16-implementing-programmatic-navigation/package.json index 6cccd46999..c0645e836b 100644 --- a/code/16-implementing-programmatic-navigation/package.json +++ b/code/16-implementing-programmatic-navigation/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/17-preventing-unwanted-route-transitions/package.json b/code/17-preventing-unwanted-route-transitions/package.json index 6cccd46999..c0645e836b 100644 --- a/code/17-preventing-unwanted-route-transitions/package.json +++ b/code/17-preventing-unwanted-route-transitions/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/18-working-with-query-params/package.json b/code/18-working-with-query-params/package.json index 6cccd46999..c0645e836b 100644 --- a/code/18-working-with-query-params/package.json +++ b/code/18-working-with-query-params/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/19-writing-more-flexible-routing-code/package.json b/code/19-writing-more-flexible-routing-code/package.json index 6cccd46999..c0645e836b 100644 --- a/code/19-writing-more-flexible-routing-code/package.json +++ b/code/19-writing-more-flexible-routing-code/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/20-sending-getting-quote-data/package.json b/code/20-sending-getting-quote-data/package.json index 6cccd46999..c0645e836b 100644 --- a/code/20-sending-getting-quote-data/package.json +++ b/code/20-sending-getting-quote-data/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { diff --git a/code/21-finished/package.json b/code/21-finished/package.json index 6cccd46999..c0645e836b 100644 --- a/code/21-finished/package.json +++ b/code/21-finished/package.json @@ -9,7 +9,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^5.2.0", - "react-scripts": "4.0.1", + "react-scripts": "^5.0.1", "web-vitals": "^0.2.4" }, "scripts": { From 2785050353c5ddb926c10758940989565dceea52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= <28806196+maxschwarzmueller@users.noreply.github.com> Date: Wed, 16 Nov 2022 11:38:25 +0100 Subject: [PATCH 7/7] fixed react-dom code --- code/09-time-to-practice-starting-code/src/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/09-time-to-practice-starting-code/src/index.js b/code/09-time-to-practice-starting-code/src/index.js index 688e5e0d6c..778ec1ba20 100644 --- a/code/09-time-to-practice-starting-code/src/index.js +++ b/code/09-time-to-practice-starting-code/src/index.js @@ -3,4 +3,5 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; -ReactDOM.render(, document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render();

    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 literal 0 HcmV?d00001 From d43427d4baafb606ec5aef662fbc56ebfbb9bac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Wed, 24 Mar 2021 14:00:38 +0100 Subject: [PATCH 2/7] fixed code and added missing files --- .../src/components/quotes/QuoteItem.js | 6 +-- code/21-finished/src/hooks.zip | Bin 0 -> 814 bytes code/21-finished/src/lib.zip | Bin 0 -> 897 bytes extra-files/MainHeader.module.css | 38 ++++++++++++++++++ extra-files/hooks.zip | Bin 0 -> 814 bytes extra-files/index.css | 26 ++++++++++++ extra-files/lib.zip | Bin 0 -> 897 bytes extra-files/sorting.js | 9 +++++ 8 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 code/21-finished/src/hooks.zip create mode 100644 code/21-finished/src/lib.zip create mode 100644 extra-files/MainHeader.module.css create mode 100644 extra-files/hooks.zip create mode 100644 extra-files/index.css create mode 100644 extra-files/lib.zip create mode 100644 extra-files/sorting.js diff --git a/code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.js b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.js index ee1ccb3ddc..06cf8b1221 100644 --- a/code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.js +++ b/code/09-time-to-practice-starting-code/src/components/quotes/QuoteItem.js @@ -1,5 +1,3 @@ -import { Link } from 'react-router-dom'; - import classes from './QuoteItem.module.css'; const QuoteItem = (props) => { @@ -11,9 +9,9 @@ const QuoteItem = (props) => {