Reset password, refactored user listing dialogs

This commit is contained in:
Adina Țeudan 2021-07-08 14:26:59 +03:00
parent d9d9b2fe18
commit 542f0a282d
19 changed files with 315 additions and 244 deletions

View File

@ -34,7 +34,8 @@ import { ActiveFieldsListingComponent } from './dialogs/file-attributes-csv-impo
import { AdminSideNavComponent } from './admin-side-nav/admin-side-nav.component';
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { ReportsScreenComponent } from './screens/reports/reports-screen.component';
import { ResetPasswordDialogComponent } from './dialogs/reset-password-dialog/reset-password-dialog.component';
import { ResetPasswordComponent } from './dialogs/add-edit-user-dialog/reset-password/reset-password.component';
import { UserDetailsComponent } from './dialogs/add-edit-user-dialog/user-details/user-details.component';
const dialogs = [
AddEditDossierTemplateDialogComponent,
@ -45,8 +46,7 @@ const dialogs = [
SmtpAuthDialogComponent,
AddEditUserDialogComponent,
ConfirmDeleteUsersDialogComponent,
FileAttributesCsvImportDialogComponent,
ResetPasswordDialogComponent
FileAttributesCsvImportDialogComponent
];
const screens = [
@ -73,6 +73,8 @@ const components = [
UsersStatsComponent,
ActiveFieldsListingComponent,
AdminSideNavComponent,
ResetPasswordComponent,
UserDetailsComponent,
...dialogs,
...screens

View File

@ -1,79 +1,17 @@
<section class="dialog">
<div class="dialog-header heading-l">
{{ (user ? 'add-edit-user.title.edit' : 'add-edit-user.title.new') | translate }}
</div>
<redaction-user-details
(closeDialog)="dialogRef.close($event)"
(toggleResetPassword)="toggleResetPassword()"
*ngIf="!resettingPassword"
[user]="user"
></redaction-user-details>
<form (submit)="save()" [formGroup]="userForm">
<div class="dialog-content">
<div class="red-input-group required w-300">
<label translate="add-edit-user.form.first-name"></label>
<input formControlName="firstName" name="firstName" type="text" />
</div>
<div class="red-input-group required w-300">
<label translate="add-edit-user.form.last-name"></label>
<input formControlName="lastName" name="lastName" type="text" />
</div>
<div class="red-input-group required w-300">
<label translate="add-edit-user.form.email"></label>
<input formControlName="email" name="email" type="email" />
</div>
<!-- <div class="red-input-group required w-300">-->
<!-- <label translate="add-edit-user.form.password"></label>-->
<!-- <input formControlName="password" name="password" type="password" />-->
<!-- </div>-->
<div class="red-input-group">
<label translate="add-edit-user.form.role"></label>
<div class="roles-wrapper">
<mat-checkbox
*ngFor="let role of ROLES"
[formControlName]="role"
color="primary"
>
{{ 'roles.' + role | translate }}
</mat-checkbox>
</div>
</div>
<div
(click)="resetPassword()"
*ngIf="!!user"
class="mt-24 fit-content link-action"
translate="add-edit-user.form.reset-password"
></div>
</div>
<div class="dialog-actions">
<button
[disabled]="userForm.invalid || !changed"
color="primary"
mat-flat-button
type="submit"
>
{{
(user ? 'add-edit-user.actions.save-changes' : 'add-edit-user.actions.save')
| translate
}}
</button>
<redaction-icon-button
(action)="delete()"
*ngIf="user"
icon="red:trash"
text="add-edit-user.actions.delete"
type="show-bg"
></redaction-icon-button>
<div
class="all-caps-label cancel"
mat-dialog-close
translate="add-edit-user.actions.cancel"
></div>
</div>
</form>
<redaction-reset-password
(close)="dialogRef.close($event)"
(toggleResetPassword)="toggleResetPassword()"
*ngIf="resettingPassword"
[user]="user"
></redaction-reset-password>
<redaction-circle-button
class="dialog-close"

View File

@ -1,7 +0,0 @@
.roles-wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 8px;
margin-top: 8px;
width: 300px;
}

View File

@ -1,9 +1,6 @@
import { Component, Inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { User } from '@redaction/red-ui-http';
import { UserService } from '@services/user.service';
import { AdminDialogService } from '../../services/admin-dialog.service';
@Component({
selector: 'redaction-add-edit-user-dialog',
@ -11,99 +8,14 @@ import { AdminDialogService } from '../../services/admin-dialog.service';
styleUrls: ['./add-edit-user-dialog.component.scss']
})
export class AddEditUserDialogComponent {
userForm: FormGroup;
readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
private readonly _ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
resettingPassword = false;
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _userService: UserService,
private readonly _dialogService: AdminDialogService,
public dialogRef: MatDialogRef<AddEditUserDialogComponent>,
@Inject(MAT_DIALOG_DATA) public user: User
) {
const rolesControls = this.ROLES.reduce(
(prev, role) => ({
...prev,
[role]: [
{
value: this.user && this.user.roles.indexOf(role) !== -1,
disabled:
this.user &&
Object.keys(this._ROLE_REQUIREMENTS).reduce(
(value, key) =>
value ||
(role === this._ROLE_REQUIREMENTS[key] &&
this.user.roles.indexOf(key) !== -1),
false
)
}
]
}),
{}
);
this.userForm = this._formBuilder.group({
firstName: [this.user?.firstName, Validators.required],
lastName: [this.user?.lastName, Validators.required],
email: [
{ value: this.user?.email, disabled: !!user },
[Validators.required, Validators.email]
],
// password: [this.user?.password, Validators.required],
...rolesControls
});
this._setRolesRequirements();
}
) {}
get changed(): boolean {
if (!this.user) return true;
for (const key of Object.keys(this.userForm.getRawValue())) {
if (this.user[key] !== this.userForm.get(key).value) {
return true;
}
}
return false;
}
get activeRoles(): string[] {
return this.ROLES.reduce((acc, role) => {
if (this.userForm.get(role).value) {
acc.push(role);
}
return acc;
}, []);
}
async save() {
this.dialogRef.close({
action: this.user ? 'UPDATE' : 'CREATE',
user: { ...this.userForm.getRawValue(), roles: this.activeRoles }
});
}
async delete() {
this.dialogRef.close('DELETE');
}
resetPassword() {
// this._dialogService.openre;
this._dialogService.openDialog('resetPassword', null, { user: this.user }, (res: any) => {
console.log(res);
});
}
private _setRolesRequirements() {
for (const key of Object.keys(this._ROLE_REQUIREMENTS)) {
this.userForm.controls[key].valueChanges.subscribe(checked => {
if (checked) {
this.userForm.patchValue({ [this._ROLE_REQUIREMENTS[key]]: true });
this.userForm.controls[this._ROLE_REQUIREMENTS[key]].disable();
} else {
this.userForm.controls[this._ROLE_REQUIREMENTS[key]].enable();
}
});
}
toggleResetPassword() {
this.resettingPassword = !this.resettingPassword;
}
}

View File

@ -0,0 +1,26 @@
<div
[translateParams]="{ userName: userName }"
[translate]="'reset-password-dialog.header'"
class="dialog-header heading-l"
></div>
<form (submit)="save()" [formGroup]="passwordForm">
<div class="dialog-content">
<div class="red-input-group required w-300">
<label translate="reset-password-dialog.form.password"></label>
<input formControlName="temporaryPassword" name="temporaryPassword" type="password" />
</div>
</div>
<div class="dialog-actions">
<button [disabled]="passwordForm.invalid" color="primary" mat-flat-button type="submit">
{{ 'reset-password-dialog.actions.save' | translate }}
</button>
<div
(click)="toggleResetPassword.emit()"
class="all-caps-label cancel"
translate="reset-password-dialog.actions.cancel"
></div>
</div>
</form>

View File

@ -0,0 +1,46 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { User, UserControllerService } from '@redaction/red-ui-http';
import { UserService } from '../../../../../services/user.service';
import { LoadingService } from '../../../../../services/loading.service';
@Component({
selector: 'redaction-reset-password',
templateUrl: './reset-password.component.html',
styleUrls: ['./reset-password.component.scss']
})
export class ResetPasswordComponent {
passwordForm: FormGroup;
@Input() user: User;
@Output() toggleResetPassword = new EventEmitter();
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _userControllerService: UserControllerService,
private readonly _userService: UserService,
private readonly _loadingService: LoadingService
) {
this.passwordForm = this._formBuilder.group({
temporaryPassword: [null, Validators.required]
});
}
get userName() {
return this._userService.getNameForId(this.user.userId);
}
async save() {
this._loadingService.start();
await this._userControllerService
.resetPassword(
{
password: this.passwordForm.get('temporaryPassword').value,
temporary: true
},
this.user.userId
)
.toPromise();
this._loadingService.stop();
this.toggleResetPassword.emit();
}
}

View File

@ -0,0 +1,66 @@
<div class="dialog-header heading-l">
{{ (user ? 'add-edit-user.title.edit' : 'add-edit-user.title.new') | translate }}
</div>
<form (submit)="save()" [formGroup]="userForm">
<div class="dialog-content">
<div class="red-input-group required w-300">
<label translate="add-edit-user.form.first-name"></label>
<input formControlName="firstName" name="firstName" type="text" />
</div>
<div class="red-input-group required w-300">
<label translate="add-edit-user.form.last-name"></label>
<input formControlName="lastName" name="lastName" type="text" />
</div>
<div class="red-input-group required w-300">
<label translate="add-edit-user.form.email"></label>
<input formControlName="email" name="email" type="email" />
</div>
<div class="red-input-group">
<label translate="add-edit-user.form.role"></label>
<div class="roles-wrapper">
<mat-checkbox *ngFor="let role of ROLES" [formControlName]="role" color="primary">
{{ 'roles.' + role | translate }}
</mat-checkbox>
</div>
</div>
<div
(click)="toggleResetPassword.emit()"
*ngIf="!!user"
class="mt-24 fit-content link-action"
translate="add-edit-user.form.reset-password"
></div>
</div>
<div class="dialog-actions">
<button
[disabled]="userForm.invalid || !changed"
color="primary"
mat-flat-button
type="submit"
>
{{
(user ? 'add-edit-user.actions.save-changes' : 'add-edit-user.actions.save')
| translate
}}
</button>
<redaction-icon-button
(action)="delete()"
*ngIf="user"
icon="red:trash"
text="add-edit-user.actions.delete"
type="show-bg"
></redaction-icon-button>
<div
class="all-caps-label cancel"
mat-dialog-close
translate="add-edit-user.actions.cancel"
></div>
</div>
</form>

View File

@ -0,0 +1,7 @@
.roles-wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 8px;
margin-top: 8px;
width: 300px;
}

View File

@ -0,0 +1,121 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { User, UserControllerService } from '@redaction/red-ui-http';
import { AdminDialogService } from '../../../services/admin-dialog.service';
import { LoadingService } from '../../../../../services/loading.service';
@Component({
selector: 'redaction-user-details',
templateUrl: './user-details.component.html',
styleUrls: ['./user-details.component.scss']
})
export class UserDetailsComponent implements OnInit {
@Input() user: User;
@Output() toggleResetPassword = new EventEmitter();
@Output() closeDialog = new EventEmitter<any>();
userForm: FormGroup;
readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
private readonly _ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _dialogService: AdminDialogService,
private readonly _loadingService: LoadingService,
private readonly _userControllerService: UserControllerService
) {}
get changed(): boolean {
if (!this.user) return true;
if (this.user.roles.length !== this.activeRoles.length) {
return true;
}
for (const key of Object.keys(this.userForm.getRawValue())) {
const keyValue = this.userForm.get(key).value;
if (key.startsWith('RED_')) {
if (this.user.roles.includes(key) !== keyValue) {
return true;
}
} else if (this.user[key] !== keyValue) {
return true;
}
}
return false;
}
get activeRoles(): string[] {
return this.ROLES.reduce((acc, role) => {
if (this.userForm.get(role).value) {
acc.push(role);
}
return acc;
}, []);
}
ngOnInit() {
const rolesControls = this.ROLES.reduce(
(prev, role) => ({
...prev,
[role]: [
{
value: this.user && this.user.roles.indexOf(role) !== -1,
disabled:
this.user &&
Object.keys(this._ROLE_REQUIREMENTS).reduce(
(value, key) =>
value ||
(role === this._ROLE_REQUIREMENTS[key] &&
this.user.roles.indexOf(key) !== -1),
false
)
}
]
}),
{}
);
this.userForm = this._formBuilder.group({
firstName: [this.user?.firstName, Validators.required],
lastName: [this.user?.lastName, Validators.required],
email: [
{ value: this.user?.email, disabled: !!this.user },
[Validators.required, Validators.email]
],
...rolesControls
});
this._setRolesRequirements();
}
async save() {
this._loadingService.start();
const userData = { ...this.userForm.getRawValue(), roles: this.activeRoles };
if (!this.user) {
await this._userControllerService.createUser(userData).toPromise();
} else {
await this._userControllerService.updateProfile(userData, this.user.userId).toPromise();
}
this.closeDialog.emit(true);
}
async delete() {
this._dialogService.openDialog('deleteUsers', null, [this.user], async () => {
this.closeDialog.emit(true);
});
}
private _setRolesRequirements() {
for (const key of Object.keys(this._ROLE_REQUIREMENTS)) {
this.userForm.controls[key].valueChanges.subscribe(checked => {
if (checked) {
this.userForm.patchValue({ [this._ROLE_REQUIREMENTS[key]]: true });
this.userForm.controls[this._ROLE_REQUIREMENTS[key]].disable();
} else {
this.userForm.controls[this._ROLE_REQUIREMENTS[key]].enable();
}
});
}
}
}

View File

@ -1,7 +1,8 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { User } from '@redaction/red-ui-http';
import { User, UserControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { LoadingService } from '../../../../services/loading.service';
@Component({
selector: 'redaction-confirm-delete-users-dialog',
@ -17,9 +18,11 @@ export class ConfirmDeleteUsersDialogComponent {
dossiersCount: number;
constructor(
@Inject(MAT_DIALOG_DATA) public users: User[],
private readonly _appStateService: AppStateService,
public dialogRef: MatDialogRef<ConfirmDeleteUsersDialogComponent>
private readonly _loadingService: LoadingService,
private readonly _userControllerService: UserControllerService,
public dialogRef: MatDialogRef<ConfirmDeleteUsersDialogComponent>,
@Inject(MAT_DIALOG_DATA) public users: User[]
) {
this.dossiersCount = this._appStateService.allDossiers.filter(dw => {
for (const user of this.users) {
@ -41,6 +44,10 @@ export class ConfirmDeleteUsersDialogComponent {
async deleteUser() {
if (this.valid) {
this._loadingService.start();
await this._userControllerService
.deleteUsers(this.users.map(u => u.userId))
.toPromise();
this.dialogRef.close(true);
} else {
this.showToast = true;

View File

@ -1,24 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ResetPasswordDialogComponent } from './reset-password-dialog.component';
describe('ResetPasswordDialogComponent', () => {
let component: ResetPasswordDialogComponent;
let fixture: ComponentFixture<ResetPasswordDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ResetPasswordDialogComponent]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ResetPasswordDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,14 +0,0 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'redaction-reset-password-dialog',
templateUrl: './reset-password-dialog.component.html',
styleUrls: ['./reset-password-dialog.component.scss']
})
export class ResetPasswordDialogComponent implements OnInit {
constructor() {}
ngOnInit(): void {
console.log('do sth');
}
}

View File

@ -131,7 +131,7 @@
type="dark-bg"
></redaction-circle-button>
<redaction-circle-button
(action)="openDeleteUserDialog([user], $event)"
(action)="openDeleteUsersDialog([user], $event)"
[disabled]="user.userId === userService.userId"
icon="red:trash"
tooltip="user-listing.action.delete"

View File

@ -43,29 +43,13 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
}
openAddEditUserDialog($event: MouseEvent, user?: User) {
this._dialogService.openDialog('addEditUser', $event, user, async result => {
if (result === 'DELETE') {
return this.openDeleteUserDialog([user]);
}
this._loadingService.start();
if (result.action === 'CREATE') {
await this._userControllerService.createUser(result.user).toPromise();
} else if (result.action === 'UPDATE') {
await this._userControllerService
.updateProfile(result.user, user.userId)
.toPromise();
}
this._dialogService.openDialog('addEditUser', $event, user, async () => {
await this._loadData();
});
}
openDeleteUserDialog(users: User[], $event?: MouseEvent) {
openDeleteUsersDialog(users: User[], $event?: MouseEvent) {
this._dialogService.openDialog('deleteUsers', $event, users, async () => {
this._loadingService.start();
await this._userControllerService.deleteUsers(users.map(u => u.userId)).toPromise();
await this._loadData();
});
}
@ -90,7 +74,7 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
}
async bulkDelete() {
this.openDeleteUserDialog(this.allEntities.filter(u => this.isSelected(u)));
this.openDeleteUsersDialog(this.allEntities.filter(u => this.isSelected(u)));
}
trackById(index: number, user: User) {

View File

@ -10,12 +10,10 @@ import { SmtpAuthDialogComponent } from '../dialogs/smtp-auth-dialog/smtp-auth-d
import { AddEditUserDialogComponent } from '../dialogs/add-edit-user-dialog/add-edit-user-dialog.component';
import { ConfirmDeleteUsersDialogComponent } from '../dialogs/confirm-delete-users-dialog/confirm-delete-users-dialog.component';
import { FileAttributesCsvImportDialogComponent } from '../dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component';
import { ResetPasswordDialogComponent } from '../dialogs/reset-password-dialog/reset-password-dialog.component';
import { ComponentType } from '@angular/cdk/portal';
type DialogType =
| 'confirm'
| 'resetPassword'
| 'addEditDictionary'
| 'editColor'
| 'addEditFileAttribute'
@ -50,10 +48,6 @@ export class AdminDialogService {
confirm: {
component: ConfirmationDialogComponent
},
resetPassword: {
component: ResetPasswordDialogComponent,
dialogConfig: { autoFocus: true }
},
addEditDictionary: {
component: AddEditDictionaryDialogComponent,
dialogConfig: { autoFocus: true }

View File

@ -339,6 +339,16 @@
"formula": "Formula"
}
},
"reset-password-dialog": {
"header": "Set Temporary Password for {{userName}}",
"form": {
"password": "Temporary password"
},
"actions": {
"save": "Save",
"cancel": "Cancel"
}
},
"comment": "Comment",
"comments": {
"add-comment": "Enter comment",

View File

@ -364,3 +364,7 @@ section.settings {
.d-flex {
display: flex;
}
.cdk-overlay-container {
z-index: 800;
}