mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +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:
@@ -1,45 +1,12 @@
|
||||
<app-organization-free-trial-warning
|
||||
*ngIf="useOrganizationWarningsService$ | async"
|
||||
@if (organization) {
|
||||
<app-organization-free-trial-warning
|
||||
[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"
|
||||
>
|
||||
{{ 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"
|
||||
|
||||
@@ -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) =>
|
||||
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$
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) =>
|
||||
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.
|
||||
|
||||
@@ -243,7 +243,6 @@ export class OrganizationPaymentDetailsComponent implements OnInit, OnDestroy {
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result?.type === "success") {
|
||||
await this.setPaymentMethod(result.paymentMethod);
|
||||
this.organizationWarningsService.refreshFreeTrialWarning();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -264,6 +263,10 @@ export class OrganizationPaymentDetailsComponent implements OnInit, OnDestroy {
|
||||
|
||||
setPaymentMethod = async (paymentMethod: MaskedPaymentMethod) => {
|
||||
if (this.viewState$.value) {
|
||||
if (!this.viewState$.value.paymentMethod) {
|
||||
this.organizationWarningsService.refreshFreeTrialWarning();
|
||||
}
|
||||
|
||||
const billingAddress =
|
||||
this.viewState$.value.billingAddress ??
|
||||
(await this.subscriberBillingClient.getBillingAddress(this.viewState$.value.organization));
|
||||
|
||||
@@ -1,21 +1,3 @@
|
||||
<bit-banner
|
||||
id="free-trial-banner"
|
||||
bannerType="premium"
|
||||
icon="bwi-billing"
|
||||
[showClose]="false"
|
||||
*ngIf="freeTrialData?.shownBanner"
|
||||
>
|
||||
{{ freeTrialData?.message }}
|
||||
<a
|
||||
bitLink
|
||||
linkType="secondary"
|
||||
(click)="changePayment()"
|
||||
class="tw-cursor-pointer"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{{ "clickHereToAddPaymentMethod" | i18n }}
|
||||
</a>
|
||||
</bit-banner>
|
||||
<app-header></app-header>
|
||||
<bit-container>
|
||||
<ng-container *ngIf="loading">
|
||||
|
||||
@@ -6,8 +6,8 @@ import { combineLatest, firstValueFrom, from, lastValueFrom, map, switchMap } fr
|
||||
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import {
|
||||
getOrganizationById,
|
||||
OrganizationService,
|
||||
getOrganizationById,
|
||||
} 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";
|
||||
@@ -25,7 +25,6 @@ import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { BillingNotificationService } from "../../services/billing-notification.service";
|
||||
import { TrialFlowService } from "../../services/trial-flow.service";
|
||||
import {
|
||||
AddCreditDialogResult,
|
||||
openAddCreditDialog,
|
||||
@@ -38,7 +37,6 @@ import {
|
||||
TRIAL_PAYMENT_METHOD_DIALOG_RESULT_TYPE,
|
||||
TrialPaymentDialogComponent,
|
||||
} from "../../shared/trial-payment-dialog/trial-payment-dialog.component";
|
||||
import { FreeTrial } from "../../types/free-trial";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./organization-payment-method.component.html",
|
||||
@@ -50,7 +48,6 @@ export class OrganizationPaymentMethodComponent implements OnDestroy {
|
||||
accountCredit?: number;
|
||||
paymentSource?: PaymentSourceResponse;
|
||||
subscriptionStatus?: string;
|
||||
protected freeTrialData?: FreeTrial;
|
||||
organization?: Organization;
|
||||
organizationSubscriptionResponse?: OrganizationSubscriptionResponse;
|
||||
|
||||
@@ -71,7 +68,6 @@ export class OrganizationPaymentMethodComponent implements OnDestroy {
|
||||
private router: Router,
|
||||
private toastService: ToastService,
|
||||
private location: Location,
|
||||
private trialFlowService: TrialFlowService,
|
||||
private organizationService: OrganizationService,
|
||||
private accountService: AccountService,
|
||||
protected syncService: SyncService,
|
||||
@@ -183,12 +179,6 @@ export class OrganizationPaymentMethodComponent implements OnDestroy {
|
||||
if (!this.paymentSource) {
|
||||
throw new Error("Payment source is not found");
|
||||
}
|
||||
|
||||
this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
|
||||
this.organization,
|
||||
this.organizationSubscriptionResponse,
|
||||
this.paymentSource,
|
||||
);
|
||||
}
|
||||
// If the flag `launchPaymentModalAutomatically` is set to true,
|
||||
// we schedule a timeout (delay of 800ms) to automatically launch the payment modal.
|
||||
|
||||
@@ -37,6 +37,7 @@ import { OrganizationFreeTrialWarning } from "../types";
|
||||
})
|
||||
export class OrganizationFreeTrialWarningComponent implements OnInit {
|
||||
@Input({ required: true }) organization!: Organization;
|
||||
@Input() includeOrganizationNameInMessaging = false;
|
||||
@Output() clicked = new EventEmitter<void>();
|
||||
|
||||
warning$!: Observable<OrganizationFreeTrialWarning | null>;
|
||||
@@ -44,6 +45,9 @@ export class OrganizationFreeTrialWarningComponent implements OnInit {
|
||||
constructor(private organizationWarningsService: OrganizationWarningsService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.warning$ = this.organizationWarningsService.getFreeTrialWarning$(this.organization);
|
||||
this.warning$ = this.organizationWarningsService.getFreeTrialWarning$(
|
||||
this.organization,
|
||||
this.includeOrganizationNameInMessaging,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ export class OrganizationWarningsService {
|
||||
|
||||
getFreeTrialWarning$ = (
|
||||
organization: Organization,
|
||||
includeOrganizationNameInMessaging = false,
|
||||
): Observable<OrganizationFreeTrialWarning | null> =>
|
||||
merge(
|
||||
this.getWarning$(organization, (response) => response.freeTrial),
|
||||
@@ -80,20 +81,30 @@ export class OrganizationWarningsService {
|
||||
if (remainingTrialDays >= 2) {
|
||||
return {
|
||||
organization,
|
||||
message: this.i18nService.t("freeTrialEndPromptCount", remainingTrialDays),
|
||||
message: includeOrganizationNameInMessaging
|
||||
? this.i18nService.t(
|
||||
"freeTrialEndPromptMultipleDays",
|
||||
organization.name,
|
||||
remainingTrialDays,
|
||||
)
|
||||
: this.i18nService.t("freeTrialEndPromptCount", remainingTrialDays),
|
||||
};
|
||||
}
|
||||
|
||||
if (remainingTrialDays == 1) {
|
||||
return {
|
||||
organization,
|
||||
message: this.i18nService.t("freeTrialEndPromptTomorrowNoOrgName"),
|
||||
message: includeOrganizationNameInMessaging
|
||||
? this.i18nService.t("freeTrialEndPromptTomorrow", organization.name)
|
||||
: this.i18nService.t("freeTrialEndPromptTomorrowNoOrgName"),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
organization,
|
||||
message: this.i18nService.t("freeTrialEndingTodayWithoutOrgName"),
|
||||
message: includeOrganizationNameInMessaging
|
||||
? this.i18nService.t("freeTrialEndPromptToday", organization.name)
|
||||
: this.i18nService.t("freeTrialEndingTodayWithoutOrgName"),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
export interface ResellerWarning {
|
||||
type: "info" | "warning";
|
||||
message: string;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class ResellerWarningService {
|
||||
private readonly RENEWAL_WARNING_DAYS = 14;
|
||||
private readonly GRACE_PERIOD_DAYS = 30;
|
||||
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
getWarning(
|
||||
organization: Organization,
|
||||
organizationBillingMetadata: OrganizationBillingMetadataResponse,
|
||||
): ResellerWarning | null {
|
||||
if (!organization.hasReseller) {
|
||||
return null; // If no reseller, return null immediately
|
||||
}
|
||||
|
||||
// Check for past due warning first (highest priority)
|
||||
if (this.shouldShowPastDueWarning(organizationBillingMetadata)) {
|
||||
const gracePeriodEnd = this.getGracePeriodEndDate(organizationBillingMetadata.invoiceDueDate);
|
||||
if (!gracePeriodEnd) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "warning",
|
||||
message: this.i18nService.t(
|
||||
"resellerPastDueWarningMsg",
|
||||
organization.providerName,
|
||||
this.formatDate(gracePeriodEnd),
|
||||
),
|
||||
} as ResellerWarning;
|
||||
}
|
||||
|
||||
// Check for open invoice warning
|
||||
if (this.shouldShowInvoiceWarning(organizationBillingMetadata)) {
|
||||
const invoiceCreatedDate = organizationBillingMetadata.invoiceCreatedDate;
|
||||
const invoiceDueDate = organizationBillingMetadata.invoiceDueDate;
|
||||
if (!invoiceCreatedDate || !invoiceDueDate) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "info",
|
||||
message: this.i18nService.t(
|
||||
"resellerOpenInvoiceWarningMgs",
|
||||
organization.providerName,
|
||||
this.formatDate(organizationBillingMetadata.invoiceCreatedDate),
|
||||
this.formatDate(organizationBillingMetadata.invoiceDueDate),
|
||||
),
|
||||
} as ResellerWarning;
|
||||
}
|
||||
|
||||
// Check for renewal warning
|
||||
if (this.shouldShowRenewalWarning(organizationBillingMetadata)) {
|
||||
const subPeriodEndDate = organizationBillingMetadata.subPeriodEndDate;
|
||||
if (!subPeriodEndDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: "info",
|
||||
message: this.i18nService.t(
|
||||
"resellerRenewalWarningMsg",
|
||||
organization.providerName,
|
||||
this.formatDate(organizationBillingMetadata.subPeriodEndDate),
|
||||
),
|
||||
} as ResellerWarning;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private shouldShowRenewalWarning(
|
||||
organizationBillingMetadata: OrganizationBillingMetadataResponse,
|
||||
): boolean {
|
||||
if (
|
||||
!organizationBillingMetadata.hasSubscription ||
|
||||
!organizationBillingMetadata.subPeriodEndDate
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const renewalDate = new Date(organizationBillingMetadata.subPeriodEndDate);
|
||||
const daysUntilRenewal = Math.ceil(
|
||||
(renewalDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24),
|
||||
);
|
||||
return daysUntilRenewal <= this.RENEWAL_WARNING_DAYS;
|
||||
}
|
||||
|
||||
private shouldShowInvoiceWarning(
|
||||
organizationBillingMetadata: OrganizationBillingMetadataResponse,
|
||||
): boolean {
|
||||
if (
|
||||
!organizationBillingMetadata.hasOpenInvoice ||
|
||||
!organizationBillingMetadata.invoiceDueDate
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const invoiceDueDate = new Date(organizationBillingMetadata.invoiceDueDate);
|
||||
return invoiceDueDate > new Date();
|
||||
}
|
||||
|
||||
private shouldShowPastDueWarning(
|
||||
organizationBillingMetadata: OrganizationBillingMetadataResponse,
|
||||
): boolean {
|
||||
if (
|
||||
!organizationBillingMetadata.hasOpenInvoice ||
|
||||
!organizationBillingMetadata.invoiceDueDate
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const invoiceDueDate = new Date(organizationBillingMetadata.invoiceDueDate);
|
||||
return invoiceDueDate <= new Date() && !organizationBillingMetadata.isSubscriptionUnpaid;
|
||||
}
|
||||
|
||||
private getGracePeriodEndDate(dueDate: Date | null): Date | null {
|
||||
if (!dueDate) {
|
||||
return null;
|
||||
}
|
||||
const gracePeriodEnd = new Date(dueDate);
|
||||
gracePeriodEnd.setDate(gracePeriodEnd.getDate() + this.GRACE_PERIOD_DAYS);
|
||||
return gracePeriodEnd;
|
||||
}
|
||||
|
||||
private formatDate(date: Date | null): string {
|
||||
if (!date) {
|
||||
return "N/A";
|
||||
}
|
||||
return new Date(date).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response";
|
||||
import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response";
|
||||
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
|
||||
import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import {
|
||||
ChangePlanDialogResultType,
|
||||
openChangePlanDialog,
|
||||
} from "../organizations/change-plan-dialog.component";
|
||||
import { FreeTrial } from "../types/free-trial";
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class TrialFlowService {
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
protected dialogService: DialogService,
|
||||
private router: Router,
|
||||
protected billingApiService: BillingApiServiceAbstraction,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
checkForOrgsWithUpcomingPaymentIssues(
|
||||
organization: Organization,
|
||||
organizationSubscription: OrganizationSubscriptionResponse,
|
||||
paymentSource: BillingSourceResponse | PaymentSourceResponse,
|
||||
): FreeTrial {
|
||||
const trialEndDate = organizationSubscription?.subscription?.trialEndDate;
|
||||
const displayBanner =
|
||||
!paymentSource &&
|
||||
organization?.isOwner &&
|
||||
organizationSubscription?.subscription?.status === "trialing";
|
||||
const trialRemainingDays = trialEndDate ? this.calculateTrialRemainingDays(trialEndDate) : 0;
|
||||
const freeTrialMessage = this.getFreeTrialMessage(trialRemainingDays);
|
||||
|
||||
return {
|
||||
remainingDays: trialRemainingDays,
|
||||
message: freeTrialMessage,
|
||||
shownBanner: displayBanner,
|
||||
organizationId: organization.id,
|
||||
organizationName: organization.name,
|
||||
};
|
||||
}
|
||||
|
||||
calculateTrialRemainingDays(trialEndDate: string): number | undefined {
|
||||
const today = new Date();
|
||||
const trialEnd = new Date(trialEndDate);
|
||||
const timeDifference = trialEnd.getTime() - today.getTime();
|
||||
|
||||
return Math.ceil(timeDifference / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
getFreeTrialMessage(trialRemainingDays: number): string {
|
||||
if (trialRemainingDays >= 2) {
|
||||
return this.i18nService.t("freeTrialEndPromptCount", trialRemainingDays);
|
||||
} else if (trialRemainingDays === 1) {
|
||||
return this.i18nService.t("freeTrialEndPromptTomorrowNoOrgName");
|
||||
} else {
|
||||
return this.i18nService.t("freeTrialEndingTodayWithoutOrgName");
|
||||
}
|
||||
}
|
||||
|
||||
async handleUnpaidSubscriptionDialog(
|
||||
org: Organization,
|
||||
organizationBillingMetadata: OrganizationBillingMetadataResponse,
|
||||
): Promise<void> {
|
||||
if (
|
||||
organizationBillingMetadata.isSubscriptionUnpaid ||
|
||||
organizationBillingMetadata.isSubscriptionCanceled
|
||||
) {
|
||||
const confirmed = await this.promptForPaymentNavigation(
|
||||
org,
|
||||
organizationBillingMetadata.isSubscriptionCanceled,
|
||||
organizationBillingMetadata.isSubscriptionUnpaid,
|
||||
);
|
||||
if (confirmed) {
|
||||
await this.navigateToPaymentMethod(org?.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async promptForPaymentNavigation(
|
||||
org: Organization,
|
||||
isCanceled: boolean,
|
||||
isUnpaid: boolean,
|
||||
): Promise<boolean> {
|
||||
if (!org?.isOwner && !org.providerId) {
|
||||
await this.dialogService.openSimpleDialog({
|
||||
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
|
||||
content: { key: "suspendedUserOrgMessage" },
|
||||
type: "danger",
|
||||
acceptButtonText: this.i18nService.t("close"),
|
||||
cancelButtonText: null,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (org.providerId) {
|
||||
await this.dialogService.openSimpleDialog({
|
||||
title: this.i18nService.t("suspendedOrganizationTitle", org.name),
|
||||
content: { key: "suspendedManagedOrgMessage", placeholders: [org.providerName] },
|
||||
type: "danger",
|
||||
acceptButtonText: this.i18nService.t("close"),
|
||||
cancelButtonText: null,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (org.isOwner && isUnpaid) {
|
||||
return await this.dialogService.openSimpleDialog({
|
||||
title: this.i18nService.t("suspendedOrganizationTitle", org.name),
|
||||
content: { key: "suspendedOwnerOrgMessage" },
|
||||
type: "danger",
|
||||
acceptButtonText: this.i18nService.t("continue"),
|
||||
cancelButtonText: this.i18nService.t("close"),
|
||||
});
|
||||
}
|
||||
|
||||
if (org.isOwner && isCanceled) {
|
||||
await this.changePlan(org);
|
||||
}
|
||||
}
|
||||
|
||||
private async navigateToPaymentMethod(orgId: string) {
|
||||
const managePaymentDetailsOutsideCheckout = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout,
|
||||
);
|
||||
const route = managePaymentDetailsOutsideCheckout ? "payment-details" : "payment-method";
|
||||
await this.router.navigate(["organizations", `${orgId}`, "billing", route], {
|
||||
state: { launchPaymentModalAutomatically: true },
|
||||
queryParams: { launchPaymentModalAutomatically: true },
|
||||
});
|
||||
}
|
||||
|
||||
private async changePlan(org: Organization) {
|
||||
const subscription = await this.organizationApiService.getSubscription(org.id);
|
||||
const reference = openChangePlanDialog(this.dialogService, {
|
||||
data: {
|
||||
organizationId: org.id,
|
||||
subscription: subscription,
|
||||
productTierType: org.productTierType,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(reference.closed);
|
||||
if (result === ChangePlanDialogResultType.Closed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,3 @@
|
||||
<bit-banner
|
||||
id="free-trial-banner"
|
||||
bannerType="premium"
|
||||
icon="bwi-billing"
|
||||
[showClose]="false"
|
||||
*ngIf="freeTrialData?.shownBanner"
|
||||
>
|
||||
{{ freeTrialData?.message }}
|
||||
<a
|
||||
bitLink
|
||||
linkType="secondary"
|
||||
(click)="changePayment()"
|
||||
class="tw-cursor-pointer"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{{ "clickHereToAddPaymentMethod" | i18n }}
|
||||
</a>
|
||||
</bit-banner>
|
||||
|
||||
<app-header *ngIf="organizationId">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -7,8 +7,8 @@ import { firstValueFrom, lastValueFrom, map } from "rxjs";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import {
|
||||
getOrganizationById,
|
||||
OrganizationService,
|
||||
getOrganizationById,
|
||||
} 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";
|
||||
@@ -24,9 +24,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { SyncService } from "@bitwarden/common/platform/sync";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { TrialFlowService } from "../services/trial-flow.service";
|
||||
import { FreeTrial } from "../types/free-trial";
|
||||
|
||||
import { AddCreditDialogResult, openAddCreditDialog } from "./add-credit-dialog.component";
|
||||
import {
|
||||
AdjustPaymentDialogComponent,
|
||||
@@ -62,8 +59,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
launchPaymentModalAutomatically = false;
|
||||
protected freeTrialData?: FreeTrial;
|
||||
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
protected organizationApiService: OrganizationApiServiceAbstraction,
|
||||
@@ -75,7 +70,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy {
|
||||
private formBuilder: FormBuilder,
|
||||
private dialogService: DialogService,
|
||||
private toastService: ToastService,
|
||||
private trialFlowService: TrialFlowService,
|
||||
private organizationService: OrganizationService,
|
||||
private accountService: AccountService,
|
||||
protected syncService: SyncService,
|
||||
@@ -151,7 +145,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy {
|
||||
organizationSubscriptionPromise,
|
||||
organizationPromise,
|
||||
]);
|
||||
this.determineOrgsWithUpcomingPaymentIssues();
|
||||
} else {
|
||||
const billingPromise = this.apiService.getUserBillingPayment();
|
||||
const subPromise = this.apiService.getUserSubscription();
|
||||
@@ -225,18 +218,6 @@ export class PaymentMethodComponent implements OnInit, OnDestroy {
|
||||
await this.load();
|
||||
};
|
||||
|
||||
determineOrgsWithUpcomingPaymentIssues() {
|
||||
if (!this.organization || !this.org || !this.billing) {
|
||||
throw new Error("Organization, organization subscription, or billing is not defined");
|
||||
}
|
||||
|
||||
this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
|
||||
this.organization,
|
||||
this.org,
|
||||
this.billing?.paymentSource,
|
||||
);
|
||||
}
|
||||
|
||||
get isCreditBalance() {
|
||||
return this.billing == null || this.billing.balance <= 0;
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export type FreeTrial = {
|
||||
remainingDays: number;
|
||||
message: string;
|
||||
shownBanner: boolean;
|
||||
organizationId: string;
|
||||
organizationName: string;
|
||||
};
|
||||
@@ -1,2 +1 @@
|
||||
export * from "./bitwarden-subscriber";
|
||||
export * from "./free-trial";
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
[route]="['../', org.id]"
|
||||
(mainContentClicked)="toggle()"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
(click)="handleUnpaidSubscription(org)"
|
||||
(click)="showInactiveSubscriptionDialog(org)"
|
||||
>
|
||||
<i
|
||||
slot="end"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { combineLatest, map, Observable, switchMap } from "rxjs";
|
||||
import { combineLatest, firstValueFrom, map, Observable, switchMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
@@ -11,13 +11,13 @@ import type { Organization } from "@bitwarden/common/admin-console/models/domain
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { DialogService, NavigationModule } from "@bitwarden/components";
|
||||
|
||||
import { TrialFlowService } from "./../../billing/services/trial-flow.service";
|
||||
import { OrganizationWarningsModule } from "@bitwarden/web-vault/app/billing/organizations/warnings/organization-warnings.module";
|
||||
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
|
||||
|
||||
@Component({
|
||||
selector: "org-switcher",
|
||||
templateUrl: "org-switcher.component.html",
|
||||
imports: [CommonModule, JslibModule, NavigationModule],
|
||||
imports: [CommonModule, JslibModule, NavigationModule, OrganizationWarningsModule],
|
||||
})
|
||||
export class OrgSwitcherComponent {
|
||||
protected organizations$: Observable<Organization[]> = this.accountService.activeAccount$.pipe(
|
||||
@@ -64,9 +64,9 @@ export class OrgSwitcherComponent {
|
||||
private route: ActivatedRoute,
|
||||
protected dialogService: DialogService,
|
||||
private organizationService: OrganizationService,
|
||||
private trialFlowService: TrialFlowService,
|
||||
protected billingApiService: BillingApiServiceAbstraction,
|
||||
private accountService: AccountService,
|
||||
private organizationWarningsService: OrganizationWarningsService,
|
||||
) {}
|
||||
|
||||
protected toggle(event?: MouseEvent) {
|
||||
@@ -75,8 +75,8 @@ export class OrgSwitcherComponent {
|
||||
this.openChange.emit(this.open);
|
||||
}
|
||||
|
||||
async handleUnpaidSubscription(org: Organization) {
|
||||
const metaData = await this.billingApiService.getOrganizationBillingMetadata(org.id);
|
||||
await this.trialFlowService.handleUnpaidSubscriptionDialog(org, metaData);
|
||||
}
|
||||
showInactiveSubscriptionDialog = async (organization: Organization) =>
|
||||
await firstValueFrom(
|
||||
this.organizationWarningsService.showInactiveSubscriptionDialog$(organization),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
<bit-banner
|
||||
id="free-trial-banner"
|
||||
bannerType="premium"
|
||||
icon="bwi-billing"
|
||||
[showClose]="false"
|
||||
*ngFor="let organization of organizationsPaymentStatus; trackBy: trackBy; index as i"
|
||||
>
|
||||
{{ freeTrialMessage(organization) }}
|
||||
<a
|
||||
bitLink
|
||||
linkType="secondary"
|
||||
(click)="navigateToPaymentMethod(organization?.organizationId)"
|
||||
rel="noreferrer noopener"
|
||||
class="tw-cursor-pointer"
|
||||
@for (organization of organizations; track organization.id) {
|
||||
<app-organization-free-trial-warning
|
||||
[organization]="organization"
|
||||
[includeOrganizationNameInMessaging]="true"
|
||||
(clicked)="navigateToPaymentMethod(organization.id)"
|
||||
>
|
||||
{{ "clickHereToAddPaymentMethod" | i18n }}
|
||||
</a>
|
||||
</bit-banner>
|
||||
</app-organization-free-trial-warning>
|
||||
}
|
||||
|
||||
<bit-banner
|
||||
id="update-browser-banner"
|
||||
|
||||
@@ -3,16 +3,16 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { Router } from "@angular/router";
|
||||
import { filter, firstValueFrom, map, Observable, switchMap } from "rxjs";
|
||||
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessageListener } from "@bitwarden/common/platform/messaging";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { BannerModule } from "@bitwarden/components";
|
||||
import { OrganizationFreeTrialWarningComponent } from "@bitwarden/web-vault/app/billing/organizations/warnings/components";
|
||||
|
||||
import { VerifyEmailComponent } from "../../../auth/settings/verify-email.component";
|
||||
import { FreeTrial } from "../../../billing/types/free-trial";
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
import { VaultBannersService, VisibleVaultBanner } from "./services/vault-banners.service";
|
||||
@@ -20,21 +20,25 @@ import { VaultBannersService, VisibleVaultBanner } from "./services/vault-banner
|
||||
@Component({
|
||||
selector: "app-vault-banners",
|
||||
templateUrl: "./vault-banners.component.html",
|
||||
imports: [VerifyEmailComponent, SharedModule, BannerModule],
|
||||
imports: [
|
||||
VerifyEmailComponent,
|
||||
SharedModule,
|
||||
BannerModule,
|
||||
OrganizationFreeTrialWarningComponent,
|
||||
],
|
||||
providers: [VaultBannersService],
|
||||
})
|
||||
export class VaultBannersComponent implements OnInit {
|
||||
visibleBanners: VisibleVaultBanner[] = [];
|
||||
premiumBannerVisible$: Observable<boolean>;
|
||||
VisibleVaultBanner = VisibleVaultBanner;
|
||||
@Input() organizationsPaymentStatus: FreeTrial[] = [];
|
||||
@Input() organizations: Organization[] = [];
|
||||
|
||||
private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
|
||||
|
||||
constructor(
|
||||
private vaultBannerService: VaultBannersService,
|
||||
private router: Router,
|
||||
private i18nService: I18nService,
|
||||
private accountService: AccountService,
|
||||
private messageListener: MessageListener,
|
||||
private configService: ConfigService,
|
||||
@@ -107,22 +111,4 @@ export class VaultBannersComponent implements OnInit {
|
||||
showPendingAuthRequest ? VisibleVaultBanner.PendingAuthRequest : null,
|
||||
].filter((banner) => banner !== null);
|
||||
}
|
||||
|
||||
freeTrialMessage(organization: FreeTrial) {
|
||||
if (organization.remainingDays >= 2) {
|
||||
return this.i18nService.t(
|
||||
"freeTrialEndPromptMultipleDays",
|
||||
organization.organizationName,
|
||||
organization.remainingDays.toString(),
|
||||
);
|
||||
} else if (organization.remainingDays === 1) {
|
||||
return this.i18nService.t("freeTrialEndPromptTomorrow", organization.organizationName);
|
||||
} else {
|
||||
return this.i18nService.t("freeTrialEndPromptToday", organization.organizationName);
|
||||
}
|
||||
}
|
||||
|
||||
trackBy(index: number) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Component, EventEmitter, inject, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import {
|
||||
combineLatest,
|
||||
distinctUntilChanged,
|
||||
@@ -26,8 +25,8 @@ import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
|
||||
|
||||
import { TrialFlowService } from "../../../../billing/services/trial-flow.service";
|
||||
import { VaultFilterService } from "../services/abstractions/vault-filter.service";
|
||||
import {
|
||||
VaultFilterList,
|
||||
@@ -61,11 +60,12 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
isLoaded = false;
|
||||
|
||||
protected destroy$: Subject<void> = new Subject<void>();
|
||||
private router = inject(Router);
|
||||
get filtersList() {
|
||||
return this.filters ? Object.values(this.filters) : [];
|
||||
}
|
||||
|
||||
protected organizationWarningsService = inject(OrganizationWarningsService);
|
||||
|
||||
allTypeFilters: CipherTypeFilter[] = [
|
||||
{
|
||||
id: "favorites",
|
||||
@@ -143,7 +143,6 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
return "searchVault";
|
||||
}
|
||||
|
||||
private trialFlowService = inject(TrialFlowService);
|
||||
protected activeUserId$ = this.accountService.activeAccount$.pipe(getUserId);
|
||||
|
||||
constructor(
|
||||
@@ -211,8 +210,9 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
variant: "error",
|
||||
message: this.i18nService.t("disabledOrganizationFilterError"),
|
||||
});
|
||||
const metadata = await this.billingApiService.getOrganizationBillingMetadata(orgNode.node.id);
|
||||
await this.trialFlowService.handleUnpaidSubscriptionDialog(orgNode.node, metadata);
|
||||
await firstValueFrom(
|
||||
this.organizationWarningsService.showInactiveSubscriptionDialog$(orgNode.node),
|
||||
);
|
||||
}
|
||||
const filter = this.activeFilter;
|
||||
if (orgNode?.node.id === "AllVaults") {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { SearchModule } from "@bitwarden/components";
|
||||
import { OrganizationWarningsModule } from "@bitwarden/web-vault/app/billing/organizations/warnings/organization-warnings.module";
|
||||
|
||||
import { VaultFilterSharedModule } from "../../individual-vault/vault-filter/shared/vault-filter-shared.module";
|
||||
|
||||
@@ -10,7 +11,7 @@ import { VaultFilterService as VaultFilterServiceAbstraction } from "./services/
|
||||
import { VaultFilterService } from "./services/vault-filter.service";
|
||||
|
||||
@NgModule({
|
||||
imports: [VaultFilterSharedModule, SearchModule],
|
||||
imports: [VaultFilterSharedModule, SearchModule, OrganizationWarningsModule],
|
||||
declarations: [VaultFilterComponent, OrganizationOptionsComponent],
|
||||
exports: [VaultFilterComponent],
|
||||
providers: [
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<app-vault-banners
|
||||
[organizationsPaymentStatus]="organizationsPaymentStatus$ | async"
|
||||
></app-vault-banners>
|
||||
<app-vault-banners [organizations]="organizations$ | async"></app-vault-banners>
|
||||
|
||||
<app-vault-header
|
||||
[filter]="filter"
|
||||
|
||||
@@ -6,14 +6,11 @@ import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
firstValueFrom,
|
||||
from,
|
||||
lastValueFrom,
|
||||
Observable,
|
||||
of,
|
||||
Subject,
|
||||
} from "rxjs";
|
||||
import {
|
||||
catchError,
|
||||
concatMap,
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
@@ -38,7 +35,6 @@ 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 {
|
||||
getOrganizationById,
|
||||
OrganizationService,
|
||||
@@ -46,12 +42,10 @@ import {
|
||||
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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
@@ -89,6 +83,8 @@ import {
|
||||
DefaultCipherFormConfigService,
|
||||
PasswordRepromptService,
|
||||
} from "@bitwarden/vault";
|
||||
import { OrganizationWarningsModule } from "@bitwarden/web-vault/app/billing/organizations/warnings/organization-warnings.module";
|
||||
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
|
||||
|
||||
import {
|
||||
getNestedCollectionTree,
|
||||
@@ -99,9 +95,6 @@ import {
|
||||
CollectionDialogTabType,
|
||||
openCollectionDialog,
|
||||
} from "../../admin-console/organizations/shared/components/collection-dialog";
|
||||
import { BillingNotificationService } from "../../billing/services/billing-notification.service";
|
||||
import { TrialFlowService } from "../../billing/services/trial-flow.service";
|
||||
import { FreeTrial } from "../../billing/types/free-trial";
|
||||
import { SharedModule } from "../../shared/shared.module";
|
||||
import { AssignCollectionsWebComponent } from "../components/assign-collections";
|
||||
import {
|
||||
@@ -150,6 +143,7 @@ const BroadcasterSubscriptionId = "VaultComponent";
|
||||
VaultFilterModule,
|
||||
VaultItemsModule,
|
||||
SharedModule,
|
||||
OrganizationWarningsModule,
|
||||
],
|
||||
providers: [
|
||||
RoutedVaultFilterService,
|
||||
@@ -183,71 +177,12 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
private searchText$ = new Subject<string>();
|
||||
private refresh$ = new BehaviorSubject<void>(null);
|
||||
private destroy$ = new Subject<void>();
|
||||
private hasSubscription$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
|
||||
private organizations$ = this.accountService.activeAccount$
|
||||
organizations$ = this.accountService.activeAccount$
|
||||
.pipe(map((a) => a?.id))
|
||||
.pipe(switchMap((id) => this.organizationService.organizations$(id)));
|
||||
|
||||
private readonly unpaidSubscriptionDialog$ = this.organizations$.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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
protected organizationsPaymentStatus$: Observable<FreeTrial[]> = combineLatest([
|
||||
this.organizations$.pipe(
|
||||
map(
|
||||
(organizations) =>
|
||||
organizations?.filter((org) => org.isOwner && org.canViewBillingHistory) ?? [],
|
||||
),
|
||||
),
|
||||
this.hasSubscription$,
|
||||
]).pipe(
|
||||
switchMap(([ownerOrgs, hasSubscription]) => {
|
||||
if (!ownerOrgs || ownerOrgs.length === 0 || !hasSubscription) {
|
||||
return of([]);
|
||||
}
|
||||
return combineLatest(
|
||||
ownerOrgs.map((org) =>
|
||||
combineLatest([
|
||||
this.organizationApiService.getSubscription(org.id),
|
||||
from(this.organizationBillingService.getPaymentSource(org.id)).pipe(
|
||||
catchError((error: unknown) => {
|
||||
this.billingNotificationService.handleError(error);
|
||||
return of(null);
|
||||
}),
|
||||
),
|
||||
]).pipe(
|
||||
map(([subscription, paymentSource]) =>
|
||||
this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
|
||||
org,
|
||||
subscription,
|
||||
paymentSource,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
map((results) => results.filter((result) => result !== null && result.shownBanner)),
|
||||
shareReplay({ refCount: false, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private syncService: SyncService,
|
||||
private route: ActivatedRoute,
|
||||
@@ -276,13 +211,9 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
private toastService: ToastService,
|
||||
private accountService: AccountService,
|
||||
private cipherFormConfigService: DefaultCipherFormConfigService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
protected billingApiService: BillingApiServiceAbstraction,
|
||||
private trialFlowService: TrialFlowService,
|
||||
private organizationBillingService: OrganizationBillingServiceAbstraction,
|
||||
private billingNotificationService: BillingNotificationService,
|
||||
private configService: ConfigService,
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
private organizationWarningsService: OrganizationWarningsService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -520,7 +451,16 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
});
|
||||
});
|
||||
|
||||
this.unpaidSubscriptionDialog$.pipe(takeUntil(this.destroy$)).subscribe();
|
||||
this.organizations$
|
||||
.pipe(
|
||||
filter((organizations) => organizations.length === 1),
|
||||
map((organizations) => organizations[0]),
|
||||
switchMap((organization) =>
|
||||
this.organizationWarningsService.showInactiveSubscriptionDialog$(organization),
|
||||
),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
firstSetup$
|
||||
.pipe(
|
||||
|
||||
@@ -1,23 +1,8 @@
|
||||
<ng-container *ngIf="freeTrial$ | async as freeTrial">
|
||||
<bit-banner
|
||||
id="update-browser-banner"
|
||||
bannerType="premium"
|
||||
icon="bwi-billing"
|
||||
[showClose]="false"
|
||||
*ngIf="!loading && freeTrial.shownBanner"
|
||||
>
|
||||
{{ freeTrial.message }}
|
||||
<a
|
||||
bitLink
|
||||
linkType="secondary"
|
||||
class="tw-cursor-pointer"
|
||||
(click)="navigateToPaymentMethod()"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{{ "clickHereToAddPaymentMethod" | i18n }}
|
||||
</a>
|
||||
</bit-banner>
|
||||
</ng-container>
|
||||
<app-organization-free-trial-warning
|
||||
[organization]="organization"
|
||||
(clicked)="navigateToPaymentMethod()"
|
||||
>
|
||||
</app-organization-free-trial-warning>
|
||||
<app-header [title]="organizationName">
|
||||
<sm-new-menu></sm-new-menu>
|
||||
</app-header>
|
||||
|
||||
@@ -14,13 +14,8 @@ import {
|
||||
take,
|
||||
share,
|
||||
firstValueFrom,
|
||||
of,
|
||||
filter,
|
||||
catchError,
|
||||
from,
|
||||
} from "rxjs";
|
||||
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import {
|
||||
getOrganizationById,
|
||||
OrganizationService,
|
||||
@@ -28,16 +23,12 @@ import {
|
||||
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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service";
|
||||
import { TrialFlowService } from "@bitwarden/web-vault/app/billing/services/trial-flow.service";
|
||||
import { FreeTrial } from "@bitwarden/web-vault/app/billing/types/free-trial";
|
||||
|
||||
import { OrganizationCounts } from "../models/view/counts.view";
|
||||
import { ProjectListView } from "../models/view/project-list.view";
|
||||
@@ -111,7 +102,6 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
||||
tasks: OrganizationTasks;
|
||||
counts: OrganizationCounts;
|
||||
}>;
|
||||
protected freeTrial$: Observable<FreeTrial>;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@@ -127,10 +117,6 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
||||
private smOnboardingTasksService: SMOnboardingTasksService,
|
||||
private logService: LogService,
|
||||
private router: Router,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private trialFlowService: TrialFlowService,
|
||||
private organizationBillingService: OrganizationBillingServiceAbstraction,
|
||||
private billingNotificationService: BillingNotificationService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
@@ -161,27 +147,6 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
||||
this.organizationEnabled = org.enabled;
|
||||
});
|
||||
|
||||
this.freeTrial$ = org$.pipe(
|
||||
filter((org) => org.isOwner && org.canViewBillingHistory && org.canViewSubscription),
|
||||
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]) => {
|
||||
return this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource);
|
||||
}),
|
||||
filter((result) => result !== null),
|
||||
takeUntil(this.destroy$),
|
||||
);
|
||||
|
||||
const projects$ = combineLatest([
|
||||
orgId$,
|
||||
this.projectService.project$.pipe(startWith(null)),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { BannerModule } from "@bitwarden/components";
|
||||
import { OrganizationFreeTrialWarningComponent } from "@bitwarden/web-vault/app/billing/organizations/warnings/components";
|
||||
import { OrganizationWarningsModule } from "@bitwarden/web-vault/app/billing/organizations/warnings/organization-warnings.module";
|
||||
import { OnboardingModule } from "@bitwarden/web-vault/app/shared/components/onboarding/onboarding.module";
|
||||
|
||||
import { SecretsManagerSharedModule } from "../shared/sm-shared.module";
|
||||
@@ -10,7 +12,14 @@ import { OverviewComponent } from "./overview.component";
|
||||
import { SectionComponent } from "./section.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SecretsManagerSharedModule, OverviewRoutingModule, OnboardingModule, BannerModule],
|
||||
imports: [
|
||||
SecretsManagerSharedModule,
|
||||
OverviewRoutingModule,
|
||||
OnboardingModule,
|
||||
BannerModule,
|
||||
OrganizationFreeTrialWarningComponent,
|
||||
OrganizationWarningsModule,
|
||||
],
|
||||
declarations: [OverviewComponent, SectionComponent],
|
||||
providers: [],
|
||||
})
|
||||
|
||||
@@ -24,7 +24,6 @@ export enum FeatureFlag {
|
||||
/* Billing */
|
||||
TrialPaymentOptional = "PM-8163-trial-payment",
|
||||
PM17772_AdminInitiatedSponsorships = "pm-17772-admin-initiated-sponsorships",
|
||||
UseOrganizationWarningsService = "use-organization-warnings-service",
|
||||
PM21881_ManagePaymentDetailsOutsideCheckout = "pm-21881-manage-payment-details-outside-checkout",
|
||||
PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover",
|
||||
PM22415_TaxIDWarnings = "pm-22415-tax-id-warnings",
|
||||
@@ -100,7 +99,6 @@ export const DefaultFeatureFlagValue = {
|
||||
/* Billing */
|
||||
[FeatureFlag.TrialPaymentOptional]: FALSE,
|
||||
[FeatureFlag.PM17772_AdminInitiatedSponsorships]: FALSE,
|
||||
[FeatureFlag.UseOrganizationWarningsService]: FALSE,
|
||||
[FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout]: FALSE,
|
||||
[FeatureFlag.PM21821_ProviderPortalTakeover]: FALSE,
|
||||
[FeatureFlag.PM22415_TaxIDWarnings]: FALSE,
|
||||
|
||||
Reference in New Issue
Block a user