mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 15:23:33 +00:00
[PM-26356] Remove client-side pre-consolidated billing code (#16707)
* Remove legacy provider files * Removing index files to make file re-org easier * Move manage-clients.component and associated API invocation to AC * Move add-existing-organization-dialog.component to AC * Move manage-client-name-dialog.component and associated API call to AC * Move misc clients files to AC * Move create-client-dialog.component and associated API call to AC * Move manage-client-subscription-dialog.component to AC * Update provider-layout.component * Cleanup * Fix linting
This commit is contained in:
@@ -105,14 +105,14 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
|||||||
memberTab = MemberDialogTab;
|
memberTab = MemberDialogTab;
|
||||||
protected dataSource = new MembersTableDataSource();
|
protected dataSource = new MembersTableDataSource();
|
||||||
|
|
||||||
organization: Signal<Organization | undefined>;
|
readonly organization: Signal<Organization | undefined>;
|
||||||
status: OrganizationUserStatusType | undefined;
|
status: OrganizationUserStatusType | undefined;
|
||||||
orgResetPasswordPolicyEnabled = false;
|
orgResetPasswordPolicyEnabled = false;
|
||||||
|
|
||||||
protected canUseSecretsManager: Signal<boolean> = computed(
|
protected readonly canUseSecretsManager: Signal<boolean> = computed(
|
||||||
() => this.organization()?.useSecretsManager ?? false,
|
() => this.organization()?.useSecretsManager ?? false,
|
||||||
);
|
);
|
||||||
protected showUserManagementControls: Signal<boolean> = computed(
|
protected readonly showUserManagementControls: Signal<boolean> = computed(
|
||||||
() => this.organization()?.canManageUsers ?? false,
|
() => this.organization()?.canManageUsers ?? false,
|
||||||
);
|
);
|
||||||
private refreshBillingMetadata$: BehaviorSubject<null> = new BehaviorSubject(null);
|
private refreshBillingMetadata$: BehaviorSubject<null> = new BehaviorSubject(null);
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ import {
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
class MockUpgradeAccountComponent {
|
class MockUpgradeAccountComponent {
|
||||||
dialogTitleMessageOverride = input<string | null>(null);
|
readonly dialogTitleMessageOverride = input<string | null>(null);
|
||||||
hideContinueWithoutUpgradingButton = input<boolean>(false);
|
readonly hideContinueWithoutUpgradingButton = input<boolean>(false);
|
||||||
planSelected = output<PersonalSubscriptionPricingTierId>();
|
planSelected = output<PersonalSubscriptionPricingTierId>();
|
||||||
closeClicked = output<UpgradeAccountStatus>();
|
closeClicked = output<UpgradeAccountStatus>();
|
||||||
}
|
}
|
||||||
@@ -44,8 +44,8 @@ class MockUpgradeAccountComponent {
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
class MockUpgradePaymentComponent {
|
class MockUpgradePaymentComponent {
|
||||||
selectedPlanId = input<PersonalSubscriptionPricingTierId | null>(null);
|
readonly selectedPlanId = input<PersonalSubscriptionPricingTierId | null>(null);
|
||||||
account = input<Account | null>(null);
|
readonly account = input<Account | null>(null);
|
||||||
goBack = output<void>();
|
goBack = output<void>();
|
||||||
complete = output<UpgradePaymentResult>();
|
complete = output<UpgradePaymentResult>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,11 +77,13 @@ export type UnifiedUpgradeDialogParams = {
|
|||||||
})
|
})
|
||||||
export class UnifiedUpgradeDialogComponent implements OnInit {
|
export class UnifiedUpgradeDialogComponent implements OnInit {
|
||||||
// Use signals for dialog state because inputs depend on parent component
|
// Use signals for dialog state because inputs depend on parent component
|
||||||
protected step = signal<UnifiedUpgradeDialogStep>(UnifiedUpgradeDialogStep.PlanSelection);
|
protected readonly step = signal<UnifiedUpgradeDialogStep>(
|
||||||
protected selectedPlan = signal<PersonalSubscriptionPricingTierId | null>(null);
|
UnifiedUpgradeDialogStep.PlanSelection,
|
||||||
protected account = signal<Account | null>(null);
|
);
|
||||||
protected planSelectionStepTitleOverride = signal<string | null>(null);
|
protected readonly selectedPlan = signal<PersonalSubscriptionPricingTierId | null>(null);
|
||||||
protected hideContinueWithoutUpgradingButton = signal<boolean>(false);
|
protected readonly account = signal<Account | null>(null);
|
||||||
|
protected readonly planSelectionStepTitleOverride = signal<string | null>(null);
|
||||||
|
protected readonly hideContinueWithoutUpgradingButton = signal<boolean>(false);
|
||||||
|
|
||||||
protected readonly PaymentStep = UnifiedUpgradeDialogStep.Payment;
|
protected readonly PaymentStep = UnifiedUpgradeDialogStep.Payment;
|
||||||
protected readonly PlanSelectionStep = UnifiedUpgradeDialogStep.PlanSelection;
|
protected readonly PlanSelectionStep = UnifiedUpgradeDialogStep.PlanSelection;
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ type CardDetails = {
|
|||||||
templateUrl: "./upgrade-account.component.html",
|
templateUrl: "./upgrade-account.component.html",
|
||||||
})
|
})
|
||||||
export class UpgradeAccountComponent implements OnInit {
|
export class UpgradeAccountComponent implements OnInit {
|
||||||
dialogTitleMessageOverride = input<string | null>(null);
|
readonly dialogTitleMessageOverride = input<string | null>(null);
|
||||||
hideContinueWithoutUpgradingButton = input<boolean>(false);
|
readonly hideContinueWithoutUpgradingButton = input<boolean>(false);
|
||||||
planSelected = output<PersonalSubscriptionPricingTierId>();
|
planSelected = output<PersonalSubscriptionPricingTierId>();
|
||||||
closeClicked = output<UpgradeAccountStatus>();
|
closeClicked = output<UpgradeAccountStatus>();
|
||||||
protected loading = signal(true);
|
protected readonly loading = signal(true);
|
||||||
protected premiumCardDetails!: CardDetails;
|
protected premiumCardDetails!: CardDetails;
|
||||||
protected familiesCardDetails!: CardDetails;
|
protected familiesCardDetails!: CardDetails;
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ export class UpgradeAccountComponent implements OnInit {
|
|||||||
protected premiumPlanType = PersonalSubscriptionPricingTierIds.Premium;
|
protected premiumPlanType = PersonalSubscriptionPricingTierIds.Premium;
|
||||||
protected closeStatus = UpgradeAccountStatus.Closed;
|
protected closeStatus = UpgradeAccountStatus.Closed;
|
||||||
|
|
||||||
protected dialogTitle = computed(() => {
|
protected readonly dialogTitle = computed(() => {
|
||||||
return this.dialogTitleMessageOverride() || "individualUpgradeWelcomeMessage";
|
return this.dialogTitleMessageOverride() || "individualUpgradeWelcomeMessage";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ export type UpgradePaymentParams = {
|
|||||||
templateUrl: "./upgrade-payment.component.html",
|
templateUrl: "./upgrade-payment.component.html",
|
||||||
})
|
})
|
||||||
export class UpgradePaymentComponent implements OnInit, AfterViewInit {
|
export class UpgradePaymentComponent implements OnInit, AfterViewInit {
|
||||||
protected selectedPlanId = input.required<PersonalSubscriptionPricingTierId>();
|
protected readonly selectedPlanId = input.required<PersonalSubscriptionPricingTierId>();
|
||||||
protected account = input.required<Account>();
|
protected readonly account = input.required<Account>();
|
||||||
protected goBack = output<void>();
|
protected goBack = output<void>();
|
||||||
protected complete = output<UpgradePaymentResult>();
|
protected complete = output<UpgradePaymentResult>();
|
||||||
protected selectedPlan: PlanDetails | null = null;
|
protected selectedPlan: PlanDetails | null = null;
|
||||||
@@ -90,7 +90,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit {
|
|||||||
billingAddress: EnterBillingAddressComponent.getFormGroup(),
|
billingAddress: EnterBillingAddressComponent.getFormGroup(),
|
||||||
});
|
});
|
||||||
|
|
||||||
protected loading = signal(true);
|
protected readonly loading = signal(true);
|
||||||
private pricingTiers$!: Observable<PersonalSubscriptionPricingTier[]>;
|
private pricingTiers$!: Observable<PersonalSubscriptionPricingTier[]>;
|
||||||
|
|
||||||
// Cart Summary data
|
// Cart Summary data
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export class VaultItemsComponent<C extends CipherViewLike> {
|
|||||||
@Input() userCanArchive: boolean;
|
@Input() userCanArchive: boolean;
|
||||||
@Input() enforceOrgDataOwnershipPolicy: boolean;
|
@Input() enforceOrgDataOwnershipPolicy: boolean;
|
||||||
|
|
||||||
private restrictedPolicies = toSignal(this.restrictedItemTypesService.restricted$);
|
private readonly restrictedPolicies = toSignal(this.restrictedItemTypesService.restricted$);
|
||||||
|
|
||||||
private _ciphers?: C[] = [];
|
private _ciphers?: C[] = [];
|
||||||
@Input() get ciphers(): C[] {
|
@Input() get ciphers(): C[] {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
ToastService,
|
ToastService,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service";
|
import { WebProviderService } from "../services/web-provider.service";
|
||||||
|
|
||||||
export type AddExistingOrganizationDialogParams = {
|
export type AddExistingOrganizationDialogParams = {
|
||||||
provider: Provider;
|
provider: Provider;
|
||||||
@@ -55,7 +55,7 @@ export class AddExistingOrganizationDialogComponent implements OnInit {
|
|||||||
|
|
||||||
addExistingOrganization = async (): Promise<void> => {
|
addExistingOrganization = async (): Promise<void> => {
|
||||||
if (this.selectedOrganization) {
|
if (this.selectedOrganization) {
|
||||||
await this.webProviderService.addOrganizationToProviderVNext(
|
await this.webProviderService.addOrganizationToProvider(
|
||||||
this.dialogParams.provider.id,
|
this.dialogParams.provider.id,
|
||||||
this.selectedOrganization.id,
|
this.selectedOrganization.id,
|
||||||
);
|
);
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<bit-dialog [loading]="loading">
|
|
||||||
<span bitDialogTitle>{{ "addExistingOrganization" | i18n }}</span>
|
|
||||||
<ng-container bitDialogContent>
|
|
||||||
<bit-table>
|
|
||||||
<ng-template body>
|
|
||||||
<tr bitRow *ngFor="let o of data.organizations">
|
|
||||||
<td bitCell width="30">
|
|
||||||
<bit-avatar [text]="o.name" [id]="o.id" size="small"></bit-avatar>
|
|
||||||
</td>
|
|
||||||
<td bitCell>
|
|
||||||
{{ o.name }}
|
|
||||||
</td>
|
|
||||||
<td bitCell>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
bitButton
|
|
||||||
[bitAction]="add(o, provider$ | async)"
|
|
||||||
class="tw-float-right"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</bit-table>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container bitDialogFooter>
|
|
||||||
<button type="button" bitButton bitDialogClose>
|
|
||||||
{{ "close" | i18n }}
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</bit-dialog>
|
|
||||||
@@ -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<Provider>;
|
|
||||||
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<boolean, AddOrganizationDialogData>(AddOrganizationComponent, {
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
@let isAdminOrServiceUser = isAdminOrServiceUser$ | async;
|
|
||||||
<app-header>
|
|
||||||
<bit-search
|
|
||||||
class="tw-grow"
|
|
||||||
[formControl]="searchControl"
|
|
||||||
[placeholder]="'search' | i18n"
|
|
||||||
></bit-search>
|
|
||||||
<a bitButton routerLink="create" *ngIf="isAdminOrServiceUser" buttonType="primary">
|
|
||||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
|
||||||
{{ "newClient" | i18n }}
|
|
||||||
</a>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
bitButton
|
|
||||||
(click)="addExistingOrganization()"
|
|
||||||
*ngIf="isAdminOrServiceUser && showAddExisting"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
|
||||||
{{ "addExistingOrganization" | i18n }}
|
|
||||||
</button>
|
|
||||||
</app-header>
|
|
||||||
|
|
||||||
<ng-container *ngIf="loading">
|
|
||||||
<i
|
|
||||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
|
||||||
title="{{ 'loading' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *ngIf="!loading">
|
|
||||||
<p *ngIf="dataSource.data.length < 1">{{ "noClientsInList" | i18n }}</p>
|
|
||||||
<ng-container *ngIf="dataSource.data.length >= 1">
|
|
||||||
<bit-table-scroll [dataSource]="dataSource" [rowSize]="53" class="tw-table tw-w-full">
|
|
||||||
<ng-container header>
|
|
||||||
<th bitCell colspan="2" bitSortable="organizationName">{{ "name" | i18n }}</th>
|
|
||||||
<th bitCell bitSortable="seats">{{ "numberOfUsers" | i18n }}</th>
|
|
||||||
<th bitCell bitSortable="plan">{{ "billingPlan" | i18n }}</th>
|
|
||||||
</ng-container>
|
|
||||||
<ng-template bitRowDef let-row>
|
|
||||||
<td bitCell width="30">
|
|
||||||
<bit-avatar [text]="row.organizationName" [id]="row.id" size="small"></bit-avatar>
|
|
||||||
</td>
|
|
||||||
<td bitCell width="320">
|
|
||||||
<a [routerLink]="['/organizations', row.organizationId]">{{ row.organizationName }}</a>
|
|
||||||
</td>
|
|
||||||
<td bitCell width="700">
|
|
||||||
<span>{{ row.userCount }}</span>
|
|
||||||
<span *ngIf="row.seats != null"> / {{ row.seats }}</span>
|
|
||||||
</td>
|
|
||||||
<td bitCell width="250" class="tw-flex tw-flex-row tw-items-center">
|
|
||||||
<span>{{ row.plan }}</span>
|
|
||||||
<div appListDropdown>
|
|
||||||
<button
|
|
||||||
*ngIf="isAdminOrServiceUser"
|
|
||||||
[bitMenuTriggerFor]="removeMenu"
|
|
||||||
bitMenuItem
|
|
||||||
buttonType="secondary"
|
|
||||||
type="button"
|
|
||||||
appA11yTitle="{{ 'options' | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<bit-menu #removeMenu>
|
|
||||||
<button bitMenuItem type="button" appStopClick (click)="remove(row)">
|
|
||||||
<span class="tw-text-danger">
|
|
||||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
|
||||||
{{ "remove" | i18n }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</bit-menu>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-template>
|
|
||||||
</bit-table-scroll>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
@@ -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<ProviderOrganizationOrganizationDetailsResponse> =
|
|
||||||
new TableDataSource();
|
|
||||||
protected searchControl = new FormControl("", { nonNullable: true });
|
|
||||||
|
|
||||||
protected providerId$: Observable<string> =
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
ToastService,
|
ToastService,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service";
|
import { WebProviderService } from "../services/web-provider.service";
|
||||||
|
|
||||||
type CreateClientDialogParams = {
|
type CreateClientDialogParams = {
|
||||||
providerId: string;
|
providerId: string;
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<app-header [title]="'newClientOrganization' | i18n"></app-header>
|
|
||||||
<p>{{ "newClientOrganizationDesc" | i18n }}</p>
|
|
||||||
<app-organization-plans [providerId]="providerId"></app-organization-plans>
|
|
||||||
@@ -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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
import { Component, Inject } from "@angular/core";
|
import { Component, Inject } from "@angular/core";
|
||||||
import { FormBuilder, Validators } from "@angular/forms";
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
|
|
||||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
||||||
import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request";
|
import { UpdateProviderOrganizationRequest } from "@bitwarden/common/admin-console/models/request/update-provider-organization.request";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import {
|
import {
|
||||||
DIALOG_DATA,
|
DIALOG_DATA,
|
||||||
@@ -51,7 +51,7 @@ export class ManageClientNameDialogComponent {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DIALOG_DATA) protected dialogParams: ManageClientNameDialogParams,
|
@Inject(DIALOG_DATA) protected dialogParams: ManageClientNameDialogParams,
|
||||||
private billingApiService: BillingApiServiceAbstraction,
|
private providerApiService: ProviderApiServiceAbstraction,
|
||||||
private dialogRef: DialogRef<ManageClientNameDialogResultType>,
|
private dialogRef: DialogRef<ManageClientNameDialogResultType>,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
@@ -65,11 +65,11 @@ export class ManageClientNameDialogComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = new UpdateClientOrganizationRequest();
|
const request = new UpdateProviderOrganizationRequest();
|
||||||
request.assignedSeats = this.dialogParams.organization.seats;
|
request.assignedSeats = this.dialogParams.organization.seats;
|
||||||
request.name = this.formGroup.value.name;
|
request.name = this.formGroup.value.name;
|
||||||
|
|
||||||
await this.billingApiService.updateProviderClientOrganization(
|
await this.providerApiService.updateProviderOrganization(
|
||||||
this.dialogParams.providerId,
|
this.dialogParams.providerId,
|
||||||
this.dialogParams.organization.id,
|
this.dialogParams.organization.id,
|
||||||
request,
|
request,
|
||||||
@@ -3,11 +3,12 @@
|
|||||||
import { Component, Inject, OnInit } from "@angular/core";
|
import { Component, Inject, OnInit } from "@angular/core";
|
||||||
import { FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from "@angular/forms";
|
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 { ProviderUserType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
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 { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
||||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
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 { ProviderPlanResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components";
|
import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components";
|
||||||
@@ -56,6 +57,7 @@ export class ManageClientSubscriptionDialogComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private billingApiService: BillingApiServiceAbstraction,
|
private billingApiService: BillingApiServiceAbstraction,
|
||||||
|
private providerApiService: ProviderApiServiceAbstraction,
|
||||||
@Inject(DIALOG_DATA) protected dialogParams: ManageClientSubscriptionDialogParams,
|
@Inject(DIALOG_DATA) protected dialogParams: ManageClientSubscriptionDialogParams,
|
||||||
private dialogRef: DialogRef<ManageClientSubscriptionDialogResultType>,
|
private dialogRef: DialogRef<ManageClientSubscriptionDialogResultType>,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
@@ -99,11 +101,11 @@ export class ManageClientSubscriptionDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const request = new UpdateClientOrganizationRequest();
|
const request = new UpdateProviderOrganizationRequest();
|
||||||
request.assignedSeats = this.formGroup.value.assignedSeats;
|
request.assignedSeats = this.formGroup.value.assignedSeats;
|
||||||
request.name = this.dialogParams.organization.organizationName;
|
request.name = this.dialogParams.organization.organizationName;
|
||||||
|
|
||||||
await this.billingApiService.updateProviderClientOrganization(
|
await this.providerApiService.updateProviderOrganization(
|
||||||
this.dialogParams.provider.id,
|
this.dialogParams.provider.id,
|
||||||
this.dialogParams.organization.id,
|
this.dialogParams.organization.id,
|
||||||
request,
|
request,
|
||||||
@@ -1,25 +1,21 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
|
||||||
import { FormControl } from "@angular/forms";
|
import { FormControl } from "@angular/forms";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import {
|
import {
|
||||||
firstValueFrom,
|
firstValueFrom,
|
||||||
from,
|
|
||||||
lastValueFrom,
|
lastValueFrom,
|
||||||
map,
|
map,
|
||||||
combineLatest,
|
combineLatest,
|
||||||
switchMap,
|
switchMap,
|
||||||
Observable,
|
Observable,
|
||||||
|
Subject,
|
||||||
|
takeUntil,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
import { debounceTime, first } from "rxjs/operators";
|
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 { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import {
|
import { ProviderType, ProviderUserType } from "@bitwarden/common/admin-console/enums";
|
||||||
ProviderStatusType,
|
|
||||||
ProviderType,
|
|
||||||
ProviderUserType,
|
|
||||||
} from "@bitwarden/common/admin-console/enums";
|
|
||||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
|
||||||
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/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 { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service";
|
||||||
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
|
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 {
|
import {
|
||||||
AddExistingOrganizationDialogComponent,
|
AddExistingOrganizationDialogComponent,
|
||||||
@@ -72,7 +68,7 @@ import { ReplacePipe } from "./replace.pipe";
|
|||||||
ReplacePipe,
|
ReplacePipe,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ManageClientsComponent {
|
export class ManageClientsComponent implements OnInit, OnDestroy {
|
||||||
loading = true;
|
loading = true;
|
||||||
dataSource: TableDataSource<ProviderOrganizationOrganizationDetailsResponse> =
|
dataSource: TableDataSource<ProviderOrganizationOrganizationDetailsResponse> =
|
||||||
new TableDataSource();
|
new TableDataSource();
|
||||||
@@ -117,10 +113,11 @@ export class ManageClientsComponent {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private billingApiService: BillingApiServiceAbstraction,
|
private billingApiService: BillingApiServiceAbstraction,
|
||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
private router: Router,
|
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
@@ -130,35 +127,31 @@ export class ManageClientsComponent {
|
|||||||
private billingNotificationService: BillingNotificationService,
|
private billingNotificationService: BillingNotificationService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
) {
|
private providerApiService: ProviderApiServiceAbstraction,
|
||||||
this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => {
|
) {}
|
||||||
this.searchControl.setValue(queryParams.search);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.provider$
|
async ngOnInit() {
|
||||||
.pipe(
|
this.activatedRoute.queryParams
|
||||||
map((provider: Provider | undefined) => {
|
.pipe(first(), takeUntil(this.destroy$))
|
||||||
if (provider?.providerStatus !== ProviderStatusType.Billable) {
|
.subscribe((queryParams) => {
|
||||||
return from(
|
this.searchControl.setValue(queryParams.search);
|
||||||
this.router.navigate(["../clients"], {
|
});
|
||||||
relativeTo: this.activatedRoute,
|
|
||||||
}),
|
await this.load();
|
||||||
);
|
|
||||||
}
|
|
||||||
return from(this.load());
|
|
||||||
}),
|
|
||||||
takeUntilDestroyed(),
|
|
||||||
)
|
|
||||||
.subscribe();
|
|
||||||
|
|
||||||
this.searchControl.valueChanges
|
this.searchControl.valueChanges
|
||||||
.pipe(debounceTime(200), takeUntilDestroyed())
|
.pipe(debounceTime(200), takeUntil(this.destroy$))
|
||||||
.subscribe((searchText) => {
|
.subscribe((searchText) => {
|
||||||
this.dataSource.filter = (data) =>
|
this.dataSource.filter = (data) =>
|
||||||
data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1;
|
data.organizationName.toLowerCase().indexOf(searchText.toLowerCase()) > -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
try {
|
try {
|
||||||
const providerId = await firstValueFrom(this.providerId$);
|
const providerId = await firstValueFrom(this.providerId$);
|
||||||
@@ -170,7 +163,7 @@ export class ManageClientsComponent {
|
|||||||
this.newClientButtonLabel = this.i18nService.t("newBusinessUnit");
|
this.newClientButtonLabel = this.i18nService.t("newBusinessUnit");
|
||||||
}
|
}
|
||||||
this.dataSource.data = (
|
this.dataSource.data = (
|
||||||
await this.billingApiService.getProviderClientOrganizations(providerId)
|
await this.providerApiService.getProviderOrganizations(providerId)
|
||||||
).data;
|
).data;
|
||||||
this.plans = (await this.billingApiService.getPlans()).data;
|
this.plans = (await this.billingApiService.getPlans()).data;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<bit-nav-item
|
<bit-nav-item
|
||||||
icon="bwi-provider"
|
icon="bwi-provider"
|
||||||
[text]="clientsTranslationKey$ | async | i18n"
|
[text]="clientsTranslationKey$ | async | i18n"
|
||||||
[route]="(isBillable | async) ? 'manage-client-organizations' : 'clients'"
|
route="clients"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
*ngIf="!provider.enabled && (providerPortalTakeover$ | async)"
|
*ngIf="!provider.enabled && (providerPortalTakeover$ | async)"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { takeUntil } from "rxjs/operators";
|
|||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { BusinessUnitPortalLogo, Icon, ProviderPortalLogo } from "@bitwarden/assets/svg";
|
import { BusinessUnitPortalLogo, Icon, ProviderPortalLogo } from "@bitwarden/assets/svg";
|
||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { ProviderStatusType, ProviderType } from "@bitwarden/common/admin-console/enums";
|
import { ProviderType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
@@ -43,7 +43,6 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
protected logo$: Observable<Icon>;
|
protected logo$: Observable<Icon>;
|
||||||
|
|
||||||
protected isBillable: Observable<boolean>;
|
|
||||||
protected canAccessBilling$: Observable<boolean>;
|
protected canAccessBilling$: Observable<boolean>;
|
||||||
|
|
||||||
protected clientsTranslationKey$: Observable<string>;
|
protected clientsTranslationKey$: Observable<string>;
|
||||||
@@ -83,15 +82,7 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.isBillable = this.provider$.pipe(
|
this.canAccessBilling$ = this.provider$.pipe(map((provider) => provider.isProviderAdmin));
|
||||||
map((provider) => provider?.providerStatus === ProviderStatusType.Billable),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.canAccessBilling$ = combineLatest([this.isBillable, this.provider$]).pipe(
|
|
||||||
map(
|
|
||||||
([hasConsolidatedBilling, provider]) => hasConsolidatedBilling && provider.isProviderAdmin,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.clientsTranslationKey$ = this.provider$.pipe(
|
this.clientsTranslationKey$ = this.provider$.pipe(
|
||||||
map((provider) =>
|
map((provider) =>
|
||||||
|
|||||||
@@ -7,17 +7,12 @@ import { AnonLayoutWrapperComponent } from "@bitwarden/components";
|
|||||||
import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component";
|
import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component";
|
||||||
import { UserLayoutComponent } from "@bitwarden/web-vault/app/layouts/user-layout.component";
|
import { UserLayoutComponent } from "@bitwarden/web-vault/app/layouts/user-layout.component";
|
||||||
|
|
||||||
import {
|
import { ProviderBillingHistoryComponent } from "../../billing/providers/billing-history/provider-billing-history.component";
|
||||||
ManageClientsComponent,
|
|
||||||
ProviderSubscriptionComponent,
|
|
||||||
hasConsolidatedBilling,
|
|
||||||
ProviderBillingHistoryComponent,
|
|
||||||
} from "../../billing/providers";
|
|
||||||
import { ProviderPaymentDetailsComponent } from "../../billing/providers/payment-details/provider-payment-details.component";
|
import { ProviderPaymentDetailsComponent } from "../../billing/providers/payment-details/provider-payment-details.component";
|
||||||
import { SetupBusinessUnitComponent } from "../../billing/providers/setup/setup-business-unit.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 { ManageClientsComponent } from "./clients/manage-clients.component";
|
||||||
import { CreateOrganizationComponent } from "./clients/create-organization.component";
|
|
||||||
import { providerPermissionsGuard } from "./guards/provider-permissions.guard";
|
import { providerPermissionsGuard } from "./guards/provider-permissions.guard";
|
||||||
import { AcceptProviderComponent } from "./manage/accept-provider.component";
|
import { AcceptProviderComponent } from "./manage/accept-provider.component";
|
||||||
import { EventsComponent } from "./manage/events.component";
|
import { EventsComponent } from "./manage/events.component";
|
||||||
@@ -88,14 +83,7 @@ const routes: Routes = [
|
|||||||
canActivate: [providerPermissionsGuard()],
|
canActivate: [providerPermissionsGuard()],
|
||||||
children: [
|
children: [
|
||||||
{ path: "", pathMatch: "full", redirectTo: "clients" },
|
{ path: "", pathMatch: "full", redirectTo: "clients" },
|
||||||
{ path: "clients/create", component: CreateOrganizationComponent },
|
{ path: "clients", component: ManageClientsComponent, data: { titleId: "clients" } },
|
||||||
{ path: "clients", component: ClientsComponent, data: { titleId: "clients" } },
|
|
||||||
{
|
|
||||||
path: "manage-client-organizations",
|
|
||||||
canActivate: [hasConsolidatedBilling],
|
|
||||||
component: ManageClientsComponent,
|
|
||||||
data: { titleId: "clients" },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "manage",
|
path: "manage",
|
||||||
children: [
|
children: [
|
||||||
@@ -128,7 +116,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "billing",
|
path: "billing",
|
||||||
canActivate: [hasConsolidatedBilling],
|
canActivate: [providerPermissionsGuard()],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
@@ -138,7 +126,6 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "subscription",
|
path: "subscription",
|
||||||
component: ProviderSubscriptionComponent,
|
component: ProviderSubscriptionComponent,
|
||||||
canActivate: [providerPermissionsGuard()],
|
|
||||||
data: {
|
data: {
|
||||||
titleId: "subscription",
|
titleId: "subscription",
|
||||||
},
|
},
|
||||||
@@ -146,7 +133,6 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "payment-details",
|
path: "payment-details",
|
||||||
component: ProviderPaymentDetailsComponent,
|
component: ProviderPaymentDetailsComponent,
|
||||||
canActivate: [providerPermissionsGuard()],
|
|
||||||
data: {
|
data: {
|
||||||
titleId: "paymentDetails",
|
titleId: "paymentDetails",
|
||||||
},
|
},
|
||||||
@@ -154,7 +140,6 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: "history",
|
path: "history",
|
||||||
component: ProviderBillingHistoryComponent,
|
component: ProviderBillingHistoryComponent,
|
||||||
canActivate: [providerPermissionsGuard()],
|
|
||||||
data: {
|
data: {
|
||||||
titleId: "billingHistory",
|
titleId: "billingHistory",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,22 +13,18 @@ import {
|
|||||||
} from "@bitwarden/web-vault/app/billing/payment/components";
|
} from "@bitwarden/web-vault/app/billing/payment/components";
|
||||||
import { OssModule } from "@bitwarden/web-vault/app/oss.module";
|
import { OssModule } from "@bitwarden/web-vault/app/oss.module";
|
||||||
|
|
||||||
import {
|
import { InvoicesComponent } from "../../billing/providers/billing-history/invoices.component";
|
||||||
CreateClientDialogComponent,
|
import { NoInvoicesComponent } from "../../billing/providers/billing-history/no-invoices.component";
|
||||||
InvoicesComponent,
|
import { ProviderBillingHistoryComponent } from "../../billing/providers/billing-history/provider-billing-history.component";
|
||||||
ManageClientNameDialogComponent,
|
|
||||||
ManageClientSubscriptionDialogComponent,
|
|
||||||
NoInvoicesComponent,
|
|
||||||
ProviderBillingHistoryComponent,
|
|
||||||
ProviderSubscriptionComponent,
|
|
||||||
ProviderSubscriptionStatusComponent,
|
|
||||||
} from "../../billing/providers";
|
|
||||||
import { AddExistingOrganizationDialogComponent } from "../../billing/providers/clients/add-existing-organization-dialog.component";
|
|
||||||
import { SetupBusinessUnitComponent } from "../../billing/providers/setup/setup-business-unit.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 { ProviderWarningsModule } from "../../billing/providers/warnings/provider-warnings.module";
|
||||||
|
|
||||||
import { AddOrganizationComponent } from "./clients/add-organization.component";
|
import { AddExistingOrganizationDialogComponent } from "./clients/add-existing-organization-dialog.component";
|
||||||
import { CreateOrganizationComponent } from "./clients/create-organization.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 { AcceptProviderComponent } from "./manage/accept-provider.component";
|
||||||
import { AddEditMemberDialogComponent } from "./manage/dialogs/add-edit-member-dialog.component";
|
import { AddEditMemberDialogComponent } from "./manage/dialogs/add-edit-member-dialog.component";
|
||||||
import { BulkConfirmDialogComponent } from "./manage/dialogs/bulk-confirm-dialog.component";
|
import { BulkConfirmDialogComponent } from "./manage/dialogs/bulk-confirm-dialog.component";
|
||||||
@@ -65,10 +61,8 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr
|
|||||||
declarations: [
|
declarations: [
|
||||||
AcceptProviderComponent,
|
AcceptProviderComponent,
|
||||||
AccountComponent,
|
AccountComponent,
|
||||||
AddOrganizationComponent,
|
|
||||||
BulkConfirmDialogComponent,
|
BulkConfirmDialogComponent,
|
||||||
BulkRemoveDialogComponent,
|
BulkRemoveDialogComponent,
|
||||||
CreateOrganizationComponent,
|
|
||||||
EventsComponent,
|
EventsComponent,
|
||||||
MembersComponent,
|
MembersComponent,
|
||||||
SetupComponent,
|
SetupComponent,
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { MockProxy, mock } from "jest-mock-extended";
|
|||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
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 { 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 { PlanType } from "@bitwarden/common/billing/enums";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||||
@@ -26,10 +24,8 @@ describe("WebProviderService", () => {
|
|||||||
let apiService: MockProxy<ApiService>;
|
let apiService: MockProxy<ApiService>;
|
||||||
let i18nService: MockProxy<I18nService>;
|
let i18nService: MockProxy<I18nService>;
|
||||||
let encryptService: MockProxy<EncryptService>;
|
let encryptService: MockProxy<EncryptService>;
|
||||||
let billingApiService: MockProxy<BillingApiServiceAbstraction>;
|
|
||||||
let stateProvider: MockProxy<StateProvider>;
|
let stateProvider: MockProxy<StateProvider>;
|
||||||
let providerApiService: MockProxy<ProviderApiServiceAbstraction>;
|
let providerApiService: MockProxy<ProviderApiServiceAbstraction>;
|
||||||
let accountService: MockProxy<AccountService>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
keyService = mock();
|
keyService = mock();
|
||||||
@@ -37,10 +33,8 @@ describe("WebProviderService", () => {
|
|||||||
apiService = mock();
|
apiService = mock();
|
||||||
i18nService = mock();
|
i18nService = mock();
|
||||||
encryptService = mock();
|
encryptService = mock();
|
||||||
billingApiService = mock();
|
|
||||||
stateProvider = mock();
|
stateProvider = mock();
|
||||||
providerApiService = mock();
|
providerApiService = mock();
|
||||||
accountService = mock();
|
|
||||||
|
|
||||||
sut = new WebProviderService(
|
sut = new WebProviderService(
|
||||||
keyService,
|
keyService,
|
||||||
@@ -48,10 +42,8 @@ describe("WebProviderService", () => {
|
|||||||
apiService,
|
apiService,
|
||||||
i18nService,
|
i18nService,
|
||||||
encryptService,
|
encryptService,
|
||||||
billingApiService,
|
|
||||||
stateProvider,
|
stateProvider,
|
||||||
providerApiService,
|
providerApiService,
|
||||||
accountService,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -99,7 +91,7 @@ describe("WebProviderService", () => {
|
|||||||
expect(keyService.getProviderKey).toHaveBeenCalledWith(providerId);
|
expect(keyService.getProviderKey).toHaveBeenCalledWith(providerId);
|
||||||
expect(encryptService.wrapSymmetricKey).toHaveBeenCalledWith(mockOrgKey, mockProviderKey);
|
expect(encryptService.wrapSymmetricKey).toHaveBeenCalledWith(mockOrgKey, mockProviderKey);
|
||||||
|
|
||||||
expect(billingApiService.createProviderClientOrganization).toHaveBeenCalledWith(
|
expect(providerApiService.createProviderOrganization).toHaveBeenCalledWith(
|
||||||
providerId,
|
providerId,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -6,13 +6,9 @@ import { switchMap } from "rxjs/operators";
|
|||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
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 { 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 { 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 { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||||
@@ -29,34 +25,11 @@ export class WebProviderService {
|
|||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private encryptService: EncryptService,
|
private encryptService: EncryptService,
|
||||||
private billingApiService: BillingApiServiceAbstraction,
|
|
||||||
private stateProvider: StateProvider,
|
private stateProvider: StateProvider,
|
||||||
private providerApiService: ProviderApiServiceAbstraction,
|
private providerApiService: ProviderApiServiceAbstraction,
|
||||||
private accountService: AccountService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async addOrganizationToProvider(providerId: string, organizationId: string) {
|
async addOrganizationToProvider(providerId: string, organizationId: string): Promise<void> {
|
||||||
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<void> {
|
|
||||||
const orgKey = await firstValueFrom(
|
const orgKey = await firstValueFrom(
|
||||||
this.stateProvider.activeUserId$.pipe(
|
this.stateProvider.activeUserId$.pipe(
|
||||||
switchMap((userId) => this.keyService.orgKeys$(userId)),
|
switchMap((userId) => this.keyService.orgKeys$(userId)),
|
||||||
@@ -96,7 +69,7 @@ export class WebProviderService {
|
|||||||
providerKey,
|
providerKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
const request = new CreateClientOrganizationRequest();
|
const request = new CreateProviderOrganizationRequest();
|
||||||
request.name = name;
|
request.name = name;
|
||||||
request.ownerEmail = ownerEmail;
|
request.ownerEmail = ownerEmail;
|
||||||
request.planType = planType;
|
request.planType = planType;
|
||||||
@@ -106,7 +79,7 @@ export class WebProviderService {
|
|||||||
request.keyPair = new OrganizationKeysRequest(publicKey, encryptedPrivateKey.encryptedString);
|
request.keyPair = new OrganizationKeysRequest(publicKey, encryptedPrivateKey.encryptedString);
|
||||||
request.collectionName = encryptedCollectionName.encryptedString;
|
request.collectionName = encryptedCollectionName.encryptedString;
|
||||||
|
|
||||||
await this.billingApiService.createProviderClientOrganization(providerId, request);
|
await this.providerApiService.createProviderOrganization(providerId, request);
|
||||||
|
|
||||||
await this.apiService.refreshIdentityToken();
|
await this.apiService.refreshIdentityToken();
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -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";
|
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import { AddableOrganizationResponse } from "@bitwarden/common/admin-console/models/response/addable-organization.response";
|
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 { ProviderSetupRequest } from "../../models/request/provider/provider-setup.request";
|
||||||
import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request";
|
import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request";
|
||||||
import { ProviderVerifyRecoverDeleteRequest } from "../../models/request/provider/provider-verify-recover-delete.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";
|
import { ProviderResponse } from "../../models/response/provider/provider.response";
|
||||||
|
|
||||||
export abstract class ProviderApiServiceAbstraction {
|
export abstract class ProviderApiServiceAbstraction {
|
||||||
@@ -14,6 +18,9 @@ export abstract class ProviderApiServiceAbstraction {
|
|||||||
request: ProviderVerifyRecoverDeleteRequest,
|
request: ProviderVerifyRecoverDeleteRequest,
|
||||||
): Promise<any>;
|
): Promise<any>;
|
||||||
abstract deleteProvider(id: string): Promise<void>;
|
abstract deleteProvider(id: string): Promise<void>;
|
||||||
|
abstract getProviderOrganizations(
|
||||||
|
providerId: string,
|
||||||
|
): Promise<ListResponse<ProviderOrganizationOrganizationDetailsResponse>>;
|
||||||
abstract getProviderAddableOrganizations(
|
abstract getProviderAddableOrganizations(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
): Promise<AddableOrganizationResponse[]>;
|
): Promise<AddableOrganizationResponse[]>;
|
||||||
@@ -24,4 +31,15 @@ export abstract class ProviderApiServiceAbstraction {
|
|||||||
organizationId: string;
|
organizationId: string;
|
||||||
},
|
},
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
|
abstract updateProviderOrganization(
|
||||||
|
providerId: string,
|
||||||
|
organizationId: string,
|
||||||
|
request: UpdateProviderOrganizationRequest,
|
||||||
|
): Promise<any>;
|
||||||
|
|
||||||
|
abstract createProviderOrganization(
|
||||||
|
providerId: string,
|
||||||
|
request: CreateProviderOrganizationRequest,
|
||||||
|
): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { OrganizationKeysRequest } from "../../../admin-console/models/request/organization-keys.request";
|
|
||||||
import { PlanType } from "../../../billing/enums";
|
import { PlanType } from "../../../billing/enums";
|
||||||
|
|
||||||
export class CreateClientOrganizationRequest {
|
import { OrganizationKeysRequest } from "./organization-keys.request";
|
||||||
|
|
||||||
|
export class CreateProviderOrganizationRequest {
|
||||||
name: string;
|
name: string;
|
||||||
ownerEmail: string;
|
ownerEmail: string;
|
||||||
planType: PlanType;
|
planType: PlanType;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
export class UpdateClientOrganizationRequest {
|
export class UpdateProviderOrganizationRequest {
|
||||||
assignedSeats: number;
|
assignedSeats: number;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
import { AddableOrganizationResponse } from "@bitwarden/common/admin-console/models/response/addable-organization.response";
|
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 { ApiService } from "../../../abstractions/api.service";
|
||||||
import { ProviderApiServiceAbstraction } from "../../abstractions/provider/provider-api.service.abstraction";
|
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 { ProviderSetupRequest } from "../../models/request/provider/provider-setup.request";
|
||||||
import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request";
|
import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request";
|
||||||
import { ProviderVerifyRecoverDeleteRequest } from "../../models/request/provider/provider-verify-recover-delete.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";
|
import { ProviderResponse } from "../../models/response/provider/provider.response";
|
||||||
|
|
||||||
export class ProviderApiService implements ProviderApiServiceAbstraction {
|
export class ProviderApiService implements ProviderApiServiceAbstraction {
|
||||||
@@ -47,6 +51,19 @@ export class ProviderApiService implements ProviderApiServiceAbstraction {
|
|||||||
await this.apiService.send("DELETE", "/providers/" + id, null, true, false);
|
await this.apiService.send("DELETE", "/providers/" + id, null, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getProviderOrganizations(
|
||||||
|
providerId: string,
|
||||||
|
): Promise<ListResponse<ProviderOrganizationOrganizationDetailsResponse>> {
|
||||||
|
const response = await this.apiService.send(
|
||||||
|
"GET",
|
||||||
|
"/providers/" + providerId + "/organizations",
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return new ListResponse(response, ProviderOrganizationOrganizationDetailsResponse);
|
||||||
|
}
|
||||||
|
|
||||||
async getProviderAddableOrganizations(
|
async getProviderAddableOrganizations(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
): Promise<AddableOrganizationResponse[]> {
|
): Promise<AddableOrganizationResponse[]> {
|
||||||
@@ -76,4 +93,31 @@ export class ProviderApiService implements ProviderApiServiceAbstraction {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateProviderOrganization(
|
||||||
|
providerId: string,
|
||||||
|
organizationId: string,
|
||||||
|
request: UpdateProviderOrganizationRequest,
|
||||||
|
): Promise<any> {
|
||||||
|
return await this.apiService.send(
|
||||||
|
"PUT",
|
||||||
|
"/providers/" + providerId + "/clients/" + organizationId,
|
||||||
|
request,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createProviderOrganization(
|
||||||
|
providerId: string,
|
||||||
|
request: CreateProviderOrganizationRequest,
|
||||||
|
): Promise<void> {
|
||||||
|
return this.apiService.send(
|
||||||
|
"POST",
|
||||||
|
"/providers/" + providerId + "/clients",
|
||||||
|
request,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
|
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 { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
|
||||||
import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response";
|
import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response";
|
||||||
import { PlanResponse } from "../../billing/models/response/plan.response";
|
import { PlanResponse } from "../../billing/models/response/plan.response";
|
||||||
import { ListResponse } from "../../models/response/list.response";
|
import { ListResponse } from "../../models/response/list.response";
|
||||||
import { OrganizationId } from "../../types/guid";
|
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 { InvoicesResponse } from "../models/response/invoices.response";
|
||||||
import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response";
|
import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response";
|
||||||
|
|
||||||
@@ -18,11 +15,6 @@ export abstract class BillingApiServiceAbstraction {
|
|||||||
|
|
||||||
abstract cancelPremiumUserSubscription(request: SubscriptionCancellationRequest): Promise<void>;
|
abstract cancelPremiumUserSubscription(request: SubscriptionCancellationRequest): Promise<void>;
|
||||||
|
|
||||||
abstract createProviderClientOrganization(
|
|
||||||
providerId: string,
|
|
||||||
request: CreateClientOrganizationRequest,
|
|
||||||
): Promise<void>;
|
|
||||||
|
|
||||||
abstract getOrganizationBillingMetadata(
|
abstract getOrganizationBillingMetadata(
|
||||||
organizationId: OrganizationId,
|
organizationId: OrganizationId,
|
||||||
): Promise<OrganizationBillingMetadataResponse>;
|
): Promise<OrganizationBillingMetadataResponse>;
|
||||||
@@ -35,20 +27,10 @@ export abstract class BillingApiServiceAbstraction {
|
|||||||
|
|
||||||
abstract getProviderClientInvoiceReport(providerId: string, invoiceId: string): Promise<string>;
|
abstract getProviderClientInvoiceReport(providerId: string, invoiceId: string): Promise<string>;
|
||||||
|
|
||||||
abstract getProviderClientOrganizations(
|
|
||||||
providerId: string,
|
|
||||||
): Promise<ListResponse<ProviderOrganizationOrganizationDetailsResponse>>;
|
|
||||||
|
|
||||||
abstract getProviderInvoices(providerId: string): Promise<InvoicesResponse>;
|
abstract getProviderInvoices(providerId: string): Promise<InvoicesResponse>;
|
||||||
|
|
||||||
abstract getProviderSubscription(providerId: string): Promise<ProviderSubscriptionResponse>;
|
abstract getProviderSubscription(providerId: string): Promise<ProviderSubscriptionResponse>;
|
||||||
|
|
||||||
abstract updateProviderClientOrganization(
|
|
||||||
providerId: string,
|
|
||||||
organizationId: string,
|
|
||||||
request: UpdateClientOrganizationRequest,
|
|
||||||
): Promise<any>;
|
|
||||||
|
|
||||||
abstract restartSubscription(
|
abstract restartSubscription(
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
request: OrganizationCreateRequest,
|
request: OrganizationCreateRequest,
|
||||||
|
|||||||
@@ -3,13 +3,10 @@
|
|||||||
|
|
||||||
import { ApiService } from "../../abstractions/api.service";
|
import { ApiService } from "../../abstractions/api.service";
|
||||||
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
|
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 { ListResponse } from "../../models/response/list.response";
|
||||||
import { OrganizationId } from "../../types/guid";
|
import { OrganizationId } from "../../types/guid";
|
||||||
import { BillingApiServiceAbstraction } from "../abstractions";
|
import { BillingApiServiceAbstraction } from "../abstractions";
|
||||||
import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request";
|
|
||||||
import { SubscriptionCancellationRequest } from "../models/request/subscription-cancellation.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 { InvoicesResponse } from "../models/response/invoices.response";
|
||||||
import { OrganizationBillingMetadataResponse } from "../models/response/organization-billing-metadata.response";
|
import { OrganizationBillingMetadataResponse } from "../models/response/organization-billing-metadata.response";
|
||||||
import { PlanResponse } from "../models/response/plan.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);
|
return this.apiService.send("POST", "/accounts/cancel", request, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
createProviderClientOrganization(
|
|
||||||
providerId: string,
|
|
||||||
request: CreateClientOrganizationRequest,
|
|
||||||
): Promise<void> {
|
|
||||||
return this.apiService.send(
|
|
||||||
"POST",
|
|
||||||
"/providers/" + providerId + "/clients",
|
|
||||||
request,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getOrganizationBillingMetadata(
|
async getOrganizationBillingMetadata(
|
||||||
organizationId: OrganizationId,
|
organizationId: OrganizationId,
|
||||||
): Promise<OrganizationBillingMetadataResponse> {
|
): Promise<OrganizationBillingMetadataResponse> {
|
||||||
@@ -92,19 +76,6 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
|||||||
return response as string;
|
return response as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProviderClientOrganizations(
|
|
||||||
providerId: string,
|
|
||||||
): Promise<ListResponse<ProviderOrganizationOrganizationDetailsResponse>> {
|
|
||||||
const response = await this.apiService.send(
|
|
||||||
"GET",
|
|
||||||
"/providers/" + providerId + "/organizations",
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
return new ListResponse(response, ProviderOrganizationOrganizationDetailsResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProviderInvoices(providerId: string): Promise<InvoicesResponse> {
|
async getProviderInvoices(providerId: string): Promise<InvoicesResponse> {
|
||||||
const response = await this.apiService.send(
|
const response = await this.apiService.send(
|
||||||
"GET",
|
"GET",
|
||||||
@@ -127,20 +98,6 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
|||||||
return new ProviderSubscriptionResponse(response);
|
return new ProviderSubscriptionResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProviderClientOrganization(
|
|
||||||
providerId: string,
|
|
||||||
organizationId: string,
|
|
||||||
request: UpdateClientOrganizationRequest,
|
|
||||||
): Promise<any> {
|
|
||||||
return await this.apiService.send(
|
|
||||||
"PUT",
|
|
||||||
"/providers/" + providerId + "/clients/" + organizationId,
|
|
||||||
request,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async restartSubscription(
|
async restartSubscription(
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
request: OrganizationCreateRequest,
|
request: OrganizationCreateRequest,
|
||||||
|
|||||||
@@ -21,16 +21,20 @@ import {
|
|||||||
imports: [BadgeModule, ButtonModule, IconModule, TypographyModule, CurrencyPipe],
|
imports: [BadgeModule, ButtonModule, IconModule, TypographyModule, CurrencyPipe],
|
||||||
})
|
})
|
||||||
export class PricingCardComponent {
|
export class PricingCardComponent {
|
||||||
tagline = input.required<string>();
|
readonly tagline = input.required<string>();
|
||||||
price = input<{ amount: number; cadence: "monthly" | "annually"; showPerUser?: boolean }>();
|
readonly price = input<{
|
||||||
button = input<{
|
amount: number;
|
||||||
|
cadence: "monthly" | "annually";
|
||||||
|
showPerUser?: boolean;
|
||||||
|
}>();
|
||||||
|
readonly button = input<{
|
||||||
type: ButtonType;
|
type: ButtonType;
|
||||||
text: string;
|
text: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
icon?: { type: string; position: "before" | "after" };
|
icon?: { type: string; position: "before" | "after" };
|
||||||
}>();
|
}>();
|
||||||
features = input<string[]>();
|
readonly features = input<string[]>();
|
||||||
activeBadge = input<{ text: string; variant?: BadgeVariant }>();
|
readonly activeBadge = input<{ text: string; variant?: BadgeVariant }>();
|
||||||
|
|
||||||
@Output() buttonClick = new EventEmitter<void>();
|
@Output() buttonClick = new EventEmitter<void>();
|
||||||
|
|
||||||
|
|||||||
@@ -36,18 +36,18 @@ import { OrgIconDirective } from "../../components/org-icon.directive";
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ItemDetailsV2Component {
|
export class ItemDetailsV2Component {
|
||||||
hideOwner = input<boolean>(false);
|
readonly hideOwner = input<boolean>(false);
|
||||||
cipher = input.required<CipherView>();
|
readonly cipher = input.required<CipherView>();
|
||||||
organization = input<Organization | undefined>();
|
readonly organization = input<Organization | undefined>();
|
||||||
folder = input<FolderView | undefined>();
|
readonly folder = input<FolderView | undefined>();
|
||||||
collections = input<CollectionView[] | undefined>();
|
readonly collections = input<CollectionView[] | undefined>();
|
||||||
showAllDetails = signal(false);
|
readonly showAllDetails = signal(false);
|
||||||
|
|
||||||
showOwnership = computed(() => {
|
readonly showOwnership = computed(() => {
|
||||||
return this.cipher().organizationId && this.organization() && !this.hideOwner();
|
return this.cipher().organizationId && this.organization() && !this.hideOwner();
|
||||||
});
|
});
|
||||||
|
|
||||||
hasSmallScreen = toSignal(
|
readonly hasSmallScreen = toSignal(
|
||||||
fromEvent(window, "resize").pipe(
|
fromEvent(window, "resize").pipe(
|
||||||
map(() => window.innerWidth),
|
map(() => window.innerWidth),
|
||||||
startWith(window.innerWidth),
|
startWith(window.innerWidth),
|
||||||
@@ -56,7 +56,7 @@ export class ItemDetailsV2Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Array to hold all details of item. Organization, Collections, and Folder
|
// Array to hold all details of item. Organization, Collections, and Folder
|
||||||
allItems = computed(() => {
|
readonly allItems = computed(() => {
|
||||||
let items: any[] = [];
|
let items: any[] = [];
|
||||||
if (this.showOwnership() && this.organization()) {
|
if (this.showOwnership() && this.organization()) {
|
||||||
items.push(this.organization());
|
items.push(this.organization());
|
||||||
@@ -70,7 +70,7 @@ export class ItemDetailsV2Component {
|
|||||||
return items;
|
return items;
|
||||||
});
|
});
|
||||||
|
|
||||||
showItems = computed(() => {
|
readonly showItems = computed(() => {
|
||||||
if (
|
if (
|
||||||
this.hasSmallScreen() &&
|
this.hasSmallScreen() &&
|
||||||
this.allItems().length > 2 &&
|
this.allItems().length > 2 &&
|
||||||
|
|||||||
Reference in New Issue
Block a user