diff --git a/client/src/graphql/recipes.ts b/client/src/graphql/recipes.ts index fe9d373..c0f0ef4 100644 --- a/client/src/graphql/recipes.ts +++ b/client/src/graphql/recipes.ts @@ -5,7 +5,12 @@ import type { Recipe } from '../types/recipe'; // Query-ene henter kun den informasjonen som en komponent trenger, sånn at det sendes minst mulig data. // Typer for svaret og variablene, sånn at typescript vet hvordan dataene ser ut. -type GetRecipesData = { recipes: Recipe[] }; +type GetRecipesData = { + recipes: { + items: Recipe[]; + totalCount: number; + }; +}; type GetRecipesVariables = { search?: string; page?: number; @@ -18,11 +23,13 @@ type GetRecipeVariables = { id: string }; export const GET_RECIPES: TypedDocumentNode = gql` query GetRecipes($search: String, $page: Int, $pageSize: Int) { recipes(search: $search, page: $page, pageSize: $pageSize) { + totalCount + items { id imageUrl title prepTimeMinutes - servings + servings} } } `; @@ -62,15 +69,18 @@ export const GET_RECIPE: TypedDocumentNode = export const GET_FEATURED_RECIPES: TypedDocumentNode = gql ` query GetFeaturedRecipes($page: Int, $pageSize: Int) { recipes(page: $page, pageSize: $pageSize) { - id - title - description - prepTimeMinutes - servings - imageUrl - tags { + totalCount + items { id - name + title + description + prepTimeMinutes + servings + imageUrl + tags { + id + name + } } } } diff --git a/client/src/mocks/recipes.ts b/client/src/mocks/recipes.ts deleted file mode 100644 index 357a764..0000000 --- a/client/src/mocks/recipes.ts +++ /dev/null @@ -1,861 +0,0 @@ -// Mock-data med 30 oppskrifter. -// Dataene er variert i kategori, tid, porsjoner, enheter og tags, slik at de også kan brukes til å teste filtrering, sortering og enhetsomregning senere. - -import type { Recipe, RecipeIngredient, Step, Tag } from '../types/recipe'; - -// Tags er felles for alle oppskrifter, slik at samme tag alltid har samme id. -const tags = { - vegetar: { id: 't1', name: 'Vegetar' }, - vegansk: { id: 't2', name: 'Vegansk' }, - glutenfri: { id: 't3', name: 'Glutenfri' }, - laktosefri: { id: 't4', name: 'Laktosefri' }, - barnevennlig: { id: 't5', name: 'Barnevennlig' }, - rask: { id: 't6', name: 'Rask' }, - sunn: { id: 't7', name: 'Sunn' }, - budsjett: { id: 't8', name: 'Budsjett' }, - kylling: { id: 't9', name: 'Kylling' }, - fisk: { id: 't10', name: 'Fisk' }, - sterk: { id: 't11', name: 'Sterk' }, -} satisfies Record; - -// Gir samme ingrediens samme id i alle oppskrifter (f.eks. er "Melk" alltid samme id), -// slik som i databasen der ingrediensen er én rad. -const ingredientIds = new Map(); -// order legges til av ingredients() under, derfor er den utelatt her med Omit. -function ing(name: string, amount: number, unit: string): Omit { - if (!ingredientIds.has(name)) { - ingredientIds.set(name, `i${ingredientIds.size + 1}`); - } - return { id: ingredientIds.get(name)!, name, amount, unit }; -} - -// Gir ingrediensene riktig rekkefølge (order) ut fra plassen i lista. -function ingredients(list: Omit[]): RecipeIngredient[] { - return list.map((ingredient, index) => ({ ...ingredient, order: index + 1 })); -} - -// Lager steg med unik id og riktig rekkefølge ut fra en liste med instruksjoner. -function steps(recipeId: string, instructions: string[]): Step[] { - return instructions.map((instruction, index) => ({ - id: `${recipeId}-s${index + 1}`, - order: index + 1, - instruction, - })); -} - -export const recipes: Recipe[] = [ - { - id: 'r1', - title: 'Pannekaker', - description: 'Klassiske tynne pannekaker som passer til middag eller dessert.', - prepTimeMinutes: 30, - servings: 4, - averageRating: 4.5, - category: 'Dessert', - imageUrl: '/images/pannekaker.webp', - status: 'Published', - createdAt: '2026-09-28T12:00:00Z', - ingredients: ingredients([ - ing('Hvetemel', 3, 'dl'), - ing('Melk', 6, 'dl'), - ing('Egg', 3, 'stk'), - ing('Salt', 0.5, 'ts'), - ing('Smør', 2, 'ss'), - ]), - steps: steps('r1', [ - 'Visp sammen mel og halvparten av melken til en klumpfri røre.', - 'Spe med resten av melken, og visp inn egg og salt.', - 'La røren svelle i 15 minutter.', - 'Stek tynne pannekaker i smør på middels varme.', - ]), - tags: [tags.vegetar, tags.barnevennlig], - }, - { - id: 'r2', - title: 'Spaghetti bolognese', - description: 'Italiensk kjøttsaus med tomat, servert med spaghetti.', - prepTimeMinutes: 45, - servings: 4, - averageRating: 4, - category: 'Middag', - imageUrl: '/images/spaghetti-bolognese.webp', - status: 'Published', - createdAt: '2026-06-02T17:30:00Z', - ingredients: ingredients([ - ing('Kjøttdeig', 400, 'g'), - ing('Løk', 1, 'stk'), - ing('Hvitløk', 2, 'fedd'), - ing('Hakkede tomater', 2, 'boks'), - ing('Spaghetti', 400, 'g'), - ing('Olivenolje', 1, 'ss'), - ing('Salt', 1, 'ts'), - ]), - steps: steps('r2', [ - 'Hakk løk og hvitløk, og fres i olivenolje til løken er myk.', - 'Tilsett kjøttdeigen og brun den godt.', - 'Hell i hakkede tomater, smak til med salt og la sausen småkoke i 20 minutter.', - 'Kok spaghetti etter anvisning på pakken, og server med sausen.', - ]), - tags: [tags.barnevennlig, tags.budsjett, tags.laktosefri], - }, - { - id: 'r3', - title: 'Kyllingwok med grønnsaker', - description: 'Rask wok med kylling, sprø grønnsaker og soyasaus.', - prepTimeMinutes: 25, - servings: 3, - averageRating: 3.5, - category: 'Middag', - imageUrl: '/images/kyllingwok.webp', - status: 'Published', - createdAt: '2026-06-10T16:00:00Z', - ingredients: ingredients([ - ing('Kyllingfilet', 400, 'g'), - ing('Brokkoli', 1, 'stk'), - ing('Paprika', 1, 'stk'), - ing('Gulrot', 2, 'stk'), - ing('Soyasaus', 3, 'ss'), - ing('Ingefær', 1, 'ss'), - ing('Ris', 3, 'dl'), - ]), - steps: steps('r3', [ - 'Kok risen etter anvisning på pakken.', - 'Skjær kyllingen i strimler og stek den i en varm wokpanne.', - 'Tilsett grønnsakene og stek i 4–5 minutter, de skal fortsatt være sprø.', - 'Rør inn soyasaus og revet ingefær, og server med risen.', - ]), - tags: [tags.kylling, tags.rask, tags.laktosefri], - }, - { - id: 'r4', - title: 'Ovnsbakt laks med poteter', - description: 'Saftig laks bakt i ovn med sitron, servert med ovnspoteter.', - prepTimeMinutes: 40, - servings: 4, - averageRating: 5, - category: 'Middag', - imageUrl: '/images/ovnsbakt-laks.webp', - status: 'Published', - createdAt: '2026-06-15T18:00:00Z', - ingredients: ingredients([ - ing('Laksefilet', 600, 'g'), - ing('Poteter', 800, 'g'), - ing('Sitron', 1, 'stk'), - ing('Olivenolje', 3, 'ss'), - ing('Dill', 1, 'ss'), - ing('Salt', 1, 'ts'), - ]), - steps: steps('r4', [ - 'Forvarm ovnen til 200 °C.', - 'Del potetene i båter, vend dem i olivenolje og salt, og stek i 20 minutter.', - 'Legg laksen i en ildfast form med sitronskiver og dill.', - 'Sett laksen inn i ovnen sammen med potetene og stek i 15 minutter til.', - ]), - tags: [tags.fisk, tags.glutenfri, tags.sunn, tags.laktosefri], - }, - { - id: 'r5', - title: 'Linsesuppe', - description: 'Mettende og rimelig suppe med røde linser og krydder.', - prepTimeMinutes: 35, - servings: 4, - averageRating: 4, - category: 'Suppe', - imageUrl: '/images/linsesuppe.webp', - status: 'Published', - createdAt: '2026-06-20T12:00:00Z', - ingredients: ingredients([ - ing('Røde linser', 3, 'dl'), - ing('Løk', 1, 'stk'), - ing('Gulrot', 2, 'stk'), - ing('Hakkede tomater', 1, 'boks'), - ing('Grønnsaksbuljong', 1, 'l'), - ing('Spisskummen', 1, 'ts'), - ]), - steps: steps('r5', [ - 'Hakk løk og gulrot, og fres dem i en kjele.', - 'Tilsett spisskummen og la det frese i ett minutt.', - 'Ha i linser, tomater og buljong, og kok i 20 minutter.', - 'Kjør suppen jevn med en stavmikser og smak til med salt.', - ]), - tags: [tags.vegansk, tags.vegetar, tags.budsjett, tags.glutenfri], - }, - { - id: 'r6', - title: 'Havregrøt med bær', - description: 'Varm og enkel frokost med havregryn, melk og friske bær.', - prepTimeMinutes: 10, - servings: 2, - averageRating: 3, - category: 'Frokost', - imageUrl: '/images/havregrot.webp', - status: 'Published', - createdAt: '2026-06-25T07:00:00Z', - ingredients: ingredients([ - ing('Havregryn', 2, 'dl'), - ing('Melk', 4, 'dl'), - ing('Blåbær', 100, 'g'), - ing('Honning', 1, 'ss'), - ing('Salt', 1, 'klype'), - ]), - steps: steps('r6', [ - 'Ha havregryn, melk og salt i en kjele.', - 'Kok opp under omrøring og la grøten småkoke i 3–4 minutter.', - 'Server med blåbær og honning på toppen.', - ]), - tags: [tags.vegetar, tags.rask, tags.sunn], - }, - { - id: 'r7', - title: 'Tacos', - description: 'Fredagsfavoritt med kjøttdeig, grønnsaker og lefser.', - prepTimeMinutes: 30, - servings: 4, - averageRating: 4.5, - category: 'Middag', - imageUrl: '/images/tacos.webp', - status: 'Published', - createdAt: '2026-07-03T17:00:00Z', - ingredients: ingredients([ - ing('Kjøttdeig', 400, 'g'), - ing('Tacokrydder', 1, 'pose'), - ing('Tortillalefser', 8, 'stk'), - ing('Salat', 1, 'stk'), - ing('Tomat', 2, 'stk'), - ing('Revet ost', 150, 'g'), - ing('Rømme', 3, 'dl'), - ]), - steps: steps('r7', [ - 'Brun kjøttdeigen i en panne.', - 'Tilsett tacokrydder og vann etter anvisning, og la det småkoke i 5 minutter.', - 'Kutt salat og tomat i biter.', - 'Varm lefsene og server alt på bordet, så kan alle lage sin egen taco.', - ]), - tags: [tags.barnevennlig], - }, - { - id: 'r8', - title: 'Kjøttkaker i brun saus', - description: 'Hjemmelagde kjøttkaker med brun saus, poteter og ertestuing.', - prepTimeMinutes: 60, - servings: 4, - averageRating: 4, - category: 'Middag', - imageUrl: '/images/kjottkaker.webp', - status: 'Published', - createdAt: '2026-07-08T16:30:00Z', - ingredients: ingredients([ - ing('Kjøttdeig', 500, 'g'), - ing('Egg', 1, 'stk'), - ing('Potetmel', 1, 'ss'), - ing('Melk', 1, 'dl'), - ing('Hvetemel', 3, 'ss'), - ing('Smør', 3, 'ss'), - ing('Poteter', 800, 'g'), - ]), - steps: steps('r8', [ - 'Bland kjøttdeig, egg, potetmel og melk til en jevn farse.', - 'Form kjøttkaker og brun dem i smør.', - 'Brun mel i smør, spe med vann og kok opp til en brun saus.', - 'Legg kjøttkakene i sausen og la dem trekke i 15 minutter.', - 'Kok potetene og server sammen med kjøttkakene.', - ]), - tags: [tags.barnevennlig], - }, - { - id: 'r9', - title: 'Bergensk fiskesuppe', - description: 'Kremet fiskesuppe med hvit fisk, laks og grønnsaker.', - prepTimeMinutes: 40, - servings: 4, - averageRating: 3.5, - category: 'Suppe', - imageUrl: '/images/fiskesuppe.webp', - status: 'Published', - createdAt: '2026-07-12T18:00:00Z', - ingredients: ingredients([ - ing('Torskefilet', 300, 'g'), - ing('Laksefilet', 200, 'g'), - ing('Gulrot', 2, 'stk'), - ing('Purre', 1, 'stk'), - ing('Fiskebuljong', 1, 'l'), - ing('Matfløte', 3, 'dl'), - ]), - steps: steps('r9', [ - 'Skjær gulrot og purre i biter, og kok dem i buljongen i 10 minutter.', - 'Tilsett matfløten og kok opp.', - 'Skjær fisken i terninger, legg den i suppen og la den trekke i 5 minutter.', - ]), - tags: [tags.fisk, tags.glutenfri], - }, - { - id: 'r10', - title: 'Gresk salat', - description: 'Frisk salat med tomat, agurk, oliven og fetaost.', - prepTimeMinutes: 15, - servings: 2, - averageRating: 4, - category: 'Salat', - imageUrl: '/images/gresk-salat.webp', - status: 'Published', - createdAt: '2026-07-15T12:00:00Z', - ingredients: ingredients([ - ing('Tomat', 3, 'stk'), - ing('Agurk', 1, 'stk'), - ing('Rødløk', 1, 'stk'), - ing('Fetaost', 150, 'g'), - ing('Oliven', 1, 'dl'), - ing('Olivenolje', 2, 'ss'), - ]), - steps: steps('r10', [ - 'Skjær tomat, agurk og rødløk i biter.', - 'Ha grønnsakene i en bolle sammen med oliven.', - 'Smuldre fetaost over og drypp med olivenolje.', - ]), - tags: [tags.vegetar, tags.glutenfri, tags.rask, tags.sunn], - }, - { - id: 'r11', - title: 'Sjokoladekake i langpanne', - description: 'Saftig sjokoladekake med glasur, perfekt til bursdager.', - prepTimeMinutes: 50, - servings: 16, - averageRating: 5, - category: 'Bakst', - imageUrl: '/images/sjokoladekake.webp', - status: 'Published', - createdAt: '2026-07-19T14:00:00Z', - ingredients: ingredients([ - ing('Hvetemel', 5, 'dl'), - ing('Sukker', 4, 'dl'), - ing('Kakao', 4, 'ss'), - ing('Egg', 4, 'stk'), - ing('Smør', 250, 'g'), - ing('Melk', 2, 'dl'), - ing('Bakepulver', 2, 'ts'), - ]), - steps: steps('r11', [ - 'Forvarm ovnen til 180 °C.', - 'Smelt smøret og la det avkjøles litt.', - 'Visp egg og sukker lyst og luftig.', - 'Bland inn de tørre ingrediensene, smør og melk.', - 'Hell røren i en langpanne og stek i ca. 25 minutter.', - ]), - tags: [tags.vegetar, tags.barnevennlig], - }, - { - id: 'r12', - title: 'Grovbrød', - description: 'Luftig grovbrød med sammalt hvete, godt til frokost og lunsj.', - prepTimeMinutes: 180, - servings: 12, - averageRating: 3, - category: 'Bakst', - imageUrl: '/images/grovbrod.webp', - status: 'Published', - createdAt: '2026-07-22T09:00:00Z', - ingredients: ingredients([ - ing('Sammalt hvete', 5, 'dl'), - ing('Hvetemel', 7, 'dl'), - ing('Tørrgjær', 1, 'pose'), - ing('Vann', 6, 'dl'), - ing('Salt', 2, 'ts'), - ing('Olivenolje', 2, 'ss'), - ]), - steps: steps('r12', [ - 'Bland de tørre ingrediensene i en bolle.', - 'Tilsett lunkent vann og olje, og elt deigen i 10 minutter.', - 'La deigen heve i 1 time.', - 'Form et brød, legg det i en brødform og la det etterheve i 30 minutter.', - 'Stek på 200 °C i ca. 45 minutter.', - ]), - tags: [tags.vegansk, tags.vegetar, tags.budsjett], - }, - { - id: 'r13', - title: 'Kanelboller', - description: 'Myke boller med kanel og sukker, best rett fra ovnen.', - prepTimeMinutes: 120, - servings: 16, - averageRating: 4.5, - category: 'Bakst', - imageUrl: '/images/kanelboller.webp', - status: 'Published', - createdAt: '2026-07-26T13:00:00Z', - ingredients: ingredients([ - ing('Hvetemel', 9, 'dl'), - ing('Melk', 3, 'dl'), - ing('Smør', 100, 'g'), - ing('Sukker', 1, 'dl'), - ing('Tørrgjær', 1, 'pose'), - ing('Kanel', 2, 'ss'), - ing('Egg', 1, 'stk'), - ]), - steps: steps('r13', [ - 'Smelt smøret, tilsett melken og varm til det er fingervarmt.', - 'Bland inn gjær, sukker og mel, og elt til en smidig deig.', - 'La deigen heve i 45 minutter.', - 'Kjevle ut deigen, smør på smør, kanel og sukker, og rull sammen.', - 'Skjær i skiver, la dem etterheve, pensle med egg og stek på 220 °C i 10 minutter.', - ]), - tags: [tags.vegetar, tags.barnevennlig], - }, - { - id: 'r14', - title: 'Chili sin carne', - description: 'Vegansk chili med bønner, mais og mye smak.', - prepTimeMinutes: 50, - servings: 4, - averageRating: 3.5, - category: 'Middag', - imageUrl: '/images/chili-sin-carne.webp', - status: 'Published', - createdAt: '2026-08-01T17:00:00Z', - ingredients: ingredients([ - ing('Kidneybønner', 2, 'boks'), - ing('Mais', 1, 'boks'), - ing('Hakkede tomater', 2, 'boks'), - ing('Løk', 1, 'stk'), - ing('Paprika', 1, 'stk'), - ing('Chilipulver', 2, 'ts'), - ing('Spisskummen', 1, 'ts'), - ]), - steps: steps('r14', [ - 'Hakk løk og paprika, og fres dem i en kjele.', - 'Tilsett chilipulver og spisskummen.', - 'Ha i tomater, bønner og mais, og la det småkoke i 30 minutter.', - 'Smak til med salt og server med ris eller brød.', - ]), - tags: [tags.vegansk, tags.vegetar, tags.sterk, tags.glutenfri, tags.budsjett], - }, - { - id: 'r15', - title: 'Tomatsuppe', - description: 'Varmende tomatsuppe med basilikum, god med brød til.', - prepTimeMinutes: 30, - servings: 4, - averageRating: 4, - category: 'Suppe', - imageUrl: '/images/tomatsuppe.webp', - status: 'Published', - createdAt: '2026-08-04T12:00:00Z', - ingredients: ingredients([ - ing('Hakkede tomater', 2, 'boks'), - ing('Løk', 1, 'stk'), - ing('Hvitløk', 2, 'fedd'), - ing('Grønnsaksbuljong', 5, 'dl'), - ing('Matfløte', 1, 'dl'), - ing('Basilikum', 1, 'stk'), - ]), - steps: steps('r15', [ - 'Fres hakket løk og hvitløk i en kjele.', - 'Tilsett tomater og buljong, og kok i 15 minutter.', - 'Kjør suppen jevn, rør inn fløten og topp med basilikum.', - ]), - tags: [tags.vegetar, tags.glutenfri, tags.budsjett], - }, - { - id: 'r16', - title: 'Smoothie bowl', - description: 'Tykk smoothie med frosne bær og banan, toppet med granola.', - prepTimeMinutes: 10, - servings: 1, - averageRating: null, - category: 'Frokost', - imageUrl: '/images/smoothie-bowl.webp', - status: 'Published', - createdAt: '2026-08-07T08:00:00Z', - ingredients: ingredients([ - ing('Frosne bær', 200, 'g'), - ing('Banan', 1, 'stk'), - ing('Havremelk', 1, 'dl'), - ing('Granola', 3, 'ss'), - ]), - steps: steps('r16', [ - 'Kjør frosne bær, banan og havremelk i en blender til en tykk smoothie.', - 'Hell smoothien i en bolle.', - 'Topp med granola og eventuelt friske bær.', - ]), - tags: [tags.vegansk, tags.vegetar, tags.rask, tags.sunn], - }, - { - id: 'r17', - title: 'Butter chicken', - description: 'Indisk kyllinggryte i mild og kremet tomatsaus.', - prepTimeMinutes: 50, - servings: 4, - averageRating: 4.5, - category: 'Middag', - imageUrl: '/images/butter-chicken.webp', - status: 'Published', - createdAt: '2026-08-10T17:30:00Z', - ingredients: ingredients([ - ing('Kyllingfilet', 600, 'g'), - ing('Smør', 3, 'ss'), - ing('Løk', 1, 'stk'), - ing('Hvitløk', 3, 'fedd'), - ing('Garam masala', 2, 'ts'), - ing('Hakkede tomater', 1, 'boks'), - ing('Matfløte', 2, 'dl'), - ing('Ris', 3, 'dl'), - ]), - steps: steps('r17', [ - 'Skjær kyllingen i biter og brun den i smør. Ta den ut av gryta.', - 'Fres løk, hvitløk og garam masala i samme gryte.', - 'Tilsett tomater og fløte, og la sausen koke i 10 minutter.', - 'Ha kyllingen tilbake og la den trekke i sausen i 10 minutter.', - 'Server med kokt ris.', - ]), - tags: [tags.kylling, tags.sterk, tags.glutenfri], - }, - { - id: 'r18', - title: 'Pizza margherita', - description: 'Hjemmelaget pizza med tomatsaus, mozzarella og basilikum.', - prepTimeMinutes: 90, - servings: 2, - averageRating: 5, - category: 'Middag', - imageUrl: '/images/pizza-margherita.webp', - status: 'Published', - createdAt: '2026-08-14T18:00:00Z', - ingredients: ingredients([ - ing('Hvetemel', 5, 'dl'), - ing('Tørrgjær', 0.5, 'pose'), - ing('Vann', 2, 'dl'), - ing('Hakkede tomater', 1, 'boks'), - ing('Mozzarella', 125, 'g'), - ing('Basilikum', 1, 'stk'), - ing('Olivenolje', 1, 'ss'), - ]), - steps: steps('r18', [ - 'Bland mel, gjær, lunkent vann og olje, og elt til en smidig deig.', - 'La deigen heve i 1 time.', - 'Kjevle ut deigen og smør tomatene utover som saus.', - 'Legg på mozzarella og stek på 250 °C i 10–12 minutter.', - 'Topp med frisk basilikum.', - ]), - tags: [tags.vegetar, tags.barnevennlig], - }, - { - id: 'r19', - title: 'Fårikål', - description: 'Norges nasjonalrett med fårekjøtt, kål og hel pepper.', - prepTimeMinutes: 180, - servings: 6, - averageRating: 3.5, - category: 'Middag', - imageUrl: '/images/farikal.webp', - status: 'Published', - createdAt: '2026-08-18T16:00:00Z', - ingredients: ingredients([ - ing('Fårekjøtt', 1.5, 'kg'), - ing('Hodekål', 1.5, 'kg'), - ing('Hel sort pepper', 2, 'ss'), - ing('Salt', 1, 'ss'), - ing('Vann', 5, 'dl'), - ing('Poteter', 1, 'kg'), - ]), - steps: steps('r19', [ - 'Del kålen i båter.', - 'Legg kjøtt og kål lagvis i en gryte, og dryss salt og pepper mellom lagene.', - 'Hell over vann, kok opp og la det småkoke i 2–2,5 timer.', - 'Server med kokte poteter.', - ]), - tags: [tags.glutenfri, tags.laktosefri], - }, - { - id: 'r20', - title: 'Eggerøre med gressløk', - description: 'Myk og kremet eggerøre, klar på få minutter.', - prepTimeMinutes: 10, - servings: 2, - averageRating: 3, - category: 'Frokost', - imageUrl: '/images/eggerore.webp', - status: 'Published', - createdAt: '2026-08-21T07:30:00Z', - ingredients: ingredients([ - ing('Egg', 4, 'stk'), - ing('Melk', 0.5, 'dl'), - ing('Smør', 1, 'ss'), - ing('Gressløk', 1, 'ss'), - ing('Salt', 1, 'klype'), - ]), - steps: steps('r20', [ - 'Visp sammen egg, melk og salt.', - 'Smelt smøret i en panne på lav varme.', - 'Hell i eggeblandingen og rør forsiktig til den stivner.', - 'Dryss over hakket gressløk.', - ]), - tags: [tags.vegetar, tags.rask, tags.glutenfri], - }, - { - id: 'r21', - title: 'Hummus', - description: 'Kremet hummus av kikerter og tahini, god som dipp.', - prepTimeMinutes: 15, - servings: 4, - averageRating: 4, - category: 'Snacks', - imageUrl: '/images/hummus.webp', - status: 'Published', - createdAt: '2026-08-24T15:00:00Z', - ingredients: ingredients([ - ing('Kikerter', 1, 'boks'), - ing('Tahini', 3, 'ss'), - ing('Sitron', 1, 'stk'), - ing('Hvitløk', 1, 'fedd'), - ing('Olivenolje', 3, 'ss'), - ing('Salt', 0.5, 'ts'), - ]), - steps: steps('r21', [ - 'Skyll kikertene i kaldt vann.', - 'Kjør alle ingrediensene i en foodprosessor til en jevn masse.', - 'Spe med litt vann hvis hummusen er for tykk.', - ]), - tags: [tags.vegansk, tags.vegetar, tags.glutenfri, tags.rask], - }, - { - id: 'r22', - title: 'Rabarbrapai', - description: 'Syrlig rabarbra under et sprøtt smuldredekke.', - prepTimeMinutes: 60, - servings: 8, - averageRating: 2.5, - category: 'Dessert', - imageUrl: '/images/rabarbrapai.webp', - status: 'Published', - createdAt: '2026-08-27T14:00:00Z', - ingredients: ingredients([ - ing('Rabarbra', 500, 'g'), - ing('Sukker', 2, 'dl'), - ing('Hvetemel', 3, 'dl'), - ing('Smør', 150, 'g'), - ing('Havregryn', 1, 'dl'), - ]), - steps: steps('r22', [ - 'Forvarm ovnen til 200 °C.', - 'Skjær rabarbraen i biter, legg den i en form og dryss over halvparten av sukkeret.', - 'Smuldre sammen mel, smør, havregryn og resten av sukkeret.', - 'Fordel smuldredeigen over rabarbraen og stek i 30 minutter.', - ]), - tags: [tags.vegetar], - }, - { - id: 'r23', - title: 'Poke bowl med laks', - description: 'Hawaiisk risbolle med rå laks, avokado og edamame.', - prepTimeMinutes: 30, - servings: 2, - averageRating: 4.5, - category: 'Lunsj', - imageUrl: '/images/poke-bowl.webp', - status: 'Published', - createdAt: '2026-08-30T12:00:00Z', - ingredients: ingredients([ - ing('Sushiris', 2, 'dl'), - ing('Laksefilet', 250, 'g'), - ing('Avokado', 1, 'stk'), - ing('Edamamebønner', 1, 'dl'), - ing('Agurk', 0.5, 'stk'), - ing('Soyasaus', 2, 'ss'), - ]), - steps: steps('r23', [ - 'Kok risen etter anvisning på pakken og la den avkjøles.', - 'Skjær laks, avokado og agurk i terninger.', - 'Legg risen i boller og fordel laks og grønnsaker på toppen.', - 'Drypp over soyasaus.', - ]), - tags: [tags.fisk, tags.sunn, tags.laktosefri], - }, - { - id: 'r24', - title: 'Ramen med kylling', - description: 'Japansk nudelsuppe med kylling, egg og vårløk.', - prepTimeMinutes: 60, - servings: 2, - averageRating: null, - category: 'Middag', - imageUrl: '/images/ramen.webp', - status: 'Draft', - createdAt: '2026-09-02T18:00:00Z', - ingredients: ingredients([ - ing('Kyllingfilet', 250, 'g'), - ing('Ramennudler', 2, 'pakke'), - ing('Kyllingbuljong', 1, 'l'), - ing('Egg', 2, 'stk'), - ing('Vårløk', 2, 'stk'), - ing('Soyasaus', 3, 'ss'), - ing('Ingefær', 1, 'ss'), - ]), - steps: steps('r24', [ - 'Kok buljongen med soyasaus og ingefær i 15 minutter.', - 'Kok eggene i 7 minutter, avkjøl dem i kaldt vann og del dem i to.', - 'Stek kyllingen og skjær den i skiver.', - 'Kok nudlene, fordel dem i boller og hell over buljongen.', - 'Topp med kylling, egg og hakket vårløk.', - ]), - tags: [tags.kylling, tags.laktosefri], - }, - { - id: 'r25', - title: 'Falafel i pita', - description: 'Sprø falafel av kikerter, servert i pitabrød med salat.', - prepTimeMinutes: 45, - servings: 4, - averageRating: 4, - category: 'Lunsj', - imageUrl: '/images/falafel.webp', - status: 'Published', - createdAt: '2026-09-05T12:30:00Z', - ingredients: ingredients([ - ing('Kikerter', 2, 'boks'), - ing('Løk', 1, 'stk'), - ing('Hvitløk', 2, 'fedd'), - ing('Persille', 1, 'dl'), - ing('Spisskummen', 2, 'ts'), - ing('Hvetemel', 3, 'ss'), - ing('Pitabrød', 4, 'stk'), - ]), - steps: steps('r25', [ - 'Kjør kikerter, løk, hvitløk, persille og krydder grovt i en foodprosessor.', - 'Bland inn melet og form små boller.', - 'Stek bollene i olje til de er gylne og sprø.', - 'Server i pitabrød med salat og tahinisaus.', - ]), - tags: [tags.vegansk, tags.vegetar], - }, - { - id: 'r26', - title: 'Pasta carbonara', - description: 'Klassisk romersk pasta med egg, bacon og parmesan.', - prepTimeMinutes: 25, - servings: 4, - averageRating: 5, - category: 'Middag', - imageUrl: '/images/carbonara.webp', - status: 'Published', - createdAt: '2026-09-08T17:00:00Z', - ingredients: ingredients([ - ing('Spaghetti', 400, 'g'), - ing('Bacon', 200, 'g'), - ing('Egg', 3, 'stk'), - ing('Parmesan', 80, 'g'), - ing('Sort pepper', 1, 'ts'), - ]), - steps: steps('r26', [ - 'Kok spaghetti etter anvisning på pakken.', - 'Stek baconet sprøtt i en panne.', - 'Visp sammen egg og revet parmesan.', - 'Bland den varme pastaen med bacon, ta pannen av varmen og rør inn eggeblandingen.', - 'Krydre med pepper og server med én gang.', - ]), - tags: [tags.rask], - }, - { - id: 'r27', - title: 'Riskrem', - description: 'Tradisjonell dessert med risgrøt og krem, servert med rød saus.', - prepTimeMinutes: 20, - servings: 6, - averageRating: 3.5, - category: 'Dessert', - imageUrl: '/images/riskrem.webp', - status: 'Published', - createdAt: '2026-09-11T15:00:00Z', - ingredients: ingredients([ - ing('Kald risgrøt', 5, 'dl'), - ing('Kremfløte', 3, 'dl'), - ing('Sukker', 2, 'ss'), - ing('Vaniljesukker', 1, 'ts'), - ing('Rød saus', 3, 'dl'), - ]), - steps: steps('r27', [ - 'Pisk kremfløten stiv med sukker og vaniljesukker.', - 'Vend kremen forsiktig inn i den kalde risgrøten.', - 'Server med rød saus.', - ]), - tags: [tags.vegetar, tags.glutenfri, tags.barnevennlig], - }, - { - id: 'r28', - title: 'Quinoasalat med kikerter', - description: 'Proteinrik salat med quinoa, kikerter og friske grønnsaker.', - prepTimeMinutes: 25, - servings: 3, - averageRating: 4, - category: 'Salat', - imageUrl: '/images/quinoasalat.webp', - status: 'Published', - createdAt: '2026-09-14T12:00:00Z', - ingredients: ingredients([ - ing('Quinoa', 2, 'dl'), - ing('Kikerter', 1, 'boks'), - ing('Agurk', 1, 'stk'), - ing('Tomat', 2, 'stk'), - ing('Sitron', 1, 'stk'), - ing('Olivenolje', 3, 'ss'), - ]), - steps: steps('r28', [ - 'Kok quinoaen etter anvisning på pakken og la den avkjøles.', - 'Skjær agurk og tomat i biter, og skyll kikertene.', - 'Bland alt og vend inn sitronsaft og olivenolje.', - ]), - tags: [tags.vegansk, tags.vegetar, tags.glutenfri, tags.sunn], - }, - { - id: 'r29', - title: 'Vafler', - description: 'Norske vafler med rømme, perfekt til kaffen.', - prepTimeMinutes: 20, - servings: 4, - averageRating: 4.5, - category: 'Dessert', - imageUrl: '/images/vafler.webp', - status: 'Published', - createdAt: '2026-09-18T14:30:00Z', - ingredients: ingredients([ - ing('Hvetemel', 4, 'dl'), - ing('Melk', 4, 'dl'), - ing('Egg', 3, 'stk'), - ing('Sukker', 0.5, 'dl'), - ing('Smør', 50, 'g'), - ing('Rømme', 1, 'dl'), - ]), - steps: steps('r29', [ - 'Visp egg og sukker lett sammen.', - 'Rør inn mel og melk vekselvis til en jevn røre.', - 'Smelt smøret og rør det inn sammen med rømmen.', - 'Stek vaflene gylne i et varmt vaffeljern.', - ]), - tags: [tags.vegetar, tags.barnevennlig, tags.rask], - }, - { - id: 'r30', - title: 'Ovnsbakt blomkål med tahini', - description: 'Hel blomkål bakt i ovn med krydder og tahinidressing.', - prepTimeMinutes: 40, - servings: 3, - averageRating: null, - category: 'Middag', - imageUrl: '/images/ovnsbakt-blomkal.webp', - status: 'Draft', - createdAt: '2026-09-22T17:00:00Z', - ingredients: ingredients([ - ing('Blomkål', 1, 'stk'), - ing('Olivenolje', 3, 'ss'), - ing('Spisskummen', 1, 'ts'), - ing('Tahini', 3, 'ss'), - ing('Sitron', 1, 'stk'), - ing('Salt', 1, 'ts'), - ]), - steps: steps('r30', [ - 'Forvarm ovnen til 220 °C.', - 'Pensle blomkålen med olivenolje, spisskummen og salt.', - 'Stek i 30–35 minutter til den er gyllen.', - 'Rør sammen tahini, sitronsaft og litt vann, og drypp over.', - ]), - tags: [tags.vegansk, tags.vegetar, tags.glutenfri, tags.sunn], - }, -]; \ No newline at end of file diff --git a/client/src/routes/HomePage.tsx b/client/src/routes/HomePage.tsx index ca671ee..2f5f4fb 100644 --- a/client/src/routes/HomePage.tsx +++ b/client/src/routes/HomePage.tsx @@ -23,7 +23,7 @@ export default function HomePage() { {/* Viser en melding mens oppskriftene lastes eller hvis noe går galt. */} {loading &&

