Skip to content
Open

MVP #31

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4b387bd
feat: implement home page
Ronin619 Jan 22, 2026
3481a32
update: gitignore
Ronin619 Jan 22, 2026
0133424
update: eslint/prettier config
Ronin619 Jan 25, 2026
6e59456
implement - table for charts
Ronin619 Jan 25, 2026
e9f4f12
update handleSubmit function
Ronin619 Jan 25, 2026
8125cb2
feat: implement sparklines and mock data
Ronin619 Jan 25, 2026
c5d43f4
feat: implement form componenent
Ronin619 Jan 26, 2026
2595a39
update: layout.js with Provider
Ronin619 Jan 26, 2026
487a1f1
implement store.js
Ronin619 Jan 26, 2026
6bd1275
feat: implement store config, rootreducer, slice file
Ronin619 Jan 26, 2026
0896872
feat: implement initial state mock data
Ronin619 Jan 26, 2026
8d60fd2
feat: implement Table component with slice state
Ronin619 Jan 26, 2026
4a65c6b
feat: implement weather data row component
Ronin619 Jan 26, 2026
69d9b39
implement: fetch weather data thunk
Ronin619 Jan 27, 2026
2c0f6ed
update: thunk function to generate required data for 5 day weather fo…
Ronin619 Jan 28, 2026
aa69cda
update weather slice reducer
Ronin619 Jan 28, 2026
710e6d3
update WeatherTable to map and render graphs to table
Ronin619 Jan 28, 2026
09ba7be
update reducer - prevent duplicate cities
Ronin619 Jan 29, 2026
310f424
feat:set up react hook form
Ronin619 Jan 29, 2026
bf181a0
install yup and implement yup resolver
Ronin619 Jan 29, 2026
fb6ff6d
feat: implement yup schema
Ronin619 Jan 29, 2026
a1d8b33
update readme
Ronin619 Jan 29, 2026
5e1ee12
update: add additional table header for set default button
Ronin619 Jan 29, 2026
83c06d4
update - add set default button to table row
Ronin619 Jan 29, 2026
4379aad
update: table styling
Ronin619 Jan 29, 2026
df20865
feat: implement set default functionality
Ronin619 Jan 30, 2026
a7dd5c3
update: weather slice to check if default city exist in redux
Ronin619 Jan 30, 2026
9a75fe7
remove useState from WeatherDataRow
Ronin619 Jan 30, 2026
6378d66
feat: implement average temp, pressure, and humidity
Ronin619 Feb 3, 2026
a2b559c
prevent fetch call with space input
Ronin619 Feb 3, 2026
d5d2090
update: handleFormSubmit dispatch argument
Ronin619 Feb 3, 2026
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: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ yarn-debug.log*
yarn-error.log*

# local env files
.env*.local
.env
local.env
.env.local

# vercel
.vercel
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
## Weather Project

## SETUP

Perform the following steps in order to fetch weather data from the Open Weather API:

1. Clone the repo
2. Run "npm install"
3. Create a ".env.local" file in the root directory.
4. In .env.local, store your Open Weather API key as "NEXT_PUBLIC_API_KEY=YOUR_API_KEY_HERE". DO NOT PUT YOUR API KEY IN QUOTATION MARKS.
5. Run 'npm run dev"

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.

56 changes: 56 additions & 0 deletions app/components/Form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useState } from 'react'
import { useDispatch } from 'react-redux'
import { fetchWeatherData } from '../store/slices/weather'
import { useForm } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import * as yup from 'yup'

export default function Form() {
const schema = yup
.object({
city: yup.string().required().min(3),
})
.required()

const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
})

const dispatch = useDispatch()
const [city, setCity] = useState('')

const handleFormSubmit = () => {
const trimmedCity = city.trim()
if (!trimmedCity) return

dispatch(fetchWeatherData(trimmedCity))
setCity('')
}

return (
<form onSubmit={handleSubmit(handleFormSubmit)}>
<div className="row justify-content-center">
<div className="col-sm-4 my-1">
<input
{...register('city', { required: true })}
type="text"
className="form-control"
id="cityInput"
value={city}
placeholder="Enter City"
onChange={(e) => setCity(e.target.value)}
/>
</div>
<div className="col-auto my-1">
<button type="submit" className="btn btn-primary" id="searchBtn">
Submit
</button>
</div>
</div>
</form>
)
}
77 changes: 77 additions & 0 deletions app/components/WeatherDataRow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
Sparklines,
SparklinesLine,
SparklinesReferenceLine,
} from 'react-sparklines'

