mirror of
https://github.com/bitwarden/browser
synced 2026-02-11 05:53:42 +00:00
[PM-26020] Implement dynamic cipher creation permissions in vault header and new… (#18579)
* Implement dynamic cipher creation permissions in vault header and new cipher menu components * Enhance new cipher menu button behavior and accessibility. Implement dynamic button label based on creation permissions, allowing direct collection creation when applicable. Update button trigger logic to improve user experience. * Update apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts Co-authored-by: SmithThe4th <gsmith@bitwarden.com> * Add canCreateCipher getter for improved readability --------- Co-authored-by: SmithThe4th <gsmith@bitwarden.com>
This commit is contained in:
@@ -76,7 +76,7 @@
|
||||
|
||||
<div *ngIf="filter.type !== 'trash'" class="tw-shrink-0">
|
||||
<vault-new-cipher-menu
|
||||
[canCreateCipher]="true"
|
||||
[canCreateCipher]="canCreateCipher"
|
||||
[canCreateFolder]="true"
|
||||
[canCreateSshKey]="true"
|
||||
[canCreateCollection]="canCreateCollections"
|
||||
|
||||
@@ -228,6 +228,10 @@ export class VaultHeaderComponent {
|
||||
return this.collection.node.canDelete(organization);
|
||||
}
|
||||
|
||||
get canCreateCipher(): boolean {
|
||||
return !this.activeOrganization?.isProviderUser || this.activeOrganization?.isMember;
|
||||
}
|
||||
|
||||
deleteCollection() {
|
||||
this.onDeleteCollection.emit();
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
[bitMenuTriggerFor]="addOptions"
|
||||
[bitMenuTriggerFor]="isOnlyCollectionCreation() ? null : addOptions"
|
||||
(click)="handleButtonClick()"
|
||||
id="newItemDropdown"
|
||||
[appA11yTitle]="'new' | i18n"
|
||||
[appA11yTitle]="getButtonLabel() | i18n"
|
||||
>
|
||||
<i class="bwi bwi-plus tw-me-2" aria-hidden="true"></i>
|
||||
{{ "new" | i18n }}
|
||||
{{ getButtonLabel() | i18n }}
|
||||
</button>
|
||||
<bit-menu #addOptions aria-labelledby="newItemDropdown">
|
||||
@for (item of cipherMenuItems$ | async; track item.type) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, input, output } from "@angular/core";
|
||||
import { map, shareReplay } from "rxjs";
|
||||
import { toObservable } from "@angular/core/rxjs-interop";
|
||||
import { combineLatest, map, shareReplay } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
@@ -38,10 +39,18 @@ export class NewCipherMenuComponent {
|
||||
/**
|
||||
* Returns an observable that emits the cipher menu items, filtered by the restricted types.
|
||||
*/
|
||||
cipherMenuItems$ = this.restrictedItemTypesService.restricted$.pipe(
|
||||
map((restrictedTypes) => {
|
||||
cipherMenuItems$ = combineLatest([
|
||||
this.restrictedItemTypesService.restricted$,
|
||||
toObservable(this.canCreateCipher),
|
||||
toObservable(this.canCreateSshKey),
|
||||
]).pipe(
|
||||
map(([restrictedTypes, canCreateCipher, canCreateSshKey]) => {
|
||||
// If user cannot create ciphers at all, return empty array
|
||||
if (!canCreateCipher) {
|
||||
return [];
|
||||
}
|
||||
return CIPHER_MENU_ITEMS.filter((item) => {
|
||||
if (!this.canCreateSshKey() && item.type === CipherType.SshKey) {
|
||||
if (!canCreateSshKey && item.type === CipherType.SshKey) {
|
||||
return false;
|
||||
}
|
||||
return !restrictedTypes.some((restrictedType) => restrictedType.cipherType === item.type);
|
||||
@@ -49,4 +58,40 @@ export class NewCipherMenuComponent {
|
||||
}),
|
||||
shareReplay({ bufferSize: 1, refCount: true }),
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the appropriate button label based on what can be created.
|
||||
* If only collections can be created (no ciphers or folders), show "New Collection".
|
||||
* Otherwise, show "New".
|
||||
*/
|
||||
protected getButtonLabel(): string {
|
||||
const canCreateCipher = this.canCreateCipher();
|
||||
const canCreateFolder = this.canCreateFolder();
|
||||
const canCreateCollection = this.canCreateCollection();
|
||||
|
||||
// If only collections can be created, be specific
|
||||
if (!canCreateCipher && !canCreateFolder && canCreateCollection) {
|
||||
return "newCollection";
|
||||
}
|
||||
|
||||
return "new";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if only collections can be created (no other options).
|
||||
* When this is true, the button should directly create a collection instead of showing a dropdown.
|
||||
*/
|
||||
protected isOnlyCollectionCreation(): boolean {
|
||||
return !this.canCreateCipher() && !this.canCreateFolder() && this.canCreateCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the button click. If only collections can be created, directly emit the collection event.
|
||||
* Otherwise, the menu trigger will handle opening the dropdown.
|
||||
*/
|
||||
protected handleButtonClick(): void {
|
||||
if (this.isOnlyCollectionCreation()) {
|
||||
this.collectionAdded.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user