mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 01:03:35 +00:00
[PM-19907] updated empty state messages for web (#16283)
* updated empty state icons and copy for web vault
This commit is contained in:
@@ -208,15 +208,6 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
applyOrganizationFilter = async (orgNode: TreeNode<OrganizationFilter>): Promise<void> => {
|
||||
if (!orgNode?.node.enabled) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
message: this.i18nService.t("disabledOrganizationFilterError"),
|
||||
});
|
||||
await firstValueFrom(
|
||||
this.organizationWarningsService.showInactiveSubscriptionDialog$(orgNode.node),
|
||||
);
|
||||
}
|
||||
const filter = this.activeFilter;
|
||||
if (orgNode?.node.id === "AllVaults") {
|
||||
filter.resetOrganization();
|
||||
|
||||
@@ -68,19 +68,20 @@
|
||||
class="tw-mt-6 tw-flex tw-h-full tw-flex-col tw-items-center tw-justify-start"
|
||||
*ngIf="isEmpty && !performingInitialLoad"
|
||||
>
|
||||
<bit-no-items [icon]="noItemIcon">
|
||||
<div slot="title" *ngIf="filter.type === 'archive'">{{ "noItemsInArchive" | i18n }}</div>
|
||||
<p slot="description" class="tw-text-center tw-max-w-md" *ngIf="filter.type === 'archive'">
|
||||
{{ "archivedItemsDescription" | i18n }}
|
||||
<bit-no-items [icon]="(emptyState$ | async)?.icon">
|
||||
<div slot="title">
|
||||
{{ (emptyState$ | async)?.title | i18n }}
|
||||
</div>
|
||||
<p slot="description" bitTypography="body2" class="tw-max-w-md tw-text-center">
|
||||
{{ (emptyState$ | async)?.description | i18n }}
|
||||
</p>
|
||||
<div slot="title" *ngIf="filter.type !== 'archive'">{{ "noItemsInList" | i18n }}</div>
|
||||
<button
|
||||
type="button"
|
||||
buttonType="primary"
|
||||
bitButton
|
||||
(click)="addCipher()"
|
||||
slot="button"
|
||||
*ngIf="filter.type !== 'trash' && filter.type !== 'archive'"
|
||||
*ngIf="showAddCipherBtn"
|
||||
>
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "newItem" | i18n }}
|
||||
|
||||
@@ -32,7 +32,14 @@ import {
|
||||
Unassigned,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe";
|
||||
import { NoResults } from "@bitwarden/assets/svg";
|
||||
import {
|
||||
NoResults,
|
||||
DeactivatedOrg,
|
||||
EmptyTrash,
|
||||
FavoritesIcon,
|
||||
ItemTypes,
|
||||
Icon,
|
||||
} from "@bitwarden/assets/svg";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import {
|
||||
@@ -134,6 +141,16 @@ import { VaultOnboardingComponent } from "./vault-onboarding/vault-onboarding.co
|
||||
|
||||
const BroadcasterSubscriptionId = "VaultComponent";
|
||||
|
||||
type EmptyStateType = "trash" | "favorites" | "archive";
|
||||
|
||||
type EmptyStateItem = {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: Icon;
|
||||
};
|
||||
|
||||
type EmptyStateMap = Record<EmptyStateType, EmptyStateItem>;
|
||||
|
||||
@Component({
|
||||
selector: "app-vault",
|
||||
templateUrl: "vault.component.html",
|
||||
@@ -160,7 +177,11 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
kdfIterations: number;
|
||||
activeFilter: VaultFilter = new VaultFilter();
|
||||
|
||||
protected noItemIcon = NoResults;
|
||||
protected deactivatedOrgIcon = DeactivatedOrg;
|
||||
protected emptyTrashIcon = EmptyTrash;
|
||||
protected favoritesIcon = FavoritesIcon;
|
||||
protected itemTypesIcon = ItemTypes;
|
||||
protected noResultsIcon = NoResults;
|
||||
protected performingInitialLoad = true;
|
||||
protected refreshing = false;
|
||||
protected processingEvent = false;
|
||||
@@ -174,12 +195,16 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
protected isEmpty: boolean;
|
||||
protected selectedCollection: TreeNode<CollectionView> | undefined;
|
||||
protected canCreateCollections = false;
|
||||
protected currentSearchText$: Observable<string>;
|
||||
protected currentSearchText$: Observable<string> = this.route.queryParams.pipe(
|
||||
map((queryParams) => queryParams.search),
|
||||
);
|
||||
private searchText$ = new Subject<string>();
|
||||
private refresh$ = new BehaviorSubject<void>(null);
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
|
||||
protected showAddCipherBtn: boolean = false;
|
||||
|
||||
organizations$ = this.accountService.activeAccount$
|
||||
.pipe(map((a) => a?.id))
|
||||
.pipe(switchMap((id) => this.organizationService.organizations$(id)));
|
||||
@@ -191,6 +216,64 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
}),
|
||||
);
|
||||
|
||||
emptyState$ = combineLatest([
|
||||
this.currentSearchText$,
|
||||
this.routedVaultFilterService.filter$,
|
||||
this.organizations$,
|
||||
]).pipe(
|
||||
map(([searchText, filter, organizations]) => {
|
||||
const selectedOrg = organizations?.find((org) => org.id === filter.organizationId);
|
||||
const isOrgDisabled = selectedOrg && !selectedOrg.enabled;
|
||||
|
||||
if (isOrgDisabled) {
|
||||
this.showAddCipherBtn = false;
|
||||
return {
|
||||
title: "organizationIsSuspended",
|
||||
description: "organizationIsSuspendedDesc",
|
||||
icon: this.deactivatedOrgIcon,
|
||||
};
|
||||
}
|
||||
|
||||
if (searchText) {
|
||||
return {
|
||||
title: "noSearchResults",
|
||||
description: "clearFiltersOrTryAnother",
|
||||
icon: this.noResultsIcon,
|
||||
};
|
||||
}
|
||||
|
||||
const emptyStateMap: EmptyStateMap = {
|
||||
trash: {
|
||||
title: "noItemsInTrash",
|
||||
description: "noItemsInTrashDesc",
|
||||
icon: this.emptyTrashIcon,
|
||||
},
|
||||
favorites: {
|
||||
title: "emptyFavorites",
|
||||
description: "emptyFavoritesDesc",
|
||||
icon: this.favoritesIcon,
|
||||
},
|
||||
archive: {
|
||||
title: "noItemsInArchive",
|
||||
description: "archivedItemsDescription",
|
||||
icon: this.itemTypesIcon,
|
||||
},
|
||||
};
|
||||
|
||||
if (filter?.type && filter.type in emptyStateMap) {
|
||||
this.showAddCipherBtn = false;
|
||||
return emptyStateMap[filter.type as EmptyStateType];
|
||||
}
|
||||
|
||||
this.showAddCipherBtn = true;
|
||||
return {
|
||||
title: "noItemsInVault",
|
||||
description: "emptyVaultDescription",
|
||||
icon: this.itemTypesIcon,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private syncService: SyncService,
|
||||
private route: ActivatedRoute,
|
||||
@@ -298,8 +381,6 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
}),
|
||||
);
|
||||
|
||||
this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search));
|
||||
|
||||
const _ciphers = this.cipherService
|
||||
.cipherListViews$(activeUserId)
|
||||
.pipe(filter((c) => c !== null));
|
||||
|
||||
@@ -1508,6 +1508,30 @@
|
||||
"noItemsInList": {
|
||||
"message": "There are no items to list."
|
||||
},
|
||||
"noItemsInTrash": {
|
||||
"message": "No items in trash"
|
||||
},
|
||||
"noItemsInTrashDesc": {
|
||||
"message": "Items you delete will appear here and be permanently deleted after 30 days"
|
||||
},
|
||||
"noItemsInVault": {
|
||||
"message": "No items in the vault"
|
||||
},
|
||||
"emptyVaultDescription": {
|
||||
"message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here."
|
||||
},
|
||||
"emptyFavorites": {
|
||||
"message": "You haven't favorited any items"
|
||||
},
|
||||
"emptyFavoritesDesc": {
|
||||
"message": "Add frequently used items to favorites for quick access."
|
||||
},
|
||||
"noSearchResults": {
|
||||
"message": "No search results returned"
|
||||
},
|
||||
"clearFiltersOrTryAnother": {
|
||||
"message": "Clear filters or try another search term"
|
||||
},
|
||||
"noPermissionToViewAllCollectionItems": {
|
||||
"message": "You do not have permission to view all items in this collection."
|
||||
},
|
||||
@@ -4804,6 +4828,12 @@
|
||||
"organizationIsDisabled": {
|
||||
"message": "Organization suspended"
|
||||
},
|
||||
"organizationIsSuspended": {
|
||||
"message": "Organization is suspended"
|
||||
},
|
||||
"organizationIsSuspendedDesc": {
|
||||
"message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance."
|
||||
},
|
||||
"secretsAccessSuspended": {
|
||||
"message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance."
|
||||
},
|
||||
@@ -4816,9 +4846,6 @@
|
||||
"serviceAccountsCannotCreate": {
|
||||
"message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance."
|
||||
},
|
||||
"disabledOrganizationFilterError": {
|
||||
"message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance."
|
||||
},
|
||||
"licenseIsExpired": {
|
||||
"message": "License is expired."
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user