Skip to content

Commit

Permalink
feat: display loading animation while restoring user on first navigat…
Browse files Browse the repository at this point in the history
…ion (#211)
  • Loading branch information
dhhyi authored Apr 29, 2020
1 parent cf35e52 commit 337d9d3
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 37 deletions.
2 changes: 1 addition & 1 deletion e2e/test-universal.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ universalTest() {

universalTest 1 "${PWA_BASE_URL}/" "router-outlet><ish-home-page"
universalTest 2 "${PWA_BASE_URL}/catComputers.1835.151" "router-outlet><ish-category-page"
universalTest 3 "${PWA_BASE_URL}/login" "Forgot your password?"
universalTest 3 "${PWA_BASE_URL}/login" "<ish-loading"
universalTest 4 "${PWA_BASE_URL}/register" "Create Account"
universalTest 5 "${PWA_BASE_URL}/catComputers.1835" "<h1>Notebooks and PCs</h1>"
universalTest 6 "${PWA_BASE_URL}/catComputers.1835" "<h3>PCs</h3>"
Expand Down
28 changes: 25 additions & 3 deletions src/app/core/facades/app.facade.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Injectable } from '@angular/core';
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
import { Store, select } from '@ngrx/store';
import { combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { combineLatest, merge } from 'rxjs';
import { filter, map, mapTo, shareReplay, startWith } from 'rxjs/operators';

import { getAvailableLocales, getCurrentLocale, getDeviceType } from 'ish-core/store/configuration';
import { LoadCountries, getAllCountries, getCountriesLoading } from 'ish-core/store/countries';
Expand All @@ -11,7 +12,10 @@ import { getBreadcrumbData, getHeaderType, getWrapperClass, isStickyHeader } fro

@Injectable({ providedIn: 'root' })
export class AppFacade {
constructor(private store: Store<{}>) {}
constructor(private store: Store<{}>, private router: Router) {
// tslint:disable-next-line: rxjs-no-ignored-subscribe
this.routingInProgress$.subscribe();
}

headerType$ = this.store.pipe(select(getHeaderType));
deviceType$ = this.store.pipe(select(getDeviceType));
Expand Down Expand Up @@ -39,6 +43,24 @@ export class AppFacade {
// COUNTRIES AND REGIONS
countriesLoading$ = this.store.pipe(select(getCountriesLoading));

routingInProgress$ = merge(
this.router.events.pipe(
filter(event => event instanceof NavigationStart),
mapTo(true)
),
this.router.events.pipe(
filter(event => event instanceof NavigationEnd),
mapTo(false)
),
this.router.events.pipe(
filter(event => event instanceof NavigationCancel),
mapTo(false)
)
).pipe(
startWith(true),
shareReplay(1)
);

countries$() {
this.store.dispatch(new LoadCountries());
return this.store.pipe(select(getAllCountries));
Expand Down
3 changes: 3 additions & 0 deletions src/app/core/guards/auth.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { TestBed, async } from '@angular/core/testing';
import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { Store } from '@ngrx/store';
import { instance, mock } from 'ts-mockito';

import { Customer } from 'ish-core/models/customer/customer.model';
import { CookiesService } from 'ish-core/services/cookies/cookies.service';
import { coreReducers } from 'ish-core/store/core-store.module';
import { LoginUserSuccess } from 'ish-core/store/user';
import { ngrxTesting } from 'ish-core/utils/dev/ngrx-testing';
Expand All @@ -26,6 +28,7 @@ describe('Auth Guard', () => {
ngrxTesting({ reducers: coreReducers }),
],
declarations: [DummyComponent],
providers: [{ provide: CookiesService, useFactory: () => instance(mock(CookiesService)) }],
}).compileComponents();
}));

Expand Down
13 changes: 10 additions & 3 deletions src/app/core/guards/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Store, select } from '@ngrx/store';
import { Observable, iif, of, race, timer } from 'rxjs';
import { mapTo, take } from 'rxjs/operators';

import { CookiesService } from 'ish-core/services/cookies/cookies.service';
import { getUserAuthorized } from 'ish-core/store/user';
import { whenTruthy } from 'ish-core/utils/operators';

Expand All @@ -21,7 +22,12 @@ import { whenTruthy } from 'ish-core/utils/operators';
*/
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate, CanActivateChild {
constructor(private store: Store<{}>, private router: Router, @Inject(PLATFORM_ID) private platformId: string) {}
constructor(
private store: Store<{}>,
private router: Router,
@Inject(PLATFORM_ID) private platformId: string,
private cookieService: CookiesService
) {}

canActivate(snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.guardAccess({ ...snapshot.queryParams, returnUrl: state.url });
Expand All @@ -45,8 +51,9 @@ export class AuthGuard implements CanActivate, CanActivateChild {
whenTruthy(),
take(1)
),
// send to login after timeout (on first routing only)
timer(this.router.navigated ? 0 : 4000).pipe(mapTo(defaultRedirect))
// send to login after timeout
// send right away if no user can be re-hydrated
timer(!this.router.navigated && this.cookieService.get('apiToken') ? 4000 : 0).pipe(mapTo(defaultRedirect))
)
);
}
Expand Down
56 changes: 31 additions & 25 deletions src/app/pages/login/login-page.component.html
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
<h1>&nbsp;</h1>
<ng-container *ngIf="routingInProgress$ | async; else ready">
<ish-loading></ish-loading>
</ng-container>

