1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 21:33:27 +00:00

[PM-18627] Remove customization settings popover (#14713)

* chore: remove customization popover strings, refs PM-18627

* chore: delete new settings callout ts/html component, refs PM-18627

* chore: remove new customization code from vault-v2 component, refs PM-18627:
:q

* chore: delete vault-page service, refs PM-18627

* chore: add state migration to remove data, refs PM-18627
This commit is contained in:
Vincent Salucci
2025-05-14 09:26:47 -05:00
committed by GitHub
parent 7de6befbf4
commit 3e0cc7ca7f
9 changed files with 77 additions and 162 deletions

View File

@@ -2208,15 +2208,6 @@
"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"

View File

@@ -1,29 +0,0 @@
<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
tabIndex="0"
bitLink
class="tw-font-bold"
linkType="primary"
routerLink="/appearance"
(keydown.enter)="goToAppearance()"
>
{{ "newCustomizationOptionsCalloutLink" | i18n }}
</a>
</div>
</bit-popover>
</ng-container>

View File

@@ -1,81 +0,0 @@
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 { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { ButtonModule, PopoverModule } from "@bitwarden/components";
import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service";
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,
private copyButtonService: VaultPopupCopyButtonsService,
private vaultSettingsService: VaultSettingsService,
) {}
async ngOnInit() {
this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const showQuickCopyActions = await firstValueFrom(this.copyButtonService.showQuickCopyActions$);
const clickItemsToAutofillVaultView = await firstValueFrom(
this.vaultSettingsService.clickItemsToAutofillVaultView$,
);
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 =
!showQuickCopyActions &&
!clickItemsToAutofillVaultView &&
!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();
}
}

View File

@@ -1,35 +0,0 @@
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

@@ -107,5 +107,4 @@
></app-vault-list-items-container>
</div>
</ng-container>
<new-settings-callout></new-settings-callout>
</popup-page>

View File

@@ -56,9 +56,7 @@ 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 ".";
@@ -90,12 +88,10 @@ enum VaultState {
ScrollingModule,
VaultHeaderV2Component,
AtRiskPasswordCalloutComponent,
NewSettingsCalloutComponent,
SpotlightComponent,
RouterModule,
TypographyModule,
],
providers: [VaultPageService],
})
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
@@ -152,7 +148,6 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
protected noResultsIcon = Icons.NoResults;
protected VaultStateEnum = VaultState;
protected showNewCustomizationSettingsCallout = false;
constructor(
private vaultPopupItemsService: VaultPopupItemsService,

View File

@@ -69,12 +69,13 @@ import { MoveLastSyncDate } from "./migrations/68-move-last-sync-date";
import { MigrateIncorrectFolderKey } from "./migrations/69-migrate-incorrect-folder-key";
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
import { RemoveAcBannersDismissed } from "./migrations/70-remove-ac-banner-dismissed";
import { RemoveNewCustomizationOptionsCalloutDismissed } from "./migrations/71-remove-new-customization-options-callout-dismissed";
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global";
import { MinVersionMigrator } from "./migrations/min-version";
export const MIN_VERSION = 3;
export const CURRENT_VERSION = 70;
export const CURRENT_VERSION = 71;
export type MinVersion = typeof MIN_VERSION;
export function createMigrationBuilder() {
@@ -146,7 +147,8 @@ export function createMigrationBuilder() {
.with(RemoveUnassignedItemsBannerDismissed, 66, 67)
.with(MoveLastSyncDate, 67, 68)
.with(MigrateIncorrectFolderKey, 68, 69)
.with(RemoveAcBannersDismissed, 69, CURRENT_VERSION);
.with(RemoveAcBannersDismissed, 69, 70)
.with(RemoveNewCustomizationOptionsCalloutDismissed, 70, CURRENT_VERSION);
}
export async function currentVersion(

View File

@@ -0,0 +1,50 @@
import { runMigrator } from "../migration-helper.spec";
import { IRREVERSIBLE } from "../migrator";
import { RemoveNewCustomizationOptionsCalloutDismissed } from "./71-remove-new-customization-options-callout-dismissed";
describe("RemoveNewCustomizationOptionsCalloutDismissed", () => {
const sut = new RemoveNewCustomizationOptionsCalloutDismissed(70, 71);
describe("migrate", () => {
it("deletes new customization options callout dismissed from all users", async () => {
const output = await runMigrator(sut, {
global_account_accounts: {
user1: {
email: "user1@email.com",
name: "User 1",
emailVerified: true,
},
user2: {
email: "user2@email.com",
name: "User 2",
emailVerified: true,
},
},
user_user1_bannersDismissed_newCustomizationOptionsCalloutDismissed: true,
user_user2_bannersDismissed_newCustomizationOptionsCalloutDismissed: true,
});
expect(output).toEqual({
global_account_accounts: {
user1: {
email: "user1@email.com",
name: "User 1",
emailVerified: true,
},
user2: {
email: "user2@email.com",
name: "User 2",
emailVerified: true,
},
},
});
});
});
describe("rollback", () => {
it("is irreversible", async () => {
await expect(runMigrator(sut, {}, "rollback")).rejects.toThrow(IRREVERSIBLE);
});
});
});

View File

@@ -0,0 +1,23 @@
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { IRREVERSIBLE, Migrator } from "../migrator";
export const SHOW_CALLOUT_KEY: KeyDefinitionLike = {
key: "newCustomizationOptionsCalloutDismissed",
stateDefinition: { name: "bannersDismissed" },
};
export class RemoveNewCustomizationOptionsCalloutDismissed extends Migrator<70, 71> {
async migrate(helper: MigrationHelper): Promise<void> {
await Promise.all(
(await helper.getAccounts()).map(async ({ userId }) => {
if (helper.getFromUser(userId, SHOW_CALLOUT_KEY) != null) {
await helper.removeFromUser(userId, SHOW_CALLOUT_KEY);
}
}),
);
}
async rollback(helper: MigrationHelper): Promise<void> {
throw IRREVERSIBLE;
}
}