From a41ef97489a788d34db187fc1fac3c517a0dff02 Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Sat, 22 Mar 2025 16:07:26 -0400 Subject: [PATCH 01/15] add provider --- app/layout.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/app/layout.js b/app/layout.js index c93f806..6677272 100644 --- a/app/layout.js +++ b/app/layout.js @@ -1,17 +1,21 @@ -import './globals.css' -import { Inter } from 'next/font/google' - -const inter = Inter({ subsets: ['latin'] }) +'use client'; +//import './globals.css'; +import { Inter } from 'next/font/google'; +import { Provider } from 'react-redux'; +import store from './store/configureStore'; +const inter = Inter({ subsets: ['latin'] }); export const metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -} + title: "Redux Weather App", + description: "Generated by create next app", +}; export default function RootLayout({ children }) { - return ( - - {children} - - ) -} + return ( + + + {children} + + + ); +} \ No newline at end of file From a7f01a414232b5c6c34705de6934b50cdd50d0a7 Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Sat, 22 Mar 2025 16:26:38 -0400 Subject: [PATCH 02/15] add citiesReducer to store --- app/store/configureStore.js | 8 ++++++++ app/store/rootReducer.js | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 app/store/configureStore.js create mode 100644 app/store/rootReducer.js diff --git a/app/store/configureStore.js b/app/store/configureStore.js new file mode 100644 index 0000000..9afb8ce --- /dev/null +++ b/app/store/configureStore.js @@ -0,0 +1,8 @@ +import { configureStore } from "@reduxjs/toolkit"; +import rootReducer from "./rootReducer"; + +const store = configureStore({ + reducer: rootReducer, +}); + +export default store; diff --git a/app/store/rootReducer.js b/app/store/rootReducer.js new file mode 100644 index 0000000..7eeccf1 --- /dev/null +++ b/app/store/rootReducer.js @@ -0,0 +1,8 @@ +import { combineReducers } from "redux"; +import citiesReducer from "./slices/cities"; + +const rootReducer = combineReducers({ + cities: citiesReducer, +}); + +export default rootReducer; From 41eb971b7c5703c20773cc0429cd5bd090e1824e Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Mon, 24 Mar 2025 14:00:42 -0400 Subject: [PATCH 03/15] add dependencies --- package-lock.json | 111 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 5 ++- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 90f6bb1..0d3d11e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,14 @@ "name": "parsity_rtk_weather", "version": "0.1.0", "dependencies": { + "@reduxjs/toolkit": "^2.6.1", "eslint": "8.50.0", "eslint-config-next": "13.5.3", "next": "13.5.3", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "react-redux": "^9.2.0", + "react-sparklines": "^1.7.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -296,6 +299,30 @@ "node": ">= 8" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.1.tgz", + "integrity": "sha512-SSlIqZNYhqm/oMkXbtofwZSt9lrncblzo6YcZ9zoX+zLngRBrCOjK4lNLdkNucJF58RHOWrD9txT3bT3piH7Zw==", + "license": "MIT", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.0.tgz", @@ -314,6 +341,12 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@typescript-eslint/parser": { "version": "6.7.3", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.3.tgz", @@ -1759,6 +1792,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -2683,6 +2726,57 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-sparklines": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/react-sparklines/-/react-sparklines-1.7.0.tgz", + "integrity": "sha512-bJFt9K4c5Z0k44G8KtxIhbG+iyxrKjBZhdW6afP+R7EnIq+iKjbWbEFISrf3WKNFsda+C46XAfnX0StS5fbDcg==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.10" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -2723,6 +2817,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.6", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", @@ -3225,6 +3325,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 6ca0f8f..6eb0e21 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,13 @@ "lint": "next lint" }, "dependencies": { + "@reduxjs/toolkit": "^2.6.1", "eslint": "8.50.0", "eslint-config-next": "13.5.3", "next": "13.5.3", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "react-redux": "^9.2.0", + "react-sparklines": "^1.7.0" } } From 04cfad759ac495dddcbf96e557afdaf336a0a229 Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Mon, 24 Mar 2025 14:39:35 -0400 Subject: [PATCH 04/15] add CityWeather component --- app/components/CityWeather.js | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 app/components/CityWeather.js diff --git a/app/components/CityWeather.js b/app/components/CityWeather.js new file mode 100644 index 0000000..eecc1e2 --- /dev/null +++ b/app/components/CityWeather.js @@ -0,0 +1,40 @@ +import { Sparklines, SparklinesLine, SparklinesSpots } from "react-sparklines"; +import "bootstrap/dist/css/bootstrap.min.css"; + +const CityWeather = ({ city }) => { + return ( +
+
+

{city.name}

+
+
+
Temperature
+ + + + +

{city.temp} °C

+
+
+
Pressure
+ + + + +

