mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-08 15:15:12 +00:00
feat(console): user metadata, rehaul detail pages (#2209)
* service, sidenav, i18n, dialog * detail layout, user detail * metadata dialog from * dialog * features * formarray * metadata component * comp * user metadata refresh * use formarray, control, bulk save * metadata revert, has feature directive * lint * lint * typo * info row user, warn color optim * card cleanup, actions for user detail * project, org, user, app rehaul * lint * scss * digit fix * features and project grid rehaul * info-section layout, org domain info * readd palette scss * add svg email warn * missing translation * rm unused ts * lockoutpolicy * check for lockout feature
This commit is contained in:
+1
-1
@@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.h1 {
|
||||
font-size: 1.2rem;
|
||||
font-size: 2rem;
|
||||
margin: 0 1rem;
|
||||
margin-left: 2rem;
|
||||
font-weight: normal;
|
||||
|
||||
+11
-5
@@ -9,14 +9,18 @@
|
||||
<p class="n-items" *ngIf="!loading && selection.selected.length > 0">{{'PROJECT.PAGES.PINNED' | translate}}</p>
|
||||
|
||||
<div class="item card" *ngFor="let item of selection.selected; index as i"
|
||||
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}"
|
||||
(click)="navigateToProject(item.projectId,item.grantId, $event)">
|
||||
<div class="text-part">
|
||||
<span *ngIf="item.details.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
|
||||
{{
|
||||
item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="name" *ngIf="item.projectName">{{ item.projectName }}</span>
|
||||
|
||||
<div class="name-row">
|
||||
<span class="name" *ngIf="item.projectName">{{ item.projectName }}</span>
|
||||
<div class="state-dot" [ngClass]="{'active': item.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE, 'inactive': item.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE}"></div>
|
||||
</div>
|
||||
|
||||
<span class="description" *ngIf="item.projectOwnerName">{{item.projectOwnerName}}</span>
|
||||
<span *ngIf="item.details.creationDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
|
||||
{{ item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
|
||||
@@ -32,14 +36,16 @@
|
||||
<p class="n-items" *ngIf="!loading && notPinned.length > 0">{{'PROJECT.PAGES.ALL' | translate}}</p>
|
||||
|
||||
<div class="item card" *ngFor="let item of notPinned; index as i"
|
||||
(click)="navigateToProject(item.projectId,item.grantId, $event)"
|
||||
[ngClass]="{ inactive: item.state !== ProjectState.PROJECT_STATE_ACTIVE}">
|
||||
(click)="navigateToProject(item.projectId,item.grantId, $event)">
|
||||
<div class="text-part">
|
||||
<span *ngIf="item.details.changeDate" class="top">{{'PROJECT.PAGES.LASTMODIFIED' | translate}}
|
||||
{{
|
||||
item.details.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="name" *ngIf="item.projectName">{{ item.projectName }}</span>
|
||||
<div class="name-row">
|
||||
<span class="name" *ngIf="item.projectName">{{ item.projectName }}</span>
|
||||
<div class="state-dot" [ngClass]="{'active': item.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE, 'inactive': item.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE}"></div>
|
||||
</div>
|
||||
<span class="description" *ngIf="item.projectOwnerName">{{item.projectOwnerName}}</span>
|
||||
<span *ngIf="item.details.creationDate" class="created">{{'PROJECT.PAGES.CREATEDON' | translate}}
|
||||
{{ item.details.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
|
||||
|
||||
+24
-5
@@ -44,7 +44,7 @@
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
margin: 1rem;
|
||||
flex-basis: 260px;
|
||||
flex-basis: 270px;
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
@@ -81,10 +81,29 @@
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-top: 1rem;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: .5rem;
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 1rem 0 .5rem 0;
|
||||
|
||||
.name {
|
||||
font-size: 1.2rem;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.state-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
&.active {
|
||||
background-color: var(--success);
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
background-color: var(--warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
|
||||
+75
-75
@@ -3,96 +3,96 @@ import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
import { GrantedProject, ProjectState } from 'src/app/proto/generated/zitadel/project_pb';
|
||||
import { GrantedProject, ProjectGrantState } from 'src/app/proto/generated/zitadel/project_pb';
|
||||
import { StorageKey, StorageService } from 'src/app/services/storage.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-granted-project-grid',
|
||||
templateUrl: './granted-project-grid.component.html',
|
||||
styleUrls: ['./granted-project-grid.component.scss'],
|
||||
animations: [
|
||||
trigger('list', [
|
||||
transition(':enter', [
|
||||
query('@animate',
|
||||
stagger(100, animateChild()),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
trigger('animate', [
|
||||
transition(':enter', [
|
||||
style({ opacity: 0, transform: 'translateY(-100%)' }),
|
||||
animate('100ms', style({ opacity: 1, transform: 'translateY(0)' })),
|
||||
]),
|
||||
transition(':leave', [
|
||||
style({ opacity: 1, transform: 'translateY(0)' }),
|
||||
animate('100ms', style({ opacity: 0, transform: 'translateY(100%)' })),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
selector: 'app-granted-project-grid',
|
||||
templateUrl: './granted-project-grid.component.html',
|
||||
styleUrls: ['./granted-project-grid.component.scss'],
|
||||
animations: [
|
||||
trigger('list', [
|
||||
transition(':enter', [
|
||||
query('@animate',
|
||||
stagger(100, animateChild()),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
trigger('animate', [
|
||||
transition(':enter', [
|
||||
style({ opacity: 0, transform: 'translateY(-100%)' }),
|
||||
animate('100ms', style({ opacity: 1, transform: 'translateY(0)' })),
|
||||
]),
|
||||
transition(':leave', [
|
||||
style({ opacity: 1, transform: 'translateY(0)' }),
|
||||
animate('100ms', style({ opacity: 0, transform: 'translateY(100%)' })),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class GrantedProjectGridComponent implements OnChanges {
|
||||
@Input() items: Array<GrantedProject.AsObject> = [];
|
||||
public notPinned: Array<GrantedProject.AsObject> = [];
|
||||
@Output() newClicked: EventEmitter<boolean> = new EventEmitter();
|
||||
@Output() changedView: EventEmitter<boolean> = new EventEmitter();
|
||||
@Input() loading: boolean = false;
|
||||
public selection: SelectionModel<GrantedProject.AsObject> = new SelectionModel<GrantedProject.AsObject>(true, []);
|
||||
@Input() items: Array<GrantedProject.AsObject> = [];
|
||||
public notPinned: Array<GrantedProject.AsObject> = [];
|
||||
@Output() newClicked: EventEmitter<boolean> = new EventEmitter();
|
||||
@Output() changedView: EventEmitter<boolean> = new EventEmitter();
|
||||
@Input() loading: boolean = false;
|
||||
public selection: SelectionModel<GrantedProject.AsObject> = new SelectionModel<GrantedProject.AsObject>(true, []);
|
||||
|
||||
public showNewProject: boolean = false;
|
||||
public ProjectState: any = ProjectState;
|
||||
public showNewProject: boolean = false;
|
||||
public ProjectGrantState: any = ProjectGrantState;
|
||||
|
||||
constructor(private storage: StorageService, private router: Router) {
|
||||
this.selection.changed.subscribe(selection => {
|
||||
this.setPrefixedItem('pinned-granted-projects', JSON.stringify(
|
||||
this.selection.selected.map(item => item.projectId),
|
||||
)).then(() => {
|
||||
selection.added.forEach(item => {
|
||||
const index = this.notPinned.findIndex(i => i.projectId === item.projectId);
|
||||
this.notPinned.splice(index, 1);
|
||||
});
|
||||
this.notPinned.push(...selection.removed);
|
||||
});
|
||||
constructor(private storage: StorageService, private router: Router) {
|
||||
this.selection.changed.subscribe(selection => {
|
||||
this.setPrefixedItem('pinned-granted-projects', JSON.stringify(
|
||||
this.selection.selected.map(item => item.projectId),
|
||||
)).then(() => {
|
||||
selection.added.forEach(item => {
|
||||
const index = this.notPinned.findIndex(i => i.projectId === item.projectId);
|
||||
this.notPinned.splice(index, 1);
|
||||
});
|
||||
}
|
||||
this.notPinned.push(...selection.removed);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public addItem(): void {
|
||||
this.newClicked.emit(true);
|
||||
}
|
||||
public addItem(): void {
|
||||
this.newClicked.emit(true);
|
||||
}
|
||||
|
||||
public ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.items?.currentValue && changes.items.currentValue.length > 0) {
|
||||
this.notPinned = Object.assign([], this.items);
|
||||
this.reorganizeItems();
|
||||
}
|
||||
public ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.items?.currentValue && changes.items.currentValue.length > 0) {
|
||||
this.notPinned = Object.assign([], this.items);
|
||||
this.reorganizeItems();
|
||||
}
|
||||
}
|
||||
|
||||
public reorganizeItems(): void {
|
||||
this.getPrefixedItem('pinned-granted-projects').then(storageEntry => {
|
||||
if (storageEntry) {
|
||||
const array: string[] = JSON.parse(storageEntry);
|
||||
const toSelect: GrantedProject.AsObject[] = this.items.filter((item, index) => {
|
||||
if (array.includes(item.projectId)) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
this.selection.select(...toSelect);
|
||||
}
|
||||
public reorganizeItems(): void {
|
||||
this.getPrefixedItem('pinned-granted-projects').then(storageEntry => {
|
||||
if (storageEntry) {
|
||||
const array: string[] = JSON.parse(storageEntry);
|
||||
const toSelect: GrantedProject.AsObject[] = this.items.filter((item, index) => {
|
||||
if (array.includes(item.projectId)) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.selection.select(...toSelect);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async getPrefixedItem(key: string): Promise<string | null> {
|
||||
const org = this.storage.getItem<Org.AsObject>(StorageKey.organization) as Org.AsObject;
|
||||
return localStorage.getItem(`${org.id}:${key}`);
|
||||
}
|
||||
private async getPrefixedItem(key: string): Promise<string | null> {
|
||||
const org = this.storage.getItem<Org.AsObject>(StorageKey.organization) as Org.AsObject;
|
||||
return localStorage.getItem(`${org.id}:${key}`);
|
||||
}
|
||||
|
||||
private async setPrefixedItem(key: string, value: any): Promise<void> {
|
||||
const org = this.storage.getItem<Org.AsObject>(StorageKey.organization) as Org.AsObject;
|
||||
return localStorage.setItem(`${org.id}:${key}`, value);
|
||||
}
|
||||
private async setPrefixedItem(key: string, value: any): Promise<void> {
|
||||
const org = this.storage.getItem<Org.AsObject>(StorageKey.organization) as Org.AsObject;
|
||||
return localStorage.setItem(`${org.id}:${key}`, value);
|
||||
}
|
||||
|
||||
public navigateToProject(projectId: string, id: string, event: any): void {
|
||||
if (event && event.srcElement && event.srcElement.localName !== 'button') {
|
||||
this.router.navigate(['/granted-projects', projectId, 'grant', id]);
|
||||
}
|
||||
public navigateToProject(projectId: string, id: string, event: any): void {
|
||||
if (event && event.srcElement && event.srcElement.localName !== 'button') {
|
||||
this.router.navigate(['/granted-projects', projectId, 'grant', id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sub {
|
||||
|
||||
Reference in New Issue
Block a user