Compare commits
No commits in common. "714fe60d9fb2aa5a1e5f67f213200c7b38e90e57" and "b8110e893a71fb228216252f403e66409ea4fae5" have entirely different histories.
714fe60d9f
...
b8110e893a
@ -2,8 +2,7 @@
|
|||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"cli": {
|
"cli": {
|
||||||
"packageManager": "npm",
|
"packageManager": "npm"
|
||||||
"analytics": false
|
|
||||||
},
|
},
|
||||||
"newProjectRoot": "projects",
|
"newProjectRoot": "projects",
|
||||||
"projects": {
|
"projects": {
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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>
|
|
||||||
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -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!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -38,5 +38,4 @@
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<div [innerHTML]="recipe.text"></div>
|
<div [innerHTML]="recipe.text"></div>
|
||||||
<app-comments-list [recipeUsername]="recipe.owner.username" [recipeSlug]="recipe.slug" />
|
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@ -6,11 +6,10 @@ import { faGlobe, faLock, faStar, faUser } from '@fortawesome/free-solid-svg-ico
|
|||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
import { RecipeService } from '../../service/recipe.service';
|
import { RecipeService } from '../../service/recipe.service';
|
||||||
import { AuthService } from '../../service/auth.service';
|
import { AuthService } from '../../service/auth.service';
|
||||||
import { RecipeCommentsList } from '../../recipe-comments-list/recipe-comments-list.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-recipe-page-content',
|
selector: 'app-recipe-page-content',
|
||||||
imports: [FaIconComponent, RecipeCommentsList],
|
imports: [FaIconComponent],
|
||||||
templateUrl: './recipe-page-content.html',
|
templateUrl: './recipe-page-content.html',
|
||||||
styleUrl: './recipe-page-content.css',
|
styleUrl: './recipe-page-content.css',
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { firstValueFrom, lastValueFrom, map } from 'rxjs';
|
|||||||
import { Recipe, RecipeInfoViews, RecipeView } from '../model/Recipe.model';
|
import { Recipe, RecipeInfoViews, RecipeView } from '../model/Recipe.model';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { QueryClient } from '@tanstack/angular-query-experimental';
|
import { QueryClient } from '@tanstack/angular-query-experimental';
|
||||||
import { RecipeComment, RecipeComments } from '../model/RecipeComment.model';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
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(
|
return firstValueFrom(
|
||||||
this.http.get<RecipeView>(`http://localhost:8080/recipes/${username}/${slug}`),
|
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}`;
|
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 (this.authService.accessToken()) {
|
||||||
if (recipeView.isStarred) {
|
if (recipeView.isStarred) {
|
||||||
await lastValueFrom(this.http.delete(this.getRecipeUrl(recipeView) + '/star'));
|
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.');
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user