From 477ab1ca0c9d10742f251160a74c88287c25092a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Gr=C3=A6sberg?= Date: Thu, 17 Sep 2026 19:06:35 +0200 Subject: [PATCH 1/4] Move everything --- .github/workflows/deploy-to-vm.yaml | 2 - .github/workflows/lint-and-format.yaml | 7 +- "v\303\246r/.prettierrc" => .prettierrc | 0 README.md | 85 ++++++++++++++---- .../eslint.config.js" => eslint.config.js | 0 "v\303\246r/index.html" => index.html | 0 .../package-lock.json" => package-lock.json | 0 "v\303\246r/package.json" => package.json | 0 .../public/favicon.svg" => public/favicon.svg | 0 .../public/icons.svg" => public/icons.svg | 0 "v\303\246r/src/App.css" => src/App.css | 0 "v\303\246r/src/App.tsx" => src/App.tsx | 0 .../assets/hero.png" => src/assets/hero.png | Bin .../assets/react.svg" => src/assets/react.svg | 0 .../assets/sivertbbdahls.png | Bin .../assets/vite.svg" => src/assets/vite.svg | 0 .../components/ViewCity.tsx | 0 .../components/listItem.tsx | 0 .../components/location-list.tsx | 0 .../hooks/fetchWeather/types.ts | 0 .../hooks/fetchWeather/useGetWeather.ts | 0 .../hooks/useGetCityCoordinates.ts | 0 .../useStorage.ts" => src/hooks/useStorage.ts | 0 "v\303\246r/src/index.css" => src/index.css | 0 "v\303\246r/src/main.tsx" => src/main.tsx | 0 .../storage.ts" => src/services/storage.ts | 0 .../tsconfig.app.json" => tsconfig.app.json | 0 "v\303\246r/tsconfig.json" => tsconfig.json | 0 .../tsconfig.node.json" => tsconfig.node.json | 0 "v\303\246r/vite.config.ts" => vite.config.ts | 0 "v\303\246r/README.md" | 75 ---------------- 31 files changed, 70 insertions(+), 99 deletions(-) rename "v\303\246r/.prettierrc" => .prettierrc (100%) rename "v\303\246r/eslint.config.js" => eslint.config.js (100%) rename "v\303\246r/index.html" => index.html (100%) rename "v\303\246r/package-lock.json" => package-lock.json (100%) rename "v\303\246r/package.json" => package.json (100%) rename "v\303\246r/public/favicon.svg" => public/favicon.svg (100%) rename "v\303\246r/public/icons.svg" => public/icons.svg (100%) rename "v\303\246r/src/App.css" => src/App.css (100%) rename "v\303\246r/src/App.tsx" => src/App.tsx (100%) rename "v\303\246r/src/assets/hero.png" => src/assets/hero.png (100%) rename "v\303\246r/src/assets/react.svg" => src/assets/react.svg (100%) rename "v\303\246r/src/assets/sivertbbdahls.png" => src/assets/sivertbbdahls.png (100%) rename "v\303\246r/src/assets/vite.svg" => src/assets/vite.svg (100%) rename "v\303\246r/src/components/ViewCity.tsx" => src/components/ViewCity.tsx (100%) rename "v\303\246r/src/components/listItem.tsx" => src/components/listItem.tsx (100%) rename "v\303\246r/src/components/location-list.tsx" => src/components/location-list.tsx (100%) rename "v\303\246r/src/hooks/fetchWeather/types.ts" => src/hooks/fetchWeather/types.ts (100%) rename "v\303\246r/src/hooks/fetchWeather/useGetWeather.ts" => src/hooks/fetchWeather/useGetWeather.ts (100%) rename "v\303\246r/src/hooks/useGetCityCoordinates.ts" => src/hooks/useGetCityCoordinates.ts (100%) rename "v\303\246r/src/hooks/useStorage.ts" => src/hooks/useStorage.ts (100%) rename "v\303\246r/src/index.css" => src/index.css (100%) rename "v\303\246r/src/main.tsx" => src/main.tsx (100%) rename "v\303\246r/src/services/storage.ts" => src/services/storage.ts (100%) rename "v\303\246r/tsconfig.app.json" => tsconfig.app.json (100%) rename "v\303\246r/tsconfig.json" => tsconfig.json (100%) rename "v\303\246r/tsconfig.node.json" => tsconfig.node.json (100%) rename "v\303\246r/vite.config.ts" => vite.config.ts (100%) delete mode 100644 "v\303\246r/README.md" diff --git a/.github/workflows/deploy-to-vm.yaml b/.github/workflows/deploy-to-vm.yaml index 10aa960..8ae18d7 100644 --- a/.github/workflows/deploy-to-vm.yaml +++ b/.github/workflows/deploy-to-vm.yaml @@ -34,13 +34,11 @@ jobs: ref: ${{ github.event.workflow_run.head_sha }} - name: Install dependencies - working-directory: vær run: | mkdir -p "$TMPDIR" npm ci --no-audit --no-fund - name: Build app - working-directory: vær run: npm run build - name: Deploy app to Apache diff --git a/.github/workflows/lint-and-format.yaml b/.github/workflows/lint-and-format.yaml index b8d2c3c..db92580 100644 --- a/.github/workflows/lint-and-format.yaml +++ b/.github/workflows/lint-and-format.yaml @@ -9,7 +9,7 @@ on: - opened jobs: - deploy: + lint-and-format: runs-on: [self-hosted] timeout-minutes: 10 @@ -23,15 +23,12 @@ jobs: uses: actions/checkout@v4 - name: Install dependencies - working-directory: vær run: | mkdir -p "$TMPDIR" npm ci --no-audit --no-fund - name: Run Prettier check - working-directory: vær - run: npx exec prettier . --check + run: npx prettier . --check - name: Run ESLint - working-directory: vær run: npm run lint diff --git "a/v\303\246r/.prettierrc" b/.prettierrc similarity index 100% rename from "v\303\246r/.prettierrc" rename to .prettierrc diff --git a/README.md b/README.md index 6c31c05..2d31cb6 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,75 @@ -# T09-Project-1 +# React + TypeScript + Vite -Repository for team IT2810-H26-T09 in IT2810-H26. +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. -## Working in this repository +Currently, two official plugins are available: -`main` is protected — you cannot push to it directly. Work on a branch and -open a pull request: +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) -```bash -git checkout -b my-feature -git push -u origin my-feature ``` -Before a pull request can be merged: +You can also install [eslint-plugin-react-x](https://npmx.dev/package/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://npmx.dev/package/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' -1. **A member of IT2810-H26-T09 must approve it.** You cannot approve your own pull - request, and approvals from staff do not count — you review each other's - code. -2. **Every review comment must be marked resolved.** Reply to each one, fix it - or explain why not, then click *Resolve conversation*. -3. **Pushing new commits dismisses the approval**, so if you change something - after being approved, ask for another review. This is deliberate: what was - approved is what gets merged. \ No newline at end of file +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) + +``` diff --git "a/v\303\246r/eslint.config.js" b/eslint.config.js similarity index 100% rename from "v\303\246r/eslint.config.js" rename to eslint.config.js diff --git "a/v\303\246r/index.html" b/index.html similarity index 100% rename from "v\303\246r/index.html" rename to index.html diff --git "a/v\303\246r/package-lock.json" b/package-lock.json similarity index 100% rename from "v\303\246r/package-lock.json" rename to package-lock.json diff --git "a/v\303\246r/package.json" b/package.json similarity index 100% rename from "v\303\246r/package.json" rename to package.json diff --git "a/v\303\246r/public/favicon.svg" b/public/favicon.svg similarity index 100% rename from "v\303\246r/public/favicon.svg" rename to public/favicon.svg diff --git "a/v\303\246r/public/icons.svg" b/public/icons.svg similarity index 100% rename from "v\303\246r/public/icons.svg" rename to public/icons.svg diff --git "a/v\303\246r/src/App.css" b/src/App.css similarity index 100% rename from "v\303\246r/src/App.css" rename to src/App.css diff --git "a/v\303\246r/src/App.tsx" b/src/App.tsx similarity index 100% rename from "v\303\246r/src/App.tsx" rename to src/App.tsx diff --git "a/v\303\246r/src/assets/hero.png" b/src/assets/hero.png similarity index 100% rename from "v\303\246r/src/assets/hero.png" rename to src/assets/hero.png diff --git "a/v\303\246r/src/assets/react.svg" b/src/assets/react.svg similarity index 100% rename from "v\303\246r/src/assets/react.svg" rename to src/assets/react.svg diff --git "a/v\303\246r/src/assets/sivertbbdahls.png" b/src/assets/sivertbbdahls.png similarity index 100% rename from "v\303\246r/src/assets/sivertbbdahls.png" rename to src/assets/sivertbbdahls.png diff --git "a/v\303\246r/src/assets/vite.svg" b/src/assets/vite.svg similarity index 100% rename from "v\303\246r/src/assets/vite.svg" rename to src/assets/vite.svg diff --git "a/v\303\246r/src/components/ViewCity.tsx" b/src/components/ViewCity.tsx similarity index 100% rename from "v\303\246r/src/components/ViewCity.tsx" rename to src/components/ViewCity.tsx diff --git "a/v\303\246r/src/components/listItem.tsx" b/src/components/listItem.tsx similarity index 100% rename from "v\303\246r/src/components/listItem.tsx" rename to src/components/listItem.tsx diff --git "a/v\303\246r/src/components/location-list.tsx" b/src/components/location-list.tsx similarity index 100% rename from "v\303\246r/src/components/location-list.tsx" rename to src/components/location-list.tsx diff --git "a/v\303\246r/src/hooks/fetchWeather/types.ts" b/src/hooks/fetchWeather/types.ts similarity index 100% rename from "v\303\246r/src/hooks/fetchWeather/types.ts" rename to src/hooks/fetchWeather/types.ts diff --git "a/v\303\246r/src/hooks/fetchWeather/useGetWeather.ts" b/src/hooks/fetchWeather/useGetWeather.ts similarity index 100% rename from "v\303\246r/src/hooks/fetchWeather/useGetWeather.ts" rename to src/hooks/fetchWeather/useGetWeather.ts diff --git "a/v\303\246r/src/hooks/useGetCityCoordinates.ts" b/src/hooks/useGetCityCoordinates.ts similarity index 100% rename from "v\303\246r/src/hooks/useGetCityCoordinates.ts" rename to src/hooks/useGetCityCoordinates.ts diff --git "a/v\303\246r/src/hooks/useStorage.ts" b/src/hooks/useStorage.ts similarity index 100% rename from "v\303\246r/src/hooks/useStorage.ts" rename to src/hooks/useStorage.ts diff --git "a/v\303\246r/src/index.css" b/src/index.css similarity index 100% rename from "v\303\246r/src/index.css" rename to src/index.css diff --git "a/v\303\246r/src/main.tsx" b/src/main.tsx similarity index 100% rename from "v\303\246r/src/main.tsx" rename to src/main.tsx diff --git "a/v\303\246r/src/services/storage.ts" b/src/services/storage.ts similarity index 100% rename from "v\303\246r/src/services/storage.ts" rename to src/services/storage.ts diff --git "a/v\303\246r/tsconfig.app.json" b/tsconfig.app.json similarity index 100% rename from "v\303\246r/tsconfig.app.json" rename to tsconfig.app.json diff --git "a/v\303\246r/tsconfig.json" b/tsconfig.json similarity index 100% rename from "v\303\246r/tsconfig.json" rename to tsconfig.json diff --git "a/v\303\246r/tsconfig.node.json" b/tsconfig.node.json similarity index 100% rename from "v\303\246r/tsconfig.node.json" rename to tsconfig.node.json diff --git "a/v\303\246r/vite.config.ts" b/vite.config.ts similarity index 100% rename from "v\303\246r/vite.config.ts" rename to vite.config.ts diff --git "a/v\303\246r/README.md" "b/v\303\246r/README.md" deleted file mode 100644 index 2d31cb6..0000000 --- "a/v\303\246r/README.md" +++ /dev/null @@ -1,75 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) - -## React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) - -``` - -You can also install [eslint-plugin-react-x](https://npmx.dev/package/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://npmx.dev/package/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) - -``` From 115228e14c0c89b04a0ee1f7f8fd475ebf8df99e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Gr=C3=A6sberg?= Date: Thu, 17 Sep 2026 19:10:44 +0200 Subject: [PATCH 2/4] Format and lint almost everything --- src/App.tsx | 1 - src/main.tsx | 1 - src/services/storage.ts | 11 ++++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 6f346a9..e595bb9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,6 @@ import './App.css' import LocationList from './components/location-list' import ViewCity from './components/ViewCity' -import useGetWeather from './hooks/fetchWeather/useGetWeather' function App() { diff --git a/src/main.tsx b/src/main.tsx index accea2b..bfe22e0 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,7 +2,6 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.tsx' -import ViewCity from './components/ViewCity.tsx' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' diff --git a/src/services/storage.ts b/src/services/storage.ts index 2a8f0b9..9e513a0 100644 --- a/src/services/storage.ts +++ b/src/services/storage.ts @@ -15,7 +15,7 @@ const DEFAULT_STATE: AppStorage = { }; export function getData(): AppStorage{ - try{ + try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return DEFAULT_STATE; @@ -25,17 +25,18 @@ export function getData(): AppStorage{ return {...DEFAULT_STATE, ...parsed}; } - catch (err){ + catch { console.error("Failed to load store data"); return DEFAULT_STATE; - } + } } export function saveData(state: AppStorage): void{ - try{ + try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); - }catch(err){ + } + catch (err) { console.error("Failed saving", err); } } From a73cca51ec30212b73e8260ab702d6ab10267999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Gr=C3=A6sberg?= Date: Thu, 17 Sep 2026 19:11:48 +0200 Subject: [PATCH 3/4] Format everything actually --- README.md | 20 +- eslint.config.js | 16 +- src/App.css | 2 +- src/App.tsx | 13 +- src/components/ViewCity.tsx | 51 +++-- src/components/listItem.tsx | 61 +++-- src/components/location-list.tsx | 85 ++++--- src/hooks/fetchWeather/types.ts | 290 ++++++++++++------------ src/hooks/fetchWeather/useGetWeather.ts | 23 +- src/hooks/useGetCityCoordinates.ts | 29 +-- src/hooks/useStorage.ts | 63 ++--- src/index.css | 8 +- src/main.tsx | 18 +- src/services/storage.ts | 65 +++--- vite.config.ts | 4 +- 15 files changed, 401 insertions(+), 347 deletions(-) diff --git a/README.md b/README.md index 2d31cb6..813ba3e 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ If you are developing a production application, we recommend updating the config ```js export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(["dist"]), { - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], extends: [ // Other configs... @@ -34,42 +34,40 @@ export default defineConfig([ ], languageOptions: { parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], + project: ["./tsconfig.node.json", "./tsconfig.app.json"], tsconfigRootDir: import.meta.dirname, }, // other options... }, }, ]) - ``` You can also install [eslint-plugin-react-x](https://npmx.dev/package/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://npmx.dev/package/eslint-plugin-react-dom) for React-specific lint rules: ```js // eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' +import reactX from "eslint-plugin-react-x" +import reactDom from "eslint-plugin-react-dom" export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(["dist"]), { - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], extends: [ // Other configs... // Enable lint rules for React - reactX.configs['recommended-typescript'], + reactX.configs["recommended-typescript"], // Enable lint rules for React DOM reactDom.configs.recommended, ], languageOptions: { parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], + project: ["./tsconfig.node.json", "./tsconfig.app.json"], tsconfigRootDir: import.meta.dirname, }, // other options... }, }, ]) - ``` diff --git a/eslint.config.js b/eslint.config.js index ef614d2..9934c1c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,14 +1,14 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' -import { defineConfig, globalIgnores } from 'eslint/config' +import js from "@eslint/js" +import globals from "globals" +import reactHooks from "eslint-plugin-react-hooks" +import reactRefresh from "eslint-plugin-react-refresh" +import tseslint from "typescript-eslint" +import { defineConfig, globalIgnores } from "eslint/config" export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(["dist"]), { - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], extends: [ js.configs.recommended, tseslint.configs.recommended, diff --git a/src/App.css b/src/App.css index f90339d..f460279 100644 --- a/src/App.css +++ b/src/App.css @@ -167,7 +167,7 @@ &::before, &::after { - content: ''; + content: ""; position: absolute; top: -4.5px; border: 5px solid transparent; diff --git a/src/App.tsx b/src/App.tsx index e595bb9..a1fc0a6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,14 @@ -import './App.css' -import LocationList from './components/location-list' -import ViewCity from './components/ViewCity' +import "./App.css" +import LocationList from "./components/location-list" +import ViewCity from "./components/ViewCity" function App() { - return ( <>
- - -
+ + +
) diff --git a/src/components/ViewCity.tsx b/src/components/ViewCity.tsx index 7a2c13a..f11f128 100644 --- a/src/components/ViewCity.tsx +++ b/src/components/ViewCity.tsx @@ -1,32 +1,33 @@ -import useGetWeather from "../hooks/fetchWeather/useGetWeather"; -import useGetCityCoordinates from "../hooks/useGetCityCoordinates"; +import useGetWeather from "../hooks/fetchWeather/useGetWeather" +import useGetCityCoordinates from "../hooks/useGetCityCoordinates" interface ViewCityProp { - city: string + city: string } -export default function ViewCity(arg: ViewCityProp){ - - const data = useGetCityCoordinates(arg.city); - - const {isLoading, error, result} = useGetWeather(data.result[0]?.latitude, data.result[0]?.longitude); +export default function ViewCity(arg: ViewCityProp) { + const data = useGetCityCoordinates(arg.city) - if(isLoading){ - return

Laster

; - } - if(error){ + const { isLoading, error, result } = useGetWeather( + data.result[0]?.latitude, + data.result[0]?.longitude + ) - return

Det skjedde en feil

- } - const firstTimeStep = result?.properties.timeseries[0]; - const windSpeed = firstTimeStep?.data.instant.details?.wind_speed; - const temperature = firstTimeStep?.data.instant.details?.air_temperature; + if (isLoading) { + return

Laster

+ } + if (error) { + return

Det skjedde en feil

+ } + const firstTimeStep = result?.properties.timeseries[0] + const windSpeed = firstTimeStep?.data.instant.details?.wind_speed + const temperature = firstTimeStep?.data.instant.details?.air_temperature - return ( -
-
Været i {arg.city}
-

Temperatur: {temperature}

-

Vindhastighet: {windSpeed}

-
- ); -} \ No newline at end of file + return ( +
+
Været i {arg.city}
+

Temperatur: {temperature}

+

Vindhastighet: {windSpeed}

+
+ ) +} diff --git a/src/components/listItem.tsx b/src/components/listItem.tsx index 91f3436..b55799b 100644 --- a/src/components/listItem.tsx +++ b/src/components/listItem.tsx @@ -1,35 +1,50 @@ - - type cityWeather = { name: string temperature?: number precipitation?: number - nextDays?: { time: string; temperature: number | undefined}[] + nextDays?: { time: string; temperature: number | undefined }[] } - const listItem = ({ city: item }: { city: cityWeather }) => { - return ( -
  • - - {item.name}: - - {typeof item.precipitation === 'number' && item.precipitation > 0.6 ? '🌧️' : '⛅'} +
  • + + {item.name}: + + {typeof item.precipitation === "number" && item.precipitation > 0.6 + ? "🌧️" + : "⛅"} + +

    + {typeof item.temperature === "number" + ? `${item.temperature.toFixed()}°` + : "—"} +

    +
    + +
    + {item.nextDays?.map((day) => ( + + {typeof day.temperature === "number" + ? `${day.temperature.toFixed()}°` + : "—"} -

    {typeof item.temperature === 'number' ? `${item.temperature.toFixed()}°` : '—'}

    - - - -
    - {item.nextDays?.map((day) => ( - - {typeof day.temperature === 'number' ? `${day.temperature.toFixed()}°` : '—'} - - ))} - -
    -
  • + ))} + + ) } diff --git a/src/components/location-list.tsx b/src/components/location-list.tsx index d28f8ff..9476553 100644 --- a/src/components/location-list.tsx +++ b/src/components/location-list.tsx @@ -1,6 +1,6 @@ -import type { WeatherApiResponse } from '../hooks/fetchWeather/types' -import useGetWeather from '../hooks/fetchWeather/useGetWeather' -import ListItem from './listItem' +import type { WeatherApiResponse } from "../hooks/fetchWeather/types" +import useGetWeather from "../hooks/fetchWeather/useGetWeather" +import ListItem from "./listItem" type City = { name: string @@ -9,35 +9,39 @@ type City = { } const cities: City[] = [ - { name: 'Oslo', lat: 59.91273, lon: 10.74609 }, - { name: 'Sørumsand', lat: 59.98621, lon: 11.24154 }, - { name: 'Trondheim', lat: 63.41667, lon: 10.41667 }, - { name: 'Las Vegas', lat: 36.1699, lon: -115.1398 }, + { name: "Oslo", lat: 59.91273, lon: 10.74609 }, + { name: "Sørumsand", lat: 59.98621, lon: 11.24154 }, + { name: "Trondheim", lat: 63.41667, lon: 10.41667 }, + { name: "Las Vegas", lat: 36.1699, lon: -115.1398 }, ] const getTemperature = (data?: WeatherApiResponse) => data?.properties?.timeseries?.[0]?.data?.instant?.details?.air_temperature const getPrecipitation = (data?: WeatherApiResponse) => - data?.properties?.timeseries?.[0]?.data?.next_6_hours?.details?.precipitation_amount + data?.properties?.timeseries?.[0]?.data?.next_6_hours?.details + ?.precipitation_amount const getNextDays = (data?: WeatherApiResponse) => { - const now = new Date() + const now = new Date() - const noonTemperatures = data?.properties.timeseries + const noonTemperatures = data?.properties.timeseries .filter((entry) => { - const date = new Date(entry.time) - return date >= now && date.getUTCHours() === 12 && date < new Date(now.getTime() + 3* 24 * 60 * 60 * 1000) + const date = new Date(entry.time) + return ( + date >= now && + date.getUTCHours() === 12 && + date < new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000) + ) }) .map((entry) => ({ - time: entry.time, - temperature: entry.data.instant.details?.air_temperature, + time: entry.time, + temperature: entry.data.instant.details?.air_temperature, })) - return noonTemperatures + return noonTemperatures } - const LocationList = () => { const weatherResults = cities.map((city) => useGetWeather(city.lat, city.lon)) @@ -59,20 +63,45 @@ const LocationList = () => { })) return ( -
    -
    - City - Next 3 Days +
    +
    + City + Next 3 Days
    -
    - -
      +
      + +
        {cityWeather.map((item) => ( - - //
      • - // {item.name}: {typeof item.temperature === 'number' ? `${item.temperature}°` : '—'} - // {typeof item.precipitation === 'number' ? `, ${item.precipitation} mm` : ''} - //
      • + + //
      • + // {item.name}: {typeof item.temperature === 'number' ? `${item.temperature}°` : '—'} + // {typeof item.precipitation === 'number' ? `, ${item.precipitation} mm` : ''} + //
      • ))}
    diff --git a/src/hooks/fetchWeather/types.ts b/src/hooks/fetchWeather/types.ts index 702841c..1c008ba 100644 --- a/src/hooks/fetchWeather/types.ts +++ b/src/hooks/fetchWeather/types.ts @@ -1,178 +1,178 @@ export const weatherSymbolCodes = [ - "clearsky_day", - "clearsky_night", - "clearsky_polartwilight", - "fair_day", - "fair_night", - "fair_polartwilight", - "lightssnowshowersandthunder_day", - "lightssnowshowersandthunder_night", - "lightssnowshowersandthunder_polartwilight", - "lightsnowshowers_day", - "lightsnowshowers_night", - "lightsnowshowers_polartwilight", - "heavyrainandthunder", - "heavysnowandthunder", - "rainandthunder", - "heavysleetshowersandthunder_day", - "heavysleetshowersandthunder_night", - "heavysleetshowersandthunder_polartwilight", - "heavysnow", - "heavyrainshowers_day", - "heavyrainshowers_night", - "heavyrainshowers_polartwilight", - "lightsleet", - "heavyrain", - "lightrainshowers_day", - "lightrainshowers_night", - "lightrainshowers_polartwilight", - "heavysleetshowers_day", - "heavysleetshowers_night", - "heavysleetshowers_polartwilight", - "lightsleetshowers_day", - "lightsleetshowers_night", - "lightsleetshowers_polartwilight", - "snow", - "heavyrainshowersandthunder_day", - "heavyrainshowersandthunder_night", - "heavyrainshowersandthunder_polartwilight", - "snowshowers_day", - "snowshowers_night", - "snowshowers_polartwilight", - "fog", - "snowshowersandthunder_day", - "snowshowersandthunder_night", - "snowshowersandthunder_polartwilight", - "lightsnowandthunder", - "heavysleetandthunder", - "lightrain", - "rainshowersandthunder_day", - "rainshowersandthunder_night", - "rainshowersandthunder_polartwilight", - "rain", - "lightsnow", - "lightrainshowersandthunder_day", - "lightrainshowersandthunder_night", - "lightrainshowersandthunder_polartwilight", - "heavysleet", - "sleetandthunder", - "lightrainandthunder", - "sleet", - "lightssleetshowersandthunder_day", - "lightssleetshowersandthunder_night", - "lightssleetshowersandthunder_polartwilight", - "lightsleetandthunder", - "partlycloudy_day", - "partlycloudy_night", - "partlycloudy_polartwilight", - "sleetshowersandthunder_day", - "sleetshowersandthunder_night", - "sleetshowersandthunder_polartwilight", - "rainshowers_day", - "rainshowers_night", - "rainshowers_polartwilight", - "snowandthunder", - "sleetshowers_day", - "sleetshowers_night", - "sleetshowers_polartwilight", - "cloudy", - "heavysnowshowersandthunder_day", - "heavysnowshowersandthunder_night", - "heavysnowshowersandthunder_polartwilight", - "heavysnowshowers_day", - "heavysnowshowers_night", - "heavysnowshowers_polartwilight" + "clearsky_day", + "clearsky_night", + "clearsky_polartwilight", + "fair_day", + "fair_night", + "fair_polartwilight", + "lightssnowshowersandthunder_day", + "lightssnowshowersandthunder_night", + "lightssnowshowersandthunder_polartwilight", + "lightsnowshowers_day", + "lightsnowshowers_night", + "lightsnowshowers_polartwilight", + "heavyrainandthunder", + "heavysnowandthunder", + "rainandthunder", + "heavysleetshowersandthunder_day", + "heavysleetshowersandthunder_night", + "heavysleetshowersandthunder_polartwilight", + "heavysnow", + "heavyrainshowers_day", + "heavyrainshowers_night", + "heavyrainshowers_polartwilight", + "lightsleet", + "heavyrain", + "lightrainshowers_day", + "lightrainshowers_night", + "lightrainshowers_polartwilight", + "heavysleetshowers_day", + "heavysleetshowers_night", + "heavysleetshowers_polartwilight", + "lightsleetshowers_day", + "lightsleetshowers_night", + "lightsleetshowers_polartwilight", + "snow", + "heavyrainshowersandthunder_day", + "heavyrainshowersandthunder_night", + "heavyrainshowersandthunder_polartwilight", + "snowshowers_day", + "snowshowers_night", + "snowshowers_polartwilight", + "fog", + "snowshowersandthunder_day", + "snowshowersandthunder_night", + "snowshowersandthunder_polartwilight", + "lightsnowandthunder", + "heavysleetandthunder", + "lightrain", + "rainshowersandthunder_day", + "rainshowersandthunder_night", + "rainshowersandthunder_polartwilight", + "rain", + "lightsnow", + "lightrainshowersandthunder_day", + "lightrainshowersandthunder_night", + "lightrainshowersandthunder_polartwilight", + "heavysleet", + "sleetandthunder", + "lightrainandthunder", + "sleet", + "lightssleetshowersandthunder_day", + "lightssleetshowersandthunder_night", + "lightssleetshowersandthunder_polartwilight", + "lightsleetandthunder", + "partlycloudy_day", + "partlycloudy_night", + "partlycloudy_polartwilight", + "sleetshowersandthunder_day", + "sleetshowersandthunder_night", + "sleetshowersandthunder_polartwilight", + "rainshowers_day", + "rainshowers_night", + "rainshowers_polartwilight", + "snowandthunder", + "sleetshowers_day", + "sleetshowers_night", + "sleetshowers_polartwilight", + "cloudy", + "heavysnowshowersandthunder_day", + "heavysnowshowersandthunder_night", + "heavysnowshowersandthunder_polartwilight", + "heavysnowshowers_day", + "heavysnowshowers_night", + "heavysnowshowers_polartwilight", ] as const export type WeatherSymbolCode = (typeof weatherSymbolCodes)[number] export type PointGeometry = { - coordinates: [longitude: number, latitude: number, altitude: number], - type: "Point" + coordinates: [longitude: number, latitude: number, altitude: number] + type: "Point" } export type ForecastUnits = { - air_pressure_at_sea_level?: string, - air_temperature?: string, - air_temperature_max?: string, - air_temperature_min?: string, - cloud_area_fraction?: string, - cloud_area_fraction_high?: string, - cloud_area_fraction_low?: string, - cloud_area_fraction_medium?: string, - dew_point_temperature?: string, - fog_area_fraction?: string, - precipitation_amount?: string, - precipitation_amount_max?: string, - precipitation_amount_min?: string, - probability_of_precipitation?: string, - probability_of_thunder?: string, - relative_humidity?: string, - ultraviolet_index_clear_sky_max?: string, - wind_from_direction?: string, - wind_speed?: string, - wind_speed_of_gust?: string + air_pressure_at_sea_level?: string + air_temperature?: string + air_temperature_max?: string + air_temperature_min?: string + cloud_area_fraction?: string + cloud_area_fraction_high?: string + cloud_area_fraction_low?: string + cloud_area_fraction_medium?: string + dew_point_temperature?: string + fog_area_fraction?: string + precipitation_amount?: string + precipitation_amount_max?: string + precipitation_amount_min?: string + probability_of_precipitation?: string + probability_of_thunder?: string + relative_humidity?: string + ultraviolet_index_clear_sky_max?: string + wind_from_direction?: string + wind_speed?: string + wind_speed_of_gust?: string } export type ForecastTimeInstant = { - air_pressure_at_sea_level?: number, - air_temperature?: number, - cloud_area_fraction?: number, - cloud_area_fraction_high?: number, - cloud_area_fraction_low?: number, - cloud_area_fraction_medium?: number, - dew_point_temperature?: number, - fog_area_fraction?: number, - relative_humidity?: number, - wind_from_direction?: number, - wind_speed?: number, - wind_speed_of_gust?: number + air_pressure_at_sea_level?: number + air_temperature?: number + cloud_area_fraction?: number + cloud_area_fraction_high?: number + cloud_area_fraction_low?: number + cloud_area_fraction_medium?: number + dew_point_temperature?: number + fog_area_fraction?: number + relative_humidity?: number + wind_from_direction?: number + wind_speed?: number + wind_speed_of_gust?: number } export type ForecastTimePeriod = { - air_temperature_max?: number, - air_temperature_min?: number, - precipitation_amount?: number, - precipitation_amount_max?: number, - precipitation_amount_min?: number, - probability_of_precipitation?: number, - probability_of_thunder?: number, - ultraviolet_index_clear_sky_max?: number + air_temperature_max?: number + air_temperature_min?: number + precipitation_amount?: number + precipitation_amount_max?: number + precipitation_amount_min?: number + probability_of_precipitation?: number + probability_of_thunder?: number + ultraviolet_index_clear_sky_max?: number } export type ForecastSummary = { - symbol_code: WeatherSymbolCode + symbol_code: WeatherSymbolCode } export type ForecastPeriod = { - details: ForecastTimePeriod, - summary: ForecastSummary + details: ForecastTimePeriod + summary: ForecastSummary } export type ForecastTimeStep = { - data: { - instant: { - details?: ForecastTimeInstant - }, - next_1_hours?: ForecastPeriod, - next_6_hours?: ForecastPeriod, - next_12_hours?: ForecastPeriod - }, - time: string + data: { + instant: { + details?: ForecastTimeInstant + } + next_1_hours?: ForecastPeriod + next_6_hours?: ForecastPeriod + next_12_hours?: ForecastPeriod + } + time: string } export type Forecast = { - meta: { - units: ForecastUnits, - updated_at: string - }, - timeseries: ForecastTimeStep[] + meta: { + units: ForecastUnits + updated_at: string + } + timeseries: ForecastTimeStep[] } export type METJSONForecast = { - geometry: PointGeometry, - properties: Forecast, - type: "Feature" + geometry: PointGeometry + properties: Forecast + type: "Feature" } export type WeatherApiResponse = METJSONForecast diff --git a/src/hooks/fetchWeather/useGetWeather.ts b/src/hooks/fetchWeather/useGetWeather.ts index 3ff66b6..163650e 100644 --- a/src/hooks/fetchWeather/useGetWeather.ts +++ b/src/hooks/fetchWeather/useGetWeather.ts @@ -2,18 +2,21 @@ import { useQuery } from "@tanstack/react-query" import { type WeatherApiResponse } from "./types" const useGetWeather = (lat: number, lon: number, alt?: number) => { - let sanitized_alt = alt - if (alt != undefined) { - sanitized_alt = alt > 9000 ? 9000 : alt - sanitized_alt = sanitized_alt < -500 ? -500 : sanitized_alt - } + let sanitized_alt = alt + if (alt != undefined) { + sanitized_alt = alt > 9000 ? 9000 : alt + sanitized_alt = sanitized_alt < -500 ? -500 : sanitized_alt + } - const { isLoading, error, data } = useQuery({ - queryKey: ["citySearch", `${lat}-${lon}`], - queryFn: () => fetch(`https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${lat}&lon=${lon}${alt != undefined ? `&altitude=${sanitized_alt}` : ""}`).then((res) => res.json() as Promise) - }) + const { isLoading, error, data } = useQuery({ + queryKey: ["citySearch", `${lat}-${lon}`], + queryFn: () => + fetch( + `https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${lat}&lon=${lon}${alt != undefined ? `&altitude=${sanitized_alt}` : ""}` + ).then((res) => res.json() as Promise), + }) - return { isLoading, error, result: data } + return { isLoading, error, result: data } } export default useGetWeather diff --git a/src/hooks/useGetCityCoordinates.ts b/src/hooks/useGetCityCoordinates.ts index 7397f5f..ffa0b74 100644 --- a/src/hooks/useGetCityCoordinates.ts +++ b/src/hooks/useGetCityCoordinates.ts @@ -1,24 +1,27 @@ import { useQuery } from "@tanstack/react-query" export type citySearchResult = { - results: { - name: string, - latitude: number, - longitude: number - elevation: number, - timezone: string - }[] + results: { + name: string + latitude: number + longitude: number + elevation: number + timezone: string + }[] } const useGetCityCoordinates = (cityName: string) => { - const { isLoading, error, data } = useQuery({ - queryKey: ["citySearch", cityName], - queryFn: () => fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${cityName}`).then((res) => res.json() as Promise) - }) + const { isLoading, error, data } = useQuery({ + queryKey: ["citySearch", cityName], + queryFn: () => + fetch( + `https://geocoding-api.open-meteo.com/v1/search?name=${cityName}` + ).then((res) => res.json() as Promise), + }) - const result = data?.results ?? [] + const result = data?.results ?? [] - return { isLoading, error, result } + return { isLoading, error, result } } export default useGetCityCoordinates diff --git a/src/hooks/useStorage.ts b/src/hooks/useStorage.ts index 589e460..450782f 100644 --- a/src/hooks/useStorage.ts +++ b/src/hooks/useStorage.ts @@ -1,40 +1,51 @@ -import { useState, useCallback } from "react"; -import { getData, saveData } from "../services/storage"; -import type {AppStorage} from "../services/storage"; +import { useState, useCallback } from "react" +import { getData, saveData } from "../services/storage" +import type { AppStorage } from "../services/storage" export function useAppStorage() { - - const [storage, setStorage] = useState(getData); + const [storage, setStorage] = useState(getData) - const updateStorage = useCallback((updater: Partial | ((prev: AppStorage) => AppStorage)) => { - setStorage((prev) => { - const nextState = typeof updater === "function" ? updater(prev) : { ...prev, ...updater }; - saveData(nextState); - return nextState; - }); - }, []); + const updateStorage = useCallback( + (updater: Partial | ((prev: AppStorage) => AppStorage)) => { + setStorage((prev) => { + const nextState = + typeof updater === "function" + ? updater(prev) + : { ...prev, ...updater } + saveData(nextState) + return nextState + }) + }, + [] + ) // Star / unstar a location easily - const toggleStarredLocation = useCallback((locationId: string) => { - updateStorage((prev) => { - const exists = prev.starredLocations.includes(locationId); - const starredLocations = exists - ? prev.starredLocations.filter((id) => id !== locationId) - : [...prev.starredLocations, locationId]; + const toggleStarredLocation = useCallback( + (locationId: string) => { + updateStorage((prev) => { + const exists = prev.starredLocations.includes(locationId) + const starredLocations = exists + ? prev.starredLocations.filter((id) => id !== locationId) + : [...prev.starredLocations, locationId] - return { ...prev, starredLocations }; - }); - }, [updateStorage]); + return { ...prev, starredLocations } + }) + }, + [updateStorage] + ) // Update last visited page - const setLastPage = useCallback((lastPage: string) => { - updateStorage({ lastPage }); - }, [updateStorage]); + const setLastPage = useCallback( + (lastPage: string) => { + updateStorage({ lastPage }) + }, + [updateStorage] + ) return { storage, updateStorage, toggleStarredLocation, setLastPage, - }; -} \ No newline at end of file + } +} diff --git a/src/index.css b/src/index.css index 094bb6c..d028fa2 100644 --- a/src/index.css +++ b/src/index.css @@ -11,8 +11,8 @@ --shadow: rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; - --sans: system-ui, 'Segoe UI', Roboto, sans-serif; - --heading: system-ui, 'Segoe UI', Roboto, sans-serif; + --sans: system-ui, "Segoe UI", Roboto, sans-serif; + --heading: system-ui, "Segoe UI", Roboto, sans-serif; --mono: ui-monospace, Consolas, monospace; font: 18px/145% var(--sans); @@ -92,5 +92,5 @@ code { ul { list-style-type: none; /* Removes the dots */ - padding: 0; /* Removes default left indentation */ -} \ No newline at end of file + padding: 0; /* Removes default left indentation */ +} diff --git a/src/main.tsx b/src/main.tsx index bfe22e0..09e3a63 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,17 +1,15 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' -import { - QueryClient, QueryClientProvider -} from '@tanstack/react-query' +import { StrictMode } from "react" +import { createRoot } from "react-dom/client" +import "./index.css" +import App from "./App.tsx" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" const queryClient = new QueryClient() -createRoot(document.getElementById('root')!).render( +createRoot(document.getElementById("root")!).render( - + - , + ) diff --git a/src/services/storage.ts b/src/services/storage.ts index 9e513a0..1ac8d22 100644 --- a/src/services/storage.ts +++ b/src/services/storage.ts @@ -1,44 +1,41 @@ const STORAGE_KEY = "vær:storage_key" -const STORAGE_VERSION = 1; +const STORAGE_VERSION = 1 //Type for tilstand -export interface AppStorage{ - version: number; - starredLocations: string[] - lastPage: string; +export interface AppStorage { + version: number + starredLocations: string[] + lastPage: string } const DEFAULT_STATE: AppStorage = { - version: STORAGE_VERSION, - starredLocations: [], - lastPage: "home", -}; - -export function getData(): AppStorage{ - try { - const raw = localStorage.getItem(STORAGE_KEY); - if (!raw) return DEFAULT_STATE; - - const parsed = JSON.parse(raw) as AppStorage; - - if (parsed.version !== STORAGE_VERSION) return DEFAULT_STATE; - - return {...DEFAULT_STATE, ...parsed}; - } - catch { - console.error("Failed to load store data"); - return DEFAULT_STATE; - } + version: STORAGE_VERSION, + starredLocations: [], + lastPage: "home", } -export function saveData(state: AppStorage): void{ - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); - - } - catch (err) { - console.error("Failed saving", err); - } +export function getData(): AppStorage { + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (!raw) return DEFAULT_STATE + + const parsed = JSON.parse(raw) as AppStorage + + if (parsed.version !== STORAGE_VERSION) return DEFAULT_STATE + + return { ...DEFAULT_STATE, ...parsed } + } catch { + console.error("Failed to load store data") + return DEFAULT_STATE + } +} + +export function saveData(state: AppStorage): void { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)) + } catch (err) { + console.error("Failed saving", err) + } } // export function updateAppStorage(patch: Partial): AppStorage { @@ -46,4 +43,4 @@ export function saveData(state: AppStorage): void{ // const updated = { ...current, ...patch }; // saveData(updated); // return updated; -// } \ No newline at end of file +// } diff --git a/vite.config.ts b/vite.config.ts index fc089ba..5eb9443 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,5 +1,5 @@ -import react from '@vitejs/plugin-react' -import { defineConfig } from 'vite' +import react from "@vitejs/plugin-react" +import { defineConfig } from "vite" // https://vite.dev/config/ export default defineConfig({ From 431de55251c4157c171515935b05561a2a5a0037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henry=20Gr=C3=A6sberg?= Date: Thu, 17 Sep 2026 19:13:24 +0200 Subject: [PATCH 4/4] Add sync --- .github/workflows/lint-and-format.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint-and-format.yaml b/.github/workflows/lint-and-format.yaml index db92580..1a36c18 100644 --- a/.github/workflows/lint-and-format.yaml +++ b/.github/workflows/lint-and-format.yaml @@ -7,6 +7,7 @@ on: pull_request: types: - opened + - synchronize jobs: lint-and-format: