Sfoglia il codice sorgente

feat(admin-ui): Support new permissions

Relates to #617
Michael Bromley 4 anni fa
parent
commit
57566b03d4
51 ha cambiato i file con 297 aggiunte e 192 eliminazioni
  1. 1 1
      packages/admin-ui/src/lib/catalog/src/components/asset-detail/asset-detail.component.html
  2. 1 1
      packages/admin-ui/src/lib/catalog/src/components/asset-list/asset-list.component.html
  3. 7 7
      packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.html
  4. 1 1
      packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.html
  5. 2 2
      packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree-node.component.html
  6. 13 4
      packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree-node.component.ts
  7. 8 8
      packages/admin-ui/src/lib/catalog/src/components/facet-detail/facet-detail.component.html
  8. 2 2
      packages/admin-ui/src/lib/catalog/src/components/facet-list/facet-list.component.html
  9. 6 6
      packages/admin-ui/src/lib/catalog/src/components/product-assets/product-assets.component.html
  10. 16 0
      packages/admin-ui/src/lib/catalog/src/components/product-assets/product-assets.component.ts
  11. 9 9
      packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.html
  12. 3 3
      packages/admin-ui/src/lib/catalog/src/components/product-list/product-list.component.html
  13. 17 17
      packages/admin-ui/src/lib/catalog/src/components/product-variants-list/product-variants-list.component.html
  14. 6 6
      packages/admin-ui/src/lib/catalog/src/components/product-variants-table/product-variants-table.component.html
  15. 1 1
      packages/admin-ui/src/lib/catalog/src/components/update-product-option-dialog/update-product-option-dialog.component.html
  16. 53 8
      packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.ts
  17. 1 1
      packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.html
  18. 2 2
      packages/admin-ui/src/lib/core/src/shared/directives/if-permissions.directive.spec.ts
  19. 3 3
      packages/admin-ui/src/lib/core/src/shared/directives/if-permissions.directive.ts
  20. 1 1
      packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.html
  21. 16 5
      packages/admin-ui/src/lib/core/src/shared/pipes/has-permission.pipe.ts
  22. 28 26
      packages/admin-ui/src/lib/customer/src/components/address-card/address-card.component.html
  23. 1 0
      packages/admin-ui/src/lib/customer/src/components/address-card/address-card.component.ts
  24. 4 2
      packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.html
  25. 1 1
      packages/admin-ui/src/lib/customer/src/components/customer-group-detail-dialog/customer-group-detail-dialog.component.html
  26. 3 3
      packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.html
  27. 1 1
      packages/admin-ui/src/lib/customer/src/components/customer-group-member-list/customer-group-member-list.component.html
  28. 3 3
      packages/admin-ui/src/lib/customer/src/components/customer-history/customer-history.component.html
  29. 4 1
      packages/admin-ui/src/lib/settings/src/components/admin-detail/admin-detail.component.ts
  30. 8 8
      packages/admin-ui/src/lib/settings/src/components/channel-detail/channel-detail.component.html
  31. 1 1
      packages/admin-ui/src/lib/settings/src/components/channel-list/channel-list.component.html
  32. 4 4
      packages/admin-ui/src/lib/settings/src/components/country-detail/country-detail.component.html
  33. 2 2
      packages/admin-ui/src/lib/settings/src/components/country-list/country-list.component.html
  34. 3 3
      packages/admin-ui/src/lib/settings/src/components/global-settings/global-settings.component.html
  35. 8 8
      packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.html
  36. 2 2
      packages/admin-ui/src/lib/settings/src/components/payment-method-list/payment-method-list.component.html
  37. 3 2
      packages/admin-ui/src/lib/settings/src/components/permission-grid/permission-grid.component.html
  38. 1 0
      packages/admin-ui/src/lib/settings/src/components/permission-grid/permission-grid.component.scss
  39. 7 0
      packages/admin-ui/src/lib/settings/src/components/permission-grid/permission-grid.component.ts
  40. 1 1
      packages/admin-ui/src/lib/settings/src/components/role-list/role-list.component.html
  41. 13 6
      packages/admin-ui/src/lib/settings/src/components/role-list/role-list.component.ts
  42. 8 8
      packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.html
  43. 2 2
      packages/admin-ui/src/lib/settings/src/components/shipping-method-list/shipping-method-list.component.html
  44. 3 3
      packages/admin-ui/src/lib/settings/src/components/tax-category-detail/tax-category-detail.component.html
  45. 2 2
      packages/admin-ui/src/lib/settings/src/components/tax-category-list/tax-category-list.component.html
  46. 6 6
      packages/admin-ui/src/lib/settings/src/components/tax-rate-detail/tax-rate-detail.component.html
  47. 2 2
      packages/admin-ui/src/lib/settings/src/components/tax-rate-list/tax-rate-list.component.html
  48. 1 1
      packages/admin-ui/src/lib/settings/src/components/zone-detail-dialog/zone-detail-dialog.component.html
  49. 4 4
      packages/admin-ui/src/lib/settings/src/components/zone-list/zone-list.component.html
  50. 1 1
      packages/admin-ui/src/lib/settings/src/components/zone-member-list/zone-member-list.component.html
  51. 1 1
      packages/admin-ui/src/lib/system/src/components/job-list/job-list.component.html

+ 1 - 1
packages/admin-ui/src/lib/catalog/src/components/asset-detail/asset-detail.component.html

@@ -6,7 +6,7 @@
     <vdr-ab-right>
         <vdr-action-bar-items locationId="asset-detail"></vdr-action-bar-items>
         <button
-            *vdrIfPermissions="'UpdateCatalog'"
+            *vdrIfPermissions="['UpdateCatalog', 'UpdateAsset']"
             class="btn btn-primary"
             (click)="save()"
             [disabled]="detailForm.invalid || detailForm.pristine"

+ 1 - 1
packages/admin-ui/src/lib/catalog/src/components/asset-list/asset-list.component.html

@@ -20,7 +20,7 @@
 <vdr-asset-gallery
     [assets]="(items$ | async)! | paginate: (paginationConfig$ | async) || {}"
     [multiSelect]="true"
-    [canDelete]="'DeleteCatalog' | hasPermission"
+    [canDelete]="['DeleteCatalog', 'DeleteAsset'] | hasPermission"
     (deleteAssets)="deleteAssets($event)"
 ></vdr-asset-gallery>
 

+ 7 - 7
packages/admin-ui/src/lib/catalog/src/components/collection-detail/collection-detail.component.html

@@ -21,7 +21,7 @@
         </button>
         <ng-template #updateButton>
             <button
-                *vdrIfPermissions="'UpdateCatalog'"
+                *vdrIfPermissions="['UpdateCatalog', 'UpdateCollection']"
                 class="btn btn-primary"
                 (click)="save()"
                 [disabled]="(detailForm.invalid || detailForm.pristine) && !assetsChanged()"
@@ -42,7 +42,7 @@
                         clrToggle
                         formControlName="visible"
                         id="visibility"
-                        [vdrDisabled]="!('UpdateCatalog' | hasPermission)"
+                        [vdrDisabled]="!(['UpdateCatalog', 'UpdateCollection'] | hasPermission)"
                     />
                     <label class="visible-toggle">
                         <ng-container *ngIf="detailForm.value.visible; else private">{{ 'catalog.public' | translate }}</ng-container>
@@ -55,7 +55,7 @@
                     id="name"
                     type="text"
                     formControlName="name"
-                    [readonly]="!('UpdateCatalog' | hasPermission)"
+                    [readonly]="!(['UpdateCatalog', 'UpdateCollection'] | hasPermission)"
                     (input)="updateSlug($event.target.value)"
                 />
             </vdr-form-field>
@@ -68,13 +68,13 @@
                     id="slug"
                     type="text"
                     formControlName="slug"
-                    [readonly]="!('UpdateCatalog' | hasPermission)"
+                    [readonly]="!(['UpdateCatalog', 'UpdateCollection'] | hasPermission)"
                     pattern="[a-z0-9_-]+"
                 />
             </vdr-form-field>
             <vdr-rich-text-editor
                 formControlName="description"
-                [readonly]="!('UpdateCatalog' | hasPermission)"
+                [readonly]="!(['UpdateCatalog', 'UpdateCollection'] | hasPermission)"
                 [label]="'common.description' | translate"
             ></vdr-rich-text-editor>
 
@@ -107,11 +107,11 @@
                     [operation]="filter"
                     [operationDefinition]="getFilterDefinition(filter)"
                     [formControlName]="i"
-                    [readonly]="!('UpdateCatalog' | hasPermission)"
+                    [readonly]="!(['UpdateCatalog', 'UpdateCollection'] | hasPermission)"
                 ></vdr-configurable-input>
             </ng-container>
 
-            <div *vdrIfPermissions="'UpdateCatalog'">
+            <div *vdrIfPermissions="['UpdateCatalog', 'UpdateCollection']">
                 <vdr-dropdown>
                     <button class="btn btn-outline" vdrDropdownTrigger>
                         <clr-icon shape="plus"></clr-icon>

+ 1 - 1
packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.html

@@ -7,7 +7,7 @@
     </vdr-ab-left>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="collection-list"></vdr-action-bar-items>
-        <a class="btn btn-primary" *vdrIfPermissions="'CreateCatalog'" [routerLink]="['./create']">
+        <a class="btn btn-primary" *vdrIfPermissions="['CreateCatalog', 'CreateCollection']" [routerLink]="['./create']">
             <clr-icon shape="plus"></clr-icon>
             {{ 'catalog.create-new-collection' | translate }}
         </a>

+ 2 - 2
packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree-node.component.html

@@ -47,7 +47,7 @@
                 <clr-icon shape="edit"></clr-icon>
                 {{ 'common.edit' | translate }}
             </a>
-            <div class="drag-handle" cdkDragHandle *vdrIfPermissions="'UpdateCatalog'">
+            <div class="drag-handle" cdkDragHandle *vdrIfPermissions="['UpdateCatalog', 'UpdateCollection']">
                 <clr-icon shape="drag-handle" size="24"></clr-icon>
             </div>
             <vdr-dropdown>
@@ -58,7 +58,7 @@
                     <a
                         class="dropdown-item"
                         [routerLink]="['./', 'create', { parentId: collection.id }]"
-                        *vdrIfPermissions="'CreateCatalog'"
+                        *vdrIfPermissions="['CreateCatalog', 'CreateCollection']"
                     >
                         <clr-icon shape="plus"></clr-icon>
                         {{ 'catalog.create-new-collection' | translate }}

+ 13 - 4
packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree-node.component.ts

@@ -9,8 +9,7 @@ import {
     SimpleChanges,
     SkipSelf,
 } from '@angular/core';
-import { Permission } from '@vendure/admin-ui/core';
-import { DataService } from '@vendure/admin-ui/core';
+import { DataService, Permission } from '@vendure/admin-ui/core';
 import { Observable } from 'rxjs';
 import { map, shareReplay } from 'rxjs/operators';
 
@@ -48,8 +47,18 @@ export class CollectionTreeNodeComponent implements OnInit, OnChanges {
             .userStatus()
             .mapStream(data => data.userStatus.permissions)
             .pipe(shareReplay(1));
-        this.hasUpdatePermission$ = permissions$.pipe(map(perms => perms.includes(Permission.UpdateCatalog)));
-        this.hasDeletePermission$ = permissions$.pipe(map(perms => perms.includes(Permission.DeleteCatalog)));
+        this.hasUpdatePermission$ = permissions$.pipe(
+            map(
+                perms =>
+                    perms.includes(Permission.UpdateCatalog) || perms.includes(Permission.UpdateCollection),
+            ),
+        );
+        this.hasDeletePermission$ = permissions$.pipe(
+            map(
+                perms =>
+                    perms.includes(Permission.DeleteCatalog) || perms.includes(Permission.DeleteCollection),
+            ),
+        );
     }
 
     ngOnChanges(changes: SimpleChanges) {

+ 8 - 8
packages/admin-ui/src/lib/catalog/src/components/facet-detail/facet-detail.component.html

@@ -21,7 +21,7 @@
         </button>
         <ng-template #updateButton>
             <button
-                *vdrIfPermissions="'UpdateCatalog'"
+                *vdrIfPermissions="['UpdateCatalog', 'UpdateFacet']"
                 class="btn btn-primary"
                 (click)="save()"
                 [disabled]="detailForm.invalid || detailForm.pristine"
@@ -39,7 +39,7 @@
                 <input
                     type="checkbox"
                     clrToggle
-                    [vdrDisabled]="!('UpdateCatalog' | hasPermission)"
+                    [vdrDisabled]="!(['UpdateCatalog', 'UpdateFacet'] | hasPermission)"
                     formControlName="visible"
                     id="visibility"
                 />
@@ -54,19 +54,19 @@
                 id="name"
                 type="text"
                 formControlName="name"
-                [readonly]="!('UpdateCatalog' | hasPermission)"
+                [readonly]="!(['UpdateCatalog', 'UpdateFacet'] | hasPermission)"
                 (input)="updateCode(facet.code, $event.target.value)"
             />
         </vdr-form-field>
         <vdr-form-field
             [label]="'common.code' | translate"
             for="code"
-            [readOnlyToggle]="'UpdateCatalog' | hasPermission"
+            [readOnlyToggle]="['UpdateCatalog', 'UpdateFacet'] | hasPermission"
         >
             <input
                 id="code"
                 type="text"
-                [readonly]="!('UpdateCatalog' | hasPermission)"
+                [readonly]="!(['UpdateCatalog', 'UpdateFacet'] | hasPermission)"
                 formControlName="code"
             />
         </vdr-form-field>
@@ -112,7 +112,7 @@
                         <input
                             type="text"
                             formControlName="name"
-                            [readonly]="!('UpdateCatalog' | hasPermission)"
+                            [readonly]="!(['UpdateCatalog', 'UpdateFacet'] | hasPermission)"
                             (input)="updateValueCode(facet.values[i]?.code, $event.target.value, i)"
                         />
                     </td>
@@ -139,7 +139,7 @@
                                     type="button"
                                     class="delete-button"
                                     (click)="deleteFacetValue(facet.values[i]?.id, i)"
-                                    [disabled]="!('UpdateCatalog' | hasPermission)"
+                                    [disabled]="!(['UpdateCatalog', 'UpdateFacet'] | hasPermission)"
                                     vdrDropdownItem
                                 >
                                     <clr-icon shape="trash" class="is-danger"></clr-icon>
@@ -156,7 +156,7 @@
             <button
                 type="button"
                 class="btn btn-secondary"
-                *vdrIfPermissions="'CreateCatalog'"
+                *vdrIfPermissions="['CreateCatalog', 'CreateFacet']"
                 (click)="addFacetValue()"
             >
                 <clr-icon shape="add"></clr-icon>

+ 2 - 2
packages/admin-ui/src/lib/catalog/src/components/facet-list/facet-list.component.html

@@ -2,7 +2,7 @@
     <vdr-ab-right>
         <vdr-action-bar-items locationId="facet-list"></vdr-action-bar-items>
         <a class="btn btn-primary"
-           *vdrIfPermissions="'CreateCatalog'"
+           *vdrIfPermissions="['CreateCatalog', 'CreateFacet']"
            [routerLink]="['./create']">
             <clr-icon shape="plus"></clr-icon>
             {{ 'catalog.create-new-facet' | translate }}
@@ -72,7 +72,7 @@
                         type="button"
                         class="delete-button"
                         (click)="deleteFacet(facet.id)"
-                        [disabled]="!('DeleteCatalog' | hasPermission)"
+                        [disabled]="!(['DeleteCatalog', 'DeleteFacet'] | hasPermission)"
                         vdrDropdownItem
                     >
                         <clr-icon shape="trash" class="is-danger"></clr-icon>

+ 6 - 6
packages/admin-ui/src/lib/catalog/src/components/product-assets/product-assets.component.html

@@ -13,7 +13,7 @@
         </div>
     </div>
     <div class="card-block"><ng-container *ngTemplateOutlet="assetList"></ng-container></div>
-    <div class="card-footer" *vdrIfPermissions="'UpdateCatalog'">
+    <div class="card-footer" *vdrIfPermissions="updatePermissions">
         <button class="btn" (click)="selectAssets()">
             <clr-icon shape="attachment"></clr-icon>
             {{ 'asset.add-asset' | translate }}
@@ -33,7 +33,7 @@
     </div>
     <ng-container *ngTemplateOutlet="assetList"></ng-container>
     <button
-        *vdrIfPermissions="'UpdateCatalog'"
+        *vdrIfPermissions="updatePermissions"
         class="compact-select btn btn-icon btn-sm btn-block"
         [title]="'asset.add-asset' | translate"
         (click)="selectAssets()"
@@ -48,14 +48,14 @@
         <div
             cdkDropList
             #dl
-            [cdkDropListDisabled]="!('UpdateCatalog' | hasPermission)"
+            [cdkDropListDisabled]="!(updatePermissions | hasPermission)"
             [cdkDropListEnterPredicate]="dropListEnterPredicate"
             (cdkDropListDropped)="dropListDropped()"
         ></div>
         <div
             *ngFor="let asset of assets"
             cdkDropList
-            [cdkDropListDisabled]="!('UpdateCatalog' | hasPermission)"
+            [cdkDropListDisabled]="!(updatePermissions | hasPermission)"
             [cdkDropListEnterPredicate]="dropListEnterPredicate"
             (cdkDropListDropped)="dropListDropped()"
         >
@@ -75,7 +75,7 @@
                     </button>
                     <button
                         type="button"
-                        [disabled]="isFeatured(asset) || !('UpdateCatalog' | hasPermission)"
+                        [disabled]="isFeatured(asset) || !(updatePermissions | hasPermission)"
                         vdrDropdownItem
                         (click)="setAsFeatured(asset)"
                     >
@@ -86,7 +86,7 @@
                         type="button"
                         class="remove-asset"
                         vdrDropdownItem
-                        [disabled]="!('UpdateCatalog' | hasPermission)"
+                        [disabled]="!(updatePermissions | hasPermission)"
                         (click)="removeAsset(asset)"
                     >
                         {{ 'asset.remove-asset' | translate }}

+ 16 - 0
packages/admin-ui/src/lib/catalog/src/components/product-assets/product-assets.component.ts

@@ -8,6 +8,7 @@ import {
     EventEmitter,
     HostBinding,
     Input,
+    Optional,
     Output,
     ViewChild,
 } from '@angular/core';
@@ -16,9 +17,12 @@ import {
     AssetPickerDialogComponent,
     AssetPreviewDialogComponent,
     ModalService,
+    Permission,
 } from '@vendure/admin-ui/core';
 import { unique } from '@vendure/common/lib/unique';
 
+import { CollectionDetailComponent } from '../collection-detail/collection-detail.component';
+
 export interface AssetChange {
     assets: Asset[];
     featuredAsset: Asset | undefined;
@@ -58,10 +62,22 @@ export class ProductAssetsComponent implements AfterViewInit {
     public activeContainer;
     public assets: Asset[] = [];
 
+    private readonly updateCollectionPermissions = [Permission.UpdateCatalog, Permission.UpdateCollection];
+    private readonly updateProductPermissions = [Permission.UpdateCatalog, Permission.UpdateProduct];
+
+    get updatePermissions(): Permission[] {
+        if (this.collectionDetailComponent) {
+            return this.updateCollectionPermissions;
+        } else {
+            return this.updateProductPermissions;
+        }
+    }
+
     constructor(
         private modalService: ModalService,
         private changeDetector: ChangeDetectorRef,
         private viewportRuler: ViewportRuler,
+        @Optional() private collectionDetailComponent?: CollectionDetailComponent,
     ) {}
 
     ngAfterViewInit() {

+ 9 - 9
packages/admin-ui/src/lib/catalog/src/components/product-detail/product-detail.component.html

@@ -2,7 +2,7 @@
     <vdr-ab-left>
         <div class="flex clr-flex-row">
             <vdr-entity-info [entity]="entity$ | async"></vdr-entity-info>
-            <clr-toggle-wrapper *vdrIfPermissions="'UpdateCatalog'">
+            <clr-toggle-wrapper *vdrIfPermissions="['UpdateCatalog', 'UpdateProduct']">
                 <input
                     type="checkbox"
                     clrToggle
@@ -32,7 +32,7 @@
         </button>
         <ng-template #updateButton>
             <button
-                *vdrIfPermissions="'UpdateCatalog'"
+                *vdrIfPermissions="['UpdateCatalog', 'UpdateProduct']"
                 class="btn btn-primary"
                 (click)="save()"
                 [disabled]="
@@ -90,7 +90,7 @@
                                     id="name"
                                     type="text"
                                     formControlName="name"
-                                    [readonly]="!('UpdateCatalog' | hasPermission)"
+                                    [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                                     (input)="updateSlug($event.target.value)"
                                 />
                             </vdr-form-field>
@@ -121,13 +121,13 @@
                                     id="slug"
                                     type="text"
                                     formControlName="slug"
-                                    [readonly]="!('UpdateCatalog' | hasPermission)"
+                                    [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                                     pattern="[a-z0-9_-]+"
                                 />
                             </vdr-form-field>
                             <vdr-rich-text-editor
                                 formControlName="description"
-                                [readonly]="!('UpdateCatalog' | hasPermission)"
+                                [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                                 [label]="'common.description' | translate"
                             ></vdr-rich-text-editor>
 
@@ -139,7 +139,7 @@
                                         entityName="Product"
                                         [customFieldsFormGroup]="detailForm.get(['product', 'customFields'])"
                                         [customField]="customField"
-                                        [readonly]="!('UpdateCatalog' | hasPermission)"
+                                        [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                                     ></vdr-custom-field-control>
                                 </ng-container>
                             </section>
@@ -148,12 +148,12 @@
                                 <vdr-facet-value-chip
                                     *ngFor="let facetValue of facetValues$ | async"
                                     [facetValue]="facetValue"
-                                    [removable]="'UpdateCatalog' | hasPermission"
+                                    [removable]="['UpdateCatalog', 'UpdateProduct'] | hasPermission"
                                     (remove)="removeProductFacetValue(facetValue.id)"
                                 ></vdr-facet-value-chip>
                                 <button
                                     class="btn btn-sm btn-secondary"
-                                    *vdrIfPermissions="'UpdateCatalog'"
+                                    *vdrIfPermissions="['UpdateCatalog', 'UpdateProduct']"
                                     (click)="selectProductFacetValue()"
                                 >
                                     <clr-icon shape="plus"></clr-icon>
@@ -215,7 +215,7 @@
                         </div>
                         <div class="flex-spacer"></div>
                         <a
-                            *vdrIfPermissions="'UpdateCatalog'"
+                            *vdrIfPermissions="['UpdateCatalog', 'UpdateProduct']"
                             [routerLink]="['./', 'manage-variants']"
                             class="btn btn-secondary edit-variants-btn"
                         >

+ 3 - 3
packages/admin-ui/src/lib/catalog/src/components/product-list/product-list.component.html

@@ -16,7 +16,7 @@
                         type="button"
                         vdrDropdownItem
                         (click)="rebuildSearchIndex()"
-                        [disabled]="!('UpdateCatalog' | hasPermission)"
+                        [disabled]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                     >
                         {{ 'catalog.rebuild-search-index' | translate }}
                     </button>
@@ -30,7 +30,7 @@
     </vdr-ab-left>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="product-list"></vdr-action-bar-items>
-        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="'CreateCatalog'">
+        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="['CreateCatalog', 'CreateProduct']">
             <clr-icon shape="plus"></clr-icon>
             <span class="full-label">{{ 'catalog.create-new-product' | translate }}</span>
         </a>
@@ -86,7 +86,7 @@
                         type="button"
                         class="delete-button"
                         (click)="deleteProduct(result.productId)"
-                        [disabled]="!('DeleteCatalog' | hasPermission)"
+                        [disabled]="!(['DeleteCatalog', 'DeleteProduct'] | hasPermission)"
                         vdrDropdownItem
                     >
                         <clr-icon shape="trash" class="is-danger"></clr-icon>

+ 17 - 17
packages/admin-ui/src/lib/catalog/src/components/product-variants-list/product-variants-list.component.html

@@ -7,31 +7,31 @@
         <ng-container *ngIf="formGroupMap.get(variant.id) as formGroup" [formGroup]="formGroup">
             <div class="card-block header-row">
                 <div class="details">
-                    <vdr-title-input class="sku" [readonly]="!('UpdateCatalog' | hasPermission)">
+                    <vdr-title-input class="sku" [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)">
                         <clr-input-container>
                             <input
                                 clrInput
                                 type="text"
                                 formControlName="sku"
-                                [readonly]="!('UpdateCatalog' | hasPermission)"
+                                [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                                 [placeholder]="'catalog.sku' | translate"
                             />
                         </clr-input-container>
                     </vdr-title-input>
-                    <vdr-title-input class="name" [readonly]="!('UpdateCatalog' | hasPermission)">
+                    <vdr-title-input class="name" [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)">
                         <clr-input-container>
                             <input
                                 clrInput
                                 type="text"
                                 formControlName="name"
-                                [readonly]="!('UpdateCatalog' | hasPermission)"
+                                [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                                 [placeholder]="'common.name' | translate"
                             />
                         </clr-input-container>
                     </vdr-title-input>
                 </div>
                 <div class="right-controls">
-                    <clr-toggle-wrapper *vdrIfPermissions="'UpdateCatalog'">
+                    <clr-toggle-wrapper *vdrIfPermissions="['UpdateCatalog', 'UpdateProduct']">
                         <input type="checkbox" clrToggle name="enabled" formControlName="enabled" />
                         <label>{{ 'common.enabled' | translate }}</label>
                     </clr-toggle-wrapper>
@@ -52,7 +52,7 @@
                             <div class="variant-form-input-row">
                                 <div class="tax-category">
                                     <clr-select-container
-                                        *vdrIfPermissions="'UpdateCatalog'; else taxCategoryLabel"
+                                        *vdrIfPermissions="['UpdateCatalog', 'UpdateProduct']; else taxCategoryLabel"
                                     >
                                         <label>{{ 'catalog.tax-category' | translate }}</label>
                                         <select clrSelect name="options" formControlName="taxCategoryId">
@@ -80,14 +80,14 @@
                                             *ngIf="!channelPriceIncludesTax"
                                             clrInput
                                             [currencyCode]="variant.currencyCode"
-                                            [readonly]="!('UpdateCatalog' | hasPermission)"
+                                            [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                                             formControlName="price"
                                         ></vdr-currency-input>
                                         <vdr-currency-input
                                             *ngIf="channelPriceIncludesTax"
                                             clrInput
                                             [currencyCode]="variant.currencyCode"
-                                            [readonly]="!('UpdateCatalog' | hasPermission)"
+                                            [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                                             formControlName="priceWithTax"
                                         ></vdr-currency-input>
                                     </clr-input-container>
@@ -100,7 +100,7 @@
                                 ></vdr-variant-price-detail>
                             </div>
                             <div class="variant-form-input-row">
-                                <clr-select-container *vdrIfPermissions="'UpdateCatalog'">
+                                <clr-select-container *vdrIfPermissions="['UpdateCatalog', 'UpdateProduct']">
                                     <label
                                         >{{ 'catalog.track-inventory' | translate }}
                                         <vdr-help-tooltip
@@ -133,7 +133,7 @@
                                         min="0"
                                         step="1"
                                         formControlName="stockOnHand"
-                                        [readonly]="!('UpdateCatalog' | hasPermission)"
+                                        [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                                         [vdrDisabled]="inventoryIsNotTracked(formGroup)"
                                     />
                                 </clr-input-container>
@@ -178,7 +178,7 @@
                                                 clrInput
                                                 type="number"
                                                 [formControl]="formGroup.get('outOfStockThreshold')"
-                                                [readonly]="!('UpdateCatalog' | hasPermission)"
+                                                [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                                                 [vdrDisabled]="
                                                     formGroup.get('useGlobalOutOfStockThreshold')?.value !==
                                                         false || inventoryIsNotTracked(formGroup)
@@ -192,7 +192,7 @@
                                                 name="useGlobalOutOfStockThreshold"
                                                 formControlName="useGlobalOutOfStockThreshold"
                                                 [vdrDisabled]="
-                                                    !('UpdateCatalog' | hasPermission) ||
+                                                    !(['UpdateCatalog', 'UpdateProduct'] | hasPermission) ||
                                                     inventoryIsNotTracked(formGroup)
                                                 "
                                             />
@@ -216,7 +216,7 @@
                                             entityName="ProductVariant"
                                             [compact]="true"
                                             [customFieldsFormGroup]="formGroup.get('customFields')"
-                                            [readonly]="!('UpdateCatalog' | hasPermission)"
+                                            [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                                             [customField]="customField"
                                         ></vdr-custom-field-control>
                                     </ng-container>
@@ -236,7 +236,7 @@
                                 [colorFrom]="optionGroupName(option.groupId)"
                                 [invert]="true"
                                 (iconClick)="editOption(option)"
-                                [icon]="('UpdateCatalog' | hasPermission) && 'pencil'"
+                                [icon]="(['UpdateCatalog', 'UpdateProduct'] | hasPermission) && 'pencil'"
                             >
                                 <span class="option-group-name">{{ optionGroupName(option.groupId) }}</span>
                                 {{ optionName(option) }}
@@ -248,17 +248,17 @@
                         <vdr-facet-value-chip
                             *ngFor="let facetValue of existingFacetValues(variant)"
                             [facetValue]="facetValue"
-                            [removable]="'UpdateCatalog' | hasPermission"
+                            [removable]="['UpdateCatalog', 'UpdateProduct'] | hasPermission"
                             (remove)="removeFacetValue(variant, facetValue.id)"
                         ></vdr-facet-value-chip>
                         <vdr-facet-value-chip
                             *ngFor="let facetValue of pendingFacetValues(variant)"
                             [facetValue]="facetValue"
-                            [removable]="'UpdateCatalog' | hasPermission"
+                            [removable]="['UpdateCatalog', 'UpdateProduct'] | hasPermission"
                             (remove)="removeFacetValue(variant, facetValue.id)"
                         ></vdr-facet-value-chip>
                         <button
-                            *vdrIfPermissions="'UpdateCatalog'"
+                            *vdrIfPermissions="['UpdateCatalog', 'UpdateProduct']"
                             class="btn btn-sm btn-secondary"
                             (click)="selectFacetValueClick.emit([variant.id])"
                         >

+ 6 - 6
packages/admin-ui/src/lib/catalog/src/components/product-variants-table/product-variants-table.component.html

@@ -31,7 +31,7 @@
                         clrInput
                         type="text"
                         formControlName="name"
-                        [readonly]="!('UpdateCatalog' | hasPermission)"
+                        [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                         [placeholder]="'common.name' | translate"
                     />
                 </clr-input-container>
@@ -42,7 +42,7 @@
                         clrInput
                         type="text"
                         formControlName="sku"
-                        [readonly]="!('UpdateCatalog' | hasPermission)"
+                        [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                         [placeholder]="'catalog.sku' | translate"
                     />
                 </clr-input-container>
@@ -62,14 +62,14 @@
                         *ngIf="!channelPriceIncludesTax"
                         clrInput
                         [currencyCode]="variant.currencyCode"
-                        [readonly]="!('UpdateCatalog' | hasPermission)"
+                        [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                         formControlName="price"
                     ></vdr-currency-input>
                     <vdr-currency-input
                         *ngIf="channelPriceIncludesTax"
                         clrInput
                         [currencyCode]="variant.currencyCode"
-                        [readonly]="!('UpdateCatalog' | hasPermission)"
+                        [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                         formControlName="priceWithTax"
                     ></vdr-currency-input>
                 </clr-input-container>
@@ -82,7 +82,7 @@
                         min="0"
                         step="1"
                         formControlName="stockOnHand"
-                        [readonly]="!('UpdateCatalog' | hasPermission)"
+                        [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                     />
                 </clr-input-container>
             </td>
@@ -93,7 +93,7 @@
                         clrToggle
                         name="enabled"
                         formControlName="enabled"
-                        [vdrDisabled]="!('UpdateCatalog' | hasPermission)"
+                        [vdrDisabled]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
                     />
                 </clr-toggle-wrapper>
             </td>

+ 1 - 1
packages/admin-ui/src/lib/catalog/src/components/update-product-option-dialog/update-product-option-dialog.component.html

@@ -24,7 +24,7 @@
             entityName="ProductOption"
             [customFieldsFormGroup]="customFieldsForm"
             [customField]="customField"
-            [readonly]="!('UpdateCatalog' | hasPermission)"
+            [readonly]="!(['UpdateCatalog', 'UpdateProduct'] | hasPermission)"
         ></vdr-custom-field-control>
     </ng-container>
 </section>

+ 53 - 8
packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.ts

@@ -4,6 +4,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import { Subscription } from 'rxjs';
 import { map, startWith } from 'rxjs/operators';
 
+import { Permission } from '../../common/generated-types';
 import { DataService } from '../../data/providers/data.service';
 import { HealthCheckService } from '../../providers/health-check/health-check.service';
 import { JobQueueService } from '../../providers/job-queue/job-queue.service';
@@ -64,31 +65,52 @@ export class MainNavComponent implements OnInit, OnDestroy {
     }
 
     private defineNavMenu() {
+        function allow(...permissions: string[]): (userPermissions: string[]) => boolean {
+            return userPermissions => {
+                for (const permission of permissions) {
+                    if (userPermissions.includes(permission)) {
+                        return true;
+                    }
+                }
+                return false;
+            };
+        }
+
         this.navBuilderService.defineNavMenuSections([
             {
-                requiresPermission: 'ReadCatalog',
+                requiresPermission: allow(
+                    Permission.ReadCatalog,
+                    Permission.ReadProduct,
+                    Permission.ReadFacet,
+                    Permission.ReadCollection,
+                    Permission.ReadAsset,
+                ),
                 id: 'catalog',
                 label: _('nav.catalog'),
                 items: [
                     {
+                        requiresPermission: allow(Permission.ReadCatalog, Permission.ReadProduct),
                         id: 'products',
                         label: _('nav.products'),
                         icon: 'library',
                         routerLink: ['/catalog', 'products'],
                     },
                     {
+                        requiresPermission: allow(Permission.ReadCatalog, Permission.ReadFacet),
                         id: 'facets',
                         label: _('nav.facets'),
                         icon: 'tag',
                         routerLink: ['/catalog', 'facets'],
                     },
                     {
+                        requiresPermission: allow(Permission.ReadCatalog, Permission.ReadCollection),
                         id: 'collections',
                         label: _('nav.collections'),
                         icon: 'folder-open',
                         routerLink: ['/catalog', 'collections'],
                     },
                     {
+                        requiresPermission: allow(Permission.ReadCatalog, Permission.ReadAsset),
                         id: 'assets',
                         label: _('nav.assets'),
                         icon: 'image-gallery',
@@ -99,9 +121,10 @@ export class MainNavComponent implements OnInit, OnDestroy {
             {
                 id: 'sales',
                 label: _('nav.sales'),
-                requiresPermission: 'ReadOrder',
+                requiresPermission: allow(Permission.ReadOrder),
                 items: [
                     {
+                        requiresPermission: allow(Permission.ReadOrder),
                         id: 'orders',
                         label: _('nav.orders'),
                         routerLink: ['/orders'],
@@ -112,15 +135,17 @@ export class MainNavComponent implements OnInit, OnDestroy {
             {
                 id: 'customers',
                 label: _('nav.customers'),
-                requiresPermission: 'ReadCustomer',
+                requiresPermission: allow(Permission.ReadCustomer, Permission.ReadCustomerGroup),
                 items: [
                     {
+                        requiresPermission: allow(Permission.ReadCustomer),
                         id: 'customers',
                         label: _('nav.customers'),
                         routerLink: ['/customer', 'customers'],
                         icon: 'user',
                     },
                     {
+                        requiresPermission: allow(Permission.ReadCustomerGroup),
                         id: 'customer-groups',
                         label: _('nav.customer-groups'),
                         routerLink: ['/customer', 'groups'],
@@ -131,9 +156,10 @@ export class MainNavComponent implements OnInit, OnDestroy {
             {
                 id: 'marketing',
                 label: _('nav.marketing'),
-                requiresPermission: 'ReadPromotion',
+                requiresPermission: allow(Permission.ReadPromotion),
                 items: [
                     {
+                        requiresPermission: allow(Permission.ReadPromotion),
                         id: 'promotions',
                         label: _('nav.promotions'),
                         routerLink: ['/marketing', 'promotions'],
@@ -144,67 +170,86 @@ export class MainNavComponent implements OnInit, OnDestroy {
             {
                 id: 'settings',
                 label: _('nav.settings'),
-                requiresPermission: 'ReadSettings',
+                requiresPermission: allow(
+                    Permission.ReadSettings,
+                    Permission.ReadChannel,
+                    Permission.ReadAdministrator,
+                    Permission.ReadShippingMethod,
+                    Permission.ReadPaymentMethod,
+                    Permission.ReadTaxCategory,
+                    Permission.ReadTaxRate,
+                    Permission.ReadCountry,
+                    Permission.ReadZone,
+                    Permission.UpdateGlobalSettings,
+                ),
                 collapsible: true,
                 collapsedByDefault: true,
                 items: [
                     {
+                        requiresPermission: allow(Permission.ReadChannel),
                         id: 'channels',
                         label: _('nav.channels'),
                         routerLink: ['/settings', 'channels'],
                         icon: 'layers',
                     },
                     {
+                        requiresPermission: allow(Permission.ReadAdministrator),
                         id: 'administrators',
                         label: _('nav.administrators'),
-                        requiresPermission: 'ReadAdministrator',
                         routerLink: ['/settings', 'administrators'],
                         icon: 'administrator',
                     },
                     {
+                        requiresPermission: allow(Permission.ReadAdministrator),
                         id: 'roles',
                         label: _('nav.roles'),
-                        requiresPermission: 'ReadAdministrator',
                         routerLink: ['/settings', 'roles'],
                         icon: 'users',
                     },
                     {
+                        requiresPermission: allow(Permission.ReadShippingMethod),
                         id: 'shipping-methods',
                         label: _('nav.shipping-methods'),
                         routerLink: ['/settings', 'shipping-methods'],
                         icon: 'truck',
                     },
                     {
+                        requiresPermission: allow(Permission.ReadPaymentMethod),
                         id: 'payment-methods',
                         label: _('nav.payment-methods'),
                         routerLink: ['/settings', 'payment-methods'],
                         icon: 'credit-card',
                     },
                     {
+                        requiresPermission: allow(Permission.ReadTaxCategory),
                         id: 'tax-categories',
                         label: _('nav.tax-categories'),
                         routerLink: ['/settings', 'tax-categories'],
                         icon: 'view-list',
                     },
                     {
+                        requiresPermission: allow(Permission.ReadTaxRate),
                         id: 'tax-rates',
                         label: _('nav.tax-rates'),
                         routerLink: ['/settings', 'tax-rates'],
                         icon: 'calculator',
                     },
                     {
+                        requiresPermission: allow(Permission.ReadCountry),
                         id: 'countries',
                         label: _('nav.countries'),
                         routerLink: ['/settings', 'countries'],
                         icon: 'flag',
                     },
                     {
+                        requiresPermission: allow(Permission.ReadZone),
                         id: 'zones',
                         label: _('nav.zones'),
                         routerLink: ['/settings', 'zones'],
                         icon: 'world',
                     },
                     {
+                        requiresPermission: allow(Permission.UpdateGlobalSettings),
                         id: 'global-settings',
                         label: _('nav.global-settings'),
                         routerLink: ['/settings', 'global-settings'],
@@ -215,7 +260,7 @@ export class MainNavComponent implements OnInit, OnDestroy {
             {
                 id: 'system',
                 label: _('nav.system'),
-                requiresPermission: 'ReadSettings',
+                requiresPermission: Permission.ReadSystem,
                 collapsible: true,
                 collapsedByDefault: true,
                 items: [

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.html

@@ -35,7 +35,7 @@
                 clrInput
                 type="text"
                 formControlName="name"
-                [readonly]="!('UpdateCatalog' | hasPermission) || settingFocalPoint"
+                [readonly]="!(['UpdateCatalog', 'UpdateAsset'] | hasPermission) || settingFocalPoint"
             />
         </clr-input-container>
 

+ 2 - 2
packages/admin-ui/src/lib/core/src/shared/directives/if-permissions.directive.spec.ts

@@ -42,9 +42,9 @@ describe('vdrIfPermissions directive', () => {
         fixture.detectChanges();
 
         const thenEl = fixture.nativeElement.querySelector('.then');
-        expect(thenEl).toBeNull();
+        expect(thenEl).not.toBeNull();
         const elseEl = fixture.nativeElement.querySelector('.else');
-        expect(elseEl).not.toBeNull();
+        expect(elseEl).toBeNull();
     });
 
     it('does not have permission', () => {

+ 3 - 3
packages/admin-ui/src/lib/core/src/shared/directives/if-permissions.directive.ts

@@ -49,11 +49,11 @@ export class IfPermissionsDirective extends IfDirectiveBase<Array<Permission[] |
                 .userStatus()
                 .mapStream(({ userStatus }) => {
                     for (const permission of permissions) {
-                        if (!userStatus.permissions.includes(permission)) {
-                            return false;
+                        if (userStatus.permissions.includes(permission)) {
+                            return true;
                         }
                     }
-                    return true;
+                    return false;
                 })
                 .pipe(tap(() => this.changeDetectorRef.markForCheck()));
         });

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component.html

@@ -9,7 +9,7 @@
                 <clr-icon shape="times"></clr-icon>
             </button>
             <div class="flex-spacer"></div>
-            <div class="drag-handle" cdkDragHandle *vdrIfPermissions="'UpdateCatalog'">
+            <div class="drag-handle" cdkDragHandle *ngIf="!readonly">
                 <clr-icon shape="drag-handle" size="24"></clr-icon>
             </div>
         </div>

+ 16 - 5
packages/admin-ui/src/lib/core/src/shared/pipes/has-permission.pipe.ts

@@ -19,7 +19,7 @@ import { DataService } from '../../data/providers/data.service';
 export class HasPermissionPipe implements PipeTransform, OnDestroy {
     private hasPermission = false;
     private currentPermissions$: Observable<string[]>;
-    private permission: string | null = null;
+    private lastPermissions: string | null = null;
     private subscription: Subscription;
 
     constructor(private dataService: DataService, private changeDetectorRef: ChangeDetectorRef) {
@@ -28,13 +28,15 @@ export class HasPermissionPipe implements PipeTransform, OnDestroy {
             .mapStream(data => data.userStatus.permissions);
     }
 
-    transform(permission: string): any {
-        if (this.permission !== permission) {
-            this.permission = permission;
+    transform(input: string | string[]): any {
+        const requiredPermissions = Array.isArray(input) ? input : [input];
+        const requiredPermissionsString = requiredPermissions.join(',');
+        if (this.lastPermissions !== requiredPermissionsString) {
+            this.lastPermissions = requiredPermissionsString;
             this.hasPermission = false;
             this.dispose();
             this.subscription = this.currentPermissions$.subscribe(permissions => {
-                this.hasPermission = permissions.includes(permission);
+                this.hasPermission = this.checkPermissions(permissions, requiredPermissions);
                 this.changeDetectorRef.markForCheck();
             });
         }
@@ -46,6 +48,15 @@ export class HasPermissionPipe implements PipeTransform, OnDestroy {
         this.dispose();
     }
 
+    private checkPermissions(userPermissions: string[], requiredPermissions: string[]): boolean {
+        for (const perm of requiredPermissions) {
+            if (userPermissions.includes(perm)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private dispose() {
         if (this.subscription) {
             this.subscription.unsubscribe();

+ 28 - 26
packages/admin-ui/src/lib/customer/src/components/address-card/address-card.component.html

@@ -1,6 +1,6 @@
 <div class="card" *ngIf="addressForm.value as address">
     <div class="card-header">
-        <div class="address-title ">
+        <div class="address-title">
             <span class="street-line" *ngIf="address.streetLine1">{{ address.streetLine1 }},</span>
             {{ address.countryCode }}
         </div>
@@ -22,32 +22,34 @@
     </div>
     <div class="card-footer">
         <vdr-entity-info [entity]="address"></vdr-entity-info>
-        <button class="btn btn-sm btn-link" (click)="editAddress()">
-            {{ 'common.edit' | translate }}
-        </button>
-        <vdr-dropdown>
-            <button type="button" class="btn btn-sm btn-link" vdrDropdownTrigger>
-                {{ 'common.more' | translate }}
-                <clr-icon shape="caret down"></clr-icon>
+        <ng-container *ngIf="editable">
+            <button class="btn btn-sm btn-link" (click)="editAddress()">
+                {{ 'common.edit' | translate }}
             </button>
-            <vdr-dropdown-menu>
-                <button
-                    vdrDropdownItem
-                    class="button"
-                    [disabled]="isDefaultShipping"
-                    (click)="setAsDefaultShippingAddress()"
-                >
-                    {{ 'customer.set-as-default-shipping-address' | translate }}
+            <vdr-dropdown>
+                <button type="button" class="btn btn-sm btn-link" vdrDropdownTrigger>
+                    {{ 'common.more' | translate }}
+                    <clr-icon shape="caret down"></clr-icon>
                 </button>
-                <button
-                    vdrDropdownItem
-                    class="button"
-                    [disabled]="isDefaultBilling"
-                    (click)="setAsDefaultBillingAddress()"
-                >
-                    {{ 'customer.set-as-default-billing-address' | translate }}
-                </button>
-            </vdr-dropdown-menu>
-        </vdr-dropdown>
+                <vdr-dropdown-menu>
+                    <button
+                        vdrDropdownItem
+                        class="button"
+                        [disabled]="isDefaultShipping"
+                        (click)="setAsDefaultShippingAddress()"
+                    >
+                        {{ 'customer.set-as-default-shipping-address' | translate }}
+                    </button>
+                    <button
+                        vdrDropdownItem
+                        class="button"
+                        [disabled]="isDefaultBilling"
+                        (click)="setAsDefaultBillingAddress()"
+                    >
+                        {{ 'customer.set-as-default-billing-address' | translate }}
+                    </button>
+                </vdr-dropdown-menu>
+            </vdr-dropdown>
+        </ng-container>
     </div>
 </div>

+ 1 - 0
packages/admin-ui/src/lib/customer/src/components/address-card/address-card.component.ts

@@ -28,6 +28,7 @@ export class AddressCardComponent implements OnInit, OnChanges {
     @Input() availableCountries: GetAvailableCountries.Items[] = [];
     @Input() isDefaultBilling: string;
     @Input() isDefaultShipping: string;
+    @Input() editable = true;
     @Output() setAsDefaultShipping = new EventEmitter<string>();
     @Output() setAsDefaultBilling = new EventEmitter<string>();
     private dataDependenciesPopulated = new BehaviorSubject<boolean>(false);

+ 4 - 2
packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.html

@@ -21,6 +21,7 @@
         </button>
         <ng-template #updateButton>
             <button
+                *vdrIfPermissions="'UpdateCustomer'"
                 class="btn btn-primary"
                 (click)="save()"
                 [disabled]="!(addressDefaultsUpdated || (detailForm.valid && detailForm.dirty))"
@@ -95,7 +96,7 @@
         {{ 'customer.not-a-member-of-any-groups' | translate }}
     </ng-template>
     <div>
-        <button class="btn btn-sm btn-secondary" (click)="addToGroup()">
+        <button class="btn btn-sm btn-secondary" (click)="addToGroup()" *vdrIfPermissions="'UpdateCustomerGroup'">
             <clr-icon shape="plus"></clr-icon>
             {{ 'customer.add-customer-to-group' | translate }}
         </button>
@@ -112,10 +113,11 @@
             [isDefaultShipping]="defaultShippingAddressId === addressForm.value.id"
             [addressForm]="addressForm"
             [customFields]="addressCustomFields"
+            [editable]="['UpdateCustomer'] | hasPermission"
             (setAsDefaultBilling)="setDefaultBillingAddressId($event)"
             (setAsDefaultShipping)="setDefaultShippingAddressId($event)"
         ></vdr-address-card>
-        <button class="btn btn-secondary" (click)="addAddress()">
+        <button class="btn btn-secondary" (click)="addAddress()" *vdrIfPermissions="'UpdateCustomer'">
             <clr-icon shape="plus"></clr-icon>
             {{ 'customer.create-new-address' | translate }}
         </button>

+ 1 - 1
packages/admin-ui/src/lib/customer/src/components/customer-group-detail-dialog/customer-group-detail-dialog.component.html

@@ -4,7 +4,7 @@
 </ng-template>
 
 <vdr-form-field [label]="'common.name' | translate" for="name">
-    <input id="name" type="text" [(ngModel)]="group.name" [readonly]="!('UpdateCustomer' | hasPermission)" />
+    <input id="name" type="text" [(ngModel)]="group.name" [readonly]="!(['CreateCustomerGroup', 'UpdateCustomerGroup'] | hasPermission)" />
 </vdr-form-field>
 
 <ng-template vdrDialogButtons>

+ 3 - 3
packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.html

@@ -2,7 +2,7 @@
     <vdr-ab-left> </vdr-ab-left>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="customer-group-list"></vdr-action-bar-items>
-        <button class="btn btn-primary" *vdrIfPermissions="'CreateCustomer'" (click)="create()">
+        <button class="btn btn-primary" *vdrIfPermissions="'CreateCustomerGroup'" (click)="create()">
             <clr-icon shape="plus"></clr-icon>
             {{ 'customer.create-new-customer-group' | translate }}
         </button>
@@ -41,7 +41,7 @@
                                 class="button"
                                 vdrDropdownItem
                                 (click)="delete(group.id)"
-                                [disabled]="!('DeleteSettings' | hasPermission)"
+                                [disabled]="!('DeleteCustomerGroup' | hasPermission)"
                             >
                                 <clr-icon shape="trash" class="is-danger"></clr-icon>
                                 {{ 'common.delete' | translate }}
@@ -83,7 +83,7 @@
                             class="delete-button"
                             (click)="removeFromGroup(activeGroup, selectedCustomerIds)"
                             vdrDropdownItem
-                            [disabled]="!('DeleteSettings' | hasPermission)"
+                            [disabled]="!('UpdateCustomerGroup' | hasPermission)"
                         >
                             <clr-icon shape="trash" class="is-danger"></clr-icon>
                             {{ 'customer.remove-from-group' | translate }}

+ 1 - 1
packages/admin-ui/src/lib/customer/src/components/customer-group-member-list/customer-group-member-list.component.html

@@ -14,7 +14,7 @@
     (pageChange)="setContentsPageNumber($event)"
     (itemsPerPageChange)="setContentsItemsPerPage($event)"
     [allSelected]="areAllSelected()"
-    [isRowSelectedFn]="('UpdateCustomer' | hasPermission) && isMemberSelected"
+    [isRowSelectedFn]="('UpdateCustomerGroup' | hasPermission) && isMemberSelected"
     (rowSelectChange)="toggleSelectMember($event)"
     (allSelectChange)="toggleSelectAll()"
 >

+ 3 - 3
packages/admin-ui/src/lib/customer/src/components/customer-history/customer-history.component.html

@@ -1,6 +1,6 @@
 <h4>{{ 'customer.customer-history' | translate }}</h4>
 <div class="entry-list">
-    <vdr-timeline-entry iconShape="note" displayType="muted">
+    <vdr-timeline-entry iconShape="note" displayType="muted" *vdrIfPermissions="'UpdateCustomer'">
         <div class="note-entry">
             <textarea [(ngModel)]="note" name="note" class="note"></textarea>
             <button class="btn btn-secondary" [disabled]="!note" (click)="addNoteToCustomer()">
@@ -134,7 +134,7 @@
                                 class="button"
                                 vdrDropdownItem
                                 (click)="updateNote.emit(entry)"
-                                [disabled]="!('UpdateOrder' | hasPermission)"
+                                [disabled]="!('UpdateCustomer' | hasPermission)"
                             >
                                 <clr-icon shape="edit"></clr-icon>
                                 {{ 'common.edit' | translate }}
@@ -144,7 +144,7 @@
                                 class="button"
                                 vdrDropdownItem
                                 (click)="deleteNote.emit(entry)"
-                                [disabled]="!('UpdateOrder' | hasPermission)"
+                                [disabled]="!('UpdateCustomer' | hasPermission)"
                             >
                                 <clr-icon shape="trash" class="is-danger"></clr-icon>
                                 {{ 'common.delete' | translate }}

+ 4 - 1
packages/admin-ui/src/lib/settings/src/components/admin-detail/admin-detail.component.ts

@@ -16,6 +16,7 @@ import {
 import { NotificationService } from '@vendure/admin-ui/core';
 import { DataService } from '@vendure/admin-ui/core';
 import { ServerConfigService } from '@vendure/admin-ui/core';
+import { CUSTOMER_ROLE_CODE } from '@vendure/common/lib/shared-constants';
 import { Observable } from 'rxjs';
 import { mergeMap, take } from 'rxjs/operators';
 
@@ -73,7 +74,9 @@ export class AdminDetailComponent
     ngOnInit() {
         this.init();
         this.administrator$ = this.entity$;
-        this.allRoles$ = this.dataService.administrator.getRoles(999).mapStream(item => item.roles.items);
+        this.allRoles$ = this.dataService.administrator
+            .getRoles(999)
+            .mapStream(item => item.roles.items.filter(i => i.code !== CUSTOMER_ROLE_CODE));
         this.dataService.client.userStatus().single$.subscribe(({ userStatus }) => {
             if (!userStatus.permissions.includes(Permission.UpdateAdministrator)) {
                 const rolesSelect = this.detailForm.get('roles');

+ 8 - 8
packages/admin-ui/src/lib/settings/src/components/channel-detail/channel-detail.component.html

@@ -17,7 +17,7 @@
             <button
                 class="btn btn-primary"
                 (click)="save()"
-                *vdrIfPermissions="'SuperAdmin'"
+                *vdrIfPermissions="['SuperAdmin', 'UpdateChannel']"
                 [disabled]="!saveButtonEnabled()"
             >
                 {{ 'common.update' | translate }}
@@ -28,17 +28,17 @@
 
 <form class="form" [formGroup]="detailForm">
     <vdr-form-field [label]="'common.code' | translate" for="code">
-        <input id="code" type="text" [readonly]="!('SuperAdmin' | hasPermission)" formControlName="code" />
+        <input id="code" type="text" [readonly]="!(['SuperAdmin', 'UpdateChannel', 'CreateChannel'] | hasPermission)" formControlName="code" />
     </vdr-form-field>
     <vdr-form-field [label]="'settings.channel-token' | translate" for="token">
-        <input id="token" type="text" [readonly]="!('SuperAdmin' | hasPermission)" formControlName="token" />
+        <input id="token" type="text" [readonly]="!(['SuperAdmin', 'UpdateChannel', 'CreateChannel'] | hasPermission)" formControlName="token" />
     </vdr-form-field>
     <vdr-form-field [label]="'settings.currency' | translate" for="defaultTaxZoneId">
         <select
             clrSelect
             name="currencyCode"
             formControlName="currencyCode"
-            [vdrDisabled]="!('SuperAdmin' | hasPermission)"
+            [vdrDisabled]="!(['SuperAdmin', 'UpdateChannel', 'CreateChannel'] | hasPermission)"
         >
             <option *ngFor="let code of currencyCodes" [value]="code">{{ code | localeCurrencyName }}</option>
         </select>
@@ -48,7 +48,7 @@
             clrSelect
             name="defaultLanguageCode"
             formControlName="defaultLanguageCode"
-            [vdrDisabled]="!('SuperAdmin' | hasPermission)"
+            [vdrDisabled]="!(['SuperAdmin', 'UpdateChannel', 'CreateChannel'] | hasPermission)"
         >
             <option *ngFor="let languageCode of availableLanguageCodes$ | async" [value]="languageCode">
                 {{ 'lang.' + languageCode | translate }} ({{ languageCode | uppercase }})
@@ -62,7 +62,7 @@
                 clrToggle
                 id="pricesIncludeTax"
                 formControlName="pricesIncludeTax"
-                [vdrDisabled]="!('SuperAdmin' | hasPermission)"
+                [vdrDisabled]="!(['SuperAdmin', 'UpdateChannel', 'CreateChannel'] | hasPermission)"
             />
         </clr-toggle-wrapper>
     </vdr-form-field>
@@ -71,7 +71,7 @@
             clrSelect
             name="defaultTaxZoneId"
             formControlName="defaultTaxZoneId"
-            [vdrDisabled]="!('SuperAdmin' | hasPermission)"
+            [vdrDisabled]="!(['SuperAdmin', 'UpdateChannel', 'CreateChannel'] | hasPermission)"
         >
             <option *ngFor="let zone of zones$ | async" [value]="zone.id">{{ zone.name }}</option>
         </select>
@@ -93,7 +93,7 @@
             clrSelect
             name="defaultShippingZoneId"
             formControlName="defaultShippingZoneId"
-            [vdrDisabled]="!('SuperAdmin' | hasPermission)"
+            [vdrDisabled]="!(['SuperAdmin', 'UpdateChannel', 'CreateChannel'] | hasPermission)"
         >
             <option *ngFor="let zone of zones$ | async" [value]="zone.id">{{ zone.name }}</option>
         </select>

+ 1 - 1
packages/admin-ui/src/lib/settings/src/components/channel-list/channel-list.component.html

@@ -35,7 +35,7 @@
                         type="button"
                         class="delete-button"
                         (click)="deleteChannel(channel.id)"
-                        [disabled]="!('SuperAdmin' | hasPermission)"
+                        [disabled]="!(['SuperAdmin', 'DeleteChannel'] | hasPermission)"
                         vdrDropdownItem
                     >
                         <clr-icon shape="trash" class="is-danger"></clr-icon>

+ 4 - 4
packages/admin-ui/src/lib/settings/src/components/country-detail/country-detail.component.html

@@ -21,7 +21,7 @@
         <ng-template #updateButton>
             <button
                 class="btn btn-primary"
-                *vdrIfPermissions="'UpdateSettings'"
+                *vdrIfPermissions="['UpdateSettings', 'UpdateCountry']"
                 (click)="save()"
                 [disabled]="detailForm.invalid || detailForm.pristine"
             >
@@ -37,7 +37,7 @@
             id="code"
             type="text"
             formControlName="code"
-            [readonly]="!('UpdateSettings' | hasPermission)"
+            [readonly]="!(['UpdateSettings', 'UpdateCountry'] | hasPermission)"
         />
     </vdr-form-field>
     <vdr-form-field [label]="'common.name' | translate" for="name">
@@ -45,7 +45,7 @@
             id="name"
             type="text"
             formControlName="name"
-            [readonly]="!('UpdateSettings' | hasPermission)"
+            [readonly]="!(['UpdateSettings', 'UpdateCountry'] | hasPermission)"
         />
     </vdr-form-field>
     <vdr-form-field [label]="'common.enabled' | translate" for="enabled">
@@ -55,7 +55,7 @@
                 clrToggle
                 id="enabled"
                 formControlName="enabled"
-                [vdrDisabled]="!('UpdateSettings' | hasPermission)"
+                [vdrDisabled]="!(['UpdateSettings', 'UpdateCountry'] | hasPermission)"
             />
         </clr-toggle-wrapper>
     </vdr-form-field>

+ 2 - 2
packages/admin-ui/src/lib/settings/src/components/country-list/country-list.component.html

@@ -11,7 +11,7 @@
 
     <vdr-ab-right>
         <vdr-action-bar-items locationId="country-list"></vdr-action-bar-items>
-        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="'CreateSettings'">
+        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="['CreateSettings', 'CreateCountry']">
             <clr-icon shape="plus"></clr-icon>
             {{ 'settings.create-new-country' | translate }}
         </a>
@@ -60,7 +60,7 @@
                         class="delete-button"
                         (click)="deleteCountry(country.id)"
                         vdrDropdownItem
-                        [disabled]="!('DeleteSettings' | hasPermission)"
+                        [disabled]="!(['DeleteSettings', 'DeleteCountry'] | hasPermission)"
                     >
                         <clr-icon shape="trash" class="is-danger"></clr-icon>
                         {{ 'common.delete' | translate }}

+ 3 - 3
packages/admin-ui/src/lib/settings/src/components/global-settings/global-settings.component.html

@@ -4,7 +4,7 @@
         <button
             class="btn btn-primary"
             (click)="save()"
-            *vdrIfPermissions="'UpdateSettings'"
+            *vdrIfPermissions="['UpdateSettings', 'UpdateGlobalSettings']"
             [disabled]="detailForm.invalid || detailForm.pristine"
         >
             {{ 'common.update' | translate }}
@@ -42,7 +42,7 @@
             id="outOfStockThreshold"
             type="number"
             formControlName="outOfStockThreshold"
-            [readonly]="!('UpdateSettings' | hasPermission)"
+            [readonly]="!(['UpdateSettings', 'UpdateGlobalSettings'] | hasPermission)"
         />
     </vdr-form-field>
     <vdr-form-field
@@ -56,7 +56,7 @@
                 clrToggle
                 name="enabled"
                 formControlName="trackInventory"
-                [vdrDisabled]="!('UpdateSettings' | hasPermission)"
+                [vdrDisabled]="!(['UpdateSettings', 'UpdateGlobalSettings'] | hasPermission)"
             />
         </clr-toggle-wrapper>
     </vdr-form-field>

+ 8 - 8
packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.html

@@ -15,7 +15,7 @@
         </button>
         <ng-template #updateButton>
             <button
-                *vdrIfPermissions="'UpdateSettings'"
+                *vdrIfPermissions="['UpdateSettings', 'UpdatePaymentMethod']"
                 class="btn btn-primary"
                 (click)="save()"
                 [disabled]="detailForm.pristine || detailForm.invalid || !selectedHandler"
@@ -32,25 +32,25 @@
             id="name"
             type="text"
             formControlName="name"
-            [readonly]="!('UpdateSettings' | hasPermission)"
+            [readonly]="!(['UpdateSettings', 'UpdatePaymentMethod'] | hasPermission)"
             (input)="updateCode(paymentMethod.code, $event.target.value)"
         />
     </vdr-form-field>
     <vdr-form-field
         [label]="'common.code' | translate"
         for="code"
-        [readOnlyToggle]="'UpdateSettings' | hasPermission"
+        [readOnlyToggle]="['UpdateSettings', 'UpdatePaymentMethod'] | hasPermission"
     >
         <input
             id="code"
             type="text"
             formControlName="code"
-            [readonly]="!('UpdateSettings' | hasPermission)"
+            [readonly]="!(['UpdateSettings', 'UpdatePaymentMethod'] | hasPermission)"
         />
     </vdr-form-field>
     <vdr-rich-text-editor
         formControlName="description"
-        [readonly]="!('UpdateSettings' | hasPermission)"
+        [readonly]="!(['UpdateSettings', 'UpdatePaymentMethod'] | hasPermission)"
         [label]="'common.description' | translate"
     ></vdr-rich-text-editor>
     <vdr-form-field [label]="'common.enabled' | translate" for="description">
@@ -59,7 +59,7 @@
                 type="checkbox"
                 clrToggle
                 id="enabled"
-                [vdrDisabled]="!('UpdateSettings' | hasPermission)"
+                [vdrDisabled]="!(['UpdateSettings', 'UpdatePaymentMethod'] | hasPermission)"
                 formControlName="enabled"
             />
         </clr-toggle-wrapper>
@@ -72,7 +72,7 @@
                 *ngIf="selectedChecker && selectedCheckerDefinition"
                 [operation]="selectedChecker"
                 [operationDefinition]="selectedCheckerDefinition"
-                [readonly]="!('UpdateSettings' | hasPermission)"
+                [readonly]="!(['UpdateSettings', 'UpdatePaymentMethod'] | hasPermission)"
                 (remove)="removeChecker()"
                 formControlName="checker"
             ></vdr-configurable-input>
@@ -101,7 +101,7 @@
                 *ngIf="selectedHandler && selectedHandlerDefinition"
                 [operation]="selectedHandler"
                 [operationDefinition]="selectedHandlerDefinition"
-                [readonly]="!('UpdateSettings' | hasPermission)"
+                [readonly]="!(['UpdateSettings', 'UpdatePaymentMethod'] | hasPermission)"
                 (remove)="removeHandler()"
                 formControlName="handler"
             ></vdr-configurable-input>

+ 2 - 2
packages/admin-ui/src/lib/settings/src/components/payment-method-list/payment-method-list.component.html

@@ -1,7 +1,7 @@
 <vdr-action-bar>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="payment-method-list"></vdr-action-bar-items>
-        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="'CreateSettings'">
+        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="['CreateSettings', 'CreatePaymentMethod']">
             <clr-icon shape="plus"></clr-icon>
             {{ 'settings.create-new-payment-method' | translate }}
         </a>
@@ -41,7 +41,7 @@
                         type="button"
                         class="delete-button"
                         (click)="deletePaymentMethod(paymentMethod.id)"
-                        [disabled]="!('DeleteSettings' | hasPermission)"
+                        [disabled]="!(['DeleteSettings', 'DeletePaymentMethod'] | hasPermission)"
                         vdrDropdownItem
                     >
                         <clr-icon shape="trash" class="is-danger"></clr-icon>

+ 3 - 2
packages/admin-ui/src/lib/settings/src/components/permission-grid/permission-grid.component.html

@@ -1,8 +1,9 @@
 <table class="table">
     <tbody>
         <tr *ngFor="let section of gridData">
-            <td class="permission-group">
-                <div>{{ section.label | translate }}</div>
+            <td class="permission-group left">
+                <div><strong>{{ section.label | translate }}</strong></div>
+                <small>{{ section.description | translate }}</small><br>
                 <button *ngIf="1 < section.permissions.length && !readonly" class="btn btn-sm btn-link" (click)="toggleAll(section.permissions)">
                     {{ 'common.toggle-all' | translate }}
                 </button>

+ 1 - 0
packages/admin-ui/src/lib/settings/src/components/permission-grid/permission-grid.component.scss

@@ -1,4 +1,5 @@
 
 td.permission-group {
+    max-width: 300px;
     background-color: var(--color-component-bg-200);
 }

+ 7 - 0
packages/admin-ui/src/lib/settings/src/components/permission-grid/permission-grid.component.ts

@@ -4,6 +4,7 @@ import { PermissionDefinition } from '@vendure/admin-ui/core';
 
 export interface PermissionGridRow {
     label: string;
+    description: string;
     permissions: PermissionDefinition[];
 }
 
@@ -63,14 +64,20 @@ export class PermissionGridComponent implements OnInit {
         this.gridData = [
             ...nonCrud.map(d => ({
                 label: d.name,
+                description: d.description,
                 permissions: [d],
             })),
             ...Array.from(crudGroups.entries()).map(([label, defs]) => {
                 return {
                     label,
+                    description: this.extractCrudDescription(defs[0]),
                     permissions: defs,
                 };
             }),
         ];
     }
+
+    private extractCrudDescription(def: PermissionDefinition): string {
+        return def.description.replace(/Grants permission to [\w]+/, 'Grants permissions on');
+    }
 }

+ 1 - 1
packages/admin-ui/src/lib/settings/src/components/role-list/role-list.component.html

@@ -9,7 +9,7 @@
 </vdr-action-bar>
 
 <vdr-data-table
-    [items]="items$ | async"
+    [items]="visibleRoles$ | async"
     [itemsPerPage]="itemsPerPage$ | async"
     [totalItems]="totalItems$ | async"
     [currentPage]="currentPage$ | async"

+ 13 - 6
packages/admin-ui/src/lib/settings/src/components/role-list/role-list.component.ts

@@ -1,15 +1,14 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
-import { CUSTOMER_ROLE_CODE, SUPER_ADMIN_ROLE_CODE } from '@vendure/common/lib/shared-constants';
-import { EMPTY } from 'rxjs';
-import { switchMap } from 'rxjs/operators';
-
 import { BaseListComponent } from '@vendure/admin-ui/core';
 import { GetRoles, Role } from '@vendure/admin-ui/core';
 import { NotificationService } from '@vendure/admin-ui/core';
 import { DataService } from '@vendure/admin-ui/core';
 import { ModalService } from '@vendure/admin-ui/core';
+import { CUSTOMER_ROLE_CODE, SUPER_ADMIN_ROLE_CODE } from '@vendure/common/lib/shared-constants';
+import { EMPTY, Observable } from 'rxjs';
+import { map, switchMap } from 'rxjs/operators';
 
 @Component({
     selector: 'vdr-role-list',
@@ -17,9 +16,10 @@ import { ModalService } from '@vendure/admin-ui/core';
     styleUrls: ['./role-list.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class RoleListComponent extends BaseListComponent<GetRoles.Query, GetRoles.Items> {
+export class RoleListComponent extends BaseListComponent<GetRoles.Query, GetRoles.Items> implements OnInit {
     readonly initialLimit = 3;
     displayLimit: { [id: string]: number } = {};
+    visibleRoles$: Observable<GetRoles.Items[]>;
 
     constructor(
         private modalService: ModalService,
@@ -35,6 +35,13 @@ export class RoleListComponent extends BaseListComponent<GetRoles.Query, GetRole
         );
     }
 
+    ngOnInit() {
+        super.ngOnInit();
+        this.visibleRoles$ = this.items$.pipe(
+            map(roles => roles.filter(role => role.code !== CUSTOMER_ROLE_CODE)),
+        );
+    }
+
     toggleDisplayLimit(role: GetRoles.Items) {
         if (this.displayLimit[role.id] === role.permissions.length) {
             this.displayLimit[role.id] = this.initialLimit;

+ 8 - 8
packages/admin-ui/src/lib/settings/src/components/shipping-method-detail/shipping-method-detail.component.html

@@ -23,7 +23,7 @@
             <button
                 class="btn btn-primary"
                 (click)="save()"
-                *vdrIfPermissions="'UpdateSettings'"
+                *vdrIfPermissions="['UpdateSettings', 'UpdateShippingMethod']"
                 [disabled]="
                     detailForm.pristine || detailForm.invalid || !selectedChecker || !selectedCalculator
                 "
@@ -40,32 +40,32 @@
             id="name"
             type="text"
             formControlName="name"
-            [readonly]="!('UpdateSettings' | hasPermission)"
+            [readonly]="!(['UpdateSettings', 'UpdateShippingMethod'] | hasPermission)"
             (input)="updateCode(shippingMethod.code, $event.target.value)"
         />
     </vdr-form-field>
     <vdr-form-field
         [label]="'common.code' | translate"
         for="code"
-        [readOnlyToggle]="'UpdateSettings' | hasPermission"
+        [readOnlyToggle]="['UpdateSettings', 'UpdateShippingMethod'] | hasPermission"
     >
         <input
             id="code"
             type="text"
             formControlName="code"
-            [readonly]="!('UpdateSettings' | hasPermission)"
+            [readonly]="!(['UpdateSettings', 'UpdateShippingMethod'] | hasPermission)"
         />
     </vdr-form-field>
     <vdr-rich-text-editor
         formControlName="description"
-        [readonly]="!('UpdateSettings' | hasPermission)"
+        [readonly]="!(['UpdateSettings', 'UpdateShippingMethod'] | hasPermission)"
         [label]="'common.description' | translate"
     ></vdr-rich-text-editor>
     <vdr-form-field [label]="'settings.fulfillment-handler' | translate" for="fulfillmentHandler" class="mb2">
         <select
             name="fulfillmentHandler"
             formControlName="fulfillmentHandler"
-            [vdrDisabled]="!('UpdateSettings' | hasPermission)"
+            [vdrDisabled]="!(['UpdateSettings', 'UpdateShippingMethod'] | hasPermission)"
         >
             <option *ngFor="let handler of fulfillmentHandlers" [value]="handler.code">
                 {{ handler.code }}: {{ handler.description }}
@@ -92,7 +92,7 @@
                 *ngIf="selectedChecker && selectedCheckerDefinition"
                 [operation]="selectedChecker"
                 [operationDefinition]="selectedCheckerDefinition"
-                [readonly]="!('UpdateSettings' | hasPermission)"
+                [readonly]="!(['UpdateSettings', 'UpdateShippingMethod'] | hasPermission)"
                 (remove)="selectedChecker = null"
                 formControlName="checker"
             ></vdr-configurable-input>
@@ -121,7 +121,7 @@
                 *ngIf="selectedCalculator && selectedCalculatorDefinition"
                 [operation]="selectedCalculator"
                 [operationDefinition]="selectedCalculatorDefinition"
-                [readonly]="!('UpdateSettings' | hasPermission)"
+                [readonly]="!(['UpdateSettings', 'UpdateShippingMethod'] | hasPermission)"
                 (remove)="selectedCalculator = null"
                 formControlName="calculator"
             ></vdr-configurable-input>

+ 2 - 2
packages/admin-ui/src/lib/settings/src/components/shipping-method-list/shipping-method-list.component.html

@@ -1,7 +1,7 @@
 <vdr-action-bar>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="shipping-method-list"></vdr-action-bar-items>
-        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="'CreateSettings'">
+        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="['CreateSettings', 'CreateShippingMethod']">
             <clr-icon shape="plus"></clr-icon>
             {{ 'settings.create-new-shipping-method' | translate }}
         </a>
@@ -41,7 +41,7 @@
                         type="button"
                         class="delete-button"
                         (click)="deleteShippingMethod(shippingMethod.id)"
-                        [disabled]="!('DeleteSettings' | hasPermission)"
+                        [disabled]="!(['DeleteSettings', 'DeleteShippingMethod'] | hasPermission)"
                         vdrDropdownItem
                     >
                         <clr-icon shape="trash" class="is-danger"></clr-icon>

+ 3 - 3
packages/admin-ui/src/lib/settings/src/components/tax-category-detail/tax-category-detail.component.html

@@ -18,7 +18,7 @@
                 class="btn btn-primary"
                 (click)="save()"
                 [disabled]="!saveButtonEnabled()"
-                *vdrIfPermissions="'UpdateSettings'"
+                *vdrIfPermissions="['UpdateSettings', 'UpdateTaxCategory']"
             >
                 {{ 'common.update' | translate }}
             </button>
@@ -32,7 +32,7 @@
             id="name"
             type="text"
             formControlName="name"
-            [readonly]="!('UpdateSettings' | hasPermission)"
+            [readonly]="!(['UpdateSettings', 'UpdateTaxCategory'] | hasPermission)"
         />
     </vdr-form-field>
     <vdr-form-field [label]="'common.default-tax-category' | translate" for="isDefault">
@@ -41,7 +41,7 @@
                 type="checkbox"
                 clrToggle
                 id="isDefault"
-                [vdrDisabled]="!('UpdateSettings' | hasPermission)"
+                [vdrDisabled]="!(['UpdateSettings', 'UpdateTaxCategory'] | hasPermission)"
                 formControlName="isDefault"
             />
         </clr-toggle-wrapper>

+ 2 - 2
packages/admin-ui/src/lib/settings/src/components/tax-category-list/tax-category-list.component.html

@@ -1,7 +1,7 @@
 <vdr-action-bar>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="tax-category-list"></vdr-action-bar-items>
-        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="'CreateSettings'">
+        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="['CreateSettings', 'CreateTaxCategory']">
             <clr-icon shape="plus"></clr-icon>
             {{ 'settings.create-new-tax-category' | translate }}
         </a>
@@ -36,7 +36,7 @@
                         type="button"
                         class="delete-button"
                         (click)="deleteTaxCategory(taxCategory)"
-                        [disabled]="!('DeleteSettings' | hasPermission)"
+                        [disabled]="!(['DeleteSettings', 'DeleteTaxCategory'] | hasPermission)"
                         vdrDropdownItem
                     >
                         <clr-icon shape="trash" class="is-danger"></clr-icon>

+ 6 - 6
packages/admin-ui/src/lib/settings/src/components/tax-rate-detail/tax-rate-detail.component.html

@@ -18,7 +18,7 @@
                 class="btn btn-primary"
                 (click)="save()"
                 [disabled]="!saveButtonEnabled()"
-                *vdrIfPermissions="'UpdateSettings'"
+                *vdrIfPermissions="['UpdateSettings', 'UpdateTaxRate']"
             >
                 {{ 'common.update' | translate }}
             </button>
@@ -32,7 +32,7 @@
             id="name"
             type="text"
             formControlName="name"
-            [readonly]="!('UpdateSettings' | hasPermission)"
+            [readonly]="!(['UpdateSettings', 'UpdateTaxRate'] | hasPermission)"
         />
     </vdr-form-field>
     <vdr-form-field [label]="'common.enabled' | translate" for="enabled">
@@ -42,7 +42,7 @@
                 clrToggle
                 id="enabled"
                 formControlName="enabled"
-                [vdrDisabled]="!('UpdateSettings' | hasPermission)"
+                [vdrDisabled]="!(['UpdateSettings', 'UpdateTaxRate'] | hasPermission)"
             />
         </clr-toggle-wrapper>
     </vdr-form-field>
@@ -53,7 +53,7 @@
                 type="number"
                 step="0.1"
                 formControlName="value"
-                [readonly]="!('UpdateSettings' | hasPermission)"
+                [readonly]="!(['UpdateSettings', 'UpdateTaxRate'] | hasPermission)"
             />
         </vdr-affixed-input>
     </vdr-form-field>
@@ -62,7 +62,7 @@
             clrSelect
             name="taxCategoryId"
             formControlName="taxCategoryId"
-            [vdrDisabled]="!('UpdateSettings' | hasPermission)"
+            [vdrDisabled]="!(['UpdateSettings', 'UpdateTaxRate'] | hasPermission)"
         >
             <option *ngFor="let taxCategory of taxCategories$ | async" [value]="taxCategory.id">
                 {{ taxCategory.name }}
@@ -74,7 +74,7 @@
             clrSelect
             name="zoneId"
             formControlName="zoneId"
-            [vdrDisabled]="!('UpdateSettings' | hasPermission)"
+            [vdrDisabled]="!(['UpdateSettings', 'UpdateTaxRate'] | hasPermission)"
         >
             <option *ngFor="let zone of zones$ | async" [value]="zone.id">{{ zone.name }}</option>
         </select>

+ 2 - 2
packages/admin-ui/src/lib/settings/src/components/tax-rate-list/tax-rate-list.component.html

@@ -1,7 +1,7 @@
 <vdr-action-bar>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="tax-rate-list"></vdr-action-bar-items>
-        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="'CreateSettings'">
+        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="['CreateSettings', 'CreateTaxRate']">
             <clr-icon shape="plus"></clr-icon>
             {{ 'settings.create-new-tax-rate' | translate }}
         </a>
@@ -45,7 +45,7 @@
                         type="button"
                         class="delete-button"
                         (click)="deleteTaxRate(taxRate)"
-                        [disabled]="!('DeleteSettings' | hasPermission)"
+                        [disabled]="!(['DeleteSettings', 'DeleteTaxRate'] | hasPermission)"
                         vdrDropdownItem
                     >
                         <clr-icon shape="trash" class="is-danger"></clr-icon>

+ 1 - 1
packages/admin-ui/src/lib/settings/src/components/zone-detail-dialog/zone-detail-dialog.component.html

@@ -4,7 +4,7 @@
 </ng-template>
 
 <vdr-form-field [label]="'common.name' | translate" for="name">
-    <input id="name" type="text" [(ngModel)]="zone.name" [readonly]="!('UpdateSettings' | hasPermission)" />
+    <input id="name" type="text" [(ngModel)]="zone.name" [readonly]="!(['UpdateSettings', 'UpdateZone'] | hasPermission)" />
 </vdr-form-field>
 
 <ng-template vdrDialogButtons>

+ 4 - 4
packages/admin-ui/src/lib/settings/src/components/zone-list/zone-list.component.html

@@ -2,7 +2,7 @@
     <vdr-ab-left> </vdr-ab-left>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="zone-list"></vdr-action-bar-items>
-        <button class="btn btn-primary" *vdrIfPermissions="'CreateSettings'" (click)="create()">
+        <button class="btn btn-primary" *vdrIfPermissions="['CreateSettings', 'CreateZone']" (click)="create()">
             <clr-icon shape="plus"></clr-icon>
             {{ 'settings.create-new-zone' | translate }}
         </button>
@@ -41,7 +41,7 @@
                                 class="button"
                                 vdrDropdownItem
                                 (click)="delete(zone.id)"
-                                [disabled]="!('DeleteSettings' | hasPermission)"
+                                [disabled]="!(['DeleteSettings', 'DeleteZone'] | hasPermission)"
                             >
                                 <clr-icon shape="trash" class="is-danger"></clr-icon>
                                 {{ 'common.delete' | translate }}
@@ -86,7 +86,7 @@
                                     class="delete-button"
                                     (click)="removeFromZone(activeZone, selectedMemberIds)"
                                     vdrDropdownItem
-                                    [disabled]="!('DeleteSettings' | hasPermission)"
+                                    [disabled]="!(['UpdateSettings', 'UpdateZone'] | hasPermission)"
                                 >
                                     <clr-icon shape="trash" class="is-danger"></clr-icon>
                                     {{ 'settings.remove-from-zone' | translate }}
@@ -118,7 +118,7 @@
                                 class="delete-button"
                                 (click)="removeFromZone(activeZone, [member.id])"
                                 vdrDropdownItem
-                                [disabled]="!('DeleteSettings' | hasPermission)"
+                                [disabled]="!(['UpdateSettings', 'UpdateZone'] | hasPermission)"
                             >
                                 <clr-icon shape="trash" class="is-danger"></clr-icon>
                                 {{ 'settings.remove-from-zone' | translate }}

+ 1 - 1
packages/admin-ui/src/lib/settings/src/components/zone-member-list/zone-member-list.component.html

@@ -11,7 +11,7 @@
 <vdr-data-table
     [items]="filteredMembers()"
     [allSelected]="areAllSelected()"
-    [isRowSelectedFn]="('UpdateSettings' | hasPermission) && isMemberSelected"
+    [isRowSelectedFn]="(['UpdateSettings', 'UpdateZone'] | hasPermission) && isMemberSelected"
     (rowSelectChange)="toggleSelectMember($event)"
     (allSelectChange)="toggleSelectAll()"
 >

+ 1 - 1
packages/admin-ui/src/lib/system/src/components/job-list/job-list.component.html

@@ -119,7 +119,7 @@
                         type="button"
                         class="delete-button"
                         (click)="cancelJob(job.id)"
-                        [disabled]="!('DeleteSettings' | hasPermission)"
+                        [disabled]="!(['DeleteSettings', 'DeleteSystem'] | hasPermission)"
                         vdrDropdownItem
                     >
                         <clr-icon shape="ban" class="is-danger"></clr-icon>