From 44dc107f5648669cf12779295504c729363df319 Mon Sep 17 00:00:00 2001
From: cv5ch <176032962+cv5ch@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:16:53 +0200
Subject: [PATCH] AccountSettingsPage now showing field and form errors, fixed
workflow and added unittests
---
.../acc-settings/acc-settings.component.html | 15 +-
.../acc-settings.component.spec.ts | 367 +++++++++++++-----
.../acc-settings/acc-settings.component.ts | 59 ++-
.../core/_validators/password.validator.ts | 22 +-
src/app/shared/buttons/button-submit.ts | 24 +-
src/app/shared/input/text/text.component.html | 48 ++-
src/app/shared/input/text/text.component.ts | 13 +-
7 files changed, 391 insertions(+), 157 deletions(-)
diff --git a/src/app/account/settings/acc-settings/acc-settings.component.html b/src/app/account/settings/acc-settings/acc-settings.component.html
index 2cd72327f..0233cbfa5 100644
--- a/src/app/account/settings/acc-settings/acc-settings.component.html
+++ b/src/app/account/settings/acc-settings/acc-settings.component.html
@@ -10,9 +10,9 @@
data-testid="input-registered-since"
>
-
+
-
+
@@ -38,6 +38,8 @@
[inputType]="showNewPassword ? 'text' : 'password'"
data-testid="input-newPassword"
[isRequired]="true"
+ [minLength]="pwdMin"
+ [maxLength]="pwdMax"
[showPasswordToggle]="true"
[passwordIsVisible]="showNewPassword"
(ShowPasswordEmit)="showNewPassword = $event"
@@ -49,6 +51,8 @@
[inputType]="showConfirmNewPassword ? 'text' : 'password'"
data-testid="input-confirmNewPassword"
[isRequired]="true"
+ [minLength]="pwdMin"
+ [maxLength]="pwdMax"
[passwordIsVisible]="showConfirmNewPassword"
[showPasswordToggle]="true"
(ShowPasswordEmit)="showConfirmNewPassword = $event"
@@ -56,8 +60,13 @@
+
+
+ Passwords do not match
+
+
-
+
diff --git a/src/app/account/settings/acc-settings/acc-settings.component.spec.ts b/src/app/account/settings/acc-settings/acc-settings.component.spec.ts
index 0941e9757..a2942298f 100644
--- a/src/app/account/settings/acc-settings/acc-settings.component.spec.ts
+++ b/src/app/account/settings/acc-settings/acc-settings.component.spec.ts
@@ -10,12 +10,19 @@ import { PipesModule } from 'src/app/shared/pipes.module';
import { CommonModule } from '@angular/common';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatIconModule } from '@angular/material/icon';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { By } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
-import { Params, provideRouter } from '@angular/router';
+import { Params, Router } from '@angular/router';
+
+import { AlertService } from '@services/shared/alert.service';
import { AccountSettingsComponent } from '@src/app/account/settings/acc-settings/acc-settings.component';
@@ -23,6 +30,9 @@ describe('AccountSettingsComponent', () => {
let component: AccountSettingsComponent;
let fixture: ComponentFixture;
+ let routerSpy: jasmine.SpyObj;
+ let alertSpy: jasmine.SpyObj;
+
const userResponse = {
type: 'user',
id: 1,
@@ -43,27 +53,34 @@ describe('AccountSettingsComponent', () => {
}
};
- // Define a partial mock service to simulate service calls.
+ // Your existing mockService
const mockService: Partial = {
- // Simulate the 'get' method to return an empty observable.
- // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
get(_serviceConfig, _id: number, _routerParams?: Params): Observable {
if (_serviceConfig.URL === SERV.USERS.URL) {
- return of({
- data: userResponse
- });
+ return of({ data: userResponse });
}
return of([]);
},
- // Simulate the 'create' method to return an empty observable.
- // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
create(serviceConfig, _object: any): Observable {
return of({});
},
-
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ update(_serviceConfig, id, _object: any): Observable {
+ return of({});
+ },
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ chelper(_serviceConfig, option: string, _payload: any): Observable {
+ return of({});
+ },
userId: 1
};
+
beforeEach(() => {
+ routerSpy = jasmine.createSpyObj('Router', ['navigate']);
+ alertSpy = jasmine.createSpyObj('AlertService', ['showSuccessMessage', 'showErrorMessage']);
+
TestBed.configureTestingModule({
declarations: [AccountSettingsComponent],
imports: [
@@ -75,7 +92,12 @@ describe('AccountSettingsComponent', () => {
ComponentsModule,
PipesModule,
NgbModule,
- MatSnackBarModule
+ MatSnackBarModule,
+ MatFormFieldModule,
+ MatInputModule,
+ MatSelectModule,
+ MatIconModule,
+ MatButtonModule
],
providers: [
provideAnimations(),
@@ -83,8 +105,15 @@ describe('AccountSettingsComponent', () => {
provide: GlobalService,
useValue: mockService
},
- provideHttpClient(withInterceptorsFromDi()),
- provideRouter([])
+ {
+ provide: Router,
+ useValue: routerSpy
+ },
+ {
+ provide: AlertService,
+ useValue: alertSpy
+ },
+ provideHttpClient(withInterceptorsFromDi())
]
}).compileComponents();
@@ -92,108 +121,240 @@ describe('AccountSettingsComponent', () => {
component = fixture.componentInstance;
fixture.detectChanges();
});
- // --- Test Methods ---
+
it('creates the component', () => {
expect(component).toBeTruthy();
});
- it('initializes the form with default values', () => {
- const formValue = component.form.getRawValue();
- expect(formValue.name).toBe(userResponse.attributes.name);
- expect(formValue.registeredSince).toBe('08/04/2025 6:25:56');
- expect(formValue.email).toBe(userResponse.attributes.email);
+ describe('Main form tests', () => {
+ it('initializes the form with default values', () => {
+ const formValue = component.form.getRawValue();
+ expect(formValue.name).toBe(userResponse.attributes.name);
+ expect(formValue.registeredSince).toBe('08/04/2025 6:25:56');
+ expect(formValue.email).toBe(userResponse.attributes.email);
+ });
- const passFormValue = component.changepasswordFormGroup.getRawValue();
- expect(passFormValue.oldPassword).toBe('');
- expect(passFormValue.newPassword).toBe('');
- expect(passFormValue.confirmNewPassword).toBe('');
- });
+ it('validates email as required', async () => {
+ const emailControl = component.form.get('email');
+ emailControl?.patchValue(null);
+ component.form.updateValueAndValidity();
+ expect(emailControl?.hasError('required')).toBeTrue();
+ });
- it('validates email as required', async () => {
- const emailControl = component.form.get('email');
- emailControl?.patchValue(null);
- expect(emailControl?.hasError('required')).toBeTrue();
- });
+ it('validates email format', () => {
+ const emailControl = component.form.get('email');
- it('validates email format', () => {
- const emailControl = component.form.get('email');
+ emailControl?.patchValue('invalid-email');
+ component.form.updateValueAndValidity();
+ expect(emailControl.hasError('email')).toBeTrue();
- // Set an invalid email format
- emailControl?.patchValue('invalid-email');
- expect(emailControl.hasError('email')).toBeTrue();
+ emailControl?.patchValue('test@example.com');
+ component.form.updateValueAndValidity();
+ expect(emailControl.hasError('email')).toBeFalse();
+ });
- // Set a valid email format
- emailControl?.patchValue('test@example.com');
- expect(emailControl.hasError('email')).toBeFalse();
- });
+ it('enables form submission when form is valid', () => {
+ component.form.patchValue({ email: 'test@example.com' });
+ component.form.updateValueAndValidity();
+ fixture.detectChanges();
- it('validates password length', () => {
- const newPasswordControl = component.form.get('newpassword');
- const confirmPasswordControl = component.form.get('confirmpass');
-
- // Set a password with length less than PWD_MIN
- newPasswordControl.patchValue('123');
- confirmPasswordControl.patchValue('123');
- expect(newPasswordControl.hasError('minlength')).toBe(true);
- expect(confirmPasswordControl.hasError('minlength')).toBe(true);
-
- // Set a password with length equal to PWD_MIN
- newPasswordControl.patchValue('1234');
- confirmPasswordControl.patchValue('1234');
- expect(newPasswordControl.hasError('minlength')).toBe(false);
- expect(confirmPasswordControl.hasError('minlength')).toBe(false);
-
- // Set a password with length greater than PWD_MAX
- newPasswordControl.patchValue('1234567890123');
- confirmPasswordControl.patchValue('1234567890123');
- expect(newPasswordControl.hasError('maxlength')).toBe(true);
- expect(confirmPasswordControl.hasError('maxlength')).toBe(true);
-
- // Set a password with length equal to PWD_MAX
- newPasswordControl.patchValue('123456789012');
- confirmPasswordControl.patchValue('123456789012');
- expect(newPasswordControl.hasError('maxlength')).toBe(false);
- expect(confirmPasswordControl.hasError('maxlength')).toBe(false);
- });
+ expect(component.form.valid).toBe(true);
+ const btn = fixture.debugElement.query(By.css('[data-testid="button-update-submit"]'));
+ const nativeBtn = btn.nativeElement.querySelector('button');
+ expect(nativeBtn?.disabled).toBeFalse();
+ });
- it('validates password match', () => {
- const newPasswordControl = component.changepasswordFormGroup.get('newPassword');
- const confirmPasswordControl = component.changepasswordFormGroup.get('confirmNewPassword');
- // Set different passwords
- newPasswordControl.patchValue('password123');
- confirmPasswordControl.patchValue('password1234');
- expect(component.changepasswordFormGroup.hasError('mismatch')).toBe(true);
-
- // Set matching passwords
- newPasswordControl.patchValue('password123');
- confirmPasswordControl.patchValue('password123');
- fixture.detectChanges();
- expect(component.changepasswordFormGroup.hasError('mismatch')).toBe(false);
- });
+ it('disables form submission when form is invalid', () => {
+ component.form.patchValue({ email: 'invalid-email' });
+ component.form.updateValueAndValidity();
+ fixture.detectChanges();
+
+ expect(component.form.valid).toBe(false);
+ const btn = fixture.debugElement.query(By.css('[data-testid="button-update-submit"]'));
+ const nativeBtn = btn.nativeElement.querySelector('button');
+ expect(nativeBtn?.disabled).toBeTrue();
+ });
+
+ it('submits form with valid email', () => {
+ spyOn(component['gs'], 'update').and.returnValue(of({}));
+
+ component.form.patchValue({ email: 'test@example.com' });
+ component.form.updateValueAndValidity();
+ fixture.detectChanges();
+
+ const btnDebugEl = fixture.debugElement.query(By.css('[data-testid="button-update-submit"]'));
+ btnDebugEl.nativeElement.click();
+ fixture.detectChanges();
+
+ expect(component['gs'].update).toHaveBeenCalledWith(SERV.USERS, component['gs'].userId, component.form.value);
+ expect(component['alert'].showSuccessMessage).toHaveBeenCalled();
+ expect(routerSpy.navigate).toHaveBeenCalledOnceWith(['users/all-users']);
+ });
+
+ it('does not submit form with invalid email', () => {
+ const updateSpy = spyOn(component['gs'], 'update');
+ component.form.patchValue({ email: 'invalid-email' });
+ component.form.updateValueAndValidity();
+ fixture.detectChanges();
- it('enables form submission when form is valid', () => {
- const EmailControl = component.form.get('email');
- EmailControl.patchValue('test@example.com');
- // The form should now be valid, and the submit button should be enabled
- expect(component.form.valid).toBe(true);
- // Find all button-submit elements
- const btns = fixture.debugElement.queryAll(By.css('[data-testid="button-submit"]'));
- // Select the correct button (e.g., first for account update)
- const btn = btns[0];
- const disabledAttr = btn.nativeElement.attributes.getNamedItem('ng-reflect-disabled');
- expect(disabledAttr ? disabledAttr.value : null).toEqual('false');
+ const btnDebugEl = fixture.debugElement.query(By.css('[data-testid="button-update-submit"]'));
+ btnDebugEl.nativeElement.click();
+ fixture.detectChanges();
+
+ expect(component.form.valid).toBeFalse();
+ expect(updateSpy).not.toHaveBeenCalled();
+ expect(component['alert'].showSuccessMessage).not.toHaveBeenCalled();
+ expect(routerSpy.navigate).not.toHaveBeenCalled();
+ });
});
- it('disables form submission when form is invalid', () => {
- const EmailControl = component.form.get('email');
- EmailControl.patchValue('invalid-email');
- // The form should now be invalid, and the submit button should be disabled
- expect(component.form.valid).toBe(false);
- // Find all button-submit elements
- const btns = fixture.debugElement.queryAll(By.css('[data-testid="button-submit"]'));
- // Select the correct button (e.g., first for account update)
- const btn = btns[0];
- const disabledAttr = btn.nativeElement.attributes.getNamedItem('ng-reflect-disabled');
- expect(disabledAttr ? disabledAttr.value : null).toEqual('true');
+ describe('Password change form tests', () => {
+ it('initializes the password form with empty default values', () => {
+ const formValue = component.changepasswordFormGroup.getRawValue();
+ expect(formValue.oldPassword).toBe('');
+ expect(formValue.newPassword).toBe('');
+ expect(formValue.confirmNewPassword).toBe('');
+ });
+
+ it('validates all password fields as required', () => {
+ const form = component.changepasswordFormGroup;
+
+ // Clear all values
+ form.patchValue({
+ oldPassword: '',
+ newPassword: '',
+ confirmNewPassword: ''
+ });
+
+ form.markAllAsTouched();
+ form.updateValueAndValidity();
+ fixture.detectChanges();
+
+ const oldPasswordControl = form.get('oldPassword');
+ const newPasswordControl = form.get('newPassword');
+ const confirmPasswordControl = form.get('confirmNewPassword');
+
+ expect(oldPasswordControl?.hasError('required')).toBeTrue();
+ expect(newPasswordControl?.hasError('required')).toBeTrue();
+ expect(confirmPasswordControl?.hasError('required')).toBeTrue();
+ });
+
+ it('validates password length', () => {
+ const newPasswordControl = component.changepasswordFormGroup.get('newPassword');
+ const confirmPasswordControl = component.changepasswordFormGroup.get('confirmNewPassword');
+
+ // Too short
+ newPasswordControl.patchValue('123');
+ confirmPasswordControl.patchValue('123');
+ component.changepasswordFormGroup.updateValueAndValidity();
+
+ expect(newPasswordControl.hasError('minlength')).toBeTrue();
+ expect(confirmPasswordControl.hasError('minlength')).toBeTrue();
+
+ // Exactly min
+ newPasswordControl.patchValue('1234');
+ confirmPasswordControl.patchValue('1234');
+ component.changepasswordFormGroup.updateValueAndValidity();
+
+ expect(newPasswordControl.hasError('minlength')).toBeFalse();
+ expect(confirmPasswordControl.hasError('minlength')).toBeFalse();
+
+ // Too long
+ newPasswordControl.patchValue('VeryNiceLongPasswordButTooLongForHashtopolis');
+ confirmPasswordControl.patchValue('VeryNiceLongPasswordButTooLongForHashtopolis');
+ component.changepasswordFormGroup.updateValueAndValidity();
+
+ expect(newPasswordControl.hasError('maxlength')).toBeTrue();
+ expect(confirmPasswordControl.hasError('maxlength')).toBeTrue();
+
+ // Exactly max
+ newPasswordControl.patchValue('Password12!#');
+ confirmPasswordControl.patchValue('Password12!#');
+ component.changepasswordFormGroup.updateValueAndValidity();
+
+ expect(newPasswordControl.hasError('maxlength')).toBeFalse();
+ expect(confirmPasswordControl.hasError('maxlength')).toBeFalse();
+ });
+
+ it('validates password match', () => {
+ // Set different passwords
+ component.changepasswordFormGroup.patchValue({
+ newPassword: 'password1234',
+ confirmNewPassword: 'P4ssw0rd1234'
+ });
+ component.changepasswordFormGroup.updateValueAndValidity();
+ expect(component.changepasswordFormGroup.hasError('passwordMismatch')).toBe(true);
+
+ // Set matching passwords
+ component.changepasswordFormGroup.patchValue({
+ newPassword: 'password1234',
+ confirmNewPassword: 'password1234'
+ });
+ component.changepasswordFormGroup.updateValueAndValidity();
+ expect(component.changepasswordFormGroup.hasError('passwordMismatch')).toBe(false);
+ });
+
+ it('Submits password form and resets on success', fakeAsync(() => {
+ // Arrange
+ const chelperSpy = spyOn(component['gs'], 'chelper').and.returnValue(
+ of({ meta: { 'Change password': 'Password changed successfully' } })
+ );
+ const resetSpy = spyOn(component, 'resetPasswordForm');
+
+ // Fill valid form values
+ component.changepasswordFormGroup.patchValue({
+ oldPassword: 'oldPass123',
+ newPassword: 'newPass456',
+ confirmNewPassword: 'newPass456'
+ });
+ component.changepasswordFormGroup.updateValueAndValidity();
+ fixture.detectChanges();
+
+ // Click the submit button
+ const btn = fixture.debugElement.query(By.css('[data-testid="button-password-submit"]'));
+ expect(btn).toBeTruthy();
+ btn.nativeElement.click();
+
+ tick(); // flush observable
+ fixture.detectChanges();
+
+ // Assert
+ expect(chelperSpy).toHaveBeenCalledOnceWith(SERV.HELPER, 'changeOwnPassword', {
+ oldPassword: 'oldPass123',
+ newPassword: 'newPass456',
+ confirmPassword: 'newPass456'
+ });
+ expect(component['alert'].showSuccessMessage).toHaveBeenCalledOnceWith('Password changed successfully');
+ expect(resetSpy).toHaveBeenCalled();
+ expect(component.isUpdatingPassLoading).toBeFalse();
+ }));
+
+ it('Does not submit password form if invalid', () => {
+ const chelperSpy = spyOn(component['gs'], 'chelper');
+ const resetSpy = spyOn(component, 'resetPasswordForm');
+
+ // Make the form invalid by patching passwords not matching
+ component.changepasswordFormGroup.patchValue({
+ oldPassword: 'oldpassword',
+ newPassword: 'newpassword',
+ confirmNewPassword: 'NewPassword'
+ });
+ component.changepasswordFormGroup.updateValueAndValidity();
+ fixture.detectChanges();
+
+ // Click the submit button
+ const btn = fixture.debugElement.query(By.css('[data-testid="button-password-submit"]'));
+ expect(btn).toBeTruthy();
+ btn.nativeElement.click();
+
+ // Service should not be called
+ expect(chelperSpy).not.toHaveBeenCalled();
+ // Alert should not be called
+ expect(component['alert'].showSuccessMessage).not.toHaveBeenCalled();
+ // Reset should not be called
+ expect(resetSpy).not.toHaveBeenCalled();
+ // isUpdatingPassLoading should remain false (no submission started)
+ expect(component.isUpdatingPassLoading).toBeFalse();
+ });
});
});
diff --git a/src/app/account/settings/acc-settings/acc-settings.component.ts b/src/app/account/settings/acc-settings/acc-settings.component.ts
index 5c969e93c..25259671a 100644
--- a/src/app/account/settings/acc-settings/acc-settings.component.ts
+++ b/src/app/account/settings/acc-settings/acc-settings.component.ts
@@ -14,6 +14,7 @@ import { ResponseWrapper } from '@src/app/core/_models/response.model';
import { JUser } from '@src/app/core/_models/user.model';
import { JsonAPISerializer } from '@src/app/core/_services/api/serializer-service';
import { RequestParamBuilder } from '@src/app/core/_services/params/builder-implementation.service';
+import { passwordMatchValidator } from '@src/app/core/_validators/password.validator';
export interface UpdateUserPassword {
oldPassword: string;
@@ -30,6 +31,9 @@ export class AccountSettingsComponent implements OnInit, OnDestroy {
static readonly PWD_MIN = 4;
static readonly PWD_MAX = 12;
+ pwdMin = AccountSettingsComponent.PWD_MIN;
+ pwdMax = AccountSettingsComponent.PWD_MAX;
+
pageTitle = 'Account Settings';
pageSubtitlePassword = 'Password Update';
@@ -40,7 +44,8 @@ export class AccountSettingsComponent implements OnInit, OnDestroy {
/** On form update show a spinner loading */
isUpdatingLoading = false;
isUpdatingPassLoading = false;
- /*
+
+ /**
* Toggles for showing/hiding password fields in the form.
* These are used to toggle visibility of the old, new, and confirm new password fields.
* Hides the password input by default.
@@ -49,10 +54,18 @@ export class AccountSettingsComponent implements OnInit, OnDestroy {
showOldPassword: boolean = false;
showNewPassword: boolean = false;
showConfirmNewPassword: boolean = false;
- strongPassword = false;
+
+ /**
+ * Array to hold subscriptions for cleanup on component destruction.
+ * This prevents memory leaks by unsubscribing from observables when the component is destroyed.
+ */
subscriptions: Subscription[] = [];
- emailControl: FormControl;
+ /**
+ * FormControl reference for easier access to form controls.
+ * This is used to access form controls in the template without needing to reference the entire form group.
+ */
+ protected readonly FormControl = FormControl;
constructor(
private titleService: AutoTitleService,
@@ -87,7 +100,7 @@ export class AccountSettingsComponent implements OnInit, OnDestroy {
* @param {string} registeredSince - Account registration date.
* @param {string} email - The user's email.
*/
- createForm(name = '', registeredSince = '', email = ''): void {
+ createForm(name: string = '', registeredSince: string = '', email: string = ''): void {
this.form = new FormGroup({
name: new FormControl({
value: name,
@@ -99,24 +112,27 @@ export class AccountSettingsComponent implements OnInit, OnDestroy {
}),
email: new FormControl(email, [Validators.required, Validators.email])
});
- //this.emailControl = this.form.get('email') as FormControl;
}
createUpdatePassForm() {
- this.changepasswordFormGroup = new FormGroup({
- oldPassword: new FormControl('', Validators.required),
- newPassword: new FormControl('', [
- Validators.required,
- Validators.minLength(AccountSettingsComponent.PWD_MIN),
- Validators.maxLength(AccountSettingsComponent.PWD_MAX)
- ]),
- confirmNewPassword: new FormControl('', [
- Validators.required,
- Validators.minLength(AccountSettingsComponent.PWD_MIN),
- Validators.maxLength(AccountSettingsComponent.PWD_MAX)
- ])
- });
+ this.changepasswordFormGroup = new FormGroup(
+ {
+ oldPassword: new FormControl('', Validators.required),
+ newPassword: new FormControl('', [
+ Validators.required,
+ Validators.minLength(AccountSettingsComponent.PWD_MIN),
+ Validators.maxLength(AccountSettingsComponent.PWD_MAX)
+ ]),
+ confirmNewPassword: new FormControl('', [
+ Validators.required,
+ Validators.minLength(AccountSettingsComponent.PWD_MIN),
+ Validators.maxLength(AccountSettingsComponent.PWD_MAX)
+ ])
+ },
+ { validators: passwordMatchValidator() }
+ );
}
+
get oldPasswordValueFromForm() {
return this.changepasswordFormGroup.get('oldPassword').value;
}
@@ -143,7 +159,7 @@ export class AccountSettingsComponent implements OnInit, OnDestroy {
this.gs.update(SERV.USERS, this.gs.userId, this.form.value).subscribe(() => {
this.alert.showSuccessMessage('User saved');
this.isUpdatingLoading = false;
- this.router.navigate(['users/all-users']);
+ void this.router.navigate(['users/all-users']);
})
);
}
@@ -153,6 +169,9 @@ export class AccountSettingsComponent implements OnInit, OnDestroy {
* Handles password submission
*/
onSubmitPass() {
+ if (this.changepasswordFormGroup.invalid || this.changepasswordFormGroup.pending) {
+ return;
+ }
this.isUpdatingPassLoading = true;
const payload: UpdateUserPassword = {
oldPassword: this.oldPasswordValueFromForm,
@@ -185,6 +204,4 @@ export class AccountSettingsComponent implements OnInit, OnDestroy {
})
);
}
-
- protected readonly FormControl = FormControl;
}
diff --git a/src/app/core/_validators/password.validator.ts b/src/app/core/_validators/password.validator.ts
index 947bd391b..42e436580 100644
--- a/src/app/core/_validators/password.validator.ts
+++ b/src/app/core/_validators/password.validator.ts
@@ -1,9 +1,19 @@
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
+export function passwordMatchValidator(): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ const newPassword = control.get('newPassword')?.value;
+ const confirmNewPassword = control.get('confirmNewPassword')?.value;
-export const passwordMatchValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
- const a = control.get('newpassword').value
- const b = control.get('confirmpass').value
-
- return a === b ? null : { 'mismatch': true };
-}
\ No newline at end of file
+ if (newPassword !== confirmNewPassword) {
+ control.get('confirmNewPassword')?.setErrors({ passwordMismatch: true });
+ return { passwordMismatch: true };
+ } else {
+ const confirmCtrl = control.get('confirmNewPassword');
+ if (confirmCtrl?.hasError('passwordMismatch')) {
+ confirmCtrl.updateValueAndValidity({ onlySelf: true, emitEvent: false });
+ }
+ return null;
+ }
+ };
+}
diff --git a/src/app/shared/buttons/button-submit.ts b/src/app/shared/buttons/button-submit.ts
index 9576f4dd1..4e9f36689 100644
--- a/src/app/shared/buttons/button-submit.ts
+++ b/src/app/shared/buttons/button-submit.ts
@@ -1,6 +1,6 @@
+import { Location } from '@angular/common';
import { Component, Input, ViewEncapsulation } from '@angular/core';
import { Router } from '@angular/router';
-import { Location } from '@angular/common';
/**
* Component for rendering a submit or cancel button.
@@ -27,21 +27,21 @@ import { Location } from '@angular/common';
* ```
*/
@Component({
- selector: 'button-submit',
- template: `
+ selector: 'button-submit',
+ template: `
`,
- encapsulation: ViewEncapsulation.None,
- standalone: false
+ encapsulation: ViewEncapsulation.None,
+ standalone: false
})
export class ButtonSubmitComponent {
/**
@@ -78,12 +78,16 @@ export class ButtonSubmitComponent {
/**
* Handle the button click based on its type.
*/
- handleClick(): void {
- if (this.type === 'cancel') {
- this.location.back(); // Go back to the previous window
- } else {
+ handleClick(event: Event): void {
+ if (this.disabled) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
return;
}
+
+ if (this.type === 'cancel') {
+ this.location.back();
+ }
}
/**
diff --git a/src/app/shared/input/text/text.component.html b/src/app/shared/input/text/text.component.html
index aae17a8b7..3345405a4 100644
--- a/src/app/shared/input/text/text.component.html
+++ b/src/app/shared/input/text/text.component.html
@@ -1,35 +1,44 @@
-
- {{ title }}
+
+
+ {{ title }}
info
+
{{ icon }}
+
+
+
+
@if (showPasswordToggle) {
diff --git a/src/app/shared/input/text/text.component.ts b/src/app/shared/input/text/text.component.ts
index 6e401d3a2..18b71c8cc 100644
--- a/src/app/shared/input/text/text.component.ts
+++ b/src/app/shared/input/text/text.component.ts
@@ -31,17 +31,28 @@ import { AbstractInputComponent } from '@src/app/shared/input/abstract-input';
})
export class InputTextComponent extends AbstractInputComponent {
@Input() pattern: string | RegExp;
- @Input() inputType: 'text' | 'password' = 'text';
+ @Input() inputType: 'text' | 'password' | 'email' = 'text';
@Input() icon: string;
@Input() width: string = '';
+ @Input() minLength?: number;
+ @Input() maxLength?: number;
@Input() showPasswordToggle: boolean = false;
@Input() passwordIsVisible: boolean = true;
+
@Output() ShowPasswordEmit = new EventEmitter();
emitShowPassword() {
this.passwordIsVisible = !this.passwordIsVisible;
this.ShowPasswordEmit.emit(this.passwordIsVisible);
}
+
+ get resolvedInputType(): string {
+ if (this.showPasswordToggle) {
+ return this.passwordIsVisible ? 'text' : 'password';
+ }
+ return this.inputType;
+ }
+
constructor() {
super();
}