diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html index de1db6f0c7a..8f740384305 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html @@ -1,10 +1,11 @@ +@let isAdminOrServiceUser = isAdminOrServiceUser$ | async; - + {{ "newClient" | i18n }} @@ -12,7 +13,7 @@ type="button" bitButton (click)="addExistingOrganization()" - *ngIf="manageOrganizations && showAddExisting" + *ngIf="isAdminOrServiceUser && showAddExisting" > {{ "addExistingOrganization" | i18n }} @@ -52,7 +53,7 @@ {{ row.plan }}
- - @@ -73,26 +89,41 @@ appA11yTitle="{{ 'options' | i18n }}" > - - - + @if (provider?.type === ProviderUserType.ProviderAdmin) { + + }
diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts index de9e63cd509..27f45cb250e 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts @@ -2,8 +2,16 @@ import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, from, lastValueFrom, map } from "rxjs"; -import { debounceTime, first, switchMap } from "rxjs/operators"; +import { + firstValueFrom, + from, + lastValueFrom, + map, + combineLatest, + switchMap, + Observable, +} from "rxjs"; +import { debounceTime, first } from "rxjs/operators"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { @@ -15,6 +23,8 @@ import { Provider } from "@bitwarden/common/admin-console/models/domain/provider import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { @@ -61,20 +71,49 @@ import { ReplacePipe } from "./replace.pipe"; ], }) export class ManageClientsComponent { - providerId: string = ""; - provider: Provider | undefined; loading = true; - isProviderAdmin = false; dataSource: TableDataSource = new TableDataSource(); protected searchControl = new FormControl("", { nonNullable: true }); protected plans: PlanResponse[] = []; + protected ProviderUserType = ProviderUserType; pageTitle = this.i18nService.t("clients"); clientColumnHeader = this.i18nService.t("client"); newClientButtonLabel = this.i18nService.t("newClient"); + protected providerId$: Observable = + this.activatedRoute.parent?.params.pipe(map((params) => params.providerId as string)) ?? + new Observable(); + + protected provider$ = this.providerId$.pipe( + switchMap((providerId) => this.providerService.get$(providerId)), + ); + + protected isAdminOrServiceUser$ = this.provider$.pipe( + map( + (provider) => + provider?.type === ProviderUserType.ProviderAdmin || + provider?.type === ProviderUserType.ServiceUser, + ), + ); + + protected providerPortalTakeover$ = this.configService.getFeatureFlag$( + FeatureFlag.PM21821_ProviderPortalTakeover, + ); + + protected suspensionActive$ = combineLatest([ + this.isAdminOrServiceUser$, + this.providerPortalTakeover$, + this.provider$.pipe(map((provider) => provider?.enabled ?? false)), + ]).pipe( + map( + ([isAdminOrServiceUser, portalTakeoverEnabled, providerEnabled]) => + isAdminOrServiceUser && portalTakeoverEnabled && !providerEnabled, + ), + ); + constructor( private billingApiService: BillingApiServiceAbstraction, private providerService: ProviderService, @@ -86,29 +125,23 @@ export class ManageClientsComponent { private validationService: ValidationService, private webProviderService: WebProviderService, private billingNotificationService: BillingNotificationService, + private configService: ConfigService, ) { this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { this.searchControl.setValue(queryParams.search); }); - this.activatedRoute.parent?.params - ?.pipe( - switchMap((params) => { - this.providerId = params.providerId; - return this.providerService.get$(this.providerId).pipe( - map((provider: Provider) => provider?.providerStatus === ProviderStatusType.Billable), - map((isBillable) => { - if (!isBillable) { - return from( - this.router.navigate(["../clients"], { - relativeTo: this.activatedRoute, - }), - ); - } else { - return from(this.load()); - } - }), - ); + this.provider$ + .pipe( + map((provider: Provider) => { + if (provider?.providerStatus !== ProviderStatusType.Billable) { + return from( + this.router.navigate(["../clients"], { + relativeTo: this.activatedRoute, + }), + ); + } + return from(this.load()); }), takeUntilDestroyed(), ) @@ -124,15 +157,15 @@ export class ManageClientsComponent { async load() { try { - this.provider = await firstValueFrom(this.providerService.get$(this.providerId)); - if (this.provider?.providerType === ProviderType.BusinessUnit) { + const providerId = await firstValueFrom(this.providerId$); + const provider = await firstValueFrom(this.providerService.get$(providerId)); + if (provider?.providerType === ProviderType.BusinessUnit) { this.pageTitle = this.i18nService.t("businessUnits"); this.clientColumnHeader = this.i18nService.t("businessUnit"); this.newClientButtonLabel = this.i18nService.t("newBusinessUnit"); } - this.isProviderAdmin = this.provider?.type === ProviderUserType.ProviderAdmin; this.dataSource.data = ( - await this.billingApiService.getProviderClientOrganizations(this.providerId) + await this.billingApiService.getProviderClientOrganizations(providerId) ).data; this.plans = (await this.billingApiService.getPlans()).data; this.loading = false; @@ -142,10 +175,11 @@ export class ManageClientsComponent { } addExistingOrganization = async () => { - if (this.provider) { + const provider = await firstValueFrom(this.provider$); + if (provider) { const reference = AddExistingOrganizationDialogComponent.open(this.dialogService, { data: { - provider: this.provider, + provider: provider, }, }); @@ -158,9 +192,10 @@ export class ManageClientsComponent { }; createClient = async () => { + const providerId = await firstValueFrom(this.providerId$); const reference = openCreateClientDialog(this.dialogService, { data: { - providerId: this.providerId, + providerId: providerId, plans: this.plans, }, }); @@ -173,9 +208,10 @@ export class ManageClientsComponent { }; manageClientName = async (organization: ProviderOrganizationOrganizationDetailsResponse) => { + const providerId = await firstValueFrom(this.providerId$); const dialogRef = openManageClientNameDialog(this.dialogService, { data: { - providerId: this.providerId, + providerId: providerId, organization: { id: organization.id, name: organization.organizationName, @@ -194,10 +230,11 @@ export class ManageClientsComponent { manageClientSubscription = async ( organization: ProviderOrganizationOrganizationDetailsResponse, ) => { + const provider = await firstValueFrom(this.provider$); const dialogRef = openManageClientSubscriptionDialog(this.dialogService, { data: { organization, - provider: this.provider!, + provider: provider!, }, }); @@ -220,7 +257,8 @@ export class ManageClientsComponent { } try { - await this.webProviderService.detachOrganization(this.providerId, organization.id); + const providerId = await firstValueFrom(this.providerId$); + await this.webProviderService.detachOrganization(providerId, organization.id); this.toastService.showToast({ variant: "success", title: "", diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts index 768f22c5738..039c42de302 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts @@ -30,6 +30,7 @@ const gearIcon = svgIcon`

{{ "noClients" | i18n }}

this.addNewOrganizationClicked.emit();