1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 03:33:30 +00:00

Apply the requested changes

This commit is contained in:
Cy Okeke
2025-07-25 18:02:45 +01:00
parent 7d1ca34698
commit 7cebaa6014
4 changed files with 35 additions and 81 deletions

View File

@@ -1,37 +1,19 @@
@let isSuspensionActive = suspensionActive$ | async;
@let isAdminOrServiceUser = isAdminOrServiceUser$ | async;
<app-header>
<bit-search
class="tw-grow"
[formControl]="searchControl"
[placeholder]="'search' | i18n"
></bit-search>
<a
bitButton
routerLink="create"
*ngIf="manageOrganizations && isSuspensionActive"
buttonType="primary"
>
<a bitButton routerLink="create" *ngIf="isAdminOrServiceUser" buttonType="primary">
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newClient" | i18n }}
</a>
<button
*ngIf="manageOrganizations && isSuspensionActive"
type="button"
bitButton
buttonType="primary"
[disabled]="true"
[title]="'providerIsDisabled' | i18n"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newClient" | i18n }}
</button>
<button
type="button"
bitButton
[disabled]="isSuspensionActive"
[title]="isSuspensionActive ? ('providerIsDisabled' | i18n) : ''"
(click)="addExistingOrganization()"
*ngIf="manageOrganizations && showAddExisting"
*ngIf="isAdminOrServiceUser && showAddExisting"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "addExistingOrganization" | i18n }}
@@ -71,7 +53,7 @@
<span>{{ row.plan }}</span>
<div appListDropdown>
<button
*ngIf="manageOrganizations"
*ngIf="isAdminOrServiceUser"
[bitMenuTriggerFor]="removeMenu"
bitMenuItem
buttonType="secondary"

View File

