├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── src
├── assets
│ ├── earth-night.jpg
│ └── night-sky.png
├── setupTests.js
├── App.test.js
├── index.css
├── lib
│ ├── httpClient.js
│ ├── utils.js
│ └── countryList.js
├── index.js
├── components
│ ├── Dialog.js
│ ├── Modal.js
│ ├── Chart.js
│ ├── Drawer.js
│ ├── MapChart.js
│ └── Tab.js
├── logo.svg
├── App.js
├── pages
│ └── home.js
└── serviceWorker.js
├── docker-compose.yml
├── Dockerfile
├── .gitignore
├── package.json
└── README.md
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maschad/covid19-banned-flights/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maschad/covid19-banned-flights/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maschad/covid19-banned-flights/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/src/assets/earth-night.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maschad/covid19-banned-flights/HEAD/src/assets/earth-night.jpg
--------------------------------------------------------------------------------
/src/assets/night-sky.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maschad/covid19-banned-flights/HEAD/src/assets/night-sky.png
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | web:
5 | build:
6 | context: .
7 | dockerfile: Dockerfile
8 | volumes:
9 | - '.:/app'
10 | - '/app/node_modules'
11 | ports:
12 | - "3001:3000"
13 | environment:
14 | - NODE_ENV=development
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12.10.0-alpine
2 |
3 | # set working directory
4 | WORKDIR /app
5 |
6 | # add `/app/node_modules/.bin` to $PATH
7 | ENV PATH /app/node_modules/.bin:$PATH
8 |
9 | COPY yarn.lock /app/yarn.lock
10 | COPY . /app/
11 |
12 | RUN yarn
13 |
14 | # start app
15 | CMD ["yarn", "start"]
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render();
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | node_modules
6 | .pnp
7 |
8 | .pnp
9 | .pnp.js
10 |
11 | # testing
12 | coverage
13 |
14 | # production
15 | fe/build
16 |
17 | # misc
18 | .DS_Store
19 | .env.local
20 | .env.development.local
21 | .env.test.local
22 | .env.production.local
23 |
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 | .env.development
28 |
--------------------------------------------------------------------------------
/src/lib/httpClient.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import axios from "axios";
4 |
5 | export const getData = async (url, cancel) => {
6 | try {
7 | const results = await axios(url, { cancelToken: cancel.token });
8 | return results.data;
9 | } catch (error) {
10 | if (axios.isCancel(error)) {
11 | // Handle if request was cancelled
12 | console.log("Request canceled", error.message);
13 | } else {
14 | // Handle usual errors
15 | console.log("Something went wrong: ", error.message);
16 | }
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://bit.ly/CRA-PWA
17 | serviceWorker.unregister();
18 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "COVID19 Travel Info",
3 | "name": "COVID19 Travel Information",
4 | "icons": [{
5 | "src": "favicon.ico",
6 | "sizes": "64x64 32x32 24x24 16x16",
7 | "type": "image/x-icon"
8 | },
9 | {
10 | "src": "logo192.png",
11 | "type": "image/png",
12 | "sizes": "192x192"
13 | },
14 | {
15 | "src": "logo512.png",
16 | "type": "image/png",
17 | "sizes": "512x512"
18 | }
19 | ],
20 | "start_url": ".",
21 | "display": "standalone",
22 | "theme_color": "#000000",
23 | "background_color": "#ffffff"
24 | }
--------------------------------------------------------------------------------
/src/lib/utils.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | export const sanitizeCountryNamesForCOVIDStats = countryName => {
4 | switch (countryName) {
5 | case "Democratic Republic of the Congo":
6 | countryName = "Congo (Kinshasa)";
7 | break;
8 |
9 | case "Republic of Congo":
10 | countryName = "Congo (Brazzaville)";
11 | break;
12 |
13 | case "South Korea":
14 | countryName = "Korea, South";
15 | break;
16 |
17 | case "United States of America":
18 | countryName = "US";
19 | break;
20 |
21 | case "United Republic of Tanzania":
22 | countryName = "Tanzania";
23 | break;
24 | default:
25 | break;
26 | }
27 |
28 | return countryName;
29 | };
30 |
31 | export const sanitizeCountryNamesForFlightInfo = countryName => {
32 | switch (countryName) {
33 | case "United States of America":
34 | countryName = "United States";
35 | break;
36 | default:
37 | break;
38 | }
39 |
40 | return countryName;
41 | };
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "banned-corona-flights",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@iconify/icons-mdi": "^1.0.83",
7 | "@iconify/react": "^1.1.3",
8 | "@material-ui/core": "^4.9.7",
9 | "@material-ui/icons": "^4.9.1",
10 | "@nivo/axes": "^0.61.0",
11 | "@nivo/line": "^0.61.1",
12 | "@testing-library/jest-dom": "^4.2.4",
13 | "@testing-library/react": "^9.3.2",
14 | "@testing-library/user-event": "^7.1.2",
15 | "axios": "^1.6.0",
16 | "qrcode.react": "^1.0.0",
17 | "react": "^16.13.1",
18 | "react-dom": "^16.13.1",
19 | "react-globe.gl": "^2.7.6",
20 | "react-scripts": "3.4.1",
21 | "typeface-roboto": "^0.0.75"
22 | },
23 | "scripts": {
24 | "start": "react-scripts start",
25 | "build": "react-scripts build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject"
28 | },
29 | "eslintConfig": {
30 | "extends": "react-app"
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/Dialog.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import React from "react";
4 | import {
5 | AppBar,
6 | Dialog,
7 | IconButton,
8 | makeStyles,
9 | Slide,
10 | Toolbar,
11 | } from "@material-ui/core";
12 | import CloseIcon from "@material-ui/icons/Close";
13 |
14 | const useStyles = makeStyles((theme) => ({
15 | appBar: {
16 | position: "relative",
17 | },
18 | title: {
19 | marginLeft: theme.spacing(2),
20 | flex: 1,
21 | },
22 | }));
23 |
24 | const Transition = React.forwardRef(function Transition(props, ref) {
25 | return ;
26 | });
27 |
28 | const CustomDialog = ({ handleDialog, dialogContent, dialog }) => {
29 | const classes = useStyles();
30 | return (
31 |
32 |
51 |
52 | );
53 | };
54 |
55 | export default CustomDialog;
56 |
--------------------------------------------------------------------------------
/src/components/Modal.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 | import React from "react";
3 | import { makeStyles, Modal, Paper, Zoom } from "@material-ui/core";
4 |
5 | const useStyles = makeStyles(theme => ({
6 | modal: {
7 | flexGrow: 1,
8 | backgroundColor: theme.palette.background.paper,
9 | border: "2px solid #FFF",
10 | borderRadius: 10,
11 | boxShadow: theme.shadows[5],
12 | padding: theme.spacing(5),
13 | position: "absolute",
14 | [theme.breakpoints.up("lg")]: {
15 | top: "27%",
16 | left: "28%",
17 | transform: "translate(-30%, -30%)",
18 | width: 450
19 | },
20 | [theme.breakpoints.down("md")]: {
21 | top: "20%",
22 | left: "25%",
23 | width: 450
24 | },
25 | [theme.breakpoints.down("sm")]: {
26 | top: "18%",
27 | left: "10%",
28 | width: 268
29 | },
30 | [theme.breakpoints.down("xs")]: {
31 | top: "14%",
32 | left: "5%",
33 | width: 202
34 | },
35 | outline: "none"
36 | }
37 | }));
38 |
39 | const CustomModal = ({ handleModal, modal, modalContent }) => {
40 | const classes = useStyles();
41 |
42 | const renderModalBody = (
43 |
44 | {modalContent}
45 |
46 | );
47 |
48 | return (
49 | handleModal(false)}
52 | aria-labelledby='modal-title'
53 | aria-describedby='modal-description'>
54 | {renderModalBody}
55 |
56 | );
57 | };
58 |
59 | export default CustomModal;
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # See Coronavirus Travel Restrictions, Across the Globe
4 |
5 | ### [Live Site](https://www.covid19globalinfo.com/)
6 |
7 | This project utilizes [pomber's](https://github.com/pomber/covid19) time series JSON API as well as a scraper that I built [here](https://github.com/maschad/covid-scraper). As well as [vasturiano's](https://github.com/vasturiano/react-globe.gl) react globe library.
8 |
9 | 
10 |
11 | ## Available Scripts
12 |
13 | In the project directory, you can run:
14 |
15 | ### `yarn start`
16 |
17 | Runs the app in the development mode.
18 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
19 |
20 | The page will reload if you make edits.
21 | You will also see any lint errors in the console.
22 |
23 | ### `yarn test`
24 |
25 | Launches the test runner in the interactive watch mode.
26 |
27 | ### `yarn build`
28 |
29 | Builds the app for production to the `build` folder.
30 | It correctly bundles React in production mode and optimizes the build for the best performance.
31 |
32 | The build is minified and the filenames include the hashes.
33 | Your app is ready to be deployed!
34 |
35 | ## Setting up
36 |
37 | Because this project requires a backend, you can run the [scraper](https://github.com/maschad/covid-scraper) I mentioned before
38 |
39 | ## Docker Instructions
40 |
41 | If you have [Docker](https://www.docker.com/) then:
42 |
43 | - Build an image
44 | ```
45 | docker build -t /banned-flights
46 | ```
47 | - Run that image
48 |
49 | ```
50 | docker run -d /banned-flights
51 | ```
52 |
53 | - Ensure it's running
54 | ```
55 | docker ps
56 | ```
57 |
--------------------------------------------------------------------------------
/src/components/Chart.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 | import React from "react";
3 |
4 | import { ResponsiveLine } from "@nivo/line";
5 |
6 | const Chart = ({ data }) => {
7 | return (
8 |
71 | );
72 | };
73 |
74 | export default Chart;
75 |
--------------------------------------------------------------------------------
/src/components/Drawer.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import React from "react";
4 | import {
5 | Drawer,
6 | List,
7 | ListItem,
8 | ListItemIcon,
9 | ListItemText,
10 | makeStyles,
11 | Typography
12 | } from "@material-ui/core";
13 |
14 | import { Icon } from "@iconify/react";
15 | import bitcoinIcon from "@iconify/icons-mdi/bitcoin";
16 | import ethereumIcon from "@iconify/icons-mdi/ethereum";
17 |
18 | const useStyles = makeStyles(() => ({
19 | list: {
20 | width: 250
21 | },
22 | titleText: {
23 | justifyContent: "center",
24 | alignItems: "center",
25 | inset: "auto"
26 | }
27 | }));
28 |
29 | const CustomDrawer = ({ drawerState, sendViaQR, toggleDrawer }) => {
30 | const classes = useStyles();
31 |
32 | const cryptoList = (
33 |
38 |
39 |
40 |
41 |
42 | Show some love without borders and bank fees :){" "}
43 |
44 |
45 |
46 | sendViaQR("Ethereum")}>
47 |
48 |
49 |
50 |
51 |
52 | sendViaQR("Bitcoin")}>
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | );
61 |
62 | return (
63 |
64 |
65 | {cryptoList}
66 |
67 |
68 | );
69 | };
70 |
71 | export default CustomDrawer;
72 |
--------------------------------------------------------------------------------
/src/components/MapChart.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import React, { memo, useEffect, useRef, useState } from "react";
4 | import Globe from "react-globe.gl";
5 | import { sanitizeCountryNamesForCOVIDStats } from "../lib/utils";
6 |
7 | const MapChart = ({ countryData, renderChart }) => {
8 | const globeEl = useRef();
9 | const [hoverD, setHoverD] = useState();
10 | const [countries, setCountries] = useState({ features: [] });
11 |
12 | useEffect(() => {
13 | //Get Country info
14 | fetch("./countries.json")
15 | .then((res) => res.json())
16 | .then(setCountries);
17 | }, []);
18 |
19 | const todaysData = (countryName) => {
20 | countryName = sanitizeCountryNamesForCOVIDStats(countryName);
21 |
22 | if (countryData[countryName] !== undefined) {
23 | return countryData[countryName][countryData[countryName].length - 1];
24 | } else {
25 | return "No info";
26 | }
27 | };
28 |
29 | const getPolygonLabel = (data) => {
30 | if (todaysData(data.ADMIN) === "No info") return "No Info";
31 | else
32 | return `
33 | ${data.ADMIN}
34 | Confirmed: ${
35 | todaysData(data.ADMIN).confirmed
36 | }
37 | Deaths: ${todaysData(data.ADMIN).deaths}
38 | Recovered: ${todaysData(data.ADMIN).recovered}
39 | `;
40 | };
41 |
42 | return (
43 | (d === hoverD ? 0.12 : 0.06)}
49 | polygonCapColor={(d) => (d === hoverD ? "green" : "black")}
50 | polygonSideColor={() => "rgba(0, 100, 0, 0.15)"}
51 | polygonStrokeColor={() => "#111"}
52 | polygonLabel={({ properties: d }) => getPolygonLabel(d)}
53 | onPolygonHover={setHoverD}
54 | onPolygonClick={({ properties: d }) => renderChart(d.ADMIN)}
55 | polygonsTransitionDuration={300}
56 | />
57 | );
58 | };
59 |
60 | export default memo(MapChart);
61 |
--------------------------------------------------------------------------------
/src/components/Tab.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import React from "react";
4 | import PropTypes from "prop-types";
5 | import { makeStyles } from "@material-ui/core/styles";
6 | import AppBar from "@material-ui/core/AppBar";
7 | import Tabs from "@material-ui/core/Tabs";
8 | import Tab from "@material-ui/core/Tab";
9 | import Typography from "@material-ui/core/Typography";
10 | import Box from "@material-ui/core/Box";
11 |
12 | function TabPanel(props) {
13 | const { children, value, index, ...other } = props;
14 |
15 | return (
16 |
23 | {value === index && {children}}
24 |
25 | );
26 | }
27 |
28 | TabPanel.propTypes = {
29 | children: PropTypes.node,
30 | index: PropTypes.any.isRequired,
31 | value: PropTypes.any.isRequired
32 | };
33 |
34 | function a11yProps(index) {
35 | return {
36 | id: `simple-tab-${index}`,
37 | "aria-controls": `simple-tabpanel-${index}`
38 | };
39 | }
40 |
41 | const useStyles = makeStyles(theme => ({
42 | root: {
43 | flexGrow: 1,
44 | backgroundColor: theme.palette.background.paper
45 | }
46 | }));
47 |
48 | const CustomTab = ({ tabTitles, tabContents }) => {
49 | const classes = useStyles();
50 | const [value, setValue] = React.useState(0);
51 |
52 | const handleChange = (event, newValue) => {
53 | setValue(newValue);
54 | };
55 |
56 | return (
57 |
58 |
59 |
63 | {tabTitles.map((title, index) => (
64 |
65 | ))}
66 |
67 |
68 | {tabContents.map((content, index) => (
69 |
70 | {content}
71 |
72 | ))}
73 |
74 | );
75 | };
76 |
77 | export default CustomTab;
78 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
26 |
30 |
31 |
40 | COVID19 Global Info
41 |
42 |
43 |
44 |
45 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import React, { useState } from "react";
4 | import "typeface-roboto";
5 |
6 | import {
7 | createMuiTheme,
8 | makeStyles,
9 | ThemeProvider
10 | } from "@material-ui/core/styles";
11 |
12 | import { grey } from "@material-ui/core/colors";
13 | import {
14 | AppBar,
15 | Button,
16 | Box,
17 | Hidden,
18 | IconButton,
19 | Toolbar,
20 | Typography,
21 | Grid
22 | } from "@material-ui/core";
23 |
24 | import FileCopyIcon from "@material-ui/icons/FileCopy";
25 |
26 | import FavoriteIcon from "@material-ui/icons/Favorite";
27 | import GitHubIcon from "@material-ui/icons/GitHub";
28 |
29 | import QRCode from "qrcode.react";
30 |
31 | import CustomDrawer from "./components/Drawer";
32 | import CustomModal from "./components/Modal";
33 | import Home from "./pages/home";
34 |
35 | const theme = createMuiTheme({
36 | palette: {
37 | primary: {
38 | main: grey[900]
39 | },
40 | secondary: {
41 | main: grey[50]
42 | }
43 | }
44 | });
45 |
46 | const useStyles = makeStyles(theme => ({
47 | addressText: {
48 | textAlign: "center",
49 | marginTop: 15,
50 | [theme.breakpoints.down("md")]: {
51 | flexWrap: "wrap",
52 | fontSize: 10,
53 | marginTop: 10
54 | }
55 | },
56 | menuButton: {
57 | marginRight: theme.spacing(2)
58 | },
59 | title: {
60 | flexGrow: 1
61 | }
62 | }));
63 |
64 | const BTCAddress = "bc1qppv3awnvxw7kas3zne6jcwj2w8a5uehaz3mn6q";
65 | const ETHAddress = "0x6ed31d002338349E486daD57939E1e4A4A7a0007";
66 |
67 | const App = () => {
68 | const classes = useStyles();
69 |
70 | const [drawerState, setDrawerState] = useState(false);
71 |
72 | const [modal, setModal] = useState(false);
73 | const [modalContent, setModalContent] = useState(null);
74 |
75 | const [copied, setCopied] = useState(false);
76 |
77 | const copyToClipboard = value => {
78 | navigator.clipboard.writeText(value);
79 | setCopied(true);
80 | };
81 |
82 | const sendViaQR = type => {
83 | let address = "";
84 | if (type === "Bitcoin") {
85 | address = BTCAddress;
86 | } else {
87 | address = ETHAddress;
88 | }
89 |
90 | setModalContent(
91 |
92 |
93 |
94 | Scan this QR Code with your {type} wallet
95 |
96 |
97 |
98 |
99 |
100 | }
102 | onClick={() => copyToClipboard(address)}
103 | />
104 |
105 | {copied ? "Copied!" : `Or Copy this Address: ${address}`}
106 |
107 |
108 | );
109 | handleModal(true);
110 | };
111 |
112 | const handleModal = value => {
113 | setModal(value);
114 | };
115 |
116 | const toggleDrawer = value => event => {
117 | if (
118 | event.type === "keydown" &&
119 | (event.key === "Tab" || event.key === "Shift")
120 | ) {
121 | return;
122 | }
123 | setDrawerState(value);
124 | };
125 |
126 | return (
127 |
128 |
129 |
130 |
131 | COVID19 Flight Info
132 |
133 |
134 |
137 |
138 |
139 | }
142 | onClick={toggleDrawer(true)}>
143 | Show some love :)
144 |
145 |
146 |
147 |
148 |
153 |
154 |
159 |
160 | );
161 | };
162 |
163 | export default App;
164 |
--------------------------------------------------------------------------------
/src/pages/home.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import React, { useState, useEffect } from "react";
4 | import axios from "axios";
5 |
6 | import {
7 | CircularProgress,
8 | makeStyles,
9 | Typography,
10 | Grid,
11 | DialogContent,
12 | DialogContentText,
13 | } from "@material-ui/core";
14 |
15 | import Chart from "../components/Chart";
16 | import GlobeChart from "../components/MapChart";
17 |
18 | import { getData } from "../lib/httpClient";
19 | import CustomTab from "../components/Tab";
20 | import CustomDialog from "../components/Dialog";
21 | import {
22 | sanitizeCountryNamesForFlightInfo,
23 | sanitizeCountryNamesForCOVIDStats,
24 | } from "../lib/utils";
25 |
26 | const url = `${process.env.REACT_APP_SCRAPER_URL}`;
27 | const pomberUrl = "https://pomber.github.io/covid19/timeseries.json";
28 |
29 | const useStyles = makeStyles((theme) => ({
30 | chart: {
31 | flexGrow: 1,
32 | height: 400,
33 | },
34 | flightInfo: {
35 | flexGrow: 1,
36 | height: 100,
37 | },
38 | root: {
39 | flexGrow: 1,
40 | },
41 | progress: {
42 | alignSelf: "center",
43 | justifySelf: "center",
44 | margin: theme.spacing(2),
45 | },
46 | modal: {
47 | top: "50%",
48 | left: "50%",
49 | transform: "translate(-50%, -50%)",
50 | position: "absolute",
51 | height: 500,
52 | width: 800,
53 | backgroundColor: theme.palette.background.paper,
54 | border: "2px solid #FFF",
55 | borderRadius: 10,
56 | boxShadow: theme.shadows[5],
57 | padding: theme.spacing(2, 4, 3),
58 | outline: "none",
59 | },
60 | mobileModal: {
61 | top: "25%",
62 | left: "25%",
63 | transform: "translate(-25%, -25%)",
64 | height: 295,
65 | width: 292,
66 | },
67 | }));
68 |
69 | const Home = () => {
70 | const classes = useStyles();
71 |
72 | const [loading, setLoading] = useState(false);
73 |
74 | const [bannedCountries, setBannedCountries] = useState("");
75 | const [countryData, setCountryData] = useState({});
76 |
77 | const [chartData, setChartData] = useState([]);
78 | const [dialog, setDialog] = useState(false);
79 | const [dialogContent, setDialogContent] = useState({});
80 |
81 | useEffect(() => {
82 | const cancel = axios.CancelToken.source();
83 |
84 | const fetchData = async () => {
85 | setLoading(true);
86 |
87 | let results = await getData(url, cancel);
88 | setBannedCountries(results);
89 |
90 | results = await getData(pomberUrl, cancel);
91 | setCountryData(results);
92 |
93 | setLoading(false);
94 | };
95 |
96 | fetchData();
97 |
98 | return () => {
99 | cancel.cancel();
100 | };
101 | }, []);
102 |
103 | const handleDialog = (value) => {
104 | setDialog(value);
105 | };
106 |
107 | const renderChart = (name) => {
108 | if (countryData[sanitizeCountryNamesForCOVIDStats(name)] !== undefined) {
109 | const getData = (type) => {
110 | return countryData[sanitizeCountryNamesForCOVIDStats(name)].map(
111 | (stat, index) => {
112 | return {
113 | x: `Day ${index}`,
114 | y: stat[type],
115 | };
116 | }
117 | );
118 | };
119 |
120 | setChartData([
121 | {
122 | id: "Confirmed",
123 | color: "hsl(40, 50%, 45%)",
124 | data: getData("confirmed"),
125 | },
126 | {
127 | id: "Deaths",
128 | color: "hsl(0, 50%, 45%)",
129 | data: getData("deaths"),
130 | },
131 | {
132 | id: "Recovered",
133 | color: "hsl(83, 70%, 50%)",
134 | data: getData("recovered"),
135 | },
136 | ]);
137 | setDialogContent(getDialogContent(name, chartData));
138 | handleDialog(true);
139 | }
140 | };
141 |
142 | const getDialogContent = (name, chartData) => {
143 | const tabTitles = ["Flight Info", "Basic Stats"];
144 | const tabContents = [];
145 |
146 | tabContents.push(
147 |
148 | {name}
149 |
150 |
151 |
152 | {bannedCountries[sanitizeCountryNamesForFlightInfo(name)] ===
153 | undefined
154 | ? "No Flight Info"
155 | : bannedCountries[sanitizeCountryNamesForFlightInfo(name)]}
156 |
157 |
158 |
159 |
160 | );
161 |
162 | tabContents.push(
163 |
164 | {name}
165 |
166 |
167 | );
168 |
169 | return (
170 |
171 |
172 |
173 | );
174 | };
175 |
176 | if (loading)
177 | return (
178 |
179 |
180 |
181 | );
182 |
183 | return (
184 |
185 |
186 |
187 |
192 |
193 |
194 | );
195 | };
196 |
197 | export default Home;
198 |
--------------------------------------------------------------------------------
/src/lib/countryList.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | // List of all countries in a simple list / array.
4 | const countryList = [
5 | "Afghanistan",
6 | "Albania",
7 | "Algeria",
8 | "American Samoa",
9 | "Andorra",
10 | "Angola",
11 | "Anguilla",
12 | "Antarctica",
13 | "Antigua and Barbuda",
14 | "Argentina",
15 | "Armenia",
16 | "Aruba",
17 | "Australia",
18 | "Austria",
19 | "Azerbaijan",
20 | "Bahamas (the)",
21 | "Bahrain",
22 | "Bangladesh",
23 | "Barbados",
24 | "Belarus",
25 | "Belgium",
26 | "Belize",
27 | "Benin",
28 | "Bermuda",
29 | "Bhutan",
30 | "Bolivia (Plurinational State of)",
31 | "Bonaire, Sint Eustatius and Saba",
32 | "Bosnia and Herzegovina",
33 | "Botswana",
34 | "Bouvet Island",
35 | "Brazil",
36 | "British Indian Ocean Territory (the)",
37 | "Brunei Darussalam",
38 | "Bulgaria",
39 | "Burkina Faso",
40 | "Burundi",
41 | "Cabo Verde",
42 | "Cambodia",
43 | "Cameroon",
44 | "Canada",
45 | "Cayman Islands (the)",
46 | "Central African Republic (the)",
47 | "Chad",
48 | "Chile",
49 | "China",
50 | "Christmas Island",
51 | "Cocos (Keeling) Islands (the)",
52 | "Colombia",
53 | "Comoros (the)",
54 | "Congo (the Democratic Republic of the)",
55 | "Congo (the)",
56 | "Cook Islands (the)",
57 | "Costa Rica",
58 | "Croatia",
59 | "Cuba",
60 | "Curaçao",
61 | "Cyprus",
62 | "Czechia",
63 | "Côte d'Ivoire",
64 | "Denmark",
65 | "Djibouti",
66 | "Dominica",
67 | "Dominican Republic (the)",
68 | "Ecuador",
69 | "Egypt",
70 | "El Salvador",
71 | "Equatorial Guinea",
72 | "Eritrea",
73 | "Estonia",
74 | "Eswatini",
75 | "Ethiopia",
76 | "Falkland Islands (the) [Malvinas]",
77 | "Faroe Islands (the)",
78 | "Fiji",
79 | "Finland",
80 | "France",
81 | "French Guiana",
82 | "French Polynesia",
83 | "French Southern Territories (the)",
84 | "Gabon",
85 | "Gambia (the)",
86 | "Georgia",
87 | "Germany",
88 | "Ghana",
89 | "Gibraltar",
90 | "Greece",
91 | "Greenland",
92 | "Grenada",
93 | "Guadeloupe",
94 | "Guam",
95 | "Guatemala",
96 | "Guernsey",
97 | "Guinea",
98 | "Guinea-Bissau",
99 | "Guyana",
100 | "Haiti",
101 | "Heard Island and McDonald Islands",
102 | "Holy See (the)",
103 | "Honduras",
104 | "Hong Kong",
105 | "Hungary",
106 | "Iceland",
107 | "India",
108 | "Indonesia",
109 | "Iran (Islamic Republic of)",
110 | "Iraq",
111 | "Ireland",
112 | "Isle of Man",
113 | "Israel",
114 | "Italy",
115 | "Jamaica",
116 | "Japan",
117 | "Jersey",
118 | "Jordan",
119 | "Kazakhstan",
120 | "Kenya",
121 | "Kiribati",
122 | "Korea (the Democratic People's Republic of)",
123 | "Korea (the Republic of)",
124 | "Kuwait",
125 | "Kyrgyzstan",
126 | "Lao People's Democratic Republic (the)",
127 | "Latvia",
128 | "Lebanon",
129 | "Lesotho",
130 | "Liberia",
131 | "Libya",
132 | "Liechtenstein",
133 | "Lithuania",
134 | "Luxembourg",
135 | "Macao",
136 | "Madagascar",
137 | "Malawi",
138 | "Malaysia",
139 | "Maldives",
140 | "Mali",
141 | "Malta",
142 | "Marshall Islands (the)",
143 | "Martinique",
144 | "Mauritania",
145 | "Mauritius",
146 | "Mayotte",
147 | "Mexico",
148 | "Micronesia (Federated States of)",
149 | "Moldova (the Republic of)",
150 | "Monaco",
151 | "Mongolia",
152 | "Montenegro",
153 | "Montserrat",
154 | "Morocco",
155 | "Mozambique",
156 | "Myanmar",
157 | "Namibia",
158 | "Nauru",
159 | "Nepal",
160 | "Netherlands (the)",
161 | "New Caledonia",
162 | "New Zealand",
163 | "Nicaragua",
164 | "Niger (the)",
165 | "Nigeria",
166 | "Niue",
167 | "Norfolk Island",
168 | "Northern Mariana Islands (the)",
169 | "Norway",
170 | "Oman",
171 | "Pakistan",
172 | "Palau",
173 | "Palestine, State of",
174 | "Panama",
175 | "Papua New Guinea",
176 | "Paraguay",
177 | "Peru",
178 | "Philippines (the)",
179 | "Pitcairn",
180 | "Poland",
181 | "Portugal",
182 | "Puerto Rico",
183 | "Qatar",
184 | "Republic of North Macedonia",
185 | "Romania",
186 | "Russian Federation (the)",
187 | "Rwanda",
188 | "Réunion",
189 | "Saint Barthélemy",
190 | "Saint Helena, Ascension and Tristan da Cunha",
191 | "Saint Kitts and Nevis",
192 | "Saint Lucia",
193 | "Saint Martin (French part)",
194 | "Saint Pierre and Miquelon",
195 | "Saint Vincent and the Grenadines",
196 | "Samoa",
197 | "San Marino",
198 | "Sao Tome and Principe",
199 | "Saudi Arabia",
200 | "Senegal",
201 | "Serbia",
202 | "Seychelles",
203 | "Sierra Leone",
204 | "Singapore",
205 | "Sint Maarten (Dutch part)",
206 | "Slovakia",
207 | "Slovenia",
208 | "Solomon Islands",
209 | "Somalia",
210 | "South Africa",
211 | "South Georgia and the South Sandwich Islands",
212 | "South Sudan",
213 | "Spain",
214 | "Sri Lanka",
215 | "Sudan (the)",
216 | "Suriname",
217 | "Svalbard and Jan Mayen",
218 | "Sweden",
219 | "Switzerland",
220 | "Syrian Arab Republic",
221 | "Taiwan (Province of China)",
222 | "Tajikistan",
223 | "Tanzania, United Republic of",
224 | "Thailand",
225 | "Timor-Leste",
226 | "Togo",
227 | "Tokelau",
228 | "Tonga",
229 | "Trinidad and Tobago",
230 | "Tunisia",
231 | "Turkey",
232 | "Turkmenistan",
233 | "Turks and Caicos Islands (the)",
234 | "Tuvalu",
235 | "Uganda",
236 | "Ukraine",
237 | "United Arab Emirates (the)",
238 | "United Kingdom of Great Britain and Northern Ireland (the)",
239 | "United States Minor Outlying Islands (the)",
240 | "United States of America (the)",
241 | "Uruguay",
242 | "Uzbekistan",
243 | "Vanuatu",
244 | "Venezuela (Bolivarian Republic of)",
245 | "Viet Nam",
246 | "Virgin Islands (British)",
247 | "Virgin Islands (U.S.)",
248 | "Wallis and Futuna",
249 | "Western Sahara",
250 | "Yemen",
251 | "Zambia",
252 | "Zimbabwe",
253 | "Åland Islands"
254 | ];
255 |
256 | export default countryList;
257 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------