diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index f175c55c826..8ce3bcd2b60 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -1,7 +1,8 @@ import { CommonModule } from "@angular/common"; import { booleanAttribute, Component, Input, OnInit } from "@angular/core"; import { Router, RouterModule } from "@angular/router"; -import { firstValueFrom, map, Observable } from "rxjs"; +import { BehaviorSubject, firstValueFrom, map, switchMap } from "rxjs"; +import { filter } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -30,10 +31,18 @@ import { AddEditQueryParams } from "../add-edit/add-edit-v2.component"; imports: [ItemModule, IconButtonModule, MenuModule, CommonModule, JslibModule, RouterModule], }) export class ItemMoreOptionsComponent implements OnInit { + private _cipher$ = new BehaviorSubject(undefined); + @Input({ required: true, }) - cipher: CipherView; + set cipher(c: CipherView) { + this._cipher$.next(c); + } + + get cipher() { + return this._cipher$.value; + } /** * Flag to hide the autofill menu options. Used for items that are @@ -43,7 +52,15 @@ export class ItemMoreOptionsComponent implements OnInit { hideAutofillOptions: boolean; protected autofillAllowed$ = this.vaultPopupAutofillService.autofillAllowed$; - protected canClone$: Observable; + + /** + * Observable that emits a boolean value indicating if the user is authorized to clone the cipher. + * @protected + */ + protected canClone$ = this._cipher$.pipe( + filter((c) => c != null), + switchMap((c) => this.cipherAuthorizationService.canCloneCipher$(c)), + ); /** Boolean dependent on the current user having access to an organization */ protected hasOrganizations = false; @@ -63,7 +80,6 @@ export class ItemMoreOptionsComponent implements OnInit { async ngOnInit(): Promise { this.hasOrganizations = await this.organizationService.hasOrganizations(); - this.canClone$ = this.cipherAuthorizationService.canCloneCipher$(this.cipher); } get canEdit() { diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index d19854598fc..447ae10870f 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -9017,6 +9017,12 @@ "providerPlan": { "message": "Managed Service Provider" }, + "managedServiceProvider": { + "message": "Managed service provider" + }, + "multiOrganizationEnterprise": { + "message": "Multi-organization enterprise" + }, "orgSeats": { "message": "Organization Seats" }, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html index ed58650f211..e72b9ed661a 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html @@ -12,7 +12,7 @@ }} -
+
plan.type === PlanType.TeamsMonthly); - const enterprisePlan = this.dialogParams.plans.find( - (plan) => plan.type === PlanType.EnterpriseMonthly, - ); - this.discountPercentage = response.discountPercentage; const discountFactor = this.discountPercentage ? (100 - this.discountPercentage) / 100 : 1; - this.planCards = [ - { - name: this.i18nService.t("planNameTeams"), - cost: teamsPlan.PasswordManager.providerPortalSeatPrice * discountFactor, - type: teamsPlan.type, - plan: teamsPlan, - selected: true, - }, - { - name: this.i18nService.t("planNameEnterprise"), - cost: enterprisePlan.PasswordManager.providerPortalSeatPrice * discountFactor, - type: enterprisePlan.type, - plan: enterprisePlan, - selected: false, - }, - ]; + this.planCards = []; + + for (let i = 0; i < this.providerPlans.length; i++) { + const providerPlan = this.providerPlans[i]; + const plan = this.dialogParams.plans.find((plan) => plan.type === providerPlan.type); + + let planName: string; + switch (plan.productTier) { + case ProductTierType.Teams: { + planName = this.i18nService.t("planNameTeams"); + break; + } + case ProductTierType.Enterprise: { + planName = this.i18nService.t("planNameEnterprise"); + break; + } + } + + this.planCards.push({ + name: planName, + cost: plan.PasswordManager.providerPortalSeatPrice * discountFactor, + type: plan.type, + plan: plan, + selected: i === 0, + }); + } this.loading = false; } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.html b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.html index 6c4bf422f7a..f08dbf0c37a 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.html @@ -4,7 +4,7 @@
{{ "billingPlan" | i18n }}
-
{{ "providerPlan" | i18n }}
+
{{ plan | i18n }}
{{ data.status.label }}
diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts index c3ad875136e..dea7d4ca197 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts @@ -1,6 +1,7 @@ import { DatePipe } from "@angular/common"; import { Component, Input } from "@angular/core"; +import { ProviderType } from "@bitwarden/common/admin-console/enums"; import { ProviderSubscriptionResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -32,6 +33,15 @@ export class ProviderSubscriptionStatusComponent { private i18nService: I18nService, ) {} + get plan(): string { + switch (this.subscription.providerType) { + case ProviderType.Msp: + return "managedServiceProvider"; + case ProviderType.MultiOrganizationEnterprise: + return "multiOrganizationEnterprise"; + } + } + get status(): string { if (this.subscription.cancelAt && this.subscription.status === "active") { return "pending_cancellation"; diff --git a/libs/common/src/admin-console/enums/provider-type.enum.ts b/libs/common/src/admin-console/enums/provider-type.enum.ts index 5f81c338f0e..d802c659f6f 100644 --- a/libs/common/src/admin-console/enums/provider-type.enum.ts +++ b/libs/common/src/admin-console/enums/provider-type.enum.ts @@ -1,4 +1,5 @@ export enum ProviderType { Msp = 0, Reseller = 1, + MultiOrganizationEnterprise = 2, } diff --git a/libs/common/src/billing/models/response/provider-subscription-response.ts b/libs/common/src/billing/models/response/provider-subscription-response.ts index 2dc9d4281de..2ecf988addd 100644 --- a/libs/common/src/billing/models/response/provider-subscription-response.ts +++ b/libs/common/src/billing/models/response/provider-subscription-response.ts @@ -1,3 +1,5 @@ +import { ProviderType } from "@bitwarden/common/admin-console/enums"; +import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; import { SubscriptionSuspensionResponse } from "@bitwarden/common/billing/models/response/subscription-suspension.response"; import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; @@ -13,6 +15,7 @@ export class ProviderSubscriptionResponse extends BaseResponse { taxInformation?: TaxInfoResponse; cancelAt?: string; suspension?: SubscriptionSuspensionResponse; + providerType: ProviderType; constructor(response: any) { super(response); @@ -34,6 +37,7 @@ export class ProviderSubscriptionResponse extends BaseResponse { if (suspension != null) { this.suspension = new SubscriptionSuspensionResponse(suspension); } + this.providerType = this.getResponseProperty("providerType"); } } @@ -44,6 +48,8 @@ export class ProviderPlanResponse extends BaseResponse { purchasedSeats: number; cost: number; cadence: string; + type: PlanType; + productTier: ProductTierType; constructor(response: any) { super(response); @@ -53,5 +59,7 @@ export class ProviderPlanResponse extends BaseResponse { this.purchasedSeats = this.getResponseProperty("PurchasedSeats"); this.cost = this.getResponseProperty("Cost"); this.cadence = this.getResponseProperty("Cadence"); + this.type = this.getResponseProperty("Type"); + this.productTier = this.getResponseProperty("ProductTier"); } } diff --git a/package-lock.json b/package-lock.json index ab1c7d90655..831de35c150 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "bufferutil": "4.0.8", "chalk": "4.1.2", "commander": "11.1.0", - "core-js": "3.36.1", + "core-js": "3.39.0", "form-data": "4.0.0", "https-proxy-agent": "7.0.5", "inquirer": "8.2.6", @@ -151,7 +151,7 @@ "gulp-json-editor": "2.6.0", "gulp-replace": "1.1.4", "gulp-zip": "6.0.0", - "html-loader": "5.0.0", + "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", "husky": "9.1.4", @@ -162,7 +162,7 @@ "lint-staged": "15.2.8", "mini-css-extract-plugin": "2.9.1", "node-ipc": "9.2.1", - "postcss": "8.4.38", + "postcss": "8.4.47", "postcss-loader": "8.1.1", "prettier": "3.3.3", "prettier-plugin-tailwindcss": "0.6.8", @@ -14735,9 +14735,9 @@ } }, "node_modules/core-js": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz", - "integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", + "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -21127,9 +21127,9 @@ "peer": true }, "node_modules/html-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-5.0.0.tgz", - "integrity": "sha512-puaGKdjdVVIFRtgIC2n5dt5bt0N5j6heXlAQZ4Do1MLjHmOT1gCE1Ogg7XZNeJlnOVHHsrZKGs5dfh+XwZ3XPw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-5.1.0.tgz", + "integrity": "sha512-Jb3xwDbsm0W3qlXrCZwcYqYGnYz55hb6aoKQTlzyZPXsPpi6tHXzAfqalecglMQgNvtEfxrCQPaKT90Irt5XDA==", "dev": true, "license": "MIT", "dependencies": { @@ -30889,9 +30889,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -30910,8 +30910,8 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" diff --git a/package.json b/package.json index 1018a1bd262..5771f614001 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "gulp-json-editor": "2.6.0", "gulp-replace": "1.1.4", "gulp-zip": "6.0.0", - "html-loader": "5.0.0", + "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", "husky": "9.1.4", @@ -123,7 +123,7 @@ "lint-staged": "15.2.8", "mini-css-extract-plugin": "2.9.1", "node-ipc": "9.2.1", - "postcss": "8.4.38", + "postcss": "8.4.47", "postcss-loader": "8.1.1", "prettier": "3.3.3", "prettier-plugin-tailwindcss": "0.6.8", @@ -174,7 +174,7 @@ "bufferutil": "4.0.8", "chalk": "4.1.2", "commander": "11.1.0", - "core-js": "3.36.1", + "core-js": "3.39.0", "form-data": "4.0.0", "https-proxy-agent": "7.0.5", "inquirer": "8.2.6",