mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 22:33:35 +00:00
[AC-1970] Add billing navigation group to provider layout (#8941)
* Add billing navigation item to provider layout with empty subscription page behind FF. * Fixing tests * Missed build error * Addison's feedback * Remove unused function * Missed one get$ conversion * Fixed background failure
This commit is contained in:
@@ -1,17 +1,17 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { combineLatest, firstValueFrom, from } from "rxjs";
|
import { firstValueFrom, from, map } from "rxjs";
|
||||||
import { concatMap, switchMap, takeUntil } from "rxjs/operators";
|
import { switchMap, takeUntil } from "rxjs/operators";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
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 { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { ProviderStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums";
|
import { ProviderUserType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
|
import { canAccessBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction";
|
||||||
import { PlanType } from "@bitwarden/common/billing/enums";
|
import { PlanType } from "@bitwarden/common/billing/enums";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
@@ -40,10 +40,6 @@ export class ClientsComponent extends BaseClientsComponent {
|
|||||||
manageOrganizations = false;
|
manageOrganizations = false;
|
||||||
showAddExisting = false;
|
showAddExisting = false;
|
||||||
|
|
||||||
protected consolidatedBillingEnabled$ = this.configService.getFeatureFlag$(
|
|
||||||
FeatureFlag.EnableConsolidatedBilling,
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
@@ -75,15 +71,10 @@ export class ClientsComponent extends BaseClientsComponent {
|
|||||||
.pipe(
|
.pipe(
|
||||||
switchMap((params) => {
|
switchMap((params) => {
|
||||||
this.providerId = params.providerId;
|
this.providerId = params.providerId;
|
||||||
return combineLatest([
|
return this.providerService.get$(this.providerId).pipe(
|
||||||
this.providerService.get(this.providerId),
|
canAccessBilling(this.configService),
|
||||||
this.consolidatedBillingEnabled$,
|
map((canAccessBilling) => {
|
||||||
]).pipe(
|
if (canAccessBilling) {
|
||||||
concatMap(([provider, consolidatedBillingEnabled]) => {
|
|
||||||
if (
|
|
||||||
consolidatedBillingEnabled &&
|
|
||||||
provider.providerStatus === ProviderStatusType.Billable
|
|
||||||
) {
|
|
||||||
return from(
|
return from(
|
||||||
this.router.navigate(["../manage-client-organizations"], {
|
this.router.navigate(["../manage-client-organizations"], {
|
||||||
relativeTo: this.activatedRoute,
|
relativeTo: this.activatedRoute,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<bit-layout variant="secondary">
|
<bit-layout variant="secondary">
|
||||||
<nav slot="sidebar" *ngIf="provider">
|
<nav slot="sidebar" *ngIf="provider$ | async as provider">
|
||||||
<a routerLink="." class="tw-m-5 tw-mt-7 tw-block" [appA11yTitle]="'providerPortal' | i18n">
|
<a routerLink="." class="tw-m-5 tw-mt-7 tw-block" [appA11yTitle]="'providerPortal' | i18n">
|
||||||
<bit-icon [icon]="logo"></bit-icon>
|
<bit-icon [icon]="logo"></bit-icon>
|
||||||
</a>
|
</a>
|
||||||
@@ -7,14 +7,14 @@
|
|||||||
<bit-nav-item
|
<bit-nav-item
|
||||||
icon="bwi-bank"
|
icon="bwi-bank"
|
||||||
[text]="'clients' | i18n"
|
[text]="'clients' | i18n"
|
||||||
[route]="
|
[route]="(canAccessBilling$ | async) ? 'manage-client-organizations' : 'clients'"
|
||||||
(enableConsolidatedBilling$ | async) &&
|
|
||||||
provider.providerStatus === ProviderStatusType.Billable
|
|
||||||
? 'manage-client-organizations'
|
|
||||||
: 'clients'
|
|
||||||
"
|
|
||||||
></bit-nav-item>
|
></bit-nav-item>
|
||||||
<bit-nav-group icon="bwi-sliders" [text]="'manage' | i18n" route="manage" *ngIf="showManageTab">
|
<bit-nav-group
|
||||||
|
icon="bwi-sliders"
|
||||||
|
[text]="'manage' | i18n"
|
||||||
|
route="manage"
|
||||||
|
*ngIf="showManageTab(provider)"
|
||||||
|
>
|
||||||
<bit-nav-item
|
<bit-nav-item
|
||||||
[text]="'people' | i18n"
|
[text]="'people' | i18n"
|
||||||
route="manage/people"
|
route="manage/people"
|
||||||
@@ -26,13 +26,20 @@
|
|||||||
*ngIf="provider.useEvents"
|
*ngIf="provider.useEvents"
|
||||||
></bit-nav-item>
|
></bit-nav-item>
|
||||||
</bit-nav-group>
|
</bit-nav-group>
|
||||||
|
<bit-nav-group
|
||||||
|
icon="bwi-billing"
|
||||||
|
[text]="'billing' | i18n"
|
||||||
|
route="billing"
|
||||||
|
*ngIf="canAccessBilling$ | async"
|
||||||
|
>
|
||||||
|
<bit-nav-item [text]="'subscription' | i18n" route="billing/subscription"></bit-nav-item>
|
||||||
|
</bit-nav-group>
|
||||||
<bit-nav-item
|
<bit-nav-item
|
||||||
icon="bwi-cogs"
|
icon="bwi-cogs"
|
||||||
[text]="'settings' | i18n"
|
[text]="'settings' | i18n"
|
||||||
route="settings"
|
route="settings"
|
||||||
*ngIf="showSettingsTab"
|
*ngIf="showSettingsTab(provider)"
|
||||||
></bit-nav-item>
|
></bit-nav-item>
|
||||||
|
|
||||||
<app-toggle-width></app-toggle-width>
|
<app-toggle-width></app-toggle-width>
|
||||||
</nav>
|
</nav>
|
||||||
<app-payment-method-warnings
|
<app-payment-method-warnings
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { ActivatedRoute, RouterModule } from "@angular/router";
|
import { ActivatedRoute, RouterModule } from "@angular/router";
|
||||||
|
import { switchMap, Observable, Subject, filter, startWith } from "rxjs";
|
||||||
|
import { takeUntil } from "rxjs/operators";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { ProviderStatusType } 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 { canAccessBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components";
|
import { IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components";
|
||||||
@@ -28,21 +30,17 @@ import { ToggleWidthComponent } from "@bitwarden/web-vault/app/layouts/toggle-wi
|
|||||||
ToggleWidthComponent,
|
ToggleWidthComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
||||||
export class ProvidersLayoutComponent {
|
|
||||||
protected readonly logo = ProviderPortalLogo;
|
protected readonly logo = ProviderPortalLogo;
|
||||||
|
|
||||||
provider: Provider;
|
private destroy$ = new Subject<void>();
|
||||||
private providerId: string;
|
protected provider$: Observable<Provider>;
|
||||||
|
protected canAccessBilling$: Observable<boolean>;
|
||||||
|
|
||||||
protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$(
|
protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$(
|
||||||
FeatureFlag.ShowPaymentMethodWarningBanners,
|
FeatureFlag.ShowPaymentMethodWarningBanners,
|
||||||
);
|
);
|
||||||
|
|
||||||
protected enableConsolidatedBilling$ = this.configService.getFeatureFlag$(
|
|
||||||
FeatureFlag.EnableConsolidatedBilling,
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private providerService: ProviderService,
|
private providerService: ProviderService,
|
||||||
@@ -51,37 +49,30 @@ export class ProvidersLayoutComponent {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
document.body.classList.remove("layout_frontend");
|
document.body.classList.remove("layout_frontend");
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
|
||||||
this.route.params.subscribe(async (params) => {
|
this.provider$ = this.route.params.pipe(
|
||||||
this.providerId = params.providerId;
|
switchMap((params) => this.providerService.get$(params.providerId)),
|
||||||
await this.load();
|
takeUntil(this.destroy$),
|
||||||
});
|
);
|
||||||
|
|
||||||
|
this.canAccessBilling$ = this.provider$.pipe(
|
||||||
|
filter((provider) => !!provider),
|
||||||
|
canAccessBilling(this.configService),
|
||||||
|
startWith(false),
|
||||||
|
takeUntil(this.destroy$),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
ngOnDestroy() {
|
||||||
this.provider = await this.providerService.get(this.providerId);
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
get showMenuBar() {
|
showManageTab(provider: Provider) {
|
||||||
return this.showManageTab || this.showSettingsTab;
|
return provider.canManageUsers || provider.canAccessEventLogs;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showManageTab() {
|
showSettingsTab(provider: Provider) {
|
||||||
return this.provider.canManageUsers || this.provider.canAccessEventLogs;
|
return provider.isProviderAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
get showSettingsTab() {
|
|
||||||
return this.provider.isProviderAdmin;
|
|
||||||
}
|
|
||||||
|
|
||||||
get manageRoute(): string {
|
|
||||||
switch (true) {
|
|
||||||
case this.provider.canManageUsers:
|
|
||||||
return "manage/people";
|
|
||||||
case this.provider.canAccessEventLogs:
|
|
||||||
return "manage/events";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly ProviderStatusType = ProviderStatusType;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { ProvidersComponent } from "@bitwarden/web-vault/app/admin-console/provi
|
|||||||
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 { ManageClientOrganizationsComponent } from "../../billing/providers/clients/manage-client-organizations.component";
|
import { ProviderSubscriptionComponent, hasConsolidatedBilling } from "../../billing/providers";
|
||||||
|
import { ManageClientOrganizationsComponent } from "../../billing/providers/clients";
|
||||||
|
|
||||||
import { ClientsComponent } from "./clients/clients.component";
|
import { ClientsComponent } from "./clients/clients.component";
|
||||||
import { CreateOrganizationComponent } from "./clients/create-organization.component";
|
import { CreateOrganizationComponent } from "./clients/create-organization.component";
|
||||||
@@ -68,6 +69,7 @@ const routes: Routes = [
|
|||||||
{ path: "clients", component: ClientsComponent, data: { titleId: "clients" } },
|
{ path: "clients", component: ClientsComponent, data: { titleId: "clients" } },
|
||||||
{
|
{
|
||||||
path: "manage-client-organizations",
|
path: "manage-client-organizations",
|
||||||
|
canActivate: [hasConsolidatedBilling],
|
||||||
component: ManageClientOrganizationsComponent,
|
component: ManageClientOrganizationsComponent,
|
||||||
data: { titleId: "clients" },
|
data: { titleId: "clients" },
|
||||||
},
|
},
|
||||||
@@ -99,6 +101,25 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "billing",
|
||||||
|
canActivate: [hasConsolidatedBilling],
|
||||||
|
data: { providerPermissions: (provider: Provider) => provider.isProviderAdmin },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
pathMatch: "full",
|
||||||
|
redirectTo: "subscription",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "subscription",
|
||||||
|
component: ProviderSubscriptionComponent,
|
||||||
|
data: {
|
||||||
|
titleId: "subscription",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "settings",
|
path: "settings",
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { OrganizationPlansComponent, TaxInfoComponent } from "@bitwarden/web-vau
|
|||||||
import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared";
|
import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared";
|
||||||
import { OssModule } from "@bitwarden/web-vault/app/oss.module";
|
import { OssModule } from "@bitwarden/web-vault/app/oss.module";
|
||||||
|
|
||||||
|
import { ProviderSubscriptionComponent } from "../../billing/providers";
|
||||||
import {
|
import {
|
||||||
CreateClientOrganizationComponent,
|
CreateClientOrganizationComponent,
|
||||||
ManageClientOrganizationSubscriptionComponent,
|
ManageClientOrganizationSubscriptionComponent,
|
||||||
@@ -62,6 +63,7 @@ import { SetupComponent } from "./setup/setup.component";
|
|||||||
CreateClientOrganizationComponent,
|
CreateClientOrganizationComponent,
|
||||||
ManageClientOrganizationsComponent,
|
ManageClientOrganizationsComponent,
|
||||||
ManageClientOrganizationSubscriptionComponent,
|
ManageClientOrganizationSubscriptionComponent,
|
||||||
|
ProviderSubscriptionComponent,
|
||||||
],
|
],
|
||||||
providers: [WebProviderService, ProviderPermissionsGuard],
|
providers: [WebProviderService, ProviderPermissionsGuard],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { combineLatest, firstValueFrom, from, lastValueFrom } from "rxjs";
|
import { firstValueFrom, from, lastValueFrom, map } from "rxjs";
|
||||||
import { concatMap, switchMap, takeUntil } from "rxjs/operators";
|
import { switchMap, takeUntil } from "rxjs/operators";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||||
import { ProviderStatusType, 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 { 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 as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
||||||
|
import { canAccessBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction";
|
||||||
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
@@ -36,10 +36,6 @@ export class ManageClientOrganizationsComponent extends BaseClientsComponent {
|
|||||||
loading = true;
|
loading = true;
|
||||||
manageOrganizations = false;
|
manageOrganizations = false;
|
||||||
|
|
||||||
private consolidatedBillingEnabled$ = this.configService.getFeatureFlag$(
|
|
||||||
FeatureFlag.EnableConsolidatedBilling,
|
|
||||||
);
|
|
||||||
|
|
||||||
protected plans: PlanResponse[];
|
protected plans: PlanResponse[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -72,23 +68,16 @@ export class ManageClientOrganizationsComponent extends BaseClientsComponent {
|
|||||||
.pipe(
|
.pipe(
|
||||||
switchMap((params) => {
|
switchMap((params) => {
|
||||||
this.providerId = params.providerId;
|
this.providerId = params.providerId;
|
||||||
return combineLatest([
|
return this.providerService.get$(this.providerId).pipe(
|
||||||
this.providerService.get(this.providerId),
|
canAccessBilling(this.configService),
|
||||||
this.consolidatedBillingEnabled$,
|
map((canAccessBilling) => {
|
||||||
]).pipe(
|
if (!canAccessBilling) {
|
||||||
concatMap(([provider, consolidatedBillingEnabled]) => {
|
|
||||||
if (
|
|
||||||
!consolidatedBillingEnabled ||
|
|
||||||
provider.providerStatus !== ProviderStatusType.Billable
|
|
||||||
) {
|
|
||||||
return from(
|
return from(
|
||||||
this.router.navigate(["../clients"], {
|
this.router.navigate(["../clients"], {
|
||||||
relativeTo: this.activatedRoute,
|
relativeTo: this.activatedRoute,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.provider = provider;
|
|
||||||
this.manageOrganizations = this.provider.type === ProviderUserType.ProviderAdmin;
|
|
||||||
return from(this.load());
|
return from(this.load());
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -104,6 +93,10 @@ export class ManageClientOrganizationsComponent extends BaseClientsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
|
this.provider = await firstValueFrom(this.providerService.get$(this.providerId));
|
||||||
|
|
||||||
|
this.manageOrganizations = this.provider.type === ProviderUserType.ProviderAdmin;
|
||||||
|
|
||||||
this.clients = (await this.apiService.getProviderClients(this.providerId)).data;
|
this.clients = (await this.apiService.getProviderClients(this.providerId)).data;
|
||||||
|
|
||||||
this.dataSource.data = this.clients;
|
this.dataSource.data = this.clients;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
|
|
||||||
|
export const hasConsolidatedBilling: CanActivateFn = async (route: ActivatedRouteSnapshot) => {
|
||||||
|
const configService = inject(ConfigService);
|
||||||
|
const providerService = inject(ProviderService);
|
||||||
|
|
||||||
|
const provider = await firstValueFrom(providerService.get$(route.params.providerId));
|
||||||
|
|
||||||
|
const consolidatedBillingEnabled = await configService.getFeatureFlag(
|
||||||
|
FeatureFlag.EnableConsolidatedBilling,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!consolidatedBillingEnabled ||
|
||||||
|
!provider ||
|
||||||
|
!provider.isProviderAdmin ||
|
||||||
|
provider.providerStatus !== ProviderStatusType.Billable
|
||||||
|
) {
|
||||||
|
return createUrlTreeFromSnapshot(route, ["/providers", route.params.providerId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./guards/has-consolidated-billing.guard";
|
||||||
|
export * from "./provider-subscription.component";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<app-header></app-header>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-provider-subscription",
|
||||||
|
templateUrl: "./provider-subscription.component.html",
|
||||||
|
})
|
||||||
|
export class ProviderSubscriptionComponent {}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { UserId } from "../../types/guid";
|
import { UserId } from "../../types/guid";
|
||||||
import { ProviderData } from "../models/data/provider.data";
|
import { ProviderData } from "../models/data/provider.data";
|
||||||
import { Provider } from "../models/domain/provider";
|
import { Provider } from "../models/domain/provider";
|
||||||
|
|
||||||
export abstract class ProviderService {
|
export abstract class ProviderService {
|
||||||
|
get$: (id: string) => Observable<Provider>;
|
||||||
get: (id: string) => Promise<Provider>;
|
get: (id: string) => Promise<Provider>;
|
||||||
getAll: () => Promise<Provider[]>;
|
getAll: () => Promise<Provider[]>;
|
||||||
save: (providers: { [id: string]: ProviderData }, userId?: UserId) => Promise<any>;
|
save: (providers: { [id: string]: ProviderData }, userId?: UserId) => Promise<any>;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec";
|
import { FakeAccountService, FakeStateProvider, mockAccountServiceWith } from "../../../spec";
|
||||||
import { FakeActiveUserState, FakeSingleUserState } from "../../../spec/fake-state";
|
import { FakeActiveUserState, FakeSingleUserState } from "../../../spec/fake-state";
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
@@ -86,6 +88,7 @@ describe("ProviderService", () => {
|
|||||||
fakeStateProvider = new FakeStateProvider(fakeAccountService);
|
fakeStateProvider = new FakeStateProvider(fakeAccountService);
|
||||||
fakeUserState = fakeStateProvider.singleUser.getFake(fakeUserId, PROVIDERS);
|
fakeUserState = fakeStateProvider.singleUser.getFake(fakeUserId, PROVIDERS);
|
||||||
fakeActiveUserState = fakeStateProvider.activeUser.getFake(PROVIDERS);
|
fakeActiveUserState = fakeStateProvider.activeUser.getFake(PROVIDERS);
|
||||||
|
|
||||||
providerService = new ProviderService(fakeStateProvider);
|
providerService = new ProviderService(fakeStateProvider);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -106,6 +109,22 @@ describe("ProviderService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("get$()", () => {
|
||||||
|
it("Returns an observable of a single provider from state that matches the specified id", async () => {
|
||||||
|
const mockData = buildMockProviders(5);
|
||||||
|
fakeUserState.nextState(arrayToRecord(mockData));
|
||||||
|
const result = providerService.get$(mockData[3].id);
|
||||||
|
const provider = await firstValueFrom(result);
|
||||||
|
expect(provider).toEqual(new Provider(mockData[3]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Returns an observable of undefined if the specified provider is not found", async () => {
|
||||||
|
const result = providerService.get$("this-provider-does-not-exist");
|
||||||
|
const provider = await firstValueFrom(result);
|
||||||
|
expect(provider).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("get()", () => {
|
describe("get()", () => {
|
||||||
it("Returns a single provider from state that matches the specified id", async () => {
|
it("Returns a single provider from state that matches the specified id", async () => {
|
||||||
const mockData = buildMockProviders(5);
|
const mockData = buildMockProviders(5);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Observable, map, firstValueFrom, of, switchMap, take } from "rxjs";
|
import { firstValueFrom, map, Observable, of, switchMap, take } from "rxjs";
|
||||||
|
|
||||||
import { UserKeyDefinition, PROVIDERS_DISK, StateProvider } from "../../platform/state";
|
import { PROVIDERS_DISK, StateProvider, UserKeyDefinition } from "../../platform/state";
|
||||||
import { UserId } from "../../types/guid";
|
import { UserId } from "../../types/guid";
|
||||||
import { ProviderService as ProviderServiceAbstraction } from "../abstractions/provider.service";
|
import { ProviderService as ProviderServiceAbstraction } from "../abstractions/provider.service";
|
||||||
import { ProviderData } from "../models/data/provider.data";
|
import { ProviderData } from "../models/data/provider.data";
|
||||||
@@ -38,6 +38,10 @@ export class ProviderService implements ProviderServiceAbstraction {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get$(id: string): Observable<Provider> {
|
||||||
|
return this.providers$().pipe(mapToSingleProvider(id));
|
||||||
|
}
|
||||||
|
|
||||||
async get(id: string): Promise<Provider> {
|
async get(id: string): Promise<Provider> {
|
||||||
return await firstValueFrom(this.providers$().pipe(mapToSingleProvider(id)));
|
return await firstValueFrom(this.providers$().pipe(mapToSingleProvider(id)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { map, Observable, OperatorFunction, switchMap } from "rxjs";
|
||||||
|
|
||||||
|
import { ProviderStatusType } from "@bitwarden/common/admin-console/enums";
|
||||||
|
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
|
|
||||||
|
type MaybeProvider = Provider | undefined;
|
||||||
|
|
||||||
|
export const canAccessBilling = (
|
||||||
|
configService: ConfigService,
|
||||||
|
): OperatorFunction<MaybeProvider, boolean> =>
|
||||||
|
switchMap<MaybeProvider, Observable<boolean>>((provider) =>
|
||||||
|
configService
|
||||||
|
.getFeatureFlag$(FeatureFlag.EnableConsolidatedBilling)
|
||||||
|
.pipe(
|
||||||
|
map((consolidatedBillingEnabled) =>
|
||||||
|
provider
|
||||||
|
? provider.isProviderAdmin &&
|
||||||
|
provider.providerStatus === ProviderStatusType.Billable &&
|
||||||
|
consolidatedBillingEnabled
|
||||||
|
: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user