fixes from testing

This commit is contained in:
conblem
2025-06-25 16:44:13 +02:00
parent 9a71fdcc03
commit edf2017922
18 changed files with 100 additions and 66 deletions

View File

@@ -30,6 +30,8 @@
"@fortawesome/angular-fontawesome": "^0.13.0",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@ng-icons/core": "^25.0.0",
"@ng-icons/heroicons": "^25.0.0",
"@ngx-translate/core": "^15.0.0",
"@tanstack/angular-query-experimental": "^5.75.4",
"@zitadel/client": "1.2.0",

View File

@@ -240,7 +240,6 @@ export class AppComponent {
this.translate.onLangChange.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((language: LangChangeEvent) => {
this.document.documentElement.lang = language.lang;
this.language = language.lang;
});
}
@@ -290,7 +289,6 @@ export class AppComponent {
? userprofile.human.profile?.preferredLanguage
: fallbackLang;
this.translate.use(lang);
this.language = lang;
this.document.documentElement.lang = lang;
});
}

View File

@@ -76,6 +76,7 @@ import { PosthogService } from './services/posthog.service';
import { NewHeaderComponent } from './modules/new-header/new-header.component';
import { provideTanStackQuery, QueryClient, withDevtools } from '@tanstack/angular-query-experimental';
import { CdkOverlayOrigin } from '@angular/cdk/overlay';
import { provideNgIconsConfig } from '@ng-icons/core';
registerLocaleData(localeDe);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/de.json'));
@@ -251,6 +252,9 @@ const authConfig: AuthConfig = {
new QueryClient(),
withDevtools(() => ({ loadDevtools: 'auto' })),
),
provideNgIconsConfig({
size: '1rem',
}),
],
bootstrap: [AppComponent],
exports: [],

View File

@@ -24,9 +24,9 @@
*ngFor="let key of FEATURE_KEYS"
[toggleStateKey]="key"
[toggleState]="toggleStates[key]"
(toggleChange)="saveFeatures(key, $event)"
(toggleChange)="saveFeatures(toggleStates, key, $event)"
></cnsl-feature-toggle>
<cnsl-login-v2-feature-toggle [toggleState]="toggleStates.loginV2" (toggleChanged)="saveFeatures('loginV2', $event)" />
<cnsl-login-v2-feature-toggle [toggleState]="toggleStates.loginV2" (toggleChanged)="saveFeatures(toggleStates, 'loginV2', $event)" />
</div>
</cnsl-card>
</div>

View File

