diff --git a/server.ts b/server.ts index 8ff45ac2cd..8ba8272976 100644 --- a/server.ts +++ b/server.ts @@ -170,6 +170,7 @@ export function app() { UserAgent: '*', Disallow: [ '/error', + '/maintenance', '/account', '/compare', '/recently', diff --git a/src/app/core/guards/error-status.guard.ts b/src/app/core/guards/error-status.guard.ts new file mode 100644 index 0000000000..ccbecfef33 --- /dev/null +++ b/src/app/core/guards/error-status.guard.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, Router } from '@angular/router'; +import { map } from 'rxjs'; + +import { AppFacade } from 'ish-core/facades/app.facade'; + +@Injectable({ providedIn: 'root' }) +export class ErrorStatusGuard implements CanActivate { + constructor(private appFacade: AppFacade, private router: Router) {} + canActivate() { + return this.appFacade.generalError$.pipe(map(error => (!error ? this.router.parseUrl('/home') : true))); + } +} diff --git a/src/app/core/utils/http-status-code/http-status-code.service.spec.ts b/src/app/core/utils/http-status-code/http-status-code.service.spec.ts index aec50657f9..9d9cfec811 100644 --- a/src/app/core/utils/http-status-code/http-status-code.service.spec.ts +++ b/src/app/core/utils/http-status-code/http-status-code.service.spec.ts @@ -25,7 +25,12 @@ describe('Http Status Code Service', () => { describe('on browser', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([{ path: 'error', children: [] }])], + imports: [ + RouterTestingModule.withRoutes([ + { path: 'error', children: [] }, + { path: 'maintenance', children: [] }, + ]), + ], }); httpStatusCodeService = TestBed.inject(HttpStatusCodeService); location = TestBed.inject(Location); @@ -56,13 +61,25 @@ describe('Http Status Code Service', () => { verify(resSpy.status(anyNumber())).never(); expect(location.path()).toEqual('/error'); })); + + it('should redirect to maintenance page for server 503 errors (Unavailable)', fakeAsync(() => { + httpStatusCodeService.setStatus(503); + tick(500); + verify(resSpy.status(anyNumber())).never(); + expect(location.path()).toEqual('/maintenance'); + })); }); }); describe.onSSREnvironment('on server', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([{ path: 'error', children: [] }])], + imports: [ + RouterTestingModule.withRoutes([ + { path: 'error', children: [] }, + { path: 'maintenance', children: [] }, + ]), + ], providers: [{ provide: RESPONSE, useValue: RES }], }); httpStatusCodeService = TestBed.inject(HttpStatusCodeService); @@ -94,6 +111,13 @@ describe('Http Status Code Service', () => { verify(resSpy.status(500)).once(); expect(location.path()).toEqual('/error'); })); + + it('should redirect to maintenance page for server 503 errors (Unavailable)', fakeAsync(() => { + httpStatusCodeService.setStatus(503); + tick(500); + verify(resSpy.status(503)).once(); + expect(location.path()).toEqual('/maintenance'); + })); }); }); }); diff --git a/src/app/core/utils/http-status-code/http-status-code.service.ts b/src/app/core/utils/http-status-code/http-status-code.service.ts index 309046d07a..c2d884fe6b 100644 --- a/src/app/core/utils/http-status-code/http-status-code.service.ts +++ b/src/app/core/utils/http-status-code/http-status-code.service.ts @@ -19,10 +19,13 @@ export class HttpStatusCodeService { this.response.status(status); } if (redirect && status >= 400) { + // 503: server is unavailable + const route = status === 503 ? '/maintenance' : '/error'; + if (SSR) { - return this.router.navigateByUrl('/error'); + return this.router.navigateByUrl(route); } else { - return this.router.navigateByUrl('/error', { skipLocationChange: status < 500 }); + return this.router.navigateByUrl(route, { skipLocationChange: status < 500 }); } } return Promise.resolve(true); diff --git a/src/app/pages/app-routing.module.ts b/src/app/pages/app-routing.module.ts index 9dab7036ea..d13e6a687c 100644 --- a/src/app/pages/app-routing.module.ts +++ b/src/app/pages/app-routing.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AuthGuard } from 'ish-core/guards/auth.guard'; +import { ErrorStatusGuard } from 'ish-core/guards/error-status.guard'; import { IdentityProviderInviteGuard } from 'ish-core/guards/identity-provider-invite.guard'; import { IdentityProviderLoginGuard } from 'ish-core/guards/identity-provider-login.guard'; import { IdentityProviderLogoutGuard } from 'ish-core/guards/identity-provider-logout.guard'; @@ -30,6 +31,17 @@ const routes: Routes = [ }, }, }, + { + path: 'maintenance', + loadChildren: () => import('./maintenance/maintenance-page.module').then(m => m.MaintenancePageModule), + canActivate: [ErrorStatusGuard], + data: { + meta: { + title: 'seo.title.maintenance', + robots: 'noindex, nofollow', + }, + }, + }, { path: 'account', loadChildren: () => import('./account/account-page.module').then(m => m.AccountPageModule), diff --git a/src/app/pages/maintenance/maintenance-page.component.html b/src/app/pages/maintenance/maintenance-page.component.html new file mode 100644 index 0000000000..5c2c05e753 --- /dev/null +++ b/src/app/pages/maintenance/maintenance-page.component.html @@ -0,0 +1,21 @@ +
+

{{ 'server.maintenance.page.title' | translate }}

+
+
+
+
+
{{ 'server.maintenance.page.contact.label' | translate }}
+
+ + {{ + 'server.maintenance.page.contact.phone' | translate + }} +
+
+ + {{ + 'server.maintenance.page.contact.email' | translate + }} +
+
+
diff --git a/src/app/pages/maintenance/maintenance-page.component.spec.ts b/src/app/pages/maintenance/maintenance-page.component.spec.ts new file mode 100644 index 0000000000..05e462b175 --- /dev/null +++ b/src/app/pages/maintenance/maintenance-page.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { TranslateModule } from '@ngx-translate/core'; +import { MockComponent, MockDirective } from 'ng-mocks'; + +import { ServerHtmlDirective } from 'ish-core/directives/server-html.directive'; + +import { MaintenancePageComponent } from './maintenance-page.component'; + +describe('Maintenance Page Component', () => { + let component: MaintenancePageComponent; + let fixture: ComponentFixture; + let element: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [MaintenancePageComponent, MockComponent(FaIconComponent), MockDirective(ServerHtmlDirective)], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MaintenancePageComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + expect(element).toBeTruthy(); + expect(() => fixture.detectChanges()).not.toThrow(); + }); +}); diff --git a/src/app/pages/maintenance/maintenance-page.component.ts b/src/app/pages/maintenance/maintenance-page.component.ts new file mode 100644 index 0000000000..86c89b1de2 --- /dev/null +++ b/src/app/pages/maintenance/maintenance-page.component.ts @@ -0,0 +1,8 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + selector: 'ish-maintenance-page', + templateUrl: './maintenance-page.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MaintenancePageComponent {} diff --git a/src/app/pages/maintenance/maintenance-page.module.ts b/src/app/pages/maintenance/maintenance-page.module.ts new file mode 100644 index 0000000000..ff5fef7f41 --- /dev/null +++ b/src/app/pages/maintenance/maintenance-page.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { SharedModule } from 'ish-shared/shared.module'; + +import { MaintenancePageComponent } from './maintenance-page.component'; + +const maintenancePageRoutes: Routes = [ + { path: '', component: MaintenancePageComponent, data: { wrapperClass: 'errorpage', headerType: 'error' } }, +]; + +@NgModule({ + imports: [RouterModule.forChild(maintenancePageRoutes), SharedModule], + declarations: [MaintenancePageComponent], +}) +export class MaintenancePageModule {} diff --git a/src/assets/i18n/de_DE.json b/src/assets/i18n/de_DE.json index cbdae6338d..98ee895788 100644 --- a/src/assets/i18n/de_DE.json +++ b/src/assets/i18n/de_DE.json @@ -1043,7 +1043,13 @@ "seo.title.checkout": "Kasse", "seo.title.error": "Fehler", "seo.title.home": "inTRONICS Startseite", + "seo.title.maintenance": "Wartung", "seo.title.search": "Suchergebnis für '{{0}}'", + "server.maintenance.page.contact.email": "info@test.intershop.de", + "server.maintenance.page.contact.label": "Sie können uns kontaktieren:", + "server.maintenance.page.contact.phone": "1-800-xxx-xxxx", + "server.maintenance.page.text": "

Es tut uns leid ...

Unsere Webseite ist auf Grund von Wartungsarbeiten kurzzeitig nicht verfügbar. Bitte kommen Sie später wieder.

", + "server.maintenance.page.title": "Wir sind bald zurück", "servererror.mailServer.error": "Ihre E-Mail konnte leider nicht versandt werden. Wir bitten um Entschuldigung. Bitte versuchen Sie es später erneut.", "servererror.page.text": "

Es tut uns leid ...

Gehen Sie zurück zur Startseite und versuchen Sie es erneut.

", "servererror.page.title": "Etwas ist schiefgegangen.", diff --git a/src/assets/i18n/en_US.json b/src/assets/i18n/en_US.json index 9340615ba9..4430eddc4f 100644 --- a/src/assets/i18n/en_US.json +++ b/src/assets/i18n/en_US.json @@ -1043,7 +1043,13 @@ "seo.title.checkout": "Checkout", "seo.title.error": "Error", "seo.title.home": "inTRONICS Home", + "seo.title.maintenance": "Maintenance", "seo.title.search": "Search Result for '{{0}}'", + "server.maintenance.page.contact.email": "info@test.intershop.de", + "server.maintenance.page.contact.label": "You can contact us:", + "server.maintenance.page.contact.phone": "1-800-xxx-xxxx", + "server.maintenance.page.text": "

