Skip to content

Commit 5467760

Browse files
committed
fix: error management
1 parent 857a75e commit 5467760

File tree

5 files changed

+112
-52
lines changed

5 files changed

+112
-52
lines changed

src/app/features/article/pages/article/article.component.html

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
@if (article()) {
2-
<div class="article-page">
1+
<div class="article-page">
2+
@if (errors()) {
3+
<div class="container">
4+
<div class="row">
5+
<div class="col-md-12">
6+
<app-list-errors [errors]="errors()" />
7+
</div>
8+
</div>
9+
</div>
10+
}
11+
@if (article(); as a) {
312
<div class="banner">
413
<div class="container">
5-
<h1>{{ article().title }}</h1>
14+
<h1>{{ a.title }}</h1>
615

7-
<app-article-meta [article]="article()">
16+
<app-article-meta [article]="a">
817
@if (canModify()) {
918
<span>
10-
<a class="btn btn-sm btn-outline-secondary" [routerLink]="['/editor', article().slug]">
19+
<a class="btn btn-sm btn-outline-secondary" [routerLink]="['/editor', a.slug]">
1120
<i class="ion-edit"></i> Edit Article
1221
</a>
1322

@@ -21,11 +30,11 @@ <h1>{{ article().title }}</h1>
2130
</span>
2231
} @else {
2332
<span>
24-
<app-follow-button [profile]="article().author" (toggle)="toggleFollowing($event)"> </app-follow-button>
33+
<app-follow-button [profile]="a.author" (toggle)="toggleFollowing($event)"> </app-follow-button>
2534

26-
<app-favorite-button [article]="article()" (toggle)="onToggleFavorite($event)">
27-
{{ article().favorited ? 'Unfavorite' : 'Favorite' }} Article
28-
<span class="counter">({{ article().favoritesCount }})</span>
35+
<app-favorite-button [article]="a" (toggle)="onToggleFavorite($event)">
36+
{{ a.favorited ? 'Unfavorite' : 'Favorite' }} Article
37+
<span class="counter">({{ a.favoritesCount }})</span>
2938
</app-favorite-button>
3039
</span>
3140
}
@@ -36,10 +45,10 @@ <h1>{{ article().title }}</h1>
3645
<div class="container page">
3746
<div class="row article-content">
3847
<div class="col-md-12">
39-
<div [innerHTML]="article().body | markdown | async"></div>
48+
<div [innerHTML]="a.body | markdown | async"></div>
4049

4150
<ul class="tag-list">
42-
@for (tag of article().tagList; track tag) {
51+
@for (tag of a.tagList; track tag) {
4352
<li class="tag-default tag-pill tag-outline">
4453
{{ tag }}
4554
</li>
@@ -51,10 +60,10 @@ <h1>{{ article().title }}</h1>
5160
<hr />
5261

5362
<div class="article-actions">
54-
<app-article-meta [article]="article()">
63+
<app-article-meta [article]="a">
5564
@if (canModify()) {
5665
<span>
57-
<a class="btn btn-sm btn-outline-secondary" [routerLink]="['/editor', article().slug]">
66+
<a class="btn btn-sm btn-outline-secondary" [routerLink]="['/editor', a.slug]">
5867
<i class="ion-edit"></i> Edit Article
5968
</a>
6069

@@ -68,11 +77,11 @@ <h1>{{ article().title }}</h1>
6877
</span>
6978
} @else {
7079
<span>
71-
<app-follow-button [profile]="article().author" (toggle)="toggleFollowing($event)" />
80+
<app-follow-button [profile]="a.author" (toggle)="toggleFollowing($event)" />
7281

73-
<app-favorite-button [article]="article()" (toggle)="onToggleFavorite($event)">
74-
{{ article().favorited ? 'Unfavorite' : 'Favorite' }} Article
75-
<span class="counter">({{ article().favoritesCount }})</span>
82+
<app-favorite-button [article]="a" (toggle)="onToggleFavorite($event)">
83+
{{ a.favorited ? 'Unfavorite' : 'Favorite' }} Article
84+
<span class="counter">({{ a.favoritesCount }})</span>
7685
</app-favorite-button>
7786
</span>
7887
}
@@ -106,11 +115,13 @@ <h1>{{ article().title }}</h1>
106115
this article.
107116
</div>
108117

118+
<app-list-errors [errors]="deleteCommentErrors()" />
119+
109120
@for (comment of comments(); track comment) {
110121
<app-article-comment [comment]="comment" (delete)="deleteComment(comment)" />
111122
}
112123
</div>
113124
</div>
114125
</div>
115-
</div>
116-
}
126+
}
127+
</div>

src/app/features/article/pages/article/article.component.ts

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { MarkdownPipe } from '../../../../shared/pipes/markdown.pipe';
1212
import { ListErrorsComponent } from '../../../../shared/components/list-errors.component';
1313
import { ArticleCommentComponent } from '../../components/article-comment.component';
1414
import { catchError } from 'rxjs/operators';
15-
import { combineLatest, throwError } from 'rxjs';
15+
import { combineLatest, EMPTY } from 'rxjs';
1616
import { Comment } from '../../models/comment.model';
1717
import { IfAuthenticatedDirective } from '../../../../core/auth/if-authenticated.directive';
1818
import { Errors } from '../../../../core/models/errors.model';
@@ -41,13 +41,15 @@ import { FollowButtonComponent } from '../../../profile/components/follow-button
4141
changeDetection: ChangeDetectionStrategy.OnPush,
4242
})
4343
export default class ArticleComponent implements OnInit {
44-
article = signal<Article>(null!);
44+
article = signal<Article | null>(null);
4545
currentUser = signal<User | null>(null);
4646
comments = signal<Comment[]>([]);
4747
canModify = signal(false);
48+
errors = signal<Errors | null>(null);
4849

4950
commentControl = new FormControl<string>('', { nonNullable: true });
5051
commentFormErrors = signal<Errors | null>(null);
52+
deleteCommentErrors = signal<Errors | null>(null);
5153

5254
isSubmitting = signal(false);
5355
isDeleting = signal(false);
@@ -66,8 +68,8 @@ export default class ArticleComponent implements OnInit {
6668
combineLatest([this.articleService.get(slug), this.commentsService.getAll(slug), this.userService.currentUser])
6769
.pipe(
6870
catchError(err => {
69-
void this.router.navigate(['/']);
70-
return throwError(() => err);
71+
this.errors.set(err.errors || { error: ['Failed to load article'] });
72+
return EMPTY;
7173
}),
7274
takeUntilDestroyed(this.destroyRef),
7375
)
@@ -80,37 +82,49 @@ export default class ArticleComponent implements OnInit {
8082
}
8183

8284
onToggleFavorite(favorited: boolean): void {
83-
this.article.update(article => ({
84-
...article,
85-
favorited,
86-
favoritesCount: favorited ? article.favoritesCount + 1 : article.favoritesCount - 1,
87-
}));
85+
this.article.update(article => {
86+
if (!article) return article;
87+
return {
88+
...article,
89+
favorited,
90+
favoritesCount: favorited ? article.favoritesCount + 1 : article.favoritesCount - 1,
91+
};
92+
});
8893
}
8994

9095
toggleFollowing(profile: Profile): void {
91-
this.article.update(article => ({
92-
...article,
93-
author: { ...article.author, following: profile.following },
94-
}));
96+
this.article.update(article => {
97+
if (!article) return article;
98+
return {
99+
...article,
100+
author: { ...article.author, following: profile.following },
101+
};
102+
});
95103
}
96104

97105
deleteArticle(): void {
106+
const article = this.article();
107+
if (!article) return;
108+
98109
this.isDeleting.set(true);
99110

100111
this.articleService
101-
.delete(this.article().slug)
112+
.delete(article.slug)
102113
.pipe(takeUntilDestroyed(this.destroyRef))
103114
.subscribe(() => {
104115
void this.router.navigate(['/']);
105116
});
106117
}
107118

108119
addComment() {
120+
const article = this.article();
121+
if (!article) return;
122+
109123
this.isSubmitting.set(true);
110124
this.commentFormErrors.set(null);
111125

112126
this.commentsService
113-
.add(this.article().slug, this.commentControl.value)
127+
.add(article.slug, this.commentControl.value)
114128
.pipe(takeUntilDestroyed(this.destroyRef))
115129
.subscribe({
116130
next: comment => {
@@ -126,11 +140,20 @@ export default class ArticleComponent implements OnInit {
126140
}
127141

128142
deleteComment(comment: Comment): void {
143+
const article = this.article();
144+
if (!article) return;
145+
146+
this.deleteCommentErrors.set(null);
129147
this.commentsService
130-
.delete(comment.id, this.article().slug)
148+
.delete(comment.id, article.slug)
131149
.pipe(takeUntilDestroyed(this.destroyRef))
132-
.subscribe(() => {
133-
this.comments.update(comments => comments.filter(item => item !== comment));
150+
.subscribe({
151+
next: () => {
152+
this.comments.update(comments => comments.filter(item => item !== comment));
153+
},
154+
error: errors => {
155+
this.deleteCommentErrors.set(errors);
156+
},
134157
});
135158
}
136159
}

src/app/features/profile/pages/profile/profile.component.html

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
@if (profile()) {
2-
<div class="profile-page">
1+
<div class="profile-page">
2+
@if (errors()) {
3+
<div class="container">
4+
<div class="row">
5+
<div class="col-xs-12 col-md-10 offset-md-1">
6+
<app-list-errors [errors]="errors()" />
7+
</div>
8+
</div>
9+
</div>
10+
}
11+
@if (profile(); as p) {
312
<div class="user-info">
413
<div class="container">
514
<div class="row">
615
<div class="col-xs-12 col-md-10 offset-md-1">
7-
<img [src]="profile().image" class="user-img" />
8-
<h4>{{ profile().username }}</h4>
9-
<p>{{ profile().bio }}</p>
16+
<img [src]="p.image" class="user-img" />
17+
<h4>{{ p.username }}</h4>
18+
<p>{{ p.bio }}</p>
1019
@if (!isUser()) {
11-
<app-follow-button [profile]="profile()" (toggle)="onToggleFollowing($event)" />
20+
<app-follow-button [profile]="p" (toggle)="onToggleFollowing($event)" />
1221
}
1322
@if (isUser()) {
1423
<a [routerLink]="['/settings']" class="btn btn-sm btn-outline-secondary action-btn">
@@ -30,7 +39,7 @@ <h4>{{ profile().username }}</h4>
3039
class="nav-link"
3140
routerLinkActive="active"
3241
[routerLinkActiveOptions]="{ exact: true }"
33-
[routerLink]="['/profile', profile().username]"
42+
[routerLink]="['/profile', p.username]"
3443
>
3544
My Posts
3645
</a>
@@ -40,7 +49,7 @@ <h4>{{ profile().username }}</h4>
4049
class="nav-link"
4150
routerLinkActive="active"
4251
[routerLinkActiveOptions]="{ exact: true }"
43-
[routerLink]="['/profile', profile().username, 'favorites']"
52+
[routerLink]="['/profile', p.username, 'favorites']"
4453
>
4554
Favorited Posts
4655
</a>
@@ -52,5 +61,5 @@ <h4>{{ profile().username }}</h4>
5261
</div>
5362
</div>
5463
</div>
55-
</div>
56-
}
64+
}
65+
</div>

src/app/features/profile/pages/profile/profile.component.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, signal } from '@angular/core';
22
import { ActivatedRoute, Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
33
import { catchError, switchMap } from 'rxjs/operators';
4-
import { combineLatest, of, throwError } from 'rxjs';
4+
import { combineLatest, EMPTY, of } from 'rxjs';
55
import { UserService } from '../../../../core/auth/services/user.service';
66
import { Profile } from '../../models/profile.model';
77
import { ProfileService } from '../../services/profile.service';
88
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
99
import { FollowButtonComponent } from '../../components/follow-button.component';
10+
import { Errors } from '../../../../core/models/errors.model';
11+
import { ListErrorsComponent } from '../../../../shared/components/list-errors.component';
1012

1113
@Component({
1214
selector: 'app-profile-page',
1315
templateUrl: './profile.component.html',
14-
imports: [FollowButtonComponent, RouterLink, RouterLinkActive, RouterOutlet, FollowButtonComponent],
16+
imports: [
17+
FollowButtonComponent,
18+
RouterLink,
19+
RouterLinkActive,
20+
RouterOutlet,
21+
FollowButtonComponent,
22+
ListErrorsComponent,
23+
],
1524
changeDetection: ChangeDetectionStrategy.OnPush,
1625
})
1726
export class ProfileComponent implements OnInit {
18-
profile = signal<Profile>(null!);
27+
profile = signal<Profile | null>(null);
1928
isUser = signal(false);
29+
errors = signal<Errors | null>(null);
2030
destroyRef = inject(DestroyRef);
2131

2232
constructor(
@@ -31,8 +41,8 @@ export class ProfileComponent implements OnInit {
3141
.get(this.route.snapshot.params['username'])
3242
.pipe(
3343
catchError(error => {
34-
void this.router.navigate(['/']);
35-
return throwError(() => error);
44+
this.errors.set(error.errors || { error: ['Failed to load profile'] });
45+
return EMPTY;
3646
}),
3747
switchMap(profile => {
3848
return combineLatest([of(profile), this.userService.currentUser]);

src/app/features/profile/services/profile.service.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import { map, shareReplay } from 'rxjs/operators';
44
import { Profile } from '../models/profile.model';
55
import { HttpClient } from '@angular/common/http';
66

7+
/**
8+
* ProfileService - Fetches public profile data for any user by username.
9+
*
10+
* Note: This is different from UserService which uses GET /user:
11+
* - GET /profiles/:username → Public profile for any user (this service)
12+
* - GET /user → Current authenticated user's own data (UserService)
13+
*/
714
@Injectable({ providedIn: 'root' })
815
export class ProfileService {
916
constructor(private readonly http: HttpClient) {}

0 commit comments

Comments
 (0)