Ver código fonte

done few exercises with React TypeScript and ReactQuery

unknown 3 anos atrás
pai
commit
667ccfb8fd
60 arquivos alterados com 41514 adições e 0 exclusões
  1. 26 0
      .gitignore
  2. 5 0
      .huskyrc
  3. 4 0
      .lintstagedrc
  4. 11 0
      .prettierrc.yaml
  5. 26 0
      build/asset-manifest.json
  6. BIN
      build/favicon.ico
  7. 1 0
      build/index.html
  8. BIN
      build/logo192.png
  9. BIN
      build/logo512.png
  10. 25 0
      build/manifest.json
  11. 3 0
      build/robots.txt
  12. 3 0
      build/static/css/2.0b01065c.chunk.css
  13. 1 0
      build/static/css/2.0b01065c.chunk.css.map
  14. 2 0
      build/static/css/main.9fbd2380.chunk.css
  15. 1 0
      build/static/css/main.9fbd2380.chunk.css.map
  16. 3 0
      build/static/js/2.ea412165.chunk.js
  17. 41 0
      build/static/js/2.ea412165.chunk.js.LICENSE.txt
  18. 1 0
      build/static/js/2.ea412165.chunk.js.map
  19. 2 0
      build/static/js/main.320be3a1.chunk.js
  20. 1 0
      build/static/js/main.320be3a1.chunk.js.map
  21. 2 0
      build/static/js/runtime-main.d21f48e7.js
  22. 1 0
      build/static/js/runtime-main.d21f48e7.js.map
  23. 64 0
      build/static/media/heart.8ddee724.svg
  24. BIN
      build/static/media/logo.1993b40e.png
  25. 48 0
      build/static/media/magnifier.acbfeeca.svg
  26. 15 0
      css-draft.css
  27. 40291 0
      package-lock.json
  28. 76 0
      package.json
  29. BIN
      public/favicon.ico
  30. 43 0
      public/index.html
  31. BIN
      public/logo192.png
  32. BIN
      public/logo512.png
  33. 25 0
      public/manifest.json
  34. 3 0
      public/robots.txt
  35. 4 0
      src/App.module.css
  36. 46 0
      src/App.tsx
  37. 78 0
      src/components/CountriesList/CountriesList.module.css
  38. 79 0
      src/components/CountriesList/CountriesList.tsx
  39. 78 0
      src/components/CountriesList/DetailCountry/DetailCountry.module.css
  40. 48 0
      src/components/CountriesList/DetailCountry/DetailCountry.tsx
  41. 8 0
      src/components/Header/Header.module.css
  42. 14 0
      src/components/Header/Header.tsx
  43. 17 0
      src/components/Header/Logo/Logo.module.css
  44. 13 0
      src/components/Header/Logo/Logo.tsx
  45. 31 0
      src/components/Header/SearchBar/SearchBar.module.css
  46. 19 0
      src/components/Header/SearchBar/SearchBar.tsx
  47. 18 0
      src/components/Loader/Loader.jsx
  48. 11 0
      src/components/Loader/Loader.module.css
  49. 14 0
      src/components/SortBar/SortBar.module.css
  50. 24 0
      src/components/SortBar/SortBar.tsx
  51. 11 0
      src/data/api.js
  52. 47 0
      src/hooks/useCountries.ts
  53. BIN
      src/img/github.jpg
  54. 64 0
      src/img/heart.svg
  55. BIN
      src/img/logo.png
  56. 48 0
      src/img/magnifier.svg
  57. 57 0
      src/index.css
  58. 34 0
      src/index.tsx
  59. 1 0
      src/react-app-env.d.ts
  60. 26 0
      tsconfig.json

+ 26 - 0
.gitignore

@@ -0,0 +1,26 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+# /build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Local Netlify folder
+.netlify

+ 5 - 0
.huskyrc

@@ -0,0 +1,5 @@
+{
+  "hooks": {
+    "pre-commit": "lint-staged"
+  }
+}

+ 4 - 0
.lintstagedrc

@@ -0,0 +1,4 @@
+{
+  "src/**/*.{json,css,scss,md}": ["prettier --write"],
+  "src/**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"]
+}

+ 11 - 0
.prettierrc.yaml

@@ -0,0 +1,11 @@
+printWidth: 80
+tabWidth: 2
+useTabs: false
+semi: true
+singleQuote: true
+trailingComma: all
+bracketSpacing: true
+jsxBracketSameLine: false
+arrowParens: avoid
+proseWrap: always
+

+ 26 - 0
build/asset-manifest.json

