From 62d3bb2eab30e099ce446f8545ac0d0dce529ee6 Mon Sep 17 00:00:00 2001 From: johannes Date: Thu, 22 Oct 2026 13:44:51 +0200 Subject: [PATCH 1/8] =?UTF-8?q?feat(frontend):=20send=20innloggingstoken?= =?UTF-8?q?=20med=20hver=20foresp=C3=B8rsel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/apollo.ts | 23 ++++++++++++++++++++--- frontend/src/auth/token.ts | 8 ++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 frontend/src/auth/token.ts diff --git a/frontend/src/apollo.ts b/frontend/src/apollo.ts index 418c1c5..35ef6be 100644 --- a/frontend/src/apollo.ts +++ b/frontend/src/apollo.ts @@ -1,4 +1,6 @@ -import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'; +import { ApolloClient, ApolloLink, HttpLink, InMemoryCache } from '@apollo/client'; +import { SetContextLink } from '@apollo/client/link/context'; +import { getToken } from './auth/token'; /** * Felles GraphQL-klient for hele appen. Svar caches i minnet, så samme søk @@ -6,8 +8,23 @@ import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'; * Se vite.config.ts. */ +// Sender selve forespørselen til backend +const httpLink = new HttpLink({ uri: `${import.meta.env.BASE_URL}graphql` }); + +// Legger tokenet i headeren på hver forespørsel, hvis brukeren er innlogget +const authLink = new SetContextLink((prevContext) => { + const token = getToken(); + return { + headers: { + ...prevContext.headers, + ...(token ? { authorization: `Bearer ${token}` } : {}), + }, + }; +}); + export const client = new ApolloClient({ - link: new HttpLink({ uri: `${import.meta.env.BASE_URL}graphql` }), + // Rekkefølgen betyr noe: først legges tokenet på, så sendes forespørselen + link: ApolloLink.from([authLink, httpLink]), cache: new InMemoryCache({ typePolicies: { // Sjangre og plattformer er begge "Facet" og kan ha samme id. @@ -15,4 +32,4 @@ export const client = new ApolloClient({ Facet: { keyFields: false }, }, }), -}); +}); \ No newline at end of file diff --git a/frontend/src/auth/token.ts b/frontend/src/auth/token.ts new file mode 100644 index 0000000..eb15600 --- /dev/null +++ b/frontend/src/auth/token.ts @@ -0,0 +1,8 @@ +/** + * Henter nøkkel, og legger inn mulighet for å lagre og slette nøkkelen + */ + +const KEY = 'token'; +export const getToken = () => localStorage.getItem(KEY); +export const saveToken = (token: string) => localStorage.setItem(KEY, token); +export const removeToken = () => localStorage.removeItem(KEY); \ No newline at end of file From 10f66ab2758e59eae2080cbdc2ae2588a69ea678 Mon Sep 17 00:00:00 2001 From: johannes Date: Thu, 22 Oct 2026 13:50:43 +0200 Subject: [PATCH 2/8] =?UTF-8?q?feat(frontend):=20GraphQL-sp=C3=B8rringer?= =?UTF-8?q?=20for=20me,=20login=20og=20register?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/graphql/auth.ts | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 frontend/src/graphql/auth.ts diff --git a/frontend/src/graphql/auth.ts b/frontend/src/graphql/auth.ts new file mode 100644 index 0000000..1963ecf --- /dev/null +++ b/frontend/src/graphql/auth.ts @@ -0,0 +1,49 @@ +import { gql, type TypedDocumentNode } from '@apollo/client'; + +/** En innlogget bruker slik backend returnerer den */ +export interface User { + id: string; + username: string; +} + +/** Det login og register tar inn */ +interface AuthVars { + username: string; + password: string; +} + +/** Henter brukeren som eier tokenet. Gir null hvis ingen er innlogget. */ +export const ME: TypedDocumentNode<{ me: User | null }> = gql` + query Me { + me { + id + username + } + } +`; + +/** Logger inn og får tilbake et token og brukeren */ +export const LOGIN: TypedDocumentNode<{ login: { token: string; user: User } }, AuthVars> = gql` + mutation Login($username: String!, $password: String!) { + login(username: $username, password: $password) { + token + user { + id + username + } + } + } +`; + +/** Registrerer og får tilbake et token og brukeren */ +export const REGISTER: TypedDocumentNode<{ register: { token: string; user: User } }, AuthVars> = gql` + mutation Register($username: String!, $password: String!) { + register(username: $username, password: $password) { + token + user { + id + username + } + } + } +`; \ No newline at end of file From dd632f394443d88f3bf028a11d124e033634ffe8 Mon Sep 17 00:00:00 2001 From: johannes Date: Thu, 22 Oct 2026 13:56:27 +0200 Subject: [PATCH 3/8] feat(frontend): useAuth-hook for innlogging, registrering og utlogging --- frontend/src/hooks/useAuth.ts | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 frontend/src/hooks/useAuth.ts diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts new file mode 100644 index 0000000..80dbf11 --- /dev/null +++ b/frontend/src/hooks/useAuth.ts @@ -0,0 +1,40 @@ +import { useApolloClient, useMutation, useQuery } from '@apollo/client/react'; +import { LOGIN, ME, REGISTER } from '../graphql/auth'; +import { removeToken, saveToken } from '../auth/token'; + +/** + * Innlogget bruker og funksjoner for å logge inn, registrere og logge ut. + * ME-spørringen sier hvem som er innlogget. Når tokenet endres, nullstilles + * Apollo-cachen, så alle spørringer hentes på nytt med riktig bruker. + */ +export function useAuth() { + const client = useApolloClient(); + const { data, loading } = useQuery(ME); + const [loginMutation] = useMutation(LOGIN); + const [registerMutation] = useMutation(REGISTER); + + // Kaster en feil ved feil brukernavn/passord. Den fanges i skjemaet. + const logIn = async (username: string, password: string) => { + const { data } = await loginMutation({ variables: { username, password } }); + if (data) { + saveToken(data.login.token); + await client.resetStore(); + } + }; + + /* Lager ny bruker og logger inn med en gang. Kaster feil hvis brukernavnet er tatt. */ + const register = async (username: string, password: string) => { + const { data } = await registerMutation({ variables: { username, password } }); + if (data) { + saveToken(data.register.token); + await client.resetStore(); + } + }; + + const logOut = async () => { + removeToken(); + await client.resetStore(); + }; + + return { user: data?.me ?? null, loading, logIn, register, logOut }; +} \ No newline at end of file From 1f944ed6efffff48a3c19930bd6676a181e39e64 Mon Sep 17 00:00:00 2001 From: johannes Date: Thu, 22 Oct 2026 14:09:33 +0200 Subject: [PATCH 4/8] feat(frontend): vis innlogget bruker og logg ut-knapp i headeren --- frontend/src/apollo.ts | 2 +- frontend/src/auth/token.ts | 2 +- frontend/src/components/Header/Header.tsx | 10 ++++++++++ frontend/src/graphql/auth.ts | 19 ++++++++++--------- frontend/src/hooks/useAuth.ts | 2 +- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/frontend/src/apollo.ts b/frontend/src/apollo.ts index 35ef6be..e85a9dc 100644 --- a/frontend/src/apollo.ts +++ b/frontend/src/apollo.ts @@ -32,4 +32,4 @@ export const client = new ApolloClient({ Facet: { keyFields: false }, }, }), -}); \ No newline at end of file +}); diff --git a/frontend/src/auth/token.ts b/frontend/src/auth/token.ts index eb15600..2a3334d 100644 --- a/frontend/src/auth/token.ts +++ b/frontend/src/auth/token.ts @@ -5,4 +5,4 @@ const KEY = 'token'; export const getToken = () => localStorage.getItem(KEY); export const saveToken = (token: string) => localStorage.setItem(KEY, token); -export const removeToken = () => localStorage.removeItem(KEY); \ No newline at end of file +export const removeToken = () => localStorage.removeItem(KEY); diff --git a/frontend/src/components/Header/Header.tsx b/frontend/src/components/Header/Header.tsx index c9dd7f3..0616d9e 100644 --- a/frontend/src/components/Header/Header.tsx +++ b/frontend/src/components/Header/Header.tsx @@ -3,6 +3,7 @@ import { GameSearch } from '../GameSearch/GameSearch'; import type { Suggestion } from '../../graphql/searchSuggestions'; import { ThemeToggle } from '../ThemeToggle/ThemeToggle'; import './Header.css'; +import { useAuth } from '../../hooks/useAuth'; interface HeaderProps { onSearch: (term: string) => void; @@ -27,6 +28,7 @@ export function Header({ showFavorites, onToggleFavorites, }: HeaderProps) { + const { user, logOut } = useAuth(); return (
@@ -34,6 +36,14 @@ export function Header({