We are sorry ...

Our website is briefly unavailable for a scheduled maintenance. Please come back again later.

", + "server.maintenance.page.title": "We will be back soon", "servererror.mailServer.error": "Your mail couldn't be sent. We apologize for the inconvenience. Please try again later.", "servererror.page.text": "

We are sorry ...

Please go back to the Home Page and try again.

", "servererror.page.title": "Something Went Wrong", diff --git a/src/assets/i18n/fr_FR.json b/src/assets/i18n/fr_FR.json index efa2efa091..7d6032a238 100644 --- a/src/assets/i18n/fr_FR.json +++ b/src/assets/i18n/fr_FR.json @@ -1043,7 +1043,13 @@ "seo.title.checkout": "Passer la commande", "seo.title.error": "Erreur", "seo.title.home": "Page d’accueil InTRONICS", + "seo.title.maintenance": "Maintenance", "seo.title.search": "Résultat de recherche pour '{{0}}'.", + "server.maintenance.page.contact.email": "info@test.intershop.de", + "server.maintenance.page.contact.label": "Vous pouvez nou contacter:", + "server.maintenance.page.contact.phone": "1-800-xxx-xxxx", + "server.maintenance.page.text": "

Nous sommes désolés ...

", + "server.maintenance.page.title": "Arrêt de maintenance", "servererror.mailServer.error": "Votre courriel n’a pas pu être envoyé. Nous nous excusons pour les inconvénients. Veuillez réessayer plus tard.", "servererror.page.text": "

Nous sommes désolés ...

Retournez à la Page d’accueil, puis réessayez.

", "servererror.page.title": "Quelque chose s’est mal passé",