@@ -21,7 +21,7 @@ import {
} from '@zitadel/proto/zitadel/feature/v2/instance_pb';
import { Source } from '@zitadel/proto/zitadel/feature/v2/feature_pb';
import { MessageInitShape } from '@bufbuild/protobuf';
import { firstValueFrom, Observable, ReplaySubject, shareReplay, switchMap } from 'rxjs';
import { Observable, ReplaySubject, shareReplay, switchMap } from 'rxjs';
import { filter, map, startWith } from 'rxjs/operators';
import { LoginV2FeatureToggleComponent } from '../feature-toggle/login-v2-feature-toggle/login-v2-feature-toggle.component';
@@ -133,18 +133,22 @@ export class FeaturesComponent {
);
}
public async saveFeatures<TKey extends ToggleStateKeys, TValue extends ToggleStates[TKey]>(key: TKey, value: TValue) {
const toggleStates = { ...(await firstValueFrom(this.toggleStates$)), [key]: value };
public async saveFeatures<TKey extends ToggleStateKeys, TValue extends ToggleStates[TKey]>(
toggleStates: ToggleStates,
key: TKey,
value: TValue,
) {
const newToggleStates = { ...toggleStates, [key]: value };
const req = FEATURE_KEYS.reduce<MessageInitShape<typeof SetInstanceFeaturesRequestSchema>>((acc, key) => {
acc[key] = toggleStates[key].enabled;
acc[key] = newToggleStates[key].enabled;
return acc;
}, {});
// to save special flags they have to be handled here
req.loginV2 = {
required: toggleStates.loginV2.enabled,
baseUri: toggleStates.loginV2.baseUri,
required: newToggleStates.loginV2.enabled,
baseUri: newToggleStates.loginV2.baseUri,
};
try {

View File

@@ -1,6 +1,6 @@
<button class="header-button" cnslInput>
<ng-content></ng-content>
<div class="cnsl-action-button">
<i class="las la-arrows-alt-v"></i>
<ng-icon size="1.2rem" name="heroChevronUpDown"></ng-icon>
</div>
</button>

View File

@@ -1,4 +1,6 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { heroChevronUpDown } from '@ng-icons/heroicons/outline';
@Component({
selector: 'cnsl-header-button',
@@ -6,5 +8,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
styleUrls: ['./header-button.component.scss'],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgIconComponent],
providers: [provideIcons({ heroChevronUpDown })],
})
export class HeaderButtonComponent {}

View File

@@ -2,13 +2,13 @@
<span class="dropdown-label">{{ 'MENU.INSTANCEOVERVIEW' | translate }}</span>
<a (click)="setInstance(instance)" mat-button class="dropdown-button"
>{{ instance.name }}
<i class="las la-1x la-angle-right"></i>
<ng-icon name="heroChevronRight"></ng-icon>
</a>
</div>
<div class="footer">
<a [routerLink]="['/instance']" (click)="settingsClicked.emit()" mat-button class="dropdown-button settings-button">
<h3>{{ 'MENU.SETTINGS' | translate }}</h3>
<i class="las la-1x la-cog"></i>
<ng-icon name="heroCog8ToothSolid"></ng-icon>
</a>
</div>

View File

@@ -3,6 +3,9 @@ import { TranslateModule } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button';
import { Router, RouterLink } from '@angular/router';
import { InstanceDetail } from '@zitadel/proto/zitadel/instance_pb';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { heroCog8ToothSolid } from '@ng-icons/heroicons/solid';
import { heroChevronRight } from '@ng-icons/heroicons/outline';
@Component({
selector: 'cnsl-instance-selector',
@@ -10,7 +13,8 @@ import { InstanceDetail } from '@zitadel/proto/zitadel/instance_pb';
styleUrls: ['./instance-selector.component.scss'],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TranslateModule, MatButtonModule, RouterLink],
imports: [TranslateModule, MatButtonModule, RouterLink, NgIconComponent],
providers: [provideIcons({ heroCog8ToothSolid, heroChevronRight })],
})
export class InstanceSelectorComponent {
@Output() public instanceChanged = new EventEmitter<string>();

View File

@@ -1,4 +1,5 @@
<div class="new-header-wrapper">
<span routerLink="/" class="new-header-title">CONSOLE</span>
<ng-container *ngIf="myInstanceQuery.data()?.instance as instance">
<ng-container *ngTemplateOutlet="slash"></ng-container>
<cnsl-header-button
@@ -26,6 +27,7 @@
></cnsl-organization-selector>
</cnsl-header-dropdown>
</ng-container>
<ng-container *ngIf="['org.read'] | hasRole | async">
<ng-container *ngTemplateOutlet="slash"></ng-container>
<cnsl-header-button cdkOverlayOrigin #orgTrigger="cdkOverlayOrigin" (click)="isOrgDropdownOpen.set(!isOrgDropdownOpen())">
<ng-container *ngIf="activeOrganizationQuery.data() as org">{{ org.name }}</ng-container>
@@ -33,6 +35,7 @@
<cnsl-header-dropdown [trigger]="orgTrigger" [isOpen]="isOrgDropdownOpen()" (closed)="isOrgDropdownOpen.set(false)">
<cnsl-organization-selector (orgChanged)="isOrgDropdownOpen.set(false)"></cnsl-organization-selector>
</cnsl-header-dropdown>
</ng-container>
</div>
<ng-template #slash>

View File

@@ -1,7 +1,14 @@
.new-header-wrapper {
padding-left: 5px;
padding-right: 5px;
display: flex;
flex-direction: row;
align-items: center;
gap: 5px;
}
.new-header-title {
font-weight: 900;
letter-spacing: 3px;
font-size: 0.8rem;
}

View File

@@ -17,6 +17,7 @@ import { toSignal } from '@angular/core/rxjs-interop';
import { BreakpointObserver } from '@angular/cdk/layout';
import { NewAdminService } from '../../services/new-admin.service';
import { NewAuthService } from '../../services/new-auth.service';
import { RouterLink } from '@angular/router';
@Component({
selector: 'cnsl-new-header',
@@ -37,6 +38,7 @@ import { NewAuthService } from '../../services/new-auth.service';
NgTemplateOutlet,
AsyncPipe,
HasRolePipeModule,
RouterLink,
],
})
export class NewHeaderComponent {

View File

@@ -1,17 +1,14 @@
<div cdkTrapFocus class="focus-trapper">
<!-- <div *ngIf="organizationsQuery.isPending() || setOrgId.isPending()">-->
<!-- Loading organizations...-->
<!-- </div>-->
<div class="org-header">
<button *ngIf="backButton" (click)="backButtonPressed.emit()" mat-button class="dropdown-button">
<span class="back-button">
<i class="las la-arrow-alt-circle-left"></i>
<ng-icon name="heroArrowLeftCircleSolid"></ng-icon>
<h3>Back to {{ backButton }}</h3>
</span>
</button>
<span class="dropdown-label">{{ 'MENU.ORGANIZATION' | translate }}</span>
<form [formGroup]="form" class="form">
<i class="las la-1x la-search search-icon"></i>
<ng-icon class="search-icon" name="heroMagnifyingGlass"></ng-icon>
<input
class="search-input"
autocomplete="off"
@@ -25,7 +22,7 @@
<!-- Make sure active org is always at the top -->
<a *ngIf="activeOrgIfSearchMatches() as org" class="dropdown-button" mat-button (click)="changeOrg(org.id)">
{{ org.name }}
<i class="las la-1x la-check"></i>
<ng-icon name="heroCheck"></ng-icon>
</a>
<ng-container *ngFor="let page of organizationsQuery.data()?.pages; last as lastPage">
<ng-container *ngFor="let org of page.result; trackBy: trackOrg">
@@ -33,15 +30,17 @@
{{ org.name }}
</a>
</ng-container>
<ng-container *ngIf="lastPage && page.details?.totalResult as totalResult">
<button
class="dropdown-button"
mat-stroked-button
*ngIf="lastPage && page.details?.totalResult as totalResult"
*ngIf="totalResult > QUERY_LIMIT"
(click)="organizationsQuery.fetchNextPage()"
[disabled]="!organizationsQuery.hasNextPage() || organizationsQuery.isFetchingNextPage()"
>
...{{ totalResult - loadedOrgsCount() }} {{ 'PAGINATOR.MORE' | translate }}
</button>
</ng-container>
</ng-container>
</div>
</div>

View File

@@ -62,7 +62,7 @@
.search-icon {
position: absolute;
top: 50%;
transform: scaleX(-1) translate(0, -50%);
transform: translate(0, -50%);
// default input padding
left: 10px;
color: if($is-dark-theme, #ffffff60, #00000060);

View File

@@ -4,7 +4,7 @@ import { NewOrganizationService } from 'src/app/services/new-organization.servic
import { NgForOf, NgIf } from '@angular/common';
import { ToastService } from 'src/app/services/toast.service';
import { FormBuilder, FormControl, ReactiveFormsModule } from '@angular/forms';
import { ListOrganizationsRequestSchema } from '@zitadel/proto/zitadel/org/v2/org_service_pb';
import { ListOrganizationsRequestSchema, ListOrganizationsResponse } from '@zitadel/proto/zitadel/org/v2/org_service_pb';
import { MessageInitShape } from '@bufbuild/protobuf';
import { debounceTime } from 'rxjs/operators';
import { toSignal } from '@angular/core/rxjs-interop';
@@ -17,6 +17,9 @@ import { TranslateModule } from '@ngx-translate/core';
import { InputModule } from '../../input/input.module';
import { MatOptionModule } from '@angular/material/core';
import { Router } from '@angular/router';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { heroCheck, heroMagnifyingGlass } from '@ng-icons/heroicons/outline';
import { heroArrowLeftCircleSolid } from '@ng-icons/heroicons/solid';
type NameQuery = Extract<
NonNullable<MessageInitShape<typeof ListOrganizationsRequestSchema>['queries']>[number]['query'],
@@ -41,7 +44,9 @@ const QUERY_LIMIT = 5;
MatMenuModule,
InputModule,
MatOptionModule,
NgIconComponent,
],
providers: [provideIcons({ heroCheck, heroMagnifyingGlass, heroArrowLeftCircleSolid })],
})
export class OrganizationSelectorComponent {
@Input()
@@ -142,29 +147,29 @@ export class OrganizationSelectorComponent {
queries: query ? [{ query }] : undefined,
},
placeholderData: keepPreviousData,
getNextPageParam: (lastPage, _, pageParam) =>
// if we received less than the limit last time we are at the end
lastPage.result.length < pageParam.query.limit
? undefined
: {
getNextPageParam: (lastPage, pages, pageParam) =>
this.countLoadedOrgs(pages) < (lastPage.details?.totalResult ?? BigInt(Number.MAX_SAFE_INTEGER))
? {
...pageParam,
query: {
...pageParam.query,
offset: pageParam.query.offset + BigInt(lastPage.result.length),
},
},
}
: undefined,
};
});
}
private getLoadedOrgsCount(organizationsQuery: ReturnType<typeof this.getOrganizationsQuery>) {
return computed(() => {
const pages = organizationsQuery.data()?.pages;
return computed(() => this.countLoadedOrgs(organizationsQuery.data()?.pages));
}
private countLoadedOrgs(pages?: ListOrganizationsResponse[]) {
if (!pages) {
return BigInt(0);
}
return pages.reduce((acc, page) => acc + BigInt(page.result.length), BigInt(0));
});
}
private getActiveOrgIfSearchMatches(nameQuery: Signal<NameQuery | undefined>) {
@@ -187,4 +192,6 @@ export class OrganizationSelectorComponent {
protected trackOrg(_: number, { id }: Organization): string {
return id;
}
protected readonly QUERY_LIMIT = QUERY_LIMIT;
}

View File

@@ -89,14 +89,6 @@ export class AuthUserDetailComponent implements OnInit {
return '';
});
protected savedLanguage = computed(() => {
const user = this.user.data();
if (!user || user.type.case !== 'human' || !user.type.value.profile?.preferredLanguage) {
return this.translate.defaultLang;
}
return user.type.value.profile?.preferredLanguage;
});
constructor(
private translate: TranslateService,
private toast: ToastService,
@@ -141,10 +133,6 @@ export class AuthUserDetailComponent implements OnInit {
this.toast.showError(error);
}
});
effect(() => {
this.translate.use(this.savedLanguage());
});
}
ngOnInit(): void {

View File

@@ -44,8 +44,6 @@ export class NewOrganizationService {
}
public async setOrgId(orgId?: string) {
console.log('beboop', orgId);
console.trace(orgId);
const organization = await this.queryClient.fetchQuery(this.organizationByIdQueryOptions(orgId ?? this.getOrgId()()));
if (organization) {
this.storage.setItem(StorageKey.organizationId, orgId, StorageLocation.session);

View File

@@ -2641,6 +2641,20 @@
read-package-up "^11.0.0"
semver "^7.3.8"
"@ng-icons/core@^25.0.0":
version "25.6.1"
resolved "https://registry.yarnpkg.com/@ng-icons/core/-/core-25.6.1.tgz#471c2597af226c5b6f53ec5d39c8ec680964b9b2"
integrity sha512-o6vCttlzXvDZRYiOKOULr7fsX8gY/DwwxzBSrBQzwa/at+pC0xRoe6uczJ9Ato+y1EDWP/PlrEMAQfvokBA6tQ==
dependencies:
tslib "^2.2.0"
"@ng-icons/heroicons@^25.0.0":
version "25.6.1"
resolved "https://registry.yarnpkg.com/@ng-icons/heroicons/-/heroicons-25.6.1.tgz#e6cd64c68c22e3ae4d93a1a1b7daa537f6d9d600"
integrity sha512-QGTIIl+S6/w2vQvYGP1zNLbNvJLLRS+1evlOPWZZzWow+77qRxs0E96CukSsjItBFUnLKvzuOfMBBcNtb2SIHQ==
dependencies:
tslib "^2.2.0"
"@ngtools/webpack@16.2.16":
version "16.2.16"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-16.2.16.tgz#512da8f3459faafd0cc1f7f7cbec96b678377be6"
@@ -8719,7 +8733,7 @@ tslib@^2.0.0, tslib@^2.0.3, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.4.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
tslib@^2.1.0, tslib@^2.7.0:
tslib@^2.1.0, tslib@^2.2.0, tslib@^2.7.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==