add server error interceptor and update full page error
This commit is contained in:
parent
6c0f123bd9
commit
90287baf62
1
src/assets/icons/offline.svg
Normal file
1
src/assets/icons/offline.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M21 11l2-2c-3.73-3.73-8.87-5.15-13.7-4.31l2.58 2.58c3.3-.02 6.61 1.22 9.12 3.73zm-2 2c-1.08-1.08-2.36-1.85-3.72-2.33l3.02 3.02.7-.69zM9 17l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zM3.41 1.64L2 3.05 5.05 6.1C3.59 6.83 2.22 7.79 1 9l2 2c1.23-1.23 2.65-2.16 4.17-2.78l2.24 2.24C7.79 10.89 6.27 11.74 5 13l2 2c1.35-1.35 3.11-2.04 4.89-2.06l7.08 7.08 1.41-1.41L3.41 1.64z"/></svg>
|
||||
|
After Width: | Height: | Size: 517 B |
@ -1,17 +1,14 @@
|
||||
@import 'variables';
|
||||
@use 'variables';
|
||||
|
||||
.full-page-section,
|
||||
.full-page-content {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.full-page-section {
|
||||
opacity: 0.7;
|
||||
background: $white;
|
||||
background: variables.$white;
|
||||
z-index: 900;
|
||||
}
|
||||
|
||||
|
||||
@ -1,15 +1,36 @@
|
||||
<ng-container *ngIf="errorService.error$ | async as error">
|
||||
<section class="full-page-section"></section>
|
||||
<section class="full-page-content">
|
||||
<mat-icon svgIcon="iqser:failure"></mat-icon>
|
||||
<div class="heading-l mt-24" translate="error.title"></div>
|
||||
<div class="mt-16 error">{{ error.message }}</div>
|
||||
<iqser-icon-button
|
||||
(action)="reload()"
|
||||
[label]="'error.reload' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
class="mt-20"
|
||||
icon="iqser:refresh"
|
||||
></iqser-icon-button>
|
||||
</section>
|
||||
<ng-container *ngIf="error.status === 0; else serverError">
|
||||
<div class="offline-box flex-align-items-center" [@animateOpenClose]="'open'">
|
||||
<mat-icon svgIcon="iqser:offline"></mat-icon>
|
||||
|
||||
<div class="heading-l ml-14" [translate]="'error.offline'"></div>
|
||||
|
||||
<iqser-circle-button
|
||||
(action)="errorService.set(undefined)"
|
||||
[tooltip]="'error.close' | translate"
|
||||
icon="iqser:close"
|
||||
tooltipPosition="below"
|
||||
></iqser-circle-button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #serverError>
|
||||
<section class="full-page-section"></section>
|
||||
|
||||
<section class="full-page-content flex-align-items-center">
|
||||
<mat-icon svgIcon="iqser:failure"></mat-icon>
|
||||
|
||||
<div class="heading-l mt-24" [translate]="'error.title'"></div>
|
||||
|
||||
<div class="mt-16 error">{{ error.message }}</div>
|
||||
|
||||
<iqser-icon-button
|
||||
(action)="reload()"
|
||||
[label]="'error.reload' | translate"
|
||||
[type]="iconButtonTypes.primary"
|
||||
class="mt-20"
|
||||
icon="iqser:refresh"
|
||||
></iqser-icon-button>
|
||||
</section>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
@ -1,13 +1,32 @@
|
||||
@import '../../../assets/styles/variables';
|
||||
@use '../../../assets/styles/variables';
|
||||
|
||||
.offline-box {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
height: 40px;
|
||||
width: 300px;
|
||||
background: variables.$white;
|
||||
border: 1px solid variables.$separator;
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
|
||||
> mat-icon {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
> iqser-circle-button {
|
||||
flex-grow: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.full-page-section {
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.full-page-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
> mat-icon {
|
||||
@ -16,12 +35,12 @@
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.heading-l {
|
||||
color: $grey-7;
|
||||
font-weight: initial;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: $red-1;
|
||||
color: variables.$primary;
|
||||
}
|
||||
}
|
||||
|
||||
:is(.offline-box, .full-page-content) .heading-l {
|
||||
color: #9398a0;
|
||||
font-weight: initial;
|
||||
}
|
||||
|
||||
@ -1,11 +1,19 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { IconButtonTypes } from '../../buttons';
|
||||
import { ErrorService } from '../error.service';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
selector: 'iqser-full-page-error',
|
||||
templateUrl: './full-page-error.component.html',
|
||||
styleUrls: ['./full-page-error.component.scss'],
|
||||
animations: [
|
||||
trigger('animateOpenClose', [
|
||||
state('open', style({ bottom: '20px' })),
|
||||
state('void', style({ bottom: '-70px' })),
|
||||
transition('* => open, open => void', animate('1s ease-in-out'))
|
||||
])
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FullPageErrorComponent {
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
export * from './error.service';
|
||||
export * from './max-retries.token';
|
||||
export * from './server-error-interceptor';
|
||||
export * from './full-page-error/full-page-error.component';
|
||||
|
||||
3
src/lib/error/max-retries.token.ts
Normal file
3
src/lib/error/max-retries.token.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
export const MAX_RETRIES_ON_SERVER_ERROR = new InjectionToken<number>('Number of retries before giving up');
|
||||
52
src/lib/error/server-error-interceptor.ts
Normal file
52
src/lib/error/server-error-interceptor.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
|
||||
import { Inject, Injectable, Optional } from '@angular/core';
|
||||
import { ErrorService } from '@iqser/common-ui';
|
||||
import { MonoTypeOperatorFunction, Observable, throwError, timer } from 'rxjs';
|
||||
import { catchError, mergeMap, retryWhen, tap } from 'rxjs/operators';
|
||||
import { MAX_RETRIES_ON_SERVER_ERROR } from './max-retries.token';
|
||||
|
||||
function updateSeconds(seconds: number) {
|
||||
if (seconds === 0 || seconds === 1) {
|
||||
return seconds + 1;
|
||||
} else {
|
||||
return seconds * seconds;
|
||||
}
|
||||
}
|
||||
|
||||
function backoffOnServerError(maxRetries = 3): MonoTypeOperatorFunction<HttpEvent<unknown>> {
|
||||
let seconds = 0;
|
||||
return retryWhen(attempts =>
|
||||
attempts.pipe(
|
||||
tap(() => (seconds = updateSeconds(seconds))),
|
||||
mergeMap((error: HttpErrorResponse, index) => {
|
||||
if ((error.status <= 500 && error.status !== 0) || index === maxRetries) {
|
||||
return throwError(error);
|
||||
} else {
|
||||
console.error('An error occurred: ', error);
|
||||
console.error(`Retrying in ${seconds} seconds...`);
|
||||
return timer(seconds * 1000);
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ServerErrorInterceptor implements HttpInterceptor {
|
||||
constructor(
|
||||
private readonly _errorService: ErrorService,
|
||||
@Optional() @Inject(MAX_RETRIES_ON_SERVER_ERROR) private readonly _maxRetries: number
|
||||
) {}
|
||||
|
||||
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
return next.handle(req).pipe(
|
||||
catchError((error: HttpErrorResponse) => {
|
||||
if (error.status >= 500 || error.status === 0) {
|
||||
this._errorService.set(error);
|
||||
}
|
||||
return throwError(error);
|
||||
}),
|
||||
backoffOnServerError(this._maxRetries || 3)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@ export class IqserIconsModule {
|
||||
'edit',
|
||||
'failure',
|
||||
'help-outline',
|
||||
'offline',
|
||||
'refresh',
|
||||
'search',
|
||||
'sort-asc',
|
||||
|
||||
@ -8,7 +8,7 @@ export class SyncWidthDirective implements OnDestroy {
|
||||
private readonly _interval: number;
|
||||
|
||||
constructor(private readonly _elementRef: ElementRef) {
|
||||
this._interval = setInterval(() => {
|
||||
this._interval = window.setInterval(() => {
|
||||
this._matchWidth();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ export function Debounce(delay = 300): MethodDecorator {
|
||||
|
||||
descriptorCopy.value = function _new(...args: unknown[]) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => (descriptor.value as (...params: unknown[]) => unknown).apply(this, args), delay);
|
||||
timeout = window.setTimeout(() => (descriptor.value as (...params: unknown[]) => unknown).apply(this, args), delay);
|
||||
};
|
||||
|
||||
return descriptorCopy;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user