diff --git a/client/.env.example b/client/.env.example index f40b204..0ba98e5 100644 --- a/client/.env.example +++ b/client/.env.example @@ -1,2 +1,2 @@ # Adressen til Graphql backend. Kopier dette til .env.local -VITE_GRAPHQL_URL=http://localhost:3001/graphql \ No newline at end of file +VITE_GRAPHQL_URL=/project2/graphql \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index e6777b2..4723f7e 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,13 +1,16 @@ -import { Route, Routes } from 'react-router' -import Layout from './components/layout/Layout' -import HomePage from './routes/HomePage' -import RecipeListPage from './routes/RecipeListPage' -import RecipeDetailPage from './routes/RecipeDetailPage' -import FavoritesPage from './routes/FavoritesPage' -import ShoppingListPage from './routes/ShoppingListPage' -import ProfilePage from './routes/ProfilePage' -import NotFoundPage from './routes/NotFoundPage' -import DesignTestPage from './routes/DesignTestPage' +import { Route, Routes } from 'react-router'; +import Layout from './components/layout/Layout'; +import HomePage from './routes/HomePage'; +import RecipeListPage from './routes/RecipeListPage'; +import RecipeDetailPage from './routes/RecipeDetailPage'; +import FavoritesPage from './routes/FavoritesPage'; +import ShoppingListPage from './routes/ShoppingListPage'; +import ProfilePage from './routes/ProfilePage'; +import NotFoundPage from './routes/NotFoundPage'; +import DesignTestPage from './routes/DesignTestPage'; +import LoginPage from './routes/LoginPage'; +import TestUserLoginPage from './routes/TestUserLoginPage'; +import RequireAuth from './components/layout/RequireAuth'; // Flyttet testsiden til DesignTestPage under routes export default function App() { @@ -21,9 +24,16 @@ export default function App() { } /> } /> - } /> } /> - } /> + + } /> + } /> + + {/* alle sidene som krever innlogging */} + }> + } /> + } /> + {/* Testside for designsystemet */} {import.meta.env.DEV && ( @@ -34,5 +44,5 @@ export default function App() { } /> - ) -} \ No newline at end of file + ); +} diff --git a/client/src/components/layout/Header.tsx b/client/src/components/layout/Header.tsx index 654a49e..032d8cc 100644 --- a/client/src/components/layout/Header.tsx +++ b/client/src/components/layout/Header.tsx @@ -1,48 +1,50 @@ -import { useState } from 'react' -import { Link, NavLink, useSearchParams } from 'react-router' -import SearchBar from './SearchBar' -import { Button } from '../ui/Button' -import { Dialog } from '../ui/Dialog' -import NewRecipePage from '../../routes/NewRecipePage' -import { RecipeStepIndicator } from '../new-recipe/RecipeStepIndicator' +import { useState } from 'react'; +import { Link, NavLink, useSearchParams } from 'react-router'; +import SearchBar from './SearchBar'; +import { Button } from '../ui/Button'; +import { Dialog } from '../ui/Dialog'; +import NewRecipePage from '../../routes/NewRecipePage'; +import { RecipeStepIndicator } from '../new-recipe/RecipeStepIndicator'; +import { useCurrentUser } from '../../lib/useCurrentUser'; // Fokusmarkering ved tastaturnavigasjon const focusRing = - 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary' + 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary'; // Aktiv lenke markeres med både farge og understrek function navLinkClass({ isActive }: { isActive: boolean }) { - const base = `whitespace-nowrap font-body transition-colors hover:text-primary ${focusRing}` + const base = `whitespace-nowrap font-body transition-colors hover:text-primary ${focusRing}`; return isActive ? `${base} text-primary underline underline-offset-4` - : `${base} text-text` + : `${base} text-text`; } // Styling for knapper/lenker i header -const buttonBase = `whitespace-nowrap rounded-medium px-3 py-2 text-sm font-body transition-colors ${focusRing}` +const buttonBase = `whitespace-nowrap rounded-medium px-3 py-2 text-sm font-body transition-colors ${focusRing}`; // Brukerknapp function userClass({ isActive }: { isActive: boolean }) { - const base = `${buttonBase} border border-primary` + const base = `${buttonBase} border border-primary`; return isActive ? `${base} bg-primary text-background` - : `${base} text-primary hover:bg-pink-light` + : `${base} text-primary hover:bg-pink-light`; } export default function Header() { + //hvem som er logget inn + const { user } = useCurrentUser(); // Søkeordet fra URL-en - const [searchParams] = useSearchParams() - const query = searchParams.get('q') ?? '' + const [searchParams] = useSearchParams(); + const query = searchParams.get('q') ?? ''; // Holder styr på hvilket steg i Ny oppskrift brukeren er på - const [currentStep, setCurrentStep] = useState(1) + const [currentStep, setCurrentStep] = useState(1); return (
- {/* Logo */} {/* Søkefelt */} - + {/* Navigasjon */}
- ) -} \ No newline at end of file + ); +} diff --git a/client/src/components/layout/RequireAuth.tsx b/client/src/components/layout/RequireAuth.tsx new file mode 100644 index 0000000..7d355ed --- /dev/null +++ b/client/src/components/layout/RequireAuth.tsx @@ -0,0 +1,20 @@ +import { Navigate, Outlet } from 'react-router'; +import { useCurrentUser } from '../../lib/useCurrentUser'; + +// Sender utloggede brukere til innloggingssiden +export default function RequireAuth() { + const { user, loading } = useCurrentUser(); + + // venter på svar om hvem som logger inn + if (loading) { + return null; + } + + // hvis ikke innlogget, sender til innloggingsside + if (!user) { + return ; + } + + // hvis innlogget, vis side + return ; +} diff --git a/client/src/lib/apolloClient.ts b/client/src/lib/apolloClient.ts index 860cdd4..f4dbc89 100644 --- a/client/src/lib/apolloClient.ts +++ b/client/src/lib/apolloClient.ts @@ -1,6 +1,5 @@ import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'; - // adressen til graphql backend const httpLink = new HttpLink({ uri: import.meta.env.VITE_GRAPHQL_URL, diff --git a/client/src/lib/useCurrentUser.ts b/client/src/lib/useCurrentUser.ts new file mode 100644 index 0000000..b10dd7e --- /dev/null +++ b/client/src/lib/useCurrentUser.ts @@ -0,0 +1,19 @@ +import type { User } from '../types/user'; + +// Midlertidig bruker til backend er klar. Sett til null for å teste som utlogget. + +const MOCK_USER: User | null = null; + +/* const MOCK_USER: User | null = { + id: '1', + name: 'testesen', + authProvider: 'TEST', +}; */ + +// Hvem som er innlogget +export function useCurrentUser() { + return { + user: MOCK_USER, + loading: false, + }; +} diff --git a/client/src/routes/LoginPage.tsx b/client/src/routes/LoginPage.tsx new file mode 100644 index 0000000..97731a4 --- /dev/null +++ b/client/src/routes/LoginPage.tsx @@ -0,0 +1,60 @@ +import { useNavigate } from 'react-router'; +import { Button } from '../components/ui/Button'; + +// Innloggingsside +export default function LoginPage() { + const navigate = useNavigate(); + + return ( +
+

Velkommen tilbake

+

+ Logg inn med Google, utforsk oppskriftene som gjest eller testbruker. +

+ + {/* Midlertidig: Google og gjest kobles til backend senere */} +
+ +

+ Lagre favorittene dine og del egne oppskrifter. +

+
+ + {/* Skiller Google innlogging fra de andre valgene */} +
+ + eller + +
+ +
+ +

Se oppskrifter uten å logge inn.

+
+ +
+ {/* Går til logg inn siden for testbrukeren */} + +

Prøv med en ferdig testkonto.

+
+
+ ); +} diff --git a/client/src/routes/TestUserLoginPage.tsx b/client/src/routes/TestUserLoginPage.tsx new file mode 100644 index 0000000..ff341e1 --- /dev/null +++ b/client/src/routes/TestUserLoginPage.tsx @@ -0,0 +1,85 @@ +import { useState, type SubmitEvent } from 'react'; +import { Link } from 'react-router'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; +import { Button } from '../components/ui/Button'; +import { Field } from '../components/ui/Field'; +import { Input } from '../components/ui/Input'; + +// Innlogging for testbrukeren +export default function TestUserLoginPage() { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + // Om passordet vises som tekst eller prikker + const [showPassword, setShowPassword] = useState(false); + + function handleSubmit(event: SubmitEvent) { + // Hindrer at siden lastes på nytt + event.preventDefault(); + + // Midlertidig: kobles til backend senere + console.log('testbruker', username); + } + + return ( +
+

Innlogging

+

+ Logg inn med testbrukeren for en ferdiglaget bruker med oppskrifter. +

+ +
+ + setUsername(event.target.value)} + placeholder="Brukernavn" + autoComplete="username" + required + /> + + + +
+ setPassword(event.target.value)} + placeholder="Passord" + autoComplete="current-password" + className="pr-12" + required + /> + {/* Øyeknapp som viser eller skjuler passordet */} + +
+
+ + +
+ + + Tilbake til innloggingsvalg + +
+ ); +} diff --git a/client/src/types/user.ts b/client/src/types/user.ts new file mode 100644 index 0000000..0098090 --- /dev/null +++ b/client/src/types/user.ts @@ -0,0 +1,7 @@ +export type AuthProvider = 'GOOGLE' | 'GUEST' | 'TEST'; + +export type User = { + id: string; + name: string; + authProvider: AuthProvider; +}; diff --git a/client/vite.config.ts b/client/vite.config.ts index 3f961fd..f2af69c 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -6,4 +6,13 @@ import tailwindcss from '@tailwindcss/vite'; export default defineConfig({ plugins: [react(), tailwindcss()], base: '/project2/', + // sender graphql videre til backend + server: { + proxy: { + '/project2/graphql': { + target: 'http://localhost:3001', + rewrite: (path) => path.replace(/^\/project2/, ''), + }, + }, + }, });