Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
.env.development.local
.env.test.local
.env.production.local
.env

npm-debug.log*
yarn-debug.log*
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Weather Project

Search for a city to view three different graphs representing the average temperature, pressure and humidity for that location. The graphs will show data based on todays forecast as well as the next four days giving you a five day average.

This project has been created by a student at Parsity, an online software engineering course. The work in this repository is wholly of the student based on a sample starter project that can be accessed by looking at the repository that this project forks.

If you have any questions about this project or the program in general, visit [parsity.io](https://parsity.io/) or email hello@parsity.io.
48,515 changes: 23,587 additions & 24,928 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@reduxjs/toolkit": "^1.9.7",
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^12.8.3",
"axios": "^1.6.0",
"bootstrap": "^5.3.2",
"dotenv": "^16.3.1",
"latest": "^0.2.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"react-redux": "^8.1.3",
"react-router-dom": "^6.18.0",
"react-scripts": "^5.0.1",
"react-sparklines": "^1.7.0",
"redux-promise": "^0.6.0",
"redux-thunk": "^2.4.2",
"web-vitals": "^1.1.1"
},
"scripts": {
Expand Down
25 changes: 0 additions & 25 deletions src/App.js

This file was deleted.

67 changes: 67 additions & 0 deletions src/Components/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { fetchWeatherData } from "../Redux/Slices/weatherSlices";
import CityData from "./CityData";

const App = () => {
// Setting state to manage user inputs and form validation
const [city, setCity] = useState('');
const [isValid, setIsvalid] = useState(true);

const dispatch = useDispatch();

const handleInputChange = (e) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend using full names for variables instead of just e, use event

setCity(e.target.value);
console.log(e.target.value)
};
// Event handler for submission, dispatching action to fetch data and form validation
const handleSubmit = (e) => {
e.preventDefault();
if (city) {
dispatch(fetchWeatherData(city));
setCity('')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semi-colons

setIsvalid(true)
} else {
setIsvalid(false)
}
};

return (
<div className="container-fluid">
<div className="row">
<div className="col-md-8 offset-md-2 text-center">
<div className="page-header">
<h1>Redux Weather App</h1>
</div>
<form className="search-form" onSubmit={(handleSubmit)}>
<div className="form-group row">
<div className="col-md-9">
<input
type="text"
id="search-query"
className={`form-control ${isValid ? '' : 'is-invalid'}`}
placeholder="Search for a five-day forecast"
value={city}
onChange={handleInputChange}
/>
{!isValid && <div className="invalid-feedback">This field is required.</div>}
</div>
<div className="col-md-3">
<button type="submit" className="btn btn-primary search">
Search
</button>
</div>
</div>
</form>
<hr />
<CityData />
</div>
</div>
</div>



)
};

export default App;
100 changes: 100 additions & 0 deletions src/Components/CityData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from "react";
import { useSelector } from "react-redux";
import { Sparklines, SparklinesLine, SparklinesReferenceLine } from "react-sparklines";


const CityData = () => {
const searches = useSelector((state) => state.weather.searches)

return (
<div className="container-fluid">
{searches.map((search, index) => (
<div key={index} className="weather-list">
{renderCityData(search)}
</div>
))}
</div>
);
};

const renderCityData = (search) => {
if (!search) {
return (
<div className="row">
<div className="col-md text-center">
<h3>No Data Found</h3>
</div>
</div>
);
}

const city = search.city.name || '';
const temperature = search.temperature || [];
const pressure = search?.pressure || [];
const humidity = search?.humidity || [];
const avgTemp = search?.avgTemp || [];
const avgPressure = search?.avgPressure || [];
const avgHumidity = search?.avgHumidity || [];
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-10 offset-md-1 text-center">
<div className="page-header">
</div>

<div className="container">
<div className="row">
<div className="col-md-3">
<h5>City</h5>
</div>
<div className="col-md-3">
<h5>Temperature (F)</h5>
</div>
<div className="col-md-3">
<h5>Pressure (hPa)</h5>
</div>
<div className="col-md-3">
<h5>Humidity (%)</h5>
</div>
</div>
</div>
<hr />
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
</div>
</div>
</div>
<div className="row">
<div className="col-md-3">
<h3>{city}</h3>
</div>
<div className="col-md-3">
<Sparklines data={temperature}>
<SparklinesLine color="blue" />
<SparklinesReferenceLine type="avg" />
</Sparklines>
<h6>{avgTemp}&deg;F</h6>
</div>
<div className="col-md-3">
<Sparklines data={pressure}>
<SparklinesLine color="purple" />
<SparklinesReferenceLine type="avg" />
</Sparklines>
<h6>{avgPressure}hPa</h6>
</div>
<div className="col-md-3">
<Sparklines data={humidity}>
<SparklinesLine color="red" />
<SparklinesReferenceLine type="avg" />
</Sparklines>
<h6>{avgHumidity}%</h6>
</div>
</div>
</div>
</div >
</div >
);
};

