1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 09:13:33 +00:00

new customization options callout

This commit is contained in:
jaasen-livefront
2025-02-18 17:29:02 -08:00
parent 80bf2db0d9
commit f3054c9b27
6 changed files with 94 additions and 6 deletions

View File

@@ -2093,6 +2093,15 @@
"vaultTimeoutAction1": { "vaultTimeoutAction1": {
"message": "Timeout action" "message": "Timeout action"
}, },
"newCustomizationOptionsCalloutTitle": {
"message": "New customization options"
},
"newCustomizationOptionsCalloutContent": {
"message": "Customize your vault experience with quick copy actions, compact mode, and more!"
},
"newCustomizationOptionsCalloutLink": {
"message": "View all Appearance settings"
},
"lock": { "lock": {
"message": "Lock", "message": "Lock",
"description": "Verb form: to make secure or inaccessible by" "description": "Verb form: to make secure or inaccessible by"

View File

@@ -0,0 +1,35 @@
import { inject, Injectable } from "@angular/core";
import { map, Observable } from "rxjs";
import {
BANNERS_DISMISSED_DISK,
StateProvider,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
export const NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY = new UserKeyDefinition<boolean>(
BANNERS_DISMISSED_DISK,
"newCustomizationOptionsCalloutDismissed",
{
deserializer: (calloutDismissed) => calloutDismissed,
clearOn: [], // Do not clear dismissed callouts
},
);
@Injectable()
export class VaultPageService {
private stateProvider = inject(StateProvider);
isCalloutDismissed(userId: UserId): Observable<boolean> {
return this.stateProvider
.getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY)
.state$.pipe(map((dismissed) => !!dismissed));
}
async dismissCallout(userId: UserId): Promise<void> {
await this.stateProvider
.getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY)
.update(() => true);
}
}

View File

@@ -85,4 +85,21 @@
></app-vault-list-items-container> ></app-vault-list-items-container>
</div> </div>
</ng-container> </ng-container>
<button
type="button"
style="position: absolute; bottom: 15px; right: 40px"
[bitPopoverTriggerFor]="newCustomizationOptionsCallout"
[position]="'above-end'"
[popoverOpen]="showNewCustomizationSettingsCallout"
#triggerRef="popoverTrigger"
></button>
<bit-popover
[title]="'newCustomizationOptionsCalloutTitle' | i18n"
#newCustomizationOptionsCallout
>
{{ "newCustomizationOptionsCalloutContent" | i18n }}
<a routerLink="/tabs/settings">
{{ "newCustomizationOptionsCalloutLink" | i18n }}
</a>
</bit-popover>
</popup-page> </popup-page>

View File

@@ -2,7 +2,7 @@ import { CdkVirtualScrollableElement, ScrollingModule } from "@angular/cdk/scrol
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { RouterLink } from "@angular/router"; import { Router, RouterLink } from "@angular/router";
import { import {
combineLatest, combineLatest,
filter, filter,
@@ -15,18 +15,22 @@ import {
} from "rxjs"; } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { import {
BannerComponent, BannerComponent,
ButtonModule, ButtonModule,
DialogService, DialogService,
IconButtonModule,
Icons, Icons,
NoItemsModule, NoItemsModule,
PopoverModule,
SharedModule,
} from "@bitwarden/components"; } from "@bitwarden/components";
import { DecryptionFailureDialogComponent, VaultIcons } from "@bitwarden/vault"; import { DecryptionFailureDialogComponent, VaultIcons } from "@bitwarden/vault";
@@ -45,6 +49,7 @@ import {
NewItemInitialValues, NewItemInitialValues,
} from "./new-item-dropdown/new-item-dropdown-v2.component"; } from "./new-item-dropdown/new-item-dropdown-v2.component";
import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component"; import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component";
import { VaultPageService } from "./vault-page.service";
import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "."; import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from ".";
@@ -60,6 +65,7 @@ enum VaultState {
standalone: true, standalone: true,
imports: [ imports: [
BlockedInjectionBanner, BlockedInjectionBanner,
PopoverModule,
PopupPageComponent, PopupPageComponent,
PopupHeaderComponent, PopupHeaderComponent,
PopOutComponent, PopOutComponent,
@@ -72,12 +78,15 @@ enum VaultState {
ButtonModule, ButtonModule,
RouterLink, RouterLink,
NewItemDropdownV2Component, NewItemDropdownV2Component,
IconButtonModule,
ScrollingModule, ScrollingModule,
VaultHeaderV2Component, VaultHeaderV2Component,
DecryptionFailureDialogComponent, DecryptionFailureDialogComponent,
BannerComponent, BannerComponent,
AtRiskPasswordCalloutComponent, AtRiskPasswordCalloutComponent,
SharedModule,
], ],
providers: [VaultPageService],
}) })
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement; @ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
@@ -110,6 +119,8 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
protected noResultsIcon = Icons.NoResults; protected noResultsIcon = Icons.NoResults;
protected VaultStateEnum = VaultState; protected VaultStateEnum = VaultState;
protected showNewCustomizationSettingsCallout = true;
protected activeUserId: UserId | null = null;
private allFilters$ = this.vaultPopupListFiltersService.allFilters$; private allFilters$ = this.vaultPopupListFiltersService.allFilters$;
@@ -121,6 +132,9 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
private destroyRef: DestroyRef, private destroyRef: DestroyRef,
private cipherService: CipherService, private cipherService: CipherService,
private dialogService: DialogService, private dialogService: DialogService,
private vaultProfileService: VaultProfileService,
private vaultPageService: VaultPageService,
private router: Router,
) { ) {
combineLatest([ combineLatest([
this.vaultPopupItemsService.emptyVault$, this.vaultPopupItemsService.emptyVault$,
@@ -158,10 +172,10 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
} }
async ngOnInit() { async ngOnInit() {
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
this.cipherService this.cipherService
.failedToDecryptCiphers$(activeUserId) .failedToDecryptCiphers$(this.activeUserId)
.pipe( .pipe(
map((ciphers) => ciphers.filter((c) => !c.isDeleted)), map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
filter((ciphers) => ciphers.length > 0), filter((ciphers) => ciphers.length > 0),
@@ -173,10 +187,22 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
cipherIds: ciphers.map((c) => c.id as CipherId), cipherIds: ciphers.map((c) => c.id as CipherId),
}); });
}); });
// const profileCreatedDate = await this.vaultProfileService.getProfileCreationDate(
// this.activeUserId,
// );
// const hasCalloutBeenDismissed = this.vaultPageService.isCalloutDismissed(this.activeUserId);
// Show the new customization settings callout if the user has not dismissed it and the account was created before December 25, 2024
this.showNewCustomizationSettingsCallout = true;
// this.showNewCustomizationSettingsCallout =
// !hasCalloutBeenDismissed && profileCreatedDate < new Date("2024-12-25");
} }
ngOnDestroy(): void { async ngOnDestroy() {
this.vaultScrollPositionService.stop(); this.vaultScrollPositionService.stop();
if (this.activeUserId) {
await this.vaultPageService.dismissCallout(this.activeUserId);
}
} }
protected readonly FeatureFlag = FeatureFlag; protected readonly FeatureFlag = FeatureFlag;

View File

@@ -35,6 +35,7 @@ export * from "./search";
export * from "./section"; export * from "./section";
export * from "./select"; export * from "./select";
export * from "./shared/compact-mode.service"; export * from "./shared/compact-mode.service";
export * from "./shared";
export * from "./table"; export * from "./table";
export * from "./tabs"; export * from "./tabs";
export * from "./toast"; export * from "./toast";

View File

@@ -194,11 +194,11 @@
--tw-ring-offset-color: #002b36; --tw-ring-offset-color: #002b36;
} }
@import "./popover/popover.component.css";
@import "./search/search.component.css"; @import "./search/search.component.css";
@import "./toast/toast.tokens.css"; @import "./toast/toast.tokens.css";
@import "./toast/toastr.css"; @import "./toast/toastr.css";
@import "./popover/popover.component.css";
/** /**
* tw-break-words does not work with table cells: * tw-break-words does not work with table cells: