diff --git a/src/app/model/RecipeComment.model.ts b/src/app/model/RecipeComment.model.ts
new file mode 100644
index 0000000..90ed00d
--- /dev/null
+++ b/src/app/model/RecipeComment.model.ts
@@ -0,0 +1,19 @@
+import { ResourceOwner } from './ResourceOwner.model';
+
+export interface RecipeComments {
+ slice: {
+ number: number;
+ size: number;
+ };
+ content: RecipeComment[];
+}
+
+export interface RecipeComment {
+ id: number;
+ created: string;
+ modified: string | null;
+ text: string;
+ rawText: string | null;
+ owner: ResourceOwner;
+ recipeId: number;
+}
diff --git a/src/app/recipe-comments-list/recipe-comments-list.component.css b/src/app/recipe-comments-list/recipe-comments-list.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/recipe-comments-list/recipe-comments-list.component.html b/src/app/recipe-comments-list/recipe-comments-list.component.html
new file mode 100644
index 0000000..601bfc3
--- /dev/null
+++ b/src/app/recipe-comments-list/recipe-comments-list.component.html
@@ -0,0 +1,42 @@
+
diff --git a/src/app/recipe-comments-list/recipe-comments-list.component.spec.ts b/src/app/recipe-comments-list/recipe-comments-list.component.spec.ts
new file mode 100644
index 0000000..aa27e51
--- /dev/null
+++ b/src/app/recipe-comments-list/recipe-comments-list.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RecipeCommentsList } from './recipe-comments-list.component';
+
+describe('CommentsList', () => {
+ let component: RecipeCommentsList;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [RecipeCommentsList],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(RecipeCommentsList);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/recipe-comments-list/recipe-comments-list.component.ts b/src/app/recipe-comments-list/recipe-comments-list.component.ts
new file mode 100644
index 0000000..8d58a8f
--- /dev/null
+++ b/src/app/recipe-comments-list/recipe-comments-list.component.ts
@@ -0,0 +1,40 @@
+import { Component, computed, inject, input } from '@angular/core';
+import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
+import { RecipeService } from '../service/recipe.service';
+import { injectMutation, injectQuery } from '@tanstack/angular-query-experimental';
+import { AuthService } from '../service/auth.service';
+
+@Component({
+ selector: 'app-comments-list',
+ imports: [ReactiveFormsModule],
+ templateUrl: './recipe-comments-list.component.html',
+ styleUrl: './recipe-comments-list.component.css',
+})
+export class RecipeCommentsList {
+ public readonly recipeUsername = input.required();
+ public readonly recipeSlug = input.required();
+
+ private readonly recipeService = inject(RecipeService);
+ private readonly authService = inject(AuthService);
+
+ protected readonly username = this.authService.username;
+ protected readonly isLoggedIn = computed(() => !!this.authService.accessToken());
+
+ protected readonly commentsQuery = injectQuery(() => ({
+ queryKey: ['recipeComments', this.recipeUsername(), this.recipeSlug()],
+ queryFn: () => this.recipeService.getComments(this.recipeUsername(), this.recipeSlug()),
+ }));
+
+ protected readonly addCommentForm = new FormGroup({
+ comment: new FormControl('', Validators.required),
+ });
+
+ protected readonly addCommentMutation = injectMutation(() => ({
+ mutationFn: (commentText: string) =>
+ this.recipeService.addComment(this.recipeUsername(), this.recipeSlug(), commentText),
+ }));
+
+ protected onCommentSubmit() {
+ this.addCommentMutation.mutate(this.addCommentForm.value.comment!);
+ }
+}
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 eb60cbd..7abb564 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
@@ -38,4 +38,5 @@
/>
}
+
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 8740835..75b5373 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
@@ -6,10 +6,11 @@ import { faGlobe, faLock, faStar, faUser } from '@fortawesome/free-solid-svg-ico
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { RecipeService } from '../../service/recipe.service';
import { AuthService } from '../../service/auth.service';
+import { RecipeCommentsList } from '../../recipe-comments-list/recipe-comments-list.component';
@Component({
selector: 'app-recipe-page-content',
- imports: [FaIconComponent],
+ imports: [FaIconComponent, RecipeCommentsList],
templateUrl: './recipe-page-content.html',
styleUrl: './recipe-page-content.css',
})
diff --git a/src/app/service/recipe.service.ts b/src/app/service/recipe.service.ts
index f0fd110..01d1443 100644
--- a/src/app/service/recipe.service.ts
+++ b/src/app/service/recipe.service.ts
@@ -4,6 +4,7 @@ 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';
+import { RecipeComment, RecipeComments } from '../model/RecipeComment.model';
@Injectable({
providedIn: 'root',
@@ -21,7 +22,7 @@ export class RecipeService {
);
}
- public async getRecipeView(username: string, slug: string): Promise {
+ public getRecipeView(username: string, slug: string): Promise {
return firstValueFrom(
this.http.get(`http://localhost:8080/recipes/${username}/${slug}`),
);
@@ -31,7 +32,7 @@ export class RecipeService {
return `http://localhost:8080/recipes/${recipeView.recipe.owner.username}/${recipeView.recipe.slug}`;
}
- public async toggleStar(recipeView: RecipeView) {
+ public async toggleStar(recipeView: RecipeView): Promise {
if (this.authService.accessToken()) {
if (recipeView.isStarred) {
await lastValueFrom(this.http.delete(this.getRecipeUrl(recipeView) + '/star'));
@@ -45,4 +46,31 @@ export class RecipeService {
throw new Error('Cannot star a recipe when not logged in.');
}
}
+
+ public getComments(username: string, slug: string): Promise {
+ return firstValueFrom(
+ this.http
+ .get(`http://localhost:8080/recipes/${username}/${slug}/comments`)
+ .pipe(map((res) => res.content)),
+ );
+ }
+
+ public async addComment(
+ username: string,
+ slug: string,
+ commentText: string,
+ ): Promise {
+ const comment = await firstValueFrom(
+ this.http.post(
+ `http://localhost:8080/recipes/${username}/${slug}/comments`,
+ {
+ text: commentText,
+ },
+ ),
+ );
+ await this.queryClient.invalidateQueries({
+ queryKey: ['recipeComments', username, slug],
+ });
+ return comment;
+ }
}
Comments
+ @if (isLoggedIn()) { +Add Comment
+ +You must be logged in to comment.
+ } +Comments
+ @if (commentsQuery.isLoading()) { +Loading comments...
+ } @else if (commentsQuery.isError()) { +There was an error loading the comments.
+ } @else if (commentsQuery.isSuccess()) { + @let comments = commentsQuery.data(); + @if (comments.length) { ++ @if (addCommentMutation.isPending()) { +-
+
{{ addCommentMutation.variables() }}
+
+ }
+ @for (comment of comments; track $index) {
+ -
+
+ }
+
+ } @else { +{{ username() }}
+{{ comment.owner.username }} at {{ comment.created }}
+ +There are no comments yet.
+ } + } +