export default function WeatherDataRow({ data, onSetDefault }) {
const avgTemp = data.temperature.reduce(
(temp, currentTemp) =>
Math.round((temp + currentTemp) / data.temperature.length),
0
)
const avgPressure = data.pressure.reduce(
(pressure, currentPressure) =>
Math.round((pressure + currentPressure) / data.pressure.length),
0
)
const avgHumidity = data.humidity.reduce(
(humidity, currentHumidity) =>
Math.round((humidity + currentHumidity) / data.humidity.length),
0
)

return (
<tr>
<th scope="row">{data.city}</th>
<td>
<Sparklines
data={data.temperature}
limit={5}
width={100}
height={20}
margin={5}
>
<SparklinesLine color="blue" />
<SparklinesReferenceLine type="mean" />
</Sparklines>
<p>{avgTemp} F</p>
</td>
<td>
<Sparklines
data={data.pressure}
limit={5}
width={100}
height={20}
margin={5}
>
<SparklinesLine color="green" />
<SparklinesReferenceLine type="mean" />
</Sparklines>
<p>{avgPressure} hPa</p>
</td>
<td>
<Sparklines
data={data.humidity}
limit={5}
width={100}
height={20}
margin={5}
>
<SparklinesLine color="red" />
<SparklinesReferenceLine type="mean" />
</Sparklines>
<p>{avgHumidity} %</p>
</td>
<td>
<button
type="button"
className="btn btn-outline-secondary default-Btn btn-sm"
onClick={() => onSetDefault(data)}
>
Set Default
</button>
</td>
</tr>
)
}
47 changes: 47 additions & 0 deletions app/components/WeatherTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import WeatherDataRow from './WeatherDataRow'
import { useSelector } from 'react-redux'
import { useState, useEffect } from 'react'

export default function WeatherTable() {
const weatherData = useSelector((state) => state.weather.weatherData)
const [defaultCityData, setDefaultCityData] = useState(null)

const handleSetDefault = (data) => {
localStorage.setItem('defaultCity', JSON.stringify(data))
}

useEffect(() => {
const storedCity = localStorage.getItem('defaultCity')
if (storedCity) setDefaultCityData(JSON.parse(storedCity))
}, [])

const rowsToRender = defaultCityData
? [
defaultCityData,
...weatherData.filter((c) => c.id !== defaultCityData.id),
]
: weatherData

return (
<table className="table mt-5">
<thead>
<tr>
<th scope="col">City</th>
<th scope="col">Temperature (F)</th>
<th scope="col">Pressure (hPa)</th>
<th scope="col">Humidity (%)</th>
<th scope="col"></th>
</tr>
</thead>
<tbody className="table-group-divider">
{rowsToRender.map((city) => (
<WeatherDataRow
key={city.id}
data={city}
onSetDefault={handleSetDefault}
/>
))}
</tbody>
</table>
)
}
Binary file removed app/favicon.ico
Binary file not shown.
19 changes: 12 additions & 7 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
:root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
--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;

Expand Down Expand Up @@ -87,19 +88,23 @@ body {

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
background: rgb(var(--background-start-rgb));
}

a {
color: inherit;
text-decoration: none;
}

th[scope='row'] {
padding-top: 25px !important;
}

.default-Btn {
margin-top: 10px !important;
border-width: 3px !important;
}

@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
Expand Down
13 changes: 7 additions & 6 deletions app/layout.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
'use client'
import './globals.css'
import { Inter } from 'next/font/google'
import { Provider } from 'react-redux'
import store from './store/configureStore'
import 'bootstrap/dist/css/bootstrap.min.css'

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

export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}

export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
<body className={inter.className}>
<Provider store={store}>{children}</Provider>
</body>
</html>
)
}
98 changes: 9 additions & 89 deletions app/page.js
Original file line number Diff line number Diff line change
@@ -1,95 +1,15 @@
import Image from 'next/image'
import styles from './page.module.css'
'use client'
import Form from './components/Form'
import WeatherTable from './components/WeatherTable'

export default function Home() {
return (
<main className={styles.main}>
<div className={styles.description}>
<p>
Get started by editing&nbsp;
<code className={styles.code}>app/page.js</code>
</p>
<div>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
By{' '}
<Image
src="/vercel.svg"
alt="Vercel Logo"
className={styles.vercelLogo}
width={100}
height={24}
priority
/>
</a>
</div>
<div className="container text-center mt-5">
<h1 className="text-center pb-2 mb-4 border-bottom">Redux Weather</h1>
<Form />
<div>
<WeatherTable />
</div>

<div className={styles.center}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>

<div className={styles.grid}>
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Docs <span>-&gt;</span>
</h2>
<p>Find in-depth information about Next.js features and API.</p>
</a>

<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Learn <span>-&gt;</span>
</h2>
<p>Learn about Next.js in an interactive course with&nbsp;quizzes!</p>
</a>

<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Templates <span>-&gt;</span>
</h2>
<p>Explore the Next.js 13 playground.</p>
</a>

<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Deploy <span>-&gt;</span>
</h2>
<p>
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
</main>
</div>
)
}
3 changes: 3 additions & 0 deletions app/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { configureStore } from '@reduxjs/toolkit'

export default configureStore({})
8 changes: 8 additions & 0 deletions app/store/configureStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { configureStore } from '@reduxjs/toolkit'
import rootReducer from './rootReducer'

const store = configureStore({
reducer: rootReducer,
})

export default store
Loading