Laster oppskrifter ...

} {error &&

Kunne ikke hente oppskriftene. Prøv igjen senere.

} - {data && } + {data && } diff --git a/client/src/routes/RecipeListPage.tsx b/client/src/routes/RecipeListPage.tsx index 52a6d56..d014d8f 100644 --- a/client/src/routes/RecipeListPage.tsx +++ b/client/src/routes/RecipeListPage.tsx @@ -39,16 +39,9 @@ export default function RecipeListPage() { }, }) - const [hasMore, setHasMore] = useState(true) const [loadingMore, setLoadingMore] = useState(false) // Nullstiller paginering når man søker på noe nytt - const [prevQuery, setPrevQuery] = useState(query) - - if (query !== prevQuery) { - setPrevQuery(query) - setHasMore(true) - } if (loading && !data) { return

Laster oppskrifter...

@@ -63,7 +56,11 @@ export default function RecipeListPage() { } // Henter oppskriftene fra GraphQL - const recipes = data?.recipes ?? [] + const recipes = data?.recipes.items ?? [] + + const totalCount = data?.recipes.totalCount ?? 0 + + const hasMore = recipes.length < totalCount // Sørger for at bildene bruker riktig backend-adresse const recipesWithImages = recipes.map((recipe) => ({ @@ -80,7 +77,7 @@ export default function RecipeListPage() { try { const nextPage = Math.floor(recipes.length / PAGE_SIZE) + 1 - const result = await fetchMore({ + await fetchMore({ variables: { search: query || undefined, page: nextPage, @@ -88,19 +85,20 @@ export default function RecipeListPage() { }, // Legger de nye oppskriftene etter de gamle - updateQuery: (previous, { fetchMoreResult }) => ({ - recipes: [ - ...previous.recipes, + updateQuery: (previous, { fetchMoreResult }) => { + if (!fetchMoreResult) return previous + return { + recipes: { ...fetchMoreResult.recipes, - ], - }), - }) + items: [...previous.recipes.items, ...fetchMoreResult.recipes.items], + }, + } + }, + }) // Hvis færre enn 12 oppskrifter ble hentet, // finnes det ikke flere sider - if ((result.data?.recipes.length ?? 0) < PAGE_SIZE) { - setHasMore(false) - } + } catch (err) { console.error('Kunne ikke hente flere oppskrifter:', err) } finally { diff --git a/server/src/schema/resolvers/recipe.ts b/server/src/schema/resolvers/recipe.ts index 3ae7738..93e62ae 100644 --- a/server/src/schema/resolvers/recipe.ts +++ b/server/src/schema/resolvers/recipe.ts @@ -1,27 +1,38 @@ import { prisma } from '../../db/client'; +import type { Prisma } from '@prisma/client'; import { GraphQLError } from 'graphql'; import type { Context } from '../../auth/session'; export const recipeResolvers = { Query: { recipes: async ( - _: unknown, - args: { search?: string; category?: string; page?: number; pageSize?: number }) => { - const page = Math.max(args.page ?? 1, 1); - const pageSize = Math.min(Math.max(args.pageSize ?? 20, 1), 50); + _: unknown, + args: { search?: string; category?: string; page?: number; pageSize?: number }, + ) => { + const page = Math.max(args.page ?? 1, 1); + const pageSize = Math.min(Math.max(args.pageSize ?? 20, 1), 50); - - return prisma.recipe.findMany({ - where: { - status: 'PUBLISHED', - ...(search && { title: { contains: search, mode: 'insensitive' } }), - ...(category && { category }), - }, - include: { ingredients: true, steps: true, tags: true }, - skip: (page - 1) * pageSize, - take: pageSize, - orderBy: { createdAt: 'desc' }, - }); + const where: Prisma.RecipeWhereInput = { status: 'PUBLISHED' }; + if (args.search) { + where.title = { contains: args.search, mode: 'insensitive' }; + } + + if (args.category) { + where.category = args.category; + } + + const [items, totalCount] = await prisma.$transaction([ + prisma.recipe.findMany({ + where, + include: { ingredients: true, steps: true, tags: true }, + skip: (page - 1) * pageSize, + take: pageSize, + orderBy: { createdAt: 'desc' }, + }), + prisma.recipe.count({ where }), + ]); + + return { items, totalCount }; }, recipe: async (_: unknown, args: { id: string }) => { return prisma.recipe.findUnique({ @@ -32,7 +43,7 @@ export const recipeResolvers = { }, Mutation: { createRecipe: async (_: unknown, args: { input: any }, context: Context) => { - // gjøre sånn at bare innloggede brukere og gjestebrukere kan lage oppskrift + // Bare innloggede brukere og gjestebrukere kan lage oppskrift if (!context.userId) { throw new GraphQLError('Du må være logget inn for å lage oppskrift.', { extensions: { code: 'UNAUTHENTICATED' }, diff --git a/server/src/schema/typeDefs/recipe.graphql b/server/src/schema/typeDefs/recipe.graphql index 0478e09..316fa01 100644 --- a/server/src/schema/typeDefs/recipe.graphql +++ b/server/src/schema/typeDefs/recipe.graphql @@ -6,6 +6,12 @@ type RecipeIngredient { order: Int! } +type RecipeConnection { + items: [Recipe!]! + totalCount: Int! +} + + type Step { id: ID! order: Int! @@ -57,7 +63,7 @@ input CreateRecipeInput { } type Query { - recipes(search: String, category: String, page: Int, pageSize: Int): [Recipe!]! + recipes(search: String, category: String, page: Int, pageSize: Int): RecipeConnection! recipe(id: ID!): Recipe }