mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
PM-24535 Web premium upgrade path for archive (#16854)
* add premium badge to web filter when the user does not have access to premium * remove feature flag pass through in favor of showing/hiding archive vault observable * refactor archive observable to be more generic * add archive premium badge for the web * show premium badge inline for archive filter * show premium subscription ended message when user has archived ciphers * fix missing refactor * remove unneeded can archive check * reference observable directly * reduce the number of firstValueFroms by combining observables into a single stream * fix failing tests * add import to storybook * update variable naming for premium filters * pass event to `promptForPremium` * remove check for organization * fix footer variable reference * refactor back to `hasArchiveFlagEnabled$` - more straight forward to the underlying logic * update archive service test with new feature flag format
This commit is contained in:
@@ -108,7 +108,7 @@ describe("ItemMoreOptionsComponent", () => {
|
||||
{ provide: RestrictedItemTypesService, useValue: { restricted$: of([]) } },
|
||||
{
|
||||
provide: CipherArchiveService,
|
||||
useValue: { userCanArchive$: () => of(true), hasArchiveFlagEnabled$: () => of(true) },
|
||||
useValue: { userCanArchive$: () => of(true), hasArchiveFlagEnabled$: of(true) },
|
||||
},
|
||||
{ provide: ToastService, useValue: { showToast: () => {} } },
|
||||
{ provide: Router, useValue: { navigate: () => Promise.resolve(true) } },
|
||||
|
||||
@@ -141,7 +141,7 @@ export class ItemMoreOptionsComponent {
|
||||
}),
|
||||
);
|
||||
|
||||
protected showArchive$: Observable<boolean> = this.cipherArchiveService.hasArchiveFlagEnabled$();
|
||||
protected showArchive$: Observable<boolean> = this.cipherArchiveService.hasArchiveFlagEnabled$;
|
||||
|
||||
protected canArchive$: Observable<boolean> = this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
|
||||
@@ -49,7 +49,7 @@ export class VaultSettingsV2Component implements OnInit, OnDestroy {
|
||||
this.userId$.pipe(switchMap((userId) => this.cipherArchiveService.userCanArchive$(userId))),
|
||||
);
|
||||
|
||||
protected readonly showArchiveItem = toSignal(this.cipherArchiveService.hasArchiveFlagEnabled$());
|
||||
protected readonly showArchiveItem = toSignal(this.cipherArchiveService.hasArchiveFlagEnabled$);
|
||||
|
||||
protected readonly userHasArchivedItems = toSignal(
|
||||
this.userId$.pipe(
|
||||
|
||||
@@ -225,7 +225,7 @@ export class ItemFooterComponent implements OnInit, OnChanges {
|
||||
switchMap((id) =>
|
||||
combineLatest([
|
||||
this.cipherArchiveService.userCanArchive$(id),
|
||||
this.cipherArchiveService.hasArchiveFlagEnabled$(),
|
||||
this.cipherArchiveService.hasArchiveFlagEnabled$,
|
||||
]),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -11,6 +11,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
@@ -59,6 +60,7 @@ export class VaultFilterComponent
|
||||
protected restrictedItemTypesService: RestrictedItemTypesService,
|
||||
protected cipherService: CipherService,
|
||||
protected cipherArchiveService: CipherArchiveService,
|
||||
premiumUpgradePromptService: PremiumUpgradePromptService,
|
||||
) {
|
||||
super(
|
||||
vaultFilterService,
|
||||
@@ -72,6 +74,7 @@ export class VaultFilterComponent
|
||||
restrictedItemTypesService,
|
||||
cipherService,
|
||||
cipherArchiveService,
|
||||
premiumUpgradePromptService,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -203,10 +203,22 @@
|
||||
{{ "eventLogs" | i18n }}
|
||||
</button>
|
||||
@if (showArchiveButton) {
|
||||
<button bitMenuItem (click)="archive()" type="button">
|
||||
<i class="bwi bwi-fw bwi-archive" aria-hidden="true"></i>
|
||||
{{ "archiveVerb" | i18n }}
|
||||
</button>
|
||||
@if (userCanArchive) {
|
||||
<button bitMenuItem (click)="archive()" type="button">
|
||||
<i class="bwi bwi-fw bwi-archive" aria-hidden="true"></i>
|
||||
{{ "archiveVerb" | i18n }}
|
||||
</button>
|
||||
}
|
||||
@if (!userCanArchive) {
|
||||
<button bitMenuItem (click)="badge.promptForPremium($event)" type="button">
|
||||
<i class="bwi bwi-fw bwi-archive" aria-hidden="true"></i>
|
||||
{{ "archiveVerb" | i18n }}
|
||||
<!-- Hide app-premium badge from accessibility tools as it results in a button within a button -->
|
||||
<div slot="end" class="-tw-mt-0.5" aria-hidden>
|
||||
<app-premium-badge #badge></app-premium-badge>
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
@if (showUnArchiveButton) {
|
||||
|
||||
@@ -72,6 +72,7 @@ describe("VaultCipherRowComponent", () => {
|
||||
|
||||
fixture = TestBed.createComponent(VaultCipherRowComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.componentRef.setInput("archiveEnabled", false);
|
||||
overlayContainer = TestBed.inject(OverlayContainer);
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
input,
|
||||
} from "@angular/core";
|
||||
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
@@ -101,8 +102,10 @@ export class VaultCipherRowComponent<C extends CipherViewLike> implements OnInit
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@Input() userCanArchive: boolean;
|
||||
/** Archive feature is enabled */
|
||||
readonly archiveEnabled = input.required<boolean>();
|
||||
/**
|
||||
* Enforge Org Data Ownership Policy Status
|
||||
* Enforce Org Data Ownership Policy Status
|
||||
*/
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@@ -142,16 +145,21 @@ export class VaultCipherRowComponent<C extends CipherViewLike> implements OnInit
|
||||
}
|
||||
|
||||
protected get showArchiveButton() {
|
||||
if (!this.archiveEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
this.userCanArchive &&
|
||||
!CipherViewLikeUtils.isArchived(this.cipher) &&
|
||||
!CipherViewLikeUtils.isDeleted(this.cipher) &&
|
||||
!this.cipher.organizationId
|
||||
!CipherViewLikeUtils.isArchived(this.cipher) && !CipherViewLikeUtils.isDeleted(this.cipher)
|
||||
);
|
||||
}
|
||||
|
||||
// If item is archived always show unarchive button, even if user is not premium
|
||||
protected get showUnArchiveButton() {
|
||||
if (!this.archiveEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CipherViewLikeUtils.isArchived(this.cipher);
|
||||
}
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@
|
||||
(onEvent)="event($event)"
|
||||
[userCanArchive]="userCanArchive"
|
||||
[enforceOrgDataOwnershipPolicy]="enforceOrgDataOwnershipPolicy"
|
||||
[archiveEnabled]="archiveFeatureEnabled$ | async"
|
||||
></tr>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { of } from "rxjs";
|
||||
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
@@ -54,6 +55,12 @@ describe("VaultItemsComponent", () => {
|
||||
t: (key: string) => key,
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CipherArchiveService,
|
||||
useValue: {
|
||||
hasArchiveFlagEnabled$: of(true),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Observable, combineLatest, map, of, startWith, switchMap } from "rxjs";
|
||||
|
||||
import { CollectionView, Unassigned, CollectionAdminView } from "@bitwarden/admin-console/common";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import {
|
||||
RestrictedCipherType,
|
||||
@@ -145,9 +146,12 @@ export class VaultItemsComponent<C extends CipherViewLike> {
|
||||
protected disableMenu$: Observable<boolean>;
|
||||
private restrictedTypes: RestrictedCipherType[] = [];
|
||||
|
||||
protected archiveFeatureEnabled$ = this.cipherArchiveService.hasArchiveFlagEnabled$;
|
||||
|
||||
constructor(
|
||||
protected cipherAuthorizationService: CipherAuthorizationService,
|
||||
protected restrictedItemTypesService: RestrictedItemTypesService,
|
||||
protected cipherArchiveService: CipherArchiveService,
|
||||
) {
|
||||
this.canDeleteSelected$ = this.selection.changed.pipe(
|
||||
startWith(null),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CommonModule } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
|
||||
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
|
||||
import { ScrollLayoutDirective, TableModule } from "@bitwarden/components";
|
||||
import { CopyCipherFieldDirective } from "@bitwarden/vault";
|
||||
|
||||
@@ -29,6 +30,7 @@ import { VaultItemsComponent } from "./vault-items.component";
|
||||
PipesModule,
|
||||
CopyCipherFieldDirective,
|
||||
ScrollLayoutDirective,
|
||||
PremiumBadgeComponent,
|
||||
],
|
||||
declarations: [VaultItemsComponent, VaultCipherRowComponent, VaultCollectionRowComponent],
|
||||
exports: [VaultItemsComponent],
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -143,6 +144,12 @@ export default {
|
||||
isCipherRestricted: () => false, // No restrictions for this story
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CipherArchiveService,
|
||||
useValue: {
|
||||
hasArchiveFlagEnabled$: of(true),
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
applicationConfig({
|
||||
|
||||
@@ -19,8 +19,10 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
|
||||
@@ -170,6 +172,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
protected restrictedItemTypesService: RestrictedItemTypesService,
|
||||
protected cipherService: CipherService,
|
||||
protected cipherArchiveService: CipherArchiveService,
|
||||
private premiumUpgradePromptService: PremiumUpgradePromptService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
@@ -252,14 +255,20 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
|
||||
async buildAllFilters(): Promise<VaultFilterList> {
|
||||
const hasArchiveFlag = await firstValueFrom(this.cipherArchiveService.hasArchiveFlagEnabled$());
|
||||
const [userId, showArchive] = await firstValueFrom(
|
||||
combineLatest([
|
||||
this.accountService.activeAccount$.pipe(getUserId),
|
||||
this.cipherArchiveService.hasArchiveFlagEnabled$,
|
||||
]),
|
||||
);
|
||||
|
||||
const builderFilter = {} as VaultFilterList;
|
||||
builderFilter.organizationFilter = await this.addOrganizationFilter();
|
||||
builderFilter.typeFilter = await this.addTypeFilter();
|
||||
builderFilter.folderFilter = await this.addFolderFilter();
|
||||
builderFilter.collectionFilter = await this.addCollectionFilter();
|
||||
if (hasArchiveFlag) {
|
||||
builderFilter.archiveFilter = await this.addArchiveFilter();
|
||||
if (showArchive) {
|
||||
builderFilter.archiveFilter = await this.addArchiveFilter(userId);
|
||||
}
|
||||
builderFilter.trashFilter = await this.addTrashFilter();
|
||||
return builderFilter;
|
||||
@@ -419,7 +428,18 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
return trashFilterSection;
|
||||
}
|
||||
|
||||
protected async addArchiveFilter(): Promise<VaultFilterSection> {
|
||||
protected async addArchiveFilter(userId: UserId): Promise<VaultFilterSection> {
|
||||
const [hasArchivedCiphers, userHasPremium] = await firstValueFrom(
|
||||
combineLatest([
|
||||
this.cipherArchiveService
|
||||
.archivedCiphers$(userId)
|
||||
.pipe(map((archivedCiphers) => archivedCiphers.length > 0)),
|
||||
this.cipherArchiveService.userHasPremium$(userId),
|
||||
]),
|
||||
);
|
||||
|
||||
const promptForPremiumOnFilter = !userHasPremium && !hasArchivedCiphers;
|
||||
|
||||
const archiveFilterSection: VaultFilterSection = {
|
||||
data$: this.vaultFilterService.buildTypeTree(
|
||||
{
|
||||
@@ -442,6 +462,12 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
isSelectable: true,
|
||||
},
|
||||
action: this.applyTypeFilter as (filterNode: TreeNode<VaultFilterType>) => Promise<void>,
|
||||
premiumOptions: {
|
||||
showBadgeForNonPremium: true,
|
||||
blockFilterAction: promptForPremiumOnFilter
|
||||
? async () => await this.premiumUpgradePromptService.promptForPremium()
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
return archiveFilterSection;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,9 @@
|
||||
*ngComponentOutlet="optionsInfo.component; injector: createInjector(f.node)"
|
||||
></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="premiumFeature">
|
||||
<app-premium-badge></app-premium-badge>
|
||||
</ng-container>
|
||||
</span>
|
||||
</span>
|
||||
<ul
|
||||
|
||||
@@ -96,6 +96,11 @@ export class VaultFilterSectionComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async onFilterSelect(filterNode: TreeNode<VaultFilterType>) {
|
||||
if (this.section?.premiumOptions?.blockFilterAction) {
|
||||
await this.section.premiumOptions.blockFilterAction();
|
||||
return;
|
||||
}
|
||||
|
||||
await this.section?.action(filterNode);
|
||||
}
|
||||
|
||||
@@ -123,6 +128,10 @@ export class VaultFilterSectionComponent implements OnInit, OnDestroy {
|
||||
return this.section?.options;
|
||||
}
|
||||
|
||||
get premiumFeature() {
|
||||
return this.section?.premiumOptions?.showBadgeForNonPremium;
|
||||
}
|
||||
|
||||
get divider() {
|
||||
return this.section?.divider;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,16 @@ export type VaultFilterSection = {
|
||||
component: any;
|
||||
};
|
||||
divider?: boolean;
|
||||
premiumOptions?: {
|
||||
/** When true, the premium badge will show on the filter for non-premium users. */
|
||||
showBadgeForNonPremium?: true;
|
||||
/**
|
||||
* Action to be called instead of applying the filter.
|
||||
* Useful when the user does not have access to a filter (e.g., premium feature)
|
||||
* and custom behavior is needed when invoking the filter.
|
||||
*/
|
||||
blockFilterAction?: () => Promise<void>;
|
||||
};
|
||||
};
|
||||
|
||||
export type VaultFilterList = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
|
||||
import { SearchModule } from "@bitwarden/components";
|
||||
|
||||
import { SharedModule } from "../../../../shared";
|
||||
@@ -7,7 +8,7 @@ import { SharedModule } from "../../../../shared";
|
||||
import { VaultFilterSectionComponent } from "./components/vault-filter-section.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [SharedModule, SearchModule],
|
||||
imports: [SharedModule, SearchModule, PremiumBadgeComponent],
|
||||
declarations: [VaultFilterSectionComponent],
|
||||
exports: [SharedModule, VaultFilterSectionComponent, SearchModule],
|
||||
})
|
||||
|
||||
@@ -34,6 +34,16 @@
|
||||
<bit-callout type="warning" *ngIf="activeFilter.isDeleted">
|
||||
{{ trashCleanupWarning }}
|
||||
</bit-callout>
|
||||
<bit-callout
|
||||
type="info"
|
||||
[title]="'premiumSubscriptionEnded' | i18n"
|
||||
*ngIf="showSubscriptionEndedMessaging$ | async"
|
||||
>
|
||||
<p>{{ "premiumSubscriptionEndedDesc" | i18n }}</p>
|
||||
<a routerLink="/settings/subscription/premium" bitButton buttonType="primary">{{
|
||||
"restartPremium" | i18n
|
||||
}}</a>
|
||||
</bit-callout>
|
||||
<app-vault-items
|
||||
#vaultItems
|
||||
[ciphers]="ciphers"
|
||||
|
||||
@@ -84,7 +84,7 @@ import {
|
||||
CipherViewLikeUtils,
|
||||
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities";
|
||||
import { DialogRef, DialogService, ToastService } from "@bitwarden/components";
|
||||
import { DialogRef, DialogService, ToastService, BannerComponent } from "@bitwarden/components";
|
||||
import { CipherListView } from "@bitwarden/sdk-internal";
|
||||
import {
|
||||
AddEditFolderDialogComponent,
|
||||
@@ -177,6 +177,7 @@ type EmptyStateMap = Record<EmptyStateType, EmptyStateItem>;
|
||||
VaultItemsModule,
|
||||
SharedModule,
|
||||
OrganizationWarningsModule,
|
||||
BannerComponent,
|
||||
],
|
||||
providers: [
|
||||
RoutedVaultFilterService,
|
||||
@@ -230,13 +231,6 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
.pipe(map((a) => a?.id))
|
||||
.pipe(switchMap((id) => this.organizationService.organizations$(id)));
|
||||
|
||||
protected userCanArchive$ = this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => {
|
||||
return this.cipherArchiveService.userCanArchive$(userId);
|
||||
}),
|
||||
);
|
||||
|
||||
emptyState$ = combineLatest([
|
||||
this.currentSearchText$,
|
||||
this.routedVaultFilterService.filter$,
|
||||
@@ -295,14 +289,28 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
}),
|
||||
);
|
||||
|
||||
protected enforceOrgDataOwnershipPolicy$ = this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
private userId$ = this.accountService.activeAccount$.pipe(getUserId);
|
||||
|
||||
protected enforceOrgDataOwnershipPolicy$ = this.userId$.pipe(
|
||||
switchMap((userId) =>
|
||||
this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId),
|
||||
),
|
||||
);
|
||||
|
||||
private userId$ = this.accountService.activeAccount$.pipe(getUserId);
|
||||
protected userCanArchive$ = this.userId$.pipe(
|
||||
switchMap((userId) => {
|
||||
return this.cipherArchiveService.userCanArchive$(userId);
|
||||
}),
|
||||
);
|
||||
|
||||
protected showSubscriptionEndedMessaging$ = this.userId$.pipe(
|
||||
switchMap((userId) =>
|
||||
combineLatest([
|
||||
this.routedVaultFilterBridgeService.activeFilter$,
|
||||
this.cipherArchiveService.showSubscriptionEndedMessaging$(userId),
|
||||
]).pipe(map(([activeFilter, showMessaging]) => activeFilter.isArchived && showMessaging)),
|
||||
),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private syncService: SyncService,
|
||||
@@ -438,13 +446,13 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
allowedCiphers$,
|
||||
filter$,
|
||||
this.currentSearchText$,
|
||||
this.cipherArchiveService.hasArchiveFlagEnabled$(),
|
||||
this.cipherArchiveService.hasArchiveFlagEnabled$,
|
||||
]).pipe(
|
||||
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
|
||||
concatMap(async ([ciphers, filter, searchText, archiveEnabled]) => {
|
||||
concatMap(async ([ciphers, filter, searchText, showArchiveVault]) => {
|
||||
const failedCiphers =
|
||||
(await firstValueFrom(this.cipherService.failedToDecryptCiphers$(activeUserId))) ?? [];
|
||||
const filterFunction = createFilterFunction(filter, archiveEnabled);
|
||||
const filterFunction = createFilterFunction(filter, showArchiveVault);
|
||||
// Append any failed to decrypt ciphers to the top of the cipher list
|
||||
const allCiphers = [...failedCiphers, ...ciphers];
|
||||
|
||||
|
||||
@@ -3133,6 +3133,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"premiumSubscriptionEnded": {
|
||||
"message": "Your Premium subscription ended"
|
||||
},
|
||||
"premiumSubscriptionEndedDesc": {
|
||||
"message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault."
|
||||
},
|
||||
"restartPremium": {
|
||||
"message": "Restart Premium"
|
||||
},
|
||||
"additionalStorageGb": {
|
||||
"message": "Additional storage (GB)"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user