From ba4fc67810fe43c823c37089b0afef96e85c0c30 Mon Sep 17 00:00:00 2001 From: levydowell <33126924+levydowell@users.noreply.github.com> Date: Tue, 14 May 2024 07:00:42 -0600 Subject: [PATCH 1/5] redux is working --- app/components/cityInput.js | 0 app/layout.js | 23 +++-- app/page.js | 126 ++++++++----------------- app/store/configureStore.js | 8 ++ app/store/rootReducer.js | 8 ++ app/store/slices/weather.js | 65 +++++++++++++ package-lock.json | 180 +++++++++++++++++++++++++++++++++++- package.json | 5 +- 8 files changed, 315 insertions(+), 100 deletions(-) create mode 100644 app/components/cityInput.js create mode 100644 app/store/configureStore.js create mode 100644 app/store/rootReducer.js create mode 100644 app/store/slices/weather.js diff --git a/app/components/cityInput.js b/app/components/cityInput.js new file mode 100644 index 0000000..e69de29 diff --git a/app/layout.js b/app/layout.js index c93f806..effcdcb 100644 --- a/app/layout.js +++ b/app/layout.js @@ -1,17 +1,16 @@ -import './globals.css' -import { Inter } from 'next/font/google' - -const inter = Inter({ subsets: ['latin'] }) - -export const metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -} +'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 default function RootLayout({ children }) { return ( - - {children} - + + + {children} + + ) } diff --git a/app/page.js b/app/page.js index f049c39..eaf2c75 100644 --- a/app/page.js +++ b/app/page.js @@ -1,95 +1,49 @@ -import Image from 'next/image' -import styles from './page.module.css' +'use client'; +import styles from './page.module.css'; +import Link from 'next/link'; +import { useEffect } from 'react'; +import { fetchWeather, fetchCity } from './store/slices/weather'; +import { useDispatch, useSelector } from 'react-redux'; +import { useState } from 'react'; -export default function Home() { - return ( -
-
-

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

-
- - By{' '} - Vercel Logo - -
-
-
- Next.js Logo -
+export default function Home() { + const dispatch = useDispatch(); + const weather = useSelector((state) => state.weather.weather); + const city = useSelector((state) => state.weather.city); + const [input, setInput] = useState(); -
- -

- Docs -> -

-

Find in-depth information about Next.js features and API.

-
+ const testLat = 44.34; + const testLon = 10.99; + + useEffect(() => { + dispatch(fetchWeather()); + }, [dispatch]); - -

- Learn -> -

-

Learn about Next.js in an interactive course with quizzes!

-
- -

- Templates -> -

-

Explore the Next.js 13 playground.

-
+ const handleClick = () => { + dispatch(fetchCity(input)) + .then((response) =>{ + console.log(response.payload); + const latitude = response.payload[0].lat; + const longitude = response.payload[0].lon; + console.log(latitude, longitude); + dispatch(fetchWeather( {latitude, longitude} )) + //get rid of this last chain when finished + .then((response) => { + console.log(response.payload); + }) + }) + }; - -

- Deploy -> -

-

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-
+ return ( +
+
+ setInput(e.target.value)} /> +
) -} + +} \ No newline at end of file diff --git a/app/store/configureStore.js b/app/store/configureStore.js new file mode 100644 index 0000000..f4e8a50 --- /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; \ No newline at end of file diff --git a/app/store/rootReducer.js b/app/store/rootReducer.js new file mode 100644 index 0000000..a245913 --- /dev/null +++ b/app/store/rootReducer.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux'; +import weatherReducer from './slices/weather'; + +const rootReducer = combineReducers({ + weather: weatherReducer, +}); + +export default rootReducer; \ No newline at end of file diff --git a/app/store/slices/weather.js b/app/store/slices/weather.js new file mode 100644 index 0000000..601c1b7 --- /dev/null +++ b/app/store/slices/weather.js @@ -0,0 +1,65 @@ +import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; +import { useDispatch, useSelector } from 'react-redux'; +import axios from 'axios'; + +export const fetchWeather = createAsyncThunk('weather/fetchData', async ({latitude, longitude}) => { + try { + + const response = await axios.get(`http://api.openweathermap.org/data/2.5/forecast?lat=${latitude}&lon=${longitude}&appid=b68f55460cf44818aabff0456c2d1963`); + return response.data; + } catch (error) { + throw Error(error.response.data.message); + } +}); + +export const fetchCity = createAsyncThunk('weather/fetchCity', async (cityName) => { + try { + const response = await axios.get(`http://api.openweathermap.org/geo/1.0/direct?q=${cityName}&limit=5&appid=b68f55460cf44818aabff0456c2d1963`); + return response.data; + } catch (error) { + console.log(error('Error fetching city name: ', error)); + } +}); + +export const weatherSlice = createSlice({ + name: 'weather', + initialState: { + weather: null, + city: null, + status: 'idle', + error: null, + }, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchWeather.pending, (state) => { + state.status = 'loading'; + state.error = null; + }) + .addCase(fetchWeather.fulfilled, (state, action) => { + state.status = 'succeeded'; + state.weather = action.payload; + state.error = null; + }) + .addCase(fetchWeather.rejected, (state, action) => { + state.status = 'failed'; + state.error = action.payload; + }); + builder + .addCase(fetchCity.pending, (state) => { + state.status = 'loading'; + state.error = null; + }) + .addCase(fetchCity.fulfilled, (state, action) => { + state.status = 'succeeded'; + state.city = action.payload; + state.error = null; + }) + .addCase(fetchCity.rejected, (state, action) => { + state.status = 'failed'; + state.error = action.payload; + }); + }, +}); + +export default weatherSlice.reducer; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 90f6bb1..a9c1969 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.2.3", + "axios": "^1.6.8", "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.1.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -296,6 +299,29 @@ "node": ">= 8" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.3.tgz", + "integrity": "sha512-76dll9EnJXg4EVcI5YNxZA/9hSAmZsFqzMmNRHvIlzw2WS/twfcVX3ysYrWGJMClwEmChQFC4yRq74tn6fdzRA==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "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 +340,11 @@ "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.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@typescript-eslint/parser": { "version": "6.7.3", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.3.tgz", @@ -615,6 +646,11 @@ "has-symbols": "^1.0.3" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -634,6 +670,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -753,6 +799,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -826,6 +883,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1491,6 +1556,25 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1499,6 +1583,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1759,6 +1856,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "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", @@ -2270,6 +2376,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2628,6 +2753,11 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -2683,6 +2813,41 @@ "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.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "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==" + }, + "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==", + "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 +2888,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, "node_modules/resolve": { "version": "1.22.6", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", @@ -3225,6 +3395,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.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..44a48a0 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,13 @@ "lint": "next lint" }, "dependencies": { + "@reduxjs/toolkit": "^2.2.3", + "axios": "^1.6.8", "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.1.2" } } From 3b8771f39b87e0649d5459c89a1d6131880027e7 Mon Sep 17 00:00:00 2001 From: levydowell <33126924+levydowell@users.noreply.github.com> Date: Wed, 15 May 2024 07:14:34 -0600 Subject: [PATCH 2/5] Figured out how to map temp data, now need to incorporate sparklines --- app/components/cityInput.js | 38 ++++++++++++++++++++++++++ app/page.js | 54 +++++++++++++++---------------------- package-lock.json | 15 ++++++++++- package.json | 3 ++- 4 files changed, 76 insertions(+), 34 deletions(-) diff --git a/app/components/cityInput.js b/app/components/cityInput.js index e69de29..70c1b0b 100644 --- a/app/components/cityInput.js +++ b/app/components/cityInput.js @@ -0,0 +1,38 @@ +'use client'; +import styles from '../page.module.css'; +import { useEffect } from 'react'; +import { fetchWeather, fetchCity } from '../store/slices/weather'; +import { useDispatch, useSelector } from 'react-redux'; +import { useState } from 'react'; + + + +export const InputField = () => { + const dispatch = useDispatch(); + const weather = useSelector((state) => state.weather.weather); + const city = useSelector((state) => state.weather.city); + const [input, setInput] = useState(); + + const handleClick = () => { + dispatch(fetchCity(input)) + .then((response) =>{ + console.log(response.payload); + const latitude = response.payload[0].lat; + const longitude = response.payload[0].lon; + console.log(latitude, longitude); + dispatch(fetchWeather( {latitude, longitude} )) + + }) + }; + + return ( +
+
+ setInput(e.target.value)} /> + +
+
+) +} + diff --git a/app/page.js b/app/page.js index eaf2c75..309b1d7 100644 --- a/app/page.js +++ b/app/page.js @@ -1,49 +1,39 @@ 'use client'; import styles from './page.module.css'; -import Link from 'next/link'; import { useEffect } from 'react'; import { fetchWeather, fetchCity } from './store/slices/weather'; import { useDispatch, useSelector } from 'react-redux'; import { useState } from 'react'; +import { InputField } from './components/cityInput'; +import React from 'react'; +import { Sparklines } from 'react-sparklines'; export default function Home() { - const dispatch = useDispatch(); + const [lineData, setLineData] = useState(''); + const weather = useSelector((state) => state.weather.weather); - const city = useSelector((state) => state.weather.city); - const [input, setInput] = useState(); + const testLog = weather; + console.log('weather', testLog); - const testLat = 44.34; - const testLon = 10.99; - useEffect(() => { - dispatch(fetchWeather()); - }, [dispatch]); - - const handleClick = () => { - dispatch(fetchCity(input)) - .then((response) =>{ - console.log(response.payload); - const latitude = response.payload[0].lat; - const longitude = response.payload[0].lon; - console.log(latitude, longitude); - dispatch(fetchWeather( {latitude, longitude} )) - //get rid of this last chain when finished - .then((response) => { - console.log(response.payload); - }) - }) - }; - return ( -
-
- setInput(e.target.value)} /> - -
-
+ + if (weather == null) { + return ( +
+ +
) + } else { + return ( +
+ +

{weather.list.map((datum) => datum.main.temp)}

+
+ ) + } + } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a9c1969..3272a7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "next": "13.5.3", "react": "18.2.0", "react-dom": "18.2.0", - "react-redux": "^9.1.2" + "react-redux": "^9.1.2", + "react-sparklines": "^1.7.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2835,6 +2836,18 @@ } } }, + "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==", + "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", diff --git a/package.json b/package.json index 44a48a0..16e2fda 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "next": "13.5.3", "react": "18.2.0", "react-dom": "18.2.0", - "react-redux": "^9.1.2" + "react-redux": "^9.1.2", + "react-sparklines": "^1.7.0" } } From 1322d42ec2eebc13a5f97dc952b57d1f590a0601 Mon Sep 17 00:00:00 2001 From: levydowell <33126924+levydowell@users.noreply.github.com> Date: Thu, 16 May 2024 07:21:27 -0600 Subject: [PATCH 3/5] sparklines populating for all cities entered. Now need to add pressure and humidity --- app/components/allData.js | 29 ++++++++++ app/globals.css | 108 +----------------------------------- app/page.js | 5 +- app/page.module.css | 3 +- app/store/slices/weather.js | 4 +- 5 files changed, 38 insertions(+), 111 deletions(-) create mode 100644 app/components/allData.js diff --git a/app/components/allData.js b/app/components/allData.js new file mode 100644 index 0000000..edb61bd --- /dev/null +++ b/app/components/allData.js @@ -0,0 +1,29 @@ +'use client'; +import React from 'react'; +import { Sparklines, SparklinesLine, SparklinesReferenceLine } from 'react-sparklines'; +import { useDispatch, useSelector } from 'react-redux'; + +export const AllData = () => { + const weather = useSelector((state) => state.weather.weather); + + let tempData = []; + // weather.list.forEach((e) => { + // if (weather.list.indexOf(e) % 3 === 0) { + // tempData.push(e); + // } + // }); + + return weather.map((cityWeather) => { + console.log('cityweather', cityWeather); + return ( +
+ datum.main.temp)} width={20} height={10} margin={1}> + + + +
+ ) + }) + + +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index d4f491e..360408c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,107 +1,3 @@ -:root { - --max-width: 1100px; - --border-radius: 12px; - --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', - 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', - 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; - - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; - - --primary-glow: conic-gradient( - from 180deg at 50% 50%, - #16abff33 0deg, - #0885ff33 55deg, - #54d6ff33 120deg, - #0071ff33 160deg, - transparent 360deg - ); - --secondary-glow: radial-gradient( - rgba(255, 255, 255, 1), - rgba(255, 255, 255, 0) - ); - - --tile-start-rgb: 239, 245, 249; - --tile-end-rgb: 228, 232, 233; - --tile-border: conic-gradient( - #00000080, - #00000040, - #00000030, - #00000020, - #00000010, - #00000010, - #00000080 - ); - - --callout-rgb: 238, 240, 241; - --callout-border-rgb: 172, 175, 176; - --card-rgb: 180, 185, 188; - --card-border-rgb: 131, 134, 135; -} - -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - - --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); - --secondary-glow: linear-gradient( - to bottom right, - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0.3) - ); - - --tile-start-rgb: 2, 13, 46; - --tile-end-rgb: 2, 5, 19; - --tile-border: conic-gradient( - #ffffff80, - #ffffff40, - #ffffff30, - #ffffff20, - #ffffff10, - #ffffff10, - #ffffff80 - ); - - --callout-rgb: 20, 20, 20; - --callout-border-rgb: 108, 108, 108; - --card-rgb: 100, 100, 100; - --card-border-rgb: 200, 200, 200; - } -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); -} - -a { - color: inherit; - text-decoration: none; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } +.graphs { + width: 25%; } diff --git a/app/page.js b/app/page.js index 309b1d7..c79b1b5 100644 --- a/app/page.js +++ b/app/page.js @@ -5,8 +5,9 @@ import { fetchWeather, fetchCity } from './store/slices/weather'; import { useDispatch, useSelector } from 'react-redux'; import { useState } from 'react'; import { InputField } from './components/cityInput'; +import { AllData } from './components/allData'; import React from 'react'; -import { Sparklines } from 'react-sparklines'; +import { Sparklines, SparklinesLine, SparklinesReferenceLine } from 'react-sparklines'; export default function Home() { @@ -30,7 +31,7 @@ export default function Home() { return (
-

{weather.list.map((datum) => datum.main.temp)}

+
) } diff --git a/app/page.module.css b/app/page.module.css index 6676d2c..79b96a3 100644 --- a/app/page.module.css +++ b/app/page.module.css @@ -97,7 +97,7 @@ .center::before, .center::after { - content: ''; + content: ""; left: 50%; position: absolute; filter: blur(45px); @@ -107,6 +107,7 @@ .logo { position: relative; } + /* Enable hover only on non-touch devices */ @media (hover: hover) and (pointer: fine) { .card:hover { diff --git a/app/store/slices/weather.js b/app/store/slices/weather.js index 601c1b7..fa6a7d9 100644 --- a/app/store/slices/weather.js +++ b/app/store/slices/weather.js @@ -24,7 +24,7 @@ export const fetchCity = createAsyncThunk('weather/fetchCity', async (cityName) export const weatherSlice = createSlice({ name: 'weather', initialState: { - weather: null, + weather: [], city: null, status: 'idle', error: null, @@ -38,7 +38,7 @@ export const weatherSlice = createSlice({ }) .addCase(fetchWeather.fulfilled, (state, action) => { state.status = 'succeeded'; - state.weather = action.payload; + state.weather.push(action.payload); state.error = null; }) .addCase(fetchWeather.rejected, (state, action) => { From 64c6c742290ebed8095cb59d95acf3ae611e4ce0 Mon Sep 17 00:00:00 2001 From: levydowell <33126924+levydowell@users.noreply.github.com> Date: Fri, 17 May 2024 07:08:34 -0600 Subject: [PATCH 4/5] Sparklines working for all graphs, and all cities entered are rendered --- app/components/allData.js | 41 ++++++++++++++++++++++++++++----------- app/globals.css | 9 +++++++++ app/page.js | 8 -------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/app/components/allData.js b/app/components/allData.js index edb61bd..8217bfa 100644 --- a/app/components/allData.js +++ b/app/components/allData.js @@ -5,22 +5,41 @@ import { useDispatch, useSelector } from 'react-redux'; export const AllData = () => { const weather = useSelector((state) => state.weather.weather); + const city = useSelector((state) => state.weather.city); - let tempData = []; - // weather.list.forEach((e) => { - // if (weather.list.indexOf(e) % 3 === 0) { - // tempData.push(e); - // } - // }); return weather.map((cityWeather) => { console.log('cityweather', cityWeather); return ( -
- datum.main.temp)} width={20} height={10} margin={1}> - - - +
+ +
+

{cityWeather.city.name}

+
+ +
+

Temperature

+ datum.main.temp)} width={20} height={10} margin={1}> + + + +
+ +
+

Pressure

+ datum.main.pressure)} width={20} height={10} margin={1}> + + + +
+ +
+

Humidity

+ datum.main.humidity)} width={20} height={10} margin={1}> + + + +
) }) diff --git a/app/globals.css b/app/globals.css index 360408c..3e985c4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,3 +1,12 @@ .graphs { width: 25%; } + +.graph-container { + display: flex; + align-items: center; +} + +.city-name { + text-align: center; +} diff --git a/app/page.js b/app/page.js index c79b1b5..d1f98b1 100644 --- a/app/page.js +++ b/app/page.js @@ -7,19 +7,11 @@ import { useState } from 'react'; import { InputField } from './components/cityInput'; import { AllData } from './components/allData'; import React from 'react'; -import { Sparklines, SparklinesLine, SparklinesReferenceLine } from 'react-sparklines'; export default function Home() { - const [lineData, setLineData] = useState(''); const weather = useSelector((state) => state.weather.weather); - const testLog = weather; - console.log('weather', testLog); - - - - if (weather == null) { return ( From 57da5361b19b868ac19fe3940d9bb56f69202034 Mon Sep 17 00:00:00 2001 From: levydowell <33126924+levydowell@users.noreply.github.com> Date: Tue, 21 May 2024 06:42:14 -0600 Subject: [PATCH 5/5] sumbission commit --- app/components/allData.js | 36 +++++++++++++++++++++++++++++------- app/components/cityInput.js | 25 +++++++++++-------------- app/globals.css | 4 ++++ app/page.js | 17 +++++++---------- app/store/slices/weather.js | 25 +++++++++++++++++-------- package-lock.json | 29 +++++++++++++++++++++++++++++ package.json | 1 + 7 files changed, 98 insertions(+), 39 deletions(-) diff --git a/app/components/allData.js b/app/components/allData.js index 8217bfa..c959e4a 100644 --- a/app/components/allData.js +++ b/app/components/allData.js @@ -1,44 +1,66 @@ 'use client'; import React from 'react'; import { Sparklines, SparklinesLine, SparklinesReferenceLine } from 'react-sparklines'; -import { useDispatch, useSelector } from 'react-redux'; +import { useSelector } from 'react-redux'; +import 'bootstrap/dist/css/bootstrap.min.css'; +/** + * + * @returns react component containing all of the weather data for each city entered. + */ export const AllData = () => { const weather = useSelector((state) => state.weather.weather); - const city = useSelector((state) => state.weather.city); + /** + * + * @param {*} data The data list returned from openweather api. + * @param {*} avgType The weather distinction nested within the data list. Naming convention must be + * exactly the same as json layout, i.e `temp`, `pressure`, or `humidity`. + * @returns The average of the sum of all data points for avgType. + */ + const findAvg = (data, avgType) => { + const typeData = data.map((datum) => datum.main[avgType]); + console.log('here', typeData, typeData.length); + const sum = typeData.reduce( + (acc, value) => acc + value, 0, + ); + + return Math.round(sum/typeData.length); + } return weather.map((cityWeather) => { - console.log('cityweather', cityWeather); return (
-
+

{cityWeather.city.name}

-

Temperature

+
Temperature
datum.main.temp)} width={20} height={10} margin={1}> +

{findAvg(cityWeather.list, 'temp')} F

-

Pressure

+
Pressure
datum.main.pressure)} width={20} height={10} margin={1}> +

{findAvg(cityWeather.list, 'pressure')} hPa

-

Humidity

+
Humidity
datum.main.humidity)} width={20} height={10} margin={1}> +

{findAvg(cityWeather.list, 'humidity')} %

) diff --git a/app/components/cityInput.js b/app/components/cityInput.js index 70c1b0b..7a6c0e8 100644 --- a/app/components/cityInput.js +++ b/app/components/cityInput.js @@ -1,38 +1,35 @@ 'use client'; -import styles from '../page.module.css'; -import { useEffect } from 'react'; +import 'bootstrap/dist/css/bootstrap.css'; import { fetchWeather, fetchCity } from '../store/slices/weather'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { useState } from 'react'; - - export const InputField = () => { const dispatch = useDispatch(); - const weather = useSelector((state) => state.weather.weather); - const city = useSelector((state) => state.weather.city); const [input, setInput] = useState(); + /** + * dispatch `fetchCity` for async api call to retrieve city coords. + * dispatch `fetchWeather` to get all weather information. + */ const handleClick = () => { dispatch(fetchCity(input)) .then((response) =>{ - console.log(response.payload); const latitude = response.payload[0].lat; const longitude = response.payload[0].lon; - console.log(latitude, longitude); dispatch(fetchWeather( {latitude, longitude} )) }) }; + // returns component with searchbar with event handler return ( -
-
- + setInput(e.target.value)} /> - +
-
+ ) } diff --git a/app/globals.css b/app/globals.css index 3e985c4..32639be 100644 --- a/app/globals.css +++ b/app/globals.css @@ -10,3 +10,7 @@ .city-name { text-align: center; } + +.set-margin { + margin: 5% 10%; +} diff --git a/app/page.js b/app/page.js index d1f98b1..a91c654 100644 --- a/app/page.js +++ b/app/page.js @@ -1,19 +1,15 @@ 'use client'; -import styles from './page.module.css'; -import { useEffect } from 'react'; -import { fetchWeather, fetchCity } from './store/slices/weather'; -import { useDispatch, useSelector } from 'react-redux'; -import { useState } from 'react'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import { useSelector } from 'react-redux'; import { InputField } from './components/cityInput'; import { AllData } from './components/allData'; import React from 'react'; - export default function Home() { const weather = useSelector((state) => state.weather.weather); - if (weather == null) { + if (weather === null) { return (
@@ -22,11 +18,12 @@ export default function Home() { } else { return (
- - +
+ + +
) } - } \ No newline at end of file diff --git a/app/store/slices/weather.js b/app/store/slices/weather.js index fa6a7d9..46a336c 100644 --- a/app/store/slices/weather.js +++ b/app/store/slices/weather.js @@ -1,26 +1,34 @@ import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; -import { useDispatch, useSelector } from 'react-redux'; import axios from 'axios'; -export const fetchWeather = createAsyncThunk('weather/fetchData', async ({latitude, longitude}) => { +/** + * createAsyncThunk that fetches the latitude and longitude of entered city. + */ +export const fetchCity = createAsyncThunk('weather/fetchCity', async (cityName) => { try { - - const response = await axios.get(`http://api.openweathermap.org/data/2.5/forecast?lat=${latitude}&lon=${longitude}&appid=b68f55460cf44818aabff0456c2d1963`); + const response = await axios.get(`http://api.openweathermap.org/geo/1.0/direct?q=${cityName}&limit=5&appid=b68f55460cf44818aabff0456c2d1963`); return response.data; } catch (error) { - throw Error(error.response.data.message); + console.log(error('Error fetching city name: ', error)); } }); -export const fetchCity = createAsyncThunk('weather/fetchCity', async (cityName) => { +/** + * createAsyncThunk that fetches weather data from the lat/lon coordinates of selected city. + */ +export const fetchWeather = createAsyncThunk('weather/fetchData', async ({latitude, longitude}) => { try { - const response = await axios.get(`http://api.openweathermap.org/geo/1.0/direct?q=${cityName}&limit=5&appid=b68f55460cf44818aabff0456c2d1963`); + + const response = await axios.get(`http://api.openweathermap.org/data/2.5/forecast?lat=${latitude}&lon=${longitude}&units=imperial&appid=b68f55460cf44818aabff0456c2d1963`); return response.data; } catch (error) { - console.log(error('Error fetching city name: ', error)); + throw Error(error.response.data.message); } }); +/** + * Redux slice to store the state of the weather and selected city. + */ export const weatherSlice = createSlice({ name: 'weather', initialState: { @@ -30,6 +38,7 @@ export const weatherSlice = createSlice({ error: null, }, reducers: {}, + //reducers for asyncThunks extraReducers: (builder) => { builder .addCase(fetchWeather.pending, (state) => { diff --git a/package-lock.json b/package-lock.json index 3272a7f..09e04a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@reduxjs/toolkit": "^2.2.3", "axios": "^1.6.8", + "bootstrap": "^5.3.3", "eslint": "8.50.0", "eslint-config-next": "13.5.3", "next": "13.5.3", @@ -300,6 +301,16 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.3.tgz", @@ -694,6 +705,24 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/package.json b/package.json index 16e2fda..3fd5a3d 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@reduxjs/toolkit": "^2.2.3", "axios": "^1.6.8", + "bootstrap": "^5.3.3", "eslint": "8.50.0", "eslint-config-next": "13.5.3", "next": "13.5.3",