diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index 2d02cbc5bdf..113d2feabe4 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -5,6 +5,8 @@ import { firstValueFrom, lastValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -12,10 +14,14 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { + AdjustStorageDialogV2Component, + AdjustStorageDialogV2ResultType, +} from "../shared/adjust-storage-dialog/adjust-storage-dialog-v2.component"; import { AdjustStorageDialogResult, openAdjustStorageDialog, -} from "../shared/adjust-storage.component"; +} from "../shared/adjust-storage-dialog/adjust-storage-dialog.component"; import { OffboardingSurveyDialogResultType, openOffboardingSurvey, @@ -38,6 +44,10 @@ export class UserSubscriptionComponent implements OnInit { cancelPromise: Promise; reinstatePromise: Promise; + protected deprecateStripeSourcesAPI$ = this.configService.getFeatureFlag$( + FeatureFlag.AC2476_DeprecateStripeSourcesAPI, + ); + constructor( private apiService: ApiService, private platformUtilsService: PlatformUtilsService, @@ -49,6 +59,7 @@ export class UserSubscriptionComponent implements OnInit { private environmentService: EnvironmentService, private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, + private configService: ConfigService, ) { this.selfHosted = platformUtilsService.isSelfHost(); } @@ -150,15 +161,33 @@ export class UserSubscriptionComponent implements OnInit { }; adjustStorage = async (add: boolean) => { - const dialogRef = openAdjustStorageDialog(this.dialogService, { - data: { - storageGbPrice: 4, - add: add, - }, - }); - const result = await lastValueFrom(dialogRef.closed); - if (result === AdjustStorageDialogResult.Adjusted) { - await this.load(); + const deprecateStripeSourcesAPI = await firstValueFrom(this.deprecateStripeSourcesAPI$); + + if (deprecateStripeSourcesAPI) { + const dialogRef = AdjustStorageDialogV2Component.open(this.dialogService, { + data: { + price: 4, + cadence: "year", + type: add ? "Add" : "Remove", + }, + }); + + const result = await lastValueFrom(dialogRef.closed); + + if (result === AdjustStorageDialogV2ResultType.Submitted) { + await this.load(); + } + } else { + const dialogRef = openAdjustStorageDialog(this.dialogService, { + data: { + storageGbPrice: 4, + add: add, + }, + }); + const result = await lastValueFrom(dialogRef.closed); + if (result === AdjustStorageDialogResult.Adjusted) { + await this.load(); + } } }; 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 f28933a4ecc..2a565face75 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 @@ -18,10 +18,14 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { + AdjustStorageDialogV2Component, + AdjustStorageDialogV2ResultType, +} from "../shared/adjust-storage-dialog/adjust-storage-dialog-v2.component"; import { AdjustStorageDialogResult, openAdjustStorageDialog, -} from "../shared/adjust-storage.component"; +} from "../shared/adjust-storage-dialog/adjust-storage-dialog.component"; import { OffboardingSurveyDialogResultType, openOffboardingSurvey, @@ -71,6 +75,10 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy FeatureFlag.EnableUpgradePasswordManagerSub, ); + protected deprecateStripeSourcesAPI$ = this.configService.getFeatureFlag$( + FeatureFlag.AC2476_DeprecateStripeSourcesAPI, + ); + constructor( private apiService: ApiService, private platformUtilsService: PlatformUtilsService, @@ -458,17 +466,36 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy adjustStorage = (add: boolean) => { return async () => { - const dialogRef = openAdjustStorageDialog(this.dialogService, { - data: { - storageGbPrice: this.storageGbPrice, - add: add, - organizationId: this.organizationId, - interval: this.billingInterval, - }, - }); - const result = await lastValueFrom(dialogRef.closed); - if (result === AdjustStorageDialogResult.Adjusted) { - await this.load(); + const deprecateStripeSourcesAPI = await firstValueFrom(this.deprecateStripeSourcesAPI$); + + if (deprecateStripeSourcesAPI) { + const dialogRef = AdjustStorageDialogV2Component.open(this.dialogService, { + data: { + price: this.storageGbPrice, + cadence: this.billingInterval, + type: add ? "Add" : "Remove", + organizationId: this.organizationId, + }, + }); + + const result = await lastValueFrom(dialogRef.closed); + + if (result === AdjustStorageDialogV2ResultType.Submitted) { + await this.load(); + } + } else { + const dialogRef = openAdjustStorageDialog(this.dialogService, { + data: { + storageGbPrice: this.storageGbPrice, + add: add, + organizationId: this.organizationId, + interval: this.billingInterval, + }, + }); + const result = await lastValueFrom(dialogRef.closed); + if (result === AdjustStorageDialogResult.Adjusted) { + await this.load(); + } } }; }; diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.html b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.html new file mode 100644 index 00000000000..7b74379acb6 --- /dev/null +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.html @@ -0,0 +1,34 @@ +
+ + +

{{ body }}

+
+ + {{ storageFieldLabel }} + + + + {{ "total" | i18n }} + {{ this.formGroup.value.storage }} GB × {{ this.price | currency: "$" }} = + {{ this.price * this.formGroup.value.storage | currency: "$" }} / + {{ this.cadence | i18n }} + + +
+
+ + + + +
+
diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.ts b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.ts new file mode 100644 index 00000000000..23d5e46fa1b --- /dev/null +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.ts @@ -0,0 +1,104 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { StorageRequest } from "@bitwarden/common/models/request/storage.request"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DialogService, ToastService } from "@bitwarden/components"; + +export interface AdjustStorageDialogV2Params { + price: number; + cadence: "month" | "year"; + type: "Add" | "Remove"; + organizationId?: string; +} + +export enum AdjustStorageDialogV2ResultType { + Submitted = "submitted", + Closed = "closed", +} + +@Component({ + templateUrl: "./adjust-storage-dialog-v2.component.html", +}) +export class AdjustStorageDialogV2Component { + protected formGroup = new FormGroup({ + storage: new FormControl(0, [ + Validators.required, + Validators.min(0), + Validators.max(99), + ]), + }); + + protected organizationId?: string; + protected price: number; + protected cadence: "month" | "year"; + + protected title: string; + protected body: string; + protected storageFieldLabel: string; + + protected ResultType = AdjustStorageDialogV2ResultType; + + constructor( + private apiService: ApiService, + @Inject(DIALOG_DATA) protected dialogParams: AdjustStorageDialogV2Params, + private dialogRef: DialogRef, + private i18nService: I18nService, + private organizationApiService: OrganizationApiServiceAbstraction, + private toastService: ToastService, + ) { + this.price = this.dialogParams.price; + this.cadence = this.dialogParams.cadence; + this.organizationId = this.dialogParams.organizationId; + switch (this.dialogParams.type) { + case "Add": + this.title = this.i18nService.t("addStorage"); + this.body = this.i18nService.t("storageAddNote"); + this.storageFieldLabel = this.i18nService.t("gbStorageAdd"); + break; + case "Remove": + this.title = this.i18nService.t("removeStorage"); + this.body = this.i18nService.t("storageRemoveNote"); + this.storageFieldLabel = this.i18nService.t("gbStorageRemove"); + break; + } + } + + submit = async () => { + const request = new StorageRequest(); + switch (this.dialogParams.type) { + case "Add": + request.storageGbAdjustment = this.formGroup.value.storage; + break; + case "Remove": + request.storageGbAdjustment = this.formGroup.value.storage * -1; + break; + } + + if (this.organizationId) { + await this.organizationApiService.updateStorage(this.organizationId, request); + } else { + await this.apiService.postAccountStorage(request); + } + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()), + }); + + this.dialogRef.close(this.ResultType.Submitted); + }; + + static open = ( + dialogService: DialogService, + dialogConfig: DialogConfig, + ) => + dialogService.open( + AdjustStorageDialogV2Component, + dialogConfig, + ); +} diff --git a/apps/web/src/app/billing/shared/adjust-storage.component.html b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.html similarity index 100% rename from apps/web/src/app/billing/shared/adjust-storage.component.html rename to apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.html diff --git a/apps/web/src/app/billing/shared/adjust-storage.component.ts b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts similarity index 95% rename from apps/web/src/app/billing/shared/adjust-storage.component.ts rename to apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts index 5cf05ea015c..a67c63a9fad 100644 --- a/apps/web/src/app/billing/shared/adjust-storage.component.ts +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts @@ -12,7 +12,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PaymentComponent } from "./payment/payment.component"; +import { PaymentComponent } from "../payment/payment.component"; export interface AdjustStorageDialogData { storageGbPrice: number; @@ -27,9 +27,9 @@ export enum AdjustStorageDialogResult { } @Component({ - templateUrl: "adjust-storage.component.html", + templateUrl: "adjust-storage-dialog.component.html", }) -export class AdjustStorageComponent { +export class AdjustStorageDialogComponent { storageGbPrice: number; add: boolean; organizationId: string; @@ -126,5 +126,5 @@ export function openAdjustStorageDialog( dialogService: DialogService, config: DialogConfig, ) { - return dialogService.open(AdjustStorageComponent, config); + return dialogService.open(AdjustStorageDialogComponent, config); } diff --git a/apps/web/src/app/billing/shared/billing-shared.module.ts b/apps/web/src/app/billing/shared/billing-shared.module.ts index 300817bad55..c9b3f2de855 100644 --- a/apps/web/src/app/billing/shared/billing-shared.module.ts +++ b/apps/web/src/app/billing/shared/billing-shared.module.ts @@ -6,7 +6,8 @@ import { SharedModule } from "../../shared"; import { AddCreditDialogComponent } from "./add-credit-dialog.component"; import { AdjustPaymentDialogV2Component } from "./adjust-payment-dialog/adjust-payment-dialog-v2.component"; import { AdjustPaymentDialogComponent } from "./adjust-payment-dialog/adjust-payment-dialog.component"; -import { AdjustStorageComponent } from "./adjust-storage.component"; +import { AdjustStorageDialogV2Component } from "./adjust-storage-dialog/adjust-storage-dialog-v2.component"; +import { AdjustStorageDialogComponent } from "./adjust-storage-dialog/adjust-storage-dialog.component"; import { BillingHistoryComponent } from "./billing-history.component"; import { OffboardingSurveyComponent } from "./offboarding-survey.component"; import { PaymentV2Component } from "./payment/payment-v2.component"; @@ -30,7 +31,7 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac declarations: [ AddCreditDialogComponent, AdjustPaymentDialogComponent, - AdjustStorageComponent, + AdjustStorageDialogComponent, BillingHistoryComponent, PaymentMethodComponent, SecretsManagerSubscribeComponent, @@ -38,12 +39,13 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac UpdateLicenseDialogComponent, OffboardingSurveyComponent, AdjustPaymentDialogV2Component, + AdjustStorageDialogV2Component, ], exports: [ SharedModule, PaymentComponent, TaxInfoComponent, - AdjustStorageComponent, + AdjustStorageDialogComponent, BillingHistoryComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent,