diff --git a/README.md b/README.md
index 21dbaa3..613e23f 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
- [React Instructions ](#react-stack-)
- [Tech Stack ](#tech-stack-)
- [Key Features ](#key-features-)
- - [🚀 Live Demo ](#-live-demo-)
+ - [🚀 Video & Live Demo ](#-live-demo-)
- [💻 Getting Started ](#-getting-started-)
- [Prerequisites](#prerequisites)
- [Setup](#setup)
@@ -135,10 +135,11 @@ You don't have to ever use `eject`. The curated feature set is suitable for smal
(back to top )
-## 🚀 Live Demo
+## 🚀 Video & Live Demo
-> Please follow the link for a live demo.
-- [The matrices webapp: Coming Soon...](https://github.com/porag-m06/Metrics-Webapp-React-Capst)
+> Please follow the link for a video & live demo.
+- [Video Presentation:](https://www.loom.com/share/c59cff2aaf524bc2989f29c857c7b3c5?sid=1172c318-b745-4211-a048-7575dce85e8e)
+- [The matrices webapp:](https://air-index.onrender.com/)
(back to top )
diff --git a/src/App.js b/src/App.js
index f820c03..903a1e9 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,12 +1,10 @@
import { Route, Routes } from 'react-router-dom';
-import Nav from './components/Nav';
import GeoLocation from './components/GeoLocation';
import PullutionDetail from './components/PollutionDetail';
function App() {
return (
-
} />
diff --git a/src/__test__/GeoLocation.test.js b/src/__test__/GeoLocation.test.js
new file mode 100644
index 0000000..c7ee830
--- /dev/null
+++ b/src/__test__/GeoLocation.test.js
@@ -0,0 +1,65 @@
+import '@testing-library/jest-dom';
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import { BrowserRouter } from 'react-router-dom';
+import configureStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import GeoLocation from '../components/GeoLocation';
+
+const middlewares = [thunk];
+const mockStore = configureStore(middlewares);
+
+describe('GeoLocation component', () => {
+ let store;
+
+ beforeEach(() => {
+ store = mockStore({
+ geolocation: {
+ locations: [],
+ isLoading: false,
+ error: undefined,
+ },
+ });
+ });
+
+ test('GEOLOCATION TEST 1: renders loading message when data is being fetched', () => {
+ store = mockStore({
+ geolocation: {
+ locations: [],
+ isLoading: true,
+ error: undefined,
+ },
+ });
+
+ render(
+
+
+
+
+ ,
+ );
+
+ expect(screen.getByText('geoLocations data is loading...')).toBeInTheDocument();
+ });
+
+ test('GEOLOCATION TEST 2: renders error message when data fetch fails', async () => {
+ store = mockStore({
+ geolocation: {
+ locations: [],
+ isLoading: false,
+ error: 'Something went wrong',
+ },
+ });
+
+ render(
+
+
+
+
+ ,
+ );
+
+ expect(screen.getByText('Something went wrong...!')).toBeInTheDocument();
+ });
+});
diff --git a/src/__test__/Nav.test.js b/src/__test__/Nav.test.js
deleted file mode 100644
index 84d9947..0000000
--- a/src/__test__/Nav.test.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import '@testing-library/jest-dom';
-import React from 'react';
-import { render, screen } from '@testing-library/react';
-import { BrowserRouter as Router } from 'react-router-dom';
-import Nav from '../components/Nav';
-
-describe('Nav', () => {
- test('NAV TEST 1: renders Space Travelers\' Hub heading', () => {
- render( );
- const headingElement = screen.getByText(/Space Travelers' Hub/i);
- expect(headingElement).toBeInTheDocument();
- });
-
- test('NAV TEST 2: renders Rockets link', () => {
- render( );
- const linkElement = screen.getByText(/Rockets/i);
- expect(linkElement).toBeInTheDocument();
- });
-
- test('NAV TEST 3: renders Missions link', () => {
- render( );
- const linkElement = screen.getByText(/Missions/i);
- expect(linkElement).toBeInTheDocument();
- });
-
- test('NAV TEST 4: renders My Profile link', () => {
- render( );
- const linkElement = screen.getByText(/My Profile/i);
- expect(linkElement).toBeInTheDocument();
- });
-});
diff --git a/src/__test__/PollutionDetail.test.js b/src/__test__/PollutionDetail.test.js
new file mode 100644
index 0000000..10ac509
--- /dev/null
+++ b/src/__test__/PollutionDetail.test.js
@@ -0,0 +1,65 @@
+import '@testing-library/jest-dom';
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { Provider } from 'react-redux';
+import { BrowserRouter } from 'react-router-dom';
+import configureStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+import PollutionDetail from '../components/PollutionDetail';
+
+const middlewares = [thunk];
+const mockStore = configureStore(middlewares);
+
+describe('PollutionDetail component', () => {
+ let store;
+
+ beforeEach(() => {
+ store = mockStore({
+ airpollution: {
+ list: [],
+ isLoading: false,
+ error: undefined,
+ },
+ });
+ });
+
+ test('POLLUTIONDETAILS TEST 1: renders loading message when data is being fetched', () => {
+ store = mockStore({
+ airpollution: {
+ list: [],
+ isLoading: true,
+ error: undefined,
+ },
+ });
+
+ render(
+
+
+
+
+ ,
+ );
+
+ expect(screen.getByText('airpollution data is loading...')).toBeInTheDocument();
+ });
+
+ test('POLLUTIONDETAILS TEST 2: renders error message when data fetch fails', () => {
+ store = mockStore({
+ airpollution: {
+ list: [],
+ isLoading: false,
+ error: 'Something went wrong',
+ },
+ });
+
+ render(
+
+
+
+
+ ,
+ );
+
+ expect(screen.getByText('Something went wrong...!')).toBeInTheDocument();
+ });
+});
diff --git a/src/assets/worldmap.png b/src/assets/worldmap.png
new file mode 100644
index 0000000..da69ea0
Binary files /dev/null and b/src/assets/worldmap.png differ
diff --git a/src/components/GeoLocation.js b/src/components/GeoLocation.js
index 90d3466..4eeae7d 100644
--- a/src/components/GeoLocation.js
+++ b/src/components/GeoLocation.js
@@ -1,10 +1,16 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
+import '../style/geolocations.css';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
+import { IoIosArrowBack } from 'react-icons/io';
+import { BsFillMicFill } from 'react-icons/bs';
+import { RiSettings5Fill } from 'react-icons/ri';
+import worldmap from '../assets/worldmap.png';
import { fetchGeoLocations } from '../redux/features/geolocations/geolocationSlice';
function GeoLocation() {
const { locations, isLoading, error } = useSelector((storeState) => storeState.geolocation);
+ const [query, setQuery] = useState('');
const dispatch = useDispatch();
const navigate = useNavigate();
@@ -12,32 +18,69 @@ function GeoLocation() {
if (locations.length === 0) { dispatch(fetchGeoLocations()); }
}, [locations, dispatch]);
+ const filteredLocations = locations.filter((location) => location.name.toLowerCase()
+ .includes(query.toLowerCase()));
+
if (isLoading) { return (geoLocations data is loading...
); }
if (error) { return (Something went wrong...!
); }
return (
-
- GEOLOCATIONS :
-
- {locations.map((location) => (
-
- {
- navigate('/city-pollution-info', { state: { location } });
- }}
- >
-
-
{location.name}
-
{location.country}
-
{`Latitude: ${location.latitude}\nLongitude: ${location.longitude}`}
-
{`Population: ${location.population / 100000} Millions`}
-
-
-
- ))}
-
-
+ <>
+
+
+
+
+
+
+
+
POLLUTION INDEX
+
major cities
+
+
+
+
STATS BY CITIES
+
+ {filteredLocations.map((location) => (
+
+ {
+ navigate('/city-pollution-info', { state: { location } });
+ }}
+ >
+
+
{(location.name).toUpperCase()}
+ {(location.country).toUpperCase()}
+ {`Latitude: ${(location.latitude).toFixed(2)}`}
+ {`Longitude: ${(location.longitude).toFixed(2)}`}
+ {`Population: ${(location.population / 1000000).toFixed(2)} M`}
+
+
+
+ ))}
+
+
+ >
);
}
diff --git a/src/components/Nav.js b/src/components/Nav.js
deleted file mode 100644
index f9b9c1f..0000000
--- a/src/components/Nav.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react';
-import '../style/nav.css';
-import { IoIosArrowBack } from 'react-icons/io';
-import { BsFillMicFill } from 'react-icons/bs';
-import { RiSettings5Fill } from 'react-icons/ri';
-// import userImg from '../assets/planet.png'; BsMic
-
-function Nav() {
- return (
-
-
-
- {' '}
- AirIdX
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default Nav;
diff --git a/src/components/PollutionDetail.js b/src/components/PollutionDetail.js
index db95461..fec158a 100644
--- a/src/components/PollutionDetail.js
+++ b/src/components/PollutionDetail.js
@@ -1,41 +1,102 @@
import React, { useEffect } from 'react';
+import '../style/pollutionDetail.css';
import { useDispatch, useSelector } from 'react-redux';
-import { useLocation } from 'react-router-dom';
+import { useLocation, useNavigate } from 'react-router-dom';
+import { IoIosArrowBack } from 'react-icons/io';
+import { BsFillMicFill, BsArrowRightCircle } from 'react-icons/bs';
+import { RiSettings5Fill } from 'react-icons/ri';
import { fetchPollutionData } from '../redux/features/pollutionDetails/pollutionDetailsSlice';
function PollutionDetail() {
+ const { list, isLoading, error } = useSelector((storeState) => storeState.airpollution);
+
const location = useLocation();
- const { name, latitude, longitude } = location.state.location;
+ const { name, latitude, longitude } = location.state?.location || {};
+ const navigate = useNavigate();
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchPollutionData({ latitude, longitude }));
}, []);
- const { list, isLoading, error } = useSelector((storeState) => storeState.airpollution);
const airQualityIndex = ['Good', 'Fair', 'Moderate', 'Poor', 'Very Poor'];
- if (isLoading) { return (geoLocations data is loading...
); }
+ if (isLoading) { return (airpollution data is loading...
); }
if (error) { return (Something went wrong...!
); }
return (
-
-
- Pollution Detail of:
- {name}
-
-
- {list.map((item) => (
-
- Air Quality Index is :
- {item.main.aqi}
- {' '}
- or,
- {airQualityIndex[item.main.aqi - 1]}
-
- ))}
-
-
+ <>
+
+
+
+
+ City of
+ {' '}
+
+ {' '}
+ {name}
+
+
+
+ >
);
}
diff --git a/src/redux/features/geolocations/geolocationSlice.js b/src/redux/features/geolocations/geolocationSlice.js
index 875889e..c6c39d5 100644
--- a/src/redux/features/geolocations/geolocationSlice.js
+++ b/src/redux/features/geolocations/geolocationSlice.js
@@ -1,18 +1,19 @@
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';
+const apiNijaCityUrl = 'https://api.api-ninjas.com/v1/city?min_population=5000000&limit=30';
+const apiKey = '/YeVAlnqFdBB70E8aGovGA==fjN5mk2cXRLs5vHK';
+
export const fetchGeoLocations = createAsyncThunk(
'locations/fetchGeoLocations',
async () => {
try {
- const promises = [];
- for (let i = 0; i < 90; i += 10) {
- const promise = axios.get(`http://geodb-free-service.wirefreethought.com/v1/geo/places?limit=10&offset=${i}&types=CITY&minPopulation=7000000&sort=name`);
- promises.push(promise);
- }
- const responses = await Promise.all(promises);
- const responseData = responses.flatMap((response) => response.data.data);
- return responseData;
+ const response = await axios.get(apiNijaCityUrl, {
+ headers: {
+ 'X-Api-Key': apiKey,
+ },
+ });
+ return response.data;
} catch (error) {
return error;
}
@@ -46,5 +47,4 @@ export const geolocationSlice = createSlice({
});
-// export const { reserveMission, leaveMission } = missionsSlice.actions;
export default geolocationSlice.reducer;
diff --git a/src/redux/features/pollutionDetails/pollutionDetailsSlice.js b/src/redux/features/pollutionDetails/pollutionDetailsSlice.js
index 5fe64fb..0b51a7d 100644
--- a/src/redux/features/pollutionDetails/pollutionDetailsSlice.js
+++ b/src/redux/features/pollutionDetails/pollutionDetailsSlice.js
@@ -6,7 +6,7 @@ export const fetchPollutionData = createAsyncThunk(
async (latlong) => {
try {
const { latitude, longitude } = latlong;
- const response = await axios.get(`http://api.openweathermap.org/data/2.5/air_pollution?lat=${latitude}&lon=${longitude}&appid=c8dceae3958b15e8f8e38d827f810487`);
+ const response = await axios.get(`//api.openweathermap.org/data/2.5/air_pollution?lat=${latitude}&lon=${longitude}&appid=c8dceae3958b15e8f8e38d827f810487`);
return response.data.list;
} catch (error) {
return error;
diff --git a/src/style/geolocations.css b/src/style/geolocations.css
new file mode 100644
index 0000000..2c8695b
--- /dev/null
+++ b/src/style/geolocations.css
@@ -0,0 +1,90 @@
+.hero {
+ height: 28vh;
+ background-color: var(--hero-banner);
+ display: flex;
+ z-index: 1;
+ position: relative;
+}
+
+.hero img {
+ width: 100vw;
+ z-index: 2;
+}
+
+.hero-right {
+ width: 50vw;
+ height: 100%;
+ right: 0;
+ top: 0;
+ background-color: var(--hero-banner);
+ position: absolute;
+ z-index: 3;
+ display: flex;
+ align-items: center;
+}
+
+.hero-right h2,
+.hero-right p {
+ margin: 0;
+ padding: 0;
+ margin-left: 0.5rem;
+}
+
+.hero-right h2 {
+ font-size: 1.3rem;
+}
+
+.middle {
+ margin: 5px;
+ font-size: 0.9rem;
+}
+
+.ulist {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ color: white;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+}
+
+.ulist li {
+ border: none;
+ margin: 0;
+ padding: 0;
+}
+
+.ulist li:nth-child(4n+2),
+.ulist li:nth-child(4n+3) {
+ background-color: var(--right-card);
+}
+
+.ulist button {
+ background: none;
+ border: none;
+ width: 100%;
+ height: 100%;
+ color: var(--font-color);
+}
+
+.card {
+ height: 25vh;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: flex-end;
+ padding: 0 0.5rem 1rem 0;
+}
+
+.card h3,
+.card h4,
+.card h5,
+.card h6 {
+ margin: 0;
+ padding: 0;
+ text-align: right;
+}
+
+.card h5 {
+ margin-bottom: 0.5rem;
+}
diff --git a/src/style/index.css b/src/style/index.css
index ca9c479..b3d2f01 100644
--- a/src/style/index.css
+++ b/src/style/index.css
@@ -6,13 +6,44 @@
--detail-border-top: #35548b;
--bg-n-left-card: #4066ae;
--right-card: #3f62a4;
- --font-color: #fefefd;
+ --font-color: white;
}
body {
margin: 0;
padding: 0;
box-sizing: border-box;
+ color: var(--font-color);
background-color: var(--bg-n-left-card);
font-family: 'Josefin Sans', 'Lato', sans-serif;
}
+
+.nav-container {
+ height: 50px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.react-icon {
+ background: none;
+ padding: 0 0.5rem 0 0.8rem;
+}
+
+.react-icon path {
+ fill: white;
+}
+
+.search input {
+ margin: 0;
+ width: 55vw;
+ height: 1.5rem;
+ border: none;
+ border-radius: 1rem;
+ padding-left: 0.8rem;
+ background-color: var(--hero-banner);
+}
+
+.search input::placeholder {
+ color: var(--font-color);
+}
diff --git a/src/style/nav.css b/src/style/nav.css
deleted file mode 100644
index 10bcd12..0000000
--- a/src/style/nav.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.nav-container {
- height: 50px;
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
-.react-icon {
- background: none;
-}
-
-.react-icon path {
- fill: white;
-}
diff --git a/src/style/pollutionDetail.css b/src/style/pollutionDetail.css
new file mode 100644
index 0000000..d4e1a64
--- /dev/null
+++ b/src/style/pollutionDetail.css
@@ -0,0 +1,78 @@
+.detail-container {
+ background-color: var(--hero-banner);
+ min-height: 90vh;
+ margin: 0;
+}
+
+.detail-container h2 {
+ margin: 0;
+ padding: 6rem 0.8rem 0 0;
+ text-align: end;
+}
+
+.dc-ul {
+ list-style: none;
+ width: 100%;
+ min-height: 77vh;
+ margin: 0;
+ padding: 0;
+}
+
+.back-btn {
+ border: none;
+ background: none;
+ margin: 0;
+ padding: 0;
+}
+
+.search span {
+ width: 55vw;
+ font-size: 1rem;
+}
+
+.index-list {
+ position: relative;
+ margin: 0;
+ padding: 0 !important;
+ min-height: 50vh;
+}
+
+.index-list p {
+ text-align: end;
+ margin: 0;
+ padding: 0 0.8rem 0 0;
+}
+
+.pollutants {
+ position: absolute;
+ background-color: var(--right-card);
+ width: 100vw;
+ height: 100%;
+ margin: 3rem 0 0 0;
+ padding: 0;
+}
+
+.pollutants p {
+ margin: 0;
+ text-align: left;
+ padding: 0.2rem 0.6rem;
+}
+
+.pollutants-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background-color: var(--hero-banner);
+}
+
+.pollutants-list li {
+ padding: 0 0 0 1rem;
+ height: 3.5rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.pollutants-list li:nth-child(even) {
+ background-color: var(--right-card);
+}