feat: set up HttpErrorInterceptor for error handling and integrated ErrorNotificationService; created and implemented ToastNotificationService for user notifications
This commit is contained in:
parent
13a087cfb0
commit
839df9e3ff
9 changed files with 122 additions and 31 deletions
|
|
@ -17,6 +17,8 @@ import { firebaseConfig } from './environments/config';
|
||||||
import * as Sentry from '@sentry/angular';
|
import * as Sentry from '@sentry/angular';
|
||||||
import { ToastrModule } from 'ngx-toastr';
|
import { ToastrModule } from 'ngx-toastr';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
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) {
|
export function createTranslateLoader(http: HttpClient) {
|
||||||
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
|
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
|
||||||
|
|
@ -61,5 +63,10 @@ export const appConfig: ApplicationConfig = {
|
||||||
deps: [Sentry.TraceService],
|
deps: [Sentry.TraceService],
|
||||||
multi: true,
|
multi: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: HTTP_INTERCEPTORS,
|
||||||
|
useClass: HttpErrorInterceptor,
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { firstValueFrom, map } from 'rxjs';
|
||||||
import { TaskService } from '../../services/task.service';
|
import { TaskService } from '../../services/task.service';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { TaskUpdateService } from '../../services/task-update.service';
|
import { TaskUpdateService } from '../../services/task-update.service';
|
||||||
|
import { ToastNotificationService } from '../../services/toast-notification.servic';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-task',
|
selector: 'app-add-task',
|
||||||
|
|
@ -53,6 +54,7 @@ export class AddTaskComponent implements OnInit {
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private taskUpdateService: TaskUpdateService,
|
private taskUpdateService: TaskUpdateService,
|
||||||
|
private toastNotificationService: ToastNotificationService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router
|
private router: Router
|
||||||
) {}
|
) {}
|
||||||
|
|
@ -379,6 +381,7 @@ export class AddTaskComponent implements OnInit {
|
||||||
|
|
||||||
this.apiService.saveNewTask(taskWithoutId).subscribe({
|
this.apiService.saveNewTask(taskWithoutId).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
|
this.toastNotificationService.createTaskSuccessToast();
|
||||||
this.taskUpdateService.notifyTaskUpdate();
|
this.taskUpdateService.notifyTaskUpdate();
|
||||||
this.removeTaskData(ngForm);
|
this.removeTaskData(ngForm);
|
||||||
this.closeOverlay();
|
this.closeOverlay();
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { TaskService } from '../../services/task.service';
|
||||||
import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner/loading-spinner.component';
|
import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner/loading-spinner.component';
|
||||||
import { finalize, Subject, takeUntil } from 'rxjs';
|
import { finalize, Subject, takeUntil } from 'rxjs';
|
||||||
import { TaskUpdateService } from '../../services/task-update.service';
|
import { TaskUpdateService } from '../../services/task-update.service';
|
||||||
|
import { ToastNotificationService } from '../../services/toast-notification.servic';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-board',
|
selector: 'app-board',
|
||||||
|
|
@ -46,6 +47,7 @@ export class BoardComponent {
|
||||||
private taskService: TaskService,
|
private taskService: TaskService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private taskUpdateService: TaskUpdateService,
|
private taskUpdateService: TaskUpdateService,
|
||||||
|
private toastNotificationService: ToastNotificationService,
|
||||||
private router: Router
|
private router: Router
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|
@ -140,6 +142,7 @@ export class BoardComponent {
|
||||||
handleItemDropped(taskId: string, status: string): void {
|
handleItemDropped(taskId: string, status: string): void {
|
||||||
this.apiService.updateTaskStatus(taskId, status).subscribe({
|
this.apiService.updateTaskStatus(taskId, status).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
|
this.toastNotificationService.taskStatusUpdatedToast();
|
||||||
this.updateTaskStatus(taskId, status);
|
this.updateTaskStatus(taskId, status);
|
||||||
},
|
},
|
||||||
error: (error) => console.error('Error updating task:', error),
|
error: (error) => console.error('Error updating task:', error),
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { LoadingDialogComponent } from './loading-dialog/loading-dialog.component';
|
import { LoadingDialogComponent } from './loading-dialog/loading-dialog.component';
|
||||||
import { OverlayService } from '../../services/overlay.service';
|
import { OverlayService } from '../../services/overlay.service';
|
||||||
import { AuthService } from '../../services/auth.service';
|
import { AuthService } from '../../services/auth.service';
|
||||||
import { ErrorHandlingService } from '../../services/error-handling.service';
|
|
||||||
import { TokenService } from '../../services/token.service';
|
import { TokenService } from '../../services/token.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
|
||||||
28
src/app/interceptors/http-interceptor.ts
Normal file
28
src/app/interceptors/http-interceptor.ts
Normal 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);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,8 +9,8 @@ import {
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
import { TokenService } from './token.service';
|
import { TokenService } from './token.service';
|
||||||
import { ErrorHandlingService } from './error-handling.service';
|
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { ToastNotificationService } from './toast-notification.servic';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
|
|
@ -21,7 +21,7 @@ export class AuthService {
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private tokenService: TokenService,
|
private tokenService: TokenService,
|
||||||
private errorHandlingService: ErrorHandlingService,
|
private toastNotificationService: ToastNotificationService,
|
||||||
private router: Router
|
private router: Router
|
||||||
) {
|
) {
|
||||||
this.currentUserIdSubject.next(this.tokenService.getUserId());
|
this.currentUserIdSubject.next(this.tokenService.getUserId());
|
||||||
|
|
@ -40,10 +40,9 @@ export class AuthService {
|
||||||
this.tokenService.storeAuthToken(token, storage);
|
this.tokenService.storeAuthToken(token, storage);
|
||||||
this.tokenService.storeUserId(userId, storage);
|
this.tokenService.storeUserId(userId, storage);
|
||||||
this.currentUserIdSubject.next(userId);
|
this.currentUserIdSubject.next(userId);
|
||||||
this.errorHandlingService.loginSuccessToast();
|
this.toastNotificationService.loginSuccessToast();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login failed:', error);
|
console.error('Login failed:', error);
|
||||||
this.errorHandlingService.handleHttpError(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,11 +53,10 @@ export class AuthService {
|
||||||
this.tokenService.deleteAuthToken();
|
this.tokenService.deleteAuthToken();
|
||||||
this.tokenService.deleteUserId();
|
this.tokenService.deleteUserId();
|
||||||
this.currentUserIdSubject.next(null);
|
this.currentUserIdSubject.next(null);
|
||||||
this.errorHandlingService.logoutSuccessToast();
|
this.toastNotificationService.logoutSuccessToast();
|
||||||
this.router.navigate(['/logout']);
|
this.router.navigate(['/logout']);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Logout failed:', error);
|
console.error('Logout failed:', error);
|
||||||
this.errorHandlingService.handleHttpError(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { ToastrService } from 'ngx-toastr';
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ErrorHandlingService {
|
export class ErrorNotificationService {
|
||||||
constructor(private toastr: ToastrService) {}
|
constructor(private toastr: ToastrService) {}
|
||||||
|
|
||||||
handleHttpError(error: unknown): void {
|
handleHttpError(error: unknown): void {
|
||||||
|
|
@ -28,36 +28,23 @@ export class ErrorHandlingService {
|
||||||
errorMessage = 'The requested resource was not found.';
|
errorMessage = 'The requested resource was not found.';
|
||||||
break;
|
break;
|
||||||
case 500:
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
errorMessage = `Unknown error: ${error.statusText || error.message}`;
|
errorMessage =
|
||||||
|
error.error?.message ||
|
||||||
|
`Unknown error: ${error.statusText || error.message}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (error instanceof Error) {
|
} else if (error instanceof Error) {
|
||||||
errorMessage = `${error.message || error.toString()}`;
|
errorMessage = `Network error: ${error.message || error.toString()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toastr.error(errorMessage, 'Error');
|
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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
63
src/app/services/toast-notification.servic.ts
Executable file
63
src/app/services/toast-notification.servic.ts
Executable 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import { AuthService } from '../../../../services/auth.service';
|
||||||
import { map } from 'rxjs';
|
import { map } from 'rxjs';
|
||||||
import { ApiService } from '../../../../services/api.service';
|
import { ApiService } from '../../../../services/api.service';
|
||||||
import { TaskUpdateService } from '../../../../services/task-update.service';
|
import { TaskUpdateService } from '../../../../services/task-update.service';
|
||||||
|
import { ToastNotificationService } from '../../../../services/toast-notification.servic';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-task-overlay',
|
selector: 'app-task-overlay',
|
||||||
|
|
@ -35,6 +36,7 @@ export class TaskOverlayComponent implements OnInit {
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private taskUpdateService: TaskUpdateService,
|
private taskUpdateService: TaskUpdateService,
|
||||||
|
private toastNotificationService: ToastNotificationService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private route: ActivatedRoute
|
private route: ActivatedRoute
|
||||||
) {}
|
) {}
|
||||||
|
|
@ -123,6 +125,7 @@ export class TaskOverlayComponent implements OnInit {
|
||||||
deleteTask(taskId: string) {
|
deleteTask(taskId: string) {
|
||||||
this.apiService.deleteTaskById(taskId).subscribe({
|
this.apiService.deleteTaskById(taskId).subscribe({
|
||||||
next: (task) => {
|
next: (task) => {
|
||||||
|
this.toastNotificationService.deleteTaskSuccessToast();
|
||||||
this.taskUpdateService.notifyTaskUpdate();
|
this.taskUpdateService.notifyTaskUpdate();
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue