mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 05:13:29 +00:00
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
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user