mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +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": {
|
"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"
|
||||||
|
|||||||
@@ -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>
|
></app-vault-list-items-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<new-settings-callout></new-settings-callout>
|
||||||
</popup-page>
|
</popup-page>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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";
|
||||||
@@ -44,7 +45,9 @@ import {
|
|||||||
NewItemDropdownV2Component,
|
NewItemDropdownV2Component,
|
||||||
NewItemInitialValues,
|
NewItemInitialValues,
|
||||||
} from "./new-item-dropdown/new-item-dropdown-v2.component";
|
} 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 { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component";
|
||||||
|
import { VaultPageService } from "./vault-page.service";
|
||||||
|
|
||||||
import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from ".";
|
import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from ".";
|
||||||
|
|
||||||
@@ -77,7 +80,9 @@ enum VaultState {
|
|||||||
DecryptionFailureDialogComponent,
|
DecryptionFailureDialogComponent,
|
||||||
BannerComponent,
|
BannerComponent,
|
||||||
AtRiskPasswordCalloutComponent,
|
AtRiskPasswordCalloutComponent,
|
||||||
|
NewSettingsCalloutComponent,
|
||||||
],
|
],
|
||||||
|
providers: [VaultPageService],
|
||||||
})
|
})
|
||||||
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||||
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
|
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
|
||||||
@@ -115,6 +120,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
protected noResultsIcon = Icons.NoResults;
|
protected noResultsIcon = Icons.NoResults;
|
||||||
|
|
||||||
protected VaultStateEnum = VaultState;
|
protected VaultStateEnum = VaultState;
|
||||||
|
protected showNewCustomizationSettingsCallout = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private vaultPopupItemsService: VaultPopupItemsService,
|
private vaultPopupItemsService: VaultPopupItemsService,
|
||||||
@@ -124,6 +130,8 @@ 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,
|
||||||
) {
|
) {
|
||||||
combineLatest([
|
combineLatest([
|
||||||
this.vaultPopupItemsService.emptyVault$,
|
this.vaultPopupItemsService.emptyVault$,
|
||||||
@@ -178,7 +186,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy() {
|
||||||
this.vaultScrollPositionService.stop();
|
this.vaultScrollPositionService.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user