diff --git a/client/angular.json b/client/angular.json index a77afcb..417e680 100644 --- a/client/angular.json +++ b/client/angular.json @@ -29,6 +29,7 @@ ], "styles": [ "./node_modules/font-awesome/css/font-awesome.css", + "./node_modules/ngx-toastr/toastr.css", "src/styles.scss" ], "scripts": [] diff --git a/client/package-lock.json b/client/package-lock.json index 94793da..ad6cad8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -19,6 +19,7 @@ "bootstrap": "^5.1.3", "font-awesome": "^4.7.0", "ngx-bootstrap": "^8.0.0", + "ngx-toastr": "^14.3.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", "zone.js": "~0.11.4" @@ -7974,6 +7975,19 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/ngx-toastr": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-14.3.0.tgz", + "integrity": "sha512-d8j/sOr60w5U7rGlcKQ0Ff4u+m2NzhqU5ZdJXn7QW3aR3Zf/rY7/Fd14BmUindTOWVr2NeTYcQXCjLpir0ldpA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=12.0.0-0", + "@angular/core": ">=12.0.0-0", + "@angular/platform-browser": ">=12.0.0-0" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -17534,6 +17548,14 @@ "tslib": "^2.0.0" } }, + "ngx-toastr": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-14.3.0.tgz", + "integrity": "sha512-d8j/sOr60w5U7rGlcKQ0Ff4u+m2NzhqU5ZdJXn7QW3aR3Zf/rY7/Fd14BmUindTOWVr2NeTYcQXCjLpir0ldpA==", + "requires": { + "tslib": "^2.3.0" + } + }, "nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", diff --git a/client/package.json b/client/package.json index c9c1b84..585ee42 100644 --- a/client/package.json +++ b/client/package.json @@ -21,6 +21,7 @@ "bootstrap": "^5.1.3", "font-awesome": "^4.7.0", "ngx-bootstrap": "^8.0.0", + "ngx-toastr": "^14.3.0", "rxjs": "~7.5.0", "tslib": "^2.3.0", "zone.js": "~0.11.4" @@ -39,4 +40,4 @@ "karma-jasmine-html-reporter": "~1.7.0", "typescript": "~4.6.2" } -} \ No newline at end of file +} diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index d32c9b3..d9bb9a1 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts @@ -1,11 +1,17 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { NotFoundComponent } from './core/not-found/not-found.component'; +import { ServerErrorComponent } from './core/server-error/server-error.component'; +import { TestErrorComponent } from './core/test-error/test-error.component'; import { HomeComponent } from './home/home.component'; import { ProductDetailsComponent } from './shop/product-details/product-details.component'; import { ShopComponent } from './shop/shop.component'; const routes: Routes = [ {path: '', component: HomeComponent}, + {path: 'test-error', component: TestErrorComponent}, + {path: 'server-error', component: ServerErrorComponent}, + {path: 'not-found', component: NotFoundComponent}, {path: 'shop', loadChildren: ()=> import('./shop/shop.module').then(mod => mod.ShopModule)}, {path: '**', redirectTo: '', pathMatch: 'full'} ]; diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index e449f23..09f114b 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -1,6 +1,6 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -8,6 +8,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { CoreModule } from './core/core.module'; import { ShopModule } from './shop/shop.module'; import { HomeModule } from './home/home.module'; +import { ErrorInterceptor } from './core/interceptors/error.interceptor'; @NgModule({ declarations: [ @@ -21,7 +22,9 @@ import { HomeModule } from './home/home.module'; CoreModule, HomeModule ], - providers: [], + providers: [ + {provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true} + ], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index e748d8b..4abdf78 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts @@ -2,14 +2,22 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { NavBarComponent } from './nav-bar/nav-bar.component'; import { RouterModule } from '@angular/router'; +import { TestErrorComponent } from './test-error/test-error.component'; +import { NotFoundComponent } from './not-found/not-found.component'; +import { ServerErrorComponent } from './server-error/server-error.component'; +import { ToastrModule } from 'ngx-toastr'; @NgModule({ - declarations: [NavBarComponent], + declarations: [NavBarComponent, TestErrorComponent, NotFoundComponent, ServerErrorComponent], imports: [ CommonModule, - RouterModule + RouterModule, + ToastrModule.forRoot({ + positionClass: 'toast-bottom-right', + preventDuplicates: true + }) ], exports: [NavBarComponent] }) diff --git a/client/src/app/core/interceptors/error.interceptor.ts b/client/src/app/core/interceptors/error.interceptor.ts new file mode 100644 index 0000000..391bf45 --- /dev/null +++ b/client/src/app/core/interceptors/error.interceptor.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { + HttpRequest, + HttpHandler, + HttpEvent, + HttpInterceptor +} from '@angular/common/http'; +import { catchError, Observable, throwError } from 'rxjs'; +import { Router } from '@angular/router'; +import { ToastrService } from 'ngx-toastr'; + +@Injectable() +export class ErrorInterceptor implements HttpInterceptor { + + constructor(private router: Router, private toastr: ToastrService) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle(request).pipe( + catchError(error => { + if (error){ + + if (error.status === 400){ + this.toastr.error(error.error.message, error.error.statusCode); + } + + if (error.status === 401){ + this.toastr.error(error.error.message, error.error.statusCode); + } + + if (error.status === 404){ + this.router.navigateByUrl('/not-found'); + } + + if (error.status === 500){ + this.router.navigateByUrl('/server-error'); + } + } + return throwError(() => new Error(error)); + }) + ); + } +} diff --git a/client/src/app/core/nav-bar/nav-bar.component.html b/client/src/app/core/nav-bar/nav-bar.component.html index e2ab019..f69c889 100644 --- a/client/src/app/core/nav-bar/nav-bar.component.html +++ b/client/src/app/core/nav-bar/nav-bar.component.html @@ -3,7 +3,7 @@
diff --git a/client/src/app/core/not-found/not-found.component.html b/client/src/app/core/not-found/not-found.component.html new file mode 100644 index 0000000..43f09e1 --- /dev/null +++ b/client/src/app/core/not-found/not-found.component.html @@ -0,0 +1,3 @@ +
+

