Skip to content
Merged
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
2 changes: 0 additions & 2 deletions .github/workflows/deploy-to-vm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 3 additions & 5 deletions .github/workflows/lint-and-format.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ on:
pull_request:
types:
- opened
- synchronize

jobs:
deploy:
lint-and-format:
runs-on: [self-hosted]
timeout-minutes: 10

Expand All @@ -23,15 +24,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
File renamed without changes.
83 changes: 66 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,73 @@
# 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/)

```bash
git checkout -b my-feature
git push -u origin my-feature
## 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...
},
},
])
```

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:

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.
```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...
},
},
])
```
22 changes: 22 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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"]),
{
files: ["**/*.{ts,tsx}"],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
globals: globals.browser,
},
},
])
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes
2 changes: 1 addition & 1 deletion vær/src/App.css → src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@

&::before,
&::after {
content: '';
content: "";
position: absolute;
top: -4.5px;
border: 5px solid transparent;
Expand Down
17 changes: 17 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import "./App.css"
import LocationList from "./components/location-list"
import ViewCity from "./components/ViewCity"

function App() {
return (
<>
<section id="center">
<LocationList />
<ViewCity city={"sørumsand"} />
</section>
<section id="spacer"></section>
</>
)
}

export default App
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
33 changes: 33 additions & 0 deletions src/components/ViewCity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import useGetWeather from "../hooks/fetchWeather/useGetWeather"
import useGetCityCoordinates from "../hooks/useGetCityCoordinates"

interface ViewCityProp {
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
)

if (isLoading) {
return <p>Laster</p>
}
if (error) {
return <p>Det skjedde en feil</p>
}
const firstTimeStep = result?.properties.timeseries[0]
const windSpeed = firstTimeStep?.data.instant.details?.wind_speed
const temperature = firstTimeStep?.data.instant.details?.air_temperature

return (
<div>
<div>Været i {arg.city}</div>
<p>Temperatur: {temperature}</p>
<p>Vindhastighet: {windSpeed}</p>
</div>
)
}
51 changes: 51 additions & 0 deletions src/components/listItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
type cityWeather = {
name: string
temperature?: number
precipitation?: number
nextDays?: { time: string; temperature: number | undefined }[]
}

const listItem = ({ city: item }: { city: cityWeather }) => {
return (
<li
key={item.name}
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
backgroundColor: "#bcebff",
color: "#000000",
padding: "10px",
borderRadius: "15px",
width: "80%",
marginRight: "5px",
}}
>
<span style={{ display: "flex" }}>
{item.name}:
<span>
{typeof item.precipitation === "number" && item.precipitation > 0.6
? "🌧️"
: "⛅"}
</span>
<p style={{ color: "#ff3232" }}>
{typeof item.temperature === "number"
? `${item.temperature.toFixed()}°`
: "—"}
</p>
</span>

<div style={{ display: "flex", flexDirection: "row", gap: "10px" }}>
{item.nextDays?.map((day) => (
<span key={day.time} style={{ color: "#ff3232" }}>
{typeof day.temperature === "number"
? `${day.temperature.toFixed()}°`
: "—"}
</span>
))}
</div>
</li>
)
}

export default listItem
111 changes: 111 additions & 0 deletions src/components/location-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import type { WeatherApiResponse } from "../hooks/fetchWeather/types"
import useGetWeather from "../hooks/fetchWeather/useGetWeather"
import ListItem from "./listItem"

type City = {
name: string
lat: number
lon: number
}

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 },
]

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

const getNextDays = (data?: WeatherApiResponse) => {
const now = new Date()

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)
)
})
.map((entry) => ({
time: entry.time,
temperature: entry.data.instant.details?.air_temperature,
}))

return noonTemperatures
}

const LocationList = () => {
const weatherResults = cities.map((city) => useGetWeather(city.lat, city.lon))

console.log(weatherResults)

if (weatherResults.some((result) => result.isLoading)) {
return <div>Loading weather…</div>
}

if (weatherResults.some((result) => result.error)) {
return <div>Could not load weather data.</div>
}

const cityWeather = cities.map((city, index) => ({
name: city.name,
temperature: getTemperature(weatherResults[index]?.result),
precipitation: getPrecipitation(weatherResults[index]?.result),
nextDays: getNextDays(weatherResults[index]?.result),
}))

return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 10,
marginTop: "20px",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
width: "80%",
marginBottom: "10px",
}}
>
<span style={{ fontWeight: "bold" }}>City</span>
<span style={{ fontWeight: "bold" }}>Next 3 Days</span>
</div>
<hr style={{ width: "80%", marginBottom: "10px" }} />

<ul
style={{
listStyleType: "none",
gap: 15,
display: "flex",
flexDirection: "column",
alignItems: "center",
width: "100%",
}}
>
{cityWeather.map((item) => (
<ListItem key={item.name} city={item} />
// <li key={item.name}>
// {item.name}: {typeof item.temperature === 'number' ? `${item.temperature}°` : '—'}
// {typeof item.precipitation === 'number' ? `, ${item.precipitation} mm` : ''}
// </li>
))}
</ul>
</div>
)
}

export default LocationList
Loading
Loading