├── .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 | ![COVID-19 Tracker](https://i.ibb.co/X87BqVY/Screenshot-2020-04-13-at-10-14-58.png) 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 | COVID-19 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 | --------------------------------------------------------------------------------