From 95f6ab9b6300f744cadbe835939bb13bf2f5677f Mon Sep 17 00:00:00 2001 From: LeonaS Date: Tue, 20 Oct 2026 11:21:48 +0200 Subject: [PATCH 1/7] chore(frontend): legg til react-router --- frontend/package.json | 3 ++- pnpm-lock.yaml | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 271323b8..7ced1dd6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,7 +13,8 @@ "@apollo/client": "^4.3.1", "graphql": "^17.0.2", "react": "^19.2.8", - "react-dom": "^19.2.8" + "react-dom": "^19.2.8", + "react-router": "^8.4.0" }, "devDependencies": { "@babel/core": "^7.29.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19ed4030..91b59948 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,6 +70,9 @@ importers: react-dom: specifier: ^19.2.8 version: 19.3.0(react@19.3.0) + react-router: + specifier: ^8.4.0 + version: 8.4.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) devDependencies: '@babel/core': specifier: ^7.29.7 @@ -596,6 +599,9 @@ packages: '@protobufjs/utf8@1.1.2': resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + '@remix-run/route-pattern@0.22.1': + resolution: {integrity: sha512-czdGJWh09Lapvcqt7Vb09KlrU2PhRJ8xQaI+AItTuD9aDJ5MAgxnS38lnys6Ll+6RJEqPiaUqTwIQhVLwFvv+w==} + '@rolldown/binding-android-arm-eabi@1.2.12': resolution: {integrity: sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -923,6 +929,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} @@ -1519,6 +1528,16 @@ packages: peerDependencies: react: ^19.3.0 + react-router@8.4.0: + resolution: {integrity: sha512-JtydAkBvU9UTEe5qNC+POhu+ZBNM7rlKY2IegwXc7Idj7xfRG16x5RZrw3mju+XlqBxfvb2ky9P3jUp9WyWC1g==} + engines: {node: '>=22.22.0'} + peerDependencies: + react: '>=19.2.7' + react-dom: '>=19.2.7' + peerDependenciesMeta: + react-dom: + optional: true + react@19.3.0: resolution: {integrity: sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==} engines: {node: '>=0.10.0'} @@ -2190,6 +2209,8 @@ snapshots: '@protobufjs/utf8@1.1.2': {} + '@remix-run/route-pattern@0.22.1': {} + '@rolldown/binding-android-arm-eabi@1.2.12': optional: true @@ -2501,6 +2522,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie-es@3.1.1: {} + cors@2.8.6: dependencies: object-assign: 4.1.1 @@ -3052,6 +3075,14 @@ snapshots: react: 19.3.0 scheduler: 0.28.0 + react-router@8.4.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0): + dependencies: + '@remix-run/route-pattern': 0.22.1 + cookie-es: 3.1.1 + react: 19.3.0 + optionalDependencies: + react-dom: 19.3.0(react@19.3.0) + react@19.3.0: {} retry@0.13.1: {} From 93b184fd167a6bf521b650d80d073af14b0d0a87 Mon Sep 17 00:00:00 2001 From: LeonaS Date: Tue, 20 Oct 2026 11:22:04 +0200 Subject: [PATCH 2/7] =?UTF-8?q?feat(frontend):=20GraphQL-sp=C3=B8rring=20f?= =?UTF-8?q?or=20ett=20spill=20(#37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/graphql/game.ts | 70 ++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 frontend/src/graphql/game.ts diff --git a/frontend/src/graphql/game.ts b/frontend/src/graphql/game.ts new file mode 100644 index 00000000..92c106c1 --- /dev/null +++ b/frontend/src/graphql/game.ts @@ -0,0 +1,70 @@ +import { gql, type TypedDocumentNode } from '@apollo/client'; + +/** Sjanger, plattform, utvikler eller utgiver. Bare navnet trengs på detaljsiden. */ +interface FacetName { + name: string; +} + +/** Alt detaljsiden viser om ett spill */ +export interface GameDetails { + id: string; + name: string; + description: string | null; + released: string | null; + website: string | null; + metacritic: number | null; + rating: number | null; + ratingsCount: number; + playtime: number | null; + esrbRating: string | null; + backgroundImage: string | null; + screenshots: string[]; + genres: FacetName[]; + platforms: FacetName[]; + developers: FacetName[]; + publishers: FacetName[]; +} + +interface GameData { + game: GameDetails | null; +} + +interface GameVars { + id: string; +} + +/** + * Henter ett spill til detaljsiden. Backend gir null hvis id-en ikke finnes. + * Apollo cacher svaret på id, så å åpne samme spill igjen gir ingen ny forespørsel. + * Sjangre, plattformer osv. hentes uten id, siden siden bare viser navnene. + */ +export const GAME: TypedDocumentNode = gql` + query Game($id: ID!) { + game(id: $id) { + id + name + description + released + website + metacritic + rating + ratingsCount + playtime + esrbRating + backgroundImage + screenshots + genres { + name + } + platforms { + name + } + developers { + name + } + publishers { + name + } + } + } +`; From be7c511a89713feb9c04d795ebe123c7409f3c7d Mon Sep 17 00:00:00 2001 From: LeonaS Date: Tue, 20 Oct 2026 11:22:20 +0200 Subject: [PATCH 3/7] feat(frontend): stil for detaljsiden (#37) --- frontend/src/pages/GameDetail/GameDetail.css | 194 +++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 frontend/src/pages/GameDetail/GameDetail.css diff --git a/frontend/src/pages/GameDetail/GameDetail.css b/frontend/src/pages/GameDetail/GameDetail.css new file mode 100644 index 00000000..bd4e3f07 --- /dev/null +++ b/frontend/src/pages/GameDetail/GameDetail.css @@ -0,0 +1,194 @@ +/* Detaljsiden. Samme papir-stil som lista: ingen bokser, bare tynne linjer og luft. */ + +/* «← tilbake» i monospace, som menyen øverst */ +.game-detail__back { + font-family: var(--mono); + font-size: 14px; +} + +.game-detail__status { + margin-top: 24px; + font-family: var(--mono); + font-size: 13px; +} + +/* Tittel til venstre, favoritt-knapp til høyre */ +.game-detail__header { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + justify-content: space-between; + gap: 12px 24px; + margin: 24px 0 20px; +} + +.game-detail__title { + margin: 24px 0 0; + font-size: 44px; + line-height: 1.1; + letter-spacing: -0.02em; +} + +.game-detail__header .game-detail__title { + margin: 0; +} + +/* Overskriften får fokus fra koden når siden åpnes. Ingen ring rundt den, siden den ikke er klikkbar. */ +.game-detail__title:focus { + outline: none; +} + +.game-detail__meta { + margin-top: 8px; + font-size: 16px; +} + +/* Samme utseende som favoritt-knappen på spillkortene */ +.game-detail__favorite { + flex-shrink: 0; + min-height: 32px; + padding: 0 10px; + font: inherit; + font-size: 14px; + color: var(--text-h); + background: none; + border: 1px solid var(--text-h); + border-radius: 0; + cursor: pointer; +} + +.game-detail__favorite:hover { + text-decoration: underline; +} + +.game-detail__favorite[aria-pressed='true'] { + color: var(--bg); + background: var(--text-h); +} + +.game-detail__favorite:focus-visible { + outline: 2px solid var(--text-h); + outline-offset: 2px; +} + +.game-detail__hero { + display: block; + width: 100%; + height: auto; + aspect-ratio: 16 / 9; + object-fit: cover; + background: var(--code-bg); /* vises mens bildet laster */ +} + +/* Store tall med små etiketter over, som utstillingsvisningen i lista */ +.game-detail__scores { + display: flex; + flex-wrap: wrap; + gap: 16px 40px; + margin: 0; + padding: 20px 0; + border-bottom: 1px solid var(--border); +} + +.game-detail__scores div { + display: flex; + flex-direction: column; + gap: 2px; +} + +.game-detail__scores dt, +.game-detail__facts dt { + font-family: var(--mono); + font-size: 12px; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.game-detail__scores dd { + margin: 0; + font-size: 28px; + line-height: 1.1; + color: var(--text-h); + font-variant-numeric: tabular-nums; +} + +/* Antall vurderinger, under rating-tallet */ +.game-detail__sub { + display: block; + margin-top: 4px; + font-family: var(--mono); + font-size: 12px; + color: var(--text); +} + +/* Beskrivelse til venstre, fakta til høyre */ +.game-detail__columns { + display: grid; + grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); + gap: 48px; + margin-top: 28px; +} + +.game-detail__heading { + margin-bottom: 12px; +} + +/* Beskrivelsen er ren tekst med linjeskift, pre-line beholder avsnittene */ +.game-detail__description { + max-width: 65ch; + white-space: pre-line; +} + +.game-detail__facts { + margin: 0; +} + +.game-detail__facts dd { + margin: 2px 0 16px; + color: var(--text-h); +} + +.game-detail__facts a { + color: var(--text-h); + text-underline-offset: 3px; +} + +.game-detail__gallery { + margin-top: 36px; +} + +.game-detail__screenshots { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; + margin: 0; + padding: 0; + list-style: none; +} + +.game-detail__screenshots img { + display: block; + width: 100%; + height: auto; + aspect-ratio: 16 / 9; + object-fit: cover; + background: var(--code-bg); +} + +@media (max-width: 1024px) { + .game-detail__title { + font-size: 32px; + } +} + +/* Smal skjerm: én kolonne, to skjermbilder per rad */ +@media (max-width: 760px) { + .game-detail__columns { + grid-template-columns: 1fr; + gap: 28px; + } + .game-detail__screenshots { + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + } +} From 6eb70e42c2b9a7c40fcff61c2d787d85b6df90bd Mon Sep 17 00:00:00 2001 From: LeonaS Date: Tue, 20 Oct 2026 11:22:37 +0200 Subject: [PATCH 4/7] feat(frontend): detaljside for spill (#37) --- frontend/src/pages/GameDetail/GameDetail.tsx | 243 +++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 frontend/src/pages/GameDetail/GameDetail.tsx diff --git a/frontend/src/pages/GameDetail/GameDetail.tsx b/frontend/src/pages/GameDetail/GameDetail.tsx new file mode 100644 index 00000000..bb55a10d --- /dev/null +++ b/frontend/src/pages/GameDetail/GameDetail.tsx @@ -0,0 +1,243 @@ +import { useEffect, useRef } from 'react'; +import { Link, useParams } from 'react-router'; +import { useQuery } from '@apollo/client/react'; +import { GAME } from '../../graphql/game'; +import './GameDetail.css'; + +interface GameDetailProps { + favorites: Set; + onToggleFavorite: (id: string) => void; +} + +// Maks antall skjermbilder som vises. Bildene er store, så vi viser ikke alle (bærekraft). +const MAX_SCREENSHOTS = 6; + +/** Komma-separert liste med navn, f.eks. sjangre eller plattformer */ +const names = (list: { name: string }[]) => list.map((f) => f.name).join(', '); + +/** Domenet i en nettadresse, eller null hvis det ikke er en gyldig http(s)-adresse (f.eks. javascript:) */ +function hostOf(url: string | null) { + if (!url) return null; + try { + const { protocol, hostname } = new URL(url); + return protocol === 'http:' || protocol === 'https:' ? hostname : null; + } catch { + return null; + } +} + +/** + * Detaljsiden for ett spill, på /game/:id. Egen side med egen URL (ikke en overlay), + * så den kan deles, bokmerkes og åpnes direkte, og tilbakeknappen i nettleseren virker. + */ +export function GameDetail({ favorites, onToggleFavorite }: GameDetailProps) { + const { id = '' } = useParams(); + const { data, loading, error } = useQuery(GAME, { variables: { id } }); + const game = data?.game; + const headingRef = useRef(null); + + // Nytt spill: start øverst på siden, ellers blir man stående der man klikket i lista + useEffect(() => { + window.scrollTo(0, 0); + }, [id]); + + // Når spillet er lastet: sett fanetittel og flytt fokus til overskriften. + // En SPA laster ikke siden på nytt, så uten dette merker ikke skjermlesere at siden er byttet. + const loadedId = game?.id; + const title = game?.name; + useEffect(() => { + if (!loadedId) return; + document.title = `${title} – Damp`; + headingRef.current?.focus(); + return () => { + document.title = 'Damp'; + }; + }, [loadedId, title]); + + const backLink = ( + + ← tilbake til resultatene + + ); + + if (loading && !game) { + return ( +
+ {backLink} +

+ Laster … +

+
+ ); + } + + if (error || !game) { + return ( +
+ {backLink} +

Fant ikke spillet

+

+ {error + ? `Kunne ikke hente spillet: ${error.message}` + : 'Spillet finnes ikke, eller lenken er feil.'} +

+
+ ); + } + + const isFavorite = favorites.has(game.id); + const year = game.released?.slice(0, 4) ?? null; + const meta = [year, names(game.genres)].filter(Boolean).join(' · '); + // Dato uten klokkeslett tolkes som UTC, så timeZone: 'UTC' hindrer at datoen bommer med én dag + const releasedDate = game.released + ? new Date(game.released).toLocaleDateString('nb-NO', { dateStyle: 'long', timeZone: 'UTC' }) + : null; + const websiteHost = hostOf(game.website); + const screenshots = game.screenshots.slice(0, MAX_SCREENSHOTS); + + return ( +
+ {backLink} + +
+
+
+ {/* tabIndex={-1}: kan få fokus fra koden (se over), men er ikke med i tab-rekkefølgen */} +

