mirror of
https://github.com/bitwarden/browser
synced 2026-01-06 10:33:57 +00:00
[AC-2708][AC-2712][AC-2713] upgrading from a Organizations new design changes (#10662)
* Changes base on the new design * changes base on the new design * Fix the family plan summary issue
This commit is contained in:
@@ -138,7 +138,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
clientOwnerEmail: ["", [Validators.email]],
|
||||
plan: [this.plan],
|
||||
productTier: [this.productTier],
|
||||
planInterval: [1],
|
||||
// planInterval: [1],
|
||||
});
|
||||
|
||||
planType: string;
|
||||
@@ -146,6 +146,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
selectedInterval: number = 1;
|
||||
planIntervals = PlanInterval;
|
||||
passwordManagerPlans: PlanResponse[];
|
||||
secretsManagerPlans: PlanResponse[];
|
||||
organization: Organization;
|
||||
sub: OrganizationSubscriptionResponse;
|
||||
billing: BillingResponse;
|
||||
@@ -188,6 +189,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
if (!this.selfHosted) {
|
||||
const plans = await this.apiService.getPlans();
|
||||
this.passwordManagerPlans = plans.data.filter((plan) => !!plan.PasswordManager);
|
||||
this.secretsManagerPlans = plans.data.filter((plan) => !!plan.SecretsManager);
|
||||
|
||||
if (
|
||||
this.productTier === ProductTierType.Enterprise ||
|
||||
@@ -230,12 +232,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
selected: false,
|
||||
},
|
||||
];
|
||||
|
||||
this.formGroup
|
||||
.get("planInterval")
|
||||
.valueChanges.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((value: number) => (this.selectedInterval = value));
|
||||
|
||||
this.discountPercentageFromSub = this.sub?.customerDiscount?.percentOff;
|
||||
|
||||
this.setInitialPlanSelection();
|
||||
@@ -243,15 +239,42 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
setInitialPlanSelection() {
|
||||
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
||||
if (
|
||||
this.organization.useSecretsManager &&
|
||||
this.currentPlan.productTier == ProductTierType.Free
|
||||
) {
|
||||
this.selectPlan(this.getPlanByType(ProductTierType.Teams));
|
||||
} else {
|
||||
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
||||
}
|
||||
}
|
||||
|
||||
getPlanByType(productTier: ProductTierType) {
|
||||
return this.selectableProducts.find((product) => product.productTier === productTier);
|
||||
}
|
||||
|
||||
isSecretsManagerTrial(): boolean {
|
||||
return (
|
||||
this.sub?.subscription?.items?.some((item) =>
|
||||
this.sub?.customerDiscount?.appliesTo?.includes(item.productId),
|
||||
) ?? false
|
||||
);
|
||||
}
|
||||
|
||||
planTypeChanged() {
|
||||
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
||||
if (
|
||||
this.organization.useSecretsManager &&
|
||||
this.currentPlan.productTier == ProductTierType.Free
|
||||
) {
|
||||
this.selectPlan(this.getPlanByType(ProductTierType.Teams));
|
||||
} else {
|
||||
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
||||
}
|
||||
}
|
||||
|
||||
updateInterval(event: number) {
|
||||
this.selectedInterval = event;
|
||||
this.planTypeChanged();
|
||||
}
|
||||
|
||||
protected getPlanIntervals() {
|
||||
@@ -270,7 +293,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
protected getPlanCardContainerClasses(plan: PlanResponse, index: number) {
|
||||
let cardState: PlanCardState;
|
||||
|
||||
if (plan == this.selectedPlan) {
|
||||
if (plan == this.currentPlan) {
|
||||
cardState = PlanCardState.Disabled;
|
||||
} else if (plan == this.selectedPlan) {
|
||||
cardState = PlanCardState.Selected;
|
||||
} else if (
|
||||
this.selectedInterval === PlanInterval.Monthly &&
|
||||
@@ -283,36 +308,19 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
|
||||
switch (cardState) {
|
||||
case PlanCardState.Selected: {
|
||||
if (this.currentPlan.productTier === ProductTierType.Teams) {
|
||||
return [
|
||||
"tw-group",
|
||||
"tw-cursor-pointer",
|
||||
"tw-block",
|
||||
"tw-rounded",
|
||||
"tw-w-1/2",
|
||||
"tw-border",
|
||||
"tw-border-solid",
|
||||
"tw-border-primary-600",
|
||||
"hover:tw-border-primary-700",
|
||||
"focus:tw-border-2",
|
||||
"focus:tw-border-primary-700",
|
||||
"focus:tw-rounded-lg",
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
"tw-group",
|
||||
"tw-cursor-pointer",
|
||||
"tw-block",
|
||||
"tw-rounded",
|
||||
"tw-border",
|
||||
"tw-border-solid",
|
||||
"tw-border-primary-600",
|
||||
"hover:tw-border-primary-700",
|
||||
"focus:tw-border-2",
|
||||
"focus:tw-border-primary-700",
|
||||
"focus:tw-rounded-lg",
|
||||
];
|
||||
}
|
||||
return [
|
||||
"tw-group",
|
||||
"tw-cursor-pointer",
|
||||
"tw-block",
|
||||
"tw-rounded",
|
||||
"tw-border",
|
||||
"tw-border-solid",
|
||||
"tw-border-primary-600",
|
||||
"hover:tw-border-primary-700",
|
||||
"focus:tw-border-2",
|
||||
"focus:tw-border-primary-700",
|
||||
"focus:tw-rounded-lg",
|
||||
];
|
||||
}
|
||||
case PlanCardState.NotSelected: {
|
||||
return [
|
||||
@@ -336,10 +344,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
"tw-text-muted",
|
||||
"tw-block",
|
||||
"tw-rounded",
|
||||
"tw-border",
|
||||
"tw-border-solid",
|
||||
"tw-border-1",
|
||||
"tw-border-secondary-300",
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -369,6 +373,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
get selectedSecretsManagerPlan() {
|
||||
return this.secretsManagerPlans.find((plan) => plan.type === this.selectedPlan.type);
|
||||
}
|
||||
|
||||
get selectedPlanInterval() {
|
||||
return this.selectedPlan.isAnnual ? "year" : "month";
|
||||
}
|
||||
@@ -397,9 +405,37 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
this.planIsEnabled(plan),
|
||||
);
|
||||
|
||||
if (
|
||||
this.currentPlan.productTier === ProductTierType.Free &&
|
||||
this.selectedInterval === PlanInterval.Monthly &&
|
||||
!this.organization.useSecretsManager
|
||||
) {
|
||||
const familyPlan = this.passwordManagerPlans.find(
|
||||
(plan) => plan.productTier == ProductTierType.Families,
|
||||
);
|
||||
result.push(familyPlan);
|
||||
}
|
||||
|
||||
if (
|
||||
this.organization.useSecretsManager &&
|
||||
this.currentPlan.productTier === ProductTierType.Free
|
||||
) {
|
||||
const familyPlanIndex = result.findIndex(
|
||||
(plan) => plan.productTier === ProductTierType.Families,
|
||||
);
|
||||
|
||||
if (familyPlanIndex !== -1) {
|
||||
result.splice(familyPlanIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentPlan.productTier !== ProductTierType.Free) {
|
||||
result.push(this.currentPlan);
|
||||
}
|
||||
|
||||
result.sort((planA, planB) => planA.displaySortOrder - planB.displaySortOrder);
|
||||
|
||||
return result.reverse();
|
||||
return result;
|
||||
}
|
||||
|
||||
get selectablePlans() {
|
||||
@@ -414,7 +450,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
passwordManagerSeatTotal(plan: PlanResponse): number {
|
||||
if (!plan.PasswordManager.hasAdditionalSeatsOption) {
|
||||
if (!plan.PasswordManager.hasAdditionalSeatsOption || this.isSecretsManagerTrial()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -422,6 +458,33 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
return result;
|
||||
}
|
||||
|
||||
secretsManagerSeatTotal(plan: PlanResponse, seats: number): number {
|
||||
if (!plan.SecretsManager.hasAdditionalSeatsOption) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return plan.SecretsManager.seatPrice * Math.abs(seats || 0);
|
||||
}
|
||||
|
||||
additionalStorageTotal(plan: PlanResponse): number {
|
||||
if (!plan.PasswordManager.hasAdditionalStorageOption) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (
|
||||
plan.PasswordManager.additionalStoragePricePerGb *
|
||||
Math.abs(this.organization.maxStorageGb || 0)
|
||||
);
|
||||
}
|
||||
|
||||
additionalServiceAccountTotal(plan: PlanResponse): number {
|
||||
if (!plan.SecretsManager.hasAdditionalServiceAccountOption || this.additionalServiceAccount) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return plan.SecretsManager.additionalPricePerServiceAccount * this.additionalServiceAccount;
|
||||
}
|
||||
|
||||
get passwordManagerSubtotal() {
|
||||
let subTotal = this.selectedPlan.PasswordManager.basePrice;
|
||||
if (this.selectedPlan.PasswordManager.hasAdditionalSeatsOption) {
|
||||
@@ -433,13 +496,37 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
return subTotal - this.discount;
|
||||
}
|
||||
|
||||
get secretsManagerSubtotal() {
|
||||
const plan = this.selectedSecretsManagerPlan;
|
||||
|
||||
if (!this.organization.useSecretsManager) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (
|
||||
plan.SecretsManager.basePrice +
|
||||
this.secretsManagerSeatTotal(plan, this.sub?.smSeats) +
|
||||
this.additionalServiceAccountTotal(plan)
|
||||
);
|
||||
}
|
||||
|
||||
get taxCharges() {
|
||||
return this.taxComponent != null && this.taxComponent.taxRate != null
|
||||
? (this.taxComponent.taxRate / 100) * this.passwordManagerSubtotal
|
||||
: 0;
|
||||
}
|
||||
|
||||
get passwordManagerSeats() {
|
||||
if (this.selectedPlan.productTier === ProductTierType.Families) {
|
||||
return this.selectedPlan.PasswordManager.baseSeats;
|
||||
}
|
||||
return this.organization.seats;
|
||||
}
|
||||
|
||||
get total() {
|
||||
if (this.organization.useSecretsManager) {
|
||||
return this.passwordManagerSubtotal + this.secretsManagerSubtotal + this.taxCharges || 0;
|
||||
}
|
||||
return this.passwordManagerSubtotal + this.taxCharges || 0;
|
||||
}
|
||||
|
||||
@@ -447,6 +534,15 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
return this.selectablePlans.some((plan) => plan.type === PlanType.TeamsStarter);
|
||||
}
|
||||
|
||||
get additionalServiceAccount() {
|
||||
const baseServiceAccount = this.selectedPlan.SecretsManager?.baseServiceAccount || 0;
|
||||
const usedServiceAccounts = this.sub?.smServiceAccounts || 0;
|
||||
|
||||
const additionalServiceAccounts = baseServiceAccount - usedServiceAccounts;
|
||||
|
||||
return additionalServiceAccounts <= 0 ? Math.abs(additionalServiceAccounts) : 0;
|
||||
}
|
||||
|
||||
changedProduct() {
|
||||
const selectedPlan = this.selectablePlans[0];
|
||||
|
||||
@@ -552,6 +648,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
request.billingAddressPostalCode = this.taxComponent.taxFormGroup?.value.postalCode;
|
||||
}
|
||||
|
||||
// Secrets Manager
|
||||
this.buildSecretsManagerRequest(request);
|
||||
|
||||
if (this.upgradeRequiresPaymentMethod || this.showPayment) {
|
||||
const tokenResult = await this.paymentComponent.createPaymentToken();
|
||||
const paymentRequest = new PaymentRequest();
|
||||
@@ -593,6 +692,22 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
return text;
|
||||
}
|
||||
|
||||
private buildSecretsManagerRequest(request: OrganizationUpgradeRequest): void {
|
||||
request.useSecretsManager = this.organization.useSecretsManager;
|
||||
if (!this.organization.useSecretsManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.selectedPlan.SecretsManager.hasAdditionalSeatsOption &&
|
||||
this.currentPlan.productTier === ProductTierType.Free
|
||||
) {
|
||||
request.additionalSmSeats = this.organization.seats;
|
||||
} else {
|
||||
request.additionalSmSeats = this.sub?.smSeats;
|
||||
}
|
||||
}
|
||||
|
||||
private upgradeFlowPrefillForm() {
|
||||
if (this.acceptingSponsorship) {
|
||||
this.formGroup.controls.productTier.setValue(ProductTierType.Families);
|
||||
|
||||
Reference in New Issue
Block a user