diff --git a/app/components/WeatherApp.js b/app/components/WeatherApp.js
new file mode 100644
index 0000000..adb2646
--- /dev/null
+++ b/app/components/WeatherApp.js
@@ -0,0 +1,96 @@
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import { fetchWeatherData, setCity } from "app/redux/weatherSlice.js";
+import WeatherChart from "./WeatherChart";
+
+function WeatherApp() {
+ const dispatch = useDispatch();
+ const { city, weatherData, loading, error } = useSelector(
+ (state) => state.weather
+ );
+ const [inputCity, setInputCity] = useState(city);
+
+ useEffect(() => {
+ if (city) {
+ dispatch(fetchWeatherData(city));
+ }
+ }, [city, dispatch]);
+
+ const handleInputChange = (e) => {
+ setInputCity(e.target.value);
+ };
+
+ const handleFormSubmit = (e) => {
+ e.preventDefault();
+ dispatch(setCity(inputCity));
+ setInputCity('')
+ };
+
+ if (loading) return
Fetching weather data for {city}...
;
+ if (error) return Failed to fetch weather data for {city} {error}
;
+
+ const temperatureData = weatherData
+ ? weatherData.list.map((item) => {
+ console.log(item)
+ const celsius = item.main.temp
+ const fahrenheit = (celsius * (9 / 5) + 32); // Convert Celsius to Fahrenheit
+
+ return fahrenheit
+ })
+ : [];
+ const pressureData = weatherData
+ ? weatherData.list.map((item) => item.main.pressure)
+ : [];
+ const humidityData = weatherData
+ ? weatherData.list.map((item) => item.main.humidity)
+ : [];
+
+ return (
+ <>
+ Weather Data
+
+
+ {weatherData && (
+ <>
+
+ >
+ )}
+ >
+ );
+}
+
+export default WeatherApp;
diff --git a/app/components/WeatherChart.js b/app/components/WeatherChart.js
new file mode 100644
index 0000000..9586d0b
--- /dev/null
+++ b/app/components/WeatherChart.js
@@ -0,0 +1,53 @@
+"use client";
+
+import React from "react";
+import {
+ Sparklines,
+ SparklinesLine,
+ SparklinesReferenceLine,
+} from "react-sparklines";
+
+function calculateAverage(data) {
+ if (!data || data.length === 0) {
+ return 0; // Return 0 for empty or invalid data
+ }
+
+ let sum = 0;
+ let validCount = 0;
+ for (const point of data) {
+ if (typeof point === "number" && !isNaN(point)) {
+ sum += point;
+ validCount++;
+ } else {
+ console.warn("Invalid data point:", point);
+ }
+
+ }
+
+ if (validCount === 0) {
+ return 0; // Return 0 if no valid data points
+ }
+
+ return sum / validCount;
+}
+
+function WeatherChart({ data, title, units, color }) {
+ const average = calculateAverage(data)
+ console.log(average)
+ return (
+
+
+ {title} ({units})
+
+
+
+
+
+
+ Average: {average.toFixed(2)} {units}
+
+
+ );
+}
+
+export default WeatherChart;
diff --git a/app/favicon.ico b/app/favicon.ico
deleted file mode 100644
index 718d6fe..0000000
Binary files a/app/favicon.ico and /dev/null differ
diff --git a/app/globals.css b/app/globals.css
index d4f491e..99c4a7e 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,107 +1,84 @@
: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)
- );
+ background-color: #000000;
+ color: blanchedalmond;
+ font-family: "Lucida Console", Monaco, monospace;
+ font-size: 16px;
+ letter-spacing: 2px;
+ word-spacing: 2px;
+ color: #000000;
+ font-weight: normal;
+ text-decoration: none;
+ font-style: normal;
+ font-variant: small-caps;
+ text-transform: uppercase;
+}
- --tile-start-rgb: 239, 245, 249;
- --tile-end-rgb: 228, 232, 233;
- --tile-border: conic-gradient(
- #00000080,
- #00000040,
- #00000030,
- #00000020,
- #00000010,
- #00000010,
- #00000080
- );
+.content {
+ max-width: 600px;
+ margin: 20px auto;
+ padding: 20px;
+ border: 3px solid #ccc;
+ border-radius: 5px;
+}
- --callout-rgb: 238, 240, 241;
- --callout-border-rgb: 172, 175, 176;
- --card-rgb: 180, 185, 188;
- --card-border-rgb: 131, 134, 135;
+.loading,
+.error {
+ text-align: center;
+ margin-top: 20px;
}
-@media (prefers-color-scheme: dark) {
- :root {
- --foreground-rgb: 255, 255, 255;
- --background-start-rgb: 0, 0, 0;
- --background-end-rgb: 0, 0, 0;
+.error {
+ color: red;
+}
- --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)
- );
+.form {
+ margin-bottom: 20px;
+ text-align: center;
+}
- --tile-start-rgb: 2, 13, 46;
- --tile-end-rgb: 2, 5, 19;
- --tile-border: conic-gradient(
- #ffffff80,
- #ffffff40,
- #ffffff30,
- #ffffff20,
- #ffffff10,
- #ffffff10,
- #ffffff80
- );
+.heading {
+ text-align: center;
+ margin-bottom: 20px;
+ color: #595757;
+}
- --callout-rgb: 20, 20, 20;
- --callout-border-rgb: 108, 108, 108;
- --card-rgb: 100, 100, 100;
- --card-border-rgb: 200, 200, 200;
- }
+.btn {
+ margin-top: 20px;
+ padding: 4px 10px;
+ background-color: #425263;
+ color: white;
+ border: none;
+ border-radius: 3px;
+ cursor: pointer;
}
-* {
- box-sizing: border-box;
- padding: 0;
- margin: 0;
+.weatherChart {
+ padding: 20px;
+ border: 1px solid #595757;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ background-color: #e2e1e1;
}
-html,
-body {
- max-width: 100vw;
- overflow-x: hidden;
+.chartHeading {
+ margin-bottom: 10px;
+ text-align: center;
+ color: #333;
}
-body {
- color: rgb(var(--foreground-rgb));
- background: linear-gradient(
- to bottom,
- transparent,
- rgb(var(--background-end-rgb))
- )
- rgb(var(--background-start-rgb));
+Sparklines {
+ height: 120px;
+ width: 200px;
}
-a {
- color: inherit;
- text-decoration: none;
+.avg {
+ margin-top: 10px;
+ text-align: center;
+ color: #555;
}
-@media (prefers-color-scheme: dark) {
- html {
- color-scheme: dark;
- }
+.chartsDiv {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
}
diff --git a/app/page.js b/app/page.js
index f049c39..ccc7a5b 100644
--- a/app/page.js
+++ b/app/page.js
@@ -1,95 +1,13 @@
-import Image from 'next/image'
-import styles from './page.module.css'
+'use client'
+import React from 'react';
+import { Provider } from 'react-redux';
+import store from "app/redux/store.js";
+import WeatherApp from "./components/WeatherApp.js";
export default function Home() {
return (
-
-
-
- Get started by editing
- app/page.js
-
-
-
-
-
-
-
-
-
-
+
+
+
)
}
diff --git a/app/redux/store.js b/app/redux/store.js
new file mode 100644
index 0000000..8175c38
--- /dev/null
+++ b/app/redux/store.js
@@ -0,0 +1,12 @@
+import { configureStore } from "@reduxjs/toolkit";
+import weatherReducer from "app/redux/weatherSlice.js";
+
+
+
+const store = configureStore({
+ reducer: {
+ weather: weatherReducer
+ }
+})
+
+export default store;
\ No newline at end of file
diff --git a/app/redux/weatherSlice.js b/app/redux/weatherSlice.js
new file mode 100644
index 0000000..6293a00
--- /dev/null
+++ b/app/redux/weatherSlice.js
@@ -0,0 +1,60 @@
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+
+const API_KEY = process.env.NEXT_PUBLIC_OPENWEATHER_API_KEY
+const API_URL = "https://api.openweathermap.org/data/2.5/forecast";
+
+export const fetchWeatherData = createAsyncThunk(
+ "weather/fetchWeatherData",
+ async (city) => {
+ try {
+ const apiUrl = `${API_URL}?q=${city}&appid=${API_KEY}&units=metric`;
+
+ const response = await fetch(apiUrl);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ return Promise.reject(error.message);
+ }
+ }
+);
+
+const initialState = {
+ city: "",
+ weatherData: null,
+ loading: false,
+ error: null,
+};
+
+const weatherSlice = createSlice({
+ name: "weather",
+ initialState,
+ reducers: {
+ setCity: (state, action) => {
+ state.city = action.payload;
+ state.error = null;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(fetchWeatherData.pending, (state) => {
+ state.loading = true;
+ state.error = null;
+ })
+ .addCase(fetchWeatherData.fulfilled, (state, action) => {
+ state.loading = false;
+ state.weatherData = action.payload;
+ })
+ .addCase(fetchWeatherData.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.error.message;
+ });
+ },
+});
+
+export const { setCity } = weatherSlice.actions;
+export default weatherSlice.reducer;
diff --git a/package-lock.json b/package-lock.json
index 90f6bb1..5a175d9 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.5.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.5.1",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.1.tgz",
+ "integrity": "sha512-UHhy3p0oUpdhnSxyDjaRDYaw8Xra75UiLbCiRozVPHjfDwNYkh0TsVm/1OmTW8Md+iDAJmYPWUKMvsMc2GtpNg==",
+ "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..505d50f 100644
--- a/package.json
+++ b/package.json
@@ -9,10 +9,13 @@
"lint": "next lint"
},
"dependencies": {
+ "@reduxjs/toolkit": "^2.5.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"
}
}