+ {game.name} +

+ {meta &&

{meta}

} +
+ + +
+ + {game.backgroundImage && ( + // alt="" fordi tittelen over allerede sier hva bildet er. Fast størrelse + // hindrer at innholdet under hopper når bildet laster. + + )} + +
+
+
Rating
+
+ {game.rating != null ? `${game.rating.toFixed(1)} / 5` : 'Ingen'} + {game.ratingsCount > 0 && ( + + {game.ratingsCount.toLocaleString('nb-NO')} vurderinger + + )} +
+
+
+
Metacritic
+
{game.metacritic ?? 'Ingen'}
+
+ {/* RAWG har 0 timer for spill uten data, så da viser vi ikke spilletid */} + {game.playtime != null && game.playtime > 0 && ( +
+
Spilletid
+
{game.playtime} t
+
+ )} + {game.esrbRating && ( +
+
Aldersgrense
+
{game.esrbRating}
+
+ )} +
+ +
+
+

+ Om spillet +

+

+ {game.description?.trim() || 'Ingen beskrivelse.'} +

+
+ +
+

+ Fakta +

+
+ {releasedDate && ( + <> +
Utgitt
+
{releasedDate}
+ + )} + {game.platforms.length > 0 && ( + <> +
Plattformer
+
{names(game.platforms)}
+ + )} + {game.developers.length > 0 && ( + <> +
Utvikler
+
{names(game.developers)}
+ + )} + {game.publishers.length > 0 && ( + <> +
Utgiver
+
{names(game.publishers)}
+ + )} + {websiteHost && game.website && ( + <> +
Nettside
+
+ {websiteHost} +
+ + )} +
+
+
+ + {screenshots.length > 0 && ( +
+

+ Skjermbilder +

+
    + {screenshots.map((src, i) => ( +
  • + {/* lazy: lastes først når man scroller ned til dem */} + {`Skjermbilde +
  • + ))} +
+
+ )} +
+
+ ); +} From 57b6d760a43f322f19694163540c4ed4ab038c08 Mon Sep 17 00:00:00 2001 From: LeonaS Date: Tue, 20 Oct 2026 11:22:49 +0200 Subject: [PATCH 5/7] feat(frontend): spillkortet lenker til detaljsiden (#37) --- frontend/src/components/GameCard/GameCard.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/GameCard/GameCard.tsx b/frontend/src/components/GameCard/GameCard.tsx index 06877430..5c8c3eef 100644 --- a/frontend/src/components/GameCard/GameCard.tsx +++ b/frontend/src/components/GameCard/GameCard.tsx @@ -1,3 +1,4 @@ +import { Link } from 'react-router'; import type { GameSummary, GameView } from '../../types/game'; import './GameCard.css'; @@ -37,7 +38,8 @@ export function GameCard({ game, isFavorite, onToggleFavorite, view = 'list' }:

- {game.name} + {/* Link i stedet for : bytter side uten å laste hele appen på nytt, og tar med /project2 */} + {game.name}

{meta &&

{meta}

} From 91b2c663cb26ec4e900afe2afca68288322402f5 Mon Sep 17 00:00:00 2001 From: LeonaS Date: Tue, 20 Oct 2026 11:23:02 +0200 Subject: [PATCH 6/7] feat(frontend): router med basename /project2 (#37) --- frontend/src/main.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index d97a9d9c..e07fd3fd 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,14 +1,21 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router'; import { ApolloProvider } from '@apollo/client/react'; import { client } from './apollo'; import './index.css'; import App from './App.tsx'; +// Appen ligger under /project2/ både lokalt og på VM-en (se vite.config.ts), så alle +// ruter og lenker regnes fra der. Skråstreken på slutten fjernes, så /project2 også matcher. +const basename = import.meta.env.BASE_URL.replace(/\/$/, ''); + createRoot(document.getElementById('root')!).render( - - - + + + + + , ); From f4ca48047032154bde4c3fd599f7597cb8f0200e Mon Sep 17 00:00:00 2001 From: LeonaS Date: Tue, 20 Oct 2026 11:23:17 +0200 Subject: [PATCH 7/7] feat(frontend): ruter for forside og detaljside (#37) --- frontend/src/App.tsx | 172 ++++++++++++++++++++++++++----------------- 1 file changed, 106 insertions(+), 66 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 19dd0f4a..f4ff32de 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,7 +1,9 @@ import { useState } from 'react'; +import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router'; import { useQuery } from '@apollo/client/react'; import { GameList } from './components/GameList/GameList'; import { Header } from './components/Header/Header'; +import { GameDetail } from './pages/GameDetail/GameDetail'; import { GAMES } from './graphql/games'; import type { Suggestion } from './graphql/searchSuggestions'; import type { GameSummary, GameView } from './types/game'; @@ -13,13 +15,17 @@ import { useDebounce } from './hooks/useDebounce'; import { defaultFilters, toFilterInput, type GameFilters } from './types/filters'; /** - * Forsiden: søk, resultatliste fra databasen og favoritter. + * Rammen rundt hele appen: toppfeltet og sidene (forsiden med filtre og resultatliste, + * og detaljsiden for ett spill). Søk, filtre, visning og favoritter ligger her, så de + * huskes når man går fra detaljsiden tilbake til lista. * Favoritter og valgt visning ligger bare i minnet her og skal senere inn i state management. */ function App() { const [favorites, setFavorites] = useState>(new Set()); const [view, setView] = useState('list'); const [showFavorites, setShowFavorites] = useState(false); + const navigate = useNavigate(); + const onHome = useLocation().pathname === '/'; // Søket i hovedlista. Settes først når brukeren trykker Enter, ikke for hver bokstav // (forslagene i søkefeltet tar seg av det), så lista og pagineringen ikke hopper @@ -28,17 +34,33 @@ function App() { const [filters, setFilters] = useState(defaultFilters); const debouncedFilters = useDebounce(filters, 300); - // Valgene i dropdownene (sjangre og plattformer) fra databasen - const { data: options } = useQuery(FILTER_OPTIONS); + // Valgene i dropdownene (sjangre og plattformer) fra databasen. + // Filtrene vises bare på forsiden, så de hentes ikke på detaljsiden. + const { data: options } = useQuery(FILTER_OPTIONS, { skip: !onHome }); - // MIDLERTIDIG: det finnes ingen detaljside ennå, så valgt spill vises i lista. - // Den som lager detaljsiden bytter denne linjen til navigering. - const handleSelectGame = (game: Suggestion) => setQuery(game.name); + // Valgt forslag i søkefeltet åpner detaljsiden til spillet + const handleSelectGame = (game: Suggestion) => navigate(`/game/${game.id}`); + + // Søkeresultater og favoritter vises på forsiden, så fra detaljsiden går vi dit + const handleSearch = (term: string) => { + setQuery(term); + if (!onHome) navigate('/'); + }; + const handleToggleFavorites = () => { + if (onHome) { + setShowFavorites((v) => !v); + } else { + setShowFavorites(true); + navigate('/'); + } + }; // Henter første side fra databasen. Kjøres på nytt bare når `query` endres (ved Enter). - // Tomt søk (null) gir de mest populære spillene. + // Tomt søk (null) gir de mest populære spillene. Hoppes over på detaljsiden, der lista + // ikke vises. Tilbake på forsiden kommer lista fra Apollo-cachen uten ny forespørsel. const { data, previousData, loading, error } = useQuery(GAMES, { variables: { search: query || null, filter: toFilterInput(debouncedFilters), first: 20 }, + skip: !onHome, }); const result = data ?? previousData; // forrige liste vises mens den nye lastes @@ -71,71 +93,89 @@ function App() { return ( <>
setShowFavorites((v) => !v)} + showFavorites={showFavorites && onHome} + onToggleFavorites={handleToggleFavorites} /> - + + + + +
+
+

{showFavorites ? 'Favoritter' : 'Søkeresultater'}

+ +
+ + + + +
+
+ +

+ {status} +

+ {error &&

Kunne ikke hente spill: {error.message}

} + + +
+ + } + /> -
-
-

{showFavorites ? 'Favoritter' : 'Søkeresultater'}

- -
- - - - -
-
- -

- {status} -

- {error &&

Kunne ikke hente spill: {error.message}

} - - } /> -
+ + {/* Ukjente adresser sendes til forsiden */} + } /> +
); }