From b63e1cb26c08bf43969c3e6de8df18325860ea40 Mon Sep 17 00:00:00 2001 From: Mike Amirault Date: Tue, 16 Dec 2025 13:34:31 -0500 Subject: [PATCH] [PM-28181] Open send dialog in drawer instead of popup in refreshed UI (#17666) * [PM-28181] Open send dialog in drawer instead of popup in refreshed UI * Fix types * [PM-28181] Use drawer to edit sends with refreshed UI * [PM-28181] Address bug where multiple Sends could not be navigated between --- .../new-send-dropdown.component.spec.ts | 94 +++++++++++++++++++ .../new-send/new-send-dropdown.component.ts | 10 +- apps/web/src/app/tools/send/send.component.ts | 20 +++- libs/common/src/enums/feature-flag.enum.ts | 2 + .../send-add-edit-dialog.component.ts | 15 +++ 5 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 apps/web/src/app/tools/send/new-send/new-send-dropdown.component.spec.ts diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.spec.ts b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.spec.ts new file mode 100644 index 00000000000..4f5dda1745e --- /dev/null +++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.spec.ts @@ -0,0 +1,94 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +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 { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +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 { SendAddEditDialogComponent } from "@bitwarden/send-ui"; + +import { NewSendDropdownComponent } from "./new-send-dropdown.component"; + +describe("NewSendDropdownComponent", () => { + let component: NewSendDropdownComponent; + let fixture: ComponentFixture; + const mockBillingAccountProfileStateService = mock(); + const mockAccountService = mock(); + const mockConfigService = mock(); + const mockI18nService = mock(); + const mockPolicyService = mock(); + const mockSendService = mock(); + const mockPremiumUpgradePromptService = mock(); + const mockSendApiService = mock(); + + beforeAll(() => { + mockBillingAccountProfileStateService.hasPremiumFromAnySource$.mockImplementation(() => + of(true), + ); + mockAccountService.activeAccount$ = of({ id: "myTestAccount" } as Account); + mockPolicyService.policyAppliesToUser$.mockImplementation(() => of(false)); + mockPremiumUpgradePromptService.promptForPremium.mockImplementation(async () => {}); + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NewSendDropdownComponent], + declarations: [], + providers: [ + { + provide: BillingAccountProfileStateService, + useValue: mockBillingAccountProfileStateService, + }, + { provide: AccountService, useValue: mockAccountService }, + { provide: ConfigService, useValue: mockConfigService }, + { provide: I18nService, useValue: mockI18nService }, + { provide: PolicyService, useValue: mockPolicyService }, + { provide: SendService, useValue: mockSendService }, + { provide: PremiumUpgradePromptService, useValue: mockPremiumUpgradePromptService }, + { provide: SendApiService, useValue: mockSendApiService }, + ], + }).compileComponents(); + fixture = TestBed.createComponent(NewSendDropdownComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should open send dialog in a popup without feature flag", async () => { + const openSpy = jest.spyOn(SendAddEditDialogComponent, "open"); + const openDrawerSpy = jest.spyOn(SendAddEditDialogComponent, "openDrawer"); + mockConfigService.getFeatureFlag.mockResolvedValue(false); + + await component.createSend(SendType.Text); + + expect(openSpy).toHaveBeenCalled(); + expect(openDrawerSpy).not.toHaveBeenCalled(); + }); + + it("should open send dialog in drawer with feature flag", async () => { + const openSpy = jest.spyOn(SendAddEditDialogComponent, "open"); + const openDrawerSpy = jest.spyOn(SendAddEditDialogComponent, "openDrawer"); + mockConfigService.getFeatureFlag.mockImplementation(async (key) => + key === FeatureFlag.SendUIRefresh ? true : false, + ); + + await component.createSend(SendType.Text); + + expect(openSpy).not.toHaveBeenCalled(); + expect(openDrawerSpy).toHaveBeenCalled(); + }); +}); diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts index 80d1d0e1e12..22f07e4fe92 100644 --- a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts +++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts @@ -6,6 +6,8 @@ import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/pre import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { ButtonModule, DialogService, MenuModule } from "@bitwarden/components"; import { DefaultSendFormConfigService, SendAddEditDialogComponent } from "@bitwarden/send-ui"; @@ -38,6 +40,7 @@ export class NewSendDropdownComponent { private accountService: AccountService, private dialogService: DialogService, private addEditFormConfigService: DefaultSendFormConfigService, + private configService: ConfigService, ) { this.canAccessPremium$ = this.accountService.activeAccount$.pipe( switchMap((account) => @@ -60,6 +63,11 @@ export class NewSendDropdownComponent { const formConfig = await this.addEditFormConfigService.buildConfig("add", undefined, type); - SendAddEditDialogComponent.open(this.dialogService, { formConfig }); + const useRefresh = await this.configService.getFeatureFlag(FeatureFlag.SendUIRefresh); + if (useRefresh) { + SendAddEditDialogComponent.openDrawer(this.dialogService, { formConfig }); + } else { + SendAddEditDialogComponent.open(this.dialogService, { formConfig }); + } } } diff --git a/apps/web/src/app/tools/send/send.component.ts b/apps/web/src/app/tools/send/send.component.ts index e9ad7aee1f1..11559976fbf 100644 --- a/apps/web/src/app/tools/send/send.component.ts +++ b/apps/web/src/app/tools/send/send.component.ts @@ -7,7 +7,9 @@ import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/sen import { NoSendsIcon } from "@bitwarden/assets/svg"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -77,6 +79,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro toastService: ToastService, private addEditFormConfigService: DefaultSendFormConfigService, accountService: AccountService, + private configService: ConfigService, ) { super( sendService, @@ -144,14 +147,21 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro * @param formConfig The form configuration. * */ async openSendItemDialog(formConfig: SendFormConfig) { - // Prevent multiple dialogs from being opened. - if (this.sendItemDialogRef) { + const useRefresh = await this.configService.getFeatureFlag(FeatureFlag.SendUIRefresh); + // Prevent multiple dialogs from being opened but allow drawers since they will prevent multiple being open themselves + if (this.sendItemDialogRef && !useRefresh) { return; } - this.sendItemDialogRef = SendAddEditDialogComponent.open(this.dialogService, { - formConfig, - }); + if (useRefresh) { + this.sendItemDialogRef = SendAddEditDialogComponent.openDrawer(this.dialogService, { + formConfig, + }); + } else { + this.sendItemDialogRef = SendAddEditDialogComponent.open(this.dialogService, { + formConfig, + }); + } const result = await lastValueFrom(this.sendItemDialogRef.closed); this.sendItemDialogRef = undefined; diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index fb8edd8aa7d..f2037f1a89f 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -50,6 +50,7 @@ export enum FeatureFlag { DesktopSendUIRefresh = "desktop-send-ui-refresh", UseSdkPasswordGenerators = "pm-19976-use-sdk-password-generators", ChromiumImporterWithABE = "pm-25855-chromium-importer-abe", + SendUIRefresh = "pm-28175-send-ui-refresh", /* DIRT */ EventManagementForDataDogAndCrowdStrike = "event-management-for-datadog-and-crowdstrike", @@ -110,6 +111,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.DesktopSendUIRefresh]: FALSE, [FeatureFlag.UseSdkPasswordGenerators]: FALSE, [FeatureFlag.ChromiumImporterWithABE]: FALSE, + [FeatureFlag.SendUIRefresh]: FALSE, /* DIRT */ [FeatureFlag.EventManagementForDataDogAndCrowdStrike]: FALSE, diff --git a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts index 6f49c0ecce5..38257df603a 100644 --- a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts +++ b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts @@ -174,4 +174,19 @@ export class SendAddEditDialogComponent { }, ); } + + /** + * Opens the send add/edit dialog in a drawer + * @param dialogService Instance of the DialogService. + * @param params The parameters for the drawer. + * @returns The drawer result. + */ + static openDrawer(dialogService: DialogService, params: SendItemDialogParams) { + return dialogService.openDrawer( + SendAddEditDialogComponent, + { + data: params, + }, + ); + } }