diff --git a/FrameRate/package-lock.json b/FrameRate/package-lock.json index d00450f..51b3d29 100644 --- a/FrameRate/package-lock.json +++ b/FrameRate/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.0", "dependencies": { "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", @@ -644,6 +645,12 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@remix-run/route-pattern": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/@remix-run/route-pattern/-/route-pattern-0.22.1.tgz", + "integrity": "sha512-czdGJWh09Lapvcqt7Vb09KlrU2PhRJ8xQaI+AItTuD9aDJ5MAgxnS38lnys6Ll+6RJEqPiaUqTwIQhVLwFvv+w==", + "license": "MIT" + }, "node_modules/@rolldown/binding-android-arm-eabi": { "version": "1.2.12", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", @@ -754,9 +761,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -774,9 +778,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -794,9 +795,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -814,9 +812,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -834,9 +829,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -854,9 +846,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1831,6 +1820,12 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz", + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3729,9 +3724,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3753,9 +3745,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3777,9 +3766,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3801,9 +3787,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4298,6 +4281,28 @@ "react": "^19.3.0" } }, + "node_modules/react-router": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-8.4.0.tgz", + "integrity": "sha512-JtydAkBvU9UTEe5qNC+POhu+ZBNM7rlKY2IegwXc7Idj7xfRG16x5RZrw3mju+XlqBxfvb2ky9P3jUp9WyWC1g==", + "license": "MIT", + "dependencies": { + "@remix-run/route-pattern": "^0.22.1", + "cookie-es": "^3.1.1" + }, + "engines": { + "node": ">=22.22.0" + }, + "peerDependencies": { + "react": ">=19.2.7", + "react-dom": ">=19.2.7" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", diff --git a/FrameRate/package.json b/FrameRate/package.json index ab4dade..5a1fc5b 100644 --- a/FrameRate/package.json +++ b/FrameRate/package.json @@ -13,7 +13,8 @@ }, "dependencies": { "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/FrameRate/src/App.tsx b/FrameRate/src/App.tsx index d80a585..f3b5914 100644 --- a/FrameRate/src/App.tsx +++ b/FrameRate/src/App.tsx @@ -1,12 +1,22 @@ import './index.css'; +import { Route, Routes } from 'react-router'; +import LoginPage from './pages/LoginPage'; import { Layout } from './components/layout/Layout'; import { FeaturedMovie } from './components/FeaturedMovie'; function App() { return ( - - - + + + + + } + /> + } /> + ); } diff --git a/FrameRate/src/components/layout/Header.tsx b/FrameRate/src/components/layout/Header.tsx index 1e7a485..d76feb6 100644 --- a/FrameRate/src/components/layout/Header.tsx +++ b/FrameRate/src/components/layout/Header.tsx @@ -1,3 +1,5 @@ +import { Link } from 'react-router'; + // ---------- Lyset i menyen ---------- /** Flytter lyset i menyen til en x-posisjon (piksler fra venstre kant av menyen). */ @@ -63,9 +65,9 @@ export function Header() { Min liste - + Logg inn - + ); } diff --git a/FrameRate/src/main.tsx b/FrameRate/src/main.tsx index df655ea..cfb690d 100644 --- a/FrameRate/src/main.tsx +++ b/FrameRate/src/main.tsx @@ -1,10 +1,13 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router'; import './index.css'; import App from './App.tsx'; createRoot(document.getElementById('root')!).render( - + + + ); diff --git a/FrameRate/src/pages/LoginPage.css b/FrameRate/src/pages/LoginPage.css new file mode 100644 index 0000000..e0213dd --- /dev/null +++ b/FrameRate/src/pages/LoginPage.css @@ -0,0 +1,118 @@ +.login-page { + min-height: 100svh; + display: grid; + place-items: center; + padding: 2rem 1rem; + box-sizing: border-box; + background: radial-gradient(ellipse at top, rgba(242, 182, 48, 0.09), transparent 65%), #0b0c10; +} +.login-card { + width: 100%; + max-width: 27rem; + padding: clamp(1.5rem, 5vw, 3rem); + box-sizing: border-box; + border: 1px solid #2a2d35; + border-radius: 12px; + background: #15171c; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4); +} +.login-brand { + color: white; + font: + bold 1.75rem Georgia, + serif; + text-decoration: none; +} +.login-eyebrow { + margin: 0.5rem 0 2rem; + color: #f2b630; + font-style: italic; +} +.login-card h1 { + margin: 0 0 0.75rem; + font: + bold 2rem Georgia, + serif; +} +.login-intro, +.login-message { + color: #a0a4ad; + line-height: 1.6; +} +.login-form { + display: flex; + flex-direction: column; + margin-top: 2rem; +} +.login-form label { + margin-bottom: 0.5rem; + font-weight: 600; +} +.login-form input { + min-width: 0; + margin-bottom: 1.25rem; + padding: 0.85rem 1rem; + border: 1px solid #3a3e48; + border-radius: 8px; + background: #0b0c10; + color: white; + font: inherit; +} +.login-form input:focus-visible { + outline: 2px solid #f2b630; + outline-offset: 2px; +} +.login-form button { + margin-top: 0.5rem; + padding: 0.85rem 1rem; + border: none; + border-radius: 8px; + background: #f2b630; + color: #0b0c10; + font: inherit; + font-weight: bold; + cursor: pointer; +} +.login-form button:hover { + background: #ffca50; +} +.login-message { + margin: 0.75rem 0; +} +.login-hint { + margin: -0.75rem 0 1.25rem; + color: #a0a4ad; + font-size: 0.875rem; + line-height: 1.6; +} +.login-switch { + color: #a0a4ad; + line-height: 1.6; +} +.login-switch button { + padding: 0; + border: none; + background: none; + color: #f2b630; + font: inherit; + font-weight: 600; + cursor: pointer; +} +.login-switch button:hover { + color: #ffca50; + text-decoration: underline; +} +.login-switch button:focus-visible, +.login-form button:focus-visible { + outline: 2px solid #f2b630; + outline-offset: 4px; +} +.login-back { + display: inline-block; + margin-top: 1rem; + color: #a0a4ad; + text-decoration: none; +} +.login-back:hover { + color: white; +} diff --git a/FrameRate/src/pages/LoginPage.tsx b/FrameRate/src/pages/LoginPage.tsx new file mode 100644 index 0000000..1872e67 --- /dev/null +++ b/FrameRate/src/pages/LoginPage.tsx @@ -0,0 +1,115 @@ +import { useState } from 'react'; +import { Link } from 'react-router'; +import './LoginPage.css'; + +function LoginPage() { + const [isSignUp, setIsSignUp] = useState(false); + const [message, setMessage] = useState(''); + return ( +
+
+ + Frame Rate + +

