1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00
Files
browser/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts
Stephon Brown da8a0104ea Billing/pm 24996/implement upgrade from free dialog (#16470)
* feat(billing): add required messages

* feat(billing): Add upgrade from free account dialog

* feat(billing): Add payment dialog for premium upgrade

* feat(billing): Add Upgrade Payment Service

* feat(billing): Add Upgrade flow service

* feat(billing): Add purchase premium subscription method to client

* fix(billing): allow for nullable taxId for families organizations

* fix(billing): Fix Cart Summary Tests

* temp-fix(billing): add currency pipe to pricing card component

* fix(billing): Fix NX error

This should compile just the library files and not its dependency files which was making it error

* fix: Update any type of private function

* update account dialog

* feat(billing): add upgrade error message

* fix(billing): remove upgrade-flow service

* feat(billing): add account billing client

* fix(billing): Remove method from subscriber-billing client

* fix(billing): rename and update upgrade payment component

* fix(billing): Rename and update upgrade payment service

* fix(billing): Rename and upgrade upgrade account component

* fix(billing): Add unified upgrade dialog component

* fix(billing): Update component and service to use new tax service

* fix(billing): Update unified upgrade dialog

* feat(billing): Add feature flag

* feat(billing): Add vault dialog launch logic

* fix(billing): Add stricter validation for payment component

* fix(billing): Update custom dialog close button

* fix(billing): Fix padding in cart summary component

* fix(billing): Update payment method component spacing

* fix(billing): Update Upgrade Payment component spacing

* fix(billing): Update upgrade account component spacing

* fix(billing): Fix accurate typing

* feat(billing): adds unified upgrade prompt service

* fix(billing): Update unified dialog to account for skipped steps

* fix(billing): Use upgradePromptService for vault

* fix(billing): Format

* fix(billing): Fix premium check
2025-10-08 10:20:15 -04:00

115 lines
4.0 KiB
TypeScript

import { Injectable } from "@angular/core";
import { combineLatest, firstValueFrom } from "rxjs";
import { switchMap, take } from "rxjs/operators";
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { DialogRef, DialogService } from "@bitwarden/components";
import {
UnifiedUpgradeDialogComponent,
UnifiedUpgradeDialogResult,
} from "../unified-upgrade-dialog/unified-upgrade-dialog.component";
@Injectable({
providedIn: "root",
})
export class UnifiedUpgradePromptService {
private unifiedUpgradeDialogRef: DialogRef<UnifiedUpgradeDialogResult> | null = null;
constructor(
private accountService: AccountService,
private configService: ConfigService,
private billingAccountProfileStateService: BillingAccountProfileStateService,
private vaultProfileService: VaultProfileService,
private dialogService: DialogService,
) {}
private shouldShowPrompt$ = combineLatest([
this.accountService.activeAccount$,
this.configService.getFeatureFlag$(FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog),
]).pipe(
switchMap(async ([account, isFlagEnabled]) => {
if (!account || !account?.id) {
return false;
}
// Early return if feature flag is disabled
if (!isFlagEnabled) {
return false;
}
// Check if user has premium
const hasPremium = await firstValueFrom(
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
);
// Early return if user already has premium
if (hasPremium) {
return false;
}
// Check profile age only if needed
const isProfileLessThanFiveMinutesOld = await this.isProfileLessThanFiveMinutesOld(
account.id,
);
return isFlagEnabled && !hasPremium && isProfileLessThanFiveMinutesOld;
}),
take(1),
);
/**
* Conditionally prompt the user based on predefined criteria.
*
* @returns A promise that resolves to the dialog result if shown, or null if not shown
*/
async displayUpgradePromptConditionally(): Promise<UnifiedUpgradeDialogResult | null> {
const shouldShow = await firstValueFrom(this.shouldShowPrompt$);
if (shouldShow) {
return this.launchUpgradeDialog();
}
return null;
}
/**
* Checks if a user's profile was created less than five minutes ago
* @param userId User ID to check
* @returns Promise that resolves to true if profile was created less than five minutes ago
*/
private async isProfileLessThanFiveMinutesOld(userId: string): Promise<boolean> {
const createdAtDate = await this.vaultProfileService.getProfileCreationDate(userId);
if (!createdAtDate) {
return false;
}
const createdAtInMs = createdAtDate.getTime();
const nowInMs = new Date().getTime();
const differenceInMs = nowInMs - createdAtInMs;
const msInAMinute = 1000 * 60; // Milliseconds in a minute for conversion 1 minute = 60 seconds * 1000 ms
const differenceInMinutes = Math.round(differenceInMs / msInAMinute);
return differenceInMinutes <= 5;
}
private async launchUpgradeDialog(): Promise<UnifiedUpgradeDialogResult | null> {
const account = await firstValueFrom(this.accountService.activeAccount$);
if (!account) {
return null;
}
this.unifiedUpgradeDialogRef = UnifiedUpgradeDialogComponent.open(this.dialogService, {
data: { account },
});
const result = await firstValueFrom(this.unifiedUpgradeDialogRef.closed);
this.unifiedUpgradeDialogRef = null;
// Return the result or null if the dialog was dismissed without a result
return result || null;
}
}