Assign channel to permission group (#3515)
This commit is contained in:
parent
eba9ee6603
commit
a63af3ab73
56 changed files with 2121 additions and 528 deletions
5
.changeset/twenty-ways-reflect.md
Normal file
5
.changeset/twenty-ways-reflect.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-dashboard": minor
|
||||
---
|
||||
|
||||
Assign channel permission to permission group
|
|
@ -15,6 +15,7 @@ export const MockedUserProvider: React.FC<{
|
|||
requestLoginByExternalPlugin: undefined,
|
||||
authenticating: false,
|
||||
authenticated: false,
|
||||
refetchUser: undefined,
|
||||
user: {
|
||||
id: "0",
|
||||
email: "email@email.me",
|
||||
|
@ -24,6 +25,8 @@ export const MockedUserProvider: React.FC<{
|
|||
userPermissions: customPermissions ?? adminUserPermissions,
|
||||
avatar: null,
|
||||
__typename: "User",
|
||||
accessibleChannels: [],
|
||||
restrictedAccessToChannels: false,
|
||||
},
|
||||
errors: [],
|
||||
}}
|
||||
|
|
|
@ -130685,10 +130685,7 @@
|
|||
"name": "deprecated",
|
||||
"description": "Marks an element of a GraphQL schema as no longer supported.",
|
||||
"isRepeatable": false,
|
||||
"locations": [
|
||||
"ENUM_VALUE",
|
||||
"FIELD_DEFINITION"
|
||||
],
|
||||
"locations": ["ENUM_VALUE", "FIELD_DEFINITION"],
|
||||
"args": [
|
||||
{
|
||||
"name": "reason",
|
||||
|
@ -130738,11 +130735,7 @@
|
|||
"name": "include",
|
||||
"description": "Directs the executor to include this field or fragment only when the `if` argument is true.",
|
||||
"isRepeatable": false,
|
||||
"locations": [
|
||||
"FIELD",
|
||||
"FRAGMENT_SPREAD",
|
||||
"INLINE_FRAGMENT"
|
||||
],
|
||||
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
|
||||
"args": [
|
||||
{
|
||||
"name": "if",
|
||||
|
@ -130766,11 +130759,7 @@
|
|||
"name": "skip",
|
||||
"description": "Directs the executor to skip this field or fragment when the `if` argument is true.",
|
||||
"isRepeatable": false,
|
||||
"locations": [
|
||||
"FIELD",
|
||||
"FRAGMENT_SPREAD",
|
||||
"INLINE_FRAGMENT"
|
||||
],
|
||||
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
|
||||
"args": [
|
||||
{
|
||||
"name": "if",
|
||||
|
@ -130794,12 +130783,7 @@
|
|||
"name": "webhookEventsInfo",
|
||||
"description": "Webhook events triggered by a specific location.",
|
||||
"isRepeatable": false,
|
||||
"locations": [
|
||||
"FIELD",
|
||||
"FIELD_DEFINITION",
|
||||
"INPUT_OBJECT",
|
||||
"OBJECT"
|
||||
],
|
||||
"locations": ["FIELD", "FIELD_DEFINITION", "INPUT_OBJECT", "OBJECT"],
|
||||
"args": [
|
||||
{
|
||||
"name": "asyncEvents",
|
||||
|
|
|
@ -349,6 +349,9 @@
|
|||
"context": "min price in channel",
|
||||
"string": "Min. value"
|
||||
},
|
||||
"0HBlkO": {
|
||||
"string": "Search channels"
|
||||
},
|
||||
"0KmZCN": {
|
||||
"context": "button",
|
||||
"string": "Open playground"
|
||||
|
@ -1509,6 +1512,9 @@
|
|||
"context": "modal button images upload",
|
||||
"string": "Upload Images"
|
||||
},
|
||||
"9FGTOt": {
|
||||
"string": "Allow access to orders of all channels"
|
||||
},
|
||||
"9IWg/f": {
|
||||
"context": "button",
|
||||
"string": "SETUP END DATE"
|
||||
|
@ -6248,6 +6254,9 @@
|
|||
"context": "page header",
|
||||
"string": "Create Page"
|
||||
},
|
||||
"grkY2V": {
|
||||
"string": "You don't have access to any channels"
|
||||
},
|
||||
"gvOzOl": {
|
||||
"string": "Page Title"
|
||||
},
|
||||
|
@ -6979,10 +6988,6 @@
|
|||
"context": "add authorization key error",
|
||||
"string": "Authorization key with this type already exists"
|
||||
},
|
||||
"mAabef": {
|
||||
"context": "checkbox label",
|
||||
"string": "Group has full access to the store"
|
||||
},
|
||||
"mCP0UD": {
|
||||
"context": "order draft creation date",
|
||||
"string": "Date"
|
||||
|
@ -8323,10 +8328,16 @@
|
|||
"context": "button",
|
||||
"string": "Create category"
|
||||
},
|
||||
"vprU7C": {
|
||||
"string": "Select visible order channels"
|
||||
},
|
||||
"vwMO04": {
|
||||
"context": "draft order",
|
||||
"string": "Created"
|
||||
},
|
||||
"vz3yxp": {
|
||||
"string": "Channels permissions"
|
||||
},
|
||||
"vzce9B": {
|
||||
"context": "customer gift cards card subtitle",
|
||||
"string": "Only five newest gift cards are shown here"
|
||||
|
|
|
@ -245,6 +245,7 @@ export function useAuthProvider({
|
|||
authenticating: authenticating && !errors.length,
|
||||
authenticated: authenticated && !!user?.isStaff && !errors.length,
|
||||
user: userDetails.data?.me,
|
||||
refetchUser: userDetails.refetch,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
|
11
src/auth/hooks/useUserAccessibleChannels.ts
Normal file
11
src/auth/hooks/useUserAccessibleChannels.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { useUser } from "..";
|
||||
|
||||
export const useUserAccessibleChannels = () => {
|
||||
const user = useUser();
|
||||
|
||||
if (!user?.user?.accessibleChannels) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return user.user.accessibleChannels;
|
||||
};
|
|
@ -31,6 +31,7 @@ export const UserContext = React.createContext<Context>({
|
|||
authenticating: false,
|
||||
authenticated: false,
|
||||
errors: [],
|
||||
refetchUser: undefined,
|
||||
});
|
||||
|
||||
const AuthRouter: React.FC = () => (
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { UserFragment } from "@dashboard/graphql";
|
||||
import { ApolloQueryResult } from "@apollo/client";
|
||||
import { UserDetailsQuery, UserFragment } from "@dashboard/graphql";
|
||||
import {
|
||||
GetExternalAccessTokenData,
|
||||
GetExternalAuthUrlData,
|
||||
|
@ -44,4 +45,5 @@ export interface UserContext {
|
|||
authenticating: boolean;
|
||||
authenticated: boolean;
|
||||
errors: UserContextError[];
|
||||
refetchUser: () => Promise<ApolloQueryResult<UserDetailsQuery>>;
|
||||
}
|
||||
|
|
|
@ -1,42 +1,13 @@
|
|||
// @ts-strict-ignore
|
||||
import { useUser } from "@dashboard/auth";
|
||||
import CardTitle from "@dashboard/components/CardTitle";
|
||||
import Skeleton from "@dashboard/components/Skeleton";
|
||||
import { PermissionData } from "@dashboard/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
Checkbox,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { makeStyles } from "@saleor/macaw-ui";
|
||||
import React from "react";
|
||||
import { PermissionData } from "@dashboard/permissionGroups/components/PermissionGroupDetailsPage";
|
||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||
import React, { ChangeEvent } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
const byAlphabeticalOrder =
|
||||
<T extends {}>(field: string) =>
|
||||
(a: T, b: T) =>
|
||||
a[field].localeCompare(b[field]);
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
checkboxContainer: {
|
||||
marginTop: theme.spacing(),
|
||||
},
|
||||
hr: {
|
||||
backgroundColor: theme.palette.divider,
|
||||
border: "none",
|
||||
height: 1,
|
||||
marginBottom: 0,
|
||||
marginTop: 0,
|
||||
},
|
||||
}),
|
||||
{ name: "AccountPermissions" },
|
||||
);
|
||||
import { Header } from "./components/Header";
|
||||
import { PermissionsExceeded } from "./components/PermissionExeeded";
|
||||
import { PermissionList } from "./components/PermissionList";
|
||||
import { messages } from "./messages";
|
||||
|
||||
interface AccountPermissionsProps {
|
||||
permissions: PermissionData[];
|
||||
|
@ -47,7 +18,7 @@ interface AccountPermissionsProps {
|
|||
};
|
||||
disabled: boolean;
|
||||
description: string;
|
||||
errorMessage: string;
|
||||
errorMessage: string | undefined;
|
||||
fullAccessLabel: string;
|
||||
onChange: (event: React.ChangeEvent<any>, cb?: () => void) => void;
|
||||
}
|
||||
|
@ -63,11 +34,10 @@ const AccountPermissions: React.FC<AccountPermissionsProps> = props => {
|
|||
errorMessage,
|
||||
} = props;
|
||||
|
||||
const permissions = Object.values(props?.permissions ?? {}).sort(
|
||||
byAlphabeticalOrder("name"),
|
||||
const permissions = Object.values(props?.permissions ?? {}).sort((a, b) =>
|
||||
a.name.localeCompare(b.name),
|
||||
);
|
||||
|
||||
const classes = useStyles(props);
|
||||
const intl = useIntl();
|
||||
const { user } = useUser();
|
||||
|
||||
|
@ -75,161 +45,99 @@ const AccountPermissions: React.FC<AccountPermissionsProps> = props => {
|
|||
onChange({
|
||||
target: {
|
||||
name: "permissions",
|
||||
value: !data.hasFullAccess ? permissions.map(perm => perm.code) : [],
|
||||
value: !data.hasFullAccess
|
||||
? permissions.filter(perm => !perm.disabled).map(perm => perm.code)
|
||||
: [],
|
||||
},
|
||||
} as any);
|
||||
} as ChangeEvent<any>);
|
||||
|
||||
onChange({
|
||||
target: {
|
||||
name: "hasFullAccess",
|
||||
value: !data.hasFullAccess,
|
||||
},
|
||||
} as any);
|
||||
} as ChangeEvent<any>);
|
||||
};
|
||||
const handlePermissionChange = (key, value) => () => {
|
||||
|
||||
const handlePermissionChange = (key: string, value: boolean) => {
|
||||
const updatedPersmissions = !value
|
||||
? data.permissions.concat([key])
|
||||
: data.permissions.filter(perm => perm !== key);
|
||||
|
||||
// If all permissions are selected, set hasFullAccess to true
|
||||
onChange({
|
||||
target: {
|
||||
name: "hasFullAccess",
|
||||
value: !!(
|
||||
permissions.length === updatedPersmissions.length &&
|
||||
!data.hasFullAccess
|
||||
),
|
||||
},
|
||||
} as ChangeEvent<any>);
|
||||
|
||||
onChange({
|
||||
target: {
|
||||
name: "permissions",
|
||||
value: !value
|
||||
? data.permissions.concat([key])
|
||||
: data.permissions.filter(perm => perm !== key),
|
||||
value: updatedPersmissions,
|
||||
},
|
||||
} as any);
|
||||
} as ChangeEvent<any>);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
id: "Fbr4Vp",
|
||||
defaultMessage: "Permissions",
|
||||
description: "dialog header",
|
||||
})}
|
||||
/>
|
||||
<Box paddingX={9} paddingY={9} paddingBottom={0}>
|
||||
<Text as="p" variant="bodyEmp" size="large" marginBottom={7}>
|
||||
{intl.formatMessage(messages.title)}
|
||||
</Text>
|
||||
|
||||
{permissionsExceeded && (
|
||||
<>
|
||||
<CardContent style={{ paddingLeft: 0 }}>
|
||||
<Typography variant="body2">
|
||||
{intl.formatMessage({
|
||||
id: "MVU6ol",
|
||||
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>
|
||||
<Typography variant="body2">
|
||||
{intl.formatMessage({
|
||||
id: "6cS4Rd",
|
||||
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 userPermissions={user?.userPermissions ?? []} />
|
||||
)}
|
||||
|
||||
{!permissionsExceeded && (
|
||||
<>
|
||||
<CardContent>
|
||||
<Typography variant="body2">{description}</Typography>
|
||||
<ListItem
|
||||
role={undefined}
|
||||
dense
|
||||
button
|
||||
onClick={handleFullAccessChange}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Checkbox
|
||||
data-test-id="full-access"
|
||||
color="secondary"
|
||||
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
|
||||
color="secondary"
|
||||
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({
|
||||
id: "VmMDLN",
|
||||
defaultMessage:
|
||||
"This group is last source of that permission",
|
||||
description: "permission list item description",
|
||||
})
|
||||
: perm.code
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
))
|
||||
)}
|
||||
</CardContent>
|
||||
</>
|
||||
)}
|
||||
<Header
|
||||
disabled={disabled}
|
||||
description={description}
|
||||
fullAccessLabel={fullAccessLabel}
|
||||
hasFullAccess={data.hasFullAccess}
|
||||
onFullAccessChange={handleFullAccessChange}
|
||||
/>
|
||||
|
||||
<Box
|
||||
width="100%"
|
||||
borderBottomStyle="solid"
|
||||
borderBottomWidth={1}
|
||||
borderColor="neutralPlain"
|
||||
height={1}
|
||||
margin={0}
|
||||
/>
|
||||
|
||||
<PermissionList
|
||||
disabled={disabled}
|
||||
permissions={permissions}
|
||||
onPermissionChange={handlePermissionChange}
|
||||
selectedPermissions={data.permissions}
|
||||
/>
|
||||
|
||||
{!!errorMessage && (
|
||||
<>
|
||||
<hr className={classes.hr} />
|
||||
<CardContent>
|
||||
<Typography variant="body2" color="error">
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<Box
|
||||
width="100%"
|
||||
borderBottomStyle="solid"
|
||||
borderBottomWidth={1}
|
||||
borderColor="neutralPlain"
|
||||
height={1}
|
||||
marginTop={6}
|
||||
marginBottom={6}
|
||||
/>
|
||||
<Text as="p" variant="body" color="textCriticalSubdued">
|
||||
{errorMessage}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { Box, Checkbox, CheckedState, Text } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
|
||||
interface HeaderProps {
|
||||
description: string;
|
||||
fullAccessLabel: string;
|
||||
disabled: boolean;
|
||||
hasFullAccess: boolean;
|
||||
onFullAccessChange: (checked: CheckedState) => void;
|
||||
}
|
||||
|
||||
export const Header = ({
|
||||
description,
|
||||
disabled,
|
||||
hasFullAccess,
|
||||
fullAccessLabel,
|
||||
onFullAccessChange,
|
||||
}: HeaderProps) => (
|
||||
<Box>
|
||||
<Text variant="body">{description}</Text>
|
||||
<Box marginTop={6} marginBottom={7}>
|
||||
<Checkbox
|
||||
disabled={disabled}
|
||||
checked={hasFullAccess}
|
||||
onCheckedChange={onFullAccessChange}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Text variant="body">{fullAccessLabel}</Text>
|
||||
</Checkbox>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
|
@ -0,0 +1 @@
|
|||
export * from "./Header";
|
|
@ -0,0 +1,54 @@
|
|||
import { UserPermissionFragment } from "@dashboard/graphql";
|
||||
import { Box, List, Text } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { messages } from "../../messages";
|
||||
|
||||
interface PermissionsExceededProps {
|
||||
userPermissions: UserPermissionFragment[];
|
||||
}
|
||||
|
||||
export const PermissionsExceeded = ({
|
||||
userPermissions,
|
||||
}: PermissionsExceededProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text as="p" variant="body">
|
||||
{intl.formatMessage(messages.exeededPermission)}
|
||||
</Text>
|
||||
|
||||
<Box
|
||||
width="100%"
|
||||
borderBottomStyle="solid"
|
||||
borderBottomWidth={1}
|
||||
borderColor="neutralPlain"
|
||||
height={1}
|
||||
marginTop={6}
|
||||
marginBottom={6}
|
||||
/>
|
||||
|
||||
<Text variant="body">
|
||||
{intl.formatMessage(messages.availablePermissions)}
|
||||
</Text>
|
||||
|
||||
<List>
|
||||
{userPermissions.map(perm => (
|
||||
<List.Item
|
||||
key={perm.code}
|
||||
marginY={4}
|
||||
marginLeft={4}
|
||||
cursor="text"
|
||||
backgroundColor={{
|
||||
hover: "interactiveNeutralHighlightDefault",
|
||||
}}
|
||||
>
|
||||
<Text variant="caption" size="large">{`- ${perm.name}`}</Text>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from "./PermissionExeeded";
|
|
@ -0,0 +1,79 @@
|
|||
import Skeleton from "@dashboard/components/Skeleton";
|
||||
import { PermissionData } from "@dashboard/permissionGroups/components/PermissionGroupDetailsPage";
|
||||
import {
|
||||
Checkbox,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
} from "@material-ui/core";
|
||||
import { Box } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { messages } from "../../messages";
|
||||
import { hasPermissionSelected } from "../../utils";
|
||||
|
||||
interface PermissionListProps {
|
||||
permissions: PermissionData[];
|
||||
selectedPermissions: string[];
|
||||
disabled?: boolean;
|
||||
onPermissionChange: (key: string, value: boolean) => void;
|
||||
}
|
||||
|
||||
export const PermissionList = ({
|
||||
permissions,
|
||||
onPermissionChange,
|
||||
selectedPermissions,
|
||||
disabled,
|
||||
}: PermissionListProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
if (permissions === undefined) {
|
||||
return (
|
||||
<Box>
|
||||
<Skeleton />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{permissions.map(perm => (
|
||||
<ListItem
|
||||
key={perm.code}
|
||||
disabled={disabled || perm.disabled}
|
||||
role={undefined}
|
||||
dense
|
||||
button
|
||||
onClick={() =>
|
||||
onPermissionChange(
|
||||
perm.code,
|
||||
hasPermissionSelected(selectedPermissions, perm.code),
|
||||
)
|
||||
}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Checkbox
|
||||
color="secondary"
|
||||
edge="start"
|
||||
checked={hasPermissionSelected(selectedPermissions, perm.code)}
|
||||
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(messages.permissionListItemDescipription)
|
||||
: perm.code
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from "./PermissionList";
|
25
src/components/AccountPermissions/messages.ts
Normal file
25
src/components/AccountPermissions/messages.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
export const messages = defineMessages({
|
||||
title: {
|
||||
id: "Fbr4Vp",
|
||||
defaultMessage: "Permissions",
|
||||
description: "dialog header",
|
||||
},
|
||||
exeededPermission: {
|
||||
id: "MVU6ol",
|
||||
defaultMessage:
|
||||
"This groups permissions exceeds your own. You are able only to manage permissions that you have.",
|
||||
description: "exceeded permissions description",
|
||||
},
|
||||
availablePermissions: {
|
||||
id: "6cS4Rd",
|
||||
defaultMessage: "Available permissions",
|
||||
description: "card section description",
|
||||
},
|
||||
permissionListItemDescipription: {
|
||||
id: "VmMDLN",
|
||||
defaultMessage: "This group is last source of that permission",
|
||||
description: "permission list item description",
|
||||
},
|
||||
});
|
|
@ -1,25 +1,27 @@
|
|||
// @ts-strict-ignore
|
||||
import {
|
||||
PermissionGroupDetailsQuery,
|
||||
ShopInfoQuery,
|
||||
UserDetailsQuery,
|
||||
PermissionEnum,
|
||||
PermissionFragment,
|
||||
UserPermissionFragment,
|
||||
UserUserPermissionWithSourcePermissionGroupsFragment,
|
||||
} from "@dashboard/graphql";
|
||||
|
||||
export const getLastSourcesOfPermission = (
|
||||
groupId: string,
|
||||
userPermissions: PermissionGroupDetailsQuery["user"]["userPermissions"],
|
||||
userPermissions: Array<
|
||||
NonNullable<UserUserPermissionWithSourcePermissionGroupsFragment>
|
||||
>,
|
||||
) =>
|
||||
userPermissions
|
||||
.filter(
|
||||
perm =>
|
||||
perm.sourcePermissionGroups.length === 1 &&
|
||||
perm.sourcePermissionGroups[0].id === groupId,
|
||||
perm.sourcePermissionGroups?.length === 1 &&
|
||||
perm.sourcePermissionGroups[0]?.id === groupId,
|
||||
)
|
||||
.map(perm => perm.code);
|
||||
|
||||
export const getPermissionsComponentChoices = (
|
||||
userPermissions: UserDetailsQuery["me"]["userPermissions"],
|
||||
shopPermissions: ShopInfoQuery["shop"]["permissions"],
|
||||
userPermissions: UserPermissionFragment[],
|
||||
shopPermissions: PermissionFragment[],
|
||||
lastSourcesOfPermissionIds: string[],
|
||||
) => {
|
||||
const userCodes = userPermissions.map(p => p.code) || [];
|
||||
|
@ -31,3 +33,8 @@ export const getPermissionsComponentChoices = (
|
|||
lastSource: lastSourcesOfPermissionIds.includes(perm.code),
|
||||
}));
|
||||
};
|
||||
|
||||
export const hasPermissionSelected = (
|
||||
permissions: string[],
|
||||
permissionCode: PermissionEnum,
|
||||
) => permissions.filter(userPerm => userPerm === permissionCode).length === 1;
|
||||
|
|
|
@ -44,13 +44,17 @@ export const AppChannelProvider: React.FC = ({ children }) => {
|
|||
|
||||
const [isPickerActive, setPickerActive] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
!isValidChannel(selectedChannel, channelData?.channels) &&
|
||||
channelData?.channels?.length > 0
|
||||
) {
|
||||
setSelectedChannel(channelData.channels[0].id);
|
||||
const channels = user?.accessibleChannels ?? [];
|
||||
const isValid = isValidChannel(selectedChannel, channels);
|
||||
|
||||
if (!isValid && channels?.length > 0) {
|
||||
setSelectedChannel(channels[0].id);
|
||||
}
|
||||
}, [channelData]);
|
||||
|
||||
if (!isValid && selectedChannel !== "") {
|
||||
setSelectedChannel("");
|
||||
}
|
||||
}, [selectedChannel, setSelectedChannel, user]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setChannel(selectedChannel);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { useUser } from "@dashboard/auth";
|
||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
|
||||
|
@ -20,8 +21,9 @@ export const Root: React.FC<PropsWithChildren<TopNavProps>> = ({
|
|||
isAlignToRight = true,
|
||||
children,
|
||||
}) => {
|
||||
const { availableChannels, channel, isPickerActive, setChannel } =
|
||||
useAppChannel(false);
|
||||
const { channel, isPickerActive, setChannel } = useAppChannel(false);
|
||||
const user = useUser();
|
||||
const channels = user?.user?.accessibleChannels ?? [];
|
||||
|
||||
return (
|
||||
<TopNavWrapper withoutBorder={withoutBorder}>
|
||||
|
@ -37,9 +39,9 @@ export const Root: React.FC<PropsWithChildren<TopNavProps>> = ({
|
|||
height="100%"
|
||||
__flex={isAlignToRight ? "initial" : 1}
|
||||
>
|
||||
{isPickerActive && (
|
||||
{isPickerActive && channels.length > 0 && (
|
||||
<AppChannelSelect
|
||||
channels={availableChannels}
|
||||
channels={channels}
|
||||
selectedChannelId={channel?.id}
|
||||
onChannelSelect={setChannel}
|
||||
/>
|
||||
|
|
135
src/components/ChannelPermission/ChannelPermission.test.tsx
Normal file
135
src/components/ChannelPermission/ChannelPermission.test.tsx
Normal file
|
@ -0,0 +1,135 @@
|
|||
import {
|
||||
act,
|
||||
render,
|
||||
screen,
|
||||
waitForElementToBeRemoved,
|
||||
} from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React from "react";
|
||||
|
||||
import { ChannelPermission } from "./ChannelPermission";
|
||||
import { allChannels } from "./fixtures";
|
||||
|
||||
jest.mock("react-intl", () => ({
|
||||
useIntl: jest.fn(() => ({
|
||||
formatMessage: jest.fn(x => x.defaultMessage),
|
||||
})),
|
||||
defineMessages: jest.fn(x => x),
|
||||
FormattedMessage: ({ defaultMessage }: { defaultMessage: string }) => (
|
||||
<>{defaultMessage}</>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("ChannelPermission", () => {
|
||||
it("should render by default header and checkbox", () => {
|
||||
// Arrange & Act
|
||||
render(
|
||||
<ChannelPermission
|
||||
selectedChannels={[]}
|
||||
allChannels={allChannels}
|
||||
disabled={false}
|
||||
disabledSelectAllChannels={false}
|
||||
onChannelChange={jest.fn}
|
||||
onHasAllChannelsChange={jest.fn}
|
||||
hasAllChannels={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText(/channels permissions/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole("checkbox")).toBeInTheDocument();
|
||||
expect(screen.getByRole("checkbox")).toBeChecked();
|
||||
expect(
|
||||
screen.getByText(/allow access to orders of all channels/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render channels select when access to all channels checkbox unchecked", () => {
|
||||
// Arrange & Act
|
||||
render(
|
||||
<ChannelPermission
|
||||
selectedChannels={[]}
|
||||
allChannels={allChannels}
|
||||
disabled={false}
|
||||
disabledSelectAllChannels={false}
|
||||
onChannelChange={jest.fn}
|
||||
onHasAllChannelsChange={jest.fn}
|
||||
hasAllChannels={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole("checkbox")).not.toBeChecked();
|
||||
expect(screen.getByRole("listbox")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render restricted checkbox disabled", () => {
|
||||
// Arrange & Act
|
||||
const mockonHasAllChannelsChange = jest.fn();
|
||||
|
||||
render(
|
||||
<ChannelPermission
|
||||
selectedChannels={[]}
|
||||
allChannels={allChannels}
|
||||
disabled={true}
|
||||
disabledSelectAllChannels={false}
|
||||
onChannelChange={jest.fn}
|
||||
onHasAllChannelsChange={mockonHasAllChannelsChange}
|
||||
hasAllChannels={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Act
|
||||
userEvent.click(screen.getByRole("checkbox"));
|
||||
|
||||
// Assert
|
||||
expect(mockonHasAllChannelsChange).not.toHaveBeenCalled();
|
||||
expect(screen.getByRole("checkbox")).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should render selected channels when has restricted channels selected", () => {
|
||||
// Arrange & Act
|
||||
const selectedChannels = [allChannels[1]];
|
||||
render(
|
||||
<ChannelPermission
|
||||
allChannels={allChannels}
|
||||
selectedChannels={selectedChannels.map(chan => chan.id)}
|
||||
disabled={false}
|
||||
disabledSelectAllChannels={false}
|
||||
onChannelChange={jest.fn}
|
||||
onHasAllChannelsChange={jest.fn}
|
||||
hasAllChannels={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText(selectedChannels[0].name)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should allow to remove selected channels", async () => {
|
||||
// Arrange & Act
|
||||
const selectedChannels = [allChannels[1]];
|
||||
render(
|
||||
<ChannelPermission
|
||||
allChannels={allChannels}
|
||||
selectedChannels={selectedChannels.map(x => x.id)}
|
||||
disabled={false}
|
||||
disabledSelectAllChannels={false}
|
||||
onChannelChange={jest.fn}
|
||||
onHasAllChannelsChange={jest.fn}
|
||||
hasAllChannels={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText(selectedChannels[0].name)).toBeInTheDocument();
|
||||
|
||||
// Act
|
||||
act(() => {
|
||||
userEvent.click(screen.getByText(/✕/i));
|
||||
});
|
||||
|
||||
// Assert
|
||||
waitForElementToBeRemoved(screen.getByText(selectedChannels[0].name));
|
||||
});
|
||||
});
|
83
src/components/ChannelPermission/ChannelPermission.tsx
Normal file
83
src/components/ChannelPermission/ChannelPermission.tsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { ChannelFragment } from "@dashboard/graphql";
|
||||
import { FormChange } from "@dashboard/hooks/useForm";
|
||||
import { mapNodeToChoice } from "@dashboard/utils/maps";
|
||||
import { Box, Checkbox, Multiselect, Text } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { messages } from "./messages";
|
||||
|
||||
interface ChannelPermissionProps {
|
||||
selectedChannels: string[];
|
||||
allChannels: ChannelFragment[];
|
||||
description?: string;
|
||||
hasAllChannels: boolean;
|
||||
disabled: boolean;
|
||||
disabledSelectAllChannels: boolean;
|
||||
onChannelChange: FormChange;
|
||||
onHasAllChannelsChange: () => void;
|
||||
}
|
||||
|
||||
export const ChannelPermission = ({
|
||||
description,
|
||||
disabled,
|
||||
onHasAllChannelsChange,
|
||||
onChannelChange,
|
||||
allChannels,
|
||||
selectedChannels,
|
||||
hasAllChannels,
|
||||
disabledSelectAllChannels,
|
||||
}: ChannelPermissionProps) => {
|
||||
const intl = useIntl();
|
||||
const channelsChoices = mapNodeToChoice(allChannels);
|
||||
|
||||
return (
|
||||
<Box height="100%">
|
||||
<Text as="p" size="large" marginBottom={5}>
|
||||
{intl.formatMessage(messages.title)}
|
||||
</Text>
|
||||
|
||||
<Box height="100%">
|
||||
{description && (
|
||||
<Text as="p" variant="body" size="small" marginBottom={5}>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Box __width="fit-content">
|
||||
<Checkbox
|
||||
disabled={disabled || disabledSelectAllChannels}
|
||||
checked={hasAllChannels}
|
||||
onCheckedChange={onHasAllChannelsChange}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Text variant="body">
|
||||
{intl.formatMessage(messages.allowAllChannels)}
|
||||
</Text>
|
||||
</Checkbox>
|
||||
</Box>
|
||||
|
||||
{!hasAllChannels && (
|
||||
<Box marginTop={5}>
|
||||
<Multiselect
|
||||
size="small"
|
||||
data-test-id="channels"
|
||||
disabled={disabled}
|
||||
options={channelsChoices}
|
||||
label={intl.formatMessage(messages.selectChannels)}
|
||||
value={channelsChoices.filter(channel =>
|
||||
selectedChannels.includes(channel.value),
|
||||
)}
|
||||
placeholder={intl.formatMessage(messages.searchChannels)}
|
||||
onChange={values => {
|
||||
onChannelChange({
|
||||
target: { name: "channels", value: values.map(v => v.value) },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
72
src/components/ChannelPermission/fixtures.ts
Normal file
72
src/components/ChannelPermission/fixtures.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { AllocationStrategyEnum, ChannelFragment } from "@dashboard/graphql";
|
||||
|
||||
export const allChannels: ChannelFragment[] = [
|
||||
{
|
||||
__typename: "Channel",
|
||||
id: "Q2hhbm5lbDoy",
|
||||
isActive: true,
|
||||
name: "Channel-1",
|
||||
slug: "channel-1",
|
||||
currencyCode: "PLN",
|
||||
defaultCountry: {
|
||||
__typename: "CountryDisplay",
|
||||
code: "PL",
|
||||
country: "Poland",
|
||||
},
|
||||
stockSettings: {
|
||||
__typename: "StockSettings",
|
||||
allocationStrategy: AllocationStrategyEnum.PRIORITIZE_HIGH_STOCK,
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: "Channel",
|
||||
id: "Q2hhbm5lbDoz",
|
||||
isActive: true,
|
||||
name: "Channel-2",
|
||||
slug: "channel-2",
|
||||
currencyCode: "PLN",
|
||||
defaultCountry: {
|
||||
__typename: "CountryDisplay",
|
||||
code: "PL",
|
||||
country: "Poland",
|
||||
},
|
||||
stockSettings: {
|
||||
__typename: "StockSettings",
|
||||
allocationStrategy: AllocationStrategyEnum.PRIORITIZE_HIGH_STOCK,
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: "Channel",
|
||||
id: "Q2hhbm5lbDou",
|
||||
isActive: true,
|
||||
name: "Channel-3",
|
||||
slug: "channel-3",
|
||||
currencyCode: "PLN",
|
||||
defaultCountry: {
|
||||
__typename: "CountryDisplay",
|
||||
code: "PL",
|
||||
country: "Poland",
|
||||
},
|
||||
stockSettings: {
|
||||
__typename: "StockSettings",
|
||||
allocationStrategy: AllocationStrategyEnum.PRIORITIZE_HIGH_STOCK,
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: "Channel",
|
||||
id: "Q2hhbm5lbDox",
|
||||
isActive: true,
|
||||
name: "Channel-4",
|
||||
slug: "default-channel",
|
||||
currencyCode: "USD",
|
||||
defaultCountry: {
|
||||
__typename: "CountryDisplay",
|
||||
code: "US",
|
||||
country: "United States of America",
|
||||
},
|
||||
stockSettings: {
|
||||
__typename: "StockSettings",
|
||||
allocationStrategy: AllocationStrategyEnum.PRIORITIZE_HIGH_STOCK,
|
||||
},
|
||||
},
|
||||
];
|
1
src/components/ChannelPermission/index.ts
Normal file
1
src/components/ChannelPermission/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./ChannelPermission";
|
20
src/components/ChannelPermission/messages.ts
Normal file
20
src/components/ChannelPermission/messages.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
export const messages = defineMessages({
|
||||
title: {
|
||||
defaultMessage: "Channels permissions",
|
||||
id: "vz3yxp",
|
||||
},
|
||||
allowAllChannels: {
|
||||
defaultMessage: "Allow access to orders of all channels",
|
||||
id: "9FGTOt",
|
||||
},
|
||||
selectChannels: {
|
||||
defaultMessage: "Select visible order channels",
|
||||
id: "vprU7C",
|
||||
},
|
||||
searchChannels: {
|
||||
defaultMessage: "Search channels",
|
||||
id: "0HBlkO",
|
||||
},
|
||||
});
|
|
@ -27,8 +27,7 @@ export const shopInfo = gql`
|
|||
name
|
||||
trackInventoryByDefault
|
||||
permissions {
|
||||
code
|
||||
name
|
||||
...Permission
|
||||
}
|
||||
version
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ const user = {
|
|||
lastName: "Newton",
|
||||
note: null,
|
||||
userPermissions: staffMember.userPermissions,
|
||||
restrictedAccessToChannels: false,
|
||||
accessibleChannels: [],
|
||||
};
|
||||
|
||||
const versions = {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// @ts-strict-ignore
|
||||
import {
|
||||
AllocationStrategyEnum,
|
||||
ChannelFragment,
|
||||
PermissionEnum,
|
||||
ShopInfoQuery,
|
||||
ShopLimitFragment,
|
||||
|
@ -355,6 +357,43 @@ export const sortPageProps: SortPage<string> = {
|
|||
},
|
||||
};
|
||||
|
||||
export const channels: ChannelFragment[] = [
|
||||
{
|
||||
id: "Q2hhbm5lbDoyMjQ0",
|
||||
isActive: true,
|
||||
name: "Channel-PLN",
|
||||
slug: "channel-pln",
|
||||
currencyCode: "PLN",
|
||||
defaultCountry: {
|
||||
code: "US",
|
||||
country: "United States of America",
|
||||
__typename: "CountryDisplay",
|
||||
},
|
||||
stockSettings: {
|
||||
allocationStrategy: AllocationStrategyEnum.PRIORITIZE_HIGH_STOCK,
|
||||
__typename: "StockSettings",
|
||||
},
|
||||
__typename: "Channel",
|
||||
},
|
||||
{
|
||||
id: "Q2hhbm5lbDoyMjQz",
|
||||
isActive: true,
|
||||
name: "Channel-USD",
|
||||
slug: "default-channel",
|
||||
currencyCode: "USD",
|
||||
defaultCountry: {
|
||||
code: "US",
|
||||
country: "United States of America",
|
||||
__typename: "CountryDisplay",
|
||||
},
|
||||
stockSettings: {
|
||||
allocationStrategy: AllocationStrategyEnum.PRIORITIZE_HIGH_STOCK,
|
||||
__typename: "StockSettings",
|
||||
},
|
||||
__typename: "Channel",
|
||||
},
|
||||
];
|
||||
|
||||
export const permissions: ShopInfoQuery["shop"]["permissions"] = [
|
||||
{
|
||||
code: PermissionEnum.MANAGE_DISCOUNTS,
|
||||
|
|
|
@ -7,6 +7,15 @@ export const userUserPermissionFragment = gql`
|
|||
}
|
||||
`;
|
||||
|
||||
export const userUserPermissionWithSourcePermissionGroupsFragment = gql`
|
||||
fragment UserUserPermissionWithSourcePermissionGroups on UserPermission {
|
||||
...UserPermission
|
||||
sourcePermissionGroups(userId: $userId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const fragmentUser = gql`
|
||||
fragment User on User {
|
||||
id
|
||||
|
@ -17,9 +26,13 @@ export const fragmentUser = gql`
|
|||
userPermissions {
|
||||
...UserPermission
|
||||
}
|
||||
avatar {
|
||||
avatar(size: 128) {
|
||||
url
|
||||
}
|
||||
accessibleChannels {
|
||||
...Channel
|
||||
}
|
||||
restrictedAccessToChannels
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -32,6 +32,10 @@ export const permissionGroupMember = gql`
|
|||
export const permissionGroupDetailsFragment = gql`
|
||||
fragment PermissionGroupDetails on Group {
|
||||
...PermissionGroup
|
||||
restrictedAccessToChannels
|
||||
accessibleChannels {
|
||||
...Channel
|
||||
}
|
||||
permissions {
|
||||
...Permission
|
||||
}
|
||||
|
|
|
@ -171,6 +171,30 @@ export const UserPermissionFragmentDoc = gql`
|
|||
name
|
||||
}
|
||||
`;
|
||||
export const UserUserPermissionWithSourcePermissionGroupsFragmentDoc = gql`
|
||||
fragment UserUserPermissionWithSourcePermissionGroups on UserPermission {
|
||||
...UserPermission
|
||||
sourcePermissionGroups(userId: $userId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
${UserPermissionFragmentDoc}`;
|
||||
export const ChannelFragmentDoc = gql`
|
||||
fragment Channel on Channel {
|
||||
id
|
||||
isActive
|
||||
name
|
||||
slug
|
||||
currencyCode
|
||||
defaultCountry {
|
||||
code
|
||||
country
|
||||
}
|
||||
stockSettings {
|
||||
allocationStrategy
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const UserFragmentDoc = gql`
|
||||
fragment User on User {
|
||||
id
|
||||
|
@ -181,11 +205,16 @@ export const UserFragmentDoc = gql`
|
|||
userPermissions {
|
||||
...UserPermission
|
||||
}
|
||||
avatar {
|
||||
avatar(size: 128) {
|
||||
url
|
||||
}
|
||||
accessibleChannels {
|
||||
...Channel
|
||||
}
|
||||
restrictedAccessToChannels
|
||||
}
|
||||
${UserPermissionFragmentDoc}`;
|
||||
${UserPermissionFragmentDoc}
|
||||
${ChannelFragmentDoc}`;
|
||||
export const CategoryFragmentDoc = gql`
|
||||
fragment Category on Category {
|
||||
id
|
||||
|
@ -223,22 +252,6 @@ export const ChannelErrorFragmentDoc = gql`
|
|||
message
|
||||
}
|
||||
`;
|
||||
export const ChannelFragmentDoc = gql`
|
||||
fragment Channel on Channel {
|
||||
id
|
||||
isActive
|
||||
name
|
||||
slug
|
||||
currencyCode
|
||||
defaultCountry {
|
||||
code
|
||||
country
|
||||
}
|
||||
stockSettings {
|
||||
allocationStrategy
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const WarehouseFragmentDoc = gql`
|
||||
fragment Warehouse on Warehouse {
|
||||
id
|
||||
|
@ -2117,6 +2130,10 @@ export const PermissionGroupMemberFragmentDoc = gql`
|
|||
export const PermissionGroupDetailsFragmentDoc = gql`
|
||||
fragment PermissionGroupDetails on Group {
|
||||
...PermissionGroup
|
||||
restrictedAccessToChannels
|
||||
accessibleChannels {
|
||||
...Channel
|
||||
}
|
||||
permissions {
|
||||
...Permission
|
||||
}
|
||||
|
@ -2125,6 +2142,7 @@ export const PermissionGroupDetailsFragmentDoc = gql`
|
|||
}
|
||||
}
|
||||
${PermissionGroupFragmentDoc}
|
||||
${ChannelFragmentDoc}
|
||||
${PermissionFragmentDoc}
|
||||
${PermissionGroupMemberFragmentDoc}`;
|
||||
export const PluginConfigurationBaseFragmentDoc = gql`
|
||||
|
@ -6138,14 +6156,14 @@ export const ShopInfoDocument = gql`
|
|||
name
|
||||
trackInventoryByDefault
|
||||
permissions {
|
||||
code
|
||||
name
|
||||
...Permission
|
||||
}
|
||||
version
|
||||
}
|
||||
}
|
||||
${CountryWithCodeFragmentDoc}
|
||||
${LanguageFragmentDoc}`;
|
||||
${LanguageFragmentDoc}
|
||||
${PermissionFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useShopInfoQuery__
|
||||
|
@ -8618,20 +8636,20 @@ export type CustomerGiftCardListQueryHookResult = ReturnType<typeof useCustomerG
|
|||
export type CustomerGiftCardListLazyQueryHookResult = ReturnType<typeof useCustomerGiftCardListLazyQuery>;
|
||||
export type CustomerGiftCardListQueryResult = Apollo.QueryResult<Types.CustomerGiftCardListQuery, Types.CustomerGiftCardListQueryVariables>;
|
||||
export const HomeDocument = gql`
|
||||
query Home($channel: String!, $datePeriod: DateRangeInput!, $PERMISSION_MANAGE_PRODUCTS: Boolean!, $PERMISSION_MANAGE_ORDERS: Boolean!) {
|
||||
salesToday: ordersTotal(period: TODAY, channel: $channel) @include(if: $PERMISSION_MANAGE_ORDERS) {
|
||||
query Home($channel: String!, $datePeriod: DateRangeInput!, $hasPermissionToManageProducts: Boolean!, $hasPermissionToManageOrders: Boolean!) {
|
||||
salesToday: ordersTotal(period: TODAY, channel: $channel) @include(if: $hasPermissionToManageOrders) {
|
||||
gross {
|
||||
amount
|
||||
currency
|
||||
}
|
||||
}
|
||||
ordersToday: orders(filter: {created: $datePeriod}, channel: $channel) @include(if: $PERMISSION_MANAGE_ORDERS) {
|
||||
ordersToday: orders(filter: {created: $datePeriod}, channel: $channel) @include(if: $hasPermissionToManageOrders) {
|
||||
totalCount
|
||||
}
|
||||
ordersToFulfill: orders(filter: {status: READY_TO_FULFILL}, channel: $channel) @include(if: $PERMISSION_MANAGE_ORDERS) {
|
||||
ordersToFulfill: orders(filter: {status: READY_TO_FULFILL}, channel: $channel) @include(if: $hasPermissionToManageOrders) {
|
||||
totalCount
|
||||
}
|
||||
ordersToCapture: orders(filter: {status: READY_TO_CAPTURE}, channel: $channel) @include(if: $PERMISSION_MANAGE_ORDERS) {
|
||||
ordersToCapture: orders(filter: {status: READY_TO_CAPTURE}, channel: $channel) @include(if: $hasPermissionToManageOrders) {
|
||||
totalCount
|
||||
}
|
||||
productsOutOfStock: products(
|
||||
|
@ -8640,7 +8658,7 @@ export const HomeDocument = gql`
|
|||
) {
|
||||
totalCount
|
||||
}
|
||||
productTopToday: reportProductSales(period: TODAY, first: 5, channel: $channel) @include(if: $PERMISSION_MANAGE_PRODUCTS) {
|
||||
productTopToday: reportProductSales(period: TODAY, first: 5, channel: $channel) @include(if: $hasPermissionToManageProducts) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
|
@ -8667,7 +8685,7 @@ export const HomeDocument = gql`
|
|||
}
|
||||
}
|
||||
}
|
||||
activities: homepageEvents(last: 10) @include(if: $PERMISSION_MANAGE_ORDERS) {
|
||||
activities: homepageEvents(last: 10) @include(if: $hasPermissionToManageOrders) {
|
||||
edges {
|
||||
node {
|
||||
amount
|
||||
|
@ -8705,8 +8723,8 @@ export const HomeDocument = gql`
|
|||
* variables: {
|
||||
* channel: // value for 'channel'
|
||||
* datePeriod: // value for 'datePeriod'
|
||||
* PERMISSION_MANAGE_PRODUCTS: // value for 'PERMISSION_MANAGE_PRODUCTS'
|
||||
* PERMISSION_MANAGE_ORDERS: // value for 'PERMISSION_MANAGE_ORDERS'
|
||||
* hasPermissionToManageProducts: // value for 'hasPermissionToManageProducts'
|
||||
* hasPermissionToManageOrders: // value for 'hasPermissionToManageOrders'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
|
|
@ -8368,7 +8368,7 @@ export type AvailableExternalAuthenticationsQuery = { __typename: 'Query', shop:
|
|||
export type UserDetailsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type UserDetailsQuery = { __typename: 'Query', me: { __typename: 'User', id: string, email: string, firstName: string, lastName: string, isStaff: boolean, userPermissions: Array<{ __typename: 'UserPermission', code: PermissionEnum, name: string }> | null, avatar: { __typename: 'Image', url: string } | null } | null };
|
||||
export type UserDetailsQuery = { __typename: 'Query', me: { __typename: 'User', id: string, email: string, firstName: string, lastName: string, isStaff: boolean, restrictedAccessToChannels: boolean, userPermissions: Array<{ __typename: 'UserPermission', code: PermissionEnum, name: string }> | null, avatar: { __typename: 'Image', url: string } | null, accessibleChannels: Array<{ __typename: 'Channel', id: string, isActive: boolean, name: string, slug: string, currencyCode: string, defaultCountry: { __typename: 'CountryDisplay', code: string, country: string }, stockSettings: { __typename: 'StockSettings', allocationStrategy: AllocationStrategyEnum } }> | null } | null };
|
||||
|
||||
export type CategoryDeleteMutationVariables = Exact<{
|
||||
id: Scalars['ID'];
|
||||
|
@ -9083,7 +9083,9 @@ export type AvailableAttributeFragment = { __typename: 'Attribute', id: string,
|
|||
|
||||
export type UserPermissionFragment = { __typename: 'UserPermission', code: PermissionEnum, name: string };
|
||||
|
||||
export type UserFragment = { __typename: 'User', id: string, email: string, firstName: string, lastName: string, isStaff: boolean, userPermissions: Array<{ __typename: 'UserPermission', code: PermissionEnum, name: string }> | null, avatar: { __typename: 'Image', url: string } | null };
|
||||
export type UserUserPermissionWithSourcePermissionGroupsFragment = { __typename: 'UserPermission', code: PermissionEnum, name: string, sourcePermissionGroups: Array<{ __typename: 'Group', id: string }> | null };
|
||||
|
||||
export type UserFragment = { __typename: 'User', id: string, email: string, firstName: string, lastName: string, isStaff: boolean, restrictedAccessToChannels: boolean, userPermissions: Array<{ __typename: 'UserPermission', code: PermissionEnum, name: string }> | null, avatar: { __typename: 'Image', url: string } | null, accessibleChannels: Array<{ __typename: 'Channel', id: string, isActive: boolean, name: string, slug: string, currencyCode: string, defaultCountry: { __typename: 'CountryDisplay', code: string, country: string }, stockSettings: { __typename: 'StockSettings', allocationStrategy: AllocationStrategyEnum } }> | null };
|
||||
|
||||
export type UserBaseFragment = { __typename: 'User', id: string, firstName: string, lastName: string };
|
||||
|
||||
|
@ -9405,7 +9407,7 @@ export type PermissionFragment = { __typename: 'Permission', code: PermissionEnu
|
|||
|
||||
export type PermissionGroupMemberFragment = { __typename: 'User', id: string, email: string, firstName: string, isActive: boolean, lastName: string, avatar: { __typename: 'Image', url: string } | null };
|
||||
|
||||
export type PermissionGroupDetailsFragment = { __typename: 'Group', id: string, name: string, userCanManage: boolean, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, users: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, email: string, isActive: boolean, avatar: { __typename: 'Image', url: string } | null }> | null };
|
||||
export type PermissionGroupDetailsFragment = { __typename: 'Group', restrictedAccessToChannels: boolean, id: string, name: string, userCanManage: boolean, accessibleChannels: Array<{ __typename: 'Channel', id: string, isActive: boolean, name: string, slug: string, currencyCode: string, defaultCountry: { __typename: 'CountryDisplay', code: string, country: string }, stockSettings: { __typename: 'StockSettings', allocationStrategy: AllocationStrategyEnum } }> | null, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, users: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, email: string, isActive: boolean, avatar: { __typename: 'Image', url: string } | null }> | null };
|
||||
|
||||
export type ConfigurationItemFragment = { __typename: 'ConfigurationItem', name: string, value: string | null, type: ConfigurationTypeFieldEnum | null, helpText: string | null, label: string | null };
|
||||
|
||||
|
@ -9691,8 +9693,8 @@ export type CustomerGiftCardListQuery = { __typename: 'Query', giftCards: { __ty
|
|||
export type HomeQueryVariables = Exact<{
|
||||
channel: Scalars['String'];
|
||||
datePeriod: DateRangeInput;
|
||||
PERMISSION_MANAGE_PRODUCTS: Scalars['Boolean'];
|
||||
PERMISSION_MANAGE_ORDERS: Scalars['Boolean'];
|
||||
hasPermissionToManageProducts: Scalars['Boolean'];
|
||||
hasPermissionToManageOrders: Scalars['Boolean'];
|
||||
}>;
|
||||
|
||||
|
||||
|
@ -10312,7 +10314,7 @@ export type PermissionGroupCreateMutationVariables = Exact<{
|
|||
}>;
|
||||
|
||||
|
||||
export type PermissionGroupCreateMutation = { __typename: 'Mutation', permissionGroupCreate: { __typename: 'PermissionGroupCreate', errors: Array<{ __typename: 'PermissionGroupError', code: PermissionGroupErrorCode, field: string | null, message: string | null }>, group: { __typename: 'Group', id: string, name: string, userCanManage: boolean, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, users: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, email: string, isActive: boolean, avatar: { __typename: 'Image', url: string } | null }> | null } | null } | null };
|
||||
export type PermissionGroupCreateMutation = { __typename: 'Mutation', permissionGroupCreate: { __typename: 'PermissionGroupCreate', errors: Array<{ __typename: 'PermissionGroupError', code: PermissionGroupErrorCode, field: string | null, message: string | null }>, group: { __typename: 'Group', restrictedAccessToChannels: boolean, id: string, name: string, userCanManage: boolean, accessibleChannels: Array<{ __typename: 'Channel', id: string, isActive: boolean, name: string, slug: string, currencyCode: string, defaultCountry: { __typename: 'CountryDisplay', code: string, country: string }, stockSettings: { __typename: 'StockSettings', allocationStrategy: AllocationStrategyEnum } }> | null, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, users: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, email: string, isActive: boolean, avatar: { __typename: 'Image', url: string } | null }> | null } | null } | null };
|
||||
|
||||
export type PermissionGroupUpdateMutationVariables = Exact<{
|
||||
id: Scalars['ID'];
|
||||
|
@ -10320,7 +10322,7 @@ export type PermissionGroupUpdateMutationVariables = Exact<{
|
|||
}>;
|
||||
|
||||
|
||||
export type PermissionGroupUpdateMutation = { __typename: 'Mutation', permissionGroupUpdate: { __typename: 'PermissionGroupUpdate', errors: Array<{ __typename: 'PermissionGroupError', code: PermissionGroupErrorCode, field: string | null, message: string | null }>, group: { __typename: 'Group', id: string, name: string, userCanManage: boolean, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, users: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, email: string, isActive: boolean, avatar: { __typename: 'Image', url: string } | null }> | null } | null } | null };
|
||||
export type PermissionGroupUpdateMutation = { __typename: 'Mutation', permissionGroupUpdate: { __typename: 'PermissionGroupUpdate', errors: Array<{ __typename: 'PermissionGroupError', code: PermissionGroupErrorCode, field: string | null, message: string | null }>, group: { __typename: 'Group', restrictedAccessToChannels: boolean, id: string, name: string, userCanManage: boolean, accessibleChannels: Array<{ __typename: 'Channel', id: string, isActive: boolean, name: string, slug: string, currencyCode: string, defaultCountry: { __typename: 'CountryDisplay', code: string, country: string }, stockSettings: { __typename: 'StockSettings', allocationStrategy: AllocationStrategyEnum } }> | null, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, users: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, email: string, isActive: boolean, avatar: { __typename: 'Image', url: string } | null }> | null } | null } | null };
|
||||
|
||||
export type PermissionGroupListQueryVariables = Exact<{
|
||||
after?: InputMaybe<Scalars['String']>;
|
||||
|
@ -10340,7 +10342,7 @@ export type PermissionGroupDetailsQueryVariables = Exact<{
|
|||
}>;
|
||||
|
||||
|
||||
export type PermissionGroupDetailsQuery = { __typename: 'Query', permissionGroup: { __typename: 'Group', id: string, name: string, userCanManage: boolean, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, users: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, email: string, isActive: boolean, avatar: { __typename: 'Image', url: string } | null }> | null } | null, user: { __typename: 'User', editableGroups: Array<{ __typename: 'Group', id: string }> | null, userPermissions: Array<{ __typename: 'UserPermission', code: PermissionEnum, sourcePermissionGroups: Array<{ __typename: 'Group', id: string }> | null }> | null } | null };
|
||||
export type PermissionGroupDetailsQuery = { __typename: 'Query', permissionGroup: { __typename: 'Group', restrictedAccessToChannels: boolean, id: string, name: string, userCanManage: boolean, accessibleChannels: Array<{ __typename: 'Channel', id: string, isActive: boolean, name: string, slug: string, currencyCode: string, defaultCountry: { __typename: 'CountryDisplay', code: string, country: string }, stockSettings: { __typename: 'StockSettings', allocationStrategy: AllocationStrategyEnum } }> | null, permissions: Array<{ __typename: 'Permission', code: PermissionEnum, name: string }> | null, users: Array<{ __typename: 'User', id: string, firstName: string, lastName: string, email: string, isActive: boolean, avatar: { __typename: 'Image', url: string } | null }> | null } | null, user: { __typename: 'User', editableGroups: Array<{ __typename: 'Group', id: string }> | null, userPermissions: Array<{ __typename: 'UserPermission', code: PermissionEnum, sourcePermissionGroups: Array<{ __typename: 'Group', id: string }> | null }> | null } | null };
|
||||
|
||||
export type PluginUpdateMutationVariables = Exact<{
|
||||
channelId?: InputMaybe<Scalars['ID']>;
|
||||
|
|
|
@ -4,30 +4,30 @@ export const home = gql`
|
|||
query Home(
|
||||
$channel: String!
|
||||
$datePeriod: DateRangeInput!
|
||||
$PERMISSION_MANAGE_PRODUCTS: Boolean!
|
||||
$PERMISSION_MANAGE_ORDERS: Boolean!
|
||||
$hasPermissionToManageProducts: Boolean!
|
||||
$hasPermissionToManageOrders: Boolean!
|
||||
) {
|
||||
salesToday: ordersTotal(period: TODAY, channel: $channel)
|
||||
@include(if: $PERMISSION_MANAGE_ORDERS) {
|
||||
@include(if: $hasPermissionToManageOrders) {
|
||||
gross {
|
||||
amount
|
||||
currency
|
||||
}
|
||||
}
|
||||
ordersToday: orders(filter: { created: $datePeriod }, channel: $channel)
|
||||
@include(if: $PERMISSION_MANAGE_ORDERS) {
|
||||
@include(if: $hasPermissionToManageOrders) {
|
||||
totalCount
|
||||
}
|
||||
ordersToFulfill: orders(
|
||||
filter: { status: READY_TO_FULFILL }
|
||||
channel: $channel
|
||||
) @include(if: $PERMISSION_MANAGE_ORDERS) {
|
||||
) @include(if: $hasPermissionToManageOrders) {
|
||||
totalCount
|
||||
}
|
||||
ordersToCapture: orders(
|
||||
filter: { status: READY_TO_CAPTURE }
|
||||
channel: $channel
|
||||
) @include(if: $PERMISSION_MANAGE_ORDERS) {
|
||||
) @include(if: $hasPermissionToManageOrders) {
|
||||
totalCount
|
||||
}
|
||||
productsOutOfStock: products(
|
||||
|
@ -40,7 +40,7 @@ export const home = gql`
|
|||
period: TODAY
|
||||
first: 5
|
||||
channel: $channel
|
||||
) @include(if: $PERMISSION_MANAGE_PRODUCTS) {
|
||||
) @include(if: $hasPermissionToManageProducts) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
|
@ -68,7 +68,7 @@ export const home = gql`
|
|||
}
|
||||
}
|
||||
activities: homepageEvents(last: 10)
|
||||
@include(if: $PERMISSION_MANAGE_ORDERS) {
|
||||
@include(if: $hasPermissionToManageOrders) {
|
||||
edges {
|
||||
node {
|
||||
amount
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
import { useUser } from "@dashboard/auth";
|
||||
import { channelsListUrl } from "@dashboard/channels/urls";
|
||||
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||
import { hasPermissions } from "@dashboard/components/RequirePermissions";
|
||||
import {
|
||||
OrderStatusFilter,
|
||||
PermissionEnum,
|
||||
StockAvailability,
|
||||
useHomeQuery,
|
||||
} from "@dashboard/graphql";
|
||||
|
@ -21,10 +23,21 @@ const HomeSection = () => {
|
|||
|
||||
const noChannel = !channel && typeof channel !== "undefined";
|
||||
|
||||
const userPermissions = user?.userPermissions || [];
|
||||
|
||||
const { data } = useHomeQuery({
|
||||
displayLoader: true,
|
||||
skip: noChannel,
|
||||
variables: { channel: channel?.slug, datePeriod: getDatePeriod(1) },
|
||||
variables: {
|
||||
channel: channel?.slug,
|
||||
datePeriod: getDatePeriod(1),
|
||||
hasPermissionToManageOrders: hasPermissions(userPermissions, [
|
||||
PermissionEnum.MANAGE_ORDERS,
|
||||
]),
|
||||
hasPermissionToManageProducts: hasPermissions(userPermissions, [
|
||||
PermissionEnum.MANAGE_PRODUCTS,
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { useUserAccessibleChannels } from "@dashboard/auth/hooks/useUserAccessibleChannels";
|
||||
import { TopNav } from "@dashboard/components/AppLayout";
|
||||
import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo";
|
||||
import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect";
|
||||
|
@ -5,7 +6,7 @@ import { RefreshLimitsQuery } from "@dashboard/graphql";
|
|||
import { sectionNames } from "@dashboard/intl";
|
||||
import { FilterPresetsProps } from "@dashboard/types";
|
||||
import { hasLimits, isLimitReached } from "@dashboard/utils/limits";
|
||||
import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui/next";
|
||||
import { Box, Button, ChevronRightIcon, Tooltip } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
|
@ -33,6 +34,8 @@ export const OrderDraftListHeader = ({
|
|||
onAdd,
|
||||
}: OrderDraftListHeaderProps) => {
|
||||
const intl = useIntl();
|
||||
const userAccessibleChannels = useUserAccessibleChannels();
|
||||
const hasAccessibleChannels = userAccessibleChannels.length > 0;
|
||||
const limitsReached = isLimitReached(limits, "orders");
|
||||
|
||||
return (
|
||||
|
@ -71,18 +74,30 @@ export const OrderDraftListHeader = ({
|
|||
/>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={disabled || limitsReached}
|
||||
onClick={onAdd}
|
||||
data-test-id="create-draft-order-button"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="LshEVn"
|
||||
defaultMessage="Create order"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
<Tooltip>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={disabled || limitsReached || !hasAccessibleChannels}
|
||||
onClick={onAdd}
|
||||
data-test-id="create-draft-order-button"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="LshEVn"
|
||||
defaultMessage="Create order"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
{!hasAccessibleChannels && (
|
||||
<FormattedMessage
|
||||
defaultMessage="You don't have access to any channels"
|
||||
id="grkY2V"
|
||||
/>
|
||||
)}
|
||||
</Tooltip.Content>
|
||||
</Tooltip>
|
||||
|
||||
{hasLimits(limits, "orders") && (
|
||||
<LimitsInfo
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
mapToMenuItems,
|
||||
useExtensions,
|
||||
} from "@dashboard/apps/hooks/useExtensions";
|
||||
import { useUserAccessibleChannels } from "@dashboard/auth/hooks/useUserAccessibleChannels";
|
||||
import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo";
|
||||
import { ListFilters } from "@dashboard/components/AppLayout/ListFilters";
|
||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||
|
@ -29,7 +30,7 @@ import {
|
|||
} from "@dashboard/types";
|
||||
import { hasLimits, isLimitReached } from "@dashboard/utils/limits";
|
||||
import { Card } from "@material-ui/core";
|
||||
import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui/next";
|
||||
import { Box, Button, ChevronRightIcon, Tooltip } from "@saleor/macaw-ui/next";
|
||||
import React, { useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
|
@ -75,6 +76,8 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
|
|||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const userAccessibleChannels = useUserAccessibleChannels();
|
||||
const hasAccessibleChannels = userAccessibleChannels.length > 0;
|
||||
const filterStructure = createFilterStructure(intl, filterOpts);
|
||||
const limitsReached = isLimitReached(limits, "orders");
|
||||
const [isFilterPresetOpen, setFilterPresetOpen] = useState(false);
|
||||
|
@ -162,32 +165,46 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
|
|||
]}
|
||||
/>
|
||||
)}
|
||||
{extensionCreateButtonItems.length > 0 ? (
|
||||
<ButtonWithDropdown
|
||||
onClick={onAdd}
|
||||
testId={"create-order-button"}
|
||||
options={extensionCreateButtonItems}
|
||||
disabled={limitsReached}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="LshEVn"
|
||||
defaultMessage="Create order"
|
||||
description="button"
|
||||
/>
|
||||
</ButtonWithDropdown>
|
||||
) : (
|
||||
<Button
|
||||
data-test-id="create-order-button"
|
||||
onClick={onAdd}
|
||||
disabled={limitsReached}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="LshEVn"
|
||||
defaultMessage="Create order"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Tooltip>
|
||||
<Tooltip.Trigger>
|
||||
{extensionCreateButtonItems.length > 0 ? (
|
||||
<ButtonWithDropdown
|
||||
onClick={onAdd}
|
||||
testId={"create-order-button"}
|
||||
options={extensionCreateButtonItems}
|
||||
disabled={limitsReached || !hasAccessibleChannels}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="LshEVn"
|
||||
defaultMessage="Create order"
|
||||
description="button"
|
||||
/>
|
||||
</ButtonWithDropdown>
|
||||
) : (
|
||||
<Button
|
||||
data-test-id="create-order-button"
|
||||
onClick={onAdd}
|
||||
disabled={limitsReached || !hasAccessibleChannels}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="LshEVn"
|
||||
defaultMessage="Create order"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
{!hasAccessibleChannels && (
|
||||
<FormattedMessage
|
||||
defaultMessage="You don't have access to any channels"
|
||||
id="grkY2V"
|
||||
/>
|
||||
)}
|
||||
</Tooltip.Content>
|
||||
</Tooltip>
|
||||
|
||||
{hasLimits(limits, "orders") && (
|
||||
<LimitsInfo
|
||||
text={intl.formatMessage(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @ts-strict-ignore
|
||||
import { useUser } from "@dashboard/auth";
|
||||
import ChannelPickerDialog from "@dashboard/channels/components/ChannelPickerDialog";
|
||||
import ActionDialog from "@dashboard/components/ActionDialog";
|
||||
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||
|
@ -100,7 +101,10 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
|||
},
|
||||
});
|
||||
|
||||
const { channel, availableChannels } = useAppChannel(false);
|
||||
const { channel } = useAppChannel(false);
|
||||
const user = useUser();
|
||||
const channels = user?.user?.accessibleChannels ?? [];
|
||||
|
||||
const limitOpts = useShopLimitsQuery({
|
||||
variables: {
|
||||
orders: true,
|
||||
|
@ -274,7 +278,7 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
|||
tabName={presets[presetIdToDelete - 1]?.name ?? "..."}
|
||||
/>
|
||||
<ChannelPickerDialog
|
||||
channelsChoices={mapNodeToChoice(availableChannels)}
|
||||
channelsChoices={mapNodeToChoice(channels)}
|
||||
confirmButtonState="success"
|
||||
defaultChoice={channel?.id}
|
||||
open={params.action === "create-order"}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @ts-strict-ignore
|
||||
import { useUser } from "@dashboard/auth";
|
||||
import ChannelPickerDialog from "@dashboard/channels/components/ChannelPickerDialog";
|
||||
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||
import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog";
|
||||
|
@ -73,6 +74,9 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
|||
usePaginationReset(orderListUrl, params, settings.rowNumber);
|
||||
|
||||
const intl = useIntl();
|
||||
const { channel, availableChannels } = useAppChannel(false);
|
||||
const user = useUser();
|
||||
const channels = user?.user?.accessibleChannels ?? [];
|
||||
|
||||
const [createOrder] = useOrderDraftCreateMutation({
|
||||
onCompleted: data => {
|
||||
|
@ -87,7 +91,6 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
|||
},
|
||||
});
|
||||
|
||||
const { channel, availableChannels } = useAppChannel(false);
|
||||
const limitOpts = useShopLimitsQuery({
|
||||
variables: {
|
||||
orders: true,
|
||||
|
@ -95,9 +98,7 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
|||
});
|
||||
|
||||
const noChannel = !channel && typeof channel !== "undefined";
|
||||
const channelOpts = availableChannels
|
||||
? mapNodeToChoice(availableChannels)
|
||||
: null;
|
||||
const channelOpts = availableChannels ? mapNodeToChoice(channels) : null;
|
||||
|
||||
const [changeFilters, resetFilters, handleSearchChange] = useFilterHandlers({
|
||||
createUrl: orderListUrl,
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
// @ts-strict-ignore
|
||||
import { permissions } from "@dashboard/fixtures";
|
||||
import { channels, permissions } from "@dashboard/fixtures";
|
||||
import React from "react";
|
||||
|
||||
import { errorsOfPermissionGroupCreate } from "../../fixtures";
|
||||
import PermissionGroupCreatePage, {
|
||||
import {
|
||||
PermissionGroupCreatePage,
|
||||
PermissionGroupCreatePageProps,
|
||||
} from "./PermissionGroupCreatePage";
|
||||
|
||||
const props: PermissionGroupCreatePageProps = {
|
||||
disabled: false,
|
||||
errors: [],
|
||||
onSubmit: () => undefined,
|
||||
onSubmit: () => new Promise(resolve => resolve(undefined)),
|
||||
permissions,
|
||||
saveButtonBarState: undefined,
|
||||
channels,
|
||||
saveButtonBarState: "default",
|
||||
hasRestrictedChannels: false,
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
// @ts-strict-ignore
|
||||
import AccountPermissions from "@dashboard/components/AccountPermissions";
|
||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||
import { Backlink } from "@dashboard/components/Backlink";
|
||||
import { ConfirmButtonTransitionState } from "@dashboard/components/ConfirmButton";
|
||||
import { ChannelPermission } from "@dashboard/components/ChannelPermission";
|
||||
import Form from "@dashboard/components/Form";
|
||||
import FormSpacer from "@dashboard/components/FormSpacer";
|
||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||
import Savebar from "@dashboard/components/Savebar";
|
||||
import {
|
||||
ChannelFragment,
|
||||
PermissionEnum,
|
||||
PermissionGroupErrorFragment,
|
||||
} from "@dashboard/graphql";
|
||||
import { SubmitPromise } from "@dashboard/hooks/useForm";
|
||||
import { FormChange, SubmitPromise } from "@dashboard/hooks/useForm";
|
||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||
import { sectionNames } from "@dashboard/intl";
|
||||
import { buttonMessages, sectionNames } from "@dashboard/intl";
|
||||
import { permissionGroupListUrl } from "@dashboard/permissionGroups/urls";
|
||||
import { getFormErrors } from "@dashboard/utils/errors";
|
||||
import getPermissionGroupErrorMessage from "@dashboard/utils/errors/permissionGroups";
|
||||
import { Box } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
|
@ -25,30 +27,40 @@ import PermissionGroupInfo from "../PermissionGroupInfo";
|
|||
export interface PermissionGroupCreateFormData {
|
||||
name: string;
|
||||
hasFullAccess: boolean;
|
||||
hasAllChannels: boolean;
|
||||
isActive: boolean;
|
||||
permissions: PermissionEnum[];
|
||||
channels: string[];
|
||||
}
|
||||
|
||||
const initialForm: PermissionGroupCreateFormData = {
|
||||
hasFullAccess: false,
|
||||
hasAllChannels: true,
|
||||
isActive: false,
|
||||
name: "",
|
||||
permissions: [],
|
||||
channels: [],
|
||||
};
|
||||
|
||||
export interface PermissionGroupCreatePageProps {
|
||||
disabled: boolean;
|
||||
errors: PermissionGroupErrorFragment[];
|
||||
permissions: PermissionData[];
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
channels: ChannelFragment[];
|
||||
hasRestrictedChannels: boolean;
|
||||
saveButtonBarState: "loading" | "success" | "error" | "default";
|
||||
onSubmit: (data: PermissionGroupCreateFormData) => SubmitPromise;
|
||||
}
|
||||
|
||||
const PermissionGroupCreatePage: React.FC<PermissionGroupCreatePageProps> = ({
|
||||
export const PermissionGroupCreatePage: React.FC<
|
||||
PermissionGroupCreatePageProps
|
||||
> = ({
|
||||
disabled,
|
||||
permissions,
|
||||
channels,
|
||||
onSubmit,
|
||||
saveButtonBarState,
|
||||
hasRestrictedChannels,
|
||||
errors,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
@ -63,55 +75,86 @@ const PermissionGroupCreatePage: React.FC<PermissionGroupCreatePageProps> = ({
|
|||
return (
|
||||
<Form
|
||||
confirmLeave
|
||||
initial={initialForm}
|
||||
initial={{
|
||||
...initialForm,
|
||||
hasAllChannels: !hasRestrictedChannels,
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
disabled={disabled}
|
||||
>
|
||||
{({ data, change, submit, isSaveDisabled }) => (
|
||||
<DetailPageLayout>
|
||||
<TopNav title="New Permission Group" />
|
||||
<DetailPageLayout.Content>
|
||||
<Backlink href={permissionGroupListUrl()}>
|
||||
{intl.formatMessage(sectionNames.permissionGroups)}
|
||||
</Backlink>
|
||||
<PermissionGroupInfo
|
||||
data={data}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
disabled={disabled}
|
||||
{({ data, change, submit, isSaveDisabled }) => {
|
||||
const handleChannelChange: FormChange = event => {
|
||||
change({
|
||||
target: {
|
||||
name: "channels",
|
||||
value: event.target.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleHasAllChannelsChange = () => {
|
||||
change({
|
||||
target: {
|
||||
name: "hasAllChannels",
|
||||
value: !data.hasAllChannels,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DetailPageLayout>
|
||||
<TopNav title="New Permission Group" />
|
||||
<DetailPageLayout.Content>
|
||||
<Backlink href={permissionGroupListUrl()}>
|
||||
{intl.formatMessage(sectionNames.permissionGroups)}
|
||||
</Backlink>
|
||||
<PermissionGroupInfo
|
||||
data={data}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
<FormSpacer />
|
||||
|
||||
<Box paddingX={6}>
|
||||
<ChannelPermission
|
||||
allChannels={channels}
|
||||
selectedChannels={data.channels}
|
||||
onChannelChange={handleChannelChange}
|
||||
onHasAllChannelsChange={handleHasAllChannelsChange}
|
||||
hasAllChannels={data.hasAllChannels}
|
||||
disabled={false}
|
||||
disabledSelectAllChannels={hasRestrictedChannels}
|
||||
/>
|
||||
</Box>
|
||||
</DetailPageLayout.Content>
|
||||
<DetailPageLayout.RightSidebar>
|
||||
<AccountPermissions
|
||||
permissionsExceeded={false}
|
||||
data={data}
|
||||
errorMessage={permissionsError}
|
||||
disabled={disabled}
|
||||
permissions={permissions}
|
||||
onChange={change}
|
||||
fullAccessLabel={intl.formatMessage(buttonMessages.selectAll)}
|
||||
description={intl.formatMessage({
|
||||
id: "CYZse9",
|
||||
defaultMessage:
|
||||
"Expand or restrict group's permissions to access certain part of saleor system.",
|
||||
description: "card description",
|
||||
})}
|
||||
/>
|
||||
</DetailPageLayout.RightSidebar>
|
||||
<Savebar
|
||||
onCancel={() => navigate(permissionGroupListUrl())}
|
||||
onSubmit={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={!!isSaveDisabled}
|
||||
/>
|
||||
</DetailPageLayout.Content>
|
||||
<DetailPageLayout.RightSidebar>
|
||||
<AccountPermissions
|
||||
permissionsExceeded={false}
|
||||
data={data}
|
||||
errorMessage={permissionsError}
|
||||
disabled={disabled}
|
||||
permissions={permissions}
|
||||
onChange={change}
|
||||
fullAccessLabel={intl.formatMessage({
|
||||
id: "mAabef",
|
||||
defaultMessage: "Group has full access to the store",
|
||||
description: "checkbox label",
|
||||
})}
|
||||
description={intl.formatMessage({
|
||||
id: "CYZse9",
|
||||
defaultMessage:
|
||||
"Expand or restrict group's permissions to access certain part of saleor system.",
|
||||
description: "card description",
|
||||
})}
|
||||
/>
|
||||
</DetailPageLayout.RightSidebar>
|
||||
<Savebar
|
||||
onCancel={() => navigate(permissionGroupListUrl())}
|
||||
onSubmit={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={isSaveDisabled}
|
||||
/>
|
||||
</DetailPageLayout>
|
||||
)}
|
||||
</DetailPageLayout>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
PermissionGroupCreatePage.displayName = "PermissionGroupCreatePage";
|
||||
export default PermissionGroupCreatePage;
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export { default } from "./PermissionGroupCreatePage";
|
||||
export * from "./PermissionGroupCreatePage";
|
||||
|
|
|
@ -1,30 +1,42 @@
|
|||
// @ts-strict-ignore
|
||||
import { permissions } from "@dashboard/fixtures";
|
||||
import { channels, permissions } from "@dashboard/fixtures";
|
||||
import { MembersListUrlSortField } from "@dashboard/permissionGroups/urls";
|
||||
import React from "react";
|
||||
|
||||
import { emptyPermissionGroup, permissionGroup, users } from "../../fixtures";
|
||||
import PermissionGroupDetailsPage, {
|
||||
PermissionGroupDetailsPageProps,
|
||||
import {
|
||||
emptyPermissionGroup,
|
||||
permissionGroup,
|
||||
permissionGroupWithChannels,
|
||||
users,
|
||||
} from "../../fixtures";
|
||||
import {
|
||||
PermissionGroupDetailsPage,
|
||||
PermissonGroupDetailsPageProps,
|
||||
} from "./PermissionGroupDetailsPage";
|
||||
export * from "./PermissionGroupDetailsPage";
|
||||
|
||||
const props: PermissionGroupDetailsPageProps = {
|
||||
const props: PermissonGroupDetailsPageProps = {
|
||||
disabled: false,
|
||||
isUserAbleToEditChannels: true,
|
||||
errors: [],
|
||||
isChecked: () => false,
|
||||
members: users,
|
||||
onAssign: () => undefined,
|
||||
onSort: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
onSubmit: () => new Promise(resolve => resolve(undefined)),
|
||||
onUnassign: () => undefined,
|
||||
permissionGroup,
|
||||
permissions,
|
||||
permissionsExceeded: false,
|
||||
saveButtonBarState: undefined,
|
||||
saveButtonBarState: "default",
|
||||
selected: 0,
|
||||
sort: null,
|
||||
sort: {
|
||||
asc: true,
|
||||
sort: MembersListUrlSortField.name,
|
||||
},
|
||||
toggle: () => undefined,
|
||||
toggleAll: () => undefined,
|
||||
toolbar: null,
|
||||
channels,
|
||||
};
|
||||
|
||||
export default {
|
||||
|
@ -45,7 +57,23 @@ export const Loading = () => (
|
|||
<PermissionGroupDetailsPage
|
||||
{...props}
|
||||
disabled={true}
|
||||
permissionGroup={undefined}
|
||||
permissions={undefined}
|
||||
permissionGroup={permissionGroup}
|
||||
permissions={permissions}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithRestrictedChannels = () => (
|
||||
<PermissionGroupDetailsPage
|
||||
{...props}
|
||||
permissionGroup={permissionGroupWithChannels}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithRestrictedChannelsAndWithoutAccessToEdit = () => (
|
||||
<PermissionGroupDetailsPage
|
||||
{...props}
|
||||
disabled={true}
|
||||
isUserAbleToEditChannels={false}
|
||||
permissionGroup={permissionGroupWithChannels}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,42 +1,50 @@
|
|||
// @ts-strict-ignore
|
||||
import { useUser } from "@dashboard/auth";
|
||||
import AccountPermissions from "@dashboard/components/AccountPermissions";
|
||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||
import { ConfirmButtonTransitionState } from "@dashboard/components/ConfirmButton";
|
||||
import { ChannelPermission } from "@dashboard/components/ChannelPermission";
|
||||
import Form from "@dashboard/components/Form";
|
||||
import FormSpacer from "@dashboard/components/FormSpacer";
|
||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||
import Savebar from "@dashboard/components/Savebar";
|
||||
import {
|
||||
ChannelFragment,
|
||||
PermissionEnum,
|
||||
PermissionGroupDetailsFragment,
|
||||
PermissionGroupErrorFragment,
|
||||
UserPermissionFragment,
|
||||
} from "@dashboard/graphql";
|
||||
import { SubmitPromise } from "@dashboard/hooks/useForm";
|
||||
import { FormChange, SubmitPromise } from "@dashboard/hooks/useForm";
|
||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||
import { buttonMessages } from "@dashboard/intl";
|
||||
import {
|
||||
MembersListUrlSortField,
|
||||
permissionGroupListUrl,
|
||||
} from "@dashboard/permissionGroups/urls";
|
||||
import {
|
||||
extractPermissionCodes,
|
||||
isGroupFullAccess,
|
||||
} from "@dashboard/permissionGroups/utils";
|
||||
import { ListActions, SortPage } from "@dashboard/types";
|
||||
import { getFormErrors } from "@dashboard/utils/errors";
|
||||
import getPermissionGroupErrorMessage from "@dashboard/utils/errors/permissionGroups";
|
||||
import { Box } from "@saleor/macaw-ui/next";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import {
|
||||
checkIfUserHasRestictedAccessToChannels,
|
||||
extractPermissionCodes,
|
||||
getInitialChannels,
|
||||
getUserAccessibleChannelsOptions,
|
||||
isGroupFullAccess,
|
||||
} from "../../utils";
|
||||
import PermissionGroupInfo from "../PermissionGroupInfo";
|
||||
import PermissionGroupMemberList from "../PermissionGroupMemberList";
|
||||
|
||||
export interface PermissionGroupDetailsPageFormData {
|
||||
name: string;
|
||||
hasFullAccess: boolean;
|
||||
hasAllChannels: boolean;
|
||||
isActive: boolean;
|
||||
permissions: PermissionEnum[];
|
||||
users: PermissionGroupDetailsFragment["users"];
|
||||
channels: string[];
|
||||
}
|
||||
|
||||
export interface PermissionData
|
||||
|
@ -45,22 +53,26 @@ export interface PermissionData
|
|||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface PermissionGroupDetailsPageProps
|
||||
export interface PermissonGroupDetailsPageProps
|
||||
extends ListActions,
|
||||
SortPage<MembersListUrlSortField> {
|
||||
channels: ChannelFragment[];
|
||||
disabled: boolean;
|
||||
isUserAbleToEditChannels: boolean;
|
||||
errors: PermissionGroupErrorFragment[];
|
||||
members: PermissionGroupDetailsFragment["users"];
|
||||
permissionGroup: PermissionGroupDetailsFragment;
|
||||
permissionGroup: PermissionGroupDetailsFragment | null | undefined;
|
||||
permissions: PermissionData[];
|
||||
permissionsExceeded: boolean;
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
saveButtonBarState: "loading" | "success" | "error" | "default";
|
||||
onAssign: () => void;
|
||||
onUnassign: (ids: string[]) => void;
|
||||
onSubmit: (data: PermissionGroupDetailsPageFormData) => SubmitPromise;
|
||||
}
|
||||
|
||||
const PermissionGroupDetailsPage: React.FC<PermissionGroupDetailsPageProps> = ({
|
||||
export const PermissionGroupDetailsPage: React.FC<
|
||||
PermissonGroupDetailsPageProps
|
||||
> = ({
|
||||
disabled,
|
||||
errors,
|
||||
members,
|
||||
|
@ -69,13 +81,23 @@ const PermissionGroupDetailsPage: React.FC<PermissionGroupDetailsPageProps> = ({
|
|||
permissions,
|
||||
permissionsExceeded,
|
||||
saveButtonBarState,
|
||||
channels,
|
||||
isUserAbleToEditChannels,
|
||||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigator();
|
||||
const user = useUser();
|
||||
|
||||
const channelsOptions = getUserAccessibleChannelsOptions(channels, user.user);
|
||||
const hasUserRestrictedChannels = checkIfUserHasRestictedAccessToChannels(
|
||||
user.user,
|
||||
);
|
||||
|
||||
const initialForm: PermissionGroupDetailsPageFormData = {
|
||||
hasFullAccess: isGroupFullAccess(permissionGroup, permissions),
|
||||
hasAllChannels: !permissionGroup?.restrictedAccessToChannels ?? false,
|
||||
channels: getInitialChannels(permissionGroup, channels?.length ?? 0),
|
||||
isActive: false,
|
||||
name: permissionGroup?.name || "",
|
||||
permissions: extractPermissionCodes(permissionGroup),
|
||||
|
@ -90,59 +112,92 @@ const PermissionGroupDetailsPage: React.FC<PermissionGroupDetailsPageProps> = ({
|
|||
|
||||
return (
|
||||
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
|
||||
{({ data, change, submit }) => (
|
||||
<DetailPageLayout>
|
||||
<TopNav
|
||||
href={permissionGroupListUrl()}
|
||||
title={permissionGroup?.name}
|
||||
/>
|
||||
<DetailPageLayout.Content>
|
||||
<PermissionGroupInfo
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
{({ data, change, submit }) => {
|
||||
const handleChannelChange: FormChange = event => {
|
||||
change({
|
||||
target: {
|
||||
name: "channels",
|
||||
value: event.target.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleHasAllChannelsChange = () => {
|
||||
change({
|
||||
target: {
|
||||
name: "hasAllChannels",
|
||||
value: !data.hasAllChannels,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DetailPageLayout>
|
||||
<TopNav
|
||||
href={permissionGroupListUrl()}
|
||||
title={permissionGroup?.name}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<PermissionGroupMemberList
|
||||
disabled={disabled}
|
||||
{...listProps}
|
||||
users={data?.users || []}
|
||||
/>
|
||||
</DetailPageLayout.Content>
|
||||
<DetailPageLayout.RightSidebar>
|
||||
<AccountPermissions
|
||||
permissionsExceeded={permissionsExceeded}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
permissions={permissions}
|
||||
onChange={change}
|
||||
errorMessage={permissionsError}
|
||||
fullAccessLabel={intl.formatMessage({
|
||||
id: "mAabef",
|
||||
defaultMessage: "Group has full access to the store",
|
||||
description: "checkbox label",
|
||||
})}
|
||||
description={intl.formatMessage({
|
||||
id: "CYZse9",
|
||||
defaultMessage:
|
||||
"Expand or restrict group's permissions to access certain part of saleor system.",
|
||||
description: "card description",
|
||||
})}
|
||||
/>
|
||||
</DetailPageLayout.RightSidebar>
|
||||
<div>
|
||||
<Savebar
|
||||
onCancel={() => navigate(permissionGroupListUrl())}
|
||||
onSubmit={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</DetailPageLayout>
|
||||
)}
|
||||
<DetailPageLayout.Content>
|
||||
<PermissionGroupInfo
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
/>
|
||||
|
||||
<FormSpacer />
|
||||
|
||||
<Box paddingX={6}>
|
||||
<ChannelPermission
|
||||
allChannels={
|
||||
// I pass all channels because Multiselect components based on ids,
|
||||
// and need data that will take information about channel
|
||||
!isUserAbleToEditChannels ? channels : channelsOptions
|
||||
}
|
||||
hasAllChannels={data.hasAllChannels}
|
||||
selectedChannels={data.channels}
|
||||
onHasAllChannelsChange={handleHasAllChannelsChange}
|
||||
onChannelChange={handleChannelChange}
|
||||
disabled={!isUserAbleToEditChannels || disabled}
|
||||
disabledSelectAllChannels={hasUserRestrictedChannels}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<FormSpacer />
|
||||
<PermissionGroupMemberList
|
||||
disabled={disabled}
|
||||
{...listProps}
|
||||
users={data?.users || []}
|
||||
/>
|
||||
</DetailPageLayout.Content>
|
||||
<DetailPageLayout.RightSidebar>
|
||||
<AccountPermissions
|
||||
permissionsExceeded={permissionsExceeded}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
permissions={permissions}
|
||||
onChange={change}
|
||||
errorMessage={permissionsError}
|
||||
fullAccessLabel={intl.formatMessage(buttonMessages.selectAll)}
|
||||
description={intl.formatMessage({
|
||||
id: "CYZse9",
|
||||
defaultMessage:
|
||||
"Expand or restrict group's permissions to access certain part of saleor system.",
|
||||
description: "card description",
|
||||
})}
|
||||
/>
|
||||
</DetailPageLayout.RightSidebar>
|
||||
<div>
|
||||
<Savebar
|
||||
onCancel={() => navigate(permissionGroupListUrl())}
|
||||
onSubmit={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</DetailPageLayout>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
PermissionGroupDetailsPage.displayName = "PermissionGroupDetailsPage";
|
||||
export default PermissionGroupDetailsPage;
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export { default } from "./PermissionGroupDetailsPage";
|
||||
export * from "./PermissionGroupDetailsPage";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @ts-strict-ignore
|
||||
import avatarImg from "@assets/images/avatars/avatar.png";
|
||||
import { channels } from "@dashboard/fixtures";
|
||||
import {
|
||||
PermissionEnum,
|
||||
PermissionGroupDetailsFragment,
|
||||
|
@ -113,6 +113,7 @@ export const emptyPermissionGroup: PermissionGroupDetailsFragment = {
|
|||
userCanManage: true,
|
||||
users: [],
|
||||
__typename: "Group",
|
||||
|
||||
permissions: [
|
||||
{
|
||||
code: PermissionEnum.MANAGE_PAGES,
|
||||
|
@ -120,6 +121,8 @@ export const emptyPermissionGroup: PermissionGroupDetailsFragment = {
|
|||
__typename: "Permission",
|
||||
},
|
||||
],
|
||||
accessibleChannels: [],
|
||||
restrictedAccessToChannels: false,
|
||||
};
|
||||
|
||||
export const errorsOfPermissionGroupCreate: PermissionGroupErrorFragment[] = [
|
||||
|
@ -161,6 +164,8 @@ export const permissionGroup: PermissionGroupDetailsFragment = {
|
|||
avatar: null,
|
||||
},
|
||||
],
|
||||
accessibleChannels: [],
|
||||
restrictedAccessToChannels: false,
|
||||
__typename: "Group",
|
||||
permissions: [
|
||||
{
|
||||
|
@ -171,7 +176,46 @@ export const permissionGroup: PermissionGroupDetailsFragment = {
|
|||
],
|
||||
};
|
||||
|
||||
export const users: RelayToFlat<SearchStaffMembersQuery["search"]> = [
|
||||
export const permissionGroupWithChannels: NonNullable<PermissionGroupDetailsFragment> =
|
||||
{
|
||||
id: "R3JvdXA6Mw==",
|
||||
name: "Editors",
|
||||
userCanManage: true,
|
||||
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,
|
||||
},
|
||||
],
|
||||
accessibleChannels: [channels[0]],
|
||||
restrictedAccessToChannels: true,
|
||||
__typename: "Group",
|
||||
permissions: [
|
||||
{
|
||||
code: PermissionEnum.MANAGE_PAGES,
|
||||
name: "Manage pages.",
|
||||
__typename: "Permission",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const users: RelayToFlat<
|
||||
NonNullable<SearchStaffMembersQuery["search"]>
|
||||
> = [
|
||||
{
|
||||
node: {
|
||||
id: "VXNlcjoyMQ==",
|
||||
|
|
|
@ -15,8 +15,8 @@ import {
|
|||
PermissionGroupListUrlQueryParams,
|
||||
PermissionGroupListUrlSortField,
|
||||
} from "./urls";
|
||||
import PermissionGroupCreate from "./views/PermissionGroupCreate";
|
||||
import PermissionGroupDetailsComponent from "./views/PermissionGroupDetails";
|
||||
import { PermissionGroupCreate } from "./views/PermissionGroupCreate";
|
||||
import { PermissionGroupDetails as PermissionGroupDetailsComponent } from "./views/PermissionGroupDetails";
|
||||
import PermissionGroupListComponent from "./views/PermissionGroupList";
|
||||
|
||||
const permissionGroupList: React.FC<RouteComponentProps<{}>> = ({
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// @ts-strict-ignore
|
||||
import { StaffMemberFragment } from "@dashboard/graphql";
|
||||
import { getUserName } from "@dashboard/misc";
|
||||
|
||||
|
@ -7,12 +6,13 @@ import { MembersListUrlSortField } from "./urls";
|
|||
export const sortMembers =
|
||||
(sort: string, asc: boolean) =>
|
||||
(a: StaffMemberFragment, b: StaffMemberFragment) => {
|
||||
let valueA;
|
||||
let valueB;
|
||||
let valueA: string = "";
|
||||
let valueB: string = "";
|
||||
|
||||
switch (sort) {
|
||||
case MembersListUrlSortField.name:
|
||||
valueA = getUserName(a);
|
||||
valueB = getUserName(b);
|
||||
valueA = getUserName(a) ?? "";
|
||||
valueB = getUserName(b) ?? "";
|
||||
break;
|
||||
case MembersListUrlSortField.email:
|
||||
valueA = a.email;
|
||||
|
@ -21,6 +21,6 @@ export const sortMembers =
|
|||
}
|
||||
|
||||
return asc
|
||||
? ("" + valueA).localeCompare(valueB)
|
||||
: ("" + valueA).localeCompare(valueB) * -1;
|
||||
? valueA.localeCompare(valueB)
|
||||
: valueA.localeCompare(valueB) * -1;
|
||||
};
|
||||
|
|
662
src/permissionGroups/utils.test.ts
Normal file
662
src/permissionGroups/utils.test.ts
Normal file
|
@ -0,0 +1,662 @@
|
|||
import { UserContext } from "@dashboard/auth/types";
|
||||
import {
|
||||
ChannelFragment,
|
||||
PermissionEnum,
|
||||
PermissionFragment,
|
||||
PermissionGroupDetailsFragment,
|
||||
UserFragment,
|
||||
} from "@dashboard/graphql";
|
||||
|
||||
import { PermissionGroupDetailsPageFormData } from "./components/PermissionGroupDetailsPage";
|
||||
import { permissionGroup, permissionGroupWithChannels } from "./fixtures";
|
||||
import {
|
||||
arePermissionsExceeded,
|
||||
channelsDiff,
|
||||
checkIfUserHasRestictedAccessToChannels,
|
||||
extractPermissionCodes,
|
||||
getInitialChannels,
|
||||
getUserAccessibleChannelsOptions,
|
||||
isGroupFullAccess,
|
||||
mapAccessibleChannelsToChoice,
|
||||
permissionsDiff,
|
||||
usersDiff,
|
||||
} from "./utils";
|
||||
|
||||
describe("Permission group utils", () => {
|
||||
describe("channelDiff", () => {
|
||||
it("should return empty added and removed channels when user is not eligible to edit channels", () => {
|
||||
// Arrange
|
||||
const isUserEligibleToEditPermissionGroup = false;
|
||||
const allAviableChannels: ChannelFragment[] = [];
|
||||
const formData = {
|
||||
channels: [],
|
||||
} as unknown as PermissionGroupDetailsPageFormData;
|
||||
|
||||
const permissionGroup = {
|
||||
restrictedAccessToChannels: false,
|
||||
accessibleChannels: [
|
||||
{
|
||||
id: "1",
|
||||
name: "channel-1",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "channel-2",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "channel-3",
|
||||
},
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
// Act
|
||||
const { addChannels, removeChannels } = channelsDiff(
|
||||
permissionGroup,
|
||||
formData,
|
||||
allAviableChannels,
|
||||
isUserEligibleToEditPermissionGroup,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(addChannels).toEqual([]);
|
||||
expect(removeChannels).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return added channel and no removed channels when user had no restricted channels", () => {
|
||||
// Arrange
|
||||
const isUserEligibleToEditPermissionGroup = true;
|
||||
const allAviableChannels: ChannelFragment[] = [];
|
||||
const formData = {
|
||||
channels: ["1"],
|
||||
} as PermissionGroupDetailsPageFormData;
|
||||
|
||||
const permissionGroup = {
|
||||
restrictedAccessToChannels: false,
|
||||
accessibleChannels: [
|
||||
{
|
||||
id: "1",
|
||||
name: "channel-1",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "channel-2",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "channel-3",
|
||||
},
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
// Act
|
||||
const { addChannels, removeChannels } = channelsDiff(
|
||||
permissionGroup,
|
||||
formData,
|
||||
allAviableChannels,
|
||||
isUserEligibleToEditPermissionGroup,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(addChannels).toEqual(["1"]);
|
||||
expect(removeChannels).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return all added and removed channels", () => {
|
||||
// Arrange
|
||||
const isUserEligibleToEditPermissionGroup = true;
|
||||
const allAviableChannels: ChannelFragment[] = [];
|
||||
const formData = {
|
||||
channels: ["2", "3", "55"],
|
||||
} as PermissionGroupDetailsPageFormData;
|
||||
|
||||
const permissionGroup = {
|
||||
restrictedAccessToChannels: true,
|
||||
accessibleChannels: [
|
||||
{
|
||||
id: "1",
|
||||
name: "channel-1",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "channel-2",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "channel-3",
|
||||
},
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
// Act
|
||||
const { addChannels, removeChannels } = channelsDiff(
|
||||
permissionGroup,
|
||||
formData,
|
||||
allAviableChannels,
|
||||
isUserEligibleToEditPermissionGroup,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(addChannels).toEqual(["55"]);
|
||||
expect(removeChannels).toEqual(["1"]);
|
||||
});
|
||||
|
||||
it("should only removed channels", () => {
|
||||
// Arrange
|
||||
const isUserEligibleToEditPermissionGroup = true;
|
||||
const allAviableChannels: ChannelFragment[] = [];
|
||||
const formData = {
|
||||
channels: ["2"],
|
||||
} as PermissionGroupDetailsPageFormData;
|
||||
|
||||
const permissionGroup = {
|
||||
restrictedAccessToChannels: true,
|
||||
accessibleChannels: [
|
||||
{
|
||||
id: "1",
|
||||
name: "channel-1",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "channel-2",
|
||||
},
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
// Act
|
||||
const { addChannels, removeChannels } = channelsDiff(
|
||||
permissionGroup,
|
||||
formData,
|
||||
allAviableChannels,
|
||||
isUserEligibleToEditPermissionGroup,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(addChannels).toEqual([]);
|
||||
expect(removeChannels).toEqual(["1"]);
|
||||
});
|
||||
|
||||
it("should return all channels when no restricted channels and allow access all", () => {
|
||||
// Arrnage
|
||||
const isUserEligibleToEditPermissionGroup = true;
|
||||
const formData = {
|
||||
channels: ["2"],
|
||||
hasAllChannels: true,
|
||||
} as PermissionGroupDetailsPageFormData;
|
||||
|
||||
const permissionGroup = {
|
||||
restrictedAccessToChannels: false,
|
||||
accessibleChannels: [
|
||||
{
|
||||
id: "1",
|
||||
name: "channel-1",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "channel-2",
|
||||
},
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
const allChannels = [
|
||||
{ id: "12", name: "channel-12" },
|
||||
{ id: "22", name: "channel-22" },
|
||||
] as ChannelFragment[];
|
||||
|
||||
// Act
|
||||
const { addChannels, removeChannels } = channelsDiff(
|
||||
permissionGroup,
|
||||
formData,
|
||||
allChannels,
|
||||
isUserEligibleToEditPermissionGroup,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(addChannels).toEqual(["12", "22"]);
|
||||
expect(removeChannels).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isGroupFullAccess", () => {
|
||||
it("should return true when have all permissions available in shop assigned", () => {
|
||||
// Arrange
|
||||
const permissionGroup = {
|
||||
permissions: [
|
||||
{ code: PermissionEnum.HANDLE_TAXES, name: "Handle taxes" },
|
||||
{ code: PermissionEnum.HANDLE_CHECKOUTS, name: "Handle checkouts" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
{ code: PermissionEnum.MANAGE_APPS, name: "Handle apps" },
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
const shopPermissions = [
|
||||
{ code: PermissionEnum.HANDLE_TAXES, name: "Handle taxes" },
|
||||
{ code: PermissionEnum.HANDLE_CHECKOUTS, name: "Handle checkouts" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
{ code: PermissionEnum.MANAGE_APPS, name: "Handle apps" },
|
||||
] as Array<Omit<PermissionFragment, "__typename">>;
|
||||
|
||||
// Act & Assert
|
||||
expect(isGroupFullAccess(permissionGroup, shopPermissions)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when permission length is different", () => {
|
||||
// Arrange
|
||||
const permissionGroup = {
|
||||
permissions: [
|
||||
{ code: PermissionEnum.HANDLE_CHECKOUTS, name: "Handle checkouts" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
{ code: PermissionEnum.MANAGE_APPS, name: "Handle apps" },
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
const shopPermissions = [
|
||||
{ code: PermissionEnum.HANDLE_TAXES, name: "Handle taxes" },
|
||||
{ code: PermissionEnum.HANDLE_CHECKOUTS, name: "Handle checkouts" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
{ code: PermissionEnum.MANAGE_APPS, name: "Handle apps" },
|
||||
] as Array<Omit<PermissionFragment, "__typename">>;
|
||||
|
||||
// Act & Assert
|
||||
expect(isGroupFullAccess(permissionGroup, shopPermissions)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when permission does not have all shop permissions", () => {
|
||||
// Arrange
|
||||
const permissionGroup = {
|
||||
permissions: [
|
||||
{ code: PermissionEnum.HANDLE_TAXES, name: "Handle taxes" },
|
||||
{ code: PermissionEnum.HANDLE_CHECKOUTS, name: "Handle checkouts" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
{ code: PermissionEnum.MANAGE_APPS, name: "Handle apps" },
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
const shopPermissions = [
|
||||
{ code: PermissionEnum.MANAGE_ORDERS, name: "Handle order" },
|
||||
{ code: PermissionEnum.HANDLE_TAXES, name: "Handle taxes" },
|
||||
{ code: PermissionEnum.HANDLE_CHECKOUTS, name: "Handle checkouts" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
{ code: PermissionEnum.MANAGE_APPS, name: "Handle apps" },
|
||||
] as Array<Omit<PermissionFragment, "__typename">>;
|
||||
|
||||
// Act & Assert
|
||||
expect(isGroupFullAccess(permissionGroup, shopPermissions)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractPermissionCodes", () => {
|
||||
it("should return list of permission codes", () => {
|
||||
// Arrange
|
||||
const permissions = {
|
||||
permissions: [
|
||||
{ code: PermissionEnum.HANDLE_TAXES, name: "Handle taxes" },
|
||||
{ code: PermissionEnum.HANDLE_CHECKOUTS, name: "Handle checkouts" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
{ code: PermissionEnum.MANAGE_APPS, name: "Handle apps" },
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
// Act & Assert
|
||||
expect(extractPermissionCodes(permissions)).toEqual([
|
||||
PermissionEnum.HANDLE_TAXES,
|
||||
PermissionEnum.HANDLE_CHECKOUTS,
|
||||
PermissionEnum.HANDLE_PAYMENTS,
|
||||
PermissionEnum.MANAGE_APPS,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("permissionsDiff", () => {
|
||||
it("should return added permissions and no removed permissions when user had no permissions", () => {
|
||||
// Arrange
|
||||
const formData = {
|
||||
permissions: [PermissionEnum.HANDLE_TAXES],
|
||||
} as PermissionGroupDetailsPageFormData;
|
||||
|
||||
const permissionGroup = {
|
||||
permissions: [],
|
||||
} as unknown as PermissionGroupDetailsFragment;
|
||||
|
||||
// Act
|
||||
const { addPermissions, removePermissions } = permissionsDiff(
|
||||
permissionGroup,
|
||||
formData,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(addPermissions).toEqual([PermissionEnum.HANDLE_TAXES]);
|
||||
expect(removePermissions).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return all added and removed permissions", () => {
|
||||
// Arrange
|
||||
const formData = {
|
||||
permissions: [
|
||||
PermissionEnum.HANDLE_TAXES,
|
||||
PermissionEnum.HANDLE_CHECKOUTS,
|
||||
],
|
||||
} as PermissionGroupDetailsPageFormData;
|
||||
|
||||
const permissionGroup = {
|
||||
permissions: [
|
||||
{ code: PermissionEnum.HANDLE_TAXES, name: "Handle taxes" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
// Act
|
||||
const { addPermissions, removePermissions } = permissionsDiff(
|
||||
permissionGroup,
|
||||
formData,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(addPermissions).toEqual([PermissionEnum.HANDLE_CHECKOUTS]);
|
||||
expect(removePermissions).toEqual([PermissionEnum.HANDLE_PAYMENTS]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("usersDiff", () => {
|
||||
it("should return added users and no removed users when user had no users", () => {
|
||||
// Arrange
|
||||
const formData = {
|
||||
users: [
|
||||
{ id: "1", email: "test1@test.com" },
|
||||
{ id: "2", email: "test2@test.com" },
|
||||
],
|
||||
} as PermissionGroupDetailsPageFormData;
|
||||
|
||||
const permissionGroup = {
|
||||
users: [],
|
||||
} as unknown as PermissionGroupDetailsFragment;
|
||||
|
||||
// Act
|
||||
const { addUsers, removeUsers } = usersDiff(permissionGroup, formData);
|
||||
|
||||
// Assert
|
||||
expect(addUsers).toEqual(["1", "2"]);
|
||||
expect(removeUsers).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return all added and removed users", () => {
|
||||
// Arrange
|
||||
const formData = {
|
||||
users: [
|
||||
{ id: "2", email: "test2@test.com" },
|
||||
{ id: "3", email: "test3@test.com" },
|
||||
],
|
||||
} as PermissionGroupDetailsPageFormData;
|
||||
|
||||
const permissionGroup = {
|
||||
users: [{ id: "1", email: "test1@test.com" }],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
// Act
|
||||
const { addUsers, removeUsers } = usersDiff(permissionGroup, formData);
|
||||
|
||||
// Assert
|
||||
expect(addUsers).toEqual(["2", "3"]);
|
||||
expect(removeUsers).toEqual(["1"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("arePermissionsExceeded", () => {
|
||||
it("should return false when number of permissions is not exceeded", () => {
|
||||
// Arrange
|
||||
const permissions = {
|
||||
permissions: [
|
||||
{ code: PermissionEnum.HANDLE_TAXES, name: "Handle taxes" },
|
||||
{ code: PermissionEnum.HANDLE_CHECKOUTS, name: "Handle checkouts" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
{ code: PermissionEnum.MANAGE_APPS, name: "Handle apps" },
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
const user = {
|
||||
userPermissions: [
|
||||
{ code: PermissionEnum.HANDLE_TAXES, name: "Handle taxe" },
|
||||
{ code: PermissionEnum.HANDLE_CHECKOUTS, name: "Handle checkouts" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
{ code: PermissionEnum.MANAGE_APPS, name: "Handle apps" },
|
||||
],
|
||||
} as UserFragment;
|
||||
|
||||
// Act
|
||||
const permissionsExceeded = arePermissionsExceeded(permissions, user);
|
||||
|
||||
// Assert
|
||||
expect(permissionsExceeded).toBe(false);
|
||||
});
|
||||
|
||||
it("should return true when number of permissions is exceeded", () => {
|
||||
// Arrange
|
||||
const permissions = {
|
||||
permissions: [
|
||||
{ code: PermissionEnum.HANDLE_TAXES, name: "Handle taxe" },
|
||||
{ code: PermissionEnum.HANDLE_CHECKOUTS, name: "Handle checkouts" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
{ code: PermissionEnum.MANAGE_APPS, name: "Handle apps" },
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
const user = {
|
||||
userPermissions: [
|
||||
{ code: PermissionEnum.HANDLE_CHECKOUTS, name: "Handle checkouts" },
|
||||
{ code: PermissionEnum.HANDLE_PAYMENTS, name: "Handle payments" },
|
||||
{ code: PermissionEnum.MANAGE_APPS, name: "Handle apps" },
|
||||
],
|
||||
} as UserFragment;
|
||||
|
||||
// Act
|
||||
const permissionsExceeded = arePermissionsExceeded(permissions, user);
|
||||
|
||||
// Assert
|
||||
expect(permissionsExceeded).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPermissionGroupAccessibleChannels", () => {
|
||||
it("should return all accessible channels ", () => {
|
||||
// Arrange
|
||||
const permissionGroup = {
|
||||
accessibleChannels: [
|
||||
{
|
||||
id: "1",
|
||||
name: "Channel 1",
|
||||
slug: "channel-1",
|
||||
currencyCode: "USD",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Channel 2",
|
||||
slug: "channel-2",
|
||||
currencyCode: "USD",
|
||||
},
|
||||
],
|
||||
} as PermissionGroupDetailsFragment;
|
||||
|
||||
// Act
|
||||
const accessibleChannels = mapAccessibleChannelsToChoice(permissionGroup);
|
||||
|
||||
// Assert
|
||||
expect(accessibleChannels).toEqual([
|
||||
{ label: "Channel 1", value: "1", disabled: false },
|
||||
{ label: "Channel 2", value: "2", disabled: false },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUserAccessibleChannelsOptions", () => {
|
||||
it("should return empty array when no users", () => {
|
||||
// Arrange
|
||||
const availableChannels = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Channel 1",
|
||||
slug: "channel-1",
|
||||
currencyCode: "USD",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Channel 2",
|
||||
slug: "channel-2",
|
||||
currencyCode: "USD",
|
||||
},
|
||||
] as ChannelFragment[];
|
||||
|
||||
// Act
|
||||
const filteredChannels =
|
||||
getUserAccessibleChannelsOptions(availableChannels);
|
||||
|
||||
// Assert
|
||||
expect(filteredChannels).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return available channels when user has no restricted channels", () => {
|
||||
// Arrange
|
||||
const availableChannels = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Channel 1",
|
||||
slug: "channel-1",
|
||||
currencyCode: "USD",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Channel 2",
|
||||
slug: "channel-2",
|
||||
currencyCode: "USD",
|
||||
},
|
||||
] as ChannelFragment[];
|
||||
|
||||
const user = {
|
||||
restrictedAccessToChannels: false,
|
||||
} as UserContext["user"];
|
||||
|
||||
// Act
|
||||
const filteredChannels = getUserAccessibleChannelsOptions(
|
||||
availableChannels,
|
||||
user,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(filteredChannels).toEqual(availableChannels);
|
||||
});
|
||||
|
||||
it("should return user accessible channels when user has accessibleChannels and has restricted access to channels", () => {
|
||||
// Arrange
|
||||
const availableChannels = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Channel 1",
|
||||
slug: "channel-1",
|
||||
currencyCode: "USD",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Channel 2",
|
||||
slug: "channel-2",
|
||||
currencyCode: "USD",
|
||||
},
|
||||
] as ChannelFragment[];
|
||||
|
||||
const user = {
|
||||
accessibleChannels: [
|
||||
{
|
||||
id: "1",
|
||||
name: "UserChannel 1",
|
||||
slug: "Userchannel-1",
|
||||
},
|
||||
],
|
||||
restrictedAccessToChannels: true,
|
||||
} as UserContext["user"];
|
||||
|
||||
// Act
|
||||
const filteredChannels = getUserAccessibleChannelsOptions(
|
||||
availableChannels,
|
||||
user,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(filteredChannels).toEqual([
|
||||
{
|
||||
id: "1",
|
||||
name: "UserChannel 1",
|
||||
slug: "Userchannel-1",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkIfUserHasRestictedChannels", () => {
|
||||
it("should return true when user has restricted channels", () => {
|
||||
// Arrange
|
||||
const user = {
|
||||
restrictedAccessToChannels: true,
|
||||
} as UserContext["user"];
|
||||
|
||||
// Act
|
||||
const hasRestrictedChannels =
|
||||
checkIfUserHasRestictedAccessToChannels(user);
|
||||
|
||||
// Assert
|
||||
expect(hasRestrictedChannels).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when user has no restricted channels", () => {
|
||||
// Arrange
|
||||
const user = {
|
||||
restrictedAccessToChannels: false,
|
||||
} as UserContext["user"];
|
||||
|
||||
// Act
|
||||
const hasRestrictedChannels =
|
||||
checkIfUserHasRestictedAccessToChannels(user);
|
||||
|
||||
// Assert
|
||||
expect(hasRestrictedChannels).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when user no user", () => {
|
||||
// Arrange and Act
|
||||
const hasRestrictedChannels =
|
||||
checkIfUserHasRestictedAccessToChannels(undefined);
|
||||
|
||||
// Assert
|
||||
expect(hasRestrictedChannels).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getInitialChannels", () => {
|
||||
it("should return empty array when no restricted channels and accessible channels length is equal all channels length", () => {
|
||||
// Arrange
|
||||
const allChannelsLength = 0;
|
||||
|
||||
// Act
|
||||
const initialChannels = getInitialChannels(
|
||||
permissionGroup,
|
||||
allChannelsLength,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(initialChannels).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return all accessible channels otherwise", () => {
|
||||
// Arrange
|
||||
const allChannelsLength = 10;
|
||||
|
||||
// Act
|
||||
const initialChannels = getInitialChannels(
|
||||
permissionGroupWithChannels,
|
||||
allChannelsLength,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(initialChannels).toEqual([
|
||||
permissionGroupWithChannels.accessibleChannels?.[0].id,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,7 @@
|
|||
// @ts-strict-ignore
|
||||
import { UserContext } from "@dashboard/auth/types";
|
||||
import { MultiAutocompleteChoiceType } from "@dashboard/components/MultiAutocompleteSelectField";
|
||||
import {
|
||||
ChannelFragment,
|
||||
PermissionFragment,
|
||||
PermissionGroupDetailsFragment,
|
||||
UserFragment,
|
||||
|
@ -7,14 +9,16 @@ import {
|
|||
import difference from "lodash/difference";
|
||||
|
||||
import { PermissionGroupDetailsPageFormData } from "./components/PermissionGroupDetailsPage";
|
||||
|
||||
/**
|
||||
* Will return true if group has all permissions available in shop assigned.
|
||||
*/
|
||||
export const isGroupFullAccess = (
|
||||
permissionGroup: PermissionGroupDetailsFragment,
|
||||
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||
shopPermissions: Array<Omit<PermissionFragment, "__typename">>,
|
||||
) => {
|
||||
if (!permissionGroup) {
|
||||
return false;
|
||||
}
|
||||
const assignedCodes = extractPermissionCodes(permissionGroup);
|
||||
|
||||
if (assignedCodes.length !== shopPermissions?.length) {
|
||||
|
@ -33,19 +37,31 @@ export const isGroupFullAccess = (
|
|||
* Return list of codes which are assigned to the permission group.
|
||||
*/
|
||||
export const extractPermissionCodes = (
|
||||
permissionGroup: PermissionGroupDetailsFragment,
|
||||
) =>
|
||||
permissionGroup?.permissions
|
||||
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||
) => {
|
||||
if (!permissionGroup) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return 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: PermissionGroupDetailsFragment,
|
||||
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||
formData: PermissionGroupDetailsPageFormData,
|
||||
) => {
|
||||
if (!permissionGroup) {
|
||||
return {
|
||||
addPermissions: [],
|
||||
removePermissions: [],
|
||||
};
|
||||
}
|
||||
|
||||
const newPermissions = formData.permissions;
|
||||
const oldPermissions = extractPermissionCodes(permissionGroup);
|
||||
|
||||
|
@ -59,11 +75,18 @@ export const permissionsDiff = (
|
|||
* Return lists of users which have to be added and removed from group.
|
||||
*/
|
||||
export const usersDiff = (
|
||||
permissionGroup: PermissionGroupDetailsFragment,
|
||||
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||
formData: PermissionGroupDetailsPageFormData,
|
||||
) => {
|
||||
const newUsers = formData.users.map(u => u.id);
|
||||
const oldUsers = permissionGroup?.users.map(u => u.id);
|
||||
if (!permissionGroup) {
|
||||
return {
|
||||
addUsers: [],
|
||||
removeUsers: [],
|
||||
};
|
||||
}
|
||||
|
||||
const newUsers = formData?.users?.map(u => u.id) ?? [];
|
||||
const oldUsers = permissionGroup?.users?.map(u => u.id) ?? [];
|
||||
|
||||
return {
|
||||
addUsers: difference(newUsers, oldUsers),
|
||||
|
@ -71,14 +94,130 @@ export const usersDiff = (
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return lists of channels which have to be added and removed from group.
|
||||
*/
|
||||
export const channelsDiff = (
|
||||
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||
formData: PermissionGroupDetailsPageFormData,
|
||||
allChannels: ChannelFragment[],
|
||||
isGroupEditable: boolean,
|
||||
) => {
|
||||
if (!permissionGroup || !isGroupEditable) {
|
||||
return {
|
||||
addChannels: [],
|
||||
removeChannels: [],
|
||||
};
|
||||
}
|
||||
|
||||
const newChannels = formData.hasAllChannels
|
||||
? allChannels.map(c => c.id)
|
||||
: formData.channels;
|
||||
const oldChannels = permissionGroup?.accessibleChannels?.map(c => c.id) ?? [];
|
||||
const hasRestrictedChannels =
|
||||
permissionGroup?.restrictedAccessToChannels ?? false;
|
||||
|
||||
if (!hasRestrictedChannels) {
|
||||
return {
|
||||
addChannels: newChannels,
|
||||
removeChannels: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
addChannels: difference(newChannels, oldChannels),
|
||||
removeChannels: difference(oldChannels, newChannels),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Permissions are exceeded when group has permission which is not handled by user
|
||||
*/
|
||||
export const arePermissionsExceeded = (
|
||||
permissionGroup: PermissionGroupDetailsFragment,
|
||||
user: UserFragment,
|
||||
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||
user: UserFragment | null | undefined,
|
||||
) => {
|
||||
if (!permissionGroup || !user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const groupPermissions = extractPermissionCodes(permissionGroup);
|
||||
const userPermissions = user.userPermissions.map(p => p.code);
|
||||
const userPermissions = user?.userPermissions?.map(p => p.code) ?? [];
|
||||
return difference(groupPermissions, userPermissions).length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return lists of permission group accessible channels.
|
||||
*/
|
||||
export const mapAccessibleChannelsToChoice = (
|
||||
permissionGroup: PermissionGroupDetailsFragment,
|
||||
isUserAbleToEdit?: boolean,
|
||||
): MultiAutocompleteChoiceType[] =>
|
||||
permissionGroup?.accessibleChannels?.map(
|
||||
channel =>
|
||||
({
|
||||
label: channel.name,
|
||||
value: channel.id,
|
||||
disabled: isUserAbleToEdit !== undefined ? !isUserAbleToEdit : false,
|
||||
} as unknown as MultiAutocompleteChoiceType),
|
||||
) ?? [];
|
||||
|
||||
export const checkIfUserBelongToPermissionGroup = (
|
||||
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||
userId: string,
|
||||
) => {
|
||||
return permissionGroup?.users?.some(u => u.id === userId) ?? false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get channels options for select field.
|
||||
*/
|
||||
export const getUserAccessibleChannelsOptions = (
|
||||
availableChannels: ChannelFragment[],
|
||||
user?: UserContext["user"],
|
||||
): ChannelFragment[] => {
|
||||
if (!user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!user.restrictedAccessToChannels) {
|
||||
return availableChannels;
|
||||
}
|
||||
|
||||
if (user.accessibleChannels !== null) {
|
||||
return user.accessibleChannels;
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if user has restricted access to channels.
|
||||
*/
|
||||
export const checkIfUserHasRestictedAccessToChannels = (
|
||||
user?: UserContext["user"],
|
||||
) => {
|
||||
if (user) {
|
||||
return user.restrictedAccessToChannels;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getInitialChannels = (
|
||||
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||
allChannelsLength: number,
|
||||
) => {
|
||||
if (!permissionGroup) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (
|
||||
!permissionGroup?.restrictedAccessToChannels &&
|
||||
permissionGroup?.accessibleChannels?.length === allChannelsLength
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return permissionGroup?.accessibleChannels?.map(channel => channel.id) ?? [];
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @ts-strict-ignore
|
||||
import { useUser } from "@dashboard/auth";
|
||||
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||
import { WindowTitle } from "@dashboard/components/WindowTitle";
|
||||
import { usePermissionGroupCreateMutation } from "@dashboard/graphql";
|
||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||
|
@ -7,20 +7,33 @@ import useNotifier from "@dashboard/hooks/useNotifier";
|
|||
import useShop from "@dashboard/hooks/useShop";
|
||||
import { extractMutationErrors } from "@dashboard/misc";
|
||||
import { PermissionData } from "@dashboard/permissionGroups/components/PermissionGroupDetailsPage";
|
||||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import PermissionGroupCreatePage, {
|
||||
import {
|
||||
PermissionGroupCreateFormData,
|
||||
PermissionGroupCreatePage,
|
||||
} from "../../components/PermissionGroupCreatePage";
|
||||
import { permissionGroupDetailsUrl } from "../../urls";
|
||||
import {
|
||||
checkIfUserHasRestictedAccessToChannels,
|
||||
getUserAccessibleChannelsOptions,
|
||||
} from "../../utils";
|
||||
|
||||
const PermissionGroupCreateView: React.FC = () => {
|
||||
export const PermissionGroupCreate: React.FC = () => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
const shop = useShop();
|
||||
const user = useUser();
|
||||
const { availableChannels } = useAppChannel(false);
|
||||
|
||||
const hasUserRestrictedAccessToChannels =
|
||||
checkIfUserHasRestictedAccessToChannels(user.user);
|
||||
const userAccessibleChannelsOptions = useMemo(
|
||||
() => getUserAccessibleChannelsOptions(availableChannels, user.user),
|
||||
[availableChannels, user.user],
|
||||
);
|
||||
|
||||
const [createPermissionGroup, createPermissionGroupResult] =
|
||||
usePermissionGroupCreateMutation({
|
||||
|
@ -33,9 +46,12 @@ const PermissionGroupCreateView: React.FC = () => {
|
|||
defaultMessage: "Permission group created",
|
||||
}),
|
||||
});
|
||||
navigate(
|
||||
permissionGroupDetailsUrl(data.permissionGroupCreate.group.id),
|
||||
);
|
||||
|
||||
if (data?.permissionGroupCreate?.group?.id) {
|
||||
navigate(
|
||||
permissionGroupDetailsUrl(data.permissionGroupCreate.group.id),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -43,22 +59,30 @@ const PermissionGroupCreateView: React.FC = () => {
|
|||
const errors =
|
||||
createPermissionGroupResult?.data?.permissionGroupCreate?.errors || [];
|
||||
|
||||
const onSubmit = (formData: PermissionGroupCreateFormData) =>
|
||||
extractMutationErrors(
|
||||
const onSubmit = (formData: PermissionGroupCreateFormData) => {
|
||||
const channelChoices = userAccessibleChannelsOptions.map(
|
||||
channel => channel.id,
|
||||
);
|
||||
|
||||
return extractMutationErrors(
|
||||
createPermissionGroup({
|
||||
variables: {
|
||||
input: {
|
||||
addPermissions: formData.hasFullAccess
|
||||
? shop.permissions.map(perm => perm.code)
|
||||
: formData.permissions,
|
||||
addPermissions: formData.permissions,
|
||||
addUsers: [],
|
||||
name: formData.name,
|
||||
addChannels: formData.hasAllChannels
|
||||
? channelChoices
|
||||
: formData.channels,
|
||||
restrictedAccessToChannels:
|
||||
hasUserRestrictedAccessToChannels || !formData.hasAllChannels,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const userPermissions = user?.user.userPermissions.map(p => p.code) || [];
|
||||
const userPermissions = user?.user?.userPermissions?.map(p => p.code) || [];
|
||||
|
||||
const permissions: PermissionData[] =
|
||||
shop?.permissions.map(
|
||||
|
@ -80,15 +104,14 @@ const PermissionGroupCreateView: React.FC = () => {
|
|||
})}
|
||||
/>
|
||||
<PermissionGroupCreatePage
|
||||
errors={errors}
|
||||
errors={errors as any}
|
||||
disabled={createPermissionGroupResult.loading}
|
||||
permissions={permissions}
|
||||
channels={userAccessibleChannelsOptions}
|
||||
hasRestrictedChannels={hasUserRestrictedAccessToChannels}
|
||||
saveButtonBarState={createPermissionGroupResult.status}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
PermissionGroupCreateView.displayName = "PermissionGroupCreateView";
|
||||
|
||||
export default PermissionGroupCreateView;
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export { default } from "./PermissionGroupCreate";
|
||||
export * from "./PermissionGroupCreate";
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
// @ts-strict-ignore
|
||||
import { useUser } from "@dashboard/auth";
|
||||
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||
import { Button } from "@dashboard/components/Button";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@dashboard/config";
|
||||
import {
|
||||
PermissionGroupDetailsQuery,
|
||||
usePermissionGroupDetailsQuery,
|
||||
usePermissionGroupUpdateMutation,
|
||||
} from "@dashboard/graphql";
|
||||
|
@ -14,11 +15,6 @@ import useStateFromProps from "@dashboard/hooks/useStateFromProps";
|
|||
import { commonMessages } from "@dashboard/intl";
|
||||
import { extractMutationErrors } from "@dashboard/misc";
|
||||
import MembersErrorDialog from "@dashboard/permissionGroups/components/MembersErrorDialog";
|
||||
import {
|
||||
arePermissionsExceeded,
|
||||
permissionsDiff,
|
||||
usersDiff,
|
||||
} from "@dashboard/permissionGroups/utils";
|
||||
import useStaffMemberSearch from "@dashboard/searches/useStaffMemberSearch";
|
||||
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
|
||||
import createSortHandler from "@dashboard/utils/handlers/sortHandler";
|
||||
|
@ -28,7 +24,8 @@ import React from "react";
|
|||
import { useIntl } from "react-intl";
|
||||
|
||||
import AssignMembersDialog from "../../components/AssignMembersDialog";
|
||||
import PermissionGroupDetailsPage, {
|
||||
import {
|
||||
PermissionGroupDetailsPage,
|
||||
PermissionGroupDetailsPageFormData,
|
||||
} from "../../components/PermissionGroupDetailsPage";
|
||||
import UnassignMembersDialog from "../../components/UnassignMembersDialog";
|
||||
|
@ -37,12 +34,23 @@ import {
|
|||
PermissionGroupDetailsUrlDialog,
|
||||
PermissionGroupDetailsUrlQueryParams,
|
||||
} from "../../urls";
|
||||
import {
|
||||
arePermissionsExceeded,
|
||||
channelsDiff,
|
||||
checkIfUserBelongToPermissionGroup,
|
||||
permissionsDiff,
|
||||
usersDiff,
|
||||
} from "../../utils";
|
||||
|
||||
interface PermissionGroupDetailsProps {
|
||||
id: string;
|
||||
params: PermissionGroupDetailsUrlQueryParams;
|
||||
}
|
||||
|
||||
type Members = NonNullable<
|
||||
NonNullable<PermissionGroupDetailsQuery["permissionGroup"]>["users"]
|
||||
>;
|
||||
|
||||
export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
||||
id,
|
||||
params,
|
||||
|
@ -55,11 +63,13 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
|||
|
||||
const { data, loading, refetch } = usePermissionGroupDetailsQuery({
|
||||
displayLoader: true,
|
||||
variables: { id, userId: user?.user.id },
|
||||
variables: { id, userId: user?.user?.id ?? "" },
|
||||
});
|
||||
|
||||
const [membersList, setMembersList] = useStateFromProps(
|
||||
data?.permissionGroup.users,
|
||||
const { availableChannels } = useAppChannel(false);
|
||||
|
||||
const [membersList, setMembersList] = useStateFromProps<Members>(
|
||||
data?.permissionGroup?.users ?? [],
|
||||
);
|
||||
|
||||
const {
|
||||
|
@ -76,16 +86,30 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
|||
|
||||
const [permissionGroupUpdate, permissionGroupUpdateResult] =
|
||||
usePermissionGroupUpdateMutation({
|
||||
onCompleted: data => {
|
||||
if (data.permissionGroupUpdate.errors.length === 0) {
|
||||
onCompleted: updatedData => {
|
||||
if (updatedData?.permissionGroupUpdate?.errors?.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges),
|
||||
});
|
||||
|
||||
// When user belong to editedd permission group refetch user details
|
||||
// as they are root of user accessible channels
|
||||
if (
|
||||
checkIfUserBelongToPermissionGroup(
|
||||
data?.permissionGroup,
|
||||
user?.user?.id ?? "",
|
||||
)
|
||||
) {
|
||||
user.refetchUser();
|
||||
}
|
||||
|
||||
refetch();
|
||||
closeModal();
|
||||
} else if (
|
||||
data.permissionGroupUpdate.errors.some(e => e.field === "removeUsers")
|
||||
updatedData?.permissionGroupUpdate?.errors.some(
|
||||
e => e.field === "removeUsers",
|
||||
)
|
||||
) {
|
||||
openModal("unassignError");
|
||||
}
|
||||
|
@ -104,22 +128,25 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
|||
);
|
||||
|
||||
const unassignMembers = () => {
|
||||
setMembersList(membersList?.filter(m => !listElements.includes(m.id)));
|
||||
setMembersList(
|
||||
membersList?.filter(m => !listElements.includes(m.id)) ?? [],
|
||||
);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const isGroupEditable =
|
||||
(data?.user.editableGroups || []).filter(g => g.id === id).length > 0;
|
||||
(data?.user?.editableGroups || []).filter(g => g.id === id).length > 0 &&
|
||||
data?.permissionGroup?.userCanManage;
|
||||
|
||||
const lastSourcesOfPermission = (data?.user.userPermissions || [])
|
||||
const lastSourcesOfPermission = (data?.user?.userPermissions || [])
|
||||
.filter(
|
||||
perm =>
|
||||
perm.sourcePermissionGroups.length === 1 &&
|
||||
perm.sourcePermissionGroups[0].id === id,
|
||||
perm.sourcePermissionGroups?.length === 1 &&
|
||||
perm.sourcePermissionGroups?.[0].id === id,
|
||||
)
|
||||
.map(perm => perm.code);
|
||||
|
||||
const userPermissions = user?.user.userPermissions.map(p => p.code) || [];
|
||||
const userPermissions = user?.user?.userPermissions?.map(p => p.code) || [];
|
||||
|
||||
const permissions = (shop?.permissions || []).map(perm => ({
|
||||
...perm,
|
||||
|
@ -129,9 +156,11 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
|||
|
||||
const permissionsExceeded = arePermissionsExceeded(
|
||||
data?.permissionGroup,
|
||||
user.user,
|
||||
user?.user,
|
||||
);
|
||||
const disabled = loading || !isGroupEditable || permissionsExceeded;
|
||||
|
||||
const isLoading = loading || permissionGroupUpdateResult.loading;
|
||||
const disabled = isLoading || !isGroupEditable || permissionsExceeded;
|
||||
|
||||
const handleSubmit = async (formData: PermissionGroupDetailsPageFormData) =>
|
||||
extractMutationErrors(
|
||||
|
@ -142,6 +171,13 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
|||
name: formData.name,
|
||||
...permissionsDiff(data?.permissionGroup, formData),
|
||||
...usersDiff(data?.permissionGroup, formData),
|
||||
...channelsDiff(
|
||||
data?.permissionGroup,
|
||||
formData,
|
||||
availableChannels,
|
||||
!!isGroupEditable,
|
||||
),
|
||||
restrictedAccessToChannels: !formData.hasAllChannels,
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
@ -152,11 +188,13 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
|||
<PermissionGroupDetailsPage
|
||||
permissionGroup={data?.permissionGroup}
|
||||
permissionsExceeded={permissionsExceeded}
|
||||
members={membersList || []}
|
||||
isUserAbleToEditChannels={!!isGroupEditable}
|
||||
channels={availableChannels}
|
||||
members={membersList}
|
||||
onAssign={() => openModal("assign")}
|
||||
onUnassign={ids => openModal("unassign", { ids })}
|
||||
errors={
|
||||
permissionGroupUpdateResult?.data?.permissionGroupUpdate.errors || []
|
||||
permissionGroupUpdateResult?.data?.permissionGroupUpdate?.errors ?? []
|
||||
}
|
||||
onSubmit={handleSubmit}
|
||||
permissions={permissions}
|
||||
|
@ -183,20 +221,17 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
|||
/>
|
||||
<AssignMembersDialog
|
||||
loading={searchResult.loading}
|
||||
staffMembers={mapEdgesToItems(searchResult?.data?.search)}
|
||||
staffMembers={mapEdgesToItems(searchResult?.data?.search) ?? []}
|
||||
onSearchChange={search}
|
||||
onFetchMore={loadMore}
|
||||
disabled={disabled}
|
||||
hasMore={searchResult?.data?.search.pageInfo.hasNextPage}
|
||||
hasMore={searchResult?.data?.search?.pageInfo?.hasNextPage ?? false}
|
||||
initialSearch=""
|
||||
confirmButtonState={permissionGroupUpdateResult.status}
|
||||
open={params.action === "assign"}
|
||||
onClose={closeModal}
|
||||
onSubmit={formData => {
|
||||
setMembersList([
|
||||
...membersList,
|
||||
...formData.filter(member => !membersList.includes(member)),
|
||||
]);
|
||||
setMembersList([...(membersList ?? []), ...formData] as Members);
|
||||
closeModal();
|
||||
}}
|
||||
/>
|
||||
|
@ -216,5 +251,3 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PermissionGroupDetails;
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export { default } from "./PermissionGroupDetails";
|
||||
export * from "./PermissionGroupDetails";
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// @ts-strict-ignore
|
||||
import {
|
||||
PermissionGroupErrorFragment,
|
||||
usePermissionGroupDeleteMutation,
|
||||
|
@ -60,7 +59,7 @@ export const PermissionGroupList: React.FC<PermissionGroupListProps> = ({
|
|||
});
|
||||
|
||||
const paginationValues = usePaginator({
|
||||
pageInfo: data?.permissionGroups.pageInfo,
|
||||
pageInfo: data?.permissionGroups?.pageInfo,
|
||||
paginationState,
|
||||
queryString: params,
|
||||
});
|
||||
|
@ -76,13 +75,13 @@ export const PermissionGroupList: React.FC<PermissionGroupListProps> = ({
|
|||
PermissionGroupListUrlQueryParams
|
||||
>(navigate, permissionGroupListUrl, params);
|
||||
|
||||
const permissionGroups = mapEdgesToItems(data?.permissionGroups);
|
||||
const permissionGroups = mapEdgesToItems(data?.permissionGroups) ?? [];
|
||||
const [deleteError, setDeleteError] =
|
||||
React.useState<PermissionGroupErrorFragment>();
|
||||
|
||||
const [permissionGroupDelete] = usePermissionGroupDeleteMutation({
|
||||
onCompleted: data => {
|
||||
if (data.permissionGroupDelete.errors.length === 0) {
|
||||
if (data?.permissionGroupDelete?.errors?.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage({
|
||||
|
@ -94,7 +93,7 @@ export const PermissionGroupList: React.FC<PermissionGroupListProps> = ({
|
|||
setDeleteError(undefined);
|
||||
closeModal();
|
||||
} else {
|
||||
setDeleteError(data.permissionGroupDelete.errors[0]);
|
||||
setDeleteError(data?.permissionGroupDelete?.errors?.[0]);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -114,7 +113,7 @@ export const PermissionGroupList: React.FC<PermissionGroupListProps> = ({
|
|||
onConfirm={() =>
|
||||
permissionGroupDelete({
|
||||
variables: {
|
||||
id: params.id,
|
||||
id: params?.id ?? "",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// @ts-strict-ignore
|
||||
import { PermissionGroupSortField } from "@dashboard/graphql";
|
||||
import { PermissionGroupListUrlSortField } from "@dashboard/permissionGroups/urls";
|
||||
import { createGetSortQueryVariables } from "@dashboard/utils/sort";
|
||||
|
@ -10,7 +9,7 @@ export function getSortQueryField(
|
|||
case PermissionGroupListUrlSortField.name:
|
||||
return PermissionGroupSortField.NAME;
|
||||
default:
|
||||
return undefined;
|
||||
return "" as PermissionGroupSortField;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ export interface SortPage<TSortKey extends string> {
|
|||
|
||||
export interface ListActionsWithoutToolbar {
|
||||
toggle: (id: string) => void;
|
||||
toggleAll: (items: React.ReactNodeArray, selected: number) => void;
|
||||
toggleAll: (items: Node[], selected: number) => void;
|
||||
isChecked: (id: string) => boolean;
|
||||
selected: number;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
function getPermissionGroupErrorMessage(
|
||||
err: PermissionGroupErrorFragment,
|
||||
err: PermissionGroupErrorFragment | undefined,
|
||||
intl: IntlShape,
|
||||
): string | undefined {
|
||||
if (err) {
|
||||
|
|
Loading…
Reference in a new issue