diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index dd1c0edf08b..3841f6d5b4b 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -105,14 +105,14 @@ export class MembersComponent extends BaseMembersComponent memberTab = MemberDialogTab; protected dataSource = new MembersTableDataSource(); - organization: Signal; + readonly organization: Signal; status: OrganizationUserStatusType | undefined; orgResetPasswordPolicyEnabled = false; - protected canUseSecretsManager: Signal = computed( + protected readonly canUseSecretsManager: Signal = computed( () => this.organization()?.useSecretsManager ?? false, ); - protected showUserManagementControls: Signal = computed( + protected readonly showUserManagementControls: Signal = computed( () => this.organization()?.canManageUsers ?? false, ); private refreshBillingMetadata$: BehaviorSubject = new BehaviorSubject(null); diff --git a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts index 505a4b9b7e4..1d707cec75f 100644 --- a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts +++ b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts @@ -32,8 +32,8 @@ import { standalone: true, }) class MockUpgradeAccountComponent { - dialogTitleMessageOverride = input(null); - hideContinueWithoutUpgradingButton = input(false); + readonly dialogTitleMessageOverride = input(null); + readonly hideContinueWithoutUpgradingButton = input(false); planSelected = output(); closeClicked = output(); } @@ -44,8 +44,8 @@ class MockUpgradeAccountComponent { standalone: true, }) class MockUpgradePaymentComponent { - selectedPlanId = input(null); - account = input(null); + readonly selectedPlanId = input(null); + readonly account = input(null); goBack = output(); complete = output(); } diff --git a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts index 5a84856225d..0d9c8902d6c 100644 --- a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts @@ -77,11 +77,13 @@ export type UnifiedUpgradeDialogParams = { }) export class UnifiedUpgradeDialogComponent implements OnInit { // Use signals for dialog state because inputs depend on parent component - protected step = signal(UnifiedUpgradeDialogStep.PlanSelection); - protected selectedPlan = signal(null); - protected account = signal(null); - protected planSelectionStepTitleOverride = signal(null); - protected hideContinueWithoutUpgradingButton = signal(false); + protected readonly step = signal( + UnifiedUpgradeDialogStep.PlanSelection, + ); + protected readonly selectedPlan = signal(null); + protected readonly account = signal(null); + protected readonly planSelectionStepTitleOverride = signal(null); + protected readonly hideContinueWithoutUpgradingButton = signal(false); protected readonly PaymentStep = UnifiedUpgradeDialogStep.Payment; protected readonly PlanSelectionStep = UnifiedUpgradeDialogStep.PlanSelection; diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts index a9d9b959282..c9b8f22d046 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts @@ -52,11 +52,11 @@ type CardDetails = { templateUrl: "./upgrade-account.component.html", }) export class UpgradeAccountComponent implements OnInit { - dialogTitleMessageOverride = input(null); - hideContinueWithoutUpgradingButton = input(false); + readonly dialogTitleMessageOverride = input(null); + readonly hideContinueWithoutUpgradingButton = input(false); planSelected = output(); closeClicked = output(); - protected loading = signal(true); + protected readonly loading = signal(true); protected premiumCardDetails!: CardDetails; protected familiesCardDetails!: CardDetails; @@ -64,7 +64,7 @@ export class UpgradeAccountComponent implements OnInit { protected premiumPlanType = PersonalSubscriptionPricingTierIds.Premium; protected closeStatus = UpgradeAccountStatus.Closed; - protected dialogTitle = computed(() => { + protected readonly dialogTitle = computed(() => { return this.dialogTitleMessageOverride() || "individualUpgradeWelcomeMessage"; }); diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts index bd88c22f98d..0b785d44e95 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts @@ -75,8 +75,8 @@ export type UpgradePaymentParams = { templateUrl: "./upgrade-payment.component.html", }) export class UpgradePaymentComponent implements OnInit, AfterViewInit { - protected selectedPlanId = input.required(); - protected account = input.required(); + protected readonly selectedPlanId = input.required(); + protected readonly account = input.required(); protected goBack = output(); protected complete = output(); protected selectedPlan: PlanDetails | null = null; @@ -90,7 +90,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { billingAddress: EnterBillingAddressComponent.getFormGroup(), }); - protected loading = signal(true); + protected readonly loading = signal(true); private pricingTiers$!: Observable; // Cart Summary data diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts index 67a5069034f..81ad29db9dd 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts @@ -67,7 +67,7 @@ export class VaultItemsComponent { @Input() userCanArchive: boolean; @Input() enforceOrgDataOwnershipPolicy: boolean; - private restrictedPolicies = toSignal(this.restrictedItemTypesService.restricted$); + private readonly restrictedPolicies = toSignal(this.restrictedItemTypesService.restricted$); private _ciphers?: C[] = []; @Input() get ciphers(): C[] { diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-existing-organization-dialog.component.html similarity index 100% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.html rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-existing-organization-dialog.component.html diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-existing-organization-dialog.component.ts similarity index 94% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-existing-organization-dialog.component.ts index f0eda893d67..a99d86b6e96 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-existing-organization-dialog.component.ts @@ -12,7 +12,7 @@ import { ToastService, } from "@bitwarden/components"; -import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; +import { WebProviderService } from "../services/web-provider.service"; export type AddExistingOrganizationDialogParams = { provider: Provider; @@ -55,7 +55,7 @@ export class AddExistingOrganizationDialogComponent implements OnInit { addExistingOrganization = async (): Promise => { if (this.selectedOrganization) { - await this.webProviderService.addOrganizationToProviderVNext( + await this.webProviderService.addOrganizationToProvider( this.dialogParams.provider.id, this.selectedOrganization.id, ); diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.html deleted file mode 100644 index 82e412c2ef6..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.html +++ /dev/null @@ -1,32 +0,0 @@ - - {{ "addExistingOrganization" | i18n }} - - - - - - - - - {{ o.name }} - - - - - - - - - - - - diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts deleted file mode 100644 index 4f4b85e544b..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts +++ /dev/null @@ -1,101 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Inject, OnInit } from "@angular/core"; -import { Observable, switchMap } from "rxjs"; - -import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { DIALOG_DATA, DialogRef, DialogService, ToastService } from "@bitwarden/components"; - -import { WebProviderService } from "../services/web-provider.service"; - -interface AddOrganizationDialogData { - providerId: string; - organizations: Organization[]; -} - -@Component({ - templateUrl: "add-organization.component.html", - standalone: false, -}) -export class AddOrganizationComponent implements OnInit { - protected provider$: Observable; - protected loading = true; - - constructor( - private dialogRef: DialogRef, - @Inject(DIALOG_DATA) protected data: AddOrganizationDialogData, - private providerService: ProviderService, - private webProviderService: WebProviderService, - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private validationService: ValidationService, - private dialogService: DialogService, - private toastService: ToastService, - private accountService: AccountService, - ) {} - - async ngOnInit() { - await this.load(); - } - - async load() { - if (this.data.providerId == null) { - return; - } - - this.provider$ = this.accountService.activeAccount$.pipe( - getUserId, - switchMap((userId) => this.providerService.get$(this.data.providerId, userId)), - ); - - this.loading = false; - } - - add(organization: Organization, provider: Provider) { - return async () => { - const confirmed = await this.dialogService.openSimpleDialog({ - title: organization.name, - content: { - key: "addOrganizationConfirmation", - placeholders: [organization.name, provider.name], - }, - type: "warning", - }); - - if (!confirmed) { - return false; - } - - try { - await this.webProviderService.addOrganizationToProvider( - this.data.providerId, - organization.id, - ); - } catch (e) { - this.validationService.showError(e); - return; - } - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("organizationJoinedProvider"), - }); - - this.dialogRef.close(true); - }; - } - - static open(dialogService: DialogService, data: AddOrganizationDialogData) { - return dialogService.open(AddOrganizationComponent, { - data, - }); - } -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html deleted file mode 100644 index 8f740384305..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.html +++ /dev/null @@ -1,78 +0,0 @@ -@let isAdminOrServiceUser = isAdminOrServiceUser$ | async; - - - - - {{ "newClient" | i18n }} - - - - - - - {{ "loading" | i18n }} - - - -

{{ "noClientsInList" | i18n }}

- - - - {{ "name" | i18n }} - {{ "numberOfUsers" | i18n }} - {{ "billingPlan" | i18n }} - - - - - - - {{ row.organizationName }} - - - {{ row.userCount }} - / {{ row.seats }} - - - {{ row.plan }} -
- - - - -
- -
-
-
-
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts deleted file mode 100644 index 6d0b52b05d6..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { FormControl } from "@angular/forms"; -import { ActivatedRoute, Router, RouterModule } from "@angular/router"; -import { combineLatest, firstValueFrom, from, map, Observable, switchMap } from "rxjs"; -import { debounceTime, first } from "rxjs/operators"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { PlanType } from "@bitwarden/common/billing/enums"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { - AvatarModule, - DialogService, - TableDataSource, - TableModule, - ToastService, -} from "@bitwarden/components"; -import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared"; -import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; - -import { WebProviderService } from "../services/web-provider.service"; - -import { AddOrganizationComponent } from "./add-organization.component"; - -const DisallowedPlanTypes = [ - PlanType.Free, - PlanType.FamiliesAnnually2019, - PlanType.FamiliesAnnually, - PlanType.TeamsStarter2023, - PlanType.TeamsStarter, -]; - -@Component({ - templateUrl: "clients.component.html", - imports: [ - SharedOrganizationModule, - HeaderModule, - CommonModule, - JslibModule, - AvatarModule, - RouterModule, - TableModule, - ], -}) -export class ClientsComponent { - addableOrganizations: Organization[] = []; - loading = true; - showAddExisting = false; - dataSource: TableDataSource = - new TableDataSource(); - protected searchControl = new FormControl("", { nonNullable: true }); - - protected providerId$: Observable = - this.activatedRoute.parent?.params.pipe(map((params) => params.providerId as string)) ?? - new Observable(); - - protected provider$ = combineLatest([ - this.providerId$, - this.accountService.activeAccount$.pipe(getUserId), - ]).pipe(switchMap(([providerId, userId]) => this.providerService.get$(providerId, userId))); - - protected isAdminOrServiceUser$ = this.provider$.pipe( - map( - (provider) => - provider?.type === ProviderUserType.ProviderAdmin || - provider?.type === ProviderUserType.ServiceUser, - ), - ); - - constructor( - private router: Router, - private providerService: ProviderService, - private apiService: ApiService, - private organizationService: OrganizationService, - private organizationApiService: OrganizationApiServiceAbstraction, - private accountService: AccountService, - private activatedRoute: ActivatedRoute, - private dialogService: DialogService, - private i18nService: I18nService, - private toastService: ToastService, - private validationService: ValidationService, - private webProviderService: WebProviderService, - ) { - this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { - this.searchControl.setValue(queryParams.search); - }); - - this.provider$ - .pipe( - map((provider) => { - if (provider?.providerStatus === ProviderStatusType.Billable) { - return from( - this.router.navigate(["../manage-client-organizations"], { - relativeTo: this.activatedRoute, - }), - ); - } - return from(this.load()); - }), - takeUntilDestroyed(), - ) - .subscribe(); - - this.searchControl.valueChanges - .pipe(debounceTime(200), takeUntilDestroyed()) - .subscribe((searchText) => { - this.dataSource.filter = (data) => - data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1; - }); - } - - async remove(organization: ProviderOrganizationOrganizationDetailsResponse) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: organization.organizationName, - content: { key: "detachOrganizationConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return; - } - - try { - const providerId = await firstValueFrom(this.providerId$); - await this.webProviderService.detachOrganization(providerId, organization.id); - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("detachedOrganization", organization.organizationName), - }); - await this.load(); - } catch (e) { - this.validationService.showError(e); - } - } - - async load() { - const providerId = await firstValueFrom(this.providerId$); - const response = await this.apiService.getProviderClients(providerId); - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - const clients = response.data != null && response.data.length > 0 ? response.data : []; - this.dataSource.data = clients; - const candidateOrgs = ( - await firstValueFrom(this.organizationService.organizations$(userId)) - ).filter((o) => o.isOwner && o.providerId == null); - const allowedOrgsIds = await Promise.all( - candidateOrgs.map((o) => this.organizationApiService.get(o.id)), - ).then((orgs) => - orgs.filter((o) => !DisallowedPlanTypes.includes(o.planType)).map((o) => o.id), - ); - this.addableOrganizations = candidateOrgs.filter((o) => allowedOrgsIds.includes(o.id)); - - this.showAddExisting = this.addableOrganizations.length !== 0; - this.loading = false; - } - - async addExistingOrganization() { - const providerId = await firstValueFrom(this.providerId$); - const dialogRef = AddOrganizationComponent.open(this.dialogService, { - providerId: providerId, - organizations: this.addableOrganizations, - }); - - if (await firstValueFrom(dialogRef.closed)) { - await this.load(); - } - } -} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-client-dialog.component.html similarity index 100% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-client-dialog.component.html diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-client-dialog.component.ts similarity index 98% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-client-dialog.component.ts index 2d6a0a75509..73e642dfa06 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-client-dialog.component.ts @@ -17,7 +17,7 @@ import { ToastService, } from "@bitwarden/components"; -import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; +import { WebProviderService } from "../services/web-provider.service"; type CreateClientDialogParams = { providerId: string; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.html deleted file mode 100644 index 1aa720b0485..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.html +++ /dev/null @@ -1,3 +0,0 @@ - -

{{ "newClientOrganizationDesc" | i18n }}

- diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts deleted file mode 100644 index 9f3582f84bb..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit, ViewChild } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; - -import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing"; - -@Component({ - selector: "app-create-organization", - templateUrl: "create-organization.component.html", - standalone: false, -}) -export class CreateOrganizationComponent implements OnInit { - @ViewChild(OrganizationPlansComponent, { static: true }) - orgPlansComponent: OrganizationPlansComponent; - - providerId: string; - - constructor(private route: ActivatedRoute) {} - - ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.params.subscribe(async (params) => { - this.providerId = params.providerId; - }); - } -} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-name-dialog.component.html similarity index 100% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.html rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-name-dialog.component.html diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-name-dialog.component.ts similarity index 82% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-name-dialog.component.ts index 06d1f80c101..045c9d8e8df 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-name-dialog.component.ts @@ -3,8 +3,8 @@ import { Component, Inject } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; -import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request"; +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; +import { UpdateProviderOrganizationRequest } from "@bitwarden/common/admin-console/models/request/update-provider-organization.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DIALOG_DATA, @@ -51,7 +51,7 @@ export class ManageClientNameDialogComponent { constructor( @Inject(DIALOG_DATA) protected dialogParams: ManageClientNameDialogParams, - private billingApiService: BillingApiServiceAbstraction, + private providerApiService: ProviderApiServiceAbstraction, private dialogRef: DialogRef, private formBuilder: FormBuilder, private i18nService: I18nService, @@ -65,11 +65,11 @@ export class ManageClientNameDialogComponent { return; } - const request = new UpdateClientOrganizationRequest(); + const request = new UpdateProviderOrganizationRequest(); request.assignedSeats = this.dialogParams.organization.seats; request.name = this.formGroup.value.name; - await this.billingApiService.updateProviderClientOrganization( + await this.providerApiService.updateProviderOrganization( this.dialogParams.providerId, this.dialogParams.organization.id, request, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-subscription-dialog.component.html similarity index 100% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.html rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-subscription-dialog.component.html diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-subscription-dialog.component.ts similarity index 93% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-subscription-dialog.component.ts index 71e549b563b..4c80402d3f7 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-subscription-dialog.component.ts @@ -3,11 +3,12 @@ import { Component, Inject, OnInit } from "@angular/core"; import { FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from "@angular/forms"; +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { UpdateProviderOrganizationRequest } from "@bitwarden/common/admin-console/models/request/update-provider-organization.request"; import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; -import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request"; import { ProviderPlanResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components"; @@ -56,6 +57,7 @@ export class ManageClientSubscriptionDialogComponent implements OnInit { constructor( private billingApiService: BillingApiServiceAbstraction, + private providerApiService: ProviderApiServiceAbstraction, @Inject(DIALOG_DATA) protected dialogParams: ManageClientSubscriptionDialogParams, private dialogRef: DialogRef, private i18nService: I18nService, @@ -99,11 +101,11 @@ export class ManageClientSubscriptionDialogComponent implements OnInit { } try { - const request = new UpdateClientOrganizationRequest(); + const request = new UpdateProviderOrganizationRequest(); request.assignedSeats = this.formGroup.value.assignedSeats; request.name = this.dialogParams.organization.organizationName; - await this.billingApiService.updateProviderClientOrganization( + await this.providerApiService.updateProviderOrganization( this.dialogParams.provider.id, this.dialogParams.organization.id, request, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-clients.component.html similarity index 100% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-clients.component.html diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-clients.component.ts similarity index 86% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-clients.component.ts index 9933c316869..a3601d2c812 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-clients.component.ts @@ -1,25 +1,21 @@ -import { Component } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormControl } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; +import { ActivatedRoute } from "@angular/router"; import { firstValueFrom, - from, lastValueFrom, map, combineLatest, switchMap, Observable, + Subject, + takeUntil, } from "rxjs"; import { debounceTime, first } from "rxjs/operators"; +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { - ProviderStatusType, - ProviderType, - ProviderUserType, -} from "@bitwarden/common/admin-console/enums"; -import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { ProviderType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; @@ -40,7 +36,7 @@ import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; -import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; +import { WebProviderService } from "../services/web-provider.service"; import { AddExistingOrganizationDialogComponent, @@ -72,7 +68,7 @@ import { ReplacePipe } from "./replace.pipe"; ReplacePipe, ], }) -export class ManageClientsComponent { +export class ManageClientsComponent implements OnInit, OnDestroy { loading = true; dataSource: TableDataSource = new TableDataSource(); @@ -117,10 +113,11 @@ export class ManageClientsComponent { ), ); + private destroy$ = new Subject(); + constructor( private billingApiService: BillingApiServiceAbstraction, private providerService: ProviderService, - private router: Router, private activatedRoute: ActivatedRoute, private dialogService: DialogService, private i18nService: I18nService, @@ -130,35 +127,31 @@ export class ManageClientsComponent { private billingNotificationService: BillingNotificationService, private configService: ConfigService, private accountService: AccountService, - ) { - this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { - this.searchControl.setValue(queryParams.search); - }); + private providerApiService: ProviderApiServiceAbstraction, + ) {} - this.provider$ - .pipe( - map((provider: Provider | undefined) => { - if (provider?.providerStatus !== ProviderStatusType.Billable) { - return from( - this.router.navigate(["../clients"], { - relativeTo: this.activatedRoute, - }), - ); - } - return from(this.load()); - }), - takeUntilDestroyed(), - ) - .subscribe(); + async ngOnInit() { + this.activatedRoute.queryParams + .pipe(first(), takeUntil(this.destroy$)) + .subscribe((queryParams) => { + this.searchControl.setValue(queryParams.search); + }); + + await this.load(); this.searchControl.valueChanges - .pipe(debounceTime(200), takeUntilDestroyed()) + .pipe(debounceTime(200), takeUntil(this.destroy$)) .subscribe((searchText) => { this.dataSource.filter = (data) => data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1; }); } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + async load() { try { const providerId = await firstValueFrom(this.providerId$); @@ -170,7 +163,7 @@ export class ManageClientsComponent { this.newClientButtonLabel = this.i18nService.t("newBusinessUnit"); } this.dataSource.data = ( - await this.billingApiService.getProviderClientOrganizations(providerId) + await this.providerApiService.getProviderOrganizations(providerId) ).data; this.plans = (await this.billingApiService.getPlans()).data; this.loading = false; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/no-clients.component.ts similarity index 100% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/no-clients.component.ts rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/no-clients.component.ts diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/replace.pipe.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/replace.pipe.ts similarity index 100% rename from bitwarden_license/bit-web/src/app/billing/providers/clients/replace.pipe.ts rename to bitwarden_license/bit-web/src/app/admin-console/providers/clients/replace.pipe.ts diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html index 724e5891dc8..ab4aaa6bd69 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html @@ -9,7 +9,7 @@ ; - protected isBillable: Observable; protected canAccessBilling$: Observable; protected clientsTranslationKey$: Observable; @@ -83,15 +82,7 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy { ), ); - this.isBillable = this.provider$.pipe( - map((provider) => provider?.providerStatus === ProviderStatusType.Billable), - ); - - this.canAccessBilling$ = combineLatest([this.isBillable, this.provider$]).pipe( - map( - ([hasConsolidatedBilling, provider]) => hasConsolidatedBilling && provider.isProviderAdmin, - ), - ); + this.canAccessBilling$ = this.provider$.pipe(map((provider) => provider.isProviderAdmin)); this.clientsTranslationKey$ = this.provider$.pipe( map((provider) => diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts index 7a554275f08..cf0dbe05d07 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts @@ -7,17 +7,12 @@ import { AnonLayoutWrapperComponent } from "@bitwarden/components"; import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component"; import { UserLayoutComponent } from "@bitwarden/web-vault/app/layouts/user-layout.component"; -import { - ManageClientsComponent, - ProviderSubscriptionComponent, - hasConsolidatedBilling, - ProviderBillingHistoryComponent, -} from "../../billing/providers"; +import { ProviderBillingHistoryComponent } from "../../billing/providers/billing-history/provider-billing-history.component"; import { ProviderPaymentDetailsComponent } from "../../billing/providers/payment-details/provider-payment-details.component"; import { SetupBusinessUnitComponent } from "../../billing/providers/setup/setup-business-unit.component"; +import { ProviderSubscriptionComponent } from "../../billing/providers/subscription/provider-subscription.component"; -import { ClientsComponent } from "./clients/clients.component"; -import { CreateOrganizationComponent } from "./clients/create-organization.component"; +import { ManageClientsComponent } from "./clients/manage-clients.component"; import { providerPermissionsGuard } from "./guards/provider-permissions.guard"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { EventsComponent } from "./manage/events.component"; @@ -88,14 +83,7 @@ const routes: Routes = [ canActivate: [providerPermissionsGuard()], children: [ { path: "", pathMatch: "full", redirectTo: "clients" }, - { path: "clients/create", component: CreateOrganizationComponent }, - { path: "clients", component: ClientsComponent, data: { titleId: "clients" } }, - { - path: "manage-client-organizations", - canActivate: [hasConsolidatedBilling], - component: ManageClientsComponent, - data: { titleId: "clients" }, - }, + { path: "clients", component: ManageClientsComponent, data: { titleId: "clients" } }, { path: "manage", children: [ @@ -128,7 +116,7 @@ const routes: Routes = [ }, { path: "billing", - canActivate: [hasConsolidatedBilling], + canActivate: [providerPermissionsGuard()], children: [ { path: "", @@ -138,7 +126,6 @@ const routes: Routes = [ { path: "subscription", component: ProviderSubscriptionComponent, - canActivate: [providerPermissionsGuard()], data: { titleId: "subscription", }, @@ -146,7 +133,6 @@ const routes: Routes = [ { path: "payment-details", component: ProviderPaymentDetailsComponent, - canActivate: [providerPermissionsGuard()], data: { titleId: "paymentDetails", }, @@ -154,7 +140,6 @@ const routes: Routes = [ { path: "history", component: ProviderBillingHistoryComponent, - canActivate: [providerPermissionsGuard()], data: { titleId: "billingHistory", }, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 24e8a757bdf..92b16699b7e 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -13,22 +13,18 @@ import { } from "@bitwarden/web-vault/app/billing/payment/components"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; -import { - CreateClientDialogComponent, - InvoicesComponent, - ManageClientNameDialogComponent, - ManageClientSubscriptionDialogComponent, - NoInvoicesComponent, - ProviderBillingHistoryComponent, - ProviderSubscriptionComponent, - ProviderSubscriptionStatusComponent, -} from "../../billing/providers"; -import { AddExistingOrganizationDialogComponent } from "../../billing/providers/clients/add-existing-organization-dialog.component"; +import { InvoicesComponent } from "../../billing/providers/billing-history/invoices.component"; +import { NoInvoicesComponent } from "../../billing/providers/billing-history/no-invoices.component"; +import { ProviderBillingHistoryComponent } from "../../billing/providers/billing-history/provider-billing-history.component"; import { SetupBusinessUnitComponent } from "../../billing/providers/setup/setup-business-unit.component"; +import { ProviderSubscriptionStatusComponent } from "../../billing/providers/subscription/provider-subscription-status.component"; +import { ProviderSubscriptionComponent } from "../../billing/providers/subscription/provider-subscription.component"; import { ProviderWarningsModule } from "../../billing/providers/warnings/provider-warnings.module"; -import { AddOrganizationComponent } from "./clients/add-organization.component"; -import { CreateOrganizationComponent } from "./clients/create-organization.component"; +import { AddExistingOrganizationDialogComponent } from "./clients/add-existing-organization-dialog.component"; +import { CreateClientDialogComponent } from "./clients/create-client-dialog.component"; +import { ManageClientNameDialogComponent } from "./clients/manage-client-name-dialog.component"; +import { ManageClientSubscriptionDialogComponent } from "./clients/manage-client-subscription-dialog.component"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; import { AddEditMemberDialogComponent } from "./manage/dialogs/add-edit-member-dialog.component"; import { BulkConfirmDialogComponent } from "./manage/dialogs/bulk-confirm-dialog.component"; @@ -65,10 +61,8 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr declarations: [ AcceptProviderComponent, AccountComponent, - AddOrganizationComponent, BulkConfirmDialogComponent, BulkRemoveDialogComponent, - CreateOrganizationComponent, EventsComponent, MembersComponent, SetupComponent, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.spec.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.spec.ts index 2ddfbabf885..b2da18dd047 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.spec.ts @@ -3,8 +3,6 @@ import { MockProxy, mock } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { PlanType } from "@bitwarden/common/billing/enums"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; @@ -26,10 +24,8 @@ describe("WebProviderService", () => { let apiService: MockProxy; let i18nService: MockProxy; let encryptService: MockProxy; - let billingApiService: MockProxy; let stateProvider: MockProxy; let providerApiService: MockProxy; - let accountService: MockProxy; beforeEach(() => { keyService = mock(); @@ -37,10 +33,8 @@ describe("WebProviderService", () => { apiService = mock(); i18nService = mock(); encryptService = mock(); - billingApiService = mock(); stateProvider = mock(); providerApiService = mock(); - accountService = mock(); sut = new WebProviderService( keyService, @@ -48,10 +42,8 @@ describe("WebProviderService", () => { apiService, i18nService, encryptService, - billingApiService, stateProvider, providerApiService, - accountService, ); }); @@ -99,7 +91,7 @@ describe("WebProviderService", () => { expect(keyService.getProviderKey).toHaveBeenCalledWith(providerId); expect(encryptService.wrapSymmetricKey).toHaveBeenCalledWith(mockOrgKey, mockProviderKey); - expect(billingApiService.createProviderClientOrganization).toHaveBeenCalledWith( + expect(providerApiService.createProviderOrganization).toHaveBeenCalledWith( providerId, expect.objectContaining({ name, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts index 16c2ab5cd3e..78931f9c445 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts @@ -6,13 +6,9 @@ import { switchMap } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; +import { CreateProviderOrganizationRequest } from "@bitwarden/common/admin-console/models/request/create-provider-organization.request"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; -import { ProviderAddOrganizationRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-add-organization.request"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { PlanType } from "@bitwarden/common/billing/enums"; -import { CreateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/create-client-organization.request"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { StateProvider } from "@bitwarden/common/platform/state"; @@ -29,34 +25,11 @@ export class WebProviderService { private apiService: ApiService, private i18nService: I18nService, private encryptService: EncryptService, - private billingApiService: BillingApiServiceAbstraction, private stateProvider: StateProvider, private providerApiService: ProviderApiServiceAbstraction, - private accountService: AccountService, ) {} - async addOrganizationToProvider(providerId: string, organizationId: string) { - const orgKey = await firstValueFrom( - this.accountService.activeAccount$.pipe( - getUserId, - switchMap((userId) => this.keyService.orgKeys$(userId)), - map((orgKeys) => orgKeys[organizationId as OrganizationId] ?? null), - ), - ); - const providerKey = await this.keyService.getProviderKey(providerId); - - const encryptedOrgKey = await this.encryptService.wrapSymmetricKey(orgKey, providerKey); - - const request = new ProviderAddOrganizationRequest(); - request.organizationId = organizationId; - request.key = encryptedOrgKey.encryptedString; - - const response = await this.apiService.postProviderAddOrganization(providerId, request); - await this.syncService.fullSync(true); - return response; - } - - async addOrganizationToProviderVNext(providerId: string, organizationId: string): Promise { + async addOrganizationToProvider(providerId: string, organizationId: string): Promise { const orgKey = await firstValueFrom( this.stateProvider.activeUserId$.pipe( switchMap((userId) => this.keyService.orgKeys$(userId)), @@ -96,7 +69,7 @@ export class WebProviderService { providerKey, ); - const request = new CreateClientOrganizationRequest(); + const request = new CreateProviderOrganizationRequest(); request.name = name; request.ownerEmail = ownerEmail; request.planType = planType; @@ -106,7 +79,7 @@ export class WebProviderService { request.keyPair = new OrganizationKeysRequest(publicKey, encryptedPrivateKey.encryptedString); request.collectionName = encryptedCollectionName.encryptedString; - await this.billingApiService.createProviderClientOrganization(providerId, request); + await this.providerApiService.createProviderOrganization(providerId, request); await this.apiService.refreshIdentityToken(); diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts deleted file mode 100644 index 898d51e0baf..00000000000 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./create-client-dialog.component"; -export * from "./manage-clients.component"; -export * from "./manage-client-name-dialog.component"; -export * from "./manage-client-subscription-dialog.component"; -export * from "./no-clients.component"; -export * from "./replace.pipe"; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/guards/has-consolidated-billing.guard.ts b/bitwarden_license/bit-web/src/app/billing/providers/guards/has-consolidated-billing.guard.ts deleted file mode 100644 index de13cca3ad4..00000000000 --- a/bitwarden_license/bit-web/src/app/billing/providers/guards/has-consolidated-billing.guard.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { inject } from "@angular/core"; -import { ActivatedRouteSnapshot, CanActivateFn, createUrlTreeFromSnapshot } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { ProviderStatusType } from "@bitwarden/common/admin-console/enums"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; - -export const hasConsolidatedBilling: CanActivateFn = async (route: ActivatedRouteSnapshot) => { - const providerService = inject(ProviderService); - const accountService = inject(AccountService); - - const userId = await firstValueFrom(getUserId(accountService.activeAccount$)); - const provider = await firstValueFrom(providerService.get$(route.params.providerId, userId)); - - if (!provider || provider.providerStatus !== ProviderStatusType.Billable) { - return createUrlTreeFromSnapshot(route, ["/providers", route.params.providerId]); - } - - return true; -}; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/index.ts b/bitwarden_license/bit-web/src/app/billing/providers/index.ts deleted file mode 100644 index 3cd83e68990..00000000000 --- a/bitwarden_license/bit-web/src/app/billing/providers/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from "./billing-history/invoices.component"; -export * from "./billing-history/no-invoices.component"; -export * from "./billing-history/provider-billing-history.component"; -export * from "./clients"; -export * from "./guards/has-consolidated-billing.guard"; -export * from "./subscription/provider-subscription.component"; -export * from "./subscription/provider-subscription-status.component"; diff --git a/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts index f998fdc8ab7..0fba60a5944 100644 --- a/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts @@ -1,8 +1,12 @@ import { AddableOrganizationResponse } from "@bitwarden/common/admin-console/models/response/addable-organization.response"; +import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { CreateProviderOrganizationRequest } from "../../models/request/create-provider-organization.request"; import { ProviderSetupRequest } from "../../models/request/provider/provider-setup.request"; import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request"; import { ProviderVerifyRecoverDeleteRequest } from "../../models/request/provider/provider-verify-recover-delete.request"; +import { UpdateProviderOrganizationRequest } from "../../models/request/update-provider-organization.request"; import { ProviderResponse } from "../../models/response/provider/provider.response"; export abstract class ProviderApiServiceAbstraction { @@ -14,6 +18,9 @@ export abstract class ProviderApiServiceAbstraction { request: ProviderVerifyRecoverDeleteRequest, ): Promise; abstract deleteProvider(id: string): Promise; + abstract getProviderOrganizations( + providerId: string, + ): Promise>; abstract getProviderAddableOrganizations( providerId: string, ): Promise; @@ -24,4 +31,15 @@ export abstract class ProviderApiServiceAbstraction { organizationId: string; }, ): Promise; + + abstract updateProviderOrganization( + providerId: string, + organizationId: string, + request: UpdateProviderOrganizationRequest, + ): Promise; + + abstract createProviderOrganization( + providerId: string, + request: CreateProviderOrganizationRequest, + ): Promise; } diff --git a/libs/common/src/billing/models/request/create-client-organization.request.ts b/libs/common/src/admin-console/models/request/create-provider-organization.request.ts similarity index 66% rename from libs/common/src/billing/models/request/create-client-organization.request.ts rename to libs/common/src/admin-console/models/request/create-provider-organization.request.ts index c7078164993..ccb437b922d 100644 --- a/libs/common/src/billing/models/request/create-client-organization.request.ts +++ b/libs/common/src/admin-console/models/request/create-provider-organization.request.ts @@ -1,9 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { OrganizationKeysRequest } from "../../../admin-console/models/request/organization-keys.request"; import { PlanType } from "../../../billing/enums"; -export class CreateClientOrganizationRequest { +import { OrganizationKeysRequest } from "./organization-keys.request"; + +export class CreateProviderOrganizationRequest { name: string; ownerEmail: string; planType: PlanType; diff --git a/libs/common/src/billing/models/request/update-client-organization.request.ts b/libs/common/src/admin-console/models/request/update-provider-organization.request.ts similarity index 73% rename from libs/common/src/billing/models/request/update-client-organization.request.ts rename to libs/common/src/admin-console/models/request/update-provider-organization.request.ts index e21344b3d6b..f65ac42464f 100644 --- a/libs/common/src/billing/models/request/update-client-organization.request.ts +++ b/libs/common/src/admin-console/models/request/update-provider-organization.request.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -export class UpdateClientOrganizationRequest { +export class UpdateProviderOrganizationRequest { assignedSeats: number; name: string; } diff --git a/libs/common/src/admin-console/services/provider/provider-api.service.ts b/libs/common/src/admin-console/services/provider/provider-api.service.ts index dc82ec011f4..f0dbb424558 100644 --- a/libs/common/src/admin-console/services/provider/provider-api.service.ts +++ b/libs/common/src/admin-console/services/provider/provider-api.service.ts @@ -1,10 +1,14 @@ import { AddableOrganizationResponse } from "@bitwarden/common/admin-console/models/response/addable-organization.response"; +import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { ApiService } from "../../../abstractions/api.service"; import { ProviderApiServiceAbstraction } from "../../abstractions/provider/provider-api.service.abstraction"; +import { CreateProviderOrganizationRequest } from "../../models/request/create-provider-organization.request"; import { ProviderSetupRequest } from "../../models/request/provider/provider-setup.request"; import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request"; import { ProviderVerifyRecoverDeleteRequest } from "../../models/request/provider/provider-verify-recover-delete.request"; +import { UpdateProviderOrganizationRequest } from "../../models/request/update-provider-organization.request"; import { ProviderResponse } from "../../models/response/provider/provider.response"; export class ProviderApiService implements ProviderApiServiceAbstraction { @@ -47,6 +51,19 @@ export class ProviderApiService implements ProviderApiServiceAbstraction { await this.apiService.send("DELETE", "/providers/" + id, null, true, false); } + async getProviderOrganizations( + providerId: string, + ): Promise> { + const response = await this.apiService.send( + "GET", + "/providers/" + providerId + "/organizations", + null, + true, + true, + ); + return new ListResponse(response, ProviderOrganizationOrganizationDetailsResponse); + } + async getProviderAddableOrganizations( providerId: string, ): Promise { @@ -76,4 +93,31 @@ export class ProviderApiService implements ProviderApiServiceAbstraction { false, ); } + + async updateProviderOrganization( + providerId: string, + organizationId: string, + request: UpdateProviderOrganizationRequest, + ): Promise { + return await this.apiService.send( + "PUT", + "/providers/" + providerId + "/clients/" + organizationId, + request, + true, + false, + ); + } + + createProviderOrganization( + providerId: string, + request: CreateProviderOrganizationRequest, + ): Promise { + return this.apiService.send( + "POST", + "/providers/" + providerId + "/clients", + request, + true, + false, + ); + } } diff --git a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts index 1dbb8053e97..d581fdaa95c 100644 --- a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts @@ -1,12 +1,9 @@ import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; -import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; import { PlanResponse } from "../../billing/models/response/plan.response"; import { ListResponse } from "../../models/response/list.response"; import { OrganizationId } from "../../types/guid"; -import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; -import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; import { InvoicesResponse } from "../models/response/invoices.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; @@ -18,11 +15,6 @@ export abstract class BillingApiServiceAbstraction { abstract cancelPremiumUserSubscription(request: SubscriptionCancellationRequest): Promise; - abstract createProviderClientOrganization( - providerId: string, - request: CreateClientOrganizationRequest, - ): Promise; - abstract getOrganizationBillingMetadata( organizationId: OrganizationId, ): Promise; @@ -35,20 +27,10 @@ export abstract class BillingApiServiceAbstraction { abstract getProviderClientInvoiceReport(providerId: string, invoiceId: string): Promise; - abstract getProviderClientOrganizations( - providerId: string, - ): Promise>; - abstract getProviderInvoices(providerId: string): Promise; abstract getProviderSubscription(providerId: string): Promise; - abstract updateProviderClientOrganization( - providerId: string, - organizationId: string, - request: UpdateClientOrganizationRequest, - ): Promise; - abstract restartSubscription( organizationId: string, request: OrganizationCreateRequest, diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index c953d920055..165ebf5c3b4 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -3,13 +3,10 @@ import { ApiService } from "../../abstractions/api.service"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; -import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response"; import { ListResponse } from "../../models/response/list.response"; import { OrganizationId } from "../../types/guid"; import { BillingApiServiceAbstraction } from "../abstractions"; -import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; import { SubscriptionCancellationRequest } from "../models/request/subscription-cancellation.request"; -import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; import { InvoicesResponse } from "../models/response/invoices.response"; import { OrganizationBillingMetadataResponse } from "../models/response/organization-billing-metadata.response"; import { PlanResponse } from "../models/response/plan.response"; @@ -35,19 +32,6 @@ export class BillingApiService implements BillingApiServiceAbstraction { return this.apiService.send("POST", "/accounts/cancel", request, true, false); } - createProviderClientOrganization( - providerId: string, - request: CreateClientOrganizationRequest, - ): Promise { - return this.apiService.send( - "POST", - "/providers/" + providerId + "/clients", - request, - true, - false, - ); - } - async getOrganizationBillingMetadata( organizationId: OrganizationId, ): Promise { @@ -92,19 +76,6 @@ export class BillingApiService implements BillingApiServiceAbstraction { return response as string; } - async getProviderClientOrganizations( - providerId: string, - ): Promise> { - const response = await this.apiService.send( - "GET", - "/providers/" + providerId + "/organizations", - null, - true, - true, - ); - return new ListResponse(response, ProviderOrganizationOrganizationDetailsResponse); - } - async getProviderInvoices(providerId: string): Promise { const response = await this.apiService.send( "GET", @@ -127,20 +98,6 @@ export class BillingApiService implements BillingApiServiceAbstraction { return new ProviderSubscriptionResponse(response); } - async updateProviderClientOrganization( - providerId: string, - organizationId: string, - request: UpdateClientOrganizationRequest, - ): Promise { - return await this.apiService.send( - "PUT", - "/providers/" + providerId + "/clients/" + organizationId, - request, - true, - false, - ); - } - async restartSubscription( organizationId: string, request: OrganizationCreateRequest, diff --git a/libs/pricing/src/components/pricing-card/pricing-card.component.ts b/libs/pricing/src/components/pricing-card/pricing-card.component.ts index 022653aa9e4..a6d6cce2a64 100644 --- a/libs/pricing/src/components/pricing-card/pricing-card.component.ts +++ b/libs/pricing/src/components/pricing-card/pricing-card.component.ts @@ -21,16 +21,20 @@ import { imports: [BadgeModule, ButtonModule, IconModule, TypographyModule, CurrencyPipe], }) export class PricingCardComponent { - tagline = input.required(); - price = input<{ amount: number; cadence: "monthly" | "annually"; showPerUser?: boolean }>(); - button = input<{ + readonly tagline = input.required(); + readonly price = input<{ + amount: number; + cadence: "monthly" | "annually"; + showPerUser?: boolean; + }>(); + readonly button = input<{ type: ButtonType; text: string; disabled?: boolean; icon?: { type: string; position: "before" | "after" }; }>(); - features = input(); - activeBadge = input<{ text: string; variant?: BadgeVariant }>(); + readonly features = input(); + readonly activeBadge = input<{ text: string; variant?: BadgeVariant }>(); @Output() buttonClick = new EventEmitter(); diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts index c775d66baac..31ba5c82d9d 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.ts @@ -36,18 +36,18 @@ import { OrgIconDirective } from "../../components/org-icon.directive"; ], }) export class ItemDetailsV2Component { - hideOwner = input(false); - cipher = input.required(); - organization = input(); - folder = input(); - collections = input(); - showAllDetails = signal(false); + readonly hideOwner = input(false); + readonly cipher = input.required(); + readonly organization = input(); + readonly folder = input(); + readonly collections = input(); + readonly showAllDetails = signal(false); - showOwnership = computed(() => { + readonly showOwnership = computed(() => { return this.cipher().organizationId && this.organization() && !this.hideOwner(); }); - hasSmallScreen = toSignal( + readonly hasSmallScreen = toSignal( fromEvent(window, "resize").pipe( map(() => window.innerWidth), startWith(window.innerWidth), @@ -56,7 +56,7 @@ export class ItemDetailsV2Component { ); // Array to hold all details of item. Organization, Collections, and Folder - allItems = computed(() => { + readonly allItems = computed(() => { let items: any[] = []; if (this.showOwnership() && this.organization()) { items.push(this.organization()); @@ -70,7 +70,7 @@ export class ItemDetailsV2Component { return items; }); - showItems = computed(() => { + readonly showItems = computed(() => { if ( this.hasSmallScreen() && this.allItems().length > 2 &&