Rate the frames

+

{isSignUp ? 'Opprett konto' : 'Velkommen tilbake'}

+

+ {isSignUp + ? 'Bli med og samle filmene og anmeldelsene dine på ett sted.' + : 'Logg inn for å samle filmene og anmeldelsene dine.'} +

+
{ + event.preventDefault(); + setMessage( + isSignUp + ? 'Oppretting av konto er ikke koblet til ennå.' + : 'Innlogging er ikke koblet til ennå.' + ); + }} + > + + + {isSignUp && ( + <> + + + + )} + + { + const confirmation = event.currentTarget.form?.elements.namedItem('confirm-password'); + if (confirmation instanceof HTMLInputElement) { + confirmation.setCustomValidity( + confirmation.value && confirmation.value !== event.currentTarget.value + ? 'Passordene må være like.' + : '' + ); + } + }} + required + /> + {isSignUp && ( + <> +

+ Bruk minst 8 tegn. +

+ + { + const input = event.currentTarget; + const password = input.form?.elements.namedItem('password'); + input.setCustomValidity( + password instanceof HTMLInputElement && input.value !== password.value + ? 'Passordene må være like.' + : '' + ); + }} + onInvalid={event => { + const input = event.currentTarget; + if (input.validity.valueMissing) + input.setCustomValidity('Bekreft passordet ditt.'); + }} + /> + + )} + +

+ {message} +

+
+

+ {isSignUp ? 'Har du allerede konto?' : 'Har du ikke konto?'}{' '} + +

+ + ← Tilbake til forsiden + +
+
+ ); +} + +export default LoginPage; diff --git a/docs/AI_Deklarasjon/AI_Deklarasjon.md b/docs/AI_Deklarasjon/AI_Deklarasjon.md index 90dbab4..750fec6 100644 --- a/docs/AI_Deklarasjon/AI_Deklarasjon.md +++ b/docs/AI_Deklarasjon/AI_Deklarasjon.md @@ -23,12 +23,13 @@ Her dokumenterer vi bruk av KI-verktøy i prosjektet, med én del per pull reque - Bestemt arkitektur og datamodell i planleggingsdokumentet: hvilke funksjoner appen skal ha, hvilke data som trengs, og at filmene importeres fra TMDB én gang til vår egen database - Sammenlignet databasen med planleggingsdokumentet - Kodegjennomgang (review) av et annet gruppemedlem, som førte til endringer i hvilke felter som lagres -# PR #21 - Setup av Prettier og linter + +## PR #21 - Setup av Prettier og linter **Vertøy:** Claude (Sonnet 5.5) via claude.ai **Brukt av:** Kevin -## Hva KI ble brukt til +### Hva KI ble brukt til - Finne riktige komandoer