diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 5b4cd52ac8e..8d4067c1167 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -138,7 +138,12 @@ jobs:
eval "$(printf '\n' | /usr/bin/gnome-keyring-daemon --start)"
cargo test -- --test-threads=1
- - name: Test Windows / macOS
- if: ${{ matrix.os!='ubuntu-latest' }}
+ - name: Test macOS
+ if: ${{ matrix.os=='macos-latest' }}
working-directory: ./apps/desktop/desktop_native
run: cargo test -- --test-threads=1
+
+ - name: Test Windows
+ if: ${{ matrix.os=='windows-latest'}}
+ working-directory: ./apps/desktop/desktop_native/core
+ run: cargo test -- --test-threads=1
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 05525be6ffd..4e6f27bdd2c 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -4308,6 +4308,9 @@
},
"enterprisePolicyRequirementsApplied": {
"message": "Enterprise policy requirements have been applied to this setting"
+ },
+ "fileSavedToDevice": {
+ "message": "File saved to device. Manage from your device downloads."
},
"showCharacterCount": {
"message": "Show character count"
diff --git a/apps/browser/src/vault/popup/components/vault/attachments.component.ts b/apps/browser/src/vault/popup/components/vault/attachments.component.ts
index ee6f1ac7d07..75819689b44 100644
--- a/apps/browser/src/vault/popup/components/vault/attachments.component.ts
+++ b/apps/browser/src/vault/popup/components/vault/attachments.component.ts
@@ -14,7 +14,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
-import { DialogService } from "@bitwarden/components";
+import { DialogService, ToastService } from "@bitwarden/components";
@Component({
selector: "app-vault-attachments",
@@ -38,6 +38,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
+ toastService: ToastService,
) {
super(
cipherService,
@@ -52,6 +53,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On
dialogService,
billingAccountProfileStateService,
accountService,
+ toastService,
);
}
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index 5345c6e15a3..9194fd7c22a 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -1285,6 +1285,9 @@
}
}
},
+ "copySuccessful": {
+ "message": "Copy Successful"
+ },
"errorRefreshingAccessToken": {
"message": "Access Token Refresh Error"
},
@@ -3061,5 +3064,8 @@
},
"ssoError": {
"message": "No free ports could be found for the sso login."
+ },
+ "fileSavedToDevice": {
+ "message": "File saved to device. Manage from your device downloads."
}
}
diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts
index b1ddcbc7e7d..2e25d390872 100644
--- a/apps/desktop/src/vault/app/vault/attachments.component.ts
+++ b/apps/desktop/src/vault/app/vault/attachments.component.ts
@@ -11,7 +11,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
-import { DialogService } from "@bitwarden/components";
+import { DialogService, ToastService } from "@bitwarden/components";
@Component({
selector: "app-vault-attachments",
@@ -30,6 +30,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
+ toastService: ToastService,
) {
super(
cipherService,
@@ -44,6 +45,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
dialogService,
billingAccountProfileStateService,
accountService,
+ toastService,
);
}
}
diff --git a/apps/web/src/app/auth/migrate-encryption/migrate-legacy-encryption.component.ts b/apps/web/src/app/auth/migrate-encryption/migrate-legacy-encryption.component.ts
index 68eaae618fd..6c894f4fa85 100644
--- a/apps/web/src/app/auth/migrate-encryption/migrate-legacy-encryption.component.ts
+++ b/apps/web/src/app/auth/migrate-encryption/migrate-legacy-encryption.component.ts
@@ -7,9 +7,9 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
+import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
-import { ToastService } from "@bitwarden/components";
+import { DialogService, ToastService } from "@bitwarden/components";
import { SharedModule } from "../../shared";
import { UserKeyRotationModule } from "../key-rotation/user-key-rotation.module";
@@ -31,12 +31,13 @@ export class MigrateFromLegacyEncryptionComponent {
private accountService: AccountService,
private keyRotationService: UserKeyRotationService,
private i18nService: I18nService,
- private platformUtilsService: PlatformUtilsService,
private cryptoService: CryptoService,
private messagingService: MessagingService,
private logService: LogService,
private syncService: SyncService,
private toastService: ToastService,
+ private dialogService: DialogService,
+ private folderApiService: FolderApiServiceAbstraction,
) {}
submit = async () => {
@@ -69,6 +70,23 @@ export class MigrateFromLegacyEncryptionComponent {
});
this.messagingService.send("logout");
} catch (e) {
+ // If the error is due to missing folders, we can delete all folders and try again
+ if (e.message === "All existing folders must be included in the rotation.") {
+ const deleteFolders = await this.dialogService.openSimpleDialog({
+ type: "warning",
+ title: { key: "encryptionKeyUpdateCannotProceed" },
+ content: { key: "keyUpdateFoldersFailed" },
+ acceptButtonText: { key: "ok" },
+ cancelButtonText: { key: "cancel" },
+ });
+
+ if (deleteFolders) {
+ await this.folderApiService.deleteAll();
+ await this.syncService.fullSync(true, true);
+ await this.submit();
+ return;
+ }
+ }
this.logService.error(e);
throw e;
}
diff --git a/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts b/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts
index 9d763886fb4..e0a6f6c53d5 100644
--- a/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts
+++ b/apps/web/src/app/auth/settings/emergency-access/attachments/emergency-access-attachments.component.ts
@@ -12,7 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
-import { DialogService } from "@bitwarden/components";
+import { DialogService, ToastService } from "@bitwarden/components";
@Component({
selector: "emergency-access-attachments",
@@ -34,6 +34,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen
dialogService: DialogService,
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
+ toastService: ToastService,
) {
super(
cipherService,
@@ -48,6 +49,7 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen
dialogService,
billingAccountProfileStateService,
accountService,
+ toastService,
);
}
diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html
index 14b6864d643..766646003ba 100644
--- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html
+++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html
@@ -7,31 +7,37 @@
{{ "upgradePlans" | i18n }}
{{ "selectAPlan" | i18n }}
+
0
? discountPercentageFromSub
- : this.discountPercentage && selectedInterval === 1
+ : this.discountPercentage && selectedInterval === planIntervals.Annually
"
bitBadge
variant="success"
>{{
"upgradeDiscount"
| i18n
- : (this.discountPercentageFromSub > 0
- ? discountPercentageFromSub
- : this.discountPercentage)
+ : (selectedInterval === planIntervals.Annually
+ ? discountPercentageFromSub + this.discountPercentage
+ : this.discountPercentageFromSub)
}}
-
+
{{ planInterval.name }}
@@ -40,6 +46,7 @@
+
{{ "recommended" | i18n }}
-
+
{{
selectableProduct.nameLocalizationKey | i18n
}}
-
+
{{ "current" | i18n }}
@@ -133,10 +151,13 @@
else nonEnterprisePlans
"
>
-
+
{{ "bitwardenPasswordManager" | i18n }}
-
{{ "enterprisePlanUpgradeMessage" | i18n }}
+
{{ "enterprisePlanUpgradeMessage" | i18n }}
-
+
{{ "bitwardenSecretsManager" | i18n }}
@@ -195,25 +219,25 @@
{{ "bitwardenPasswordManager" | i18n }}
{{ "teamsPlanUpgradeMessage" | i18n }}
{{ "familyPlanUpgradeMessage" | i18n }}
{{ "secretsManagerSubInfo" | i18n }}
- {{ "secretsManagerWithFreePasswordManagerInfo" | i18n }}
+ {{ "secretsManagerComplimentaryPasswordManager" | i18n }}
@@ -392,23 +416,37 @@
0
- "
+ *ngIf="selectedPlan.PasswordManager.hasAdditionalStorageOption && storageGb > 0"
>
- {{ organization.maxStorageGb }}
+ {{ storageGb }}
{{ "additionalStorageGbMessage" | i18n }}
×
{{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }}
/{{ "year" | i18n }}
- {{
- organization.maxStorageGb * selectedPlan.PasswordManager.additionalStoragePricePerGb
- | currency: "$"
- }}
+ {{ additionalStorageTotal(selectedPlan) | currency: "$" }}
+
+
+
+
+
+ {{
+ "providerDiscount"
+ | i18n: this.discountPercentageFromSub + this.discountPercentage
+ | lowercase
+ }}
+
+ {{
+ calculateTotalAppliedDiscount(
+ passwordManagerSeatTotal(selectedPlan) + additionalStorageTotal(selectedPlan)
+ ) | currency: "$"
+ }}
+
@@ -459,18 +497,40 @@
bitTypography="body2"
*ngIf="
selectedPlan?.SecretsManager?.hasAdditionalServiceAccountOption &&
- additionalServiceAccount
+ additionalServiceAccount > 0
"
>
{{ additionalServiceAccount }}
- {{ "additionalStorageGbMessage" | i18n }}
+ {{ "serviceAccounts" | i18n | lowercase }}
×
{{ selectedPlan?.SecretsManager?.additionalPricePerServiceAccount | currency: "$" }}
/{{ "month" | i18n }}
{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}
+
+
+
+
+ {{
+ "providerDiscount"
+ | i18n: this.discountPercentageFromSub + this.discountPercentage
+ | lowercase
+ }}
+
+ {{
+ calculateTotalAppliedDiscount(
+ additionalServiceAccountTotal(selectedPlan) +
+ secretsManagerSeatTotal(selectedPlan, sub.smSeats)
+ ) | currency: "$"
+ }}
+
+
@@ -512,24 +572,39 @@
0
- "
+ *ngIf="selectedPlan.PasswordManager.hasAdditionalStorageOption && storageGb > 0"
>
- {{ organization.maxStorageGb }}
+ {{ storageGb }}
{{ "additionalStorageGbMessage" | i18n }}
×
{{ additionalStoragePriceMonthly(selectedPlan) | currency: "$" }}
/{{ "month" | i18n }}
{{
- organization.maxStorageGb * selectedPlan.PasswordManager.additionalStoragePricePerGb
- | currency: "$"
+ storageGb * selectedPlan.PasswordManager.additionalStoragePricePerGb | currency: "$"
}}
+
+
+
+ 0 ? 'block' : 'none'"
+ >
+ {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }}
+
+ 0 ? 'block' : 'none'"
+ class="tw-line-through tw-text-xs"
+ >{{ calculateTotalAppliedDiscount(total) | currency: "$" }}
+
+
{{ "secretsManager" | i18n }}
@@ -575,18 +650,41 @@
bitTypography="body2"
*ngIf="
selectedPlan.SecretsManager.hasAdditionalServiceAccountOption &&
- additionalServiceAccount
+ additionalServiceAccount > 0
"
>
{{ additionalServiceAccount }}
- {{ "additionalStorageGbMessage" | i18n }}
+ {{ "serviceAccounts" | i18n | lowercase }}
×
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
/{{ "month" | i18n }}
{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}
+
+
+
+ 0 ? 'block' : 'none'"
+ >
+ {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }}
+
+ 0 ? 'block' : 'none'"
+ class="tw-line-through tw-text-xs"
+ >{{
+ additionalServiceAccountTotal(selectedPlan) +
+ secretsManagerSeatTotal(selectedPlan, sub?.smSeats) | currency: "$"
+ }}
+
+
@@ -641,18 +739,40 @@
bitTypography="body2"
*ngIf="
selectedPlan.SecretsManager.hasAdditionalServiceAccountOption &&
- additionalServiceAccount
+ additionalServiceAccount > 0
"
>
{{ additionalServiceAccount }}
- {{ "additionalStorageGbMessage" | i18n }}
+ {{ "serviceAccounts" | i18n }}
×
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
/{{ "month" | i18n }}
{{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}
+
+
+
+
+ {{
+ "providerDiscount"
+ | i18n: this.discountPercentageFromSub + this.discountPercentage
+ | lowercase
+ }}
+
+ {{
+ calculateTotalAppliedDiscount(
+ additionalServiceAccountTotal(selectedPlan) +
+ secretsManagerSeatTotal(selectedPlan, sub.smSeats)
+ ) | currency: "$"
+ }}
+
+
{{ "passwordManager" | i18n }}
@@ -663,7 +783,7 @@
*ngIf="selectedPlan.PasswordManager.basePrice"
>
- {{ organization.seats }}
+ {{ sub?.seats }}
{{ "members" | i18n }} ×
{{
(selectedPlan.isAnnual
@@ -694,7 +814,7 @@
{{ "additionalUsers" | i18n }}:
- {{ organization.seats || 0 }}
+ {{ sub?.seats || 0 }}
{{ "members" | i18n }}
×
{{ selectedPlan.PasswordManager.seatPrice | currency: "$" }}
@@ -756,12 +876,12 @@
bitTypography="body2"
*ngIf="
selectedPlan.SecretsManager.hasAdditionalServiceAccountOption &&
- additionalServiceAccount
+ additionalServiceAccount > 0
"
>
{{ additionalServiceAccount }}
- {{ "additionalStorageGbMessage" | i18n }}
+ {{ "serviceAccounts" | i18n }}
×
{{ selectedPlan.SecretsManager.additionalPricePerServiceAccount | currency: "$" }}
/{{ "month" | i18n }}
@@ -795,7 +915,7 @@
{{ "additionalUsers" | i18n }}:
- {{ organization.seats }}
+ {{ sub?.seats }}
{{ "members" | i18n }}
×
{{ selectedPlan.PasswordManager.seatPrice | currency: "$" }}
@@ -811,6 +931,46 @@
+
+
+
+
+
+
+ {{
+ "providerDiscount"
+ | i18n: this.discountPercentageFromSub + this.discountPercentage
+ | lowercase
+ }}
+
+ {{
+ calculateTotalAppliedDiscount(total) | currency: "$"
+ }}
+
+
+ 0 ? 'block' : 'none'"
+ >
+ {{ "providerDiscount" | i18n: this.discountPercentageFromSub | lowercase }}
+
+ 0 ? 'block' : 'none'"
+ class="tw-line-through tw-text-xs"
+ >{{ calculateTotalAppliedDiscount(total) | currency: "$" }}
+
+
+
+
{{ total | currency: "USD" : "$" }}
- / {{ selectedPlanInterval | i18n }}
+
+ / {{ selectedPlanInterval | i18n }}
diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts
index 9a20fe38efc..dc9f6cce688 100644
--- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts
+++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts
@@ -246,27 +246,28 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
selected: false,
},
];
- this.discountPercentageFromSub = this.sub?.customerDiscount?.percentOff;
+ this.discountPercentageFromSub = this.isSecretsManagerTrial()
+ ? 0
+ : (this.sub?.customerDiscount?.percentOff ?? 0);
this.setInitialPlanSelection();
this.loading = false;
}
setInitialPlanSelection() {
- if (
- this.organization.useSecretsManager &&
- this.currentPlan.productTier == ProductTierType.Free
- ) {
- this.selectPlan(this.getPlanByType(ProductTierType.Teams));
- } else {
- this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
- }
+ this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
}
getPlanByType(productTier: ProductTierType) {
return this.selectableProducts.find((product) => product.productTier === productTier);
}
+ secretsManagerTrialDiscount() {
+ return this.sub?.customerDiscount?.appliesTo?.includes("sm-standalone")
+ ? this.discountPercentage
+ : this.discountPercentageFromSub + this.discountPercentage;
+ }
+
isSecretsManagerTrial(): boolean {
return (
this.sub?.subscription?.items?.some((item) =>
@@ -276,14 +277,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
}
planTypeChanged() {
- if (
- this.organization.useSecretsManager &&
- this.currentPlan.productTier == ProductTierType.Free
- ) {
- this.selectPlan(this.getPlanByType(ProductTierType.Teams));
- } else {
- this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
- }
+ this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
}
updateInterval(event: number) {
@@ -304,6 +298,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
];
}
+ optimizedNgForRender(index: number) {
+ return index;
+ }
+
protected getPlanCardContainerClasses(plan: PlanResponse, index: number) {
let cardState: PlanCardState;
@@ -370,6 +368,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
) {
return;
}
+
+ if (plan === this.currentPlan) {
+ return;
+ }
this.selectedPlan = plan;
this.formGroup.patchValue({ productTier: plan.productTier });
}
@@ -463,6 +465,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
return result;
}
+ get storageGb() {
+ return this.sub?.maxStorageGb - 1;
+ }
+
passwordManagerSeatTotal(plan: PlanResponse): number {
if (!plan.PasswordManager.hasAdditionalSeatsOption || this.isSecretsManagerTrial()) {
return 0;
@@ -486,8 +492,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
}
return (
- plan.PasswordManager.additionalStoragePricePerGb *
- Math.abs(this.organization.maxStorageGb || 0)
+ plan.PasswordManager.additionalStoragePricePerGb * Math.abs(this.sub?.maxStorageGb - 1 || 0)
);
}
@@ -499,7 +504,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
}
additionalServiceAccountTotal(plan: PlanResponse): number {
- if (!plan.SecretsManager.hasAdditionalServiceAccountOption || this.additionalServiceAccount) {
+ if (
+ !plan.SecretsManager.hasAdditionalServiceAccountOption ||
+ this.additionalServiceAccount == 0
+ ) {
return 0;
}
@@ -541,7 +549,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
if (this.selectedPlan.productTier === ProductTierType.Families) {
return this.selectedPlan.PasswordManager.baseSeats;
}
- return this.organization.seats;
+ return this.sub?.seats;
}
get total() {
@@ -565,7 +573,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
}
get additionalServiceAccount() {
- const baseServiceAccount = this.selectedPlan.SecretsManager?.baseServiceAccount || 0;
+ const baseServiceAccount = this.currentPlan.SecretsManager?.baseServiceAccount || 0;
const usedServiceAccounts = this.sub?.smServiceAccounts || 0;
const additionalServiceAccounts = baseServiceAccount - usedServiceAccounts;
@@ -652,7 +660,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
if (!this.acceptingSponsorship && !this.isInTrialFlow) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate(["/organizations/" + orgId]);
+ this.router.navigate(["/organizations/" + orgId + "/members"]);
}
if (this.isInTrialFlow) {
@@ -676,11 +684,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
private async updateOrganization() {
const request = new OrganizationUpgradeRequest();
if (this.selectedPlan.productTier !== ProductTierType.Families) {
- request.additionalSeats = this.organization.seats;
+ request.additionalSeats = this.sub?.seats;
}
- if (this.organization.maxStorageGb > this.selectedPlan.PasswordManager.baseStorageGb) {
+ if (this.sub?.maxStorageGb > this.selectedPlan.PasswordManager.baseStorageGb) {
request.additionalStorageGb =
- this.organization.maxStorageGb - this.selectedPlan.PasswordManager.baseStorageGb;
+ this.sub?.maxStorageGb - this.selectedPlan.PasswordManager.baseStorageGb;
}
request.premiumAccessAddon =
this.selectedPlan.PasswordManager.hasPremiumAccessOption &&
@@ -768,6 +776,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
request.additionalSmSeats = this.organization.seats;
} else {
request.additionalSmSeats = this.sub?.smSeats;
+ request.additionalServiceAccounts = this.additionalServiceAccount;
}
}
@@ -812,6 +821,16 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
this.totalOpened = !this.totalOpened;
}
+ calculateTotalAppliedDiscount(total: number) {
+ const discountPercent =
+ this.selectedInterval == PlanInterval.Annually
+ ? this.discountPercentage + this.discountPercentageFromSub
+ : this.discountPercentageFromSub;
+
+ const discountedTotal = total / (1 - discountPercent / 100);
+ return discountedTotal;
+ }
+
get paymentSourceClasses() {
if (this.billing.paymentSource == null) {
return [];
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 25c8c547b2b..341324c4a2a 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
@@ -69,14 +69,25 @@
>
-
{{
- "details" | i18n
- }}
+
{{ "details" | i18n
+ }} 0 && !isSecretsManagerTrial()"
+ bitBadge
+ variant="success"
+ >{{ "providerDiscount" | i18n: customerDiscount?.percentOff }}
- |
+ |
{{ i.productName | i18n }} -
{{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @
{{ i.amount | currency: "$" }}
@@ -91,7 +102,19 @@
{{ "freeForOneYear" | i18n }}
- {{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}
+
+
+ {{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}
+
+ {{
+ calculateTotalAppliedDiscount(i.quantity * i.amount) | currency: "$"
+ }}
+ / {{ "year" | i18n }}
+
|
@@ -112,7 +135,7 @@
-
+
{{ "exportPasswordDescription" | i18n }}
diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
index d83d189cd79..e4f5ec9d32d 100644
--- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
+++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
@@ -121,7 +121,6 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
encryptedExportType = EncryptedExportType;
protected showFilePassword: boolean;
- filePasswordValue: string = null;
private _disabledByPolicy = false;
organizations$: Observable
;
@@ -278,18 +277,9 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
generatePassword = async () => {
const [options] = await this.passwordGenerationService.getOptions();
- this.filePasswordValue = await this.passwordGenerationService.generatePassword(options);
- this.exportForm.get("filePassword").setValue(this.filePasswordValue);
- this.exportForm.get("confirmFilePassword").setValue(this.filePasswordValue);
- };
-
- copyPasswordToClipboard = async () => {
- this.platformUtilsService.copyToClipboard(this.filePasswordValue);
- this.toastService.showToast({
- variant: "success",
- title: null,
- message: this.i18nService.t("valueCopied", this.i18nService.t("password")),
- });
+ const generatedPassword = await this.passwordGenerationService.generatePassword(options);
+ this.exportForm.get("filePassword").setValue(generatedPassword);
+ this.exportForm.get("confirmFilePassword").setValue(generatedPassword);
};
submit = async () => {