@@ -0,0 +1,26 @@
+{
+  "files": {
+    "main.css": "/grisha19961116/Helena/static/css/main.9fbd2380.chunk.css",
+    "main.js": "/grisha19961116/Helena/static/js/main.320be3a1.chunk.js",
+    "main.js.map": "/grisha19961116/Helena/static/js/main.320be3a1.chunk.js.map",
+    "runtime-main.js": "/grisha19961116/Helena/static/js/runtime-main.d21f48e7.js",
+    "runtime-main.js.map": "/grisha19961116/Helena/static/js/runtime-main.d21f48e7.js.map",
+    "static/css/2.0b01065c.chunk.css": "/grisha19961116/Helena/static/css/2.0b01065c.chunk.css",
+    "static/js/2.ea412165.chunk.js": "/grisha19961116/Helena/static/js/2.ea412165.chunk.js",
+    "static/js/2.ea412165.chunk.js.map": "/grisha19961116/Helena/static/js/2.ea412165.chunk.js.map",
+    "index.html": "/grisha19961116/Helena/index.html",
+    "static/css/2.0b01065c.chunk.css.map": "/grisha19961116/Helena/static/css/2.0b01065c.chunk.css.map",
+    "static/css/main.9fbd2380.chunk.css.map": "/grisha19961116/Helena/static/css/main.9fbd2380.chunk.css.map",
+    "static/js/2.ea412165.chunk.js.LICENSE.txt": "/grisha19961116/Helena/static/js/2.ea412165.chunk.js.LICENSE.txt",
+    "static/media/heart.8ddee724.svg": "/grisha19961116/Helena/static/media/heart.8ddee724.svg",
+    "static/media/logo.1993b40e.png": "/grisha19961116/Helena/static/media/logo.1993b40e.png",
+    "static/media/magnifier.acbfeeca.svg": "/grisha19961116/Helena/static/media/magnifier.acbfeeca.svg"
+  },
+  "entrypoints": [
+    "static/js/runtime-main.d21f48e7.js",
+    "static/css/2.0b01065c.chunk.css",
+    "static/js/2.ea412165.chunk.js",
+    "static/css/main.9fbd2380.chunk.css",
+    "static/js/main.320be3a1.chunk.js"
+  ]
+}

BIN
build/favicon.ico


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
build/index.html


BIN
build/logo192.png


BIN
build/logo512.png


+ 25 - 0
build/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"
+}

+ 3 - 0
build/robots.txt

@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:

Diferenças do arquivo suprimidas por serem muito extensas
+ 3 - 0
build/static/css/2.0b01065c.chunk.css


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
build/static/css/2.0b01065c.chunk.css.map


Diferenças do arquivo suprimidas por serem muito extensas
+ 2 - 0
build/static/css/main.9fbd2380.chunk.css


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
build/static/css/main.9fbd2380.chunk.css.map


Diferenças do arquivo suprimidas por serem muito extensas
+ 3 - 0
build/static/js/2.ea412165.chunk.js


+ 41 - 0
build/static/js/2.ea412165.chunk.js.LICENSE.txt

