diff --git a/src/app/recipe-page/recipe-page-content/recipe-page-content.html b/src/app/recipe-page/recipe-page-content/recipe-page-content.html index 52459f8..4a4ad04 100644 --- a/src/app/recipe-page/recipe-page-content/recipe-page-content.html +++ b/src/app/recipe-page/recipe-page-content/recipe-page-content.html @@ -1,11 +1,27 @@ -

{{ recipe.title }}

-@if (mainImageUrl.isSuccess()) { - - -} -
+@let recipe = recipeView().recipe; +
+
+

{{ recipe.title }}

+ @if (isLoggedIn()) { + + } @else { +
+ + {{ recipe.starCount }} +
+ } +
+ @if (mainImageUrl.isSuccess()) { + + } +
+
diff --git a/src/app/recipe-page/recipe-page-content/recipe-page-content.ts b/src/app/recipe-page/recipe-page-content/recipe-page-content.ts index cc4853b..6c5feec 100644 --- a/src/app/recipe-page/recipe-page-content/recipe-page-content.ts +++ b/src/app/recipe-page/recipe-page-content/recipe-page-content.ts @@ -1,22 +1,42 @@ -import { Component, inject, Input } from '@angular/core'; -import { Recipe } from '../../model/Recipe.model'; -import { injectQuery } from '@tanstack/angular-query-experimental'; +import { Component, computed, inject, input, Input } from '@angular/core'; +import { RecipeView } from '../../model/Recipe.model'; +import { injectMutation, injectQuery } from '@tanstack/angular-query-experimental'; import { ImageService } from '../../service/image.service'; +import { faStar } from "@fortawesome/free-solid-svg-icons"; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { RecipeService } from '../../service/recipe.service'; +import { AuthService } from '../../service/auth.service'; @Component({ selector: 'app-recipe-page-content', - imports: [], + imports: [ + FaIconComponent + ], templateUrl: './recipe-page-content.html', styleUrl: './recipe-page-content.css', }) export class RecipePageContent { - @Input({ required: true }) - public recipe!: Recipe; + + public recipeView = input.required(); private readonly imageService = inject(ImageService); + private readonly recipeService = inject(RecipeService); + private readonly authService = inject(AuthService); - protected mainImageUrl = injectQuery(() => ({ - queryKey: ['images', this.recipe.mainImage.owner.username, this.recipe.mainImage.filename], - queryFn: () => this.imageService.getImage(this.recipe.mainImage.url), + protected readonly isLoggedIn = computed(() => !!this.authService.accessToken()); + + protected readonly mainImageUrl = injectQuery(() => { + const recipe = this.recipeView().recipe; + return { + queryKey: ['images', recipe.mainImage.owner.username, recipe.mainImage.filename], + queryFn: () => this.imageService.getImage(recipe.mainImage.url), + } + }); + + protected readonly starMutation = injectMutation(() => ({ + mutationFn: () => this.recipeService.toggleStar(this.recipeView()), })); + + protected readonly faStar = faStar; + } diff --git a/src/app/recipe-page/recipe-page.html b/src/app/recipe-page/recipe-page.html index a9e75ee..6d2f806 100644 --- a/src/app/recipe-page/recipe-page.html +++ b/src/app/recipe-page/recipe-page.html @@ -1,8 +1,8 @@ -@if (recipe.isLoading()) { +@if (recipeView.isLoading()) {

Loading...

-} @else if (recipe.isSuccess()) { - -} @else if (recipe.error(); as error) { +} @else if (recipeView.isSuccess()) { + +} @else if (recipeView.error(); as error) {

{{ error.message }}

} @else {

There was an error loading the recipe.

diff --git a/src/app/recipe-page/recipe-page.ts b/src/app/recipe-page/recipe-page.ts index 0e482bb..3b6fed4 100644 --- a/src/app/recipe-page/recipe-page.ts +++ b/src/app/recipe-page/recipe-page.ts @@ -16,8 +16,8 @@ export class RecipePage { private username = this.route.snapshot.paramMap.get('username') as string; private slug = this.route.snapshot.paramMap.get('slug') as string; - protected recipe = injectQuery(() => ({ + protected recipeView = injectQuery(() => ({ queryKey: ['recipe', this.username, this.slug], - queryFn: () => this.recipeService.getRecipe(this.username, this.slug), + queryFn: () => this.recipeService.getRecipeView(this.username, this.slug), })); } diff --git a/src/app/recipes-page/recipe-card/recipe-card.css b/src/app/recipes-page/recipe-card/recipe-card.css index f3ab082..411c4be 100644 --- a/src/app/recipes-page/recipe-card/recipe-card.css +++ b/src/app/recipes-page/recipe-card/recipe-card.css @@ -1,5 +1,22 @@ -.recipe-card-image { +#recipe-card-image { max-height: 200px; width: 100%; object-fit: cover; } + +article { + display: flex; + flex-direction: column; + row-gap: 5px; +} + +#recipe-title { + margin: 0; + font-size: 18px; +} + +#title-and-visibility, +#user-and-stars { + display: flex; + justify-content: space-between; +} diff --git a/src/app/recipes-page/recipe-card/recipe-card.html b/src/app/recipes-page/recipe-card/recipe-card.html index c2fba67..749385b 100644 --- a/src/app/recipes-page/recipe-card/recipe-card.html +++ b/src/app/recipes-page/recipe-card/recipe-card.html @@ -1,13 +1,12 @@
@if (mainImage.isSuccess()) { - - + } -
+
-

{{ recipe.title }}

+

{{ recipe.title }}

@if (recipe.isPublic) { @@ -15,7 +14,7 @@ }
-
+
{{ recipe.owner.username }} {{ recipe.starCount }}
diff --git a/src/app/service/recipe.service.ts b/src/app/service/recipe.service.ts index e07c3c8..096daba 100644 --- a/src/app/service/recipe.service.ts +++ b/src/app/service/recipe.service.ts @@ -1,13 +1,17 @@ import { inject, Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { firstValueFrom, map } from 'rxjs'; +import { firstValueFrom, lastValueFrom, map } from 'rxjs'; import { Recipe, RecipeInfoViews, RecipeView } from '../model/Recipe.model'; +import { AuthService } from './auth.service'; +import { QueryClient } from '@tanstack/angular-query-experimental'; @Injectable({ providedIn: 'root', }) export class RecipeService { private readonly http = inject(HttpClient); + private readonly authService = inject(AuthService); + private readonly queryClient = inject(QueryClient); public getRecipes(): Promise { return firstValueFrom( @@ -17,11 +21,29 @@ export class RecipeService { ); } - public async getRecipe(username: string, slug: string): Promise { + public async getRecipeView(username: string, slug: string): Promise { return firstValueFrom( - this.http - .get(`http://localhost:8080/recipes/${username}/${slug}`) - .pipe(map((recipeView) => recipeView.recipe)), + this.http.get(`http://localhost:8080/recipes/${username}/${slug}`) ); } + + private getRecipeUrl(recipeView: RecipeView): string { + return `http://localhost:8080/recipes/${recipeView.recipe.owner.username}/${recipeView.recipe.slug}`; + } + + public async toggleStar(recipeView: RecipeView) { + if (this.authService.accessToken()) { + if (recipeView.isStarred) { + await lastValueFrom(this.http.delete(this.getRecipeUrl(recipeView) + '/star')); + } else { + await lastValueFrom(this.http.post(this.getRecipeUrl(recipeView) + '/star', null)); + } + await this.queryClient.invalidateQueries({ + queryKey: ['recipe', recipeView.recipe.owner.username, recipeView.recipe.slug], + }) + } else { + throw new Error('Cannot star a recipe when not logged in.') + } + } + }