diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 03009cdfce..db1f960b9b 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -4097,6 +4097,12 @@
"itemLocation": {
"message": "Item Location"
},
+ "fileSends": {
+ "message": "File Sends"
+ },
+ "textSends": {
+ "message": "Text Sends"
+ },
"bitwardenNewLook": {
"message": "Bitwarden has a new look!"
},
diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.html b/apps/browser/src/tools/popup/send-v2/send-v2.component.html
index 52f7c3ed8f..a8dd3e24f2 100644
--- a/apps/browser/src/tools/popup/send-v2/send-v2.component.html
+++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.html
@@ -8,12 +8,32 @@
-
+
{{ "sendsNoItemsTitle" | i18n }}
{{ "sendsNoItemsMessage" | i18n }}
-
+
+
+
+
+ {{ "noItemsMatchSearch" | i18n }}
+ {{ "clearFiltersOrTryAnother" | i18n }}
+
+
+
+
+
+
diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts
index d7a302b790..53a0441eec 100644
--- a/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts
+++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.spec.ts
@@ -1,11 +1,12 @@
import { CommonModule } from "@angular/common";
import { ComponentFixture, TestBed } from "@angular/core/testing";
-import { RouterLink } from "@angular/router";
+import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { RouterTestingModule } from "@angular/router/testing";
-import { mock } from "jest-mock-extended";
-import { Observable, of } from "rxjs";
+import { MockProxy, mock } from "jest-mock-extended";
+import { of, BehaviorSubject } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
@@ -15,6 +16,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
@@ -22,7 +24,10 @@ import { ButtonModule, NoItemsModule } from "@bitwarden/components";
import {
NewSendDropdownComponent,
SendListItemsContainerComponent,
+ SendItemsService,
+ SendSearchComponent,
SendListFiltersComponent,
+ SendListFiltersService,
} from "@bitwarden/send-ui";
import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component";
@@ -30,31 +35,49 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
-import { SendV2Component } from "./send-v2.component";
+import { SendV2Component, SendState } from "./send-v2.component";
describe("SendV2Component", () => {
let component: SendV2Component;
let fixture: ComponentFixture
;
- let sendViews$: Observable;
+ let sendItemsService: MockProxy;
+ let sendListFiltersService: SendListFiltersService;
+ let sendListFiltersServiceFilters$: BehaviorSubject<{ sendType: SendType | null }>;
+ let sendItemsServiceEmptyList$: BehaviorSubject;
+ let sendItemsServiceNoFilteredResults$: BehaviorSubject;
beforeEach(async () => {
- sendViews$ = of([
- { id: "1", name: "Send A" },
- { id: "2", name: "Send B" },
- ] as SendView[]);
+ sendListFiltersServiceFilters$ = new BehaviorSubject({ sendType: null });
+ sendItemsServiceEmptyList$ = new BehaviorSubject(false);
+ sendItemsServiceNoFilteredResults$ = new BehaviorSubject(false);
+
+ sendItemsService = mock({
+ filteredAndSortedSends$: of([
+ { id: "1", name: "Send A" },
+ { id: "2", name: "Send B" },
+ ] as SendView[]),
+ latestSearchText$: of(""),
+ });
+
+ sendListFiltersService = new SendListFiltersService(mock(), new FormBuilder());
+
+ sendListFiltersService.filters$ = sendListFiltersServiceFilters$;
+ sendItemsService.emptyList$ = sendItemsServiceEmptyList$;
+ sendItemsService.noFilteredResults$ = sendItemsServiceNoFilteredResults$;
await TestBed.configureTestingModule({
imports: [
CommonModule,
RouterTestingModule,
JslibModule,
- NoItemsModule,
+ ReactiveFormsModule,
ButtonModule,
NoItemsModule,
- RouterLink,
NewSendDropdownComponent,
SendListItemsContainerComponent,
SendListFiltersComponent,
+ SendSearchComponent,
+ SendV2Component,
PopupPageComponent,
PopupHeaderComponent,
PopOutComponent,
@@ -66,21 +89,24 @@ describe("SendV2Component", () => {
{ provide: AvatarService, useValue: mock() },
{
provide: BillingAccountProfileStateService,
- useValue: mock(),
+ useValue: { hasPremiumFromAnySource$: of(false) },
},
{ provide: ConfigService, useValue: mock() },
{ provide: EnvironmentService, useValue: mock() },
{ provide: LogService, useValue: mock() },
{ provide: PlatformUtilsService, useValue: mock() },
{ provide: SendApiService, useValue: mock() },
- { provide: SendService, useValue: { sendViews$ } },
+ { provide: SendItemsService, useValue: mock() },
+ { provide: SearchService, useValue: mock() },
+ { provide: SendService, useValue: { sendViews$: new BehaviorSubject([]) } },
+ { provide: SendItemsService, useValue: sendItemsService },
{ provide: I18nService, useValue: { t: (key: string) => key } },
+ { provide: SendListFiltersService, useValue: sendListFiltersService },
],
}).compileComponents();
fixture = TestBed.createComponent(SendV2Component);
component = fixture.componentInstance;
-
fixture.detectChanges();
});
@@ -88,14 +114,21 @@ describe("SendV2Component", () => {
expect(component).toBeTruthy();
});
- it("should sort sends by name on initialization", async () => {
- const sortedSends = [
- { id: "1", name: "Send A" },
- { id: "2", name: "Send B" },
- ] as SendView[];
+ it("should update the title based on the current filter", () => {
+ sendListFiltersServiceFilters$.next({ sendType: SendType.File });
+ fixture.detectChanges();
+ expect(component["title"]).toBe("fileSends");
+ });
- await component.ngOnInit();
+ it("should set listState to Empty when emptyList$ emits true", () => {
+ sendItemsServiceEmptyList$.next(true);
+ fixture.detectChanges();
+ expect(component["listState"]).toBe(SendState.Empty);
+ });
- expect(component.sends).toEqual(sortedSends);
+ it("should set listState to NoResults when noFilteredResults$ emits true", () => {
+ sendItemsServiceNoFilteredResults$.next(true);
+ fixture.detectChanges();
+ expect(component["listState"]).toBe(SendState.NoResults);
});
});
diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts
index 6ee5f832be..5c1ec89fde 100644
--- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts
+++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts
@@ -1,18 +1,20 @@
import { CommonModule } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
+import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { RouterLink } from "@angular/router";
-import { mergeMap, Subject, takeUntil } from "rxjs";
+import { combineLatest } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
-import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
-import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
-import { ButtonModule, NoItemsModule } from "@bitwarden/components";
+import { ButtonModule, Icons, NoItemsModule } from "@bitwarden/components";
import {
NoSendsIcon,
NewSendDropdownComponent,
SendListItemsContainerComponent,
+ SendItemsService,
+ SendSearchComponent,
SendListFiltersComponent,
+ SendListFiltersService,
} from "@bitwarden/send-ui";
import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component";
@@ -20,6 +22,11 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
+export enum SendState {
+ Empty,
+ NoResults,
+}
+
@Component({
templateUrl: "send-v2.component.html",
standalone: true,
@@ -36,29 +43,56 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
NewSendDropdownComponent,
SendListItemsContainerComponent,
SendListFiltersComponent,
+ SendSearchComponent,
],
})
export class SendV2Component implements OnInit, OnDestroy {
sendType = SendType;
- private destroy$ = new Subject();
+ sendState = SendState;
- sends: SendView[] = [];
+ protected listState: SendState | null = null;
+
+ protected sends$ = this.sendItemsService.filteredAndSortedSends$;
+
+ protected title: string = "allSends";
protected noItemIcon = NoSendsIcon;
- constructor(protected sendService: SendService) {}
+ protected noResultsIcon = Icons.NoResults;
- async ngOnInit() {
- this.sendService.sendViews$
- .pipe(
- mergeMap(async (sends) => {
- this.sends = sends.sort((a, b) => a.name.localeCompare(b.name));
- }),
- takeUntil(this.destroy$),
- )
- .subscribe();
+ constructor(
+ protected sendItemsService: SendItemsService,
+ protected sendListFiltersService: SendListFiltersService,
+ ) {
+ combineLatest([
+ this.sendItemsService.emptyList$,
+ this.sendItemsService.noFilteredResults$,
+ this.sendListFiltersService.filters$,
+ ])
+ .pipe(takeUntilDestroyed())
+ .subscribe(([emptyList, noFilteredResults, currentFilter]) => {
+ if (currentFilter?.sendType !== null) {
+ this.title = `${this.sendType[currentFilter.sendType].toLowerCase()}Sends`;
+ } else {
+ this.title = "allSends";
+ }
+
+ if (emptyList) {
+ this.listState = SendState.Empty;
+ return;
+ }
+
+ if (noFilteredResults) {
+ this.listState = SendState.NoResults;
+ return;
+ }
+
+ this.listState = null;
+ });
}
+ ngOnInit(): void {}
+
ngOnDestroy(): void {}
}
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index 5cfc860d60..14bd61a092 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -3043,5 +3043,11 @@
},
"data": {
"message": "Data"
+ },
+ "fileSends": {
+ "message": "File Sends"
+ },
+ "textSends": {
+ "message": "Text Sends"
}
}
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index 96a50b5405..be48d1b301 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -8795,6 +8795,12 @@
"purchasedSeatsRemoved": {
"message": "purchased seats removed"
},
+ "fileSends": {
+ "message": "File Sends"
+ },
+ "textSends": {
+ "message": "Text Sends"
+ },
"includesXMembers": {
"message": "for $COUNT$ member",
"placeholders": {
diff --git a/libs/tools/send/send-ui/src/index.ts b/libs/tools/send/send-ui/src/index.ts
index 02326ac222..d208709c36 100644
--- a/libs/tools/send/send-ui/src/index.ts
+++ b/libs/tools/send/send-ui/src/index.ts
@@ -2,4 +2,7 @@ export * from "./icons";
export * from "./send-form";
export { NewSendDropdownComponent } from "./new-send-dropdown/new-send-dropdown.component";
export { SendListItemsContainerComponent } from "./send-list-items-container/send-list-items-container.component";
+export { SendItemsService } from "./services/send-items.service";
+export { SendSearchComponent } from "./send-search/send-search.component";
export { SendListFiltersComponent } from "./send-list-filters/send-list-filters.component";
+export { SendListFiltersService } from "./services/send-list-filters.service";
diff --git a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.html b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.html
index e74e2f0562..17f1233d70 100644
--- a/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.html
+++ b/libs/tools/send/send-ui/src/send-list-filters/send-list-filters.component.html
@@ -1,7 +1,7 @@
-