瀏覽代碼

feat(admin-ui): Implement custom fields on newly-supported entities

Relates to #1185
Michael Bromley 4 年之前
父節點
當前提交
2da2ec9b5a
共有 21 個文件被更改,包括 589 次插入99 次删除
  1. 192 8
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  2. 21 24
      packages/admin-ui/src/lib/core/src/data/definitions/customer-definitions.ts
  3. 25 4
      packages/admin-ui/src/lib/core/src/data/definitions/settings-definitions.ts
  4. 2 2
      packages/admin-ui/src/lib/core/src/data/providers/settings-data.service.ts
  5. 21 5
      packages/admin-ui/src/lib/customer/src/components/customer-group-detail-dialog/customer-group-detail-dialog.component.html
  6. 40 6
      packages/admin-ui/src/lib/customer/src/components/customer-group-detail-dialog/customer-group-detail-dialog.component.ts
  7. 23 19
      packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.ts
  8. 12 1
      packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.html
  9. 28 2
      packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.ts
  10. 11 0
      packages/admin-ui/src/lib/settings/src/components/country-detail/country-detail.component.html
  11. 32 2
      packages/admin-ui/src/lib/settings/src/components/country-detail/country-detail.component.ts
  12. 11 0
      packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.html
  13. 26 1
      packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.ts
  14. 11 0
      packages/admin-ui/src/lib/settings/src/components/tax-category-detail/tax-category-detail.component.html
  15. 30 3
      packages/admin-ui/src/lib/settings/src/components/tax-category-detail/tax-category-detail.component.ts
  16. 11 0
      packages/admin-ui/src/lib/settings/src/components/tax-rate-detail/tax-rate-detail.component.html
  17. 26 1
      packages/admin-ui/src/lib/settings/src/components/tax-rate-detail/tax-rate-detail.component.ts
  18. 18 4
      packages/admin-ui/src/lib/settings/src/components/zone-detail-dialog/zone-detail-dialog.component.html
  19. 35 6
      packages/admin-ui/src/lib/settings/src/components/zone-detail-dialog/zone-detail-dialog.component.ts
  20. 4 4
      packages/admin-ui/src/lib/settings/src/components/zone-list/zone-list.component.ts
  21. 10 7
      packages/dev-server/dev-config.ts

+ 192 - 8
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -5687,6 +5687,11 @@ export type CustomerFragment = (
   )>> }
 );
 
+export type CustomerGroupFragment = (
+  { __typename?: 'CustomerGroup' }
+  & Pick<CustomerGroup, 'id' | 'createdAt' | 'updatedAt' | 'name'>
+);
+
 export type GetCustomerListQueryVariables = Exact<{
   options?: Maybe<CustomerListOptions>;
 }>;
