diff --git a/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.spec.ts b/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.spec.ts index b18e3a7f5c3..3ba2f634785 100644 --- a/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.spec.ts +++ b/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.spec.ts @@ -5,8 +5,6 @@ import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-pro import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService, Account } 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 { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync/sync.service"; @@ -26,7 +24,6 @@ import { describe("UnifiedUpgradePromptService", () => { let sut: UnifiedUpgradePromptService; const mockAccountService = mock(); - const mockConfigService = mock(); const mockBillingService = mock(); const mockVaultProfileService = mock(); const mockSyncService = mock(); @@ -59,7 +56,6 @@ describe("UnifiedUpgradePromptService", () => { function setupTestService() { sut = new UnifiedUpgradePromptService( mockAccountService, - mockConfigService, mockBillingService, mockVaultProfileService, mockSyncService, @@ -80,7 +76,6 @@ describe("UnifiedUpgradePromptService", () => { beforeEach(() => { mockAccountService.activeAccount$ = accountSubject.asObservable(); mockPlatformUtilsService.isSelfHost.mockReturnValue(false); - mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); mockStateProvider.getUserState$.mockReturnValue(of(false)); setupTestService(); @@ -96,7 +91,6 @@ describe("UnifiedUpgradePromptService", () => { mockAccountService.activeAccount$ = accountSubject.asObservable(); mockDialogOpen.mockReset(); mockReset(mockDialogService); - mockReset(mockConfigService); mockReset(mockBillingService); mockReset(mockVaultProfileService); mockReset(mockSyncService); @@ -112,11 +106,10 @@ describe("UnifiedUpgradePromptService", () => { mockStateProvider.getUserState$.mockReturnValue(of(false)); mockStateProvider.setUserState.mockResolvedValue(undefined); }); - it("should subscribe to account and feature flag observables when checking display conditions", async () => { + it("should subscribe to account observables when checking display conditions", async () => { // Arrange mockPlatformUtilsService.isSelfHost.mockReturnValue(false); mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); - mockConfigService.getFeatureFlag$.mockReturnValue(of(false)); mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); setupTestService(); @@ -125,34 +118,12 @@ describe("UnifiedUpgradePromptService", () => { await sut.displayUpgradePromptConditionally(); // Assert - expect(mockConfigService.getFeatureFlag$).toHaveBeenCalledWith( - FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog, - ); expect(mockAccountService.activeAccount$).toBeDefined(); }); - it("should not show dialog when feature flag is disabled", async () => { - // Arrange - mockPlatformUtilsService.isSelfHost.mockReturnValue(false); - mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); - mockConfigService.getFeatureFlag$.mockReturnValue(of(false)); - mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); - const recentDate = new Date(); - recentDate.setMinutes(recentDate.getMinutes() - 3); // 3 minutes old - mockVaultProfileService.getProfileCreationDate.mockResolvedValue(recentDate); - - setupTestService(); - // Act - const result = await sut.displayUpgradePromptConditionally(); - - // Assert - expect(result).toBeNull(); - expect(mockDialogOpen).not.toHaveBeenCalled(); - }); it("should not show dialog when user has premium", async () => { // Arrange mockPlatformUtilsService.isSelfHost.mockReturnValue(false); - mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(true)); mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); setupTestService(); @@ -167,7 +138,6 @@ describe("UnifiedUpgradePromptService", () => { it("should not show dialog when user has any organization membership", async () => { // Arrange - mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); mockOrganizationService.memberOrganizations$.mockReturnValue(of([{ id: "org1" } as any])); mockPlatformUtilsService.isSelfHost.mockReturnValue(false); @@ -183,7 +153,6 @@ describe("UnifiedUpgradePromptService", () => { it("should not show dialog when profile is older than 5 minutes", async () => { // Arrange - mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); const oldDate = new Date(); @@ -202,7 +171,6 @@ describe("UnifiedUpgradePromptService", () => { it("should show dialog when all conditions are met", async () => { //Arrange - mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); const recentDate = new Date(); @@ -224,7 +192,6 @@ describe("UnifiedUpgradePromptService", () => { it("should not show dialog when account is null/undefined", async () => { // Arrange - mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); accountSubject.next(null); // Set account to null setupTestService(); @@ -238,7 +205,6 @@ describe("UnifiedUpgradePromptService", () => { it("should not show dialog when profile creation date is unavailable", async () => { // Arrange - mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); mockVaultProfileService.getProfileCreationDate.mockResolvedValue(null); @@ -256,7 +222,6 @@ describe("UnifiedUpgradePromptService", () => { it("should not show dialog when running in self-hosted environment", async () => { // Arrange - mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); const recentDate = new Date(); @@ -275,7 +240,6 @@ describe("UnifiedUpgradePromptService", () => { it("should not show dialog when user has previously dismissed the modal", async () => { // Arrange - mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); const recentDate = new Date(); @@ -295,7 +259,6 @@ describe("UnifiedUpgradePromptService", () => { it("should save dismissal state when user closes the dialog", async () => { // Arrange - mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); const recentDate = new Date(); @@ -320,7 +283,6 @@ describe("UnifiedUpgradePromptService", () => { it("should not save dismissal state when user upgrades to premium", async () => { // Arrange - mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); const recentDate = new Date(); diff --git a/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts b/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts index 3ea8f19341d..f5a32483a4d 100644 --- a/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts +++ b/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts @@ -6,8 +6,6 @@ import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-pro import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; 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 { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync/sync.service"; @@ -38,7 +36,6 @@ export class UnifiedUpgradePromptService { private unifiedUpgradeDialogRef: DialogRef | null = null; constructor( private accountService: AccountService, - private configService: ConfigService, private billingAccountProfileStateService: BillingAccountProfileStateService, private vaultProfileService: VaultProfileService, private syncService: SyncService, @@ -70,26 +67,13 @@ export class UnifiedUpgradePromptService { isProfileLessThanFiveMinutesOld$, hasOrganizations$, this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), - this.configService.getFeatureFlag$(FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog), hasDismissedModal$, ]).pipe( - map( - ([ - isProfileLessThanFiveMinutesOld, - hasOrganizations, - hasPremium, - isFlagEnabled, - hasDismissed, - ]) => { - return ( - isProfileLessThanFiveMinutesOld && - !hasOrganizations && - !hasPremium && - isFlagEnabled && - !hasDismissed - ); - }, - ), + map(([isProfileLessThanFiveMinutesOld, hasOrganizations, hasPremium, hasDismissed]) => { + return ( + isProfileLessThanFiveMinutesOld && !hasOrganizations && !hasPremium && !hasDismissed + ); + }), ); }), take(1), diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts index 2ba9dd6fad4..20148018c39 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from "@angular/core/testing"; -import { BehaviorSubject, firstValueFrom, take, timeout } from "rxjs"; +import { BehaviorSubject } from "rxjs"; import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -17,11 +17,7 @@ import { import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { - PREMIUM_BANNER_REPROMPT_KEY, - VaultBannersService, - VisibleVaultBanner, -} from "./vault-banners.service"; +import { VaultBannersService, VisibleVaultBanner } from "./vault-banners.service"; describe("VaultBannersService", () => { let service: VaultBannersService; @@ -79,101 +75,6 @@ describe("VaultBannersService", () => { jest.useRealTimers(); }); - describe("Premium", () => { - it("waits until sync is completed before showing premium banner", async () => { - hasPremiumFromAnySource$.next(false); - isSelfHost.mockReturnValue(false); - lastSync$.next(null); - - service = TestBed.inject(VaultBannersService); - - const premiumBanner$ = service.shouldShowPremiumBanner$(userId); - - // Should not emit when sync is null - await expect(firstValueFrom(premiumBanner$.pipe(take(1), timeout(100)))).rejects.toThrow(); - - // Should emit when sync is completed - lastSync$.next(new Date("2024-05-14")); - expect(await firstValueFrom(premiumBanner$)).toBe(true); - }); - - it("does not show a premium banner for self-hosted users", async () => { - hasPremiumFromAnySource$.next(false); - isSelfHost.mockReturnValue(true); - - service = TestBed.inject(VaultBannersService); - - expect(await firstValueFrom(service.shouldShowPremiumBanner$(userId))).toBe(false); - }); - - it("does not show a premium banner when they have access to premium", async () => { - hasPremiumFromAnySource$.next(true); - isSelfHost.mockReturnValue(false); - - service = TestBed.inject(VaultBannersService); - - expect(await firstValueFrom(service.shouldShowPremiumBanner$(userId))).toBe(false); - }); - - describe("dismissing", () => { - beforeEach(async () => { - jest.useFakeTimers(); - const date = new Date("2023-06-08"); - date.setHours(0, 0, 0, 0); - jest.setSystemTime(date.getTime()); - - service = TestBed.inject(VaultBannersService); - await service.dismissBanner(userId, VisibleVaultBanner.Premium); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it("updates state on first dismiss", async () => { - const state = await firstValueFrom( - fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$, - ); - - const oneWeekLater = new Date("2023-06-15"); - oneWeekLater.setHours(0, 0, 0, 0); - - expect(state).toEqual({ - numberOfDismissals: 1, - nextPromptDate: oneWeekLater.getTime(), - }); - }); - - it("updates state on second dismiss", async () => { - const state = await firstValueFrom( - fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$, - ); - - const oneMonthLater = new Date("2023-07-08"); - oneMonthLater.setHours(0, 0, 0, 0); - - expect(state).toEqual({ - numberOfDismissals: 2, - nextPromptDate: oneMonthLater.getTime(), - }); - }); - - it("updates state on third dismiss", async () => { - const state = await firstValueFrom( - fakeStateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY).state$, - ); - - const oneYearLater = new Date("2024-06-08"); - oneYearLater.setHours(0, 0, 0, 0); - - expect(state).toEqual({ - numberOfDismissals: 3, - nextPromptDate: oneYearLater.getTime(), - }); - }); - }); - }); - describe("OutdatedBrowser", () => { beforeEach(async () => { // Hardcode `MSIE` in userAgent string diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index 1c53274d9d7..6371f78c0f5 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { Observable, combineLatest, firstValueFrom, map, filter, mergeMap, take } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -7,7 +7,6 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateProvider, - PREMIUM_BANNER_DISK_LOCAL, BANNERS_DISMISSED_DISK, UserKeyDefinition, SingleUserState, @@ -18,30 +17,14 @@ import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; export const VisibleVaultBanner = { OutdatedBrowser: "outdated-browser", - Premium: "premium", VerifyEmail: "verify-email", PendingAuthRequest: "pending-auth-request", } as const; export type VisibleVaultBanner = UnionOfValues; -type PremiumBannerReprompt = { - numberOfDismissals: number; - /** Timestamp representing when to show the prompt next */ - nextPromptDate: number; -}; - /** Banners that will be re-shown on a new session */ -type SessionBanners = Omit; - -export const PREMIUM_BANNER_REPROMPT_KEY = new UserKeyDefinition( - PREMIUM_BANNER_DISK_LOCAL, - "bannerReprompt", - { - deserializer: (bannerReprompt) => bannerReprompt, - clearOn: [], // Do not clear user tutorials - }, -); +type SessionBanners = VisibleVaultBanner; export const BANNERS_DISMISSED_DISK_KEY = new UserKeyDefinition( BANNERS_DISMISSED_DISK, @@ -76,33 +59,6 @@ export class VaultBannersService { return pendingAuthRequests.length > 0 && !alreadyDismissed; } - shouldShowPremiumBanner$(userId: UserId): Observable { - const premiumBannerState = this.premiumBannerState(userId); - const premiumSources$ = combineLatest([ - this.billingAccountProfileStateService.hasPremiumFromAnySource$(userId), - premiumBannerState.state$, - ]); - - return this.syncService.lastSync$(userId).pipe( - filter((lastSync) => lastSync !== null), - take(1), // Wait until the first sync is complete before considering the premium status - mergeMap(() => premiumSources$), - map(([canAccessPremium, dismissedState]) => { - const shouldShowPremiumBanner = - !canAccessPremium && !this.platformUtilsService.isSelfHost(); - - // Check if nextPromptDate is in the past passed - if (shouldShowPremiumBanner && dismissedState?.nextPromptDate) { - const nextPromptDate = new Date(dismissedState.nextPromptDate); - const now = new Date(); - return now >= nextPromptDate; - } - - return shouldShowPremiumBanner; - }), - ); - } - /** Returns true when the update browser banner should be shown */ async shouldShowUpdateBrowserBanner(userId: UserId): Promise { const outdatedBrowser = window.navigator.userAgent.indexOf("MSIE") !== -1; @@ -128,23 +84,11 @@ export class VaultBannersService { /** Dismiss the given banner and perform any respective side effects */ async dismissBanner(userId: UserId, banner: SessionBanners): Promise { - if (banner === VisibleVaultBanner.Premium) { - await this.dismissPremiumBanner(userId); - } else { - await this.sessionBannerState(userId).update((current) => { - const bannersDismissed = current ?? []; + await this.sessionBannerState(userId).update((current) => { + const bannersDismissed = current ?? []; - return [...bannersDismissed, banner]; - }); - } - } - - /** - * - * @returns a SingleUserState for the premium banner reprompt state - */ - private premiumBannerState(userId: UserId): SingleUserState { - return this.stateProvider.getUser(userId, PREMIUM_BANNER_REPROMPT_KEY); + return [...bannersDismissed, banner]; + }); } /** @@ -161,42 +105,4 @@ export class VaultBannersService { // use nullish coalescing to default to an empty array return (await firstValueFrom(this.sessionBannerState(userId).state$)) ?? []; } - - /** Increment dismissal state of the premium banner */ - private async dismissPremiumBanner(userId: UserId): Promise { - await this.premiumBannerState(userId).update((current) => { - const numberOfDismissals = current?.numberOfDismissals ?? 0; - const now = new Date(); - - // Set midnight of the current day - now.setHours(0, 0, 0, 0); - - // First dismissal, re-prompt in 1 week - if (numberOfDismissals === 0) { - now.setDate(now.getDate() + 7); - return { - numberOfDismissals: 1, - nextPromptDate: now.getTime(), - }; - } - - // Second dismissal, re-prompt in 1 month - if (numberOfDismissals === 1) { - now.setMonth(now.getMonth() + 1); - return { - numberOfDismissals: 2, - nextPromptDate: now.getTime(), - }; - } - - // 3+ dismissals, re-prompt each year - // Avoid day/month edge cases and only increment year - const nextYear = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate()); - nextYear.setHours(0, 0, 0, 0); - return { - numberOfDismissals: numberOfDismissals + 1, - nextPromptDate: nextYear.getTime(), - }; - }); - } } diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html index 44b2975ee19..f197853ac18 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.html @@ -44,15 +44,3 @@ (onDismiss)="dismissBanner(VisibleVaultBanner.VerifyEmail)" (onVerified)="dismissBanner(VisibleVaultBanner.VerifyEmail)" > - - - {{ "premiumUpgradeUnlockFeatures" | i18n }} - - {{ "goPremium" | i18n }} - - diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts index 7730ab974fb..786f9a76a20 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.spec.ts @@ -1,5 +1,4 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { By } from "@angular/platform-browser"; import { RouterTestingModule } from "@angular/router/testing"; import { mock } from "jest-mock-extended"; import { BehaviorSubject, Subject } from "rxjs"; @@ -8,15 +7,13 @@ import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { MessageListener } from "@bitwarden/common/platform/messaging"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; -import { BannerComponent, BannerModule } from "@bitwarden/components"; +import { BannerModule } from "@bitwarden/components"; import { VerifyEmailComponent } from "../../../auth/settings/verify-email.component"; import { SharedModule } from "../../../shared"; @@ -30,11 +27,9 @@ describe("VaultBannersComponent", () => { let messageSubject: Subject<{ command: string }>; const premiumBanner$ = new BehaviorSubject(false); const pendingAuthRequest$ = new BehaviorSubject(false); - const PM24996_ImplementUpgradeFromFreeDialogFlag$ = new BehaviorSubject(false); const mockUserId = Utils.newGuid() as UserId; const bannerService = mock({ - shouldShowPremiumBanner$: jest.fn((userId: UserId) => premiumBanner$), shouldShowUpdateBrowserBanner: jest.fn(), shouldShowVerifyEmailBanner: jest.fn(), shouldShowPendingAuthRequestBanner: jest.fn((userId: UserId) => @@ -88,17 +83,6 @@ describe("VaultBannersComponent", () => { allMessages$: messageSubject.asObservable(), }), }, - { - provide: ConfigService, - useValue: mock({ - getFeatureFlag$: jest.fn((flag: FeatureFlag) => { - if (flag === FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog) { - return PM24996_ImplementUpgradeFromFreeDialogFlag$; - } - return new BehaviorSubject(false); - }), - }), - }, ], }) .overrideProvider(VaultBannersService, { useValue: bannerService }) @@ -112,53 +96,6 @@ describe("VaultBannersComponent", () => { fixture.detectChanges(); }); - describe("premiumBannerVisible$", () => { - beforeEach(() => { - // Reset feature flag to default (false) before each test - PM24996_ImplementUpgradeFromFreeDialogFlag$.next(false); - }); - - it("shows premium banner when shouldShowPremiumBanner is true and feature flag is off", async () => { - premiumBanner$.next(true); - PM24996_ImplementUpgradeFromFreeDialogFlag$.next(false); - - fixture.detectChanges(); - - const banner = fixture.debugElement.query(By.directive(BannerComponent)); - expect(banner.componentInstance.bannerType()).toBe("premium"); - }); - - it("hides premium banner when feature flag is enabled", async () => { - premiumBanner$.next(true); - PM24996_ImplementUpgradeFromFreeDialogFlag$.next(true); - - fixture.detectChanges(); - - const banner = fixture.debugElement.query(By.directive(BannerComponent)); - expect(banner).toBeNull(); - }); - - it("dismisses premium banner when shouldShowPremiumBanner is false", async () => { - premiumBanner$.next(false); - PM24996_ImplementUpgradeFromFreeDialogFlag$.next(false); - - fixture.detectChanges(); - - const banner = fixture.debugElement.query(By.directive(BannerComponent)); - expect(banner).toBeNull(); - }); - - it("hides premium banner when both shouldShowPremiumBanner is false and feature flag is enabled", async () => { - premiumBanner$.next(false); - PM24996_ImplementUpgradeFromFreeDialogFlag$.next(true); - - fixture.detectChanges(); - - const banner = fixture.debugElement.query(By.directive(BannerComponent)); - expect(banner).toBeNull(); - }); - }); - describe("determineVisibleBanner", () => { [ { diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts index 80626d258f8..9ddfdedd61b 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts @@ -1,14 +1,11 @@ import { Component, Input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; -import { combineLatest, filter, firstValueFrom, map, Observable, switchMap } from "rxjs"; +import { filter, firstValueFrom, map } 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 { 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"; @@ -32,7 +29,6 @@ import { VaultBannersService, VisibleVaultBanner } from "./services/vault-banner }) export class VaultBannersComponent implements OnInit { visibleBanners: VisibleVaultBanner[] = []; - premiumBannerVisible$: Observable; VisibleVaultBanner = VisibleVaultBanner; // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals @@ -45,23 +41,7 @@ export class VaultBannersComponent implements OnInit { private router: Router, private accountService: AccountService, private messageListener: MessageListener, - private configService: ConfigService, ) { - this.premiumBannerVisible$ = this.activeUserId$.pipe( - filter((userId): userId is UserId => userId != null), - switchMap((userId) => - combineLatest([ - this.vaultBannerService.shouldShowPremiumBanner$(userId), - this.configService.getFeatureFlag$(FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog), - ]).pipe( - map( - ([shouldShowBanner, PM24996_ImplementUpgradeFromFreeDialogEnabled]) => - shouldShowBanner && !PM24996_ImplementUpgradeFromFreeDialogEnabled, - ), - ), - ), - ); - // Listen for auth request messages and show banner immediately this.messageListener.allMessages$ .pipe( diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 080b52de05d..5dfee6054b2 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -27,7 +27,6 @@ export enum FeatureFlag { TrialPaymentOptional = "PM-8163-trial-payment", PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button", PM25379_UseNewOrganizationMetadataStructure = "pm-25379-use-new-organization-metadata-structure", - PM24996_ImplementUpgradeFromFreeDialog = "pm-24996-implement-upgrade-from-free-dialog", PM26793_FetchPremiumPriceFromPricingService = "pm-26793-fetch-premium-price-from-pricing-service", PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog", PM26462_Milestone_3 = "pm-26462-milestone-3", @@ -137,7 +136,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.TrialPaymentOptional]: FALSE, [FeatureFlag.PM24032_NewNavigationPremiumUpgradeButton]: FALSE, [FeatureFlag.PM25379_UseNewOrganizationMetadataStructure]: FALSE, - [FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog]: FALSE, [FeatureFlag.PM26793_FetchPremiumPriceFromPricingService]: FALSE, [FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog]: FALSE, [FeatureFlag.PM26462_Milestone_3]: FALSE, diff --git a/libs/state/src/core/state-definitions.ts b/libs/state/src/core/state-definitions.ts index 445e5fecde7..ae6938b2069 100644 --- a/libs/state/src/core/state-definitions.ts +++ b/libs/state/src/core/state-definitions.ts @@ -198,9 +198,7 @@ export const CIPHERS_DISK_LOCAL = new StateDefinition("ciphersLocal", "disk", { export const CIPHERS_MEMORY = new StateDefinition("ciphersMemory", "memory", { browser: "memory-large-object", }); -export const PREMIUM_BANNER_DISK_LOCAL = new StateDefinition("premiumBannerReprompt", "disk", { - web: "disk-local", -}); + export const BANNERS_DISMISSED_DISK = new StateDefinition("bannersDismissed", "disk"); export const VAULT_APPEARANCE = new StateDefinition("vaultAppearance", "disk"); export const SECURITY_TASKS_DISK = new StateDefinition("securityTasks", "disk");