@@ -0,0 +1,41 @@
+/*
+object-assign
+(c) Sindre Sorhus
+@license MIT
+*/
+
+/** @license React v0.20.1
+ * scheduler.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+/** @license React v17.0.1
+ * react-dom.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+/** @license React v17.0.1
+ * react-jsx-runtime.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+/** @license React v17.0.1
+ * react.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
build/static/js/2.ea412165.chunk.js.map


Diferenças do arquivo suprimidas por serem muito extensas
+ 2 - 0
build/static/js/main.320be3a1.chunk.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
build/static/js/main.320be3a1.chunk.js.map


Diferenças do arquivo suprimidas por serem muito extensas
+ 2 - 0
build/static/js/runtime-main.d21f48e7.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
build/static/js/runtime-main.d21f48e7.js.map


+ 64 - 0
build/static/media/heart.8ddee724.svg

@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<g>
+	<g>
+		<path d="M331,202.274v60c16.538,0,30-13.462,30-30C361,215.736,347.538,202.274,331,202.274z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<rect x="211" y="202.27" width="90" height="60"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M374.124,20.399c-47.556,0-81.21,19.887-103.124,42.354v109.521h60c33.091,0,60,26.909,60,60s-26.909,60-60,60h-74.702
+			l-12.003,60h61.069l-35.927,139.327c20.691-19.354,40.457-37.425,58.984-54.337C437.245,337.933,512,272.308,512,176.302
+			C512,87.415,451.088,20.399,374.124,20.399z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M151,232.274c0,16.538,13.462,30,30,30v-60C164.462,202.274,151,215.736,151,232.274z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M207.704,382.274l18.003-90H181c-33.091,0-60-26.909-60-60s26.909-60,60-60h60V64.485
+			c-21.105-21.912-55.056-44.086-106.876-44.086C57.162,20.399,0,87.416,0,176.302c0,94.161,69.73,158.086,176.591,254.854
+			c19.62,17.769,40.67,36.901,62.642,57.378l27.402-106.26H207.704z"/>
+	</g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>

BIN
build/static/media/logo.1993b40e.png


+ 48 - 0
build/static/media/magnifier.acbfeeca.svg

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 485.104 485.104" style="enable-background:new 0 0 485.104 485.104;" xml:space="preserve">
+<g>
+	<path d="M110.028,115.171c-4.76-4.767-12.483-4.752-17.227,0c-32.314,32.33-32.314,84.898-0.016,117.197
+		c2.38,2.379,5.487,3.569,8.614,3.569c3.123,0,6.234-1.19,8.613-3.569c4.76-4.76,4.76-12.469,0-17.228
+		c-22.795-22.803-22.795-59.923,0.016-82.742C114.788,127.64,114.788,119.923,110.028,115.171z"/>
+	<path d="M471.481,405.861L324.842,259.23c37.405-66.25,28.109-151.948-28.217-208.317C263.787,18.075,220.133,0,173.718,0
+		C127.287,0,83.633,18.075,50.81,50.913c-67.717,67.74-67.701,177.979,0.02,245.738c32.85,32.823,76.488,50.897,122.919,50.897
+		c30.489,0,59.708-7.939,85.518-22.595L405.824,471.51c18.113,18.121,47.493,18.129,65.641,0
+		c8.706-8.71,13.593-20.512,13.608-32.823C485.073,426.37,480.171,414.567,471.481,405.861z M85.28,262.191
+		c-48.729-48.756-48.729-128.079-0.016-176.828c23.62-23.627,55.029-36.634,88.453-36.634c33.407,0,64.816,13.007,88.451,36.627
+		c48.715,48.756,48.699,128.094-0.015,176.85c-23.62,23.612-55.014,36.612-88.406,36.612
+		C140.341,298.818,108.919,285.811,85.28,262.191z"/>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>

+ 15 - 0
css-draft.css

@@ -0,0 +1,15 @@
+/* Container */
+
+/* PaintingList */
+.PaintingList {
+  padding: 0;
+  margin: 0;
+  list-style: none;
+  max-width: 1170px;
+  display: flex;
+  margin: -10px auto;
+}
+
+.PaintingList__item {
+  margin: 10px;
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 40291 - 0
package-lock.json


+ 76 - 0
package.json

@@ -0,0 +1,76 @@
+{
+  "homepage": "https://github.com/grisha19961116/Helena",
+  "name": "react-21-22",
+  "version": "0.1.0",
+  "private": true,
+  "dependencies": {
+    "@material-ui/core": "^4.11.3",
+    "@reduxjs/toolkit": "^1.5.0",
+    "@testing-library/jest-dom": "^5.11.6",
+    "@testing-library/react": "^11.2.2",
+    "@testing-library/user-event": "^12.2.2",
+    "@types/jest": "^26.0.15",
+    "@types/node": "^14.14.9",
+    "@types/react": "^17.0.0",
+    "@types/react-dom": "^17.0.9",
+    "@types/yup": "^0.29.13",
+    "axios": "^0.21.1",
+    "bootstrap": "^4.6.0",
+    "canvas": "^2.8.0",
+    "formik": "^2.2.6",
+    "gh-pages": "^3.1.0",
+    "modern-normalize": "^1.0.0",
+    "react": "^17.0.1",
+    "react-dom": "^17.0.1",
+    "react-js-pagination": "^3.0.3",
+    "react-loader-spinner": "^4.0.0",
+    "react-query": "^3.24.3",
+    "react-redux": "^7.2.2",
+    "react-router-dom": "^5.2.0",
+    "react-scripts": "4.0.1",
+    "react-toastify": "^6.2.0",
+    "redux": "^4.0.5",
+    "redux-devtools-extension": "^2.13.8",
+    "redux-logger": "^3.0.6",
+    "redux-persist": "^6.0.0",
+    "redux-toolkit": "^1.1.2",
+    "typescript": "^4.3.5",
+    "uuid": "^8.3.1",
+    "web-vitals": "^0.2.4",
+    "yup": "^0.32.8"
+  },
+  "scripts": {
+    "start": "set PORT=3007 && react-scripts start",
+    "build": "react-scripts build",
+    "test": "react-scripts test",
+    "eject": "react-scripts eject",
+    "predeploy": "npm run build",
+    "deploy": "gh-pages -d build"
+  },
+  "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"
+    ]
+  },
+  "devDependencies": {
+    "@types/react-redux": "^7.1.18",
+    "@types/react-router-dom": "^5.1.8",
+    "husky": "^4.3.0",
+    "lint-staged": "^10.5.2",
+    "prettier": "^2.2.1",
+    "prop-types": "^15.7.2"
+  }
+}

