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 ( + <> +
+
+ + React logo + Vite logo +
+
+

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 @@ +Vite 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 ( - <> -
-
- - React logo - Vite logo -
-
-

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}

+ +
) } -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 ( + <> +
+
+ + React logo + Vite logo +
+
+

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/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 ; +} \ 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 ( + <> +
+
+ + React logo + Vite logo +
+
+

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