Browse Source

UI: Redesign login pages

pull/14562/head
Vladyslav_Prykhodko 10 months ago
parent
commit
2f9d142cc9
  1. 86
      ui-ngx/src/app/modules/login/pages/login/create-password.component.html
  2. 1
      ui-ngx/src/app/modules/login/pages/login/create-password.component.scss
  3. 14
      ui-ngx/src/app/modules/login/pages/login/create-password.component.ts
  4. 22
      ui-ngx/src/app/modules/login/pages/login/force-two-factor-auth-login.component.html
  5. 1
      ui-ngx/src/app/modules/login/pages/login/force-two-factor-auth-login.component.scss
  6. 7
      ui-ngx/src/app/modules/login/pages/login/link-expired.component.html
  7. 1
      ui-ngx/src/app/modules/login/pages/login/link-expired.component.scss
  8. 16
      ui-ngx/src/app/modules/login/pages/login/link-expired.component.ts
  9. 114
      ui-ngx/src/app/modules/login/pages/login/login.component.html
  10. 78
      ui-ngx/src/app/modules/login/pages/login/login.component.scss
  11. 25
      ui-ngx/src/app/modules/login/pages/login/login.component.ts
  12. 48
      ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html
  13. 1
      ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.scss
  14. 5
      ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts
  15. 90
      ui-ngx/src/app/modules/login/pages/login/reset-password.component.html
  16. 6
      ui-ngx/src/app/modules/login/pages/login/reset-password.component.scss
  17. 17
      ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts
  18. 4
      ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.html
  19. 1
      ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.scss
  20. 1
      ui-ngx/src/assets/locale/locale.constant-ar_AE.json
  21. 1
      ui-ngx/src/assets/locale/locale.constant-ca_ES.json
  22. 1
      ui-ngx/src/assets/locale/locale.constant-cs_CZ.json
  23. 1
      ui-ngx/src/assets/locale/locale.constant-de_DE.json
  24. 1
      ui-ngx/src/assets/locale/locale.constant-el_GR.json
  25. 15
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  26. 1
      ui-ngx/src/assets/locale/locale.constant-es_ES.json
  27. 1
      ui-ngx/src/assets/locale/locale.constant-fa_IR.json
  28. 1
      ui-ngx/src/assets/locale/locale.constant-fr_FR.json
  29. 1
      ui-ngx/src/assets/locale/locale.constant-it_IT.json
  30. 1
      ui-ngx/src/assets/locale/locale.constant-ja_JP.json
  31. 1
      ui-ngx/src/assets/locale/locale.constant-ka_GE.json
  32. 1
      ui-ngx/src/assets/locale/locale.constant-ko_KR.json
  33. 1
      ui-ngx/src/assets/locale/locale.constant-lt_LT.json
  34. 1
      ui-ngx/src/assets/locale/locale.constant-lv_LV.json
  35. 1
      ui-ngx/src/assets/locale/locale.constant-nl_BE.json
  36. 1
      ui-ngx/src/assets/locale/locale.constant-nl_NL.json
  37. 1
      ui-ngx/src/assets/locale/locale.constant-no_NO.json
  38. 1
      ui-ngx/src/assets/locale/locale.constant-pl_PL.json
  39. 1
      ui-ngx/src/assets/locale/locale.constant-pt_BR.json
  40. 1
      ui-ngx/src/assets/locale/locale.constant-ro_RO.json
  41. 1
      ui-ngx/src/assets/locale/locale.constant-sl_SI.json
  42. 1
      ui-ngx/src/assets/locale/locale.constant-tr_TR.json
  43. 1
      ui-ngx/src/assets/locale/locale.constant-uk_UA.json
  44. 1
      ui-ngx/src/assets/locale/locale.constant-zh_CN.json
  45. 1
      ui-ngx/src/assets/locale/locale.constant-zh_TW.json
  46. 33
      ui-ngx/src/styles.scss

86
ui-ngx/src/app/modules/login/pages/login/create-password.component.html

@ -17,55 +17,49 @@
--> -->
<div class="tb-create-password-content mat-app-background tb-dark flex flex-row items-center justify-center" style="width: 100%;"> <div class="tb-create-password-content mat-app-background tb-dark flex flex-row items-center justify-center" style="width: 100%;">
<mat-card appearance="raised" class="tb-create-password-card flex-initial"> <mat-card appearance="raised" class="tb-create-password-card flex-initial">
<mat-card-header> <mat-card-header class="px-6 pt-6">
<mat-card-title class="layout-padding"> <mat-card-title class="text-xl font-normal" translate>login.create-password</mat-card-title>
<span translate class="mat-headline-5">login.create-password</span>
</mat-card-title>
</mat-card-header> </mat-card-header>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> <div class="h-3 px-4"><div tb-toast></div></div>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading">
</mat-progress-bar> </mat-progress-bar>
<span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span> <span style="height: 4px;" *ngIf="!isLoading"></span>
<mat-card-content> <mat-card-content class="!p-6">
<form [formGroup]="createPassword" (ngSubmit)="onCreatePassword()"> <form [formGroup]="createPassword" (ngSubmit)="onCreatePassword()">
<fieldset [disabled]="isLoading$ | async"> <mat-form-field class="mat-block" appearance="outline">
<div tb-toast class="layout-padding flex flex-col"> <mat-label translate>common.password</mat-label>
<span style="height: 50px;"></span> <input matInput
<mat-form-field class="mat-block tb-appearance-transparent"> type="password"
<mat-label translate>common.password</mat-label> autofocus
<input matInput cdkOverlayOrigin
type="password" autocomplete="new-password"
autofocus #passwordTrigger="cdkOverlayOrigin"
cdkOverlayOrigin (focus)="passwordTooltip.onFocus()"
#passwordTrigger="cdkOverlayOrigin" (blur)="passwordTooltip.onBlur()"
(focus)="passwordTooltip.onFocus()" formControlName="newPassword"/>
(blur)="passwordTooltip.onBlur()" <mat-icon class="material-icons" matPrefix>lock</mat-icon>
formControlName="newPassword"/> <tb-toggle-password matSuffix></tb-toggle-password>
<mat-icon class="material-icons" matPrefix>lock</mat-icon> <mat-error *ngIf="this.createPassword.get('newPassword').errors">
<tb-toggle-password matSuffix></tb-toggle-password> {{ 'security.password-requirement.password-not-meet-requirements' | translate }}
<mat-error *ngIf="this.createPassword.get('newPassword').errors"> </mat-error>
{{ 'security.password-requirement.password-not-meet-requirements' | translate }} </mat-form-field>
</mat-error> <mat-form-field class="mat-block" style="margin-bottom: 18px" appearance="outline">
</mat-form-field> <mat-label translate>login.password-again</mat-label>
<mat-form-field class="mat-block tb-appearance-transparent"> <input matInput type="password" formControlName="newPassword2"/>
<mat-label translate>login.password-again</mat-label> <mat-icon class="material-icons" matPrefix>lock</mat-icon>
<input matInput type="password" formControlName="newPassword2"/> <tb-toggle-password matSuffix></tb-toggle-password>
<mat-icon class="material-icons" matPrefix>lock</mat-icon> <mat-error *ngIf="createPassword.get('newPassword2').hasError('passwordsNotMatch')">
<tb-toggle-password matSuffix></tb-toggle-password> {{ 'security.password-requirement.new-passwords-not-match' | translate }}
<mat-error *ngIf="createPassword.get('newPassword2').hasError('passwordsNotMatch')"> </mat-error>
{{ 'security.password-requirement.new-passwords-not-match' | translate }} </mat-form-field>
</mat-error> <div class="flex flex-col items-center justify-start gap-4 gt-xs:flex-row gt-xs:items-start gt-xs:justify-center">
</mat-form-field> <button mat-flat-button color="accent" type="submit" [disabled]="isLoading">
<div class="flex flex-col items-center justify-start gap-4 gt-xs:flex-row gt-xs:items-start gt-xs:justify-center"> {{ 'login.create-password' | translate }}
<button mat-raised-button color="accent" type="submit" [disabled]="(isLoading$ | async)"> </button>
{{ 'login.create-password' | translate }} <button mat-button type="button" [disabled]="isLoading" routerLink="/login">
</button> {{ 'action.cancel' | translate }}
<button mat-button type="button" [disabled]="(isLoading$ | async)" </button>
routerLink="/login"> </div>
{{ 'action.cancel' | translate }}
</button>
</div>
</div>
</fieldset>
</form> </form>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

1
ui-ngx/src/app/modules/login/pages/login/create-password.component.scss