BIN
public/favicon.ico


+ 43 - 0
public/index.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <meta
+      name="description"
+      content="Web site created using create-react-app"
+    />
+    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+    <!--
+      manifest.json provides metadata used when your web app is installed on a
+      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
+    -->
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <!--
+      Notice the use of %PUBLIC_URL% in the tags above.
+      It will be replaced with the URL of the `public` folder during the build.
+      Only files inside the `public` folder can be referenced from the HTML.
+
+      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+      work correctly both with client-side routing and a non-root public URL.
+      Learn how to configure a non-root public URL by running `npm run build`.
+    -->
+    <title>React App</title>
+  </head>
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+    <!--
+      This HTML file is a template.
+      If you open it directly in the browser, you will see an empty page.
+
+      You can add webfonts, meta tags, or analytics to this file.
+      The build step will place the bundled scripts into the <body> tag.
+
+      To begin the development, run `npm start` or `yarn start`.
+      To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+  </body>
+</html>

BIN
public/logo192.png


BIN
public/logo512.png


+ 25 - 0
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"
+}

+ 3 - 0
public/robots.txt

@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:

+ 4 - 0
src/App.module.css

@@ -0,0 +1,4 @@
+.appWrapper {
+  min-width: 100vw;
+  min-height: 100vh;
+}

+ 46 - 0
src/App.tsx

@@ -0,0 +1,46 @@
+import { lazy } from 'react';
+
+import s from './App.module.css';
+import useCountries from './hooks/useCountries';
+
+const CountriesList = lazy(
+  () =>
+    import(
+      './components/CountriesList/CountriesList' /* webpackChunkName: "CountriesList" */
+    ),
+);
+const Header = lazy(
+  () => import('./components/Header/Header' /* webpackChunkName: "Header" */),
+);
+const SortBar = lazy(
+  () =>
+    import('./components/SortBar/SortBar' /* webpackChunkName: "SortBar" */),
+);
+const Loader = lazy(
+  () => import('./components/Loader/Loader' /* webpackChunkName: "Loader" */),
+);
+
+function App() {
+  const [isLoading, setFilter, setSorting, countries] = useCountries();
+
+  const handleFilter = (e: any) => {
+    e.preventDefault();
+    setFilter(e.target.value);
+  };
+
+  const handleSort = (e: any) => {
+    const sortBy = e.target.dataset.sort;
+    sortBy && setSorting(sortBy);
+  };
+
+  return (
+    <div className={s.appWrapper}>
+      <Header handleFilter={handleFilter} />
+      <SortBar handleSort={handleSort} />
+      {countries !== [] && <CountriesList countries={countries} />}
+      {isLoading && <Loader />}
+    </div>
+  );
+}
+
+export default App;

+ 78 - 0
src/components/CountriesList/CountriesList.module.css

@@ -0,0 +1,78 @@
+.countriesList {
+  margin: 0 auto;
+  padding: 0 60px;
+}
+
+.countriesList_item {
+  position: relative;
+  width: 1800px;
+  height: 80px;
+  display: flex;
+  flex-wrap: nowrap;
+  align-items: center;
+  align-content: center;
+  background: #ffffff;
+  color: #000000;
+  box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25);
+  border-radius: 20px;
+}
+
+.countriesList_item:not(:last-child) {
+  margin-bottom: 10px;
+}
+.countriesList_item::before,
+.countriesList_item::after {
+  content: '';
+  position: absolute;
+  width: 1px;
+  height: 80px;
+  background: #b2b2b2;
+}
+
+.countriesList_item::before {
+  left: 138px;
+}
+
+.countriesList_item::after {
+  left: 1300px;
+}
+
+.countriesList_item_number {
+  text-align: center;
+  width: 140px;
+}
+
+.countriesList_item_country {
+  padding-left: 20px;
+  width: 1160px;
+}
+
+.countriesList_item_confirmed {
+  padding-left: 20px;
+}
+
+.countriesList_item_number,
+.countriesList_item_country,
+.countriesList_item_confirmed {
+  font-family: Roboto;
+  font-style: normal;
+  font-weight: bold;
+  font-size: 24px;
+  line-height: 28px;
+}
+
+.countriesList_item:first-child {
+  background: #2196f3;
+  color: #ffffff;
+}
+
+/* .countriesList_item_number:on{
+  color: #ffffff;
+}
+.countriesList_item_country:first-child{
+  color: #ffffff;
+}
+
+.countriesList_item_confirmed:first-child{
+  color: #ffffff;
+} */

+ 79 - 0
src/components/CountriesList/CountriesList.tsx