<div class="row" data-testing-id="account-login-page">
<div class="col-lg-6">
<div class="section">
<div *ngIf="!(isLoggedIn$ | async)">
<h2>{{ 'account.login.signin.heading' | translate }}</h2>
<p *ngIf="loginMessageKey$ | async as loginMessageKey" class="alert alert-info">
{{ loginMessageKey | translate }}
</p>
<ng-template #ready>
<h1>&nbsp;</h1>

<ish-login-form></ish-login-form>
<div class="row" data-testing-id="account-login-page">
<div class="col-lg-6">
<div class="section">
<div *ngIf="!(isLoggedIn$ | async)">
<h2>{{ 'account.login.signin.heading' | translate }}</h2>
<p *ngIf="loginMessageKey$ | async as loginMessageKey" class="alert alert-info">
{{ loginMessageKey | translate }}
</p>

<ish-login-form></ish-login-form>
</div>
</div>
</div>
</div>

<div class="col-lg-6">
<h2>{{ 'account.new_user.heading' | translate }}</h2>
<p>{{ 'account.create.benefit.text' | translate }}</p>
<ul>
<li>{{ 'account.create.benefit1.text' | translate }}</li>
<li>{{ 'account.create.benefit2.text' | translate }}</li>
<li>{{ 'account.create.benefit3.text' | translate }}</li>
<li>{{ 'account.create.benefit4.text' | translate }}</li>
<li>{{ 'account.create.benefit5.text' | translate }}</li>
</ul>
<div class="col-lg-6">
<h2>{{ 'account.new_user.heading' | translate }}</h2>
<p>{{ 'account.create.benefit.text' | translate }}</p>
<ul>
<li>{{ 'account.create.benefit1.text' | translate }}</li>
<li>{{ 'account.create.benefit2.text' | translate }}</li>
<li>{{ 'account.create.benefit3.text' | translate }}</li>
<li>{{ 'account.create.benefit4.text' | translate }}</li>
<li>{{ 'account.create.benefit5.text' | translate }}</li>
</ul>

<a class="btn btn-primary" routerLink="/register" [queryParams]="{ returnUrl: '/account' }"
>{{ 'account.create.button.label' | translate }}
</a>
<a class="btn btn-primary" routerLink="/register" [queryParams]="{ returnUrl: '/account' }"
>{{ 'account.create.button.label' | translate }}
</a>
</div>
</div>
</div>
</ng-template>
9 changes: 7 additions & 2 deletions src/app/pages/login/login-page.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { MockComponent } from 'ng-mocks';
import { instance, mock } from 'ts-mockito';

import { AccountFacade } from 'ish-core/facades/account.facade';
import { AppFacade } from 'ish-core/facades/app.facade';
import { LoadingComponent } from 'ish-shared/components/common/loading/loading.component';
import { LoginFormComponent } from 'ish-shared/forms/components/login-form/login-form.component';

import { LoginPageComponent } from './login-page.component';
Expand All @@ -17,8 +19,11 @@ describe('Login Page Component', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule, TranslateModule.forRoot()],
declarations: [LoginPageComponent, MockComponent(LoginFormComponent)],
providers: [{ provide: AccountFacade, useFactory: () => instance(mock(AccountFacade)) }],
declarations: [LoginPageComponent, MockComponent(LoadingComponent), MockComponent(LoginFormComponent)],
providers: [
{ provide: AccountFacade, useFactory: () => instance(mock(AccountFacade)) },
{ provide: AppFacade, useFactory: () => instance(mock(AppFacade)) },
],
}).compileComponents();
}));

Expand Down
20 changes: 17 additions & 3 deletions src/app/pages/login/login-page.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { isPlatformServer } from '@angular/common';
import { ChangeDetectionStrategy, Component, Inject, OnInit, PLATFORM_ID } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';

import { AccountFacade } from 'ish-core/facades/account.facade';
import { AppFacade } from 'ish-core/facades/app.facade';

/**
* The Login Page Container displays the login page component {@link LoginPageComponent} as wrapper for the login form
Expand All @@ -15,11 +17,23 @@ import { AccountFacade } from 'ish-core/facades/account.facade';
export class LoginPageComponent implements OnInit {
isLoggedIn$: Observable<boolean>;
loginMessageKey$: Observable<string>;
routingInProgress$: Observable<boolean>;

constructor(private accountFacade: AccountFacade, private route: ActivatedRoute) {}
constructor(
private accountFacade: AccountFacade,
private route: ActivatedRoute,
private appFacade: AppFacade,
@Inject(PLATFORM_ID) private platformId: string
) {}

ngOnInit() {
this.isLoggedIn$ = this.accountFacade.isLoggedIn$;
if (isPlatformServer(this.platformId)) {
// SSR response should always display loading animation
this.routingInProgress$ = of(true);
} else {
this.routingInProgress$ = this.appFacade.routingInProgress$;
}

this.loginMessageKey$ = this.route.queryParamMap.pipe(
map(params => params.get('messageKey')),
Expand Down

0 comments on commit 337d9d3

Please sign in to comment.