1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

Merge branch 'main' of https://github.com/bitwarden/clients into vault/pm-10426/admin-console-add-edit

This commit is contained in:
Nick Krantz
2024-09-25 13:03:23 -05:00
8 changed files with 117 additions and 36 deletions

View File

@@ -3,7 +3,9 @@ import { Component, importProvidersFrom } from "@angular/core";
import { RouterModule } from "@angular/router";
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import {
AvatarModule,
BadgeModule,
@@ -318,6 +320,30 @@ export default {
});
},
},
{
provide: PolicyService,
useFactory: () => {
return {
policyAppliesToActiveUser$: () => {
return {
pipe: () => ({
subscribe: () => ({}),
}),
};
},
};
},
},
{
provide: SendService,
useFactory: () => {
return {
sends$: () => {
return { pipe: () => ({}) };
},
};
},
},
],
}),
applicationConfig({

View File

@@ -1,20 +1,15 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { RouterModule } from "@angular/router";
import { filter, map, switchMap } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { LinkModule } from "@bitwarden/components";
@Component({
selector: "popup-tab-navigation",
templateUrl: "popup-tab-navigation.component.html",
standalone: true,
imports: [CommonModule, LinkModule, RouterModule],
host: {
class: "tw-block tw-h-full tw-w-full tw-flex tw-flex-col",
},
})
export class PopupTabNavigationComponent {
navButtons = [
const allNavButtons = [
{
label: "Vault",
page: "/tabs/vault",
@@ -39,5 +34,35 @@ export class PopupTabNavigationComponent {
iconKey: "cog",
iconKeyActive: "cog-f",
},
];
];
@Component({
selector: "popup-tab-navigation",
templateUrl: "popup-tab-navigation.component.html",
standalone: true,
imports: [CommonModule, LinkModule, RouterModule],
host: {
class: "tw-block tw-h-full tw-w-full tw-flex tw-flex-col",
},
})
export class PopupTabNavigationComponent {
navButtons = allNavButtons;
constructor(
private policyService: PolicyService,
private sendService: SendService,
) {
this.policyService
.policyAppliesToActiveUser$(PolicyType.DisableSend)
.pipe(
filter((policyAppliesToActiveUser) => policyAppliesToActiveUser),
switchMap(() => this.sendService.sends$),
map((sends) => sends.length > 1),
takeUntilDestroyed(),
)
.subscribe((hasSends) => {
this.navButtons = hasSends
? allNavButtons
: allNavButtons.filter((b) => b.page !== "/tabs/send");
});
}
}

View File

@@ -1,12 +1,20 @@
<popup-page>
<popup-header slot="header" [pageTitle]="'send' | i18n">
<ng-container slot="end">
<tools-new-send-dropdown></tools-new-send-dropdown>
<tools-new-send-dropdown *ngIf="!sendsDisabled"></tools-new-send-dropdown>
<app-pop-out></app-pop-out>
<app-current-account></app-current-account>
</ng-container>
</popup-header>
<div slot="above-scroll-area" class="tw-p-4">
<bit-callout *ngIf="sendsDisabled" [title]="'sendDisabled' | i18n">
{{ "sendDisabledWarning" | i18n }}
</bit-callout>
<ng-container *ngIf="!sendsDisabled">
<tools-send-search></tools-send-search>
<app-send-list-filters></app-send-list-filters>
</ng-container>
</div>
<div
*ngIf="listState === sendState.Empty"
@@ -15,7 +23,7 @@
<bit-no-items [icon]="noItemIcon" class="tw-text-main">
<ng-container slot="title">{{ "sendsNoItemsTitle" | i18n }}</ng-container>
<ng-container slot="description">{{ "sendsNoItemsMessage" | i18n }}</ng-container>
<tools-new-send-dropdown slot="button"></tools-new-send-dropdown>
<tools-new-send-dropdown *ngIf="!sendsDisabled" slot="button"></tools-new-send-dropdown>
</bit-no-items>
</div>
@@ -31,9 +39,4 @@
</div>
<app-send-list-items-container [headerText]="title | i18n" [sends]="sends$ | async" />
</ng-container>
<div slot="above-scroll-area" class="tw-p-4" *ngIf="listState !== sendState.Empty">
<tools-send-search></tools-send-search>
<app-send-list-filters></app-send-list-filters>
</div>
</popup-page>

View File

@@ -7,6 +7,7 @@ import { of, BehaviorSubject } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
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";
@@ -46,6 +47,7 @@ describe("SendV2Component", () => {
let sendListFiltersServiceFilters$: BehaviorSubject<{ sendType: SendType | null }>;
let sendItemsServiceEmptyList$: BehaviorSubject<boolean>;
let sendItemsServiceNoFilteredResults$: BehaviorSubject<boolean>;
let policyService: MockProxy<PolicyService>;
beforeEach(async () => {
sendListFiltersServiceFilters$ = new BehaviorSubject({ sendType: null });
@@ -60,6 +62,9 @@ describe("SendV2Component", () => {
latestSearchText$: of(""),
});
policyService = mock<PolicyService>();
policyService.policyAppliesToActiveUser$.mockReturnValue(of(true)); // Return `true` by default
sendListFiltersService = new SendListFiltersService(mock(), new FormBuilder());
sendListFiltersService.filters$ = sendListFiltersServiceFilters$;
@@ -104,6 +109,7 @@ describe("SendV2Component", () => {
{ provide: I18nService, useValue: { t: (key: string) => key } },
{ provide: SendListFiltersService, useValue: sendListFiltersService },
{ provide: PopupRouterCacheService, useValue: mock<PopupRouterCacheService>() },
{ provide: PolicyService, useValue: policyService },
],
}).compileComponents();

View File

@@ -5,8 +5,10 @@ import { RouterLink } from "@angular/router";
import { combineLatest } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { ButtonModule, Icons, NoItemsModule } from "@bitwarden/components";
import { ButtonModule, CalloutModule, Icons, NoItemsModule } from "@bitwarden/components";
import {
NoSendsIcon,
NewSendDropdownComponent,
@@ -31,6 +33,7 @@ export enum SendState {
templateUrl: "send-v2.component.html",
standalone: true,
imports: [
CalloutModule,
PopupPageComponent,
PopupHeaderComponent,
PopOutComponent,
@@ -61,9 +64,12 @@ export class SendV2Component implements OnInit, OnDestroy {
protected noResultsIcon = Icons.NoResults;
protected sendsDisabled = false;
constructor(
protected sendItemsService: SendItemsService,
protected sendListFiltersService: SendListFiltersService,
private policyService: PolicyService,
) {
combineLatest([
this.sendItemsService.emptyList$,
@@ -90,6 +96,13 @@ export class SendV2Component implements OnInit, OnDestroy {
this.listState = null;
});
this.policyService
.policyAppliesToActiveUser$(PolicyType.DisableSend)
.pipe(takeUntilDestroyed())
.subscribe((sendsDisabled) => {
this.sendsDisabled = sendsDisabled;
});
}
ngOnInit(): void {}

View File

@@ -38,7 +38,7 @@ export class MoreFromBitwardenPageV2Component {
private organizationService: OrganizationService,
) {
this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$;
this.familySponsorshipAvailable$ = this.organizationService.canManageSponsorships$;
this.familySponsorshipAvailable$ = this.organizationService.familySponsorshipAvailable$;
}
async openFreeBitwardenFamiliesPage() {

View File

@@ -117,6 +117,10 @@ export abstract class OrganizationService {
* Emits true if the user can create or manage a Free Bitwarden Families sponsorship.
*/
canManageSponsorships$: Observable<boolean>;
/**
* Emits true if any of the user's organizations have a Free Bitwarden Families sponsorship available.
*/
familySponsorshipAvailable$: Observable<boolean>;
hasOrganizations: () => Promise<boolean>;
get$: (id: string) => Observable<Organization | undefined>;
get: (id: string) => Promise<Organization>;

View File

@@ -88,6 +88,10 @@ export class OrganizationService implements InternalOrganizationServiceAbstracti
mapToBooleanHasAnyOrganizations(),
);
familySponsorshipAvailable$ = this.organizations$.pipe(
map((orgs) => orgs.some((o) => o.familySponsorshipAvailable)),
);
async hasOrganizations(): Promise<boolean> {
return await firstValueFrom(this.organizations$.pipe(mapToBooleanHasAnyOrganizations()));
}