1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-04 17:43:39 +00:00

[PM-23513] Complete usage of OrganizationWarningsService (#16272)

* Use OrganizationWarningsService in AC VaultComponent

* Use OrganizationWarningsService in OrgSwitcherComponent

* Use OrganizationWarningsService in VaultFilterComponent

* Use OrganizationWarningsService in VaultComponent

* Use OrganizationWarningsService in SM OverviewComponent

* Remove TrialFlowService from unused codepaths

* Remove TrialFlowService

* Refresh free trial warning on standard payment method update

* Fix lint errors

* Fix lint errors

* Remove FF

* Fix free trial banner on deprecated ac vault component
This commit is contained in:
Alex Morask
2025-09-19 11:26:48 -05:00
committed by GitHub
parent 5253b3a94d
commit d8339f0196
27 changed files with 109 additions and 869 deletions

View File

@@ -1,45 +1,12 @@
<app-organization-free-trial-warning
*ngIf="useOrganizationWarningsService$ | async"
[organization]="organization"
(clicked)="navigateToPaymentMethod()"
>
</app-organization-free-trial-warning>
<app-organization-reseller-renewal-warning
*ngIf="useOrganizationWarningsService$ | async"
[organization]="organization"
>
</app-organization-reseller-renewal-warning>
<ng-container *ngIf="freeTrialWhenWarningsServiceDisabled$ | async as freeTrial">
<bit-banner
id="free-trial-banner"
icon="bwi-billing"
bannerType="premium"
[showClose]="false"
*ngIf="!refreshing && freeTrial.shownBanner"
@if (organization) {
<app-organization-free-trial-warning
[organization]="organization"
(clicked)="navigateToPaymentMethod()"
>
{{ freeTrial.message }}
<a
bitLink
linkType="secondary"
(click)="navigateToPaymentMethod()"
class="tw-cursor-pointer"
rel="noreferrer noopener"
>
{{ "clickHereToAddPaymentMethod" | i18n }}
</a>
</bit-banner>
</ng-container>
<ng-container *ngIf="resellerWarningWhenWarningsServiceDisabled$ | async as resellerWarning">
<bit-banner
id="reseller-warning-banner"
icon="bwi-billing"
bannerType="info"
[showClose]="false"
*ngIf="!refreshing"
>
{{ resellerWarning?.message }}
</bit-banner>
</ng-container>
</app-organization-free-trial-warning>
<app-organization-reseller-renewal-warning [organization]="organization">
</app-organization-reseller-renewal-warning>
}
<app-org-vault-header
[filter]="filter"

View File

@@ -6,14 +6,12 @@ import {
BehaviorSubject,
combineLatest,
firstValueFrom,
from,
lastValueFrom,
merge,
Observable,
of,
Subject,
} from "rxjs";
import {
catchError,
concatMap,
debounceTime,
distinctUntilChanged,
@@ -37,12 +35,10 @@ import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { NoResults } from "@bitwarden/assets/svg";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.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 { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@@ -86,13 +82,6 @@ import {
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
import { VaultItemsComponent } from "@bitwarden/web-vault/app/vault/components/vault-items/vault-items.component";
import { BillingNotificationService } from "../../../billing/services/billing-notification.service";
import {
ResellerWarning,
ResellerWarningService,
} from "../../../billing/services/reseller-warning.service";
import { TrialFlowService } from "../../../billing/services/trial-flow.service";
import { FreeTrial } from "../../../billing/types/free-trial";
import { SharedModule } from "../../../shared";
import { AssignCollectionsWebComponent } from "../../../vault/components/assign-collections";
import {
@@ -183,11 +172,7 @@ export class VaultComponent implements OnInit, OnDestroy {
protected selectedCollection: TreeNode<CollectionAdminView> | undefined;
protected isEmpty: boolean;
protected showCollectionAccessRestricted: boolean;
private hasSubscription$ = new BehaviorSubject<boolean>(false);
protected currentSearchText$: Observable<string>;
protected useOrganizationWarningsService$: Observable<boolean>;
protected freeTrialWhenWarningsServiceDisabled$: Observable<FreeTrial>;
protected resellerWarningWhenWarningsServiceDisabled$: Observable<ResellerWarning | null>;
protected prevCipherId: string | null = null;
protected userId: UserId;
/**
@@ -209,31 +194,6 @@ export class VaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultItems", { static: false }) vaultItemsComponent: VaultItemsComponent<CipherView>;
private readonly unpaidSubscriptionDialog$ = this.accountService.activeAccount$.pipe(
map((account) => account?.id),
switchMap((id) =>
this.organizationService.organizations$(id).pipe(
filter((organizations) => organizations.length === 1),
map(([organization]) => organization),
switchMap((organization) =>
from(this.billingApiService.getOrganizationBillingMetadata(organization.id)).pipe(
tap((organizationMetaData) => {
this.hasSubscription$.next(organizationMetaData.hasSubscription);
}),
switchMap((organizationMetaData) =>
from(
this.trialFlowService.handleUnpaidSubscriptionDialog(
organization,
organizationMetaData,
),
),
),
),
),
),
),
);
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
@@ -262,13 +222,8 @@ export class VaultComponent implements OnInit, OnDestroy {
private toastService: ToastService,
private configService: ConfigService,
private cipherFormConfigService: CipherFormConfigService,
private organizationApiService: OrganizationApiServiceAbstraction,
private trialFlowService: TrialFlowService,
protected billingApiService: BillingApiServiceAbstraction,
private organizationBillingService: OrganizationBillingServiceAbstraction,
private resellerWarningService: ResellerWarningService,
private accountService: AccountService,
private billingNotificationService: BillingNotificationService,
private organizationWarningsService: OrganizationWarningsService,
private collectionService: CollectionService,
) {}
@@ -661,74 +616,17 @@ export class VaultComponent implements OnInit, OnDestroy {
.subscribe();
// Billing Warnings
this.useOrganizationWarningsService$ = this.configService.getFeatureFlag$(
FeatureFlag.UseOrganizationWarningsService,
);
this.useOrganizationWarningsService$
.pipe(
switchMap((enabled) =>
enabled
? this.organizationWarningsService.showInactiveSubscriptionDialog$(this.organization)
: this.unpaidSubscriptionDialog$,
),
takeUntil(this.destroy$),
)
.subscribe();
organization$
.pipe(
switchMap((organization) =>
this.organizationWarningsService.showSubscribeBeforeFreeTrialEndsDialog$(organization),
merge(
this.organizationWarningsService.showInactiveSubscriptionDialog$(organization),
this.organizationWarningsService.showSubscribeBeforeFreeTrialEndsDialog$(organization),
),
),
takeUntil(this.destroy$),
)
.subscribe();
const freeTrial$ = combineLatest([
organization$,
this.hasSubscription$.pipe(filter((hasSubscription) => hasSubscription !== null)),
]).pipe(
filter(
([org, hasSubscription]) => org.isOwner && hasSubscription && org.canViewBillingHistory,
),
switchMap(([org]) =>
combineLatest([
of(org),
this.organizationApiService.getSubscription(org.id),
from(this.organizationBillingService.getPaymentSource(org.id)).pipe(
catchError((error: unknown) => {
this.billingNotificationService.handleError(error);
return of(null);
}),
),
]),
),
map(([org, sub, paymentSource]) =>
this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource),
),
filter((result) => result !== null),
);
this.freeTrialWhenWarningsServiceDisabled$ = this.useOrganizationWarningsService$.pipe(
filter((enabled) => !enabled),
switchMap(() => freeTrial$),
);
const resellerWarning$ = organization$.pipe(
filter((org) => org.isOwner),
switchMap((org) =>
from(this.billingApiService.getOrganizationBillingMetadata(org.id)).pipe(
map((metadata) => ({ org, metadata })),
),
),
map(({ org, metadata }) => this.resellerWarningService.getWarning(org, metadata)),
);
this.resellerWarningWhenWarningsServiceDisabled$ = this.useOrganizationWarningsService$.pipe(
filter((enabled) => !enabled),
switchMap(() => resellerWarning$),
);
// End Billing Warnings
firstSetup$

View File

@@ -5,47 +5,16 @@
@let loading = loading$ | async;
@if (organization) {
@if (useOrganizationWarningsService$ | async) {
@if (!refreshing) {
<app-organization-free-trial-warning
[organization]="organization"
(clicked)="navigateToPaymentMethod()"
>
</app-organization-free-trial-warning>
}
@if (useOrganizationWarningsService$ | async) {
<app-organization-reseller-renewal-warning [organization]="organization">
</app-organization-reseller-renewal-warning>
}
@let freeTrial = freeTrialWhenWarningsServiceDisabled$ | async;
@if (!refreshing && freeTrial?.shownBanner) {
<bit-banner id="free-trial-banner" icon="bwi-billing" bannerType="premium" [showClose]="false">
{{ freeTrial.message }}
<a
bitLink
linkType="secondary"
(click)="navigateToPaymentMethod()"
class="tw-cursor-pointer"
rel="noreferrer noopener"
>
{{ "clickHereToAddPaymentMethod" | i18n }}
</a>
</bit-banner>
}
@let resellerWarning = resellerWarningWhenWarningsServiceDisabled$ | async;
@if (!refreshing && resellerWarning) {
<bit-banner
id="reseller-warning-banner"
icon="bwi-billing"
bannerType="info"
[showClose]="false"
>
{{ resellerWarning?.message }}
</bit-banner>
}
@if (filter) {
<app-org-vault-header
[filter]="filter"

View File

@@ -5,8 +5,8 @@ import {
BehaviorSubject,
combineLatest,
firstValueFrom,
from,
lastValueFrom,
merge,
Observable,
of,
Subject,
@@ -25,7 +25,6 @@ import {
switchMap,
take,
takeUntil,
tap,
} from "rxjs/operators";
import {
@@ -39,12 +38,10 @@ import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
import { NoResults } from "@bitwarden/assets/svg";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.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 { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@@ -94,13 +91,6 @@ import {
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
import { VaultItemsComponent } from "@bitwarden/web-vault/app/vault/components/vault-items/vault-items.component";
import { BillingNotificationService } from "../../../billing/services/billing-notification.service";
import {
ResellerWarning,
ResellerWarningService,
} from "../../../billing/services/reseller-warning.service";
import { TrialFlowService } from "../../../billing/services/trial-flow.service";
import { FreeTrial } from "../../../billing/types/free-trial";
import { SharedModule } from "../../../shared";
import { AssignCollectionsWebComponent } from "../../../vault/components/assign-collections";
import {
@@ -194,10 +184,6 @@ export class vNextVaultComponent implements OnInit, OnDestroy {
protected showCollectionAccessRestricted$: Observable<boolean>;
protected isEmpty$: Observable<boolean> = of(false);
private hasSubscription$ = new BehaviorSubject<boolean>(false);
protected useOrganizationWarningsService$: Observable<boolean>;
protected freeTrialWhenWarningsServiceDisabled$: Observable<FreeTrial>;
protected resellerWarningWhenWarningsServiceDisabled$: Observable<ResellerWarning | null>;
protected prevCipherId: string | null = null;
protected userId$: Observable<UserId>;
@@ -227,31 +213,6 @@ export class vNextVaultComponent implements OnInit, OnDestroy {
| VaultItemsComponent<CipherView>
| undefined;
private readonly unpaidSubscriptionDialog$ = this.accountService.activeAccount$.pipe(
getUserId,
switchMap((id) =>
this.organizationService.organizations$(id).pipe(
filter((organizations) => organizations.length === 1),
map(([organization]) => organization),
switchMap((organization) =>
from(this.billingApiService.getOrganizationBillingMetadata(organization.id)).pipe(
tap((organizationMetaData) => {
this.hasSubscription$.next(organizationMetaData.hasSubscription);
}),
switchMap((organizationMetaData) =>
from(
this.trialFlowService.handleUnpaidSubscriptionDialog(
organization,
organizationMetaData,
),
),
),
),
),
),
),
);
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
@@ -280,13 +241,8 @@ export class vNextVaultComponent implements OnInit, OnDestroy {
private toastService: ToastService,
private configService: ConfigService,
private cipherFormConfigService: CipherFormConfigService,
private organizationApiService: OrganizationApiServiceAbstraction,
private trialFlowService: TrialFlowService,
protected billingApiService: BillingApiServiceAbstraction,
private organizationBillingService: OrganizationBillingServiceAbstraction,
private resellerWarningService: ResellerWarningService,
private accountService: AccountService,
private billingNotificationService: BillingNotificationService,
private organizationWarningsService: OrganizationWarningsService,
private collectionService: CollectionService,
private restrictedItemTypesService: RestrictedItemTypesService,
@@ -461,68 +417,17 @@ export class vNextVaultComponent implements OnInit, OnDestroy {
);
// Billing Warnings
this.useOrganizationWarningsService$ = this.configService.getFeatureFlag$(
FeatureFlag.UseOrganizationWarningsService,
);
const freeTrial$ = combineLatest([
this.organization$,
this.hasSubscription$.pipe(filter((hasSubscription) => hasSubscription !== null)),
]).pipe(
filter(
([org, hasSubscription]) => org.isOwner && hasSubscription && org.canViewBillingHistory,
),
switchMap(([org]) =>
combineLatest([
of(org),
this.organizationApiService.getSubscription(org.id),
from(this.organizationBillingService.getPaymentSource(org.id)).pipe(
map((paymentSource) => {
if (paymentSource == null) {
throw new Error("Payment source not found.");
}
return paymentSource;
}),
),
]),
),
map(([org, sub, paymentSource]) =>
this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource),
),
filter((result) => result !== null),
catchError((error: unknown) => {
this.billingNotificationService.handleError(error);
return of();
}),
);
this.freeTrialWhenWarningsServiceDisabled$ = this.useOrganizationWarningsService$.pipe(
filter((enabled) => !enabled),
switchMap(() => freeTrial$),
);
this.resellerWarningWhenWarningsServiceDisabled$ = combineLatest([
this.organization$,
this.useOrganizationWarningsService$,
]).pipe(
filter(([org, enabled]) => !enabled && org.isOwner),
switchMap(([org]) =>
from(this.billingApiService.getOrganizationBillingMetadata(org.id)).pipe(
map((metadata) => ({ org, metadata })),
),
),
map(({ org, metadata }) => this.resellerWarningService.getWarning(org, metadata)),
);
this.organization$
.pipe(
switchMap((organization) =>
this.organizationWarningsService.showSubscribeBeforeFreeTrialEndsDialog$(organization),
merge(
this.organizationWarningsService.showInactiveSubscriptionDialog$(organization),
this.organizationWarningsService.showSubscribeBeforeFreeTrialEndsDialog$(organization),
),
),
takeUntilDestroyed(),
)
.subscribe();
// End Billing Warnings
this.editableCollections$ = combineLatest([
@@ -781,17 +686,6 @@ export class vNextVaultComponent implements OnInit, OnDestroy {
)
.subscribe();
combineLatest([this.useOrganizationWarningsService$, this.organization$])
.pipe(
switchMap(([enabled, organization]) =>
enabled
? this.organizationWarningsService.showInactiveSubscriptionDialog$(organization)
: this.unpaidSubscriptionDialog$,
),
takeUntil(this.destroy$),
)
.subscribe();
// Handle last of initial setup - workaround for some state issues where we need to manually
// push the collections we've loaded back into the VaultFilterService.
// FIXME: figure out how we can remove this.