diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d11cafec..0fb784847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ All notable, unreleased changes to this project will be documented in this file. - Reset pagination when guest change the sorting of the list - #474 by @gabmartinez - Filter column ids before send it to GridAttributes operation - #476 by @gabmartinez - Display Is Published column correctly in main Product Listing - #475 by @gabmartinez +- Add Permission Groups section - #406 by @krzysztofwolski ## 2.0.0 diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index fcf473bdc..fd5a5eabb 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -16,6 +16,9 @@ "configurationMenuPages": { "string": "Manage and add additional pages" }, + "configurationMenuPermissionGroups": { + "string": "Manage your permission groups and their permissions" + }, "configurationMenuProductTypes": { "string": "Define types of products you sell" }, @@ -902,16 +905,24 @@ "context": "dialog title", "string": "Delete collections" }, - "src_dot_components_dot_AccountPermissions_dot_1848599267": { - "context": "checkbox label", - "string": "User has full access to the store" + "src_dot_components_dot_AccountPermissionGroups_dot_2836633439": { + "string": "Permission groups" }, "src_dot_components_dot_AccountPermissions_dot_2690176844": { "context": "dialog header", "string": "Permissions" }, - "src_dot_components_dot_AccountPermissions_dot_3639008725": { - "string": "Expand or restrict user's permissions to access certain part of saleor system." + "src_dot_components_dot_AccountPermissions_dot_2731975416": { + "context": "card section description", + "string": "Available permissions" + }, + "src_dot_components_dot_AccountPermissions_dot_2853621891": { + "context": "exceeded permissions description", + "string": "This groups permissions exceeds your own. You are able only to manage permissions that you have." + }, + "src_dot_components_dot_AccountPermissions_dot_3674526441": { + "context": "permission list item description", + "string": "This group is last source of that permission" }, "src_dot_components_dot_AccountStatus_dot_2183517419": { "context": "section header", @@ -1193,6 +1204,10 @@ "context": "button", "string": "Create Order" }, + "src_dot_components_dot_Navigator_dot_modes_dot_createPermissionGroup": { + "context": "button", + "string": "Create Permission Group" + }, "src_dot_components_dot_Navigator_dot_modes_dot_createProduct": { "context": "button", "string": "Create Product" @@ -2930,6 +2945,144 @@ "context": "payment status", "string": "Partially refunded" }, + "src_dot_permissionGroups": { + "context": "permission groups section name", + "string": "Permission Groups" + }, + "src_dot_permissionGroups_dot_components_dot_AssignMembersDialog_dot_1004218338": { + "context": "staff member status", + "string": "Inactive" + }, + "src_dot_permissionGroups_dot_components_dot_AssignMembersDialog_dot_1731102929": { + "context": "button", + "string": "Assign" + }, + "src_dot_permissionGroups_dot_components_dot_AssignMembersDialog_dot_2986043376": { + "context": "dialog header", + "string": "Assign Staff Members" + }, + "src_dot_permissionGroups_dot_components_dot_AssignMembersDialog_dot_3111990517": { + "string": "Search by name, email, etc..." + }, + "src_dot_permissionGroups_dot_components_dot_AssignMembersDialog_dot_3247064221": { + "context": "staff member status", + "string": "Active" + }, + "src_dot_permissionGroups_dot_components_dot_AssignMembersDialog_dot_3532084010": { + "string": "Search Staff Members" + }, + "src_dot_permissionGroups_dot_components_dot_MembersErrorDialog_dot_3005518110": { + "context": "dialog title", + "string": "Unassign users" + }, + "src_dot_permissionGroups_dot_components_dot_MembersErrorDialog_dot_334280454": { + "context": "dialog content", + "string": "You are not able to modify this group members. Solve this problem to continue with request." + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupCreatePage_dot_3765873075": { + "context": "checkbox label", + "string": "Group has full access to the store" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupCreatePage_dot_3920591233": { + "context": "card description", + "string": "Expand or restrict group's permissions to access certain part of saleor system." + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupDeleteDialog_dot_2297471173": { + "context": "dialog content", + "string": "Are you sure you want to delete {name}?" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupDeleteDialog_dot_3528974446": { + "context": "dialog title", + "string": "Delete permission group" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupDetailsPage_dot_3765873075": { + "context": "checkbox label", + "string": "Group has full access to the store" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupDetailsPage_dot_3920591233": { + "context": "card description", + "string": "Expand or restrict group's permissions to access certain part of saleor system." + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupInfo_dot_2081191949": { + "context": "text field label", + "string": "Group name" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupListPage_dot_613749311": { + "context": "button", + "string": "create permission group" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupList_dot_2011544055": { + "context": "permission group name", + "string": "Permission Group Name" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupList_dot_2734377620": { + "string": "Members" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupList_dot_4190792473": { + "string": "Actions" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupList_dot_425941108": { + "string": "No permission groups found" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupMemberList_dot_1004218338": { + "context": "staff member status", + "string": "Inactive" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupMemberList_dot_136112637": { + "context": "empty list message", + "string": "You haven’t assigned any member to this permission group yet." + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupMemberList_dot_1512872240": { + "string": "No members found" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupMemberList_dot_1789607185": { + "string": "Email Address" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupMemberList_dot_2457011428": { + "context": "button", + "string": "Assign members" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupMemberList_dot_2652621342": { + "context": "empty list message", + "string": "Please use Assign Members button to do so." + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupMemberList_dot_3247064221": { + "context": "staff member status", + "string": "Active" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupMemberList_dot_3397233744": { + "context": "header", + "string": "Group members" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupMemberList_dot_4190792473": { + "string": "Actions" + }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupMemberList_dot_636461959": { + "context": "staff member full name", + "string": "Name" + }, + "src_dot_permissionGroups_dot_components_dot_UnassignMembersDialog_dot_3005518110": { + "context": "dialog title", + "string": "Unassign users" + }, + "src_dot_permissionGroups_dot_components_dot_UnassignMembersDialog_dot_778652830": { + "context": "dialog content", + "string": "Are you sure you want to unassign {counter,plural,one{this member} other{{displayQuantity} members}}?" + }, + "src_dot_permissionGroups_dot_views_dot_PermissionGroupCreate_dot_1140231710": { + "context": "window title", + "string": "Create category" + }, + "src_dot_permissionGroups_dot_views_dot_PermissionGroupCreate_dot_3930588328": { + "string": "Permission group created" + }, + "src_dot_permissionGroups_dot_views_dot_PermissionGroupDetails_dot_870815507": { + "context": "button title", + "string": "Unassign" + }, + "src_dot_permissionGroups_dot_views_dot_PermissionGroupList_dot_92979760": { + "string": "Permission Group Deleted" + }, "src_dot_plugins": { "context": "plugins section name", "string": "Plugins" @@ -3698,6 +3851,10 @@ "context": "service accounts section name", "string": "Service Accounts" }, + "src_dot_services_dot_components_dot_ServiceCreatePage_dot_1848599267": { + "context": "checkbox label", + "string": "User has full access to the store" + }, "src_dot_services_dot_components_dot_ServiceCreatePage_dot_248507553": { "context": "header", "string": "Create New Account" @@ -3706,6 +3863,10 @@ "context": "checkbox label", "string": "Service account is active" }, + "src_dot_services_dot_components_dot_ServiceCreatePage_dot_3639008725": { + "context": "card description", + "string": "Expand or restrict user's permissions to access certain part of saleor system." + }, "src_dot_services_dot_components_dot_ServiceDefaultToken_dot_1336855942": { "string": "Generated Token" }, @@ -3731,10 +3892,18 @@ "context": "delete service account", "string": "Are you sure you want to delete {name}?" }, + "src_dot_services_dot_components_dot_ServiceDetailsPage_dot_1848599267": { + "context": "checkbox label", + "string": "User has full access to the store" + }, "src_dot_services_dot_components_dot_ServiceDetailsPage_dot_27827485": { "context": "checkbox label", "string": "Service account is active" }, + "src_dot_services_dot_components_dot_ServiceDetailsPage_dot_3639008725": { + "context": "card description", + "string": "Expand or restrict user's permissions to access certain part of saleor system." + }, "src_dot_services_dot_components_dot_ServiceInfo_dot_3789449123": { "context": "service account", "string": "Account Name" @@ -4222,19 +4391,18 @@ "context": "dialog header", "string": "Invite Staff Member" }, - "src_dot_staff_dot_components_dot_StaffAddMemberDialog_dot_1570990296": { - "string": "User has full access" - }, - "src_dot_staff_dot_components_dot_StaffAddMemberDialog_dot_2690176844": { - "string": "Permissions" - }, - "src_dot_staff_dot_components_dot_StaffAddMemberDialog_dot_351138560": { - "string": "Expand or restrict user’s permissions to access certain part of saleor system." - }, "src_dot_staff_dot_components_dot_StaffAddMemberDialog_dot_449055697": { "context": "button", "string": "Send invite" }, + "src_dot_staff_dot_components_dot_StaffDetailsPage_dot_22572205": { + "context": "card description", + "string": "User is assigned to:" + }, + "src_dot_staff_dot_components_dot_StaffDetailsPage_dot_2690176844": { + "context": "dialog header", + "string": "Permissions" + }, "src_dot_staff_dot_components_dot_StaffDetailsPage_dot_881953347": { "context": "checkbox label", "string": "User is active" @@ -4721,6 +4889,9 @@ "context": "add authorization key error", "string": "Authorization key with this type already exists" }, + "src_dot_utils_dot_errors_dot_assignNonStaffMember": { + "string": "Only staff members can be assigned" + }, "src_dot_utils_dot_errors_dot_attributeAlreadyAssigned": { "string": "This attribute has already been assigned to this product type" }, @@ -4734,6 +4905,9 @@ "context": "error message", "string": "Billing address is not set" }, + "src_dot_utils_dot_errors_dot_cannotAddAndRemove": { + "string": "Cannot add and remove group the same time" + }, "src_dot_utils_dot_errors_dot_cannotCancelFulfillment": { "context": "error message", "string": "This fulfillment cannot be cancelled" @@ -4750,6 +4924,9 @@ "context": "error message", "string": "Manual payments can not be refunded" }, + "src_dot_utils_dot_errors_dot_cannotRemoveFromLastGroup": { + "string": "Cannot remove user from last group" + }, "src_dot_utils_dot_errors_dot_cannotVoid": { "context": "error message", "string": "Only pre-authorized payments can be voided" @@ -4775,6 +4952,12 @@ "context": "error message", "string": "Only draft orders can be edited" }, + "src_dot_utils_dot_errors_dot_outOfScopeGroup": { + "string": "Group is out of your permissions scope" + }, + "src_dot_utils_dot_errors_dot_outOfScopeUser": { + "string": "User is out of your permissions scope" + }, "src_dot_utils_dot_errors_dot_passwordNumeric": { "string": "Password cannot be entirely numeric" }, @@ -4782,6 +4965,9 @@ "context": "error message", "string": "There's no payment associated with the order" }, + "src_dot_utils_dot_errors_dot_permissionOutOfScope": { + "string": "Those permissions are out of your scope" + }, "src_dot_utils_dot_errors_dot_shippingNotApplicable": { "context": "error message", "string": "Shipping method is not valid for chosen shipping address" @@ -4803,6 +4989,9 @@ "src_dot_utils_dot_errors_dot_tooSimilar": { "string": "These passwords are too similar" }, + "src_dot_utils_dot_errors_dot_unique": { + "string": "This needs to be unique" + }, "src_dot_utils_dot_errors_dot_unknownError": { "string": "Unknown error" }, diff --git a/schema.graphql b/schema.graphql index c7a635ecc..710daf06a 100644 --- a/schema.graphql +++ b/schema.graphql @@ -4,28 +4,40 @@ schema { } type AccountAddressCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! address: Address } type AccountAddressDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! address: Address } type AccountAddressUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! address: Address } type AccountDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } @@ -39,7 +51,7 @@ type AccountError { enum AccountErrorCode { ACTIVATE_OWN_ACCOUNT ACTIVATE_SUPERUSER_ACCOUNT - CANNOT_ADD_AND_REMOVE + DUPLICATED_INPUT_ITEM DEACTIVATE_OWN_ACCOUNT DEACTIVATE_SUPERUSER_ACCOUNT DELETE_NON_STAFF_USER @@ -71,7 +83,10 @@ input AccountInput { } type AccountRegister { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) requiresConfirmation: Boolean accountErrors: [AccountError!]! user: User @@ -84,24 +99,36 @@ input AccountRegisterInput { } type AccountRequestDeletion { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! } type AccountSetDefaultAddress { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! } type AccountUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } type AccountUpdateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } @@ -124,14 +151,20 @@ type Address implements Node { } type AddressCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! address: Address } type AddressDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! address: Address @@ -152,7 +185,10 @@ input AddressInput { } type AddressSetDefault { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! } @@ -163,7 +199,10 @@ enum AddressTypeEnum { } type AddressUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! address: Address @@ -190,19 +229,38 @@ type AddressValidationData { } type AssignNavigation { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) menu: Menu menuErrors: [MenuError!]! } type Attribute implements Node & ObjectWithMetadata { id: ID! - productTypes(before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection! - productVariantTypes(before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection! + productTypes( + before: String + after: String + first: Int + last: Int + ): ProductTypeCountableConnection! + productVariantTypes( + before: String + after: String + first: Int + last: Int + ): ProductTypeCountableConnection! privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) inputType: AttributeInputTypeEnum name: String slug: String @@ -217,7 +275,10 @@ type Attribute implements Node & ObjectWithMetadata { } type AttributeAssign { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productType: ProductType productErrors: [ProductAttributeError!]! } @@ -228,19 +289,28 @@ input AttributeAssignInput { } type AttributeBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! productErrors: [ProductError!]! } type AttributeClearMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! attribute: Attribute } type AttributeClearPrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! attribute: Attribute } @@ -257,7 +327,10 @@ type AttributeCountableEdge { } type AttributeCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) attribute: Attribute productErrors: [ProductError!]! } @@ -277,7 +350,10 @@ input AttributeCreateInput { } type AttributeDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! attribute: Attribute } @@ -307,7 +383,10 @@ enum AttributeInputTypeEnum { } type AttributeReorderValues { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) attribute: Attribute productErrors: [ProductError!]! } @@ -320,8 +399,6 @@ enum AttributeSortField { VISIBLE_IN_STOREFRONT FILTERABLE_IN_STOREFRONT FILTERABLE_IN_DASHBOARD - DASHBOARD_VARIANT_POSITION - DASHBOARD_PRODUCT_POSITION STOREFRONT_SEARCH_POSITION AVAILABLE_IN_GRID } @@ -339,7 +416,10 @@ type AttributeTranslatableContent implements Node { } type AttributeTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) translationErrors: [TranslationError!]! attribute: Attribute } @@ -356,13 +436,19 @@ enum AttributeTypeEnum { } type AttributeUnassign { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productType: ProductType productErrors: [ProductError!]! } type AttributeUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) attribute: Attribute productErrors: [ProductError!]! } @@ -382,13 +468,19 @@ input AttributeUpdateInput { } type AttributeUpdateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! attribute: Attribute } type AttributeUpdatePrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! attribute: Attribute } @@ -403,13 +495,19 @@ type AttributeValue implements Node { } type AttributeValueBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! productErrors: [ProductError!]! } type AttributeValueCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) attribute: Attribute productErrors: [ProductError!]! attributeValue: AttributeValue @@ -420,7 +518,10 @@ input AttributeValueCreateInput { } type AttributeValueDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) attribute: Attribute productErrors: [ProductError!]! attributeValue: AttributeValue @@ -439,7 +540,10 @@ type AttributeValueTranslatableContent implements Node { } type AttributeValueTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) translationErrors: [TranslationError!]! attributeValue: AttributeValue } @@ -458,7 +562,10 @@ enum AttributeValueType { } type AttributeValueUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) attribute: Attribute productErrors: [ProductError!]! attributeValue: AttributeValue @@ -470,14 +577,20 @@ type AuthorizationKey { } type AuthorizationKeyAdd { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) authorizationKey: AuthorizationKey shop: Shop shopErrors: [ShopError!]! } type AuthorizationKeyDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) authorizationKey: AuthorizationKey shop: Shop shopErrors: [ShopError!]! @@ -525,30 +638,61 @@ type Category implements Node & ObjectWithMetadata { level: Int! privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") - ancestors(before: String, after: String, first: Int, last: Int): CategoryCountableConnection - products(before: String, after: String, first: Int, last: Int): ProductCountableConnection - url: String @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11.") - children(before: String, after: String, first: Int, last: Int): CategoryCountableConnection + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) + ancestors( + before: String + after: String + first: Int + last: Int + ): CategoryCountableConnection + products( + before: String + after: String + first: Int + last: Int + ): ProductCountableConnection + url: String + @deprecated(reason: "This field will be removed after 2020-07-31.") + children( + before: String + after: String + first: Int + last: Int + ): CategoryCountableConnection backgroundImage(size: Int): Image translation(languageCode: LanguageCodeEnum!): CategoryTranslation } type CategoryBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! productErrors: [ProductError!]! } type CategoryClearMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! category: Category } type CategoryClearPrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! category: Category } @@ -565,13 +709,19 @@ type CategoryCountableEdge { } type CategoryCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! category: Category } type CategoryDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! category: Category } @@ -614,7 +764,10 @@ type CategoryTranslatableContent implements Node { } type CategoryTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) translationErrors: [TranslationError!]! category: Category } @@ -630,19 +783,28 @@ type CategoryTranslation implements Node { } type CategoryUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! category: Category } type CategoryUpdateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! category: Category } type CategoryUpdatePrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! category: Category } @@ -665,8 +827,14 @@ type Checkout implements Node & ObjectWithMetadata { id: ID! privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) availableShippingMethods: [ShippingMethod]! availablePaymentGateways: [PaymentGateway]! email: String! @@ -678,31 +846,46 @@ type Checkout implements Node & ObjectWithMetadata { } type CheckoutAddPromoCode { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout checkoutErrors: [CheckoutError!]! } type CheckoutBillingAddressUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout checkoutErrors: [CheckoutError!]! } type CheckoutClearMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkoutErrors: [CheckoutError!]! checkout: Checkout } type CheckoutClearPrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkoutErrors: [CheckoutError!]! checkout: Checkout } type CheckoutComplete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order confirmationNeeded: Boolean! checkoutErrors: [CheckoutError!]! @@ -720,7 +903,10 @@ type CheckoutCountableEdge { } type CheckoutCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) created: Boolean checkoutErrors: [CheckoutError!]! checkout: Checkout @@ -734,19 +920,28 @@ input CheckoutCreateInput { } type CheckoutCustomerAttach { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout checkoutErrors: [CheckoutError!]! } type CheckoutCustomerDetach { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout checkoutErrors: [CheckoutError!]! } type CheckoutEmailUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout checkoutErrors: [CheckoutError!]! } @@ -798,7 +993,10 @@ type CheckoutLineCountableEdge { } type CheckoutLineDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout checkoutErrors: [CheckoutError!]! } @@ -809,50 +1007,74 @@ input CheckoutLineInput { } type CheckoutLinesAdd { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout checkoutErrors: [CheckoutError!]! } type CheckoutLinesUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout checkoutErrors: [CheckoutError!]! } type CheckoutPaymentCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout payment: Payment paymentErrors: [PaymentError!]! } type CheckoutRemovePromoCode { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout checkoutErrors: [CheckoutError!]! } type CheckoutShippingAddressUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout checkoutErrors: [CheckoutError!]! } type CheckoutShippingMethodUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkout: Checkout checkoutErrors: [CheckoutError!]! } type CheckoutUpdateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkoutErrors: [CheckoutError!]! checkout: Checkout } type CheckoutUpdatePrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) checkoutErrors: [CheckoutError!]! checkout: Checkout } @@ -874,39 +1096,65 @@ type Collection implements Node & ObjectWithMetadata { slug: String! privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") - products(before: String, after: String, first: Int, last: Int): ProductCountableConnection + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) + products( + before: String + after: String + first: Int + last: Int + ): ProductCountableConnection backgroundImage(size: Int): Image translation(languageCode: LanguageCodeEnum!): CollectionTranslation } type CollectionAddProducts { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) collection: Collection productErrors: [ProductError!]! } type CollectionBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! productErrors: [ProductError!]! } type CollectionBulkPublish { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! productErrors: [ProductError!]! } type CollectionClearMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! collection: Collection } type CollectionClearPrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! collection: Collection } @@ -923,7 +1171,10 @@ type CollectionCountableEdge { } type CollectionCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! collection: Collection } @@ -942,7 +1193,10 @@ input CollectionCreateInput { } type CollectionDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! collection: Collection } @@ -971,13 +1225,19 @@ enum CollectionPublished { } type CollectionRemoveProducts { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) collection: Collection productErrors: [ProductError!]! } type CollectionReorderProducts { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) collection: Collection productErrors: [ProductError!]! } @@ -1005,7 +1265,10 @@ type CollectionTranslatableContent implements Node { } type CollectionTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) translationErrors: [TranslationError!]! collection: Collection } @@ -1021,19 +1284,28 @@ type CollectionTranslation implements Node { } type CollectionUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! collection: Collection } type CollectionUpdateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! collection: Collection } type CollectionUpdatePrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! collection: Collection } @@ -1059,13 +1331,19 @@ enum ConfigurationTypeFieldEnum { } type ConfirmAccount { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! } type ConfirmEmailChange { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! } @@ -1331,7 +1609,10 @@ type CountryDisplay { type CreateToken { token: String - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } @@ -1345,19 +1626,28 @@ type CreditCard { } type CustomerBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! accountErrors: [AccountError!]! } type CustomerCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } type CustomerDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } @@ -1408,7 +1698,10 @@ input CustomerInput { } type CustomerUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } @@ -1430,13 +1723,19 @@ input DateTimeRangeInput { scalar Decimal type DeleteMetadata { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) metadataErrors: [MetadataError!]! item: ObjectWithMetadata } type DeletePrivateMetadata { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) metadataErrors: [MetadataError!]! item: ObjectWithMetadata } @@ -1452,8 +1751,14 @@ type DigitalContent implements Node & ObjectWithMetadata { id: ID! privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) } type DigitalContentCountableConnection { @@ -1468,14 +1773,20 @@ type DigitalContentCountableEdge { } type DigitalContentCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) variant: ProductVariant content: DigitalContent productErrors: [ProductError!]! } type DigitalContentDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) variant: ProductVariant productErrors: [ProductError!]! } @@ -1488,7 +1799,10 @@ input DigitalContentInput { } type DigitalContentUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) variant: ProductVariant content: DigitalContent productErrors: [ProductError!]! @@ -1512,7 +1826,10 @@ type DigitalContentUrl implements Node { } type DigitalContentUrlCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! digitalContentUrl: DigitalContentUrl } @@ -1554,19 +1871,28 @@ type Domain { } type DraftOrderBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! orderErrors: [OrderError!]! } type DraftOrderComplete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order orderErrors: [OrderError!]! } type DraftOrderCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) orderErrors: [OrderError!]! order: Order } @@ -1584,7 +1910,10 @@ input DraftOrderCreateInput { } type DraftOrderDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) orderErrors: [OrderError!]! order: Order } @@ -1601,34 +1930,49 @@ input DraftOrderInput { } type DraftOrderLineDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order orderLine: OrderLine orderErrors: [OrderError!]! } type DraftOrderLineUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order orderErrors: [OrderError!]! orderLine: OrderLine } type DraftOrderLinesBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! orderErrors: [OrderError!]! } type DraftOrderLinesCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order orderLines: [OrderLine!] orderErrors: [OrderError!]! } type DraftOrderUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) orderErrors: [OrderError!]! order: Order } @@ -1646,14 +1990,23 @@ type Fulfillment implements Node & ObjectWithMetadata { created: DateTime! privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) lines: [FulfillmentLine] statusDisplay: String } type FulfillmentCancel { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) fulfillment: Fulfillment order: Order orderErrors: [OrderError!]! @@ -1664,17 +2017,26 @@ input FulfillmentCancelInput { } type FulfillmentClearMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) fulfillment: Fulfillment } type FulfillmentClearPrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) fulfillment: Fulfillment } type FulfillmentCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) fulfillment: Fulfillment order: Order orderErrors: [OrderError!]! @@ -1703,17 +2065,26 @@ enum FulfillmentStatus { } type FulfillmentUpdateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) fulfillment: Fulfillment } type FulfillmentUpdatePrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) fulfillment: Fulfillment } type FulfillmentUpdateTracking { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) fulfillment: Fulfillment order: Order orderErrors: [OrderError!]! @@ -1750,7 +2121,10 @@ type GiftCard implements Node { } type GiftCardActivate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) giftCard: GiftCard giftCardErrors: [GiftCardError!]! } @@ -1767,7 +2141,10 @@ type GiftCardCountableEdge { } type GiftCardCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) giftCardErrors: [GiftCardError!]! giftCard: GiftCard } @@ -1781,7 +2158,10 @@ input GiftCardCreateInput { } type GiftCardDeactivate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) giftCard: GiftCard giftCardErrors: [GiftCardError!]! } @@ -1802,7 +2182,10 @@ enum GiftCardErrorCode { } type GiftCardUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) giftCardErrors: [GiftCardError!]! giftCard: GiftCard } @@ -1834,7 +2217,10 @@ type GroupCountableEdge { } type HomepageCollectionUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shop: Shop shopErrors: [ShopError!]! } @@ -1914,7 +2300,10 @@ type Menu implements Node { } type MenuBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! menuErrors: [MenuError!]! } @@ -1931,7 +2320,10 @@ type MenuCountableEdge { } type MenuCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) menuErrors: [MenuError!]! menu: Menu } @@ -1942,7 +2334,10 @@ input MenuCreateInput { } type MenuDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) menuErrors: [MenuError!]! menu: Menu } @@ -1988,7 +2383,10 @@ type MenuItem implements Node { } type MenuItemBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! menuErrors: [MenuError!]! } @@ -2005,7 +2403,10 @@ type MenuItemCountableEdge { } type MenuItemCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) menuErrors: [MenuError!]! menuItem: MenuItem } @@ -2021,7 +2422,10 @@ input MenuItemCreateInput { } type MenuItemDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) menuErrors: [MenuError!]! menuItem: MenuItem } @@ -2039,7 +2443,10 @@ input MenuItemInput { } type MenuItemMove { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) menu: Menu menuErrors: [MenuError!]! } @@ -2063,7 +2470,10 @@ type MenuItemTranslatableContent implements Node { } type MenuItemTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) translationErrors: [TranslationError!]! menuItem: MenuItem } @@ -2075,7 +2485,10 @@ type MenuItemTranslation implements Node { } type MenuItemUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) menuErrors: [MenuError!]! menuItem: MenuItem } @@ -2095,7 +2508,10 @@ input MenuSortingInput { } type MenuUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) menuErrors: [MenuError!]! menu: Menu } @@ -2153,7 +2569,10 @@ type MetadataItem { type Money { currency: String! amount: Float! - localized: String! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. Price formatting according to the current locale should be handled by the frontend client.") + localized: String! + @deprecated( + reason: "Price formatting according to the current locale should be handled by the frontend client. This field will be removed after 2020-07-31." + ) } type MoneyRange { @@ -2173,108 +2592,324 @@ type Mutation { createWarehouse(input: WarehouseCreateInput!): WarehouseCreate updateWarehouse(id: ID!, input: WarehouseUpdateInput!): WarehouseUpdate deleteWarehouse(id: ID!): WarehouseDelete - assignWarehouseShippingZone(id: ID!, shippingZoneIds: [ID!]!): WarehouseShippingZoneAssign - unassignWarehouseShippingZone(id: ID!, shippingZoneIds: [ID!]!): WarehouseShippingZoneUnassign - authorizationKeyAdd(input: AuthorizationKeyInput!, keyType: AuthorizationKeyType!): AuthorizationKeyAdd + assignWarehouseShippingZone( + id: ID! + shippingZoneIds: [ID!]! + ): WarehouseShippingZoneAssign + unassignWarehouseShippingZone( + id: ID! + shippingZoneIds: [ID!]! + ): WarehouseShippingZoneUnassign + authorizationKeyAdd( + input: AuthorizationKeyInput! + keyType: AuthorizationKeyType! + ): AuthorizationKeyAdd authorizationKeyDelete(keyType: AuthorizationKeyType!): AuthorizationKeyDelete - staffNotificationRecipientCreate(input: StaffNotificationRecipientInput!): StaffNotificationRecipientCreate - staffNotificationRecipientUpdate(id: ID!, input: StaffNotificationRecipientInput!): StaffNotificationRecipientUpdate + staffNotificationRecipientCreate( + input: StaffNotificationRecipientInput! + ): StaffNotificationRecipientCreate + staffNotificationRecipientUpdate( + id: ID! + input: StaffNotificationRecipientInput! + ): StaffNotificationRecipientUpdate staffNotificationRecipientDelete(id: ID!): StaffNotificationRecipientDelete homepageCollectionUpdate(collection: ID): HomepageCollectionUpdate shopDomainUpdate(input: SiteDomainInput): ShopDomainUpdate shopSettingsUpdate(input: ShopSettingsInput!): ShopSettingsUpdate shopFetchTaxRates: ShopFetchTaxRates - shopSettingsTranslate(input: ShopSettingsTranslationInput!, languageCode: LanguageCodeEnum!): ShopSettingsTranslate + shopSettingsTranslate( + input: ShopSettingsTranslationInput! + languageCode: LanguageCodeEnum! + ): ShopSettingsTranslate shopAddressUpdate(input: AddressInput): ShopAddressUpdate shippingPriceCreate(input: ShippingPriceInput!): ShippingPriceCreate shippingPriceDelete(id: ID!): ShippingPriceDelete shippingPriceBulkDelete(ids: [ID]!): ShippingPriceBulkDelete shippingPriceUpdate(id: ID!, input: ShippingPriceInput!): ShippingPriceUpdate - shippingPriceTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): ShippingPriceTranslate + shippingPriceTranslate( + id: ID! + input: NameTranslationInput! + languageCode: LanguageCodeEnum! + ): ShippingPriceTranslate shippingZoneCreate(input: ShippingZoneCreateInput!): ShippingZoneCreate shippingZoneDelete(id: ID!): ShippingZoneDelete shippingZoneBulkDelete(ids: [ID]!): ShippingZoneBulkDelete - shippingZoneUpdate(id: ID!, input: ShippingZoneUpdateInput!): ShippingZoneUpdate + shippingZoneUpdate( + id: ID! + input: ShippingZoneUpdateInput! + ): ShippingZoneUpdate attributeCreate(input: AttributeCreateInput!): AttributeCreate attributeDelete(id: ID!): AttributeDelete attributeBulkDelete(ids: [ID]!): AttributeBulkDelete - attributeAssign(operations: [AttributeAssignInput]!, productTypeId: ID!): AttributeAssign + attributeAssign( + operations: [AttributeAssignInput]! + productTypeId: ID! + ): AttributeAssign attributeUnassign(attributeIds: [ID]!, productTypeId: ID!): AttributeUnassign attributeUpdate(id: ID!, input: AttributeUpdateInput!): AttributeUpdate - attributeTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): AttributeTranslate - attributeUpdateMetadata(id: ID!, input: MetaInput!): AttributeUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") - attributeClearMetadata(id: ID!, input: MetaPath!): AttributeClearMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `DeleteMetadata` mutation instead.") - attributeUpdatePrivateMetadata(id: ID!, input: MetaInput!): AttributeUpdatePrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `UpdatePrivateMetadata` mutation instead.") - attributeClearPrivateMetadata(id: ID!, input: MetaPath!): AttributeClearPrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `DeletePrivateMetadata` mutation instead.") - attributeValueCreate(attribute: ID!, input: AttributeValueCreateInput!): AttributeValueCreate + attributeTranslate( + id: ID! + input: NameTranslationInput! + languageCode: LanguageCodeEnum! + ): AttributeTranslate + attributeUpdateMetadata(id: ID!, input: MetaInput!): AttributeUpdateMeta + @deprecated( + reason: "Use the `updateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + attributeClearMetadata(id: ID!, input: MetaPath!): AttributeClearMeta + @deprecated( + reason: "Use the `deleteMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + attributeUpdatePrivateMetadata( + id: ID! + input: MetaInput! + ): AttributeUpdatePrivateMeta + @deprecated( + reason: "Use the `updatePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + attributeClearPrivateMetadata( + id: ID! + input: MetaPath! + ): AttributeClearPrivateMeta + @deprecated( + reason: "Use the `deletePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + attributeValueCreate( + attribute: ID! + input: AttributeValueCreateInput! + ): AttributeValueCreate attributeValueDelete(id: ID!): AttributeValueDelete attributeValueBulkDelete(ids: [ID]!): AttributeValueBulkDelete - attributeValueUpdate(id: ID!, input: AttributeValueCreateInput!): AttributeValueUpdate - attributeValueTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): AttributeValueTranslate - attributeReorderValues(attributeId: ID!, moves: [ReorderInput]!): AttributeReorderValues + attributeValueUpdate( + id: ID! + input: AttributeValueCreateInput! + ): AttributeValueUpdate + attributeValueTranslate( + id: ID! + input: NameTranslationInput! + languageCode: LanguageCodeEnum! + ): AttributeValueTranslate + attributeReorderValues( + attributeId: ID! + moves: [ReorderInput]! + ): AttributeReorderValues categoryCreate(input: CategoryInput!, parent: ID): CategoryCreate categoryDelete(id: ID!): CategoryDelete categoryBulkDelete(ids: [ID]!): CategoryBulkDelete categoryUpdate(id: ID!, input: CategoryInput!): CategoryUpdate - categoryTranslate(id: ID!, input: TranslationInput!, languageCode: LanguageCodeEnum!): CategoryTranslate - categoryUpdateMetadata(id: ID!, input: MetaInput!): CategoryUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") - categoryClearMetadata(id: ID!, input: MetaPath!): CategoryClearMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `DeleteMetadata` mutation instead.") - categoryUpdatePrivateMetadata(id: ID!, input: MetaInput!): CategoryUpdatePrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `UpdatePrivateMetadata` mutation instead.") - categoryClearPrivateMetadata(id: ID!, input: MetaPath!): CategoryClearPrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `DeletePrivateMetadata` mutation instead.") - collectionAddProducts(collectionId: ID!, products: [ID]!): CollectionAddProducts + categoryTranslate( + id: ID! + input: TranslationInput! + languageCode: LanguageCodeEnum! + ): CategoryTranslate + categoryUpdateMetadata(id: ID!, input: MetaInput!): CategoryUpdateMeta + @deprecated( + reason: "Use the `updateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + categoryClearMetadata(id: ID!, input: MetaPath!): CategoryClearMeta + @deprecated( + reason: "Use the `deleteMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + categoryUpdatePrivateMetadata( + id: ID! + input: MetaInput! + ): CategoryUpdatePrivateMeta + @deprecated( + reason: "Use the `updatePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + categoryClearPrivateMetadata( + id: ID! + input: MetaPath! + ): CategoryClearPrivateMeta + @deprecated( + reason: "Use the `deletePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + collectionAddProducts( + collectionId: ID! + products: [ID]! + ): CollectionAddProducts collectionCreate(input: CollectionCreateInput!): CollectionCreate collectionDelete(id: ID!): CollectionDelete - collectionReorderProducts(collectionId: ID!, moves: [MoveProductInput]!): CollectionReorderProducts + collectionReorderProducts( + collectionId: ID! + moves: [MoveProductInput]! + ): CollectionReorderProducts collectionBulkDelete(ids: [ID]!): CollectionBulkDelete - collectionBulkPublish(ids: [ID]!, isPublished: Boolean!): CollectionBulkPublish - collectionRemoveProducts(collectionId: ID!, products: [ID]!): CollectionRemoveProducts + collectionBulkPublish( + ids: [ID]! + isPublished: Boolean! + ): CollectionBulkPublish + collectionRemoveProducts( + collectionId: ID! + products: [ID]! + ): CollectionRemoveProducts collectionUpdate(id: ID!, input: CollectionInput!): CollectionUpdate - collectionTranslate(id: ID!, input: TranslationInput!, languageCode: LanguageCodeEnum!): CollectionTranslate - collectionUpdateMetadata(id: ID!, input: MetaInput!): CollectionUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") - collectionClearMetadata(id: ID!, input: MetaPath!): CollectionClearMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `DeleteMetadata` mutation instead.") - collectionUpdatePrivateMetadata(id: ID!, input: MetaInput!): CollectionUpdatePrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `UpdatePrivateMetadata` mutation instead.") - collectionClearPrivateMetadata(id: ID!, input: MetaPath!): CollectionClearPrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `DeletePrivateMetadata` mutation instead.") + collectionTranslate( + id: ID! + input: TranslationInput! + languageCode: LanguageCodeEnum! + ): CollectionTranslate + collectionUpdateMetadata(id: ID!, input: MetaInput!): CollectionUpdateMeta + @deprecated( + reason: "Use the `updateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + collectionClearMetadata(id: ID!, input: MetaPath!): CollectionClearMeta + @deprecated( + reason: "Use the `deleteMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + collectionUpdatePrivateMetadata( + id: ID! + input: MetaInput! + ): CollectionUpdatePrivateMeta + @deprecated( + reason: "Use the `updatePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + collectionClearPrivateMetadata( + id: ID! + input: MetaPath! + ): CollectionClearPrivateMeta + @deprecated( + reason: "Use the `deletePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) productCreate(input: ProductCreateInput!): ProductCreate productDelete(id: ID!): ProductDelete productBulkDelete(ids: [ID]!): ProductBulkDelete productBulkPublish(ids: [ID]!, isPublished: Boolean!): ProductBulkPublish productUpdate(id: ID!, input: ProductInput!): ProductUpdate - productTranslate(id: ID!, input: TranslationInput!, languageCode: LanguageCodeEnum!): ProductTranslate - productUpdateMetadata(id: ID!, input: MetaInput!): ProductUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") - productClearMetadata(id: ID!, input: MetaPath!): ProductClearMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `DeleteMetadata` mutation instead.") - productUpdatePrivateMetadata(id: ID!, input: MetaInput!): ProductUpdatePrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `UpdatePrivateMetadata` mutation instead.") - productClearPrivateMetadata(id: ID!, input: MetaPath!): ProductClearPrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `DeletePrivateMetadata` mutation instead.") + productTranslate( + id: ID! + input: TranslationInput! + languageCode: LanguageCodeEnum! + ): ProductTranslate + productUpdateMetadata(id: ID!, input: MetaInput!): ProductUpdateMeta + @deprecated( + reason: "Use the `updateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + productClearMetadata(id: ID!, input: MetaPath!): ProductClearMeta + @deprecated( + reason: "Use the `deleteMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + productUpdatePrivateMetadata( + id: ID! + input: MetaInput! + ): ProductUpdatePrivateMeta + @deprecated( + reason: "Use the `updatePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + productClearPrivateMetadata( + id: ID! + input: MetaPath! + ): ProductClearPrivateMeta + @deprecated( + reason: "Use the `deletePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) productImageCreate(input: ProductImageCreateInput!): ProductImageCreate productImageDelete(id: ID!): ProductImageDelete productImageBulkDelete(ids: [ID]!): ProductImageBulkDelete productImageReorder(imagesIds: [ID]!, productId: ID!): ProductImageReorder - productImageUpdate(id: ID!, input: ProductImageUpdateInput!): ProductImageUpdate + productImageUpdate( + id: ID! + input: ProductImageUpdateInput! + ): ProductImageUpdate productTypeCreate(input: ProductTypeInput!): ProductTypeCreate productTypeDelete(id: ID!): ProductTypeDelete productTypeBulkDelete(ids: [ID]!): ProductTypeBulkDelete productTypeUpdate(id: ID!, input: ProductTypeInput!): ProductTypeUpdate - productTypeReorderAttributes(moves: [ReorderInput]!, productTypeId: ID!, type: AttributeTypeEnum!): ProductTypeReorderAttributes - productTypeUpdateMetadata(id: ID!, input: MetaInput!): ProductTypeUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") - productTypeClearMetadata(id: ID!, input: MetaPath!): ProductTypeClearMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `DeleteMetadata` mutation instead.") - productTypeUpdatePrivateMetadata(id: ID!, input: MetaInput!): ProductTypeUpdatePrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `UpdatePrivateMetadata` mutation instead.") - productTypeClearPrivateMetadata(id: ID!, input: MetaPath!): ProductTypeClearPrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `DeletePrivateMetadata` mutation instead.") - digitalContentCreate(input: DigitalContentUploadInput!, variantId: ID!): DigitalContentCreate + productTypeReorderAttributes( + moves: [ReorderInput]! + productTypeId: ID! + type: AttributeTypeEnum! + ): ProductTypeReorderAttributes + productTypeUpdateMetadata(id: ID!, input: MetaInput!): ProductTypeUpdateMeta + @deprecated( + reason: "Use the `updateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + productTypeClearMetadata(id: ID!, input: MetaPath!): ProductTypeClearMeta + @deprecated( + reason: "Use the `deleteMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + productTypeUpdatePrivateMetadata( + id: ID! + input: MetaInput! + ): ProductTypeUpdatePrivateMeta + @deprecated( + reason: "Use the `updatePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + productTypeClearPrivateMetadata( + id: ID! + input: MetaPath! + ): ProductTypeClearPrivateMeta + @deprecated( + reason: "Use the `deletePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + digitalContentCreate( + input: DigitalContentUploadInput! + variantId: ID! + ): DigitalContentCreate digitalContentDelete(variantId: ID!): DigitalContentDelete - digitalContentUpdate(input: DigitalContentInput!, variantId: ID!): DigitalContentUpdate - digitalContentUrlCreate(input: DigitalContentUrlCreateInput!): DigitalContentUrlCreate + digitalContentUpdate( + input: DigitalContentInput! + variantId: ID! + ): DigitalContentUpdate + digitalContentUrlCreate( + input: DigitalContentUrlCreateInput! + ): DigitalContentUrlCreate productVariantCreate(input: ProductVariantCreateInput!): ProductVariantCreate productVariantDelete(id: ID!): ProductVariantDelete - productVariantBulkCreate(product: ID!, variants: [ProductVariantBulkCreateInput]!): ProductVariantBulkCreate + productVariantBulkCreate( + product: ID! + variants: [ProductVariantBulkCreateInput]! + ): ProductVariantBulkCreate productVariantBulkDelete(ids: [ID]!): ProductVariantBulkDelete - productVariantStocksCreate(stocks: [StockInput!]!, variantId: ID!): ProductVariantStocksCreate - productVariantStocksDelete(variantId: ID!, warehouseIds: [ID!]): ProductVariantStocksDelete - productVariantStocksUpdate(stocks: [StockInput!]!, variantId: ID!): ProductVariantStocksUpdate - productVariantUpdate(id: ID!, input: ProductVariantInput!): ProductVariantUpdate - productVariantTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): ProductVariantTranslate - productVariantUpdateMetadata(id: ID!, input: MetaInput!): ProductVariantUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") - productVariantClearMetadata(id: ID!, input: MetaPath!): ProductVariantClearMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `DeleteMetadata` mutation instead.") - productVariantUpdatePrivateMetadata(id: ID!, input: MetaInput!): ProductVariantUpdatePrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `UpdatePrivateMetadata` mutation instead.") - productVariantClearPrivateMetadata(id: ID!, input: MetaPath!): ProductVariantClearPrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `DeletePrivateMetadata` mutation instead.") + productVariantStocksCreate( + stocks: [StockInput!]! + variantId: ID! + ): ProductVariantStocksCreate + productVariantStocksDelete( + variantId: ID! + warehouseIds: [ID!] + ): ProductVariantStocksDelete + productVariantStocksUpdate( + stocks: [StockInput!]! + variantId: ID! + ): ProductVariantStocksUpdate + productVariantUpdate( + id: ID! + input: ProductVariantInput! + ): ProductVariantUpdate + productVariantTranslate( + id: ID! + input: NameTranslationInput! + languageCode: LanguageCodeEnum! + ): ProductVariantTranslate + productVariantUpdateMetadata( + id: ID! + input: MetaInput! + ): ProductVariantUpdateMeta + @deprecated( + reason: "Use the `updateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + productVariantClearMetadata( + id: ID! + input: MetaPath! + ): ProductVariantClearMeta + @deprecated( + reason: "Use the `deleteMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + productVariantUpdatePrivateMetadata( + id: ID! + input: MetaInput! + ): ProductVariantUpdatePrivateMeta + @deprecated( + reason: "Use the `updatePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + productVariantClearPrivateMetadata( + id: ID! + input: MetaPath! + ): ProductVariantClearPrivateMeta + @deprecated( + reason: "Use the `deletePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) variantImageAssign(imageId: ID!, variantId: ID!): VariantImageAssign variantImageUnassign(imageId: ID!, variantId: ID!): VariantImageUnassign paymentCapture(amount: Decimal, paymentId: ID!): PaymentCapture @@ -2286,40 +2921,92 @@ type Mutation { pageBulkDelete(ids: [ID]!): PageBulkDelete pageBulkPublish(ids: [ID]!, isPublished: Boolean!): PageBulkPublish pageUpdate(id: ID!, input: PageInput!): PageUpdate - pageTranslate(id: ID!, input: PageTranslationInput!, languageCode: LanguageCodeEnum!): PageTranslate + pageTranslate( + id: ID! + input: PageTranslationInput! + languageCode: LanguageCodeEnum! + ): PageTranslate draftOrderComplete(id: ID!): DraftOrderComplete draftOrderCreate(input: DraftOrderCreateInput!): DraftOrderCreate draftOrderDelete(id: ID!): DraftOrderDelete draftOrderBulkDelete(ids: [ID]!): DraftOrderBulkDelete draftOrderLinesBulkDelete(ids: [ID]!): DraftOrderLinesBulkDelete - draftOrderLinesCreate(id: ID!, input: [OrderLineCreateInput]!): DraftOrderLinesCreate + draftOrderLinesCreate( + id: ID! + input: [OrderLineCreateInput]! + ): DraftOrderLinesCreate draftOrderLineDelete(id: ID!): DraftOrderLineDelete draftOrderLineUpdate(id: ID!, input: OrderLineInput!): DraftOrderLineUpdate draftOrderUpdate(id: ID!, input: DraftOrderInput!): DraftOrderUpdate orderAddNote(order: ID!, input: OrderAddNoteInput!): OrderAddNote orderCancel(id: ID!, restock: Boolean!): OrderCancel orderCapture(amount: Decimal!, id: ID!): OrderCapture - orderClearPrivateMeta(id: ID!, input: MetaPath!): OrderClearPrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `DeletePrivateMetadata` mutation instead.") - orderClearMeta(input: MetaPath!, token: UUID!): OrderClearMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `DeleteMetadata` mutation instead.") - orderFulfillmentCancel(id: ID!, input: FulfillmentCancelInput!): FulfillmentCancel - orderFulfillmentCreate(input: FulfillmentCreateInput!, order: ID): FulfillmentCreate - orderFulfillmentUpdateTracking(id: ID!, input: FulfillmentUpdateTrackingInput!): FulfillmentUpdateTracking - orderFulfillmentClearMeta(id: ID!, input: MetaPath!): FulfillmentClearMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `DeleteMetadata` mutation instead.") - orderFulfillmentClearPrivateMeta(id: ID!, input: MetaPath!): FulfillmentClearPrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `DeletePrivateMetadata` mutation instead.") - orderFulfillmentUpdateMeta(id: ID!, input: MetaInput!): FulfillmentUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") - orderFulfillmentUpdatePrivateMeta(id: ID!, input: MetaInput!): FulfillmentUpdatePrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `UpdatePrivateMetadata` mutation instead.") + orderClearPrivateMeta(id: ID!, input: MetaPath!): OrderClearPrivateMeta + @deprecated( + reason: "Use the `deletePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + orderClearMeta(input: MetaPath!, token: UUID!): OrderClearMeta + @deprecated( + reason: "Use the `deleteMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + orderFulfillmentCancel( + id: ID! + input: FulfillmentCancelInput! + ): FulfillmentCancel + orderFulfillmentCreate( + input: FulfillmentCreateInput! + order: ID + ): FulfillmentCreate + orderFulfillmentUpdateTracking( + id: ID! + input: FulfillmentUpdateTrackingInput! + ): FulfillmentUpdateTracking + orderFulfillmentClearMeta(id: ID!, input: MetaPath!): FulfillmentClearMeta + @deprecated( + reason: "Use the `deleteMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + orderFulfillmentClearPrivateMeta( + id: ID! + input: MetaPath! + ): FulfillmentClearPrivateMeta + @deprecated( + reason: "Use the `deletePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + orderFulfillmentUpdateMeta(id: ID!, input: MetaInput!): FulfillmentUpdateMeta + @deprecated( + reason: "Use the `updateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + orderFulfillmentUpdatePrivateMeta( + id: ID! + input: MetaInput! + ): FulfillmentUpdatePrivateMeta + @deprecated( + reason: "Use the `updatePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) orderMarkAsPaid(id: ID!): OrderMarkAsPaid orderRefund(amount: Decimal!, id: ID!): OrderRefund orderUpdate(id: ID!, input: OrderUpdateInput!): OrderUpdate - orderUpdateMeta(input: MetaInput!, token: UUID!): OrderUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") - orderUpdatePrivateMeta(id: ID!, input: MetaInput!): OrderUpdatePrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `UpdatePrivateMetadata` mutation instead.") - orderUpdateShipping(order: ID!, input: OrderUpdateShippingInput): OrderUpdateShipping + orderUpdateMeta(input: MetaInput!, token: UUID!): OrderUpdateMeta + @deprecated( + reason: "Use the `updateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + orderUpdatePrivateMeta(id: ID!, input: MetaInput!): OrderUpdatePrivateMeta + @deprecated( + reason: "Use the `updatePrivateMetadata` mutation instead. This field will be removed after 2020-07-31." + ) + orderUpdateShipping( + order: ID! + input: OrderUpdateShippingInput + ): OrderUpdateShipping orderVoid(id: ID!): OrderVoid orderBulkCancel(ids: [ID]!, restock: Boolean!): OrderBulkCancel deleteMetadata(id: ID!, keys: [String!]!): DeleteMetadata deletePrivateMetadata(id: ID!, keys: [String!]!): DeletePrivateMetadata updateMetadata(id: ID!, input: [MetadataInput!]!): UpdateMetadata - updatePrivateMetadata(id: ID!, input: [MetadataInput!]!): UpdatePrivateMetadata + updatePrivateMetadata( + id: ID! + input: [MetadataInput!]! + ): UpdatePrivateMetadata assignNavigation(menu: ID, navigationType: NavigationType!): AssignNavigation menuCreate(input: MenuCreateInput!): MenuCreate menuDelete(id: ID!): MenuDelete @@ -2329,7 +3016,11 @@ type Mutation { menuItemDelete(id: ID!): MenuItemDelete menuItemBulkDelete(ids: [ID]!): MenuItemBulkDelete menuItemUpdate(id: ID!, input: MenuItemInput!): MenuItemUpdate - menuItemTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): MenuItemTranslate + menuItemTranslate( + id: ID! + input: NameTranslationInput! + languageCode: LanguageCodeEnum! + ): MenuItemTranslate menuItemMove(menu: ID!, moves: [MenuItemMoveInput]!): MenuItemMove giftCardActivate(id: ID!): GiftCardActivate giftCardCreate(input: GiftCardCreateInput!): GiftCardCreate @@ -2342,54 +3033,134 @@ type Mutation { saleUpdate(id: ID!, input: SaleInput!): SaleUpdate saleCataloguesAdd(id: ID!, input: CatalogueInput!): SaleAddCatalogues saleCataloguesRemove(id: ID!, input: CatalogueInput!): SaleRemoveCatalogues - saleTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): SaleTranslate + saleTranslate( + id: ID! + input: NameTranslationInput! + languageCode: LanguageCodeEnum! + ): SaleTranslate voucherCreate(input: VoucherInput!): VoucherCreate voucherDelete(id: ID!): VoucherDelete voucherBulkDelete(ids: [ID]!): VoucherBulkDelete voucherUpdate(id: ID!, input: VoucherInput!): VoucherUpdate voucherCataloguesAdd(id: ID!, input: CatalogueInput!): VoucherAddCatalogues - voucherCataloguesRemove(id: ID!, input: CatalogueInput!): VoucherRemoveCatalogues - voucherTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): VoucherTranslate + voucherCataloguesRemove( + id: ID! + input: CatalogueInput! + ): VoucherRemoveCatalogues + voucherTranslate( + id: ID! + input: NameTranslationInput! + languageCode: LanguageCodeEnum! + ): VoucherTranslate tokenCreate(email: String!, password: String!): CreateToken tokenRefresh(token: String!): Refresh tokenVerify(token: String!): VerifyToken - checkoutAddPromoCode(checkoutId: ID!, promoCode: String!): CheckoutAddPromoCode - checkoutBillingAddressUpdate(billingAddress: AddressInput!, checkoutId: ID!): CheckoutBillingAddressUpdate - checkoutComplete(checkoutId: ID!, redirectUrl: String, storeSource: Boolean = false): CheckoutComplete + checkoutAddPromoCode( + checkoutId: ID! + promoCode: String! + ): CheckoutAddPromoCode + checkoutBillingAddressUpdate( + billingAddress: AddressInput! + checkoutId: ID! + ): CheckoutBillingAddressUpdate + checkoutComplete( + checkoutId: ID! + redirectUrl: String + storeSource: Boolean = false + ): CheckoutComplete checkoutCreate(input: CheckoutCreateInput!): CheckoutCreate - checkoutCustomerAttach(checkoutId: ID!, customerId: ID): CheckoutCustomerAttach + checkoutCustomerAttach( + checkoutId: ID! + customerId: ID + ): CheckoutCustomerAttach checkoutCustomerDetach(checkoutId: ID!): CheckoutCustomerDetach checkoutEmailUpdate(checkoutId: ID, email: String!): CheckoutEmailUpdate checkoutLineDelete(checkoutId: ID!, lineId: ID): CheckoutLineDelete - checkoutLinesAdd(checkoutId: ID!, lines: [CheckoutLineInput]!): CheckoutLinesAdd - checkoutLinesUpdate(checkoutId: ID!, lines: [CheckoutLineInput]!): CheckoutLinesUpdate - checkoutRemovePromoCode(checkoutId: ID!, promoCode: String!): CheckoutRemovePromoCode - checkoutPaymentCreate(checkoutId: ID!, input: PaymentInput!): CheckoutPaymentCreate - checkoutShippingAddressUpdate(checkoutId: ID!, shippingAddress: AddressInput!): CheckoutShippingAddressUpdate - checkoutShippingMethodUpdate(checkoutId: ID, shippingMethodId: ID!): CheckoutShippingMethodUpdate - checkoutUpdateMetadata(id: ID!, input: MetaInput!): CheckoutUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") - checkoutClearMetadata(id: ID!, input: MetaPath!): CheckoutClearMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `DeleteMetadata` mutation instead.") - checkoutUpdatePrivateMetadata(id: ID!, input: MetaInput!): CheckoutUpdatePrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `UpdatePrivateMetadata` mutation instead.") - checkoutClearPrivateMetadata(id: ID!, input: MetaPath!): CheckoutClearPrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `DeletePrivateMetadata` mutation instead.") - requestPasswordReset(email: String!, redirectUrl: String!): RequestPasswordReset + checkoutLinesAdd( + checkoutId: ID! + lines: [CheckoutLineInput]! + ): CheckoutLinesAdd + checkoutLinesUpdate( + checkoutId: ID! + lines: [CheckoutLineInput]! + ): CheckoutLinesUpdate + checkoutRemovePromoCode( + checkoutId: ID! + promoCode: String! + ): CheckoutRemovePromoCode + checkoutPaymentCreate( + checkoutId: ID! + input: PaymentInput! + ): CheckoutPaymentCreate + checkoutShippingAddressUpdate( + checkoutId: ID! + shippingAddress: AddressInput! + ): CheckoutShippingAddressUpdate + checkoutShippingMethodUpdate( + checkoutId: ID + shippingMethodId: ID! + ): CheckoutShippingMethodUpdate + checkoutUpdateMetadata(id: ID!, input: MetaInput!): CheckoutUpdateMeta + @deprecated( + reason: "Use the `updateMetadata` mutation. This field will be removed after 2020-07-31." + ) + checkoutClearMetadata(id: ID!, input: MetaPath!): CheckoutClearMeta + @deprecated( + reason: "Use the `deleteMetadata` mutation. This field will be removed after 2020-07-31." + ) + checkoutUpdatePrivateMetadata( + id: ID! + input: MetaInput! + ): CheckoutUpdatePrivateMeta + @deprecated( + reason: "Use the `updatePrivateMetadata` mutation. This field will be removed after 2020-07-31." + ) + checkoutClearPrivateMetadata( + id: ID! + input: MetaPath! + ): CheckoutClearPrivateMeta + @deprecated( + reason: "Use the `deletePrivateMetadata` mutation. This field will be removed after 2020-07-31." + ) + requestPasswordReset( + email: String! + redirectUrl: String! + ): RequestPasswordReset confirmAccount(email: String!, token: String!): ConfirmAccount setPassword(token: String!, email: String!, password: String!): SetPassword passwordChange(newPassword: String!, oldPassword: String!): PasswordChange - requestEmailChange(newEmail: String!, password: String!, redirectUrl: String!): RequestEmailChange + requestEmailChange( + newEmail: String! + password: String! + redirectUrl: String! + ): RequestEmailChange confirmEmailChange(token: String!): ConfirmEmailChange - accountAddressCreate(input: AddressInput!, type: AddressTypeEnum): AccountAddressCreate + accountAddressCreate( + input: AddressInput! + type: AddressTypeEnum + ): AccountAddressCreate accountAddressUpdate(id: ID!, input: AddressInput!): AccountAddressUpdate accountAddressDelete(id: ID!): AccountAddressDelete - accountSetDefaultAddress(id: ID!, type: AddressTypeEnum!): AccountSetDefaultAddress + accountSetDefaultAddress( + id: ID! + type: AddressTypeEnum! + ): AccountSetDefaultAddress accountRegister(input: AccountRegisterInput!): AccountRegister accountUpdate(input: AccountInput!): AccountUpdate accountRequestDeletion(redirectUrl: String!): AccountRequestDeletion accountDelete(token: String!): AccountDelete - accountUpdateMeta(input: MetaInput!): AccountUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") + accountUpdateMeta(input: MetaInput!): AccountUpdateMeta + @deprecated( + reason: "Use the `updateMetadata` mutation. This field will be removed after 2020-07-31." + ) addressCreate(input: AddressInput!, userId: ID!): AddressCreate addressUpdate(id: ID!, input: AddressInput!): AddressUpdate addressDelete(id: ID!): AddressDelete - addressSetDefault(addressId: ID!, type: AddressTypeEnum!, userId: ID!): AddressSetDefault + addressSetDefault( + addressId: ID! + type: AddressTypeEnum! + userId: ID! + ): AddressSetDefault customerCreate(input: UserCreateInput!): CustomerCreate customerUpdate(id: ID!, input: CustomerInput!): CustomerUpdate customerDelete(id: ID!): CustomerDelete @@ -2401,19 +3172,53 @@ type Mutation { userAvatarUpdate(image: Upload!): UserAvatarUpdate userAvatarDelete: UserAvatarDelete userBulkSetActive(ids: [ID]!, isActive: Boolean!): UserBulkSetActive - userUpdateMetadata(id: ID!, input: MetaInput!): UserUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") - userClearMetadata(id: ID!, input: MetaPath!): UserClearMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `DeleteMetadata` mutation instead.") - userUpdatePrivateMetadata(id: ID!, input: MetaInput!): UserUpdatePrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `UpdatePrivateMetadata` mutation instead.") - userClearPrivateMetadata(id: ID!, input: MetaPath!): UserClearPrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `DeletePrivateMetadata` mutation instead.") + userUpdateMetadata(id: ID!, input: MetaInput!): UserUpdateMeta + @deprecated( + reason: "Use the `updateMetadata` mutation. This field will be removed after 2020-07-31." + ) + userClearMetadata(id: ID!, input: MetaPath!): UserClearMeta + @deprecated( + reason: "Use the `deleteMetadata` mutation. This field will be removed after 2020-07-31." + ) + userUpdatePrivateMetadata(id: ID!, input: MetaInput!): UserUpdatePrivateMeta + @deprecated( + reason: "Use the `updatePrivateMetadata` mutation. This field will be removed after 2020-07-31." + ) + userClearPrivateMetadata(id: ID!, input: MetaPath!): UserClearPrivateMeta + @deprecated( + reason: "Use the `deletePrivateMetadata` mutation. This field will be removed after 2020-07-31." + ) serviceAccountCreate(input: ServiceAccountInput!): ServiceAccountCreate - serviceAccountUpdate(id: ID!, input: ServiceAccountInput!): ServiceAccountUpdate + serviceAccountUpdate( + id: ID! + input: ServiceAccountInput! + ): ServiceAccountUpdate serviceAccountDelete(id: ID!): ServiceAccountDelete - serviceAccountUpdatePrivateMetadata(id: ID!, input: MetaInput!): ServiceAccountUpdatePrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `UpdatePrivateMetadata` mutation instead.") - serviceAccountClearPrivateMetadata(id: ID!, input: MetaPath!): ServiceAccountClearPrivateMeta @deprecated(reason: "Will be removed in Saleor 2.11.Use the `DeletePrivateMetadata` mutation instead.") - serviceAccountTokenCreate(input: ServiceAccountTokenInput!): ServiceAccountTokenCreate + serviceAccountUpdatePrivateMetadata( + id: ID! + input: MetaInput! + ): ServiceAccountUpdatePrivateMeta + @deprecated( + reason: "Use the `updatePrivateMetadata` mutation. This field will be removed after 2020-07-31." + ) + serviceAccountClearPrivateMetadata( + id: ID! + input: MetaPath! + ): ServiceAccountClearPrivateMeta + @deprecated( + reason: "Use the `deletePrivateMetadata` mutation. This field will be removed after 2020-07-31." + ) + serviceAccountTokenCreate( + input: ServiceAccountTokenInput! + ): ServiceAccountTokenCreate serviceAccountTokenDelete(id: ID!): ServiceAccountTokenDelete - permissionGroupCreate(input: PermissionGroupCreateInput!): PermissionGroupCreate - permissionGroupUpdate(id: ID!, input: PermissionGroupUpdateInput!): PermissionGroupUpdate + permissionGroupCreate( + input: PermissionGroupCreateInput! + ): PermissionGroupCreate + permissionGroupUpdate( + id: ID! + input: PermissionGroupUpdateInput! + ): PermissionGroupUpdate permissionGroupDelete(id: ID!): PermissionGroupDelete } @@ -2438,8 +3243,14 @@ interface Node { interface ObjectWithMetadata { privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) } type Order implements Node & ObjectWithMetadata { @@ -2465,8 +3276,14 @@ type Order implements Node & ObjectWithMetadata { weight: Weight privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) fulfillments: [Fulfillment]! lines: [OrderLine]! actions: [OrderAction]! @@ -2496,7 +3313,10 @@ enum OrderAction { } type OrderAddNote { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order event: OrderEvent orderErrors: [OrderError!]! @@ -2507,30 +3327,45 @@ input OrderAddNoteInput { } type OrderBulkCancel { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! orderErrors: [OrderError!]! } type OrderCancel { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order orderErrors: [OrderError!]! } type OrderCapture { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order orderErrors: [OrderError!]! } type OrderClearMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order } type OrderClearPrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order } @@ -2583,6 +3418,7 @@ enum OrderErrorCode { UNIQUE VOID_INACTIVE_PAYMENT ZERO_QUANTITY + INSUFFICIENT_STOCK } type OrderEvent implements Node { @@ -2689,13 +3525,19 @@ input OrderLineInput { } type OrderMarkAsPaid { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order orderErrors: [OrderError!]! } type OrderRefund { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order orderErrors: [OrderError!]! } @@ -2732,7 +3574,10 @@ enum OrderStatusFilter { } type OrderUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) orderErrors: [OrderError!]! order: Order } @@ -2744,17 +3589,26 @@ input OrderUpdateInput { } type OrderUpdateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order } type OrderUpdatePrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order } type OrderUpdateShipping { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order orderErrors: [OrderError!]! } @@ -2764,7 +3618,10 @@ input OrderUpdateShippingInput { } type OrderVoid { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) order: Order orderErrors: [OrderError!]! } @@ -2784,13 +3641,19 @@ type Page implements Node { } type PageBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! pageErrors: [PageError!]! } type PageBulkPublish { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! pageErrors: [PageError!]! } @@ -2807,13 +3670,19 @@ type PageCountableEdge { } type PageCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) pageErrors: [PageError!]! page: Page } type PageDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) pageErrors: [PageError!]! page: Page } @@ -2878,7 +3747,10 @@ type PageTranslatableContent implements Node { } type PageTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) translationErrors: [TranslationError!]! page: PageTranslatableContent } @@ -2902,13 +3774,19 @@ input PageTranslationInput { } type PageUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) pageErrors: [PageError!]! page: Page } type PasswordChange { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! } @@ -2937,7 +3815,10 @@ type Payment implements Node { } type PaymentCapture { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) payment: Payment paymentErrors: [PaymentError!]! } @@ -2991,13 +3872,19 @@ input PaymentInput { } type PaymentRefund { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) payment: Payment paymentErrors: [PaymentError!]! } type PaymentSecureConfirm { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) payment: Payment paymentErrors: [PaymentError!]! } @@ -3008,7 +3895,10 @@ type PaymentSource { } type PaymentVoid { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) payment: Payment paymentErrors: [PaymentError!]! } @@ -3038,7 +3928,10 @@ enum PermissionEnum { } type PermissionGroupCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) group: Group permissionGroupErrors: [PermissionGroupError!]! } @@ -3050,7 +3943,10 @@ input PermissionGroupCreateInput { } type PermissionGroupDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) permissionGroupErrors: [PermissionGroupError!]! group: Group } @@ -3065,7 +3961,7 @@ type PermissionGroupError { enum PermissionGroupErrorCode { ASSIGN_NON_STAFF_MEMBER - CANNOT_ADD_AND_REMOVE + DUPLICATED_INPUT_ITEM CANNOT_REMOVE_FROM_LAST_GROUP LEFT_NOT_MANAGEABLE_PERMISSION OUT_OF_SCOPE_PERMISSION @@ -3088,7 +3984,10 @@ input PermissionGroupSortingInput { } type PermissionGroupUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) group: Group permissionGroupErrors: [PermissionGroupError!]! } @@ -3151,7 +4050,10 @@ input PluginSortingInput { } type PluginUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) plugin: Plugin pluginsErrors: [PluginError!]! } @@ -3183,9 +4085,16 @@ type Product implements Node & ObjectWithMetadata { weight: Weight privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") - url: String! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11.") + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) + url: String! + @deprecated(reason: "This field will be removed after 2020-07-31.") thumbnail(size: Int): Image pricing: ProductPricingInfo isAvailable: Boolean @@ -3210,25 +4119,37 @@ type ProductAttributeError { } type ProductBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! productErrors: [ProductError!]! } type ProductBulkPublish { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! productErrors: [ProductError!]! } type ProductClearMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! product: Product } type ProductClearPrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! product: Product } @@ -3245,7 +4166,10 @@ type ProductCountableEdge { } type ProductCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! product: Product } @@ -3273,7 +4197,10 @@ input ProductCreateInput { } type ProductDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! product: Product } @@ -3289,6 +4216,7 @@ enum ProductErrorCode { ATTRIBUTE_ALREADY_ASSIGNED ATTRIBUTE_CANNOT_BE_ASSIGNED ATTRIBUTE_VARIANTS_DISABLED + DUPLICATED_INPUT_ITEM GRAPHQL_ERROR INVALID NOT_PRODUCTS_IMAGE @@ -3321,13 +4249,19 @@ type ProductImage implements Node { } type ProductImageBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! productErrors: [ProductError!]! } type ProductImageCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) product: Product image: ProductImage productErrors: [ProductError!]! @@ -3340,21 +4274,30 @@ input ProductImageCreateInput { } type ProductImageDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) product: Product image: ProductImage productErrors: [ProductError!]! } type ProductImageReorder { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) product: Product images: [ProductImage] productErrors: [ProductError!]! } type ProductImageUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) product: Product image: ProductImage productErrors: [ProductError!]! @@ -3425,7 +4368,10 @@ type ProductTranslatableContent implements Node { } type ProductTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) translationErrors: [TranslationError!]! product: Product } @@ -3450,30 +4396,56 @@ type ProductType implements Node & ObjectWithMetadata { weight: Weight privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") - products(before: String, after: String, first: Int, last: Int): ProductCountableConnection + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) + products( + before: String + after: String + first: Int + last: Int + ): ProductCountableConnection taxRate: TaxRateType taxType: TaxType variantAttributes: [Attribute] productAttributes: [Attribute] - availableAttributes(filter: AttributeFilterInput, before: String, after: String, first: Int, last: Int): AttributeCountableConnection + availableAttributes( + filter: AttributeFilterInput + before: String + after: String + first: Int + last: Int + ): AttributeCountableConnection } type ProductTypeBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! productErrors: [ProductError!]! } type ProductTypeClearMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productType: ProductType } type ProductTypeClearPrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productType: ProductType } @@ -3495,13 +4467,19 @@ type ProductTypeCountableEdge { } type ProductTypeCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productType: ProductType } type ProductTypeDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productType: ProductType } @@ -3531,7 +4509,10 @@ input ProductTypeInput { } type ProductTypeReorderAttributes { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productType: ProductType productErrors: [ProductError!]! } @@ -3548,37 +4529,55 @@ input ProductTypeSortingInput { } type ProductTypeUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productType: ProductType } type ProductTypeUpdateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productType: ProductType } type ProductTypeUpdatePrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productType: ProductType } type ProductUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! product: Product } type ProductUpdateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! product: Product } type ProductUpdatePrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! product: Product } @@ -3592,14 +4591,32 @@ type ProductVariant implements Node & ObjectWithMetadata { weight: Weight privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") - quantity: Int! @deprecated(reason: "This field will be removed in Saleor 2.11. Use the stock field instead.") - quantityAllocated: Int @deprecated(reason: "This field will be removed in Saleor 2.11. Use the stock field instead.") - stockQuantity: Int! @deprecated(reason: "This field will be removed in Saleor 2.11. Use the stock field instead.") + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) + quantity: Int! + @deprecated( + reason: "Use the stock field instead. This field will be removed after 2020-07-31." + ) + quantityAllocated: Int + @deprecated( + reason: "Use the stock field instead. This field will be removed after 2020-07-31." + ) + stockQuantity: Int! + @deprecated( + reason: "Use the stock field instead. This field will be removed after 2020-07-31." + ) priceOverride: Money pricing: VariantPricingInfo - isAvailable: Boolean @deprecated(reason: "This field will be removed in Saleor 2.11. Use the stock field instead.") + isAvailable: Boolean + @deprecated( + reason: "Use the stock field instead. This field will be removed after 2020-07-31." + ) attributes: [SelectedAttribute!]! costPrice: Money margin: Int @@ -3612,7 +4629,10 @@ type ProductVariant implements Node & ObjectWithMetadata { } type ProductVariantBulkCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! productVariants: [ProductVariant!]! bulkProductErrors: [BulkProductError!]! @@ -3629,19 +4649,28 @@ input ProductVariantBulkCreateInput { } type ProductVariantBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! productErrors: [ProductError!]! } type ProductVariantClearMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productVariant: ProductVariant } type ProductVariantClearPrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productVariant: ProductVariant } @@ -3658,7 +4687,10 @@ type ProductVariantCountableEdge { } type ProductVariantCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productVariant: ProductVariant } @@ -3676,7 +4708,10 @@ input ProductVariantCreateInput { } type ProductVariantDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productVariant: ProductVariant } @@ -3692,19 +4727,28 @@ input ProductVariantInput { } type ProductVariantStocksCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productVariant: ProductVariant bulkStockErrors: [BulkStockError!]! } type ProductVariantStocksDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productVariant: ProductVariant stockErrors: [StockError!]! } type ProductVariantStocksUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productVariant: ProductVariant bulkStockErrors: [BulkStockError!]! } @@ -3717,7 +4761,10 @@ type ProductVariantTranslatableContent implements Node { } type ProductVariantTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) translationErrors: [TranslationError!]! productVariant: ProductVariant } @@ -3729,87 +4776,300 @@ type ProductVariantTranslation implements Node { } type ProductVariantUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productVariant: ProductVariant } type ProductVariantUpdateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productVariant: ProductVariant } type ProductVariantUpdatePrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productErrors: [ProductError!]! productVariant: ProductVariant } type Query { webhook(id: ID!): Webhook - webhooks(sortBy: WebhookSortingInput, filter: WebhookFilterInput, before: String, after: String, first: Int, last: Int): WebhookCountableConnection + webhooks( + sortBy: WebhookSortingInput + filter: WebhookFilterInput + before: String + after: String + first: Int + last: Int + ): WebhookCountableConnection webhookEvents: [WebhookEvent] webhookSamplePayload(eventType: WebhookSampleEventTypeEnum!): JSONString warehouse(id: ID!): Warehouse - warehouses(filter: WarehouseFilterInput, sortBy: WarehouseSortingInput, before: String, after: String, first: Int, last: Int): WarehouseCountableConnection - translations(kind: TranslatableKinds!, before: String, after: String, first: Int, last: Int): TranslatableItemConnection + warehouses( + filter: WarehouseFilterInput + sortBy: WarehouseSortingInput + before: String + after: String + first: Int + last: Int + ): WarehouseCountableConnection + translations( + kind: TranslatableKinds! + before: String + after: String + first: Int + last: Int + ): TranslatableItemConnection translation(id: ID!, kind: TranslatableKinds!): TranslatableItem stock(id: ID!): Stock - stocks(filter: StockFilterInput, before: String, after: String, first: Int, last: Int): StockCountableConnection + stocks( + filter: StockFilterInput + before: String + after: String + first: Int + last: Int + ): StockCountableConnection shop: Shop! shippingZone(id: ID!): ShippingZone - shippingZones(before: String, after: String, first: Int, last: Int): ShippingZoneCountableConnection + shippingZones( + before: String + after: String + first: Int + last: Int + ): ShippingZoneCountableConnection digitalContent(id: ID!): DigitalContent - digitalContents(before: String, after: String, first: Int, last: Int): DigitalContentCountableConnection - attributes(filter: AttributeFilterInput, sortBy: AttributeSortingInput, before: String, after: String, first: Int, last: Int): AttributeCountableConnection + digitalContents( + before: String + after: String + first: Int + last: Int + ): DigitalContentCountableConnection + attributes( + filter: AttributeFilterInput + sortBy: AttributeSortingInput + before: String + after: String + first: Int + last: Int + ): AttributeCountableConnection attribute(id: ID!): Attribute - categories(filter: CategoryFilterInput, sortBy: CategorySortingInput, level: Int, before: String, after: String, first: Int, last: Int): CategoryCountableConnection + categories( + filter: CategoryFilterInput + sortBy: CategorySortingInput + level: Int + before: String + after: String + first: Int + last: Int + ): CategoryCountableConnection category(id: ID!): Category collection(id: ID!): Collection - collections(filter: CollectionFilterInput, sortBy: CollectionSortingInput, before: String, after: String, first: Int, last: Int): CollectionCountableConnection + collections( + filter: CollectionFilterInput + sortBy: CollectionSortingInput + before: String + after: String + first: Int + last: Int + ): CollectionCountableConnection product(id: ID!): Product - products(filter: ProductFilterInput, sortBy: ProductOrder, stockAvailability: StockAvailability, before: String, after: String, first: Int, last: Int): ProductCountableConnection + products( + filter: ProductFilterInput + sortBy: ProductOrder + stockAvailability: StockAvailability + before: String + after: String + first: Int + last: Int + ): ProductCountableConnection productType(id: ID!): ProductType - productTypes(filter: ProductTypeFilterInput, sortBy: ProductTypeSortingInput, before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection + productTypes( + filter: ProductTypeFilterInput + sortBy: ProductTypeSortingInput + before: String + after: String + first: Int + last: Int + ): ProductTypeCountableConnection productVariant(id: ID!): ProductVariant - productVariants(ids: [ID], before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection - reportProductSales(period: ReportingPeriod!, before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection + productVariants( + ids: [ID] + before: String + after: String + first: Int + last: Int + ): ProductVariantCountableConnection + reportProductSales( + period: ReportingPeriod! + before: String + after: String + first: Int + last: Int + ): ProductVariantCountableConnection payment(id: ID!): Payment - payments(before: String, after: String, first: Int, last: Int): PaymentCountableConnection + payments( + before: String + after: String + first: Int + last: Int + ): PaymentCountableConnection page(id: ID, slug: String): Page - pages(sortBy: PageSortingInput, filter: PageFilterInput, before: String, after: String, first: Int, last: Int): PageCountableConnection - homepageEvents(before: String, after: String, first: Int, last: Int): OrderEventCountableConnection + pages( + sortBy: PageSortingInput + filter: PageFilterInput + before: String + after: String + first: Int + last: Int + ): PageCountableConnection + homepageEvents( + before: String + after: String + first: Int + last: Int + ): OrderEventCountableConnection order(id: ID!): Order - orders(sortBy: OrderSortingInput, filter: OrderFilterInput, created: ReportingPeriod, status: OrderStatusFilter, before: String, after: String, first: Int, last: Int): OrderCountableConnection - draftOrders(sortBy: OrderSortingInput, filter: OrderDraftFilterInput, created: ReportingPeriod, before: String, after: String, first: Int, last: Int): OrderCountableConnection + orders( + sortBy: OrderSortingInput + filter: OrderFilterInput + created: ReportingPeriod + status: OrderStatusFilter + before: String + after: String + first: Int + last: Int + ): OrderCountableConnection + draftOrders( + sortBy: OrderSortingInput + filter: OrderDraftFilterInput + created: ReportingPeriod + before: String + after: String + first: Int + last: Int + ): OrderCountableConnection ordersTotal(period: ReportingPeriod): TaxedMoney orderByToken(token: UUID!): Order menu(id: ID, name: String): Menu - menus(sortBy: MenuSortingInput, filter: MenuFilterInput, before: String, after: String, first: Int, last: Int): MenuCountableConnection + menus( + sortBy: MenuSortingInput + filter: MenuFilterInput + before: String + after: String + first: Int + last: Int + ): MenuCountableConnection menuItem(id: ID!): MenuItem - menuItems(sortBy: MenuItemSortingInput, filter: MenuItemFilterInput, before: String, after: String, first: Int, last: Int): MenuItemCountableConnection + menuItems( + sortBy: MenuItemSortingInput + filter: MenuItemFilterInput + before: String + after: String + first: Int + last: Int + ): MenuItemCountableConnection giftCard(id: ID!): GiftCard - giftCards(before: String, after: String, first: Int, last: Int): GiftCardCountableConnection + giftCards( + before: String + after: String + first: Int + last: Int + ): GiftCardCountableConnection plugin(id: ID!): Plugin - plugins(filter: PluginFilterInput, sortBy: PluginSortingInput, before: String, after: String, first: Int, last: Int): PluginCountableConnection + plugins( + filter: PluginFilterInput + sortBy: PluginSortingInput + before: String + after: String + first: Int + last: Int + ): PluginCountableConnection sale(id: ID!): Sale - sales(filter: SaleFilterInput, sortBy: SaleSortingInput, query: String, before: String, after: String, first: Int, last: Int): SaleCountableConnection + sales( + filter: SaleFilterInput + sortBy: SaleSortingInput + query: String + before: String + after: String + first: Int + last: Int + ): SaleCountableConnection voucher(id: ID!): Voucher - vouchers(filter: VoucherFilterInput, sortBy: VoucherSortingInput, query: String, before: String, after: String, first: Int, last: Int): VoucherCountableConnection + vouchers( + filter: VoucherFilterInput + sortBy: VoucherSortingInput + query: String + before: String + after: String + first: Int + last: Int + ): VoucherCountableConnection taxTypes: [TaxType] checkout(token: UUID): Checkout - checkouts(before: String, after: String, first: Int, last: Int): CheckoutCountableConnection + checkouts( + before: String + after: String + first: Int + last: Int + ): CheckoutCountableConnection checkoutLine(id: ID): CheckoutLine - checkoutLines(before: String, after: String, first: Int, last: Int): CheckoutLineCountableConnection - addressValidationRules(countryCode: CountryCode!, countryArea: String, city: String, cityArea: String): AddressValidationData + checkoutLines( + before: String + after: String + first: Int + last: Int + ): CheckoutLineCountableConnection + addressValidationRules( + countryCode: CountryCode! + countryArea: String + city: String + cityArea: String + ): AddressValidationData address(id: ID!): Address - customers(filter: CustomerFilterInput, sortBy: UserSortingInput, before: String, after: String, first: Int, last: Int): UserCountableConnection - permissionGroups(filter: PermissionGroupFilterInput, sortBy: PermissionGroupSortingInput, before: String, after: String, first: Int, last: Int): GroupCountableConnection + customers( + filter: CustomerFilterInput + sortBy: UserSortingInput + before: String + after: String + first: Int + last: Int + ): UserCountableConnection + permissionGroups( + filter: PermissionGroupFilterInput + sortBy: PermissionGroupSortingInput + before: String + after: String + first: Int + last: Int + ): GroupCountableConnection permissionGroup(id: ID!): Group me: User - staffUsers(filter: StaffUserInput, sortBy: UserSortingInput, before: String, after: String, first: Int, last: Int): UserCountableConnection - serviceAccounts(filter: ServiceAccountFilterInput, sortBy: ServiceAccountSortingInput, before: String, after: String, first: Int, last: Int): ServiceAccountCountableConnection + staffUsers( + filter: StaffUserInput + sortBy: UserSortingInput + before: String + after: String + first: Int + last: Int + ): UserCountableConnection + serviceAccounts( + filter: ServiceAccountFilterInput + sortBy: ServiceAccountSortingInput + before: String + after: String + first: Int + last: Int + ): ServiceAccountCountableConnection serviceAccount(id: ID!): ServiceAccount user(id: ID!): User _entities(representations: [_Any]): [_Entity] @@ -3837,13 +5097,19 @@ enum ReportingPeriod { } type RequestEmailChange { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! } type RequestPasswordReset { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! } @@ -3854,20 +5120,41 @@ type Sale implements Node { value: Float! startDate: DateTime! endDate: DateTime - categories(before: String, after: String, first: Int, last: Int): CategoryCountableConnection - collections(before: String, after: String, first: Int, last: Int): CollectionCountableConnection - products(before: String, after: String, first: Int, last: Int): ProductCountableConnection + categories( + before: String + after: String + first: Int + last: Int + ): CategoryCountableConnection + collections( + before: String + after: String + first: Int + last: Int + ): CollectionCountableConnection + products( + before: String + after: String + first: Int + last: Int + ): ProductCountableConnection translation(languageCode: LanguageCodeEnum!): SaleTranslation } type SaleAddCatalogues { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) sale: Sale discountErrors: [DiscountError!]! } type SaleBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! discountErrors: [DiscountError!]! } @@ -3884,13 +5171,19 @@ type SaleCountableEdge { } type SaleCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) discountErrors: [DiscountError!]! sale: Sale } type SaleDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) discountErrors: [DiscountError!]! sale: Sale } @@ -3914,7 +5207,10 @@ input SaleInput { } type SaleRemoveCatalogues { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) sale: Sale discountErrors: [DiscountError!]! } @@ -3940,7 +5236,10 @@ type SaleTranslatableContent implements Node { } type SaleTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) translationErrors: [TranslationError!]! sale: Sale } @@ -3957,7 +5256,10 @@ enum SaleType { } type SaleUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) discountErrors: [DiscountError!]! sale: Sale } @@ -3981,12 +5283,21 @@ type ServiceAccount implements Node & ObjectWithMetadata { tokens: [ServiceAccountToken] privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) } type ServiceAccountClearPrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! serviceAccount: ServiceAccount } @@ -4003,14 +5314,20 @@ type ServiceAccountCountableEdge { } type ServiceAccountCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) authToken: String accountErrors: [AccountError!]! serviceAccount: ServiceAccount } type ServiceAccountDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! serviceAccount: ServiceAccount } @@ -4043,14 +5360,20 @@ type ServiceAccountToken implements Node { } type ServiceAccountTokenCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) authToken: String accountErrors: [AccountError!]! serviceAccountToken: ServiceAccountToken } type ServiceAccountTokenDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! serviceAccountToken: ServiceAccountToken } @@ -4061,20 +5384,29 @@ input ServiceAccountTokenInput { } type ServiceAccountUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! serviceAccount: ServiceAccount } type ServiceAccountUpdatePrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! serviceAccount: ServiceAccount } type SetPassword { token: String - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } @@ -4094,7 +5426,7 @@ enum ShippingErrorCode { NOT_FOUND REQUIRED UNIQUE - CANNOT_ADD_AND_REMOVE + DUPLICATED_INPUT_ITEM } type ShippingMethod implements Node { @@ -4128,20 +5460,29 @@ enum ShippingMethodTypeEnum { } type ShippingPriceBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! shippingErrors: [ShippingError!]! } type ShippingPriceCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shippingZone: ShippingZone shippingErrors: [ShippingError!]! shippingMethod: ShippingMethod } type ShippingPriceDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shippingMethod: ShippingMethod shippingZone: ShippingZone shippingErrors: [ShippingError!]! @@ -4159,13 +5500,19 @@ input ShippingPriceInput { } type ShippingPriceTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) translationErrors: [TranslationError!]! shippingMethod: ShippingMethod } type ShippingPriceUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shippingZone: ShippingZone shippingErrors: [ShippingError!]! shippingMethod: ShippingMethod @@ -4182,7 +5529,10 @@ type ShippingZone implements Node { } type ShippingZoneBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! shippingErrors: [ShippingError!]! } @@ -4199,7 +5549,10 @@ type ShippingZoneCountableEdge { } type ShippingZoneCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shippingZone: ShippingZone shippingErrors: [ShippingError!]! } @@ -4212,13 +5565,19 @@ input ShippingZoneCreateInput { } type ShippingZoneDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shippingErrors: [ShippingError!]! shippingZone: ShippingZone } type ShippingZoneUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shippingZone: ShippingZone shippingErrors: [ShippingError!]! } @@ -4264,13 +5623,19 @@ type Shop { } type ShopAddressUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shop: Shop shopErrors: [ShopError!]! } type ShopDomainUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shop: Shop shopErrors: [ShopError!]! } @@ -4292,7 +5657,10 @@ enum ShopErrorCode { } type ShopFetchTaxRates { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shop: Shop shopErrors: [ShopError!]! } @@ -4314,7 +5682,10 @@ input ShopSettingsInput { } type ShopSettingsTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shop: Shop translationErrors: [TranslationError!]! } @@ -4325,7 +5696,10 @@ input ShopSettingsTranslationInput { } type ShopSettingsUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shop: Shop shopErrors: [ShopError!]! } @@ -4343,13 +5717,19 @@ input SiteDomainInput { } type StaffBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! staffErrors: [StaffError!]! } type StaffCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) staffErrors: [StaffError!]! user: User } @@ -4365,7 +5745,10 @@ input StaffCreateInput { } type StaffDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) staffErrors: [StaffError!]! user: User } @@ -4392,13 +5775,19 @@ type StaffNotificationRecipient implements Node { } type StaffNotificationRecipientCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shopErrors: [ShopError!]! staffNotificationRecipient: StaffNotificationRecipient } type StaffNotificationRecipientDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shopErrors: [ShopError!]! staffNotificationRecipient: StaffNotificationRecipient } @@ -4410,13 +5799,19 @@ input StaffNotificationRecipientInput { } type StaffNotificationRecipientUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) shopErrors: [ShopError!]! staffNotificationRecipient: StaffNotificationRecipient } type StaffUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) staffErrors: [StaffError!]! user: User } @@ -4565,7 +5960,18 @@ enum TransactionKind { CONFIRM } -union TranslatableItem = ProductTranslatableContent | CollectionTranslatableContent | CategoryTranslatableContent | AttributeTranslatableContent | AttributeValueTranslatableContent | ProductVariantTranslatableContent | PageTranslatableContent | ShippingMethodTranslatableContent | SaleTranslatableContent | VoucherTranslatableContent | MenuItemTranslatableContent +union TranslatableItem = + ProductTranslatableContent + | CollectionTranslatableContent + | CategoryTranslatableContent + | AttributeTranslatableContent + | AttributeValueTranslatableContent + | ProductVariantTranslatableContent + | PageTranslatableContent + | ShippingMethodTranslatableContent + | SaleTranslatableContent + | VoucherTranslatableContent + | MenuItemTranslatableContent type TranslatableItemConnection { pageInfo: PageInfo! @@ -4615,13 +6021,19 @@ input TranslationInput { scalar UUID type UpdateMetadata { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) metadataErrors: [MetadataError!]! item: ObjectWithMetadata } type UpdatePrivateMetadata { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) metadataErrors: [MetadataError!]! item: ObjectWithMetadata } @@ -4642,13 +6054,32 @@ type User implements Node & ObjectWithMetadata { defaultBillingAddress: Address privateMetadata: [MetadataItem]! metadata: [MetadataItem]! - privateMeta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `privetaMetadata` field instead. ") - meta: [MetaStore]! @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.11. use the `metadata` field instead. ") + privateMeta: [MetaStore]! + @deprecated( + reason: "Use the `privetaMetadata` field. This field will be removed after 2020-07-31." + ) + meta: [MetaStore]! + @deprecated( + reason: "Use the `metadata` field. This field will be removed after 2020-07-31." + ) addresses: [Address] checkout: Checkout - giftCards(before: String, after: String, first: Int, last: Int): GiftCardCountableConnection - orders(before: String, after: String, first: Int, last: Int): OrderCountableConnection - permissions: [Permission] @deprecated(reason: "Will be removed in Saleor 2.11.Use the `userPermissions` instead.") + giftCards( + before: String + after: String + first: Int + last: Int + ): GiftCardCountableConnection + orders( + before: String + after: String + first: Int + last: Int + ): OrderCountableConnection + permissions: [Permission] + @deprecated( + reason: "Will be removed in Saleor 2.11.Use the `userPermissions` instead." + ) userPermissions: [UserPermission] permissionGroups: [Group] editableGroups: [Group] @@ -4658,31 +6089,46 @@ type User implements Node & ObjectWithMetadata { } type UserAvatarDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! } type UserAvatarUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) user: User accountErrors: [AccountError!]! } type UserBulkSetActive { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! accountErrors: [AccountError!]! } type UserClearMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } type UserClearPrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } @@ -4728,13 +6174,19 @@ input UserSortingInput { } type UserUpdateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } type UserUpdatePrivateMeta { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) accountErrors: [AccountError!]! user: User } @@ -4746,14 +6198,20 @@ type VAT { } type VariantImageAssign { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productVariant: ProductVariant image: ProductImage productErrors: [ProductError!]! } type VariantImageUnassign { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) productVariant: ProductVariant image: ProductImage productErrors: [ProductError!]! @@ -4788,21 +6246,42 @@ type Voucher implements Node { discountValue: Float! minSpent: Money minCheckoutItemsQuantity: Int - categories(before: String, after: String, first: Int, last: Int): CategoryCountableConnection - collections(before: String, after: String, first: Int, last: Int): CollectionCountableConnection - products(before: String, after: String, first: Int, last: Int): ProductCountableConnection + categories( + before: String + after: String + first: Int + last: Int + ): CategoryCountableConnection + collections( + before: String + after: String + first: Int + last: Int + ): CollectionCountableConnection + products( + before: String + after: String + first: Int + last: Int + ): ProductCountableConnection countries: [CountryDisplay] translation(languageCode: LanguageCodeEnum!): VoucherTranslation } type VoucherAddCatalogues { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) voucher: Voucher discountErrors: [DiscountError!]! } type VoucherBulkDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) count: Int! discountErrors: [DiscountError!]! } @@ -4819,13 +6298,19 @@ type VoucherCountableEdge { } type VoucherCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) discountErrors: [DiscountError!]! voucher: Voucher } type VoucherDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) discountErrors: [DiscountError!]! voucher: Voucher } @@ -4864,7 +6349,10 @@ input VoucherInput { } type VoucherRemoveCatalogues { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) voucher: Voucher discountErrors: [DiscountError!]! } @@ -4892,7 +6380,10 @@ type VoucherTranslatableContent implements Node { } type VoucherTranslate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) translationErrors: [TranslationError!]! voucher: Voucher } @@ -4910,7 +6401,10 @@ enum VoucherTypeEnum { } type VoucherUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) discountErrors: [DiscountError!]! voucher: Voucher } @@ -4920,7 +6414,12 @@ type Warehouse implements Node { name: String! slug: String! companyName: String! - shippingZones(before: String, after: String, first: Int, last: Int): ShippingZoneCountableConnection! + shippingZones( + before: String + after: String + first: Int + last: Int + ): ShippingZoneCountableConnection! address: Address! email: String! } @@ -4948,7 +6447,10 @@ type WarehouseCountableEdge { } type WarehouseCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) warehouseErrors: [WarehouseError!]! warehouse: Warehouse } @@ -4963,7 +6465,10 @@ input WarehouseCreateInput { } type WarehouseDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) warehouseErrors: [WarehouseError!]! warehouse: Warehouse } @@ -4989,13 +6494,19 @@ input WarehouseFilterInput { } type WarehouseShippingZoneAssign { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) warehouse: Warehouse warehouseErrors: [WarehouseError!]! } type WarehouseShippingZoneUnassign { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) warehouse: Warehouse warehouseErrors: [WarehouseError!]! } @@ -5010,7 +6521,10 @@ input WarehouseSortingInput { } type WarehouseUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) warehouseErrors: [WarehouseError!]! warehouse: Warehouse } @@ -5045,7 +6559,10 @@ type WebhookCountableEdge { } type WebhookCreate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) webhookErrors: [WebhookError!]! webhook: Webhook } @@ -5060,7 +6577,10 @@ input WebhookCreateInput { } type WebhookDelete { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) webhook: Webhook webhookErrors: [WebhookError!]! } @@ -5126,7 +6646,10 @@ input WebhookSortingInput { } type WebhookUpdate { - errors: [Error!]! @deprecated(reason: "Use typed errors with error codes.") + errors: [Error!]! + @deprecated( + reason: "Use typed errors with error codes. This field will be removed after 2020-07-31." + ) webhook: Webhook webhookErrors: [WebhookError!]! } @@ -5156,7 +6679,17 @@ enum WeightUnitsEnum { scalar _Any -union _Entity = Address | ServiceAccount | User | Group | ProductVariant | Product | ProductType | Collection | Category | ProductImage +union _Entity = + Address + | ServiceAccount + | User + | Group + | ProductVariant + | Product + | ProductType + | Collection + | Category + | ProductImage type _Service { sdl: String diff --git a/src/auth/misc.ts b/src/auth/misc.ts index 975ffc5cf..d886c35cb 100644 --- a/src/auth/misc.ts +++ b/src/auth/misc.ts @@ -2,4 +2,4 @@ import { PermissionEnum } from "../types/globalTypes"; import { User } from "./types/User"; export const hasPermission = (permission: PermissionEnum, user: User) => - user.permissions.map(perm => perm.code).includes(permission); + user.userPermissions.map(perm => perm.code).includes(permission); diff --git a/src/auth/mutations.ts b/src/auth/mutations.ts index 60ca7b91b..e5480427b 100644 --- a/src/auth/mutations.ts +++ b/src/auth/mutations.ts @@ -1,6 +1,6 @@ import gql from "graphql-tag"; -import { accountFragmentError } from "@saleor/customers/mutations"; +import { accountErrorFragment } from "@saleor/customers/mutations"; import { TypedMutation } from "../mutations"; import { RequestPasswordReset, @@ -16,7 +16,7 @@ export const fragmentUser = gql` email firstName lastName - permissions { + userPermissions { code name } @@ -65,7 +65,7 @@ export const TypedVerifyTokenMutation = TypedMutation< >(tokenVerifyMutation); export const requestPasswordReset = gql` - ${accountFragmentError} + ${accountErrorFragment} mutation RequestPasswordReset($email: String!, $redirectUrl: String!) { requestPasswordReset(email: $email, redirectUrl: $redirectUrl) { errors: accountErrors { @@ -80,7 +80,7 @@ export const RequestPasswordResetMutation = TypedMutation< >(requestPasswordReset); export const setPassword = gql` - ${accountFragmentError} + ${accountErrorFragment} ${fragmentUser} mutation SetPassword($email: String!, $password: String!, $token: String!) { setPassword(email: $email, password: $password, token: $token) { diff --git a/src/auth/types/SetPassword.ts b/src/auth/types/SetPassword.ts index 60c480343..9a09baf70 100644 --- a/src/auth/types/SetPassword.ts +++ b/src/auth/types/SetPassword.ts @@ -14,8 +14,8 @@ export interface SetPassword_setPassword_errors { field: string | null; } -export interface SetPassword_setPassword_user_permissions { - __typename: "Permission"; +export interface SetPassword_setPassword_user_userPermissions { + __typename: "UserPermission"; code: PermissionEnum; name: string; } @@ -31,7 +31,7 @@ export interface SetPassword_setPassword_user { email: string; firstName: string; lastName: string; - permissions: (SetPassword_setPassword_user_permissions | null)[] | null; + userPermissions: (SetPassword_setPassword_user_userPermissions | null)[] | null; avatar: SetPassword_setPassword_user_avatar | null; } diff --git a/src/auth/types/TokenAuth.ts b/src/auth/types/TokenAuth.ts index 3ebad9151..8d72009a9 100644 --- a/src/auth/types/TokenAuth.ts +++ b/src/auth/types/TokenAuth.ts @@ -14,8 +14,8 @@ export interface TokenAuth_tokenCreate_errors { message: string | null; } -export interface TokenAuth_tokenCreate_user_permissions { - __typename: "Permission"; +export interface TokenAuth_tokenCreate_user_userPermissions { + __typename: "UserPermission"; code: PermissionEnum; name: string; } @@ -31,7 +31,7 @@ export interface TokenAuth_tokenCreate_user { email: string; firstName: string; lastName: string; - permissions: (TokenAuth_tokenCreate_user_permissions | null)[] | null; + userPermissions: (TokenAuth_tokenCreate_user_userPermissions | null)[] | null; avatar: TokenAuth_tokenCreate_user_avatar | null; } diff --git a/src/auth/types/User.ts b/src/auth/types/User.ts index 5fc9a5260..d504cc3e2 100644 --- a/src/auth/types/User.ts +++ b/src/auth/types/User.ts @@ -8,8 +8,8 @@ import { PermissionEnum } from "./../../types/globalTypes"; // GraphQL fragment: User // ==================================================== -export interface User_permissions { - __typename: "Permission"; +export interface User_userPermissions { + __typename: "UserPermission"; code: PermissionEnum; name: string; } @@ -25,6 +25,6 @@ export interface User { email: string; firstName: string; lastName: string; - permissions: (User_permissions | null)[] | null; + userPermissions: (User_userPermissions | null)[] | null; avatar: User_avatar | null; } diff --git a/src/auth/types/VerifyToken.ts b/src/auth/types/VerifyToken.ts index 4cec7649d..10ece6970 100644 --- a/src/auth/types/VerifyToken.ts +++ b/src/auth/types/VerifyToken.ts @@ -8,8 +8,8 @@ import { PermissionEnum } from "./../../types/globalTypes"; // GraphQL mutation operation: VerifyToken // ==================================================== -export interface VerifyToken_tokenVerify_user_permissions { - __typename: "Permission"; +export interface VerifyToken_tokenVerify_user_userPermissions { + __typename: "UserPermission"; code: PermissionEnum; name: string; } @@ -25,7 +25,7 @@ export interface VerifyToken_tokenVerify_user { email: string; firstName: string; lastName: string; - permissions: (VerifyToken_tokenVerify_user_permissions | null)[] | null; + userPermissions: (VerifyToken_tokenVerify_user_userPermissions | null)[] | null; avatar: VerifyToken_tokenVerify_user_avatar | null; } diff --git a/src/components/AccountPermissionGroups/AccountPermissionGroups.tsx b/src/components/AccountPermissionGroups/AccountPermissionGroups.tsx new file mode 100644 index 000000000..2123b5cf7 --- /dev/null +++ b/src/components/AccountPermissionGroups/AccountPermissionGroups.tsx @@ -0,0 +1,79 @@ +import React from "react"; +import { useIntl } from "react-intl"; + +import Typography from "@material-ui/core/Typography"; +import { FormChange } from "@saleor/hooks/useForm"; +import { SearchPermissionGroups_search_edges_node } from "@saleor/searches/types/SearchPermissionGroups"; +import { FetchMoreProps, SearchPageProps } from "@saleor/types"; +import { getFormErrors } from "@saleor/utils/errors"; +import { StaffErrorFragment } from "@saleor/staff/types/StaffErrorFragment"; +import getStaffErrorMessage from "@saleor/utils/errors/staff"; +import MultiAutocompleteSelectField, { + MultiAutocompleteChoiceType +} from "../MultiAutocompleteSelectField"; + +interface AccountPermissionGroupsProps extends FetchMoreProps, SearchPageProps { + formData: { + permissionGroups: string[]; + }; + disabled: boolean; + errors: StaffErrorFragment[]; + availablePermissionGroups: SearchPermissionGroups_search_edges_node[]; + onChange: FormChange; + displayValues: MultiAutocompleteChoiceType[]; +} + +const AccountPermissionGroups: React.FC = props => { + const { + availablePermissionGroups, + disabled, + displayValues, + errors, + formData, + hasMore, + loading, + onChange, + onFetchMore, + onSearchChange + } = props; + + const intl = useIntl(); + + const choices = availablePermissionGroups?.map(pg => ({ + label: pg.name, + value: pg.id + })); + const formErrors = getFormErrors(["addGroups", "removeGroups"], errors); + return ( + <> + + {!!formErrors.addGroups && ( + + {getStaffErrorMessage(formErrors.addGroups, intl)} + + )} + {!!formErrors.removeGroups && ( + + {getStaffErrorMessage(formErrors.removeGroups, intl)} + + )} + + ); +}; + +AccountPermissionGroups.displayName = "AccountPermissionGroups"; +export default AccountPermissionGroups; diff --git a/src/components/AccountPermissionGroups/index.ts b/src/components/AccountPermissionGroups/index.ts new file mode 100644 index 000000000..4a0d190a7 --- /dev/null +++ b/src/components/AccountPermissionGroups/index.ts @@ -0,0 +1,2 @@ +export { default } from "./AccountPermissionGroups"; +export * from "./AccountPermissionGroups"; diff --git a/src/components/AccountPermissions/AccountPermissions.tsx b/src/components/AccountPermissions/AccountPermissions.tsx index d518673da..1bcc23822 100644 --- a/src/components/AccountPermissions/AccountPermissions.tsx +++ b/src/components/AccountPermissions/AccountPermissions.tsx @@ -1,14 +1,20 @@ +import React from "react"; +import { useIntl } from "react-intl"; import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; import { makeStyles } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; - import CardTitle from "@saleor/components/CardTitle"; -import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox"; -import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInfo"; import Skeleton from "@saleor/components/Skeleton"; +import { + List, + ListItem, + ListItemText, + ListItemIcon, + Checkbox +} from "@material-ui/core"; +import useUser from "@saleor/hooks/useUser"; +import { PermissionData } from "@saleor/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage"; const useStyles = makeStyles( theme => ({ @@ -27,40 +33,60 @@ const useStyles = makeStyles( ); interface AccountPermissionsProps { - permissions: ShopInfo_shop_permissions[]; + permissions: PermissionData[]; + permissionsExceeded: boolean; data: { hasFullAccess: boolean; permissions: string[]; }; disabled: boolean; + description: string; + errorMessage: string; + fullAccessLabel: string; onChange: (event: React.ChangeEvent, cb?: () => void) => void; } const AccountPermissions: React.FC = props => { - const { data, disabled, permissions, onChange } = props; + const { + data, + disabled, + permissions, + permissionsExceeded, + onChange, + description, + fullAccessLabel, + errorMessage + } = props; const classes = useStyles(props); const intl = useIntl(); + const { user } = useUser(); - const handleFullAccessChange = (event: React.ChangeEvent) => - onChange(event, () => - onChange({ - target: { - name: "permissions", - value: event.target.value ? permissions.map(perm => perm.code) : [] - } - } as any) - ); - const handlePermissionChange = (event: React.ChangeEvent) => { + const handleFullAccessChange = () => { onChange({ target: { name: "permissions", - value: event.target.value - ? data.permissions.concat([event.target.name]) - : data.permissions.filter(perm => perm !== event.target.name) + value: !data.hasFullAccess ? permissions.map(perm => perm.code) : [] + } + } as any); + onChange({ + target: { + name: "hasFullAccess", + value: !data.hasFullAccess } } as any); }; + const handlePermissionChange = (key, value) => () => { + onChange({ + target: { + name: "permissions", + value: !value + ? data.permissions.concat([key]) + : data.permissions.filter(perm => perm !== key) + } + } as any); + }; + return ( = props => { description: "dialog header" })} /> - - - - -
- -
-
- {!data.hasFullAccess && ( + {permissionsExceeded && ( <> + + + {intl.formatMessage({ + defaultMessage: + "This groups permissions exceeds your own. You are able only to manage permissions that you have.", + description: "exceeded permissions description" + })} + +
- {permissions === undefined ? ( - - ) : ( - permissions.map(perm => ( -
- userPerm === perm.code - ).length === 1 - } - disabled={disabled} - label={perm.name.replace(/\./, "")} - name={perm.code} - onChange={handlePermissionChange} - /> -
- )) - )} + + {intl.formatMessage({ + defaultMessage: "Available permissions", + description: "card section description" + })} + + + {user.userPermissions.map(perm => ( + + + + ))} +
)} + {!permissionsExceeded && ( + <> + + {description} + + + + + + + + {!data.hasFullAccess && ( + <> +
+ + {permissions === undefined ? ( + + ) : ( + permissions.map(perm => ( + userPerm === perm.code + ).length === 1 + )} + > + + userPerm === perm.code + ).length === 1 + } + tabIndex={-1} + disableRipple + name={perm.code} + inputProps={{ "aria-labelledby": perm.code }} + /> + + + + )) + )} + + + )} + {!!errorMessage && ( + <> +
+ + {errorMessage} + + + )} + + )}
); }; diff --git a/src/components/AccountPermissions/utils.ts b/src/components/AccountPermissions/utils.ts new file mode 100644 index 000000000..10f2f5a57 --- /dev/null +++ b/src/components/AccountPermissions/utils.ts @@ -0,0 +1,30 @@ +import { User_userPermissions } from "@saleor/auth/types/User"; +import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInfo"; +import { PermissionGroupDetails_user_userPermissions } from "@saleor/permissionGroups/types/PermissionGroupDetails"; + +export const getLastSourcesOfPermission = ( + groupId: string, + userPermissions: PermissionGroupDetails_user_userPermissions[] +) => + userPermissions + .filter( + perm => + perm.sourcePermissionGroups.length === 1 && + perm.sourcePermissionGroups[0].id === groupId + ) + .map(perm => perm.code); + +export const getPermissionsComponentChoices = ( + userPermissions: User_userPermissions[], + shopPermissions: ShopInfo_shop_permissions[], + lastSourcesOfPermissionIds: string[] +) => { + const userCodes = userPermissions.map(p => p.code) || []; + + return shopPermissions.map(perm => ({ + ...perm, + __typename: "PermissionData", + disabled: !userCodes.includes(perm.code), + lastSource: lastSourcesOfPermissionIds.includes(perm.code) + })); +}; diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index 4b7dc68e5..0decf3a2b 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -25,7 +25,6 @@ import useNavigator from "@saleor/hooks/useNavigator"; import useTheme from "@saleor/hooks/useTheme"; import useUser from "@saleor/hooks/useUser"; import ArrowDropdown from "@saleor/icons/ArrowDropdown"; -import { maybe } from "@saleor/misc"; import { staffMemberDetailsUrl } from "@saleor/staff/urls"; import Container from "../Container"; import ErrorPage from "../ErrorPage"; @@ -314,7 +313,7 @@ const AppLayout: React.FC = ({ children }) => { const menuStructure = createMenuStructure(intl); const configurationMenu = createConfigurationMenu(intl); - const userPermissions = maybe(() => user.permissions, []); + const userPermissions = user?.userPermissions || []; const renderConfigure = configurationMenu.some(section => section.menuItems.some( diff --git a/src/components/AppLayout/MenuList.tsx b/src/components/AppLayout/MenuList.tsx index a77814a1f..2ca3d9171 100644 --- a/src/components/AppLayout/MenuList.tsx +++ b/src/components/AppLayout/MenuList.tsx @@ -195,7 +195,7 @@ const MenuList: React.FC = props => { const configutationMenu = createConfigurationMenu(intl).map(menu => { menu.menuItems.map(item => - user.permissions.map(perm => perm.code).includes(item.permission) + user.userPermissions.map(perm => perm.code).includes(item.permission) ); }); @@ -238,7 +238,9 @@ const MenuList: React.FC = props => { if ( menuItem.permission && - !user.permissions.map(perm => perm.code).includes(menuItem.permission) + !user.userPermissions + .map(perm => perm.code) + .includes(menuItem.permission) ) { return null; } diff --git a/src/components/IconButtonTableCell/IconButtonTableCell.tsx b/src/components/IconButtonTableCell/IconButtonTableCell.tsx index 46d9070a2..b5d427cb1 100644 --- a/src/components/IconButtonTableCell/IconButtonTableCell.tsx +++ b/src/components/IconButtonTableCell/IconButtonTableCell.tsx @@ -2,12 +2,14 @@ import IconButton from "@material-ui/core/IconButton"; import { makeStyles } from "@material-ui/core/styles"; import TableCell from "@material-ui/core/TableCell"; import React from "react"; +import classNames from "classnames"; import { stopPropagation } from "../../misc"; import { ICONBUTTON_SIZE } from "../../theme"; export interface IconButtonTableCellProps { children: React.ReactNode; + className?: string; disabled?: boolean; onClick: () => void; } @@ -25,17 +27,12 @@ const useStyles = makeStyles( { name: "IconButtonTableCell" } ); const IconButtonTableCell: React.FC = props => { - const { - children, - - disabled, - onClick - } = props; + const { children, className, disabled, onClick } = props; const classes = useStyles(props); return ( - + {displayValues.map(value => (
-
+
{value.label} - handleSelect(value.value)} - > - - + {!value.disabled && ( + handleSelect(value.value)} + > + + + )}
))} diff --git a/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectFieldContent.tsx b/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectFieldContent.tsx index 2f3032c11..0d93d9c59 100644 --- a/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectFieldContent.tsx +++ b/src/components/MultiAutocompleteSelectField/MultiAutocompleteSelectFieldContent.tsx @@ -1,8 +1,3 @@ -import CircularProgress from "@material-ui/core/CircularProgress"; -import MenuItem from "@material-ui/core/MenuItem"; -import Paper from "@material-ui/core/Paper"; -import { makeStyles } from "@material-ui/core/styles"; -import AddIcon from "@material-ui/icons/Add"; import classNames from "classnames"; import { GetItemPropsOptions } from "downshift"; import React from "react"; @@ -10,11 +5,17 @@ import SVG from "react-inlinesvg"; import { FormattedMessage } from "react-intl"; import chevronDown from "@assets/images/ChevronDown.svg"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import MenuItem from "@material-ui/core/MenuItem"; +import Paper from "@material-ui/core/Paper"; +import { makeStyles } from "@material-ui/core/styles"; +import AddIcon from "@material-ui/icons/Add"; import Checkbox from "@saleor/components/Checkbox"; import useElementScroll, { isScrolledToBottom } from "@saleor/hooks/useElementScroll"; import { FetchMoreProps } from "@saleor/types"; + import Hr from "../Hr"; const menuItemHeight = 46; @@ -24,6 +25,7 @@ const offset = 24; export interface MultiAutocompleteChoiceType { label: string; value: any; + disabled?: boolean; } export interface MultiAutocompleteSelectFieldContentProps extends Partial { @@ -66,7 +68,7 @@ const useStyles = makeStyles( }, content: { maxHeight: menuItemHeight * maxMenuItems + theme.spacing(2), - overflow: "scroll", + overflowY: "scroll", padding: 8 }, hide: { @@ -141,9 +143,7 @@ function getChoiceIndex( return choiceIndex; } -const MultiAutocompleteSelectFieldContent: React.FC< - MultiAutocompleteSelectFieldContentProps -> = props => { +const MultiAutocompleteSelectFieldContent: React.FC = props => { const { choices, displayCustomValue, @@ -210,6 +210,7 @@ const MultiAutocompleteSelectFieldContent: React.FC< className={classes.menuItem} key={value.value} selected={true} + disabled={value.disabled} component="div" {...getItemProps({ item: value.value @@ -219,6 +220,7 @@ const MultiAutocompleteSelectFieldContent: React.FC< {value.label} @@ -239,6 +241,7 @@ const MultiAutocompleteSelectFieldContent: React.FC< className={classes.menuItem} key={suggestion.value} selected={highlightedIndex === choiceIndex} + disabled={suggestion.disabled} component="div" {...getItemProps({ index: choiceIndex, diff --git a/src/components/Navigator/modes/commands/actions.ts b/src/components/Navigator/modes/commands/actions.ts index 07fad303f..6a85d568a 100644 --- a/src/components/Navigator/modes/commands/actions.ts +++ b/src/components/Navigator/modes/commands/actions.ts @@ -2,6 +2,7 @@ import { score } from "fuzzaldrin"; import { IntlShape } from "react-intl"; import { categoryAddUrl } from "@saleor/categories/urls"; +import { permissionGroupAddUrl } from "@saleor/permissionGroups/urls"; import { collectionAddUrl } from "@saleor/collections/urls"; import { customerAddUrl } from "@saleor/customers/urls"; import { voucherAddUrl } from "@saleor/discounts/urls"; @@ -49,6 +50,13 @@ export function searchInCommands( return false; } }, + { + label: intl.formatMessage(messages.createPermissionGroup), + onClick: () => { + navigate(permissionGroupAddUrl); + return false; + } + }, { label: intl.formatMessage(messages.createCustomer), onClick: () => { diff --git a/src/components/Navigator/modes/messages.ts b/src/components/Navigator/modes/messages.ts index 6d3fef9d3..5793db07b 100644 --- a/src/components/Navigator/modes/messages.ts +++ b/src/components/Navigator/modes/messages.ts @@ -31,6 +31,10 @@ const messages = defineMessages({ defaultMessage: "Create Order", description: "button" }, + createPermissionGroup: { + defaultMessage: "Create Permission Group", + description: "button" + }, createProduct: { defaultMessage: "Create Product", description: "button" diff --git a/src/components/RequirePermissions.tsx b/src/components/RequirePermissions.tsx index 176ec0076..db6043255 100644 --- a/src/components/RequirePermissions.tsx +++ b/src/components/RequirePermissions.tsx @@ -1,10 +1,10 @@ import React from "react"; -import { User_permissions } from "@saleor/auth/types/User"; +import { User_userPermissions } from "@saleor/auth/types/User"; import { PermissionEnum } from "@saleor/types/globalTypes"; export function hasPermissions( - userPermissions: User_permissions[], + userPermissions: User_userPermissions[], requiredPermissions: PermissionEnum[] ): boolean { return requiredPermissions.reduce( @@ -17,7 +17,7 @@ export function hasPermissions( export interface RequirePermissionsProps { children: React.ReactNode | React.ReactNodeArray; requiredPermissions: PermissionEnum[]; - userPermissions: User_permissions[]; + userPermissions: User_userPermissions[]; } const RequirePermissions: React.FC = ({ diff --git a/src/config.ts b/src/config.ts index 92bfdbf6b..83d7fcd5a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -32,6 +32,7 @@ export interface AppListViewSettings { [ListViews.SALES_LIST]: ListSettings; [ListViews.SHIPPING_METHODS_LIST]: ListSettings; [ListViews.STAFF_MEMBERS_LIST]: ListSettings; + [ListViews.PERMISSION_GROUP_LIST]: ListSettings; [ListViews.VOUCHER_LIST]: ListSettings; [ListViews.WEBHOOK_LIST]: ListSettings; } @@ -73,6 +74,9 @@ export const defaultListSettings: AppListViewSettings = { [ListViews.STAFF_MEMBERS_LIST]: { rowNumber: PAGINATE_BY }, + [ListViews.PERMISSION_GROUP_LIST]: { + rowNumber: PAGINATE_BY + }, [ListViews.VOUCHER_LIST]: { rowNumber: PAGINATE_BY }, diff --git a/src/configuration/index.tsx b/src/configuration/index.tsx index 3734d4163..6b1c17a84 100644 --- a/src/configuration/index.tsx +++ b/src/configuration/index.tsx @@ -14,6 +14,7 @@ import ProductTypes from "@saleor/icons/ProductTypes"; import ShippingMethods from "@saleor/icons/ShippingMethods"; import SiteSettings from "@saleor/icons/SiteSettings"; import StaffMembers from "@saleor/icons/StaffMembers"; +import PermissionGroups from "@saleor/icons/PermissionGroups"; import Taxes from "@saleor/icons/Taxes"; import Webhooks from "@saleor/icons/Webhooks"; import { sectionNames } from "@saleor/intl"; @@ -26,6 +27,7 @@ import { serviceListUrl } from "@saleor/services/urls"; import { shippingZonesListUrl } from "@saleor/shipping/urls"; import { siteSettingsUrl } from "@saleor/siteSettings/urls"; import { staffListUrl } from "@saleor/staff/urls"; +import { permissionGroupListUrl } from "@saleor/permissionGroups/urls"; import { taxSection } from "@saleor/taxes/urls"; import { PermissionEnum } from "@saleor/types/globalTypes"; import { webhookListUrl } from "@saleor/webhooks/urls"; @@ -101,6 +103,17 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] { permission: PermissionEnum.MANAGE_STAFF, title: intl.formatMessage(sectionNames.staff), url: staffListUrl() + }, + { + description: intl.formatMessage({ + defaultMessage: + "Manage your permission groups and their permissions", + id: "configurationMenuPermissionGroups" + }), + icon: , + permission: PermissionEnum.MANAGE_STAFF, + title: intl.formatMessage(sectionNames.permissionGroups), + url: permissionGroupListUrl() } ] }, diff --git a/src/customers/mutations.ts b/src/customers/mutations.ts index c07d36e89..30729d183 100644 --- a/src/customers/mutations.ts +++ b/src/customers/mutations.ts @@ -36,7 +36,7 @@ import { UpdateCustomerAddressVariables } from "./types/UpdateCustomerAddress"; -export const accountFragmentError = gql` +export const accountErrorFragment = gql` fragment AccountErrorFragment on AccountError { code field @@ -44,7 +44,7 @@ export const accountFragmentError = gql` `; const updateCustomer = gql` - ${accountFragmentError} + ${accountErrorFragment} ${customerDetailsFragment} mutation UpdateCustomer($id: ID!, $input: CustomerInput!) { customerUpdate(id: $id, input: $input) { @@ -63,7 +63,7 @@ export const TypedUpdateCustomerMutation = TypedMutation< >(updateCustomer); const createCustomer = gql` - ${accountFragmentError} + ${accountErrorFragment} mutation CreateCustomer($input: UserCreateInput!) { customerCreate(input: $input) { errors: accountErrors { @@ -81,7 +81,7 @@ export const TypedCreateCustomerMutation = TypedMutation< >(createCustomer); const removeCustomer = gql` - ${accountFragmentError} + ${accountErrorFragment} mutation RemoveCustomer($id: ID!) { customerDelete(id: $id) { errors: accountErrors { @@ -96,7 +96,7 @@ export const TypedRemoveCustomerMutation = TypedMutation< >(removeCustomer); const setCustomerDefaultAddress = gql` - ${accountFragmentError} + ${accountErrorFragment} ${customerAddressesFragment} mutation SetCustomerDefaultAddress( $addressId: ID! @@ -119,7 +119,7 @@ export const TypedSetCustomerDefaultAddressMutation = TypedMutation< >(setCustomerDefaultAddress); const createCustomerAddress = gql` - ${accountFragmentError} + ${accountErrorFragment} ${customerAddressesFragment} ${fragmentAddress} mutation CreateCustomerAddress($id: ID!, $input: AddressInput!) { @@ -142,7 +142,7 @@ export const TypedCreateCustomerAddressMutation = TypedMutation< >(createCustomerAddress); const updateCustomerAddress = gql` - ${accountFragmentError} + ${accountErrorFragment} ${fragmentAddress} mutation UpdateCustomerAddress($id: ID!, $input: AddressInput!) { addressUpdate(id: $id, input: $input) { @@ -161,7 +161,7 @@ export const TypedUpdateCustomerAddressMutation = TypedMutation< >(updateCustomerAddress); const removeCustomerAddress = gql` - ${accountFragmentError} + ${accountErrorFragment} ${customerAddressesFragment} mutation RemoveCustomerAddress($id: ID!) { addressDelete(id: $id) { @@ -180,7 +180,7 @@ export const TypedRemoveCustomerAddressMutation = TypedMutation< >(removeCustomerAddress); export const bulkRemoveCustomers = gql` - ${accountFragmentError} + ${accountErrorFragment} mutation BulkRemoveCustomers($ids: [ID]!) { customerBulkDelete(ids: $ids) { errors: accountErrors { diff --git a/src/fixtures.ts b/src/fixtures.ts index cfbbd5561..ebb678030 100644 --- a/src/fixtures.ts +++ b/src/fixtures.ts @@ -9,6 +9,7 @@ import { TabPageProps } from "./types"; import { PermissionEnum } from "./types/globalTypes"; +import { User_userPermissions } from "./auth/types/User"; const pageInfo = { hasNextPage: true, @@ -381,3 +382,106 @@ export const date = { from: "2019-12-09", to: "2019-12-38" }; + +export const adminUserPermissions: User_userPermissions[] = [ + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_CHECKOUTS, + name: "Manage checkouts" + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_CHECKOUTS, + name: "Manage checkouts" + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_DISCOUNTS, + name: "Manage sales and vouchers." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_GIFT_CARD, + name: "Manage gift cards." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_GIFT_CARD, + name: "Manage gift cards." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_MENUS, + name: "Manage navigation." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_ORDERS, + name: "Manage orders." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_ORDERS, + name: "Manage orders." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_PAGES, + name: "Manage pages." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_PLUGINS, + name: "Manage plugins" + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_PRODUCTS, + name: "Manage products." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_SERVICE_ACCOUNTS, + name: "Manage service account" + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_SERVICE_ACCOUNTS, + name: "Manage service account" + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_SETTINGS, + name: "Manage settings." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_SHIPPING, + name: "Manage shipping." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_STAFF, + name: "Manage staff." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_STAFF, + name: "Manage staff." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_TRANSLATIONS, + name: "Manage translations." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_USERS, + name: "Manage customers." + }, + { + __typename: "UserPermission", + code: PermissionEnum.MANAGE_USERS, + name: "Manage customers." + } +]; diff --git a/src/home/views/index.tsx b/src/home/views/index.tsx index 9cf020bb9..81af44182 100644 --- a/src/home/views/index.tsx +++ b/src/home/views/index.tsx @@ -53,7 +53,7 @@ const HomeSection = () => { ordersToFulfill={maybe(() => data.ordersToFulfill.totalCount)} productsOutOfStock={maybe(() => data.productsOutOfStock.totalCount)} userName={getUserName(user, true)} - userPermissions={maybe(() => user.permissions, [])} + userPermissions={user?.userPermissions || []} /> )} diff --git a/src/icons/PermissionGroups.tsx b/src/icons/PermissionGroups.tsx new file mode 100644 index 000000000..80b149abc --- /dev/null +++ b/src/icons/PermissionGroups.tsx @@ -0,0 +1,16 @@ +import createSvgIcon from "@material-ui/icons/utils/createSvgIcon"; +import React from "react"; + +const PermissionGroups = createSvgIcon( + <> + + , + "PermissionGroups" +); + +export default PermissionGroups; diff --git a/src/index.tsx b/src/index.tsx index b6380ceb7..db050df08 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -45,6 +45,7 @@ import PageSection from "./pages"; import PluginsSection from "./plugins"; import ProductSection from "./products"; import ProductTypesSection from "./productTypes"; +import PermissionGroupSection from "./permissionGroups"; import ServiceSection from "./services"; import { serviceSection } from "./services/urls"; import ShippingSection from "./shipping"; @@ -217,6 +218,11 @@ const Routes: React.FC = () => { path="/staff" component={StaffSection} /> + = ({ id, params }) => { () => data.order.availableShippingMethods, [] )} - userPermissions={maybe(() => user.permissions, [])} + userPermissions={user?.userPermissions || []} onOrderCancel={() => openModal("cancel")} onOrderFulfill={() => openModal("fulfill")} onFulfillmentCancel={fulfillmentId => @@ -450,7 +450,7 @@ export const OrderDetails: React.FC = ({ id, params }) => { onProfileView={() => navigate(customerUrl(order.user.id)) } - userPermissions={maybe(() => user.permissions, [])} + userPermissions={user?.userPermissions || []} /> undefined, + onFetchMore: () => undefined, + onSearchChange: () => undefined, + onSubmit: () => undefined, + open: true, + staffMembers: users +}; + +storiesOf( + "Views / Permission Groups / Permission Group User Assignment", + module +) + .addDecorator(Decorator) + .add("submitting loading", () => ( + + )) + .add("search loading", () => ( + + )) + .add("default", () => ); diff --git a/src/permissionGroups/components/AssignMembersDialog/AssignMembersDialog.tsx b/src/permissionGroups/components/AssignMembersDialog/AssignMembersDialog.tsx new file mode 100644 index 000000000..834038fb5 --- /dev/null +++ b/src/permissionGroups/components/AssignMembersDialog/AssignMembersDialog.tsx @@ -0,0 +1,302 @@ +import { FormattedMessage, useIntl } from "react-intl"; +import InfiniteScroll from "react-infinite-scroller"; +import classNames from "classnames"; + +import ConfirmButton, { + ConfirmButtonTransitionState +} from "@saleor/components/ConfirmButton"; +import { buttonMessages } from "@saleor/intl"; +import { makeStyles } from "@material-ui/core/styles"; +import { getUserName, getUserInitials } from "@saleor/misc"; +import { DialogProps, FetchMoreProps, SearchPageProps } from "@saleor/types"; +import useElementScroll from "@saleor/hooks/useElementScroll"; +import Button from "@material-ui/core/Button"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import React from "react"; +import ResponsiveTable from "@saleor/components/ResponsiveTable"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableRow from "@material-ui/core/TableRow"; +import TextField from "@material-ui/core/TextField"; +import useSearchQuery from "@saleor/hooks/useSearchQuery"; +import { SearchStaffMembers_search_edges_node } from "@saleor/searches/types/SearchStaffMembers"; +import { Checkbox, Typography } from "@material-ui/core"; +import Skeleton from "@saleor/components/Skeleton"; +import CardSpacer from "@saleor/components/CardSpacer"; + +const useStyles = makeStyles( + theme => ({ + avatar: { + alignItems: "center", + borderRadius: "100%", + display: "grid", + float: "left", + height: 32, + justifyContent: "center", + overflow: "hidden", + width: 32 + }, + avatarCell: { + padding: 0, + width: 32 + }, + avatarDefault: { + "& p": { + color: "#fff", + lineHeight: "47px" + }, + background: theme.palette.primary.main, + height: 32, + textAlign: "center", + width: 32 + }, + avatarImage: { + pointerEvents: "none", + width: "100%" + }, + checkboxCell: { + "&&:not(first-child)": { + paddingLeft: 0, + paddingRight: 0, + width: 48 + } + }, + colActions: { + textAlign: "right" + }, + colName: { + paddingLeft: theme.spacing() + }, + + dropShadow: { + boxShadow: `0px -5px 10px 0px ${theme.palette.divider}` + }, + inputContainer: { + overflowY: "visible" + }, + loadMoreLoaderContainer: { + alignItems: "center", + display: "flex", + gridColumnEnd: "span 3", + height: theme.spacing(4), + justifyContent: "center" + }, + overflow: { + overflowY: "visible" + }, + scrollArea: { + maxHeight: 400, + overflowY: "scroll", + paddingTop: 0 + }, + statusText: { + color: "#9E9D9D" + }, + wideCell: { + width: "80%" + } + }), + { name: "AssignStaffMembersDialog" } +); + +export interface AssignMembersDialogProps + extends DialogProps, + FetchMoreProps, + SearchPageProps { + confirmButtonState: ConfirmButtonTransitionState; + disabled: boolean; + staffMembers: SearchStaffMembers_search_edges_node[]; + hasMore: boolean; + onFetchMore: () => void; + onSubmit: (data: SearchStaffMembers_search_edges_node[]) => void; +} + +function handleStaffMemberAssign( + member: SearchStaffMembers_search_edges_node, + isSelected: boolean, + selectedMembers: SearchStaffMembers_search_edges_node[], + setSelectedMembers: (data: SearchStaffMembers_search_edges_node[]) => void +) { + if (isSelected) { + setSelectedMembers( + selectedMembers.filter(selectedMember => selectedMember.id !== member.id) + ); + } else { + setSelectedMembers([...selectedMembers, member]); + } +} + +const AssignMembersDialog: React.FC = ({ + confirmButtonState, + disabled, + loading, + onClose, + onFetchMore, + hasMore, + onSearchChange, + onSubmit, + open, + staffMembers +}) => { + const intl = useIntl(); + const classes = useStyles({}); + const [query, onQueryChange] = useSearchQuery(onSearchChange); + + const [selectedMembers, setSelectedMembers] = React.useState< + SearchStaffMembers_search_edges_node[] + >([]); + + const anchor = React.useRef(); + const scrollPosition = useElementScroll(anchor); + const dropShadow = + anchor.current && scrollPosition + ? scrollPosition.y + anchor.current.clientHeight < + anchor.current.scrollHeight + : false; + + return ( + + + + + + + }} + disabled={disabled} + /> + + + + + + {staffMembers && + staffMembers.map(member => { + const isSelected = selectedMembers.some( + selectedMember => selectedMember.id === member.id + ); + + return ( + + + + handleStaffMemberAssign( + member, + isSelected, + selectedMembers, + setSelectedMembers + ) + } + /> + + +
+ {!!member?.avatar?.url ? ( + + ) : ( +
+ {getUserInitials(member)} +
+ )} +
+
+ + + {getUserName(member) || } + + + {!!member ? ( + member.isActive ? ( + intl.formatMessage({ + defaultMessage: "Active", + description: "staff member status" + }) + ) : ( + intl.formatMessage({ + defaultMessage: "Inactive", + description: "staff member status" + }) + ) + ) : ( + + )} + + +
+ ); + })} +
+
+ {loading && ( + <> + {staffMembers?.length > 0 && } +
+ +
+ + )} +
+
+ + + { + onSubmit(selectedMembers); + }} + > + + + +
+ ); +}; +AssignMembersDialog.displayName = "AssignMembersDialog"; +export default AssignMembersDialog; diff --git a/src/permissionGroups/components/AssignMembersDialog/index.ts b/src/permissionGroups/components/AssignMembersDialog/index.ts new file mode 100644 index 000000000..fe1ce90e6 --- /dev/null +++ b/src/permissionGroups/components/AssignMembersDialog/index.ts @@ -0,0 +1,2 @@ +export { default } from "./AssignMembersDialog"; +export * from "./AssignMembersDialog"; diff --git a/src/permissionGroups/components/MembersErrorDialog/MembersErrorDialog.stories.tsx b/src/permissionGroups/components/MembersErrorDialog/MembersErrorDialog.stories.tsx new file mode 100644 index 000000000..0ae5c543b --- /dev/null +++ b/src/permissionGroups/components/MembersErrorDialog/MembersErrorDialog.stories.tsx @@ -0,0 +1,20 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; +import Decorator from "@saleor/storybook/Decorator"; +import MembersErrorDialog, { + MembersErrorDialogProps +} from "./MembersErrorDialog"; + +const props: MembersErrorDialogProps = { + confirmButtonState: "default", + onClose: () => undefined, + onConfirm: () => undefined, + open: true +}; + +storiesOf( + "Views / Permission Groups / Permission Group Unassign Error Modal", + module +) + .addDecorator(Decorator) + .add("Unassign member", () => ); diff --git a/src/permissionGroups/components/MembersErrorDialog/MembersErrorDialog.tsx b/src/permissionGroups/components/MembersErrorDialog/MembersErrorDialog.tsx new file mode 100644 index 000000000..cc685ec90 --- /dev/null +++ b/src/permissionGroups/components/MembersErrorDialog/MembersErrorDialog.tsx @@ -0,0 +1,45 @@ +import DialogContentText from "@material-ui/core/DialogContentText"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import ActionDialog from "@saleor/components/ActionDialog"; +import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; + +export interface MembersErrorDialogProps { + confirmButtonState: ConfirmButtonTransitionState; + open: boolean; + onConfirm: () => void; + onClose: () => void; +} + +const MembersErrorDialog: React.FC = ({ + confirmButtonState, + onClose, + onConfirm, + open +}) => { + const intl = useIntl(); + + return ( + + + + + + ); +}; +MembersErrorDialog.displayName = "MembersErrorDialog"; +export default MembersErrorDialog; diff --git a/src/permissionGroups/components/MembersErrorDialog/index.ts b/src/permissionGroups/components/MembersErrorDialog/index.ts new file mode 100644 index 000000000..8e9e6a649 --- /dev/null +++ b/src/permissionGroups/components/MembersErrorDialog/index.ts @@ -0,0 +1,2 @@ +export { default } from "./MembersErrorDialog"; +export * from "./MembersErrorDialog"; diff --git a/src/permissionGroups/components/PermissionGroupCreatePage/PermissionGroupCreatePage.stories.tsx b/src/permissionGroups/components/PermissionGroupCreatePage/PermissionGroupCreatePage.stories.tsx new file mode 100644 index 000000000..613450145 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupCreatePage/PermissionGroupCreatePage.stories.tsx @@ -0,0 +1,31 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; +import { permissions } from "@saleor/fixtures"; + +import PermissionGroupCreatePage, { + PermissionGroupCreatePageProps +} from "@saleor/permissionGroups/components/PermissionGroupCreatePage"; +import Decorator from "@saleor/storybook/Decorator"; +import { errorsOfPermissionGroupCreate } from "@saleor/permissionGroups/fixtures"; + +const props: PermissionGroupCreatePageProps = { + disabled: false, + errors: [], + onBack: () => undefined, + onSubmit: () => undefined, + permissions, + saveButtonBarState: undefined +}; + +storiesOf("Views / Permission Groups / Permission Group Create", module) + .addDecorator(Decorator) + .add("default", () => ) + .add("loading", () => ( + + )) + .add("errors", () => ( + + )); diff --git a/src/permissionGroups/components/PermissionGroupCreatePage/PermissionGroupCreatePage.tsx b/src/permissionGroups/components/PermissionGroupCreatePage/PermissionGroupCreatePage.tsx new file mode 100644 index 000000000..27e22d81d --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupCreatePage/PermissionGroupCreatePage.tsx @@ -0,0 +1,107 @@ +import React from "react"; +import { useIntl } from "react-intl"; +import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; +import { PermissionEnum } from "@saleor/types/globalTypes"; +import AccountPermissions from "@saleor/components/AccountPermissions"; +import Container from "@saleor/components/Container"; +import Form from "@saleor/components/Form"; +import Grid from "@saleor/components/Grid"; +import SaveButtonBar from "@saleor/components/SaveButtonBar"; +import AppHeader from "@saleor/components/AppHeader"; +import { sectionNames } from "@saleor/intl"; +import { PermissionGroupErrorFragment } from "@saleor/permissionGroups/types/PermissionGroupErrorFragment"; +import { getFormErrors } from "@saleor/utils/errors"; +import getPermissionGroupErrorMessage from "@saleor/utils/errors/permissionGroups"; +import PermissionGroupInfo from "../PermissionGroupInfo"; +import { PermissionData } from "../PermissionGroupDetailsPage"; + +export interface PermissionGroupCreatePageFormData { + name: string; + hasFullAccess: boolean; + isActive: boolean; + permissions: PermissionEnum[]; +} + +const initialForm: PermissionGroupCreatePageFormData = { + hasFullAccess: false, + isActive: false, + name: "", + permissions: [] +}; + +export interface PermissionGroupCreatePageProps { + disabled: boolean; + errors: PermissionGroupErrorFragment[]; + permissions: PermissionData[]; + saveButtonBarState: ConfirmButtonTransitionState; + onBack: () => void; + onSubmit(data: PermissionGroupCreatePageFormData); +} + +const PermissionGroupCreatePage: React.FC = ({ + disabled, + permissions, + onBack, + onSubmit, + saveButtonBarState, + errors +}) => { + const intl = useIntl(); + + const formErrors = getFormErrors(["addPermissions"], errors || []); + const permissionsError = getPermissionGroupErrorMessage( + formErrors.addPermissions, + intl + ); + + return ( +
+ {({ data, change, submit, hasChanged }) => ( + + + {intl.formatMessage(sectionNames.permissionGroups)} + + +
+ +
+
+ +
+
+
+ +
+
+ )} +
+ ); +}; +PermissionGroupCreatePage.displayName = "PermissionGroupCreatePage"; +export default PermissionGroupCreatePage; diff --git a/src/permissionGroups/components/PermissionGroupCreatePage/index.ts b/src/permissionGroups/components/PermissionGroupCreatePage/index.ts new file mode 100644 index 000000000..0a89cd1b7 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupCreatePage/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PermissionGroupCreatePage"; +export * from "./PermissionGroupCreatePage"; diff --git a/src/permissionGroups/components/PermissionGroupDeleteDialog/PermissionGroupDeleteDialog.stories.tsx b/src/permissionGroups/components/PermissionGroupDeleteDialog/PermissionGroupDeleteDialog.stories.tsx new file mode 100644 index 000000000..b76c86949 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupDeleteDialog/PermissionGroupDeleteDialog.stories.tsx @@ -0,0 +1,19 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; + +import PermissionGroupDeleteDialog, { + PermissionDeleteDialogProps +} from "@saleor/permissionGroups/components/PermissionGroupDeleteDialog"; +import Decorator from "@saleor/storybook/Decorator"; + +const props: PermissionDeleteDialogProps = { + confirmButtonState: "default", + name: "Full Access", + onClose: () => undefined, + onConfirm: () => undefined, + open: true +}; + +storiesOf("Views / Permission Groups / Permission Group Delete", module) + .addDecorator(Decorator) + .add("remove single", () => ); diff --git a/src/permissionGroups/components/PermissionGroupDeleteDialog/PermissionGroupDeleteDialog.tsx b/src/permissionGroups/components/PermissionGroupDeleteDialog/PermissionGroupDeleteDialog.tsx new file mode 100644 index 000000000..1c66b87ee --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupDeleteDialog/PermissionGroupDeleteDialog.tsx @@ -0,0 +1,50 @@ +import DialogContentText from "@material-ui/core/DialogContentText"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import ActionDialog from "@saleor/components/ActionDialog"; +import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; + +export interface PermissionDeleteDialogProps { + confirmButtonState: ConfirmButtonTransitionState; + open: boolean; + name: string; + onConfirm: () => void; + onClose: () => void; +} + +const PermissionGroupDeleteDialog: React.FC = ({ + confirmButtonState, + name, + onClose, + onConfirm, + open +}) => { + const intl = useIntl(); + + return ( + + + {name} + }} + /> + + + ); +}; +PermissionGroupDeleteDialog.displayName = "PermissionGroupDeleteDialog"; +export default PermissionGroupDeleteDialog; diff --git a/src/permissionGroups/components/PermissionGroupDeleteDialog/index.ts b/src/permissionGroups/components/PermissionGroupDeleteDialog/index.ts new file mode 100644 index 000000000..72deba8d1 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupDeleteDialog/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PermissionGroupDeleteDialog"; +export * from "./PermissionGroupDeleteDialog"; diff --git a/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.stories.tsx b/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.stories.tsx new file mode 100644 index 000000000..4b78b6a20 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.stories.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { permissions } from "@saleor/fixtures"; +import PermissionGroupDetailsPage, { + PermissionGroupDetailsPageProps +} from "@saleor/permissionGroups/components/PermissionGroupDetailsPage"; +import Decorator from "@saleor/storybook/Decorator"; +import { storiesOf } from "@storybook/react"; +import { + emptyPermissionGroup, + permissionGroup, + users +} from "@saleor/permissionGroups/fixtures"; + +const props: PermissionGroupDetailsPageProps = { + disabled: false, + errors: [], + isChecked: () => false, + members: users, + membersModified: false, + onAssign: () => undefined, + onBack: () => undefined, + onSort: () => undefined, + onSubmit: () => undefined, + onUnassign: () => undefined, + permissionGroup, + permissions, + permissionsExceeded: false, + saveButtonBarState: undefined, + selected: 0, + sort: null, + toggle: () => undefined, + toggleAll: () => undefined, + toolbar: null +}; + +storiesOf("Views / Permission Groups / Permission Group Details", module) + .addDecorator(Decorator) + .add("default", () => ) + .add("no members", () => ( + + )) + .add("loading", () => ( + + )); diff --git a/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.tsx b/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.tsx new file mode 100644 index 000000000..bfeb88002 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage.tsx @@ -0,0 +1,149 @@ +import React from "react"; +import { useIntl } from "react-intl"; + +import FormSpacer from "@saleor/components/FormSpacer"; +import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; +import { PermissionEnum } from "@saleor/types/globalTypes"; +import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInfo"; +import { ListActions, SortPage } from "@saleor/types"; +import AccountPermissions from "@saleor/components/AccountPermissions"; +import AppHeader from "@saleor/components/AppHeader"; +import PageHeader from "@saleor/components/PageHeader"; +import { sectionNames } from "@saleor/intl"; +import Container from "@saleor/components/Container"; +import Form from "@saleor/components/Form"; +import Grid from "@saleor/components/Grid"; +import SaveButtonBar from "@saleor/components/SaveButtonBar"; +import { + isGroupFullAccess, + extractPermissionCodes +} from "@saleor/permissionGroups/utils"; +import { MembersListUrlSortField } from "@saleor/permissionGroups/urls"; +import { PermissionGroupErrorFragment } from "@saleor/permissionGroups/types/PermissionGroupErrorFragment"; +import { getFormErrors } from "@saleor/utils/errors"; +import getPermissionGroupErrorMessage from "@saleor/utils/errors/permissionGroups"; +import PermissionGroupInfo from "../PermissionGroupInfo"; +import { + PermissionGroupDetails_permissionGroup, + PermissionGroupDetails_permissionGroup_users +} from "../../types/PermissionGroupDetails"; +import PermissionGroupMemberList from "../PermissionGroupMemberList"; + +export interface PermissionGroupDetailsPageFormData { + name: string; + hasFullAccess: boolean; + isActive: boolean; + permissions: PermissionEnum[]; + users: PermissionGroupDetails_permissionGroup_users[]; +} + +export interface PermissionData extends ShopInfo_shop_permissions { + lastSource?: boolean; + disabled?: boolean; +} + +export interface PermissionGroupDetailsPageProps + extends ListActions, + SortPage { + disabled: boolean; + errors: PermissionGroupErrorFragment[]; + members: PermissionGroupDetails_permissionGroup_users[]; + membersModified: boolean; + permissionGroup: PermissionGroupDetails_permissionGroup; + permissions: PermissionData[]; + permissionsExceeded: boolean; + saveButtonBarState: ConfirmButtonTransitionState; + onAssign: () => void; + onBack: () => void; + onUnassign: (ids: string[]) => void; + onSubmit(data: PermissionGroupDetailsPageFormData); +} + +const PermissionGroupDetailsPage: React.FC = ({ + disabled, + errors, + members, + membersModified, + onBack, + onSubmit, + permissionGroup, + permissions, + permissionsExceeded, + saveButtonBarState, + ...listProps +}) => { + const intl = useIntl(); + + const initialForm: PermissionGroupDetailsPageFormData = { + hasFullAccess: isGroupFullAccess(permissionGroup, permissions), + isActive: false, + name: permissionGroup?.name || "", + permissions: extractPermissionCodes(permissionGroup), + users: members + }; + + const formErrors = getFormErrors(["addPermissions"], errors); + const permissionsError = getPermissionGroupErrorMessage( + formErrors.addPermissions, + intl + ); + + return ( +
+ {({ data, change, submit, hasChanged }) => ( + + + {intl.formatMessage(sectionNames.permissionGroups)} + + + + +
+ + + +
+
+ +
+
+
+ +
+
+ )} +
+ ); +}; +PermissionGroupDetailsPage.displayName = "PermissionGroupDetailsPage"; +export default PermissionGroupDetailsPage; diff --git a/src/permissionGroups/components/PermissionGroupDetailsPage/index.ts b/src/permissionGroups/components/PermissionGroupDetailsPage/index.ts new file mode 100644 index 000000000..122f3ffc1 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupDetailsPage/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PermissionGroupDetailsPage"; +export * from "./PermissionGroupDetailsPage"; diff --git a/src/permissionGroups/components/PermissionGroupInfo/PermissionGroupInfo.tsx b/src/permissionGroups/components/PermissionGroupInfo/PermissionGroupInfo.tsx new file mode 100644 index 000000000..650d3426f --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupInfo/PermissionGroupInfo.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { useIntl } from "react-intl"; + +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import TextField from "@material-ui/core/TextField"; +import CardTitle from "@saleor/components/CardTitle"; +import { FormChange } from "@saleor/hooks/useForm"; +import { commonMessages } from "@saleor/intl"; +import { PermissionGroupErrorFragment } from "@saleor/permissionGroups/types/PermissionGroupErrorFragment"; +import { getFieldError, getFormErrors } from "@saleor/utils/errors"; +import getPermissionGroupErrorMessage from "@saleor/utils/errors/permissionGroups"; + +export interface PermissionGroupInfoProps { + disabled: boolean; + errors: PermissionGroupErrorFragment[]; + onChange: FormChange; + data: { + name: string; + }; +} + +const PermissionGroupInfo: React.FC = ({ + disabled, + onChange, + data, + errors +}) => { + const intl = useIntl(); + + const formErrors = getFormErrors(["name"], errors); + + return ( + + + + + + + ); +}; + +PermissionGroupInfo.displayName = "PermissionGroupInfo"; +export default PermissionGroupInfo; diff --git a/src/permissionGroups/components/PermissionGroupInfo/index.ts b/src/permissionGroups/components/PermissionGroupInfo/index.ts new file mode 100644 index 000000000..caf2a9eaf --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupInfo/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PermissionGroupInfo"; +export * from "./PermissionGroupInfo"; diff --git a/src/permissionGroups/components/PermissionGroupList/PermissionGroupList.tsx b/src/permissionGroups/components/PermissionGroupList/PermissionGroupList.tsx new file mode 100644 index 000000000..3018c2a5b --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupList/PermissionGroupList.tsx @@ -0,0 +1,182 @@ +import { makeStyles } from "@material-ui/core/styles"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableFooter from "@material-ui/core/TableFooter"; +import TableRow from "@material-ui/core/TableRow"; +import DeleteIcon from "@material-ui/icons/Delete"; +import EditIcon from "@material-ui/icons/Edit"; +import IconButton from "@material-ui/core/IconButton"; +import { TableHead } from "@material-ui/core"; + +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import ResponsiveTable from "@saleor/components/ResponsiveTable"; +import Skeleton from "@saleor/components/Skeleton"; +import TablePagination from "@saleor/components/TablePagination"; +import { getArrowDirection } from "@saleor/utils/sort"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import { ListProps, SortPage } from "@saleor/types"; +import { maybe, renderCollection, stopPropagation } from "@saleor/misc"; + +import { PermissionGroupList_permissionGroups_edges_node } from "@saleor/permissionGroups/types/PermissionGroupList"; +import { PermissionGroupListUrlSortField } from "@saleor/permissionGroups/urls"; + +const useStyles = makeStyles( + theme => ({ + [theme.breakpoints.up("lg")]: { + colActions: { + width: 180 + }, + colMembers: { + width: 180 + }, + colName: { + width: "auto" + } + }, + colActions: { + paddingRight: theme.spacing(), + textAlign: "right" + }, + colActionsHeader: { + textAlign: "right" + }, + colMembers: { + textAlign: "right" + }, + colName: { + paddingLeft: 0 + }, + link: { + cursor: "pointer" + } + }), + { name: "PermissionGroupList" } +); +const numberOfColumns = 3; + +interface PermissionGroupListProps + extends ListProps, + SortPage { + permissionGroups: PermissionGroupList_permissionGroups_edges_node[]; + onDelete: (id: string) => void; +} + +const PermissionGroupList: React.FC = props => { + const { + disabled, + permissionGroups, + pageInfo, + onDelete, + onNextPage, + onPreviousPage, + onRowClick, + onSort, + sort + } = props; + const classes = useStyles(props); + + return ( + + + + onSort(PermissionGroupListUrlSortField.name)} + className={classes.colName} + > + + + + + + + + + + + + + + + + + {renderCollection( + permissionGroups, + permissionGroup => ( + permissionGroup.id)} + > + + {permissionGroup ? ( + {permissionGroup.name} + ) : ( + + )} + + + {permissionGroup ? ( + {permissionGroup.users.length} + ) : ( + + )} + + + {permissionGroup ? ( + <> + + onDelete(permissionGroup.id) + )} + > + + + + + + + ) : ( + + )} + + + ), + () => ( + + + + + + ) + )} + + + ); +}; +PermissionGroupList.displayName = "PermissionGroupList"; +export default PermissionGroupList; diff --git a/src/permissionGroups/components/PermissionGroupList/index.ts b/src/permissionGroups/components/PermissionGroupList/index.ts new file mode 100644 index 000000000..9cfd81517 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PermissionGroupList"; +export * from "./PermissionGroupList"; diff --git a/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.stories.tsx b/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.stories.tsx new file mode 100644 index 000000000..ffbbd6738 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.stories.tsx @@ -0,0 +1,42 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; +import { + listActionsProps, + pageListProps, + sortPageProps +} from "@saleor/fixtures"; + +import PermissionGroupListPage, { + PermissionGroupListPageProps +} from "@saleor/permissionGroups/components/PermissionGroupListPage"; +import Decorator from "@saleor/storybook/Decorator"; +import { PermissionGroupListUrlSortField } from "@saleor/permissionGroups/urls"; +import { permissionGroups } from "@saleor/permissionGroups/fixtures"; + +const props: PermissionGroupListPageProps = { + permissionGroups, + ...listActionsProps, + ...pageListProps.default, + ...sortPageProps, + disabled: false, + onBack: () => undefined, + onDelete: () => undefined, + sort: { + ...sortPageProps.sort, + sort: PermissionGroupListUrlSortField.name + } +}; + +storiesOf("Views / Permission Groups / Permission Group List", module) + .addDecorator(Decorator) + .add("default", () => ) + .add("loading", () => ( + + )) + .add("no data", () => ( + + )); diff --git a/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.tsx b/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.tsx new file mode 100644 index 000000000..1c646c0fa --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupListPage/PermissionGroupListPage.tsx @@ -0,0 +1,51 @@ +import Button from "@material-ui/core/Button"; +import Card from "@material-ui/core/Card"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import AppHeader from "@saleor/components/AppHeader"; +import Container from "@saleor/components/Container"; +import PageHeader from "@saleor/components/PageHeader"; +import { sectionNames } from "@saleor/intl"; +import { PermissionGroupListUrlSortField } from "../../urls"; +import { PageListProps, SortPage } from "../../../types"; +import { PermissionGroupList_permissionGroups_edges_node } from "../../types/PermissionGroupList"; +import PermissionGroupList from "../PermissionGroupList"; + +export interface PermissionGroupListPageProps + extends PageListProps, + SortPage { + permissionGroups: PermissionGroupList_permissionGroups_edges_node[]; + onBack: () => void; + onDelete: (id: string) => void; + onRowClick: (id: string) => () => void; +} + +const PermissionGroupListPage: React.FC = ({ + onAdd, + onBack, + ...listProps +}) => { + const intl = useIntl(); + + return ( + + + {intl.formatMessage(sectionNames.configuration)} + + + + + + + + + ); +}; +PermissionGroupListPage.displayName = "PermissionGroupListPage"; +export default PermissionGroupListPage; diff --git a/src/permissionGroups/components/PermissionGroupListPage/index.ts b/src/permissionGroups/components/PermissionGroupListPage/index.ts new file mode 100644 index 000000000..718963154 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupListPage/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PermissionGroupListPage"; +export * from "./PermissionGroupListPage"; diff --git a/src/permissionGroups/components/PermissionGroupMemberList/PermissionGroupMemberList.tsx b/src/permissionGroups/components/PermissionGroupMemberList/PermissionGroupMemberList.tsx new file mode 100644 index 000000000..cc4dd3651 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupMemberList/PermissionGroupMemberList.tsx @@ -0,0 +1,288 @@ +import Card from "@material-ui/core/Card"; +import { makeStyles } from "@material-ui/core/styles"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableHead from "@saleor/components/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import Typography from "@material-ui/core/Typography"; +import classNames from "classnames"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; +import DeleteIcon from "@material-ui/icons/Delete"; +import ResponsiveTable from "@saleor/components/ResponsiveTable"; +import Skeleton from "@saleor/components/Skeleton"; +import { + getUserInitials, + getUserName, + stopPropagation, + renderCollection +} from "@saleor/misc"; +import { ListActions, SortPage } from "@saleor/types"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import Checkbox from "@saleor/components/Checkbox"; + +import { Button, IconButton } from "@material-ui/core"; +import CardTitle from "@saleor/components/CardTitle"; +import { PermissionGroupDetails_permissionGroup_users } from "@saleor/permissionGroups/types/PermissionGroupDetails"; +import { MembersListUrlSortField } from "@saleor/permissionGroups/urls"; +import { getArrowDirection } from "@saleor/utils/sort"; +import { sortMembers } from "@saleor/permissionGroups/sort"; + +const useStyles = makeStyles( + theme => ({ + [theme.breakpoints.up("lg")]: { + colActions: { + width: 120 + }, + colEmail: { + width: 300 + }, + colName: { + width: "auto" + } + }, + avatar: { + alignItems: "center", + borderRadius: "100%", + display: "grid", + float: "left", + height: 47, + justifyContent: "center", + marginRight: theme.spacing(1), + overflow: "hidden", + width: 47 + }, + avatarDefault: { + "& p": { + color: "#fff", + lineHeight: "47px" + }, + background: theme.palette.primary.main, + height: 47, + textAlign: "center", + width: 47 + }, + avatarImage: { + pointerEvents: "none", + width: "100%" + }, + colActions: { + paddingRight: theme.spacing(), + textAlign: "right" + }, + helperText: { + marginBottom: theme.spacing(3), + marginTop: theme.spacing(3), + textAlign: "center" + }, + statusText: { + color: "#9E9D9D" + }, + tableRow: {} + }), + { name: "PermissionGroup" } +); +const numberOfColumns = 4; + +interface PermissionGroupProps + extends ListActions, + SortPage { + users: PermissionGroupDetails_permissionGroup_users[]; + disabled: boolean; + onUnassign: (ida: string[]) => void; + onAssign: () => void; +} + +const PermissionGroupMemberList: React.FC = props => { + const { + disabled, + users, + onUnassign, + onAssign, + onSort, + toggle, + toolbar, + isChecked, + selected, + toggleAll, + sort + } = props; + + const classes = useStyles(props); + const intl = useIntl(); + + const members = users?.sort(sortMembers(sort?.sort, sort?.asc)); + + return ( + + + + + } + /> + {members?.length === 0 ? ( +
+ + + + + + +
+ ) : ( + + + onSort(MembersListUrlSortField.name)} + direction={ + sort?.sort === MembersListUrlSortField.name + ? getArrowDirection(sort.asc) + : undefined + } + > + + + onSort(MembersListUrlSortField.email)} + direction={ + sort?.sort === MembersListUrlSortField.email + ? getArrowDirection(sort.asc) + : undefined + } + > + + + + + + + + {renderCollection( + members, + user => { + const isSelected = user ? isChecked(user.id) : false; + + return ( + + + toggle(user.id)} + /> + + +
+ {user?.avatar?.url ? ( + + ) : ( +
+ {getUserInitials(user)} +
+ )} +
+ + {getUserName(user) || } + + + {!user ? ( + + ) : user.isActive ? ( + intl.formatMessage({ + defaultMessage: "Active", + description: "staff member status" + }) + ) : ( + intl.formatMessage({ + defaultMessage: "Inactive", + description: "staff member status" + }) + )} + +
+ + {user?.email || } + + + {user ? ( + <> + + onUnassign([user.id]) + )} + > + + + + ) : ( + + )} + +
+ ); + }, + () => ( + + + + + + ) + )} +
+
+ )} +
+ ); +}; +PermissionGroupMemberList.displayName = "PermissionGroupMemberList"; +export default PermissionGroupMemberList; diff --git a/src/permissionGroups/components/PermissionGroupMemberList/index.ts b/src/permissionGroups/components/PermissionGroupMemberList/index.ts new file mode 100644 index 000000000..758f0af47 --- /dev/null +++ b/src/permissionGroups/components/PermissionGroupMemberList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PermissionGroupMemberList"; +export * from "./PermissionGroupMemberList"; diff --git a/src/permissionGroups/components/UnassignMembersDialog/UnassignMembersDialog.stories.tsx b/src/permissionGroups/components/UnassignMembersDialog/UnassignMembersDialog.stories.tsx new file mode 100644 index 000000000..145388d54 --- /dev/null +++ b/src/permissionGroups/components/UnassignMembersDialog/UnassignMembersDialog.stories.tsx @@ -0,0 +1,21 @@ +import { storiesOf } from "@storybook/react"; +import React from "react"; +import UnassignMembersDialog, { + UnassignMembersDialogProps +} from "@saleor/permissionGroups/components/UnassignMembersDialog"; +import Decorator from "@saleor/storybook/Decorator"; + +const props: UnassignMembersDialogProps = { + confirmButtonState: "default", + onClose: () => undefined, + onConfirm: () => undefined, + open: true, + quantity: 3 +}; + +storiesOf( + "Views / Permission Groups / Permission Group Unassign Member", + module +) + .addDecorator(Decorator) + .add("Unassign members", () => ); diff --git a/src/permissionGroups/components/UnassignMembersDialog/UnassignMembersDialog.tsx b/src/permissionGroups/components/UnassignMembersDialog/UnassignMembersDialog.tsx new file mode 100644 index 000000000..d8e63072c --- /dev/null +++ b/src/permissionGroups/components/UnassignMembersDialog/UnassignMembersDialog.tsx @@ -0,0 +1,51 @@ +import DialogContentText from "@material-ui/core/DialogContentText"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import ActionDialog from "@saleor/components/ActionDialog"; +import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; + +export interface UnassignMembersDialogProps { + confirmButtonState: ConfirmButtonTransitionState; + quantity: number; + open: boolean; + onConfirm: () => void; + onClose: () => void; +} + +const UnassignMembersDialog: React.FC = ({ + confirmButtonState, + quantity, + onClose, + onConfirm, + open +}) => { + const intl = useIntl(); + + return ( + + + {quantity} + }} + /> + + + ); +}; +UnassignMembersDialog.displayName = "UnassignMembersDialog"; +export default UnassignMembersDialog; diff --git a/src/permissionGroups/components/UnassignMembersDialog/index.ts b/src/permissionGroups/components/UnassignMembersDialog/index.ts new file mode 100644 index 000000000..e1a246182 --- /dev/null +++ b/src/permissionGroups/components/UnassignMembersDialog/index.ts @@ -0,0 +1,2 @@ +export { default } from "./UnassignMembersDialog"; +export * from "./UnassignMembersDialog"; diff --git a/src/permissionGroups/fixtures.ts b/src/permissionGroups/fixtures.ts new file mode 100644 index 000000000..57ba617a5 --- /dev/null +++ b/src/permissionGroups/fixtures.ts @@ -0,0 +1,229 @@ +import * as avatarImg from "@assets/images/avatars/avatar1.png"; +import { SearchStaffMembers_search_edges_node } from "@saleor/searches/types/SearchStaffMembers"; +/* eslint-disable sort-keys */ +import { + PermissionEnum, + PermissionGroupErrorCode +} from "@saleor/types/globalTypes"; + +import { StaffMemberDetails_user_permissionGroups } from "@saleor/staff/types/StaffMemberDetails"; +import { PermissionGroupDetails_permissionGroup } from "./types/PermissionGroupDetails"; +import { PermissionGroupList_permissionGroups_edges_node } from "./types/PermissionGroupList"; +import { PermissionGroupErrorFragment } from "./types/PermissionGroupErrorFragment"; + +export const permissionGroups: PermissionGroupList_permissionGroups_edges_node[] = [ + { + node: { + id: "R3JvdXA6Mg==", + name: "Customer Support", + users: [ + { + id: "VXNlcjoyMQ==", + firstName: "", + lastName: "", + __typename: "User" as "User" + } + ], + __typename: "Group" as "Group" + }, + __typename: "GroupCountableEdge" as "GroupCountableEdge" + }, + { + node: { + id: "R3JvdXA6MQ==", + name: "Full Access", + users: [ + { + id: "VXNlcjoyMQ==", + firstName: "", + lastName: "", + __typename: "User" as "User" + } + ], + __typename: "Group" as "Group" + }, + __typename: "GroupCountableEdge" as "GroupCountableEdge" + }, + { + node: { + id: "R3JvdXA6NA==", + name: "Management", + users: [], + __typename: "Group" as "Group" + }, + __typename: "GroupCountableEdge" as "GroupCountableEdge" + }, + { + node: { + id: "R3JvdXA6Mw==", + name: "Editors", + users: [ + { + id: "VXNlcjoyMw==", + firstName: "Bryan", + lastName: "Rodgers", + __typename: "User" as "User" + }, + { + id: "VXNlcjoyMg==", + firstName: "Joshua", + lastName: "Mitchell", + __typename: "User" as "User" + } + ], + __typename: "Group" as "Group" + }, + __typename: "GroupCountableEdge" as "GroupCountableEdge" + }, + { + node: { + id: "R3JvdXA6NQ==", + name: "Publishers", + users: [], + __typename: "Group" as "Group" + }, + __typename: "GroupCountableEdge" as "GroupCountableEdge" + } +].map(edge => edge.node); + +export const userPermissionGroups: StaffMemberDetails_user_permissionGroups[] = [ + { + id: "R3JvdXA6MQ==", + name: "Full Access", + userCanManage: false, + __typename: "Group" + }, + { + id: "R3JvdXA6Mg==", + name: "Customer Support", + userCanManage: true, + __typename: "Group" + } +]; + +export const emptyPermissionGroup: PermissionGroupDetails_permissionGroup = { + id: "R3JvdXA6Mw==", + name: "Editors", + users: [], + __typename: "Group", + permissions: [ + { + code: PermissionEnum.MANAGE_PAGES, + name: "Manage pages.", + __typename: "Permission" + } + ] +}; + +export const errorsOfPermissionGroupCreate: PermissionGroupErrorFragment[] = [ + { + field: "name", + code: PermissionGroupErrorCode.UNIQUE, + __typename: "PermissionGroupError" + }, + { + field: "permissions", + code: PermissionGroupErrorCode.OUT_OF_SCOPE_PERMISSION, + __typename: "PermissionGroupError" + } +]; + +export const permissionGroup: PermissionGroupDetails_permissionGroup = { + id: "R3JvdXA6Mw==", + name: "Editors", + users: [ + { + id: "VXNlcjoyMg==", + firstName: "Joshua", + lastName: "Mitchell", + __typename: "User", + email: "joshua.mitchell@example.com", + isActive: true, + avatar: null + }, + { + id: "VXNlcjoyMw==", + firstName: "Bryan", + lastName: "Rodgers", + __typename: "User", + email: "bryan.rodgers@example.com", + isActive: true, + avatar: null + } + ], + __typename: "Group", + permissions: [ + { + code: PermissionEnum.MANAGE_PAGES, + name: "Manage pages.", + __typename: "Permission" + } + ] +}; + +export const users: SearchStaffMembers_search_edges_node[] = [ + { + node: { + id: "VXNlcjoyMQ==", + email: "admin@example.com", + firstName: "", + lastName: "", + isActive: true, + avatar: { + alt: null, + url: avatarImg, + __typename: "Image" as "Image" + }, + __typename: "User" as "User" + }, + __typename: "UserCountableEdge" as "UserCountableEdge" + }, + { + node: { + id: "VXNlcjoyMw==", + email: "bryan.rodgers@example.com", + firstName: "Bryan", + lastName: "Rodgers", + isActive: true, + avatar: { + alt: null, + url: avatarImg, + __typename: "Image" as "Image" + }, + __typename: "User" as "User" + }, + __typename: "UserCountableEdge" as "UserCountableEdge" + }, + { + node: { + id: "VXNlcjoyMg==", + email: "joshua.mitchell@example.com", + firstName: "Joshua", + lastName: "Mitchell", + isActive: true, + avatar: { + alt: null, + url: avatarImg, + __typename: "Image" as "Image" + }, + __typename: "User" as "User" + }, + __typename: "UserCountableEdge" as "UserCountableEdge" + }, + { + node: { + id: "VXNlcjoyMg==", + email: "joshua.mitchell@example.com", + firstName: "Joshua", + lastName: "Mitchell", + isActive: true, + avatar: { + alt: null, + url: avatarImg, + __typename: "Image" as "Image" + }, + __typename: "User" as "User" + }, + __typename: "UserCountableEdge" as "UserCountableEdge" + } +].map(edge => edge.node); diff --git a/src/permissionGroups/index.tsx b/src/permissionGroups/index.tsx new file mode 100644 index 000000000..da2282d04 --- /dev/null +++ b/src/permissionGroups/index.tsx @@ -0,0 +1,79 @@ +import { parse as parseQs } from "qs"; +import React from "react"; +import { useIntl } from "react-intl"; +import { Route, RouteComponentProps, Switch } from "react-router-dom"; + +import { sectionNames } from "@saleor/intl"; +import { asSortParams } from "@saleor/utils/sort"; +import { WindowTitle } from "../components/WindowTitle"; +import { + permissionGroupListPath, + permissionGroupDetailsPath, + permissionGroupAddPath, + PermissionGroupListUrlQueryParams, + PermissionGroupListUrlSortField, + PermissionGroupDetailsUrlQueryParams, + MembersListUrlSortField +} from "./urls"; +import PermissionGroupListComponent from "./views/PermissionGroupList"; +import PermissionGroupDetailsComponent from "./views/PermissionGroupDetails"; +import PermissionGroupCreate from "./views/PermissionGroupCreate"; + +const permissionGroupList: React.FC> = ({ + location +}) => { + const qs = parseQs(location.search.substr(1)); + const params: PermissionGroupListUrlQueryParams = asSortParams( + qs, + PermissionGroupListUrlSortField + ); + + return ; +}; + +interface PermissionGroupDetailsRouteProps { + id: string; +} +const PermissionGroupDetails: React.FC> = ({ match }) => { + const qs = parseQs(location.search.substr(1)); + const params: PermissionGroupDetailsUrlQueryParams = asSortParams( + qs, + MembersListUrlSortField + ); + + return ( + + ); +}; + +const Component = () => { + const intl = useIntl(); + + return ( + <> + + + + + + + + ); +}; + +export default Component; diff --git a/src/permissionGroups/mutations.ts b/src/permissionGroups/mutations.ts new file mode 100644 index 000000000..2dca8eb89 --- /dev/null +++ b/src/permissionGroups/mutations.ts @@ -0,0 +1,84 @@ +import gql from "graphql-tag"; + +import makeMutation from "@saleor/hooks/makeMutation"; + +import { permissionGroupDetailsFragment } from "./queries"; +import { + PermissionGroupCreate, + PermissionGroupCreateVariables +} from "./types/PermissionGroupCreate"; +import { + PermissionGroupDelete, + PermissionGroupDeleteVariables +} from "./types/PermissionGroupDelete"; +import { + PermissionGroupUpdate, + PermissionGroupUpdateVariables +} from "./types/PermissionGroupUpdate"; + +export const permissionGroupErrorFragment = gql` + fragment PermissionGroupErrorFragment on PermissionGroupError { + code + field + } +`; + +export const permissionGroupDelete = gql` + ${permissionGroupErrorFragment} + mutation PermissionGroupDelete($id: ID!) { + permissionGroupDelete(id: $id) { + errors: permissionGroupErrors { + ...PermissionGroupErrorFragment + } + } + } +`; +export const usePermissionGroupDelete = makeMutation< + PermissionGroupDelete, + PermissionGroupDeleteVariables +>(permissionGroupDelete); + +export const permissionGroupCreate = gql` + ${permissionGroupDetailsFragment} + ${permissionGroupErrorFragment} + + mutation PermissionGroupCreate($input: PermissionGroupCreateInput!) { + permissionGroupCreate(input: $input) { + errors: permissionGroupErrors { + ...PermissionGroupErrorFragment + } + group { + ...PermissionGroupDetailsFragment + } + } + } +`; + +export const usePermissionGroupCreate = makeMutation< + PermissionGroupCreate, + PermissionGroupCreateVariables +>(permissionGroupCreate); + +export const permissionGroupUpdate = gql` + ${permissionGroupDetailsFragment} + ${permissionGroupErrorFragment} + + mutation PermissionGroupUpdate( + $id: ID! + $input: PermissionGroupUpdateInput! + ) { + permissionGroupUpdate(id: $id, input: $input) { + errors: permissionGroupErrors { + ...PermissionGroupErrorFragment + } + group { + ...PermissionGroupDetailsFragment + } + } + } +`; + +export const usePermissionGroupUpdate = makeMutation< + PermissionGroupUpdate, + PermissionGroupUpdateVariables +>(permissionGroupUpdate); diff --git a/src/permissionGroups/queries.ts b/src/permissionGroups/queries.ts new file mode 100644 index 000000000..80f84e6d0 --- /dev/null +++ b/src/permissionGroups/queries.ts @@ -0,0 +1,106 @@ +import gql from "graphql-tag"; + +import makeQuery from "@saleor/hooks/makeQuery"; +import { staffMemberFragment } from "@saleor/staff/queries"; + +import { pageInfoFragment } from "../queries"; +import { + PermissionGroupList, + PermissionGroupListVariables +} from "./types/PermissionGroupList"; +import { + PermissionGroupDetails, + PermissionGroupDetailsVariables +} from "./types/PermissionGroupDetails"; +export const permissionGroupFragment = gql` + fragment PermissionGroupFragment on Group { + id + name + users { + id + firstName + lastName + } + } +`; + +export const permissionFragment = gql` + fragment PermissionFragment on Permission { + code + name + } +`; + +export const permissionGroupDetailsFragment = gql` + ${permissionGroupFragment} + ${permissionFragment} + ${staffMemberFragment} + fragment PermissionGroupDetailsFragment on Group { + ...PermissionGroupFragment + permissions { + ...PermissionFragment + } + users { + ...StaffMemberFragment + } + } +`; + +export const permissionGroupListQuery = gql` + ${pageInfoFragment} + ${permissionGroupFragment} + query PermissionGroupList( + $after: String + $before: String + $first: Int + $last: Int + $filter: PermissionGroupFilterInput + $sort: PermissionGroupSortingInput + ) { + permissionGroups( + after: $after + before: $before + first: $first + last: $last + filter: $filter + sortBy: $sort + ) { + edges { + node { + ...PermissionGroupFragment + } + } + pageInfo { + ...PageInfoFragment + } + } + } +`; +export const usePermissionGroupListQuery = makeQuery< + PermissionGroupList, + PermissionGroupListVariables +>(permissionGroupListQuery); + +export const permissionGroupDetailsQuery = gql` + ${permissionGroupDetailsFragment} + query PermissionGroupDetails($id: ID!, $userId: ID!) { + permissionGroup(id: $id) { + ...PermissionGroupDetailsFragment + } + user(id: $userId) { + editableGroups { + id + } + userPermissions { + code + sourcePermissionGroups(userId: $userId) { + id + } + } + } + } +`; +export const usePermissionGroupDetailsQuery = makeQuery< + PermissionGroupDetails, + PermissionGroupDetailsVariables +>(permissionGroupDetailsQuery); diff --git a/src/permissionGroups/sort.ts b/src/permissionGroups/sort.ts new file mode 100644 index 000000000..0eca72665 --- /dev/null +++ b/src/permissionGroups/sort.ts @@ -0,0 +1,25 @@ +import { getUserName } from "@saleor/misc"; +import { PermissionGroupDetails_permissionGroup_users } from "./types/PermissionGroupDetails"; +import { MembersListUrlSortField } from "./urls"; + +export const sortMembers = (sort: string, asc: boolean) => ( + a: PermissionGroupDetails_permissionGroup_users, + b: PermissionGroupDetails_permissionGroup_users +) => { + let valueA; + let valueB; + switch (sort) { + case MembersListUrlSortField.name: + valueA = getUserName(a); + valueB = getUserName(b); + break; + case MembersListUrlSortField.email: + valueA = a.email; + valueB = b.email; + break; + } + + return asc + ? ("" + valueA).localeCompare(valueB) + : ("" + valueA).localeCompare(valueB) * -1; +}; diff --git a/src/permissionGroups/types/PermissionFragment.ts b/src/permissionGroups/types/PermissionFragment.ts new file mode 100644 index 000000000..85ffe73d3 --- /dev/null +++ b/src/permissionGroups/types/PermissionFragment.ts @@ -0,0 +1,15 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { PermissionEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL fragment: PermissionFragment +// ==================================================== + +export interface PermissionFragment { + __typename: "Permission"; + code: PermissionEnum; + name: string; +} diff --git a/src/permissionGroups/types/PermissionGroupCreate.ts b/src/permissionGroups/types/PermissionGroupCreate.ts new file mode 100644 index 000000000..b80003a2c --- /dev/null +++ b/src/permissionGroups/types/PermissionGroupCreate.ts @@ -0,0 +1,58 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { PermissionGroupCreateInput, PermissionGroupErrorCode, PermissionEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: PermissionGroupCreate +// ==================================================== + +export interface PermissionGroupCreate_permissionGroupCreate_errors { + __typename: "PermissionGroupError"; + code: PermissionGroupErrorCode; + field: string | null; +} + +export interface PermissionGroupCreate_permissionGroupCreate_group_users_avatar { + __typename: "Image"; + url: string; +} + +export interface PermissionGroupCreate_permissionGroupCreate_group_users { + __typename: "User"; + id: string; + firstName: string; + lastName: string; + email: string; + isActive: boolean; + avatar: PermissionGroupCreate_permissionGroupCreate_group_users_avatar | null; +} + +export interface PermissionGroupCreate_permissionGroupCreate_group_permissions { + __typename: "Permission"; + code: PermissionEnum; + name: string; +} + +export interface PermissionGroupCreate_permissionGroupCreate_group { + __typename: "Group"; + id: string; + name: string; + users: (PermissionGroupCreate_permissionGroupCreate_group_users | null)[] | null; + permissions: (PermissionGroupCreate_permissionGroupCreate_group_permissions | null)[] | null; +} + +export interface PermissionGroupCreate_permissionGroupCreate { + __typename: "PermissionGroupCreate"; + errors: PermissionGroupCreate_permissionGroupCreate_errors[]; + group: PermissionGroupCreate_permissionGroupCreate_group | null; +} + +export interface PermissionGroupCreate { + permissionGroupCreate: PermissionGroupCreate_permissionGroupCreate | null; +} + +export interface PermissionGroupCreateVariables { + input: PermissionGroupCreateInput; +} diff --git a/src/permissionGroups/types/PermissionGroupDelete.ts b/src/permissionGroups/types/PermissionGroupDelete.ts new file mode 100644 index 000000000..dd5a8f9d1 --- /dev/null +++ b/src/permissionGroups/types/PermissionGroupDelete.ts @@ -0,0 +1,28 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { PermissionGroupErrorCode } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: PermissionGroupDelete +// ==================================================== + +export interface PermissionGroupDelete_permissionGroupDelete_errors { + __typename: "PermissionGroupError"; + code: PermissionGroupErrorCode; + field: string | null; +} + +export interface PermissionGroupDelete_permissionGroupDelete { + __typename: "PermissionGroupDelete"; + errors: PermissionGroupDelete_permissionGroupDelete_errors[]; +} + +export interface PermissionGroupDelete { + permissionGroupDelete: PermissionGroupDelete_permissionGroupDelete | null; +} + +export interface PermissionGroupDeleteVariables { + id: string; +} diff --git a/src/permissionGroups/types/PermissionGroupDetails.ts b/src/permissionGroups/types/PermissionGroupDetails.ts new file mode 100644 index 000000000..bfe0a6a1a --- /dev/null +++ b/src/permissionGroups/types/PermissionGroupDetails.ts @@ -0,0 +1,70 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { PermissionEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL query operation: PermissionGroupDetails +// ==================================================== + +export interface PermissionGroupDetails_permissionGroup_users_avatar { + __typename: "Image"; + url: string; +} + +export interface PermissionGroupDetails_permissionGroup_users { + __typename: "User"; + id: string; + firstName: string; + lastName: string; + email: string; + isActive: boolean; + avatar: PermissionGroupDetails_permissionGroup_users_avatar | null; +} + +export interface PermissionGroupDetails_permissionGroup_permissions { + __typename: "Permission"; + code: PermissionEnum; + name: string; +} + +export interface PermissionGroupDetails_permissionGroup { + __typename: "Group"; + id: string; + name: string; + users: (PermissionGroupDetails_permissionGroup_users | null)[] | null; + permissions: (PermissionGroupDetails_permissionGroup_permissions | null)[] | null; +} + +export interface PermissionGroupDetails_user_editableGroups { + __typename: "Group"; + id: string; +} + +export interface PermissionGroupDetails_user_userPermissions_sourcePermissionGroups { + __typename: "Group"; + id: string; +} + +export interface PermissionGroupDetails_user_userPermissions { + __typename: "UserPermission"; + code: PermissionEnum; + sourcePermissionGroups: PermissionGroupDetails_user_userPermissions_sourcePermissionGroups[] | null; +} + +export interface PermissionGroupDetails_user { + __typename: "User"; + editableGroups: (PermissionGroupDetails_user_editableGroups | null)[] | null; + userPermissions: (PermissionGroupDetails_user_userPermissions | null)[] | null; +} + +export interface PermissionGroupDetails { + permissionGroup: PermissionGroupDetails_permissionGroup | null; + user: PermissionGroupDetails_user | null; +} + +export interface PermissionGroupDetailsVariables { + id: string; + userId: string; +} diff --git a/src/permissionGroups/types/PermissionGroupDetailsFragment.ts b/src/permissionGroups/types/PermissionGroupDetailsFragment.ts new file mode 100644 index 000000000..b6cc4b41c --- /dev/null +++ b/src/permissionGroups/types/PermissionGroupDetailsFragment.ts @@ -0,0 +1,38 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { PermissionEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL fragment: PermissionGroupDetailsFragment +// ==================================================== + +export interface PermissionGroupDetailsFragment_users_avatar { + __typename: "Image"; + url: string; +} + +export interface PermissionGroupDetailsFragment_users { + __typename: "User"; + id: string; + firstName: string; + lastName: string; + email: string; + isActive: boolean; + avatar: PermissionGroupDetailsFragment_users_avatar | null; +} + +export interface PermissionGroupDetailsFragment_permissions { + __typename: "Permission"; + code: PermissionEnum; + name: string; +} + +export interface PermissionGroupDetailsFragment { + __typename: "Group"; + id: string; + name: string; + users: (PermissionGroupDetailsFragment_users | null)[] | null; + permissions: (PermissionGroupDetailsFragment_permissions | null)[] | null; +} diff --git a/src/permissionGroups/types/PermissionGroupErrorFragment.ts b/src/permissionGroups/types/PermissionGroupErrorFragment.ts new file mode 100644 index 000000000..be180ba95 --- /dev/null +++ b/src/permissionGroups/types/PermissionGroupErrorFragment.ts @@ -0,0 +1,15 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { PermissionGroupErrorCode } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL fragment: PermissionGroupErrorFragment +// ==================================================== + +export interface PermissionGroupErrorFragment { + __typename: "PermissionGroupError"; + code: PermissionGroupErrorCode; + field: string | null; +} diff --git a/src/permissionGroups/types/PermissionGroupFragment.ts b/src/permissionGroups/types/PermissionGroupFragment.ts new file mode 100644 index 000000000..e7adff753 --- /dev/null +++ b/src/permissionGroups/types/PermissionGroupFragment.ts @@ -0,0 +1,21 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL fragment: PermissionGroupFragment +// ==================================================== + +export interface PermissionGroupFragment_users { + __typename: "User"; + id: string; + firstName: string; + lastName: string; +} + +export interface PermissionGroupFragment { + __typename: "Group"; + id: string; + name: string; + users: (PermissionGroupFragment_users | null)[] | null; +} diff --git a/src/permissionGroups/types/PermissionGroupList.ts b/src/permissionGroups/types/PermissionGroupList.ts new file mode 100644 index 000000000..d797e562f --- /dev/null +++ b/src/permissionGroups/types/PermissionGroupList.ts @@ -0,0 +1,55 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { PermissionGroupFilterInput, PermissionGroupSortingInput } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL query operation: PermissionGroupList +// ==================================================== + +export interface PermissionGroupList_permissionGroups_edges_node_users { + __typename: "User"; + id: string; + firstName: string; + lastName: string; +} + +export interface PermissionGroupList_permissionGroups_edges_node { + __typename: "Group"; + id: string; + name: string; + users: (PermissionGroupList_permissionGroups_edges_node_users | null)[] | null; +} + +export interface PermissionGroupList_permissionGroups_edges { + __typename: "GroupCountableEdge"; + node: PermissionGroupList_permissionGroups_edges_node; +} + +export interface PermissionGroupList_permissionGroups_pageInfo { + __typename: "PageInfo"; + endCursor: string | null; + hasNextPage: boolean; + hasPreviousPage: boolean; + startCursor: string | null; +} + +export interface PermissionGroupList_permissionGroups { + __typename: "GroupCountableConnection"; + edges: PermissionGroupList_permissionGroups_edges[]; + pageInfo: PermissionGroupList_permissionGroups_pageInfo; +} + +export interface PermissionGroupList { + permissionGroups: PermissionGroupList_permissionGroups | null; +} + +export interface PermissionGroupListVariables { + after?: string | null; + before?: string | null; + first?: number | null; + last?: number | null; + filter?: PermissionGroupFilterInput | null; + sort?: PermissionGroupSortingInput | null; +} diff --git a/src/permissionGroups/types/PermissionGroupUpdate.ts b/src/permissionGroups/types/PermissionGroupUpdate.ts new file mode 100644 index 000000000..d47ea0051 --- /dev/null +++ b/src/permissionGroups/types/PermissionGroupUpdate.ts @@ -0,0 +1,59 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { PermissionGroupUpdateInput, PermissionGroupErrorCode, PermissionEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: PermissionGroupUpdate +// ==================================================== + +export interface PermissionGroupUpdate_permissionGroupUpdate_errors { + __typename: "PermissionGroupError"; + code: PermissionGroupErrorCode; + field: string | null; +} + +export interface PermissionGroupUpdate_permissionGroupUpdate_group_users_avatar { + __typename: "Image"; + url: string; +} + +export interface PermissionGroupUpdate_permissionGroupUpdate_group_users { + __typename: "User"; + id: string; + firstName: string; + lastName: string; + email: string; + isActive: boolean; + avatar: PermissionGroupUpdate_permissionGroupUpdate_group_users_avatar | null; +} + +export interface PermissionGroupUpdate_permissionGroupUpdate_group_permissions { + __typename: "Permission"; + code: PermissionEnum; + name: string; +} + +export interface PermissionGroupUpdate_permissionGroupUpdate_group { + __typename: "Group"; + id: string; + name: string; + users: (PermissionGroupUpdate_permissionGroupUpdate_group_users | null)[] | null; + permissions: (PermissionGroupUpdate_permissionGroupUpdate_group_permissions | null)[] | null; +} + +export interface PermissionGroupUpdate_permissionGroupUpdate { + __typename: "PermissionGroupUpdate"; + errors: PermissionGroupUpdate_permissionGroupUpdate_errors[]; + group: PermissionGroupUpdate_permissionGroupUpdate_group | null; +} + +export interface PermissionGroupUpdate { + permissionGroupUpdate: PermissionGroupUpdate_permissionGroupUpdate | null; +} + +export interface PermissionGroupUpdateVariables { + id: string; + input: PermissionGroupUpdateInput; +} diff --git a/src/permissionGroups/urls.ts b/src/permissionGroups/urls.ts new file mode 100644 index 000000000..3cf733c96 --- /dev/null +++ b/src/permissionGroups/urls.ts @@ -0,0 +1,59 @@ +import { stringify as stringifyQs } from "qs"; +import urlJoin from "url-join"; + +import { + BulkAction, + Dialog, + Pagination, + TabActionDialog, + Sort, + SingleAction +} from "@saleor/types"; + +const permissionGroupSection = "/permission-groups/"; + +export const permissionGroupListPath = permissionGroupSection; + +export type PermissionGroupListUrlDialog = "remove" | TabActionDialog; +export enum PermissionGroupListUrlSortField { + name = "name" +} +export type PermissionGroupListUrlSort = Sort; +export type PermissionGroupListUrlQueryParams = Dialog< + PermissionGroupListUrlDialog +> & + Pagination & + PermissionGroupListUrlSort & + SingleAction; +export const permissionGroupListUrl = ( + params?: PermissionGroupListUrlQueryParams +) => permissionGroupListPath + "?" + stringifyQs(params); + +export const permissionGroupAddPath = urlJoin(permissionGroupSection, "add"); +export const permissionGroupAddUrl = permissionGroupAddPath; + +export enum MembersListUrlSortField { + name = "name", + email = "email" +} +export type MembersListUrlSort = Sort; + +export const permissionGroupDetailsPath = (id: string) => + urlJoin(permissionGroupSection, id); +export type PermissionGroupDetailsUrlDialog = + | "remove" + | "assign" + | "unassign" + | "unassignError"; +export type PermissionGroupDetailsUrlQueryParams = BulkAction & + Pagination & + MembersListUrlSort & + Dialog; + +export const permissionGroupDetailsUrl = ( + id: string, + params?: PermissionGroupDetailsUrlQueryParams +) => + permissionGroupDetailsPath(encodeURIComponent(id)) + + "?" + + stringifyQs(params); diff --git a/src/permissionGroups/utils.ts b/src/permissionGroups/utils.ts new file mode 100644 index 000000000..87030b101 --- /dev/null +++ b/src/permissionGroups/utils.ts @@ -0,0 +1,81 @@ +import difference from "lodash-es/difference"; + +import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInfo"; +import { User } from "@saleor/auth/types/User"; +import { PermissionGroupDetails_permissionGroup } from "./types/PermissionGroupDetails"; +import { PermissionGroupDetailsPageFormData } from "./components/PermissionGroupDetailsPage"; + +/** + * Will return true if group has all permissions available in shop assigned. + */ +export const isGroupFullAccess = ( + permissionGroup: PermissionGroupDetails_permissionGroup, + shopPermissions: ShopInfo_shop_permissions[] +) => { + const assignedCodes = extractPermissionCodes(permissionGroup); + + if (assignedCodes.length !== shopPermissions?.length) { + return false; + } + + for (const permission of shopPermissions) { + if (assignedCodes.indexOf(permission.code) === undefined) { + return false; + } + } + return true; +}; + +/** + * Return list of codes which are assigned to the permission group. + */ +export const extractPermissionCodes = ( + permissionGroup: PermissionGroupDetails_permissionGroup +) => + permissionGroup?.permissions + ? permissionGroup.permissions.map(perm => perm.code) + : []; + +/** + * Return lists of permissions which have to be added and removed from group. + */ +export const permissionsDiff = ( + permissionGroup: PermissionGroupDetails_permissionGroup, + formData: PermissionGroupDetailsPageFormData +) => { + const newPermissions = formData.permissions; + const oldPermissions = extractPermissionCodes(permissionGroup); + + return { + addPermissions: difference(newPermissions, oldPermissions), + removePermissions: difference(oldPermissions, newPermissions) + }; +}; + +/** + * Return lists of users which have to be added and removed from group. + */ +export const usersDiff = ( + permissionGroup: PermissionGroupDetails_permissionGroup, + formData: PermissionGroupDetailsPageFormData +) => { + const newUsers = formData.users.map(u => u.id); + const oldUsers = permissionGroup?.users.map(u => u.id); + + return { + addUsers: difference(newUsers, oldUsers), + removeUsers: difference(oldUsers, newUsers) + }; +}; + +/** + * Permissions are exceeded when group has permission which is not handled by user + */ +export const arePermissionsExceeded = ( + permissionGroup: PermissionGroupDetails_permissionGroup, + user: User +) => { + const groupPermissions = extractPermissionCodes(permissionGroup); + const userPermissions = user.userPermissions.map(p => p.code); + return difference(groupPermissions, userPermissions).length > 0; +}; diff --git a/src/permissionGroups/views/PermissionGroupCreate/PermissionGroupCreate.tsx b/src/permissionGroups/views/PermissionGroupCreate/PermissionGroupCreate.tsx new file mode 100644 index 000000000..0acda5ed8 --- /dev/null +++ b/src/permissionGroups/views/PermissionGroupCreate/PermissionGroupCreate.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import { useIntl } from "react-intl"; +import { WindowTitle } from "@saleor/components/WindowTitle"; +import useNavigator from "@saleor/hooks/useNavigator"; +import useNotifier from "@saleor/hooks/useNotifier"; +import useShop from "@saleor/hooks/useShop"; +import useUser from "@saleor/hooks/useUser"; +import { PermissionData } from "@saleor/permissionGroups/components/PermissionGroupDetailsPage"; +import { PermissionGroupCreate } from "../../types/PermissionGroupCreate"; +import { permissionGroupListUrl, permissionGroupDetailsUrl } from "../../urls"; +import { usePermissionGroupCreate } from "../../mutations"; +import PermissionGroupCreatePage from "../../components/PermissionGroupCreatePage"; + +const PermissionGroupCreateView: React.FC = () => { + const navigate = useNavigator(); + const notify = useNotifier(); + const intl = useIntl(); + const shop = useShop(); + const user = useUser(); + + const handleSuccess = (data: PermissionGroupCreate) => { + if (data?.permissionGroupCreate?.errors.length === 0) { + notify({ + text: intl.formatMessage({ + defaultMessage: "Permission group created" + }) + }); + navigate(permissionGroupDetailsUrl(data.permissionGroupCreate.group.id)); + } + }; + + const [ + createPermissionGroup, + createPermissionGroupResult + ] = usePermissionGroupCreate({ + onCompleted: handleSuccess + }); + + const errors = + createPermissionGroupResult?.data?.permissionGroupCreate?.errors || []; + + const onSubmit = formData => + createPermissionGroup({ + variables: { + input: { + addPermissions: formData.hasFullAccess + ? shop.permissions.map(perm => perm.code) + : formData.permissions, + addUsers: [], + name: formData.name + } + } + }); + + const userPermissions = user?.user.userPermissions.map(p => p.code) || []; + + const permissions: PermissionData[] = + shop?.permissions.map( + p => + ({ + ...p, + disabled: !userPermissions.includes(p.code), + lastSource: false + } as PermissionData) + ) || []; + + return ( + <> + + navigate(permissionGroupListUrl())} + /> + + ); +}; +PermissionGroupCreateView.displayName = "PermissionGroupCreateView"; + +export default PermissionGroupCreateView; diff --git a/src/permissionGroups/views/PermissionGroupCreate/index.ts b/src/permissionGroups/views/PermissionGroupCreate/index.ts new file mode 100644 index 000000000..56c9e6f4f --- /dev/null +++ b/src/permissionGroups/views/PermissionGroupCreate/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PermissionGroupCreate"; +export * from "./PermissionGroupCreate"; diff --git a/src/permissionGroups/views/PermissionGroupDetails/PermissionGroupDetails.tsx b/src/permissionGroups/views/PermissionGroupDetails/PermissionGroupDetails.tsx new file mode 100644 index 000000000..71f12c489 --- /dev/null +++ b/src/permissionGroups/views/PermissionGroupDetails/PermissionGroupDetails.tsx @@ -0,0 +1,216 @@ +import React, { useState } from "react"; +import useNavigator from "@saleor/hooks/useNavigator"; +import useShop from "@saleor/hooks/useShop"; +import useBulkActions from "@saleor/hooks/useBulkActions"; +import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { useIntl } from "react-intl"; +import useNotifier from "@saleor/hooks/useNotifier"; +import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; +import { Button } from "@material-ui/core"; +import { commonMessages } from "@saleor/intl"; +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import { + arePermissionsExceeded, + permissionsDiff, + usersDiff +} from "@saleor/permissionGroups/utils"; +import useStateFromProps from "@saleor/hooks/useStateFromProps"; +import useStaffMemberSearch from "@saleor/searches/useStaffMemberSearch"; +import useUser from "@saleor/hooks/useUser"; +import MembersErrorDialog from "@saleor/permissionGroups/components/MembersErrorDialog"; +import PermissionGroupDetailsPage from "../../components/PermissionGroupDetailsPage"; +import AssignMembersDialog from "../../components/AssignMembersDialog"; +import UnassignMembersDialog from "../../components/UnassignMembersDialog"; +import { usePermissionGroupDetailsQuery } from "../../queries"; +import { usePermissionGroupUpdate } from "../../mutations"; + +import { + permissionGroupDetailsUrl, + PermissionGroupDetailsUrlQueryParams, + PermissionGroupDetailsUrlDialog, + permissionGroupListUrl +} from "../../urls"; +import { PermissionGroupUpdate } from "../../types/PermissionGroupUpdate"; + +interface PermissionGroupDetailsProps { + id: string; + params: PermissionGroupDetailsUrlQueryParams; +} + +export const PermissionGroupDetails: React.FC = ({ + id, + params +}) => { + const navigate = useNavigator(); + const shop = useShop(); + const notify = useNotifier(); + const intl = useIntl(); + const user = useUser(); + + const { data, loading, refetch } = usePermissionGroupDetailsQuery({ + displayLoader: true, + variables: { id, userId: user?.user.id } + }); + + const [membersList, setMembersList] = useStateFromProps( + data?.permissionGroup.users + ); + + const [membersModified, setMembersModified] = useState(false); + + const { search, result: searchResult, loadMore } = useStaffMemberSearch({ + variables: DEFAULT_INITIAL_SEARCH_DATA + }); + + const handleUpdateSuccess = (data: PermissionGroupUpdate) => { + if (data.permissionGroupUpdate.errors.length === 0) { + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + refetch(); + closeModal(); + } else if ( + data.permissionGroupUpdate.errors.some(e => e.field === "removeUsers") + ) { + openModal("unassignError"); + } + }; + + const { isSelected, listElements, toggle, toggleAll } = useBulkActions( + params.ids + ); + + const [ + permissionGroupUpdate, + permissionGroupUpdateResult + ] = usePermissionGroupUpdate({ + onCompleted: handleUpdateSuccess + }); + + const [openModal, closeModal] = createDialogActionHandlers< + PermissionGroupDetailsUrlDialog, + PermissionGroupDetailsUrlQueryParams + >(navigate, params => permissionGroupDetailsUrl(id, params), params); + + const handleSort = createSortHandler( + navigate, + params => permissionGroupDetailsUrl(id, params), + params + ); + + const unassignMembers = () => { + setMembersList(membersList?.filter(m => !listElements.includes(m.id))); + setMembersModified(true); + closeModal(); + }; + + const isGroupEditable = + (data?.user.editableGroups || []).filter(g => g.id === id).length > 0; + + const lastSourcesOfPermission = (data?.user.userPermissions || []) + .filter( + perm => + perm.sourcePermissionGroups.length === 1 && + perm.sourcePermissionGroups[0].id === id + ) + .map(perm => perm.code); + + const userPermissions = user?.user.userPermissions.map(p => p.code) || []; + + const permissions = (shop?.permissions || []).map(perm => ({ + ...perm, + disabled: !userPermissions.includes(perm.code), + lastSource: lastSourcesOfPermission.includes(perm.code) + })); + + const permissionsExceeded = arePermissionsExceeded( + data?.permissionGroup, + user.user + ); + const disabled = loading || !isGroupEditable || permissionsExceeded; + + return ( + <> + navigate(permissionGroupListUrl())} + onAssign={() => openModal("assign")} + onUnassign={ids => openModal("unassign", { ids })} + errors={ + permissionGroupUpdateResult?.data?.permissionGroupUpdate.errors || [] + } + onSubmit={formData => + permissionGroupUpdate({ + variables: { + id, + input: { + name: formData.name, + ...permissionsDiff(data?.permissionGroup, formData), + ...usersDiff(data?.permissionGroup, formData) + } + } + }) + } + permissions={permissions} + saveButtonBarState={permissionGroupUpdateResult.status} + disabled={disabled} + toggle={toggle} + toggleAll={toggleAll} + isChecked={isSelected} + selected={listElements.length} + sort={getSortParams(params)} + toolbar={ + + } + onSort={handleSort} + /> + edge.node)} + onSearchChange={search} + onFetchMore={loadMore} + disabled={disabled} + hasMore={searchResult?.data?.search.pageInfo.hasNextPage} + initialSearch="" + confirmButtonState={permissionGroupUpdateResult.status} + open={params.action === "assign"} + onClose={closeModal} + onSubmit={formData => { + setMembersList([ + ...membersList, + ...formData.filter(member => !membersList.includes(member)) + ]); + setMembersModified(true); + closeModal(); + }} + /> + + + + ); +}; + +export default PermissionGroupDetails; diff --git a/src/permissionGroups/views/PermissionGroupDetails/index.ts b/src/permissionGroups/views/PermissionGroupDetails/index.ts new file mode 100644 index 000000000..af25c63e2 --- /dev/null +++ b/src/permissionGroups/views/PermissionGroupDetails/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PermissionGroupDetails"; +export * from "./PermissionGroupDetails"; diff --git a/src/permissionGroups/views/PermissionGroupList/PermissionGroupList.tsx b/src/permissionGroups/views/PermissionGroupList/PermissionGroupList.tsx new file mode 100644 index 000000000..74ca807b7 --- /dev/null +++ b/src/permissionGroups/views/PermissionGroupList/PermissionGroupList.tsx @@ -0,0 +1,129 @@ +import React from "react"; +import { useIntl } from "react-intl"; + +import useListSettings from "@saleor/hooks/useListSettings"; +import useNavigator from "@saleor/hooks/useNavigator"; + +import usePaginator, { + createPaginationState +} from "@saleor/hooks/usePaginator"; +import { configurationMenuUrl } from "@saleor/configuration"; +import useNotifier from "@saleor/hooks/useNotifier"; +import { ListViews } from "@saleor/types"; +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import PermissionGroupDeleteDialog from "@saleor/permissionGroups/components/PermissionGroupDeleteDialog"; +import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { usePermissionGroupListQuery } from "@saleor/permissionGroups/queries"; +import { PermissionGroupDelete } from "@saleor/permissionGroups/types/PermissionGroupDelete"; +import { usePermissionGroupDelete } from "@saleor/permissionGroups/mutations"; +import { getStringOrPlaceholder } from "@saleor/misc"; +import PermissionGroupListPage from "../../components/PermissionGroupListPage"; +import { + permissionGroupListUrl, + permissionGroupAddUrl, + PermissionGroupListUrlQueryParams, + permissionGroupDetailsUrl, + PermissionGroupListUrlDialog +} from "../../urls"; +import { getSortQueryVariables } from "./sort"; + +interface PermissionGroupListProps { + params: PermissionGroupListUrlQueryParams; +} + +export const PermissionGroupList: React.FC = ({ + params +}) => { + const navigate = useNavigator(); + const paginate = usePaginator(); + const notify = useNotifier(); + const intl = useIntl(); + const { updateListSettings, settings } = useListSettings( + ListViews.STAFF_MEMBERS_LIST + ); + + const paginationState = createPaginationState(settings.rowNumber, params); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + sort: getSortQueryVariables(params) + }), + [params] + ); + const { data, loading, refetch } = usePermissionGroupListQuery({ + displayLoader: true, + variables: queryVariables + }); + + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + data?.permissionGroups.pageInfo, + paginationState, + params + ); + + const handleSort = createSortHandler( + navigate, + permissionGroupListUrl, + params + ); + + const [openModal, closeModal] = createDialogActionHandlers< + PermissionGroupListUrlDialog, + PermissionGroupListUrlQueryParams + >(navigate, permissionGroupListUrl, params); + + const permissionGroups = data?.permissionGroups?.edges.map(edge => edge.node); + + const handleDeleteSuccess = (data: PermissionGroupDelete) => { + if (data.permissionGroupDelete.errors.length === 0) { + notify({ + text: intl.formatMessage({ + defaultMessage: "Permission Group Deleted" + }) + }); + refetch(); + closeModal(); + } + }; + + const [permissionGroupDelete] = usePermissionGroupDelete({ + onCompleted: handleDeleteSuccess + }); + return ( + <> + navigate(permissionGroupAddUrl)} + onBack={() => navigate(configurationMenuUrl)} + onDelete={id => openModal("remove", { id })} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onUpdateListSettings={updateListSettings} + onRowClick={id => () => navigate(permissionGroupDetailsUrl(id))} + onSort={handleSort} + /> + + permissionGroupDelete({ + variables: { + id: params.id + } + }) + } + name={getStringOrPlaceholder( + permissionGroups?.find(group => group.id === params.id)?.name + )} + confirmButtonState={"default"} + open={params.action === "remove"} + onClose={closeModal} + /> + + ); +}; + +export default PermissionGroupList; diff --git a/src/permissionGroups/views/PermissionGroupList/index.ts b/src/permissionGroups/views/PermissionGroupList/index.ts new file mode 100644 index 000000000..9cfd81517 --- /dev/null +++ b/src/permissionGroups/views/PermissionGroupList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PermissionGroupList"; +export * from "./PermissionGroupList"; diff --git a/src/permissionGroups/views/PermissionGroupList/sort.ts b/src/permissionGroups/views/PermissionGroupList/sort.ts new file mode 100644 index 000000000..db3b6832f --- /dev/null +++ b/src/permissionGroups/views/PermissionGroupList/sort.ts @@ -0,0 +1,18 @@ +import { PermissionGroupListUrlSortField } from "@saleor/permissionGroups/urls"; +import { PermissionGroupSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField( + sort: PermissionGroupListUrlSortField +): PermissionGroupSortField { + switch (sort) { + case PermissionGroupListUrlSortField.name: + return PermissionGroupSortField.NAME; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/searches/types/SearchPermissionGroups.ts b/src/searches/types/SearchPermissionGroups.ts new file mode 100644 index 000000000..205fe7c97 --- /dev/null +++ b/src/searches/types/SearchPermissionGroups.ts @@ -0,0 +1,43 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: SearchPermissionGroups +// ==================================================== + +export interface SearchPermissionGroups_search_edges_node { + __typename: "Group"; + id: string; + name: string; + userCanManage: boolean; +} + +export interface SearchPermissionGroups_search_edges { + __typename: "GroupCountableEdge"; + node: SearchPermissionGroups_search_edges_node; +} + +export interface SearchPermissionGroups_search_pageInfo { + __typename: "PageInfo"; + endCursor: string | null; + hasNextPage: boolean; + hasPreviousPage: boolean; + startCursor: string | null; +} + +export interface SearchPermissionGroups_search { + __typename: "GroupCountableConnection"; + edges: SearchPermissionGroups_search_edges[]; + pageInfo: SearchPermissionGroups_search_pageInfo; +} + +export interface SearchPermissionGroups { + search: SearchPermissionGroups_search | null; +} + +export interface SearchPermissionGroupsVariables { + after?: string | null; + first: number; + query: string; +} diff --git a/src/searches/types/SearchStaffMembers.ts b/src/searches/types/SearchStaffMembers.ts new file mode 100644 index 000000000..08634b3a3 --- /dev/null +++ b/src/searches/types/SearchStaffMembers.ts @@ -0,0 +1,52 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: SearchStaffMembers +// ==================================================== + +export interface SearchStaffMembers_search_edges_node_avatar { + __typename: "Image"; + alt: string | null; + url: string; +} + +export interface SearchStaffMembers_search_edges_node { + __typename: "User"; + id: string; + email: string; + firstName: string; + lastName: string; + isActive: boolean; + avatar: SearchStaffMembers_search_edges_node_avatar | null; +} + +export interface SearchStaffMembers_search_edges { + __typename: "UserCountableEdge"; + node: SearchStaffMembers_search_edges_node; +} + +export interface SearchStaffMembers_search_pageInfo { + __typename: "PageInfo"; + endCursor: string | null; + hasNextPage: boolean; + hasPreviousPage: boolean; + startCursor: string | null; +} + +export interface SearchStaffMembers_search { + __typename: "UserCountableConnection"; + edges: SearchStaffMembers_search_edges[]; + pageInfo: SearchStaffMembers_search_pageInfo; +} + +export interface SearchStaffMembers { + search: SearchStaffMembers_search | null; +} + +export interface SearchStaffMembersVariables { + after?: string | null; + first: number; + query: string; +} diff --git a/src/searches/usePermissionGroupSearch.ts b/src/searches/usePermissionGroupSearch.ts new file mode 100644 index 000000000..edae98092 --- /dev/null +++ b/src/searches/usePermissionGroupSearch.ts @@ -0,0 +1,35 @@ +import gql from "graphql-tag"; + +import makeTopLevelSearch from "@saleor/hooks/makeTopLevelSearch"; +import { pageInfoFragment } from "@saleor/queries"; +import { + SearchPermissionGroups, + SearchPermissionGroupsVariables +} from "./types/SearchPermissionGroups"; + +export const searchPermissionGroups = gql` + ${pageInfoFragment} + query SearchPermissionGroups($after: String, $first: Int!, $query: String!) { + search: permissionGroups( + after: $after + first: $first + filter: { search: $query } + ) { + edges { + node { + id + name + userCanManage + } + } + pageInfo { + ...PageInfoFragment + } + } + } +`; + +export default makeTopLevelSearch< + SearchPermissionGroups, + SearchPermissionGroupsVariables +>(searchPermissionGroups); diff --git a/src/searches/useStaffMemberSearch.ts b/src/searches/useStaffMemberSearch.ts new file mode 100644 index 000000000..87c007c33 --- /dev/null +++ b/src/searches/useStaffMemberSearch.ts @@ -0,0 +1,41 @@ +import gql from "graphql-tag"; + +import makeTopLevelSearch from "@saleor/hooks/makeTopLevelSearch"; +import { pageInfoFragment } from "@saleor/queries"; +import { + SearchStaffMembers, + SearchStaffMembersVariables +} from "./types/SearchStaffMembers"; + +export const searchStaffMembers = gql` + ${pageInfoFragment} + query SearchStaffMembers($after: String, $first: Int!, $query: String!) { + search: staffUsers( + after: $after + first: $first + filter: { search: $query } + ) { + edges { + node { + id + email + firstName + lastName + isActive + avatar { + alt + url + } + } + } + pageInfo { + ...PageInfoFragment + } + } + } +`; + +export default makeTopLevelSearch< + SearchStaffMembers, + SearchStaffMembersVariables +>(searchStaffMembers); diff --git a/src/services/components/ServiceCreatePage/ServiceCreatePage.tsx b/src/services/components/ServiceCreatePage/ServiceCreatePage.tsx index d9eaa4e5c..22edb1f59 100644 --- a/src/services/components/ServiceCreatePage/ServiceCreatePage.tsx +++ b/src/services/components/ServiceCreatePage/ServiceCreatePage.tsx @@ -15,6 +15,8 @@ import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInf import { sectionNames } from "@saleor/intl"; import { PermissionEnum } from "@saleor/types/globalTypes"; import { AccountErrorFragment } from "@saleor/customers/types/AccountErrorFragment"; +import { getFormErrors } from "@saleor/utils/errors"; +import getAccountErrorMessage from "@saleor/utils/errors/account"; import ServiceInfo from "../ServiceInfo"; export interface ServiceCreatePageFormData { @@ -49,6 +51,10 @@ const ServiceCreatePage: React.FC = props => { name: "", permissions: [] }; + + const formErrors = getFormErrors(["permissions"], errors || []); + const permissionsError = getAccountErrorMessage(formErrors.permissions, intl); + return (
{({ data, change, hasChanged, submit }) => ( @@ -73,9 +79,20 @@ const ServiceCreatePage: React.FC = props => {
= props => { } = props; const intl = useIntl(); + const formErrors = getFormErrors(["permissions"], errors || []); + const permissionsError = getAccountErrorMessage(formErrors.permissions, intl); + const initialForm: ServiceDetailsPageFormData = { hasFullAccess: maybe( () => @@ -115,9 +120,20 @@ const ServiceDetailsPage: React.FC = props => {
(serviceCreateMutation); const serviceDeleteMutation = gql` - ${accountFragmentError} + ${accountErrorFragment} mutation ServiceDelete($id: ID!) { serviceAccountDelete(id: $id) { errors: accountErrors { @@ -52,7 +52,7 @@ export const ServiceDeleteMutation = TypedMutation< >(serviceDeleteMutation); const serviceUpdateMutation = gql` - ${accountFragmentError} + ${accountErrorFragment} ${serviceDetailsFragment} mutation ServiceUpdate($id: ID!, $input: ServiceAccountInput!) { serviceAccountUpdate(id: $id, input: $input) { @@ -72,7 +72,7 @@ export const ServiceUpdateMutation = TypedMutation< >(serviceUpdateMutation); const serviceTokenCreate = gql` - ${accountFragmentError} + ${accountErrorFragment} mutation ServiceTokenCreate($input: ServiceAccountTokenInput!) { serviceAccountTokenCreate(input: $input) { authToken @@ -88,7 +88,7 @@ export const ServiceTokenCreateMutation = TypedMutation< >(serviceTokenCreate); const serviceTokenDelete = gql` - ${accountFragmentError} + ${accountErrorFragment} mutation ServiceTokenDelete($id: ID!) { serviceAccountTokenDelete(id: $id) { errors: accountErrors { diff --git a/src/shipping/views/ShippingZonesList.tsx b/src/shipping/views/ShippingZonesList.tsx index dc41c7991..488e629ae 100644 --- a/src/shipping/views/ShippingZonesList.tsx +++ b/src/shipping/views/ShippingZonesList.tsx @@ -177,7 +177,7 @@ export const ShippingZonesList: React.FC = ({ } - userPermissions={maybe(() => user.permissions, [])} + userPermissions={user?.userPermissions || []} /> void; - onConfirm: (data: FormData) => void; + onConfirm: (data: AddMemberFormData) => void; } const StaffAddMemberDialog: React.FC = props => { - const { confirmButtonState, errors, open, onClose, onConfirm } = props; + const { confirmButtonState, errors, onClose, onConfirm, open } = props; const classes = useStyles(props); const dialogErrors = useModalDialogErrors(errors, open); const intl = useIntl(); - const formErrors = getFormErrors( ["firstName", "lastName", "email"], dialogErrors @@ -78,7 +82,7 @@ const StaffAddMemberDialog: React.FC = props => { return ( - {({ change, data, hasChanged }) => ( + {({ change, data: formData, hasChanged }) => ( <> = props => {
@@ -111,14 +121,18 @@ const StaffAddMemberDialog: React.FC = props => { +
+ {!!formErrors.id && ( + + + {getStaffErrorMessage(formErrors.id, intl)} + + + )} ); }; diff --git a/src/staff/fixtures.ts b/src/staff/fixtures.ts index 5909919f2..ce002d923 100644 --- a/src/staff/fixtures.ts +++ b/src/staff/fixtures.ts @@ -145,5 +145,9 @@ export const staffMember: StaffMemberDetails_user = { id: "VXNlcjoyMQ==", isActive: true, lastName: "Smith", - permissions + permissionGroups: [], + userPermissions: permissions.map(p => ({ + ...p, + __typename: "UserPermission" + })) }; diff --git a/src/staff/mutations.ts b/src/staff/mutations.ts index 424caa9d4..2987a1f9a 100644 --- a/src/staff/mutations.ts +++ b/src/staff/mutations.ts @@ -1,9 +1,12 @@ import gql from "graphql-tag"; - import makeMutation from "@saleor/hooks/makeMutation"; -import { accountFragmentError } from "@saleor/customers/mutations"; +import { accountErrorFragment } from "@saleor/customers/mutations"; import { TypedMutation } from "../mutations"; import { staffMemberDetailsFragment } from "./queries"; +import { + ChangeStaffPassword, + ChangeStaffPasswordVariables +} from "./types/ChangeStaffPassword"; import { StaffAvatarDelete } from "./types/StaffAvatarDelete"; import { StaffAvatarUpdate, @@ -21,17 +24,21 @@ import { StaffMemberUpdate, StaffMemberUpdateVariables } from "./types/StaffMemberUpdate"; -import { - ChangeStaffPassword, - ChangeStaffPasswordVariables -} from "./types/ChangeStaffPassword"; -const staffErrorFragment = gql` +export const staffErrorFragment = gql` fragment StaffErrorFragment on StaffError { code field } `; + +export const staffFragmentError = gql` + fragment StaffErrorFragment on StaffError { + code + field + } +`; + const staffMemberAddMutation = gql` ${staffErrorFragment} ${staffMemberDetailsFragment} @@ -52,7 +59,7 @@ export const TypedStaffMemberAddMutation = TypedMutation< >(staffMemberAddMutation); const staffMemberUpdateMutation = gql` - ${accountFragmentError} + ${staffErrorFragment} ${staffMemberDetailsFragment} mutation StaffMemberUpdate($id: ID!, $input: StaffUpdateInput!) { staffUpdate(id: $id, input: $input) { @@ -86,7 +93,7 @@ export const TypedStaffMemberDeleteMutation = TypedMutation< >(staffMemberDeleteMutation); const staffAvatarUpdateMutation = gql` - ${accountFragmentError} + ${accountErrorFragment} mutation StaffAvatarUpdate($image: Upload!) { userAvatarUpdate(image: $image) { errors: accountErrors { @@ -107,7 +114,7 @@ export const TypedStaffAvatarUpdateMutation = TypedMutation< >(staffAvatarUpdateMutation); const staffAvatarDeleteMutation = gql` - ${accountFragmentError} + ${accountErrorFragment} mutation StaffAvatarDelete { userAvatarDelete { errors: accountErrors { @@ -128,7 +135,7 @@ export const TypedStaffAvatarDeleteMutation = TypedMutation< >(staffAvatarDeleteMutation); const changeStaffPassword = gql` - ${accountFragmentError} + ${accountErrorFragment} mutation ChangeStaffPassword($newPassword: String!, $oldPassword: String!) { passwordChange(newPassword: $newPassword, oldPassword: $oldPassword) { errors: accountErrors { diff --git a/src/staff/queries.ts b/src/staff/queries.ts index 382b12c82..8c0015064 100644 --- a/src/staff/queries.ts +++ b/src/staff/queries.ts @@ -23,7 +23,12 @@ export const staffMemberDetailsFragment = gql` ${staffMemberFragment} fragment StaffMemberDetailsFragment on User { ...StaffMemberFragment - permissions { + permissionGroups { + id + name + userCanManage + } + userPermissions { code name } diff --git a/src/staff/types/StaffMemberAdd.ts b/src/staff/types/StaffMemberAdd.ts index 150d76de9..d42e80dcf 100644 --- a/src/staff/types/StaffMemberAdd.ts +++ b/src/staff/types/StaffMemberAdd.ts @@ -19,8 +19,15 @@ export interface StaffMemberAdd_staffCreate_user_avatar { url: string; } -export interface StaffMemberAdd_staffCreate_user_permissions { - __typename: "Permission"; +export interface StaffMemberAdd_staffCreate_user_permissionGroups { + __typename: "Group"; + id: string; + name: string; + userCanManage: boolean; +} + +export interface StaffMemberAdd_staffCreate_user_userPermissions { + __typename: "UserPermission"; code: PermissionEnum; name: string; } @@ -33,7 +40,8 @@ export interface StaffMemberAdd_staffCreate_user { isActive: boolean; lastName: string; avatar: StaffMemberAdd_staffCreate_user_avatar | null; - permissions: (StaffMemberAdd_staffCreate_user_permissions | null)[] | null; + permissionGroups: (StaffMemberAdd_staffCreate_user_permissionGroups | null)[] | null; + userPermissions: (StaffMemberAdd_staffCreate_user_userPermissions | null)[] | null; } export interface StaffMemberAdd_staffCreate { diff --git a/src/staff/types/StaffMemberDetails.ts b/src/staff/types/StaffMemberDetails.ts index 34e87db3e..1935e7461 100644 --- a/src/staff/types/StaffMemberDetails.ts +++ b/src/staff/types/StaffMemberDetails.ts @@ -13,8 +13,15 @@ export interface StaffMemberDetails_user_avatar { url: string; } -export interface StaffMemberDetails_user_permissions { - __typename: "Permission"; +export interface StaffMemberDetails_user_permissionGroups { + __typename: "Group"; + id: string; + name: string; + userCanManage: boolean; +} + +export interface StaffMemberDetails_user_userPermissions { + __typename: "UserPermission"; code: PermissionEnum; name: string; } @@ -27,7 +34,8 @@ export interface StaffMemberDetails_user { isActive: boolean; lastName: string; avatar: StaffMemberDetails_user_avatar | null; - permissions: (StaffMemberDetails_user_permissions | null)[] | null; + permissionGroups: (StaffMemberDetails_user_permissionGroups | null)[] | null; + userPermissions: (StaffMemberDetails_user_userPermissions | null)[] | null; } export interface StaffMemberDetails { diff --git a/src/staff/types/StaffMemberDetailsFragment.ts b/src/staff/types/StaffMemberDetailsFragment.ts index f29a7335f..a52f00126 100644 --- a/src/staff/types/StaffMemberDetailsFragment.ts +++ b/src/staff/types/StaffMemberDetailsFragment.ts @@ -13,8 +13,15 @@ export interface StaffMemberDetailsFragment_avatar { url: string; } -export interface StaffMemberDetailsFragment_permissions { - __typename: "Permission"; +export interface StaffMemberDetailsFragment_permissionGroups { + __typename: "Group"; + id: string; + name: string; + userCanManage: boolean; +} + +export interface StaffMemberDetailsFragment_userPermissions { + __typename: "UserPermission"; code: PermissionEnum; name: string; } @@ -27,5 +34,6 @@ export interface StaffMemberDetailsFragment { isActive: boolean; lastName: string; avatar: StaffMemberDetailsFragment_avatar | null; - permissions: (StaffMemberDetailsFragment_permissions | null)[] | null; + permissionGroups: (StaffMemberDetailsFragment_permissionGroups | null)[] | null; + userPermissions: (StaffMemberDetailsFragment_userPermissions | null)[] | null; } diff --git a/src/staff/types/StaffMemberUpdate.ts b/src/staff/types/StaffMemberUpdate.ts index 3d0ac85ab..b7ce4c317 100644 --- a/src/staff/types/StaffMemberUpdate.ts +++ b/src/staff/types/StaffMemberUpdate.ts @@ -19,8 +19,15 @@ export interface StaffMemberUpdate_staffUpdate_user_avatar { url: string; } -export interface StaffMemberUpdate_staffUpdate_user_permissions { - __typename: "Permission"; +export interface StaffMemberUpdate_staffUpdate_user_permissionGroups { + __typename: "Group"; + id: string; + name: string; + userCanManage: boolean; +} + +export interface StaffMemberUpdate_staffUpdate_user_userPermissions { + __typename: "UserPermission"; code: PermissionEnum; name: string; } @@ -33,7 +40,8 @@ export interface StaffMemberUpdate_staffUpdate_user { isActive: boolean; lastName: string; avatar: StaffMemberUpdate_staffUpdate_user_avatar | null; - permissions: (StaffMemberUpdate_staffUpdate_user_permissions | null)[] | null; + permissionGroups: (StaffMemberUpdate_staffUpdate_user_permissionGroups | null)[] | null; + userPermissions: (StaffMemberUpdate_staffUpdate_user_userPermissions | null)[] | null; } export interface StaffMemberUpdate_staffUpdate { diff --git a/src/staff/utils.ts b/src/staff/utils.ts new file mode 100644 index 000000000..b2b870d3f --- /dev/null +++ b/src/staff/utils.ts @@ -0,0 +1,20 @@ +import difference from "lodash-es/difference"; + +import { StaffMemberDetails_user } from "./types/StaffMemberDetails"; +import { StaffDetailsFormData } from "./components/StaffDetailsPage"; + +/** + * Return lists of groups which have to be added and removed from user. + */ +export const groupsDiff = ( + user: StaffMemberDetails_user, + formData: StaffDetailsFormData +) => { + const newGroups = formData.permissionGroups; + const oldGroups = user.permissionGroups.map(u => u.id); + + return { + addGroups: difference(newGroups, oldGroups), + removeGroups: difference(oldGroups, newGroups) + }; +}; diff --git a/src/staff/views/StaffDetails.tsx b/src/staff/views/StaffDetails.tsx index 0315f7e3a..73a88b6f8 100644 --- a/src/staff/views/StaffDetails.tsx +++ b/src/staff/views/StaffDetails.tsx @@ -1,17 +1,20 @@ -import DialogContentText from "@material-ui/core/DialogContentText"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; +import DialogContentText from "@material-ui/core/DialogContentText"; import ActionDialog from "@saleor/components/ActionDialog"; +import NotFoundPage from "@saleor/components/NotFoundPage"; import { WindowTitle } from "@saleor/components/WindowTitle"; +import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; -import useShop from "@saleor/hooks/useShop"; import useUser from "@saleor/hooks/useUser"; import { commonMessages } from "@saleor/intl"; -import NotFoundPage from "@saleor/components/NotFoundPage"; -import { maybe } from "../../misc"; +import { maybe, getStringOrPlaceholder } from "@saleor/misc"; +import usePermissionGroupSearch from "@saleor/searches/usePermissionGroupSearch"; + import StaffDetailsPage from "../components/StaffDetailsPage/StaffDetailsPage"; +import StaffPasswordResetDialog from "../components/StaffPasswordResetDialog"; import { TypedStaffAvatarDeleteMutation, TypedStaffAvatarUpdateMutation, @@ -20,6 +23,7 @@ import { useChangeStaffPassword } from "../mutations"; import { TypedStaffMemberDetailsQuery } from "../queries"; +import { ChangeStaffPassword } from "../types/ChangeStaffPassword"; import { StaffAvatarDelete } from "../types/StaffAvatarDelete"; import { StaffAvatarUpdate } from "../types/StaffAvatarUpdate"; import { StaffMemberDelete } from "../types/StaffMemberDelete"; @@ -29,8 +33,7 @@ import { staffMemberDetailsUrl, StaffMemberDetailsUrlQueryParams } from "../urls"; -import StaffPasswordResetDialog from "../components/StaffPasswordResetDialog"; -import { ChangeStaffPassword } from "../types/ChangeStaffPassword"; +import { groupsDiff } from "../utils"; interface OrderListProps { id: string; @@ -42,7 +45,6 @@ export const StaffDetails: React.FC = ({ id, params }) => { const notify = useNotifier(); const user = useUser(); const intl = useIntl(); - const shop = useShop(); const closeModal = () => navigate( @@ -66,6 +68,14 @@ export const StaffDetails: React.FC = ({ id, params }) => { const handleBack = () => navigate(staffListUrl()); + const { + loadMore: loadMorePermissionGroups, + search: searchPermissionGroups, + result: searchPermissionGroupsOpts + } = usePermissionGroupSearch({ + variables: DEFAULT_INITIAL_SEARCH_DATA + }); + return ( {({ data, loading }) => { @@ -122,23 +132,27 @@ export const StaffDetails: React.FC = ({ id, params }) => { onCompleted={handleStaffMemberAvatarDelete} > {(deleteStaffAvatar, deleteAvatarResult) => { - const isUserSameAsViewer = maybe( - () => user.user.id === data.user.id, - true - ); + const isUserSameAsViewer = + user.user?.id === data?.user?.id; return ( <> navigate( staffMemberDetailsUrl(id, { @@ -153,7 +167,7 @@ export const StaffDetails: React.FC = ({ id, params }) => { }) ) } - onSubmit={variables => + onSubmit={variables => { updateStaffMember({ variables: { id, @@ -161,11 +175,12 @@ export const StaffDetails: React.FC = ({ id, params }) => { email: variables.email, firstName: variables.firstName, isActive: variables.isActive, - lastName: variables.lastName + lastName: variables.lastName, + ...groupsDiff(data?.user, variables) } } - }) - } + }); + }} onImageUpload={file => updateStaffAvatar({ variables: { @@ -180,9 +195,19 @@ export const StaffDetails: React.FC = ({ id, params }) => { }) ) } - permissions={maybe(() => shop.permissions)} + availablePermissionGroups={searchPermissionGroupsOpts.data?.search.edges.map( + edge => edge.node + )} staffMember={staffMember} saveButtonBarState={updateResult.status} + fetchMorePermissionGroups={{ + hasMore: + searchPermissionGroupsOpts.data?.search + .pageInfo.hasNextPage, + loading: searchPermissionGroupsOpts.loading, + onFetchMore: loadMorePermissionGroups + }} + onSearchChange={searchPermissionGroups} /> = ({ id, params }) => { data.user.email, "...") + email: getStringOrPlaceholder( + data?.user?.email + ) }} /> @@ -221,7 +248,9 @@ export const StaffDetails: React.FC = ({ id, params }) => { values={{ email: ( - {maybe(() => data.user.email, "...")} + {getStringOrPlaceholder( + data?.user?.email + )} ) }} @@ -230,12 +259,10 @@ export const StaffDetails: React.FC = ({ id, params }) => { - changePasswordOpts.data.passwordChange - .errors, - [] - )} + errors={ + changePasswordOpts?.data?.passwordChange + ?.errors || [] + } open={params.action === "change-password"} onClose={closeModal} onSubmit={data => diff --git a/src/staff/views/StaffList/StaffList.tsx b/src/staff/views/StaffList/StaffList.tsx index 44f7cc748..86a5c3c71 100644 --- a/src/staff/views/StaffList/StaffList.tsx +++ b/src/staff/views/StaffList/StaffList.tsx @@ -1,31 +1,32 @@ import React from "react"; -import urlJoin from "url-join"; - -import useListSettings from "@saleor/hooks/useListSettings"; -import useNavigator from "@saleor/hooks/useNavigator"; -import useNotifier from "@saleor/hooks/useNotifier"; -import usePaginator, { - createPaginationState -} from "@saleor/hooks/usePaginator"; import { useIntl } from "react-intl"; +import urlJoin from "url-join"; import { newPasswordUrl } from "@saleor/auth/urls"; import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog"; import SaveFilterTabDialog, { SaveFilterTabDialogFormData } from "@saleor/components/SaveFilterTabDialog"; -import { APP_MOUNT_URI } from "@saleor/config"; +import { APP_MOUNT_URI, DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; import { configurationMenuUrl } from "@saleor/configuration"; +import useListSettings from "@saleor/hooks/useListSettings"; +import useNavigator from "@saleor/hooks/useNavigator"; +import useNotifier from "@saleor/hooks/useNotifier"; +import usePaginator, { + createPaginationState +} from "@saleor/hooks/usePaginator"; import useShop from "@saleor/hooks/useShop"; import { commonMessages } from "@saleor/intl"; -import { maybe } from "@saleor/misc"; +import usePermissionGroupSearch from "@saleor/searches/usePermissionGroupSearch"; import { ListViews } from "@saleor/types"; -import { getSortParams } from "@saleor/utils/sort"; -import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import { getSortParams } from "@saleor/utils/sort"; +import { getStringOrPlaceholder } from "@saleor/misc"; + import StaffAddMemberDialog, { - FormData as AddStaffMemberForm + AddMemberFormData } from "../../components/StaffAddMemberDialog"; import StaffListPage from "../../components/StaffListPage"; import { TypedStaffMemberAddMutation } from "../../mutations"; @@ -41,11 +42,11 @@ import { areFiltersApplied, deleteFilterTab, getActiveFilters, + getFilterOpts, + getFilterQueryParam, getFilterTabs, getFilterVariables, - saveFilterTab, - getFilterQueryParam, - getFilterOpts + saveFilterTab } from "./filters"; import { getSortQueryVariables } from "./sort"; @@ -64,7 +65,7 @@ export const StaffList: React.FC = ({ params }) => { const shop = useShop(); const paginationState = createPaginationState(settings.rowNumber, params); - const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); + const currencySymbol = shop?.defaultCurrency || "USD"; const queryVariables = React.useMemo( () => ({ ...paginationState, @@ -73,11 +74,19 @@ export const StaffList: React.FC = ({ params }) => { }), [params] ); - const { data, loading } = useStaffListQuery({ + const { data: staffQueryData, loading } = useStaffListQuery({ displayLoader: true, variables: queryVariables }); + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + staffQueryData?.staffUsers.pageInfo, + paginationState, + params + ); + + const handleSort = createSortHandler(navigate, staffListUrl, params); + const tabs = getFilterTabs(); const currentTab = @@ -131,13 +140,22 @@ export const StaffList: React.FC = ({ params }) => { } }; + const { + loadMore: loadMorePermissionGroups, + search: searchPermissionGroups, + result: searchPermissionGroupsOpts + } = usePermissionGroupSearch({ + variables: DEFAULT_INITIAL_SEARCH_DATA + }); + return ( {(addStaffMember, addStaffMemberData) => { - const handleStaffMemberAdd = (variables: AddStaffMemberForm) => + const handleStaffMemberAdd = (variables: AddMemberFormData) => addStaffMember({ variables: { input: { + addGroups: variables.permissionGroups, email: variables.email, firstName: variables.firstName, lastName: variables.lastName, @@ -150,14 +168,6 @@ export const StaffList: React.FC = ({ params }) => { } }); - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.staffUsers.pageInfo), - paginationState, - params - ); - - const handleSort = createSortHandler(navigate, staffListUrl, params); - return ( <> = ({ params }) => { settings={settings} pageInfo={pageInfo} sort={getSortParams(params)} - staffMembers={maybe(() => - data.staffUsers.edges.map(edge => edge.node) + staffMembers={staffQueryData?.staffUsers.edges.map( + edge => edge.node )} onAdd={() => openModal("add")} onBack={() => navigate(configurationMenuUrl)} @@ -188,14 +198,23 @@ export const StaffList: React.FC = ({ params }) => { onSort={handleSort} /> addStaffMemberData.data.staffCreate.errors, - [] + availablePermissionGroups={searchPermissionGroupsOpts.data?.search.edges.map( + edge => edge.node )} + confirmButtonState={addStaffMemberData.status} + initialSearch="" + disabled={loading} + errors={addStaffMemberData.data?.staffCreate.errors || []} open={params.action === "add"} onClose={closeModal} onConfirm={handleStaffMemberAdd} + fetchMorePermissionGroups={{ + hasMore: + searchPermissionGroupsOpts.data?.search.pageInfo.hasNextPage, + loading: searchPermissionGroupsOpts.loading, + onFetchMore: loadMorePermissionGroups + }} + onSearchChange={searchPermissionGroups} /> = ({ params }) => { confirmButtonState="default" onClose={closeModal} onSubmit={handleTabDelete} - tabName={maybe(() => tabs[currentTab - 1].name, "...")} + tabName={getStringOrPlaceholder(tabs[currentTab - 1]?.name)} /> ); diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index c9ef2cfca..2fc6dce8d 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -34261,6 +34261,44 @@ exports[`Storyshots Views / Configuration default 1`] = ` +
+
+
+ +
+
+
+ Permission Groups +
+
+ Manage your permission groups and their permissions +
+
+
+
`; +exports[`Storyshots Views / Permission Groups / Permission Group Create default 1`] = ` +
+
+
+
+
+
+
+ + General Informations + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+ + Permissions + +
+
+
+
+
+
+ Expand or restrict group's permissions to access certain part of saleor system. +
+
+
+ + + + + + +
+
+ + Group has full access to the store + +
+
+
+
+
+
+
+ + + + + + +
+
+ + Manage sales and vouchers + +
+
+
+
+ + + + + + +
+
+ + Manage navigation + +
+
+
+
+ + + + + + +
+
+ + Manage orders + +
+
+
+
+ + + + + + +
+
+ + Manage pages + +
+
+
+
+ + + + + + +
+
+ + Manage products + +
+
+
+
+ + + + + + +
+
+ + Manage settings + +
+
+
+
+ + + + + + +
+
+ + Manage shipping + +
+
+
+
+ + + + + + +
+
+ + Manage staff + +
+
+
+
+ + + + + + +
+
+ + Manage customers + +
+
+
+
+ + + + + + +
+
+ + Manage plugins + +
+
+
+
+ + + + + + +
+
+ + Manage apps + +
+
+
+
+ + + + + + +
+
+ + Manage webhooks + +
+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group Create errors 1`] = ` +
+
+
+
+
+
+
+ + General Informations + +
+
+
+
+
+
+ +
+ + +
+

+ This name should be unique +

+
+
+
+
+
+
+
+ + Permissions + +
+
+
+
+
+
+ Expand or restrict group's permissions to access certain part of saleor system. +
+
+
+ + + + + + +
+
+ + Group has full access to the store + +
+
+
+
+
+
+
+ + + + + + +
+
+ + Manage sales and vouchers + +
+
+
+
+ + + + + + +
+
+ + Manage navigation + +
+
+
+
+ + + + + + +
+
+ + Manage orders + +
+
+
+
+ + + + + + +
+
+ + Manage pages + +
+
+
+
+ + + + + + +
+
+ + Manage products + +
+
+
+
+ + + + + + +
+
+ + Manage settings + +
+
+
+
+ + + + + + +
+
+ + Manage shipping + +
+
+
+
+ + + + + + +
+
+ + Manage staff + +
+
+
+
+ + + + + + +
+
+ + Manage customers + +
+
+
+
+ + + + + + +
+
+ + Manage plugins + +
+
+
+
+ + + + + + +
+
+ + Manage apps + +
+
+
+
+ + + + + + +
+
+ + Manage webhooks + +
+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group Create loading 1`] = ` +
+
+
+
+
+
+
+ + General Informations + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+ + Permissions + +
+
+
+
+
+
+ Expand or restrict group's permissions to access certain part of saleor system. +
+
+
+ + + + + + +
+
+ + Group has full access to the store + +
+
+
+
+
+
+
+ + + + + + +
+
+ + Manage sales and vouchers + +
+
+
+
+ + + + + + +
+
+ + Manage navigation + +
+
+
+
+ + + + + + +
+
+ + Manage orders + +
+
+
+
+ + + + + + +
+
+ + Manage pages + +
+
+
+
+ + + + + + +
+
+ + Manage products + +
+
+
+
+ + + + + + +
+
+ + Manage settings + +
+
+
+
+ + + + + + +
+
+ + Manage shipping + +
+
+
+
+ + + + + + +
+
+ + Manage staff + +
+
+
+
+ + + + + + +
+
+ + Manage customers + +
+
+
+
+ + + + + + +
+
+ + Manage plugins + +
+
+
+
+ + + + + + +
+
+ + Manage apps + +
+
+
+
+ + + + + + +
+
+ + Manage webhooks + +
+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group Delete remove single 1`] = ` +
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group Details default 1`] = ` +
+
+
+
+
+ Editors +
+
+
+
+
+
+
+
+
+ + General Informations + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+ + Group members + +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ Name +
+
+
+
+
+ Email Address +
+
+
+
+
+ Actions +
+
+
+ + +
+ +
+
+ admin +
+
+ Active +
+
+ admin@example.com + + +
+ + +
+ +
+
+ Bryan Rodgers +
+
+ Active +
+
+ bryan.rodgers@example.com + + +
+ + +
+ +
+
+ Joshua Mitchell +
+
+ Active +
+
+ joshua.mitchell@example.com + + +
+ + +
+ +
+
+ Joshua Mitchell +
+
+ Active +
+
+ joshua.mitchell@example.com + + +
+
+
+
+
+
+
+ + Permissions + +
+
+
+
+
+
+ Expand or restrict group's permissions to access certain part of saleor system. +
+
+
+ + + + + + +
+
+ + Group has full access to the store + +
+
+
+
+
+
+
+ + + + + + +
+
+ + Manage sales and vouchers + +
+
+
+
+ + + + + + +
+
+ + Manage navigation + +
+
+
+
+ + + + + + +
+
+ + Manage orders + +
+
+
+
+ + + + + + +
+
+ + Manage pages + +
+
+
+
+ + + + + + +
+
+ + Manage products + +
+
+
+
+ + + + + + +
+
+ + Manage settings + +
+
+
+
+ + + + + + +
+
+ + Manage shipping + +
+
+
+
+ + + + + + +
+
+ + Manage staff + +
+
+
+
+ + + + + + +
+
+ + Manage customers + +
+
+
+
+ + + + + + +
+
+ + Manage plugins + +
+
+
+
+ + + + + + +
+
+ + Manage apps + +
+
+
+
+ + + + + + +
+
+ + Manage webhooks + +
+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group Details loading 1`] = ` +
+
+
+
+
+ + ‌ + +
+
+
+
+
+
+
+
+
+ + General Informations + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+ + Group members + +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ Name +
+
+
+
+
+ Email Address +
+
+
+
+
+ Actions +
+
+
+ + +
+ +
+
+ admin +
+
+ Active +
+
+ admin@example.com + + +
+ + +
+ +
+
+ Bryan Rodgers +
+
+ Active +
+
+ bryan.rodgers@example.com + + +
+ + +
+ +
+
+ Joshua Mitchell +
+
+ Active +
+
+ joshua.mitchell@example.com + + +
+ + +
+ +
+
+ Joshua Mitchell +
+
+ Active +
+
+ joshua.mitchell@example.com + + +
+
+
+
+
+
+
+ + Permissions + +
+
+
+
+
+
+ Expand or restrict group's permissions to access certain part of saleor system. +
+
+
+ + + + + + +
+
+ + Group has full access to the store + +
+
+
+
+
+ + ‌ + +
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group Details no members 1`] = ` +
+
+
+
+
+ Editors +
+
+
+
+
+
+
+
+
+ + General Informations + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+ + Group members + +
+ +
+
+
+
+
+
+ You haven’t assigned any member to this permission group yet. +
+
+ Please use Assign Members button to do so. +
+
+
+
+
+
+
+ + Permissions + +
+
+
+
+
+
+ Expand or restrict group's permissions to access certain part of saleor system. +
+
+
+ + + + + + +
+
+ + Group has full access to the store + +
+
+
+
+
+
+
+ + + + + + +
+
+ + Manage sales and vouchers + +
+
+
+
+ + + + + + +
+
+ + Manage navigation + +
+
+
+
+ + + + + + +
+
+ + Manage orders + +
+
+
+
+ + + + + + +
+
+ + Manage pages + +
+
+
+
+ + + + + + +
+
+ + Manage products + +
+
+
+
+ + + + + + +
+
+ + Manage settings + +
+
+
+
+ + + + + + +
+
+ + Manage shipping + +
+
+
+
+ + + + + + +
+
+ + Manage staff + +
+
+
+
+ + + + + + +
+
+ + Manage customers + +
+
+
+
+ + + + + + +
+
+ + Manage plugins + +
+
+
+
+ + + + + + +
+
+ + Manage apps + +
+
+
+
+ + + + + + +
+
+ + Manage webhooks + +
+
+
+
+
+
+
+
+ +
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group List default 1`] = ` +
+
+
+
+ Permission Groups +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ Permission Group Name +
+ +
+
+
+
+ Members +
+
+
+ Actions +
+
+
+
+
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group List loading 1`] = ` +
+
+
+
+ Permission Groups +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ Permission Group Name +
+ +
+
+
+
+ Members +
+
+
+ Actions +
+ + ‌ + + + + ‌ + + + + ‌ + +
+
+
+
+
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group List no data 1`] = ` +
+
+
+
+ Permission Groups +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
+ Permission Group Name +
+ +
+
+
+
+ Members +
+
+
+ Actions +
+ No permission groups found +
+
+
+
+
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group Unassign Error Modal Unassign member 1`] = ` +
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group Unassign Member Unassign members 1`] = ` +
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group User Assignment default 1`] = ` +
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group User Assignment search loading 1`] = ` +
+`; + +exports[`Storyshots Views / Permission Groups / Permission Group User Assignment submitting loading 1`] = ` +
+`; + exports[`Storyshots Views / Plugins / Edit secret field password 1`] = `
-
+
+ User has full access to the store - +

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -117403,29 +123588,50 @@ exports[`Storyshots Views / Services / Create service form errors 1`] = ` Expand or restrict user's permissions to access certain part of saleor system.
-
+
+ User has full access to the store - +

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -117894,31 +124400,52 @@ exports[`Storyshots Views / Services / Create service loading 1`] = ` Expand or restrict user's permissions to access certain part of saleor system.
-
+
+ User has full access to the store - +

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -118533,29 +125336,50 @@ exports[`Storyshots Views / Services / Service details default 1`] = ` Expand or restrict user's permissions to access certain part of saleor system.
-
+
+ User has full access to the store - +

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -119222,29 +126347,50 @@ exports[`Storyshots Views / Services / Service details default token 1`] = ` Expand or restrict user's permissions to access certain part of saleor system.
-
+
+ User has full access to the store - +

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -119833,29 +127280,50 @@ exports[`Storyshots Views / Services / Service details form errors 1`] = ` Expand or restrict user's permissions to access certain part of saleor system.
-
+
+ User has full access to the store - +

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -120453,31 +128222,52 @@ exports[`Storyshots Views / Services / Service details loading 1`] = ` Expand or restrict user's permissions to access certain part of saleor system.
-
+
+ User has full access to the store - +

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -129041,32 +137107,118 @@ exports[`Storyshots Views / Staff / Staff member details default 1`] = `
- Expand or restrict user's permissions to access certain part of saleor system. + User is assigned to:
-
+ +
+
+
+
+
+
- User has full access to the store - - +
+ Full Access +
+
+
+
+
+
+ Customer Support +
+ +
+
@@ -129726,342 +137878,71 @@ exports[`Storyshots Views / Staff / Staff member details loading 1`] = `
- Expand or restrict user's permissions to access certain part of saleor system. + User is assigned to:
- -
-
-
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- +
+ +
+ +
+
+
-
-
-
- - Permissions - -
-
-
-
-
-
- Expand or restrict user's permissions to access certain part of saleor system. -
-
- -
-
-
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
-
-
- - Account Status - -
-
-
-
-
-
- If you want to disable this account uncheck the box below -
- -
-
-
+
diff --git a/src/storybook/config.js b/src/storybook/config.js index c1d03a182..cc81d2176 100644 --- a/src/storybook/config.js +++ b/src/storybook/config.js @@ -6,7 +6,7 @@ const req = requireContext("../", true, /.stories.tsx$/); function loadStories() { // Story autodiscovery - req.keys().forEach(filename => req(filename)); + req.keys().forEach((filename) => req(filename)); // Components require("./stories/components/ActionDialog"); @@ -97,10 +97,6 @@ function loadStories() { require("./stories/navigation/MenuItemDialog"); require("./stories/navigation/MenuListPage"); - // Staff - require("./stories/staff/StaffListPage"); - require("./stories/staff/StaffDetailsPage"); - // Pages require("./stories/pages/PageDetailsPage"); require("./stories/pages/PageListPage"); diff --git a/src/storybook/stories/configuration/ConfigurationPage.tsx b/src/storybook/stories/configuration/ConfigurationPage.tsx index 24fac7643..07dcb15fc 100644 --- a/src/storybook/stories/configuration/ConfigurationPage.tsx +++ b/src/storybook/stories/configuration/ConfigurationPage.tsx @@ -3,10 +3,10 @@ import React from "react"; import { useIntl } from "react-intl"; import { User } from "@saleor/auth/types/User"; -import { createConfigurationMenu } from "../../../configuration"; -import ConfigurationPage from "../../../configuration/ConfigurationPage"; -import { staffMember } from "../../../staff/fixtures"; -import Decorator from "../../Decorator"; +import { createConfigurationMenu } from "@saleor/configuration"; +import ConfigurationPage from "@saleor/configuration/ConfigurationPage"; +import { staffMember } from "@saleor/staff/fixtures"; +import Decorator from "@saleor/storybook/Decorator"; const user = { __typename: staffMember.__typename, @@ -20,7 +20,7 @@ const user = { isStaff: true, lastName: "Newton", note: null, - permissions: staffMember.permissions + userPermissions: staffMember.userPermissions }; const Story: React.FC<{ user: User }> = ({ user }) => { @@ -42,7 +42,7 @@ storiesOf("Views / Configuration", module) )); diff --git a/src/storybook/stories/home/HomePage.tsx b/src/storybook/stories/home/HomePage.tsx index 49a29f23a..2f53932d4 100644 --- a/src/storybook/stories/home/HomePage.tsx +++ b/src/storybook/stories/home/HomePage.tsx @@ -3,7 +3,7 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import placeholderImage from "@assets/images/placeholder60x60.png"; -import { permissions } from "@saleor/fixtures"; +import { adminUserPermissions } from "@saleor/fixtures"; import { PermissionEnum } from "@saleor/types/globalTypes"; import HomePage, { HomePageProps } from "../../../home/components/HomePage"; import { shop as shopFixture } from "../../../home/fixtures"; @@ -24,7 +24,7 @@ const homePageProps: Omit = { sales: shop.salesToday.gross, topProducts: shop.productTopToday.edges.map(edge => edge.node), userName: "admin@example.com", - userPermissions: permissions + userPermissions: adminUserPermissions }; storiesOf("Views / HomePage", module) @@ -52,7 +52,7 @@ storiesOf("Views / HomePage", module) .add("product permissions", () => ( perm.code === PermissionEnum.MANAGE_PRODUCTS )} /> @@ -60,7 +60,7 @@ storiesOf("Views / HomePage", module) .add("order permissions", () => ( perm.code === PermissionEnum.MANAGE_ORDERS )} /> diff --git a/src/storybook/stories/orders/OrderCustomer.tsx b/src/storybook/stories/orders/OrderCustomer.tsx index c810af43c..dc2db8025 100644 --- a/src/storybook/stories/orders/OrderCustomer.tsx +++ b/src/storybook/stories/orders/OrderCustomer.tsx @@ -2,7 +2,7 @@ import { Omit } from "@material-ui/core"; import { storiesOf } from "@storybook/react"; import React from "react"; -import { permissions } from "@saleor/fixtures"; +import { adminUserPermissions } from "@saleor/fixtures"; import OrderCustomer, { OrderCustomerProps } from "../../../orders/components/OrderCustomer"; @@ -20,7 +20,7 @@ const props: Omit = { onProfileView: () => undefined, onShippingAddressEdit: undefined, order, - userPermissions: permissions, + userPermissions: adminUserPermissions, users: clients }; diff --git a/src/storybook/stories/orders/OrderDetailsPage.tsx b/src/storybook/stories/orders/OrderDetailsPage.tsx index 5fc2bfd2a..47925b654 100644 --- a/src/storybook/stories/orders/OrderDetailsPage.tsx +++ b/src/storybook/stories/orders/OrderDetailsPage.tsx @@ -3,7 +3,7 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import placeholderImage from "@assets/images/placeholder60x60.png"; -import { permissions } from "@saleor/fixtures"; +import { adminUserPermissions } from "@saleor/fixtures"; import OrderDetailsPage, { OrderDetailsPageProps } from "../../../orders/components/OrderDetailsPage"; @@ -34,7 +34,7 @@ const props: Omit = { onProfileView: () => undefined, onShippingAddressEdit: undefined, order, - userPermissions: permissions + userPermissions: adminUserPermissions }; storiesOf("Views / Orders / Order details", module) diff --git a/src/storybook/stories/orders/OrderDraftPage.tsx b/src/storybook/stories/orders/OrderDraftPage.tsx index aed9dd0dc..84582631c 100644 --- a/src/storybook/stories/orders/OrderDraftPage.tsx +++ b/src/storybook/stories/orders/OrderDraftPage.tsx @@ -3,7 +3,7 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import placeholderImage from "@assets/images/placeholder60x60.png"; -import { fetchMoreProps, permissions } from "@saleor/fixtures"; +import { fetchMoreProps, adminUserPermissions } from "@saleor/fixtures"; import OrderDraftPage, { OrderDraftPageProps } from "../../../orders/components/OrderDraftPage"; @@ -32,7 +32,7 @@ const props: Omit = { onShippingMethodEdit: undefined, order, saveButtonBarState: "default", - userPermissions: permissions, + userPermissions: adminUserPermissions, users: clients, usersLoading: false }; diff --git a/src/storybook/stories/shipping/ShippingZonesListPage.tsx b/src/storybook/stories/shipping/ShippingZonesListPage.tsx index 26b40357c..479d64402 100644 --- a/src/storybook/stories/shipping/ShippingZonesListPage.tsx +++ b/src/storybook/stories/shipping/ShippingZonesListPage.tsx @@ -4,7 +4,7 @@ import React from "react"; import { listActionsProps, pageListProps, - permissions + adminUserPermissions } from "../../../fixtures"; import ShippingZonesListPage, { ShippingZonesListPageProps @@ -22,7 +22,7 @@ const props: ShippingZonesListPageProps = { onRemove: () => undefined, onSubmit: () => undefined, shippingZones, - userPermissions: permissions + userPermissions: adminUserPermissions }; storiesOf("Views / Shipping / Shipping zones list", module) diff --git a/src/types.ts b/src/types.ts index 4ba92d2e5..3db6bcc62 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ import { MutationResult } from "react-apollo"; -import { User_permissions } from "./auth/types/User"; +import { User_userPermissions } from "./auth/types/User"; import { ConfirmButtonTransitionState } from "./components/ConfirmButton"; import { IFilter } from "./components/Filter"; import { MultiAutocompleteChoiceType } from "./components/MultiAutocompleteSelectField"; @@ -31,6 +31,7 @@ export enum ListViews { PAGES_LIST = "PAGES_LIST", PLUGINS_LIST = "PLUGIN_LIST", PRODUCT_LIST = "PRODUCT_LIST", + PERMISSION_GROUP_LIST = "PERMISSION_GROUP_LIST", PRODUCT_TYPE_LIST = "PRODUCT_TYPE_LIST", SALES_LIST = "SALES_LIST", SHIPPING_METHODS_LIST = "SHIPPING_METHODS_LIST", @@ -60,6 +61,13 @@ export interface SortPage { sort: Sort; onSort: (field: TSortKey, id?: string) => void; } + +/** + * @param toggle Will be use to change status of item + * @param isChecked Returns true for ids of chosen items + * @param selected Number of chosen items. + */ + export interface ListActionsWithoutToolbar { toggle: (id: string) => void; toggleAll: (items: React.ReactNodeArray, selected: number) => void; @@ -165,7 +173,7 @@ export interface FetchMoreProps { export type TabActionDialog = "save-search" | "delete-search"; export interface UserPermissionProps { - userPermissions: User_permissions[]; + userPermissions: User_userPermissions[]; } export interface MutationResultAdditionalProps { diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index fc853df2e..72cec5cbe 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -9,13 +9,13 @@ export enum AccountErrorCode { ACTIVATE_OWN_ACCOUNT = "ACTIVATE_OWN_ACCOUNT", ACTIVATE_SUPERUSER_ACCOUNT = "ACTIVATE_SUPERUSER_ACCOUNT", - CANNOT_ADD_AND_REMOVE = "CANNOT_ADD_AND_REMOVE", DEACTIVATE_OWN_ACCOUNT = "DEACTIVATE_OWN_ACCOUNT", DEACTIVATE_SUPERUSER_ACCOUNT = "DEACTIVATE_SUPERUSER_ACCOUNT", DELETE_NON_STAFF_USER = "DELETE_NON_STAFF_USER", DELETE_OWN_ACCOUNT = "DELETE_OWN_ACCOUNT", DELETE_STAFF_ACCOUNT = "DELETE_STAFF_ACCOUNT", DELETE_SUPERUSER_ACCOUNT = "DELETE_SUPERUSER_ACCOUNT", + DUPLICATED_INPUT_ITEM = "DUPLICATED_INPUT_ITEM", GRAPHQL_ERROR = "GRAPHQL_ERROR", INVALID = "INVALID", INVALID_CREDENTIALS = "INVALID_CREDENTIALS", @@ -45,8 +45,6 @@ export enum AttributeInputTypeEnum { export enum AttributeSortField { AVAILABLE_IN_GRID = "AVAILABLE_IN_GRID", - DASHBOARD_PRODUCT_POSITION = "DASHBOARD_PRODUCT_POSITION", - DASHBOARD_VARIANT_POSITION = "DASHBOARD_VARIANT_POSITION", FILTERABLE_IN_DASHBOARD = "FILTERABLE_IN_DASHBOARD", FILTERABLE_IN_STOREFRONT = "FILTERABLE_IN_STOREFRONT", IS_VARIANT_ONLY = "IS_VARIANT_ONLY", @@ -460,6 +458,7 @@ export enum OrderErrorCode { CAPTURE_INACTIVE_PAYMENT = "CAPTURE_INACTIVE_PAYMENT", FULFILL_ORDER_LINE = "FULFILL_ORDER_LINE", GRAPHQL_ERROR = "GRAPHQL_ERROR", + INSUFFICIENT_STOCK = "INSUFFICIENT_STOCK", INVALID = "INVALID", NOT_EDITABLE = "NOT_EDITABLE", NOT_FOUND = "NOT_FOUND", @@ -558,6 +557,7 @@ export enum PaymentChargeStatusEnum { } export enum PermissionEnum { + MANAGE_APPS = "MANAGE_APPS", MANAGE_CHECKOUTS = "MANAGE_CHECKOUTS", MANAGE_DISCOUNTS = "MANAGE_DISCOUNTS", MANAGE_GIFT_CARD = "MANAGE_GIFT_CARD", @@ -566,7 +566,7 @@ export enum PermissionEnum { MANAGE_PAGES = "MANAGE_PAGES", MANAGE_PLUGINS = "MANAGE_PLUGINS", MANAGE_PRODUCTS = "MANAGE_PRODUCTS", - MANAGE_APPS = "MANAGE_APPS", + MANAGE_SERVICE_ACCOUNTS = "MANAGE_SERVICE_ACCOUNTS", MANAGE_SETTINGS = "MANAGE_SETTINGS", MANAGE_SHIPPING = "MANAGE_SHIPPING", MANAGE_STAFF = "MANAGE_STAFF", @@ -575,6 +575,21 @@ export enum PermissionEnum { MANAGE_WEBHOOKS = "MANAGE_WEBHOOKS", } +export enum PermissionGroupErrorCode { + ASSIGN_NON_STAFF_MEMBER = "ASSIGN_NON_STAFF_MEMBER", + CANNOT_REMOVE_FROM_LAST_GROUP = "CANNOT_REMOVE_FROM_LAST_GROUP", + DUPLICATED_INPUT_ITEM = "DUPLICATED_INPUT_ITEM", + LEFT_NOT_MANAGEABLE_PERMISSION = "LEFT_NOT_MANAGEABLE_PERMISSION", + OUT_OF_SCOPE_PERMISSION = "OUT_OF_SCOPE_PERMISSION", + OUT_OF_SCOPE_USER = "OUT_OF_SCOPE_USER", + REQUIRED = "REQUIRED", + UNIQUE = "UNIQUE", +} + +export enum PermissionGroupSortField { + NAME = "NAME", +} + export enum PluginSortField { IS_ACTIVE = "IS_ACTIVE", NAME = "NAME", @@ -585,6 +600,7 @@ export enum ProductErrorCode { ATTRIBUTE_ALREADY_ASSIGNED = "ATTRIBUTE_ALREADY_ASSIGNED", ATTRIBUTE_CANNOT_BE_ASSIGNED = "ATTRIBUTE_CANNOT_BE_ASSIGNED", ATTRIBUTE_VARIANTS_DISABLED = "ATTRIBUTE_VARIANTS_DISABLED", + DUPLICATED_INPUT_ITEM = "DUPLICATED_INPUT_ITEM", GRAPHQL_ERROR = "GRAPHQL_ERROR", INVALID = "INVALID", NOT_FOUND = "NOT_FOUND", @@ -639,7 +655,7 @@ export enum ServiceAccountSortField { export enum ShippingErrorCode { ALREADY_EXISTS = "ALREADY_EXISTS", - CANNOT_ADD_AND_REMOVE = "CANNOT_ADD_AND_REMOVE", + DUPLICATED_INPUT_ITEM = "DUPLICATED_INPUT_ITEM", GRAPHQL_ERROR = "GRAPHQL_ERROR", INVALID = "INVALID", MAX_LESS_THAN_MIN = "MAX_LESS_THAN_MIN", @@ -1087,6 +1103,29 @@ export interface PageTranslationInput { contentJson?: any | null; } +export interface PermissionGroupCreateInput { + addPermissions?: PermissionEnum[] | null; + addUsers?: string[] | null; + name: string; +} + +export interface PermissionGroupFilterInput { + search?: string | null; +} + +export interface PermissionGroupSortingInput { + direction: OrderDirection; + field: PermissionGroupSortField; +} + +export interface PermissionGroupUpdateInput { + addPermissions?: PermissionEnum[] | null; + addUsers?: string[] | null; + name?: string | null; + removePermissions?: PermissionEnum[] | null; + removeUsers?: string[] | null; +} + export interface PluginFilterInput { active?: boolean | null; search?: string | null; diff --git a/src/utils/errors/account.ts b/src/utils/errors/account.ts index bc7846667..d34851474 100644 --- a/src/utils/errors/account.ts +++ b/src/utils/errors/account.ts @@ -9,6 +9,12 @@ const messages = defineMessages({ invalidPassword: { defaultMessage: "Invalid password" }, + outOfScopeGroup: { + defaultMessage: "Group is out of your permissions scope" + }, + outOfScopeUser: { + defaultMessage: "User is out of your permissions scope" + }, passwordNumeric: { defaultMessage: "Password cannot be entirely numeric" }, @@ -20,6 +26,9 @@ const messages = defineMessages({ }, tooSimilar: { defaultMessage: "These passwords are too similar" + }, + unique: { + defaultMessage: "This needs to be unique" } }); @@ -35,6 +44,10 @@ function getAccountErrorMessage( return intl.formatMessage(commonErrorMessages.invalid); case AccountErrorCode.INVALID_PASSWORD: return intl.formatMessage(messages.invalidPassword); + case AccountErrorCode.OUT_OF_SCOPE_USER: + return intl.formatMessage(messages.outOfScopeUser); + case AccountErrorCode.OUT_OF_SCOPE_GROUP: + return intl.formatMessage(messages.outOfScopeGroup); case AccountErrorCode.PASSWORD_ENTIRELY_NUMERIC: return intl.formatMessage(messages.passwordNumeric); case AccountErrorCode.PASSWORD_TOO_COMMON: @@ -45,6 +58,8 @@ function getAccountErrorMessage( return intl.formatMessage(messages.tooSimilar); case AccountErrorCode.REQUIRED: return intl.formatMessage(commonMessages.requiredField); + case AccountErrorCode.UNIQUE: + return intl.formatMessage(messages.unique); default: return intl.formatMessage(commonErrorMessages.unknownError); } diff --git a/src/utils/errors/permissionGroups.ts b/src/utils/errors/permissionGroups.ts new file mode 100644 index 000000000..f4d85a826 --- /dev/null +++ b/src/utils/errors/permissionGroups.ts @@ -0,0 +1,53 @@ +import { defineMessages, IntlShape } from "react-intl"; + +import { commonMessages } from "@saleor/intl"; +import { PermissionGroupErrorFragment } from "@saleor/permissionGroups/types/PermissionGroupErrorFragment"; +import { PermissionGroupErrorCode } from "@saleor/types/globalTypes"; + +import commonErrorMessages from "./common"; + +const messages = defineMessages({ + assignNonStaffMember: { + defaultMessage: "Only staff members can be assigned" + }, + cannotRemoveFromLastGroup: { + defaultMessage: "Cannot remove user from last group" + }, + duplicatedInputItem: { + defaultMessage: "Cannot add and remove group the same time" + }, + permissionOutOfScope: { + defaultMessage: "Those permissions are out of your scope" + }, + unique: { + defaultMessage: "This name should be unique" + } +}); + +function getPermissionGroupErrorMessage( + err: PermissionGroupErrorFragment, + intl: IntlShape +): string { + if (err) { + switch (err.code) { + case PermissionGroupErrorCode.ASSIGN_NON_STAFF_MEMBER: + return intl.formatMessage(messages.assignNonStaffMember); + case PermissionGroupErrorCode.DUPLICATED_INPUT_ITEM: + return intl.formatMessage(messages.duplicatedInputItem); + case PermissionGroupErrorCode.OUT_OF_SCOPE_PERMISSION: + return intl.formatMessage(messages.permissionOutOfScope); + case PermissionGroupErrorCode.CANNOT_REMOVE_FROM_LAST_GROUP: + return intl.formatMessage(messages.cannotRemoveFromLastGroup); + case PermissionGroupErrorCode.UNIQUE: + return intl.formatMessage(messages.unique); + case PermissionGroupErrorCode.REQUIRED: + return intl.formatMessage(commonMessages.requiredField); + default: + return intl.formatMessage(commonErrorMessages.unknownError); + } + } + + return undefined; +} + +export default getPermissionGroupErrorMessage; diff --git a/src/utils/handlers/multiAutocompleteSelectChangeHandler.ts b/src/utils/handlers/multiAutocompleteSelectChangeHandler.ts index 72f792740..8376727de 100644 --- a/src/utils/handlers/multiAutocompleteSelectChangeHandler.ts +++ b/src/utils/handlers/multiAutocompleteSelectChangeHandler.ts @@ -1,7 +1,10 @@ import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField"; import { ChangeEvent, FormChange } from "@saleor/hooks/useForm"; -import { toggle } from "../lists"; +import { toggle } from "@saleor/utils/lists"; +/** + * @param change Use toggleValue callback delivered by form + */ function createMultiAutocompleteSelectHandler( change: FormChange, setSelected: (choices: MultiAutocompleteChoiceType[]) => void,