mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-21395] Vault Nudges Bugs (#14737)
* updates to empty vault and has items nudges
This commit is contained in:
@@ -5273,8 +5273,14 @@
|
||||
"hasItemsVaultNudgeTitle": {
|
||||
"message": "Welcome to your vault!"
|
||||
},
|
||||
"hasItemsVaultNudgeBody": {
|
||||
"message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else"
|
||||
"hasItemsVaultNudgeBodyOne": {
|
||||
"message": "Autofill items for the current page"
|
||||
},
|
||||
"hasItemsVaultNudgeBodyTwo": {
|
||||
"message": "Favorite items for easy access"
|
||||
},
|
||||
"hasItemsVaultNudgeBodyThree": {
|
||||
"message": "Search your vault for something else"
|
||||
},
|
||||
"newLoginNudgeTitle": {
|
||||
"message": "Save time with autofill"
|
||||
|
||||
@@ -44,9 +44,13 @@
|
||||
<div class="tw-mb-4" *ngIf="showHasItemsVaultSpotlight$ | async">
|
||||
<bit-spotlight
|
||||
[title]="'hasItemsVaultNudgeTitle' | i18n"
|
||||
[subtitle]="'hasItemsVaultNudgeBody' | i18n"
|
||||
(onDismiss)="dismissVaultNudgeSpotlight(VaultNudgeType.HasVaultItems)"
|
||||
>
|
||||
<ul class="tw-pl-4 tw-text-main" bitTypography="body2">
|
||||
<li>{{ "hasItemsVaultNudgeBodyOne" | i18n }}</li>
|
||||
<li>{{ "hasItemsVaultNudgeBodyTwo" | i18n }}</li>
|
||||
<li>{{ "hasItemsVaultNudgeBodyThree" | i18n }}</li>
|
||||
</ul>
|
||||
</bit-spotlight>
|
||||
</div>
|
||||
<vault-at-risk-password-callout
|
||||
|
||||
@@ -19,10 +19,17 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { ButtonModule, DialogService, Icons, NoItemsModule } from "@bitwarden/components";
|
||||
import {
|
||||
ButtonModule,
|
||||
DialogService,
|
||||
Icons,
|
||||
NoItemsModule,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import {
|
||||
DecryptionFailureDialogComponent,
|
||||
SpotlightComponent,
|
||||
@@ -86,6 +93,7 @@ enum VaultState {
|
||||
NewSettingsCalloutComponent,
|
||||
SpotlightComponent,
|
||||
RouterModule,
|
||||
TypographyModule,
|
||||
],
|
||||
providers: [VaultPageService],
|
||||
})
|
||||
@@ -158,6 +166,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||
private introCarouselService: IntroCarouselService,
|
||||
private vaultNudgesService: VaultNudgesService,
|
||||
private router: Router,
|
||||
private i18nService: I18nService,
|
||||
) {
|
||||
combineLatest([
|
||||
this.vaultPopupItemsService.emptyVault$,
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
<div class="tw-flex tw-justify-between tw-items-start tw-flex-grow">
|
||||
<div>
|
||||
<h2 bitTypography="h4" class="tw-font-semibold !tw-mb-1">{{ title }}</h2>
|
||||
<p class="tw-text-main tw-mb-0" bitTypography="body2" [innerHTML]="subtitle"></p>
|
||||
<p
|
||||
*ngIf="subtitle"
|
||||
class="tw-text-main tw-mb-0"
|
||||
bitTypography="body2"
|
||||
[innerHTML]="subtitle"
|
||||
></p>
|
||||
<ng-content *ngIf="!subtitle"></ng-content>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -14,7 +14,7 @@ export class SpotlightComponent {
|
||||
// The title of the component
|
||||
@Input({ required: true }) title: string | null = null;
|
||||
// The subtitle of the component
|
||||
@Input({ required: true }) subtitle: string | null = null;
|
||||
@Input() subtitle?: string | null = null;
|
||||
// The text to display on the button
|
||||
@Input() buttonText?: string;
|
||||
// Wheter the component can be dismissed, if true, the component will not show a close button
|
||||
|
||||
@@ -28,7 +28,10 @@ export class EmptyVaultNudgeService extends DefaultSingleNudgeService {
|
||||
this.collectionService.decryptedCollections$,
|
||||
]).pipe(
|
||||
switchMap(([nudgeStatus, ciphers, orgs, collections]) => {
|
||||
const vaultHasContents = !(ciphers == null || ciphers.length === 0);
|
||||
const filteredCiphers = ciphers?.filter((cipher) => {
|
||||
return cipher.deletedDate == null;
|
||||
});
|
||||
const vaultHasContents = !(filteredCiphers == null || filteredCiphers.length === 0);
|
||||
if (orgs == null || orgs.length === 0) {
|
||||
return nudgeStatus.hasBadgeDismissed || nudgeStatus.hasSpotlightDismissed
|
||||
? of(nudgeStatus)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { combineLatest, Observable, switchMap } from "rxjs";
|
||||
import { combineLatest, from, Observable, of, switchMap } from "rxjs";
|
||||
import { catchError } from "rxjs/operators";
|
||||
|
||||
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -9,6 +10,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
|
||||
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
|
||||
import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service";
|
||||
|
||||
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Custom Nudge Service Checking Nudge Status For Welcome Nudge With Populated Vault
|
||||
*/
|
||||
@@ -21,27 +24,42 @@ export class HasItemsNudgeService extends DefaultSingleNudgeService {
|
||||
logService = inject(LogService);
|
||||
|
||||
nudgeStatus$(nudgeType: VaultNudgeType, userId: UserId): Observable<NudgeStatus> {
|
||||
const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe(
|
||||
catchError(() => {
|
||||
this.logService.error("Error getting profile creation date");
|
||||
// Default to today to ensure we show the nudge
|
||||
return of(new Date());
|
||||
}),
|
||||
);
|
||||
|
||||
return combineLatest([
|
||||
this.cipherService.cipherViews$(userId),
|
||||
this.getNudgeStatus$(nudgeType, userId),
|
||||
profileDate$,
|
||||
of(Date.now() - THIRTY_DAYS_MS),
|
||||
]).pipe(
|
||||
switchMap(async ([ciphers, nudgeStatus]) => {
|
||||
try {
|
||||
const creationDate = await this.vaultProfileService.getProfileCreationDate(userId);
|
||||
const thirtyDays = new Date(new Date().getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
const isRecentAcct = creationDate >= thirtyDays;
|
||||
switchMap(async ([ciphers, nudgeStatus, profileDate, profileCutoff]) => {
|
||||
const profileOlderThanCutoff = profileDate.getTime() < profileCutoff;
|
||||
const filteredCiphers = ciphers?.filter((cipher) => {
|
||||
return cipher.deletedDate == null;
|
||||
});
|
||||
|
||||
if (!isRecentAcct || nudgeStatus.hasSpotlightDismissed) {
|
||||
return nudgeStatus;
|
||||
} else {
|
||||
return {
|
||||
hasBadgeDismissed: ciphers == null || ciphers.length === 0,
|
||||
hasSpotlightDismissed: ciphers == null || ciphers.length === 0,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error("Failed to fetch profile creation date: ", error);
|
||||
if (profileOlderThanCutoff && filteredCiphers.length > 0) {
|
||||
const dismissedStatus = {
|
||||
hasSpotlightDismissed: true,
|
||||
hasBadgeDismissed: true,
|
||||
};
|
||||
// permanently dismiss both the Empty Vault Nudge and Has Items Vault Nudge if the profile is older than 30 days
|
||||
await this.setNudgeStatus(nudgeType, dismissedStatus, userId);
|
||||
await this.setNudgeStatus(VaultNudgeType.EmptyVaultNudge, dismissedStatus, userId);
|
||||
return dismissedStatus;
|
||||
} else if (nudgeStatus.hasSpotlightDismissed) {
|
||||
return nudgeStatus;
|
||||
} else {
|
||||
return {
|
||||
hasBadgeDismissed: filteredCiphers == null || filteredCiphers.length === 0,
|
||||
hasSpotlightDismissed: filteredCiphers == null || filteredCiphers.length === 0,
|
||||
};
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user