mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[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 commit0b68aaae23. * Revert "revert changes to index and tw-theme" This reverts commit4a05f0ca20. * Revert "use classes instead of inline styling" This reverts commit0e441c4284. * Revert "new customization options callout" This reverts commitf3054c9b27. * 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
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<ng-container *ngIf="showNewCustomizationSettingsCallout">
|
||||
<button
|
||||
type="button"
|
||||
class="tw-absolute tw-bottom-[12px] tw-right-[47px]"
|
||||
[bitPopoverTriggerFor]="newCustomizationOptionsCallout"
|
||||
[position]="'above-end'"
|
||||
[popoverOpen]="true"
|
||||
#triggerRef="popoverTrigger"
|
||||
></button>
|
||||
<bit-popover
|
||||
[title]="'newCustomizationOptionsCalloutTitle' | i18n"
|
||||
#newCustomizationOptionsCallout
|
||||
(closed)="dismissCallout()"
|
||||
>
|
||||
<div bitTypography="body2" (click)="goToAppearance()">
|
||||
{{ "newCustomizationOptionsCalloutContent" | i18n }}
|
||||
<a routerLink="/appearance">
|
||||
{{ "newCustomizationOptionsCalloutLink" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
</bit-popover>
|
||||
</ng-container>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<boolean>(
|
||||
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<void> {
|
||||
await this.stateProvider
|
||||
.getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY)
|
||||
.update(() => false);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -85,4 +85,5 @@
|
||||
></app-vault-list-items-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<new-settings-callout></new-settings-callout>
|
||||
</popup-page>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user