mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +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:
@@ -2208,15 +2208,6 @@
|
|||||||
"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"
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -107,5 +107,4 @@
|
|||||||
></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>
|
||||||
|
|||||||
@@ -56,9 +56,7 @@ 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 ".";
|
||||||
|
|
||||||
@@ -90,12 +88,10 @@ enum VaultState {
|
|||||||
ScrollingModule,
|
ScrollingModule,
|
||||||
VaultHeaderV2Component,
|
VaultHeaderV2Component,
|
||||||
AtRiskPasswordCalloutComponent,
|
AtRiskPasswordCalloutComponent,
|
||||||
NewSettingsCalloutComponent,
|
|
||||||
SpotlightComponent,
|
SpotlightComponent,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
TypographyModule,
|
TypographyModule,
|
||||||
],
|
],
|
||||||
providers: [VaultPageService],
|
|
||||||
})
|
})
|
||||||
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||||
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
|
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
|
||||||
@@ -152,7 +148,6 @@ 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,
|
||||||
|
|||||||
@@ -69,12 +69,13 @@ import { MoveLastSyncDate } from "./migrations/68-move-last-sync-date";
|
|||||||
import { MigrateIncorrectFolderKey } from "./migrations/69-migrate-incorrect-folder-key";
|
import { MigrateIncorrectFolderKey } from "./migrations/69-migrate-incorrect-folder-key";
|
||||||
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
|
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
|
||||||
import { RemoveAcBannersDismissed } from "./migrations/70-remove-ac-banner-dismissed";
|
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 { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
|
||||||
import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global";
|
import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global";
|
||||||
import { MinVersionMigrator } from "./migrations/min-version";
|
import { MinVersionMigrator } from "./migrations/min-version";
|
||||||
|
|
||||||
export const MIN_VERSION = 3;
|
export const MIN_VERSION = 3;
|
||||||
export const CURRENT_VERSION = 70;
|
export const CURRENT_VERSION = 71;
|
||||||
export type MinVersion = typeof MIN_VERSION;
|
export type MinVersion = typeof MIN_VERSION;
|
||||||
|
|
||||||
export function createMigrationBuilder() {
|
export function createMigrationBuilder() {
|
||||||
@@ -146,7 +147,8 @@ export function createMigrationBuilder() {
|
|||||||
.with(RemoveUnassignedItemsBannerDismissed, 66, 67)
|
.with(RemoveUnassignedItemsBannerDismissed, 66, 67)
|
||||||
.with(MoveLastSyncDate, 67, 68)
|
.with(MoveLastSyncDate, 67, 68)
|
||||||
.with(MigrateIncorrectFolderKey, 68, 69)
|
.with(MigrateIncorrectFolderKey, 68, 69)
|
||||||
.with(RemoveAcBannersDismissed, 69, CURRENT_VERSION);
|
.with(RemoveAcBannersDismissed, 69, 70)
|
||||||
|
.with(RemoveNewCustomizationOptionsCalloutDismissed, 70, CURRENT_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function currentVersion(
|
export async function currentVersion(
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user