export default CityData;
49 changes: 49 additions & 0 deletions src/JSON-Ref.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"coord": {
"lon": 10.99,
"lat": 44.34
},
"weather": [
{
"id": 501,
"main": "Rain",
"description": "moderate rain",
"icon": "10d"
}
],
"base": "stations",
"main": {
"temp": 298.48,
"feels_like": 298.74,
"temp_min": 297.56,
"temp_max": 300.05,
"pressure": 1015,
"humidity": 64,
"sea_level": 1015,
"grnd_level": 933
},
"visibility": 10000,
"wind": {
"speed": 0.62,
"deg": 349,
"gust": 1.18
},
"rain": {
"1h": 3.16
},
"clouds": {
"all": 100
},
"dt": 1661870592,
"sys": {
"type": 2,
"id": 2075663,
"country": "IT",
"sunrise": 1661834187,
"sunset": 1661882248
},
"timezone": 7200,
"id": 3163858,
"name": "Zocca",
"cod": 200
}
77 changes: 77 additions & 0 deletions src/Redux/Slices/weatherSlices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from 'axios';

export const apiKey = process.env.REACT_APP_REDUX_WEATHER_KEY;

// Calculate the average of the array
const calcAverage = (array) => {
if (array.length === 0) return 0;
const sum = array.reduce((acc, value) => acc + value, 0);
const average = sum / array.length
return Math.round(average)
}

// Fetch weather data from API
export const fetchWeatherData = createAsyncThunk(
'weather/fetchData',
async (city, thunkAPI) => {
try {
const { data } = await axios.get(`http://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${apiKey}&units=imperial`);
return data
} catch (error) {
if (!error?.response) {
throw error
}
return thunkAPI.rejectWithValue({ error: 'An error occured' })
}
});

// Declaring initial state for weather slice
// Using a searches array to store the history of user searches
const initialState = {
loading: false,
searches: [],
error: undefined,
};

// Created slice of the Redux store for weather data
// Tells the store what to do with the data when its returned
const weatherSlice = createSlice({
name: 'weather',
initialState,
extraReducers: (builder) => {
builder
.addCase(fetchWeatherData.pending, (state) => {
state.loading = true;
})
.addCase(fetchWeatherData.fulfilled, (state, action) => {

// Extracting the needed values from API and assigning variables
const tempData = action.payload.list.map(item => item.main.temp);
const pressureData = action.payload.list.map(item => item.main.pressure);
const humidityData = action.payload.list.map(item => item.main.humidity);
// Creating a new object based on search with needed values and creating the average
const newSearch = {
city: action.payload.city,
temperature: tempData,
pressure: pressureData,
humidity: humidityData,
avgTemp: calcAverage(tempData),
avgPressure: calcAverage(pressureData),
avgHumidity: calcAverage(humidityData),
};
state.searches = [...state.searches, newSearch]
state.loading = false;
state.error = undefined;
})
.addCase(fetchWeatherData.rejected, (state, action) => {
state.loading = false;
state.data = undefined;
state.error = action?.payload;
})
}
});

export default weatherSlice.reducer;


12 changes: 12 additions & 0 deletions src/Redux/Store/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { configureStore } from "@reduxjs/toolkit";
import weatherReducer from '../Slices/weatherSlices'

// Configuring the Redux store with weather reducer and assigning
// weather reducer to "weather" slice of store
const store = configureStore({
reducer: {
weather: weatherReducer,
}
});

export default store;
Loading