Change password view enhancement to support and display password validations

This commit is contained in:
Jose Alberto Hernandez
2025-04-06 12:24:55 -05:00
parent f4f480ac64
commit 0dd32c853a
24 changed files with 352 additions and 102 deletions
@@ -3,68 +3,40 @@
<div mat-dialog-content>
<form [formGroup]="changePasswordForm">
<div fxLayout="column">
<mat-form-field>
<mat-label>{{ 'labels.inputs.Password' | translate }}</mat-label>
<input matInput formControlName="password" type="{{ passwordInputType[0] }}" />
<button
type="button"
mat-button
matSuffix
mat-icon-button
(mousedown)="togglePasswordVisibility(0)"
(mouseup)="togglePasswordVisibility(0)"
>
<fa-icon *ngIf="passwordInputType[0] === 'password'" icon="eye"></fa-icon>
<fa-icon *ngIf="passwordInputType[0] === 'text'" icon="eye-slash"></fa-icon>
</button>
<mat-error *ngIf="changePasswordForm.controls.password.hasError('required')">
{{ 'labels.inputs.Password' | translate }} <strong>{{ 'labels.commons.is required' | translate }}</strong>
</mat-error>
<mat-error *ngIf="changePasswordForm.controls.password.hasError('minlength')">
{{ 'labels.commons.Password should be at least' | translate }}
<strong>{{ 'labels.commons.12 characters long' | translate }}</strong>
</mat-error>
<mat-error *ngIf="changePasswordForm.controls.password.hasError('maxlength')">
{{ 'labels.commons.Password should not be more than' | translate }}
<strong>{{ 'labels.commons.50 characters long' | translate }}</strong>
</mat-error>
<mat-error
*ngIf="
changePasswordForm.controls.password.hasError('pattern') &&
!(
changePasswordForm.controls.password.hasError('minlength') ||
changePasswordForm.controls.password.hasError('maxlength')
)
"
>
{{ 'labels.commons.Password should include a' | translate }}
<strong>{{ 'labels.commons.numeral' | translate }}</strong> {{ 'labels.commons.and at' | translate }}
<strong>{{ 'labels.commons.least one uppercase' | translate }}</strong
>, <strong>{{ 'labels.commons.lowercase and special character' | translate }}</strong>
</mat-error>
</mat-form-field>
<mifosx-input-password
#password
formControlName="password"
label="{{ 'Password' | translateKey: 'inputs' }}"
></mifosx-input-password>
<div class="error">
<p *ngIf="changePasswordForm.get('password').errors?.['uppercase']">
{{ changePasswordForm.get('password').errors?.['uppercase'] | translateKey: 'inputs' }}
</p>
<p *ngIf="changePasswordForm.get('password').errors?.['number']">
{{ changePasswordForm.get('password').errors?.['number'] | translateKey: 'inputs' }}
</p>
<p *ngIf="changePasswordForm.get('password').errors?.['repeated']">
{{ changePasswordForm.get('password').errors?.['repeated'] | translateKey: 'inputs' }}
</p>
<p *ngIf="changePasswordForm.get('password').errors?.['specialChar']">
{{ changePasswordForm.get('password').errors?.['specialChar'] | translateKey: 'inputs' }}
</p>
<p *ngIf="changePasswordForm.get('password').errors?.['minlength']">
{{ 'Password minimun length must be' | translateKey: 'inputs' }} {{ minPasswordLength }}
</p>
</div>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Repeat Password' | translate }}</mat-label>
<input matInput formControlName="repeatPassword" type="{{ passwordInputType[1] }}" />
<button
type="button"
mat-button
matSuffix
mat-icon-button
(mousedown)="togglePasswordVisibility(1)"
(mouseup)="togglePasswordVisibility(1)"
>
<fa-icon *ngIf="passwordInputType[1] === 'password'" icon="eye"></fa-icon>
<fa-icon *ngIf="passwordInputType[1] === 'text'" icon="eye-slash"></fa-icon>
</button>
<mat-error *ngIf="changePasswordForm.controls.repeatPassword.hasError('required')">
{{ 'labels.inputs.Password' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-error *ngIf="changePasswordForm.controls.repeatPassword.hasError('notequal')">
{{ 'labels.inputs.Password' | translate }} <strong>{{ 'labels.commons.does not match' | translate }}</strong>
</mat-error>
</mat-form-field>
<mifosx-input-password
#repeatPassword
formControlName="repeatPassword"
label="{{ 'Confirm Password' | translateKey: 'inputs' }}"
></mifosx-input-password>
</div>
<div class="error">
<p *ngIf="changePasswordForm.get('repeatPassword').errors?.['notequal']">
{{ 'Passwords do not match' | translateKey: 'inputs' }}
</p>
</div>
</form>
</div>
@@ -0,0 +1,8 @@
.error {
color: red;
}
mat-dialog-content {
min-width: 240px;
width: 240px;
}
@@ -4,6 +4,7 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { UntypedFormBuilder, Validators, ValidatorFn, AbstractControl, ValidationErrors } from '@angular/forms';
import { Subscription } from 'rxjs';
import { PasswordsUtility } from 'app/core/utils/passwords-utility';
import { environment } from 'environments/environment';
/**
* Change Password Dialog component.
@@ -14,6 +15,8 @@ import { PasswordsUtility } from 'app/core/utils/passwords-utility';
styleUrls: ['./change-password-dialog.component.scss']
})
export class ChangePasswordDialogComponent implements OnInit {
minPasswordLength: number = environment.minPasswordLength | 12;
/** Change Password Form */
changePasswordForm: any;
/** Password input field type. */
@@ -37,15 +40,6 @@ export class ChangePasswordDialogComponent implements OnInit {
this.createChangePasswordForm();
}
/**
* Toggles the visibility of the password input field.
*
* Changes the input type between 'password' and 'text'.
*/
togglePasswordVisibility(index: number) {
this.passwordInputType[index] = this.passwordInputType[index] === 'password' ? 'text' : 'password';
}
/** Change Password form */
createChangePasswordForm() {
this.changePasswordForm = this.formBuilder.group({
@@ -0,0 +1,16 @@
<mat-form-field>
<mat-label>{{ label }}</mat-label>
<input
matInput
[disabled]="disabled"
[errorStateMatcher]="matcher"
(focusout)="onFocusOut()"
(input)="onInput($event)"
[required]="required"
[type]="type"
[value]="value"
/>
<mat-icon matSuffix (click)="onVisibilityClick($event)">{{ icon }}</mat-icon>
<mat-error *ngIf="ngControl.hasError('required')">{{ label }} is a required field.</mat-error>
<mat-error *ngIf="ngControl.hasError(customErrorName)">{{ customErrorMessage }}</mat-error>
</mat-form-field>
@@ -0,0 +1,11 @@
:host {
width: 100%;
mat-icon {
font-size: 16px;
}
.mat-form-field {
width: 100%;
}
}
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InputPasswordComponent } from './input-password.component';
describe('InputPasswordComponent', () => {
let component: InputPasswordComponent;
let fixture: ComponentFixture<InputPasswordComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [InputPasswordComponent]
}).compileComponents();
fixture = TestBed.createComponent(InputPasswordComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,114 @@
import { Component, Input, Optional, Self, ViewChild } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormGroupDirective,
NgControl,
NgForm,
Validators
} from '@angular/forms';
import { MatInput } from '@angular/material/input';
import { ErrorStateMatcher } from '@angular/material/core';
@Component({
selector: 'mifosx-input-password',
templateUrl: './input-password.component.html',
styleUrls: ['./input-password.component.scss']
})
export class InputPasswordComponent implements ControlValueAccessor, ErrorStateMatcher {
disabled = false;
icon = 'visibility_off';
@Input()
label: string = null;
@Input()
customErrorMessage: string = null;
@Input()
customErrorName: string = null;
get matcher() {
return this;
}
@ViewChild(MatInput)
matInput: MatInput;
get required(): boolean {
return this._required ?? this.ngControl?.control?.hasValidator(Validators.required) ?? false;
}
@Input()
set required(value: boolean) {
this._required = value;
}
touched = false;
type = 'password';
value: string = null;
private _required: boolean = null;
private _visible = false;
constructor(@Optional() @Self() public ngControl: NgControl) {
if (ngControl !== null) {
ngControl.valueAccessor = this;
}
}
isErrorState(control: AbstractControl | null, form: FormGroupDirective | NgForm | null): boolean {
return this.touched && (this.ngControl?.control?.invalid ?? false);
}
onChange = (value: any) => {};
onFocusOut(): void {
this.touched = true;
this.onTouched();
this.matInput.updateErrorState();
}
onInput($event: any): void {
this.value = $event.currentTarget.value;
this.touched = true;
this.onChange(this.value);
}
onTouched = () => {};
onVisibilityClick($event: Event): void {
if (this._visible) {
this.icon = 'visibility_off';
this.type = 'password';
} else {
this.icon = 'visibility';
this.type = 'text';
}
// Invert the value.
this._visible = !this._visible;
$event.stopPropagation();
}
registerOnChange(fn: any) {
this.onChange = fn;
}
registerOnTouched(fn: any) {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
writeValue(obj: any): void {
this.value = obj;
}
}
+3 -1
View File
@@ -50,6 +50,7 @@ import { ThemeToggleComponent } from './theme-toggle/theme-toggle.component';
import { LongTextComponent } from './long-text/long-text.component';
import { DropdownComponent } from './dropdown/dropdown.component';
import { InputAmountComponent } from './input-amount/input-amount.component';
import { InputPasswordComponent } from './input-password/input-password.component';
/**
* Shared Module
@@ -106,7 +107,8 @@ import { InputAmountComponent } from './input-amount/input-amount.component';
ThemeToggleComponent,
LongTextComponent,
DropdownComponent,
InputAmountComponent
InputAmountComponent,
InputPasswordComponent
],
exports: [
FileUploadComponent,