feat: set up HttpErrorInterceptor for error handling and integrated ErrorNotificationService; created and implemented ToastNotificationService for user notifications

This commit is contained in:
Chneemann 2025-03-29 16:28:07 +01:00
parent 13a087cfb0
commit 839df9e3ff
9 changed files with 122 additions and 31 deletions

View file

@ -17,6 +17,8 @@ import { firebaseConfig } from './environments/config';
import * as Sentry from '@sentry/angular';
import { ToastrModule } from 'ngx-toastr';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpErrorInterceptor } from './interceptors/http-interceptor';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
@ -61,5 +63,10 @@ export const appConfig: ApplicationConfig = {
deps: [Sentry.TraceService],
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: HttpErrorInterceptor,
multi: true,
},
],
};

View file

@ -15,6 +15,7 @@ import { firstValueFrom, map } from 'rxjs';
import { TaskService } from '../../services/task.service';
import { ApiService } from '../../services/api.service';
import { TaskUpdateService } from '../../services/task-update.service';
import { ToastNotificationService } from '../../services/toast-notification.servic';
@Component({
selector: 'app-add-task',
@ -53,6 +54,7 @@ export class AddTaskComponent implements OnInit {
private apiService: ApiService,
private authService: AuthService,
private taskUpdateService: TaskUpdateService,
private toastNotificationService: ToastNotificationService,
private route: ActivatedRoute,
private router: Router
) {}
@ -379,6 +381,7 @@ export class AddTaskComponent implements OnInit {
this.apiService.saveNewTask(taskWithoutId).subscribe({
next: (response) => {
this.toastNotificationService.createTaskSuccessToast();
this.taskUpdateService.notifyTaskUpdate();
this.removeTaskData(ngForm);
this.closeOverlay();

View file

@ -15,6 +15,7 @@ import { TaskService } from '../../services/task.service';
import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner/loading-spinner.component';
import { finalize, Subject, takeUntil } from 'rxjs';
import { TaskUpdateService } from '../../services/task-update.service';
import { ToastNotificationService } from '../../services/toast-notification.servic';
@Component({
selector: 'app-board',
@ -46,6 +47,7 @@ export class BoardComponent {
private taskService: TaskService,
private apiService: ApiService,
private taskUpdateService: TaskUpdateService,
private toastNotificationService: ToastNotificationService,
private router: Router
) {}
@ -140,6 +142,7 @@ export class BoardComponent {
handleItemDropped(taskId: string, status: string): void {
this.apiService.updateTaskStatus(taskId, status).subscribe({
next: () => {
this.toastNotificationService.taskStatusUpdatedToast();
this.updateTaskStatus(taskId, status);
},
error: (error) => console.error('Error updating task:', error),

View file

@ -12,7 +12,6 @@ import { TranslateModule } from '@ngx-translate/core';
import { LoadingDialogComponent } from './loading-dialog/loading-dialog.component';
import { OverlayService } from '../../services/overlay.service';
import { AuthService } from '../../services/auth.service';
import { ErrorHandlingService } from '../../services/error-handling.service';
import { TokenService } from '../../services/token.service';
@Component({

View file

@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpErrorResponse,
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ErrorNotificationService } from '../services/error-notification.service';
@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
constructor(private errorNotificationService: ErrorNotificationService) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
this.errorNotificationService.handleHttpError(error);
return throwError(error);
})
);
}
}

View file

@ -9,8 +9,8 @@ import {
} from 'rxjs';
import { ApiService } from './api.service';
import { TokenService } from './token.service';
import { ErrorHandlingService } from './error-handling.service';
import { Router } from '@angular/router';
import { ToastNotificationService } from './toast-notification.servic';
@Injectable({
providedIn: 'root',
@ -21,7 +21,7 @@ export class AuthService {
constructor(
private apiService: ApiService,
private tokenService: TokenService,
private errorHandlingService: ErrorHandlingService,
private toastNotificationService: ToastNotificationService,
private router: Router
) {
this.currentUserIdSubject.next(this.tokenService.getUserId());
@ -40,10 +40,9 @@ export class AuthService {
this.tokenService.storeAuthToken(token, storage);
this.tokenService.storeUserId(userId, storage);
this.currentUserIdSubject.next(userId);
this.errorHandlingService.loginSuccessToast();
this.toastNotificationService.loginSuccessToast();
} catch (error) {
console.error('Login failed:', error);
this.errorHandlingService.handleHttpError(error);
}
}
@ -54,11 +53,10 @@ export class AuthService {
this.tokenService.deleteAuthToken();
this.tokenService.deleteUserId();
this.currentUserIdSubject.next(null);
this.errorHandlingService.logoutSuccessToast();
this.toastNotificationService.logoutSuccessToast();
this.router.navigate(['/logout']);
} catch (error) {
console.error('Logout failed:', error);
this.errorHandlingService.handleHttpError(error);
}
}

View file

@ -5,7 +5,7 @@ import { ToastrService } from 'ngx-toastr';
@Injectable({
providedIn: 'root',
})
export class ErrorHandlingService {
export class ErrorNotificationService {
constructor(private toastr: ToastrService) {}
handleHttpError(error: unknown): void {
@ -28,36 +28,23 @@ export class ErrorHandlingService {
errorMessage = 'The requested resource was not found.';
break;
case 500:
errorMessage = 'Server error: Please try again later.';
errorMessage =
'Server error: Please try again later. If the issue persists, please contact support.';
break;
case 408:
errorMessage =
'Request timed out. Please check your connection and try again.';
break;
default:
errorMessage = `Unknown error: ${error.statusText || error.message}`;
errorMessage =
error.error?.message ||
`Unknown error: ${error.statusText || error.message}`;
break;
}
} else if (error instanceof Error) {
errorMessage = `${error.message || error.toString()}`;
errorMessage = `Network error: ${error.message || error.toString()}`;
}
this.toastr.error(errorMessage, 'Error');
}
loginSuccessToast(): void {
this.toastr.success(
'You have successfully logged in.',
'Login Successful',
{
timeOut: 3000,
}
);
}
logoutSuccessToast(): void {
this.toastr.info(
'You have successfully logged out. Have a nice day!',
'Logout Successful',
{
timeOut: 3000,
}
);
}
}

View file

@ -0,0 +1,63 @@
import { Injectable } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
@Injectable({
providedIn: 'root',
})
export class ToastNotificationService {
constructor(private toastr: ToastrService) {}
// Auth
loginSuccessToast(): void {
this.createSuccessToast(
'You have successfully logged in.',
'Login Successful'
);
}
logoutSuccessToast(): void {
this.createInfoToast(
'You have successfully logged out. Have a nice day!',
'Logout Successful'
);
}
// Tasks
createTaskSuccessToast(): void {
this.createSuccessToast('Task created successfully!', 'Task Created');
}
deleteTaskSuccessToast(): void {
this.createSuccessToast('Task deleted successfully!', 'Task Deleted');
}
taskStatusUpdatedToast(): void {
this.createInfoToast('Task successfully moved!', 'Task Moved');
}
// Helperfunctions
createSuccessToast(message: string, title: string = 'Success'): void {
this.toastr.success(message, title, {
timeOut: 3000,
positionClass: 'toast-top-right',
progressBar: true,
});
}
createErrorToast(message: string, title: string = 'Error'): void {
this.toastr.error(message, title, {
timeOut: 3000,
positionClass: 'toast-top-right',
progressBar: true,
});
}
createInfoToast(message: string, title: string = 'Info'): void {
this.toastr.info(message, title, {
timeOut: 3000,
positionClass: 'toast-top-right',
progressBar: true,
});
}
}

View file

@ -12,6 +12,7 @@ import { AuthService } from '../../../../services/auth.service';
import { map } from 'rxjs';
import { ApiService } from '../../../../services/api.service';
import { TaskUpdateService } from '../../../../services/task-update.service';
import { ToastNotificationService } from '../../../../services/toast-notification.servic';
@Component({
selector: 'app-task-overlay',
@ -35,6 +36,7 @@ export class TaskOverlayComponent implements OnInit {
private authService: AuthService,
private apiService: ApiService,
private taskUpdateService: TaskUpdateService,
private toastNotificationService: ToastNotificationService,
private router: Router,
private route: ActivatedRoute
) {}
@ -123,6 +125,7 @@ export class TaskOverlayComponent implements OnInit {
deleteTask(taskId: string) {
this.apiService.deleteTaskById(taskId).subscribe({
next: (task) => {
this.toastNotificationService.deleteTaskSuccessToast();
this.taskUpdateService.notifyTaskUpdate();
},
error: (err) => {