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 211e82a3c07..aa9539cdb0d 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 @@ -63,17 +63,36 @@ -
- {{ "details" | i18n }} +
+ {{ + "details" | i18n + }} - - - {{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @ - {{ i.amount | currency : "$" }} - - {{ i.quantity * i.amount | currency : "$" }} /{{ i.interval | i18n }} - + + + + {{ productName(i.bitwardenProduct) }} - + {{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @ + {{ i.amount | currency : "$" }} + + + {{ i.quantity * i.amount | currency : "$" }} /{{ i.interval | i18n }} + + + + + + {{ "passwordManager" | i18n }} - {{ "freeOrganization" | i18n }} + {{ "free" | i18n }} + + + {{ "secretsManager" | i18n }} - {{ "freeOrganization" | i18n }} + {{ "free" | 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 b9fc6798ec9..73782a5fc6f 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 @@ -10,7 +10,9 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { PlanType } from "@bitwarden/common/billing/enums"; +import { BitwardenProductType } from "@bitwarden/common/billing/enums/bitwarden-product-type.enum"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; +import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -26,6 +28,7 @@ import { }) export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy { sub: OrganizationSubscriptionResponse; + lineItems: BillingSubscriptionItemResponse[] = []; organizationId: string; userOrg: Organization; showChangePlan = false; @@ -68,6 +71,15 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy .subscribe(); } + productName(product: BitwardenProductType) { + switch (product) { + case BitwardenProductType.PasswordManager: + return this.i18nService.t("passwordManager"); + case BitwardenProductType.SecretsManager: + return this.i18nService.t("secretsManager"); + } + } + ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); @@ -81,6 +93,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy this.userOrg = this.organizationService.get(this.organizationId); if (this.userOrg.canViewSubscription) { this.sub = await this.organizationApiService.getSubscription(this.organizationId); + this.lineItems = this.sub?.subscription?.items?.sort(sortSubscriptionItems) ?? []; } const apiKeyResponse = await this.organizationApiService.getApiKeyInformation( @@ -332,3 +345,21 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy return this.subscription == null && this.sub.planType === PlanType.Free && !this.showChangePlan; } } + +/** + * Helper to sort subscription items by product type and then by addon status + */ +function sortSubscriptionItems( + a: BillingSubscriptionItemResponse, + b: BillingSubscriptionItemResponse +) { + if (a.bitwardenProduct == b.bitwardenProduct) { + // sort addon items to the bottom + return a.addonSubscriptionItem == b.addonSubscriptionItem + ? 0 + : a.addonSubscriptionItem + ? 1 + : -1; + } + return a.bitwardenProduct - b.bitwardenProduct; +} diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index c127ce921fb..19c8fd98051 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -6610,6 +6610,9 @@ "changeKdfLoggedOutWarning": { "message": "Proceeding will log you out of all active sessions. You will need to log back in and complete two-step login setup. We recommend exporting your vault before changing your encryption settings to prevent data loss." }, + "secretsManager": { + "message": "Secrets Manager" + }, "secretsManagerBeta": { "message": "Secrets Manager Beta" }, @@ -6847,5 +6850,8 @@ }, "passwordManager": { "message": "Password Manager" + }, + "freeOrganization": { + "message": "Free Organization" } } diff --git a/libs/common/src/billing/models/response/subscription.response.ts b/libs/common/src/billing/models/response/subscription.response.ts index 8230d98417a..f966a3e9bfa 100644 --- a/libs/common/src/billing/models/response/subscription.response.ts +++ b/libs/common/src/billing/models/response/subscription.response.ts @@ -1,4 +1,5 @@ import { BaseResponse } from "../../../models/response/base.response"; +import { BitwardenProductType } from "../../enums/bitwarden-product-type.enum"; export class SubscriptionResponse extends BaseResponse { storageName: string; @@ -62,6 +63,8 @@ export class BillingSubscriptionItemResponse extends BaseResponse { quantity: number; interval: string; sponsoredSubscriptionItem: boolean; + addonSubscriptionItem: boolean; + bitwardenProduct: BitwardenProductType; constructor(response: any) { super(response); @@ -70,6 +73,8 @@ export class BillingSubscriptionItemResponse extends BaseResponse { this.quantity = this.getResponseProperty("Quantity"); this.interval = this.getResponseProperty("Interval"); this.sponsoredSubscriptionItem = this.getResponseProperty("SponsoredSubscriptionItem"); + this.addonSubscriptionItem = this.getResponseProperty("AddonSubscriptionItem"); + this.bitwardenProduct = this.getResponseProperty("BitwardenProduct"); } }