@@ -0,0 +1,79 @@
+import { useState } from 'react';
+import { lazy } from 'react';
+
+import s from './CountriesList.module.css';
+
+const DetailCountry = lazy(
+  () =>
+    import(
+      './DetailCountry/DetailCountry' /* webpackChunkName: "DetailCountry" */
+    ),
+);
+
+interface ICountry {
+  Country: string;
+  TotalConfirmed: number;
+  TotalDeaths: number;
+  TotalRecovered: number;
+  ID: string;
+}
+
+interface IHandleCloseDetail {
+  (e: any): void;
+}
+
+const CountriesList = ({ countries }: { countries: ICountry[] }) => {
+  const [isOpen, setIsOpen] = useState<boolean>(false);
+  const [country, setCountry] = useState<ICountry>({
+    Country: 'missed',
+    TotalConfirmed: 0,
+    TotalDeaths: 0,
+    TotalRecovered: 0,
+    ID: `unknown`,
+  });
+
+  const handleOpenDetail = (e: any) => {
+    const tagName = e.target.tagName;
+    if (tagName === 'H4' || tagName === 'LI') {
+      const ID = e.target.dataset.id;
+      const selectedCountry = countries.find(el => el.ID === ID);
+      selectedCountry && setCountry(selectedCountry);
+      setIsOpen(true);
+    }
+  };
+
+  const handleCloseDetail: IHandleCloseDetail = e =>
+    e.target.id === 'close' && setIsOpen(false);
+
+  return (
+    <>
+      <ul className={s.countriesList} onClick={handleOpenDetail}>
+        {countries.length > 0 &&
+          countries.map((country: ICountry, index: number) => {
+            const { Country, TotalConfirmed, ID } = country;
+            return (
+              <li className={s.countriesList_item} key={ID} data-id={ID}>
+                <h4 className={s.countriesList_item_number} data-id={ID}>
+                  {index + 1}
+                </h4>
+                <h4 className={s.countriesList_item_country} data-id={ID}>
+                  {Country}
+                </h4>
+                <h4 className={s.countriesList_item_confirmed} data-id={ID}>
+                  {TotalConfirmed}
+                </h4>
+              </li>
+            );
+          })}
+      </ul>
+      {isOpen && (
+        <DetailCountry
+          handleCloseDetail={handleCloseDetail}
+          country={country}
+        />
+      )}
+    </>
+  );
+};
+
+export default CountriesList;

+ 78 - 0
src/components/CountriesList/DetailCountry/DetailCountry.module.css

@@ -0,0 +1,78 @@
+.detailWrapper {
+  position: fixed;
+  display: flex;
+  justify-content: center;
+  align-content: center;
+  align-items: center;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  background: rgba(0, 0, 0, 0.5);
+}
+
+.detailNotification {
+  width: 600px;
+  height: 400px;
+  background: #ffffff;
+  border-radius: 20px;
+  padding: 20px;
+}
+
+.detailNotification__title {
+  margin-bottom: 40px;
+  text-align: center;
+  font-family: Roboto;
+  font-style: normal;
+  font-weight: bold;
+  font-size: 48px;
+  line-height: 56px;
+  color: #000000;
+}
+
+.detailNotification__row {
+  display: flex;
+  flex-wrap: nowrap;
+  justify-content: space-between;
+  width: 560px;
+  height: 30px;
+  margin-bottom: 40px;
+}
+
+.detailNotification__row__title {
+  font-family: Roboto;
+  font-style: normal;
+  font-weight: normal;
+  font-size: 24px;
+  line-height: 28px;
+  color: #666666;
+  margin-left: 40px;
+  width: 160px;
+}
+
+.detailNotification__row__amount {
+  font-family: Roboto;
+  font-style: normal;
+  font-weight: normal;
+  font-size: 24px;
+  line-height: 28px;
+  color: #666666;
+  width: 330px;
+  text-align: right;
+}
+
+.detailNotification__btn {
+  display: block;
+  margin: 0 auto;
+  text-transform: uppercase;
+  width: 171px;
+  height: 49px;
+  background: #2196f3;
+  border-radius: 20px;
+  font-family: Roboto;
+  font-style: normal;
+  font-weight: bold;
+  font-size: 24px;
+  line-height: 28px;
+  color: #ffffff;
+}

+ 48 - 0
src/components/CountriesList/DetailCountry/DetailCountry.tsx

@@ -0,0 +1,48 @@
+import s from './DetailCountry.module.css';
+import heartSvg from '../../../img/heart.svg';
+
+interface IProps {
+  country: {
+    Country: string;
+    TotalConfirmed: number;
+    TotalDeaths: number;
+    TotalRecovered: number;
+  };
+  handleCloseDetail: any;
+}
+
+const DetailCountry = ({ country, handleCloseDetail }: IProps) => {
+  const { Country, TotalConfirmed, TotalDeaths, TotalRecovered } = country;
+
+  return (
+    <div className={s.detailWrapper} id="close" onClick={handleCloseDetail}>
+      <div className={s.detailNotification}>
+        <h3 className={s.detailNotification__title}>{Country}</h3>
+        <div className={s.detailNotification__row}>
+          <img src={heartSvg} width="30px" height="30px" alt="heart" />
+          <h4 className={s.detailNotification__row__title}>Total Confirmed</h4>
+          <h4 className={s.detailNotification__row__amount}>
+            {TotalConfirmed}
+          </h4>
+        </div>
+        <div className={s.detailNotification__row}>
+          <img src={heartSvg} width="30px" height="30px" alt="death" />
+          <h4 className={s.detailNotification__row__title}>Total Deaths</h4>
+          <h4 className={s.detailNotification__row__amount}>{TotalDeaths}</h4>
+        </div>
+        <div className={s.detailNotification__row}>
+          <img src={heartSvg} width="30px" height="30px" alt="recovered" />
+          <h4 className={s.detailNotification__row__title}>Total Recovered</h4>
+          <h4 className={s.detailNotification__row__amount}>
+            {TotalRecovered}
+          </h4>
+        </div>
+        <button className={s.detailNotification__btn} id="close">
+          ok
+        </button>
+      </div>
+    </div>
+  );
+};
+
+export default DetailCountry;

+ 8 - 0
src/components/Header/Header.module.css

@@ -0,0 +1,8 @@
+.headerWrapper {
+  padding: 40px 60px 0px 60px;
+  margin-bottom: 30px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  align-content: center;
+}

+ 14 - 0
src/components/Header/Header.tsx

@@ -0,0 +1,14 @@
+import s from './Header.module.css';
+import Logo from './Logo/Logo';
+import SearchBar from './SearchBar/SearchBar';
+
+const Header = ({ handleFilter }: { handleFilter: any }) => {
+  return (
+    <div className={s.headerWrapper}>
+      <Logo />
+      <SearchBar handleFilter={handleFilter} />
+    </div>
+  );
+};
+
+export default Header;

+ 17 - 0
src/components/Header/Logo/Logo.module.css

@@ -0,0 +1,17 @@
+.logoWrapper {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  align-content: center;
+  width: 580px;
+}
+
+.logoStatistic {
+  font-family: Roboto;
+  font-style: normal;
+  font-weight: bold;
+  font-size: 72px;
+  line-height: 84px;
+  color: #000000;
+  text-transform: uppercase;
+}

+ 13 - 0
src/components/Header/Logo/Logo.tsx

@@ -0,0 +1,13 @@
+import s from './Logo.module.css';
+import logoImg from '../../../img/logo.png';
+
+const Logo = () => {
+  return (
+    <div className={s.logoWrapper}>
+      <img src={logoImg} alt="logo" width="200px" height="200px" />
+      <h1 className={s.logoStatistic}>statistic</h1>
+    </div>
+  );
+};
+
+export default Logo;

+ 31 - 0
src/components/Header/SearchBar/SearchBar.module.css

@@ -0,0 +1,31 @@
+.searchBar {
+  display: flex;
+  justify-content: space-between;
+  width: 600px;
+  height: 80px;
+  padding-left: 15px;
+  background: #ffffff;
+  box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25);
+  border-radius: 20px;
+}
+
+.searchBar__input {
+  width: 450px;
+  border: none;
+  outline: none;
+  padding-left: 10px;
+  font-family: Roboto;
+  font-style: normal;
+  font-weight: normal;
+  font-size: 48px;
+  line-height: 56px;
+  color: #000000;
+}
+
+.searchBar__btn {
+  width: 80px;
+  display: flex;
+  justify-content: center;
+  border: none;
+  background: #ffffff;
+}

+ 19 - 0
src/components/Header/SearchBar/SearchBar.tsx

@@ -0,0 +1,19 @@
+import s from './SearchBar.module.css';
+import magnifierImg from '../../../img/magnifier.svg';
+
+const SearchBar = ({ handleFilter }: { handleFilter: any }) => {
+  return (
+    <form className={s.searchBar} onChange={handleFilter}>
+      <input
+        type="text"
+        className={s.searchBar__input}
+        placeholder="Write country name..."
+      ></input>
+      <button className={s.searchBar__btn} type="submit">
+        <img src={magnifierImg} alt="magnifier" width="30px" height="30px" />
+      </button>
+    </form>
+  );
+};
+
+export default SearchBar;

+ 18 - 0
src/components/Loader/Loader.jsx

