diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html index 0cd21d0f688..1d8a7846d9d 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html @@ -294,6 +294,10 @@

{{ "manageSubscription" | i18n }}

+ +

{{ "manageSubscription" | i18n }}

+

{{ resellerSeatsRemainingMessage }}

+

{{ "selfHostingTitleProper" | i18n }} diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index 003f816ac30..50c755af63b 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -4,13 +4,17 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { firstValueFrom, lastValueFrom, Observable, Subject } from "rxjs"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { getOrganizationById, OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums"; +import { + OrganizationApiKeyType, + OrganizationUserStatusType, +} from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; @@ -61,12 +65,15 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy showSubscription = true; showSelfHost = false; organizationIsManagedByConsolidatedBillingMSP = false; + resellerSeatsRemainingMessage: string; protected readonly subscriptionHiddenIcon = SubscriptionHiddenIcon; protected readonly teamsStarter = ProductTierType.TeamsStarter; private destroy$ = new Subject(); + private seatsRemainingMessage: string; + constructor( private apiService: ApiService, private i18nService: I18nService, @@ -79,6 +86,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy private configService: ConfigService, private toastService: ToastService, private billingApiService: BillingApiServiceAbstraction, + private organizationUserApiService: OrganizationUserApiService, ) {} async ngOnInit() { @@ -104,6 +112,28 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy } } } + + if (this.userOrg.hasReseller) { + const allUsers = await this.organizationUserApiService.getAllUsers(this.userOrg.id); + + const userCount = allUsers.data.filter((user) => + [ + OrganizationUserStatusType.Invited, + OrganizationUserStatusType.Accepted, + OrganizationUserStatusType.Confirmed, + ].includes(user.status), + ).length; + + const remainingSeats = this.userOrg.seats - userCount; + + const seatsRemaining = this.i18nService.t( + "seatsRemaining", + remainingSeats.toString(), + this.userOrg.seats.toString(), + ); + + this.resellerSeatsRemainingMessage = seatsRemaining; + } } ngOnDestroy() { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index c6e8b492c1c..34551f68cc0 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -10333,5 +10333,18 @@ "example": "Acme c" } } + }, + "seatsRemaining": { + "message": "You have $REMAINING$ seats remaining out of $TOTAL$ seats assigned to this organization. Contact your provider to manage your subscription.", + "placeholders": { + "remaining": { + "content": "$1", + "example": "5" + }, + "total": { + "content": "$2", + "example": "10" + } + } } }