├── .eslintrc.js
├── .github
└── FUNDING.yml
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
└── src
├── App.js
├── App.module.css
├── api
└── index.js
├── components
├── Cards
│ ├── Card
│ │ ├── Card.jsx
│ │ └── Card.module.css
│ ├── Cards.jsx
│ └── Cards.module.css
├── Chart
│ ├── Chart.jsx
│ └── Chart.module.css
├── CountryPicker
│ ├── CountryPicker.jsx
│ └── CountryPicker.module.css
└── index.js
├── images
└── image.png
└── index.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true,
5 | },
6 | extends: [
7 | 'plugin:react/recommended',
8 | 'airbnb',
9 | ],
10 | globals: {
11 | Atomics: 'readonly',
12 | SharedArrayBuffer: 'readonly',
13 | },
14 | parserOptions: {
15 | ecmaFeatures: {
16 | jsx: true,
17 | },
18 | ecmaVersion: 2018,
19 | sourceType: 'module',
20 | },
21 | plugins: [
22 | 'react',
23 | ],
24 | rules: {
25 | "import/prefer-default-export": 0,
26 | "max-len": [
27 | 2,
28 | 250
29 | ],
30 | "no-multiple-empty-lines": [
31 | "error",
32 | {
33 | "max": 1,
34 | "maxEOF": 1
35 | }
36 | ],
37 | "no-underscore-dangle": [
38 | "error",
39 | {
40 | "allow": [
41 | "_d",
42 | "_dh",
43 | "_h",
44 | "_id",
45 | "_m",
46 | "_n",
47 | "_t",
48 | "_text"
49 | ]
50 | }
51 | ],
52 | "object-curly-newline": 0,
53 | "react/prop-types": 0,
54 | "react/jsx-filename-extension": 0,
55 | "react/jsx-one-expression-per-line": 0,
56 | "jsx-a11y/click-events-have-key-events": 0,
57 | "jsx-a11y/alt-text": 0,
58 | "jsx-a11y/no-autofocus": 0,
59 | "jsx-a11y/no-static-element-interactions": 0,
60 | "react/no-array-index-key": 0,
61 | "jsx-a11y/anchor-is-valid": [
62 | "error",
63 | {
64 | "components": [
65 | "Link"
66 | ],
67 | "specialLink": [
68 | "to",
69 | "hrefLeft",
70 | "hrefRight"
71 | ],
72 | "aspects": [
73 | "noHref",
74 | "invalidHref",
75 | "preferButton"
76 | ]
77 | }
78 | ]
79 | }
80 | };
81 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: adrianhajdin
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # COVID-19 Tracker
2 |
3 | ### [Live Site](https://covid19statswebsite.netlify.com/)
4 |
5 | 
6 |
7 | ### [🌟 Become a top 1% Next.js 13 developer in only one course](https://jsmastery.pro/next13)
8 | ### [🚀 Land your dream programming job in 6 months](https://jsmastery.pro/masterclass)
9 |
10 | ## Stay up to date with new projects
11 | New major projects coming soon, subscribe to the mailing list to stay up to date https://resource.jsmasterypro.com/newsletter
12 |
13 | ## Introduction
14 | This is a code repository for the corresponding video tutorial.
15 |
16 | In this video, we will create a full COVID-19 Tracker. We're going to use React, Charts.JS and Material UI.
17 |
18 | By the end of this video, you will have a strong understanding of React's workflow and the use of hooks.
19 |
20 | API used: https://covid19.mathdro.id/api
21 |
22 | Setup:
23 | - run ```npm i && npm start```
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "corona_app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.9.7",
7 | "@testing-library/jest-dom": "^4.2.4",
8 | "@testing-library/react": "^9.5.0",
9 | "@testing-library/user-event": "^7.2.1",
10 | "axios": "^0.19.2",
11 | "chart.js": "^2.9.3",
12 | "classnames": "^2.2.6",
13 | "react": "^16.13.1",
14 | "react-chartjs-2": "^2.9.0",
15 | "react-countup": "^4.3.3",
16 | "react-dom": "^16.13.1",
17 | "react-scripts": "3.4.1"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": "react-app"
27 | },
28 | "browserslist": {
29 | "production": [
30 | ">0.2%",
31 | "not dead",
32 | "not op_mini all"
33 | ],
34 | "development": [
35 | "last 1 chrome version",
36 | "last 1 firefox version",
37 | "last 1 safari version"
38 | ]
39 | },
40 | "devDependencies": {
41 | "eslint": "^6.8.0",
42 | "eslint-config-airbnb": "^18.1.0",
43 | "eslint-plugin-import": "^2.20.2",
44 | "eslint-plugin-jsx-a11y": "^6.2.3",
45 | "eslint-plugin-react": "^7.19.0",
46 | "eslint-plugin-react-hooks": "^2.5.1"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrianhajdin/project_corona_tracker/c6b004c2a7f12a13b2c728f90db957e8346d208d/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Corona Virus Tracker
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Cards, CountryPicker, Chart } from './components';
4 | import { fetchData } from './api/';
5 | import styles from './App.module.css';
6 |
7 | import image from './images/image.png';
8 |
9 | class App extends React.Component {
10 | state = {
11 | data: {},
12 | country: '',
13 | }
14 |
15 | async componentDidMount() {
16 | const data = await fetchData();
17 |
18 | this.setState({ data });
19 | }
20 |
21 | handleCountryChange = async (country) => {
22 | const data = await fetchData(country);
23 |
24 | this.setState({ data, country: country });
25 | }
26 |
27 | render() {
28 | const { data, country } = this.state;
29 |
30 | return (
31 |
32 |

33 |
34 |
35 |
36 |
37 | );
38 | }
39 | }
40 |
41 | export default App;
--------------------------------------------------------------------------------
/src/App.module.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: rgb(250, 250,250);
3 | }
4 |
5 | .container {
6 | display: flex;
7 | align-items: center;
8 | flex-direction: column;
9 | }
10 |
11 | .image {
12 | width: 370px;
13 | margin-top: 50px;
14 | }
15 |
16 | @media only screen and (max-width: 770px) {
17 | .container {
18 | margin: 0 10%;
19 | }
20 |
21 | .image {
22 | width: 100%;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/api/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | const url = 'https://covid19.mathdro.id/api';
4 |
5 | export const fetchData = async (country) => {
6 | let changeableUrl = url;
7 |
8 | if (country) {
9 | changeableUrl = `${url}/countries/${country}`;
10 | }
11 |
12 | try {
13 | const { data: { confirmed, recovered, deaths, lastUpdate } } = await axios.get(changeableUrl);
14 |
15 | return { confirmed, recovered, deaths, lastUpdate };
16 | } catch (error) {
17 | return error;
18 | }
19 | };
20 |
21 | // export const fetchDailyData = async () => {
22 | // try {
23 | // const { data } = await axios.get(`${url}/daily`);
24 |
25 | // return data.map(({ confirmed, deaths, reportDate: date }) => ({ confirmed: confirmed.total, deaths: deaths.total, date }));
26 | // } catch (error) {
27 | // return error;
28 | // }
29 | // };
30 |
31 | // Instead of Global, it fetches the daily data for the US
32 | export const fetchDailyData = async () => {
33 | try {
34 | const { data } = await axios.get('https://api.covidtracking.com/v1/us/daily.json');
35 |
36 | return data.map(({ positive, recovered, death, dateChecked: date }) => ({ confirmed: positive, recovered, deaths: death, date }));
37 | } catch (error) {
38 | return error;
39 | }
40 | };
41 |
42 | export const fetchCountries = async () => {
43 | try {
44 | const { data: { countries } } = await axios.get(`${url}/countries`);
45 |
46 | return countries.map((country) => country.name);
47 | } catch (error) {
48 | return error;
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/src/components/Cards/Card/Card.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Card, CardContent, Typography, Grid } from '@material-ui/core';
3 | import CountUp from 'react-countup';
4 | import cx from 'classnames';
5 |
6 | import styles from './Card.module.css';
7 |
8 | const CardComponent = ({ className, cardTitle, value, lastUpdate, cardSubtitle }) => (
9 |
10 |
11 |
12 | {cardTitle}
13 |
14 |
15 |
16 |
17 |
18 | {new Date(lastUpdate).toDateString()}
19 |
20 |
21 | {cardSubtitle}
22 |
23 |
24 |
25 | );
26 |
27 | export default CardComponent;
28 |
--------------------------------------------------------------------------------
/src/components/Cards/Card/Card.module.css:
--------------------------------------------------------------------------------
1 | .card {
2 | margin: 0 2% !important;
3 | }
4 | @media only screen and (max-width: 770px) {
5 | .card {
6 | margin: 2% 0 !important;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/components/Cards/Cards.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Typography, Grid } from '@material-ui/core';
3 | import CardComponent from './Card/Card';
4 | import styles from './Cards.module.css';
5 |
6 | const Info = ({ data: { confirmed, recovered, deaths, lastUpdate } }) => {
7 | if (!confirmed) {
8 | return 'Loading...';
9 | }
10 |
11 | return (
12 |
13 | Global
14 |
15 |
22 |
29 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default Info;
42 |
--------------------------------------------------------------------------------
/src/components/Cards/Cards.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | margin: 50px 0;
6 | }
7 |
8 | .infected {
9 | border-bottom: 10px solid rgba(0, 0, 255, 0.5);
10 | }
11 |
12 | .recovered{
13 | border-bottom: 10px solid rgba(0, 255, 0, 0.5);
14 | }
15 |
16 | .deaths{
17 | border-bottom: 10px solid rgba(255, 0, 0, 0.5);
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/Chart/Chart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Line, Bar } from 'react-chartjs-2';
3 |
4 | import { fetchDailyData } from '../../api';
5 |
6 | import styles from './Chart.module.css';
7 |
8 | const Chart = ({ data: { confirmed, recovered, deaths }, country }) => {
9 | const [dailyData, setDailyData] = useState({});
10 |
11 | useEffect(() => {
12 | const fetchMyAPI = async () => {
13 | const initialDailyData = await fetchDailyData();
14 |
15 | setDailyData(initialDailyData);
16 | };
17 |
18 | fetchMyAPI();
19 | }, []);
20 |
21 | const barChart = (
22 | confirmed ? (
23 |
39 | ) : null
40 | );
41 |
42 | const lineChart = (
43 | dailyData[0] ? (
44 | new Date(date).toLocaleDateString()),
47 | datasets: [{
48 | data: dailyData.map((data) => data.confirmed),
49 | label: 'Infected',
50 | borderColor: '#3333ff',
51 | fill: true,
52 | }, {
53 | data: dailyData.map((data) => data.deaths),
54 | label: 'Deaths',
55 | borderColor: 'red',
56 | backgroundColor: 'rgba(255, 0, 0, 0.5)',
57 | fill: true,
58 | }, {
59 | data: dailyData.map((data) => data.recovered),
60 | label: 'Recovered',
61 | borderColor: 'green',
62 | backgroundColor: 'rgba(0, 255, 0, 0.5)',
63 | fill: true,
64 | },
65 | ],
66 | }}
67 | />
68 | ) : null
69 | );
70 |
71 | return (
72 |
73 | {country ? barChart : lineChart}
74 |
75 | );
76 | };
77 |
78 | export default Chart;
79 |
--------------------------------------------------------------------------------
/src/components/Chart/Chart.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | justify-content: center;
4 | width: 65%;
5 | }
6 |
7 | @media only screen and (max-width: 770px) {
8 | .container {
9 | width: 100%;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/components/CountryPicker/CountryPicker.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { NativeSelect, FormControl } from '@material-ui/core';
3 |
4 | import { fetchCountries } from '../../api';
5 |
6 | import styles from './CountryPicker.module.css';
7 |
8 | const Countries = ({ handleCountryChange }) => {
9 | const [countries, setCountries] = useState([]);
10 |
11 | useEffect(() => {
12 | const fetchAPI = async () => {
13 | setCountries(await fetchCountries());
14 | };
15 |
16 | fetchAPI();
17 | }, []);
18 |
19 | return (
20 |
21 | handleCountryChange(e.target.value)}>
22 |
23 | {countries.map((country, i) => )}
24 |
25 |
26 | );
27 | };
28 |
29 | export default Countries;
30 |
--------------------------------------------------------------------------------
/src/components/CountryPicker/CountryPicker.module.css:
--------------------------------------------------------------------------------
1 | .formControl {
2 | width: 30%;
3 | margin-bottom: 30px !important;
4 | }
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Chart } from './Chart/Chart';
2 | export { default as CountryPicker } from './CountryPicker/CountryPicker';
3 | export { default as Cards } from './Cards/Cards';
4 |
--------------------------------------------------------------------------------
/src/images/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrianhajdin/project_corona_tracker/c6b004c2a7f12a13b2c728f90db957e8346d208d/src/images/image.png
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import App from './App';
5 |
6 | ReactDOM.render(, document.getElementById('root'));
7 |
--------------------------------------------------------------------------------