This commit is contained in:
Charles Showalter 2022-05-17 06:48:37 -07:00
commit 3f0a50d2bd
17 changed files with 119 additions and 26 deletions

View File

@ -4,16 +4,14 @@ import { NotFoundComponent } from './core/not-found/not-found.component';
import { ServerErrorComponent } from './core/server-error/server-error.component'; import { ServerErrorComponent } from './core/server-error/server-error.component';
import { TestErrorComponent } from './core/test-error/test-error.component'; import { TestErrorComponent } from './core/test-error/test-error.component';
import { HomeComponent } from './home/home.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 = [ const routes: Routes = [
{path: '', component: HomeComponent}, {path: '', component: HomeComponent, data: {breadcrumb: 'Home'}},
{path: 'test-error', component: TestErrorComponent}, {path: 'test-error', component: TestErrorComponent, data: {breadcrumb: 'Test Errors'}},
{path: 'server-error', component: ServerErrorComponent}, {path: 'server-error', component: ServerErrorComponent, data: {breadcrumb: 'Server Error'}},
{path: 'not-found', component: NotFoundComponent}, {path: 'not-found', component: NotFoundComponent, data: {breadcrumb: 'Not Found'}},
{path: 'shop', loadChildren: ()=> import('./shop/shop.module').then(mod => mod.ShopModule)}, {path: 'shop', loadChildren: ()=> import('./shop/shop.module').then(mod => mod.ShopModule), data: {breadcrumb: 'Shop'}},
{path: '**', redirectTo: '', pathMatch: 'full'} {path: '**', redirectTo: 'not-found', pathMatch: 'full'}
]; ];

View File

@ -1,4 +1,5 @@
<app-nav-bar></app-nav-bar> <app-nav-bar></app-nav-bar>
<div class="container" style="margin-top: 140px;"> <app-section-header></app-section-header>
<div class="container">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>

View File

@ -6,19 +6,25 @@ import { TestErrorComponent } from './test-error/test-error.component';
import { NotFoundComponent } from './not-found/not-found.component'; import { NotFoundComponent } from './not-found/not-found.component';
import { ServerErrorComponent } from './server-error/server-error.component'; import { ServerErrorComponent } from './server-error/server-error.component';
import { ToastrModule } from 'ngx-toastr'; import { ToastrModule } from 'ngx-toastr';
import { SectionHeaderComponent } from './section-header/section-header.component';
import { BreadcrumbModule } from 'xng-breadcrumb';
@NgModule({ @NgModule({
declarations: [NavBarComponent, TestErrorComponent, NotFoundComponent, ServerErrorComponent], declarations: [NavBarComponent, TestErrorComponent, NotFoundComponent, ServerErrorComponent, SectionHeaderComponent],
imports: [ imports: [
CommonModule, CommonModule,
RouterModule, RouterModule,
BreadcrumbModule,
ToastrModule.forRoot({ ToastrModule.forRoot({
positionClass: 'toast-bottom-right', positionClass: 'toast-bottom-right',
preventDuplicates: true preventDuplicates: true
}) })
], ],
exports: [NavBarComponent] exports: [
NavBarComponent,
SectionHeaderComponent
]
}) })
export class CoreModule { } export class CoreModule { }

View File