@@ -0,0 +1,18 @@
+import 'react-loader-spinner/dist/loader/css/react-spinner-loader.css';
+import Loader from 'react-loader-spinner';
+
+import s from './Loader.module.css';
+
+const Load = () => {
+  return (
+    <Loader
+      className={s.loader}
+      type="Puff"
+      color="#0ca0f5"
+      height={100}
+      width={100}
+      timeout={300000}
+    />
+  );
+};
+export default Load;

+ 11 - 0
src/components/Loader/Loader.module.css

@@ -0,0 +1,11 @@
+.loader {
+  position: fixed;
+  display: inline-block;
+  width: 5vw;
+  height: 5vh;
+  top: 86vh;
+  left: 0;
+  right: 0;
+  margin-left: auto;
+  margin-right: 3vw;
+}

+ 14 - 0
src/components/SortBar/SortBar.module.css

@@ -0,0 +1,14 @@
+.sortBarWrapper {
+  display: flex;
+  justify-content: center;
+  margin-bottom: 20px;
+}
+
+.sortBarWrapper__btn:first-child {
+  margin-right: 30px;
+}
+.sortBarWrapper__btn {
+  height: 50px;
+  background-color: rgb(42, 159, 255);
+  color: white;
+}

+ 24 - 0
src/components/SortBar/SortBar.tsx

@@ -0,0 +1,24 @@
+import s from './SortBar.module.css';
+
+const SortBar = ({ handleSort }: { handleSort: any }) => {
+  return (
+    <div className={s.sortBarWrapper} onClick={handleSort}>
+      <button
+        className={s.sortBarWrapper__btn}
+        data-sort="Country"
+        type="button"
+      >
+        Sort By Country Name
+      </button>
+      <button
+        className={s.sortBarWrapper__btn}
+        data-sort="TotalConfirmed"
+        type="button"
+      >
+        Sort By TotalConfirmed
+      </button>
+    </div>
+  );
+};
+
+export default SortBar;

+ 11 - 0
src/data/api.js

@@ -0,0 +1,11 @@
+const axios = require('axios');
+axios.defaults.baseURL = 'https://api.covid19api.com/';
+
+const getAllCountries = async () => {
+  const {
+    data: { Countries },
+  } = await axios.get(`/summary`);
+  return Countries;
+};
+
+export { getAllCountries };

+ 47 - 0
src/hooks/useCountries.ts

@@ -0,0 +1,47 @@
+import { useQuery } from 'react-query';
+import { useState, useMemo } from 'react';
+import { getAllCountries } from '../data/api';
+import { chain } from 'lodash';
+
+export enum SortBy {
+  Country = 'Country',
+  TotalConfirmed = 'TotalConfirmed',
+}
+
+interface ICountry {
+  Country: string;
+  TotalConfirmed: number;
+  TotalDeaths: number;
+  TotalRecovered: number;
+  ID: string;
+}
+
+type UseCountriesResult = [
+  isLoading: boolean,
+  setFilter: (filter: string) => void,
+  setSorting: (sortBy: SortBy) => void,
+  memoizedCountries: ICountry[],
+];
+
+export default function useCountries(): UseCountriesResult {
+  const [filter, setFilter] = useState<string>('');
+  const [sorting, setSorting] = useState<SortBy>(SortBy.Country);
+
+  const { isLoading, data: countries } = useQuery<ICountry[]>(
+    'countries',
+    getAllCountries,
+  );
+
+  const memoizedCountries = useMemo(
+    () =>
+      chain(countries)
+        .filter(({ Country }) =>
+          Country.toLowerCase().includes(filter.toLowerCase()),
+        )
+        .sortBy([sorting])
+        .value(),
+    [countries, filter, sorting],
+  );
+
+  return [isLoading, setFilter, setSorting, memoizedCountries];
+}

BIN
src/img/github.jpg


+ 64 - 0
src/img/heart.svg

@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<g>
+	<g>
+		<path d="M331,202.274v60c16.538,0,30-13.462,30-30C361,215.736,347.538,202.274,331,202.274z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<rect x="211" y="202.27" width="90" height="60"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M374.124,20.399c-47.556,0-81.21,19.887-103.124,42.354v109.521h60c33.091,0,60,26.909,60,60s-26.909,60-60,60h-74.702
+			l-12.003,60h61.069l-35.927,139.327c20.691-19.354,40.457-37.425,58.984-54.337C437.245,337.933,512,272.308,512,176.302
+			C512,87.415,451.088,20.399,374.124,20.399z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M151,232.274c0,16.538,13.462,30,30,30v-60C164.462,202.274,151,215.736,151,232.274z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M207.704,382.274l18.003-90H181c-33.091,0-60-26.909-60-60s26.909-60,60-60h60V64.485
+			c-21.105-21.912-55.056-44.086-106.876-44.086C57.162,20.399,0,87.416,0,176.302c0,94.161,69.73,158.086,176.591,254.854
+			c19.62,17.769,40.67,36.901,62.642,57.378l27.402-106.26H207.704z"/>
+	</g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>

