1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +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:
Kyle Denney
2025-11-03 10:16:01 -06:00
committed by GitHub
parent 3c16547f11
commit e1e3966cc2
55 changed files with 1462 additions and 355 deletions

View File

@@ -19,14 +19,23 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
import { CalloutModule, DialogService, ToastService } from "@bitwarden/components";
import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
selector: "app-send-add-edit",
templateUrl: "add-edit.component.html",
imports: [CommonModule, JslibModule, ReactiveFormsModule, CalloutModule],
providers: [
{
provide: PremiumUpgradePromptService,
useClass: DesktopPremiumUpgradePromptService,
},
],
})
export class AddEditComponent extends BaseAddEditComponent {
constructor(
@@ -45,6 +54,7 @@ export class AddEditComponent extends BaseAddEditComponent {
billingAccountProfileStateService: BillingAccountProfileStateService,
accountService: AccountService,
toastService: ToastService,
premiumUpgradePromptService: PremiumUpgradePromptService,
) {
super(
i18nService,
@@ -62,6 +72,7 @@ export class AddEditComponent extends BaseAddEditComponent {
billingAccountProfileStateService,
accountService,
toastService,
premiumUpgradePromptService,
);
}

View File

@@ -4193,5 +4193,29 @@
},
"cardNumberLabel": {
"message": "Card number"
},
"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"
}
}

View File

@@ -1,20 +1,31 @@
import { TestBed } from "@angular/core/testing";
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 { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { DialogService } from "@bitwarden/components";
import { DesktopPremiumUpgradePromptService } from "./desktop-premium-upgrade-prompt.service";
describe("DesktopPremiumUpgradePromptService", () => {
let service: DesktopPremiumUpgradePromptService;
let messager: MockProxy<MessagingService>;
let configService: MockProxy<ConfigService>;
let dialogService: MockProxy<DialogService>;
beforeEach(async () => {
messager = mock<MessagingService>();
configService = mock<ConfigService>();
dialogService = mock<DialogService>();
await TestBed.configureTestingModule({
providers: [
DesktopPremiumUpgradePromptService,
{ provide: MessagingService, useValue: messager },
{ provide: ConfigService, useValue: configService },
{ provide: DialogService, useValue: dialogService },
],
}).compileComponents();
@@ -22,9 +33,38 @@ describe("DesktopPremiumUpgradePromptService", () => {
});
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(messager.send).not.toHaveBeenCalled();
});
it("sends openPremium message when feature flag is disabled", async () => {
configService.getFeatureFlag.mockResolvedValue(false);
await service.promptForPremium();
expect(configService.getFeatureFlag).toHaveBeenCalledWith(
FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog,
);
expect(messager.send).toHaveBeenCalledWith("openPremium");
expect(openSpy).not.toHaveBeenCalled();
});
});
});

View File

@@ -1,15 +1,29 @@
import { inject } from "@angular/core";
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 { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.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 desktop.
*/
export class DesktopPremiumUpgradePromptService implements PremiumUpgradePromptService {
private messagingService = inject(MessagingService);
private configService = inject(ConfigService);
private dialogService = inject(DialogService);
async promptForPremium() {
this.messagingService.send("openPremium");
const showNewDialog = await this.configService.getFeatureFlag(
FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog,
);
if (showNewDialog) {
PremiumUpgradeDialogComponent.open(this.dialogService);
} else {
this.messagingService.send("openPremium");
}
}
}

View File

@@ -9,6 +9,7 @@ config.content = [
"../../libs/key-management-ui/src/**/*.{html,ts}",
"../../libs/angular/src/**/*.{html,ts}",
"../../libs/vault/src/**/*.{html,ts,mdx}",
"../../libs/pricing/src/**/*.{html,ts}",
];
module.exports = config;