@ -6,7 +6,7 @@ import {
HttpInterceptor HttpInterceptor
} from '@angular/common/http'; } from '@angular/common/http';
import { catchError, Observable, throwError } from 'rxjs'; import { catchError, Observable, throwError } from 'rxjs';
import { Router } from '@angular/router'; import { NavigationExtras, Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
@Injectable() @Injectable()
@ -20,8 +20,13 @@ export class ErrorInterceptor implements HttpInterceptor {
if (error){ if (error){
if (error.status === 400){ if (error.status === 400){
if (error.error.errors){
//log('error thrown ' + error.error);
throw error.error;
} else {
this.toastr.error(error.error.message, error.error.statusCode); this.toastr.error(error.error.message, error.error.statusCode);
} }
}
if (error.status === 401){ if (error.status === 401){
this.toastr.error(error.error.message, error.error.statusCode); this.toastr.error(error.error.message, error.error.statusCode);
@ -32,7 +37,8 @@ export class ErrorInterceptor implements HttpInterceptor {
} }
if (error.status === 500){ if (error.status === 500){
this.router.navigateByUrl('/server-error'); const navigationExtras: NavigationExtras = {state: {error: error.error}};
this.router.navigateByUrl('/server-error', navigationExtras);
} }
} }
return throwError(() => new Error(error)); return throwError(() => new Error(error));

View File

@ -0,0 +1,14 @@
<ng-container *ngIf="(breadcrumb$ | async) as breadcrumbs">
<section *ngIf="breadcrumbs.length > 0 && breadcrumbs[breadcrumbs.length-1].label !== 'Home'" class="py-5" style="margin-top: 105px; background-color:#f5f5f5;">
<div class="container">
<div class="row d-flex align-item-center">
<div class="col-9">
<h1>{{breadcrumbs.length > 0 && breadcrumbs[breadcrumbs.length-1].label | titlecase}}</h1>
</div>
<div class="col-3">
<xng-breadcrumb></xng-breadcrumb>
</div>
</div>
</div>
</section>
</ng-container>

View File

@ -0,0 +1,19 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { BreadcrumbService } from 'xng-breadcrumb';
@Component({
selector: 'app-section-header',
templateUrl: './section-header.component.html',
styleUrls: ['./section-header.component.scss']
})
export class SectionHeaderComponent implements OnInit {
breadcrumb$: Observable<any[]>;
constructor(private bcService: BreadcrumbService) { }
ngOnInit(): void {
this.breadcrumb$ = this.bcService.breadcrumbs$;
}
}

View File

@ -1,3 +1,17 @@
<div class="container mt-5"> <div class="container mt-5">
<h1>Internal Server Error</h1> <h4>Server Internal Error - Refreshing the page clears the exception</h4>
<ng-container *ngIf="e">
<h5 class="text-danger">{{e.message}}</h5>
<p class="font-weight-bold">Note: if you are seeing this, this is not a client side error.</p>
<p>What's next?</p>
<ol>
<li>Open Chrom Dev Tools</li>
<li>Inspect the Network Tab</li>
<li>Check the failing request</li>
<li>Examin the request URL. Is it correct?</li>
<li>Reproduce Error in Postman</li>
</ol>
<p>Following is the stack trace - This is where your investigation begins.</p>
<code class="mt-5" style="background-color: whitesmoke;">{{e.details}}</code>
</ng-container>
</div> </div>

View File

@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'app-server-error', selector: 'app-server-error',
@ -6,8 +7,12 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./server-error.component.scss'] styleUrls: ['./server-error.component.scss']
}) })
export class ServerErrorComponent implements OnInit { export class ServerErrorComponent implements OnInit {
e: any;
constructor() { } constructor(private router: Router) {
const navigation = this.router.getCurrentNavigation();
this.e = navigation?.extras?.state?.error;
}
ngOnInit(): void { ngOnInit(): void {
} }

View File

@ -3,4 +3,9 @@
<button (click)="get404Error()" class="btn btn-outline-primary me-3">Test 404 Error</button> <button (click)="get404Error()" class="btn btn-outline-primary me-3">Test 404 Error</button>
<button (click)="get400Error()" class="btn btn-outline-primary me-3">Test 400 Error</button> <button (click)="get400Error()" class="btn btn-outline-primary me-3">Test 400 Error</button>
<button (click)="get400ValidationError()" class="btn btn-outline-primary me-3">Test 400 Validation Error</button> <button (click)="get400ValidationError()" class="btn btn-outline-primary me-3">Test 400 Validation Error</button>
<div class="row mt-5" *ngIf="validationErrors">
<ul class="text-danger">
<li *ngFor="let error of validationErrors">{{error}}</li>
</ul>
</div>
</div> </div>

View File

@ -9,6 +9,7 @@ import { environment } from 'src/environments/environment';
}) })
export class TestErrorComponent implements OnInit { export class TestErrorComponent implements OnInit {
baseURL = environment.apiUrl; baseURL = environment.apiUrl;
validationErrors: any;
constructor(private http: HttpClient) { } constructor(private http: HttpClient) { }
@ -49,7 +50,9 @@ export class TestErrorComponent implements OnInit {
this.http.get(this.baseURL + 'products/five').subscribe( this.http.get(this.baseURL + 'products/five').subscribe(
{ {
next: (response) => { console.log(response); }, next: (response) => { console.log(response); },
error: (e: any) => { console.log(e); }, error: (e: any) => {
//console.log(e.errors);
this.validationErrors = e.errors; },
complete: () => { console.log('complete'); } complete: () => { console.log('complete'); }
} }
); );

View File

@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { IProduct } from 'src/app/shared/models/product'; import { IProduct } from 'src/app/shared/models/product';
import { BreadcrumbService } from 'xng-breadcrumb';
import { ShopService } from '../shop.service'; import { ShopService } from '../shop.service';
@Component({ @Component({
@ -11,7 +12,7 @@ import { ShopService } from '../shop.service';
export class ProductDetailsComponent implements OnInit { export class ProductDetailsComponent implements OnInit {
product: IProduct; product: IProduct;
constructor(private shopService: ShopService, private activatedRoute: ActivatedRoute) { } constructor(private shopService: ShopService, private activatedRoute: ActivatedRoute, private bcService: BreadcrumbService) { }
ngOnInit(): void { ngOnInit(): void {
this.loadProduct(); this.loadProduct();
@ -20,7 +21,10 @@ export class ProductDetailsComponent implements OnInit {
loadProduct(){ loadProduct(){
this.shopService.getProduct(+this.activatedRoute.snapshot.paramMap.get('id')).subscribe( this.shopService.getProduct(+this.activatedRoute.snapshot.paramMap.get('id')).subscribe(
{ {
next: (product) => { this.product = product; }, next: (product) => {
this.product = product;
this.bcService.set('@productDetails', product.name)
},
error: (e: any) => { console.log(e); }, error: (e: any) => { console.log(e); },
complete: () => { console.log('complete'); } complete: () => { console.log('complete'); }
} }

View File

@ -1,13 +1,13 @@
<div class="card h-100 shadow-sm"> <div class="card h-100 shadow-sm">
<img src="{{product.pictureUrl}}" alt="{{product.name}}" class="img-fluid bg-light"> <img src="{{product.pictureUrl}}" alt="{{product.name}}" class="img-fluid bg-light">
<div class="d-flex align-items-center justify-content-center hover-overlay">
<button type="button" class="btn btn-sm btn-primary fa fa-shopping-cart me-2"></button>
<button routerLink="/shop/{{product.id}}" type="button" class="btn btn-sm btn-primary">View</button>
</div>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<a routerLink="/shop/{{product.id}}"> <a routerLink="/shop/{{product.id}}">
<h6 class="text-uppercase">{{product.name}}</h6> <h6 class="text-uppercase">{{product.name}}</h6>
</a> </a>
<span class="mb-2">{{product.price | currency}}</span> <span class="mb-2">{{product.price | currency}}</span>
<div class="btn-group mt-auto">
<button type="button" class="btn btn-sm btn-outline-secondary fa fa-shopping-cart me-2"></button>
<button routerLink="/shop/{{product.id}}" type="button" class="btn btn-sm btn-outline-secondary">View</button>
</div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,4 @@
.btn {
width: 30%;
height: 40px;
}

View File

@ -1,12 +1,11 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { ShopComponent } from './shop.component'; import { ShopComponent } from './shop.component';
import { ProductDetailsComponent } from './product-details/product-details.component'; import { ProductDetailsComponent } from './product-details/product-details.component';
const routes: Routes = [ const routes: Routes = [
{path: '', component: ShopComponent}, {path: '', component: ShopComponent},
{path: ':id', component: ProductDetailsComponent}, {path: ':id', component: ProductDetailsComponent, data: {breadcrumb: {alias: 'productDetails'}}},
] ]
@NgModule({ @NgModule({

View File

@ -1,5 +1,5 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row mt-5">
<section class="col-3"> <section class="col-3">
<h5 class="text-warning ml-3">Sort</h5> <h5 class="text-warning ml-3">Sort</h5>
<select class="form-select mb-3" style="width: 100%;" (change)="onSortSelected($event.target.value)"> <select class="form-select mb-3" style="width: 100%;" (change)="onSortSelected($event.target.value)">

View File

@ -5,3 +5,18 @@
/* Importing Datepicker SCSS file. */ /* Importing Datepicker SCSS file. */
@import "node_modules/ngx-bootstrap/datepicker/bs-datepicker"; @import "node_modules/ngx-bootstrap/datepicker/bs-datepicker";
.xng-breadcrumb-root {
padding: 8px 16px;
display: inline;
border-radius: 0;
}
label.xng-breadcrumb-trail {
margin-bottom: 0;
color: orange;
}
xng-breadcrumb-separator {
padding: 0 0;
}