404 Not Found

+
diff --git a/client/src/app/core/not-found/not-found.component.scss b/client/src/app/core/not-found/not-found.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/core/not-found/not-found.component.ts b/client/src/app/core/not-found/not-found.component.ts new file mode 100644 index 0000000..7cb4124 --- /dev/null +++ b/client/src/app/core/not-found/not-found.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-not-found', + templateUrl: './not-found.component.html', + styleUrls: ['./not-found.component.scss'] +}) +export class NotFoundComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/client/src/app/core/server-error/server-error.component.html b/client/src/app/core/server-error/server-error.component.html new file mode 100644 index 0000000..95017c1 --- /dev/null +++ b/client/src/app/core/server-error/server-error.component.html @@ -0,0 +1,3 @@ +
+

Internal Server Error

+
diff --git a/client/src/app/core/server-error/server-error.component.scss b/client/src/app/core/server-error/server-error.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/core/server-error/server-error.component.ts b/client/src/app/core/server-error/server-error.component.ts new file mode 100644 index 0000000..bc3f70f --- /dev/null +++ b/client/src/app/core/server-error/server-error.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-server-error', + templateUrl: './server-error.component.html', + styleUrls: ['./server-error.component.scss'] +}) +export class ServerErrorComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/client/src/app/core/test-error/test-error.component.html b/client/src/app/core/test-error/test-error.component.html new file mode 100644 index 0000000..0a413af --- /dev/null +++ b/client/src/app/core/test-error/test-error.component.html @@ -0,0 +1,6 @@ +
+ + + + +
diff --git a/client/src/app/core/test-error/test-error.component.scss b/client/src/app/core/test-error/test-error.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/core/test-error/test-error.component.ts b/client/src/app/core/test-error/test-error.component.ts new file mode 100644 index 0000000..64a9955 --- /dev/null +++ b/client/src/app/core/test-error/test-error.component.ts @@ -0,0 +1,58 @@ +import { HttpClient } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { environment } from 'src/environments/environment'; + +@Component({ + selector: 'app-test-error', + templateUrl: './test-error.component.html', + styleUrls: ['./test-error.component.scss'] +}) +export class TestErrorComponent implements OnInit { + baseURL = environment.apiUrl; + + constructor(private http: HttpClient) { } + + ngOnInit(): void { + } + + get404Error(){ + this.http.get(this.baseURL + 'products/42').subscribe( + { + next: (response) => { console.log(response); }, + error: (e: any) => { console.log(e); }, + complete: () => { console.log('complete'); } + } + ); + } + + get500Error(){ + this.http.get(this.baseURL + 'buggy/servererror').subscribe( + { + next: (response) => { console.log(response); }, + error: (e: any) => { console.log(e); }, + complete: () => { console.log('complete'); } + } + ); + } + + get400Error(){ + this.http.get(this.baseURL + 'buggy/badrequest').subscribe( + { + next: (response) => { console.log(response); }, + error: (e: any) => { console.log(e); }, + complete: () => { console.log('complete'); } + } + ); + } + + get400ValidationError(){ + this.http.get(this.baseURL + 'products/five').subscribe( + { + next: (response) => { console.log(response); }, + error: (e: any) => { console.log(e); }, + complete: () => { console.log('complete'); } + } + ); + } + +} diff --git a/client/src/environments/environment.ts b/client/src/environments/environment.ts index f56ff47..251e17d 100644 --- a/client/src/environments/environment.ts +++ b/client/src/environments/environment.ts @@ -3,7 +3,8 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + apiUrl: 'https://localhost:5001/api/' }; /*