From fb20aa556b62ff6832bd52ef1e22319599625b6f Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Mon, 24 Feb 2025 14:51:55 -0800 Subject: [PATCH] [PM-18131] - Add popover prompt for new settings (#13518) * Update the Appearance settings page to include click to fill setting * fix tests * fix tests * add tests * new customization options callout * use classes instead of inline styling * revert changes to index and tw-theme * remove shared module * Revert "remove shared module" This reverts commit 0b68aaae232bd4c7c3cd19a2f88038ef0fce118d. * Revert "revert changes to index and tw-theme" This reverts commit 4a05f0ca203b15bcf9edb49f564e5d4b75a337f3. * Revert "use classes instead of inline styling" This reverts commit 0e441c4284bef133aa4428b636acf70b651bd4a5. * Revert "new customization options callout" This reverts commit f3054c9b2767436708cd890fa77e3309fda94871. * new customization options callout * use classes instead of inline styling * revert changes to index and tw-theme * remove shared module * finalize new settings callout * revert changes to vault-v2.component * remove unused modules. add error handling. adjust html --- apps/browser/src/_locales/en/messages.json | 9 +++ .../new-settings-callout.component.html | 22 ++++++ .../new-settings-callout.component.ts | 69 +++++++++++++++++++ .../components/vault-v2/vault-page.service.ts | 41 +++++++++++ .../vault-v2/vault-v2.component.html | 1 + .../components/vault-v2/vault-v2.component.ts | 10 ++- 6 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html create mode 100644 apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts create mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 239de13afd..b2d042945b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2143,6 +2143,15 @@ "vaultTimeoutAction1": { "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": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html new file mode 100644 index 0000000000..727a8e938a --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html @@ -0,0 +1,22 @@ + + + + + {{ "newCustomizationOptionsCalloutContent" | i18n }} + + {{ "newCustomizationOptionsCalloutLink" | i18n }} + + + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts new file mode 100644 index 0000000000..7154990647 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts @@ -0,0 +1,69 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +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 { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { ButtonModule, PopoverModule } from "@bitwarden/components"; + +import { VaultPageService } from "../vault-page.service"; + +@Component({ + selector: "new-settings-callout", + templateUrl: "new-settings-callout.component.html", + standalone: true, + imports: [PopoverModule, JslibModule, CommonModule, ButtonModule], + providers: [VaultPageService], +}) +export class NewSettingsCalloutComponent implements OnInit, OnDestroy { + protected showNewCustomizationSettingsCallout = false; + protected activeUserId: UserId | null = null; + + constructor( + private accountService: AccountService, + private vaultProfileService: VaultProfileService, + private vaultPageService: VaultPageService, + private router: Router, + private logService: LogService, + ) {} + + async ngOnInit() { + this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + let profileCreatedDate: Date; + + try { + profileCreatedDate = await this.vaultProfileService.getProfileCreationDate(this.activeUserId); + } catch (e) { + this.logService.error("Error getting profile creation date", e); + // Default to before the cutoff date to ensure the callout is shown + profileCreatedDate = new Date("2024-12-24"); + } + + const hasCalloutBeenDismissed = await firstValueFrom( + this.vaultPageService.isCalloutDismissed(this.activeUserId), + ); + + this.showNewCustomizationSettingsCallout = + !hasCalloutBeenDismissed && profileCreatedDate < new Date("2024-12-25"); + } + + async goToAppearance() { + await this.router.navigate(["/appearance"]); + } + + async dismissCallout() { + if (this.activeUserId) { + await this.vaultPageService.dismissCallout(this.activeUserId); + } + } + + async ngOnDestroy() { + await this.dismissCallout(); + } +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts new file mode 100644 index 0000000000..75354298c2 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts @@ -0,0 +1,41 @@ +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( + BANNERS_DISMISSED_DISK, + "newCustomizationOptionsCalloutDismissed", + { + deserializer: (calloutDismissed) => calloutDismissed, + clearOn: [], // Do not clear dismissed callouts + }, +); + +@Injectable() +export class VaultPageService { + private stateProvider = inject(StateProvider); + + async unDismissCallout(userId: UserId): Promise { + await this.stateProvider + .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) + .update(() => false); + } + + isCalloutDismissed(userId: UserId): Observable { + return this.stateProvider + .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) + .state$.pipe(map((dismissed) => !!dismissed)); + } + + async dismissCallout(userId: UserId): Promise { + await this.stateProvider + .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) + .update(() => true); + } +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 8cb538a429..bb7cd8e52d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -85,4 +85,5 @@ > + diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 73b691bc4a..1ae1b205af 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -15,6 +15,7 @@ import { } from "rxjs"; 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 { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -44,7 +45,9 @@ import { NewItemDropdownV2Component, NewItemInitialValues, } from "./new-item-dropdown/new-item-dropdown-v2.component"; +import { NewSettingsCalloutComponent } from "./new-settings-callout/new-settings-callout.component"; import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component"; +import { VaultPageService } from "./vault-page.service"; import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "."; @@ -77,7 +80,9 @@ enum VaultState { DecryptionFailureDialogComponent, BannerComponent, AtRiskPasswordCalloutComponent, + NewSettingsCalloutComponent, ], + providers: [VaultPageService], }) export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { @ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement; @@ -115,6 +120,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { protected noResultsIcon = Icons.NoResults; protected VaultStateEnum = VaultState; + protected showNewCustomizationSettingsCallout = false; constructor( private vaultPopupItemsService: VaultPopupItemsService, @@ -124,6 +130,8 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { private destroyRef: DestroyRef, private cipherService: CipherService, private dialogService: DialogService, + private vaultProfileService: VaultProfileService, + private vaultPageService: VaultPageService, ) { combineLatest([ this.vaultPopupItemsService.emptyVault$, @@ -178,7 +186,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { }); } - ngOnDestroy(): void { + ngOnDestroy() { this.vaultScrollPositionService.stop(); }