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
4 changes: 4 additions & 0 deletions OpenWeatherAPI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const openWeatherApi = {
key: "47141b27ae5c4862c3e7faece74bb0ed",
rootUrl: "https://api.openweathermap.org/data/2.5"
}
11 changes: 11 additions & 0 deletions app/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function Header() {
return (
<div className="row justify-content-md-center">
<div className="col col-8 text-center">
<h1>Weather App</h1>
</div>
</div>
)
}

export default Header;
58 changes: 58 additions & 0 deletions app/features/forecastsList/ForecastsList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use client';
import { useSelector } from "react-redux";
import { Sparklines, SparklinesLine, SparklinesReferenceLine } from "react-sparklines";


export const ForecastList = () => {

const forecastsListState = useSelector((state) => state.forecastsList.forecasts)

const getAverage = (array) => {
return array.reduce((a, b) => a + b) / array.length;
}


const columnHeader =
<div className="row justify-content-md-center text-center">
<div className="col-3">City</div>
<div className="col-3">Temperature</div>
<div className="col-3">Pressure</div>
<div className="col-3">Humidity</div>
Comment on lines +17 to +20

Choose a reason for hiding this comment

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

DRY,
you could have done something like:
const columnTitles = ['city', 'Temperature', 'Pressure', 'Humidity']
And then you map them

</div>

const forcasts = forecastsListState.map((forecast) => {

let { humidities, pressures, temperatures } = forecast;

Choose a reason for hiding this comment

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

const , not let.


return <div key={forecast.id} className="row justify-content-md-center text-center">
<div className="col-3">{forecast.city}</div>
<div className="col-3">
<Sparklines data={temperatures}>
<SparklinesLine color="#63CBDE" />
<SparklinesReferenceLine type="avg" />
</Sparklines>
{getAverage(temperatures)}</div>
<div className="col-3">
<Sparklines data={pressures}>
<SparklinesLine color="green" />
<SparklinesReferenceLine type="avg" />
</Sparklines>
{getAverage(pressures)}</div>
<div className="col-3">
<Sparklines data={humidities}>
<SparklinesLine color="#A627A4" />
<SparklinesReferenceLine type="avg" />
</Sparklines>
{getAverage(humidities)}</div>
</div>
})



return (
<>
<div>{columnHeader}</div>
<div>{forcasts}</div>

Choose a reason for hiding this comment

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

Suggested change
<div>{forcasts}</div>
<div>{forecasts}</div>

</>
)
}
49 changes: 49 additions & 0 deletions app/features/forecastsList/forecastsListSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { openWeatherApi } from "@/OpenWeatherAPI";
import axios from "axios";
import { parseFiveDayForecast } from "@/app/utilities/fiveDayForcastParser";

const rootUrl = openWeatherApi.rootUrl

export const fetchFiveDayForecast = createAsyncThunk('data/fetchFiveDayForecast', async (locationId) => {
const singleForecastResponse = await axios.get(`${rootUrl}/weather?q=${locationId},us&units=imperial&appid=${openWeatherApi.key}`);
let { coord: { lon: longitude, lat: latitude } } = singleForecastResponse.data;

const fiveDayForecastResponse = await axios.get(`${rootUrl}/forecast?lat=${latitude}&lon=${longitude}&units=imperial&appid=${openWeatherApi.key}`)

return fiveDayForecastResponse.data
})

const initialState = {
forecasts: [],
status: 'idle', // to track loading state
error: null,
}

export const forecastsListSlice = createSlice({
name: "forecasts",
initialState,
reducers: {
addToForecastsList: (state) => {
state.list.shift(action.payload)
}
},
extraReducers: (builder) => {
builder
.addCase(fetchFiveDayForecast.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchFiveDayForecast.fulfilled, (state, action) => {
state.status = 'succeeded';
state.forecasts.unshift(parseFiveDayForecast(action.payload))
})
.addCase(fetchFiveDayForecast.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
},
})

export const { } = forecastsListSlice.actions;

export default forecastsListSlice.reducer;
50 changes: 50 additions & 0 deletions app/features/searchInput/SearchInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use client';
import { useDispatch, useSelector } from "react-redux";
import { setInputState, reset } from "./searchInputSlice";
import { fetchFiveDayForecast, fetchSingleDayForcast } from "../forecastsList/forecastsListSlice";

Choose a reason for hiding this comment

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

clean unused imports



function SearchInput() {
const string = useSelector((state) => state.searchInput.string)
const dispatch = useDispatch()

const handleEnterKey = (e) => {
let key = e.key

Choose a reason for hiding this comment

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

bad use of let, this is a const


if (key === 'Enter') {
dispatch(fetchFiveDayForecast(string))
dispatch(reset())
}
}

return (
<div className="row justify-content-md-center top-buffer">
<div className="col col-12">
<div className="input-group mb-3">
<input
type="text"
className="form-control"
placeholder="Get a five-day forecast in your different cities"
defaultValue={string}
onChange={(e) =>
dispatch(setInputState(e.target.value))
}
onKeyDown={(e) => handleEnterKey(e)}
>
</input>
<div className="input-group-append">
<button type="button" className="btn btn-primary"
onClick={() => {
dispatch(fetchFiveDayForecast(string))
dispatch(reset())
}}>
Search
</button>
</div>
</div>
</div>
</div>
Comment on lines +21 to +46

Choose a reason for hiding this comment

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

very clean html, easy to understand and well aligned

)
}

export default SearchInput;
23 changes: 23 additions & 0 deletions app/features/searchInput/searchInputSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
string: "",

}

export const searchInputSlice = createSlice({
name: "searchInputSlice",
initialState,
reducers: {
setInputState: (state, action) => {
state.string = action.payload
},
reset: (state) => {
state.string = ""
}
},
})

export const { setInputState, reset } = searchInputSlice.actions;

export default searchInputSlice.reducer;
112 changes: 12 additions & 100 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -1,107 +1,19 @@
: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;
/* h1 {
text-align: center;
} */

--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;
/* .container {
background: black;
}

@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;
}
.col {
background: gray;
}

* {
box-sizing: border-box;
padding: 0;
margin: 0;
.row {
background: lightgreen;
}

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;
}
}
.top-buffer {
margin-top: 20px;
} */
17 changes: 0 additions & 17 deletions app/layout.js

This file was deleted.

19 changes: 19 additions & 0 deletions app/layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client'
import 'bootstrap/dist/css/bootstrap.css'
import 'react-bootstrap'
import './globals.css'
import { Inter } from 'next/font/google'
import { Provider } from 'react-redux'
import store from './store/store'

const inter = Inter({ subsets: ['latin'] })

export default function RootLayout({ children }) {
return (
<html lang="en">
<Provider store={store}>
<body className={inter.className}>{children}</body>
</Provider>
</html>
)
}
Loading