BIN
src/img/logo.png


+ 48 - 0
src/img/magnifier.svg

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 485.104 485.104" style="enable-background:new 0 0 485.104 485.104;" xml:space="preserve">
+<g>
+	<path d="M110.028,115.171c-4.76-4.767-12.483-4.752-17.227,0c-32.314,32.33-32.314,84.898-0.016,117.197
+		c2.38,2.379,5.487,3.569,8.614,3.569c3.123,0,6.234-1.19,8.613-3.569c4.76-4.76,4.76-12.469,0-17.228
+		c-22.795-22.803-22.795-59.923,0.016-82.742C114.788,127.64,114.788,119.923,110.028,115.171z"/>
+	<path d="M471.481,405.861L324.842,259.23c37.405-66.25,28.109-151.948-28.217-208.317C263.787,18.075,220.133,0,173.718,0
+		C127.287,0,83.633,18.075,50.81,50.913c-67.717,67.74-67.701,177.979,0.02,245.738c32.85,32.823,76.488,50.897,122.919,50.897
+		c30.489,0,59.708-7.939,85.518-22.595L405.824,471.51c18.113,18.121,47.493,18.129,65.641,0
+		c8.706-8.71,13.593-20.512,13.608-32.823C485.073,426.37,480.171,414.567,471.481,405.861z M85.28,262.191
+		c-48.729-48.756-48.729-128.079-0.016-176.828c23.62-23.627,55.029-36.634,88.453-36.634c33.407,0,64.816,13.007,88.451,36.627
+		c48.715,48.756,48.699,128.094-0.015,176.85c-23.62,23.612-55.014,36.612-88.406,36.612
+		C140.341,298.818,108.919,285.811,85.28,262.191z"/>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>

+ 57 - 0
src/index.css

@@ -0,0 +1,57 @@
+html {
+  overflow-y: scroll;
+  overflow-x: hidden;
+}
+
+body {
+  min-width: 100vw;
+  min-height: 100vh;
+  margin: 0;
+  padding: 0;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+    sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+    monospace;
+}
+
+img {
+  display: block;
+  max-width: 100%;
+  height: auto;
+}
+
+.box.big {
+  width: 200px;
+  height: 200px;
+  margin-bottom: 100px;
+}
+
+.box.red {
+  background-color: teal;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5 {
+  margin: 0;
+  padding: 0;
+}
+ul {
+  padding: 0;
+  margin: 0;
+}
+
+div {
+  margin: 0;
+  padding: 0;
+}
+
+@import 'https://fonts.googleapis.com/css?family=Mountains+of+Christmas';

+ 34 - 0
src/index.tsx

@@ -0,0 +1,34 @@
+import React, { Suspense } from 'react';
+import ReactDOM from 'react-dom';
+import 'react-toastify/dist/ReactToastify.css';
+import { ToastContainer } from 'react-toastify';
+import { QueryClient, QueryClientProvider } from 'react-query';
+import { ReactQueryDevtools } from 'react-query/devtools';
+import 'modern-normalize/modern-normalize.css';
+import './index.css';
+import App from './App';
+
+const queryClient = new QueryClient();
+
+ReactDOM.render(
+  <React.StrictMode>
+    <Suspense fallback={null}>
+      <QueryClientProvider client={queryClient}>
+        <App />
+        <ReactQueryDevtools initialIsOpen={false} />
+      </QueryClientProvider>
+      <ToastContainer
+        position="top-right"
+        autoClose={3000}
+        hideProgressBar={false}
+        newestOnTop={false}
+        closeOnClick
+        rtl={false}
+        pauseOnFocusLoss
+        draggable
+        pauseOnHover
+      />
+    </Suspense>
+  </React.StrictMode>,
+  document.getElementById('root'),
+);

+ 1 - 0
src/react-app-env.d.ts

@@ -0,0 +1 @@
+/// <reference types="react-scripts" />

+ 26 - 0
tsconfig.json

@@ -0,0 +1,26 @@
+{
+  "compilerOptions": {
+    "target": "es5",
+    "lib": [
+      "dom",
+      "dom.iterable",
+      "esnext"
+    ],
+    "allowJs": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
+    "strict": true,
+    "forceConsistentCasingInFileNames": true,
+    "noFallthroughCasesInSwitch": true,
+    "module": "esnext",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "react-jsx"
+  },
+  "include": [
+    "src"
+  ]
+}