Add reset password modal to user management
Introduced a new ResetPasswordModalComponent for resetting user passwords from the setup user page. Updated the setup-user component and template to include a reset password button and modal, and refactored password-hide-show to support resetting its state. Also adjusted reset-password component to enable userId input.mazdak/UX-2303
parent
1ddb35bbac
commit
3b70e23da0
@ -0,0 +1,78 @@
|
||||
<div class="modal fade" id="resetPasswordModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
{{ 'resetPassword' | translate }}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" (click)="closeModal()"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<form [formGroup]="resetPasswordForm">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ 'userID' | translate }}</label>
|
||||
<input [readonly]="true" class="form-control" formControlName="userId" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ 'enterNewPassword' | translate }}</label>
|
||||
|
||||
<div class="password-wrapper">
|
||||
<input class="form-control" formControlName="newPassword" placeholder="{{'enterNewPassword' | translate}}" [type]="newPasswordType" appNoWhitespaces />
|
||||
|
||||
<app-password-hide-show #newPasswordPsh class="password-eye align-items-stretch" [showPassword]="true"
|
||||
(onEyeClick)="togglePasswordType()">
|
||||
</app-password-hide-show>
|
||||
</div>
|
||||
<div class="text-danger" *ngIf="resetPasswordForm.get('newPassword')?.touched ">
|
||||
<div *ngIf="resetPasswordForm.get('newPassword')?.hasError('required')">
|
||||
{{ 'fieldRequired' | translate }}
|
||||
</div>
|
||||
<div *ngIf="resetPasswordForm.get('newPassword')?.hasError('pattern')">
|
||||
{{ 'passwordPattern' | translate }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ 'confirmPassword' | translate }}</label>
|
||||
|
||||
<div class="password-wrapper">
|
||||
<input class="form-control" formControlName="confirmPassword" placeholder="{{'confirmPassword' | translate}}" [type]="confirmPasswordType" appNoWhitespaces />
|
||||
|
||||
<app-password-hide-show #confirmPasswordPsh class="password-eye align-items-stretch" [showPassword]="true"
|
||||
(onEyeClick)="toggleConfirmPasswordType()">
|
||||
</app-password-hide-show>
|
||||
</div>
|
||||
<div class="text-danger" *ngIf="resetPasswordForm.get('confirmPassword')?.touched ">
|
||||
<div *ngIf="resetPasswordForm.get('confirmPassword')?.hasError('required')">
|
||||
{{ 'fieldRequired' | translate }}
|
||||
</div>
|
||||
<div *ngIf="resetPasswordForm.hasError('passwordMismatch')">
|
||||
{{ 'passwordsDoNotMatch' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer d-flex justify-content-end gap-2">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm px-4" (click)="closeModal()">
|
||||
{{ 'cancel' | translate }}
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-primary btn-sm px-4" [disabled]="resetPasswordForm.invalid" (click)="submit()">
|
||||
{{ 'save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ResetPasswordModalComponent } from './reset-password-modal.component';
|
||||
|
||||
describe('ResetPasswordModalComponent', () => {
|
||||
let component: ResetPasswordModalComponent;
|
||||
let fixture: ComponentFixture<ResetPasswordModalComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ResetPasswordModalComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ResetPasswordModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,112 @@
|
||||
import { Component, Input, OnInit, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators, AbstractControl, ValidationErrors, ReactiveFormsModule } from '@angular/forms';
|
||||
import { HttpURIService } from '../../app.http.uri.service';
|
||||
import { URIKey } from '../../utils/uri-enums';
|
||||
import { I18NService } from '../../services/i18n.service';
|
||||
import { StorageService } from '../../shared/services/storage.service';
|
||||
import { SuccessMessages } from '../../utils/enums';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { PasswordHideShowComponent } from '../../shared/components/password-hide-show/password-hide-show.component';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-reset-password-modal',
|
||||
standalone: true,
|
||||
imports: [TranslateModule, ReactiveFormsModule, CommonModule, PasswordHideShowComponent],
|
||||
templateUrl: './reset-password-modal.component.html'
|
||||
})
|
||||
export class ResetPasswordModalComponent implements OnInit {
|
||||
newPasswordType: string = 'password'
|
||||
confirmPasswordType: string = 'password'
|
||||
@Input() userId!: string;
|
||||
|
||||
resetPasswordForm!: FormGroup;
|
||||
@ViewChild('newPasswordPsh') passwordHideShow?:PasswordHideShowComponent
|
||||
@ViewChild('confirmPasswordPsh') confirmPasswordHideShow?:PasswordHideShowComponent
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private httpURIService: HttpURIService,
|
||||
private i18nService: I18NService,
|
||||
private storageService: StorageService
|
||||
) {}
|
||||
togglePasswordType() {
|
||||
this.newPasswordType = this.passwordHideShow?.showPassword ? 'password' : 'text';
|
||||
}
|
||||
toggleConfirmPasswordType() {
|
||||
this.confirmPasswordType = this.confirmPasswordHideShow?.showPassword ? 'password' : 'text';
|
||||
}
|
||||
ngOnInit(): void {
|
||||
this.resetPasswordForm = this.fb.group({
|
||||
userId: [''],
|
||||
newPassword: ['', [
|
||||
Validators.required,
|
||||
Validators.minLength(8),
|
||||
Validators.maxLength(20),
|
||||
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).+$/)
|
||||
]],
|
||||
confirmPassword: ['', Validators.required]
|
||||
}, { validators: this.passwordMatchValidator });
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['userId'] && this.resetPasswordForm) {
|
||||
this.resetPasswordForm.reset({
|
||||
userId: this.userId,
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
|
||||
this.newPasswordType = 'password';
|
||||
this.confirmPasswordType = 'password';
|
||||
|
||||
this.passwordHideShow?.reset();
|
||||
this.confirmPasswordHideShow?.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
|
||||
const newPassword = group.get('newPassword')?.value;
|
||||
const confirmPassword = group.get('confirmPassword')?.value;
|
||||
return newPassword === confirmPassword ? null : { passwordMismatch: true };
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (this.resetPasswordForm.invalid) return;
|
||||
|
||||
const payload = {
|
||||
userId: this.userId,
|
||||
newPassword: this.resetPasswordForm.get('newPassword')?.value,
|
||||
porOrgaCode: this.storageService.getItem('POR_ORGACODE')
|
||||
};
|
||||
|
||||
this.httpURIService.requestPOST(URIKey.RESET_PASSWORD_URI, payload)
|
||||
.subscribe({
|
||||
next: (res) => {
|
||||
if (!(res instanceof HttpErrorResponse)) {
|
||||
this.i18nService.success(SuccessMessages.RESET_PASSWORD_SUCCESS, []);
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
this.resetPasswordForm.reset();
|
||||
this.newPasswordType = 'password';
|
||||
this.confirmPasswordType = 'password';
|
||||
|
||||
this.passwordHideShow?.reset();
|
||||
this.confirmPasswordHideShow?.reset();
|
||||
|
||||
const modal = document.getElementById('resetPasswordModal');
|
||||
modal?.classList.remove('show');
|
||||
modal?.setAttribute('aria-hidden', 'true');
|
||||
modal!.style.display = 'none';
|
||||
document.body.classList.remove('modal-open');
|
||||
document.querySelector('.modal-backdrop')?.remove();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue