mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[PM-21791] Nudge UI Bug Fixes (#15010)
* remove margin bottom from empty vault nudge * update page title to vault options * show badge on import of vault settings * add margin between no items title and icon * add mock to test * add comment for destroying vault settings page * fix logic for manage/create collection * account for deleted ciphers when showing the import nudge * refactor name of vault import nudge
This commit is contained in:
@@ -46,7 +46,7 @@
|
|||||||
[title]="'hasItemsVaultNudgeTitle' | i18n"
|
[title]="'hasItemsVaultNudgeTitle' | i18n"
|
||||||
(onDismiss)="dismissVaultNudgeSpotlight(NudgeType.HasVaultItems)"
|
(onDismiss)="dismissVaultNudgeSpotlight(NudgeType.HasVaultItems)"
|
||||||
>
|
>
|
||||||
<ul class="tw-pl-4 tw-text-main" bitTypography="body2">
|
<ul class="tw-pl-4 tw-text-main tw-mb-0" bitTypography="body2">
|
||||||
<li>{{ "hasItemsVaultNudgeBodyOne" | i18n }}</li>
|
<li>{{ "hasItemsVaultNudgeBodyOne" | i18n }}</li>
|
||||||
<li>{{ "hasItemsVaultNudgeBodyTwo" | i18n }}</li>
|
<li>{{ "hasItemsVaultNudgeBodyTwo" | i18n }}</li>
|
||||||
<li>{{ "hasItemsVaultNudgeBodyThree" | i18n }}</li>
|
<li>{{ "hasItemsVaultNudgeBodyThree" | i18n }}</li>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<popup-page>
|
<popup-page>
|
||||||
<popup-header slot="header" [pageTitle]="'vault' | i18n" showBackButton>
|
<popup-header slot="header" [pageTitle]="'settingsVaultOptions' | i18n" showBackButton>
|
||||||
<ng-container slot="end">
|
<ng-container slot="end">
|
||||||
<app-pop-out></app-pop-out>
|
<app-pop-out></app-pop-out>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -14,7 +14,17 @@
|
|||||||
</bit-item>
|
</bit-item>
|
||||||
<bit-item>
|
<bit-item>
|
||||||
<button type="button" bit-item-content (click)="import()">
|
<button type="button" bit-item-content (click)="import()">
|
||||||
{{ "importItems" | i18n }}
|
<div class="tw-flex tw-items-center tw-justify-center tw-gap-2">
|
||||||
|
<p>{{ "importItems" | i18n }}</p>
|
||||||
|
<span
|
||||||
|
*ngIf="emptyVaultImportBadge$ | async"
|
||||||
|
bitBadge
|
||||||
|
variant="notification"
|
||||||
|
[attr.aria-label]="'nudgeBadgeAria' | i18n"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<i slot="end" class="bwi bwi-popout" aria-hidden="true"></i>
|
<i slot="end" class="bwi bwi-popout" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</bit-item>
|
</bit-item>
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { Router, RouterModule } from "@angular/router";
|
import { Router, RouterModule } from "@angular/router";
|
||||||
|
import { firstValueFrom, switchMap } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||||
import { ItemModule, ToastOptions, ToastService } from "@bitwarden/components";
|
import { BadgeComponent, ItemModule, ToastOptions, ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { BrowserApi } from "../../../platform/browser/browser-api";
|
import { BrowserApi } from "../../../platform/browser/browser-api";
|
||||||
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
|
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
|
||||||
@@ -23,22 +27,38 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
|
|||||||
PopupHeaderComponent,
|
PopupHeaderComponent,
|
||||||
PopOutComponent,
|
PopOutComponent,
|
||||||
ItemModule,
|
ItemModule,
|
||||||
|
BadgeComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class VaultSettingsV2Component implements OnInit {
|
export class VaultSettingsV2Component implements OnInit, OnDestroy {
|
||||||
lastSync = "--";
|
lastSync = "--";
|
||||||
|
|
||||||
|
protected emptyVaultImportBadge$ = this.accountService.activeAccount$.pipe(
|
||||||
|
getUserId,
|
||||||
|
switchMap((userId) =>
|
||||||
|
this.nudgeService.showNudgeBadge$(NudgeType.VaultSettingsImportNudge, userId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
|
private nudgeService: NudgesService,
|
||||||
|
private accountService: AccountService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await this.setLastSync();
|
await this.setLastSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async ngOnDestroy(): Promise<void> {
|
||||||
|
// When a user navigates away from the page, dismiss the empty vault import nudge
|
||||||
|
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.nudgeService.dismissNudge(NudgeType.VaultSettingsImportNudge, userId);
|
||||||
|
}
|
||||||
|
|
||||||
async import() {
|
async import() {
|
||||||
await this.router.navigate(["/import"]);
|
await this.router.navigate(["/import"]);
|
||||||
if (await BrowserApi.isPopupOpen()) {
|
if (await BrowserApi.isPopupOpen()) {
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ export * from "./account-security-nudge.service";
|
|||||||
export * from "./has-items-nudge.service";
|
export * from "./has-items-nudge.service";
|
||||||
export * from "./download-bitwarden-nudge.service";
|
export * from "./download-bitwarden-nudge.service";
|
||||||
export * from "./empty-vault-nudge.service";
|
export * from "./empty-vault-nudge.service";
|
||||||
|
export * from "./vault-settings-import-nudge.service";
|
||||||
export * from "./new-item-nudge.service";
|
export * from "./new-item-nudge.service";
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { inject, Injectable } from "@angular/core";
|
||||||
|
import { combineLatest, Observable, of, switchMap } from "rxjs";
|
||||||
|
|
||||||
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
|
||||||
|
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
|
||||||
|
import { NudgeStatus, NudgeType } from "../nudges.service";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Nudge Service for the vault settings import badge.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: "root",
|
||||||
|
})
|
||||||
|
export class VaultSettingsImportNudgeService extends DefaultSingleNudgeService {
|
||||||
|
cipherService = inject(CipherService);
|
||||||
|
organizationService = inject(OrganizationService);
|
||||||
|
collectionService = inject(CollectionService);
|
||||||
|
|
||||||
|
nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> {
|
||||||
|
return combineLatest([
|
||||||
|
this.getNudgeStatus$(nudgeType, userId),
|
||||||
|
this.cipherService.cipherViews$(userId),
|
||||||
|
this.organizationService.organizations$(userId),
|
||||||
|
this.collectionService.decryptedCollections$,
|
||||||
|
]).pipe(
|
||||||
|
switchMap(([nudgeStatus, ciphers, orgs, collections]) => {
|
||||||
|
const vaultHasMoreThanOneItem = (ciphers?.length ?? 0) > 1;
|
||||||
|
const { hasBadgeDismissed, hasSpotlightDismissed } = nudgeStatus;
|
||||||
|
|
||||||
|
// When the user has no organizations, return the nudge status directly
|
||||||
|
if ((orgs?.length ?? 0) === 0) {
|
||||||
|
return hasBadgeDismissed || hasSpotlightDismissed
|
||||||
|
? of(nudgeStatus)
|
||||||
|
: of({
|
||||||
|
hasSpotlightDismissed: vaultHasMoreThanOneItem,
|
||||||
|
hasBadgeDismissed: vaultHasMoreThanOneItem,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgIds = new Set(orgs.map((org) => org.id));
|
||||||
|
const canCreateCollections = orgs.some((org) => org.canCreateNewCollections);
|
||||||
|
const hasManageCollections = collections.some(
|
||||||
|
(c) => c.manage && orgIds.has(c.organizationId),
|
||||||
|
);
|
||||||
|
|
||||||
|
// When the user has dismissed the nudge or spotlight, return the nudge status directly
|
||||||
|
if (hasBadgeDismissed || hasSpotlightDismissed) {
|
||||||
|
return of(nudgeStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the user belongs to an organization and cannot create collections or manage collections,
|
||||||
|
// hide the nudge and spotlight
|
||||||
|
if (!hasManageCollections && !canCreateCollections) {
|
||||||
|
return of({
|
||||||
|
hasSpotlightDismissed: true,
|
||||||
|
hasBadgeDismissed: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, return the nudge status based on the vault contents
|
||||||
|
return of({
|
||||||
|
hasSpotlightDismissed: vaultHasMoreThanOneItem,
|
||||||
|
hasBadgeDismissed: vaultHasMoreThanOneItem,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
HasItemsNudgeService,
|
HasItemsNudgeService,
|
||||||
EmptyVaultNudgeService,
|
EmptyVaultNudgeService,
|
||||||
DownloadBitwardenNudgeService,
|
DownloadBitwardenNudgeService,
|
||||||
|
VaultSettingsImportNudgeService,
|
||||||
} from "./custom-nudges-services";
|
} from "./custom-nudges-services";
|
||||||
import { DefaultSingleNudgeService } from "./default-single-nudge.service";
|
import { DefaultSingleNudgeService } from "./default-single-nudge.service";
|
||||||
import { NudgesService, NudgeType } from "./nudges.service";
|
import { NudgesService, NudgeType } from "./nudges.service";
|
||||||
@@ -64,6 +65,10 @@ describe("Vault Nudges Service", () => {
|
|||||||
provide: EmptyVaultNudgeService,
|
provide: EmptyVaultNudgeService,
|
||||||
useValue: mock<EmptyVaultNudgeService>(),
|
useValue: mock<EmptyVaultNudgeService>(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: VaultSettingsImportNudgeService,
|
||||||
|
useValue: mock<VaultSettingsImportNudgeService>(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: ApiService,
|
provide: ApiService,
|
||||||
useValue: mock<ApiService>(),
|
useValue: mock<ApiService>(),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
DownloadBitwardenNudgeService,
|
DownloadBitwardenNudgeService,
|
||||||
NewItemNudgeService,
|
NewItemNudgeService,
|
||||||
AccountSecurityNudgeService,
|
AccountSecurityNudgeService,
|
||||||
|
VaultSettingsImportNudgeService,
|
||||||
} from "./custom-nudges-services";
|
} from "./custom-nudges-services";
|
||||||
import { DefaultSingleNudgeService, SingleNudgeService } from "./default-single-nudge.service";
|
import { DefaultSingleNudgeService, SingleNudgeService } from "./default-single-nudge.service";
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ export enum NudgeType {
|
|||||||
* Add future nudges here
|
* Add future nudges here
|
||||||
*/
|
*/
|
||||||
EmptyVaultNudge = "empty-vault-nudge",
|
EmptyVaultNudge = "empty-vault-nudge",
|
||||||
|
VaultSettingsImportNudge = "vault-settings-import-nudge",
|
||||||
HasVaultItems = "has-vault-items",
|
HasVaultItems = "has-vault-items",
|
||||||
AutofillNudge = "autofill-nudge",
|
AutofillNudge = "autofill-nudge",
|
||||||
AccountSecurity = "account-security",
|
AccountSecurity = "account-security",
|
||||||
@@ -64,6 +66,7 @@ export class NudgesService {
|
|||||||
private customNudgeServices: Partial<Record<NudgeType, SingleNudgeService>> = {
|
private customNudgeServices: Partial<Record<NudgeType, SingleNudgeService>> = {
|
||||||
[NudgeType.HasVaultItems]: inject(HasItemsNudgeService),
|
[NudgeType.HasVaultItems]: inject(HasItemsNudgeService),
|
||||||
[NudgeType.EmptyVaultNudge]: inject(EmptyVaultNudgeService),
|
[NudgeType.EmptyVaultNudge]: inject(EmptyVaultNudgeService),
|
||||||
|
[NudgeType.VaultSettingsImportNudge]: inject(VaultSettingsImportNudgeService),
|
||||||
[NudgeType.AccountSecurity]: inject(AccountSecurityNudgeService),
|
[NudgeType.AccountSecurity]: inject(AccountSecurityNudgeService),
|
||||||
[NudgeType.AutofillNudge]: inject(AutofillNudgeService),
|
[NudgeType.AutofillNudge]: inject(AutofillNudgeService),
|
||||||
[NudgeType.DownloadBitwarden]: inject(DownloadBitwardenNudgeService),
|
[NudgeType.DownloadBitwarden]: inject(DownloadBitwardenNudgeService),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="tw-mx-auto tw-flex tw-flex-col tw-items-center tw-justify-center tw-pt-6">
|
<div class="tw-mx-auto tw-flex tw-flex-col tw-items-center tw-justify-center tw-pt-6">
|
||||||
<div class="tw-max-w-sm tw-flex tw-flex-col tw-items-center">
|
<div class="tw-max-w-sm tw-flex tw-flex-col tw-items-center">
|
||||||
<bit-icon [icon]="icon" aria-hidden="true"></bit-icon>
|
<bit-icon [icon]="icon" aria-hidden="true"></bit-icon>
|
||||||
<h3 class="tw-font-semibold tw-text-center">
|
<h3 class="tw-font-semibold tw-text-center tw-mt-4">
|
||||||
<ng-content select="[slot=title]"></ng-content>
|
<ng-content select="[slot=title]"></ng-content>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="tw-text-center">
|
<p class="tw-text-center">
|
||||||
|
|||||||
Reference in New Issue
Block a user