mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 09:13:33 +00:00
new customization options callout
This commit is contained in:
@@ -2093,6 +2093,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,35 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,4 +85,21 @@
|
|||||||
></app-vault-list-items-container>
|
></app-vault-list-items-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
style="position: absolute; bottom: 15px; right: 40px"
|
||||||
|
[bitPopoverTriggerFor]="newCustomizationOptionsCallout"
|
||||||
|
[position]="'above-end'"
|
||||||
|
[popoverOpen]="showNewCustomizationSettingsCallout"
|
||||||
|
#triggerRef="popoverTrigger"
|
||||||
|
></button>
|
||||||
|
<bit-popover
|
||||||
|
[title]="'newCustomizationOptionsCalloutTitle' | i18n"
|
||||||
|
#newCustomizationOptionsCallout
|
||||||
|
>
|
||||||
|
{{ "newCustomizationOptionsCalloutContent" | i18n }}
|
||||||
|
<a routerLink="/tabs/settings">
|
||||||
|
{{ "newCustomizationOptionsCalloutLink" | i18n }}
|
||||||
|
</a>
|
||||||
|
</bit-popover>
|
||||||
</popup-page>
|
</popup-page>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { CdkVirtualScrollableElement, ScrollingModule } from "@angular/cdk/scrol
|
|||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { RouterLink } from "@angular/router";
|
import { Router, RouterLink } from "@angular/router";
|
||||||
import {
|
import {
|
||||||
combineLatest,
|
combineLatest,
|
||||||
filter,
|
filter,
|
||||||
@@ -15,18 +15,22 @@ 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";
|
||||||
import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import {
|
import {
|
||||||
BannerComponent,
|
BannerComponent,
|
||||||
ButtonModule,
|
ButtonModule,
|
||||||
DialogService,
|
DialogService,
|
||||||
|
IconButtonModule,
|
||||||
Icons,
|
Icons,
|
||||||
NoItemsModule,
|
NoItemsModule,
|
||||||
|
PopoverModule,
|
||||||
|
SharedModule,
|
||||||
} from "@bitwarden/components";
|
} from "@bitwarden/components";
|
||||||
import { DecryptionFailureDialogComponent, VaultIcons } from "@bitwarden/vault";
|
import { DecryptionFailureDialogComponent, VaultIcons } from "@bitwarden/vault";
|
||||||
|
|
||||||
@@ -45,6 +49,7 @@ import {
|
|||||||
NewItemInitialValues,
|
NewItemInitialValues,
|
||||||
} from "./new-item-dropdown/new-item-dropdown-v2.component";
|
} from "./new-item-dropdown/new-item-dropdown-v2.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 ".";
|
||||||
|
|
||||||
@@ -60,6 +65,7 @@ enum VaultState {
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
BlockedInjectionBanner,
|
BlockedInjectionBanner,
|
||||||
|
PopoverModule,
|
||||||
PopupPageComponent,
|
PopupPageComponent,
|
||||||
PopupHeaderComponent,
|
PopupHeaderComponent,
|
||||||
PopOutComponent,
|
PopOutComponent,
|
||||||
@@ -72,12 +78,15 @@ enum VaultState {
|
|||||||
ButtonModule,
|
ButtonModule,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
NewItemDropdownV2Component,
|
NewItemDropdownV2Component,
|
||||||
|
IconButtonModule,
|
||||||
ScrollingModule,
|
ScrollingModule,
|
||||||
VaultHeaderV2Component,
|
VaultHeaderV2Component,
|
||||||
DecryptionFailureDialogComponent,
|
DecryptionFailureDialogComponent,
|
||||||
BannerComponent,
|
BannerComponent,
|
||||||
AtRiskPasswordCalloutComponent,
|
AtRiskPasswordCalloutComponent,
|
||||||
|
SharedModule,
|
||||||
],
|
],
|
||||||
|
providers: [VaultPageService],
|
||||||
})
|
})
|
||||||
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||||
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
|
@ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement;
|
||||||
@@ -110,6 +119,8 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
protected noResultsIcon = Icons.NoResults;
|
protected noResultsIcon = Icons.NoResults;
|
||||||
|
|
||||||
protected VaultStateEnum = VaultState;
|
protected VaultStateEnum = VaultState;
|
||||||
|
protected showNewCustomizationSettingsCallout = true;
|
||||||
|
protected activeUserId: UserId | null = null;
|
||||||
|
|
||||||
private allFilters$ = this.vaultPopupListFiltersService.allFilters$;
|
private allFilters$ = this.vaultPopupListFiltersService.allFilters$;
|
||||||
|
|
||||||
@@ -121,6 +132,9 @@ 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,
|
||||||
|
private router: Router,
|
||||||
) {
|
) {
|
||||||
combineLatest([
|
combineLatest([
|
||||||
this.vaultPopupItemsService.emptyVault$,
|
this.vaultPopupItemsService.emptyVault$,
|
||||||
@@ -158,10 +172,10 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|
||||||
this.cipherService
|
this.cipherService
|
||||||
.failedToDecryptCiphers$(activeUserId)
|
.failedToDecryptCiphers$(this.activeUserId)
|
||||||
.pipe(
|
.pipe(
|
||||||
map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
|
map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
|
||||||
filter((ciphers) => ciphers.length > 0),
|
filter((ciphers) => ciphers.length > 0),
|
||||||
@@ -173,10 +187,22 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
cipherIds: ciphers.map((c) => c.id as CipherId),
|
cipherIds: ciphers.map((c) => c.id as CipherId),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// const profileCreatedDate = await this.vaultProfileService.getProfileCreationDate(
|
||||||
|
// this.activeUserId,
|
||||||
|
// );
|
||||||
|
// const hasCalloutBeenDismissed = this.vaultPageService.isCalloutDismissed(this.activeUserId);
|
||||||
|
// Show the new customization settings callout if the user has not dismissed it and the account was created before December 25, 2024
|
||||||
|
this.showNewCustomizationSettingsCallout = true;
|
||||||
|
// this.showNewCustomizationSettingsCallout =
|
||||||
|
// !hasCalloutBeenDismissed && profileCreatedDate < new Date("2024-12-25");
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
async ngOnDestroy() {
|
||||||
this.vaultScrollPositionService.stop();
|
this.vaultScrollPositionService.stop();
|
||||||
|
if (this.activeUserId) {
|
||||||
|
await this.vaultPageService.dismissCallout(this.activeUserId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly FeatureFlag = FeatureFlag;
|
protected readonly FeatureFlag = FeatureFlag;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export * from "./search";
|
|||||||
export * from "./section";
|
export * from "./section";
|
||||||
export * from "./select";
|
export * from "./select";
|
||||||
export * from "./shared/compact-mode.service";
|
export * from "./shared/compact-mode.service";
|
||||||
|
export * from "./shared";
|
||||||
export * from "./table";
|
export * from "./table";
|
||||||
export * from "./tabs";
|
export * from "./tabs";
|
||||||
export * from "./toast";
|
export * from "./toast";
|
||||||
|
|||||||
@@ -194,11 +194,11 @@
|
|||||||
--tw-ring-offset-color: #002b36;
|
--tw-ring-offset-color: #002b36;
|
||||||
}
|
}
|
||||||
|
|
||||||
@import "./popover/popover.component.css";
|
|
||||||
@import "./search/search.component.css";
|
@import "./search/search.component.css";
|
||||||
|
|
||||||
@import "./toast/toast.tokens.css";
|
@import "./toast/toast.tokens.css";
|
||||||
@import "./toast/toastr.css";
|
@import "./toast/toastr.css";
|
||||||
|
@import "./popover/popover.component.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tw-break-words does not work with table cells:
|
* tw-break-words does not work with table cells:
|
||||||
|
|||||||
Reference in New Issue
Block a user