diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..6484d1d
--- /dev/null
+++ b/.env.example
@@ -0,0 +1 @@
+VITE_TMDB_TOKEN=[legg-inn-api-key-her]
\ No newline at end of file
diff --git a/backup/App.css b/backup/App.css
new file mode 100644
index 0000000..f90339d
--- /dev/null
+++ b/backup/App.css
@@ -0,0 +1,184 @@
+.counter {
+ font-size: 16px;
+ padding: 5px 10px;
+ border-radius: 5px;
+ color: var(--accent);
+ background: var(--accent-bg);
+ border: 2px solid transparent;
+ transition: border-color 0.3s;
+ margin-bottom: 24px;
+
+ &:hover {
+ border-color: var(--accent-border);
+ }
+ &:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+ }
+}
+
+.hero {
+ position: relative;
+
+ .base,
+ .framework,
+ .vite {
+ inset-inline: 0;
+ margin: 0 auto;
+ }
+
+ .base {
+ width: 170px;
+ position: relative;
+ z-index: 0;
+ }
+
+ .framework,
+ .vite {
+ position: absolute;
+ }
+
+ .framework {
+ z-index: 1;
+ top: 34px;
+ height: 28px;
+ transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
+ scale(1.4);
+ }
+
+ .vite {
+ z-index: 0;
+ top: 107px;
+ height: 26px;
+ width: auto;
+ transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
+ scale(0.8);
+ }
+}
+
+#center {
+ display: flex;
+ flex-direction: column;
+ gap: 25px;
+ place-content: center;
+ place-items: center;
+ flex-grow: 1;
+
+ @media (max-width: 1024px) {
+ padding: 32px 20px 24px;
+ gap: 18px;
+ }
+}
+
+#next-steps {
+ display: flex;
+ border-top: 1px solid var(--border);
+ text-align: left;
+
+ & > div {
+ flex: 1 1 0;
+ padding: 32px;
+ @media (max-width: 1024px) {
+ padding: 24px 20px;
+ }
+ }
+
+ .icon {
+ margin-bottom: 16px;
+ width: 22px;
+ height: 22px;
+ }
+
+ @media (max-width: 1024px) {
+ flex-direction: column;
+ text-align: center;
+ }
+}
+
+#docs {
+ border-right: 1px solid var(--border);
+
+ @media (max-width: 1024px) {
+ border-right: none;
+ border-bottom: 1px solid var(--border);
+ }
+}
+
+#next-steps ul {
+ list-style: none;
+ padding: 0;
+ display: flex;
+ gap: 8px;
+ margin: 32px 0 0;
+
+ .logo {
+ height: 18px;
+ }
+
+ a {
+ color: var(--text-h);
+ font-size: 16px;
+ border-radius: 6px;
+ background: var(--social-bg);
+ display: flex;
+ padding: 6px 12px;
+ align-items: center;
+ gap: 8px;
+ text-decoration: none;
+ transition: box-shadow 0.3s;
+
+ &:hover {
+ box-shadow: var(--shadow);
+ }
+ .button-icon {
+ height: 18px;
+ width: 18px;
+ }
+ }
+
+ @media (max-width: 1024px) {
+ margin-top: 20px;
+ flex-wrap: wrap;
+ justify-content: center;
+
+ li {
+ flex: 1 1 calc(50% - 8px);
+ }
+
+ a {
+ width: 100%;
+ justify-content: center;
+ box-sizing: border-box;
+ }
+ }
+}
+
+#spacer {
+ height: 88px;
+ border-top: 1px solid var(--border);
+ @media (max-width: 1024px) {
+ height: 48px;
+ }
+}
+
+.ticks {
+ position: relative;
+ width: 100%;
+
+ &::before,
+ &::after {
+ content: '';
+ position: absolute;
+ top: -4.5px;
+ border: 5px solid transparent;
+ }
+
+ &::before {
+ left: 0;
+ border-left-color: var(--border);
+ }
+ &::after {
+ right: 0;
+ border-right-color: var(--border);
+ }
+}
diff --git a/backup/App.tsx b/backup/App.tsx
new file mode 100644
index 0000000..9ca8e8d
--- /dev/null
+++ b/backup/App.tsx
@@ -0,0 +1,122 @@
+import { useState } from 'react'
+import heroImg from './assets/hero.png'
+import reactLogo from './assets/react.svg'
+import viteLogo from './assets/vite.svg'
+import './App.css'
+
+function App() {
+ const [count, setCount] = useState(0)
+
+ return (
+ <>
+
+
+
+
Get started
+
+ Edit src/App.tsx and save to test HMR
+
+
+
+
+
+
+
+
+
+
+
Documentation
+
Your questions, answered
+
+
+
+
+
Connect with us
+
Join the Vite community
+
+
+
+
+
+
+ >
+ )
+}
+
+export default App
diff --git a/backup/assets/hero.png b/backup/assets/hero.png
new file mode 100644
index 0000000..02251f4
Binary files /dev/null and b/backup/assets/hero.png differ
diff --git a/backup/assets/react.svg b/backup/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/backup/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/backup/assets/vite.svg b/backup/assets/vite.svg
new file mode 100644
index 0000000..5101b67
--- /dev/null
+++ b/backup/assets/vite.svg
@@ -0,0 +1 @@
+
diff --git a/backup/index.css b/backup/index.css
new file mode 100644
index 0000000..5fb3313
--- /dev/null
+++ b/backup/index.css
@@ -0,0 +1,111 @@
+:root {
+ --text: #6b6375;
+ --text-h: #08060d;
+ --bg: #fff;
+ --border: #e5e4e7;
+ --code-bg: #f4f3ec;
+ --accent: #aa3bff;
+ --accent-bg: rgba(170, 59, 255, 0.1);
+ --accent-border: rgba(170, 59, 255, 0.5);
+ --social-bg: rgba(244, 243, 236, 0.5);
+ --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;
+ --mono: ui-monospace, Consolas, monospace;
+
+ font: 18px/145% var(--sans);
+ letter-spacing: 0.18px;
+ color-scheme: light dark;
+ color: var(--text);
+ background: var(--bg);
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ @media (max-width: 1024px) {
+ font-size: 16px;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --text: #9ca3af;
+ --text-h: #f3f4f6;
+ --bg: #16171d;
+ --border: #2e303a;
+ --code-bg: #1f2028;
+ --accent: #c084fc;
+ --accent-bg: rgba(192, 132, 252, 0.15);
+ --accent-border: rgba(192, 132, 252, 0.5);
+ --social-bg: rgba(47, 48, 58, 0.5);
+ --shadow:
+ rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
+ }
+
+ #social .button-icon {
+ filter: invert(1) brightness(2);
+ }
+}
+
+#root {
+ width: 1126px;
+ max-width: 100%;
+ margin: 0 auto;
+ text-align: center;
+ border-inline: 1px solid var(--border);
+ min-height: 100svh;
+ display: flex;
+ flex-direction: column;
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+}
+
+h1,
+h2 {
+ font-family: var(--heading);
+ font-weight: 500;
+ color: var(--text-h);
+}
+
+h1 {
+ font-size: 56px;
+ letter-spacing: -1.68px;
+ margin: 32px 0;
+ @media (max-width: 1024px) {
+ font-size: 36px;
+ margin: 20px 0;
+ }
+}
+h2 {
+ font-size: 24px;
+ line-height: 118%;
+ letter-spacing: -0.24px;
+ margin: 0 0 8px;
+ @media (max-width: 1024px) {
+ font-size: 20px;
+ }
+}
+p {
+ margin: 0;
+}
+
+code,
+.counter {
+ font-family: var(--mono);
+ display: inline-flex;
+ border-radius: 4px;
+ color: var(--text-h);
+}
+
+code {
+ font-size: 15px;
+ line-height: 135%;
+ padding: 4px 8px;
+ background: var(--code-bg);
+}
diff --git a/backup/main.tsx b/backup/main.tsx
new file mode 100644
index 0000000..bef5202
--- /dev/null
+++ b/backup/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/package.json b/package.json
index dc52a18..20fbe65 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@tanstack/react-query": "^5.102.8",
"react": "^19.2.8",
"react-dom": "^19.2.8"
},
@@ -17,6 +18,7 @@
"@babel/core": "^7.29.7",
"@eslint/js": "^10.0.1",
"@rolldown/plugin-babel": "^0.2.3",
+ "@tanstack/eslint-plugin-query": "^5.102.8",
"@types/babel__core": "^7.20.5",
"@types/node": "^24.13.3",
"@types/react": "^19.2.18",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7d776fb..074ca13 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ '@tanstack/react-query':
+ specifier: ^5.102.8
+ version: 5.102.8(react@19.2.8)
react:
specifier: ^19.2.8
version: 19.2.8
@@ -24,6 +27,9 @@ importers:
'@rolldown/plugin-babel':
specifier: ^0.2.3
version: 0.2.3(@babel/core@7.29.7)(rolldown@1.2.7)(vite@8.2.2(@types/node@24.13.3))
+ '@tanstack/eslint-plugin-query':
+ specifier: ^5.102.8
+ version: 5.102.8(eslint@10.9.1)(typescript@6.0.3)
'@types/babel__core':
specifier: ^7.20.5
version: 7.20.5
@@ -327,6 +333,23 @@ packages:
'@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
+ '@tanstack/eslint-plugin-query@5.102.8':
+ resolution: {integrity: sha512-zRjG2PL3zvoqnsSNJFyfX5Mo+OwRwTbLERwZVO3oNQQn82V949INwaLrprzsOW/ivuow7gASeAUUC24xH7kwSQ==}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: ^5.6.0 || ^6.0.0 || ^7.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@tanstack/query-core@5.102.8':
+ resolution: {integrity: sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg==}
+
+ '@tanstack/react-query@5.102.8':
+ resolution: {integrity: sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A==}
+ peerDependencies:
+ react: ^18 || ^19
+
'@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@@ -1180,6 +1203,22 @@ snapshots:
'@rolldown/pluginutils@1.0.1': {}
+ '@tanstack/eslint-plugin-query@5.102.8(eslint@10.9.1)(typescript@6.0.3)':
+ dependencies:
+ '@typescript-eslint/utils': 8.69.0(eslint@10.9.1)(typescript@6.0.3)
+ eslint: 10.9.1
+ optionalDependencies:
+ typescript: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@tanstack/query-core@5.102.8': {}
+
+ '@tanstack/react-query@5.102.8(react@19.2.8)':
+ dependencies:
+ '@tanstack/query-core': 5.102.8
+ react: 19.2.8
+
'@types/babel__core@7.20.5':
dependencies:
'@babel/parser': 7.29.8
diff --git a/src/App.tsx b/src/App.tsx
index 9ca8e8d..d1d2c93 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,122 +1,42 @@
+// App.tsx
import { useState } from 'react'
-import heroImg from './assets/hero.png'
-import reactLogo from './assets/react.svg'
-import viteLogo from './assets/vite.svg'
-import './App.css'
+import { useCurMovies, usePopMovies } from './hooks/useMovies'
+import { Button } from './components/Button'
function App() {
- const [count, setCount] = useState(0)
+ const [view, setView] = useState<'popular' | 'current'>('popular')
- return (
- <>
-
-
-
-
Get started
-
- Edit src/App.tsx and save to test HMR
-
-
-
-
+ const popular = usePopMovies(view === 'popular')
+ const current = useCurMovies(view === 'current')
+
+ const viewConfig = {
+ popular: { title: 'Populære filmer', query: popular },
+ current: { title: 'Nye filmer', query: current },
+ }
-
+ const { title, query } = viewConfig[view]
+ const { data, isLoading, error } = query
-
-
-
-
Documentation
-
Your questions, answered
-
-
-
-
-
Connect with us
-
Join the Vite community
-
-
-
+ if (isLoading) {
+ return Laster filmer...
+ }
-
-
- >
+ if (error) {
+ return Noe gikk galt: {error.message}
+ }
+
+ return (
+
+
{title}
+
+ {data.results.map((movie: any) => (
+ - {movie.title}
+ ))}
+
+
)
}
-export default App
+export default App
\ No newline at end of file
diff --git a/src/App.txt b/src/App.txt
new file mode 100644
index 0000000..9ca8e8d
--- /dev/null
+++ b/src/App.txt
@@ -0,0 +1,122 @@
+import { useState } from 'react'
+import heroImg from './assets/hero.png'
+import reactLogo from './assets/react.svg'
+import viteLogo from './assets/vite.svg'
+import './App.css'
+
+function App() {
+ const [count, setCount] = useState(0)
+
+ return (
+ <>
+
+
+
+
Get started
+
+ Edit src/App.tsx and save to test HMR
+
+
+ setCount((count) => count + 1)}
+ >
+ Count is {count}
+
+
+
+
+
+
+
+
+
Documentation
+
Your questions, answered
+
+
+
+
+
Connect with us
+
Join the Vite community
+
+
+
+
+
+
+ >
+ )
+}
+
+export default App
diff --git a/src/api/tmdb.ts b/src/api/tmdb.ts
new file mode 100644
index 0000000..05627dc
--- /dev/null
+++ b/src/api/tmdb.ts
@@ -0,0 +1,35 @@
+// TOKEN er lagret i root/.env.local, denne må alle legge til manuelt selv
+// og så må vi finne en løsning for å ha den tilgjengelig på serveren når vi
+// får satt den opp senere.
+
+// BASE_URL er lenken vi bruker i en API request, her til TMDB
+const TOKEN = import.meta.env.VITE_TMDB_TOKEN // v4 read access token
+const BASE_URL = 'https://api.themoviedb.org/3'
+
+export async function fetchPopularMovies() {
+ const res = await fetch(
+ `${BASE_URL}/movie/popular?language=en-US&page=1`,
+ {
+ headers: {
+ accept: 'application/json',
+ Authorization: `Bearer ${TOKEN}`,
+ },
+ }
+ )
+ if (!res.ok) throw new Error('Klarte ikke hente populære filmer...')
+ return res.json()
+}
+
+export async function fetchCurrentMovies() {
+ const res = await fetch(
+ `${BASE_URL}/movie/now_playing?language=en-US&page=1`,
+ {
+ headers: {
+ accept: 'application/json',
+ Authorization: `Bearer ${TOKEN}`,
+ },
+ }
+ )
+ if (!res.ok) throw new Error('Klarte ikke hente aktuelle filmer...')
+ return res.json()
+}
\ No newline at end of file
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
new file mode 100644
index 0000000..332f004
--- /dev/null
+++ b/src/components/Button.tsx
@@ -0,0 +1,8 @@
+type ButtonProps = {
+ text: string;
+ onClick: () => void;
+};
+
+export function Button({ text, onClick }: ButtonProps) {
+ return {text};
+}
\ No newline at end of file
diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/hooks/useMovies.ts b/src/hooks/useMovies.ts
new file mode 100644
index 0000000..3d84e80
--- /dev/null
+++ b/src/hooks/useMovies.ts
@@ -0,0 +1,19 @@
+// hooks/useMovies.ts
+import { useQuery } from '@tanstack/react-query'
+import { fetchCurrentMovies, fetchPopularMovies } from '../api/tmdb'
+
+export function usePopMovies(enabled: boolean) {
+ return useQuery({
+ queryKey: ['movies', 'popular'],
+ queryFn: fetchPopularMovies,
+ enabled,
+ })
+}
+
+export function useCurMovies(enabled: boolean) {
+ return useQuery({
+ queryKey: ['movies', 'current'],
+ queryFn: fetchCurrentMovies,
+ enabled,
+ })
+}
\ No newline at end of file
diff --git a/src/hooks/usePopMovies.ts b/src/hooks/usePopMovies.ts
new file mode 100644
index 0000000..621df77
--- /dev/null
+++ b/src/hooks/usePopMovies.ts
@@ -0,0 +1,10 @@
+// hooks/useMovies.ts
+import { useQuery } from '@tanstack/react-query'
+import { fetchCurrentMovies, fetchPopularMovies } from '../api/tmdb'
+
+export function useMovies() {
+ return useQuery({
+ queryKey: ['movies', 'popular'],
+ queryFn: fetchPopularMovies,
+ })
+}
\ No newline at end of file
diff --git a/src/layout/MainLayout.tsx b/src/layout/MainLayout.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/main.tsx b/src/main.tsx
index bef5202..fbea0ce 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,10 +1,15 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import './index.css'
import App from './App.tsx'
+const queryClient = new QueryClient()
+
createRoot(document.getElementById('root')!).render(
-
+
+
+
,
)
diff --git a/src/tmp.txt b/src/tmp.txt
new file mode 100644
index 0000000..9ca8e8d
--- /dev/null
+++ b/src/tmp.txt
@@ -0,0 +1,122 @@
+import { useState } from 'react'
+import heroImg from './assets/hero.png'
+import reactLogo from './assets/react.svg'
+import viteLogo from './assets/vite.svg'
+import './App.css'
+
+function App() {
+ const [count, setCount] = useState(0)
+
+ return (
+ <>
+
+
+
+
Get started
+
+ Edit src/App.tsx and save to test HMR
+
+
+ setCount((count) => count + 1)}
+ >
+ Count is {count}
+
+
+
+
+
+
+
+
+
Documentation
+
Your questions, answered
+
+
+
+
+
Connect with us
+
Join the Vite community
+
+
+
+
+
+
+ >
+ )
+}
+
+export default App
Connect with us
+Join the Vite community
++-
+
+
+ GitHub
+
+
+ -
+
+
+ Discord
+
+
+ -
+
+
+ X.com
+
+
+ -
+
+
+ Bluesky
+
+
+
+