parent
7878067c9c
commit
082a12e0b8
117 changed files with 16289 additions and 3180 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
2413
schema.graphql
2413
schema.graphql
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<AccountPermissionGroupsProps> = 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 (
|
||||
<>
|
||||
<MultiAutocompleteSelectField
|
||||
displayValues={displayValues}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Permission groups"
|
||||
})}
|
||||
choices={disabled ? [] : choices}
|
||||
name="permissionGroups"
|
||||
value={formData?.permissionGroups}
|
||||
onChange={onChange}
|
||||
fetchChoices={onSearchChange}
|
||||
data-tc="permissionGroups"
|
||||
onFetchMore={onFetchMore}
|
||||
hasMore={hasMore}
|
||||
loading={loading}
|
||||
/>
|
||||
{!!formErrors.addGroups && (
|
||||
<Typography color="error">
|
||||
{getStaffErrorMessage(formErrors.addGroups, intl)}
|
||||
</Typography>
|
||||
)}
|
||||
{!!formErrors.removeGroups && (
|
||||
<Typography color="error">
|
||||
{getStaffErrorMessage(formErrors.removeGroups, intl)}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
AccountPermissionGroups.displayName = "AccountPermissionGroups";
|
||||
export default AccountPermissionGroups;
|
2
src/components/AccountPermissionGroups/index.ts
Normal file
2
src/components/AccountPermissionGroups/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./AccountPermissionGroups";
|
||||
export * from "./AccountPermissionGroups";
|
|
@ -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<any>, cb?: () => void) => void;
|
||||
}
|
||||
|
||||
const AccountPermissions: React.FC<AccountPermissionsProps> = 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<any>) =>
|
||||
onChange(event, () =>
|
||||
onChange({
|
||||
target: {
|
||||
name: "permissions",
|
||||
value: event.target.value ? permissions.map(perm => perm.code) : []
|
||||
}
|
||||
} as any)
|
||||
);
|
||||
const handlePermissionChange = (event: React.ChangeEvent<any>) => {
|
||||
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 (
|
||||
<Card>
|
||||
<CardTitle
|
||||
|
@ -69,49 +95,121 @@ const AccountPermissions: React.FC<AccountPermissionsProps> = props => {
|
|||
description: "dialog header"
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="Expand or restrict user's permissions to access certain part of saleor system." />
|
||||
</Typography>
|
||||
<div className={classes.checkboxContainer}>
|
||||
<ControlledCheckbox
|
||||
checked={data.hasFullAccess}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "User has full access to the store",
|
||||
description: "checkbox label"
|
||||
})}
|
||||
name="hasFullAccess"
|
||||
onChange={handleFullAccessChange}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
{!data.hasFullAccess && (
|
||||
{permissionsExceeded && (
|
||||
<>
|
||||
<CardContent>
|
||||
<Typography>
|
||||
{intl.formatMessage({
|
||||
defaultMessage:
|
||||
"This groups permissions exceeds your own. You are able only to manage permissions that you have.",
|
||||
description: "exceeded permissions description"
|
||||
})}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<hr className={classes.hr} />
|
||||
<CardContent>
|
||||
{permissions === undefined ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
permissions.map(perm => (
|
||||
<div key={perm.code}>
|
||||
<ControlledCheckbox
|
||||
checked={
|
||||
data.permissions.filter(
|
||||
userPerm => userPerm === perm.code
|
||||
).length === 1
|
||||
}
|
||||
disabled={disabled}
|
||||
label={perm.name.replace(/\./, "")}
|
||||
name={perm.code}
|
||||
onChange={handlePermissionChange}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
<Typography>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Available permissions",
|
||||
description: "card section description"
|
||||
})}
|
||||
</Typography>
|
||||
<List dense={true}>
|
||||
{user.userPermissions.map(perm => (
|
||||
<ListItem key={perm.code}>
|
||||
<ListItemText primary={`- ${perm.name}`} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</CardContent>
|
||||
</>
|
||||
)}
|
||||
{!permissionsExceeded && (
|
||||
<>
|
||||
<CardContent>
|
||||
<Typography>{description}</Typography>
|
||||
<ListItem
|
||||
role={undefined}
|
||||
dense
|
||||
button
|
||||
onClick={handleFullAccessChange}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Checkbox
|
||||
edge="start"
|
||||
checked={data.hasFullAccess}
|
||||
disabled={disabled}
|
||||
tabIndex={-1}
|
||||
disableRipple
|
||||
inputProps={{ "aria-labelledby": "fullAccess" }}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={fullAccessLabel} />
|
||||
</ListItem>
|
||||
</CardContent>
|
||||
{!data.hasFullAccess && (
|
||||
<>
|
||||
<hr className={classes.hr} />
|
||||
<CardContent>
|
||||
{permissions === undefined ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
permissions.map(perm => (
|
||||
<ListItem
|
||||
key={perm.code}
|
||||
disabled={perm.disabled}
|
||||
role={undefined}
|
||||
dense
|
||||
button
|
||||
onClick={handlePermissionChange(
|
||||
perm.code,
|
||||
data.permissions.filter(
|
||||
userPerm => userPerm === perm.code
|
||||
).length === 1
|
||||
)}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Checkbox
|
||||
edge="start"
|
||||
checked={
|
||||
data.permissions.filter(
|
||||
userPerm => userPerm === perm.code
|
||||
).length === 1
|
||||
}
|
||||
tabIndex={-1}
|
||||
disableRipple
|
||||
name={perm.code}
|
||||
inputProps={{ "aria-labelledby": perm.code }}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
id={perm.code}
|
||||
primary={perm.name.replace(/\./, "")}
|
||||
secondary={
|
||||
perm.lastSource &&
|
||||
intl.formatMessage({
|
||||
defaultMessage:
|
||||
"This group is last source of that permission",
|
||||
description: "permission list item description"
|
||||
})
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
))
|
||||
)}
|
||||
</CardContent>
|
||||
</>
|
||||
)}
|
||||
{!!errorMessage && (
|
||||
<>
|
||||
<hr className={classes.hr} />
|
||||
<CardContent>
|
||||
<Typography color="error">{errorMessage}</Typography>
|
||||
</CardContent>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
30
src/components/AccountPermissions/utils.ts
Normal file
30
src/components/AccountPermissions/utils.ts
Normal file
|
@ -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)
|
||||
}));
|
||||
};
|
|
@ -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<AppLayoutProps> = ({ 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(
|
||||
|
|
|
@ -195,7 +195,7 @@ const MenuList: React.FC<MenuListProps> = 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<MenuListProps> = props => {
|
|||
|
||||
if (
|
||||
menuItem.permission &&
|
||||
!user.permissions.map(perm => perm.code).includes(menuItem.permission)
|
||||
!user.userPermissions
|
||||
.map(perm => perm.code)
|
||||
.includes(menuItem.permission)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -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<IconButtonTableCellProps> = props => {
|
||||
const {
|
||||
children,
|
||||
|
||||
disabled,
|
||||
onClick
|
||||
} = props;
|
||||
const { children, className, disabled, onClick } = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
<TableCell className={classes.root}>
|
||||
<TableCell className={classNames(classes.root, className)}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
disabled={disabled}
|
||||
|
|
|
@ -50,6 +50,20 @@ const useStyles = makeStyles(
|
|||
container: {
|
||||
flexGrow: 1,
|
||||
position: "relative"
|
||||
},
|
||||
disabledChipInner: {
|
||||
"& svg": {
|
||||
color: theme.palette.secondary.contrastText
|
||||
},
|
||||
alignItems: "center",
|
||||
background: fade(theme.palette.secondary.main, 0.8),
|
||||
borderRadius: 18,
|
||||
color: theme.palette.primary.contrastText,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
margin: theme.spacing(1, 0),
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(1)
|
||||
}
|
||||
}),
|
||||
{ name: "MultiAutocompleteSelectField" }
|
||||
|
@ -172,16 +186,22 @@ const MultiAutocompleteSelectFieldComponent: React.FC<MultiAutocompleteSelectFie
|
|||
<div className={classes.chipContainer}>
|
||||
{displayValues.map(value => (
|
||||
<div className={classes.chip} key={value.value}>
|
||||
<div className={classes.chipInner}>
|
||||
<div
|
||||
className={
|
||||
!value.disabled ? classes.chipInner : classes.disabledChipInner
|
||||
}
|
||||
>
|
||||
<Typography className={classes.chipLabel}>
|
||||
{value.label}
|
||||
</Typography>
|
||||
<IconButton
|
||||
className={classes.chipClose}
|
||||
onClick={() => handleSelect(value.value)}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
{!value.disabled && (
|
||||
<IconButton
|
||||
className={classes.chipClose}
|
||||
onClick={() => handleSelect(value.value)}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
@ -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<FetchMoreProps> {
|
||||
|
@ -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<MultiAutocompleteSelectFieldContentProps> = 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<
|
|||
<Checkbox
|
||||
className={classes.checkbox}
|
||||
checked={true}
|
||||
disabled={value.disabled}
|
||||
disableRipple
|
||||
/>
|
||||
<span className={classes.menuItemLabel}>{value.label}</span>
|
||||
|
@ -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,
|
||||
|
|
|
@ -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: () => {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<RequirePermissionsProps> = ({
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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: <PermissionGroups fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_STAFF,
|
||||
title: intl.formatMessage(sectionNames.permissionGroups),
|
||||
url: permissionGroupListUrl()
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
104
src/fixtures.ts
104
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."
|
||||
}
|
||||
];
|
||||
|
|
|
@ -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 || []}
|
||||
/>
|
||||
)}
|
||||
</HomePageQuery>
|
||||
|
|
16
src/icons/PermissionGroups.tsx
Normal file
16
src/icons/PermissionGroups.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
import createSvgIcon from "@material-ui/icons/utils/createSvgIcon";
|
||||
import React from "react";
|
||||
|
||||
const PermissionGroups = createSvgIcon(
|
||||
<>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M0 0H22.7652L30.4497 7.70511V16.7623C32.4381 16.9409 33.8497 17.6711 34.6541 18.9799C35.5326 20.4092 35.4932 22.2483 35.212 23.9721C34.9657 25.4823 34.4931 27.1133 34.0547 28.6263C33.9837 28.8716 33.9135 29.1138 33.8454 29.3518C33.4479 30.7401 33.1267 31.9645 33.0062 32.9688H40V40H20H18.8281H0V0ZM31.8926 28.7927C31.4864 30.2113 31.089 31.679 30.964 32.9688H27.982C27.8555 31.6772 27.4528 30.2082 27.0405 28.7889C26.9647 28.5282 26.8882 28.2678 26.8119 28.0082L26.811 28.0052L26.8106 28.0036C26.3664 26.4922 25.9303 25.0083 25.699 23.6394C25.4224 22.0022 25.4937 20.8052 25.9593 20.0393C26.3673 19.3682 27.2649 18.75 29.434 18.75C31.6024 18.75 32.5084 19.3679 32.9236 20.0435C33.3956 20.8115 33.4741 22.0095 33.2073 23.6451C32.9824 25.0238 32.5494 26.5201 32.1087 28.0431L32.1086 28.0434C32.0365 28.2926 31.9642 28.5425 31.8926 28.7927ZM25.0899 29.3556C25.4929 30.743 25.8176 31.9661 25.9395 32.9688H18.8281V37.9688H2.03125V2.03125H21.3281V9.14062H28.4184V16.7623C26.4311 16.941 25.0216 17.6715 24.2236 18.9841C23.3534 20.4155 23.4052 22.2556 23.6961 23.9778C23.9494 25.4766 24.4257 27.0953 24.8681 28.5988L24.8681 28.5988C24.9434 28.8548 25.0178 29.1075 25.0899 29.3556ZM23.3594 3.47223L26.9868 7.10938H23.3594V3.47223ZM14.0625 16.4844H4.6875V14.4531H14.0625V16.4844ZM18.5938 21.25H4.76562V19.2188H18.5938V21.25ZM37.9688 35V37.9688H20.8594V35H37.9688ZM10 31.6406C10 32.5467 9.26547 33.2812 8.35938 33.2812C7.45328 33.2812 6.71875 32.5467 6.71875 31.6406C6.71875 30.7345 7.45328 30 8.35938 30C9.26547 30 10 30.7345 10 31.6406ZM12.0312 31.6406C12.0312 33.6685 10.3873 35.3125 8.35938 35.3125C6.33145 35.3125 4.6875 33.6685 4.6875 31.6406C4.6875 29.6127 6.33145 27.9688 8.35938 27.9688C10.3873 27.9688 12.0312 29.6127 12.0312 31.6406Z"
|
||||
fill="#06847B"
|
||||
/>
|
||||
</>,
|
||||
"PermissionGroups"
|
||||
);
|
||||
|
||||
export default PermissionGroups;
|
|
@ -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}
|
||||
/>
|
||||
<SectionRoute
|
||||
permissions={[PermissionEnum.MANAGE_STAFF]}
|
||||
path="/permission-groups"
|
||||
component={PermissionGroupSection}
|
||||
/>
|
||||
<SectionRoute
|
||||
permissions={[PermissionEnum.MANAGE_SETTINGS]}
|
||||
path="/site-settings"
|
||||
|
|
|
@ -176,6 +176,10 @@ export const sectionNames = defineMessages({
|
|||
defaultMessage: "Pages",
|
||||
description: "pages section name"
|
||||
},
|
||||
permissionGroups: {
|
||||
defaultMessage: "Permission Groups",
|
||||
description: "permission groups section name"
|
||||
},
|
||||
plugins: {
|
||||
defaultMessage: "Plugins",
|
||||
description: "plugins section name"
|
||||
|
|
|
@ -192,7 +192,7 @@ export const OrderDetails: React.FC<OrderDetailsProps> = ({ 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<OrderDetailsProps> = ({ id, params }) => {
|
|||
onProfileView={() =>
|
||||
navigate(customerUrl(order.user.id))
|
||||
}
|
||||
userPermissions={maybe(() => user.permissions, [])}
|
||||
userPermissions={user?.userPermissions || []}
|
||||
/>
|
||||
<OrderDraftCancelDialog
|
||||
confirmButtonState={orderDraftCancel.opts.status}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import AssignMembersDialog, {
|
||||
AssignMembersDialogProps
|
||||
} from "@saleor/permissionGroups/components/AssignMembersDialog";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { users } from "../../fixtures";
|
||||
|
||||
const props: AssignMembersDialogProps = {
|
||||
confirmButtonState: "default",
|
||||
disabled: false,
|
||||
hasMore: true,
|
||||
initialSearch: "",
|
||||
loading: false,
|
||||
onClose: () => 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", () => (
|
||||
<AssignMembersDialog
|
||||
{...props}
|
||||
confirmButtonState={"loading"}
|
||||
loading={false}
|
||||
disabled={true}
|
||||
staffMembers={[]}
|
||||
/>
|
||||
))
|
||||
.add("search loading", () => (
|
||||
<AssignMembersDialog {...props} loading={true} staffMembers={[]} />
|
||||
))
|
||||
.add("default", () => <AssignMembersDialog {...props} />);
|
|
@ -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<AssignMembersDialogProps> = ({
|
||||
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<HTMLDivElement>();
|
||||
const scrollPosition = useElementScroll(anchor);
|
||||
const dropShadow =
|
||||
anchor.current && scrollPosition
|
||||
? scrollPosition.y + anchor.current.clientHeight <
|
||||
anchor.current.scrollHeight
|
||||
: false;
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={open} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign Staff Members"
|
||||
description="dialog header"
|
||||
/>
|
||||
</DialogTitle>
|
||||
<DialogContent className={classes.inputContainer}>
|
||||
<TextField
|
||||
name="query"
|
||||
value={query}
|
||||
onChange={onQueryChange}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Search Staff Members"
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
defaultMessage: "Search by name, email, etc..."
|
||||
})}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
autoComplete: "off",
|
||||
endAdornment: loading && <CircularProgress size={16} />
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogContent className={classes.scrollArea}>
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
loadMore={onFetchMore}
|
||||
hasMore={hasMore}
|
||||
useWindow={false}
|
||||
threshold={100}
|
||||
key="infinite-scroll"
|
||||
>
|
||||
<ResponsiveTable>
|
||||
<TableBody>
|
||||
{staffMembers &&
|
||||
staffMembers.map(member => {
|
||||
const isSelected = selectedMembers.some(
|
||||
selectedMember => selectedMember.id === member.id
|
||||
);
|
||||
|
||||
return (
|
||||
<TableRow key={member.id}>
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
className={classes.checkboxCell}
|
||||
>
|
||||
<Checkbox
|
||||
color="primary"
|
||||
checked={isSelected}
|
||||
onChange={() =>
|
||||
handleStaffMemberAssign(
|
||||
member,
|
||||
isSelected,
|
||||
selectedMembers,
|
||||
setSelectedMembers
|
||||
)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.avatarCell}>
|
||||
<div className={classes.avatar}>
|
||||
{!!member?.avatar?.url ? (
|
||||
<img
|
||||
className={classes.avatarImage}
|
||||
src={member.avatar.url}
|
||||
/>
|
||||
) : (
|
||||
<div className={classes.avatarDefault}>
|
||||
<Typography>{getUserInitials(member)}</Typography>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colName}>
|
||||
<Typography>
|
||||
{getUserName(member) || <Skeleton />}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant={"caption"}
|
||||
className={classes.statusText}
|
||||
>
|
||||
{!!member ? (
|
||||
member.isActive ? (
|
||||
intl.formatMessage({
|
||||
defaultMessage: "Active",
|
||||
description: "staff member status"
|
||||
})
|
||||
) : (
|
||||
intl.formatMessage({
|
||||
defaultMessage: "Inactive",
|
||||
description: "staff member status"
|
||||
})
|
||||
)
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
{loading && (
|
||||
<>
|
||||
{staffMembers?.length > 0 && <CardSpacer />}
|
||||
<div className={classes.loadMoreLoaderContainer}>
|
||||
<CircularProgress size={24} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</InfiniteScroll>
|
||||
</DialogContent>
|
||||
<DialogActions
|
||||
className={classNames({
|
||||
[classes.dropShadow]: dropShadow
|
||||
})}
|
||||
>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage {...buttonMessages.back} />
|
||||
</Button>
|
||||
<ConfirmButton
|
||||
color="primary"
|
||||
variant="contained"
|
||||
type="submit"
|
||||
transitionState={confirmButtonState}
|
||||
onClick={() => {
|
||||
onSubmit(selectedMembers);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage defaultMessage="Assign" description="button" />
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
AssignMembersDialog.displayName = "AssignMembersDialog";
|
||||
export default AssignMembersDialog;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./AssignMembersDialog";
|
||||
export * from "./AssignMembersDialog";
|
|
@ -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", () => <MembersErrorDialog {...props} />);
|
|
@ -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<MembersErrorDialogProps> = ({
|
||||
confirmButtonState,
|
||||
onClose,
|
||||
onConfirm,
|
||||
open
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Unassign users",
|
||||
description: "dialog title"
|
||||
})}
|
||||
variant="default"
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="You are not able to modify this group members. Solve this problem to continue with request."
|
||||
description="dialog content"
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
MembersErrorDialog.displayName = "MembersErrorDialog";
|
||||
export default MembersErrorDialog;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./MembersErrorDialog";
|
||||
export * from "./MembersErrorDialog";
|
|
@ -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", () => <PermissionGroupCreatePage {...props} />)
|
||||
.add("loading", () => (
|
||||
<PermissionGroupCreatePage {...props} disabled={true} />
|
||||
))
|
||||
.add("errors", () => (
|
||||
<PermissionGroupCreatePage
|
||||
{...props}
|
||||
errors={errorsOfPermissionGroupCreate}
|
||||
/>
|
||||
));
|
|
@ -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<PermissionGroupCreatePageProps> = ({
|
||||
disabled,
|
||||
permissions,
|
||||
onBack,
|
||||
onSubmit,
|
||||
saveButtonBarState,
|
||||
errors
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const formErrors = getFormErrors(["addPermissions"], errors || []);
|
||||
const permissionsError = getPermissionGroupErrorMessage(
|
||||
formErrors.addPermissions,
|
||||
intl
|
||||
);
|
||||
|
||||
return (
|
||||
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
|
||||
{({ data, change, submit, hasChanged }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.permissionGroups)}
|
||||
</AppHeader>
|
||||
<Grid>
|
||||
<div>
|
||||
<PermissionGroupInfo
|
||||
data={data}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<AccountPermissions
|
||||
permissionsExceeded={false}
|
||||
data={data}
|
||||
errorMessage={permissionsError}
|
||||
disabled={disabled}
|
||||
permissions={permissions}
|
||||
onChange={change}
|
||||
fullAccessLabel={intl.formatMessage({
|
||||
defaultMessage: "Group has full access to the store",
|
||||
description: "checkbox label"
|
||||
})}
|
||||
description={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Expand or restrict group's permissions to access certain part of saleor system.",
|
||||
description: "card description"
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<div>
|
||||
<SaveButtonBar
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
PermissionGroupCreatePage.displayName = "PermissionGroupCreatePage";
|
||||
export default PermissionGroupCreatePage;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PermissionGroupCreatePage";
|
||||
export * from "./PermissionGroupCreatePage";
|
|
@ -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", () => <PermissionGroupDeleteDialog {...props} />);
|
|
@ -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<PermissionDeleteDialogProps> = ({
|
||||
confirmButtonState,
|
||||
name,
|
||||
onClose,
|
||||
onConfirm,
|
||||
open
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete permission group",
|
||||
description: "dialog title"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {name}?"
|
||||
description="dialog content"
|
||||
values={{
|
||||
name: <strong>{name}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
PermissionGroupDeleteDialog.displayName = "PermissionGroupDeleteDialog";
|
||||
export default PermissionGroupDeleteDialog;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PermissionGroupDeleteDialog";
|
||||
export * from "./PermissionGroupDeleteDialog";
|
|
@ -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", () => <PermissionGroupDetailsPage {...props} />)
|
||||
.add("no members", () => (
|
||||
<PermissionGroupDetailsPage
|
||||
{...props}
|
||||
members={[]}
|
||||
permissionGroup={emptyPermissionGroup}
|
||||
/>
|
||||
))
|
||||
.add("loading", () => (
|
||||
<PermissionGroupDetailsPage
|
||||
{...props}
|
||||
disabled={true}
|
||||
permissionGroup={undefined}
|
||||
permissions={undefined}
|
||||
/>
|
||||
));
|
|
@ -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<MembersListUrlSortField> {
|
||||
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<PermissionGroupDetailsPageProps> = ({
|
||||
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 (
|
||||
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
|
||||
{({ data, change, submit, hasChanged }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.permissionGroups)}
|
||||
</AppHeader>
|
||||
<PageHeader title={permissionGroup?.name} />
|
||||
|
||||
<Grid>
|
||||
<div>
|
||||
<PermissionGroupInfo
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<PermissionGroupMemberList
|
||||
disabled={disabled}
|
||||
{...listProps}
|
||||
users={data?.users || []}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<AccountPermissions
|
||||
permissionsExceeded={permissionsExceeded}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
permissions={permissions}
|
||||
onChange={change}
|
||||
errorMessage={permissionsError}
|
||||
fullAccessLabel={intl.formatMessage({
|
||||
defaultMessage: "Group has full access to the store",
|
||||
description: "checkbox label"
|
||||
})}
|
||||
description={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Expand or restrict group's permissions to access certain part of saleor system.",
|
||||
description: "card description"
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<div>
|
||||
<SaveButtonBar
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !(hasChanged || membersModified)}
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
PermissionGroupDetailsPage.displayName = "PermissionGroupDetailsPage";
|
||||
export default PermissionGroupDetailsPage;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PermissionGroupDetailsPage";
|
||||
export * from "./PermissionGroupDetailsPage";
|
|
@ -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<PermissionGroupInfoProps> = ({
|
||||
disabled,
|
||||
onChange,
|
||||
data,
|
||||
errors
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const formErrors = getFormErrors(["name"], errors);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage(commonMessages.generalInformations)}
|
||||
></CardTitle>
|
||||
<CardContent>
|
||||
<TextField
|
||||
name="name"
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Group name",
|
||||
description: "text field label"
|
||||
})}
|
||||
value={data.name}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
error={!!getFieldError(errors, "name")}
|
||||
helperText={getPermissionGroupErrorMessage(formErrors.name, intl)}
|
||||
fullWidth
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
PermissionGroupInfo.displayName = "PermissionGroupInfo";
|
||||
export default PermissionGroupInfo;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PermissionGroupInfo";
|
||||
export * from "./PermissionGroupInfo";
|
|
@ -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<PermissionGroupListUrlSortField> {
|
||||
permissionGroups: PermissionGroupList_permissionGroups_edges_node[];
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
const PermissionGroupList: React.FC<PermissionGroupListProps> = props => {
|
||||
const {
|
||||
disabled,
|
||||
permissionGroups,
|
||||
pageInfo,
|
||||
onDelete,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onRowClick,
|
||||
onSort,
|
||||
sort
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
<ResponsiveTable>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === PermissionGroupListUrlSortField.name
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
arrowPosition="right"
|
||||
onClick={() => onSort(PermissionGroupListUrlSortField.name)}
|
||||
className={classes.colName}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Permission Group Name"
|
||||
description="permission group name"
|
||||
/>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader className={classes.colMembers} textAlign="right">
|
||||
<FormattedMessage defaultMessage="Members" />
|
||||
</TableCellHeader>
|
||||
<TableCell className={classes.colActionsHeader}>
|
||||
<FormattedMessage defaultMessage="Actions" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={numberOfColumns}
|
||||
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={
|
||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||
}
|
||||
onPreviousPage={onPreviousPage}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
permissionGroups,
|
||||
permissionGroup => (
|
||||
<TableRow
|
||||
className={!!permissionGroup ? classes.link : undefined}
|
||||
hover={!!permissionGroup}
|
||||
key={permissionGroup ? permissionGroup.id : "skeleton"}
|
||||
onClick={
|
||||
permissionGroup ? onRowClick(permissionGroup.id) : undefined
|
||||
}
|
||||
data-tc="id"
|
||||
data-tc-id={maybe(() => permissionGroup.id)}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
{permissionGroup ? (
|
||||
<span data-tc="name">{permissionGroup.name}</span>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colMembers}>
|
||||
{permissionGroup ? (
|
||||
<span data-tc="members">{permissionGroup.users.length}</span>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colActions}>
|
||||
{permissionGroup ? (
|
||||
<>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={stopPropagation(() =>
|
||||
onDelete(permissionGroup.id)
|
||||
)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<IconButton color="primary">
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
<FormattedMessage defaultMessage="No permission groups found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
);
|
||||
};
|
||||
PermissionGroupList.displayName = "PermissionGroupList";
|
||||
export default PermissionGroupList;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PermissionGroupList";
|
||||
export * from "./PermissionGroupList";
|
|
@ -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", () => <PermissionGroupListPage {...props} />)
|
||||
.add("loading", () => (
|
||||
<PermissionGroupListPage
|
||||
{...props}
|
||||
permissionGroups={undefined}
|
||||
disabled={true}
|
||||
/>
|
||||
))
|
||||
.add("no data", () => (
|
||||
<PermissionGroupListPage {...props} permissionGroups={[]} disabled={true} />
|
||||
));
|
|
@ -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<PermissionGroupListUrlSortField> {
|
||||
permissionGroups: PermissionGroupList_permissionGroups_edges_node[];
|
||||
onBack: () => void;
|
||||
onDelete: (id: string) => void;
|
||||
onRowClick: (id: string) => () => void;
|
||||
}
|
||||
|
||||
const PermissionGroupListPage: React.FC<PermissionGroupListPageProps> = ({
|
||||
onAdd,
|
||||
onBack,
|
||||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.configuration)}
|
||||
</AppHeader>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.permissionGroups)}>
|
||||
<Button color="primary" variant="contained" onClick={onAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="create permission group"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Card>
|
||||
<PermissionGroupList {...listProps} />
|
||||
</Card>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
PermissionGroupListPage.displayName = "PermissionGroupListPage";
|
||||
export default PermissionGroupListPage;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PermissionGroupListPage";
|
||||
export * from "./PermissionGroupListPage";
|
|
@ -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<MembersListUrlSortField> {
|
||||
users: PermissionGroupDetails_permissionGroup_users[];
|
||||
disabled: boolean;
|
||||
onUnassign: (ida: string[]) => void;
|
||||
onAssign: () => void;
|
||||
}
|
||||
|
||||
const PermissionGroupMemberList: React.FC<PermissionGroupProps> = 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 (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Group members",
|
||||
description: "header"
|
||||
})}
|
||||
toolbar={
|
||||
<Button
|
||||
color={disabled ? "secondary" : "primary"}
|
||||
onClick={onAssign}
|
||||
disabled={disabled}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign members"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
{members?.length === 0 ? (
|
||||
<div className={classNames(classes.helperText)}>
|
||||
<Typography color="textSecondary">
|
||||
<FormattedMessage
|
||||
defaultMessage="You haven’t assigned any member to this permission group yet."
|
||||
description="empty list message"
|
||||
/>
|
||||
</Typography>
|
||||
<Typography color="textSecondary">
|
||||
<FormattedMessage
|
||||
defaultMessage="Please use Assign Members button to do so."
|
||||
description="empty list message"
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
) : (
|
||||
<ResponsiveTable>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={members}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCellHeader
|
||||
className={classes.colName}
|
||||
arrowPosition="right"
|
||||
onClick={() => onSort(MembersListUrlSortField.name)}
|
||||
direction={
|
||||
sort?.sort === MembersListUrlSortField.name
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Name"
|
||||
description="staff member full name"
|
||||
/>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
className={classes.colEmail}
|
||||
arrowPosition="right"
|
||||
onClick={() => onSort(MembersListUrlSortField.email)}
|
||||
direction={
|
||||
sort?.sort === MembersListUrlSortField.email
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<FormattedMessage defaultMessage="Email Address" />
|
||||
</TableCellHeader>
|
||||
<TableCellHeader textAlign="right">
|
||||
<FormattedMessage defaultMessage="Actions" />
|
||||
</TableCellHeader>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
members,
|
||||
user => {
|
||||
const isSelected = user ? isChecked(user.id) : false;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className={classNames({
|
||||
[classes.tableRow]: !!user
|
||||
})}
|
||||
hover={!!user}
|
||||
selected={isSelected}
|
||||
key={user ? user.id : "skeleton"}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(user.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colName}>
|
||||
<div className={classes.avatar}>
|
||||
{user?.avatar?.url ? (
|
||||
<img
|
||||
className={classes.avatarImage}
|
||||
src={user?.avatar?.url}
|
||||
/>
|
||||
) : (
|
||||
<div className={classes.avatarDefault}>
|
||||
<Typography>{getUserInitials(user)}</Typography>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Typography>
|
||||
{getUserName(user) || <Skeleton />}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant={"caption"}
|
||||
className={classes.statusText}
|
||||
>
|
||||
{!user ? (
|
||||
<Skeleton />
|
||||
) : user.isActive ? (
|
||||
intl.formatMessage({
|
||||
defaultMessage: "Active",
|
||||
description: "staff member status"
|
||||
})
|
||||
) : (
|
||||
intl.formatMessage({
|
||||
defaultMessage: "Inactive",
|
||||
description: "staff member status"
|
||||
})
|
||||
)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colEmail}>
|
||||
{user?.email || <Skeleton />}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colActions}>
|
||||
{user ? (
|
||||
<>
|
||||
<IconButton
|
||||
disabled={disabled}
|
||||
color="primary"
|
||||
onClick={stopPropagation(() =>
|
||||
onUnassign([user.id])
|
||||
)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
<FormattedMessage defaultMessage="No members found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
PermissionGroupMemberList.displayName = "PermissionGroupMemberList";
|
||||
export default PermissionGroupMemberList;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PermissionGroupMemberList";
|
||||
export * from "./PermissionGroupMemberList";
|
|
@ -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", () => <UnassignMembersDialog {...props} />);
|
|
@ -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<UnassignMembersDialogProps> = ({
|
||||
confirmButtonState,
|
||||
quantity,
|
||||
onClose,
|
||||
onConfirm,
|
||||
open
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Unassign users",
|
||||
description: "dialog title"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to unassign {counter,plural,one{this member} other{{displayQuantity} members}}?"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: quantity,
|
||||
displayQuantity: <strong>{quantity}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
UnassignMembersDialog.displayName = "UnassignMembersDialog";
|
||||
export default UnassignMembersDialog;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./UnassignMembersDialog";
|
||||
export * from "./UnassignMembersDialog";
|
229
src/permissionGroups/fixtures.ts
Normal file
229
src/permissionGroups/fixtures.ts
Normal file
|
@ -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);
|
79
src/permissionGroups/index.tsx
Normal file
79
src/permissionGroups/index.tsx
Normal file
|
@ -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<RouteComponentProps<{}>> = ({
|
||||
location
|
||||
}) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: PermissionGroupListUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
PermissionGroupListUrlSortField
|
||||
);
|
||||
|
||||
return <PermissionGroupListComponent params={params} />;
|
||||
};
|
||||
|
||||
interface PermissionGroupDetailsRouteProps {
|
||||
id: string;
|
||||
}
|
||||
const PermissionGroupDetails: React.FC<RouteComponentProps<
|
||||
PermissionGroupDetailsRouteProps
|
||||
>> = ({ match }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: PermissionGroupDetailsUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
MembersListUrlSortField
|
||||
);
|
||||
|
||||
return (
|
||||
<PermissionGroupDetailsComponent
|
||||
id={decodeURIComponent(match.params.id)}
|
||||
params={params}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Component = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.permissionGroups)} />
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path={permissionGroupListPath}
|
||||
component={permissionGroupList}
|
||||
/>
|
||||
<Route
|
||||
path={permissionGroupAddPath}
|
||||
component={PermissionGroupCreate}
|
||||
/>
|
||||
<Route
|
||||
path={permissionGroupDetailsPath(":id")}
|
||||
component={PermissionGroupDetails}
|
||||
/>
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Component;
|
84
src/permissionGroups/mutations.ts
Normal file
84
src/permissionGroups/mutations.ts
Normal file
|
@ -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);
|
106
src/permissionGroups/queries.ts
Normal file
106
src/permissionGroups/queries.ts
Normal file
|
@ -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);
|
25
src/permissionGroups/sort.ts
Normal file
25
src/permissionGroups/sort.ts
Normal file
|
@ -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;
|
||||
};
|
15
src/permissionGroups/types/PermissionFragment.ts
Normal file
15
src/permissionGroups/types/PermissionFragment.ts
Normal file
|
@ -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;
|
||||
}
|
58
src/permissionGroups/types/PermissionGroupCreate.ts
Normal file
58
src/permissionGroups/types/PermissionGroupCreate.ts
Normal file
|
@ -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;
|
||||
}
|
28
src/permissionGroups/types/PermissionGroupDelete.ts
Normal file
28
src/permissionGroups/types/PermissionGroupDelete.ts
Normal file
|
@ -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;
|
||||
}
|
70
src/permissionGroups/types/PermissionGroupDetails.ts
Normal file
70
src/permissionGroups/types/PermissionGroupDetails.ts
Normal file
|
@ -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;
|
||||
}
|
38
src/permissionGroups/types/PermissionGroupDetailsFragment.ts
Normal file
38
src/permissionGroups/types/PermissionGroupDetailsFragment.ts
Normal file
|
@ -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;
|
||||
}
|
15
src/permissionGroups/types/PermissionGroupErrorFragment.ts
Normal file
15
src/permissionGroups/types/PermissionGroupErrorFragment.ts
Normal file
|
@ -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;
|
||||
}
|
21
src/permissionGroups/types/PermissionGroupFragment.ts
Normal file
21
src/permissionGroups/types/PermissionGroupFragment.ts
Normal file
|
@ -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;
|
||||
}
|
55
src/permissionGroups/types/PermissionGroupList.ts
Normal file
55
src/permissionGroups/types/PermissionGroupList.ts
Normal file
|
@ -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;
|
||||
}
|
59
src/permissionGroups/types/PermissionGroupUpdate.ts
Normal file
59
src/permissionGroups/types/PermissionGroupUpdate.ts
Normal file
|
@ -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;
|
||||
}
|
59
src/permissionGroups/urls.ts
Normal file
59
src/permissionGroups/urls.ts
Normal file
|
@ -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<PermissionGroupListUrlSortField>;
|
||||
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<MembersListUrlSortField>;
|
||||
|
||||
export const permissionGroupDetailsPath = (id: string) =>
|
||||
urlJoin(permissionGroupSection, id);
|
||||
export type PermissionGroupDetailsUrlDialog =
|
||||
| "remove"
|
||||
| "assign"
|
||||
| "unassign"
|
||||
| "unassignError";
|
||||
export type PermissionGroupDetailsUrlQueryParams = BulkAction &
|
||||
Pagination &
|
||||
MembersListUrlSort &
|
||||
Dialog<PermissionGroupDetailsUrlDialog>;
|
||||
|
||||
export const permissionGroupDetailsUrl = (
|
||||
id: string,
|
||||
params?: PermissionGroupDetailsUrlQueryParams
|
||||
) =>
|
||||
permissionGroupDetailsPath(encodeURIComponent(id)) +
|
||||
"?" +
|
||||
stringifyQs(params);
|
81
src/permissionGroups/utils.ts
Normal file
81
src/permissionGroups/utils.ts
Normal file
|
@ -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;
|
||||
};
|
|
@ -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 (
|
||||
<>
|
||||
<WindowTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Create category",
|
||||
description: "window title"
|
||||
})}
|
||||
/>
|
||||
<PermissionGroupCreatePage
|
||||
errors={errors}
|
||||
disabled={createPermissionGroupResult.loading}
|
||||
permissions={permissions}
|
||||
saveButtonBarState={createPermissionGroupResult.status}
|
||||
onSubmit={onSubmit}
|
||||
onBack={() => navigate(permissionGroupListUrl())}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
PermissionGroupCreateView.displayName = "PermissionGroupCreateView";
|
||||
|
||||
export default PermissionGroupCreateView;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PermissionGroupCreate";
|
||||
export * from "./PermissionGroupCreate";
|
|
@ -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<PermissionGroupDetailsProps> = ({
|
||||
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 (
|
||||
<>
|
||||
<PermissionGroupDetailsPage
|
||||
permissionGroup={data?.permissionGroup}
|
||||
permissionsExceeded={permissionsExceeded}
|
||||
members={membersList || []}
|
||||
membersModified={membersModified}
|
||||
onBack={() => 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={
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => openModal("unassign", { ids: listElements })}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Unassign",
|
||||
description: "button title"
|
||||
})}
|
||||
</Button>
|
||||
}
|
||||
onSort={handleSort}
|
||||
/>
|
||||
<AssignMembersDialog
|
||||
loading={searchResult.loading}
|
||||
staffMembers={searchResult?.data?.search.edges.map(edge => 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();
|
||||
}}
|
||||
/>
|
||||
<UnassignMembersDialog
|
||||
onConfirm={unassignMembers}
|
||||
confirmButtonState={permissionGroupUpdateResult.status}
|
||||
quantity={listElements.length}
|
||||
open={params.action === "unassign"}
|
||||
onClose={closeModal}
|
||||
/>
|
||||
<MembersErrorDialog
|
||||
onConfirm={closeModal}
|
||||
confirmButtonState={permissionGroupUpdateResult.status}
|
||||
open={params.action === "unassignError"}
|
||||
onClose={closeModal}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PermissionGroupDetails;
|
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PermissionGroupDetails";
|
||||
export * from "./PermissionGroupDetails";
|
|
@ -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<PermissionGroupListProps> = ({
|
||||
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 (
|
||||
<>
|
||||
<PermissionGroupListPage
|
||||
disabled={loading}
|
||||
settings={settings}
|
||||
pageInfo={pageInfo}
|
||||
sort={getSortParams(params)}
|
||||
permissionGroups={permissionGroups}
|
||||
onAdd={() => navigate(permissionGroupAddUrl)}
|
||||
onBack={() => navigate(configurationMenuUrl)}
|
||||
onDelete={id => openModal("remove", { id })}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
onRowClick={id => () => navigate(permissionGroupDetailsUrl(id))}
|
||||
onSort={handleSort}
|
||||
/>
|
||||
<PermissionGroupDeleteDialog
|
||||
onConfirm={() =>
|
||||
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;
|
2
src/permissionGroups/views/PermissionGroupList/index.ts
Normal file
2
src/permissionGroups/views/PermissionGroupList/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PermissionGroupList";
|
||||
export * from "./PermissionGroupList";
|
18
src/permissionGroups/views/PermissionGroupList/sort.ts
Normal file
18
src/permissionGroups/views/PermissionGroupList/sort.ts
Normal file
|
@ -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
|
||||
);
|
43
src/searches/types/SearchPermissionGroups.ts
Normal file
43
src/searches/types/SearchPermissionGroups.ts
Normal file
|
@ -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;
|
||||
}
|
52
src/searches/types/SearchStaffMembers.ts
Normal file
52
src/searches/types/SearchStaffMembers.ts
Normal file
|
@ -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;
|
||||
}
|
35
src/searches/usePermissionGroupSearch.ts
Normal file
35
src/searches/usePermissionGroupSearch.ts
Normal file
|
@ -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);
|
41
src/searches/useStaffMemberSearch.ts
Normal file
41
src/searches/useStaffMemberSearch.ts
Normal file
|
@ -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);
|
|
@ -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<ServiceCreatePageProps> = props => {
|
|||
name: "",
|
||||
permissions: []
|
||||
};
|
||||
|
||||
const formErrors = getFormErrors(["permissions"], errors || []);
|
||||
const permissionsError = getAccountErrorMessage(formErrors.permissions, intl);
|
||||
|
||||
return (
|
||||
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
|
||||
{({ data, change, hasChanged, submit }) => (
|
||||
|
@ -73,9 +79,20 @@ const ServiceCreatePage: React.FC<ServiceCreatePageProps> = props => {
|
|||
</div>
|
||||
<AccountPermissions
|
||||
data={data}
|
||||
errorMessage={permissionsError}
|
||||
disabled={disabled}
|
||||
permissions={permissions}
|
||||
permissionsExceeded={false}
|
||||
onChange={change}
|
||||
fullAccessLabel={intl.formatMessage({
|
||||
defaultMessage: "User has full access to the store",
|
||||
description: "checkbox label"
|
||||
})}
|
||||
description={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Expand or restrict user's permissions to access certain part of saleor system.",
|
||||
description: "card description"
|
||||
})}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<AccountStatus
|
||||
|
|
|
@ -17,6 +17,8 @@ import { maybe } from "@saleor/misc";
|
|||
import { ServiceDetails_serviceAccount } from "@saleor/services/types/ServiceDetails";
|
||||
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 ServiceDefaultToken from "../ServiceDefaultToken";
|
||||
import ServiceInfo from "../ServiceInfo";
|
||||
import ServiceTokens from "../ServiceTokens";
|
||||
|
@ -63,6 +65,9 @@ const ServiceDetailsPage: React.FC<ServiceDetailsPageProps> = 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<ServiceDetailsPageProps> = props => {
|
|||
<div>
|
||||
<AccountPermissions
|
||||
data={data}
|
||||
errorMessage={permissionsError}
|
||||
disabled={disabled}
|
||||
permissions={permissions}
|
||||
permissionsExceeded={false}
|
||||
onChange={change}
|
||||
fullAccessLabel={intl.formatMessage({
|
||||
defaultMessage: "User has full access to the store",
|
||||
description: "checkbox label"
|
||||
})}
|
||||
description={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Expand or restrict user's permissions to access certain part of saleor system.",
|
||||
description: "card description"
|
||||
})}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<AccountStatus
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { PermissionEnum } from "@saleor/types/globalTypes";
|
||||
|
||||
import { ServiceDetails_serviceAccount } from "./types/ServiceDetails";
|
||||
import { ServiceList_serviceAccounts_edges_node } from "./types/ServiceList";
|
||||
|
||||
|
|
|
@ -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 { serviceDetailsFragment, serviceFragment } from "./queries";
|
||||
import { ServiceCreate, ServiceCreateVariables } from "./types/ServiceCreate";
|
||||
|
@ -16,7 +16,7 @@ import {
|
|||
import { ServiceUpdate, ServiceUpdateVariables } from "./types/ServiceUpdate";
|
||||
|
||||
const serviceCreateMutation = gql`
|
||||
${accountFragmentError}
|
||||
${accountErrorFragment}
|
||||
${serviceFragment}
|
||||
mutation ServiceCreate($input: ServiceAccountInput!) {
|
||||
serviceAccountCreate(input: $input) {
|
||||
|
@ -37,7 +37,7 @@ export const ServiceCreateMutation = TypedMutation<
|
|||
>(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 {
|
||||
|
|
|
@ -177,7 +177,7 @@ export const ShippingZonesList: React.FC<ShippingZonesListProps> = ({
|
|||
<DeleteIcon />
|
||||
</IconButton>
|
||||
}
|
||||
userPermissions={maybe(() => user.permissions, [])}
|
||||
userPermissions={user?.userPermissions || []}
|
||||
/>
|
||||
|
||||
<ActionDialog
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
|
@ -5,9 +8,6 @@ import DialogContent from "@material-ui/core/DialogContent";
|
|||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ConfirmButton, {
|
||||
ConfirmButtonTransitionState
|
||||
} from "@saleor/components/ConfirmButton";
|
||||
|
@ -17,20 +17,22 @@ import { buttonMessages, commonMessages } from "@saleor/intl";
|
|||
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
|
||||
import { getFormErrors } from "@saleor/utils/errors";
|
||||
import { StaffErrorFragment } from "@saleor/staff/types/StaffErrorFragment";
|
||||
import { SearchPermissionGroups_search_edges_node } from "@saleor/searches/types/SearchPermissionGroups";
|
||||
import { FetchMoreProps, SearchPageProps } from "@saleor/types";
|
||||
import getStaffErrorMessage from "@saleor/utils/errors/staff";
|
||||
|
||||
export interface FormData {
|
||||
export interface AddMemberFormData {
|
||||
email: string;
|
||||
firstName: string;
|
||||
fullAccess: boolean;
|
||||
lastName: string;
|
||||
permissionGroups: string[];
|
||||
}
|
||||
|
||||
const initialForm: FormData = {
|
||||
const initialForm: AddMemberFormData = {
|
||||
email: "",
|
||||
firstName: "",
|
||||
fullAccess: false,
|
||||
lastName: ""
|
||||
lastName: "",
|
||||
permissionGroups: []
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -55,21 +57,23 @@ const useStyles = makeStyles(
|
|||
{ name: "StaffAddMemberDialog" }
|
||||
);
|
||||
|
||||
interface StaffAddMemberDialogProps {
|
||||
interface StaffAddMemberDialogProps extends SearchPageProps {
|
||||
availablePermissionGroups: SearchPermissionGroups_search_edges_node[];
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
disabled: boolean;
|
||||
errors: StaffErrorFragment[];
|
||||
fetchMorePermissionGroups: FetchMoreProps;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: (data: FormData) => void;
|
||||
onConfirm: (data: AddMemberFormData) => void;
|
||||
}
|
||||
|
||||
const StaffAddMemberDialog: React.FC<StaffAddMemberDialogProps> = 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<StaffAddMemberDialogProps> = props => {
|
|||
return (
|
||||
<Dialog onClose={onClose} open={open}>
|
||||
<Form initial={initialForm} onSubmit={onConfirm}>
|
||||
{({ change, data, hasChanged }) => (
|
||||
{({ change, data: formData, hasChanged }) => (
|
||||
<>
|
||||
<DialogTitle>
|
||||
<FormattedMessage
|
||||
|
@ -90,20 +94,26 @@ const StaffAddMemberDialog: React.FC<StaffAddMemberDialogProps> = props => {
|
|||
<div className={classes.textFieldGrid}>
|
||||
<TextField
|
||||
error={!!formErrors.firstName}
|
||||
helperText={getStaffErrorMessage(formErrors.firstName, intl)}
|
||||
helperText={
|
||||
!!formErrors.firstName &&
|
||||
getStaffErrorMessage(formErrors.firstName, intl)
|
||||
}
|
||||
label={intl.formatMessage(commonMessages.firstName)}
|
||||
name="firstName"
|
||||
type="text"
|
||||
value={data.firstName}
|
||||
value={formData.firstName}
|
||||
onChange={change}
|
||||
/>
|
||||
<TextField
|
||||
error={!!formErrors.lastName}
|
||||
helperText={getStaffErrorMessage(formErrors.lastName, intl)}
|
||||
helperText={
|
||||
!!formErrors.lastName &&
|
||||
getStaffErrorMessage(formErrors.lastName, intl)
|
||||
}
|
||||
label={intl.formatMessage(commonMessages.lastName)}
|
||||
name="lastName"
|
||||
type="text"
|
||||
value={data.lastName}
|
||||
value={formData.lastName}
|
||||
onChange={change}
|
||||
/>
|
||||
</div>
|
||||
|
@ -111,14 +121,18 @@ const StaffAddMemberDialog: React.FC<StaffAddMemberDialogProps> = props => {
|
|||
<TextField
|
||||
error={!!formErrors.email}
|
||||
fullWidth
|
||||
helperText={getStaffErrorMessage(formErrors.email, intl)}
|
||||
helperText={
|
||||
!!formErrors.email &&
|
||||
getStaffErrorMessage(formErrors.email, intl)
|
||||
}
|
||||
label={intl.formatMessage(commonMessages.email)}
|
||||
name="email"
|
||||
type="email"
|
||||
value={data.email}
|
||||
value={formData.email}
|
||||
onChange={change}
|
||||
/>
|
||||
</DialogContent>
|
||||
<hr className={classes.hr} />
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage {...buttonMessages.back} />
|
||||
|
|
|
@ -1,29 +1,32 @@
|
|||
import { Omit } from "@material-ui/core";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import { permissions } from "@saleor/fixtures";
|
||||
import StaffDetailsPage, {
|
||||
StaffDetailsPageProps
|
||||
} from "../../../staff/components/StaffDetailsPage";
|
||||
import { staffMember } from "../../../staff/fixtures";
|
||||
import Decorator from "../../Decorator";
|
||||
} from "@saleor/staff/components/StaffDetailsPage";
|
||||
import { staffMember } from "@saleor/staff/fixtures";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { userPermissionGroups } from "@saleor/permissionGroups/fixtures";
|
||||
|
||||
const props: Omit<StaffDetailsPageProps, "classes"> = {
|
||||
availablePermissionGroups: [],
|
||||
canEditAvatar: false,
|
||||
canEditPreferences: false,
|
||||
canEditStatus: true,
|
||||
canRemove: true,
|
||||
disabled: false,
|
||||
errors: [],
|
||||
fetchMorePermissionGroups: undefined,
|
||||
initialSearch: "",
|
||||
onBack: () => undefined,
|
||||
onChangePassword: () => undefined,
|
||||
onDelete: () => undefined,
|
||||
onImageDelete: () => undefined,
|
||||
onImageUpload: () => undefined,
|
||||
onSearchChange: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
permissions,
|
||||
saveButtonBarState: "default",
|
||||
staffMember
|
||||
staffMember: { ...staffMember, permissionGroups: userPermissionGroups }
|
||||
};
|
||||
|
||||
storiesOf("Views / Staff / Staff member details", module)
|
||||
|
@ -32,15 +35,7 @@ storiesOf("Views / Staff / Staff member details", module)
|
|||
.add("loading", () => (
|
||||
<StaffDetailsPage {...props} disabled={true} staffMember={undefined} />
|
||||
))
|
||||
.add("not admin", () => (
|
||||
<StaffDetailsPage
|
||||
{...props}
|
||||
staffMember={{
|
||||
...staffMember,
|
||||
permissions: staffMember.permissions.slice(1)
|
||||
}}
|
||||
/>
|
||||
))
|
||||
.add("not admin", () => <StaffDetailsPage {...props} canEditStatus={false} />)
|
||||
.add("himself", () => (
|
||||
<StaffDetailsPage
|
||||
{...props}
|
|
@ -1,150 +1,199 @@
|
|||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import AccountPermissions from "@saleor/components/AccountPermissions";
|
||||
import { Card, CardContent, Typography } from "@material-ui/core";
|
||||
import AccountPermissionGroups from "@saleor/components/AccountPermissionGroups";
|
||||
import AccountStatus from "@saleor/components/AccountStatus";
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import Container from "@saleor/components/Container";
|
||||
import Form from "@saleor/components/Form";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInfo";
|
||||
import useLocale from "@saleor/hooks/useLocale";
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { getUserName, maybe } from "../../../misc";
|
||||
import { PermissionEnum } from "../../../types/globalTypes";
|
||||
import { getUserName } from "@saleor/misc";
|
||||
import { SearchPermissionGroups_search_edges_node } from "@saleor/searches/types/SearchPermissionGroups";
|
||||
import { FetchMoreProps, SearchPageProps } from "@saleor/types";
|
||||
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
|
||||
import { StaffErrorFragment } from "@saleor/staff/types/StaffErrorFragment";
|
||||
import { StaffMemberDetails_user } from "../../types/StaffMemberDetails";
|
||||
import StaffPassword from "../StaffPassword/StaffPassword";
|
||||
import StaffPreferences from "../StaffPreferences";
|
||||
import StaffProperties from "../StaffProperties/StaffProperties";
|
||||
import StaffPassword from "../StaffPassword/StaffPassword";
|
||||
|
||||
interface FormData {
|
||||
hasFullAccess: boolean;
|
||||
isActive: boolean;
|
||||
permissions: PermissionEnum[];
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
export interface StaffDetailsFormData {
|
||||
email: string;
|
||||
firstName: string;
|
||||
isActive: boolean;
|
||||
lastName: string;
|
||||
permissionGroups: string[];
|
||||
}
|
||||
|
||||
export interface StaffDetailsPageProps {
|
||||
export interface StaffDetailsPageProps extends SearchPageProps {
|
||||
availablePermissionGroups: SearchPermissionGroups_search_edges_node[];
|
||||
canEditAvatar: boolean;
|
||||
canEditPreferences: boolean;
|
||||
canEditStatus: boolean;
|
||||
canRemove: boolean;
|
||||
disabled: boolean;
|
||||
permissions: ShopInfo_shop_permissions[];
|
||||
fetchMorePermissionGroups: FetchMoreProps;
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
staffMember: StaffMemberDetails_user;
|
||||
errors: StaffErrorFragment[];
|
||||
onBack: () => void;
|
||||
onChangePassword: () => void;
|
||||
onDelete: () => void;
|
||||
onImageDelete: () => void;
|
||||
onSubmit: (data: FormData) => void;
|
||||
onSubmit: (data: StaffDetailsFormData) => void;
|
||||
onImageUpload(file: File);
|
||||
}
|
||||
|
||||
const StaffDetailsPage: React.FC<StaffDetailsPageProps> = ({
|
||||
availablePermissionGroups,
|
||||
canEditAvatar,
|
||||
canEditPreferences,
|
||||
canEditStatus,
|
||||
canRemove,
|
||||
disabled,
|
||||
permissions,
|
||||
saveButtonBarState,
|
||||
staffMember,
|
||||
errors,
|
||||
fetchMorePermissionGroups,
|
||||
initialSearch,
|
||||
onBack,
|
||||
onChangePassword,
|
||||
onDelete,
|
||||
onImageDelete,
|
||||
onImageUpload,
|
||||
onSubmit
|
||||
onSearchChange,
|
||||
onSubmit,
|
||||
saveButtonBarState,
|
||||
staffMember
|
||||
}: StaffDetailsPageProps) => {
|
||||
const intl = useIntl();
|
||||
const { locale, setLocale } = useLocale();
|
||||
const [
|
||||
permissionGroupsDisplayValues,
|
||||
setPermissionGroupsDisplayValues
|
||||
] = useStateFromProps<MultiAutocompleteChoiceType[]>(
|
||||
(staffMember?.permissionGroups || []).map(group => ({
|
||||
disabled: !group.userCanManage,
|
||||
label: group.name,
|
||||
value: group.id
|
||||
})) || []
|
||||
);
|
||||
|
||||
const initialForm: FormData = {
|
||||
email: maybe(() => staffMember.email, ""),
|
||||
firstName: maybe(() => staffMember.firstName, ""),
|
||||
hasFullAccess: maybe(
|
||||
() =>
|
||||
permissions.filter(
|
||||
perm =>
|
||||
maybe(() => staffMember.permissions, []).filter(
|
||||
userPerm => userPerm.code === perm.code
|
||||
).length === 0
|
||||
).length === 0,
|
||||
false
|
||||
),
|
||||
isActive: maybe(() => staffMember.isActive, false),
|
||||
lastName: maybe(() => staffMember.lastName, ""),
|
||||
permissions: maybe(() => staffMember.permissions, []).map(perm => perm.code)
|
||||
const initialForm: StaffDetailsFormData = {
|
||||
email: staffMember?.email || "",
|
||||
firstName: staffMember?.firstName || "",
|
||||
isActive: !!staffMember?.isActive,
|
||||
lastName: staffMember?.lastName || "",
|
||||
permissionGroups: staffMember?.permissionGroups.map(pg => pg.id) || []
|
||||
};
|
||||
|
||||
return (
|
||||
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
|
||||
{({ data, change, hasChanged, submit }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.staff)}
|
||||
</AppHeader>
|
||||
<PageHeader title={getUserName(staffMember)} />
|
||||
<Grid>
|
||||
<div>
|
||||
<StaffProperties
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
canEditAvatar={canEditAvatar}
|
||||
staffMember={staffMember}
|
||||
onChange={change}
|
||||
onImageUpload={onImageUpload}
|
||||
onImageDelete={onImageDelete}
|
||||
/>
|
||||
{canEditPreferences && (
|
||||
<>
|
||||
<CardSpacer />
|
||||
<StaffPassword onChangePassword={onChangePassword} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{canEditPreferences && (
|
||||
<StaffPreferences locale={locale} onLocaleChange={setLocale} />
|
||||
)}
|
||||
{canEditStatus && (
|
||||
<>
|
||||
<AccountPermissions
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
permissions={permissions}
|
||||
onChange={change}
|
||||
{({ data: formData, change, hasChanged, submit, toggleValue }) => {
|
||||
const permissionGroupsChange = createMultiAutocompleteSelectHandler(
|
||||
toggleValue,
|
||||
setPermissionGroupsDisplayValues,
|
||||
permissionGroupsDisplayValues,
|
||||
availablePermissionGroups?.map(group => ({
|
||||
label: group.name,
|
||||
value: group.id
|
||||
})) || []
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.staff)}
|
||||
</AppHeader>
|
||||
<PageHeader title={getUserName(staffMember)} />
|
||||
<Grid>
|
||||
<div>
|
||||
<StaffProperties
|
||||
errors={errors}
|
||||
data={formData}
|
||||
disabled={disabled}
|
||||
canEditAvatar={canEditAvatar}
|
||||
staffMember={staffMember}
|
||||
onChange={change}
|
||||
onImageUpload={onImageUpload}
|
||||
onImageDelete={onImageDelete}
|
||||
/>
|
||||
{canEditPreferences && (
|
||||
<>
|
||||
<CardSpacer />
|
||||
<StaffPassword onChangePassword={onChangePassword} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{canEditPreferences && (
|
||||
<StaffPreferences
|
||||
locale={locale}
|
||||
onLocaleChange={setLocale}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<AccountStatus
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "User is active",
|
||||
description: "checkbox label"
|
||||
})}
|
||||
onChange={change}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
disabled={disabled || !hasChanged}
|
||||
state={saveButtonBarState}
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
onDelete={canRemove ? onDelete : undefined}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
)}
|
||||
{canEditStatus && (
|
||||
<>
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Permissions",
|
||||
description: "dialog header"
|
||||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "User is assigned to:",
|
||||
description: "card description"
|
||||
})}
|
||||
</Typography>
|
||||
|
||||
<AccountPermissionGroups
|
||||
formData={formData}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
initialSearch={initialSearch}
|
||||
availablePermissionGroups={availablePermissionGroups}
|
||||
onChange={permissionGroupsChange}
|
||||
onSearchChange={onSearchChange}
|
||||
displayValues={permissionGroupsDisplayValues}
|
||||
{...fetchMorePermissionGroups}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<CardSpacer />
|
||||
<AccountStatus
|
||||
data={formData}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "User is active",
|
||||
description: "checkbox label"
|
||||
})}
|
||||
onChange={change}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
disabled={disabled || !hasChanged}
|
||||
state={saveButtonBarState}
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
onDelete={canRemove ? onDelete : undefined}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,12 +9,10 @@ import {
|
|||
tabPageProps,
|
||||
sortPageProps,
|
||||
filterPageProps
|
||||
} from "../../../fixtures";
|
||||
import StaffListPage, {
|
||||
StaffListPageProps
|
||||
} from "../../../staff/components/StaffListPage";
|
||||
import { staffMembers } from "../../../staff/fixtures";
|
||||
import Decorator from "../../Decorator";
|
||||
} from "@saleor/fixtures";
|
||||
import { staffMembers } from "@saleor/staff/fixtures";
|
||||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import StaffListPage, { StaffListPageProps } from ".";
|
||||
|
||||
const props: StaffListPageProps = {
|
||||
...pageListProps.default,
|
|
@ -10,6 +10,9 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||
import photoIcon from "@assets/images/photo-icon.svg";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getFormErrors } from "@saleor/utils/errors";
|
||||
import { StaffErrorFragment } from "@saleor/staff/types/StaffErrorFragment";
|
||||
import getStaffErrorMessage from "@saleor/utils/errors/staff";
|
||||
import { getUserInitials, maybe } from "../../../misc";
|
||||
import { StaffMemberDetails_user } from "../../types/StaffMemberDetails";
|
||||
|
||||
|
@ -100,6 +103,7 @@ interface StaffPropertiesProps {
|
|||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
errors: StaffErrorFragment[];
|
||||
disabled: boolean;
|
||||
staffMember: StaffMemberDetails_user;
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
|
@ -112,6 +116,7 @@ const StaffProperties: React.FC<StaffPropertiesProps> = props => {
|
|||
canEditAvatar,
|
||||
className,
|
||||
data,
|
||||
errors,
|
||||
staffMember,
|
||||
onChange,
|
||||
onImageDelete,
|
||||
|
@ -123,6 +128,7 @@ const StaffProperties: React.FC<StaffPropertiesProps> = props => {
|
|||
const imgInputAnchor = React.createRef<HTMLInputElement>();
|
||||
|
||||
const clickImgInput = () => imgInputAnchor.current.click();
|
||||
const formErrors = getFormErrors(["id"], errors || []);
|
||||
|
||||
return (
|
||||
<Card className={className}>
|
||||
|
@ -205,6 +211,13 @@ const StaffProperties: React.FC<StaffPropertiesProps> = props => {
|
|||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
{!!formErrors.id && (
|
||||
<CardContent>
|
||||
<Typography color="error">
|
||||
{getStaffErrorMessage(formErrors.id, intl)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -145,5 +145,9 @@ export const staffMember: StaffMemberDetails_user = {
|
|||
id: "VXNlcjoyMQ==",
|
||||
isActive: true,
|
||||
lastName: "Smith",
|
||||
permissions
|
||||
permissionGroups: [],
|
||||
userPermissions: permissions.map(p => ({
|
||||
...p,
|
||||
__typename: "UserPermission"
|
||||
}))
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -23,7 +23,12 @@ export const staffMemberDetailsFragment = gql`
|
|||
${staffMemberFragment}
|
||||
fragment StaffMemberDetailsFragment on User {
|
||||
...StaffMemberFragment
|
||||
permissions {
|
||||
permissionGroups {
|
||||
id
|
||||
name
|
||||
userCanManage
|
||||
}
|
||||
userPermissions {
|
||||
code
|
||||
name
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue