Skip to content
Merged

Dev #32

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7a75b86
Update README.md
hasanmo Sep 9, 2026
a335ec4
Create roadmap.md
hasanmo Sep 10, 2026
e010fbd
init
Sep 11, 2026
29f779c
readme.md update
Sep 11, 2026
bb9ad62
feature(weather): add App layout and activity selector
oliveror Sep 11, 2026
dc159e9
feature(weather): add WeatherList and WeatherCard
oliveror Sep 12, 2026
a9f4fdd
feature(weather): add loading, error and empty states
Sep 14, 2026
2c022f6
feature(ui): share query feedback components
Sep 14, 2026
76c16b1
feature(weather): add ranked cards and city details
oliveror Sep 14, 2026
e4f4064
docs(weather): add minor tweaks to architecture and weather cards docs
oliveror Sep 14, 2026
565282f
docs: choose Open-Meteo weather API
Sep 14, 2026
445c718
Merge pull request #21 from IT2810-H26/13-issue-12-lage-loading--erro…
oliverdr Sep 15, 2026
0ac6ed9
Merge pull request #23 from IT2810-H26/issue-4-choose-weather-api
oliverdr Sep 15, 2026
1a6cd2f
fix(e2e): scope weather detail assertion
Sep 15, 2026
2344f5d
feat: implement Open-Meteo weather service (#25)
hasanmo Sep 15, 2026
e77f226
fix(weather): add direct retry action to error cards
oliveror Sep 15, 2026
72f393f
fix(ui): apply app-header styling class
oliveror Sep 15, 2026
129dc26
fix(accessibility): trap focus in weather details dialog
oliveror Sep 15, 2026
82952e6
feat(weather): add empty state for weather list
oliveror Sep 15, 2026
ae0dedf
Merge branch 'dev' into feature/base-app-layout
oliveror Sep 15, 2026
c065cce
Merge pull request #22 from IT2810-H26/feature/base-app-layout
olestan Sep 15, 2026
8832bee
feat: add city weather and activity models (#24)
hasanmo Sep 15, 2026
48ba68f
feat(weather): integrate shared city and activity models
Sep 16, 2026
11985f9
Merge pull request #27 from IT2810-H26/feature/integrate-weather-models
olestan Sep 17, 2026
e57f698
Issue 14: lage responsivt design med plain css (#26)
olestan Sep 17, 2026
3c4699d
feature(ci): sett opp runners og deploy til VM (#28)
oliverdr Sep 18, 2026
13e2b24
fix(quality): rett formatering og værkort-sjekker
Sep 18, 2026
526fb79
Merge pull request #31 from IT2810-H26/fix/quality-checks
oliverdr Sep 18, 2026
fee6166
fix(git): koble main og dev etter squash-merge
Sep 18, 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: 4 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
self-hosted-runner:
labels:
- t31-ci
- t31-deploy
120 changes: 120 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: CI

on:
push:
branches: [dev, main]
pull_request:
branches: [dev, main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
# la main bli ferdig, også hvis vi må rulle tilbake
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

defaults:
run:
shell: bash

jobs:
quality:
name: Quality checks
runs-on: [self-hosted, linux, x64, t31-ci]
timeout-minutes: 30
env:
CI: 'true'
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: .node-version

- name: Use the pinned npm version
run: npm install --global npm@11.11.1

- name: Install dependencies
run: npm ci

- name: Check code, contracts, migration metadata, tests and builds
run: npm run check

- name: Test release switching and rollback
run: bash scripts/test-deploy-project1.sh

- name: Install test browsers
run: npm run test:e2e:install

- name: Run browser checks
run: npm run test:e2e -- --workers=2

- name: Preserve browser failure evidence
if: failure()
# denne node20-versjonen finnes på git.ntnu.no og støtter enterprise
uses: actions/upload-artifact@v3.2.1-node20
with:
name: browser-results-${{ github.run_id }}-${{ github.run_attempt }}
path: test-results/
retention-days: 7

- name: Save the tested production build
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
uses: actions/upload-artifact@v3.2.1-node20
with:
name: project1-${{ github.sha }}-${{ github.run_attempt }}
path: web/dist/
if-no-files-found: error
retention-days: 7

deploy:
name: Deploy Project 1
needs: quality
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
runs-on: [self-hosted, linux, x64, t31-deploy]
timeout-minutes: 10
environment:
name: production
url: http://it2810-31.idi.ntnu.no/project1/
concurrency:
group: project1-production
cancel-in-progress: false
steps:
- name: Check that this commit is still the tip of main
id: latest
uses: actions/github-script@v7
with:
script: |
const { data } = await github.rest.repos.getCommit({
...context.repo,
ref: 'main',
});
core.setOutput('current', data.sha === context.sha);
if (data.sha !== context.sha) {
core.notice('A newer commit is on main; skip this outdated deployment.');
}
- name: Check out the deployment script from the tested commit
if: steps.latest.outputs.current == 'true'
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Download the tested production build
if: steps.latest.outputs.current == 'true'
uses: actions/download-artifact@v3-node20
with:
name: project1-${{ github.sha }}-${{ github.run_attempt }}
path: web/dist/

- name: Publish to Apache and verify the release
if: steps.latest.outputs.current == 'true'
env:
RELEASE_ID: ${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
run: bash scripts/deploy-project1.sh web/dist "$RELEASE_ID"
95 changes: 93 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,98 @@ Applikasjonen skal:
- vise tydelig lasting og feilmelding ved API-feil
- fungere på både mobil og desktop

Værdata hentes direkte når applikasjonen brukes og mellomlagres ikke.
Værdata hentes direkte når applikasjonen brukes og mellomlagres ikke

## Weather service

`weatherService` har ansvar for å hente værdata fra Open-Meteo og konvertere API-responsen til prosjektets egen `WeatherData`-type.

Dette gjør at React-komponentene ikke trenger å kjenne til:

- URL-en til Open-Meteo
- feltnavnene i API-responsen
- hvordan API-kallet bygges
- hvordan HTTP-feil håndteres

Komponentene arbeider bare med prosjektets egne TypeScript-typer.

### Hente værdata

```ts
const weather = await getWeather(city);
```

Her skjer følgende:

1. `city` inneholder navn, breddegrad og lengdegrad.
2. `getWeather(city)` sender et `fetch`-kall til Open-Meteo.
3. `await` venter til API-kallet er ferdig.
4. Relevante felter fra API-responsen mappes til prosjektets egen WeatherData-type.
5. Resultatet lagres i variabelen `weather`.

Variabelen kan deretter brukes slik:

```ts
console.log(weather.temperature);
console.log(weather.windSpeed);
console.log(weather.precipitation);
```

### Eksempel med feilhåndtering

```ts
try {
const weather = await getWeather(city);
console.log(weather);
} catch (error) {
console.error('Kunne ikke hente værdata:', error);
}
```

Hvis API-kallet mislykkes, kaster `getWeather` en feil som React-komponenten kan fange og vise som en forståelig feilmelding til brukeren.

### Dataflyt

```mermaid
flowchart LR
City["City"] --> Service["getWeather(city)"]
Service --> API["Open-Meteo"]
API --> Service
Service --> Data["WeatherData"]
Data --> Component["React-komponent"]
```

Denne oppdelingen gjør API-håndteringen samlet på ett sted og lar React-komponentene bruke en enkel og stabil `WeatherData`-modell.

## Værdata

Prosjektet bruker Open-Meteo Forecast API.

API-et kan brukes direkte fra nettleseren uten API-nøkkel. Byens
breddegrad og lengdegrad sendes til `/v1/forecast`. Applikasjonen ber om
temperatur, følt temperatur, luftfuktighet, nedbør, værkode, vindstyrke
og informasjon om dag eller natt.

### Valgte værdata

Vi velger følgende data:

| Felt | Bruk |
| ---------------------- | ---------------------- |
| `temperature_2m` | Temperatur nå |
| `apparent_temperature` | Føles som |
| `weather_code` | Værtype og ikon |
| `precipitation` | Nedbør |
| `wind_speed_10m` | Vindstyrke |
| `relative_humidity_2m` | Luftfuktighet |
| `is_day` | Dag/natt |
| `time` | Tidspunkt for målingen |

Eksempel:

```bash
https://api.open-meteo.com/v1/forecast?latitude=63.4305&longitude=10.3951&current=temperature_2m,apparent_temperature,relative_humidity_2m,precipitation,weather_code,wind_speed_10m,is_day&wind_speed_unit=ms&timezone=auto
```

## Teknologi

Expand Down Expand Up @@ -122,4 +213,4 @@ Velg et issue før du starter, og lenk det i PR-en, for eksempel `Closes #8`. Å

## Dokumentasjon

[Roadmap](roadmap.md) · [Utviklerguide og planlagt dataflyt](docs/development.md) · [Krav og gjenstående arbeid](docs/requirements.md) · [Tester](docs/testing.md)
[Roadmap](roadmap.md) · [Utviklerguide og planlagt dataflyt](docs/development.md) · [Delte statuskomponenter](docs/ui-feedback.md) · [Krav og gjenstående arbeid](docs/requirements.md) · [Tester](docs/testing.md)
15 changes: 15 additions & 0 deletions deploy/apache-project1.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# slå på med a2enconf t31-project1, sjekk configen og last apache på nytt
RedirectMatch 302 ^/project1$ /project1/
Alias /project1/ /srv/t31-project1/current/

<Directory /srv/t31-project1/>
Options +FollowSymLinks -Indexes -ExecCGI -Includes
AllowOverride None
Require all granted
DirectoryIndex index.html
# prosjekt 1 er bare statiske filer, ikke kjør noe backend her
SetHandler default-handler
<IfModule mod_headers.c>
Header set Cache-Control "no-cache"
</IfModule>
</Directory>
5 changes: 4 additions & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ The README covers everyday setup. This page contains optional infrastructure com
## Structure and type safety

- `web/src/WeatherExample.tsx`: the single weather example.
- `web/src/components/feedback/`: shared loading, error, empty and offline messages plus a typed TanStack Query adapter; see [usage and extension](ui-feedback.md).
- `web/src/features/weather/`: typed city definitions, Zod response validation and TanStack Query options.
- `web/src/shared/storage.ts`: storage helper for future preferences and favorite city IDs.
- `project2/api/`: independent Node/Hono/GraphQL API with native PostgreSQL and Drizzle migrations.
- `project2/contracts/`: generated resolver/client contracts; regenerate with `npm run types:api`.

Project 1 calls Open-Meteo directly. Wind is requested in m/s, timestamps use Europe/Oslo and precipitation is labelled with the provider's interval. Only the active response is held in memory; weather payloads are never persisted. Future storage features should store preferences and city IDs only.

`fetchWeather` returns `CityWeather | null`: HTTP 204, a JSON `null` response, or a validated response with a missing/null `current` observation means no weather is available. Partial observations, invalid units and malformed responses still fail Zod validation and produce a retryable error. Consumers must handle the empty result before reading weather fields. `WeatherExample` shows loading during initial requests and retries, explains requests paused while offline, and provides a retry button for errors and empty results.

Project 2 requires GraphQL and a database installed directly on the VM. Hosted Supabase and Docker backend/database deployment do not fit that requirement. Docker here is only an optional frontend development tool; Redis is not needed.

## Native PostgreSQL setup for Project 2
Expand Down Expand Up @@ -77,7 +80,7 @@ Project 2's eventual client belongs at `/project2/`. Run its built API on Node,

## Planned Project 1 structure

The following diagrams are retained from the team's project plan; these components are not implemented by the skeleton.
The following diagrams are retained from the team's project plan. Shared loading/error feedback is implemented; the full app, list and details composition remains planned.

## Dataflyt

Expand Down
17 changes: 17 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@ Playwright smoke tests intercept provider data and check the production example,
- `docker compose config --quiet`: passed. The optional frontend image/container was not built or started during this verification.
- Verification toolchain: isolated Node 24.21.0 and npm 11.11.1. The machine's default Node/npm were not changed; follow README setup before installing locally.

## Loading, error and empty states — 2026-09-14

Codex assisted with the loading/error/empty-state implementation, regression tests and documentation. The changes were checked with an isolated Node 24.21.0 / npm 11.11.1 toolchain after `npm ci`.

- `npm run check`: passed formatting, zero-warning lint, generated GraphQL contract verification, strict TypeScript, migration metadata, all 18 Vitest tests (15 frontend, 3 API), the unchanged success snapshot and both builds.
- Frontend tests cover a held loading response, HTTP failure with manual retry and loading feedback, no-content/null/missing observations, incomplete measurements, invalid JSON, network failure, and offline pause/resume. Invalid data never reaches the rendered weather fields.
- `npm run test:e2e`: all 25 tests passed in Chromium, Firefox, WebKit and emulated iPhone portrait/landscape. New checks cover pending requests, error semantics, keyboard retry, empty-result recovery, disabled duplicate retries and missing-field responses without uncaught page errors. Axe reported no violations in loading, error, empty or successful states.

Automated tests use mocked provider responses. Real-device and screen-reader testing, live provider behavior, deployment and teammate review remain separate checks.

## Shared feedback components — 2026-09-14

Codex assisted with extracting standalone loading, error, empty and offline messages, a typed TanStack Query adapter, scoped CSS, component tests and the [usage guide](ui-feedback.md). The weather example now consumes the shared components. Component tests cover standalone use, keyboard actions, disabled actions, independent instances, custom empty-list rules, valid falsy data and disabled queries.

- `npm run check`: passed formatting, zero-warning lint, generated contracts, strict TypeScript, migration metadata, all 27 Vitest tests (24 frontend, 3 API), the unchanged success snapshot and both builds, using Node 24.21.0 and npm 11.11.1.
- `npm run test:e2e`: all 25 tests passed with the shared components in Chromium, Firefox, WebKit and emulated iPhone portrait/landscape. This includes keyboard retry, state transitions, empty-result recovery, malformed data, overflow and axe accessibility checks. Axe found no violations in the tested loading, error, empty and success states.

## Remaining manual checks

- Final client: navigation, activity assessment, filters, sort, favorites and both storage lifetimes.
Expand Down
65 changes: 65 additions & 0 deletions docs/ui-feedback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Shared UI feedback

Import feedback components from `web/src/components/feedback`. They are self-authored React components with plain CSS and no additional runtime dependencies. `WeatherExample` is the working integration.

## Standalone messages

| Component | Default message | Semantics |
| ---------------- | ------------------------------------------- | ------------- |
| `LoadingMessage` | Laster… | `role=status` |
| `ErrorMessage` | Noe gikk galt. Prøv igjen om litt. | `role=alert` |
| `EmptyMessage` | Ingen resultater. | `role=status` |
| `OfflineMessage` | Du er frakoblet. Venter på nettforbindelse. | `role=status` |

Each component accepts a custom `message` string and an optional `action` with `label`, `onClick` and `disabled`. They work without TanStack Query or a provider, so features can use them for local filters, favorites and other UI state.

For example, inside a feature with a `resetFilters` callback:

```tsx
import { EmptyMessage } from './components/feedback';

<EmptyMessage
message="Ingen byer passer filtrene dine."
action={{ label: 'Nullstill filtre', onClick: resetFilters }}
/>;
```

Actions use native buttons with an explicit `type="button"`, visible keyboard focus and a minimum 44px height. Disabled actions cannot be activated. The button sits outside the live message region, so the alert contains only the explanation. Roles are fixed by the component to keep semantics consistent across features.

## TanStack Query integration

`QueryFeedback` selects a message from an existing typed query result. It does not fetch data, create a query client, render results or change query options. Features continue to own query keys, live REST requests, Zod validation and data presentation.

For a feature with an existing `citiesQuery` whose successful data is an array:

```tsx
import { QueryFeedback } from './components/feedback';

<QueryFeedback
query={citiesQuery}
isEmpty={(cities) => cities.length === 0}
messages={{
loading: 'Henter byer…',
error: 'Kunne ikke hente byene. Prøv igjen om litt.',
empty: 'Ingen byer er tilgjengelige akkurat nå.',
}}
retryLabel="Hent byer på nytt"
/>;
```

- `query`: the existing query result; data and error types are inferred.
- `messages`: optional overrides for `loading`, `error`, `empty` and `offline`. Omitted values use the standalone defaults. Use understandable, feature-specific text rather than raw provider errors.
- `isEmpty`: optional domain rule. Null/undefined data is always empty and is never passed to the predicate; valid values such as `0` and `false` are not empty. Empty arrays require an explicit length check.
- `retryLabel`: accessible button text, defaulting to `Prøv igjen`. Errors and empty responses offer a manual `refetch`; retry buttons are disabled while a request is fetching or paused.

Paused and fetching states take precedence over previous errors or empty results. A disabled query that is pending but idle shows no loading message. Successful nonempty data shows no feedback; the feature renders its own data alongside `QueryFeedback`.

Use one adapter for a list loaded by one query. Use per-card feedback when cards fetch independently, and include the city name in messages and retry labels so users can distinguish actions. For client-side filtering, use `EmptyMessage` with an action that clears filters rather than refetching unchanged server data.

## Styling and extension

The components import `feedback.css` themselves. All selectors are scoped under `feedback-message`, so unrelated alerts and buttons keep their own styles. Wrapping text, logical spacing and native button sizing allow the same components to fit cards and full-page sections.

Add feature wording and behavior through props. Change common spacing, colors or focus styles in the shared stylesheet. Keep weather types, API URLs and feature-specific state out of the standalone components. The adapter reads TanStack Query state directly; it does not duplicate that state in effects or component state.

Component tests cover standalone use, keyboard actions, disabled actions, independent instances, custom empty-list rules, valid falsy data and disabled queries. The weather integration tests and Playwright suite cover the full request/retry lifecycle and accessibility checks.
Loading