@@ -5792,7 +5797,7 @@ export type CreateCustomerGroupMutationVariables = Exact<{
 
 export type CreateCustomerGroupMutation = { createCustomerGroup: (
     { __typename?: 'CustomerGroup' }
-    & Pick<CustomerGroup, 'id' | 'createdAt' | 'updatedAt' | 'name'>
+    & CustomerGroupFragment
   ) };
 
 export type UpdateCustomerGroupMutationVariables = Exact<{
@@ -5802,7 +5807,7 @@ export type UpdateCustomerGroupMutationVariables = Exact<{
 
 export type UpdateCustomerGroupMutation = { updateCustomerGroup: (
     { __typename?: 'CustomerGroup' }
-    & Pick<CustomerGroup, 'id' | 'createdAt' | 'updatedAt' | 'name'>
+    & CustomerGroupFragment
   ) };
 
 export type DeleteCustomerGroupMutationVariables = Exact<{
@@ -5825,7 +5830,7 @@ export type GetCustomerGroupsQuery = { customerGroups: (
     & Pick<CustomerGroupList, 'totalItems'>
     & { items: Array<(
       { __typename?: 'CustomerGroup' }
-      & Pick<CustomerGroup, 'id' | 'createdAt' | 'updatedAt' | 'name'>
+      & CustomerGroupFragment
     )> }
   ) };
 
@@ -5837,7 +5842,6 @@ export type GetCustomerGroupWithCustomersQueryVariables = Exact<{
 
 export type GetCustomerGroupWithCustomersQuery = { customerGroup?: Maybe<(
     { __typename?: 'CustomerGroup' }
-    & Pick<CustomerGroup, 'id' | 'createdAt' | 'updatedAt' | 'name'>
     & { customers: (
       { __typename?: 'CustomerList' }
       & Pick<CustomerList, 'totalItems'>
@@ -5846,6 +5850,7 @@ export type GetCustomerGroupWithCustomersQuery = { customerGroup?: Maybe<(
         & Pick<Customer, 'id' | 'createdAt' | 'updatedAt' | 'emailAddress' | 'firstName' | 'lastName'>
       )> }
     ) }
+    & CustomerGroupFragment
   )> };
 
 export type AddCustomersToGroupMutationVariables = Exact<{
@@ -5856,7 +5861,7 @@ export type AddCustomersToGroupMutationVariables = Exact<{
 
 export type AddCustomersToGroupMutation = { addCustomersToGroup: (
     { __typename?: 'CustomerGroup' }
-    & Pick<CustomerGroup, 'id' | 'createdAt' | 'updatedAt' | 'name'>
+    & CustomerGroupFragment
   ) };
 
 export type RemoveCustomersFromGroupMutationVariables = Exact<{
@@ -5867,7 +5872,7 @@ export type RemoveCustomersFromGroupMutationVariables = Exact<{
 
 export type RemoveCustomersFromGroupMutation = { removeCustomersFromGroup: (
     { __typename?: 'CustomerGroup' }
-    & Pick<CustomerGroup, 'id' | 'createdAt' | 'updatedAt' | 'name'>
+    & CustomerGroupFragment
   ) };
 
 export type GetCustomerHistoryQueryVariables = Exact<{
@@ -7329,7 +7334,7 @@ export type DeleteCountryMutation = { deleteCountry: (
 
 export type ZoneFragment = (
   { __typename?: 'Zone' }
-  & Pick<Zone, 'id' | 'name'>
+  & Pick<Zone, 'id' | 'createdAt' | 'updatedAt' | 'name'>
   & { members: Array<(
     { __typename?: 'Country' }
     & CountryFragment
@@ -7341,11 +7346,11 @@ export type GetZonesQueryVariables = Exact<{ [key: string]: never; }>;
 
 export type GetZonesQuery = { zones: Array<(
     { __typename?: 'Zone' }
-    & Pick<Zone, 'id' | 'createdAt' | 'updatedAt' | 'name'>
     & { members: Array<(
       { __typename?: 'Country' }
       & Pick<Country, 'createdAt' | 'updatedAt' | 'id' | 'name' | 'code' | 'enabled'>
     )> }
+    & ZoneFragment
   )> };
 
 export type GetZoneQueryVariables = Exact<{
@@ -8051,6 +8056,30 @@ export type GetServerConfigQuery = { globalSettings: (
         ) | (
           { __typename?: 'TextCustomFieldConfig' }
           & CustomFields_TextCustomFieldConfig_Fragment
+        )>, Country: Array<(
+          { __typename?: 'StringCustomFieldConfig' }
+          & CustomFields_StringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'LocaleStringCustomFieldConfig' }
+          & CustomFields_LocaleStringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'IntCustomFieldConfig' }
+          & CustomFields_IntCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'FloatCustomFieldConfig' }
+          & CustomFields_FloatCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'BooleanCustomFieldConfig' }
+          & CustomFields_BooleanCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'DateTimeCustomFieldConfig' }
+          & CustomFields_DateTimeCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'RelationCustomFieldConfig' }
+          & CustomFields_RelationCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'TextCustomFieldConfig' }
+          & CustomFields_TextCustomFieldConfig_Fragment
         )>, Customer: Array<(
           { __typename?: 'StringCustomFieldConfig' }
           & CustomFields_StringCustomFieldConfig_Fragment
@@ -8075,6 +8104,30 @@ export type GetServerConfigQuery = { globalSettings: (
         ) | (
           { __typename?: 'TextCustomFieldConfig' }
           & CustomFields_TextCustomFieldConfig_Fragment
+        )>, CustomerGroup: Array<(
+          { __typename?: 'StringCustomFieldConfig' }
+          & CustomFields_StringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'LocaleStringCustomFieldConfig' }
+          & CustomFields_LocaleStringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'IntCustomFieldConfig' }
+          & CustomFields_IntCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'FloatCustomFieldConfig' }
+          & CustomFields_FloatCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'BooleanCustomFieldConfig' }
+          & CustomFields_BooleanCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'DateTimeCustomFieldConfig' }
+          & CustomFields_DateTimeCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'RelationCustomFieldConfig' }
+          & CustomFields_RelationCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'TextCustomFieldConfig' }
+          & CustomFields_TextCustomFieldConfig_Fragment
         )>, Facet: Array<(
           { __typename?: 'StringCustomFieldConfig' }
           & CustomFields_StringCustomFieldConfig_Fragment
@@ -8219,6 +8272,30 @@ export type GetServerConfigQuery = { globalSettings: (
         ) | (
           { __typename?: 'TextCustomFieldConfig' }
           & CustomFields_TextCustomFieldConfig_Fragment
+        )>, PaymentMethod: Array<(
+          { __typename?: 'StringCustomFieldConfig' }
+          & CustomFields_StringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'LocaleStringCustomFieldConfig' }
+          & CustomFields_LocaleStringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'IntCustomFieldConfig' }
+          & CustomFields_IntCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'FloatCustomFieldConfig' }
+          & CustomFields_FloatCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'BooleanCustomFieldConfig' }
+          & CustomFields_BooleanCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'DateTimeCustomFieldConfig' }
+          & CustomFields_DateTimeCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'RelationCustomFieldConfig' }
+          & CustomFields_RelationCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'TextCustomFieldConfig' }
+          & CustomFields_TextCustomFieldConfig_Fragment
         )>, Product: Array<(
           { __typename?: 'StringCustomFieldConfig' }
           & CustomFields_StringCustomFieldConfig_Fragment
@@ -8315,6 +8392,30 @@ export type GetServerConfigQuery = { globalSettings: (
         ) | (
           { __typename?: 'TextCustomFieldConfig' }
           & CustomFields_TextCustomFieldConfig_Fragment
+        )>, Promotion: Array<(
+          { __typename?: 'StringCustomFieldConfig' }
+          & CustomFields_StringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'LocaleStringCustomFieldConfig' }
+          & CustomFields_LocaleStringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'IntCustomFieldConfig' }
+          & CustomFields_IntCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'FloatCustomFieldConfig' }
+          & CustomFields_FloatCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'BooleanCustomFieldConfig' }
+          & CustomFields_BooleanCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'DateTimeCustomFieldConfig' }
+          & CustomFields_DateTimeCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'RelationCustomFieldConfig' }
+          & CustomFields_RelationCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'TextCustomFieldConfig' }
+          & CustomFields_TextCustomFieldConfig_Fragment
         )>, ShippingMethod: Array<(
           { __typename?: 'StringCustomFieldConfig' }
           & CustomFields_StringCustomFieldConfig_Fragment
@@ -8339,6 +8440,54 @@ export type GetServerConfigQuery = { globalSettings: (
         ) | (
           { __typename?: 'TextCustomFieldConfig' }
           & CustomFields_TextCustomFieldConfig_Fragment
+        )>, TaxCategory: Array<(
+          { __typename?: 'StringCustomFieldConfig' }
+          & CustomFields_StringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'LocaleStringCustomFieldConfig' }
+          & CustomFields_LocaleStringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'IntCustomFieldConfig' }
+          & CustomFields_IntCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'FloatCustomFieldConfig' }
+          & CustomFields_FloatCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'BooleanCustomFieldConfig' }
+          & CustomFields_BooleanCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'DateTimeCustomFieldConfig' }
+          & CustomFields_DateTimeCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'RelationCustomFieldConfig' }
+          & CustomFields_RelationCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'TextCustomFieldConfig' }
+          & CustomFields_TextCustomFieldConfig_Fragment
+        )>, TaxRate: Array<(
+          { __typename?: 'StringCustomFieldConfig' }
+          & CustomFields_StringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'LocaleStringCustomFieldConfig' }
+          & CustomFields_LocaleStringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'IntCustomFieldConfig' }
+          & CustomFields_IntCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'FloatCustomFieldConfig' }
+          & CustomFields_FloatCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'BooleanCustomFieldConfig' }
+          & CustomFields_BooleanCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'DateTimeCustomFieldConfig' }
+          & CustomFields_DateTimeCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'RelationCustomFieldConfig' }
+          & CustomFields_RelationCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'TextCustomFieldConfig' }
+          & CustomFields_TextCustomFieldConfig_Fragment
         )>, User: Array<(
           { __typename?: 'StringCustomFieldConfig' }
           & CustomFields_StringCustomFieldConfig_Fragment
@@ -8363,6 +8512,30 @@ export type GetServerConfigQuery = { globalSettings: (
         ) | (
           { __typename?: 'TextCustomFieldConfig' }
           & CustomFields_TextCustomFieldConfig_Fragment
+        )>, Zone: Array<(
+          { __typename?: 'StringCustomFieldConfig' }
+          & CustomFields_StringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'LocaleStringCustomFieldConfig' }
+          & CustomFields_LocaleStringCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'IntCustomFieldConfig' }
+          & CustomFields_IntCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'FloatCustomFieldConfig' }
+          & CustomFields_FloatCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'BooleanCustomFieldConfig' }
+          & CustomFields_BooleanCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'DateTimeCustomFieldConfig' }
+          & CustomFields_DateTimeCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'RelationCustomFieldConfig' }
+          & CustomFields_RelationCustomFieldConfig_Fragment
+        ) | (
+          { __typename?: 'TextCustomFieldConfig' }
+          & CustomFields_TextCustomFieldConfig_Fragment
         )> }
       ) }
     ) }
@@ -9008,6 +9181,10 @@ export namespace Customer {
   export type Addresses = NonNullable<(NonNullable<CustomerFragment['addresses']>)[number]>;
 }
 
