mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 18:23:31 +00:00
[PM-23713] premium badge interaction (#16911)
* feature flag * new upgrade dialog component and moved pricing service into libs first draft * moved pricing service to libs/common removed toast service from the pricing service and implemented error handling in calling components # Conflicts: # apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts * moved new premium upgrade dialog component to libs/angular * badge opens new dialog in browser extension * adds new dialog to desktop and fixes tests * updates send dropdown to use premium prompt service * styling and copy updates * implement in web and desktop * unit tests * converting premium reports to use premium badge, and some cleanup * fixes issue after merge * linter errors * pr feedback * handle async promise correctly * full sync after the premium upgrade is complete * fixing test * add padding to bottom of card in new dialog * add support for self hosting * fixing tests * fix test * Update has-premium.guard.ts * pr feedback * fix build and pr feedback * fix build * prettier * fixing stories and making badge line height consistent * pr feedback * updated upgrade dialog to no longer use pricing card * fixing incorrect markup and removing unused bits * formatting * pr feedback removing unused message keys and adding back in code that was erroneously removed * change detection * close dialog when error * claude pr feedback
This commit is contained in:
@@ -1523,12 +1523,6 @@
|
||||
"enableAutoBiometricsPrompt": {
|
||||
"message": "Ask for biometrics on launch"
|
||||
},
|
||||
"premiumRequired": {
|
||||
"message": "Premium required"
|
||||
},
|
||||
"premiumRequiredDesc": {
|
||||
"message": "A Premium membership is required to use this feature."
|
||||
},
|
||||
"authenticationTimeout": {
|
||||
"message": "Authentication timeout"
|
||||
},
|
||||
@@ -5772,6 +5766,30 @@
|
||||
"atRiskLoginsSecured": {
|
||||
"message": "Great job securing your at-risk logins!"
|
||||
},
|
||||
"upgradeNow": {
|
||||
"message": "Upgrade now"
|
||||
},
|
||||
"builtInAuthenticator": {
|
||||
"message": "Built-in authenticator"
|
||||
},
|
||||
"secureFileStorage": {
|
||||
"message": "Secure file storage"
|
||||
},
|
||||
"emergencyAccess": {
|
||||
"message": "Emergency access"
|
||||
},
|
||||
"breachMonitoring": {
|
||||
"message": "Breach monitoring"
|
||||
},
|
||||
"andMoreFeatures": {
|
||||
"message": "And more!"
|
||||
},
|
||||
"planDescPremium": {
|
||||
"message": "Complete online security"
|
||||
},
|
||||
"upgradeToPremium": {
|
||||
"message": "Upgrade to Premium"
|
||||
},
|
||||
"settingDisabledByPolicy": {
|
||||
"message": "This setting is disabled by your organization's policy.",
|
||||
"description": "This hint text is displayed when a user setting is disabled due to an organization policy."
|
||||
|
||||
@@ -155,11 +155,12 @@ describe("OpenAttachmentsComponent", () => {
|
||||
});
|
||||
|
||||
it("routes the user to the premium page when they cannot access premium features", async () => {
|
||||
const premiumUpgradeService = TestBed.inject(PremiumUpgradePromptService);
|
||||
hasPremiumFromAnySource$.next(false);
|
||||
|
||||
await component.openAttachments();
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(["/premium"]);
|
||||
expect(premiumUpgradeService.promptForPremium).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("disables attachments when the edit form is disabled", () => {
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
||||
import { BadgeModule, ItemModule, ToastService, TypographyModule } from "@bitwarden/components";
|
||||
import { CipherFormContainer } from "@bitwarden/vault";
|
||||
|
||||
@@ -67,6 +68,7 @@ export class OpenAttachmentsComponent implements OnInit {
|
||||
private filePopoutUtilsService: FilePopoutUtilsService,
|
||||
private accountService: AccountService,
|
||||
private cipherFormContainer: CipherFormContainer,
|
||||
private premiumUpgradeService: PremiumUpgradePromptService,
|
||||
) {
|
||||
this.accountService.activeAccount$
|
||||
.pipe(
|
||||
@@ -115,7 +117,7 @@ export class OpenAttachmentsComponent implements OnInit {
|
||||
/** Routes the user to the attachments screen, if available */
|
||||
async openAttachments() {
|
||||
if (!this.canAccessAttachments) {
|
||||
await this.router.navigate(["/premium"]);
|
||||
await this.premiumUpgradeService.promptForPremium();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,25 +2,69 @@ import { TestBed } from "@angular/core/testing";
|
||||
import { Router } from "@angular/router";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { BrowserPremiumUpgradePromptService } from "./browser-premium-upgrade-prompt.service";
|
||||
|
||||
describe("BrowserPremiumUpgradePromptService", () => {
|
||||
let service: BrowserPremiumUpgradePromptService;
|
||||
let router: MockProxy<Router>;
|
||||
let configService: MockProxy<ConfigService>;
|
||||
let dialogService: MockProxy<DialogService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
router = mock<Router>();
|
||||
configService = mock<ConfigService>();
|
||||
dialogService = mock<DialogService>();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [BrowserPremiumUpgradePromptService, { provide: Router, useValue: router }],
|
||||
providers: [
|
||||
BrowserPremiumUpgradePromptService,
|
||||
{ provide: Router, useValue: router },
|
||||
{ provide: ConfigService, useValue: configService },
|
||||
{ provide: DialogService, useValue: dialogService },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
service = TestBed.inject(BrowserPremiumUpgradePromptService);
|
||||
});
|
||||
|
||||
describe("promptForPremium", () => {
|
||||
it("navigates to the premium update screen", async () => {
|
||||
let openSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
openSpy = jest.spyOn(PremiumUpgradeDialogComponent, "open").mockImplementation();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("opens the new premium upgrade dialog when feature flag is enabled", async () => {
|
||||
configService.getFeatureFlag.mockResolvedValue(true);
|
||||
|
||||
await service.promptForPremium();
|
||||
|
||||
expect(configService.getFeatureFlag).toHaveBeenCalledWith(
|
||||
FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog,
|
||||
);
|
||||
expect(openSpy).toHaveBeenCalledWith(dialogService);
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("navigates to the premium update screen when feature flag is disabled", async () => {
|
||||
configService.getFeatureFlag.mockResolvedValue(false);
|
||||
|
||||
await service.promptForPremium();
|
||||
|
||||
expect(configService.getFeatureFlag).toHaveBeenCalledWith(
|
||||
FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog,
|
||||
);
|
||||
expect(router.navigate).toHaveBeenCalledWith(["/premium"]);
|
||||
expect(openSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
import { inject } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
/**
|
||||
* This class handles the premium upgrade process for the browser extension.
|
||||
*/
|
||||
export class BrowserPremiumUpgradePromptService implements PremiumUpgradePromptService {
|
||||
private router = inject(Router);
|
||||
private configService = inject(ConfigService);
|
||||
private dialogService = inject(DialogService);
|
||||
|
||||
async promptForPremium() {
|
||||
/**
|
||||
* Navigate to the premium update screen.
|
||||
*/
|
||||
await this.router.navigate(["/premium"]);
|
||||
const showNewDialog = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog,
|
||||
);
|
||||
|
||||
if (showNewDialog) {
|
||||
PremiumUpgradeDialogComponent.open(this.dialogService);
|
||||
} else {
|
||||
/**
|
||||
* Navigate to the premium update screen.
|
||||
*/
|
||||
await this.router.navigate(["/premium"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ config.content = [
|
||||
"../../libs/vault/src/**/*.{html,ts}",
|
||||
"../../libs/angular/src/**/*.{html,ts}",
|
||||
"../../libs/vault/src/**/*.{html,ts}",
|
||||
"../../libs/pricing/src/**/*.{html,ts}",
|
||||
];
|
||||
|
||||
module.exports = config;
|
||||
|
||||
Reference in New Issue
Block a user