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"
+ }
+ }
}
}