Skip to content

Commit

Permalink
fix(console): LDAP UI optimization for better required field recognit…
Browse files Browse the repository at this point in the history
…ion, improve onboarding all done visibility (#5659)

* fix: onboarding all done styles

* ldap UI change

* rm log

* set required marker to formfield, max width of string-list comp

* seperate formfields

* formarray

* clear action

* validator

* hide pwd field

* rm dead code

* lint
  • Loading branch information
peintnermax committed Apr 18, 2023
1 parent 0ed2906 commit c420de1
Show file tree
Hide file tree
Showing 25 changed files with 277 additions and 197 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
<ng-template #labelTemplate>
<label class="cnsl-label-wrapper" [attr.for]="_control.id" [attr.aria-owns]="_control.id">
<ng-content select="cnsl-label"></ng-content>
<span *ngIf="_control.required && !hideRequiredMarker" aria-hidden="true" class="cnsl-form-field-required-marker"
>*</span
>
</label>
</ng-template>

<div class="cnsl-form-field-wrapper" (click)="_control.onContainerClick && _control.onContainerClick($event)">
<ng-content select="cnsl-label"></ng-content>
<ng-template [ngTemplateOutlet]="labelTemplate"></ng-template>
<div class="cnsl-rel" #inputContainer>
<ng-content></ng-content>
<ng-content select="cnslSuffix"></ng-content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ interface ValidationError {
'[class.ng-valid]': '_shouldForward("valid")',
'[class.ng-invalid]': '_shouldForward("invalid")',
'[class.ng-pending]': '_shouldForward("pending")',
'[class.ng-required]': '_control.required',
'[class.cnsl-form-field-disabled]': '_control.disabled',
'[class.cnsl-form-field-autofilled]': '_control.autofilled',
'[class.cnsl-focused]': '_control.focused',
Expand All @@ -69,6 +70,7 @@ export class CnslFormFieldComponent extends CnslFormFieldBase implements OnDestr
@ContentChild(MatFormFieldControl) _controlNonStatic!: MatFormFieldControl<any>;
@ContentChild(MatFormFieldControl, { static: true }) _controlStatic!: MatFormFieldControl<any>;
@Input() public disableValidationErrors = false;
@Input() public hideRequiredMarker = false;

get _control(): MatFormFieldControl<any> {
return this._explicitFormFieldControl || this._controlNonStatic || this._controlStatic;
Expand Down
12 changes: 12 additions & 0 deletions console/src/app/modules/form-field/validators/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export function requiredValidator(c: AbstractControl): ValidationErrors | null {
return i18nErr(Validators.required(c), 'ERRORS.REQUIRED');
}

export function minArrayLengthValidator(minArrLength: number): ValidatorFn {
return (c: AbstractControl): ValidationErrors | null => {
return arrayLengthValidator(c, minArrLength, 'ERRORS.ATLEASTONE');
};
}

export function emailValidator(c: AbstractControl): ValidationErrors | null {
return i18nErr(Validators.email(c), 'ERRORS.NOTANEMAIL');
}
Expand Down Expand Up @@ -56,6 +62,12 @@ function regexpValidator(c: AbstractControl, regexp: RegExp, i18nKey: string): V
return !c.value || regexp.test(c.value) ? null : i18nErr({ invalid: true }, i18nKey, { regexp: regexp });
}

function arrayLengthValidator(c: AbstractControl, length: number, i18nKey: string): ValidationErrors | null {
const arr: string[] = c.value;
const invalidStrings: string[] = arr.filter((val: string) => val.trim() === '');
return arr && invalidStrings.length === 0 && arr.length >= length ? null : i18nErr({ invalid: true }, i18nKey);
}

function i18nErr(err: ValidationErrors | null | undefined, i18nKey: string, params?: any): ValidationErrors | null {
if (err === null) {
return null;
Expand Down
16 changes: 12 additions & 4 deletions console/src/app/modules/label/label.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,31 @@
$foreground: map-get($theme, foreground);
$secondary-text: map-get($foreground, secondary-text);

.cnsl-label {
display: block;
.cnsl-label-wrapper {
display: flex;
font-size: 12px;
color: $secondary-text;
transition: color 0.2s ease;
margin-bottom: 4px;
font-weight: 400;

.cnsl-label {
display: block;
}

.cnsl-form-field-required-marker {
margin-left: 1px;
}
}

.cnsl-form-field-disabled {
.cnsl-label {
.cnsl-label-wrapper {
color: if($is-dark-theme, #ffffff80, #00000061);
}
}

.cnsl-form-field-invalid {
.cnsl-label {
.cnsl-label-wrapper {
color: $warn-color;
}
}
Expand Down
6 changes: 6 additions & 0 deletions console/src/app/modules/onboarding/onboarding.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@
.state-circle {
display: none;
}

.action-card {
.action-content {
opacity: 1;
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,65 @@
<form [formGroup]="form" class="attribute-form">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.LDAPIDATTRIBUTE' | translate }}*</cnsl-label>
<input cnslInput formControlName="idAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.AVATARURLATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="avatarUrlAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.DISPLAYNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="displayNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.EMAILATTRIBUTEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="emailAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.EMAILVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="emailVerifiedAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.FIRSTNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="firstNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.LASTNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="lastNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.NICKNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="nickNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PHONEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="phoneAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PHONEVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="phoneVerifiedAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PREFERREDLANGUAGEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="preferredLanguageAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PREFERREDUSERNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="preferredUsernameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PROFILEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="profileAttribute" />
<cnsl-label>{{ 'IDP.LDAPIDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="idAttribute" required />
</cnsl-form-field>

<div class="attribute-more-row">
<span>{{ 'ACTIONS.MORE' | translate }}</span>
<button (click)="showMore = !showMore" type="button" mat-icon-button>
<mat-icon *ngIf="showMore">keyboard_arrow_up</mat-icon>
<mat-icon *ngIf="!showMore">keyboard_arrow_down</mat-icon>
</button>
</div>

<ng-container *ngIf="showMore">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.AVATARURLATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="avatarUrlAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.DISPLAYNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="displayNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.EMAILATTRIBUTEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="emailAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.EMAILVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="emailVerifiedAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.FIRSTNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="firstNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.LASTNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="lastNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.NICKNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="nickNameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PHONEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="phoneAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PHONEVERIFIEDATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="phoneVerifiedAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PREFERREDLANGUAGEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="preferredLanguageAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PREFERREDUSERNAMEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="preferredUsernameAttribute" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.PROFILEATTRIBUTE' | translate }}</cnsl-label>
<input cnslInput formControlName="profileAttribute" />
</cnsl-form-field>
</ng-container>
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@
max-width: 400px;
padding-bottom: 1rem;
}

.attribute-more-row {
display: flex;
align-items: center;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class LDAPAttributesComponent implements OnChanges, OnDestroy {
profileAttribute: new FormControl('', []),
});

public showMore: boolean = false;
constructor() {
this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
if (value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,27 @@ <h1>{{ 'IDP.CREATE.LDAP.TITLE' | translate }}</h1>
<div class="identity-provider-content">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
<input cnslInput formControlName="name" required />
</cnsl-form-field>

<h2 class="subheader">{{ 'IDP.LDAPCONNECTION' | translate }}</h2>

<cnsl-string-list
class="string-list-component-wrapper"
title="{{ 'IDP.SERVERS' | translate }}"
formControlName="serversList"
[required]="true"
></cnsl-string-list>

<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.BASEDN' | translate }}</cnsl-label>
<input cnslInput formControlName="baseDn" />
<input cnslInput formControlName="baseDn" required />
</cnsl-form-field>

<div [ngClass]="{ 'identity-provider-2-col': !provider }">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.BINDDN' | translate }}</cnsl-label>
<input cnslInput formControlName="bindDn" />
<input cnslInput formControlName="bindDn" required />
</cnsl-form-field>

<mat-checkbox
Expand All @@ -46,14 +47,14 @@ <h2 class="subheader">{{ 'IDP.LDAPCONNECTION' | translate }}</h2>
[ngModelOptions]="{ standalone: true }"
>{{ 'IDP.UPDATEBINDPASSWORD' | translate }}</mat-checkbox
>
<cnsl-form-field *ngIf="!provider || (provider && updateBindPassword)" class="formfield">
<cnsl-form-field class="formfield pwd" [ngClass]="{ show: !provider || (provider && updateBindPassword) }">
<cnsl-label>{{ 'IDP.BINDPASSWORD' | translate }}</cnsl-label>
<input
cnslInput
name="bindpassword"
formControlName="bindPassword"
type="password"
autocomplete="new-password"
[required]="!provider"
/>
</cnsl-form-field>
</div>
Expand All @@ -62,39 +63,30 @@ <h2 class="subheader">{{ 'IDP.LDAPUSERBINDING' | translate }}</h2>

<cnsl-form-field class="formfield">
<cnsl-label>{{ 'IDP.USERBASE' | translate }}</cnsl-label>
<input cnslInput formControlName="userBase" />
<input cnslInput formControlName="userBase" required />
</cnsl-form-field>

<cnsl-string-list
class="string-list-component-wrapper"
title="{{ 'IDP.USERFILTERS' | translate }}"
formControlName="userFiltersList"
[required]="true"
></cnsl-string-list>

<cnsl-string-list
class="string-list-component-wrapper"
title="{{ 'IDP.USEROBJECTCLASSES' | translate }}"
formControlName="userObjectClassesList"
[required]="true"
></cnsl-string-list>

<div class="identity-provider-optional-h-wrapper">
<h2>{{ 'IDP.LDAPATTRIBUTES' | translate }}</h2>

<button (click)="showAttributes = !showAttributes" type="button" mat-icon-button>
<mat-icon *ngIf="showAttributes">keyboard_arrow_up</mat-icon>
<mat-icon *ngIf="!showAttributes">keyboard_arrow_down</mat-icon>
</button>

<span *ngIf="!provider?.config?.ldap?.attributes?.idAttribute" class="state error">{{
'IDP.REQUIRED' | translate
}}</span>
</div>
<div *ngIf="showAttributes">
<cnsl-ldap-attributes
[initialAttributes]="provider?.config?.ldap?.attributes"
(attributesChanged)="attributes = $event"
></cnsl-ldap-attributes>
</div>
<cnsl-ldap-attributes
[initialAttributes]="provider?.config?.ldap?.attributes"
(attributesChanged)="attributes = $event"
></cnsl-ldap-attributes>

<div class="identity-provider-optional-h-wrapper">
<h2>{{ 'IDP.OPTIONAL' | translate }}</h2>
Expand Down Expand Up @@ -123,7 +115,7 @@ <h2>{{ 'IDP.OPTIONAL' | translate }}</h2>
color="primary"
mat-raised-button
class="continue-button"
[disabled]="form.invalid || attributes.toObject().idAttribute === '' || form.disabled"
[disabled]="!form.valid || !attributes.toObject().idAttribute || form.disabled"
type="submit"
>
<span *ngIf="id">{{ 'ACTIONS.SAVE' | translate }}</span>
Expand Down

1 comment on commit c420de1

@vercel
Copy link

@vercel vercel bot commented on c420de1 Apr 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs – ./

docs-git-main-zitadel.vercel.app
docs-zitadel.vercel.app
zitadel-docs.vercel.app

Please sign in to comment.