Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Adressen til Graphql backend. Kopier dette til .env.local
VITE_GRAPHQL_URL=http://localhost:3001/graphql
VITE_GRAPHQL_URL=/project2/graphql
38 changes: 24 additions & 14 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -21,9 +24,16 @@ export default function App() {
<Route path="recipes" element={<RecipeListPage />} />
<Route path="recipes/:id" element={<RecipeDetailPage />} />

<Route path="favorites" element={<FavoritesPage />} />
<Route path="shopping-list" element={<ShoppingListPage />} />
<Route path="profile" element={<ProfilePage />} />

<Route path="login" element={<LoginPage />} />
<Route path="login/test" element={<TestUserLoginPage />} />

{/* alle sidene som krever innlogging */}
<Route element={<RequireAuth />}>
<Route path="favorites" element={<FavoritesPage />} />
<Route path="profile" element={<ProfilePage />} />
</Route>

{/* Testside for designsystemet */}
{import.meta.env.DEV && (
Expand All @@ -34,5 +44,5 @@ export default function App() {
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
)
}
);
}
79 changes: 29 additions & 50 deletions client/src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<header className="border-b border-secondary bg-background px-6 py-4">
<div className="mx-auto flex max-w-6xl flex-wrap items-center justify-between gap-x-8 gap-y-4">

{/* Logo */}
<Link
to="/"
Expand All @@ -52,38 +54,25 @@ export default function Header() {
</Link>

{/* Søkefelt */}
<SearchBar
key={query}
initialQuery={query}
/>
<SearchBar key={query} initialQuery={query} />

{/* Navigasjon */}
<nav aria-label="Hovedmeny">
<ul className="flex flex-wrap items-center gap-x-6 gap-y-3">

<li>
<NavLink
to="/recipes"
className={navLinkClass}
>
<NavLink to="/recipes" className={navLinkClass}>
Oppskrifter
</NavLink>
</li>

<li>
<NavLink
to="/favorites"
className={navLinkClass}
>
<NavLink to="/favorites" className={navLinkClass}>
Favoritter
</NavLink>
</li>

<li>
<NavLink
to="/shopping-list"
className={navLinkClass}
>
<NavLink to="/shopping-list" className={navLinkClass}>
Handleliste
</NavLink>
</li>
Expand All @@ -92,18 +81,13 @@ export default function Header() {
<li>
<Dialog
trigger={
<Button
variant="primary"
size="small"
>
<Button variant="primary" size="small">
Ny Oppskrift
</Button>
}
title="Ny oppskrift"
headerContent={
<RecipeStepIndicator
currentStep={currentStep}
/>
<RecipeStepIndicator currentStep={currentStep} />
}
>
<NewRecipePage
Expand All @@ -112,19 +96,14 @@ export default function Header() {
/>
</Dialog>
</li>

<li>
<NavLink
to="/profile"
className={userClass}
>
Bruker
<NavLink to={user ? '/profile' : '/login'} className={userClass}>
{user ? 'Bruker' : 'Logg inn'}
</NavLink>
</li>

</ul>
</nav>
</div>
</header>
)
}
);
}
20 changes: 20 additions & 0 deletions client/src/components/layout/RequireAuth.tsx
Original file line number Diff line number Diff line change
@@ -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 <Navigate to="/login" replace />;
}

// hvis innlogget, vis side
return <Outlet />;
}
1 change: 0 additions & 1 deletion client/src/lib/apolloClient.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
19 changes: 19 additions & 0 deletions client/src/lib/useCurrentUser.ts
Original file line number Diff line number Diff line change
@@ -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,
};
}
60 changes: 60 additions & 0 deletions client/src/routes/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useNavigate } from 'react-router';
import { Button } from '../components/ui/Button';

// Innloggingsside
export default function LoginPage() {
const navigate = useNavigate();

return (
<div className="mx-auto mt-12 flex max-w-lg flex-col gap-4 rounded-large bg-peach p-10 shadow-lg">
<h1 className="font-heading text-6xl font-bold">Velkommen tilbake</h1>
<p>
Logg inn med Google, utforsk oppskriftene som gjest eller testbruker.
</p>

{/* Midlertidig: Google og gjest kobles til backend senere */}
<div className="mt-2 flex flex-col gap-2">
<Button
variant="primary"
className="font-bold"
onClick={() => console.log('google')}
>
Logg inn med Google
</Button>
<p className="text-center text-sm">
Lagre favorittene dine og del egne oppskrifter.
</p>
</div>

{/* Skiller Google innlogging fra de andre valgene */}
<div className="flex items-center gap-3 text-sm">
<span className="h-px flex-1 bg-background" />
eller
<span className="h-px flex-1 bg-background" />
</div>

<div className="flex flex-col gap-2">
<Button
variant="secondary"
className="font-bold"
onClick={() => console.log('gjest')}
>
Fortsett som gjest
</Button>
<p className="text-center text-sm">Se oppskrifter uten å logge inn.</p>
</div>

<div className="flex flex-col gap-2">
{/* Går til logg inn siden for testbrukeren */}
<Button
variant="secondary"
className="font-bold"
onClick={() => navigate('/login/test')}
>
Logg inn som testbruker
</Button>
<p className="text-center text-sm">Prøv med en ferdig testkonto.</p>
</div>
</div>
);
}
Loading