mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-13 18:22:12 +00:00
fix(console): horizontal toggle for users, projects, improve UI/UX (#4047)
* fix(console): horizontal toggle for users, projects * improve input contrast * toggles, profile UI fix * lint * fix safari styles * fix button placement redirects * style lint Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -80,7 +80,7 @@
|
||||
min-width: 320px;
|
||||
|
||||
.formfield {
|
||||
flex: 1;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
button {
|
||||
|
||||
@@ -9,22 +9,21 @@
|
||||
<p class="sub cnsl-secondary-text max-width-description">{{ 'PROJECT.PAGES.LISTDESCRIPTION' | translate }}</p>
|
||||
|
||||
<div class="projects-controls">
|
||||
<div class="project-type-actions">
|
||||
<button
|
||||
class="type-button"
|
||||
[ngClass]="{ active: (projectType$ | async) === ProjectType.PROJECTTYPE_OWNED }"
|
||||
(click)="setType(ProjectType.PROJECTTYPE_OWNED)"
|
||||
>
|
||||
{{ 'PROJECT.PAGES.TYPE.OWNED' | translate }} ({{ (mgmtService?.ownedProjectsCount | async) ?? 0 }})
|
||||
</button>
|
||||
<button
|
||||
class="type-button"
|
||||
[ngClass]="{ active: (projectType$ | async) === ProjectType.PROJECTTYPE_GRANTED }"
|
||||
(click)="setType(ProjectType.PROJECTTYPE_GRANTED)"
|
||||
>
|
||||
{{ 'PROJECT.PAGES.TYPE.GRANTED' | translate }} ({{ (mgmtService?.grantedProjectsCount | async) ?? 0 }})
|
||||
</button>
|
||||
<div class="project-toggle-group">
|
||||
<cnsl-nav-toggle
|
||||
label="{{ 'PROJECT.PAGES.TYPE.OWNED' | translate }}"
|
||||
[count]="mgmtService.ownedProjectsCount | async"
|
||||
(clicked)="setType(ProjectType.PROJECTTYPE_OWNED)"
|
||||
[active]="(projectType$ | async) === ProjectType.PROJECTTYPE_OWNED"
|
||||
></cnsl-nav-toggle>
|
||||
<cnsl-nav-toggle
|
||||
label="{{ 'PROJECT.PAGES.TYPE.GRANTED' | translate }}"
|
||||
[count]="mgmtService.grantedProjectsCount | async"
|
||||
(clicked)="setType(ProjectType.PROJECTTYPE_GRANTED)"
|
||||
[active]="(projectType$ | async) === ProjectType.PROJECTTYPE_GRANTED"
|
||||
></cnsl-nav-toggle>
|
||||
</div>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
<button class="grid-btn" (click)="grid = !grid" mat-icon-button [attr.data-e2e]="'toggle-grid'">
|
||||
<i *ngIf="grid" class="show list view las la-th-list"></i>
|
||||
|
||||
@@ -23,34 +23,33 @@
|
||||
|
||||
.projects-controls {
|
||||
display: flex;
|
||||
padding-bottom: 0.5rem;
|
||||
align-items: center;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid map-get($foreground, dividers);
|
||||
|
||||
.project-type-actions {
|
||||
.project-toggle-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.type-button {
|
||||
border: none;
|
||||
background: none;
|
||||
text-align: left;
|
||||
padding: 0.75rem 0;
|
||||
opacity: 0.6;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
color: map-get($foreground, text);
|
||||
.toggle-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:first-child {
|
||||
margin-right: 1rem;
|
||||
i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
.info-i {
|
||||
font-size: 1.2rem;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: 600;
|
||||
opacity: 1;
|
||||
.current-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: rgb(84, 142, 230);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
export class ProjectsComponent {
|
||||
public zitadelProjectId: string = '';
|
||||
public projectType$: BehaviorSubject<any> = new BehaviorSubject(ProjectType.PROJECTTYPE_OWNED);
|
||||
public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED;
|
||||
public ProjectType: any = ProjectType;
|
||||
public grid: boolean = true;
|
||||
constructor(
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ActionKeysModule } from 'src/app/modules/action-keys/action-keys.module
|
||||
import { CardModule } from 'src/app/modules/card/card.module';
|
||||
import { FilterProjectModule } from 'src/app/modules/filter-project/filter-project.module';
|
||||
import { InputModule } from 'src/app/modules/input/input.module';
|
||||
import { NavToggleModule } from 'src/app/modules/nav-toggle/nav-toggle.module';
|
||||
import { PaginatorModule } from 'src/app/modules/paginator/paginator.module';
|
||||
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
|
||||
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
||||
@@ -55,6 +56,7 @@ import { ProjectsComponent } from './projects.component';
|
||||
LocalizedDatePipeModule,
|
||||
RefreshTableModule,
|
||||
MatRippleModule,
|
||||
NavToggleModule,
|
||||
],
|
||||
})
|
||||
export class ProjectsModule {}
|
||||
|
||||
@@ -1,62 +1,74 @@
|
||||
<form [formGroup]="profileForm" *ngIf="profileForm" (ngSubmit)="submitForm()">
|
||||
<div class="user-form-content">
|
||||
<div class="user-form-content inner">
|
||||
<button [disabled]="user && disabled" class="camera-wrapper" type="button"
|
||||
(click)="showEditImage ? openUploadDialog() : null">
|
||||
<div class="user-top-content">
|
||||
<div class="user-form-content">
|
||||
<button
|
||||
[disabled]="user && disabled"
|
||||
class="camera-wrapper"
|
||||
type="button"
|
||||
(click)="showEditImage ? openUploadDialog() : null"
|
||||
>
|
||||
<div class="i-wrapper" *ngIf="showEditImage">
|
||||
<i class="las la-camera"></i>
|
||||
</div>
|
||||
<cnsl-avatar *ngIf="user && user.profile?.displayName && user.profile?.firstName && user.profile?.lastName"
|
||||
class="avatar" [name]="user.profile?.displayName ?? ''" [avatarUrl]="user?.profile?.avatarUrl || ''"
|
||||
[forColor]="preferredLoginName" [size]="80">
|
||||
<cnsl-avatar
|
||||
*ngIf="user && user.profile?.displayName && user.profile?.firstName && user.profile?.lastName"
|
||||
class="avatar"
|
||||
[name]="user.profile?.displayName ?? ''"
|
||||
[avatarUrl]="user?.profile?.avatarUrl || ''"
|
||||
[forColor]="preferredLoginName"
|
||||
[size]="80"
|
||||
>
|
||||
</cnsl-avatar>
|
||||
</button>
|
||||
|
||||
<div className="usernamediv">
|
||||
<div class="usernamediv">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="userName" />
|
||||
</cnsl-form-field>
|
||||
<button [disabled]="user && disabled" type="button" mat-stroked-button class="edit"
|
||||
(click)="changeUsername()">{{'USER.PROFILE.CHANGEUSERNAME' |
|
||||
translate}}</button>
|
||||
<button [disabled]="user && disabled" type="button" mat-stroked-button class="edit" (click)="changeUsername()">
|
||||
{{ 'USER.PROFILE.CHANGEUSERNAME' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="firstName" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.LASTNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="lastName" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.NICKNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="nickName" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.DISPLAYNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="displayName" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.GENDER' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="gender">
|
||||
<mat-option *ngFor="let gender of genders" [value]="gender">
|
||||
{{ 'GENDERS.'+gender | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.PREFERRED_LANGUAGE' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="preferredLanguage">
|
||||
<mat-option *ngFor="let language of languages" [value]="language">
|
||||
{{ 'LANGUAGES.'+language | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
<div class="user-grid">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="firstName" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.LASTNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="lastName" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.NICKNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="nickName" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.DISPLAYNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="displayName" />
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.GENDER' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="gender">
|
||||
<mat-option *ngFor="let gender of genders" [value]="gender">
|
||||
{{ 'GENDERS.' + gender | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.PREFERRED_LANGUAGE' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="preferredLanguage">
|
||||
<mat-option *ngFor="let language of languages" [value]="language">
|
||||
{{ 'LANGUAGES.' + language | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-container">
|
||||
<button [disabled]="disabled" class="submit-button" type="submit" color="primary" mat-raised-button>{{
|
||||
'ACTIONS.SAVE' | translate }}</button>
|
||||
<button [disabled]="disabled" class="submit-button" type="submit" color="primary" mat-raised-button>
|
||||
{{ 'ACTIONS.SAVE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
@@ -2,31 +2,28 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -.5rem;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
|
||||
&.inner {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.usernamediv {
|
||||
margin-left: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.usernamediv {
|
||||
margin-left: .5rem;
|
||||
margin-bottom: .5rem;
|
||||
.formfield {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.formfield {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.edit {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
.edit {
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
}
|
||||
|
||||
.camera-wrapper {
|
||||
margin: 0 .5rem;
|
||||
margin: 0 0.5rem;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
@@ -36,37 +33,49 @@
|
||||
justify-content: center;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
transition: all .3s ease;
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
|
||||
.i-wrapper {
|
||||
border-radius: 50%;
|
||||
background-color: #00000050;
|
||||
display: none;
|
||||
background-color: #00000080;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2 ease;
|
||||
|
||||
i {
|
||||
font-size: 3rem;
|
||||
font-size: 1.2rem;
|
||||
margin: 0.25rem;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.i-wrapper {
|
||||
background-color: #00000080;
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.formfield {
|
||||
flex: 1 1 33%;
|
||||
margin: 0 .5rem;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.user-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
margin: 0;
|
||||
|
||||
@media only screen and (min-width: 700px) {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +84,6 @@
|
||||
justify-content: flex-end;
|
||||
|
||||
.submit-button {
|
||||
border-radius: .5rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
<span class="title" mat-dialog-title>{{'USER.PROFILE.AVATAR.UPLOADTITLE' | translate}}</span>
|
||||
<span class="title" mat-dialog-title>{{ 'USER.PROFILE.AVATAR.UPLOADTITLE' | translate }}</span>
|
||||
<div mat-dialog-content>
|
||||
<p class="desc cnsl-secondary-text">{{'USER.PROFILE.AVATAR.CURRENT' | translate}}</p>
|
||||
<p class="desc cnsl-secondary-text">{{ 'USER.PROFILE.AVATAR.CURRENT' | translate }}</p>
|
||||
<div class="current-pic-wrapper">
|
||||
<img class="pic" [src]="data.profilePic" *ngIf="data.profilePic" />
|
||||
<span class="fill-space"></span>
|
||||
<input #selectedFile style="display: none;" class="file-input" type="file" (change)="onDrop($event)">
|
||||
<button class="btn" mat-raised-button color="primary" type="button"
|
||||
(click)="selectedFile.click();">{{'USER.PROFILE.AVATAR.UPLOADBTN' | translate}}</button>
|
||||
<button *ngIf="data.profilePic" matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" (click)="deletePic()"
|
||||
mat-icon-button>
|
||||
<mat-icon>remove_circle</mat-icon>
|
||||
<input #selectedFile style="display: none" class="file-input" type="file" (change)="onDrop($event)" />
|
||||
<button class="btn" mat-raised-button color="primary" type="button" (click)="selectedFile.click()">
|
||||
{{ 'USER.PROFILE.AVATAR.UPLOADBTN' | translate }}
|
||||
</button>
|
||||
<button
|
||||
*ngIf="data.profilePic"
|
||||
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
|
||||
color="warn"
|
||||
(click)="deletePic()"
|
||||
mat-icon-button
|
||||
>
|
||||
<i class="las la-minus-circle"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions class="action">
|
||||
<button color="primary" mat-stroked-button class="ok-button" (click)="closeDialog()">
|
||||
{{'ACTIONS.CLOSE' | translate}}
|
||||
{{ 'ACTIONS.CLOSE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,9 @@ export class ProfilePictureComponent {
|
||||
this.data.profilePic = resp.user?.human?.profile?.avatarUrl ?? '';
|
||||
});
|
||||
})
|
||||
.catch((error) => this.toast.showError(error));
|
||||
.catch((error) => {
|
||||
this.toast.showError(error.error, false);
|
||||
});
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
|
||||
@@ -18,6 +18,7 @@ import { AvatarModule } from 'src/app/modules/avatar/avatar.module';
|
||||
import { CardModule } from 'src/app/modules/card/card.module';
|
||||
import { FilterUserModule } from 'src/app/modules/filter-user/filter-user.module';
|
||||
import { InputModule } from 'src/app/modules/input/input.module';
|
||||
import { NavToggleModule } from 'src/app/modules/nav-toggle/nav-toggle.module';
|
||||
import { PaginatorModule } from 'src/app/modules/paginator/paginator.module';
|
||||
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
|
||||
import { TableActionsModule } from 'src/app/modules/table-actions/table-actions.module';
|
||||
@@ -49,6 +50,7 @@ import { UserTableComponent } from './user-table/user-table.component';
|
||||
TranslateModule,
|
||||
FilterUserModule,
|
||||
RouterModule,
|
||||
NavToggleModule,
|
||||
RefreshTableModule,
|
||||
TableActionsModule,
|
||||
ActionKeysModule,
|
||||
|
||||
@@ -8,13 +8,17 @@
|
||||
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes"
|
||||
[showBorder]="true"
|
||||
>
|
||||
<div leftActions class="user-table-left-actions">
|
||||
<button class="type-button" [ngClass]="{ active: type === Type.TYPE_HUMAN }" (click)="setType(Type.TYPE_HUMAN)">
|
||||
{{ 'USER.TABLE.TYPES.HUMAN' | translate }}
|
||||
</button>
|
||||
<button class="type-button" [ngClass]="{ active: type === Type.TYPE_MACHINE }" (click)="setType(Type.TYPE_MACHINE)">
|
||||
{{ 'USER.TABLE.TYPES.MACHINE' | translate }}
|
||||
</button>
|
||||
<div leftActions class="user-toggle-group">
|
||||
<cnsl-nav-toggle
|
||||
label="{{ 'USER.TABLE.TYPES.HUMAN' | translate }}"
|
||||
(clicked)="setType(Type.TYPE_HUMAN)"
|
||||
[active]="type === Type.TYPE_HUMAN"
|
||||
></cnsl-nav-toggle>
|
||||
<cnsl-nav-toggle
|
||||
label="{{ 'USER.TABLE.TYPES.MACHINE' | translate }}"
|
||||
(clicked)="setType(Type.TYPE_MACHINE)"
|
||||
[active]="type === Type.TYPE_MACHINE"
|
||||
></cnsl-nav-toggle>
|
||||
</div>
|
||||
|
||||
<ng-template cnslHasRole [hasRole]="['user.write']" actions>
|
||||
|
||||
@@ -4,31 +4,31 @@
|
||||
$foreground: map-get($theme, foreground);
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
|
||||
.user-table-left-actions {
|
||||
.user-toggle-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
|
||||
.type-button {
|
||||
border: none;
|
||||
background: none;
|
||||
text-align: left;
|
||||
padding: 0.75rem 0;
|
||||
opacity: 0.6;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
color: map-get($foreground, text);
|
||||
.toggle-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:first-child {
|
||||
margin-right: 1rem;
|
||||
i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
.info-i {
|
||||
font-size: 1.2rem;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: 600;
|
||||
opacity: 1;
|
||||
.current-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: rgb(84, 142, 230);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user