+export namespace CustomerGroup {
+  export type Fragment = CustomerGroupFragment;
+}
+
 export namespace GetCustomerList {
   export type Variables = GetCustomerListQueryVariables;
   export type Query = GetCustomerListQuery;
@@ -10074,19 +10251,26 @@ export namespace GetServerConfig {
   export type Asset = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['Asset']>)[number]>;
   export type Channel = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['Channel']>)[number]>;
   export type Collection = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['Collection']>)[number]>;
+  export type Country = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['Country']>)[number]>;
   export type Customer = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['Customer']>)[number]>;
+  export type CustomerGroup = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['CustomerGroup']>)[number]>;
   export type Facet = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['Facet']>)[number]>;
   export type FacetValue = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['FacetValue']>)[number]>;
   export type Fulfillment = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['Fulfillment']>)[number]>;
   export type _GlobalSettings = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['GlobalSettings']>)[number]>;
   export type Order = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['Order']>)[number]>;
   export type OrderLine = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['OrderLine']>)[number]>;
+  export type PaymentMethod = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['PaymentMethod']>)[number]>;
   export type Product = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['Product']>)[number]>;
   export type ProductOption = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['ProductOption']>)[number]>;
   export type ProductOptionGroup = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['ProductOptionGroup']>)[number]>;
   export type ProductVariant = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['ProductVariant']>)[number]>;
+  export type Promotion = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['Promotion']>)[number]>;
   export type ShippingMethod = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['ShippingMethod']>)[number]>;
+  export type TaxCategory = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['TaxCategory']>)[number]>;
+  export type TaxRate = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['TaxRate']>)[number]>;
   export type User = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['User']>)[number]>;
+  export type Zone = NonNullable<(NonNullable<(NonNullable<(NonNullable<(NonNullable<GetServerConfigQuery['globalSettings']>)['serverConfig']>)['customFieldConfig']>)['Zone']>)[number]>;
 }
 
 export namespace JobInfo {

+ 21 - 24
packages/admin-ui/src/lib/core/src/data/definitions/customer-definitions.ts

@@ -48,6 +48,15 @@ export const CUSTOMER_FRAGMENT = gql`
     ${ADDRESS_FRAGMENT}
 `;
 
+export const CUSTOMER_GROUP_FRAGMENT = gql`
+    fragment CustomerGroup on CustomerGroup {
+        id
+        createdAt
+        updatedAt
+        name
+    }
+`;
+
 export const GET_CUSTOMER_LIST = gql`
     query GetCustomerList($options: CustomerListOptions) {
         customers(options: $options) {
@@ -145,23 +154,19 @@ export const UPDATE_CUSTOMER_ADDRESS = gql`
 export const CREATE_CUSTOMER_GROUP = gql`
     mutation CreateCustomerGroup($input: CreateCustomerGroupInput!) {
         createCustomerGroup(input: $input) {
-            id
-            createdAt
-            updatedAt
-            name
+            ...CustomerGroup
         }
     }
+    ${CUSTOMER_GROUP_FRAGMENT}
 `;
 
 export const UPDATE_CUSTOMER_GROUP = gql`
     mutation UpdateCustomerGroup($input: UpdateCustomerGroupInput!) {
         updateCustomerGroup(input: $input) {
-            id
-            createdAt
-            updatedAt
-            name
+            ...CustomerGroup
         }
     }
+    ${CUSTOMER_GROUP_FRAGMENT}
 `;
 
 export const DELETE_CUSTOMER_GROUP = gql`
@@ -177,23 +182,18 @@ export const GET_CUSTOMER_GROUPS = gql`
     query GetCustomerGroups($options: CustomerGroupListOptions) {
         customerGroups(options: $options) {
             items {
-                id
-                createdAt
-                updatedAt
-                name
+                ...CustomerGroup
             }
             totalItems
         }
     }
+    ${CUSTOMER_GROUP_FRAGMENT}
 `;
 
 export const GET_CUSTOMER_GROUP_WITH_CUSTOMERS = gql`
     query GetCustomerGroupWithCustomers($id: ID!, $options: CustomerListOptions) {
         customerGroup(id: $id) {
-            id
-            createdAt
-            updatedAt
-            name
+            ...CustomerGroup
             customers(options: $options) {
                 items {
                     id
@@ -207,28 +207,25 @@ export const GET_CUSTOMER_GROUP_WITH_CUSTOMERS = gql`
             }
         }
     }
+    ${CUSTOMER_GROUP_FRAGMENT}
 `;
 
 export const ADD_CUSTOMERS_TO_GROUP = gql`
     mutation AddCustomersToGroup($groupId: ID!, $customerIds: [ID!]!) {
         addCustomersToGroup(customerGroupId: $groupId, customerIds: $customerIds) {
-            id
-            createdAt
-            updatedAt
-            name
+            ...CustomerGroup
         }
     }
+    ${CUSTOMER_GROUP_FRAGMENT}
 `;
 
 export const REMOVE_CUSTOMERS_FROM_GROUP = gql`
     mutation RemoveCustomersFromGroup($groupId: ID!, $customerIds: [ID!]!) {
         removeCustomersFromGroup(customerGroupId: $groupId, customerIds: $customerIds) {
-            id
-            createdAt
-            updatedAt
-            name
+            ...CustomerGroup
         }
     }
+    ${CUSTOMER_GROUP_FRAGMENT}
 `;
 
 export const GET_CUSTOMER_HISTORY = gql`

+ 25 - 4
packages/admin-ui/src/lib/core/src/data/definitions/settings-definitions.ts

@@ -88,6 +88,8 @@ export const DELETE_COUNTRY = gql`
 export const ZONE_FRAGMENT = gql`
     fragment Zone on Zone {
         id
+        createdAt
+        updatedAt
         name
         members {
             ...Country
@@ -99,10 +101,7 @@ export const ZONE_FRAGMENT = gql`
 export const GET_ZONES = gql`
     query GetZones {
         zones {
-            id
-            createdAt
-            updatedAt
-            name
+            ...Zone
             members {
                 createdAt
                 updatedAt
@@ -113,6 +112,7 @@ export const GET_ZONES = gql`
             }
         }
     }
+    ${ZONE_FRAGMENT}
 `;
 
 export const GET_ZONE = gql`
@@ -647,9 +647,15 @@ export const GET_SERVER_CONFIG = gql`
                     Collection {
                         ...CustomFields
                     }
+                    Country {
+                        ...CustomFields
+                    }
                     Customer {
                         ...CustomFields
                     }
+                    CustomerGroup {
+                        ...CustomFields
+                    }
                     Facet {
                         ...CustomFields
                     }
@@ -668,6 +674,9 @@ export const GET_SERVER_CONFIG = gql`
                     OrderLine {
                         ...CustomFields
                     }
+                    PaymentMethod {
+                        ...CustomFields
+                    }
                     Product {
                         ...CustomFields
                     }
@@ -680,12 +689,24 @@ export const GET_SERVER_CONFIG = gql`
                     ProductVariant {
                         ...CustomFields
                     }
+                    Promotion {
+                        ...CustomFields
+                    }
                     ShippingMethod {
                         ...CustomFields
                     }
+                    TaxCategory {
+                        ...CustomFields
+                    }
+                    TaxRate {
+                        ...CustomFields
+                    }
                     User {
                         ...CustomFields
                     }
+                    Zone {
+                        ...CustomFields
+                    }
                 }
             }
         }

+ 2 - 2
packages/admin-ui/src/lib/core/src/data/providers/settings-data.service.ts

@@ -131,13 +131,13 @@ export class SettingsDataService {
 
     createCountry(input: CreateCountryInput) {
         return this.baseDataService.mutate<CreateCountry.Mutation, CreateCountry.Variables>(CREATE_COUNTRY, {
-            input: pick(input, ['code', 'enabled', 'translations']),
+            input: pick(input, ['code', 'enabled', 'translations', 'customFields']),
         });
     }
 
     updateCountry(input: UpdateCountryInput) {
         return this.baseDataService.mutate<UpdateCountry.Mutation, UpdateCountry.Variables>(UPDATE_COUNTRY, {
-            input: pick(input, ['id', 'code', 'enabled', 'translations']),
+            input: pick(input, ['id', 'code', 'enabled', 'translations', 'customFields']),
         });
     }
 

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

@@ -2,11 +2,27 @@
     <span *ngIf="group.id">{{ 'customer.update-customer-group' | translate }}</span>
     <span *ngIf="!group.id">{{ 'customer.create-customer-group' | translate }}</span>
 </ng-template>
-
-<vdr-form-field [label]="'common.name' | translate" for="name">
-    <input id="name" type="text" [(ngModel)]="group.name" [readonly]="!(['CreateCustomerGroup', 'UpdateCustomerGroup'] | hasPermission)" />
-</vdr-form-field>
-
+<form [formGroup]="form">
+    <vdr-form-field [label]="'common.name' | translate" for="name">
+        <input
+            id="name"
+            type="text"
+            formControlName="name"
+            [readonly]="!(['CreateCustomerGroup', 'UpdateCustomerGroup'] | hasPermission)"
+        />
+    </vdr-form-field>
+    <section formGroupName="customFields" *ngIf="customFields.length">
+        <label>{{ 'common.custom-fields' | translate }}</label>
+        <ng-container *ngFor="let customField of customFields">
+            <vdr-custom-field-control
+                *ngIf="customField.name"
+                entityName="CustomerGroup"
+                [customFieldsFormGroup]="form.get(['customFields'])"
+                [customField]="customField"
+            ></vdr-custom-field-control>
+        </ng-container>
+    </section>
+</form>
 <ng-template vdrDialogButtons>
     <button type="button" class="btn" (click)="cancel()">{{ 'common.cancel' | translate }}</button>
     <button type="submit" (click)="save()" [disabled]="!group.name" class="btn btn-primary">

+ 40 - 6
packages/admin-ui/src/lib/customer/src/components/customer-group-detail-dialog/customer-group-detail-dialog.component.ts

@@ -1,5 +1,12 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { Dialog } from '@vendure/admin-ui/core';
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
+import {
+    CreateCustomerGroupInput,
+    CustomFieldConfig,
+    Dialog,
+    ServerConfigService,
+    UpdateCustomerGroupInput,
+} from '@vendure/admin-ui/core';
 
 @Component({
     selector: 'vdr-customer-group-detail-dialog',
@@ -7,15 +14,42 @@ import { Dialog } from '@vendure/admin-ui/core';
     styleUrls: ['./customer-group-detail-dialog.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class CustomerGroupDetailDialogComponent implements Dialog<string> {
-    group: { id?: string; name: string };
-    resolveWith: (result?: string) => void;
+export class CustomerGroupDetailDialogComponent implements Dialog<CreateCustomerGroupInput>, OnInit {
+    group: { id?: string; name: string; customFields?: { [name: string]: any } };
+    resolveWith: (result?: CreateCustomerGroupInput) => void;
+    customFields: CustomFieldConfig[];
+    form: FormGroup;
+
+    constructor(private serverConfigService: ServerConfigService, private formBuilder: FormBuilder) {
+        this.customFields = this.serverConfigService.getCustomFieldsFor('CustomerGroup');
+    }
+
+    ngOnInit() {
+        this.form = this.formBuilder.group({
+            name: [this.group.name, Validators.required],
+            customFields: this.formBuilder.group(
+                this.customFields.reduce((hash, field) => ({ ...hash, [field.name]: '' }), {}),
+            ),
+        });
+        if (this.customFields.length) {
+            const customFieldsGroup = this.form.get('customFields') as FormGroup;
+
+            for (const fieldDef of this.customFields) {
+                const key = fieldDef.name;
+                const value = this.group.customFields?.[key];
+                const control = customFieldsGroup.get(key);
+                if (control) {
+                    control.patchValue(value);
+                }
+            }
+        }
+    }
 
     cancel() {
         this.resolveWith();
     }
 
     save() {
-        this.resolveWith(this.group.name);
+        this.resolveWith(this.form.value);
     }
 }

+ 23 - 19
packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.ts

@@ -48,17 +48,17 @@ export class CustomerGroupListComponent implements OnInit {
     ngOnInit(): void {
         this.groups$ = this.dataService.customer
             .getCustomerGroupList()
-            .mapStream((data) => data.customerGroups.items);
+            .mapStream(data => data.customerGroups.items);
         const activeGroupId$ = this.route.paramMap.pipe(
-            map((pm) => pm.get('contents')),
+            map(pm => pm.get('contents')),
             distinctUntilChanged(),
             tap(() => (this.selectedCustomerIds = [])),
         );
-        this.listIsEmpty$ = this.groups$.pipe(map((groups) => groups.length === 0));
+        this.listIsEmpty$ = this.groups$.pipe(map(groups => groups.length === 0));
         this.activeGroup$ = combineLatest(this.groups$, activeGroupId$).pipe(
             map(([groups, activeGroupId]) => {
                 if (activeGroupId) {
-                    return groups.find((g) => g.id === activeGroupId);
+                    return groups.find(g => g.id === activeGroupId);
                 }
             }),
         );
@@ -79,23 +79,25 @@ export class CustomerGroupListComponent implements OnInit {
                                 },
                             },
                         })
-                        .mapStream((res) => res.customerGroup?.customers);
+                        .mapStream(res => res.customerGroup?.customers);
                 } else {
                     return of(undefined);
                 }
             }),
         );
 
-        this.members$ = membersResult$.pipe(map((res) => res?.items ?? []));
-        this.membersTotal$ = membersResult$.pipe(map((res) => res?.totalItems ?? 0));
+        this.members$ = membersResult$.pipe(map(res => res?.items ?? []));
+        this.membersTotal$ = membersResult$.pipe(map(res => res?.totalItems ?? 0));
     }
 
     create() {
         this.modalService
             .fromComponent(CustomerGroupDetailDialogComponent, { locals: { group: { name: '' } } })
             .pipe(
-                switchMap((name) =>
-                    name ? this.dataService.customer.createCustomerGroup({ name, customerIds: [] }) : EMPTY,
+                switchMap(result =>
+                    result
+                        ? this.dataService.customer.createCustomerGroup({ ...result, customerIds: [] })
+                        : EMPTY,
                 ),
                 // refresh list
                 switchMap(() => this.dataService.customer.getCustomerGroupList().single$),
@@ -106,7 +108,7 @@ export class CustomerGroupListComponent implements OnInit {
                         entity: 'CustomerGroup',
                     });
                 },
-                (err) => {
+                err => {
                     this.notificationService.error(_('common.notify-create-error'), {
                         entity: 'CustomerGroup',
                     });
@@ -124,11 +126,11 @@ export class CustomerGroupListComponent implements OnInit {
                 ],
             })
             .pipe(
-                switchMap((response) =>
+                switchMap(response =>
                     response ? this.dataService.customer.deleteCustomerGroup(groupId) : EMPTY,
                 ),
 
-                switchMap((result) => {
+                switchMap(result => {
                     if (result.deleteCustomerGroup.result === DeletionResult.DELETED) {
                         // refresh list
                         return this.dataService.customer
@@ -140,7 +142,7 @@ export class CustomerGroupListComponent implements OnInit {
                 }),
             )
             .subscribe(
-                (result) => {
+                result => {
                     if (typeof result.errorMessage === 'string') {
                         this.notificationService.error(result.errorMessage);
                     } else {
@@ -149,7 +151,7 @@ export class CustomerGroupListComponent implements OnInit {
                         });
                     }
                 },
-                (err) => {
+                err => {
                     this.notificationService.error(_('common.notify-delete-error'), {
                         entity: 'CustomerGroup',
                     });
@@ -161,8 +163,10 @@ export class CustomerGroupListComponent implements OnInit {
         this.modalService
             .fromComponent(CustomerGroupDetailDialogComponent, { locals: { group } })
             .pipe(
-                switchMap((name) =>
-                    name ? this.dataService.customer.updateCustomerGroup({ id: group.id, name }) : EMPTY,
+                switchMap(result =>
+                    result
+                        ? this.dataService.customer.updateCustomerGroup({ id: group.id, ...result })
+                        : EMPTY,
                 ),
             )
             .subscribe(
@@ -171,7 +175,7 @@ export class CustomerGroupListComponent implements OnInit {
                         entity: 'CustomerGroup',
                     });
                 },
-                (err) => {
+                err => {
                     this.notificationService.error(_('common.notify-update-error'), {
                         entity: 'CustomerGroup',
                     });
@@ -196,7 +200,7 @@ export class CustomerGroupListComponent implements OnInit {
                 verticalAlign: 'top',
             })
             .pipe(
-                switchMap((customerIds) =>
+                switchMap(customerIds =>
                     customerIds
                         ? this.dataService.customer
                               .addCustomersToGroup(group.id, customerIds)
@@ -205,7 +209,7 @@ export class CustomerGroupListComponent implements OnInit {
                 ),
             )
             .subscribe({
-                next: (result) => {
+                next: result => {
                     this.notificationService.success(_(`customer.add-customers-to-group-success`), {
                         customerCount: result.length,
                         groupName: group.name,

+ 12 - 1
packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.html

@@ -3,7 +3,7 @@
         <div class="flex clr-align-items-center">
             <vdr-entity-info [entity]="entity$ | async"></vdr-entity-info>
             <clr-toggle-wrapper *vdrIfPermissions="'UpdatePromotion'">
-                <input type="checkbox" clrToggle name="enabled" [formControl]="detailForm.get(['enabled'])" />
+                <input type="checkbox" clrToggle name="enabled" [formControl]="detailForm.get(['enabled'])"/>
                 <label>{{ 'common.enabled' | translate }}</label>
             </clr-toggle-wrapper>
         </div>
@@ -65,6 +65,17 @@
             formControlName="perCustomerUsageLimit"
         />
     </vdr-form-field>
+    <section formGroupName="customFields" *ngIf="customFields.length">
+        <label>{{ 'common.custom-fields' | translate }}</label>
+        <ng-container *ngFor="let customField of customFields">
+            <vdr-custom-field-control
+                *ngIf="customFieldIsSet(customField.name)"
+                entityName="Promotion"
+                [customFieldsFormGroup]="detailForm.get('customFields')"
+                [customField]="customField"
+            ></vdr-custom-field-control>
+        </ng-container>
+    </section>
 
     <div class="clr-row">
         <div class="clr-col" formArrayName="conditions">

+ 28 - 2
packages/admin-ui/src/lib/marketing/src/components/promotion-detail/promotion-detail.component.ts

@@ -8,6 +8,7 @@ import {
     ConfigurableOperationDefinition,
     ConfigurableOperationInput,
     CreatePromotionInput,
+    CustomFieldConfig,
     DataService,
     encodeConfigArgValue,
     getConfigArgValue,
@@ -27,10 +28,13 @@ import { mergeMap, take } from 'rxjs/operators';
     styleUrls: ['./promotion-detail.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Fragment>
-    implements OnInit, OnDestroy {
+export class PromotionDetailComponent
+    extends BaseDetailComponent<Promotion.Fragment>
+    implements OnInit, OnDestroy
+{
     promotion$: Observable<Promotion.Fragment>;
     detailForm: FormGroup;
+    customFields: CustomFieldConfig[];
     conditions: ConfigurableOperation[] = [];
     actions: ConfigurableOperation[] = [];
 
@@ -47,6 +51,7 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
         private notificationService: NotificationService,
     ) {
         super(route, router, serverConfigService, dataService);
+        this.customFields = this.getCustomFieldConfig('Promotion');
         this.detailForm = this.formBuilder.group({
             name: ['', Validators.required],
             enabled: true,
@@ -56,6 +61,9 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
             endsAt: null,
             conditions: this.formBuilder.array([]),
             actions: this.formBuilder.array([]),
+            customFields: this.formBuilder.group(
+                this.customFields.reduce((hash, field) => ({ ...hash, [field.name]: '' }), {}),
+            ),
         });
     }
 
@@ -73,6 +81,10 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
         this.destroy();
     }
 
+    customFieldIsSet(name: string): boolean {
+        return !!this.detailForm.get(['customFields', name]);
+    }
+
     getAvailableConditions(): ConfigurableOperationDefinition[] {
         return this.allConditions.filter(o => !this.conditions.find(c => c.code === o.code));
     }
@@ -136,6 +148,7 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
             endsAt: formValue.endsAt,
             conditions: this.mapOperationsToInputs(this.conditions, formValue.conditions),
             actions: this.mapOperationsToInputs(this.actions, formValue.actions),
+            customFields: formValue.customFields,
         };
         this.dataService.promotion.createPromotion(input).subscribe(
             ({ createPromotion }) => {
@@ -180,6 +193,7 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
                         endsAt: formValue.endsAt,
                         conditions: this.mapOperationsToInputs(this.conditions, formValue.conditions),
                         actions: this.mapOperationsToInputs(this.actions, formValue.actions),
+                        customFields: formValue.customFields,
                     };
                     return this.dataService.promotion.updatePromotion(input);
                 }),
@@ -216,6 +230,18 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
             this.addOperation('conditions', o);
         });
         entity.actions.forEach(o => this.addOperation('actions', o));
+        if (this.customFields.length) {
+            const customFieldsGroup = this.detailForm.get('customFields') as FormGroup;
+
+            for (const fieldDef of this.customFields) {
+                const key = fieldDef.name;
+                const value = (entity as any).customFields[key];
+                const control = customFieldsGroup.get(key);
+                if (control) {
+                    control.patchValue(value);
+                }
+            }
+        }
     }
 
     /**

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

@@ -59,4 +59,15 @@
             />
         </clr-toggle-wrapper>
     </vdr-form-field>
+    <section formGroupName="customFields" *ngIf="customFields.length">
+        <label>{{ 'common.custom-fields' | translate }}</label>
+        <ng-container *ngFor="let customField of customFields">
+            <vdr-custom-field-control
+                *ngIf="customFieldIsSet(customField.name)"
+                entityName="Country"
+                [customFieldsFormGroup]="detailForm.get('customFields')"
+                [customField]="customField"
+            ></vdr-custom-field-control>
+        </ng-container>
+    </section>
 </form>

+ 32 - 2
packages/admin-ui/src/lib/settings/src/components/country-detail/country-detail.component.ts

@@ -1,4 +1,4 @@
-import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@@ -7,6 +7,7 @@ import {
     Country,
     CreateCountryInput,
     createUpdatedTranslatable,
+    CustomFieldConfig,
     DataService,
     findTranslation,
     LanguageCode,
@@ -22,12 +23,15 @@ import { mergeMap, take } from 'rxjs/operators';
     selector: 'vdr-country-detail',
     templateUrl: './country-detail.component.html',
     styleUrls: ['./country-detail.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
 })
 export class CountryDetailComponent
     extends BaseDetailComponent<Country.Fragment>
-    implements OnInit, OnDestroy {
+    implements OnInit, OnDestroy
+{
     country$: Observable<Country.Fragment>;
     detailForm: FormGroup;
+    customFields: CustomFieldConfig[];
     readonly updatePermission = [Permission.UpdateSettings, Permission.UpdateCountry];
 
     constructor(
@@ -40,10 +44,14 @@ export class CountryDetailComponent
         private notificationService: NotificationService,
     ) {
         super(route, router, serverConfigService, dataService);
+        this.customFields = this.getCustomFieldConfig('Country');
         this.detailForm = this.formBuilder.group({
             code: ['', Validators.required],
             name: ['', Validators.required],
             enabled: [true],
+            customFields: this.formBuilder.group(
+                this.customFields.reduce((hash, field) => ({ ...hash, [field.name]: '' }), {}),
+            ),
         });
     }
 
@@ -56,6 +64,10 @@ export class CountryDetailComponent
         this.destroy();
     }
 
+    customFieldIsSet(name: string): boolean {
+        return !!this.detailForm.get(['customFields', name]);
+    }
+
     create() {
         if (!this.detailForm.dirty) {
             return;
@@ -68,6 +80,7 @@ export class CountryDetailComponent
                     const input: CreateCountryInput = createUpdatedTranslatable({
                         translatable: country,
                         updatedFields: formValue,
+                        customFieldConfig: this.customFields,
                         languageCode,
                         defaultTranslation: {
                             name: formValue.name,
@@ -103,6 +116,7 @@ export class CountryDetailComponent
                     const input: UpdateCountryInput = createUpdatedTranslatable({
                         translatable: country,
                         updatedFields: formValue,
+                        customFieldConfig: this.customFields,
                         languageCode,
                         defaultTranslation: {
                             name: formValue.name,
@@ -136,5 +150,21 @@ export class CountryDetailComponent
             name: currentTranslation ? currentTranslation.name : '',
             enabled: country.enabled,
         });
+
+        if (this.customFields.length) {
+            const customFieldsGroup = this.detailForm.get(['customFields']) as FormGroup;
+
+            for (const fieldDef of this.customFields) {
+                const key = fieldDef.name;
+                const value =
+                    fieldDef.type === 'localeString'
+                        ? (currentTranslation as any).customFields[key]
+                        : (country as any).customFields[key];
+                const control = customFieldsGroup.get(key);
+                if (control) {
+                    control.patchValue(value);
+                }
+            }
+        }
     }
 }

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

@@ -64,6 +64,17 @@
             />
         </clr-toggle-wrapper>
     </vdr-form-field>
+    <section formGroupName="customFields" *ngIf="customFields.length">
+        <label>{{ 'common.custom-fields' | translate }}</label>
+        <ng-container *ngFor="let customField of customFields">
+            <vdr-custom-field-control
+                *ngIf="customFieldIsSet(customField.name)"
+                entityName="PaymentMethod"
+                [customFieldsFormGroup]="detailForm.get('customFields')"
+                [customField]="customField"
+            ></vdr-custom-field-control>
+        </ng-container>
+    </section>
 
     <div class="clr-row mt4">
         <div class="clr-col">

+ 26 - 1
packages/admin-ui/src/lib/settings/src/components/payment-method-detail/payment-method-detail.component.ts

@@ -9,6 +9,7 @@ import {
     ConfigurableOperation,
     ConfigurableOperationDefinition,
     CreatePaymentMethodInput,
+    CustomFieldConfig,
     DataService,
     encodeConfigArgValue,
     getConfigArgValue,
@@ -31,8 +32,10 @@ import { mergeMap, take } from 'rxjs/operators';
 })
 export class PaymentMethodDetailComponent
     extends BaseDetailComponent<PaymentMethod.Fragment>
-    implements OnInit, OnDestroy {
+    implements OnInit, OnDestroy
+{
     detailForm: FormGroup;
+    customFields: CustomFieldConfig[];
     checkers: ConfigurableOperationDefinition[] = [];
     handlers: ConfigurableOperationDefinition[] = [];
     selectedChecker?: ConfigurableOperation | null;
@@ -51,6 +54,7 @@ export class PaymentMethodDetailComponent
         private notificationService: NotificationService,
     ) {
         super(route, router, serverConfigService, dataService);
+        this.customFields = this.getCustomFieldConfig('PaymentMethod');
         this.detailForm = this.formBuilder.group({
             code: ['', Validators.required],
             name: ['', Validators.required],
@@ -58,6 +62,9 @@ export class PaymentMethodDetailComponent
             enabled: [true, Validators.required],
             checker: {},
             handler: {},
+            customFields: this.formBuilder.group(
+                this.customFields.reduce((hash, field) => ({ ...hash, [field.name]: '' }), {}),
+            ),
         });
     }
 
@@ -92,6 +99,10 @@ export class PaymentMethodDetailComponent
         }
     }
 
+    customFieldIsSet(name: string): boolean {
+        return !!this.detailForm.get(['customFields', name]);
+    }
+
     configArgsIsPopulated(): boolean {
         const configArgsGroup = this.detailForm.get('configArgs') as FormGroup | undefined;
         if (!configArgsGroup) {
@@ -154,6 +165,7 @@ export class PaymentMethodDetailComponent
                             ? toConfigurableOperationInput(selectedChecker, formValue.checker)
                             : null,
                         handler: toConfigurableOperationInput(selectedHandler, formValue.handler),
+                        customFields: formValue.customFields,
                     };
                     return this.dataService.settings.createPaymentMethod(input);
                 }),
@@ -196,6 +208,7 @@ export class PaymentMethodDetailComponent
                             ? toConfigurableOperationInput(selectedChecker, formValue.checker)
                             : null,
                         handler: toConfigurableOperationInput(selectedHandler, formValue.handler),
+                        customFields: formValue.customFields,
                     };
                     return this.dataService.settings.updatePaymentMethod(input);
                 }),
@@ -237,5 +250,17 @@ export class PaymentMethodDetailComponent
                 args: paymentMethod.handler.args.map(a => ({ ...a, value: getConfigArgValue(a.value) })),
             };
         }
+        if (this.customFields.length) {
+            const customFieldsGroup = this.detailForm.get('customFields') as FormGroup;
+
+            for (const fieldDef of this.customFields) {
+                const key = fieldDef.name;
+                const value = (paymentMethod as any).customFields[key];
+                const control = customFieldsGroup.get(key);
+                if (control) {
+                    control.patchValue(value);
+                }
+            }
+        }
     }
 }

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

@@ -46,4 +46,15 @@
             />
         </clr-toggle-wrapper>
     </vdr-form-field>
+    <section formGroupName="customFields" *ngIf="customFields.length">
+        <label>{{ 'common.custom-fields' | translate }}</label>
+        <ng-container *ngFor="let customField of customFields">
+            <vdr-custom-field-control
+                *ngIf="customFieldIsSet(customField.name)"
+                entityName="TaxCategory"
+                [customFieldsFormGroup]="detailForm.get('customFields')"
+                [customField]="customField"
+            ></vdr-custom-field-control>
+        </ng-container>
+    </section>
 </form>

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

@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnIni
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
-import { BaseDetailComponent, Permission } from '@vendure/admin-ui/core';
+import { BaseDetailComponent, CustomFieldConfig, Permission } from '@vendure/admin-ui/core';
 import {
     ConfigurableOperation,
     CreateTaxCategoryInput,
@@ -24,9 +24,11 @@ import { mergeMap, take } from 'rxjs/operators';
 })
 export class TaxCategoryDetailComponent
     extends BaseDetailComponent<TaxCategory.Fragment>
-    implements OnInit, OnDestroy {
+    implements OnInit, OnDestroy
+{
     taxCategory$: Observable<TaxCategory.Fragment>;
     detailForm: FormGroup;
+    customFields: CustomFieldConfig[];
     readonly updatePermission = [Permission.UpdateSettings, Permission.UpdateTaxCategory];
 
     private taxCondition: ConfigurableOperation;
@@ -42,9 +44,13 @@ export class TaxCategoryDetailComponent
         private notificationService: NotificationService,
     ) {
         super(route, router, serverConfigService, dataService);
+        this.customFields = this.getCustomFieldConfig('TaxCategory');
         this.detailForm = this.formBuilder.group({
             name: ['', Validators.required],
             isDefault: false,
+            customFields: this.formBuilder.group(
+                this.customFields.reduce((hash, field) => ({ ...hash, [field.name]: '' }), {}),
+            ),
         });
     }
 
@@ -61,12 +67,20 @@ export class TaxCategoryDetailComponent
         return this.detailForm.dirty && this.detailForm.valid;
     }
 
+    customFieldIsSet(name: string): boolean {
+        return !!this.detailForm.get(['customFields', name]);
+    }
+
     create() {
         if (!this.detailForm.dirty) {
             return;
         }
         const formValue = this.detailForm.value;
-        const input = { name: formValue.name, isDefault: formValue.isDefault } as CreateTaxCategoryInput;
+        const input = {
+            name: formValue.name,
+            isDefault: formValue.isDefault,
+            customFields: formValue.customFields,
+        } as CreateTaxCategoryInput;
         this.dataService.settings.createTaxCategory(input).subscribe(
             data => {
                 this.notificationService.success(_('common.notify-create-success'), {
@@ -97,6 +111,7 @@ export class TaxCategoryDetailComponent
                         id: taxCategory.id,
                         name: formValue.name,
                         isDefault: formValue.isDefault,
+                        customFields: formValue.customFields,
                     } as UpdateTaxCategoryInput;
                     return this.dataService.settings.updateTaxCategory(input);
                 }),
@@ -125,5 +140,17 @@ export class TaxCategoryDetailComponent
             name: entity.name,
             isDefault: entity.isDefault,
         });
+        if (this.customFields.length) {
+            const customFieldsGroup = this.detailForm.get('customFields') as FormGroup;
+
+            for (const fieldDef of this.customFields) {
+                const key = fieldDef.name;
+                const value = (entity as any).customFields[key];
+                const control = customFieldsGroup.get(key);
+                if (control) {
+                    control.patchValue(value);
+                }
+            }
+        }
     }
 }

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

@@ -79,4 +79,15 @@
             <option *ngFor="let zone of zones$ | async" [value]="zone.id">{{ zone.name }}</option>
         </select>
     </vdr-form-field>
+    <section formGroupName="customFields" *ngIf="customFields.length">
+        <label>{{ 'common.custom-fields' | translate }}</label>
+        <ng-container *ngFor="let customField of customFields">
+            <vdr-custom-field-control
+                *ngIf="customFieldIsSet(customField.name)"
+                entityName="TaxRate"
+                [customFieldsFormGroup]="detailForm.get('customFields')"
+                [customField]="customField"
+            ></vdr-custom-field-control>
+        </ng-container>
+    </section>
 </form>

+ 26 - 1
packages/admin-ui/src/lib/settings/src/components/tax-rate-detail/tax-rate-detail.component.ts

@@ -6,6 +6,7 @@ import {
     BaseDetailComponent,
     CreateTaxRateInput,
     CustomerGroup,
+    CustomFieldConfig,
     DataService,
     GetZones,
     LanguageCode,
@@ -27,11 +28,13 @@ import { mergeMap, take } from 'rxjs/operators';
 })
 export class TaxRateDetailComponent
     extends BaseDetailComponent<TaxRate.Fragment>
-    implements OnInit, OnDestroy {
+    implements OnInit, OnDestroy
+{
     taxCategories$: Observable<TaxCategory.Fragment[]>;
     zones$: Observable<GetZones.Zones[]>;
     groups$: Observable<CustomerGroup[]>;
     detailForm: FormGroup;
+    customFields: CustomFieldConfig[];
     readonly updatePermission = [Permission.UpdateSettings, Permission.UpdateTaxRate];
 
     constructor(
@@ -44,6 +47,7 @@ export class TaxRateDetailComponent
         private notificationService: NotificationService,
     ) {
         super(route, router, serverConfigService, dataService);
+        this.customFields = this.getCustomFieldConfig('TaxRate');
         this.detailForm = this.formBuilder.group({
             name: ['', Validators.required],
             enabled: [true],
@@ -51,6 +55,9 @@ export class TaxRateDetailComponent
             taxCategoryId: [''],
             zoneId: [''],
             customerGroupId: [''],
+            customFields: this.formBuilder.group(
+                this.customFields.reduce((hash, field) => ({ ...hash, [field.name]: '' }), {}),
+            ),
         });
     }
 
@@ -66,6 +73,10 @@ export class TaxRateDetailComponent
         this.destroy();
     }
 
+    customFieldIsSet(name: string): boolean {
+        return !!this.detailForm.get(['customFields', name]);
+    }
+
     saveButtonEnabled(): boolean {
         return this.detailForm.dirty && this.detailForm.valid;
     }
@@ -82,6 +93,7 @@ export class TaxRateDetailComponent
             categoryId: formValue.taxCategoryId,
             zoneId: formValue.zoneId,
             customerGroupId: formValue.customerGroupId,
+            customFields: formValue.customFields,
         } as CreateTaxRateInput;
         this.dataService.settings.createTaxRate(input).subscribe(
             data => {
@@ -117,6 +129,7 @@ export class TaxRateDetailComponent
                         categoryId: formValue.taxCategoryId,
                         zoneId: formValue.zoneId,
                         customerGroupId: formValue.customerGroupId,
+                        customFields: formValue.customFields,
                     } as UpdateTaxRateInput;
                     return this.dataService.settings.updateTaxRate(input);
                 }),
@@ -149,5 +162,17 @@ export class TaxRateDetailComponent
             zoneId: entity.zone ? entity.zone.id : '',
             customerGroupId: entity.customerGroup ? entity.customerGroup.id : '',
         });
+        if (this.customFields.length) {
+            const customFieldsGroup = this.detailForm.get('customFields') as FormGroup;
+
+            for (const fieldDef of this.customFields) {
+                const key = fieldDef.name;
+                const value = (entity as any).customFields[key];
+                const control = customFieldsGroup.get(key);
+                if (control) {
+                    control.patchValue(value);
+                }
+            }
+        }
     }
 }

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

@@ -2,10 +2,24 @@
     <span *ngIf="zone.id">{{ 'settings.update-zone' | translate }}</span>
     <span *ngIf="!zone.id">{{ 'settings.create-zone' | translate }}</span>
 </ng-template>
-
-<vdr-form-field [label]="'common.name' | translate" for="name">
-    <input id="name" type="text" [(ngModel)]="zone.name" [readonly]="!(['UpdateSettings', 'UpdateZone'] | hasPermission)" />
-</vdr-form-field>
+<form [formGroup]="form">
+    <vdr-form-field [label]="'common.name' | translate" for="name">
+        <input
+            id="name"
+            type="text"
+            formControlName="name"
+            [readonly]="!(['UpdateSettings', 'UpdateZone'] | hasPermission)"
+        />
+    </vdr-form-field>
+    <ng-container *ngFor="let customField of customFields">
+        <vdr-custom-field-control
+            *ngIf="customField.name"
+            entityName="Zone"
+            [customFieldsFormGroup]="form.get(['customFields'])"
+            [customField]="customField"
+        ></vdr-custom-field-control>
+    </ng-container>
+</form>
 
 <ng-template vdrDialogButtons>
     <button type="button" class="btn" (click)="cancel()">{{ 'common.cancel' | translate }}</button>

+ 35 - 6
packages/admin-ui/src/lib/settings/src/components/zone-detail-dialog/zone-detail-dialog.component.ts

@@ -1,5 +1,6 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { Dialog } from '@vendure/admin-ui/core';
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { CreateZoneInput, CustomFieldConfig, Dialog, ServerConfigService } from '@vendure/admin-ui/core';
 
 @Component({
     selector: 'vdr-zone-detail-dialog',
@@ -7,15 +8,43 @@ import { Dialog } from '@vendure/admin-ui/core';
     styleUrls: ['./zone-detail-dialog.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class ZoneDetailDialogComponent implements Dialog<string> {
-    zone: { id?: string; name: string };
-    resolveWith: (result?: string) => void;
+export class ZoneDetailDialogComponent implements Dialog<CreateZoneInput>, OnInit {
+    zone: { id?: string; name: string; customFields?: { [name: string]: any } };
+    resolveWith: (result?: CreateZoneInput) => void;
+
+    customFields: CustomFieldConfig[];
+    form: FormGroup;
+
+    constructor(private serverConfigService: ServerConfigService, private formBuilder: FormBuilder) {
+        this.customFields = this.serverConfigService.getCustomFieldsFor('CustomerGroup');
+    }
+
+    ngOnInit() {
+        this.form = this.formBuilder.group({
+            name: [this.zone.name, Validators.required],
+            customFields: this.formBuilder.group(
+                this.customFields.reduce((hash, field) => ({ ...hash, [field.name]: '' }), {}),
+            ),
+        });
+        if (this.customFields.length) {
+            const customFieldsGroup = this.form.get('customFields') as FormGroup;
+
+            for (const fieldDef of this.customFields) {
+                const key = fieldDef.name;
+                const value = this.zone.customFields?.[key];
+                const control = customFieldsGroup.get(key);
+                if (control) {
+                    control.patchValue(value);
+                }
+            }
+        }
+    }
 
     cancel() {
         this.resolveWith();
     }
 
     save() {
-        this.resolveWith(this.zone.name);
+        this.resolveWith(this.form.value);
     }
 }

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

@@ -55,8 +55,8 @@ export class ZoneListComponent implements OnInit {
         this.modalService
             .fromComponent(ZoneDetailDialogComponent, { locals: { zone: { name: '' } } })
             .pipe(
-                switchMap(name =>
-                    name ? this.dataService.settings.createZone({ name, memberIds: [] }) : EMPTY,
+                switchMap(result =>
+                    result ? this.dataService.settings.createZone({ ...result, memberIds: [] }) : EMPTY,
                 ),
                 // refresh list
                 switchMap(() => this.dataService.settings.getZones().single$),
@@ -120,8 +120,8 @@ export class ZoneListComponent implements OnInit {
         this.modalService
             .fromComponent(ZoneDetailDialogComponent, { locals: { zone } })
             .pipe(
-                switchMap(name =>
-                    name ? this.dataService.settings.updateZone({ id: zone.id, name }) : EMPTY,
+                switchMap(result =>
+                    result ? this.dataService.settings.updateZone({ id: zone.id, ...result }) : EMPTY,
                 ),
             )
             .subscribe(

+ 10 - 7
packages/dev-server/dev-config.ts

@@ -56,13 +56,16 @@ export const devConfig: VendureConfig = {
         paymentMethodHandlers: [dummyPaymentHandler],
     },
     customFields: {
-        Country: [{ name: 'foo', type: 'localeString' }],
-        CustomerGroup: [{ name: 'foo', type: 'string' }],
-        PaymentMethod: [{ name: 'foo', type: 'string' }],
-        Promotion: [{ name: 'foo', type: 'string' }],
-        TaxCategory: [{ name: 'foo', type: 'string' }],
-        TaxRate: [{ name: 'foo', type: 'string' }],
-        Zone: [{ name: 'foo', type: 'string' }],
+        // Country: [
+        //     { name: 'foo', type: 'localeString' },
+        //     { name: 'rating', type: 'int' },
+        // ],
+        // CustomerGroup: [{ name: 'foo', type: 'string' }],
+        // PaymentMethod: [{ name: 'foo', type: 'string' }],
+        // Promotion: [{ name: 'foo', type: 'string' }],
+        // TaxCategory: [{ name: 'foo', type: 'string' }],
+        // TaxRate: [{ name: 'foo', type: 'string' }],
+        // Zone: [{ name: 'foo', type: 'string' }],
     },
     logger: new DefaultLogger({ level: LogLevel.Debug }),
     importExportOptions: {