@ -20,6 +20,7 @@
flex: 1 1 0; flex: 1 1 0;
.tb-create-password-content { .tb-create-password-content {
background-color: #eee; background-color: #eee;
--mdc-elevated-card-container-elevation: none;
.tb-create-password-card { .tb-create-password-card {
@media #{$mat-gt-xs} { @media #{$mat-gt-xs} {
width: 450px !important; width: 450px !important;

14
ui-ngx/src/app/modules/login/pages/login/create-password.component.ts

@ -16,7 +16,6 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { AuthService } from '@core/auth/auth.service'; import { AuthService } from '@core/auth/auth.service';
import { PageComponent } from '@shared/components/page.component';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { UserPasswordPolicy } from '@shared/models/settings.models'; import { UserPasswordPolicy } from '@shared/models/settings.models';
@ -27,17 +26,18 @@ import { passwordsMatchValidator, passwordStrengthValidator } from '@shared/mode
templateUrl: './create-password.component.html', templateUrl: './create-password.component.html',
styleUrls: ['./create-password.component.scss'] styleUrls: ['./create-password.component.scss']
}) })
export class CreatePasswordComponent extends PageComponent { export class CreatePasswordComponent {
passwordPolicy: UserPasswordPolicy; passwordPolicy: UserPasswordPolicy;
createPassword: FormGroup; createPassword: FormGroup;
isLoading = false;
private activateToken: string; private activateToken: string;
constructor(private route: ActivatedRoute, constructor(private route: ActivatedRoute,
private authService: AuthService, private authService: AuthService,
private fb: FormBuilder) { private fb: FormBuilder) {
super();
this.activateToken = this.route.snapshot.queryParams['activateToken'] || ''; this.activateToken = this.route.snapshot.queryParams['activateToken'] || '';
this.passwordPolicy = this.route.snapshot.data['passwordPolicy']; this.passwordPolicy = this.route.snapshot.data['passwordPolicy'];
@ -60,9 +60,11 @@ export class CreatePasswordComponent extends PageComponent {
if (this.createPassword.invalid) { if (this.createPassword.invalid) {
this.createPassword.markAllAsTouched(); this.createPassword.markAllAsTouched();
} else { } else {
this.authService.activate( this.isLoading = true
this.activateToken, this.authService.activate(this.activateToken, this.createPassword.get('newPassword').value, true)
this.createPassword.get('newPassword').value, true).subscribe(); .subscribe({
error: () => {this.isLoading = false;}
});
} }
} }
} }

22
ui-ngx/src/app/modules/login/pages/login/force-two-factor-auth-login.component.html

@ -41,7 +41,7 @@
</button> </button>
} }
@if (config) { @if (config) {
<button type="button" mat-raised-button color="accent" class="navigation w-full" (click)="cancelLogin()"> <button type="button" mat-flat-button color="accent" class="navigation w-full" (click)="cancelLogin()">
{{ 'login.login' | translate }} {{ 'login.login' | translate }}
</button> </button>
} }
@ -66,7 +66,7 @@
<p class="mat-body qr-code-description mb-4" translate>login.scan-qr-code</p> <p class="mat-body qr-code-description mb-4" translate>login.scan-qr-code</p>
<canvas class="flex-1" #canvas [style.display]="totpAuthURL ? 'block' : 'none'"></canvas> <canvas class="flex-1" #canvas [style.display]="totpAuthURL ? 'block' : 'none'"></canvas>
<p class="mat-body qr-code-description" translate>login.enter-key-manually</p> <p class="mat-body qr-code-description" translate>login.enter-key-manually</p>
<div class="flex flex-row items-center mb-8 w-full overflow-hidden"> <div class="mb-8 flex w-full flex-row items-center overflow-hidden">
<span tbTruncateWithTooltip class="w-full">{{ totpAuthURLSecret }}</span> <span tbTruncateWithTooltip class="w-full">{{ totpAuthURLSecret }}</span>
<tb-copy-button <tb-copy-button
class="attribute-copy" class="attribute-copy"
@ -79,7 +79,7 @@
> >
</tb-copy-button> </tb-copy-button>
</div> </div>
<div class="flex flex-col items-center justify-start gap-2 w-full"> <div class="flex w-full flex-col items-center justify-start gap-2">
<button type="button" mat-stroked-button class="navigation w-full" (click)="appState.set(ProvidersState.ENTER_CODE)"> <button type="button" mat-stroked-button class="navigation w-full" (click)="appState.set(ProvidersState.ENTER_CODE)">
{{ 'login.continue' | translate }} {{ 'login.continue' | translate }}
</button> </button>
@ -126,7 +126,7 @@
</tb-phone-input> </tb-phone-input>
</div> </div>
</form> </form>
<div class="flex flex-col items-center justify-start gap-2 w-full"> <div class="flex w-full flex-col items-center justify-start gap-2">
<button type="button" mat-stroked-button [disabled]="(isLoading$ | async) || smsConfigForm.invalid || !smsConfigForm.dirty" class="navigation w-full" (click)="sendSmsCode()"> <button type="button" mat-stroked-button [disabled]="(isLoading$ | async) || smsConfigForm.invalid || !smsConfigForm.dirty" class="navigation w-full" (click)="sendSmsCode()">
{{ 'login.send-code' | translate }} {{ 'login.send-code' | translate }}
</button> </button>
@ -162,7 +162,7 @@
<div tb-toast class="flex flex-col items-center justify-start"> <div tb-toast class="flex flex-col items-center justify-start">
<form [formGroup]="emailConfigForm" class="mb-8 w-full"> <form [formGroup]="emailConfigForm" class="mb-8 w-full">
<p class="mat-body step-description input" translate>login.email-description</p> <p class="mat-body step-description input" translate>login.email-description</p>
<mat-form-field class="mat-block input-container flex-1"> <mat-form-field class="mat-block input-container flex-1" appearance="outline">
<input matInput formControlName="email" <input matInput formControlName="email"
type="email" required type="email" required
placeholder="{{ 'login.email-label' | translate }}" /> placeholder="{{ 'login.email-label' | translate }}" />
@ -174,7 +174,7 @@
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</form> </form>
<div class="flex flex-col items-center justify-start gap-2 w-full"> <div class="flex w-full flex-col items-center justify-start gap-2">
<button type="button" mat-stroked-button [disabled]="(isLoading$ | async) || emailConfigForm.invalid" class="navigation w-full" (click)="sendEmailCode()"> <button type="button" mat-stroked-button [disabled]="(isLoading$ | async) || emailConfigForm.invalid" class="navigation w-full" (click)="sendEmailCode()">
{{ 'login.send-code' | translate }} {{ 'login.send-code' | translate }}
</button> </button>
@ -223,7 +223,7 @@
</button> </button>
</div> </div>
<p class="mat-body-2 description" translate>login.backup-code-warn</p> <p class="mat-body-2 description" translate>login.backup-code-warn</p>
<button type="button" mat-raised-button color="accent" class="navigation w-full" (click)="backupCodeState.set(BackupCodeState.SUCCESS)"> <button type="button" mat-flat-button color="accent" class="navigation w-full" (click)="backupCodeState.set(BackupCodeState.SUCCESS)">
{{ 'login.continue' | translate }} {{ 'login.continue' | translate }}
</button> </button>
</div> </div>
@ -259,7 +259,7 @@
} }
</p> </p>
<form [formGroup]="configForm" class="flex flex-col items-center justify-start"> <form [formGroup]="configForm" class="flex flex-col items-center justify-start">
<mat-form-field class="mat-block w-full"> <mat-form-field class="mat-block w-full" appearance="outline">
<input matInput formControlName="verificationCode" <input matInput formControlName="verificationCode"
maxlength="6" type="text" required maxlength="6" type="text" required
inputmode="numeric" pattern="[0-9]*" inputmode="numeric" pattern="[0-9]*"
@ -278,7 +278,7 @@
<mat-error>{{ 'login.verification-code-many-request' | translate }}</mat-error> <mat-error>{{ 'login.verification-code-many-request' | translate }}</mat-error>
} }
</mat-form-field> </mat-form-field>
<div class="flex flex-col items-center justify-start gap-2 w-full"> <div class="flex w-full flex-col items-center justify-start gap-2">
<button type="button" mat-flat-button color="accent" [disabled]="(isLoading$ | async) || configForm.invalid || !configForm.dirty" class="navigation w-full" (click)="saveConfig(providerType)"> <button type="button" mat-flat-button color="accent" [disabled]="(isLoading$ | async) || configForm.invalid || !configForm.dirty" class="navigation w-full" (click)="saveConfig(providerType)">
{{ 'login.confirm' | translate }} {{ 'login.confirm' | translate }}
</button> </button>
@ -300,8 +300,8 @@
<mat-card-content> <mat-card-content>
<p class="mat-body mb-16" translate>{{ twoFactorAuthProvidersSuccessCardTranslate.get(providerType).description | translate }}</p> <p class="mat-body mb-16" translate>{{ twoFactorAuthProvidersSuccessCardTranslate.get(providerType).description | translate }}</p>
<div class="flex flex-col items-center justify-start"> <div class="flex flex-col items-center justify-start">
<div class="flex flex-col items-center justify-start gap-2 w-full"> <div class="flex w-full flex-col items-center justify-start gap-2">
<button type="button" mat-raised-button color="accent" class="navigation w-full" (click)="cancelLogin()"> <button type="button" mat-flat-button color="accent" class="navigation w-full" (click)="cancelLogin()">
{{ 'login.login' | translate }} {{ 'login.login' | translate }}
</button> </button>
@if (isAnyProviderAvailable) { @if (isAnyProviderAvailable) {

1
ui-ngx/src/app/modules/login/pages/login/force-two-factor-auth-login.component.scss

@ -23,6 +23,7 @@
.tb-two-factor-auth-login-content { .tb-two-factor-auth-login-content {
background-color: #eee; background-color: #eee;
--mdc-elevated-card-container-elevation: none;
.progress-bar { .progress-bar {
z-index: 10; z-index: 10;

7
ui-ngx/src/app/modules/login/pages/login/link-expired.component.html

@ -23,16 +23,11 @@
<span class="mat-headline-5">{{ title | translate }}</span> <span class="mat-headline-5">{{ title | translate }}</span>
</mat-card-title> </mat-card-title>
</mat-card-header> </mat-card-header>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span>
<mat-card-content style="padding: 24px 16px"> <mat-card-content style="padding: 24px 16px">
<div>{{ message | translate }}</div> <div>{{ message | translate }}</div>
</mat-card-content> </mat-card-content>
<mat-card-actions class="flex flex-row justify-center"> <mat-card-actions class="flex flex-row justify-center">
<button mat-raised-button color="accent" <button mat-flat-button color="accent" routerLink="/login">
[disabled]="(isLoading$ | async)"
(click)="navigateToLoginPage()">
{{ 'login.login' | translate }} {{ 'login.login' | translate }}
</button> </button>
</mat-card-actions> </mat-card-actions>

1
ui-ngx/src/app/modules/login/pages/login/link-expired.component.scss

@ -20,6 +20,7 @@
flex: 1 1 0; flex: 1 1 0;
.tb-expired-link-content { .tb-expired-link-content {
background-color: #eee; background-color: #eee;
--mdc-elevated-card-container-elevation: none;
.tb-expired-link-card { .tb-expired-link-card {
letter-spacing: 0.15px; letter-spacing: 0.15px;
line-height: 24px; line-height: 24px;

16
ui-ngx/src/app/modules/login/pages/login/link-expired.component.ts

@ -15,33 +15,23 @@
/// ///
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Store } from '@ngrx/store'; import { ActivatedRoute } from '@angular/router';
import { AppState } from '@core/core.state';
import { PageComponent } from '@shared/components/page.component';
import { ActivatedRoute, Router } from '@angular/router';
@Component({ @Component({
selector: 'tb-link-expired', selector: 'tb-link-expired',
templateUrl: './link-expired.component.html', templateUrl: './link-expired.component.html',
styleUrls: ['./link-expired.component.scss'] styleUrls: ['./link-expired.component.scss']
}) })
export class LinkExpiredComponent extends PageComponent { export class LinkExpiredComponent {
isPasswordLinkExpired: boolean; isPasswordLinkExpired: boolean;
title: string; title: string;
message: string; message: string;
constructor(protected store: Store<AppState>, constructor(private route: ActivatedRoute) {
private route: ActivatedRoute,
private router: Router) {
super(store);
this.isPasswordLinkExpired = this.route.snapshot.data.passwordLinkExpired; this.isPasswordLinkExpired = this.route.snapshot.data.passwordLinkExpired;
this.title = this.isPasswordLinkExpired ? 'login.reset-password-link-expired' : 'login.activation-link-expired'; this.title = this.isPasswordLinkExpired ? 'login.reset-password-link-expired' : 'login.activation-link-expired';
this.message = this.isPasswordLinkExpired ? 'login.reset-password-link-expired-message' : this.message = this.isPasswordLinkExpired ? 'login.reset-password-link-expired-message' :
'login.activation-link-expired-message'; 'login.activation-link-expired-message';
} }
navigateToLoginPage() {
this.router.navigateByUrl('login');
}
} }

114
ui-ngx/src/app/modules/login/pages/login/login.component.html

@ -17,71 +17,67 @@
--> -->
<div class="tb-login-content mat-app-background tb-dark flex flex-1 items-center justify-center"> <div class="tb-login-content mat-app-background tb-dark flex flex-1 items-center justify-center">
<mat-card appearance="raised" style="max-height: 80vh; overflow-y: auto;"> <mat-card appearance="raised" style="max-height: 80vh; overflow-y: auto;">
<mat-card-content> <mat-card-content class="!p-6">
<form class="tb-login-form" [formGroup]="loginFormGroup" (ngSubmit)="login()"> <form class="tb-login-form flex flex-col" [formGroup]="loginFormGroup" (ngSubmit)="login()">
<fieldset class="flex flex-col"> <div class="flex flex-col items-center justify-start" style="padding: 16px 0;">
<div class="flex flex-col items-center justify-start" style="padding: 15px 0;"> <tb-logo link="https://thingsboard.io" target="_blank" class="login-logo"></tb-logo>
<tb-logo link="https://thingsboard.io" target="_blank" class="login-logo"></tb-logo> </div>
</div> <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading">
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> </mat-progress-bar>
</mat-progress-bar> <span style="height: 4px;" *ngIf="!isLoading"></span>
<span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span> <span tb-toast style="height: 20px;"></span>
<div tb-toast class="layout-padding flex flex-col"> <div class="flex flex-col">
<span style="height: 50px;"></span> @if(oauth2Clients?.length) {
@if(oauth2Clients?.length) { <div class="oauth-container tb-default flex flex-col gap-4">
<div class="oauth-container tb-default flex flex-col gap-4"> @if(oauth2Clients.length !== 1) {
@if(oauth2Clients.length === 1) { <div class="title">{{ 'login.sign-in-with' | translate: {name: ''} }}</div>
<a mat-stroked-button class="login-with-button" [href]=getOAuth2Uri(oauth2Clients[0])> }
<tb-icon matButtonIcon class="tb-mat-20">{{ oauth2Clients[0].icon }}</tb-icon> <div class="login-button-container flex flex-wrap justify-center gap-4">
{{ 'login.login-with' | translate: {name: oauth2Clients[0].name} }} @for(oauth2Client of oauth2Clients; track oauth2Client.url) {
</a> <a mat-stroked-button class="login-with-button"
} @else { [href]=getOAuth2Uri(oauth2Client)>
<div class="login-button-container flex flex-wrap justify-center gap-4"> <tb-icon matButtonIcon class="tb-mat-20">{{ oauth2Client.icon }}</tb-icon>
@for(oauth2Client of oauth2Clients; track oauth2Client.url) { @if(oauth2Clients.length === 1) {
<a mat-stroked-button class="login-with-button multiple" {{ 'login.sign-in-with' | translate: {name: oauth2Clients[0].name} }}
[matTooltip]="('login.login-with' | translate: {name: oauth2Client.name})" } @else {
[href]=getOAuth2Uri(oauth2Client)> {{ oauth2Client.name }}
@if(oauth2Client.icon) {
<tb-icon class="tb-mat-20">{{ oauth2Client.icon }}</tb-icon>
} @else {
<span>{{ oauth2Client.name }}</span>
}
</a>
} }
</div> </a>
} }
<div class="container-divider">
<div class="line"><mat-divider></mat-divider></div>
<div class="text">{{ "login.or" | translate | uppercase }}</div>
<div class="line"><mat-divider></mat-divider></div>
</div>
</div> </div>
} <div class="container-divider">
<mat-form-field class="tb-appearance-transparent"> <mat-divider class="tb-dark flex-1"></mat-divider>
<mat-label translate>login.username</mat-label> <div class="text">{{ "login.or" | translate | uppercase }}</div>
<input id="username-input" matInput type="email" autofocus formControlName="username"/> <mat-divider class="tb-dark flex-1"></mat-divider>
<mat-icon matPrefix>email</mat-icon> </div>
<mat-error *ngIf="loginFormGroup.get('username').invalid">
{{ 'login.invalid-email-format' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="tb-appearance-transparent">
<mat-label translate>common.password</mat-label>
<input id="password-input" matInput type="password" formControlName="password"/>
<tb-toggle-password matSuffix></tb-toggle-password>
<mat-icon matPrefix>lock</mat-icon>
</mat-form-field>
<div class="forgot-password flex items-center justify-end">
<button class="tb-reset-password" mat-button type="button" routerLink="/login/resetPasswordRequest">
{{ (passwordViolation ? 'login.reset-password' : 'login.forgot-password') | translate }}
</button>
</div> </div>
<div class="tb-action-button flex flex-col"> } @else {
<button mat-raised-button color="accent" [disabled]="(isLoading$ | async)" <div class="oauth-container flex flex-col">
type="submit">{{ 'login.login' | translate }}</button> <div class="title !mb-4" translate>login.sign-in-to-your-account</div>
</div> </div>
}
<mat-form-field class="tb-autofilled" appearance="outline" hideRequiredMarker>
<mat-label translate>login.username</mat-label>
<input id="username-input" matInput type="email" autofocus formControlName="username" autocomplete="username"/>
<mat-icon matPrefix>email</mat-icon>
<mat-error *ngIf="loginFormGroup.get('username').invalid">
{{ 'login.invalid-email-format' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="tb-autofilled" appearance="outline">
<mat-label translate>common.password</mat-label>
<input id="password-input" matInput type="password" formControlName="password" autocomplete="current-password"/>
<tb-toggle-password matSuffix></tb-toggle-password>
<mat-icon matPrefix>lock</mat-icon>
</mat-form-field>
<button mat-flat-button color="accent" [disabled]="isLoading"
class="tb-action-button" type="submit">{{ 'login.sign-in' | translate }}</button>
<div class="forgot-password flex items-center justify-end gap-5">
<button class="tb-reset-password" mat-button type="button" routerLink="/login/resetPasswordRequest">
{{ (passwordViolation ? 'login.reset-password' : 'login.forgot-password') | translate }}
</button>
</div> </div>
</fieldset> </div>
</form> </form>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

78
ui-ngx/src/app/modules/login/pages/login/login.component.scss

@ -23,48 +23,49 @@
margin-top: 36px; margin-top: 36px;
margin-bottom: 76px; margin-bottom: 76px;
background-color: #eee; background-color: #eee;
--mdc-elevated-card-container-elevation: none;
.tb-login-form { .tb-login-form {
@media #{$mat-gt-xs} { @media #{$mat-gt-xs} {
width: 550px !important; width: 480px !important;
} }
.forgot-password { .forgot-password {
padding: 0 0.5em 1em; --mat-text-button-horizontal-padding: 0;
.tb-reset-password { --mdc-text-button-container-height: 20px;
padding: 0 6px;
}
} }
.tb-action-button{ .tb-action-button{
padding: 20px 0 16px; margin: 20px 0 16px;
} }
} }
.oauth-container{ .container-divider {
padding: 0; display: flex;
flex-direction: row;
.container-divider { align-items: center;
display: flex; justify-content: center;
flex-direction: row; width: 100%;
align-items: center; margin-bottom: 16px;
justify-content: center;
width: 100%;
.line { .mat-divider-horizontal{
flex: 1; --mat-divider-color: var(--mdc-outlined-text-field-outline-color);
} }
.mat-divider-horizontal{ .text {
position: relative; padding-right: 8px;
} padding-left: 8px;
font-size: 16px;
line-height: 24px;
}
}
.text { .oauth-container{
padding-right: 8px; .title {
padding-left: 8px; font-size: 20px;
font-size: 16px; line-height: 24px;
line-height: 24px; letter-spacing: .1px;
letter-spacing: 0.15px; text-align: center;
} margin-bottom: 4px;
} }
.material-icons{ .material-icons{
@ -74,33 +75,32 @@
a.login-with-button { a.login-with-button {
color: rgba(black, 0.87); color: rgba(black, 0.87);
background-color: map-get($tb-dark-theme-background, raised-button); background-color: #ffffff;
} }
.login-button-container { .login-button-container {
a.login-with-button { a.login-with-button {
--mdc-outlined-button-container-shape: 8px; max-width: 170px;
max-width: 123px;
min-width: 60px; min-width: 60px;
flex-grow: 1; flex-grow: 1;
flex-basis: 120px; flex-basis: 130px;
.tb-mat-20 { .tb-mat-20 {
margin: 0; margin-right: 12px;
vertical-align: text-top;
} }
} }
&:has(> :nth-child(2):last-child) { &:has(> :nth-child(1):last-child) {
a.login-with-button { a.login-with-button {
max-width: 180px; max-width: 100%;
flex-basis: 180px;
} }
} }
&:has(> :nth-child(3):last-child) { &:has(> :nth-child(2):last-child),
&:has(> :nth-child(4):last-child) {
a.login-with-button { a.login-with-button {
max-width: 180px; max-width: 100%;
flex-basis: 180px;
} }
} }
} }

25
ui-ngx/src/app/modules/login/pages/login/login.component.ts

@ -16,9 +16,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { AuthService } from '@core/auth/auth.service'; import { AuthService } from '@core/auth/auth.service';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { PageComponent } from '@shared/components/page.component';
import { UntypedFormBuilder, Validators } from '@angular/forms'; import { UntypedFormBuilder, Validators } from '@angular/forms';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { Constants } from '@shared/models/constants'; import { Constants } from '@shared/models/constants';
@ -31,9 +28,10 @@ import { validateEmail } from '@app/core/utils';
templateUrl: './login.component.html', templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'] styleUrls: ['./login.component.scss']
}) })
export class LoginComponent extends PageComponent implements OnInit { export class LoginComponent implements OnInit {
passwordViolation = false; passwordViolation = false;
isLoading = false;
loginFormGroup = this.fb.group({ loginFormGroup = this.fb.group({
username: ['', [Validators.required, validateEmail]], username: ['', [Validators.required, validateEmail]],
@ -41,11 +39,9 @@ export class LoginComponent extends PageComponent implements OnInit {
}); });
oauth2Clients: Array<OAuth2ClientLoginInfo> = null; oauth2Clients: Array<OAuth2ClientLoginInfo> = null;
constructor(protected store: Store<AppState>, constructor(private authService: AuthService,
private authService: AuthService,
public fb: UntypedFormBuilder, public fb: UntypedFormBuilder,
private router: Router) { private router: Router) {
super(store);
} }
ngOnInit() { ngOnInit() {
@ -54,9 +50,11 @@ export class LoginComponent extends PageComponent implements OnInit {
login(): void { login(): void {
if (this.loginFormGroup.valid) { if (this.loginFormGroup.valid) {
this.authService.login(this.loginFormGroup.value).subscribe( this.isLoading = true;
() => {}, this.authService.login(this.loginFormGroup.value).subscribe({
(error: HttpErrorResponse) => { next: () => {},
error: (error: HttpErrorResponse) => {
this.isLoading = false;
if (error && error.error && error.error.errorCode) { if (error && error.error && error.error.errorCode) {
if (error.error.errorCode === Constants.serverErrorCode.credentialsExpired) { if (error.error.errorCode === Constants.serverErrorCode.credentialsExpired) {
this.router.navigateByUrl(`login/resetExpiredPassword?resetToken=${error.error.resetToken}`); this.router.navigateByUrl(`login/resetExpiredPassword?resetToken=${error.error.resetToken}`);
@ -65,12 +63,9 @@ export class LoginComponent extends PageComponent implements OnInit {
} }
} }
} }
);
} else {
Object.keys(this.loginFormGroup.controls).forEach(field => {
const control = this.loginFormGroup.get(field);
control.markAsTouched({onlySelf: true});
}); });
} else {
this.loginFormGroup.markAllAsTouched();
} }
} }

48
ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html

@ -18,38 +18,32 @@
<div class="tb-request-password-reset-content mat-app-background tb-dark flex flex-row items-center justify-center" <div class="tb-request-password-reset-content mat-app-background tb-dark flex flex-row items-center justify-center"
style="width: 100%;"> style="width: 100%;">
<mat-card appearance="raised" class="tb-request-password-reset-card flex flex-initial"> <mat-card appearance="raised" class="tb-request-password-reset-card flex flex-initial">
<mat-card-header> <mat-card-header class="px-6 pt-6">
<mat-card-title class="layout-padding"> <mat-card-title class="text-xl font-normal" translate>login.request-password-reset</mat-card-title>
<span translate class="mat-headline-5">login.request-password-reset</span>
</mat-card-title>
</mat-card-header> </mat-card-header>
<div class="h-3 px-4"><div tb-toast></div></div>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar> </mat-progress-bar>
<span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span> <span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span>
<mat-card-content> <mat-card-content class="!p-6">
<form [formGroup]="requestPasswordRequest" (ngSubmit)="sendResetPasswordLink()"> <form [formGroup]="requestPasswordRequest" (ngSubmit)="sendResetPasswordLink()">
<fieldset [disabled]="isLoading$ | async"> <mat-form-field class="mat-block" style="margin-bottom: 18px" appearance="outline" hideRequiredMarker>
<div tb-toast class="layout-padding flex flex-col"> <mat-label translate>login.email</mat-label>
<span style="height: 50px;"></span> <input matInput type="email" autofocus formControlName="email"/>
<mat-form-field class="mat-block tb-appearance-transparent" hideRequiredMarker> <mat-icon class="material-icons" matPrefix>email</mat-icon>
<mat-label translate>login.email</mat-label> <mat-error *ngIf="requestPasswordRequest.get('email').invalid">
<input matInput type="email" autofocus formControlName="email"/> {{ 'user.invalid-email-format' | translate }}
<mat-icon class="material-icons" matPrefix>email</mat-icon> </mat-error>
<mat-error *ngIf="requestPasswordRequest.get('email').invalid"> </mat-form-field>
{{ 'user.invalid-email-format' | translate }} <div class="flex flex-col items-center justify-start gap-4 gt-xs:flex-row gt-xs:items-start gt-xs:justify-center">
</mat-error> <button mat-flat-button color="accent" type="submit" [disabled]="(isLoading$ | async) || this.clicked">
</mat-form-field> {{ 'login.request-password-reset' | translate }}
<div class="flex flex-col items-center justify-start gap-4 gt-xs:flex-row gt-xs:items-start gt-xs:justify-center"> </button>
<button mat-raised-button color="accent" type="submit" [disabled]="(isLoading$ | async) || this.clicked"> <button mat-button type="button" [disabled]="(isLoading$ | async)"
{{ 'login.request-password-reset' | translate }} routerLink="/login">
</button> {{ 'action.cancel' | translate }}
<button mat-button type="button" [disabled]="(isLoading$ | async)" </button>
routerLink="/login"> </div>
{{ 'action.cancel' | translate }}
</button>
</div>
</div>
</fieldset>
</form> </form>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

1
ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.scss

@ -20,6 +20,7 @@
flex: 1 1 0; flex: 1 1 0;
.tb-request-password-reset-content { .tb-request-password-reset-content {
background-color: #eee; background-color: #eee;
--mdc-elevated-card-container-elevation: none;
.tb-request-password-reset-card { .tb-request-password-reset-card {
@media #{$mat-gt-xs} { @media #{$mat-gt-xs} {
width: 450px !important; width: 450px !important;

5
ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts

@ -29,7 +29,7 @@ import { validateEmail } from '@app/core/utils';
templateUrl: './reset-password-request.component.html', templateUrl: './reset-password-request.component.html',
styleUrls: ['./reset-password-request.component.scss'] styleUrls: ['./reset-password-request.component.scss']
}) })
export class ResetPasswordRequestComponent extends PageComponent implements OnInit { export class ResetPasswordRequestComponent extends PageComponent {
clicked: boolean = false; clicked: boolean = false;
@ -44,9 +44,6 @@ export class ResetPasswordRequestComponent extends PageComponent implements OnIn
super(store); super(store);
} }
ngOnInit() {
}
disableInputs() { disableInputs() {
this.requestPasswordRequest.disable(); this.requestPasswordRequest.disable();
this.clicked = true; this.clicked = true;

90
ui-ngx/src/app/modules/login/pages/login/reset-password.component.html

@ -17,58 +17,52 @@
--> -->
<div class="tb-reset-password-content mat-app-background tb-dark flex flex-row items-center justify-center" style="width: 100%;"> <div class="tb-reset-password-content mat-app-background tb-dark flex flex-row items-center justify-center" style="width: 100%;">
<mat-card appearance="raised" class="tb-reset-password-card flex-initial"> <mat-card appearance="raised" class="tb-reset-password-card flex-initial">
<mat-card-header> <mat-card-header class="px-6 pt-6">
<mat-card-title class="layout-padding tb-card-title mb-4"> <mat-card-title class="text-xl font-normal" translate>login.password-reset</mat-card-title>
<span translate class="mat-headline-5 tb-card-title">login.password-reset</span> <mat-card-subtitle *ngIf="isExpiredPassword" class="mt-4 whitespace-pre-line text-sm font-normal" translate>
</mat-card-title> login.expired-password-reset-message
<mat-card-subtitle *ngIf="isExpiredPassword" class="layout-padding tb-card-title">
<div class="tb-card-title whitespace-pre-line">{{ 'login.expired-password-reset-message' | translate }}</div>
</mat-card-subtitle> </mat-card-subtitle>
</mat-card-header> </mat-card-header>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> <div class="h-3 px-4"><div tb-toast></div></div>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading">
</mat-progress-bar> </mat-progress-bar>
<span style="height: 4px;" *ngIf="!(isLoading$ | async)"></span> <span style="height: 4px;" *ngIf="!isLoading"></span>
<mat-card-content> <mat-card-content class="!p-6">
<form [formGroup]="resetPassword" (ngSubmit)="onResetPassword()"> <form [formGroup]="resetPassword" (ngSubmit)="onResetPassword()">
<fieldset [disabled]="isLoading$ | async"> <mat-form-field class="mat-block" appearance="outline">
<div tb-toast class="layout-padding flex flex-col"> <mat-label translate>login.new-password</mat-label>
<span style="height: 50px;"></span> <input matInput
<mat-form-field class="mat-block tb-appearance-transparent"> type="password"
<mat-label translate>login.new-password</mat-label> autofocus
<input matInput cdkOverlayOrigin
type="password" autocomplete="new-password"
autofocus #passwordTrigger="cdkOverlayOrigin"
cdkOverlayOrigin (focus)="passwordTooltip.onFocus()"
#passwordTrigger="cdkOverlayOrigin" (blur)="passwordTooltip.onBlur()"
(focus)="passwordTooltip.onFocus()" formControlName="newPassword"/>
(blur)="passwordTooltip.onBlur()" <mat-icon class="material-icons" matPrefix>lock</mat-icon>
formControlName="newPassword"/> <tb-toggle-password matSuffix></tb-toggle-password>
<mat-icon class="material-icons" matPrefix>lock</mat-icon> <mat-error *ngIf="this.resetPassword.get('newPassword').errors">
<tb-toggle-password matSuffix></tb-toggle-password> {{ 'security.password-requirement.password-not-meet-requirements' | translate }}
<mat-error *ngIf="this.resetPassword.get('newPassword').errors"> </mat-error>
{{ 'security.password-requirement.password-not-meet-requirements' | translate }} </mat-form-field>
</mat-error> <mat-form-field class="mat-block" appearance="outline" style="margin-bottom: 18px">
</mat-form-field> <mat-label translate>login.new-password-again</mat-label>
<mat-form-field class="mat-block tb-appearance-transparent"> <input matInput type="password" formControlName="newPassword2"/>
<mat-label translate>login.new-password-again</mat-label> <mat-icon class="material-icons" matPrefix>lock</mat-icon>
<input matInput type="password" formControlName="newPassword2"/> <tb-toggle-password matSuffix></tb-toggle-password>
<mat-icon class="material-icons" matPrefix>lock</mat-icon> <mat-error *ngIf="resetPassword.get('newPassword2').hasError('passwordsNotMatch')">
<tb-toggle-password matSuffix></tb-toggle-password> {{ 'security.password-requirement.new-passwords-not-match' | translate }}
<mat-error *ngIf="resetPassword.get('newPassword2').hasError('passwordsNotMatch')"> </mat-error>
{{ 'security.password-requirement.new-passwords-not-match' | translate }} </mat-form-field>
</mat-error> <div class="flex flex-col items-center justify-start gap-4 gt-sm:flex-row gt-sm:items-start gt-sm:justify-center">
</mat-form-field> <button mat-flat-button color="accent" type="submit" [disabled]="isLoading">
<div class="flex flex-col items-center justify-start gap-4 gt-sm:flex-row gt-sm:items-start gt-sm:justify-center"> {{ 'login.reset-password' | translate }}
<button mat-raised-button color="accent" type="submit" [disabled]="(isLoading$ | async)"> </button>
{{ 'login.reset-password' | translate }} <button mat-button type="button" [disabled]="isLoading" routerLink="/login">
</button> {{ 'action.cancel' | translate }}
<button mat-button type="button" [disabled]="(isLoading$ | async)" </button>
routerLink="/login"> </div>
{{ 'action.cancel' | translate }}
</button>
</div>
</div>
</fieldset>
</form> </form>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

6
ui-ngx/src/app/modules/login/pages/login/reset-password.component.scss

@ -20,15 +20,11 @@
flex: 1 1 0; flex: 1 1 0;
.tb-reset-password-content { .tb-reset-password-content {
background-color: #eee; background-color: #eee;
--mdc-elevated-card-container-elevation: none;
.tb-reset-password-card { .tb-reset-password-card {
@media #{$mat-gt-sm} { @media #{$mat-gt-sm} {
width: 450px !important; width: 450px !important;
} }
} }
.tb-card-title{
padding-top: 0;
padding-bottom: 0;
}
} }
} }

17
ui-ngx/src/app/modules/login/pages/login/reset-password.component.ts

@ -16,7 +16,6 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { AuthService } from '@core/auth/auth.service'; import { AuthService } from '@core/auth/auth.service';
import { PageComponent } from '@shared/components/page.component';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { UserPasswordPolicy } from '@shared/models/settings.models'; import { UserPasswordPolicy } from '@shared/models/settings.models';
@ -27,9 +26,10 @@ import { passwordsMatchValidator, passwordStrengthValidator } from '@shared/mode
templateUrl: './reset-password.component.html', templateUrl: './reset-password.component.html',
styleUrls: ['./reset-password.component.scss'] styleUrls: ['./reset-password.component.scss']
}) })
export class ResetPasswordComponent extends PageComponent { export class ResetPasswordComponent {
isExpiredPassword: boolean; isExpiredPassword: boolean;
isLoading = false;
resetPassword: FormGroup; resetPassword: FormGroup;
passwordPolicy: UserPasswordPolicy; passwordPolicy: UserPasswordPolicy;
@ -40,7 +40,6 @@ export class ResetPasswordComponent extends PageComponent {
private router: Router, private router: Router,
private authService: AuthService, private authService: AuthService,
private fb: FormBuilder) { private fb: FormBuilder) {
super();
this.resetToken = this.route.snapshot.queryParams['resetToken'] || ''; this.resetToken = this.route.snapshot.queryParams['resetToken'] || '';
this.passwordPolicy = this.route.snapshot.data['passwordPolicy']; this.passwordPolicy = this.route.snapshot.data['passwordPolicy'];
@ -62,13 +61,13 @@ export class ResetPasswordComponent extends PageComponent {
onResetPassword() { onResetPassword() {
if (this.resetPassword.invalid) { if (this.resetPassword.invalid) {
this.resetPassword.markAllAsTouched(); this.resetPassword.markAllAsTouched();
} else { } else {
this.authService.resetPassword( this.isLoading = true;
this.resetToken, this.authService.resetPassword(this.resetToken, this.resetPassword.get('newPassword').value).subscribe({
this.resetPassword.get('newPassword').value).subscribe( next: () => this.router.navigateByUrl('login'),
() => this.router.navigateByUrl('login') error: () => {this.isLoading = false;}
); });
} }
} }
} }

4
ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.html

@ -41,7 +41,7 @@
<div tb-toast class="flex flex-col"> <div tb-toast class="flex flex-col">
<p class="mat-body">{{ providerDescription }}</p> <p class="mat-body">{{ providerDescription }}</p>
<div class="code-block flex flex-row gap-2"> <div class="code-block flex flex-row gap-2">
<mat-form-field class="mat-block tb-appearance-transparent flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<input matInput formControlName="verificationCode" <input matInput formControlName="verificationCode"
required [maxlength]="maxLengthInput" type="text" required [maxlength]="maxLengthInput" type="text"
[attr.inputmode]="inputMode" [pattern]="pattern" [attr.inputmode]="inputMode" [pattern]="pattern"
@ -62,7 +62,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<span style="height: 50px;"></span> <span style="height: 50px;"></span>
<button mat-raised-button <button mat-flat-button
color="accent" color="accent"
[disabled]="(isLoading$ | async) || verificationForm.invalid" [disabled]="(isLoading$ | async) || verificationForm.invalid"
type="submit"> type="submit">

1
ui-ngx/src/app/modules/login/pages/login/two-factor-auth-login.component.scss

@ -21,6 +21,7 @@
.tb-two-factor-auth-login-content { .tb-two-factor-auth-login-content {
background-color: #eee; background-color: #eee;
--mdc-elevated-card-container-elevation: none;
.tb-two-factor-auth-login-card { .tb-two-factor-auth-login-card {
padding: 48px 48px 48px 16px; padding: 48px 48px 48px 16px;

1
ui-ngx/src/assets/locale/locale.constant-ar_AE.json

@ -3882,7 +3882,6 @@
"two-factor-authentication": "المصادقة الثنائية العاملة", "two-factor-authentication": "المصادقة الثنائية العاملة",
"passwords-mismatch-error": "يجب أن تكون كلمات المرور المدخلة متطابقة!", "passwords-mismatch-error": "يجب أن تكون كلمات المرور المدخلة متطابقة!",
"password-again": "إعادة كلمة المرور", "password-again": "إعادة كلمة المرور",
"sign-in": "الرجاء تسجيل الدخول",
"username": "اسم المستخدم (البريد الإلكتروني)", "username": "اسم المستخدم (البريد الإلكتروني)",
"remember-me": "تذكرني", "remember-me": "تذكرني",
"forgot-password": "نسيت كلمة المرور؟", "forgot-password": "نسيت كلمة المرور؟",

1
ui-ngx/src/assets/locale/locale.constant-ca_ES.json

@ -3233,7 +3233,6 @@
"create-password": "Crear contrasenya", "create-password": "Crear contrasenya",
"passwords-mismatch-error": "¡Les contraseñas introduïdes han de ser iguals!", "passwords-mismatch-error": "¡Les contraseñas introduïdes han de ser iguals!",
"password-again": "Repetiu la contrasenya de nou", "password-again": "Repetiu la contrasenya de nou",
"sign-in": "Per favor, inicieu sessió",
"username": "Nom d'usuari (correu electrònic)", "username": "Nom d'usuari (correu electrònic)",
"remember-me": "Recordar-me", "remember-me": "Recordar-me",
"forgot-password": "Ha oblidat la contrasenya?", "forgot-password": "Ha oblidat la contrasenya?",

1
ui-ngx/src/assets/locale/locale.constant-cs_CZ.json

@ -2151,7 +2151,6 @@
"create-password": "Vytvořit heslo", "create-password": "Vytvořit heslo",
"passwords-mismatch-error": "Zadaná hesla se musí shodovat!", "passwords-mismatch-error": "Zadaná hesla se musí shodovat!",
"password-again": "Heslo znovu", "password-again": "Heslo znovu",
"sign-in": "Prosím zaregistrujte se",
"username": "Uživatelské jméno (email)", "username": "Uživatelské jméno (email)",
"remember-me": "Zapamatovat si mě", "remember-me": "Zapamatovat si mě",
"forgot-password": "Zapomněli jste heslo?", "forgot-password": "Zapomněli jste heslo?",

1
ui-ngx/src/assets/locale/locale.constant-de_DE.json

@ -3768,7 +3768,6 @@
"two-factor-authentication": "Zwei-Faktor-Authentifizierung", "two-factor-authentication": "Zwei-Faktor-Authentifizierung",
"passwords-mismatch-error": "Die eingegebenen Passwörter müssen übereinstimmen!", "passwords-mismatch-error": "Die eingegebenen Passwörter müssen übereinstimmen!",
"password-again": "Passwort wiederholen", "password-again": "Passwort wiederholen",
"sign-in": "Bitte anmelden",
"username": "Benutzername (E-Mail)", "username": "Benutzername (E-Mail)",
"remember-me": "Angemeldet bleiben", "remember-me": "Angemeldet bleiben",
"forgot-password": "Passwort vergessen?", "forgot-password": "Passwort vergessen?",

1
ui-ngx/src/assets/locale/locale.constant-el_GR.json

@ -3768,7 +3768,6 @@
"two-factor-authentication": "Έλεγχος ταυτότητας δύο παραγόντων", "two-factor-authentication": "Έλεγχος ταυτότητας δύο παραγόντων",
"passwords-mismatch-error": "Οι κωδικοί πρέπει να είναι ίδιοι!", "passwords-mismatch-error": "Οι κωδικοί πρέπει να είναι ίδιοι!",
"password-again": "Επαλήθευση κωδικού", "password-again": "Επαλήθευση κωδικού",
"sign-in": "Παρακαλώ συνδεθείτε",
"username": "Όνομα χρήστη (email)", "username": "Όνομα χρήστη (email)",
"remember-me": "Να με θυμάσαι", "remember-me": "Να με θυμάσαι",
"forgot-password": "Ξεχάσατε τον κωδικό;", "forgot-password": "Ξεχάσατε τον κωδικό;",

15
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -4259,24 +4259,25 @@
}, },
"login": { "login": {
"login": "Login", "login": "Login",
"request-password-reset": "Request Password Reset", "request-password-reset": "Request password reset",
"reset-password": "Reset Password", "reset-password": "Reset password",
"create-password": "Create Password", "create-password": "Create password",
"two-factor-authentication": "Two factor authentication", "two-factor-authentication": "Two factor authentication",
"passwords-mismatch-error": "Entered passwords must be same!", "passwords-mismatch-error": "Entered passwords must be same!",
"password-again": "Password again", "password-again": "Password again",
"sign-in": "Please sign in", "sign-in": "Sign in",
"username": "Username (email)", "username": "Username (email)",
"remember-me": "Remember me", "remember-me": "Remember me",
"forgot-password": "Forgot Password?", "forgot-password": "Forgot your password?",
"password-reset": "Password Reset", "password-reset": "Password reset",
"expired-password-reset-message": "Your password has expired! \nPlease enter a new password.", "expired-password-reset-message": "Your password has expired! \nPlease enter a new password.",
"new-password": "New password", "new-password": "New password",
"new-password-again": "Confirm new password", "new-password-again": "Confirm new password",
"password-link-sent-message": "Reset link has been sent", "password-link-sent-message": "Reset link has been sent",
"email": "Email", "email": "Email",
"invalid-email-format": "Invalid email format.", "invalid-email-format": "Invalid email format.",
"login-with": "Login with {{name}}", "sign-in-with": "Sign in with {{name}}",
"sign-in-to-your-account": "Sign in to your account",
"or": "or", "or": "or",
"error": "Login error", "error": "Login error",
"verify-your-identity": "Verify your identity", "verify-your-identity": "Verify your identity",

1
ui-ngx/src/assets/locale/locale.constant-es_ES.json

@ -3768,7 +3768,6 @@
"two-factor-authentication": "Autenticación de dos factores", "two-factor-authentication": "Autenticación de dos factores",
"passwords-mismatch-error": "¡Las contraseñas ingresadas deben coincidir!", "passwords-mismatch-error": "¡Las contraseñas ingresadas deben coincidir!",
"password-again": "Repetir contraseña", "password-again": "Repetir contraseña",
"sign-in": "Por favor inicia sesión",
"username": "Nombre de usuario (correo electrónico)", "username": "Nombre de usuario (correo electrónico)",
"remember-me": "Recordarme", "remember-me": "Recordarme",
"forgot-password": "¿Olvidaste tu contraseña?", "forgot-password": "¿Olvidaste tu contraseña?",

1
ui-ngx/src/assets/locale/locale.constant-fa_IR.json

@ -1131,7 +1131,6 @@
"create-password": "ايجاد رمز عبور", "create-password": "ايجاد رمز عبور",
"passwords-mismatch-error": "!رمزهاي عبور وارد شده بايد مشابه باشند", "passwords-mismatch-error": "!رمزهاي عبور وارد شده بايد مشابه باشند",
"password-again": "رمز عبور دوباره", "password-again": "رمز عبور دوباره",
"sign-in": "لطفا وارد شويد",
"username": "(نام کاربري (پست الکترونيک", "username": "(نام کاربري (پست الکترونيک",
"remember-me": "مرا به خاطر داشته باش", "remember-me": "مرا به خاطر داشته باش",
"forgot-password": "رمز عبور را فراموش کرده ايد؟", "forgot-password": "رمز عبور را فراموش کرده ايد؟",

1
ui-ngx/src/assets/locale/locale.constant-fr_FR.json

@ -3768,7 +3768,6 @@
"two-factor-authentication": "Authentification à deux facteurs", "two-factor-authentication": "Authentification à deux facteurs",
"passwords-mismatch-error": "Les mots de passe saisis doivent être identiques !", "passwords-mismatch-error": "Les mots de passe saisis doivent être identiques !",
"password-again": "Ressaisir le mot de passe", "password-again": "Ressaisir le mot de passe",
"sign-in": "Veuillez vous connecter",
"username": "Nom d'utilisateur (email)", "username": "Nom d'utilisateur (email)",
"remember-me": "Se souvenir de moi", "remember-me": "Se souvenir de moi",
"forgot-password": "Mot de passe oublié ?", "forgot-password": "Mot de passe oublié ?",

1
ui-ngx/src/assets/locale/locale.constant-it_IT.json

@ -3652,7 +3652,6 @@
"two-factor-authentication": "Autenticazione a due fattori", "two-factor-authentication": "Autenticazione a due fattori",
"passwords-mismatch-error": "Le password inserite devono coincidere!", "passwords-mismatch-error": "Le password inserite devono coincidere!",
"password-again": "Ripeti password", "password-again": "Ripeti password",
"sign-in": "Accedi",
"username": "Nome utente (email)", "username": "Nome utente (email)",
"remember-me": "Ricordami", "remember-me": "Ricordami",
"forgot-password": "Password dimenticata?", "forgot-password": "Password dimenticata?",

1
ui-ngx/src/assets/locale/locale.constant-ja_JP.json

@ -1016,7 +1016,6 @@
"create-password": "パスワードの作成", "create-password": "パスワードの作成",
"passwords-mismatch-error": "入力されたパスワードは同じでなければなりません!", "passwords-mismatch-error": "入力されたパスワードは同じでなければなりません!",
"password-again": "パスワードをもう一度", "password-again": "パスワードをもう一度",
"sign-in": "サインインしてください",
"username": "ユーザー名(電子メール)", "username": "ユーザー名(電子メール)",
"remember-me": "ログイン情報を記憶", "remember-me": "ログイン情報を記憶",
"forgot-password": "パスワードをお忘れですか?", "forgot-password": "パスワードをお忘れですか?",

1
ui-ngx/src/assets/locale/locale.constant-ka_GE.json

@ -1250,7 +1250,6 @@
"create-password": "პაროლის შექმნა", "create-password": "პაროლის შექმნა",
"passwords-mismatch-error": "პაროლები არ ემთხვევა", "passwords-mismatch-error": "პაროლები არ ემთხვევა",
"password-again": "შეიყვანეთ პაროლი თავიდა", "password-again": "შეიყვანეთ პაროლი თავიდა",
"sign-in": "სისტემაში შესვლა",
"username": "მომხმარებლის სახელი", "username": "მომხმარებლის სახელი",
"remember-me": "დამახსოვრება", "remember-me": "დამახსოვრება",
"forgot-password": " დაგავიწყდა პაროლი?", "forgot-password": " დაგავიწყდა პაროლი?",

1
ui-ngx/src/assets/locale/locale.constant-ko_KR.json

@ -1708,7 +1708,6 @@
"create-password": "비밀번호 생성", "create-password": "비밀번호 생성",
"passwords-mismatch-error": "입력된 비밀번호는 같아야 합니다!", "passwords-mismatch-error": "입력된 비밀번호는 같아야 합니다!",
"password-again": "비밀번호 확인", "password-again": "비밀번호 확인",
"sign-in": "로그인",
"username": "사용자명 (이메일)", "username": "사용자명 (이메일)",
"remember-me": "아이디 저장", "remember-me": "아이디 저장",
"forgot-password": "비밀번호찾기", "forgot-password": "비밀번호찾기",

1
ui-ngx/src/assets/locale/locale.constant-lt_LT.json

@ -3767,7 +3767,6 @@
"two-factor-authentication": "Dviejų veiksnių autentifikavimas", "two-factor-authentication": "Dviejų veiksnių autentifikavimas",
"passwords-mismatch-error": "Įvesti slaptažodžiai turi sutapti!", "passwords-mismatch-error": "Įvesti slaptažodžiai turi sutapti!",
"password-again": "Pakartokite slaptažodį", "password-again": "Pakartokite slaptažodį",
"sign-in": "Prisijungti",
"username": "Vartotojo vardas (el. paštas)", "username": "Vartotojo vardas (el. paštas)",
"remember-me": "Prisiminti mane", "remember-me": "Prisiminti mane",
"forgot-password": "Pamiršote slaptažodį?", "forgot-password": "Pamiršote slaptažodį?",

1
ui-ngx/src/assets/locale/locale.constant-lv_LV.json

@ -1178,7 +1178,6 @@
"create-password": "Radīt paroli", "create-password": "Radīt paroli",
"passwords-mismatch-error": "Ievadītajai parolei ir jāsakrīt!", "passwords-mismatch-error": "Ievadītajai parolei ir jāsakrīt!",
"password-again": "Atkārtot paroli", "password-again": "Atkārtot paroli",
"sign-in": "Lūdzu pierakstīties",
"username": "Lietotājvārds (email)", "username": "Lietotājvārds (email)",
"remember-me": "Atcerēties mani", "remember-me": "Atcerēties mani",
"forgot-password": "Aizmirsu paroli?", "forgot-password": "Aizmirsu paroli?",

1
ui-ngx/src/assets/locale/locale.constant-nl_BE.json

@ -3847,7 +3847,6 @@
"two-factor-authentication": "Twee-factor-authenticatie", "two-factor-authentication": "Twee-factor-authenticatie",
"passwords-mismatch-error": "Ingevoerde wachtwoorden moeten hetzelfde zijn.", "passwords-mismatch-error": "Ingevoerde wachtwoorden moeten hetzelfde zijn.",
"password-again": "Wachtwoord opnieuw ingeven", "password-again": "Wachtwoord opnieuw ingeven",
"sign-in": "Gelieve in te loggen",
"username": "Gebruikersnaam (e-mail)", "username": "Gebruikersnaam (e-mail)",
"remember-me": "Onthoud mij", "remember-me": "Onthoud mij",
"forgot-password": "Wachtwoord vergeten?", "forgot-password": "Wachtwoord vergeten?",

1
ui-ngx/src/assets/locale/locale.constant-nl_NL.json

@ -3652,7 +3652,6 @@
"two-factor-authentication": "Twee-factor authenticatie", "two-factor-authentication": "Twee-factor authenticatie",
"passwords-mismatch-error": "Ingevoerde wachtwoorden moeten gelijk zijn!", "passwords-mismatch-error": "Ingevoerde wachtwoorden moeten gelijk zijn!",
"password-again": "Wachtwoord opnieuw", "password-again": "Wachtwoord opnieuw",
"sign-in": "Meld u aan",
"username": "Gebruikersnaam (e-mail)", "username": "Gebruikersnaam (e-mail)",
"remember-me": "Onthoud mij", "remember-me": "Onthoud mij",
"forgot-password": "Wachtwoord vergeten?", "forgot-password": "Wachtwoord vergeten?",

1
ui-ngx/src/assets/locale/locale.constant-no_NO.json

@ -3652,7 +3652,6 @@
"two-factor-authentication": "Tofaktorautentisering", "two-factor-authentication": "Tofaktorautentisering",
"passwords-mismatch-error": "Passordene må være like!", "passwords-mismatch-error": "Passordene må være like!",
"password-again": "Gjenta passord", "password-again": "Gjenta passord",
"sign-in": "Vennligst logg inn",
"username": "Brukernavn (epost)", "username": "Brukernavn (epost)",
"remember-me": "Husk meg", "remember-me": "Husk meg",
"forgot-password": "Glemt passord?", "forgot-password": "Glemt passord?",

1
ui-ngx/src/assets/locale/locale.constant-pl_PL.json

@ -3802,7 +3802,6 @@
"two-factor-authentication": "Uwierzytelnianie dwuskładnikowe", "two-factor-authentication": "Uwierzytelnianie dwuskładnikowe",
"passwords-mismatch-error": "Wpisane hasła muszą być takie same!", "passwords-mismatch-error": "Wpisane hasła muszą być takie same!",
"password-again": "Hasło ponownie", "password-again": "Hasło ponownie",
"sign-in": "proszę, zaloguj się",
"username": "Nazwa użytkownika (e-mail)", "username": "Nazwa użytkownika (e-mail)",
"remember-me": "Zapamiętaj mnie", "remember-me": "Zapamiętaj mnie",
"forgot-password": "Zapomniałeś hasła?", "forgot-password": "Zapomniałeś hasła?",

1
ui-ngx/src/assets/locale/locale.constant-pt_BR.json

@ -1383,7 +1383,6 @@
"create-password": "Criar senha", "create-password": "Criar senha",
"passwords-mismatch-error": "As senhas inseridas devem ser idênticas!", "passwords-mismatch-error": "As senhas inseridas devem ser idênticas!",
"password-again": "Repetir senha", "password-again": "Repetir senha",
"sign-in": "Inscreva-se",
"username": "Nome de usuário (e-mail)", "username": "Nome de usuário (e-mail)",
"remember-me": "Lembrar-me", "remember-me": "Lembrar-me",
"forgot-password": "Esqueceu a senha?", "forgot-password": "Esqueceu a senha?",

1
ui-ngx/src/assets/locale/locale.constant-ro_RO.json

@ -1236,7 +1236,6 @@
"create-password": "Creează Parolă", "create-password": "Creează Parolă",
"passwords-mismatch-error": "Parola reintrodusă trebuie să fie identică!", "passwords-mismatch-error": "Parola reintrodusă trebuie să fie identică!",
"password-again": "Rescrie Parola", "password-again": "Rescrie Parola",
"sign-in": "Intră în Cont",
"username": "Nume Utilizator (Adresa De eMail)", "username": "Nume Utilizator (Adresa De eMail)",
"remember-me": "Ține-mă minte!", "remember-me": "Ține-mă minte!",
"forgot-password": "Ai Uitat Parola?", "forgot-password": "Ai Uitat Parola?",

1
ui-ngx/src/assets/locale/locale.constant-sl_SI.json

@ -1708,7 +1708,6 @@
"create-password": "Ustvari geslo", "create-password": "Ustvari geslo",
"passwords-mismatch-error": "Vnesena gesla morajo biti enaka!", "passwords-mismatch-error": "Vnesena gesla morajo biti enaka!",
"password-again": "Ponovi geslo", "password-again": "Ponovi geslo",
"sign-in": "Prosimo, prijavite se",
"username": "Uporabniško ime (e-pošta)", "username": "Uporabniško ime (e-pošta)",
"remember-me": "Zapomni si me", "remember-me": "Zapomni si me",
"forgot-password": "Ste pozabili geslo?", "forgot-password": "Ste pozabili geslo?",

1
ui-ngx/src/assets/locale/locale.constant-tr_TR.json

@ -3768,7 +3768,6 @@
"two-factor-authentication": "İki adımlı doğrulama", "two-factor-authentication": "İki adımlı doğrulama",
"passwords-mismatch-error": "Girilen şifreler aynı olmalıdır!", "passwords-mismatch-error": "Girilen şifreler aynı olmalıdır!",
"password-again": "Şifre tekrar", "password-again": "Şifre tekrar",
"sign-in": "Lütfen giriş yapın",
"username": "Kullanıcı adı (e-posta)", "username": "Kullanıcı adı (e-posta)",
"remember-me": "Beni hatırla", "remember-me": "Beni hatırla",
"forgot-password": "Şifrenizi mi unuttunuz?", "forgot-password": "Şifrenizi mi unuttunuz?",

1
ui-ngx/src/assets/locale/locale.constant-uk_UA.json

@ -1793,7 +1793,6 @@
"create-password": "Створити пароль", "create-password": "Створити пароль",
"passwords-mismatch-error": "Введені паролі повинні бути однаковими!", "passwords-mismatch-error": "Введені паролі повинні бути однаковими!",
"password-again": "Введіть пароль ще раз", "password-again": "Введіть пароль ще раз",
"sign-in": "Будь ласка, увійдіть в систему",
"username": "Ім'я користувача (ел. пошта)", "username": "Ім'я користувача (ел. пошта)",
"remember-me": "Запам'ятати мене", "remember-me": "Запам'ятати мене",
"forgot-password": "Забули пароль?", "forgot-password": "Забули пароль?",

1
ui-ngx/src/assets/locale/locale.constant-zh_CN.json

@ -3473,7 +3473,6 @@
"two-factor-authentication": "双因素认证", "two-factor-authentication": "双因素认证",
"passwords-mismatch-error": "输入的密码必须相同!", "passwords-mismatch-error": "输入的密码必须相同!",
"password-again": "再次输入密码", "password-again": "再次输入密码",
"sign-in": "登录 ",
"username": "用户名(电子邮件)", "username": "用户名(电子邮件)",
"remember-me": "记住我", "remember-me": "记住我",
"forgot-password": "忘记密码?", "forgot-password": "忘记密码?",

1
ui-ngx/src/assets/locale/locale.constant-zh_TW.json

@ -2404,7 +2404,6 @@
"two-factor-authentication": "雙因素身份驗證", "two-factor-authentication": "雙因素身份驗證",
"passwords-mismatch-error": "輸入的密碼必須相同!", "passwords-mismatch-error": "輸入的密碼必須相同!",
"password-again": "再次輸入密碼", "password-again": "再次輸入密碼",
"sign-in": "登入 ",
"username": "用戶名(電子郵件)", "username": "用戶名(電子郵件)",
"remember-me": "記住我", "remember-me": "記住我",
"forgot-password": "忘記密碼?", "forgot-password": "忘記密碼?",

33
ui-ngx/src/styles.scss

@ -636,6 +636,39 @@ pre.tb-highlight {
} }
} }
.tb-autofilled:has(input:-webkit-autofill){
--mdc-outlined-text-field-label-text-color: var(--mdc-outlined-text-field-input-text-color);
--mdc-outlined-text-field-hover-label-text-color: var(--mdc-outlined-text-field-input-text-color);
.mat-mdc-form-field-flex {
&:before {
display: block;
height: auto;
content: "";
position: absolute;
inset: 0;
background: #7986cb;
opacity: 0.5;
pointer-events: none;
border-radius: 4px;
}
}
.mdc-floating-label {
--mat-mdc-form-field-label-transform: translateY(calc(calc(6.7px + var(--mat-form-field-container-height) / 2) * -1)) scale(var(--mat-mdc-form-field-floating-label-scale, 0.75));
}
.mat-mdc-form-field-infix {
input {
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus,
&:-webkit-autofill:active {
-webkit-background-clip: text !important;
-webkit-text-fill-color: var(--mdc-outlined-text-field-input-text-color);
}
}
}
}
// Material table // Material table
.mat-toolbar.mat-primary { .mat-toolbar.mat-primary {

Loading…
Cancel
Save