mirror of
https://github.com/bitwarden/browser
synced 2026-02-27 10:03:23 +00:00
refactor(billing): simplify subscription visibility and fix redirect race condition (#19255) (#19257)
(cherry picked from commit abbfda124f)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<div class="tw-max-w-3xl tw-mx-auto">
|
||||
<bit-section *ngIf="shouldShowNewDesign$ | async">
|
||||
<bit-section>
|
||||
<div class="tw-text-center">
|
||||
<div class="tw-mt-8 tw-mb-6">
|
||||
<span bitBadge variant="secondary" [truncate]="false">
|
||||
|
||||
@@ -71,7 +71,6 @@ export class CloudHostedPremiumComponent {
|
||||
protected hasPremiumFromAnyOrganization$: Observable<boolean>;
|
||||
protected hasPremiumPersonally$: Observable<boolean>;
|
||||
protected hasSubscription$: Observable<boolean>;
|
||||
protected shouldShowNewDesign$: Observable<boolean>;
|
||||
protected shouldShowUpgradeDialogOnInit$: Observable<boolean>;
|
||||
protected personalPricingTiers$: Observable<PersonalSubscriptionPricingTier[]>;
|
||||
protected premiumCardData$: Observable<{
|
||||
@@ -131,25 +130,15 @@ export class CloudHostedPremiumComponent {
|
||||
this.subscriber = subscriber;
|
||||
});
|
||||
|
||||
this.shouldShowNewDesign$ = combineLatest([
|
||||
this.hasPremiumFromAnyOrganization$,
|
||||
this.hasPremiumPersonally$,
|
||||
]).pipe(map(([hasOrgPremium, hasPersonalPremium]) => !hasOrgPremium && !hasPersonalPremium));
|
||||
|
||||
// redirect to user subscription page if they already have premium personally
|
||||
// redirect to individual vault if they already have premium from an org
|
||||
combineLatest([
|
||||
this.hasPremiumFromAnyOrganization$,
|
||||
this.hasPremiumPersonally$,
|
||||
this.hasSubscription$,
|
||||
])
|
||||
combineLatest([this.hasSubscription$, this.hasPremiumFromAnyOrganization$])
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
switchMap(([hasPremiumFromOrg, hasPremiumPersonally, hasSubscription]) => {
|
||||
if (hasPremiumPersonally && hasSubscription) {
|
||||
take(1),
|
||||
switchMap(([hasSubscription, hasPremiumFromAnyOrganization]) => {
|
||||
if (hasSubscription) {
|
||||
return from(this.navigateToSubscriptionPage());
|
||||
}
|
||||
if (hasPremiumFromOrg) {
|
||||
if (hasPremiumFromAnyOrganization) {
|
||||
return from(this.navigateToIndividualVault());
|
||||
}
|
||||
return of(true);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<app-header>
|
||||
@if (!selfHosted) {
|
||||
<bit-tab-nav-bar slot="tabs">
|
||||
<bit-tab-link [route]="(hasPremium$ | async) ? 'user-subscription' : 'premium'">{{
|
||||
"subscription" | i18n
|
||||
}}</bit-tab-link>
|
||||
<bit-tab-link
|
||||
[route]="(showSubscriptionPageLink$ | async) ? 'user-subscription' : 'premium'"
|
||||
>{{ "subscription" | i18n }}</bit-tab-link
|
||||
>
|
||||
<bit-tab-link route="payment-details">{{ "paymentDetails" | i18n }}</bit-tab-link>
|
||||
<bit-tab-link route="billing-history">{{ "billingHistory" | i18n }}</bit-tab-link>
|
||||
</bit-tab-nav-bar>
|
||||
|
||||
@@ -19,7 +19,7 @@ import { AccountBillingClient } from "../clients/account-billing.client";
|
||||
providers: [AccountBillingClient],
|
||||
})
|
||||
export class SubscriptionComponent implements OnInit {
|
||||
hasPremium$: Observable<boolean>;
|
||||
showSubscriptionPageLink$: Observable<boolean>;
|
||||
selfHosted: boolean;
|
||||
|
||||
constructor(
|
||||
@@ -27,9 +27,9 @@ export class SubscriptionComponent implements OnInit {
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
accountService: AccountService,
|
||||
configService: ConfigService,
|
||||
private accountBillingClient: AccountBillingClient,
|
||||
accountBillingClient: AccountBillingClient,
|
||||
) {
|
||||
this.hasPremium$ = combineLatest([
|
||||
this.showSubscriptionPageLink$ = combineLatest([
|
||||
configService.getFeatureFlag$(FeatureFlag.PM29594_UpdateIndividualSubscriptionPage),
|
||||
accountService.activeAccount$,
|
||||
]).pipe(
|
||||
|
||||
@@ -89,18 +89,34 @@ export class AccountSubscriptionComponent {
|
||||
{ initialValue: false },
|
||||
);
|
||||
|
||||
readonly hasPremiumFromAnyOrganization = toSignal(
|
||||
this.accountService.activeAccount$.pipe(
|
||||
switchMap((account) => {
|
||||
if (!account) {
|
||||
return of(false);
|
||||
}
|
||||
return this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(account.id);
|
||||
}),
|
||||
),
|
||||
{ initialValue: false },
|
||||
);
|
||||
|
||||
readonly subscription = resource({
|
||||
loader: async () => {
|
||||
const redirectToPremiumPage = async (): Promise<null> => {
|
||||
params: () => ({
|
||||
account: this.account(),
|
||||
}),
|
||||
loader: async ({ params: { account } }) => {
|
||||
if (!account) {
|
||||
await this.router.navigate(["/settings/subscription/premium"]);
|
||||
return null;
|
||||
};
|
||||
if (!this.account()) {
|
||||
return await redirectToPremiumPage();
|
||||
}
|
||||
const subscription = await this.accountBillingClient.getSubscription();
|
||||
if (!subscription) {
|
||||
return await redirectToPremiumPage();
|
||||
const hasPremiumFromAnyOrganization = this.hasPremiumFromAnyOrganization();
|
||||
await this.router.navigate([
|
||||
hasPremiumFromAnyOrganization ? "/vault" : "/settings/subscription/premium",
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
return subscription;
|
||||
},
|
||||
|
||||
@@ -20,11 +20,10 @@
|
||||
} @else {
|
||||
<bit-nav-item [text]="'preferences' | i18n" route="settings/preferences"></bit-nav-item>
|
||||
}
|
||||
<bit-nav-item
|
||||
[text]="'subscription' | i18n"
|
||||
route="settings/subscription"
|
||||
*ngIf="showSubscription$ | async"
|
||||
></bit-nav-item>
|
||||
@let subscriptionRoute = subscriptionRoute$ | async;
|
||||
@if (subscriptionRoute) {
|
||||
<bit-nav-item [text]="'subscription' | i18n" [route]="subscriptionRoute"></bit-nav-item>
|
||||
}
|
||||
<bit-nav-item [text]="'domainRules' | i18n" route="settings/domain-rules"></bit-nav-item>
|
||||
@if (showEmergencyAccess()) {
|
||||
<bit-nav-item
|
||||
|
||||
@@ -43,16 +43,15 @@ import { WebLayoutModule } from "./web-layout.module";
|
||||
export class UserLayoutComponent implements OnInit {
|
||||
protected readonly logo = PasswordManagerLogo;
|
||||
protected readonly showEmergencyAccess: Signal<boolean>;
|
||||
protected showSubscription$: Observable<boolean>;
|
||||
protected readonly sendEnabled$: Observable<boolean> = this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => this.policyService.policyAppliesToUser$(PolicyType.DisableSend, userId)),
|
||||
map((isDisabled) => !isDisabled),
|
||||
);
|
||||
protected consolidatedSessionTimeoutComponent$: Observable<boolean>;
|
||||
protected hasPremiumPersonally$: Observable<boolean>;
|
||||
protected hasPremiumFromAnyOrganization$: Observable<boolean>;
|
||||
protected hasSubscription$: Observable<boolean>;
|
||||
protected subscriptionRoute$: Observable<string | null>;
|
||||
|
||||
constructor(
|
||||
private syncService: SyncService,
|
||||
@@ -75,10 +74,6 @@ export class UserLayoutComponent implements OnInit {
|
||||
FeatureFlag.ConsolidatedSessionTimeoutComponent,
|
||||
);
|
||||
|
||||
this.hasPremiumPersonally$ = this.ifAccountExistsCheck((userId) =>
|
||||
this.billingAccountProfileStateService.hasPremiumPersonally$(userId),
|
||||
);
|
||||
|
||||
this.hasPremiumFromAnyOrganization$ = this.ifAccountExistsCheck((userId) =>
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(userId),
|
||||
);
|
||||
@@ -90,16 +85,17 @@ export class UserLayoutComponent implements OnInit {
|
||||
),
|
||||
);
|
||||
|
||||
this.showSubscription$ = combineLatest([
|
||||
this.hasPremiumPersonally$,
|
||||
this.hasPremiumFromAnyOrganization$,
|
||||
this.subscriptionRoute$ = combineLatest([
|
||||
this.hasSubscription$,
|
||||
this.hasPremiumFromAnyOrganization$,
|
||||
]).pipe(
|
||||
map(([hasPremiumPersonally, hasPremiumFromAnyOrganization, hasSubscription]) => {
|
||||
if (hasPremiumFromAnyOrganization && !hasPremiumPersonally) {
|
||||
return false;
|
||||
map(([hasSubscription, hasPremiumFromAnyOrganization]) => {
|
||||
if (!hasPremiumFromAnyOrganization || hasSubscription) {
|
||||
return hasSubscription
|
||||
? "settings/subscription/user-subscription"
|
||||
: "settings/subscription/premium";
|
||||
}
|
||||
return hasSubscription;
|
||||
return null;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user