@sinequa/atomic-angular 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3415,14 +3415,10 @@ function AuthGuard() {
3415
3415
  const { loginPath, useCredentials, useSSO } = globalConfig;
3416
3416
  if (state.url.startsWith("/login"))
3417
3417
  return true;
3418
- // If the user is not authenticated, navigate to the login page or loading page based on the authentication method
3418
+ // If the user is not authenticated, navigate to the login page.
3419
+ // The login page handles every authentication method (credentials, OAuth, SAML).
3419
3420
  if (!isAuthenticated() && !useSSO) {
3420
- if (useCredentials) {
3421
- router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
3422
- }
3423
- else {
3424
- router.navigate(["loading"], { queryParams: { returnUrl: state.url } });
3425
- }
3421
+ router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
3426
3422
  return false;
3427
3423
  }
3428
3424
  // If the user is authenticated, initialize the principal store if it's in the initial state
@@ -4031,7 +4027,7 @@ class NavigationService {
4031
4027
  * - Maps all router events to `RouterEvent`.
4032
4028
  * - Filters the events to only include instances of `NavigationEnd`.
4033
4029
  * - Taps into the event stream to extract the route name from the URL and notify the audit service of route changes,
4034
- * excluding the "loading" route and duplicate navigations.
4030
+ * excluding duplicate navigations.
4035
4031
  * - Updates the `urlAfterNavigation` property with the current URL after navigation.
4036
4032
  * - Shares the replayed value with a buffer size of 1 to ensure subscribers receive the latest emitted value.
4037
4033
  *
@@ -4039,8 +4035,7 @@ class NavigationService {
4039
4035
  */
4040
4036
  navigationEnd$ = this.router.events.pipe(map((event) => event), filter((event) => event instanceof NavigationEnd), tap((event) => {
4041
4037
  const url = event.url.slice(1).split("?")[0]; // Extract route name
4042
- // TODO: use a "loading" configuration from globalConfig
4043
- if (url && url !== "loading" && url !== this.urlAfterNavigation) {
4038
+ if (url && url !== this.urlAfterNavigation) {
4044
4039
  this.auditService.notifyRouteChange(url);
4045
4040
  }
4046
4041
  }), tap((event) => {
@@ -5372,6 +5367,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
5372
5367
  }]
5373
5368
  }] });
5374
5369
 
5370
+ /**
5371
+ * @deprecated This component and its `/loading` route are no longer used.
5372
+ * Authentication is handled at bootstrap by `withBootstrapApp`/`signIn`, and the
5373
+ * `AuthGuard` now redirects unauthenticated users to the login page directly.
5374
+ * Will be removed in a future major release. Do not use in new code.
5375
+ */
5375
5376
  class LoadingComponent {
5376
5377
  state = computed(() => getState(this.application), ...(ngDevMode ? [{ debugName: "state" }] : []));
5377
5378
  application = inject(ApplicationStore);
@@ -8989,6 +8990,24 @@ class AggregationListComponent {
8989
8990
  }
8990
8991
  });
8991
8992
  }
8993
+ else if (Array.isArray(activeFilters.values) && activeFilters.values.length) {
8994
+ // multiple values stored as a string array, e.g. { values: ["alice_martin", "caroline_dubois"] }
8995
+ // (no `filters` sub-array and no single `value`) — mark each matching item as selected
8996
+ activeFilters.values.forEach((value) => {
8997
+ const found = aggItems.find(item => item.value?.toString().toLocaleLowerCase() === value?.toLocaleLowerCase());
8998
+ if (!found) {
8999
+ // value not in the loaded items — add it so it shows up as selected
9000
+ aggItems.unshift({
9001
+ value,
9002
+ display: value,
9003
+ $selected: true
9004
+ });
9005
+ }
9006
+ else {
9007
+ found.$selected = true;
9008
+ }
9009
+ });
9010
+ }
8992
9011
  else {
8993
9012
  // single filter
8994
9013
  const found = aggItems.find(item => item.value?.toString().toLocaleLowerCase() === activeFilters.value?.toLocaleLowerCase());
@@ -10740,6 +10759,15 @@ class SignInComponent {
10740
10759
  destroyRef;
10741
10760
  cn = cn;
10742
10761
  config = globalConfig;
10762
+ /**
10763
+ * True when authentication is handled outside the credentials form — i.e. by the
10764
+ * browser/proxy (`useSSO`) or by an auto-configured OAuth/SAML provider. In those
10765
+ * modes this screen shows a loader instead of a login form and initiates the
10766
+ * handshake automatically by calling `handleLogin()`.
10767
+ */
10768
+ externalAuth = !!(globalConfig.useSSO ||
10769
+ globalConfig.autoOAuthProvider ||
10770
+ globalConfig.autoSAMLProvider);
10743
10771
  class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
10744
10772
  forgotPassword = output();
10745
10773
  username = model("", ...(ngDevMode ? [{ debugName: "username" }] : []));
@@ -10761,6 +10789,30 @@ class SignInComponent {
10761
10789
  expiresSoonNotified = signal(false, ...(ngDevMode ? [{ debugName: "expiresSoonNotified" }] : []));
10762
10790
  constructor(destroyRef) {
10763
10791
  this.destroyRef = destroyRef;
10792
+ // If the user is already authenticated when landing here (e.g. page refresh on
10793
+ // /login, or an external handshake completed before this screen was created),
10794
+ // don't sit on the loader: go straight to the returnUrl.
10795
+ if (this.authenticated()) {
10796
+ const url = this.route.snapshot.queryParams["returnUrl"] || "/";
10797
+ this.router.navigateByUrl(url);
10798
+ }
10799
+ // When authentication is delegated to the browser/proxy (SSO) or an OAuth/SAML
10800
+ // provider, no credentials form is shown: this screen shows a loader and initiates
10801
+ // the handshake automatically by calling `handleLogin()`. If the handshake never
10802
+ // completes, fall back to /error after 5s; the fallback is cancelled as soon as
10803
+ // the login succeeds (the `authenticated` event then drives navigation).
10804
+ if (this.externalAuth && !this.authenticated()) {
10805
+ const timeout = setTimeout(() => {
10806
+ this.router.navigate(["/error"], {
10807
+ queryParams: { returnUrl: this.route.snapshot.queryParams["returnUrl"] }
10808
+ });
10809
+ }, 5000);
10810
+ destroyRef.onDestroy(() => clearTimeout(timeout));
10811
+ this.handleLogin().then(result => {
10812
+ if (result)
10813
+ clearTimeout(timeout);
10814
+ });
10815
+ }
10764
10816
  effect(() => {
10765
10817
  const principal = getState(this.principalStore);
10766
10818
  if (this.authenticated() && principal && !this.expiresSoonNotified()) {
@@ -10806,14 +10858,16 @@ class SignInComponent {
10806
10858
  this.router.navigate(["/login"]);
10807
10859
  }
10808
10860
  async handleLogin() {
10809
- login().then((result) => {
10861
+ return login().then((result) => {
10810
10862
  if (result) {
10811
10863
  this.auditService.notifyLogin();
10812
10864
  }
10865
+ return result;
10813
10866
  }).catch(error => {
10814
10867
  warn("An error occurred while logging in", error);
10815
10868
  this.auditService.notify({ type: 'Login_Denied' });
10816
10869
  this.router.navigate(["error"]);
10870
+ return false;
10817
10871
  });
10818
10872
  }
10819
10873
  async handleLoginWithCredentials() {
@@ -10856,7 +10910,7 @@ class SignInComponent {
10856
10910
  }
10857
10911
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: SignInComponent, deps: [{ token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
10858
10912
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: SignInComponent, isStandalone: true, selector: "signIn, signin, sign-in", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, username: { classPropertyName: "username", publicName: "username", isSignal: true, isRequired: false, transformFunction: null }, password: { classPropertyName: "password", publicName: "password", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { forgotPassword: "forgotPassword", username: "usernameChange", password: "passwordChange" }, host: { properties: { "class": "cn('grid h-dvh w-full place-content-center', class())" } }, providers: [provideTranslocoScope("login")], ngImport: i0, template: `
10859
- @if (!authenticated()) {
10913
+ @if (!authenticated() && !externalAuth) {
10860
10914
  <Card
10861
10915
  hover="no"
10862
10916
  cdkTrapFocus
@@ -10867,60 +10921,54 @@ class SignInComponent {
10867
10921
  </CardHeader>
10868
10922
 
10869
10923
  <CardContent class="grid gap-4">
10870
- @let useCredentials =
10871
- !config.autoOAuthProvider && !config.autoSAMLProvider;
10872
- @if (useCredentials) {
10873
- <!-- authentication using credentials -->
10874
- <div class="grid gap-2">
10875
- <label class="text-sm font-medium" for="username">{{
10876
- "login.username" | transloco
10877
- }}</label>
10878
- <input
10879
- id="username"
10880
- type="text"
10881
- required
10882
- [(ngModel)]="username"
10883
- (keydown.enter)="handleLoginWithCredentials()" />
10884
- </div>
10885
-
10886
- <div class="grid gap-2">
10887
- <label class="text-sm font-medium" for="password">{{
10888
- "login.password" | transloco
10889
- }}</label>
10890
- <input
10891
- id="password"
10892
- type="password"
10893
- required
10894
- [(ngModel)]="password"
10895
- (keydown.enter)="handleLoginWithCredentials()" />
10896
- </div>
10897
-
10898
- <span
10899
- class="text-muted-foreground cursor-pointer justify-self-start text-xs hover:underline"
10900
- role="button"
10901
- tabindex="0"
10902
- (click)="forgotPassword.emit()"
10903
- (keydown.enter)="forgotPassword.emit()">
10904
- {{ "login.forgotPassword" | transloco }}
10905
- </span>
10906
- <button variant="primary"
10907
- [disabled]="!isValid()"
10908
- (click)="handleLoginWithCredentials()">
10909
- {{ "login.connect" | transloco }}
10910
- </button>
10911
- }
10912
- @else {
10913
- <!-- authentication using OAuth or SAML provider -->
10914
- <button (click)="handleLogin()">
10915
- {{ "login.SignInWith" | transloco : { provider: config.autoOAuthProvider ? "OAuth" : "SAML" } }}
10916
- </button>
10917
- }
10924
+ <!-- authentication using credentials -->
10925
+ <div class="grid gap-2">
10926
+ <label class="text-sm font-medium" for="username">{{
10927
+ "login.username" | transloco
10928
+ }}</label>
10929
+ <input
10930
+ id="username"
10931
+ type="text"
10932
+ required
10933
+ [(ngModel)]="username"
10934
+ (keydown.enter)="handleLoginWithCredentials()" />
10935
+ </div>
10936
+
10937
+ <div class="grid gap-2">
10938
+ <label class="text-sm font-medium" for="password">{{
10939
+ "login.password" | transloco
10940
+ }}</label>
10941
+ <input
10942
+ id="password"
10943
+ type="password"
10944
+ required
10945
+ [(ngModel)]="password"
10946
+ (keydown.enter)="handleLoginWithCredentials()" />
10947
+ </div>
10948
+
10949
+ <span
10950
+ class="text-muted-foreground cursor-pointer justify-self-start text-xs hover:underline"
10951
+ role="button"
10952
+ tabindex="0"
10953
+ (click)="forgotPassword.emit()"
10954
+ (keydown.enter)="forgotPassword.emit()">
10955
+ {{ "login.forgotPassword" | transloco }}
10956
+ </span>
10957
+ <button variant="primary"
10958
+ [disabled]="!isValid()"
10959
+ (click)="handleLoginWithCredentials()">
10960
+ {{ "login.connect" | transloco }}
10961
+ </button>
10918
10962
  </CardContent>
10919
10963
  </Card>
10920
10964
  } @else {
10921
- <app-wait />
10965
+ <div class="flex h-dvh w-full items-center justify-center">
10966
+ <div class="flex flex-col items-center space-y-4">
10967
+ <span class="loader"></span>
10968
+ </div>
10969
+ </div>
10922
10970
  }
10923
- `, isInline: true, styles: ["input{background-color:var(--background)}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size"] }, { kind: "directive", type: CardComponent, selector: ".card, card, Card", inputs: ["class", "variant", "hover"] }, { kind: "directive", type: CardHeaderComponent, selector: ".card-header, card-header, CardHeader, cardheader", inputs: ["class"] }, { kind: "directive", type: CardContentComponent, selector: ".card-content, card-content, CardContent, cardcontent", inputs: ["class"] }, { kind: "component", type: LoadingComponent, selector: "app-wait" }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
10971
+ `, isInline: true, styles: ["input{background-color:var(--background)}.loader{--w: 96px;--h: 96px;transform:rotate(45deg);perspective:1000px;border-radius:50%;width:var(--w);height:var(--h);color:#0040bf}.loader:before,.loader:after{content:\"\";display:block;position:absolute;top:0;left:0;width:inherit;height:inherit;border-radius:50%;transform:rotateX(70deg);animation:1s spin linear infinite}.loader:after{color:#ff854a;transform:rotateY(70deg);animation-delay:.4s}@keyframes spin{0%,to{box-shadow:.4em 0 0 0 currentcolor}12%{box-shadow:.4em .4em 0 0 currentcolor}25%{box-shadow:0 .4em 0 0 currentcolor}37%{box-shadow:-.4em .4em 0 0 currentcolor}50%{box-shadow:-.4em 0 0 0 currentcolor}62%{box-shadow:-.4em -.4em 0 0 currentcolor}75%{box-shadow:0 -.4em 0 0 currentcolor}87%{box-shadow:.4em -.4em 0 0 currentcolor}}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: InputComponent, selector: "input[type=\"text\"], input[type=\"email\"], input[type=\"number\"], input[type=\"password\"], input[type=\"tel\"], input[type=\"url\"], input[type=\"time\"], input[type=\"file\"]", inputs: ["class", "variant", "decoration"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size"] }, { kind: "directive", type: CardComponent, selector: ".card, card, Card", inputs: ["class", "variant", "hover"] }, { kind: "directive", type: CardHeaderComponent, selector: ".card-header, card-header, CardHeader, cardheader", inputs: ["class"] }, { kind: "directive", type: CardContentComponent, selector: ".card-content, card-content, CardContent, cardcontent", inputs: ["class"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
10924
10972
  }
10925
10973
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: SignInComponent, decorators: [{
10926
10974
  type: Component,
@@ -10933,10 +10981,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
10933
10981
  ButtonComponent,
10934
10982
  CardComponent,
10935
10983
  CardHeaderComponent,
10936
- CardContentComponent,
10937
- LoadingComponent
10984
+ CardContentComponent
10938
10985
  ], providers: [provideTranslocoScope("login")], template: `
10939
- @if (!authenticated()) {
10986
+ @if (!authenticated() && !externalAuth) {
10940
10987
  <Card
10941
10988
  hover="no"
10942
10989
  cdkTrapFocus
@@ -10947,62 +10994,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
10947
10994
  </CardHeader>
10948
10995
 
10949
10996
  <CardContent class="grid gap-4">
10950
- @let useCredentials =
10951
- !config.autoOAuthProvider && !config.autoSAMLProvider;
10952
- @if (useCredentials) {
10953
- <!-- authentication using credentials -->
10954
- <div class="grid gap-2">
10955
- <label class="text-sm font-medium" for="username">{{
10956
- "login.username" | transloco
10957
- }}</label>
10958
- <input
10959
- id="username"
10960
- type="text"
10961
- required
10962
- [(ngModel)]="username"
10963
- (keydown.enter)="handleLoginWithCredentials()" />
10964
- </div>
10965
-
10966
- <div class="grid gap-2">
10967
- <label class="text-sm font-medium" for="password">{{
10968
- "login.password" | transloco
10969
- }}</label>
10970
- <input
10971
- id="password"
10972
- type="password"
10973
- required
10974
- [(ngModel)]="password"
10975
- (keydown.enter)="handleLoginWithCredentials()" />
10976
- </div>
10977
-
10978
- <span
10979
- class="text-muted-foreground cursor-pointer justify-self-start text-xs hover:underline"
10980
- role="button"
10981
- tabindex="0"
10982
- (click)="forgotPassword.emit()"
10983
- (keydown.enter)="forgotPassword.emit()">
10984
- {{ "login.forgotPassword" | transloco }}
10985
- </span>
10986
- <button variant="primary"
10987
- [disabled]="!isValid()"
10988
- (click)="handleLoginWithCredentials()">
10989
- {{ "login.connect" | transloco }}
10990
- </button>
10991
- }
10992
- @else {
10993
- <!-- authentication using OAuth or SAML provider -->
10994
- <button (click)="handleLogin()">
10995
- {{ "login.SignInWith" | transloco : { provider: config.autoOAuthProvider ? "OAuth" : "SAML" } }}
10996
- </button>
10997
- }
10997
+ <!-- authentication using credentials -->
10998
+ <div class="grid gap-2">
10999
+ <label class="text-sm font-medium" for="username">{{
11000
+ "login.username" | transloco
11001
+ }}</label>
11002
+ <input
11003
+ id="username"
11004
+ type="text"
11005
+ required
11006
+ [(ngModel)]="username"
11007
+ (keydown.enter)="handleLoginWithCredentials()" />
11008
+ </div>
11009
+
11010
+ <div class="grid gap-2">
11011
+ <label class="text-sm font-medium" for="password">{{
11012
+ "login.password" | transloco
11013
+ }}</label>
11014
+ <input
11015
+ id="password"
11016
+ type="password"
11017
+ required
11018
+ [(ngModel)]="password"
11019
+ (keydown.enter)="handleLoginWithCredentials()" />
11020
+ </div>
11021
+
11022
+ <span
11023
+ class="text-muted-foreground cursor-pointer justify-self-start text-xs hover:underline"
11024
+ role="button"
11025
+ tabindex="0"
11026
+ (click)="forgotPassword.emit()"
11027
+ (keydown.enter)="forgotPassword.emit()">
11028
+ {{ "login.forgotPassword" | transloco }}
11029
+ </span>
11030
+ <button variant="primary"
11031
+ [disabled]="!isValid()"
11032
+ (click)="handleLoginWithCredentials()">
11033
+ {{ "login.connect" | transloco }}
11034
+ </button>
10998
11035
  </CardContent>
10999
11036
  </Card>
11000
11037
  } @else {
11001
- <app-wait />
11038
+ <div class="flex h-dvh w-full items-center justify-center">
11039
+ <div class="flex flex-col items-center space-y-4">
11040
+ <span class="loader"></span>
11041
+ </div>
11042
+ </div>
11002
11043
  }
11003
11044
  `, host: {
11004
11045
  "[class]": "cn('grid h-dvh w-full place-content-center', class())"
11005
- }, styles: ["input{background-color:var(--background)}\n"] }]
11046
+ }, styles: ["input{background-color:var(--background)}.loader{--w: 96px;--h: 96px;transform:rotate(45deg);perspective:1000px;border-radius:50%;width:var(--w);height:var(--h);color:#0040bf}.loader:before,.loader:after{content:\"\";display:block;position:absolute;top:0;left:0;width:inherit;height:inherit;border-radius:50%;transform:rotateX(70deg);animation:1s spin linear infinite}.loader:after{color:#ff854a;transform:rotateY(70deg);animation-delay:.4s}@keyframes spin{0%,to{box-shadow:.4em 0 0 0 currentcolor}12%{box-shadow:.4em .4em 0 0 currentcolor}25%{box-shadow:0 .4em 0 0 currentcolor}37%{box-shadow:-.4em .4em 0 0 currentcolor}50%{box-shadow:-.4em 0 0 0 currentcolor}62%{box-shadow:-.4em -.4em 0 0 currentcolor}75%{box-shadow:0 -.4em 0 0 currentcolor}87%{box-shadow:.4em -.4em 0 0 currentcolor}}\n"] }]
11006
11047
  }], ctorParameters: () => [{ type: i0.DestroyRef }], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], forgotPassword: [{ type: i0.Output, args: ["forgotPassword"] }], username: [{ type: i0.Input, args: [{ isSignal: true, alias: "username", required: false }] }, { type: i0.Output, args: ["usernameChange"] }], password: [{ type: i0.Input, args: [{ isSignal: true, alias: "password", required: false }] }, { type: i0.Output, args: ["passwordChange"] }] } });
11007
11048
 
11008
11049
  class AuthPageComponent {