diff --git a/src/app/user-management/change-password/change-password.component.html b/src/app/user-management/change-password/change-password.component.html index 7b66156..36762c2 100644 --- a/src/app/user-management/change-password/change-password.component.html +++ b/src/app/user-management/change-password/change-password.component.html @@ -16,7 +16,19 @@
- +
+
+ + +
+ +
+ {{ 'fieldRequired' | translate }} +
+
-
- {{ 'fieldRequired' | translate }} +
+
+ {{ 'fieldRequired' | translate }} +
+ +
+ {{ 'passwordPattern' | translate }} +
@@ -129,10 +146,21 @@ (onEyeClick)="togglePasswordType1()"> - -
- {{ newPasswordError$ | translate }} -
+
+
+ {{ 'fieldRequired' | translate }} +
+ +
+ {{ 'passwordPattern' | translate }} +
+
+ Old password and new password cannot be the same +
+ + +
+ @@ -153,8 +181,18 @@ (onEyeClick)="togglePasswordType2()"> -
- {{ confirmPasswordError$ | translate }} +
+
+ {{ 'fieldRequired' | translate }} +
+ +
+ {{ 'passwordPattern' | translate }} +
+ +
+ {{ 'passwordsDoNotMatch' | translate }} +
diff --git a/src/app/user-management/change-password/change-password.component.ts b/src/app/user-management/change-password/change-password.component.ts index 65aff72..1b10c47 100644 --- a/src/app/user-management/change-password/change-password.component.ts +++ b/src/app/user-management/change-password/change-password.component.ts @@ -43,30 +43,74 @@ constructor(private fb: FormBuilder, private httpURIService: HttpURIService, pri this.passwordType2 = this.passwordHideShow2?.showPassword ? 'password' : 'text'; } + ngOnInit(): void { + this.checkIfFirstTimeChangePasswordOrNot(); + + if (this.isFirstLogin) { + this.firstTimeLoginForm = this.fb.group({ + oldPassword: ['', Validators.required], + newPassword: ['', [ + Validators.required, + Validators.minLength(8), + Validators.maxLength(20), + Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/) + ]], + confirmPassword: ['', [ + Validators.required, + Validators.minLength(8), + Validators.maxLength(20), + Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/) + ]] + }, { validators: this.passwordMatchValidator }); + } + + else { + this.changePasswordForm = this.fb.group({ + oldPassword: ['', Validators.required], + newPassword: ['', [ + Validators.required, + Validators.minLength(8), + Validators.maxLength(20), + Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/) + ] + ], + confirmPassword: ['', [ + Validators.required, + Validators.minLength(8), + Validators.maxLength(20), + Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/) + ] + ] + }, { validators: [ + this.passwordMatchValidator, + this.oldAndNewPasswordNotSame, + ] + }, + ); + + } +} + passwordMatchValidator(group: AbstractControl): ValidationErrors | null { const newPassword = group.get('newPassword')?.value; const confirmPassword = group.get('confirmPassword')?.value; return newPassword === confirmPassword ? null : { passwordMismatch: true }; } + + oldAndNewPasswordNotSame(group: AbstractControl): ValidationErrors | null { + const oldPassword = group.get('oldPassword')?.value; + const newPassword = group.get('newPassword')?.value; - ngOnInit(): void { - this.checkIfFirstTimeChangePasswordOrNot(); - - if (this.isFirstLogin) { - this.firstTimeLoginForm = this.fb.group({ - newPassword: ['', [Validators.required, Validators.minLength(6)]], - confirmPassword: ['', [Validators.required, Validators.minLength(6)]] - }, { validators: this.passwordMatchValidator }); - } else { - this.changePasswordForm = this.fb.group({ - oldPassword: ['', Validators.required], - newPassword: ['', [Validators.required, Validators.minLength(6)]], - confirmPassword: ['', [Validators.required, Validators.minLength(6)]] -}, { validators: this.passwordMatchValidator }); - + if (!oldPassword || !newPassword) { + return null; } + + return oldPassword === newPassword + ? { oldAndNewPasswordSame: true } + : null; } + checkIfFirstTimeChangePasswordOrNot(): void { try { const currentUser: any = JSON.parse( @@ -80,37 +124,6 @@ constructor(private fb: FormBuilder, private httpURIService: HttpURIService, pri } } - initChangePasswordForm(): void { - this.changePasswordForm = this.fb.group( - { - oldPassword: ['', Validators.required], - enterNewPassword: ['', [Validators.required, Validators.minLength(6)]], - confirmPassword: ['', [Validators.required, Validators.minLength(6)]], - }, - { validators: this.passwordMatchValidator } - ); - } - - - get newPasswordError$() { - const control = this.changePasswordForm.get('newPassword'); - if (!control || !control.touched) return null; - - if (control.hasError('required')) return 'fieldRequired'; - if (control.hasError('minlength')) return 'passwordTooShort'; - return null; - } - - get confirmPasswordError$() { - const control = this.changePasswordForm.get('confirmPassword'); - if (!control || !control.touched) return null; - - if (control.hasError('required')) return 'fieldRequired'; - if (control.hasError('minlength')) return 'passwordTooShort'; - if (this.changePasswordForm.hasError('passwordMismatch')) return 'passwordsDoNotMatch'; - - return null; - } getFormPayload() { const form = this.isFirstLogin ? this.firstTimeLoginForm : this.changePasswordForm; diff --git a/src/app/user-management/reset-password/reset-password.component.html b/src/app/user-management/reset-password/reset-password.component.html index 0c3ccaf..b6cabee 100644 --- a/src/app/user-management/reset-password/reset-password.component.html +++ b/src/app/user-management/reset-password/reset-password.component.html @@ -39,13 +39,9 @@ formControlName="userId" placeholder="{{ 'userID' | translate }}" appNoWhitespaces - /> - + /> -
- {{ 'fieldRequired' | translate }} -
+ @@ -57,23 +53,26 @@ class="mandatory">*
-
- - - - -
-
- {{ newPasswordError | translate }} +
+ + + + +
+
+
+ {{ 'fieldRequired' | translate }} +
+
+ {{ 'passwordPattern' | translate }} +
+
-
@@ -94,8 +93,20 @@ -
- {{ confirmPasswordError | translate }} +
+
+ {{ 'fieldRequired' | translate }} +
+ +
+ {{ 'passwordPattern' | translate }} +
+ +
+ {{ 'passwordsDoNotMatch' | translate }} +
diff --git a/src/app/user-management/reset-password/reset-password.component.ts b/src/app/user-management/reset-password/reset-password.component.ts index c92d9f8..767a4d3 100644 --- a/src/app/user-management/reset-password/reset-password.component.ts +++ b/src/app/user-management/reset-password/reset-password.component.ts @@ -29,15 +29,27 @@ export class ResetPasswordComponent implements OnInit{ ngOnInit(): void { const userIdValue = this.storageService.getItem('USER_ID') this.resetPasswordForm = this.fb.group({ - - userId: [userIdValue || '', Validators.required], - newPassword: ['', [Validators.required, Validators.minLength(6)]], - confirmPassword: ['', [Validators.required, Validators.minLength(6)]] + userId: [{value: userIdValue || '', disabled: true}], + newPassword: ['', [ + Validators.required, + Validators.minLength(8), + Validators.maxLength(20), + Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/) + ] + ], + confirmPassword: ['', [ + Validators.required, + Validators.minLength(8), + Validators.maxLength(20), + Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/) + ] + ] }, { validators: this.passwordMatchValidator } ); + this.resetPasswordForm.get('newPassword')?.valueChanges.subscribe(()=>{ this.resetPasswordForm.get('confirmPassword')?.updateValueAndValidity(); }); @@ -50,39 +62,30 @@ export class ResetPasswordComponent implements OnInit{ togglePasswordType2() { this.passwordType2 = this.passwordHideShow2?.showPassword ? 'password' : 'text'; } + passwordMatchValidator(group: AbstractControl): ValidationErrors | null { - const newPassword = group.get('newPassword')?.value; - const confirmPassword = group.get('confirmPassword')?.value; - - return newPassword === confirmPassword ? null : { passwordMismatch: true }; - } - - get newPasswordError() { - const control = this.resetPasswordForm.get('newPassword'); - if (!control || !control.touched) return null; - - if (control.hasError('required')) return 'fieldRequired'; - if (control.hasError('minlength')) return 'passwordTooShort'; - return null; + const newPassword = group.get('newPassword'); + const confirmPassword = group.get('confirmPassword'); + if (!newPassword || !confirmPassword) return null; + if (confirmPassword.errors && !confirmPassword.errors['passwordMismatch']) { + return null; + } + if (newPassword.value !== confirmPassword.value) { + confirmPassword.setErrors({ passwordMismatch: true }); + return { passwordMismatch: true }; + } else { + confirmPassword.setErrors(null); + return null; + } } - get confirmPasswordError() { - const control = this.resetPasswordForm.get('confirmPassword'); - if (!control || !control.touched) return null; - - if (control.hasError('required')) return 'fieldRequired'; - if (control.hasError('minlength')) return 'passwordTooShort'; - if (this.resetPasswordForm.hasError('passwordMismatch')) return 'passwordsDoNotMatch'; - - return null; - } onSubmit() { if (this.resetPasswordForm.invalid) return; const payload = { - userId: this.resetPasswordForm.value.userId, - newPassword: this.resetPasswordForm.value.newPassword, + userId: this.resetPasswordForm.get('userId')?.value, + newPassword: this.resetPasswordForm.get('newPassword')?.value, porOrgaCode: this.storageService.getItem('POR_ORGACODE') }; this.httpURIService.requestPOST(URIKey.RESET_PASSWORD_URI, payload) @@ -93,7 +96,7 @@ export class ResetPasswordComponent implements OnInit{ } } }); - } + } } diff --git a/src/app/user-management/setup-user/setup-user.component.html b/src/app/user-management/setup-user/setup-user.component.html index 5d4aa2e..7b8dc27 100644 --- a/src/app/user-management/setup-user/setup-user.component.html +++ b/src/app/user-management/setup-user/setup-user.component.html @@ -41,8 +41,28 @@
- {{ 'fieldRequired' | translate }} +
+ {{ 'fieldRequired' | translate }} +
+ +
+
+ {{'userIdMinLength' | translate }}
+ +
+ {{'emptySpaceRestriction' | translate}} +
+ @@ -62,11 +82,28 @@ maxlength="500" placeholder="{{ 'userName' | translate }}" appNoWhitespaces rows="3" /> - - +
- {{ 'fieldRequired' | translate }} +
+ {{ 'fieldRequired' | translate }} +
+
+
+ {{'nameMinLength' | translate }} +
+
+ {{'emptySpaceRestriction' | translate}} +
@@ -113,7 +150,15 @@ placeholder="{{ 'passwordPlaceHolder' | translate }}" appNoWhitespaces/>
- {{ 'fieldRequired' | translate }} +
+ {{ 'fieldRequired' | translate }} +
+
+ {{ 'passwordPattern' | translate }} +
@@ -193,9 +238,10 @@ - - - + + + + @@ -204,6 +250,7 @@ +
{{'userID' | translate}}{{'Name' | translate}}{{'action' | translate}}{{'userID' | translate}}{{'Name' | translate}}{{'Role' | translate}}{{'action' | translate}}
{{ item.userId }} {{ item.userFullname }}{{item.role}} diff --git a/src/app/user-management/setup-user/setup-user.component.ts b/src/app/user-management/setup-user/setup-user.component.ts index 48202df..28bf7fd 100644 --- a/src/app/user-management/setup-user/setup-user.component.ts +++ b/src/app/user-management/setup-user/setup-user.component.ts @@ -10,8 +10,10 @@ import { ButtonManagementService } from '../../services/button-management.servic import { StorageService } from '../../shared/services/storage.service'; import { TableFilterPipe } from '../../shared/pipes/table-filter.pipe'; import { URIKey } from '../../utils/uri-enums'; -import { HttpParams } from '@angular/common/http'; +import { HttpErrorResponse, HttpParams } from '@angular/common/http'; import { HttpURIService } from '../../app.http.uri.service'; +import { I18NService } from '../../services/i18n.service'; +import { SuccessMessages } from '../../utils/enums'; @@ -52,7 +54,8 @@ export class SetupUserComponent implements OnInit { private fb: FormBuilder, private buttonManagementService: ButtonManagementService, private storageService: StorageService, - private httpService: HttpURIService + private httpService: HttpURIService, + private i18nService: I18NService ){} onSearch(value: string): void { @@ -95,13 +98,14 @@ export class SetupUserComponent implements OnInit { } this.httpService.requestPOST(URIKey.CREATE_USER, newUser).subscribe({ - next: () => { - this.userForm.reset(); - this.mode = 'edit'; - this.loadUsersDirect() - }, - - error: (err: any) => console.error(err) + next: (response) => { + if (!(response instanceof HttpErrorResponse)) { + this.i18nService.success(SuccessMessages.USER_CREATED_SUCCESS, []); + this.userForm.reset(); + this.mode = 'edit'; + this.loadUsersDirect() + } + } }); @@ -126,9 +130,25 @@ ngOnInit(): void { this.getButtonPermissions(); this.userForm = this.fb.group({ - userId: ['', [Validators.required]], - userFullname: ['', [Validators.required, Validators.maxLength(500)]], - defaultPassword: ['', Validators.required], + userId: ['', [ + Validators.required, + Validators.minLength(5), + Validators.pattern(/^\S+$/) + ] + ], + userFullname: ['', [ + Validators.required, + Validators.minLength(5), + Validators.pattern(/^\S+$/) + ] + ], + defaultPassword: ['', [ + Validators.required, + Validators.pattern( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,20}$/ + ) + ] + ], email: ['', [Validators.required, Validators.email]], userRole: [null, Validators.required] }); @@ -182,14 +202,13 @@ ngOnInit(): void { let params = new HttpParams().set('userId', userId); this.httpService.requestDELETE(URIKey.DELETE_USER, params).subscribe({ next: (response) =>{ + if (!(response instanceof HttpErrorResponse)) { + this.i18nService.success(SuccessMessages.USER_DELETE_SUCCESS, []); this.loadUsersDirect(); - this.userForm.reset() + this.userForm.reset(); this.selectedUserId = null; - }, - error: (err) =>{ - console.error('Error fetching users:', err); - this.allItems = []; - this.isLoading = false; + } + } }) } diff --git a/src/app/utils/enums.ts b/src/app/utils/enums.ts index 9cc8c4d..b4898ac 100644 --- a/src/app/utils/enums.ts +++ b/src/app/utils/enums.ts @@ -59,7 +59,10 @@ TRANSACTION_SUCCESSFUL = "TRANSACTION_SUCCESSFUL", SAVED_SUCCESSFULLY = "SAVED_SUCCESSFULLY", RECORD_DELETED_SUCCESSFULY = "RECORD_DELETED_SUCCESSFULY", ACCOUNT_CLOSED_SUCCESSFULLY = "ACCOUNT_CLOSED_SUCCESSFULLY", -SUCCESS_MESSAGE = "SUCCESS_MESSAGE" +SUCCESS_MESSAGE = "SUCCESS_MESSAGE", +USER_CREATED_SUCCESS = "USER_CREATED_SUCCESS", +USER_DELETE_SUCCESS = "USER_DELETE_SUCCESS" + } export enum MESSAGEKEY { diff --git a/src/assets/i18n/Arabic.json b/src/assets/i18n/Arabic.json index a9689a9..1f67ef6 100644 --- a/src/assets/i18n/Arabic.json +++ b/src/assets/i18n/Arabic.json @@ -260,5 +260,11 @@ "ERR_SEC_0005": "المستخدم غير موجود", "ERR_SEC_0006": "كلمة المرور التي تم إدخالها غير صحيحة", "toDateGreaterThanToday": "يجب أن يكون التاريخ الحالي أقل من التاريخ الحالي", - "fromDateGreaterThanToday": "يجب أن يكون تاريخ البدء أقل من التاريخ الحالي" + "fromDateGreaterThanToday": "يجب أن يكون تاريخ البدء أقل من التاريخ الحالي", + "userIdMinLength": "يجب أن يكون معرف المستخدم مكوّنًا من 5 أحرف على الأقل", + "nameMinLength": "يجب أن يكون الاسم مكوّنًا من 5 أحرف على الأقل", + "emptySpaceRestriction": "المسافات الفارغة غير مسموح بها", + "USER_CREATED_SUCCESS": "تم إنشاء المستخدم", + "USER_DELETE_SUCCESS": "تم حذف المستخدم" + } \ No newline at end of file diff --git a/src/assets/i18n/English.json b/src/assets/i18n/English.json index 35076b6..c30915b 100644 --- a/src/assets/i18n/English.json +++ b/src/assets/i18n/English.json @@ -112,7 +112,7 @@ "oldPassword":"Old Password", "newPasswordStatic":"New Password", "savePassword":"Save", - "passwordPattern":"Password must be over 8 characters and include an uppercase letter, a lower case letter, a number and a special character", + "passwordPattern":"Password must be 8–20 characters and include uppercase, lowercase, number, and special character", "SMSGatewaySelect":"Select Gateway", "selectIdentValueType": "Select Type", "IdTypeSelect":"Select Select Type", @@ -260,5 +260,10 @@ "ERR_SEC_0005": "User not found", "ERR_SEC_0006": "Incorrect password", "toDateGreaterThanToday": "To Date must be less than Current Date", - "fromDateGreaterThanToday": "From Date must be less than Current Date" + "fromDateGreaterThanToday": "From Date must be less than Current Date", + "userIdMinLength" : "User ID must be at least 5 characters", + "nameMinLength" : "Name must be at least 5 characters", + "emptySpaceRestriction" : "Empty spaces are not allowed", + "USER_CREATED_SUCCESS": "User Created", + "USER_DELETE_SUCCESS": "User Deleted" } \ No newline at end of file