{city.pressure} hPa

+
+
+
Humidity
+ + + + +

{city.humidity} %

+
+
+
+
+ ); +}; + +export default CityWeather; From 2c9b2ab54ec2ee0e593b94ad07401200c894447f Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Mon, 24 Mar 2025 14:40:48 -0400 Subject: [PATCH 05/15] add cities slice with api call --- app/store/slices/cities.js | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 app/store/slices/cities.js diff --git a/app/store/slices/cities.js b/app/store/slices/cities.js new file mode 100644 index 0000000..d3309b2 --- /dev/null +++ b/app/store/slices/cities.js @@ -0,0 +1,46 @@ +import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; + +const apiKey = "1d224a4af2354cde18df2f7243cc84f4"; + +export const addCity = createAsyncThunk( + "city/fetchCityWeather", + async (cityName) => { + const response = await fetch( + `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&appid=${apiKey}&units=metric` + ); + const data = await response.json(); + return { + name: cityName, + temp: data.main.temp, + pressure: data.main.pressure, + humidity: data.main.humidity, + }; + } +); + +const citySlice = createSlice({ + name: "city", + initialState: { + cities: [], + status: "idle", + error: null, + }, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(addCity.pending, (state) => { + state.status = "loading"; + }) + .addCase(addCity.fulfilled, (state, action) => { + state.status = "fulfilled"; + console.log(action.payload); + state.cities.push(action.payload); + }) + .addCase(addCity.rejected, (state, action) => { + state.status = "rejected"; + state.error = action.error.message; + }); + }, +}); + +export default citySlice.reducer; From 8758c9f54cb26926238ee1a9ee908c0dc236a8fe Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Mon, 24 Mar 2025 18:23:35 -0400 Subject: [PATCH 06/15] use five day forecast for sparklines data --- app/components/CityWeather.js | 8 ++++---- app/store/slices/cities.js | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/components/CityWeather.js b/app/components/CityWeather.js index eecc1e2..4268c02 100644 --- a/app/components/CityWeather.js +++ b/app/components/CityWeather.js @@ -9,15 +9,15 @@ const CityWeather = ({ city }) => {
Temperature
- + -

{city.temp} °C

+

{city.temp} °F

Pressure
- + @@ -25,7 +25,7 @@ const CityWeather = ({ city }) => {
Humidity
- + diff --git a/app/store/slices/cities.js b/app/store/slices/cities.js index d3309b2..2a00e7a 100644 --- a/app/store/slices/cities.js +++ b/app/store/slices/cities.js @@ -6,14 +6,15 @@ export const addCity = createAsyncThunk( "city/fetchCityWeather", async (cityName) => { const response = await fetch( - `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&appid=${apiKey}&units=metric` + `https://api.openweathermap.org/data/2.5/forecast?q=${cityName}&appid=${apiKey}&units=imperial` ); const data = await response.json(); + console.log(data); return { - name: cityName, - temp: data.main.temp, - pressure: data.main.pressure, - humidity: data.main.humidity, + name: data.city.name, + temp: data.list.map((threeHour) => threeHour.main.temp), + pressure: data.list.map((threeHour) => threeHour.main.pressure), + humidity: data.list.map((threeHour) => threeHour.main.humidity), }; } ); From 9bcbcc314d46584635010d259376e8bd0667c193 Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Mon, 24 Mar 2025 19:28:52 -0400 Subject: [PATCH 07/15] add average for temp, pressure, humidity --- app/components/CityWeather.js | 6 +++--- app/store/slices/cities.js | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/components/CityWeather.js b/app/components/CityWeather.js index 4268c02..eb96cf0 100644 --- a/app/components/CityWeather.js +++ b/app/components/CityWeather.js @@ -13,7 +13,7 @@ const CityWeather = ({ city }) => { -

{city.temp} °F

+

{city.avgTemp} °F

Pressure
@@ -21,7 +21,7 @@ const CityWeather = ({ city }) => { -

{city.pressure} hPa

+

{city.avgPressure} hPa

Humidity
@@ -29,7 +29,7 @@ const CityWeather = ({ city }) => { -

{city.humidity} %

+

{city.avgHumidity} %

diff --git a/app/store/slices/cities.js b/app/store/slices/cities.js index 2a00e7a..f6140b7 100644 --- a/app/store/slices/cities.js +++ b/app/store/slices/cities.js @@ -10,11 +10,30 @@ export const addCity = createAsyncThunk( ); const data = await response.json(); console.log(data); + + const avgTemp = data.list.reduce( + (acc, threeHour) => acc + threeHour.main.temp, + 0 + ) / data.list.length; + + const avgHumidity = + data.list.reduce((acc, threeHour) => acc + threeHour.main.humidity, 0) / + data.list.length; + + const avgPressure = + data.list.reduce( + (acc, threeHour) => acc + threeHour.main.pressure, + 0 + ) / data.list.length; + return { name: data.city.name, temp: data.list.map((threeHour) => threeHour.main.temp), pressure: data.list.map((threeHour) => threeHour.main.pressure), humidity: data.list.map((threeHour) => threeHour.main.humidity), + avgTemp: avgTemp, + avgHumidity: avgHumidity, + avgPressure: avgPressure, }; } ); From 3bd8e77556ece9dbfc63bc0977f0d93ac06b78a7 Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Mon, 24 Mar 2025 20:33:43 -0400 Subject: [PATCH 08/15] adjust formatting from card to table --- app/components/CityWeather.js | 59 +++++++-------- app/page.js | 135 ++++++++++++---------------------- 2 files changed, 74 insertions(+), 120 deletions(-) diff --git a/app/components/CityWeather.js b/app/components/CityWeather.js index eb96cf0..6cb3dbe 100644 --- a/app/components/CityWeather.js +++ b/app/components/CityWeather.js @@ -1,39 +1,34 @@ -import { Sparklines, SparklinesLine, SparklinesSpots } from "react-sparklines"; +import { Sparklines, SparklinesLine, SparklinesReferenceLine } from "react-sparklines"; import "bootstrap/dist/css/bootstrap.min.css"; const CityWeather = ({ city }) => { return ( -
-
-

{city.name}

-
-
-
Temperature
- - - - -

{city.avgTemp} °F

-
-
-
Pressure
- - - - -

{city.avgPressure} hPa

-
-
-
Humidity
- - - - -

{city.avgHumidity} %

-
-
-
-
+ + +
{city.name}
+ + + + + + +

{city.avgTemp} °F

+ + + + + + +

{city.avgPressure} hPa

+ + + + + + +

{city.avgHumidity} %

+ + ); }; diff --git a/app/page.js b/app/page.js index f049c39..d627c5d 100644 --- a/app/page.js +++ b/app/page.js @@ -1,95 +1,54 @@ -import Image from 'next/image' -import styles from './page.module.css' +'use client'; +import { useState } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { addCity } from './store/slices/cities'; +import CityWeather from './components/CityWeather'; +import 'bootstrap/dist/css/bootstrap.min.css'; export default function Home() { - return ( -
-
-

- Get started by editing  - app/page.js -

- -
+ const cities = useSelector((state) => state.cities.cities); + const dispatch = useDispatch(); + const [newCity, setNewCity] = useState(''); -
- Next.js Logo { + if (newCity) { + dispatch(addCity(newCity)); + setNewCity(''); + } + }; + + return ( +
+

Weather Forecast

+
+ setNewCity(e.target.value)} + placeholder='Add a city' + className='form-control' /> +
- -
- ) + + ); } From 5f4ae0b89d6c3bbfe108be94a58711decf665cc8 Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Mon, 24 Mar 2025 20:36:09 -0400 Subject: [PATCH 09/15] add average humidity and pressure to city data object --- app/store/slices/cities.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/store/slices/cities.js b/app/store/slices/cities.js index f6140b7..15a1422 100644 --- a/app/store/slices/cities.js +++ b/app/store/slices/cities.js @@ -11,20 +11,23 @@ export const addCity = createAsyncThunk( const data = await response.json(); console.log(data); - const avgTemp = data.list.reduce( - (acc, threeHour) => acc + threeHour.main.temp, - 0 - ) / data.list.length; + const avgTemp = + Math.round(data.list.reduce( + (acc, threeHour) => acc + threeHour.main.temp, + 0 + ) / data.list.length); const avgHumidity = - data.list.reduce((acc, threeHour) => acc + threeHour.main.humidity, 0) / - data.list.length; + Math.round(data.list.reduce( + (acc, threeHour) => acc + threeHour.main.humidity, + 0 + ) / data.list.length); const avgPressure = - data.list.reduce( + Math.round(data.list.reduce( (acc, threeHour) => acc + threeHour.main.pressure, 0 - ) / data.list.length; + ) / data.list.length); return { name: data.city.name, From e492ea9ddab02dec2e9afab63efb26fb6e52060a Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Mon, 24 Mar 2025 20:36:39 -0400 Subject: [PATCH 10/15] remove metadata --- app/layout.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/layout.js b/app/layout.js index 6677272..08a807f 100644 --- a/app/layout.js +++ b/app/layout.js @@ -1,15 +1,9 @@ 'use client'; -//import './globals.css'; import { Inter } from 'next/font/google'; import { Provider } from 'react-redux'; import store from './store/configureStore'; const inter = Inter({ subsets: ['latin'] }); -export const metadata = { - title: "Redux Weather App", - description: "Generated by create next app", -}; - export default function RootLayout({ children }) { return ( From 4a5cff9bef100300a8ae8255b7571c29ae06eba0 Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Wed, 26 Mar 2025 14:56:51 -0400 Subject: [PATCH 11/15] delete console log --- app/store/slices/cities.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/store/slices/cities.js b/app/store/slices/cities.js index 15a1422..3eee9d1 100644 --- a/app/store/slices/cities.js +++ b/app/store/slices/cities.js @@ -56,7 +56,6 @@ const citySlice = createSlice({ }) .addCase(addCity.fulfilled, (state, action) => { state.status = "fulfilled"; - console.log(action.payload); state.cities.push(action.payload); }) .addCase(addCity.rejected, (state, action) => { From baaf9813fe84efbd338119adc5f3b4968d807e4d Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Wed, 26 Mar 2025 14:57:21 -0400 Subject: [PATCH 12/15] delete console log --- app/store/slices/cities.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/store/slices/cities.js b/app/store/slices/cities.js index 3eee9d1..693f494 100644 --- a/app/store/slices/cities.js +++ b/app/store/slices/cities.js @@ -9,7 +9,6 @@ export const addCity = createAsyncThunk( `https://api.openweathermap.org/data/2.5/forecast?q=${cityName}&appid=${apiKey}&units=imperial` ); const data = await response.json(); - console.log(data); const avgTemp = Math.round(data.list.reduce( From e20f6fbf76d6c9bf9183ede17110ec4d65ee4c3a Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Wed, 26 Mar 2025 15:18:00 -0400 Subject: [PATCH 13/15] fix: add duplicate cities and formatting --- app/page.js | 58 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/app/page.js b/app/page.js index d627c5d..becde07 100644 --- a/app/page.js +++ b/app/page.js @@ -1,45 +1,53 @@ -'use client'; -import { useState } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { addCity } from './store/slices/cities'; -import CityWeather from './components/CityWeather'; -import 'bootstrap/dist/css/bootstrap.min.css'; +"use client"; +import { useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { addCity } from "./store/slices/cities"; +import CityWeather from "./components/CityWeather"; +import "bootstrap/dist/css/bootstrap.min.css"; export default function Home() { const cities = useSelector((state) => state.cities.cities); const dispatch = useDispatch(); - const [newCity, setNewCity] = useState(''); + const [newCity, setNewCity] = useState(""); - const handleAddCity = () => { - if (newCity) { - dispatch(addCity(newCity)); - setNewCity(''); - } - }; +const handleAddCity = () => { + if (!cities.some((city) => city.name.toLowerCase() === newCity.toLowerCase())) { + dispatch(addCity(newCity)); + } + setNewCity(""); +}; return ( -
-

Weather Forecast

-
+
+

Weather Forecast

+
setNewCity(e.target.value)} - placeholder='Add a city' - className='form-control' + placeholder="Add a city" + className="form-control" /> -
- +
- - - - + + + + From 1efa0dfc8dfce72da3a6a7ccab04c0603153bec2 Mon Sep 17 00:00:00 2001 From: Joseph Mitchell Date: Wed, 26 Mar 2025 15:18:40 -0400 Subject: [PATCH 14/15] add formatting --- app/components/CityWeather.js | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/app/components/CityWeather.js b/app/components/CityWeather.js index 6cb3dbe..7aad112 100644 --- a/app/components/CityWeather.js +++ b/app/components/CityWeather.js @@ -3,32 +3,32 @@ import "bootstrap/dist/css/bootstrap.min.css"; const CityWeather = ({ city }) => { return ( - - - - - - + + + + + + ); }; From 5d15bfa291bf1422cf6cc2557208d2830551fa0d Mon Sep 17 00:00:00 2001 From: j-emitch <48036253+j-emitch@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:47:20 -0400 Subject: [PATCH 15/15] Remove hardcoded API key from cities slice Remove hardcoded API key for security reasons. --- app/store/slices/cities.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/store/slices/cities.js b/app/store/slices/cities.js index 693f494..11596a1 100644 --- a/app/store/slices/cities.js +++ b/app/store/slices/cities.js @@ -1,6 +1,6 @@ import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; -const apiKey = "1d224a4af2354cde18df2f7243cc84f4"; +const apiKey = ""; export const addCity = createAsyncThunk( "city/fetchCityWeather",
CityTempPressureHumidity + City + + Temp + + Pressure + + Humidity +
-
{city.name}
-
- - - - -

{city.avgTemp} °F

-
- - - - -

{city.avgPressure} hPa

-
- - - - -

{city.avgHumidity} %

-
+
{city.name}
+
+ + + + +

{city.avgTemp} °F

+
+ + + + +

{city.avgPressure} hPa

+
+ + + + +

{city.avgHumidity} %

+