1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 11:43:51 +00:00

Merge branch 'main' into billing/pm-29602/update-cart-summary

This commit is contained in:
Stephon Brown
2026-01-27 18:45:22 -05:00
29 changed files with 112 additions and 247 deletions

View File

@@ -1,7 +1,6 @@
{
"devFlags": {},
"flags": {
"accountSwitching": false,
"sdk": true
}
}

View File

@@ -4,8 +4,5 @@
"base": "https://localhost:8080"
},
"skipWelcomeOnInstall": true
},
"flags": {
"accountSwitching": true
}
}

View File

@@ -1,5 +0,0 @@
{
"flags": {
"accountSwitching": true
}
}

View File

@@ -7,13 +7,16 @@
</popup-header>
<ng-container *ngIf="availableAccounts$ | async as availableAccounts">
<bit-section [disableMargin]="!enableAccountSwitching">
<bit-section [disableMargin]="!(enableAccountSwitching$ | async)">
<ng-container *ngFor="let availableAccount of availableAccounts; first as isFirst">
<div *ngIf="availableAccount.isActive" [ngClass]="{ 'tw-mb-6': enableAccountSwitching }">
<div
*ngIf="availableAccount.isActive"
[ngClass]="{ 'tw-mb-6': enableAccountSwitching$ | async }"
>
<auth-account [account]="availableAccount" (loading)="loading = $event"></auth-account>
</div>
<ng-container *ngIf="enableAccountSwitching">
<ng-container *ngIf="enableAccountSwitching$ | async">
<bit-section-header *ngIf="isFirst">
<h2 bitTypography="h6">{{ "availableAccounts" | i18n }}</h2>
</bit-section-header>

View File

@@ -1,7 +1,7 @@
import { CommonModule, Location } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { Subject, firstValueFrom, map, of, startWith, switchMap } from "rxjs";
import { Observable, Subject, firstValueFrom, map, of, startWith, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { LockService, LogoutService } from "@bitwarden/auth/common";
@@ -24,7 +24,6 @@ import {
TypographyModule,
} from "@bitwarden/components";
import { enableAccountSwitching } from "../../../platform/flags";
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
@@ -59,7 +58,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
loading = false;
activeUserCanLock = false;
enableAccountSwitching = true;
enableAccountSwitching$: Observable<boolean>;
constructor(
private accountSwitcherService: AccountSwitcherService,
@@ -72,7 +71,9 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
private authService: AuthService,
private lockService: LockService,
private logoutService: LogoutService,
) {}
) {
this.enableAccountSwitching$ = this.accountSwitcherService.accountSwitchingEnabled$();
}
get accountLimit() {
return this.accountSwitcherService.ACCOUNT_LIMIT;
@@ -97,19 +98,21 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
switchMap((accounts) => {
// If account switching is disabled, don't show the lock all button
// as only one account should be shown.
if (!enableAccountSwitching()) {
return of(false);
}
return this.accountSwitcherService.accountSwitchingEnabled$().pipe(
switchMap((enabled) => {
if (!enabled) {
return of(false);
}
// When there are an inactive accounts provide the option to lock all accounts
// Note: "Add account" is counted as an inactive account, so check for more than one account
return of(accounts.length > 1);
// When there are inactive accounts provide the option to lock all accounts
// Note: "Add account" is counted as an inactive account, so check for more than one account
return of(accounts.length > 1);
}),
);
}),
);
async ngOnInit() {
this.enableAccountSwitching = enableAccountSwitching();
const availableVaultTimeoutActions = await firstValueFrom(
this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(),
);

View File

@@ -9,6 +9,7 @@ import {
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import {
Environment,
EnvironmentService,
@@ -37,6 +38,7 @@ describe("AccountSwitcherService", () => {
const environmentService = mock<EnvironmentService>();
const logService = mock<LogService>();
const authService = mock<AuthService>();
const configService = mock<ConfigService>();
let accountSwitcherService: AccountSwitcherService;
@@ -60,6 +62,7 @@ describe("AccountSwitcherService", () => {
messagingService,
environmentService,
logService,
configService,
authService,
);
});

View File

@@ -7,6 +7,7 @@ import {
filter,
firstValueFrom,
map,
of,
switchMap,
throwError,
timeout,
@@ -17,11 +18,14 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { UserId } from "@bitwarden/common/types/guid";
import { BrowserApi } from "../../../../platform/browser/browser-api";
import { fromChromeEvent } from "../../../../platform/browser/from-chrome-event";
export type AvailableAccount = {
@@ -52,6 +56,7 @@ export class AccountSwitcherService {
private messagingService: MessagingService,
private environmentService: EnvironmentService,
private logService: LogService,
private configService: ConfigService,
authService: AuthService,
) {
this.availableAccounts$ = combineLatest([
@@ -123,6 +128,19 @@ export class AccountSwitcherService {
);
}
/*
* PM-5594: This was a compile-time flag (default true) which made an exception for Safari in platform/flags.
* The truthiness of AccountSwitching has been enshrined at this point, so those compile-time flags have been removed
* in favor of this method to allow easier access to the config service for controlling Safari. Unwinding the Safari
* flag should be more straightforward from this consolidation.
*/
accountSwitchingEnabled$(): Observable<boolean> {
if (BrowserApi.isSafariApi) {
return this.configService.getFeatureFlag$(FeatureFlag.SafariAccountSwitching);
}
return of(true);
}
get specialAccountAddId() {
return this.SPECIAL_ADD_ACCOUNT_ID;
}

View File

@@ -8,7 +8,9 @@
<div class="tw-bg-background-alt">
<p>
{{
accountSwitcherEnabled ? ("excludedDomainsDescAlt" | i18n) : ("excludedDomainsDesc" | i18n)
(accountSwitcherEnabled$ | async)
? ("excludedDomainsDescAlt" | i18n)
: ("excludedDomainsDesc" | i18n)
}}
</p>
<bit-section *ngIf="!isLoading">

View File

@@ -15,7 +15,7 @@ import {
FormArray,
} from "@angular/forms";
import { RouterModule } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { Observable, Subject, takeUntil } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
@@ -35,7 +35,7 @@ import {
TypographyModule,
} from "@bitwarden/components";
import { enableAccountSwitching } from "../../../platform/flags";
import { AccountSwitcherService } from "../../../auth/popup/account-switching/services/account-switcher.service";
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component";
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
@@ -74,7 +74,8 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
@ViewChildren("uriInput") uriInputElements: QueryList<ElementRef<HTMLInputElement>> =
new QueryList();
accountSwitcherEnabled = false;
readonly accountSwitcherEnabled$: Observable<boolean> =
this.accountSwitcherService.accountSwitchingEnabled$();
dataIsPristine = true;
isLoading = false;
excludedDomainsState: string[] = [];
@@ -95,9 +96,8 @@ export class ExcludedDomainsComponent implements AfterViewInit, OnDestroy {
private toastService: ToastService,
private formBuilder: FormBuilder,
private popupRouterCacheService: PopupRouterCacheService,
) {
this.accountSwitcherEnabled = enableAccountSwitching();
}
private accountSwitcherService: AccountSwitcherService,
) {}
get domainForms() {
return this.domainListForm.get("domains") as FormArray;

View File

@@ -8,12 +8,8 @@ import {
import { GroupPolicyEnvironment } from "../admin-console/types/group-policy-environment";
import { BrowserApi } from "./browser/browser-api";
// required to avoid linting errors when there are no flags
export type Flags = {
accountSwitching?: boolean;
} & SharedFlags;
export type Flags = SharedFlags;
// required to avoid linting errors when there are no flags
export type DevFlags = {
@@ -31,14 +27,3 @@ export function devFlagEnabled(flag: keyof DevFlags) {
export function devFlagValue(flag: keyof DevFlags) {
return baseDevFlagValue(flag);
}
/** Helper method to sync flag specifically for account switching, which as platform-based values.
* If this pattern needs to be repeated, it's better handled by increasing complexity of webpack configurations
* Not by expanding these flag getters.
*/
export function enableAccountSwitching(): boolean {
if (BrowserApi.isSafariApi) {
return false;
}
return flagEnabled("accountSwitching");
}

View File

@@ -5,8 +5,7 @@
[showRefresh]="showRefresh"
(onRefresh)="refreshCurrentTab()"
[description]="(showEmptyAutofillTip$ | async) ? ('autofillSuggestionsTip' | i18n) : undefined"
showAutofillButton
isAutofillList
[disableDescriptionMargin]="showEmptyAutofillTip$ | async"
[primaryActionAutofill]="clickItemsToAutofillVaultView$ | async"
[groupByType]="groupByType()"
></app-vault-list-items-container>

View File

@@ -1,7 +1,7 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { combineLatest, map, Observable, startWith } from "rxjs";
import { combineLatest, map, Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
@@ -42,12 +42,6 @@ export class AutofillVaultListItemsComponent {
*/
protected showRefresh: boolean = BrowserPopupUtils.inSidebar(window);
/** Flag indicating whether the login item should automatically autofill when clicked */
protected clickItemsToAutofillVaultView$: Observable<boolean> =
this.vaultSettingsService.clickItemsToAutofillVaultView$.pipe(
startWith(true), // Start with true to avoid flashing the fill button on first load
);
protected readonly groupByType = toSignal(
this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => !hasFilter)),
);

View File

@@ -8,14 +8,14 @@
></button>
<bit-menu #moreOptions>
@if (!decryptionFailure) {
<ng-container *ngIf="canAutofill && !hideAutofillOptions">
<ng-container *ngIf="canAutofill && showAutofill()">
<ng-container *ngIf="autofillAllowed$ | async">
<button type="button" bitMenuItem (click)="doAutofill()">
{{ "autofill" | i18n }}
</button>
</ng-container>
</ng-container>
<ng-container *ngIf="showViewOption">
<ng-container>
<button type="button" bitMenuItem (click)="onView()">
{{ "view" | i18n }}
</button>

View File

@@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common";
import { booleanAttribute, Component, Input } from "@angular/core";
import { booleanAttribute, Component, input, Input } from "@angular/core";
import { Router, RouterModule } from "@angular/router";
import { BehaviorSubject, combineLatest, firstValueFrom, map, Observable, switchMap } from "rxjs";
import { filter } from "rxjs/operators";
@@ -76,22 +76,10 @@ export class ItemMoreOptionsComponent {
}
/**
* Flag to show view item menu option. Used when something else is
* assigned as the primary action for the item, such as autofill.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input({ transform: booleanAttribute })
showViewOption = false;
/**
* Flag to hide the autofill menu options. Used for items that are
* Flag to show the autofill menu options. Used for items that are
* already in the autofill list suggestion.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
@Input({ transform: booleanAttribute })
hideAutofillOptions = false;
readonly showAutofill = input(false, { transform: booleanAttribute });
protected autofillAllowed$ = this.vaultPopupAutofillService.autofillAllowed$;

View File

@@ -90,11 +90,11 @@
</ng-container>
<cdk-virtual-scroll-viewport [itemSize]="itemHeight$ | async" bitScrollLayout>
<bit-item *cdkVirtualFor="let cipher of group.ciphers">
<bit-item *cdkVirtualFor="let cipher of group.ciphers" class="tw-group/vault-item">
<button
bit-item-content
type="button"
(click)="primaryActionOnSelect(cipher)"
(click)="onCipherSelect(cipher)"
(dblclick)="launchCipher(cipher)"
[appA11yTitle]="
cipherItemTitleKey()(cipher)
@@ -125,19 +125,14 @@
</button>
<ng-container slot="end">
<bit-item-action *ngIf="!hideAutofillButton()">
<button
type="button"
bitBadge
variant="primary"
(click)="doAutofill(cipher)"
[title]="autofillShortcutTooltip() ?? ('autofillTitle' | i18n: cipher.name)"
[attr.aria-label]="'autofillTitle' | i18n: cipher.name"
<bit-item-action *ngIf="isAutofillList()">
<span
class="tw-opacity-0 tw-text-sm tw-text-primary-600 tw-px-2 group-hover/vault-item:tw-opacity-100 group-focus-within/vault-item:tw-opacity-100"
>
{{ "fill" | i18n }}
</button>
</span>
</bit-item-action>
<bit-item-action *ngIf="!showAutofillButton() && CipherViewLikeUtils.canLaunch(cipher)">
<bit-item-action *ngIf="!isAutofillList() && CipherViewLikeUtils.canLaunch(cipher)">
<button
type="button"
bitIconButton="bwi-external-link"
@@ -149,8 +144,7 @@
<app-item-copy-actions [cipher]="cipher"></app-item-copy-actions>
<app-item-more-options
[cipher]="cipher"
[hideAutofillOptions]="hideAutofillMenuOptions()"
[showViewOption]="primaryActionAutofill()"
[showAutofill]="!isAutofillList()"
></app-item-more-options>
</ng-container>
</bit-item>

View File

@@ -136,24 +136,18 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
*/
private viewCipherTimeout?: number;
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
ciphers = input<PopupCipherViewLike[]>([]);
readonly ciphers = input<PopupCipherViewLike[]>([]);
/**
* If true, we will group ciphers by type (Login, Card, Identity)
* within subheadings in a single container, converted to a WritableSignal.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
groupByType = input<boolean | undefined>(false);
readonly groupByType = input<boolean | undefined>(false);
/**
* Computed signal for a grouped list of ciphers with an optional header
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
cipherGroups = computed<
readonly cipherGroups = computed<
{
subHeaderKey?: string;
ciphers: PopupCipherViewLike[];
@@ -195,9 +189,7 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
/**
* Title for the vault list item section.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
title = input<string | undefined>(undefined);
readonly title = input<string | undefined>(undefined);
/**
* Optionally allow the items to be collapsed.
@@ -205,24 +197,20 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
* The key must be added to the state definition in `vault-popup-section.service.ts` since the
* collapsed state is stored locally.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
collapsibleKey = input<keyof PopupSectionOpen | undefined>(undefined);
readonly collapsibleKey = input<keyof PopupSectionOpen | undefined>(undefined);
/**
* Optional description for the vault list item section. Will be shown below the title even when
* no ciphers are available.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
description = input<string | undefined>(undefined);
readonly description = input<string | undefined>(undefined);
/**
* Option to show a refresh button in the section header.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
showRefresh = input(false, { transform: booleanAttribute });
readonly showRefresh = input(false, { transform: booleanAttribute });
/**
* Event emitted when the refresh button is clicked.
@@ -235,23 +223,16 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
/**
* Flag indicating that the current tab location is blocked
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
currentURIIsBlocked = toSignal(this.vaultPopupAutofillService.currentTabIsOnBlocklist$);
readonly currentUriIsBlocked = toSignal(this.vaultPopupAutofillService.currentTabIsOnBlocklist$);
/**
* Resolved i18n key to use for suggested cipher items
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
cipherItemTitleKey = computed(() => {
readonly cipherItemTitleKey = computed(() => {
return (cipher: CipherViewLike) => {
const login = CipherViewLikeUtils.getLogin(cipher);
const hasUsername = login?.username != null;
const key =
this.primaryActionAutofill() && !this.currentURIIsBlocked()
? "autofillTitle"
: "viewItemTitle";
const key = !this.currentUriIsBlocked() ? "autofillTitle" : "viewItemTitle";
return hasUsername ? `${key}WithField` : key;
};
});
@@ -259,47 +240,25 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
/**
* Option to show the autofill button for each item.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
showAutofillButton = input(false, { transform: booleanAttribute });
readonly isAutofillList = input(false, { transform: booleanAttribute });
/**
* Flag indicating whether the suggested cipher item autofill button should be shown or not
* Computed property whether the cipher select action should perform autofill
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
hideAutofillButton = computed(
() => !this.showAutofillButton() || this.currentURIIsBlocked() || this.primaryActionAutofill(),
readonly shouldAutofillOnSelect = computed(
() => this.isAutofillList() && !this.currentUriIsBlocked(),
);
/**
* Flag indicating whether the cipher item autofill menu options should be shown or not
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
hideAutofillMenuOptions = computed(() => this.currentURIIsBlocked() || this.showAutofillButton());
/**
* Option to perform autofill operation as the primary action for autofill suggestions.
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
primaryActionAutofill = input(false, { transform: booleanAttribute });
/**
* Remove the bottom margin from the bit-section in this component
* (used for containers at the end of the page where bottom margin is not needed)
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
disableSectionMargin = input(false, { transform: booleanAttribute });
readonly disableSectionMargin = input(false, { transform: booleanAttribute });
/**
* Remove the description margin
*/
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
disableDescriptionMargin = input(false, { transform: booleanAttribute });
readonly disableDescriptionMargin = input(false, { transform: booleanAttribute });
/**
* The tooltip text for the organization icon for ciphers that belong to an organization.
@@ -313,9 +272,7 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
return collections[0]?.name;
}
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
// eslint-disable-next-line @angular-eslint/prefer-signals
protected autofillShortcutTooltip = signal<string | undefined>(undefined);
protected readonly autofillShortcutTooltip = signal<string | undefined>(undefined);
constructor(
private i18nService: I18nService,
@@ -340,10 +297,8 @@ export class VaultListItemsContainerComponent implements AfterViewInit {
}
}
primaryActionOnSelect(cipher: PopupCipherViewLike) {
return this.primaryActionAutofill() && !this.currentURIIsBlocked()
? this.doAutofill(cipher)
: this.onViewCipher(cipher);
onCipherSelect(cipher: PopupCipherViewLike) {
return this.shouldAutofillOnSelect() ? this.doAutofill(cipher) : this.onViewCipher(cipher);
}
/**

View File

@@ -50,16 +50,10 @@
<vault-permit-cipher-details-popover></vault-permit-cipher-details-popover>
</bit-label>
</bit-form-control>
<bit-form-control>
<bit-form-control disableMargin>
<input bitCheckbox formControlName="showQuickCopyActions" type="checkbox" />
<bit-label>{{ "showQuickCopyActions" | i18n }}</bit-label>
</bit-form-control>
<bit-form-control disableMargin>
<input bitCheckbox formControlName="clickItemsToAutofillVaultView" type="checkbox" />
<bit-label>
{{ "clickToAutofill" | i18n }}
</bit-label>
</bit-form-control>
</bit-card>
</form>
</popup-page>

View File

@@ -59,14 +59,12 @@ describe("AppearanceV2Component", () => {
const enableRoutingAnimation$ = new BehaviorSubject<boolean>(true);
const enableCompactMode$ = new BehaviorSubject<boolean>(false);
const showQuickCopyActions$ = new BehaviorSubject<boolean>(false);
const clickItemsToAutofillVaultView$ = new BehaviorSubject<boolean>(false);
const setSelectedTheme = jest.fn().mockResolvedValue(undefined);
const setShowFavicons = jest.fn().mockResolvedValue(undefined);
const setEnableBadgeCounter = jest.fn().mockResolvedValue(undefined);
const setEnableRoutingAnimation = jest.fn().mockResolvedValue(undefined);
const setEnableCompactMode = jest.fn().mockResolvedValue(undefined);
const setShowQuickCopyActions = jest.fn().mockResolvedValue(undefined);
const setClickItemsToAutofillVaultView = jest.fn().mockResolvedValue(undefined);
const mockWidthService: Partial<PopupSizeService> = {
width$: new BehaviorSubject("default"),
@@ -113,10 +111,7 @@ describe("AppearanceV2Component", () => {
},
{
provide: VaultSettingsService,
useValue: {
clickItemsToAutofillVaultView$,
setClickItemsToAutofillVaultView,
},
useValue: mock<VaultSettingsService>(),
},
],
})
@@ -147,7 +142,6 @@ describe("AppearanceV2Component", () => {
enableCompactMode: false,
showQuickCopyActions: false,
width: "default",
clickItemsToAutofillVaultView: false,
});
});
@@ -193,11 +187,5 @@ describe("AppearanceV2Component", () => {
expect(mockWidthService.setWidth).toHaveBeenCalledWith("wide");
});
it("updates the click items to autofill vault view setting", () => {
component.appearanceForm.controls.clickItemsToAutofillVaultView.setValue(true);
expect(setClickItemsToAutofillVaultView).toHaveBeenCalledWith(true);
});
});
});

View File

@@ -66,7 +66,6 @@ export class AppearanceV2Component implements OnInit {
enableCompactMode: false,
showQuickCopyActions: false,
width: "default" as PopupWidthOption,
clickItemsToAutofillVaultView: false,
});
/** To avoid flashes of inaccurate values, only show the form after the entire form is populated. */
@@ -112,9 +111,6 @@ export class AppearanceV2Component implements OnInit {
this.copyButtonsService.showQuickCopyActions$,
);
const width = await firstValueFrom(this.popupSizeService.width$);
const clickItemsToAutofillVaultView = await firstValueFrom(
this.vaultSettingsService.clickItemsToAutofillVaultView$,
);
// Set initial values for the form
this.appearanceForm.setValue({
@@ -125,7 +121,6 @@ export class AppearanceV2Component implements OnInit {
enableCompactMode,
showQuickCopyActions,
width,
clickItemsToAutofillVaultView,
});
this.formLoading = false;
@@ -171,16 +166,6 @@ export class AppearanceV2Component implements OnInit {
.subscribe((width) => {
void this.updateWidth(width);
});
this.appearanceForm.controls.clickItemsToAutofillVaultView.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((clickItemsToAutofillVaultView) => {
void this.updateClickItemsToAutofillVaultView(clickItemsToAutofillVaultView);
});
}
async updateClickItemsToAutofillVaultView(clickItemsToAutofillVaultView: boolean) {
await this.vaultSettingsService.setClickItemsToAutofillVaultView(clickItemsToAutofillVaultView);
}
async updateFavicon(enableFavicon: boolean) {

View File

@@ -514,7 +514,7 @@ export class vNextMembersComponent {
if (result.error != null) {
this.toastService.showToast({
variant: "error",
message: this.i18nService.t(result.error),
message: result.error,
});
this.logService.error(result.error);
return;

View File

@@ -57,12 +57,8 @@
<ng-container *ngIf="subscription">
<ng-container *ngIf="enableDiscountDisplay$ | async as enableDiscount; else noDiscount">
<div class="tw-flex tw-items-center tw-gap-2 tw-flex-wrap tw-justify-end">
<span [attr.aria-label]="'nextChargeDateAndAmount' | i18n">
{{
(sub.subscription.periodEndDate | date: "MMM d, y") +
", " +
(discountedSubscriptionAmount | currency: "$")
}}
<span [attr.aria-label]="'nextChargeDate' | i18n">
{{ sub.subscription.periodEndDate | date: "MMM d, y" }}
</span>
<billing-discount-badge
[discount]="getDiscount(sub?.customerDiscount)"
@@ -71,12 +67,8 @@
</ng-container>
<ng-template #noDiscount>
<div class="tw-flex tw-items-center tw-gap-2 tw-flex-wrap tw-justify-end">
<span [attr.aria-label]="'nextChargeDateAndAmount' | i18n">
{{
(sub.subscription.periodEndDate | date: "MMM d, y") +
", " +
(subscriptionAmount | currency: "$")
}}
<span [attr.aria-label]="'nextChargeDate' | i18n">
{{ sub.subscription.periodEndDate | date: "MMM d, y" }}
</span>
</div>
</ng-template>

View File

@@ -3281,6 +3281,9 @@
"nextChargeHeader": {
"message": "Next Charge"
},
"nextChargeDate": {
"message": "Next charge date"
},
"plan": {
"message": "Plan"
},

View File

@@ -94,7 +94,12 @@
[bitAction]="loadMoreEvents"
*ngIf="continuationToken"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<i
*ngIf="loading"
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span>{{ "loadMore" | i18n }}</span>
</button>
</ng-container>

View File

@@ -18,6 +18,7 @@ export enum FeatureFlag {
/* Auth */
PM23801_PrefetchPasswordPrelogin = "pm-23801-prefetch-password-prelogin",
SafariAccountSwitching = "pm-5594-safari-account-switching",
/* Autofill */
MacOsNativeCredentialSync = "macos-native-credential-sync",
@@ -134,6 +135,7 @@ export const DefaultFeatureFlagValue = {
/* Auth */
[FeatureFlag.PM23801_PrefetchPasswordPrelogin]: FALSE,
[FeatureFlag.SafariAccountSwitching]: FALSE,
/* Billing */
[FeatureFlag.TrialPaymentOptional]: FALSE,

View File

@@ -16,11 +16,6 @@ export abstract class VaultSettingsService {
* An observable monitoring the state of the show identities on the current tab.
*/
abstract showIdentitiesCurrentTab$: Observable<boolean>;
/**
* An observable monitoring the state of the click items on the Vault view
* for Autofill suggestions.
*/
abstract clickItemsToAutofillVaultView$: Observable<boolean>;
/**
* Saves the enable passkeys setting to disk.
@@ -37,10 +32,4 @@ export abstract class VaultSettingsService {
* @param value The new value for the show identities on tab page setting.
*/
abstract setShowIdentitiesCurrentTab(value: boolean): Promise<void>;
/**
* Saves the click items on vault View for Autofill suggestions to disk.
* @param value The new value for the click items on vault View for
* Autofill suggestions setting.
*/
abstract setClickItemsToAutofillVaultView(value: boolean): Promise<void>;
}

View File

@@ -25,12 +25,3 @@ export const SHOW_IDENTITIES_CURRENT_TAB = new UserKeyDefinition<boolean>(
clearOn: [], // do not clear user settings
},
);
export const CLICK_ITEMS_AUTOFILL_VAULT_VIEW = new UserKeyDefinition<boolean>(
VAULT_SETTINGS_DISK,
"clickItemsToAutofillOnVaultView",
{
deserializer: (obj) => obj,
clearOn: [], // do not clear user settings
},
);

View File

@@ -1,4 +1,4 @@
import { Observable, combineLatest, map, shareReplay } from "rxjs";
import { Observable, combineLatest, map } from "rxjs";
import { ActiveUserState, GlobalState, StateProvider } from "../../../platform/state";
import { VaultSettingsService as VaultSettingsServiceAbstraction } from "../../abstractions/vault-settings/vault-settings.service";
@@ -7,7 +7,6 @@ import {
SHOW_CARDS_CURRENT_TAB,
SHOW_IDENTITIES_CURRENT_TAB,
USER_ENABLE_PASSKEYS,
CLICK_ITEMS_AUTOFILL_VAULT_VIEW,
} from "../key-state/vault-settings.state";
import { RestrictedItemTypesService } from "../restricted-item-types.service";
@@ -49,17 +48,6 @@ export class VaultSettingsService implements VaultSettingsServiceAbstraction {
readonly showIdentitiesCurrentTab$: Observable<boolean> =
this.showIdentitiesCurrentTabState.state$.pipe(map((x) => x ?? true));
private clickItemsToAutofillVaultViewState: ActiveUserState<boolean> =
this.stateProvider.getActive(CLICK_ITEMS_AUTOFILL_VAULT_VIEW);
/**
* {@link VaultSettingsServiceAbstraction.clickItemsToAutofillVaultView$$}
*/
readonly clickItemsToAutofillVaultView$: Observable<boolean> =
this.clickItemsToAutofillVaultViewState.state$.pipe(
map((x) => x ?? false),
shareReplay({ bufferSize: 1, refCount: false }),
);
constructor(
private stateProvider: StateProvider,
private restrictedItemTypesService: RestrictedItemTypesService,
@@ -79,13 +67,6 @@ export class VaultSettingsService implements VaultSettingsServiceAbstraction {
await this.showIdentitiesCurrentTabState.update(() => value);
}
/**
* {@link VaultSettingsServiceAbstraction.setClickItemsToAutofillVaultView}
*/
async setClickItemsToAutofillVaultView(value: boolean): Promise<void> {
await this.clickItemsToAutofillVaultViewState.update(() => value);
}
/**
* {@link VaultSettingsServiceAbstraction.setEnablePasskeys}
*/

8
package-lock.json generated
View File

@@ -106,7 +106,7 @@
"@types/koa__multer": "2.0.7",
"@types/koa__router": "12.0.4",
"@types/koa-bodyparser": "4.3.7",
"@types/koa-json": "2.0.23",
"@types/koa-json": "2.0.24",
"@types/lowdb": "1.0.15",
"@types/lunr": "2.3.7",
"@types/node": "22.19.7",
@@ -15768,9 +15768,9 @@
}
},
"node_modules/@types/koa-json": {
"version": "2.0.23",
"resolved": "https://registry.npmjs.org/@types/koa-json/-/koa-json-2.0.23.tgz",
"integrity": "sha512-LJKLFouztosawgU5xrtanK4neLCQKXl+vuVN96YMeVdKTYObLq2Qybggm9V426Jwam8Gi/zOrPw1g+QH0VaEHw==",
"version": "2.0.24",
"resolved": "https://registry.npmjs.org/@types/koa-json/-/koa-json-2.0.24.tgz",
"integrity": "sha512-FF+nQil6YO8vXMuLnOgGHYspSZVVpi+W79m9/s7LBSOQhlX7QY02X3Evk/g1GgWNLbO674AQaziX6OCCKzQ6Aw==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -73,7 +73,7 @@
"@types/koa__multer": "2.0.7",
"@types/koa__router": "12.0.4",
"@types/koa-bodyparser": "4.3.7",
"@types/koa-json": "2.0.23",
"@types/koa-json": "2.0.24",
"@types/lowdb": "1.0.15",
"@types/lunr": "2.3.7",
"@types/node": "22.19.7",