Compare commits

..

No commits in common. "714fe60d9fb2aa5a1e5f67f213200c7b38e90e57" and "b8110e893a71fb228216252f403e66409ea4fae5" have entirely different histories.

9 changed files with 4 additions and 158 deletions

View File

@ -2,8 +2,7 @@
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "npm",
"analytics": false
"packageManager": "npm"
},
"newProjectRoot": "projects",
"projects": {

View File

@ -1,19 +0,0 @@
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;
}

View File

@ -1,42 +0,0 @@
<div id="comments-list-container">
<h2>Comments</h2>
@if (isLoggedIn()) {
<div id="add-comment-container">
<h3>Add Comment</h3>
<form [formGroup]="addCommentForm" (ngSubmit)="onCommentSubmit()">
<label for="comment">Comment</label>
<input id="comment" formControlName="comment" type="text" />
<button type="submit" [disabled]="!addCommentForm.valid">Post Comment</button>
</form>
</div>
} @else {
<p>You must be logged in to comment.</p>
}
<h3>Comments</h3>
@if (commentsQuery.isLoading()) {
<p>Loading comments...</p>
} @else if (commentsQuery.isError()) {
<p>There was an error loading the comments.</p>
} @else if (commentsQuery.isSuccess()) {
@let comments = commentsQuery.data();
@if (comments.length) {
<ul>
@if (addCommentMutation.isPending()) {
<li style="opacity: 0.5">
<p>{{ username() }}</p>
<div>{{ addCommentMutation.variables() }}</div>
</li>
}
@for (comment of comments; track $index) {
<li>
<p>{{ comment.owner.username }} at {{ comment.created }}</p>
<div [innerHTML]="comment.text"></div>
</li>
}
</ul>
} @else {
<p>There are no comments yet.</p>
}
}
</div>

View File

@ -1,22 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RecipeCommentsList } from './recipe-comments-list.component';
describe('CommentsList', () => {
let component: RecipeCommentsList;
let fixture: ComponentFixture<RecipeCommentsList>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RecipeCommentsList],
}).compileComponents();
fixture = TestBed.createComponent(RecipeCommentsList);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,40 +0,0 @@
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<string>();
public readonly recipeSlug = input.required<string>();
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!);
}
}

View File

@ -38,5 +38,4 @@
/>
}
<div [innerHTML]="recipe.text"></div>
<app-comments-list [recipeUsername]="recipe.owner.username" [recipeSlug]="recipe.slug" />
</article>

View File

@ -6,11 +6,10 @@ 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, RecipeCommentsList],
imports: [FaIconComponent],
templateUrl: './recipe-page-content.html',
styleUrl: './recipe-page-content.css',
})

View File

@ -4,7 +4,6 @@ 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',
@ -22,7 +21,7 @@ export class RecipeService {
);
}
public getRecipeView(username: string, slug: string): Promise<RecipeView> {
public async getRecipeView(username: string, slug: string): Promise<RecipeView> {
return firstValueFrom(
this.http.get<RecipeView>(`http://localhost:8080/recipes/${username}/${slug}`),
);
@ -32,7 +31,7 @@ export class RecipeService {
return `http://localhost:8080/recipes/${recipeView.recipe.owner.username}/${recipeView.recipe.slug}`;
}
public async toggleStar(recipeView: RecipeView): Promise<void> {
public async toggleStar(recipeView: RecipeView) {
if (this.authService.accessToken()) {
if (recipeView.isStarred) {
await lastValueFrom(this.http.delete(this.getRecipeUrl(recipeView) + '/star'));
@ -46,31 +45,4 @@ export class RecipeService {
throw new Error('Cannot star a recipe when not logged in.');
}
}
public getComments(username: string, slug: string): Promise<RecipeComment[]> {
return firstValueFrom(
this.http
.get<RecipeComments>(`http://localhost:8080/recipes/${username}/${slug}/comments`)
.pipe(map((res) => res.content)),
);
}
public async addComment(
username: string,
slug: string,
commentText: string,
): Promise<RecipeComment> {
const comment = await firstValueFrom(
this.http.post<RecipeComment>(
`http://localhost:8080/recipes/${username}/${slug}/comments`,
{
text: commentText,
},
),
);
await this.queryClient.invalidateQueries({
queryKey: ['recipeComments', username, slug],
});
return comment;
}
}