mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-13 18:32:10 +00:00
fix(console): cleanup user detail and member components, user/me redirect, permission guards, filter, org policy guard, user table, scss cleanup (#808)
* fix: remove user.write guard for filtering * border color * fix user routing from member tables * idp detail layout * generic contact component * fix redirect to auth user, user grant disable * disable policy action without permission, i18n * user-create flex fix, contact ng-content * rm unused styles * sidenav divider * lint * chore(deps-dev): bump @angular/cli from 10.1.3 to 10.1.4 in /console (#806) * fix: user session with external login (#797) * fix: user session with external login * fix: tests * fix: tests * fix: change idp config name * fix(container): stop copying / and instead only copy zitadel (#691) * chore: stop copying / and instead only copy zitadel * Update Dockerfile * Update release.yml * enable anchors debug * fix(container): don't copy alpine content into scratch execpt pwd * chore: remove need step * merge master * chore(deps-dev): bump @angular/cli from 10.1.3 to 10.1.4 in /console Bumps [@angular/cli](https://github.com/angular/angular-cli) from 10.1.3 to 10.1.4. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/compare/v10.1.3...v10.1.4) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Florian Forster <florian@caos.ch> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular/language-service from 10.1.3 to 10.1.4 in /console (#805) * fix: user session with external login (#797) * fix: user session with external login * fix: tests * fix: tests * fix: change idp config name * fix(container): stop copying / and instead only copy zitadel (#691) * chore: stop copying / and instead only copy zitadel * Update Dockerfile * Update release.yml * enable anchors debug * fix(container): don't copy alpine content into scratch execpt pwd * chore: remove need step * merge master * chore(deps-dev): bump @angular/language-service in /console Bumps [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) from 10.1.3 to 10.1.4. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/10.1.4/packages/language-service) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Florian Forster <florian@caos.ch> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump codelyzer from 6.0.0 to 6.0.1 in /console (#804) * fix: user session with external login (#797) * fix: user session with external login * fix: tests * fix: tests * fix: change idp config name * fix(container): stop copying / and instead only copy zitadel (#691) * chore: stop copying / and instead only copy zitadel * Update Dockerfile * Update release.yml * enable anchors debug * fix(container): don't copy alpine content into scratch execpt pwd * chore: remove need step * merge master * chore(deps-dev): bump codelyzer from 6.0.0 to 6.0.1 in /console Bumps [codelyzer](https://github.com/mgechev/codelyzer) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/mgechev/codelyzer/releases) - [Changelog](https://github.com/mgechev/codelyzer/blob/master/CHANGELOG.md) - [Commits](https://github.com/mgechev/codelyzer/commits/6.0.1) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Florian Forster <florian@caos.ch> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @angular-devkit/build-angular from 0.1000.8 to 0.1001.4 in /console (#803) * fix: user session with external login (#797) * fix: user session with external login * fix: tests * fix: tests * fix: change idp config name * fix(container): stop copying / and instead only copy zitadel (#691) * chore: stop copying / and instead only copy zitadel * Update Dockerfile * Update release.yml * enable anchors debug * fix(container): don't copy alpine content into scratch execpt pwd * chore: remove need step * merge master * chore(deps-dev): bump @angular-devkit/build-angular in /console Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 0.1000.8 to 0.1001.4. - [Release notes](https://github.com/angular/angular-cli/releases) - [Commits](https://github.com/angular/angular-cli/commits) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Florian Forster <florian@caos.ch> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> * chore(deps): bump uuid from 8.3.0 to 8.3.1 in /console (#802) * fix: user session with external login (#797) * fix: user session with external login * fix: tests * fix: tests * fix: change idp config name * fix(container): stop copying / and instead only copy zitadel (#691) * chore: stop copying / and instead only copy zitadel * Update Dockerfile * Update release.yml * enable anchors debug * fix(container): don't copy alpine content into scratch execpt pwd * chore: remove need step * merge master * chore(deps): bump uuid from 8.3.0 to 8.3.1 in /console Bumps [uuid](https://github.com/uuidjs/uuid) from 8.3.0 to 8.3.1. - [Release notes](https://github.com/uuidjs/uuid/releases) - [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md) - [Commits](https://github.com/uuidjs/uuid/compare/v8.3.0...v8.3.1) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Florian Forster <florian@caos.ch> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * create memberstable as common component * iam member cleanup * iam + org m table, user table service user avatar * toast config * fix selection emitter * fix project grant table width * project grant members refactor * theme optimizations * member table col delete * lint * fix table row color * refactor grey color * lint scss * org list redirect on click, fix user table undef * refresh table after grant add Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
@@ -15,14 +15,15 @@
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
display: flex wrap;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
margin: 0 -.5rem;
|
||||
|
||||
.section {
|
||||
padding: .5rem;
|
||||
flex-basis: 100%;
|
||||
color: #8795a1;
|
||||
color: var(--grey);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,15 @@
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
display: flex wrap;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
margin: 0 -.5rem;
|
||||
|
||||
.section {
|
||||
padding: .5rem;
|
||||
flex-basis: 100%;
|
||||
color: #8795a1;
|
||||
color: var(--grey);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,18 +27,12 @@
|
||||
</div>
|
||||
</app-card>
|
||||
|
||||
<!-- <div class="col" *ngIf="user"> -->
|
||||
<app-card *ngIf="user" class="app-card" title="{{ 'USER.PROFILE.TITLE' | translate }}">
|
||||
<app-detail-form [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
|
||||
(changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)">
|
||||
</app-detail-form>
|
||||
</app-card>
|
||||
|
||||
<!-- <app-card title="Theme" class="app-card theme-card">
|
||||
<app-theme-setting></app-theme-setting>
|
||||
</app-card> -->
|
||||
<!-- </div> -->
|
||||
|
||||
<app-card *ngIf="user && user.human && user.id" title="{{ 'USER.EXTERNALIDP.TITLE' | translate }}"
|
||||
description="{{ 'USER.EXTERNALIDP.DESC' | translate }}">
|
||||
<app-external-idps [userId]="user.id" [service]="userService"></app-external-idps>
|
||||
@@ -46,101 +40,10 @@
|
||||
|
||||
<app-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
||||
<div class="method-col">
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.PROFILE.PASSWORD' | translate }}</span>
|
||||
|
||||
<span>*********</span>
|
||||
<div>
|
||||
<a [routerLink]="['password']" mat-icon-button>
|
||||
<mat-icon class="icon">chevron_right</mat-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.EMAIL' | translate }}</span>
|
||||
|
||||
<ng-container *ngIf="!emailEditState; else emailEdit">
|
||||
<div class="actions">
|
||||
<span class="name">{{user?.human?.email}}</span>
|
||||
<mat-icon class="icon" *ngIf="user?.human?.isEmailVerified" color="primary"
|
||||
aria-hidden="false" aria-label="verified icon">
|
||||
check_circle_outline</mat-icon>
|
||||
<ng-container *ngIf="user?.human?.email && !user?.human?.isEmailVerified">
|
||||
<mat-icon class="icon" color="warn" aria-hidden="false" aria-label="not verified icon">
|
||||
highlight_off
|
||||
</mat-icon>
|
||||
<a class="verify" matTooltip="{{'USER.LOGINMETHODS.EMAIL.RESEND' | translate}}"
|
||||
(click)="resendVerification()">{{'USER.LOGINMETHODS.RESENDCODE' | translate}}</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button (click)="emailEditState = true" mat-icon-button>
|
||||
<mat-icon class="icon">edit</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #emailEdit>
|
||||
<mat-form-field class="name">
|
||||
<mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>
|
||||
<input *ngIf="user.human && user.human.email !== undefined && user.human.email !== null"
|
||||
matInput [(ngModel)]="user.human.email" />
|
||||
</mat-form-field>
|
||||
<button (click)="emailEditState = false" mat-icon-button>
|
||||
<mat-icon class="icon">close</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="user.human" [disabled]="!user.human.email" type="button" color="primary"
|
||||
(click)="saveEmail()" mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.PHONE' | translate }}</span>
|
||||
|
||||
<ng-container *ngIf="!phoneEditState; else phoneEdit">
|
||||
<div class="actions">
|
||||
<span class="name">{{user?.human?.phone}}</span>
|
||||
<mat-icon class="icon" *ngIf="user?.human?.isPhoneVerified" color="primary"
|
||||
aria-hidden="false" aria-label="verified icon">
|
||||
check_circle_outline</mat-icon>
|
||||
<ng-container *ngIf="user?.human?.phone && !user?.human?.isPhoneVerified">
|
||||
<mat-icon class="icon" color="warn" aria-hidden="false" aria-label="not verified icon">
|
||||
highlight_off
|
||||
</mat-icon>
|
||||
<a class="verify" matTooltip="{{'USER.LOGINMETHODS.ENTERCODE_DESC' | translate}}"
|
||||
(click)="enterCode()">{{'USER.LOGINMETHODS.ENTERCODE' | translate}}</a>
|
||||
<a class="verify" matTooltip="{{'USER.LOGINMETHODS.PHONE.RESEND' | translate}}"
|
||||
(click)="resendPhoneVerification()">{{'USER.LOGINMETHODS.RESENDCODE' | translate}}</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button (click)="phoneEditState = true" mat-icon-button>
|
||||
<mat-icon class="icon">edit</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #phoneEdit>
|
||||
<mat-form-field class="name">
|
||||
<mat-label>{{ 'USER.PHONE' | translate }}</mat-label>
|
||||
<input *ngIf="user.human && user.human.phone !== undefined && user.human.phone !== null"
|
||||
matInput [(ngModel)]="user.human.phone" />
|
||||
</mat-form-field>
|
||||
<button (click)="phoneEditState = false" mat-icon-button>
|
||||
<mat-icon class="icon">close</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="user.human && user.human.phone" color="warn" (click)="deletePhone()"
|
||||
mat-icon-button>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
<button *ngIf="user.human" [disabled]="!user.human.phone" type="button" color="primary"
|
||||
(click)="savePhone()" mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
<app-contact *ngIf="user?.human" [human]="user.human" (savedPhone)="savePhone($event)"
|
||||
(savedEmail)="saveEmail($event)" (enteredPhoneCode)="enteredPhoneCode($event)"
|
||||
(deletedPhone)="deletePhone()" (resendEmailVerification)="resendEmailVerification()"
|
||||
(resendPhoneVerification)="resendPhoneVerification()"></app-contact>
|
||||
</app-card>
|
||||
|
||||
<app-auth-user-mfa *ngIf="user" #mfaComponent></app-auth-user-mfa>
|
||||
@@ -149,7 +52,7 @@
|
||||
<div *ngIf="user" class="side" metainfo>
|
||||
<div class="details">
|
||||
<div class="row" *ngIf="user?.preferredLoginName">
|
||||
<span class="first">Preferred Loginname:</span>
|
||||
<span class="first">{{'USER.PREFERRED_LOGINNAME' | translate}}</span>
|
||||
<span class="second"><span style="display: block;">{{user.preferredLoginName}}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.sub {
|
||||
color: #8795a1;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
.theme {
|
||||
@@ -53,59 +53,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.method-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: -.5rem;
|
||||
|
||||
.method-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: .5rem;
|
||||
border-bottom: 1px solid #ffffff20;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.label,
|
||||
.name {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: .9rem;
|
||||
color: #8795a1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin: .5rem;
|
||||
}
|
||||
|
||||
.verify {
|
||||
text-decoration: none;
|
||||
font-size: .8rem;
|
||||
color: #8795a1;
|
||||
border-radius: .5rem;
|
||||
cursor: pointer;
|
||||
word-wrap: none;
|
||||
white-space: nowrap;
|
||||
margin-right: 1rem;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.col {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -116,14 +63,6 @@
|
||||
flex: 1;
|
||||
margin: .5rem;
|
||||
}
|
||||
|
||||
// .theme-card {
|
||||
// max-width: 300px;
|
||||
|
||||
// @media only screen and (max-width: 450px) {
|
||||
// max-width: none;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.side {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
||||
@@ -7,8 +6,6 @@ import { Gender, UserAddress, UserEmail, UserPhone, UserProfile, UserView } from
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { CodeDialogComponent } from './code-dialog/code-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth-user-detail',
|
||||
templateUrl: './auth-user-detail.component.html',
|
||||
@@ -22,9 +19,6 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
|
||||
private subscription: Subscription = new Subscription();
|
||||
|
||||
public emailEditState: boolean = false;
|
||||
public phoneEditState: boolean = false;
|
||||
|
||||
public loading: boolean = false;
|
||||
|
||||
public copied: string = '';
|
||||
@@ -36,7 +30,6 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
public translate: TranslateService,
|
||||
private toast: ToastService,
|
||||
public userService: GrpcAuthService,
|
||||
private dialog: MatDialog,
|
||||
) {
|
||||
this.loading = true;
|
||||
this.userService.GetMyUser().then(user => {
|
||||
@@ -79,50 +72,31 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
public saveEmail(): void {
|
||||
this.emailEditState = false;
|
||||
|
||||
if (this.user.human) {
|
||||
this.userService
|
||||
.SaveMyUserEmail(this.user.human.email).then((data: UserEmail) => {
|
||||
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.email = data.toObject().email;
|
||||
}
|
||||
this.emailEditState = false;
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
this.emailEditState = false;
|
||||
});
|
||||
}
|
||||
public saveEmail(email: string): void {
|
||||
this.userService
|
||||
.SaveMyUserEmail(email).then((data: UserEmail) => {
|
||||
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.email = data.toObject().email;
|
||||
}
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public enterCode(): void {
|
||||
if (this.user.human) {
|
||||
const dialogRef = this.dialog.open(CodeDialogComponent, {
|
||||
data: {
|
||||
number: this.user.human.phone,
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(code => {
|
||||
if (code) {
|
||||
this.userService.VerifyMyUserPhone(code).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
public enteredPhoneCode(code: string): void {
|
||||
this.userService.VerifyMyUserPhone(code).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public changedLanguage(language: string): void {
|
||||
this.translate.use(language);
|
||||
}
|
||||
|
||||
public resendVerification(): void {
|
||||
public resendEmailVerification(): void {
|
||||
this.userService.ResendEmailVerification().then(() => {
|
||||
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
|
||||
}).catch(error => {
|
||||
@@ -139,32 +113,26 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
public deletePhone(): void {
|
||||
if (this.user.human) {
|
||||
this.userService.RemoveMyUserPhone().then(() => {
|
||||
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.phone = '';
|
||||
}
|
||||
this.phoneEditState = false;
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
this.userService.RemoveMyUserPhone().then(() => {
|
||||
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.phone = '';
|
||||
}
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public savePhone(): void {
|
||||
this.phoneEditState = false;
|
||||
public savePhone(phone: string): void {
|
||||
if (this.user.human) {
|
||||
this.userService
|
||||
.SaveMyUserPhone(this.user.human.phone).then((data: UserPhone) => {
|
||||
.SaveMyUserPhone(phone).then((data: UserPhone) => {
|
||||
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.phone = data.toObject().phone;
|
||||
}
|
||||
this.phoneEditState = false;
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
this.phoneEditState = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
.theme-conent,
|
||||
.crescent {
|
||||
background-color: $primary-dark;
|
||||
transition: background-color .4s cubic-bezier(.645, .045, .355, 1);
|
||||
transition: background-color .3s cubic-bezier(.645, .045, .355, 1); // cubic-bezier(.645, .045, .355, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ $light-background: rgb(220, 220, 220);
|
||||
background: $light-background;
|
||||
transform: scale(0);
|
||||
transform-origin: top right;
|
||||
transition: transform .4s cubic-bezier(.645, .045, .355, 1);
|
||||
transition: transform .2s cubic-bezier(.645, .045, .355, 1);
|
||||
}
|
||||
|
||||
p {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<div class="method-col">
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.PROFILE.PASSWORD' | translate }}</span>
|
||||
|
||||
<span>*********</span>
|
||||
<div>
|
||||
<ng-content select="[phoneAction]"></ng-content>
|
||||
<a [disabled]="!canWrite" [routerLink]="['password']" mat-icon-button>
|
||||
<mat-icon class="icon">chevron_right</mat-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.EMAIL' | translate }}</span>
|
||||
|
||||
<ng-container *ngIf="!emailEditState; else emailEdit">
|
||||
<div class="actions">
|
||||
<span class="name">{{human?.email}}</span>
|
||||
<mat-icon class="icon" *ngIf="human?.isEmailVerified" color="primary" aria-hidden="false"
|
||||
aria-label="verified icon">
|
||||
check_circle_outline</mat-icon>
|
||||
<ng-container *ngIf="human?.email && !human?.isEmailVerified">
|
||||
<mat-icon class="icon" color="warn" aria-hidden="false" aria-label="not verified icon">
|
||||
highlight_off
|
||||
</mat-icon>
|
||||
<a *ngIf="canWrite" class="verify" matTooltip="{{'USER.LOGINMETHODS.EMAIL.RESEND' | translate}}"
|
||||
(click)="emitEmailVerification()">{{'USER.LOGINMETHODS.RESENDCODE' | translate}}</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button [disabled]="!canWrite" (click)="emailEditState = true" mat-icon-button>
|
||||
<mat-icon class="icon">edit</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #emailEdit>
|
||||
<mat-form-field class="name">
|
||||
<mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>
|
||||
<input *ngIf="human && human.email !== undefined && human.email !== null" matInput
|
||||
[(ngModel)]="human.email" />
|
||||
</mat-form-field>
|
||||
<button (click)="emailEditState = false" mat-icon-button>
|
||||
<mat-icon class="icon">close</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="human" [disabled]="!human.email" type="button" color="primary" (click)="saveEmail()"
|
||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.PHONE' | translate }}</span>
|
||||
|
||||
<ng-container *ngIf="!phoneEditState; else phoneEdit">
|
||||
<div class="actions">
|
||||
<span class="name">{{human?.phone}}</span>
|
||||
<mat-icon class="icon" *ngIf="human?.isPhoneVerified" color="primary" aria-hidden="false"
|
||||
aria-label="verified icon">
|
||||
check_circle_outline</mat-icon>
|
||||
<ng-container *ngIf="human?.phone && !human?.isPhoneVerified">
|
||||
<mat-icon class="icon" matTooltip="not verified" color="warn" aria-hidden="false"
|
||||
aria-label="not verified icon">
|
||||
highlight_off
|
||||
</mat-icon>
|
||||
<a *ngIf="!disablePhoneCode && !canWrite" class="verify"
|
||||
matTooltip="{{'USER.LOGINMETHODS.ENTERCODE_DESC' | translate}}"
|
||||
(click)="enterCode()">{{'USER.LOGINMETHODS.ENTERCODE' | translate}}</a>
|
||||
<a *ngIf="canWrite" class="verify" matTooltip="{{'USER.LOGINMETHODS.PHONE.RESEND' | translate}}"
|
||||
(click)="emitPhoneVerification()">{{'USER.LOGINMETHODS.RESENDCODE' | translate}}</a>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button [disabled]="!canWrite" (click)="phoneEditState = true" mat-icon-button>
|
||||
<mat-icon class="icon">edit</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #phoneEdit>
|
||||
<mat-form-field class="name">
|
||||
<mat-label>{{ 'USER.PHONE' | translate }}</mat-label>
|
||||
<input *ngIf="human && human.phone !== undefined && human.phone !== null" matInput
|
||||
[(ngModel)]="human.phone" />
|
||||
</mat-form-field>
|
||||
<button (click)="phoneEditState = false" mat-icon-button>
|
||||
<mat-icon class="icon">close</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="human && human.phone" color="warn" (click)="emitDeletePhone()" mat-icon-button>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
<button *ngIf="human" [disabled]="!human.phone" type="button" color="primary" (click)="savePhone()"
|
||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,47 @@
|
||||
.method-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: -.5rem;
|
||||
|
||||
.method-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: .5rem;
|
||||
border-bottom: 1px solid #ffffff20;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.actions {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: .9rem;
|
||||
min-width: 100px;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin: .5rem;
|
||||
}
|
||||
|
||||
.verify {
|
||||
text-decoration: none;
|
||||
font-size: .8rem;
|
||||
color: var(--grey);
|
||||
border-radius: .5rem;
|
||||
cursor: pointer;
|
||||
word-wrap: none;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ContactComponent } from './contact.component';
|
||||
|
||||
describe('ContactComponent', () => {
|
||||
let component: ContactComponent;
|
||||
let fixture: ComponentFixture<ContactComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ContactComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ContactComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { HumanView as AuthHumanView } from 'src/app/proto/generated/auth_pb';
|
||||
import { HumanView as MgmtHumanView } from 'src/app/proto/generated/management_pb';
|
||||
|
||||
import { CodeDialogComponent } from '../auth-user-detail/code-dialog/code-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contact',
|
||||
templateUrl: './contact.component.html',
|
||||
styleUrls: ['./contact.component.scss'],
|
||||
})
|
||||
export class ContactComponent implements OnInit {
|
||||
@Input() disablePhoneCode: boolean = false;
|
||||
@Input() canWrite: boolean = false;
|
||||
@Input() human!: AuthHumanView.AsObject | MgmtHumanView.AsObject;
|
||||
@Output() savedPhone: EventEmitter<string> = new EventEmitter();
|
||||
@Output() savedEmail: EventEmitter<string> = new EventEmitter();
|
||||
@Output() resendEmailVerification: EventEmitter<void> = new EventEmitter();
|
||||
@Output() resendPhoneVerification: EventEmitter<void> = new EventEmitter();
|
||||
@Output() enteredPhoneCode: EventEmitter<string> = new EventEmitter();
|
||||
@Output() deletedPhone: EventEmitter<void> = new EventEmitter();
|
||||
|
||||
public emailEditState: boolean = false;
|
||||
public phoneEditState: boolean = false;
|
||||
constructor(private dialog: MatDialog) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
savePhone(): void {
|
||||
this.phoneEditState = false;
|
||||
this.savedPhone.emit(this.human.phone);
|
||||
}
|
||||
|
||||
emitDeletePhone(): void {
|
||||
this.phoneEditState = false;
|
||||
this.deletedPhone.emit();
|
||||
}
|
||||
|
||||
saveEmail(): void {
|
||||
this.emailEditState = false;
|
||||
this.savedEmail.emit(this.human.email);
|
||||
}
|
||||
|
||||
emitEmailVerification(): void {
|
||||
this.resendEmailVerification.emit();
|
||||
}
|
||||
|
||||
emitPhoneVerification(): void {
|
||||
this.resendPhoneVerification.emit();
|
||||
}
|
||||
|
||||
public enterCode(): void {
|
||||
if (this.human) {
|
||||
const dialogRef = this.dialog.open(CodeDialogComponent, {
|
||||
data: {
|
||||
number: this.human.phone,
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(code => {
|
||||
if (code) {
|
||||
this.enteredPhoneCode.emit(code);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
|
||||
firstName: [{ value: '', disabled: this.disabled }, Validators.required],
|
||||
lastName: [{ value: '', disabled: this.disabled }, Validators.required],
|
||||
nickName: [{ value: '', disabled: this.disabled }],
|
||||
gender: [{ value: 0 }, { disabled: this.disabled }],
|
||||
gender: [{ value: 0, disabled: this.disabled }],
|
||||
preferredLanguage: [{ value: '', disabled: this.disabled }],
|
||||
});
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
|
||||
firstName: [{ value: '', disabled: this.disabled }, Validators.required],
|
||||
lastName: [{ value: '', disabled: this.disabled }, Validators.required],
|
||||
nickName: [{ value: '', disabled: this.disabled }],
|
||||
gender: [{ value: 0 }, { disabled: this.disabled }],
|
||||
gender: [{ value: 0, disabled: this.disabled }],
|
||||
preferredLanguage: [{ value: '', disabled: this.disabled }],
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: #8795a1;
|
||||
color: var(--grey);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
.left {
|
||||
color: #8795a1;
|
||||
color: var(--grey);
|
||||
margin-right: 1rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: .5rem;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
.sub-header {
|
||||
font-size: .8rem;
|
||||
color: #8795a1;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
.people {
|
||||
@@ -84,7 +84,7 @@
|
||||
box-sizing: border-box;
|
||||
font-size: 8px;
|
||||
border-radius: .5rem;
|
||||
transition: background-color .2s ease-in-out;
|
||||
transition: background-color .3s cubic-bezier(.645, .045, .355, 1);
|
||||
background-color: $accent-color;
|
||||
cursor: pointer;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AuthGuard } from 'src/app/guards/auth.guard';
|
||||
import { RoleGuard } from 'src/app/guards/role.guard';
|
||||
import { UserGuard } from 'src/app/guards/user.guard';
|
||||
|
||||
import { AuthUserDetailComponent } from './auth-user-detail/auth-user-detail.component';
|
||||
import { PasswordComponent } from './password/password.component';
|
||||
@@ -41,7 +42,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: ':id',
|
||||
component: UserDetailComponent,
|
||||
canActivate: [AuthGuard, RoleGuard],
|
||||
canActivate: [AuthGuard, UserGuard, RoleGuard],
|
||||
data: {
|
||||
roles: ['user.read'],
|
||||
animation: 'HomePage',
|
||||
|
||||
@@ -46,6 +46,7 @@ import { PasswordComponent } from './password/password.component';
|
||||
import { UserDetailRoutingModule } from './user-detail-routing.module';
|
||||
import { UserDetailComponent } from './user-detail/user-detail.component';
|
||||
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
|
||||
import { ContactComponent } from './contact/contact.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -60,6 +61,7 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
|
||||
MembershipsComponent,
|
||||
MachineKeysComponent,
|
||||
ExternalIdpsComponent,
|
||||
ContactComponent,
|
||||
],
|
||||
imports: [
|
||||
UserDetailRoutingModule,
|
||||
|
||||
@@ -68,129 +68,34 @@
|
||||
|
||||
<app-card *ngIf="user.human" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
|
||||
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
|
||||
<div class="method-col">
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.PROFILE.PASSWORD' | translate }}</span>
|
||||
<span>******</span>
|
||||
<div>
|
||||
<button [disabled]="(canWrite$ | async) == false" (click)="sendSetPasswordNotification()"
|
||||
mat-stroked-button color="primary"
|
||||
*ngIf="user.state === UserState.USERSTATE_INITIAL">{{ 'USER.PASSWORD.RESENDNOTIFICATION' | translate }}</button>
|
||||
<a [disabled]="(canWrite$ | async) == false" [routerLink]="['password']" mat-icon-button>
|
||||
<mat-icon class="icon">chevron_right</mat-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.EMAIL' | translate }}</span>
|
||||
|
||||
<ng-container *ngIf="!emailEditState; else emailEdit">
|
||||
<div class="actions">
|
||||
<span class="name">{{user?.human?.email}}</span>
|
||||
<mat-icon class="icon" *ngIf="user?.human?.isEmailVerified" color="primary"
|
||||
aria-hidden="false" aria-label="verified icon">
|
||||
check_circle_outline</mat-icon>
|
||||
<ng-container *ngIf="user?.human?.email && !user?.human?.isEmailVerified">
|
||||
<mat-icon class="icon" color="warn" aria-hidden="false" aria-label="not verified icon">
|
||||
highlight_off
|
||||
</mat-icon>
|
||||
<ng-container *ngIf="(canWrite$ | async)">
|
||||
<a class="verify" matTooltip="{{'USER.LOGINMETHODS.EMAIL.RESEND' | translate}}"
|
||||
(click)="resendVerification()">{{'USER.LOGINMETHODS.RESENDCODE' | translate}}</a>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button [disabled]="(canWrite$ | async) == false" (click)="emailEditState = true"
|
||||
mat-icon-button>
|
||||
<mat-icon class="icon">edit</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #emailEdit>
|
||||
<mat-form-field class="name">
|
||||
<mat-label>{{ 'USER.EMAIL' | translate }}</mat-label>
|
||||
<input matInput
|
||||
*ngIf="user.human && user.human.email !== undefined && user.human.email !== null"
|
||||
[(ngModel)]="user.human.email" />
|
||||
</mat-form-field>
|
||||
<button (click)="emailEditState = false" mat-icon-button>
|
||||
<mat-icon class="icon">close</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="user.human" [disabled]="!user.human.email || (canWrite$ | async) == false"
|
||||
type="button" color="primary" (click)="saveEmail()"
|
||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="method-row">
|
||||
<span class="label">{{ 'USER.PHONE' | translate }}</span>
|
||||
|
||||
<ng-container *ngIf="!phoneEditState; else phoneEdit">
|
||||
<div class="actions">
|
||||
<span class="name">{{user?.human?.phone}}</span>
|
||||
<mat-icon class="icon" *ngIf="user?.human?.isPhoneVerified" color="primary"
|
||||
aria-hidden="false" aria-label="verified icon">
|
||||
check_circle_outline</mat-icon>
|
||||
<ng-container *ngIf="user?.human?.phone && !user?.human?.isPhoneVerified">
|
||||
<mat-icon class="icon" color="warn" aria-hidden="false" aria-label="not verified icon">
|
||||
highlight_off
|
||||
</mat-icon>
|
||||
<ng-container *ngIf="(canWrite$ | async)">
|
||||
<a class="verify" matTooltip="{{'USER.LOGINMETHODS.PHONE.RESEND' | translate}}"
|
||||
(click)="resendPhoneVerification()">{{'USER.LOGINMETHODS.RESENDCODE' | translate}}</a>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button [disabled]="(canWrite$ | async) == false" (click)="phoneEditState = true"
|
||||
mat-icon-button>
|
||||
<mat-icon class="icon">edit</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #phoneEdit>
|
||||
<mat-form-field class="name">
|
||||
<mat-label>{{ 'USER.PHONE' | translate }}</mat-label>
|
||||
<input *ngIf="user.human && user.human.phone !== undefined && user.human.phone !== null"
|
||||
matInput [disabled]="(canWrite$ | async) == false" [(ngModel)]="user.human.phone" />
|
||||
</mat-form-field>
|
||||
<button matTooltip="{{ 'ACTIONS.CLOSE' | translate }}" (click)="phoneEditState = false"
|
||||
mat-icon-button [disabled]="(canWrite$ | async) == false">
|
||||
<mat-icon class="icon">close</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="user.human?.phone" color="warn" (click)="deletePhone()"
|
||||
[disabled]="(canWrite$ | async) == false" mat-icon-button
|
||||
matTooltip="{{ 'ACTIONS.CLEAR' | translate }}">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
|
||||
<button *ngIf="user.human" [disabled]="!user.human.phone || (canWrite$ | async) == false"
|
||||
type="button" color="primary" (click)="savePhone()"
|
||||
matTooltip="{{ 'ACTIONS.SAVE' | translate }}" mat-raised-button>
|
||||
{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
<app-contact disablePhoneCode="true"
|
||||
[canWrite]="(['user.write:' + user?.id, 'user.write'] | hasRole | async)" *ngIf="user?.human"
|
||||
[human]="user.human" (savedPhone)="savePhone($event)" (savedEmail)="saveEmail($event)"
|
||||
(deletedPhone)="deletePhone()" (resendEmailVerification)="resendEmailVerification()"
|
||||
(resendPhoneVerification)="resendPhoneVerification()">
|
||||
<button phoneAction [disabled]="(canWrite$ | async) == false" (click)="sendSetPasswordNotification()"
|
||||
mat-stroked-button color="primary"
|
||||
*ngIf="user.state === UserState.USERSTATE_INITIAL">{{ 'USER.PASSWORD.RESENDNOTIFICATION' | translate }}</button>
|
||||
</app-contact>
|
||||
</app-card>
|
||||
|
||||
<app-user-mfa *ngIf="user && user.human" [user]="user"></app-user-mfa>
|
||||
|
||||
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
|
||||
description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
|
||||
<app-user-grants [userId]="user.id" [allowCreate]="['user.grant.write'] | hasRole | async"
|
||||
<app-user-grants [userId]="user.id"
|
||||
[allowWrite]="['user.grant.write$'+ 'user.grant.write:'+user?.id] | hasRole | async"
|
||||
[displayedColumns]="['select', 'projectId', 'creationDate', 'changeDate', 'roleNamesList']"
|
||||
[allowDelete]="['user.grant.delete'] | hasRole | async"></app-user-grants>
|
||||
[allowDelete]="['user.grant.delete$', 'user.grant.delete'+ user?.id] | hasRole | async">
|
||||
</app-user-grants>
|
||||
</app-card>
|
||||
</div>
|
||||
|
||||
<div *ngIf="user" class="side" metainfo>
|
||||
<div class="details">
|
||||
<div class="row" *ngIf="user?.preferredLoginName">
|
||||
<span class="first">Preferred Loginname:</span>
|
||||
<span class="first">{{'USER.PREFERRED_LOGINNAME' | translate}}</span>
|
||||
<span class="second"><span style="display: block;">{{user.preferredLoginName}}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,69 +23,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.method-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: -.5rem;
|
||||
|
||||
.method-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: .5rem;
|
||||
border-bottom: 1px solid #ffffff20;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.label,
|
||||
.name {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: .9rem;
|
||||
color: #818a8a;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin: .5rem;
|
||||
}
|
||||
|
||||
.verify {
|
||||
text-decoration: none;
|
||||
font-size: .8rem;
|
||||
color: #8795a1;
|
||||
border-radius: .5rem;
|
||||
cursor: pointer;
|
||||
word-wrap: none;
|
||||
white-space: nowrap;
|
||||
margin-right: 1rem;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 700px) {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.label,
|
||||
.name {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.img-phone-email {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,6 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
public languages: string[] = ['de', 'en'];
|
||||
|
||||
private subscription: Subscription = new Subscription();
|
||||
public emailEditState: boolean = false;
|
||||
public phoneEditState: boolean = false;
|
||||
|
||||
public ChangeType: any = ChangeType;
|
||||
public loading: boolean = false;
|
||||
@@ -127,7 +125,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
public resendVerification(): void {
|
||||
public resendEmailVerification(): void {
|
||||
this.mgmtUserService.ResendEmailVerification(this.user.id).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
|
||||
}).catch(error => {
|
||||
@@ -136,6 +134,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public resendPhoneVerification(): void {
|
||||
console.log('resend phone ver', this.user.id);
|
||||
this.mgmtUserService.ResendPhoneVerification(this.user.id).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
|
||||
}).catch(error => {
|
||||
@@ -149,37 +148,32 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
if (this.user.human) {
|
||||
this.user.human.phone = '';
|
||||
}
|
||||
this.phoneEditState = false;
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public saveEmail(): void {
|
||||
this.emailEditState = false;
|
||||
if (this.user && this.user.human?.email) {
|
||||
this.mgmtUserService
|
||||
.SaveUserEmail(this.user.id, this.user.human.email).then((data: UserEmail) => {
|
||||
this.toast.showInfo('USER.TOAST.EMAILSENT', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.email = data.toObject().email;
|
||||
}
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
public saveEmail(email: string): void {
|
||||
if (this.user.id && email) {
|
||||
this.mgmtUserService.SaveUserEmail(this.user.id, email).then((data: UserEmail) => {
|
||||
this.toast.showInfo('USER.TOAST.EMAILSENT', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.email = data.toObject().email;
|
||||
}
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public savePhone(): void {
|
||||
this.phoneEditState = false;
|
||||
if (this.user && this.user.human?.phone) {
|
||||
public savePhone(phone: string): void {
|
||||
if (this.user.id && phone) {
|
||||
this.mgmtUserService
|
||||
.SaveUserPhone(this.user.id, this.user.human.phone).then((data: UserPhone) => {
|
||||
.SaveUserPhone(this.user.id, phone).then((data: UserPhone) => {
|
||||
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
|
||||
if (this.user.human) {
|
||||
this.user.human.phone = data.toObject().phone;
|
||||
}
|
||||
this.phoneEditState = false;
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,6 @@ h1 {
|
||||
}
|
||||
|
||||
.sub {
|
||||
color: #8795a1;
|
||||
color: var(--grey);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
|
||||
[timestamp]="userResult?.viewTimestamp" [selection]="selection"
|
||||
[emitRefreshOnPreviousRoute]="refreshOnPreviousRoute">
|
||||
<ng-template appHasRole [appHasRole]="['user.write']" actions>
|
||||
<mat-form-field @appearfade *ngIf="userSearchKey != undefined" actions class="filtername">
|
||||
<mat-label>{{'USER.PAGES.FILTER' | translate}}</mat-label>
|
||||
<input matInput (keyup)="applyFilter($event)"
|
||||
[placeholder]="('USER.TABLE.FILTER.' + userSearchKey.toString()) | translate" #input>
|
||||
</mat-form-field>
|
||||
<mat-form-field @appearfade *ngIf="userSearchKey != undefined" actions class="filtername">
|
||||
<mat-label>{{'USER.PAGES.FILTER' | translate}}</mat-label>
|
||||
<input matInput (keyup)="applyFilter($event)"
|
||||
[placeholder]="('USER.TABLE.FILTER.' + userSearchKey.toString()) | translate" #input>
|
||||
</mat-form-field>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['user.write']" actions>
|
||||
<button (click)="deactivateSelectedUsers()" matTooltip="{{'USER.TABLE.DEACTIVATE' | translate}}"
|
||||
class="icon-button" mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
||||
<mat-icon svgIcon="mdi_account_cancel"></mat-icon>
|
||||
@@ -25,18 +25,24 @@
|
||||
<div class="table-wrapper">
|
||||
<table class="table" mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<th mat-header-cell *matHeaderCellDef class="selection">
|
||||
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
|
||||
[checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let user">
|
||||
<td mat-cell *matCellDef="let user" class="selection">
|
||||
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(user) : null" [checked]="selection.isSelected(user)">
|
||||
<app-avatar *ngIf="user[userType] && user[userType].displayName" class="avatar"
|
||||
[name]="user[userType].displayName" [size]="32">
|
||||
<app-avatar
|
||||
*ngIf="user[userType] && user[userType].displayName && user[userType]?.firstName && user[userType]?.lastName; else cog"
|
||||
class="avatar" [name]="user[userType].displayName" [size]="32">
|
||||
</app-avatar>
|
||||
<ng-template #cog>
|
||||
<div class="sa-icon">
|
||||
<i class="las la-user-cog"></i>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
|
||||
@@ -8,7 +7,7 @@
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0 1rem;
|
||||
padding: .5rem;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
@@ -19,6 +18,11 @@
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.selection {
|
||||
width: 50px;
|
||||
max-width: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,3 +56,13 @@ th {
|
||||
.filtername {
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
||||
.sa-icon {
|
||||
display: block;
|
||||
width: 32px;
|
||||
margin: 0 .5rem;
|
||||
|
||||
i {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatInput } from '@angular/material/input';
|
||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@@ -29,6 +30,7 @@ export class UserTableComponent implements OnInit {
|
||||
@Input() refreshOnPreviousRoute: string = '';
|
||||
@Input() disabled: boolean = false;
|
||||
@ViewChild(MatPaginator) public paginator!: MatPaginator;
|
||||
@ViewChild('input') public filter!: MatInput;
|
||||
public dataSource: MatTableDataSource<UserView.AsObject> = new MatTableDataSource<UserView.AsObject>();
|
||||
public selection: SelectionModel<UserView.AsObject> = new SelectionModel<UserView.AsObject>(true, []);
|
||||
public userResult!: UserSearchResponse.AsObject;
|
||||
@@ -38,6 +40,7 @@ export class UserTableComponent implements OnInit {
|
||||
|
||||
@Output() public changedSelection: EventEmitter<Array<UserView.AsObject>> = new EventEmitter();
|
||||
UserSearchKey: any = UserSearchKey;
|
||||
|
||||
constructor(
|
||||
public translate: TranslateService,
|
||||
private userService: ManagementService,
|
||||
@@ -119,6 +122,7 @@ export class UserTableComponent implements OnInit {
|
||||
|
||||
public applyFilter(event: Event): void {
|
||||
const filterValue = (event.target as HTMLInputElement).value;
|
||||
|
||||
this.getData(
|
||||
this.paginator.pageSize,
|
||||
this.paginator.pageIndex * this.paginator.pageSize,
|
||||
@@ -128,6 +132,12 @@ export class UserTableComponent implements OnInit {
|
||||
}
|
||||
|
||||
public setFilter(key: UserSearchKey): void {
|
||||
setTimeout(() => {
|
||||
if (this.filter) {
|
||||
(this.filter as any).nativeElement.focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
if (this.userSearchKey !== key) {
|
||||
this.userSearchKey = key;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user