From 16fa3a0250188bb4cac0dc8273136f163183df37 Mon Sep 17 00:00:00 2001
From: doregg <dor.moshe@frontegg.com>
Date: Sun, 10 Mar 2024 13:35:51 +0200
Subject: [PATCH] Example app makeover

---
 .../example/src/app/app-routing.module.ts     | 26 ++++--
 projects/example/src/app/app.component.html   | 38 +++++++-
 projects/example/src/app/app.component.scss   | 22 +++++
 projects/example/src/app/app.component.ts     | 47 +++++++++-
 projects/example/src/app/app.module.ts        | 31 ++++++-
 .../authorized-content-page.component.html    | 24 +++++
 .../authorized-content-page.component.scss    |  5 +
 .../authorized-content-page.component.ts      | 25 +++++
 .../entitlements-page.component.html          | 24 +++++
 .../entitlements-page.component.scss          | 49 ++++++++++
 .../entitlements-page.component.ts            | 92 +++++++++++++++++++
 .../home-page/home-page.component.html        |  7 ++
 .../home-page/home-page.component.ts          | 24 +++++
 .../src/app/components/home.component.html    | 20 ----
 .../src/app/components/home.component.ts      | 44 ---------
 .../src/app/components/not-found.component.ts |  2 +-
 .../base-step-up/base-step-up.component.html  | 14 +++
 .../base-step-up/base-step-up.component.scss  |  8 ++
 .../base-step-up/base-step-up.component.ts    | 47 ++++++++++
 .../step-up-full/step-up-full.component.html  | 15 +++
 .../step-up-full/step-up-full.component.scss  |  3 +
 .../step-up-full/step-up-full.component.ts    | 46 ++++++++++
 .../step-up-high-max-age-page.component.ts    | 11 +++
 .../step-up-no-max-age-page.component.ts      |  9 ++
 .../step-up-small-max-age-page.component.ts   | 11 +++
 projects/example/src/app/links.ts             | 13 +++
 projects/example/src/styles.scss              | 22 +++++
 27 files changed, 598 insertions(+), 81 deletions(-)
 create mode 100644 projects/example/src/app/app.component.scss
 create mode 100644 projects/example/src/app/components/authorized-content/authorized-content-page.component.html
 create mode 100644 projects/example/src/app/components/authorized-content/authorized-content-page.component.scss
 create mode 100644 projects/example/src/app/components/authorized-content/authorized-content-page.component.ts
 create mode 100644 projects/example/src/app/components/entitlements-page/entitlements-page.component.html
 create mode 100644 projects/example/src/app/components/entitlements-page/entitlements-page.component.scss
 create mode 100644 projects/example/src/app/components/entitlements-page/entitlements-page.component.ts
 create mode 100644 projects/example/src/app/components/home-page/home-page.component.html
 create mode 100644 projects/example/src/app/components/home-page/home-page.component.ts
 delete mode 100644 projects/example/src/app/components/home.component.html
 delete mode 100644 projects/example/src/app/components/home.component.ts
 create mode 100644 projects/example/src/app/components/step-up/base-step-up/base-step-up.component.html
 create mode 100644 projects/example/src/app/components/step-up/base-step-up/base-step-up.component.scss
 create mode 100644 projects/example/src/app/components/step-up/base-step-up/base-step-up.component.ts
 create mode 100644 projects/example/src/app/components/step-up/step-up-full/step-up-full.component.html
 create mode 100644 projects/example/src/app/components/step-up/step-up-full/step-up-full.component.scss
 create mode 100644 projects/example/src/app/components/step-up/step-up-full/step-up-full.component.ts
 create mode 100644 projects/example/src/app/components/step-up/step-up-high-max-age-page.component.ts
 create mode 100644 projects/example/src/app/components/step-up/step-up-no-max-age-page.component.ts
 create mode 100644 projects/example/src/app/components/step-up/step-up-small-max-age-page.component.ts
 create mode 100644 projects/example/src/app/links.ts

diff --git a/projects/example/src/app/app-routing.module.ts b/projects/example/src/app/app-routing.module.ts
index bb3bd5b9..c3ddf216 100644
--- a/projects/example/src/app/app-routing.module.ts
+++ b/projects/example/src/app/app-routing.module.ts
@@ -1,16 +1,30 @@
 import { NgModule } from '@angular/core';
 import { Routes, RouterModule } from '@angular/router';
+import { FronteggAuthGuard } from '@frontegg/angular';
 import { PrivateRouteComponent } from './components/private-route.component';
 import { NotFoundComponent } from './components/not-found.component';
