From ec8a8719ae2192a8ee4bed4a4fe212bc87db1b29 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:54:04 -0700 Subject: [PATCH] setup proof-of-concept for Vault/Billing --- .../premium/setup-premium.component.html | 8 ++++ .../premium/setup-premium.component.ts | 41 +++++++++++++++++++ .../premium/setup-premium.service.ts | 4 +- apps/web/src/app/oss-routing.module.ts | 8 +++- .../guards/setup-premium-redirect.guard.ts | 23 +++++++++++ .../registration-finish.component.ts | 5 ++- 6 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/app/billing/individual/premium/setup-premium.component.html create mode 100644 apps/web/src/app/billing/individual/premium/setup-premium.component.ts create mode 100644 apps/web/src/app/vault/guards/setup-premium-redirect.guard.ts diff --git a/apps/web/src/app/billing/individual/premium/setup-premium.component.html b/apps/web/src/app/billing/individual/premium/setup-premium.component.html new file mode 100644 index 00000000000..e59219ca13e --- /dev/null +++ b/apps/web/src/app/billing/individual/premium/setup-premium.component.html @@ -0,0 +1,8 @@ +

Setup Premium

+ +
+ + +
diff --git a/apps/web/src/app/billing/individual/premium/setup-premium.component.ts b/apps/web/src/app/billing/individual/premium/setup-premium.component.ts new file mode 100644 index 00000000000..75e9df2d7e6 --- /dev/null +++ b/apps/web/src/app/billing/individual/premium/setup-premium.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit } from "@angular/core"; +import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { ButtonModule } from "@bitwarden/components"; +import { UserId } from "@bitwarden/user-core"; + +import { SetupPremiumService } from "./setup-premium.service"; + +@Component({ + templateUrl: "./setup-premium.component.html", + standalone: true, + imports: [ButtonModule], +}) +export class SetupPremiumComponent implements OnInit { + userId: UserId; + + constructor( + private accountService: AccountService, + private router: Router, + private setupPremiumService: SetupPremiumService, + ) {} + + async ngOnInit(): Promise { + const currentAcct = await firstValueFrom(this.accountService.activeAccount$); + this.userId = currentAcct.id; + } + + async clickSetup() { + // Insert logic for setting up premium... + + await this.setupPremiumService.clearIntentToSetupPremium(this.userId); + await this.router.navigate(["/vault"]); + } + + async clickMaybeLater() { + await this.setupPremiumService.clearIntentToSetupPremium(this.userId); + await this.router.navigate(["/vault"]); + } +} diff --git a/apps/web/src/app/billing/individual/premium/setup-premium.service.ts b/apps/web/src/app/billing/individual/premium/setup-premium.service.ts index ea3fdc6d080..ea9360f59e0 100644 --- a/apps/web/src/app/billing/individual/premium/setup-premium.service.ts +++ b/apps/web/src/app/billing/individual/premium/setup-premium.service.ts @@ -24,7 +24,9 @@ export class SetupPremiumService { } async getIntentToSetupPremium(userId: UserId) { - await firstValueFrom(this.stateProvider.getUserState$(INTENT_TO_SETUP_PREMIUM_KEY, userId)); + return await firstValueFrom( + this.stateProvider.getUserState$(INTENT_TO_SETUP_PREMIUM_KEY, userId), + ); } async clearIntentToSetupPremium(userId: UserId) { diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 45ed6dc8eb9..e60f0f1814a 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -65,6 +65,7 @@ import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/v import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module"; import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component"; +import { SetupPremiumComponent } from "./billing/individual/premium/setup-premium.component"; import { SponsoredFamiliesComponent } from "./billing/settings/sponsored-families.component"; import { CompleteTrialInitiationComponent } from "./billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component"; import { freeTrialTextResolver } from "./billing/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver"; @@ -86,6 +87,7 @@ import { BrowserExtensionPromptInstallComponent } from "./vault/components/brows import { BrowserExtensionPromptComponent } from "./vault/components/browser-extension-prompt/browser-extension-prompt.component"; import { SetupExtensionComponent } from "./vault/components/setup-extension/setup-extension.component"; import { setupExtensionRedirectGuard } from "./vault/guards/setup-extension-redirect.guard"; +import { setupPremiumRedirectGuard } from "./vault/guards/setup-premium-redirect.guard"; import { VaultModule } from "./vault/individual-vault/vault.module"; const routes: Routes = [ @@ -617,6 +619,10 @@ const routes: Routes = [ }, ], }, + { + path: "setup-premium", + component: SetupPremiumComponent, + }, ], }, { @@ -626,7 +632,7 @@ const routes: Routes = [ children: [ { path: "vault", - canActivate: [setupExtensionRedirectGuard], + canActivate: [setupPremiumRedirectGuard, setupExtensionRedirectGuard], loadChildren: () => VaultModule, }, { diff --git a/apps/web/src/app/vault/guards/setup-premium-redirect.guard.ts b/apps/web/src/app/vault/guards/setup-premium-redirect.guard.ts new file mode 100644 index 00000000000..0094c725ad3 --- /dev/null +++ b/apps/web/src/app/vault/guards/setup-premium-redirect.guard.ts @@ -0,0 +1,23 @@ +import { inject } from "@angular/core"; +import { CanActivateFn, Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; + +import { SetupPremiumService } from "../../billing/individual/premium/setup-premium.service"; + +export const setupPremiumRedirectGuard: CanActivateFn = async () => { + const router = inject(Router); + const accountService = inject(AccountService); + const setupPremiumService = inject(SetupPremiumService); + + const currentAcct = await firstValueFrom(accountService.activeAccount$); + + const intentToSetupPremium = await setupPremiumService.getIntentToSetupPremium(currentAcct.id); + + if (intentToSetupPremium) { + return router.createUrlTree(["/setup-premium"]); + } + + return true; +}; diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts index 18623ea692e..8f4bae3bc85 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts @@ -142,8 +142,7 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { // ``` // export const MarketingInitiative = Object.freeze({ // Premium: "premium", - // Families: "families", - // // Other variants in the future + // // Families: "families", // easy to add if asked to in the future // } as const); // -- Proof of Concept (end) -- @@ -211,11 +210,13 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { await this.loginSuccessHandlerService.run(authenticationResult.userId); + // -- Proof of Concept (start) -- if (this.intendsToSetupPremium) { await this.registrationFinishService.establishIntentToSetupPremium( authenticationResult.userId, ); } + // -- Proof of Concept (end) -- await this.router.navigate(["/vault"]); } catch (e) {