Skip to content

Commit

Permalink
Improve password input fields (#4712)
Browse files Browse the repository at this point in the history
* Improve password input fields
- add `show` style button to all input fields
- fixes #4219

* Move as much as possible into component
- matSuffix directive must remain in parent element to work
- cannot then bump that content into a directive off of the input
  • Loading branch information
richard-cox authored Nov 2, 2020
1 parent 16ec1ce commit fdaa499
Show file tree
Hide file tree
Showing 22 changed files with 186 additions and 57 deletions.
3 changes: 3 additions & 0 deletions src/frontend/packages/core/src/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { NotSetupGuardService } from './not-setup-guard.service';
import { PageHeaderService } from './page-header-service/page-header.service';
import { PageNotFoundComponentComponent } from './page-not-found-component/page-not-found-component.component';
import { SafeImgPipe } from './safe-img.pipe';
import { ShowHideButtonComponent } from './show-hide-button/show-hide-button.component';
import { StatefulIconComponent } from './stateful-icon/stateful-icon.component';
import { TruncatePipe } from './truncate.pipe';
import { UserProfileService } from './user-profile.service';
Expand Down Expand Up @@ -67,6 +68,7 @@ import { WindowRef } from './window-ref/window-ref.service';
StatefulIconComponent,
NoContentMessageComponent,
DisableRouterLinkDirective,
ShowHideButtonComponent
],
providers: [
AuthGuardService,
Expand Down Expand Up @@ -107,6 +109,7 @@ import { WindowRef } from './window-ref/window-ref.service';
DisableRouterLinkDirective,
NoContentMessageComponent,
UserAvatarComponent,
ShowHideButtonComponent
],
entryComponents: [
LogOutDialogComponent
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Store } from '@ngrx/store';
import { Observable, of as observableOf } from 'rxjs';
import { map, catchError, tap } from 'rxjs/operators';
import { catchError, map, tap } from 'rxjs/operators';

import { RouterNav } from '../../../store/src/actions/router.actions';
import { AppState } from '../../../store/src/app-state';
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';

const { proxyAPIVersion } = environment;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<button mat-icon-button (click)="show = !show" [attr.aria-label]="'Hide password'" [attr.aria-pressed]="!show"
type="button">
<mat-icon>{{!show ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { ShowHideButtonComponent } from './show-hide-button.component';

describe('ShowHideButtonComponent', () => {
let component: ShowHideButtonComponent;
let fixture: ComponentFixture<ShowHideButtonComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ShowHideButtonComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(ShowHideButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
selector: 'app-show-hide-button',
templateUrl: './show-hide-button.component.html',
styleUrls: ['./show-hide-button.component.scss']
})
export class ShowHideButtonComponent {

private pShow = false;

@Output()
changed = new EventEmitter<boolean>();

@Input()
get show(): boolean {
return this.pShow;
}
set show(state: boolean) {
this.pShow = state;
this.changed.emit(this.pShow);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mat-checkbox {
margin-left: 15px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,30 @@ <h1>Backup Endpoints</h1>
<form [formGroup]="passwordForm" class="stepper-form">
<mat-form-field>
<mat-label>Password</mat-label>
<input matInput formControlName="password" name="password" required [type]="!show ? 'password' : 'text'">
<button mat-icon-button matSuffix (click)="show = !show" [attr.aria-label]="'Hide password'"
[attr.aria-pressed]="!show">
<mat-icon>{{!show ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
<input matInput formControlName="password" name="password" required
[type]="!showPassword[1] ? 'password' : 'text'">
<span matSuffix>
<app-show-hide-button (changed)="showPassword[1] = $event"></app-show-hide-button>
</span>
<mat-error *ngIf="passwordForm.controls.password.errors && passwordForm.controls.password.errors.required">
Password is required</mat-error>
<mat-error *ngIf="passwordForm.controls.password.errors && passwordForm.controls.password.errors.minlength">
Password must be at least {{passwordForm.controls.password.errors.minlength.requiredLength}} characters
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Confirm Password</mat-label>
<input matInput formControlName="password2" name="password2" required
[type]="!showPassword[2] ? 'password' : 'text'" [pattern]="passwordForm.controls.password2.value">
<span matSuffix>
<app-show-hide-button (changed)="showPassword[2] = $event"></app-show-hide-button>
</span>
<mat-error *ngIf="passwordForm.controls.password2.errors && passwordForm.controls.password2.errors.required">
Password is required</mat-error>
<mat-error *ngIf="passwordForm.controls.password2.errors && passwordForm.controls.password2.errors.pattern">
Passwords must match
</mat-error>
</mat-form-field>
</form>
</div>
</app-step>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Component } from '@angular/core';
import { Component, OnDestroy } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import moment from 'moment';
import { Observable, of, Subject } from 'rxjs';
import { Observable, of, Subject, Subscription } from 'rxjs';
import { filter, first, map } from 'rxjs/operators';

import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog';
import { httpErrorResponseToSafeString } from '../../../../../../store/src/jetstream';
import { stratosEntityCatalog } from '../../../../../../store/src/stratos-entity-catalog';
import { EndpointModel } from '../../../../../../store/src/types/endpoint.types';
import { safeUnsubscribe } from '../../../../core/utils.service';
import { ConfirmationDialogConfig } from '../../../../shared/components/confirmation-dialog.config';
import { ConfirmationDialogService } from '../../../../shared/components/confirmation-dialog.service';
import { ITableListDataSource } from '../../../../shared/components/list/data-sources-controllers/list-data-source-types';
Expand All @@ -26,7 +27,9 @@ import { BackupEndpointTypes } from '../backup-restore.types';
BackupEndpointsService
]
})
export class BackupEndpointsComponent {
export class BackupEndpointsComponent implements OnDestroy {

sub: Subscription;

// Step 1
columns: ITableColumn<EndpointModel>[] = [
Expand Down Expand Up @@ -66,7 +69,7 @@ export class BackupEndpointsComponent {
// Step 2
passwordValid$: Observable<boolean>;
passwordForm: FormGroup;
show = false;
showPassword: boolean[] = [];

constructor(
public service: BackupEndpointsService,
Expand All @@ -76,6 +79,9 @@ export class BackupEndpointsComponent {
this.setupPasswordStep();
}

ngOnDestroy(): void {
safeUnsubscribe(this.sub);
}

setupSelectStep() {
const endpointObs = stratosEntityCatalog.endpoint.store.getAll.getPaginationService();
Expand Down Expand Up @@ -105,7 +111,11 @@ export class BackupEndpointsComponent {
setupPasswordStep() {
this.passwordForm = new FormGroup({
password: new FormControl('', [Validators.required, Validators.minLength(6)]),
password2: new FormControl(''),
});
this.sub = this.passwordForm.controls.password.valueChanges.subscribe(value => this.passwordForm.controls.password2.setValidators(
[Validators.required, Validators.pattern(value)]
));
this.passwordValid$ = this.passwordForm.statusChanges.pipe(
map(() => {
this.service.password = this.passwordForm.controls.password.value;
Expand Down Expand Up @@ -162,7 +172,7 @@ export class BackupEndpointsComponent {
}

return result.asObservable();
}
};


private getEndpointTypeString(endpoint: EndpointModel): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ <h1>Restore Endpoints</h1>
<mat-form-field>
<mat-label>Password</mat-label>
<input matInput formControlName="password" required [type]="!show ? 'password' : 'text'">
<button mat-icon-button matSuffix (click)="show = !show" [attr.aria-label]="'Hide password'"
[attr.aria-pressed]="!show">
<mat-icon>{{!show ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
<span matSuffix>
<app-show-hide-button (changed)="show = $event"></app-show-hide-button>
</span>
<mat-error *ngIf="passwordForm.controls.password.errors && passwordForm.controls.password.errors.required">
Password is required</mat-error>
<mat-error *ngIf="passwordForm.controls.password.errors && passwordForm.controls.password.errors.minlength">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
<input matInput placeholder="Username" formControlName="username">
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Password" type="password" formControlName="password">
<input matInput formControlName="password" name="password" required [type]="!showPassword ? 'password' : 'text'">
<span matSuffix>
<app-show-hide-button (changed)="showPassword = $event"></app-show-hide-button>
</span>
</mat-form-field>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@ import { IAuthForm } from '../../../../../../store/src/extension-types';
styleUrls: ['./credentials-auth-form.component.scss']
})
export class CredentialsAuthFormComponent implements IAuthForm {

showPassword = false;

@Input() formGroup: FormGroup;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,23 @@
<input matInput required [(ngModel)]="username" name="username" placeholder="Username">
</mat-form-field>
<mat-form-field *ngIf="!ssoLogin" [hideRequiredMarker]="true">
<input matInput required type="password" [(ngModel)]="password" name="password" placeholder="Password">
<input matInput required [type]="!showPassword ? 'password' : 'text'" [(ngModel)]="password" name="password"
placeholder="Password">
<span matSuffix>
<app-show-hide-button (changed)="showPassword = $event"></app-show-hide-button>
</span>
</mat-form-field>
<button class="login__submit" color="primary" *ngIf="!loggedIn" type="submit" mat-button mat-raised-button [disabled]="!ssoLogin && !loginForm.valid">Login</button>
<button class="login__submit" color="primary" *ngIf="!loggedIn" type="submit" mat-button mat-raised-button
[disabled]="!ssoLogin && !loginForm.valid">Login</button>
</form>
</div>
<div id="login__loading" class="login__loading">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
</div>
</mat-card>
<div id="login-error-message" class="login-message" [ngClass]="{'login-message--show': !!message, 'login-message-error': this.error}">
<div id="login-error-message" class="login-message"
[ngClass]="{'login-message--show': !!message, 'login-message-error': this.error}">
{{ message }}
</div>
</app-intro-screen>
</app-intro-screen>
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export class LoginPageComponent implements OnInit, OnDestroy {

subscription: Subscription;

showPassword = false;

ngOnInit() {
this.ssoLogin = false;
this.store.dispatch(new VerifySession());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ <h1>{{title}} Setup with Local Admin Account</h1>
<form class="local-setup-wizard__form" [formGroup]="passwordForm" class="stepper-form">
<div class="local-setup-wizard__form-block">
<mat-form-field>
<input matInput formControlName="adminPassword" placeholder="Password" type="password">
<input matInput formControlName="adminPassword" placeholder="Password" name="password" required
[type]="!showPassword[1] ? 'password' : 'text'">
<span matSuffix>
<app-show-hide-button (changed)="showPassword[1] = $event"></app-show-hide-button>
</span>
</mat-form-field>
<mat-form-field>
<input matInput formControlName="adminPasswordConfirm" placeholder="Confirm Password" type="password">
<input matInput formControlName="adminPasswordConfirm" placeholder="Confirm Password"
name="adminPasswordConfirm" required [type]="!showPassword[2] ? 'password' : 'text'">
<span matSuffix>
<app-show-hide-button (changed)="showPassword[2] = $event"></app-show-hide-button>
</span>
</mat-form-field>
</div>
</form>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { Component, OnInit, Inject } from '@angular/core';
import { FormGroup, FormControl, Validators, ValidatorFn, AbstractControl } from '@angular/forms';
import { Observable, BehaviorSubject, of as obsof } from 'rxjs';
import { StepOnNextFunction } from '../../../shared/components/stepper/step/step.component';
import { Component, Inject, OnInit } from '@angular/core';
import { AbstractControl, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { InternalAppState } from '../../../../../store/src/app-state';
import { filter, delay, take, map, tap } from 'rxjs/operators';
import { UAASetupState, LocalAdminSetupData } from '../../../../../store/src/types/uaa-setup.types';
import { AuthState } from '../../../../../store/src/reducers/auth.reducer';
import { BehaviorSubject, Observable } from 'rxjs';
import { delay, filter, map, take, tap } from 'rxjs/operators';

import { VerifySession } from '../../../../../store/src/actions/auth.actions';
import { SetupSaveConfig } from '../../../../../store/src/actions/setup.actions';
import { InternalAppState } from '../../../../../store/src/app-state';
import { AuthState } from '../../../../../store/src/reducers/auth.reducer';
import { LocalAdminSetupData, UAASetupState } from '../../../../../store/src/types/uaa-setup.types';
import { APP_TITLE } from '../../../core/core.types';
import { StepOnNextFunction } from '../../../shared/components/stepper/step/step.component';

@Component({
selector: 'app-local-account-wizard',
Expand All @@ -22,6 +23,8 @@ export class LocalAccountWizardComponent implements OnInit {
validateLocalAuthForm: Observable<boolean>;
applyingSetup$ = new BehaviorSubject<boolean>(false);

showPassword: boolean[] = [];

constructor(private store: Store<Pick<InternalAppState, 'uaaSetup' | 'auth'>>, @Inject(APP_TITLE) public title: string) { }

ngOnInit() {
Expand Down Expand Up @@ -72,10 +75,10 @@ export class LocalAccountWizardComponent implements OnInit {
message: state[0].message
};
}));
}
};

confirmPasswordValidator(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => {
return (control: AbstractControl): { [key: string]: any, } => {
const same = control.value === this.passwordForm.value.adminPassword;
return same ? null : { passwordMatch: { value: control.value } };
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ <h1>{{title}} Setup with Cloud Foundry UAA</h1>
<mat-form-field>
<input matInput formControlName="clientSecret" placeholder="Client Secret"
[type]="!show ? 'password' : 'text'">
<button mat-icon-button matSuffix (click)="show = !show" [attr.aria-label]="'Hide password'"
[attr.aria-pressed]="!show">
<mat-icon>{{!show ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
<span matSuffix>
<app-show-hide-button (changed)="show = $event"></app-show-hide-button>
</span>
</mat-form-field>
</div>
<p class="uaa-wizard__form-section">Enter the username and password of an admin user (this is used to
Expand All @@ -38,10 +37,9 @@ <h1>{{title}} Setup with Cloud Foundry UAA</h1>
<mat-form-field>
<input matInput type="password" formControlName="adminPassword" placeholder="Admin Password"
[type]="!showPassword ? 'password' : 'text'">
<button mat-icon-button matSuffix (click)="showPassword = !showPassword"
[attr.aria-label]="'Hide password'" [attr.aria-pressed]="!showPassword">
<mat-icon>{{!showPassword ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
<span matSuffix>
<app-show-hide-button (changed)="showPassword = $event"></app-show-hide-button>
</span>
</mat-form-field>
</div>
<!-- Single Sign-on -->
Expand Down
Loading

0 comments on commit fdaa499

Please sign in to comment.