@@ -3,7 +3,7 @@ import { Component } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormControl } from "@angular/forms";
import { ActivatedRoute, Router, RouterModule } from "@angular/router";
import { firstValueFrom, from, map, combineLatest, switchMap, Observable } from "rxjs";
import { firstValueFrom, from, map, Observable, switchMap, tap } from "rxjs";
import { debounceTime, first } from "rxjs/operators";
import { JslibModule } from "@bitwarden/angular/jslib.module";
@@ -13,13 +13,10 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { PlanType } from "@bitwarden/common/billing/enums";
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 {
@@ -58,24 +55,21 @@ const DisallowedPlanTypes = [
})
export class ClientsComponent {
providerId: string = "";
provider: Provider | undefined;
addableOrganizations: Organization[] = [];
loading = true;
manageOrganizations = false;
showAddExisting = false;
dataSource: TableDataSource<ProviderOrganizationOrganizationDetailsResponse> =
new TableDataSource();
protected searchControl = new FormControl("", { nonNullable: true });
protected providerId$: Observable<string> =
this.activatedRoute.parent?.params.pipe(map((params) => params.providerId as string)) ??
new Observable();
this.activatedRoute.parent?.params.pipe(
map((params) => params.providerId as string),
tap((providerId) => (this.providerId = providerId)),
) ?? new Observable();
protected provider$ = this.providerId$.pipe(
switchMap((providerId) => {
this.providerId = providerId;
return this.providerService.get$(providerId);
}),
switchMap((providerId) => this.providerService.get$(providerId)),
);
protected isAdminOrServiceUser$ = this.provider$.pipe(
@@ -86,21 +80,6 @@ export class ClientsComponent {
),
);
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 router: Router,
private providerService: ProviderService,
@@ -114,7 +93,6 @@ export class ClientsComponent {
private toastService: ToastService,
private validationService: ValidationService,
private webProviderService: WebProviderService,
private configService: ConfigService,
) {
this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => {
this.searchControl.setValue(queryParams.search);
@@ -123,10 +101,6 @@ export class ClientsComponent {
this.provider$
.pipe(
map((provider) => {
this.provider = provider;
this.manageOrganizations =
provider.type === ProviderUserType.ProviderAdmin ||
provider.type === ProviderUserType.ServiceUser;
if (provider?.providerStatus === ProviderStatusType.Billable) {
return from(
this.router.navigate(["../manage-client-organizations"], {

View File

@@ -1,4 +1,5 @@
@let isSuspensionActive = suspensionActive$ | async;
@let provider = provider$ | async;
<app-header [title]="pageTitle">
<bit-search [placeholder]="'search' | i18n" [formControl]="searchControl"></bit-search>
<button
@@ -8,9 +9,7 @@
[bitMenuTriggerFor]="clientMenu"
[disabled]="isSuspensionActive"
[title]="isSuspensionActive ? ('providerIsDisabled' | i18n) : ''"
appA11yTitle="{{
isSuspensionActive ? ('add' | i18n) : isSuspensionActive ? ('providerIsDisabled' | i18n) : ''
}}"
appA11yTitle="{{ 'add' | i18n }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "add" | i18n }}
@@ -110,18 +109,20 @@
<i aria-hidden="true" class="bwi bwi-family"></i>
{{ "manageSubscription" | i18n }}
</button>
<button *ngIf="isProviderAdmin" type="button" bitMenuItem (click)="remove(row)">
<span class="tw-text-danger">
<i aria-hidden="true" class="bwi bwi-close"></i> {{ "unlinkOrganization" | i18n }}
</span>
</button>
@if (provider?.type === ProviderUserType.ProviderAdmin) {
<button type="button" bitMenuItem (click)="remove(row)">
<span class="tw-text-danger">
<i aria-hidden="true" class="bwi bwi-close"></i> {{ "unlinkOrganization" | i18n }}
</span>
</button>
}
</bit-menu>
</td>
</ng-template>
</bit-table-scroll>
<div *ngIf="dataSource.data.length === 0" class="tw-mt-10">
<app-no-clients
[showAddOrganizationButton]="isProviderAdmin"
[showAddOrganizationButton]="provider?.type === ProviderUserType.ProviderAdmin"
[disableAddOrganizationButton]="isSuspensionActive"
(addNewOrganizationClicked)="createClient()"
/>

View File

@@ -10,6 +10,7 @@ import {
combineLatest,
switchMap,
Observable,
tap,
} from "rxjs";
import { debounceTime, first } from "rxjs/operators";
@@ -72,29 +73,26 @@ import { ReplacePipe } from "./replace.pipe";
})
export class ManageClientsComponent {
providerId: string = "";
provider: Provider | undefined;
loading = true;
isProviderAdmin = false;
isServiceUser = false;
dataSource: TableDataSource<ProviderOrganizationOrganizationDetailsResponse> =
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<string> =
this.activatedRoute.parent?.params.pipe(map((params) => params.providerId as string)) ??
new Observable();
this.activatedRoute.parent?.params.pipe(
map((params) => params.providerId as string),
tap((providerId) => (this.providerId = providerId)),
) ?? new Observable();
protected provider$ = this.providerId$.pipe(
switchMap((providerId) => {
this.providerId = providerId;
return this.providerService.get$(providerId);
}),
switchMap((providerId) => this.providerService.get$(providerId)),
);
protected isAdminOrServiceUser$ = this.provider$.pipe(
@@ -146,9 +144,8 @@ export class ManageClientsComponent {
relativeTo: this.activatedRoute,
}),
);
} else {
return from(this.load());
}
return from(this.load());
}),
takeUntilDestroyed(),
)
@@ -164,14 +161,12 @@ export class ManageClientsComponent {
async load() {
try {
this.provider = await firstValueFrom(this.providerService.get$(this.providerId));
if (this.provider?.providerType === ProviderType.BusinessUnit) {
const provider = await firstValueFrom(this.providerService.get$(this.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.isServiceUser = this.provider?.type === ProviderUserType.ServiceUser;
this.dataSource.data = (
await this.billingApiService.getProviderClientOrganizations(this.providerId)
).data;
@@ -183,10 +178,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,
},
});
@@ -235,10 +231,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!,
},
});