1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-13 06:54:07 +00:00

Suspended client change and tooltips

This commit is contained in:
Cy Okeke
2025-07-21 15:25:17 +01:00
parent 8b5e6adc37
commit 612708432e
5 changed files with 124 additions and 9 deletions

View File

@@ -4,13 +4,32 @@
[formControl]="searchControl"
[placeholder]="'search' | i18n"
></bit-search>
<a bitButton routerLink="create" *ngIf="manageOrganizations" buttonType="primary">
<a
bitButton
routerLink="create"
*ngIf="canAddClients"
buttonType="primary"
[title]="addClientTooltip"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newClient" | i18n }}
</a>
<button
*ngIf="manageOrganizations && !canAddClients"
type="button"
bitButton
buttonType="primary"
[disabled]="true"
[title]="addClientTooltip"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "newClient" | i18n }}
</button>
<button
type="button"
bitButton
[disabled]="!canAddClients"
[title]="addClientTooltip"
(click)="addExistingOrganization()"
*ngIf="manageOrganizations && showAddExisting"
>

View File

@@ -13,6 +13,7 @@ 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";
@@ -55,6 +56,7 @@ const DisallowedPlanTypes = [
})
export class ClientsComponent {
providerId: string = "";
provider: Provider | undefined;
addableOrganizations: Organization[] = [];
loading = true;
manageOrganizations = false;
@@ -63,6 +65,22 @@ export class ClientsComponent {
new TableDataSource();
protected searchControl = new FormControl("", { nonNullable: true });
// Computed properties for provider suspension state
get isProviderDisabled(): boolean {
return !this.provider?.enabled;
}
get canAddClients(): boolean {
return this.manageOrganizations && !this.isProviderDisabled;
}
get addClientTooltip(): string {
if (this.isProviderDisabled) {
return this.i18nService.t("providerIsDisabled");
}
return "";
}
constructor(
private router: Router,
private providerService: ProviderService,
@@ -141,8 +159,11 @@ export class ClientsComponent {
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const clients = response.data != null && response.data.length > 0 ? response.data : [];
this.dataSource.data = clients;
this.manageOrganizations =
(await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin;
// Store the provider for disabled state checks
this.provider = await this.providerService.get(this.providerId);
this.manageOrganizations = this.provider.type === ProviderUserType.ProviderAdmin;
const candidateOrgs = (
await firstValueFrom(this.organizationService.organizations$(userId))
).filter((o) => o.isOwner && o.providerId == null);

View File

@@ -6,6 +6,16 @@
[label]="'providerPortal' | i18n"
></bit-nav-logo>
<!-- Warning icon for suspended provider -->
<div *ngIf="!provider.enabled" class="tw-px-4 tw-py-2 tw-flex tw-items-center tw-text-danger">
<i
class="bwi bwi-exclamation-triangle tw-mr-2"
title="{{ 'providerIsDisabled' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-text-sm tw-font-medium">{{ "providerIsDisabled" | i18n }}</span>
</div>
<bit-nav-item
icon="bwi-provider"
[text]="clientsTranslationKey$ | async | i18n"

View File

@@ -5,17 +5,31 @@
buttonType="primary"
type="button"
[bitMenuTriggerFor]="clientMenu"
appA11yTitle="{{ 'add' | i18n }}"
[disabled]="!canAddClients"
[title]="addClientTooltip"
appA11yTitle="{{ canAddClients ? ('add' | i18n) : addClientTooltip }}"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "add" | i18n }}
</button>
<bit-menu #clientMenu>
<button type="button" bitMenuItem (click)="createClient()">
<button
type="button"
bitMenuItem
[disabled]="!canAddClients"
[title]="addClientTooltip"
(click)="createClient()"
>
<i aria-hidden="true" class="bwi bwi-business"></i>
{{ newClientButtonLabel }}
</button>
<button type="button" bitMenuItem (click)="addExistingOrganization()">
<button
type="button"
bitMenuItem
[disabled]="!canAddClients"
[title]="addClientTooltip"
(click)="addExistingOrganization()"
>
<i aria-hidden="true" class="bwi bwi-filter"></i>
{{ "existingOrganization" | i18n }}
</button>
@@ -73,14 +87,27 @@
appA11yTitle="{{ 'options' | i18n }}"
></button>
<bit-menu #rowMenu>
<button type="button" bitMenuItem (click)="manageClientName(row)">
<button
type="button"
bitMenuItem
[disabled]="!canManageClientNames"
[title]="manageClientNameTooltip"
(click)="manageClientName(row)"
>
<i aria-hidden="true" class="bwi bwi-pencil-square"></i>
{{ "updateName" | i18n }}
</button>
<button type="button" bitMenuItem (click)="manageClientSubscription(row)">
<button
type="button"
bitMenuItem
[disabled]="!canManageClientSubscriptions"
[title]="manageSubscriptionTooltip"
(click)="manageClientSubscription(row)"
>
<i aria-hidden="true" class="bwi bwi-family"></i>
{{ "manageSubscription" | i18n }}
</button>
<!-- Keep unlink enabled even when provider is suspended -->
<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 }}
@@ -92,7 +119,7 @@
</bit-table-scroll>
<div *ngIf="dataSource.data.length === 0" class="tw-mt-10">
<app-no-clients
[showAddOrganizationButton]="isProviderAdmin"
[showAddOrganizationButton]="canAddClients"
(addNewOrganizationClicked)="createClient()"
/>
</div>

View File

@@ -75,6 +75,44 @@ export class ManageClientsComponent {
clientColumnHeader = this.i18nService.t("client");
newClientButtonLabel = this.i18nService.t("newClient");
// Computed properties for provider suspension state
get isProviderDisabled(): boolean {
return !this.provider?.enabled;
}
get canAddClients(): boolean {
return this.isProviderAdmin && !this.isProviderDisabled;
}
get canManageClientNames(): boolean {
return this.isProviderAdmin && !this.isProviderDisabled;
}
get canManageClientSubscriptions(): boolean {
return this.isProviderAdmin && !this.isProviderDisabled;
}
get addClientTooltip(): string {
if (this.isProviderDisabled) {
return this.i18nService.t("providerIsDisabled");
}
return "";
}
get manageClientNameTooltip(): string {
if (this.isProviderDisabled) {
return this.i18nService.t("providerIsDisabled");
}
return "";
}
get manageSubscriptionTooltip(): string {
if (this.isProviderDisabled) {
return this.i18nService.t("providerIsDisabled");
}
return "";
}
constructor(
private billingApiService: BillingApiServiceAbstraction,
private providerService: ProviderService,