From fad38629b845e1995941ee12e510553974d10e38 Mon Sep 17 00:00:00 2001
From: Kristian
Date: Fri, 18 Sep 2026 01:00:55 +0200
Subject: [PATCH 1/6] pin favorites
---
src/components/location-list.tsx | 19 ++++++++++++++-----
src/components/starButton.tsx | 4 ++--
src/index.css | 1 +
3 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/src/components/location-list.tsx b/src/components/location-list.tsx
index b249aa9..7112473 100644
--- a/src/components/location-list.tsx
+++ b/src/components/location-list.tsx
@@ -1,10 +1,11 @@
import type { WeatherApiResponse, City } from "../hooks/fetchWeather/types"
import { useGetWeatherForCities } from "../hooks/fetchWeather/useGetWeather"
import ListItem from "./listItem"
+import { useAppStorage} from "../hooks/useStorage"
const cities: City[] = [
- { name: "Oslo", lat: 59.91273, lon: 10.74609 },
{ name: "Sørumsand", lat: 59.98621, lon: 11.24154 },
+ { name: "Oslo", lat: 59.91273, lon: 10.74609 },
{ name: "Trondheim", lat: 63.41667, lon: 10.41667 },
{ name: "Las Vegas", lat: 36.1699, lon: -115.1398 },
]
@@ -27,9 +28,8 @@ const getNextDays = (data?: WeatherApiResponse) => {
date.getUTCHours() === 12 &&
date <
new Date(
- now.getTime() -
- now.getHours() * 60 * 60 * 1000 +
- 4 * 24 * 60 * 60 * 1000
+ now.getTime() +
+ 3 * 24 * 60 * 60 * 1000
)
)
})
@@ -47,10 +47,19 @@ interface LocationListProps {
}
const LocationList = ({ onCitySelect }: LocationListProps) => {
+ const { storage } = useAppStorage()
+
+ if (storage.starredLocations.length > 0) {
+ for (const starredLocation of storage.starredLocations) {
+ if (!cities.some((city) => city.name === starredLocation)) {
+ cities.unshift({ name: starredLocation, lat: 0, lon: 0})
+ }
+ }}
+
const { isLoading, results } = useGetWeatherForCities(cities)
if (isLoading) {
- return Loading...
+ return Laster...
}
const cityWeather = cities.map((city, index) => ({
diff --git a/src/components/starButton.tsx b/src/components/starButton.tsx
index 7831590..8d35f7b 100644
--- a/src/components/starButton.tsx
+++ b/src/components/starButton.tsx
@@ -10,9 +10,9 @@ const StarButton = ({ cityName }: { cityName: string }) => {
return (
)
diff --git a/src/index.css b/src/index.css
index 8d5f416..05689fc 100644
--- a/src/index.css
+++ b/src/index.css
@@ -96,6 +96,7 @@ ul {
}
button {
+ color: #000;
border: none;
background: transparent;
cursor: pointer;
From fc0aa6d0ec47bd8fb26a5c1f4486a3a71b761c8e Mon Sep 17 00:00:00 2001
From: Kristian
Date: Fri, 18 Sep 2026 01:27:11 +0200
Subject: [PATCH 2/6] search favorite works
---
src/App.tsx | 2 +-
src/components/ViewCity.tsx | 8 ++++----
src/components/listItem.tsx | 4 +++-
src/components/location-list.tsx | 13 +++++++------
src/components/starButton.tsx | 9 ++++++---
src/hooks/useStorage.ts | 9 +++++----
src/services/storage.ts | 4 +++-
7 files changed, 29 insertions(+), 20 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index 27b10c2..78d622d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -15,7 +15,7 @@ function App() {
>
diff --git a/src/components/ViewCity.tsx b/src/components/ViewCity.tsx
index c9d59b3..1bba61e 100644
--- a/src/components/ViewCity.tsx
+++ b/src/components/ViewCity.tsx
@@ -3,11 +3,11 @@ import useGetCityCoordinates from "../hooks/fetchCities/useGetCityCoordinates"
import StarButton from "./starButton"
interface ViewCityProp {
- city: string
+ cityName: string
}
export default function ViewCity(arg: ViewCityProp) {
- const data = useGetCityCoordinates({ cityName: arg.city })
+ const data = useGetCityCoordinates({ cityName: arg.cityName })
const { isLoading, error, result } = useGetWeather(
data.result[0]?.latitude,
@@ -101,7 +101,7 @@ export default function ViewCity(arg: ViewCityProp) {
fontWeight: "500",
}}
>
- {arg.city}
+ {arg.cityName}
-
+
{
alignItems: "center",
}}
>
-
+
{item.name}:
{typeof item.precipitation === "number" && item.precipitation > 0.6
diff --git a/src/components/location-list.tsx b/src/components/location-list.tsx
index 7112473..9407808 100644
--- a/src/components/location-list.tsx
+++ b/src/components/location-list.tsx
@@ -48,15 +48,14 @@ interface LocationListProps {
const LocationList = ({ onCitySelect }: LocationListProps) => {
const { storage } = useAppStorage()
-
if (storage.starredLocations.length > 0) {
for (const starredLocation of storage.starredLocations) {
- if (!cities.some((city) => city.name === starredLocation)) {
- cities.unshift({ name: starredLocation, lat: 0, lon: 0})
+ if (!cities.some((city) => city.name === starredLocation.name)) {
+ cities.unshift(starredLocation)
}
- }}
-
- const { isLoading, results } = useGetWeatherForCities(cities)
+ }}
+
+ const { isLoading, results } = useGetWeatherForCities(cities)
if (isLoading) {
return Laster...
@@ -64,6 +63,8 @@ const LocationList = ({ onCitySelect }: LocationListProps) => {
const cityWeather = cities.map((city, index) => ({
name: city.name,
+ lat: city.lat,
+ lon: city.lon,
temperature: getTemperature(results[index]),
precipitation: getPrecipitation(results[index]),
nextDays: getNextDays(results[index]),
diff --git a/src/components/starButton.tsx b/src/components/starButton.tsx
index 8d35f7b..38e721c 100644
--- a/src/components/starButton.tsx
+++ b/src/components/starButton.tsx
@@ -1,15 +1,18 @@
+import type { City } from "../hooks/fetchWeather/types"
import { useAppStorage } from "../hooks/useStorage"
-const StarButton = ({ cityName }: { cityName: string }) => {
+const StarButton = ({ city }: { city: City }) => {
const { storage, toggleStarredLocation } = useAppStorage()
+ const isStarred = storage.starredLocations.some((starredCity) => starredCity.name === city.name)
+
const handleClick = () => {
- toggleStarredLocation(cityName)
+ toggleStarredLocation(city)
}
return (
-
+
{
+const listItem = ({ city, storage, toggleStarredLocation }: { city: cityWeather; storage: AppStorage; toggleStarredLocation: (city: City) => void }) => {
return (
-
+
{
alignItems: "center",
}}
>
-
- {item.name}:
+
+ {city.name}:
- {typeof item.precipitation === "number" && item.precipitation > 0.6
+ {typeof city.precipitation === "number" && city.precipitation > 0.6
? "🌧️"
: "⛅"}
- {typeof item.temperature === "number"
- ? `${item.temperature.toFixed()}°`
+ {typeof city.temperature === "number"
+ ? `${city.temperature.toFixed()}°`
: "—"}
@@ -43,7 +45,7 @@ const listItem = ({ city: item }: { city: cityWeather }) => {
alignSelf: "end",
}}
>
- {item.nextDays?.map((day) => (
+ {city.nextDays?.map((day) => (
{typeof day.precipitation === "number" && day.precipitation > 0.6
? "🌧️"
diff --git a/src/components/location-list.tsx b/src/components/location-list.tsx
index 9407808..66a5321 100644
--- a/src/components/location-list.tsx
+++ b/src/components/location-list.tsx
@@ -1,7 +1,7 @@
import type { WeatherApiResponse, City } from "../hooks/fetchWeather/types"
import { useGetWeatherForCities } from "../hooks/fetchWeather/useGetWeather"
import ListItem from "./listItem"
-import { useAppStorage} from "../hooks/useStorage"
+import type { AppStorage } from "../services/storage"
const cities: City[] = [
{ name: "Sørumsand", lat: 59.98621, lon: 11.24154 },
@@ -44,11 +44,15 @@ const getNextDays = (data?: WeatherApiResponse) => {
interface LocationListProps {
onCitySelect: (cityName: string) => void
+ storage: AppStorage
+ toggleStarredLocation: (city: City) => void
}
-const LocationList = ({ onCitySelect }: LocationListProps) => {
- const { storage } = useAppStorage()
+const LocationList = ({ onCitySelect, storage, toggleStarredLocation }: LocationListProps) => {
if (storage.starredLocations.length > 0) {
+ if (storage.starredLocations.length > 4) {
+ cities.splice(0, 4)
+ }
for (const starredLocation of storage.starredLocations) {
if (!cities.some((city) => city.name === starredLocation.name)) {
cities.unshift(starredLocation)
@@ -109,7 +113,7 @@ const LocationList = ({ onCitySelect }: LocationListProps) => {
key={item.name}
style={{ width: "80%" }}
>
-
+
))}
diff --git a/src/components/starButton.tsx b/src/components/starButton.tsx
index 38e721c..530e9c8 100644
--- a/src/components/starButton.tsx
+++ b/src/components/starButton.tsx
@@ -1,8 +1,7 @@
import type { City } from "../hooks/fetchWeather/types"
-import { useAppStorage } from "../hooks/useStorage"
+import type { AppStorage } from "../services/storage";
-const StarButton = ({ city }: { city: City }) => {
- const { storage, toggleStarredLocation } = useAppStorage()
+const StarButton = ({ city, storage, toggleStarredLocation }: { city: City; storage: AppStorage; toggleStarredLocation: (city: City) => void }) => {
const isStarred = storage.starredLocations.some((starredCity) => starredCity.name === city.name)
diff --git a/src/hooks/useStorage.ts b/src/hooks/useStorage.ts
index fc21ae1..b8074a2 100644
--- a/src/hooks/useStorage.ts
+++ b/src/hooks/useStorage.ts
@@ -24,9 +24,9 @@ export function useAppStorage() {
const toggleStarredLocation = useCallback(
(location: City) => {
updateStorage((prev) => {
- const exists = prev.starredLocations.includes(location)
+ const exists = prev.starredLocations.some((loc) => loc.name === location.name)
const starredLocations = exists
- ? prev.starredLocations.filter((id) => id !== location)
+ ? prev.starredLocations.filter((loc) => loc.name !== location.name)
: [...prev.starredLocations, location]
return { ...prev, starredLocations }
From 6b246e5a43c7276deb224912ef5d1fb39eb6573d Mon Sep 17 00:00:00 2001
From: Kristian
Date: Fri, 18 Sep 2026 17:54:04 +0200
Subject: [PATCH 4/6] state actually works
---
src/App.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index 84255ec..3b348ea 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -10,14 +10,14 @@ function App() {
const handleSearch = (query: string) => {
setSelectedCity(query)
}
- const storageHook = useAppStorage()
+ const {storage, toggleStarredLocation} = useAppStorage()
return (
<>
>
From f926fc13c7c240267740b66faaf1d29ca12c62e8 Mon Sep 17 00:00:00 2001
From: Kristian
Date: Fri, 18 Sep 2026 18:24:31 +0200
Subject: [PATCH 5/6] tests and everything works
---
src/App.tsx | 9 ++++--
src/components/ViewCity.tsx | 15 ++++++++--
src/components/listItem.tsx | 13 +++++++--
src/components/location-list.tsx | 39 +++++++++++++------------
src/components/starButton.tsx | 20 +++++++++----
src/hooks/useStorage.ts | 4 ++-
tests/components/ViewCity.test.tsx | 6 ++--
tests/components/listItem.test.tsx | 6 +++-
tests/components/location-list.test.tsx | 2 +-
9 files changed, 76 insertions(+), 38 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index 3b348ea..fd4b486 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -10,14 +10,17 @@ function App() {
const handleSearch = (query: string) => {
setSelectedCity(query)
}
- const {storage, toggleStarredLocation} = useAppStorage()
+ const storageHook = useAppStorage()
return (
<>
>
diff --git a/src/components/ViewCity.tsx b/src/components/ViewCity.tsx
index 1381e37..8f4c7aa 100644
--- a/src/components/ViewCity.tsx
+++ b/src/components/ViewCity.tsx
@@ -6,8 +6,10 @@ import type { City } from "../hooks/fetchWeather/types"
interface ViewCityProp {
cityName: string
- storage: AppStorage
- toggleStarredLocation: (city: City) => void
+ storageHook?: {
+ storage: AppStorage
+ toggleStarredLocation: (city: City) => void
+ }
}
export default function ViewCity(arg: ViewCityProp) {
@@ -118,7 +120,14 @@ export default function ViewCity(arg: ViewCityProp) {
-
+
void }) => {
+const listItem = ({
+ city,
+ storageHook,
+}: {
+ city: cityWeather
+ storageHook?: {
+ storage: AppStorage
+ toggleStarredLocation: (city: City) => void
+ }
+}) => {
return (
-
+
{city.name}:
{typeof city.precipitation === "number" && city.precipitation > 0.6
diff --git a/src/components/location-list.tsx b/src/components/location-list.tsx
index 66a5321..e6640bd 100644
--- a/src/components/location-list.tsx
+++ b/src/components/location-list.tsx
@@ -26,11 +26,7 @@ const getNextDays = (data?: WeatherApiResponse) => {
return (
date >= now &&
date.getUTCHours() === 12 &&
- date <
- new Date(
- now.getTime() +
- 3 * 24 * 60 * 60 * 1000
- )
+ date < new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000)
)
})
.map((entry) => ({
@@ -44,22 +40,27 @@ const getNextDays = (data?: WeatherApiResponse) => {
interface LocationListProps {
onCitySelect: (cityName: string) => void
- storage: AppStorage
- toggleStarredLocation: (city: City) => void
+ storageHook?: {
+ storage: AppStorage
+ toggleStarredLocation: (city: City) => void
+ }
}
-const LocationList = ({ onCitySelect, storage, toggleStarredLocation }: LocationListProps) => {
- if (storage.starredLocations.length > 0) {
- if (storage.starredLocations.length > 4) {
- cities.splice(0, 4)
- }
- for (const starredLocation of storage.starredLocations) {
- if (!cities.some((city) => city.name === starredLocation.name)) {
- cities.unshift(starredLocation)
+const LocationList = ({ onCitySelect, storageHook }: LocationListProps) => {
+ if (storageHook) {
+ if (storageHook.storage.starredLocations.length > 0) {
+ if (storageHook.storage.starredLocations.length >= 4) {
+ cities.splice(0, 4)
+ }
+ for (const starredLocation of storageHook.storage.starredLocations) {
+ if (!cities.some((city) => city.name === starredLocation.name)) {
+ cities.unshift(starredLocation)
+ }
}
- }}
-
- const { isLoading, results } = useGetWeatherForCities(cities)
+ }
+ }
+
+ const { isLoading, results } = useGetWeatherForCities(cities)
if (isLoading) {
return Laster...
@@ -113,7 +114,7 @@ const LocationList = ({ onCitySelect, storage, toggleStarredLocation }: Location
key={item.name}
style={{ width: "80%" }}
>
-
+
))}
diff --git a/src/components/starButton.tsx b/src/components/starButton.tsx
index 530e9c8..83a0408 100644
--- a/src/components/starButton.tsx
+++ b/src/components/starButton.tsx
@@ -1,12 +1,22 @@
import type { City } from "../hooks/fetchWeather/types"
-import type { AppStorage } from "../services/storage";
+import type { AppStorage } from "../services/storage"
-const StarButton = ({ city, storage, toggleStarredLocation }: { city: City; storage: AppStorage; toggleStarredLocation: (city: City) => void }) => {
-
- const isStarred = storage.starredLocations.some((starredCity) => starredCity.name === city.name)
+const StarButton = ({
+ city,
+ storageHook,
+}: {
+ city: City
+ storageHook?: {
+ storage: AppStorage
+ toggleStarredLocation: (city: City) => void
+ }
+}) => {
+ const isStarred = storageHook?.storage.starredLocations.some(
+ (starredCity) => starredCity.name === city.name
+ )
const handleClick = () => {
- toggleStarredLocation(city)
+ storageHook?.toggleStarredLocation(city)
}
return (
diff --git a/src/hooks/useStorage.ts b/src/hooks/useStorage.ts
index b8074a2..4725873 100644
--- a/src/hooks/useStorage.ts
+++ b/src/hooks/useStorage.ts
@@ -24,7 +24,9 @@ export function useAppStorage() {
const toggleStarredLocation = useCallback(
(location: City) => {
updateStorage((prev) => {
- const exists = prev.starredLocations.some((loc) => loc.name === location.name)
+ const exists = prev.starredLocations.some(
+ (loc) => loc.name === location.name
+ )
const starredLocations = exists
? prev.starredLocations.filter((loc) => loc.name !== location.name)
: [...prev.starredLocations, location]
diff --git a/tests/components/ViewCity.test.tsx b/tests/components/ViewCity.test.tsx
index 4937ef3..442c694 100644
--- a/tests/components/ViewCity.test.tsx
+++ b/tests/components/ViewCity.test.tsx
@@ -51,7 +51,7 @@ describe("ViewCity", () => {
result: [{ latitude: 59.9, longitude: 10.7, country: "Norway" }],
})
- render()
+ render()
expect(screen.getByText("Oslo")).toBeTruthy()
expect(screen.getByText("Norway")).toBeTruthy()
@@ -68,7 +68,7 @@ describe("ViewCity", () => {
it("should render a loading message while weather data is loading", () => {
mockUseGetWeather.mockReturnValue({ isLoading: true, error: null })
- render()
+ render()
expect(screen.getByText("Laster...")).toBeTruthy()
})
@@ -79,7 +79,7 @@ describe("ViewCity", () => {
error: new Error("Request failed"),
})
- render()
+ render()
expect(screen.getByText("Det skjedde en feil")).toBeTruthy()
})
diff --git a/tests/components/listItem.test.tsx b/tests/components/listItem.test.tsx
index 0fe4d7f..4369e9f 100644
--- a/tests/components/listItem.test.tsx
+++ b/tests/components/listItem.test.tsx
@@ -10,6 +10,8 @@ describe("ListItem", () => {
{
})
it("should show fallback values when weather data is unavailable", () => {
- render()
+ render(
+
+ )
expect(screen.getByText("⛅")).toBeTruthy()
expect(screen.getByText("—")).toBeTruthy()
diff --git a/tests/components/location-list.test.tsx b/tests/components/location-list.test.tsx
index adb9d99..d909f04 100644
--- a/tests/components/location-list.test.tsx
+++ b/tests/components/location-list.test.tsx
@@ -36,7 +36,7 @@ describe("LocationList", () => {
render( undefined} />)
- expect(screen.getByText("Loading...")).toBeTruthy()
+ expect(screen.getByText("Laster...")).toBeTruthy()
})
it("should render a weather row for every configured city", () => {
From f320c869ae75f13034e4ff9ef4b40fb3b649601a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Henry=20Gr=C3=A6sberg?=
Date: Fri, 18 Sep 2026 18:32:43 +0200
Subject: [PATCH 6/6] Add automatic lint fixing for simple issues
---
package.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/package.json b/package.json
index 5fc0534..e0b0da8 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
+ "lint-fix": "eslint . --fix",
"preview": "vite preview",
"fmt": "npx prettier . --write",
"test": "vitest run",