-import { AppHomeComponent } from './components/home.component';
-import { FronteggAuthGuard } from '@frontegg/angular';
+import { HomePage } from './components/home-page/home-page.component';
+import { StepUpHighMaxAgePage } from './components/step-up/step-up-high-max-age-page.component';
+import { EntitlementsPage } from './components/entitlements-page/entitlements-page.component';
+
+import { StepUpSmallMaxAgePage } from './components/step-up/step-up-small-max-age-page.component';
+import { StepUpNoMaxAgePage } from './components/step-up/step-up-no-max-age-page.component';
+import { StepUpFull } from './components/step-up/step-up-full/step-up-full.component';
+import { AuthorizedContentPage } from './components/authorized-content/authorized-content-page.component';
+import { ROUTE_PATHS } from './links';
 
 const protectSingleRoutes: Routes = [
-  { path: '', component: AppHomeComponent },
-  { path: 'test-private-route', component: PrivateRouteComponent, canActivate: [FronteggAuthGuard] },
-  { path: 'test', pathMatch: 'full', redirectTo: 'nested-router' },
+  { path: ROUTE_PATHS.HOME_PAGE, component: HomePage },
+  { path: ROUTE_PATHS.ENTITLEMENTS, component: EntitlementsPage },
+  { path: ROUTE_PATHS.STEP_UP_HIGH_MAX_AGE, component: StepUpHighMaxAgePage },
+  { path: ROUTE_PATHS.STEP_UP_SMALL_MAX_AGE, component: StepUpSmallMaxAgePage },
+  { path: ROUTE_PATHS.STEP_UP_NO_MAX_AGE, component: StepUpNoMaxAgePage },
+  { path: ROUTE_PATHS.STEPPED_UP_FULL, component: StepUpFull },
+  { path: ROUTE_PATHS.AUTHORIZED_CONTENT, component: AuthorizedContentPage },
+  { path: ROUTE_PATHS.TEST_PRIVATE_ROUTE, component: PrivateRouteComponent, canActivate: [FronteggAuthGuard] },
+  { path: ROUTE_PATHS.TEST, pathMatch: 'full', redirectTo: ROUTE_PATHS.NESTED_ROUTER },
   {
-    path: 'nested-router',
+    path: ROUTE_PATHS.NESTED_ROUTER,
     loadChildren: () => import('./nested-module/nested-module.module').then(m => m.NestedModule),
   },
   { path: '**', component: NotFoundComponent },
diff --git a/projects/example/src/app/app.component.html b/projects/example/src/app/app.component.html
index dd16d673..a724c015 100644
--- a/projects/example/src/app/app.component.html
+++ b/projects/example/src/app/app.component.html
@@ -1,5 +1,37 @@
-<div *ngIf="!isLoading">
-    <router-outlet></router-outlet>
-    <div *authorizedContent="['Admin']">Authorized content section for Admin's (example)</div>
+<div *ngIf="!isLoading" class="centered-flex-container">
+  <h1>Frontegg App</h1>
+
+  <div class='buttons-list'>
+    <button *ngIf="!authenticated && fronteggAppService.fronteggApp.options.hostedLoginBox === true" data-test-id="open-hosted" (click)="loginWithRedirect()">Login</button>
+    <a *ngIf="!authenticated && fronteggAppService.fronteggApp.options.hostedLoginBox === false" data-test-id="open-embedded" routerLink="/account/login"><button>Login</button></a>
+
+    <button *ngIf="authenticated" data-test-id="open-admin-portal-btn" (click)="showApp()">Open Admin Portal</button>
+
+    <span *ngIf="authenticated" (click)="logout()"><button>Logout</button></span>
+  </div>
 
+  <div *ngIf="authenticated">
+    <p>Authenticated as {{user?.name}}</p>
+  </div>
+
+  <div>
+    {{ fronteggAppService.fronteggApp.options.hostedLoginBox ? 'Hosted' : 'Embedded' }}
+  </div>
+
+  <nav class="buttons-list">
+    Pages:
+    <a routerLink="{{ROUTE_PATHS.HOME_PAGE}}"><button>Home</button></a>
+    <a routerLink="/{{ROUTE_PATHS.ENTITLEMENTS}}"><button>Entitlements</button></a>
+    <a routerLink="/{{ROUTE_PATHS.STEP_UP_NO_MAX_AGE}}"><button>Step up - no max age</button></a>
+    <a routerLink="/{{ROUTE_PATHS.STEP_UP_HIGH_MAX_AGE}}"><button>Step up - max age 5000</button></a>
+    <a routerLink="/{{ROUTE_PATHS.STEP_UP_SMALL_MAX_AGE}}"><button>Step up - max age 35</button></a>
+    <a routerLink="/{{ROUTE_PATHS.STEPPED_UP_FULL}}"><button>Stepped up (full)</button></a>
+    <a routerLink="/{{ROUTE_PATHS.AUTHORIZED_CONTENT}}"><button>Authorized content</button></a>
+    <a routerLink="/{{ROUTE_PATHS.TEST_PRIVATE_ROUTE}}"><button>Private route</button></a>
+    <a routerLink="/{{ROUTE_PATHS.UNKNOWN_ROUTE}}"><button>Fallback route</button></a>
+  </nav>
+
+  <section class='router-content'>
+    <router-outlet></router-outlet>
+  </section>
 </div>
diff --git a/projects/example/src/app/app.component.scss b/projects/example/src/app/app.component.scss
new file mode 100644
index 00000000..6d820fb2
--- /dev/null
+++ b/projects/example/src/app/app.component.scss
@@ -0,0 +1,22 @@
+.router-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  min-width: 600px;
+  min-height: 400px;
+  margin: 25px 0;
+  padding: 10px;
+  border: 2px dotted rgb(21, 125, 243);
+}
+
+.buttons-list {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-top: 10px;
+}
+
+.buttons-list > * {
+  margin-left: 4px;
+}
\ No newline at end of file
diff --git a/projects/example/src/app/app.component.ts b/projects/example/src/app/app.component.ts
index d2504008..a6447db9 100644
--- a/projects/example/src/app/app.component.ts
+++ b/projects/example/src/app/app.component.ts
@@ -1,24 +1,63 @@
 import { Component, OnDestroy, OnInit } from '@angular/core';
-import { FronteggAppService } from '@frontegg/angular';
+import { Router } from '@angular/router';
+import { FronteggAppService, FronteggAuthService } from '@frontegg/angular';
 import { Subscription } from 'rxjs';
+import { ROUTE_PATHS } from './links';
 
 @Component({
   selector: 'app-root',
   templateUrl: './app.component.html',
+  styleUrls: ['./app.component.scss']
 })
 export class AppComponent implements OnInit, OnDestroy {
   isLoading = true;
   loadingSubscription: Subscription;
+  isAuthenticatedSubscription: Subscription;
+  userSubscription?: Subscription;
+  authenticated = false;
+  user?: any;
+  ROUTE_PATHS = ROUTE_PATHS;
 
-  constructor(private fronteggAppService: FronteggAppService) {
-    this.loadingSubscription = fronteggAppService.isLoading$.subscribe((isLoading) => this.isLoading = isLoading);
+  constructor(
+      public fronteggAppService: FronteggAppService,
+      private fronteggAuthService: FronteggAuthService,
+      private router: Router,
+  ) {
+    this.loadingSubscription = fronteggAppService.isLoading$.subscribe((isLoading) => {
+      this.isLoading = isLoading;
+    });
+
+    this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => {
+      this.authenticated = isAuthenticated;
+    });
+  }
+
+  showApp(): void {
+    this.fronteggAppService.showAdminPortal();
+  }
+
+  logout(): void {
+    if (this.fronteggAppService.fronteggApp.options.hostedLoginBox) {
+      this.fronteggAuthService.logout();
+      return;
+    }
+    
+    this.router.navigateByUrl(this.fronteggAppService.authRoutes.logoutUrl);
+  }
+
+  loginWithRedirect(): void {
+    this.fronteggAuthService.loginWithRedirect();
   }
 
   ngOnInit(): void {
-    console.log('AppComponent', 'ngOnInit');
+    this.userSubscription = this.fronteggAuthService.user$.subscribe((user: any) => {
+      this.user = user;
+    });
   }
 
   ngOnDestroy(): void {
     this.loadingSubscription.unsubscribe();
+    this.isAuthenticatedSubscription.unsubscribe();
+    this.userSubscription?.unsubscribe();
   }
 }
diff --git a/projects/example/src/app/app.module.ts b/projects/example/src/app/app.module.ts
index 0595d781..a200f053 100644
--- a/projects/example/src/app/app.module.ts
+++ b/projects/example/src/app/app.module.ts
@@ -6,13 +6,34 @@ import { CommonModule } from '@angular/common';
 import { NotFoundComponent } from './components/not-found.component';
 import { EmptyAppComponent } from './components/empty.component';
 import { PrivateRouteComponent } from './components/private-route.component';
-import { AppHomeComponent } from './components/home.component';
-import { FronteggComponent, FronteggAppModule } from '@frontegg/angular';
+import { HomePage } from './components/home-page/home-page.component';
+import { FronteggAppModule } from '@frontegg/angular';
 import { CheckoutDialogModule } from './checkout-dialog/checkout-dialog.module';
+import { StepUpHighMaxAgePage } from './components/step-up/step-up-high-max-age-page.component';
+import { EntitlementsPage } from './components/entitlements-page/entitlements-page.component';
+import { BaseStepUp } from './components/step-up/base-step-up/base-step-up.component';
+import { StepUpSmallMaxAgePage } from './components/step-up/step-up-small-max-age-page.component';
+import { StepUpNoMaxAgePage } from './components/step-up/step-up-no-max-age-page.component';
+import { StepUpFull } from './components/step-up/step-up-full/step-up-full.component';
+import { AuthorizedContentPage } from './components/authorized-content/authorized-content-page.component';
 
+const IS_HOSTED_MODE = false;
 
 @NgModule({
-  declarations: [ AppComponent, NotFoundComponent, AppHomeComponent, EmptyAppComponent, PrivateRouteComponent ],
+  declarations: [
+    AppComponent,
+    NotFoundComponent,
+    HomePage,
+    BaseStepUp,
+    StepUpHighMaxAgePage,
+    StepUpSmallMaxAgePage,
+    StepUpNoMaxAgePage,
+    StepUpFull,
+    EntitlementsPage,
+    EmptyAppComponent,
+    AuthorizedContentPage,
+    PrivateRouteComponent,
+  ],
   imports: [
     CommonModule,
     BrowserModule,
@@ -25,9 +46,13 @@ import { CheckoutDialogModule } from './checkout-dialog/checkout-dialog.module';
           baseUrl: 'https://demo.frontegg.com',
           clientId: 'b6adfe4c-d695-4c04-b95f-3ec9fd0c6cca',
         },
+        hostedLoginBox: IS_HOSTED_MODE, // don't remove it. Change it via the const value above
         authOptions: {
           keepSessionAlive: true,
         },
+        entitlementsOptions: {
+          enabled: true
+        }
       },
     ),
     CheckoutDialogModule,
diff --git a/projects/example/src/app/components/authorized-content/authorized-content-page.component.html b/projects/example/src/app/components/authorized-content/authorized-content-page.component.html
new file mode 100644
index 00000000..3a81c5f9
--- /dev/null
+++ b/projects/example/src/app/components/authorized-content/authorized-content-page.component.html
@@ -0,0 +1,24 @@
+<section *ngIf="!authenticated">    
+  Not Authenticated
+</section>
+
+<div><b>Admin</b> role section: (empty when not authorized)</div>
+<section class="authorized-section-container">
+  <div *authorizedContent="['Admin']">
+    Authorized content section for <b>Admin</b> role
+  </div>
+</section>
+
+<div><b>Dora & Admin</b> role section: (empty when not authorized for both)</div>
+<section class="authorized-section-container">
+  <div *authorizedContent="['Dora', 'Admin']">
+    Authorized content section for <b>Dora & Admin</b> role
+  </div>
+</section>
+
+<div><b>Dora</b> role section: (empty when not authorized)</div>
+<section class="authorized-section-container">
+  <div *authorizedContent="['Dora']">
+    Authorized content section for <b>Dora</b> role
+  </div>
+</section>
diff --git a/projects/example/src/app/components/authorized-content/authorized-content-page.component.scss b/projects/example/src/app/components/authorized-content/authorized-content-page.component.scss
new file mode 100644
index 00000000..1d17a6a3
--- /dev/null
+++ b/projects/example/src/app/components/authorized-content/authorized-content-page.component.scss
@@ -0,0 +1,5 @@
+.authorized-section-container {
+  padding: 10px;
+  margin: 8px 0 24px 0;
+  border: 2px dotted rgb(21, 125, 243);
+}
\ No newline at end of file
diff --git a/projects/example/src/app/components/authorized-content/authorized-content-page.component.ts b/projects/example/src/app/components/authorized-content/authorized-content-page.component.ts
new file mode 100644
index 00000000..57e232bf
--- /dev/null
+++ b/projects/example/src/app/components/authorized-content/authorized-content-page.component.ts
@@ -0,0 +1,25 @@
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { FronteggAppService } from '@frontegg/angular';
+import { Subscription } from 'rxjs';
+
+@Component({
+  selector: 'authorized-content-page',
+  templateUrl: 'authorized-content-page.component.html',
+  styleUrls: ['authorized-content-page.component.scss'],
+})
+export class AuthorizedContentPage implements OnInit, OnDestroy {
+  authenticated = false;
+  isAuthenticatedSubscription?: Subscription;
+
+  constructor(private fronteggAppService: FronteggAppService) {}
+
+  ngOnInit(): void {
+    this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => {
+      this.authenticated = isAuthenticated;
+    });
+  }
+
+  ngOnDestroy(): void {
+    this.isAuthenticatedSubscription?.unsubscribe();
+  }
+}
diff --git a/projects/example/src/app/components/entitlements-page/entitlements-page.component.html b/projects/example/src/app/components/entitlements-page/entitlements-page.component.html
new file mode 100644
index 00000000..3148ce8d
--- /dev/null
+++ b/projects/example/src/app/components/entitlements-page/entitlements-page.component.html
@@ -0,0 +1,24 @@
+<section *ngIf="!authenticated">    
+  Not Authenticated
+</section>
+
+<section *ngIf="authenticated" class="outer-container">    
+  <div class="boxes-container">
+    <span *ngFor="let ent of entitlementsResults | keyvalue">
+      <div *ngIf="ent.value.isEntitled" class="truthy">
+        {{ ent.value.name }}
+      </div>
+      
+      <div *ngIf="!ent.value.isEntitled" class="falsy">
+        {{ ent.value.name }}
+        <br/><br/>
+        <i>{{ent.value.justification?.toString()}}</i>
+      </div>
+    </span>
+  </div>
+
+  <div class="centered-flex-container" [ngStyle]="{ 'flex-direction': 'row' }">
+    <button (click)="onLoadEntitlementsClicked()">Load entitlements</button>
+    <button (click)="onLoadEntitlementsWithCallbackClicked()">Load entitlements with callback</button>
+  </div>
+</section>
\ No newline at end of file
diff --git a/projects/example/src/app/components/entitlements-page/entitlements-page.component.scss b/projects/example/src/app/components/entitlements-page/entitlements-page.component.scss
new file mode 100644
index 00000000..fa822b40
--- /dev/null
+++ b/projects/example/src/app/components/entitlements-page/entitlements-page.component.scss
@@ -0,0 +1,49 @@
+.truthy {
+  background-color: lightgreen;
+}
+
+.falsy {
+  background-color: pink;
+}
+
+.truthy, .falsy {
+  display: inline-block;
+  height: 100px;
+  width: 420px;
+  word-wrap: break-word;
+  margin: 10px;
+  padding: 5px;
+  border: 1px dotted black;
+
+  animation: fadeInAnimation ease 3s;
+  animation-iteration-count: 1;
+  animation-fill-mode: forwards;
+
+  @keyframes fadeInAnimation {
+    0% {
+        opacity: 0;
+    }
+    100% {
+        opacity: 1;
+    }
+  }
+}
+
+.outer-container {
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+}
+
+.boxes-container {
+  display: flex;
+  flex-wrap: wrap;
+  max-width: 900px;
+  justify-content: center;
+  align-items: center;
+  text-align: center;
+}
+
+.centered-flex-container button {
+  margin: 10px 10px 0 0;
+}
\ No newline at end of file
diff --git a/projects/example/src/app/components/entitlements-page/entitlements-page.component.ts b/projects/example/src/app/components/entitlements-page/entitlements-page.component.ts
new file mode 100644
index 00000000..337b8d9f
--- /dev/null
+++ b/projects/example/src/app/components/entitlements-page/entitlements-page.component.ts
@@ -0,0 +1,92 @@
+import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
+import { FronteggAppService, FronteggEntitlementsService } from '@frontegg/angular';
+import { Entitlement } from '@frontegg/types';
+import { Subscription } from 'rxjs';
+
+enum EntitlementsQueryType {
+  FEATURE = 'featureEntitlements',
+  PERMISSION = 'permissionEntitlements',
+  ENTITLEMENTS = 'entitlements',
+}
+
+@Component({
+  selector: 'entitlements-page',
+  templateUrl: './entitlements-page.component.html',
+  styleUrls: ['./entitlements-page.component.scss']
+})
+export class EntitlementsPage implements OnDestroy {
+  authenticated = false;
+
+  entitlementsResults: {
+    [key: string]: { 
+      name: string,
+      isEntitled: Entitlement['isEntitled'],
+      justification?: Entitlement['justification']
+    } 
+  } = {};
+
+  user?: any;
+  subscriptions: Subscription[] = [];
+  isAuthenticatedSubscription: Subscription;
+
+  constructor(
+      private fronteggAppService: FronteggAppService,
+      private fronteggEntitlementsService: FronteggEntitlementsService,
+      private ngZone: NgZone,
+  ) {
+    this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => {
+      this.authenticated = isAuthenticated;
+    });
+
+    this.feedEntitlements();
+  }
+
+  feedEntitlements() {
+    const entitlementsRequests = [
+      { queryType: EntitlementsQueryType.FEATURE, arg: 'sso', },
+      { queryType: EntitlementsQueryType.FEATURE, arg: 'sso', customAttributes: { env: 'dev' } },
+      { queryType: EntitlementsQueryType.ENTITLEMENTS, arg: { featureKey: 'proteins.*' }, customAttributes: { pro: '20gr' }},
+      { queryType: EntitlementsQueryType.PERMISSION, arg: 'dora.protein.*', },
+      { queryType: EntitlementsQueryType.ENTITLEMENTS, arg: { permissionKey: 'fe.secure.*' } },
+      { queryType: EntitlementsQueryType.PERMISSION, arg: 'fe.secure.*', customAttributes: { env: 'dev' } },
+    ];
+
+    const subscriptionGenerators: any = {
+      featureEntitlements: this.fronteggEntitlementsService.featureEntitlements$.bind(this.fronteggEntitlementsService),
+      permissionEntitlements: this.fronteggEntitlementsService.permissionEntitlements$.bind(this.fronteggEntitlementsService),
+      entitlements: this.fronteggEntitlementsService.entitlements$.bind(this.fronteggEntitlementsService),
+    }
+
+    this.subscriptions = entitlementsRequests.map(({ queryType, arg, customAttributes }) => (
+      subscriptionGenerators[queryType](arg, {
+        next: (
+          result: Entitlement
+        ) => {
+          this.ngZone.run(() => {
+            const customAttributesNamePart = customAttributes ? `, ${JSON.stringify(customAttributes)}` : '';
+            const name = `${queryType}$(${JSON.stringify(arg)}${customAttributesNamePart})`;
+            this.entitlementsResults[name] = {
+              name,
+              ...result
+            };
+          });
+        }
+      })
+    ));
+  }
+
+  onLoadEntitlementsClicked() {
+    this.fronteggEntitlementsService.loadEntitlements();
+  }
+
+  onLoadEntitlementsWithCallbackClicked() {
+    this.fronteggEntitlementsService.loadEntitlements(
+      (isSucceeded: boolean) => console.log(`Load entitlements on demand ${isSucceeded ? 'succeeded' : 'failed'}`)
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.subscriptions.forEach(subscription => subscription.unsubscribe());
+    this.isAuthenticatedSubscription.unsubscribe();
+  }
+}
diff --git a/projects/example/src/app/components/home-page/home-page.component.html b/projects/example/src/app/components/home-page/home-page.component.html
new file mode 100644
index 00000000..1bb61d0d
--- /dev/null
+++ b/projects/example/src/app/components/home-page/home-page.component.html
@@ -0,0 +1,7 @@
+<section *ngIf="!authenticated">    
+  Not Authenticated
+</section>
+
+<section *ngIf="authenticated">    
+  Home Page
+</section>
diff --git a/projects/example/src/app/components/home-page/home-page.component.ts b/projects/example/src/app/components/home-page/home-page.component.ts
new file mode 100644
index 00000000..f98474c2
--- /dev/null
+++ b/projects/example/src/app/components/home-page/home-page.component.ts
@@ -0,0 +1,24 @@
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { FronteggAppService } from '@frontegg/angular';
+import { Subscription } from 'rxjs';
+
+@Component({
+  selector: 'home-page',
+  templateUrl: 'home-page.component.html',
+})
+export class HomePage implements OnInit, OnDestroy {
+  authenticated = false;
+  isAuthenticatedSubscription?: Subscription;
+
+  constructor(private fronteggAppService: FronteggAppService) {}
+
+  ngOnInit(): void {
+    this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => {
+      this.authenticated = isAuthenticated;
+    });
+  }
+
+  ngOnDestroy(): void {
+    this.isAuthenticatedSubscription?.unsubscribe();
+  }
+}
diff --git a/projects/example/src/app/components/home.component.html b/projects/example/src/app/components/home.component.html
deleted file mode 100644
index 665e19a5..00000000
--- a/projects/example/src/app/components/home.component.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<div>
-    <br />
-    <br />
-    <br />
-    <h1>Toolbar Frontegg App</h1>
-
-
-    <br />
-    <button data-test-id="open-admin-portal-btn" (click)="showApp()"><h2>Open Frontegg app</h2></button>
-    <button data-test-id="open-hosted" (click)="loginWithRedirect()"><h2>Open Hosted Login</h2></button>
-    <br />
-    <br />
-    <p>Authenticated: {{authenticated}}</p>
-    <p>Authenticated as: {{user?.name}}</p>
-    <a routerLink="/account/login"><b>Login</b></a> ---
-    <span (click)="doLogout()"><b>Logout</b></span> ---
-    <a routerLink="/test-private-route"><b>private-route</b></a>
-    <br />
-    <br />
-</div>
diff --git a/projects/example/src/app/components/home.component.ts b/projects/example/src/app/components/home.component.ts
deleted file mode 100644
index efb671ed..00000000
--- a/projects/example/src/app/components/home.component.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { FronteggAppService, FronteggAuthService } from '@frontegg/angular';
-import { Router } from '@angular/router';
-
-@Component({
-  selector: 'app-home',
-  templateUrl: 'home.component.html',
-})
-export class AppHomeComponent implements OnInit {
-  authenticated?: boolean;
-  user?: any;
-
-  constructor(private fronteggAppService: FronteggAppService,
-              private fronteggAuthService: FronteggAuthService,
-              private router: Router) {
-  }
-
-  ngOnInit(): void {
-    this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => {
-      this.authenticated = isAuthenticated;
-    });
-
-    this.fronteggAuthService.user$.subscribe((user: any) => {
-      this.user = user;
-    });
-  }
-
-  showApp(): void {
-    this.fronteggAppService.showAdminPortal();
-  }
-
-  doLogout(): void {
-    if (this.fronteggAppService.fronteggApp.options.hostedLoginBox) {
-      this.fronteggAuthService.logout();
-    } else {
-      this.router.navigateByUrl(this.fronteggAppService.authRoutes.logoutUrl);
-    }
-  }
-
-  loginWithRedirect(): void {
-    this.fronteggAuthService.loginWithRedirect();
-  }
-
-}
diff --git a/projects/example/src/app/components/not-found.component.ts b/projects/example/src/app/components/not-found.component.ts
index 078fe38a..5a99ba07 100644
--- a/projects/example/src/app/components/not-found.component.ts
+++ b/projects/example/src/app/components/not-found.component.ts
@@ -1,7 +1,7 @@
 import { Component, OnInit } from '@angular/core';
 
 @Component({
-  selector: 'app-not-fpund',
+  selector: 'app-not-found',
   template: `
     <div>
       404 not found
diff --git a/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.html b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.html
new file mode 100644
index 00000000..24edcc47
--- /dev/null
+++ b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.html
@@ -0,0 +1,14 @@
+<section *ngIf="!authenticated">    
+  Not Authenticated
+</section>
+
+<section *ngIf="authenticated" class="centered-flex-container">
+  <div *ngIf="maxAge" class="max-age-container">Max age is <span class="positive">{{ maxAge }}</span></div>
+  <div *ngIf="!maxAge" class="max-age-container">No max age</div>
+  
+  <button *ngIf="!isSteppedUp" (click)="stepUp({ maxAge: maxAge })" class="step-up-button">Step Up</button>
+
+  <div class="stepped-up-container" *ngIf="isSteppedUp">
+    <p>Honey, you are <b class="positive">STEPPED UP</b>!</p>
+  </div>
+</section>
\ No newline at end of file
diff --git a/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.scss b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.scss
new file mode 100644
index 00000000..e1cf7f17
--- /dev/null
+++ b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.scss
@@ -0,0 +1,8 @@
+.max-age-container {
+  margin-bottom: 4px;
+}
+
+.step-up-button {
+  font-size: 1.2em;
+  padding: 0.7em;
+}
\ No newline at end of file
diff --git a/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.ts b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.ts
new file mode 100644
index 00000000..4082d995
--- /dev/null
+++ b/projects/example/src/app/components/step-up/base-step-up/base-step-up.component.ts
@@ -0,0 +1,47 @@
+import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
+import { FronteggAppService, FronteggAuthService } from '@frontegg/angular';
+import { StepUpOptions } from '@frontegg/types';
+import { Subscription } from 'rxjs';
+
+@Component({
+  selector: 'base-step-up',
+  templateUrl: './base-step-up.component.html',
+  styleUrls: ['./base-step-up.component.scss']
+})
+export class BaseStepUp implements OnDestroy, OnInit {
+  isLoading = true;
+  steppedUpSubscription?: Subscription;
+  isAuthenticatedSubscription: Subscription;
+  isSteppedUp = false;
+  authenticated = false;
+  maxAge: number | undefined;
+
+  constructor(
+      private fronteggAppService: FronteggAppService,
+      private fronteggAuthService: FronteggAuthService,
+      private ngZone: NgZone,
+  ) {
+    this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => {
+      this.authenticated = isAuthenticated;
+    });
+  }
+
+  ngOnInit(): void {
+    this.steppedUpSubscription = this.fronteggAuthService.isSteppedUp$({
+      next: (isSteppedUp: boolean) => {
+        this.ngZone.run(() => {
+          this.isSteppedUp = isSteppedUp;
+        });
+      }
+    }, { maxAge: this.maxAge });
+  }
+
+  stepUp(options?: StepUpOptions) {
+    this.fronteggAuthService.stepUp(options);
+  }
+
+  ngOnDestroy(): void {
+    this.isAuthenticatedSubscription.unsubscribe();
+    this.steppedUpSubscription?.unsubscribe();
+  }
+}
diff --git a/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.html b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.html
new file mode 100644
index 00000000..b671259a
--- /dev/null
+++ b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.html
@@ -0,0 +1,15 @@
+<section *ngIf="!authenticated">    
+  Not Authenticated
+</section>
+
+<section *ngIf="authenticated" class="centered-flex-container">
+  <div *ngIf="maxAge" class="max-age-container">Max age is <span class="positive">{{ maxAge }}</span></div>
+  
+  <div class="stepped-up-container" *ngIf="isSteppedUp">
+    <p>Honey, you are <b class="positive">STEPPED UP</b>!</p>
+  </div>
+
+  <div class="stepped-up-container" *ngIf="!isSteppedUp">
+    <p>Honey, you are NOT stepped up! If you see it there is a BUG!</p>
+  </div>
+</section>
\ No newline at end of file
diff --git a/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.scss b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.scss
new file mode 100644
index 00000000..10d4b726
--- /dev/null
+++ b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.scss
@@ -0,0 +1,3 @@
+.max-age-container {
+  margin-bottom: 4px;
+}
\ No newline at end of file
diff --git a/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.ts b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.ts
new file mode 100644
index 00000000..89db1341
--- /dev/null
+++ b/projects/example/src/app/components/step-up/step-up-full/step-up-full.component.ts
@@ -0,0 +1,46 @@
+import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
+import { FronteggAppService, FronteggAuthService } from '@frontegg/angular';
+import { Subscription } from 'rxjs';
+
+@Component({
+  selector: 'step-up-full',
+  templateUrl: './step-up-full.component.html',
+  styleUrls: ['./step-up-full.component.scss']
+})
+export class StepUpFull implements OnDestroy, OnInit {
+  isLoading = true;
+  steppedUpSubscription?: Subscription;
+  isAuthenticatedSubscription: Subscription;
+  isSteppedUp = false;
+  authenticated = false;
+  maxAge = 5000;
+
+  constructor(
+      private fronteggAppService: FronteggAppService,
+      private fronteggAuthService: FronteggAuthService,
+      private ngZone: NgZone,
+  ) {
+    this.isAuthenticatedSubscription = this.fronteggAppService.isAuthenticated$.subscribe((isAuthenticated: boolean) => {
+      this.authenticated = isAuthenticated;
+    });
+  }
+
+  ngOnInit(): void {
+    this.steppedUpSubscription = this.fronteggAuthService.isSteppedUp$({
+      next: (isSteppedUp: boolean) => {
+        this.ngZone.run(() => {
+          this.isSteppedUp = isSteppedUp;
+
+          if (isSteppedUp) return;
+            
+          this.fronteggAuthService.stepUp({ maxAge: this.maxAge });
+        });
+      }
+    }, { maxAge: this.maxAge });
+  }
+
+  ngOnDestroy(): void {
+    this.isAuthenticatedSubscription.unsubscribe();
+    this.steppedUpSubscription?.unsubscribe();
+  }
+}
diff --git a/projects/example/src/app/components/step-up/step-up-high-max-age-page.component.ts b/projects/example/src/app/components/step-up/step-up-high-max-age-page.component.ts
new file mode 100644
index 00000000..df0e4045
--- /dev/null
+++ b/projects/example/src/app/components/step-up/step-up-high-max-age-page.component.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+import { BaseStepUp } from './base-step-up/base-step-up.component';
+
+@Component({
+  selector: 'step-up-high-max-age-page',
+  templateUrl: './base-step-up/base-step-up.component.html',
+  styleUrls: ['./base-step-up/base-step-up.component.scss'],
+})
+export class StepUpHighMaxAgePage extends BaseStepUp {
+  maxAge = 5000;
+}
diff --git a/projects/example/src/app/components/step-up/step-up-no-max-age-page.component.ts b/projects/example/src/app/components/step-up/step-up-no-max-age-page.component.ts
new file mode 100644
index 00000000..12df03ee
--- /dev/null
+++ b/projects/example/src/app/components/step-up/step-up-no-max-age-page.component.ts
@@ -0,0 +1,9 @@
+import { Component } from '@angular/core';
+import { BaseStepUp } from './base-step-up/base-step-up.component';
+
+@Component({
+  selector: 'step-up-no-max-age-page',
+  templateUrl: './base-step-up/base-step-up.component.html',
+  styleUrls: ['./base-step-up/base-step-up.component.scss'],
+})
+export class StepUpNoMaxAgePage extends BaseStepUp {}
diff --git a/projects/example/src/app/components/step-up/step-up-small-max-age-page.component.ts b/projects/example/src/app/components/step-up/step-up-small-max-age-page.component.ts
new file mode 100644
index 00000000..2430797a
--- /dev/null
+++ b/projects/example/src/app/components/step-up/step-up-small-max-age-page.component.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+import { BaseStepUp } from './base-step-up/base-step-up.component';
+
+@Component({
+  selector: 'step-up-small-max-age-page',
+  templateUrl: './base-step-up/base-step-up.component.html',
+  styleUrls: ['./base-step-up/base-step-up.component.scss'],
+})
+export class StepUpSmallMaxAgePage extends BaseStepUp {
+  maxAge = 35;
+}
diff --git a/projects/example/src/app/links.ts b/projects/example/src/app/links.ts
new file mode 100644
index 00000000..9e441de7
--- /dev/null
+++ b/projects/example/src/app/links.ts
@@ -0,0 +1,13 @@
+export const ROUTE_PATHS = {
+  HOME_PAGE: '',
+  ENTITLEMENTS: 'entitlements',
+  STEP_UP_HIGH_MAX_AGE: 'step-up-high-max-age',
+  STEP_UP_SMALL_MAX_AGE: 'step-up-small-max-age',
+  STEP_UP_NO_MAX_AGE: 'step-up-no-max-age',
+  STEPPED_UP_FULL: 'stepped-up-full',
+  UNKNOWN_ROUTE: 'unknown-route',
+  AUTHORIZED_CONTENT: 'authorized-content',
+  TEST_PRIVATE_ROUTE: 'test-private-route',
+  NESTED_ROUTER: 'nested-router',
+  TEST: 'test',
+};
diff --git a/projects/example/src/styles.scss b/projects/example/src/styles.scss
index 732e93b1..cae790c9 100644
--- a/projects/example/src/styles.scss
+++ b/projects/example/src/styles.scss
@@ -1,3 +1,5 @@
+$positive-color: dodgerblue;
+
 * {
   box-sizing: border-box;
 }
@@ -24,3 +26,23 @@ html, body {
     text-decoration: none;
   }
 }
+
+button {
+  border-radius: 4px;
+  background: $positive-color;
+  color: white;
+  border: 0;
+  padding: 8px 16px;
+  margin-bottom: 4px;
+  cursor: pointer;
+}
+
+.centered-flex-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.positive {
+  color: $positive-color;
+}
\ No newline at end of file