mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +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."
|
||||
},
|
||||
|
||||
26
libs/assets/src/svg/svgs/favorites.icon.ts
Normal file
26
libs/assets/src/svg/svgs/favorites.icon.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { svgIcon } from "../icon-service";
|
||||
|
||||
export const FavoritesIcon = svgIcon`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="5.29 4.98 86.89 90.19">
|
||||
<g clip-path="url(#clip0_2211_2391)">
|
||||
<path class="tw-stroke-illustration-outline tw-fill-illustration-bg-primary" d="M45.7322 7.73645C46.8425 5.06767 50.6228 5.06767 51.7332 7.73645L60.8269 29.5929C61.511 31.2373 63.0575 32.3607 64.8328 32.5031L88.4343 34.3947C91.316 34.6257 92.4843 38.2221 90.2888 40.1027L72.3083 55.5001C70.9554 56.6589 70.3647 58.4773 70.7781 60.2101L76.2712 83.2335C76.9419 86.0452 73.8838 88.2672 71.4167 86.7609L51.2078 74.422C49.688 73.4941 47.7773 73.4941 46.2576 74.422L26.0486 86.7609C23.5815 88.2672 20.5234 86.0452 21.1941 83.2335L26.6873 60.2101C27.1007 58.4773 26.51 56.6589 25.157 55.5001L7.17651 40.1027C4.98107 38.2221 6.14942 34.6258 9.03101 34.3947L32.6326 32.5031C34.4079 32.3607 35.9543 31.2373 36.6384 29.5929L45.7322 7.73645Z" stroke-width="1.5"/>
|
||||
<path class="tw-stroke-illustration-outline tw-fill-illustration-bg-tertiary" d="M86.5363 75.5456L67.0229 86.8083L64.3035 82.0996C63.2768 82.3932 60.0282 82.2577 55.7085 78.0915C51.1997 73.7429 53.1 69.4322 54.8897 67.3528L51.1991 60.9624C51.1991 60.9624 49.0085 57.1701 47.5085 54.572C46.5871 52.9761 46.8445 50.4707 48.594 49.461C50.3435 48.4512 52.9064 49.1601 53.9008 50.8825C56.3862 55.1873 60.6993 62.6543 60.6993 62.6543L60.3136 61.9865C59.0344 59.7715 57.8733 56.6618 60.0887 55.3831C60.1803 55.3302 60.2715 55.2816 60.3624 55.2371C62.6556 54.1121 64.7529 56.4687 66.0303 58.6805C65.81 57.9107 65.7395 56.1576 67.2199 55.3032C69.5661 53.949 71.7744 56.9755 73.1292 59.3214L72.6308 58.4584C72.1365 57.4731 71.6772 55.4212 73.4319 54.4084C75.2982 53.3313 76.7992 54.9314 77.3409 55.7398C78.3682 57.3892 80.6222 61.1108 81.42 62.8029C82.2178 64.495 82.7029 67.7429 82.8457 69.1553L86.5363 75.5456Z"/>
|
||||
<path class="tw-stroke-illustration-outline" d="M66.0303 58.6805C65.81 57.9107 65.7395 56.1576 67.2199 55.3032V55.3032C69.5661 53.949 71.7744 56.9755 73.1292 59.3214L73.9905 60.8128L72.6308 58.4584C72.1365 57.4731 71.6772 55.4212 73.4319 54.4084C75.2982 53.3313 76.7992 54.9314 77.3409 55.7398C78.3682 57.3892 80.6222 61.1108 81.42 62.8029C82.2178 64.495 82.7029 67.7429 82.8457 69.1553L86.5363 75.5456L67.0229 86.8083L64.3035 82.0996C63.2768 82.3932 60.0282 82.2577 55.7085 78.0915C51.1997 73.7429 53.1 69.4322 54.8897 67.3528M66.0303 58.6805L67.9727 62.0439M66.0303 58.6805V58.6805C64.7529 56.4687 62.6556 54.1121 60.3624 55.2371C60.2715 55.2816 60.1803 55.3302 60.0887 55.3831V55.3831C57.8733 56.6618 59.0344 59.7715 60.3136 61.9865L60.6993 62.6543C60.6993 62.6543 56.3862 55.1873 53.9008 50.8825C52.9064 49.1601 50.3435 48.4512 48.594 49.461C46.8445 50.4707 46.5871 52.9761 47.5085 54.572C49.0085 57.1701 51.1991 60.9624 51.1991 60.9624L54.8897 67.3528M57.6091 72.0616L54.8897 67.3528" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path class="tw-stroke-illustration-outline tw-fill-illustration-bg-secondary" d="M70.9905 93.6786L67.5232 87.6748C67.247 87.1965 67.4108 86.585 67.8892 86.309L85.6704 76.046C86.1487 75.7699 86.7604 75.9338 87.0366 76.4121L90.5039 82.4159C90.7802 82.8942 90.6163 83.5057 90.138 83.7818L72.3567 94.0447C71.8784 94.3208 71.2667 94.1569 70.9905 93.6786Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g clip-path="url(#clip1_2211_2391)">
|
||||
<path class="tw-stroke-illustration-tertiary" d="M41.5571 37.1704L45.0496 43.2177" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path class="tw-stroke-illustration-tertiary" d="M54.9774 35.4178L53.187 42.1557" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path class="tw-stroke-illustration-tertiary" d="M65.7288 43.6858L59.6986 47.1663" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path class="tw-stroke-illustration-tertiary" d="M35.1155 61.3549L41.1457 57.8744" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path class="tw-stroke-illustration-tertiary" d="M33.329 47.9126L40.0662 49.7286" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2211_2391">
|
||||
<rect width="96" height="96" class="tw-fill-bg-tertiary" transform="translate(0.5 0.400024)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_2211_2391">
|
||||
<rect width="37.6674" height="20.0537" class="tw-fill-bg-tertiary" transform="matrix(0.86609 -0.499888 0.500112 0.86596 25.2179 45.5771)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>`;
|
||||
@@ -12,6 +12,7 @@ export * from "./deactivated-org";
|
||||
export * from "./devices.icon";
|
||||
export * from "./domain.icon";
|
||||
export * from "./empty-trash";
|
||||
export * from "./favorites.icon";
|
||||
export * from "./gear";
|
||||
export * from "./generator";
|
||||
export * from "./item-types";
|
||||
|
||||
Reference in New Issue
Block a user