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,
|
requestLoginByExternalPlugin: undefined,
|
||||||
authenticating: false,
|
authenticating: false,
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
|
refetchUser: undefined,
|
||||||
user: {
|
user: {
|
||||||
id: "0",
|
id: "0",
|
||||||
email: "email@email.me",
|
email: "email@email.me",
|
||||||
|
@ -24,6 +25,8 @@ export const MockedUserProvider: React.FC<{
|
||||||
userPermissions: customPermissions ?? adminUserPermissions,
|
userPermissions: customPermissions ?? adminUserPermissions,
|
||||||
avatar: null,
|
avatar: null,
|
||||||
__typename: "User",
|
__typename: "User",
|
||||||
|
accessibleChannels: [],
|
||||||
|
restrictedAccessToChannels: false,
|
||||||
},
|
},
|
||||||
errors: [],
|
errors: [],
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -130685,10 +130685,7 @@
|
||||||
"name": "deprecated",
|
"name": "deprecated",
|
||||||
"description": "Marks an element of a GraphQL schema as no longer supported.",
|
"description": "Marks an element of a GraphQL schema as no longer supported.",
|
||||||
"isRepeatable": false,
|
"isRepeatable": false,
|
||||||
"locations": [
|
"locations": ["ENUM_VALUE", "FIELD_DEFINITION"],
|
||||||
"ENUM_VALUE",
|
|
||||||
"FIELD_DEFINITION"
|
|
||||||
],
|
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
"name": "reason",
|
"name": "reason",
|
||||||
|
@ -130738,11 +130735,7 @@
|
||||||
"name": "include",
|
"name": "include",
|
||||||
"description": "Directs the executor to include this field or fragment only when the `if` argument is true.",
|
"description": "Directs the executor to include this field or fragment only when the `if` argument is true.",
|
||||||
"isRepeatable": false,
|
"isRepeatable": false,
|
||||||
"locations": [
|
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
|
||||||
"FIELD",
|
|
||||||
"FRAGMENT_SPREAD",
|
|
||||||
"INLINE_FRAGMENT"
|
|
||||||
],
|
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
"name": "if",
|
"name": "if",
|
||||||
|
@ -130766,11 +130759,7 @@
|
||||||
"name": "skip",
|
"name": "skip",
|
||||||
"description": "Directs the executor to skip this field or fragment when the `if` argument is true.",
|
"description": "Directs the executor to skip this field or fragment when the `if` argument is true.",
|
||||||
"isRepeatable": false,
|
"isRepeatable": false,
|
||||||
"locations": [
|
"locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
|
||||||
"FIELD",
|
|
||||||
"FRAGMENT_SPREAD",
|
|
||||||
"INLINE_FRAGMENT"
|
|
||||||
],
|
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
"name": "if",
|
"name": "if",
|
||||||
|
@ -130794,12 +130783,7 @@
|
||||||
"name": "webhookEventsInfo",
|
"name": "webhookEventsInfo",
|
||||||
"description": "Webhook events triggered by a specific location.",
|
"description": "Webhook events triggered by a specific location.",
|
||||||
"isRepeatable": false,
|
"isRepeatable": false,
|
||||||
"locations": [
|
"locations": ["FIELD", "FIELD_DEFINITION", "INPUT_OBJECT", "OBJECT"],
|
||||||
"FIELD",
|
|
||||||
"FIELD_DEFINITION",
|
|
||||||
"INPUT_OBJECT",
|
|
||||||
"OBJECT"
|
|
||||||
],
|
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
"name": "asyncEvents",
|
"name": "asyncEvents",
|
||||||
|
@ -130853,4 +130837,4 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -349,6 +349,9 @@
|
||||||
"context": "min price in channel",
|
"context": "min price in channel",
|
||||||
"string": "Min. value"
|
"string": "Min. value"
|
||||||
},
|
},
|
||||||
|
"0HBlkO": {
|
||||||
|
"string": "Search channels"
|
||||||
|
},
|
||||||
"0KmZCN": {
|
"0KmZCN": {
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "Open playground"
|
"string": "Open playground"
|
||||||
|
@ -1509,6 +1512,9 @@
|
||||||
"context": "modal button images upload",
|
"context": "modal button images upload",
|
||||||
"string": "Upload Images"
|
"string": "Upload Images"
|
||||||
},
|
},
|
||||||
|
"9FGTOt": {
|
||||||
|
"string": "Allow access to orders of all channels"
|
||||||
|
},
|
||||||
"9IWg/f": {
|
"9IWg/f": {
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "SETUP END DATE"
|
"string": "SETUP END DATE"
|
||||||
|
@ -6248,6 +6254,9 @@
|
||||||
"context": "page header",
|
"context": "page header",
|
||||||
"string": "Create Page"
|
"string": "Create Page"
|
||||||
},
|
},
|
||||||
|
"grkY2V": {
|
||||||
|
"string": "You don't have access to any channels"
|
||||||
|
},
|
||||||
"gvOzOl": {
|
"gvOzOl": {
|
||||||
"string": "Page Title"
|
"string": "Page Title"
|
||||||
},
|
},
|
||||||
|
@ -6979,10 +6988,6 @@
|
||||||
"context": "add authorization key error",
|
"context": "add authorization key error",
|
||||||
"string": "Authorization key with this type already exists"
|
"string": "Authorization key with this type already exists"
|
||||||
},
|
},
|
||||||
"mAabef": {
|
|
||||||
"context": "checkbox label",
|
|
||||||
"string": "Group has full access to the store"
|
|
||||||
},
|
|
||||||
"mCP0UD": {
|
"mCP0UD": {
|
||||||
"context": "order draft creation date",
|
"context": "order draft creation date",
|
||||||
"string": "Date"
|
"string": "Date"
|
||||||
|
@ -8323,10 +8328,16 @@
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "Create category"
|
"string": "Create category"
|
||||||
},
|
},
|
||||||
|
"vprU7C": {
|
||||||
|
"string": "Select visible order channels"
|
||||||
|
},
|
||||||
"vwMO04": {
|
"vwMO04": {
|
||||||
"context": "draft order",
|
"context": "draft order",
|
||||||
"string": "Created"
|
"string": "Created"
|
||||||
},
|
},
|
||||||
|
"vz3yxp": {
|
||||||
|
"string": "Channels permissions"
|
||||||
|
},
|
||||||
"vzce9B": {
|
"vzce9B": {
|
||||||
"context": "customer gift cards card subtitle",
|
"context": "customer gift cards card subtitle",
|
||||||
"string": "Only five newest gift cards are shown here"
|
"string": "Only five newest gift cards are shown here"
|
||||||
|
|
|
@ -245,6 +245,7 @@ export function useAuthProvider({
|
||||||
authenticating: authenticating && !errors.length,
|
authenticating: authenticating && !errors.length,
|
||||||
authenticated: authenticated && !!user?.isStaff && !errors.length,
|
authenticated: authenticated && !!user?.isStaff && !errors.length,
|
||||||
user: userDetails.data?.me,
|
user: userDetails.data?.me,
|
||||||
|
refetchUser: userDetails.refetch,
|
||||||
errors,
|
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,
|
authenticating: false,
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
errors: [],
|
errors: [],
|
||||||
|
refetchUser: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const AuthRouter: React.FC = () => (
|
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 {
|
import {
|
||||||
GetExternalAccessTokenData,
|
GetExternalAccessTokenData,
|
||||||
GetExternalAuthUrlData,
|
GetExternalAuthUrlData,
|
||||||
|
@ -44,4 +45,5 @@ export interface UserContext {
|
||||||
authenticating: boolean;
|
authenticating: boolean;
|
||||||
authenticated: boolean;
|
authenticated: boolean;
|
||||||
errors: UserContextError[];
|
errors: UserContextError[];
|
||||||
|
refetchUser: () => Promise<ApolloQueryResult<UserDetailsQuery>>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,13 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import { useUser } from "@dashboard/auth";
|
import { useUser } from "@dashboard/auth";
|
||||||
import CardTitle from "@dashboard/components/CardTitle";
|
import { PermissionData } from "@dashboard/permissionGroups/components/PermissionGroupDetailsPage";
|
||||||
import Skeleton from "@dashboard/components/Skeleton";
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
import { PermissionData } from "@dashboard/permissionGroups/components/PermissionGroupDetailsPage/PermissionGroupDetailsPage";
|
import React, { ChangeEvent } from "react";
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
Checkbox,
|
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListItemIcon,
|
|
||||||
ListItemText,
|
|
||||||
Typography,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import React from "react";
|
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
const byAlphabeticalOrder =
|
import { Header } from "./components/Header";
|
||||||
<T extends {}>(field: string) =>
|
import { PermissionsExceeded } from "./components/PermissionExeeded";
|
||||||
(a: T, b: T) =>
|
import { PermissionList } from "./components/PermissionList";
|
||||||
a[field].localeCompare(b[field]);
|
import { messages } from "./messages";
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
checkboxContainer: {
|
|
||||||
marginTop: theme.spacing(),
|
|
||||||
},
|
|
||||||
hr: {
|
|
||||||
backgroundColor: theme.palette.divider,
|
|
||||||
border: "none",
|
|
||||||
height: 1,
|
|
||||||
marginBottom: 0,
|
|
||||||
marginTop: 0,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "AccountPermissions" },
|
|
||||||
);
|
|
||||||
|
|
||||||
interface AccountPermissionsProps {
|
interface AccountPermissionsProps {
|
||||||
permissions: PermissionData[];
|
permissions: PermissionData[];
|
||||||
|
@ -47,7 +18,7 @@ interface AccountPermissionsProps {
|
||||||
};
|
};
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
errorMessage: string;
|
errorMessage: string | undefined;
|
||||||
fullAccessLabel: string;
|
fullAccessLabel: string;
|
||||||
onChange: (event: React.ChangeEvent<any>, cb?: () => void) => void;
|
onChange: (event: React.ChangeEvent<any>, cb?: () => void) => void;
|
||||||
}
|
}
|
||||||
|
@ -63,11 +34,10 @@ const AccountPermissions: React.FC<AccountPermissionsProps> = props => {
|
||||||
errorMessage,
|
errorMessage,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const permissions = Object.values(props?.permissions ?? {}).sort(
|
const permissions = Object.values(props?.permissions ?? {}).sort((a, b) =>
|
||||||
byAlphabeticalOrder("name"),
|
a.name.localeCompare(b.name),
|
||||||
);
|
);
|
||||||
|
|
||||||
const classes = useStyles(props);
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
|
@ -75,161 +45,99 @@ const AccountPermissions: React.FC<AccountPermissionsProps> = props => {
|
||||||
onChange({
|
onChange({
|
||||||
target: {
|
target: {
|
||||||
name: "permissions",
|
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({
|
onChange({
|
||||||
target: {
|
target: {
|
||||||
name: "hasFullAccess",
|
name: "hasFullAccess",
|
||||||
value: !data.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({
|
onChange({
|
||||||
target: {
|
target: {
|
||||||
name: "permissions",
|
name: "permissions",
|
||||||
value: !value
|
value: updatedPersmissions,
|
||||||
? data.permissions.concat([key])
|
|
||||||
: data.permissions.filter(perm => perm !== key),
|
|
||||||
},
|
},
|
||||||
} as any);
|
} as ChangeEvent<any>);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Box paddingX={9} paddingY={9} paddingBottom={0}>
|
||||||
<CardTitle
|
<Text as="p" variant="bodyEmp" size="large" marginBottom={7}>
|
||||||
title={intl.formatMessage({
|
{intl.formatMessage(messages.title)}
|
||||||
id: "Fbr4Vp",
|
</Text>
|
||||||
defaultMessage: "Permissions",
|
|
||||||
description: "dialog header",
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{permissionsExceeded && (
|
{permissionsExceeded && (
|
||||||
<>
|
<PermissionsExceeded userPermissions={user?.userPermissions ?? []} />
|
||||||
<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 && (
|
{!permissionsExceeded && (
|
||||||
<>
|
<>
|
||||||
<CardContent>
|
<Header
|
||||||
<Typography variant="body2">{description}</Typography>
|
disabled={disabled}
|
||||||
<ListItem
|
description={description}
|
||||||
role={undefined}
|
fullAccessLabel={fullAccessLabel}
|
||||||
dense
|
hasFullAccess={data.hasFullAccess}
|
||||||
button
|
onFullAccessChange={handleFullAccessChange}
|
||||||
onClick={handleFullAccessChange}
|
/>
|
||||||
>
|
|
||||||
<ListItemIcon>
|
<Box
|
||||||
<Checkbox
|
width="100%"
|
||||||
data-test-id="full-access"
|
borderBottomStyle="solid"
|
||||||
color="secondary"
|
borderBottomWidth={1}
|
||||||
edge="start"
|
borderColor="neutralPlain"
|
||||||
checked={data.hasFullAccess}
|
height={1}
|
||||||
disabled={disabled}
|
margin={0}
|
||||||
tabIndex={-1}
|
/>
|
||||||
disableRipple
|
|
||||||
inputProps={{ "aria-labelledby": "fullAccess" }}
|
<PermissionList
|
||||||
/>
|
disabled={disabled}
|
||||||
</ListItemIcon>
|
permissions={permissions}
|
||||||
<ListItemText primary={fullAccessLabel} />
|
onPermissionChange={handlePermissionChange}
|
||||||
</ListItem>
|
selectedPermissions={data.permissions}
|
||||||
</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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{!!errorMessage && (
|
{!!errorMessage && (
|
||||||
<>
|
<>
|
||||||
<hr className={classes.hr} />
|
<Box
|
||||||
<CardContent>
|
width="100%"
|
||||||
<Typography variant="body2" color="error">
|
borderBottomStyle="solid"
|
||||||
{errorMessage}
|
borderBottomWidth={1}
|
||||||
</Typography>
|
borderColor="neutralPlain"
|
||||||
</CardContent>
|
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 {
|
import {
|
||||||
PermissionGroupDetailsQuery,
|
PermissionEnum,
|
||||||
ShopInfoQuery,
|
PermissionFragment,
|
||||||
UserDetailsQuery,
|
UserPermissionFragment,
|
||||||
|
UserUserPermissionWithSourcePermissionGroupsFragment,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
|
|
||||||
export const getLastSourcesOfPermission = (
|
export const getLastSourcesOfPermission = (
|
||||||
groupId: string,
|
groupId: string,
|
||||||
userPermissions: PermissionGroupDetailsQuery["user"]["userPermissions"],
|
userPermissions: Array<
|
||||||
|
NonNullable<UserUserPermissionWithSourcePermissionGroupsFragment>
|
||||||
|
>,
|
||||||
) =>
|
) =>
|
||||||
userPermissions
|
userPermissions
|
||||||
.filter(
|
.filter(
|
||||||
perm =>
|
perm =>
|
||||||
perm.sourcePermissionGroups.length === 1 &&
|
perm.sourcePermissionGroups?.length === 1 &&
|
||||||
perm.sourcePermissionGroups[0].id === groupId,
|
perm.sourcePermissionGroups[0]?.id === groupId,
|
||||||
)
|
)
|
||||||
.map(perm => perm.code);
|
.map(perm => perm.code);
|
||||||
|
|
||||||
export const getPermissionsComponentChoices = (
|
export const getPermissionsComponentChoices = (
|
||||||
userPermissions: UserDetailsQuery["me"]["userPermissions"],
|
userPermissions: UserPermissionFragment[],
|
||||||
shopPermissions: ShopInfoQuery["shop"]["permissions"],
|
shopPermissions: PermissionFragment[],
|
||||||
lastSourcesOfPermissionIds: string[],
|
lastSourcesOfPermissionIds: string[],
|
||||||
) => {
|
) => {
|
||||||
const userCodes = userPermissions.map(p => p.code) || [];
|
const userCodes = userPermissions.map(p => p.code) || [];
|
||||||
|
@ -31,3 +33,8 @@ export const getPermissionsComponentChoices = (
|
||||||
lastSource: lastSourcesOfPermissionIds.includes(perm.code),
|
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);
|
const [isPickerActive, setPickerActive] = React.useState(false);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (
|
const channels = user?.accessibleChannels ?? [];
|
||||||
!isValidChannel(selectedChannel, channelData?.channels) &&
|
const isValid = isValidChannel(selectedChannel, channels);
|
||||||
channelData?.channels?.length > 0
|
|
||||||
) {
|
if (!isValid && channels?.length > 0) {
|
||||||
setSelectedChannel(channelData.channels[0].id);
|
setSelectedChannel(channels[0].id);
|
||||||
}
|
}
|
||||||
}, [channelData]);
|
|
||||||
|
if (!isValid && selectedChannel !== "") {
|
||||||
|
setSelectedChannel("");
|
||||||
|
}
|
||||||
|
}, [selectedChannel, setSelectedChannel, user]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setChannel(selectedChannel);
|
setChannel(selectedChannel);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useUser } from "@dashboard/auth";
|
||||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
import React, { PropsWithChildren } from "react";
|
import React, { PropsWithChildren } from "react";
|
||||||
|
|
||||||
|
@ -20,8 +21,9 @@ export const Root: React.FC<PropsWithChildren<TopNavProps>> = ({
|
||||||
isAlignToRight = true,
|
isAlignToRight = true,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { availableChannels, channel, isPickerActive, setChannel } =
|
const { channel, isPickerActive, setChannel } = useAppChannel(false);
|
||||||
useAppChannel(false);
|
const user = useUser();
|
||||||
|
const channels = user?.user?.accessibleChannels ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TopNavWrapper withoutBorder={withoutBorder}>
|
<TopNavWrapper withoutBorder={withoutBorder}>
|
||||||
|
@ -37,9 +39,9 @@ export const Root: React.FC<PropsWithChildren<TopNavProps>> = ({
|
||||||
height="100%"
|
height="100%"
|
||||||
__flex={isAlignToRight ? "initial" : 1}
|
__flex={isAlignToRight ? "initial" : 1}
|
||||||
>
|
>
|
||||||
{isPickerActive && (
|
{isPickerActive && channels.length > 0 && (
|
||||||
<AppChannelSelect
|
<AppChannelSelect
|
||||||
channels={availableChannels}
|
channels={channels}
|
||||||
selectedChannelId={channel?.id}
|
selectedChannelId={channel?.id}
|
||||||
onChannelSelect={setChannel}
|
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
|
name
|
||||||
trackInventoryByDefault
|
trackInventoryByDefault
|
||||||
permissions {
|
permissions {
|
||||||
code
|
...Permission
|
||||||
name
|
|
||||||
}
|
}
|
||||||
version
|
version
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ const user = {
|
||||||
lastName: "Newton",
|
lastName: "Newton",
|
||||||
note: null,
|
note: null,
|
||||||
userPermissions: staffMember.userPermissions,
|
userPermissions: staffMember.userPermissions,
|
||||||
|
restrictedAccessToChannels: false,
|
||||||
|
accessibleChannels: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const versions = {
|
const versions = {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import {
|
import {
|
||||||
|
AllocationStrategyEnum,
|
||||||
|
ChannelFragment,
|
||||||
PermissionEnum,
|
PermissionEnum,
|
||||||
ShopInfoQuery,
|
ShopInfoQuery,
|
||||||
ShopLimitFragment,
|
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"] = [
|
export const permissions: ShopInfoQuery["shop"]["permissions"] = [
|
||||||
{
|
{
|
||||||
code: PermissionEnum.MANAGE_DISCOUNTS,
|
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`
|
export const fragmentUser = gql`
|
||||||
fragment User on User {
|
fragment User on User {
|
||||||
id
|
id
|
||||||
|
@ -17,9 +26,13 @@ export const fragmentUser = gql`
|
||||||
userPermissions {
|
userPermissions {
|
||||||
...UserPermission
|
...UserPermission
|
||||||
}
|
}
|
||||||
avatar {
|
avatar(size: 128) {
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
accessibleChannels {
|
||||||
|
...Channel
|
||||||
|
}
|
||||||
|
restrictedAccessToChannels
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,10 @@ export const permissionGroupMember = gql`
|
||||||
export const permissionGroupDetailsFragment = gql`
|
export const permissionGroupDetailsFragment = gql`
|
||||||
fragment PermissionGroupDetails on Group {
|
fragment PermissionGroupDetails on Group {
|
||||||
...PermissionGroup
|
...PermissionGroup
|
||||||
|
restrictedAccessToChannels
|
||||||
|
accessibleChannels {
|
||||||
|
...Channel
|
||||||
|
}
|
||||||
permissions {
|
permissions {
|
||||||
...Permission
|
...Permission
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,6 +171,30 @@ export const UserPermissionFragmentDoc = gql`
|
||||||
name
|
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`
|
export const UserFragmentDoc = gql`
|
||||||
fragment User on User {
|
fragment User on User {
|
||||||
id
|
id
|
||||||
|
@ -181,11 +205,16 @@ export const UserFragmentDoc = gql`
|
||||||
userPermissions {
|
userPermissions {
|
||||||
...UserPermission
|
...UserPermission
|
||||||
}
|
}
|
||||||
avatar {
|
avatar(size: 128) {
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
accessibleChannels {
|
||||||
|
...Channel
|
||||||
|
}
|
||||||
|
restrictedAccessToChannels
|
||||||
}
|
}
|
||||||
${UserPermissionFragmentDoc}`;
|
${UserPermissionFragmentDoc}
|
||||||
|
${ChannelFragmentDoc}`;
|
||||||
export const CategoryFragmentDoc = gql`
|
export const CategoryFragmentDoc = gql`
|
||||||
fragment Category on Category {
|
fragment Category on Category {
|
||||||
id
|
id
|
||||||
|
@ -223,22 +252,6 @@ export const ChannelErrorFragmentDoc = gql`
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const ChannelFragmentDoc = gql`
|
|
||||||
fragment Channel on Channel {
|
|
||||||
id
|
|
||||||
isActive
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
currencyCode
|
|
||||||
defaultCountry {
|
|
||||||
code
|
|
||||||
country
|
|
||||||
}
|
|
||||||
stockSettings {
|
|
||||||
allocationStrategy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export const WarehouseFragmentDoc = gql`
|
export const WarehouseFragmentDoc = gql`
|
||||||
fragment Warehouse on Warehouse {
|
fragment Warehouse on Warehouse {
|
||||||
id
|
id
|
||||||
|
@ -2117,6 +2130,10 @@ export const PermissionGroupMemberFragmentDoc = gql`
|
||||||
export const PermissionGroupDetailsFragmentDoc = gql`
|
export const PermissionGroupDetailsFragmentDoc = gql`
|
||||||
fragment PermissionGroupDetails on Group {
|
fragment PermissionGroupDetails on Group {
|
||||||
...PermissionGroup
|
...PermissionGroup
|
||||||
|
restrictedAccessToChannels
|
||||||
|
accessibleChannels {
|
||||||
|
...Channel
|
||||||
|
}
|
||||||
permissions {
|
permissions {
|
||||||
...Permission
|
...Permission
|
||||||
}
|
}
|
||||||
|
@ -2125,6 +2142,7 @@ export const PermissionGroupDetailsFragmentDoc = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${PermissionGroupFragmentDoc}
|
${PermissionGroupFragmentDoc}
|
||||||
|
${ChannelFragmentDoc}
|
||||||
${PermissionFragmentDoc}
|
${PermissionFragmentDoc}
|
||||||
${PermissionGroupMemberFragmentDoc}`;
|
${PermissionGroupMemberFragmentDoc}`;
|
||||||
export const PluginConfigurationBaseFragmentDoc = gql`
|
export const PluginConfigurationBaseFragmentDoc = gql`
|
||||||
|
@ -6138,14 +6156,14 @@ export const ShopInfoDocument = gql`
|
||||||
name
|
name
|
||||||
trackInventoryByDefault
|
trackInventoryByDefault
|
||||||
permissions {
|
permissions {
|
||||||
code
|
...Permission
|
||||||
name
|
|
||||||
}
|
}
|
||||||
version
|
version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${CountryWithCodeFragmentDoc}
|
${CountryWithCodeFragmentDoc}
|
||||||
${LanguageFragmentDoc}`;
|
${LanguageFragmentDoc}
|
||||||
|
${PermissionFragmentDoc}`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useShopInfoQuery__
|
* __useShopInfoQuery__
|
||||||
|
@ -8618,20 +8636,20 @@ export type CustomerGiftCardListQueryHookResult = ReturnType<typeof useCustomerG
|
||||||
export type CustomerGiftCardListLazyQueryHookResult = ReturnType<typeof useCustomerGiftCardListLazyQuery>;
|
export type CustomerGiftCardListLazyQueryHookResult = ReturnType<typeof useCustomerGiftCardListLazyQuery>;
|
||||||
export type CustomerGiftCardListQueryResult = Apollo.QueryResult<Types.CustomerGiftCardListQuery, Types.CustomerGiftCardListQueryVariables>;
|
export type CustomerGiftCardListQueryResult = Apollo.QueryResult<Types.CustomerGiftCardListQuery, Types.CustomerGiftCardListQueryVariables>;
|
||||||
export const HomeDocument = gql`
|
export const HomeDocument = gql`
|
||||||
query Home($channel: String!, $datePeriod: DateRangeInput!, $PERMISSION_MANAGE_PRODUCTS: Boolean!, $PERMISSION_MANAGE_ORDERS: Boolean!) {
|
query Home($channel: String!, $datePeriod: DateRangeInput!, $hasPermissionToManageProducts: Boolean!, $hasPermissionToManageOrders: Boolean!) {
|
||||||
salesToday: ordersTotal(period: TODAY, channel: $channel) @include(if: $PERMISSION_MANAGE_ORDERS) {
|
salesToday: ordersTotal(period: TODAY, channel: $channel) @include(if: $hasPermissionToManageOrders) {
|
||||||
gross {
|
gross {
|
||||||
amount
|
amount
|
||||||
currency
|
currency
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ordersToday: orders(filter: {created: $datePeriod}, channel: $channel) @include(if: $PERMISSION_MANAGE_ORDERS) {
|
ordersToday: orders(filter: {created: $datePeriod}, channel: $channel) @include(if: $hasPermissionToManageOrders) {
|
||||||
totalCount
|
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
|
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
|
totalCount
|
||||||
}
|
}
|
||||||
productsOutOfStock: products(
|
productsOutOfStock: products(
|
||||||
|
@ -8640,7 +8658,7 @@ export const HomeDocument = gql`
|
||||||
) {
|
) {
|
||||||
totalCount
|
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 {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
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 {
|
edges {
|
||||||
node {
|
node {
|
||||||
amount
|
amount
|
||||||
|
@ -8705,8 +8723,8 @@ export const HomeDocument = gql`
|
||||||
* variables: {
|
* variables: {
|
||||||
* channel: // value for 'channel'
|
* channel: // value for 'channel'
|
||||||
* datePeriod: // value for 'datePeriod'
|
* datePeriod: // value for 'datePeriod'
|
||||||
* PERMISSION_MANAGE_PRODUCTS: // value for 'PERMISSION_MANAGE_PRODUCTS'
|
* hasPermissionToManageProducts: // value for 'hasPermissionToManageProducts'
|
||||||
* PERMISSION_MANAGE_ORDERS: // value for 'PERMISSION_MANAGE_ORDERS'
|
* hasPermissionToManageOrders: // value for 'hasPermissionToManageOrders'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8368,7 +8368,7 @@ export type AvailableExternalAuthenticationsQuery = { __typename: 'Query', shop:
|
||||||
export type UserDetailsQueryVariables = Exact<{ [key: string]: never; }>;
|
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<{
|
export type CategoryDeleteMutationVariables = Exact<{
|
||||||
id: Scalars['ID'];
|
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 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 };
|
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 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 };
|
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<{
|
export type HomeQueryVariables = Exact<{
|
||||||
channel: Scalars['String'];
|
channel: Scalars['String'];
|
||||||
datePeriod: DateRangeInput;
|
datePeriod: DateRangeInput;
|
||||||
PERMISSION_MANAGE_PRODUCTS: Scalars['Boolean'];
|
hasPermissionToManageProducts: Scalars['Boolean'];
|
||||||
PERMISSION_MANAGE_ORDERS: 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<{
|
export type PermissionGroupUpdateMutationVariables = Exact<{
|
||||||
id: Scalars['ID'];
|
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<{
|
export type PermissionGroupListQueryVariables = Exact<{
|
||||||
after?: InputMaybe<Scalars['String']>;
|
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<{
|
export type PluginUpdateMutationVariables = Exact<{
|
||||||
channelId?: InputMaybe<Scalars['ID']>;
|
channelId?: InputMaybe<Scalars['ID']>;
|
||||||
|
|
|
@ -4,30 +4,30 @@ export const home = gql`
|
||||||
query Home(
|
query Home(
|
||||||
$channel: String!
|
$channel: String!
|
||||||
$datePeriod: DateRangeInput!
|
$datePeriod: DateRangeInput!
|
||||||
$PERMISSION_MANAGE_PRODUCTS: Boolean!
|
$hasPermissionToManageProducts: Boolean!
|
||||||
$PERMISSION_MANAGE_ORDERS: Boolean!
|
$hasPermissionToManageOrders: Boolean!
|
||||||
) {
|
) {
|
||||||
salesToday: ordersTotal(period: TODAY, channel: $channel)
|
salesToday: ordersTotal(period: TODAY, channel: $channel)
|
||||||
@include(if: $PERMISSION_MANAGE_ORDERS) {
|
@include(if: $hasPermissionToManageOrders) {
|
||||||
gross {
|
gross {
|
||||||
amount
|
amount
|
||||||
currency
|
currency
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ordersToday: orders(filter: { created: $datePeriod }, channel: $channel)
|
ordersToday: orders(filter: { created: $datePeriod }, channel: $channel)
|
||||||
@include(if: $PERMISSION_MANAGE_ORDERS) {
|
@include(if: $hasPermissionToManageOrders) {
|
||||||
totalCount
|
totalCount
|
||||||
}
|
}
|
||||||
ordersToFulfill: orders(
|
ordersToFulfill: orders(
|
||||||
filter: { status: READY_TO_FULFILL }
|
filter: { status: READY_TO_FULFILL }
|
||||||
channel: $channel
|
channel: $channel
|
||||||
) @include(if: $PERMISSION_MANAGE_ORDERS) {
|
) @include(if: $hasPermissionToManageOrders) {
|
||||||
totalCount
|
totalCount
|
||||||
}
|
}
|
||||||
ordersToCapture: orders(
|
ordersToCapture: orders(
|
||||||
filter: { status: READY_TO_CAPTURE }
|
filter: { status: READY_TO_CAPTURE }
|
||||||
channel: $channel
|
channel: $channel
|
||||||
) @include(if: $PERMISSION_MANAGE_ORDERS) {
|
) @include(if: $hasPermissionToManageOrders) {
|
||||||
totalCount
|
totalCount
|
||||||
}
|
}
|
||||||
productsOutOfStock: products(
|
productsOutOfStock: products(
|
||||||
|
@ -40,7 +40,7 @@ export const home = gql`
|
||||||
period: TODAY
|
period: TODAY
|
||||||
first: 5
|
first: 5
|
||||||
channel: $channel
|
channel: $channel
|
||||||
) @include(if: $PERMISSION_MANAGE_PRODUCTS) {
|
) @include(if: $hasPermissionToManageProducts) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
|
@ -68,7 +68,7 @@ export const home = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activities: homepageEvents(last: 10)
|
activities: homepageEvents(last: 10)
|
||||||
@include(if: $PERMISSION_MANAGE_ORDERS) {
|
@include(if: $hasPermissionToManageOrders) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
amount
|
amount
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
import { useUser } from "@dashboard/auth";
|
import { useUser } from "@dashboard/auth";
|
||||||
import { channelsListUrl } from "@dashboard/channels/urls";
|
import { channelsListUrl } from "@dashboard/channels/urls";
|
||||||
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||||
|
import { hasPermissions } from "@dashboard/components/RequirePermissions";
|
||||||
import {
|
import {
|
||||||
OrderStatusFilter,
|
OrderStatusFilter,
|
||||||
|
PermissionEnum,
|
||||||
StockAvailability,
|
StockAvailability,
|
||||||
useHomeQuery,
|
useHomeQuery,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
|
@ -21,10 +23,21 @@ const HomeSection = () => {
|
||||||
|
|
||||||
const noChannel = !channel && typeof channel !== "undefined";
|
const noChannel = !channel && typeof channel !== "undefined";
|
||||||
|
|
||||||
|
const userPermissions = user?.userPermissions || [];
|
||||||
|
|
||||||
const { data } = useHomeQuery({
|
const { data } = useHomeQuery({
|
||||||
displayLoader: true,
|
displayLoader: true,
|
||||||
skip: noChannel,
|
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 (
|
return (
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useUserAccessibleChannels } from "@dashboard/auth/hooks/useUserAccessibleChannels";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout";
|
import { TopNav } from "@dashboard/components/AppLayout";
|
||||||
import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo";
|
import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo";
|
||||||
import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect";
|
import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect";
|
||||||
|
@ -5,7 +6,7 @@ import { RefreshLimitsQuery } from "@dashboard/graphql";
|
||||||
import { sectionNames } from "@dashboard/intl";
|
import { sectionNames } from "@dashboard/intl";
|
||||||
import { FilterPresetsProps } from "@dashboard/types";
|
import { FilterPresetsProps } from "@dashboard/types";
|
||||||
import { hasLimits, isLimitReached } from "@dashboard/utils/limits";
|
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 React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -33,6 +34,8 @@ export const OrderDraftListHeader = ({
|
||||||
onAdd,
|
onAdd,
|
||||||
}: OrderDraftListHeaderProps) => {
|
}: OrderDraftListHeaderProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const userAccessibleChannels = useUserAccessibleChannels();
|
||||||
|
const hasAccessibleChannels = userAccessibleChannels.length > 0;
|
||||||
const limitsReached = isLimitReached(limits, "orders");
|
const limitsReached = isLimitReached(limits, "orders");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -71,18 +74,30 @@ export const OrderDraftListHeader = ({
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box display="flex" alignItems="center" gap={2}>
|
<Box display="flex" alignItems="center" gap={2}>
|
||||||
<Button
|
<Tooltip>
|
||||||
variant="primary"
|
<Tooltip.Trigger>
|
||||||
disabled={disabled || limitsReached}
|
<Button
|
||||||
onClick={onAdd}
|
variant="primary"
|
||||||
data-test-id="create-draft-order-button"
|
disabled={disabled || limitsReached || !hasAccessibleChannels}
|
||||||
>
|
onClick={onAdd}
|
||||||
<FormattedMessage
|
data-test-id="create-draft-order-button"
|
||||||
id="LshEVn"
|
>
|
||||||
defaultMessage="Create order"
|
<FormattedMessage
|
||||||
description="button"
|
id="LshEVn"
|
||||||
/>
|
defaultMessage="Create order"
|
||||||
</Button>
|
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") && (
|
{hasLimits(limits, "orders") && (
|
||||||
<LimitsInfo
|
<LimitsInfo
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
mapToMenuItems,
|
mapToMenuItems,
|
||||||
useExtensions,
|
useExtensions,
|
||||||
} from "@dashboard/apps/hooks/useExtensions";
|
} from "@dashboard/apps/hooks/useExtensions";
|
||||||
|
import { useUserAccessibleChannels } from "@dashboard/auth/hooks/useUserAccessibleChannels";
|
||||||
import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo";
|
import { LimitsInfo } from "@dashboard/components/AppLayout/LimitsInfo";
|
||||||
import { ListFilters } from "@dashboard/components/AppLayout/ListFilters";
|
import { ListFilters } from "@dashboard/components/AppLayout/ListFilters";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
|
@ -29,7 +30,7 @@ import {
|
||||||
} from "@dashboard/types";
|
} from "@dashboard/types";
|
||||||
import { hasLimits, isLimitReached } from "@dashboard/utils/limits";
|
import { hasLimits, isLimitReached } from "@dashboard/utils/limits";
|
||||||
import { Card } from "@material-ui/core";
|
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 React, { useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -75,6 +76,8 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
|
||||||
...listProps
|
...listProps
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const userAccessibleChannels = useUserAccessibleChannels();
|
||||||
|
const hasAccessibleChannels = userAccessibleChannels.length > 0;
|
||||||
const filterStructure = createFilterStructure(intl, filterOpts);
|
const filterStructure = createFilterStructure(intl, filterOpts);
|
||||||
const limitsReached = isLimitReached(limits, "orders");
|
const limitsReached = isLimitReached(limits, "orders");
|
||||||
const [isFilterPresetOpen, setFilterPresetOpen] = useState(false);
|
const [isFilterPresetOpen, setFilterPresetOpen] = useState(false);
|
||||||
|
@ -162,32 +165,46 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{extensionCreateButtonItems.length > 0 ? (
|
|
||||||
<ButtonWithDropdown
|
<Tooltip>
|
||||||
onClick={onAdd}
|
<Tooltip.Trigger>
|
||||||
testId={"create-order-button"}
|
{extensionCreateButtonItems.length > 0 ? (
|
||||||
options={extensionCreateButtonItems}
|
<ButtonWithDropdown
|
||||||
disabled={limitsReached}
|
onClick={onAdd}
|
||||||
>
|
testId={"create-order-button"}
|
||||||
<FormattedMessage
|
options={extensionCreateButtonItems}
|
||||||
id="LshEVn"
|
disabled={limitsReached || !hasAccessibleChannels}
|
||||||
defaultMessage="Create order"
|
>
|
||||||
description="button"
|
<FormattedMessage
|
||||||
/>
|
id="LshEVn"
|
||||||
</ButtonWithDropdown>
|
defaultMessage="Create order"
|
||||||
) : (
|
description="button"
|
||||||
<Button
|
/>
|
||||||
data-test-id="create-order-button"
|
</ButtonWithDropdown>
|
||||||
onClick={onAdd}
|
) : (
|
||||||
disabled={limitsReached}
|
<Button
|
||||||
>
|
data-test-id="create-order-button"
|
||||||
<FormattedMessage
|
onClick={onAdd}
|
||||||
id="LshEVn"
|
disabled={limitsReached || !hasAccessibleChannels}
|
||||||
defaultMessage="Create order"
|
>
|
||||||
description="button"
|
<FormattedMessage
|
||||||
/>
|
id="LshEVn"
|
||||||
</Button>
|
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") && (
|
{hasLimits(limits, "orders") && (
|
||||||
<LimitsInfo
|
<LimitsInfo
|
||||||
text={intl.formatMessage(
|
text={intl.formatMessage(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
|
import { useUser } from "@dashboard/auth";
|
||||||
import ChannelPickerDialog from "@dashboard/channels/components/ChannelPickerDialog";
|
import ChannelPickerDialog from "@dashboard/channels/components/ChannelPickerDialog";
|
||||||
import ActionDialog from "@dashboard/components/ActionDialog";
|
import ActionDialog from "@dashboard/components/ActionDialog";
|
||||||
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
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({
|
const limitOpts = useShopLimitsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
orders: true,
|
orders: true,
|
||||||
|
@ -274,7 +278,7 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
||||||
tabName={presets[presetIdToDelete - 1]?.name ?? "..."}
|
tabName={presets[presetIdToDelete - 1]?.name ?? "..."}
|
||||||
/>
|
/>
|
||||||
<ChannelPickerDialog
|
<ChannelPickerDialog
|
||||||
channelsChoices={mapNodeToChoice(availableChannels)}
|
channelsChoices={mapNodeToChoice(channels)}
|
||||||
confirmButtonState="success"
|
confirmButtonState="success"
|
||||||
defaultChoice={channel?.id}
|
defaultChoice={channel?.id}
|
||||||
open={params.action === "create-order"}
|
open={params.action === "create-order"}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
|
import { useUser } from "@dashboard/auth";
|
||||||
import ChannelPickerDialog from "@dashboard/channels/components/ChannelPickerDialog";
|
import ChannelPickerDialog from "@dashboard/channels/components/ChannelPickerDialog";
|
||||||
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||||
import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog";
|
import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog";
|
||||||
|
@ -73,6 +74,9 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
||||||
usePaginationReset(orderListUrl, params, settings.rowNumber);
|
usePaginationReset(orderListUrl, params, settings.rowNumber);
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { channel, availableChannels } = useAppChannel(false);
|
||||||
|
const user = useUser();
|
||||||
|
const channels = user?.user?.accessibleChannels ?? [];
|
||||||
|
|
||||||
const [createOrder] = useOrderDraftCreateMutation({
|
const [createOrder] = useOrderDraftCreateMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
|
@ -87,7 +91,6 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { channel, availableChannels } = useAppChannel(false);
|
|
||||||
const limitOpts = useShopLimitsQuery({
|
const limitOpts = useShopLimitsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
orders: true,
|
orders: true,
|
||||||
|
@ -95,9 +98,7 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const noChannel = !channel && typeof channel !== "undefined";
|
const noChannel = !channel && typeof channel !== "undefined";
|
||||||
const channelOpts = availableChannels
|
const channelOpts = availableChannels ? mapNodeToChoice(channels) : null;
|
||||||
? mapNodeToChoice(availableChannels)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const [changeFilters, resetFilters, handleSearchChange] = useFilterHandlers({
|
const [changeFilters, resetFilters, handleSearchChange] = useFilterHandlers({
|
||||||
createUrl: orderListUrl,
|
createUrl: orderListUrl,
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
// @ts-strict-ignore
|
import { channels, permissions } from "@dashboard/fixtures";
|
||||||
import { permissions } from "@dashboard/fixtures";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { errorsOfPermissionGroupCreate } from "../../fixtures";
|
import { errorsOfPermissionGroupCreate } from "../../fixtures";
|
||||||
import PermissionGroupCreatePage, {
|
import {
|
||||||
|
PermissionGroupCreatePage,
|
||||||
PermissionGroupCreatePageProps,
|
PermissionGroupCreatePageProps,
|
||||||
} from "./PermissionGroupCreatePage";
|
} from "./PermissionGroupCreatePage";
|
||||||
|
|
||||||
const props: PermissionGroupCreatePageProps = {
|
const props: PermissionGroupCreatePageProps = {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
errors: [],
|
errors: [],
|
||||||
onSubmit: () => undefined,
|
onSubmit: () => new Promise(resolve => resolve(undefined)),
|
||||||
permissions,
|
permissions,
|
||||||
saveButtonBarState: undefined,
|
channels,
|
||||||
|
saveButtonBarState: "default",
|
||||||
|
hasRestrictedChannels: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import AccountPermissions from "@dashboard/components/AccountPermissions";
|
import AccountPermissions from "@dashboard/components/AccountPermissions";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
||||||
import { Backlink } from "@dashboard/components/Backlink";
|
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 Form from "@dashboard/components/Form";
|
||||||
|
import FormSpacer from "@dashboard/components/FormSpacer";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import {
|
import {
|
||||||
|
ChannelFragment,
|
||||||
PermissionEnum,
|
PermissionEnum,
|
||||||
PermissionGroupErrorFragment,
|
PermissionGroupErrorFragment,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import { SubmitPromise } from "@dashboard/hooks/useForm";
|
import { FormChange, SubmitPromise } from "@dashboard/hooks/useForm";
|
||||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
import { sectionNames } from "@dashboard/intl";
|
import { buttonMessages, sectionNames } from "@dashboard/intl";
|
||||||
import { permissionGroupListUrl } from "@dashboard/permissionGroups/urls";
|
import { permissionGroupListUrl } from "@dashboard/permissionGroups/urls";
|
||||||
import { getFormErrors } from "@dashboard/utils/errors";
|
import { getFormErrors } from "@dashboard/utils/errors";
|
||||||
import getPermissionGroupErrorMessage from "@dashboard/utils/errors/permissionGroups";
|
import getPermissionGroupErrorMessage from "@dashboard/utils/errors/permissionGroups";
|
||||||
|
import { Box } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -25,30 +27,40 @@ import PermissionGroupInfo from "../PermissionGroupInfo";
|
||||||
export interface PermissionGroupCreateFormData {
|
export interface PermissionGroupCreateFormData {
|
||||||
name: string;
|
name: string;
|
||||||
hasFullAccess: boolean;
|
hasFullAccess: boolean;
|
||||||
|
hasAllChannels: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
permissions: PermissionEnum[];
|
permissions: PermissionEnum[];
|
||||||
|
channels: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialForm: PermissionGroupCreateFormData = {
|
const initialForm: PermissionGroupCreateFormData = {
|
||||||
hasFullAccess: false,
|
hasFullAccess: false,
|
||||||
|
hasAllChannels: true,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
name: "",
|
name: "",
|
||||||
permissions: [],
|
permissions: [],
|
||||||
|
channels: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface PermissionGroupCreatePageProps {
|
export interface PermissionGroupCreatePageProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
errors: PermissionGroupErrorFragment[];
|
errors: PermissionGroupErrorFragment[];
|
||||||
permissions: PermissionData[];
|
permissions: PermissionData[];
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
channels: ChannelFragment[];
|
||||||
|
hasRestrictedChannels: boolean;
|
||||||
|
saveButtonBarState: "loading" | "success" | "error" | "default";
|
||||||
onSubmit: (data: PermissionGroupCreateFormData) => SubmitPromise;
|
onSubmit: (data: PermissionGroupCreateFormData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PermissionGroupCreatePage: React.FC<PermissionGroupCreatePageProps> = ({
|
export const PermissionGroupCreatePage: React.FC<
|
||||||
|
PermissionGroupCreatePageProps
|
||||||
|
> = ({
|
||||||
disabled,
|
disabled,
|
||||||
permissions,
|
permissions,
|
||||||
|
channels,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
saveButtonBarState,
|
saveButtonBarState,
|
||||||
|
hasRestrictedChannels,
|
||||||
errors,
|
errors,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
@ -63,55 +75,86 @@ const PermissionGroupCreatePage: React.FC<PermissionGroupCreatePageProps> = ({
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
confirmLeave
|
confirmLeave
|
||||||
initial={initialForm}
|
initial={{
|
||||||
|
...initialForm,
|
||||||
|
hasAllChannels: !hasRestrictedChannels,
|
||||||
|
}}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{({ data, change, submit, isSaveDisabled }) => (
|
{({ data, change, submit, isSaveDisabled }) => {
|
||||||
<DetailPageLayout>
|
const handleChannelChange: FormChange = event => {
|
||||||
<TopNav title="New Permission Group" />
|
change({
|
||||||
<DetailPageLayout.Content>
|
target: {
|
||||||
<Backlink href={permissionGroupListUrl()}>
|
name: "channels",
|
||||||
{intl.formatMessage(sectionNames.permissionGroups)}
|
value: event.target.value,
|
||||||
</Backlink>
|
},
|
||||||
<PermissionGroupInfo
|
});
|
||||||
data={data}
|
};
|
||||||
errors={errors}
|
|
||||||
onChange={change}
|
const handleHasAllChannelsChange = () => {
|
||||||
disabled={disabled}
|
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>
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
PermissionGroupCreatePage.displayName = "PermissionGroupCreatePage";
|
|
||||||
export default PermissionGroupCreatePage;
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export { default } from "./PermissionGroupCreatePage";
|
|
||||||
export * from "./PermissionGroupCreatePage";
|
export * from "./PermissionGroupCreatePage";
|
||||||
|
|
|
@ -1,30 +1,42 @@
|
||||||
// @ts-strict-ignore
|
import { channels, permissions } from "@dashboard/fixtures";
|
||||||
import { permissions } from "@dashboard/fixtures";
|
import { MembersListUrlSortField } from "@dashboard/permissionGroups/urls";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { emptyPermissionGroup, permissionGroup, users } from "../../fixtures";
|
import {
|
||||||
import PermissionGroupDetailsPage, {
|
emptyPermissionGroup,
|
||||||
PermissionGroupDetailsPageProps,
|
permissionGroup,
|
||||||
|
permissionGroupWithChannels,
|
||||||
|
users,
|
||||||
|
} from "../../fixtures";
|
||||||
|
import {
|
||||||
|
PermissionGroupDetailsPage,
|
||||||
|
PermissonGroupDetailsPageProps,
|
||||||
} from "./PermissionGroupDetailsPage";
|
} from "./PermissionGroupDetailsPage";
|
||||||
|
export * from "./PermissionGroupDetailsPage";
|
||||||
|
|
||||||
const props: PermissionGroupDetailsPageProps = {
|
const props: PermissonGroupDetailsPageProps = {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
isUserAbleToEditChannels: true,
|
||||||
errors: [],
|
errors: [],
|
||||||
isChecked: () => false,
|
isChecked: () => false,
|
||||||
members: users,
|
members: users,
|
||||||
onAssign: () => undefined,
|
onAssign: () => undefined,
|
||||||
onSort: () => undefined,
|
onSort: () => undefined,
|
||||||
onSubmit: () => undefined,
|
onSubmit: () => new Promise(resolve => resolve(undefined)),
|
||||||
onUnassign: () => undefined,
|
onUnassign: () => undefined,
|
||||||
permissionGroup,
|
permissionGroup,
|
||||||
permissions,
|
permissions,
|
||||||
permissionsExceeded: false,
|
permissionsExceeded: false,
|
||||||
saveButtonBarState: undefined,
|
saveButtonBarState: "default",
|
||||||
selected: 0,
|
selected: 0,
|
||||||
sort: null,
|
sort: {
|
||||||
|
asc: true,
|
||||||
|
sort: MembersListUrlSortField.name,
|
||||||
|
},
|
||||||
toggle: () => undefined,
|
toggle: () => undefined,
|
||||||
toggleAll: () => undefined,
|
toggleAll: () => undefined,
|
||||||
toolbar: null,
|
toolbar: null,
|
||||||
|
channels,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -45,7 +57,23 @@ export const Loading = () => (
|
||||||
<PermissionGroupDetailsPage
|
<PermissionGroupDetailsPage
|
||||||
{...props}
|
{...props}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
permissionGroup={undefined}
|
permissionGroup={permissionGroup}
|
||||||
permissions={undefined}
|
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 AccountPermissions from "@dashboard/components/AccountPermissions";
|
||||||
import { TopNav } from "@dashboard/components/AppLayout/TopNav";
|
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 Form from "@dashboard/components/Form";
|
||||||
import FormSpacer from "@dashboard/components/FormSpacer";
|
import FormSpacer from "@dashboard/components/FormSpacer";
|
||||||
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
import { DetailPageLayout } from "@dashboard/components/Layouts";
|
||||||
import Savebar from "@dashboard/components/Savebar";
|
import Savebar from "@dashboard/components/Savebar";
|
||||||
import {
|
import {
|
||||||
|
ChannelFragment,
|
||||||
PermissionEnum,
|
PermissionEnum,
|
||||||
PermissionGroupDetailsFragment,
|
PermissionGroupDetailsFragment,
|
||||||
PermissionGroupErrorFragment,
|
PermissionGroupErrorFragment,
|
||||||
UserPermissionFragment,
|
UserPermissionFragment,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import { SubmitPromise } from "@dashboard/hooks/useForm";
|
import { FormChange, SubmitPromise } from "@dashboard/hooks/useForm";
|
||||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
|
import { buttonMessages } from "@dashboard/intl";
|
||||||
import {
|
import {
|
||||||
MembersListUrlSortField,
|
MembersListUrlSortField,
|
||||||
permissionGroupListUrl,
|
permissionGroupListUrl,
|
||||||
} from "@dashboard/permissionGroups/urls";
|
} from "@dashboard/permissionGroups/urls";
|
||||||
import {
|
|
||||||
extractPermissionCodes,
|
|
||||||
isGroupFullAccess,
|
|
||||||
} from "@dashboard/permissionGroups/utils";
|
|
||||||
import { ListActions, SortPage } from "@dashboard/types";
|
import { ListActions, SortPage } from "@dashboard/types";
|
||||||
import { getFormErrors } from "@dashboard/utils/errors";
|
import { getFormErrors } from "@dashboard/utils/errors";
|
||||||
import getPermissionGroupErrorMessage from "@dashboard/utils/errors/permissionGroups";
|
import getPermissionGroupErrorMessage from "@dashboard/utils/errors/permissionGroups";
|
||||||
|
import { Box } from "@saleor/macaw-ui/next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import {
|
||||||
|
checkIfUserHasRestictedAccessToChannels,
|
||||||
|
extractPermissionCodes,
|
||||||
|
getInitialChannels,
|
||||||
|
getUserAccessibleChannelsOptions,
|
||||||
|
isGroupFullAccess,
|
||||||
|
} from "../../utils";
|
||||||
import PermissionGroupInfo from "../PermissionGroupInfo";
|
import PermissionGroupInfo from "../PermissionGroupInfo";
|
||||||
import PermissionGroupMemberList from "../PermissionGroupMemberList";
|
import PermissionGroupMemberList from "../PermissionGroupMemberList";
|
||||||
|
|
||||||
export interface PermissionGroupDetailsPageFormData {
|
export interface PermissionGroupDetailsPageFormData {
|
||||||
name: string;
|
name: string;
|
||||||
hasFullAccess: boolean;
|
hasFullAccess: boolean;
|
||||||
|
hasAllChannels: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
permissions: PermissionEnum[];
|
permissions: PermissionEnum[];
|
||||||
users: PermissionGroupDetailsFragment["users"];
|
users: PermissionGroupDetailsFragment["users"];
|
||||||
|
channels: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PermissionData
|
export interface PermissionData
|
||||||
|
@ -45,22 +53,26 @@ export interface PermissionData
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PermissionGroupDetailsPageProps
|
export interface PermissonGroupDetailsPageProps
|
||||||
extends ListActions,
|
extends ListActions,
|
||||||
SortPage<MembersListUrlSortField> {
|
SortPage<MembersListUrlSortField> {
|
||||||
|
channels: ChannelFragment[];
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
isUserAbleToEditChannels: boolean;
|
||||||
errors: PermissionGroupErrorFragment[];
|
errors: PermissionGroupErrorFragment[];
|
||||||
members: PermissionGroupDetailsFragment["users"];
|
members: PermissionGroupDetailsFragment["users"];
|
||||||
permissionGroup: PermissionGroupDetailsFragment;
|
permissionGroup: PermissionGroupDetailsFragment | null | undefined;
|
||||||
permissions: PermissionData[];
|
permissions: PermissionData[];
|
||||||
permissionsExceeded: boolean;
|
permissionsExceeded: boolean;
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: "loading" | "success" | "error" | "default";
|
||||||
onAssign: () => void;
|
onAssign: () => void;
|
||||||
onUnassign: (ids: string[]) => void;
|
onUnassign: (ids: string[]) => void;
|
||||||
onSubmit: (data: PermissionGroupDetailsPageFormData) => SubmitPromise;
|
onSubmit: (data: PermissionGroupDetailsPageFormData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PermissionGroupDetailsPage: React.FC<PermissionGroupDetailsPageProps> = ({
|
export const PermissionGroupDetailsPage: React.FC<
|
||||||
|
PermissonGroupDetailsPageProps
|
||||||
|
> = ({
|
||||||
disabled,
|
disabled,
|
||||||
errors,
|
errors,
|
||||||
members,
|
members,
|
||||||
|
@ -69,13 +81,23 @@ const PermissionGroupDetailsPage: React.FC<PermissionGroupDetailsPageProps> = ({
|
||||||
permissions,
|
permissions,
|
||||||
permissionsExceeded,
|
permissionsExceeded,
|
||||||
saveButtonBarState,
|
saveButtonBarState,
|
||||||
|
channels,
|
||||||
|
isUserAbleToEditChannels,
|
||||||
...listProps
|
...listProps
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
|
const user = useUser();
|
||||||
|
|
||||||
|
const channelsOptions = getUserAccessibleChannelsOptions(channels, user.user);
|
||||||
|
const hasUserRestrictedChannels = checkIfUserHasRestictedAccessToChannels(
|
||||||
|
user.user,
|
||||||
|
);
|
||||||
|
|
||||||
const initialForm: PermissionGroupDetailsPageFormData = {
|
const initialForm: PermissionGroupDetailsPageFormData = {
|
||||||
hasFullAccess: isGroupFullAccess(permissionGroup, permissions),
|
hasFullAccess: isGroupFullAccess(permissionGroup, permissions),
|
||||||
|
hasAllChannels: !permissionGroup?.restrictedAccessToChannels ?? false,
|
||||||
|
channels: getInitialChannels(permissionGroup, channels?.length ?? 0),
|
||||||
isActive: false,
|
isActive: false,
|
||||||
name: permissionGroup?.name || "",
|
name: permissionGroup?.name || "",
|
||||||
permissions: extractPermissionCodes(permissionGroup),
|
permissions: extractPermissionCodes(permissionGroup),
|
||||||
|
@ -90,59 +112,92 @@ const PermissionGroupDetailsPage: React.FC<PermissionGroupDetailsPageProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
|
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
|
||||||
{({ data, change, submit }) => (
|
{({ data, change, submit }) => {
|
||||||
<DetailPageLayout>
|
const handleChannelChange: FormChange = event => {
|
||||||
<TopNav
|
change({
|
||||||
href={permissionGroupListUrl()}
|
target: {
|
||||||
title={permissionGroup?.name}
|
name: "channels",
|
||||||
/>
|
value: event.target.value,
|
||||||
<DetailPageLayout.Content>
|
},
|
||||||
<PermissionGroupInfo
|
});
|
||||||
data={data}
|
};
|
||||||
disabled={disabled}
|
|
||||||
errors={errors}
|
const handleHasAllChannelsChange = () => {
|
||||||
onChange={change}
|
change({
|
||||||
|
target: {
|
||||||
|
name: "hasAllChannels",
|
||||||
|
value: !data.hasAllChannels,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DetailPageLayout>
|
||||||
|
<TopNav
|
||||||
|
href={permissionGroupListUrl()}
|
||||||
|
title={permissionGroup?.name}
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
<DetailPageLayout.Content>
|
||||||
<PermissionGroupMemberList
|
<PermissionGroupInfo
|
||||||
disabled={disabled}
|
data={data}
|
||||||
{...listProps}
|
disabled={disabled}
|
||||||
users={data?.users || []}
|
errors={errors}
|
||||||
/>
|
onChange={change}
|
||||||
</DetailPageLayout.Content>
|
/>
|
||||||
<DetailPageLayout.RightSidebar>
|
|
||||||
<AccountPermissions
|
<FormSpacer />
|
||||||
permissionsExceeded={permissionsExceeded}
|
|
||||||
data={data}
|
<Box paddingX={6}>
|
||||||
disabled={disabled}
|
<ChannelPermission
|
||||||
permissions={permissions}
|
allChannels={
|
||||||
onChange={change}
|
// I pass all channels because Multiselect components based on ids,
|
||||||
errorMessage={permissionsError}
|
// and need data that will take information about channel
|
||||||
fullAccessLabel={intl.formatMessage({
|
!isUserAbleToEditChannels ? channels : channelsOptions
|
||||||
id: "mAabef",
|
}
|
||||||
defaultMessage: "Group has full access to the store",
|
hasAllChannels={data.hasAllChannels}
|
||||||
description: "checkbox label",
|
selectedChannels={data.channels}
|
||||||
})}
|
onHasAllChannelsChange={handleHasAllChannelsChange}
|
||||||
description={intl.formatMessage({
|
onChannelChange={handleChannelChange}
|
||||||
id: "CYZse9",
|
disabled={!isUserAbleToEditChannels || disabled}
|
||||||
defaultMessage:
|
disabledSelectAllChannels={hasUserRestrictedChannels}
|
||||||
"Expand or restrict group's permissions to access certain part of saleor system.",
|
/>
|
||||||
description: "card description",
|
</Box>
|
||||||
})}
|
|
||||||
/>
|
<FormSpacer />
|
||||||
</DetailPageLayout.RightSidebar>
|
<PermissionGroupMemberList
|
||||||
<div>
|
disabled={disabled}
|
||||||
<Savebar
|
{...listProps}
|
||||||
onCancel={() => navigate(permissionGroupListUrl())}
|
users={data?.users || []}
|
||||||
onSubmit={submit}
|
/>
|
||||||
state={saveButtonBarState}
|
</DetailPageLayout.Content>
|
||||||
disabled={disabled}
|
<DetailPageLayout.RightSidebar>
|
||||||
/>
|
<AccountPermissions
|
||||||
</div>
|
permissionsExceeded={permissionsExceeded}
|
||||||
</DetailPageLayout>
|
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>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
PermissionGroupDetailsPage.displayName = "PermissionGroupDetailsPage";
|
|
||||||
export default PermissionGroupDetailsPage;
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export { default } from "./PermissionGroupDetailsPage";
|
|
||||||
export * from "./PermissionGroupDetailsPage";
|
export * from "./PermissionGroupDetailsPage";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import avatarImg from "@assets/images/avatars/avatar.png";
|
import avatarImg from "@assets/images/avatars/avatar.png";
|
||||||
|
import { channels } from "@dashboard/fixtures";
|
||||||
import {
|
import {
|
||||||
PermissionEnum,
|
PermissionEnum,
|
||||||
PermissionGroupDetailsFragment,
|
PermissionGroupDetailsFragment,
|
||||||
|
@ -113,6 +113,7 @@ export const emptyPermissionGroup: PermissionGroupDetailsFragment = {
|
||||||
userCanManage: true,
|
userCanManage: true,
|
||||||
users: [],
|
users: [],
|
||||||
__typename: "Group",
|
__typename: "Group",
|
||||||
|
|
||||||
permissions: [
|
permissions: [
|
||||||
{
|
{
|
||||||
code: PermissionEnum.MANAGE_PAGES,
|
code: PermissionEnum.MANAGE_PAGES,
|
||||||
|
@ -120,6 +121,8 @@ export const emptyPermissionGroup: PermissionGroupDetailsFragment = {
|
||||||
__typename: "Permission",
|
__typename: "Permission",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
accessibleChannels: [],
|
||||||
|
restrictedAccessToChannels: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const errorsOfPermissionGroupCreate: PermissionGroupErrorFragment[] = [
|
export const errorsOfPermissionGroupCreate: PermissionGroupErrorFragment[] = [
|
||||||
|
@ -161,6 +164,8 @@ export const permissionGroup: PermissionGroupDetailsFragment = {
|
||||||
avatar: null,
|
avatar: null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
accessibleChannels: [],
|
||||||
|
restrictedAccessToChannels: false,
|
||||||
__typename: "Group",
|
__typename: "Group",
|
||||||
permissions: [
|
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: {
|
node: {
|
||||||
id: "VXNlcjoyMQ==",
|
id: "VXNlcjoyMQ==",
|
||||||
|
|
|
@ -15,8 +15,8 @@ import {
|
||||||
PermissionGroupListUrlQueryParams,
|
PermissionGroupListUrlQueryParams,
|
||||||
PermissionGroupListUrlSortField,
|
PermissionGroupListUrlSortField,
|
||||||
} from "./urls";
|
} from "./urls";
|
||||||
import PermissionGroupCreate from "./views/PermissionGroupCreate";
|
import { PermissionGroupCreate } from "./views/PermissionGroupCreate";
|
||||||
import PermissionGroupDetailsComponent from "./views/PermissionGroupDetails";
|
import { PermissionGroupDetails as PermissionGroupDetailsComponent } from "./views/PermissionGroupDetails";
|
||||||
import PermissionGroupListComponent from "./views/PermissionGroupList";
|
import PermissionGroupListComponent from "./views/PermissionGroupList";
|
||||||
|
|
||||||
const permissionGroupList: React.FC<RouteComponentProps<{}>> = ({
|
const permissionGroupList: React.FC<RouteComponentProps<{}>> = ({
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import { StaffMemberFragment } from "@dashboard/graphql";
|
import { StaffMemberFragment } from "@dashboard/graphql";
|
||||||
import { getUserName } from "@dashboard/misc";
|
import { getUserName } from "@dashboard/misc";
|
||||||
|
|
||||||
|
@ -7,12 +6,13 @@ import { MembersListUrlSortField } from "./urls";
|
||||||
export const sortMembers =
|
export const sortMembers =
|
||||||
(sort: string, asc: boolean) =>
|
(sort: string, asc: boolean) =>
|
||||||
(a: StaffMemberFragment, b: StaffMemberFragment) => {
|
(a: StaffMemberFragment, b: StaffMemberFragment) => {
|
||||||
let valueA;
|
let valueA: string = "";
|
||||||
let valueB;
|
let valueB: string = "";
|
||||||
|
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
case MembersListUrlSortField.name:
|
case MembersListUrlSortField.name:
|
||||||
valueA = getUserName(a);
|
valueA = getUserName(a) ?? "";
|
||||||
valueB = getUserName(b);
|
valueB = getUserName(b) ?? "";
|
||||||
break;
|
break;
|
||||||
case MembersListUrlSortField.email:
|
case MembersListUrlSortField.email:
|
||||||
valueA = a.email;
|
valueA = a.email;
|
||||||
|
@ -21,6 +21,6 @@ export const sortMembers =
|
||||||
}
|
}
|
||||||
|
|
||||||
return asc
|
return asc
|
||||||
? ("" + valueA).localeCompare(valueB)
|
? valueA.localeCompare(valueB)
|
||||||
: ("" + valueA).localeCompare(valueB) * -1;
|
: 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 {
|
import {
|
||||||
|
ChannelFragment,
|
||||||
PermissionFragment,
|
PermissionFragment,
|
||||||
PermissionGroupDetailsFragment,
|
PermissionGroupDetailsFragment,
|
||||||
UserFragment,
|
UserFragment,
|
||||||
|
@ -7,14 +9,16 @@ import {
|
||||||
import difference from "lodash/difference";
|
import difference from "lodash/difference";
|
||||||
|
|
||||||
import { PermissionGroupDetailsPageFormData } from "./components/PermissionGroupDetailsPage";
|
import { PermissionGroupDetailsPageFormData } from "./components/PermissionGroupDetailsPage";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will return true if group has all permissions available in shop assigned.
|
* Will return true if group has all permissions available in shop assigned.
|
||||||
*/
|
*/
|
||||||
export const isGroupFullAccess = (
|
export const isGroupFullAccess = (
|
||||||
permissionGroup: PermissionGroupDetailsFragment,
|
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||||
shopPermissions: Array<Omit<PermissionFragment, "__typename">>,
|
shopPermissions: Array<Omit<PermissionFragment, "__typename">>,
|
||||||
) => {
|
) => {
|
||||||
|
if (!permissionGroup) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const assignedCodes = extractPermissionCodes(permissionGroup);
|
const assignedCodes = extractPermissionCodes(permissionGroup);
|
||||||
|
|
||||||
if (assignedCodes.length !== shopPermissions?.length) {
|
if (assignedCodes.length !== shopPermissions?.length) {
|
||||||
|
@ -33,19 +37,31 @@ export const isGroupFullAccess = (
|
||||||
* Return list of codes which are assigned to the permission group.
|
* Return list of codes which are assigned to the permission group.
|
||||||
*/
|
*/
|
||||||
export const extractPermissionCodes = (
|
export const extractPermissionCodes = (
|
||||||
permissionGroup: PermissionGroupDetailsFragment,
|
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||||
) =>
|
) => {
|
||||||
permissionGroup?.permissions
|
if (!permissionGroup) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissionGroup?.permissions
|
||||||
? permissionGroup.permissions.map(perm => perm.code)
|
? permissionGroup.permissions.map(perm => perm.code)
|
||||||
: [];
|
: [];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return lists of permissions which have to be added and removed from group.
|
* Return lists of permissions which have to be added and removed from group.
|
||||||
*/
|
*/
|
||||||
export const permissionsDiff = (
|
export const permissionsDiff = (
|
||||||
permissionGroup: PermissionGroupDetailsFragment,
|
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||||
formData: PermissionGroupDetailsPageFormData,
|
formData: PermissionGroupDetailsPageFormData,
|
||||||
) => {
|
) => {
|
||||||
|
if (!permissionGroup) {
|
||||||
|
return {
|
||||||
|
addPermissions: [],
|
||||||
|
removePermissions: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const newPermissions = formData.permissions;
|
const newPermissions = formData.permissions;
|
||||||
const oldPermissions = extractPermissionCodes(permissionGroup);
|
const oldPermissions = extractPermissionCodes(permissionGroup);
|
||||||
|
|
||||||
|
@ -59,11 +75,18 @@ export const permissionsDiff = (
|
||||||
* Return lists of users which have to be added and removed from group.
|
* Return lists of users which have to be added and removed from group.
|
||||||
*/
|
*/
|
||||||
export const usersDiff = (
|
export const usersDiff = (
|
||||||
permissionGroup: PermissionGroupDetailsFragment,
|
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||||
formData: PermissionGroupDetailsPageFormData,
|
formData: PermissionGroupDetailsPageFormData,
|
||||||
) => {
|
) => {
|
||||||
const newUsers = formData.users.map(u => u.id);
|
if (!permissionGroup) {
|
||||||
const oldUsers = permissionGroup?.users.map(u => u.id);
|
return {
|
||||||
|
addUsers: [],
|
||||||
|
removeUsers: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUsers = formData?.users?.map(u => u.id) ?? [];
|
||||||
|
const oldUsers = permissionGroup?.users?.map(u => u.id) ?? [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addUsers: difference(newUsers, oldUsers),
|
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
|
* Permissions are exceeded when group has permission which is not handled by user
|
||||||
*/
|
*/
|
||||||
export const arePermissionsExceeded = (
|
export const arePermissionsExceeded = (
|
||||||
permissionGroup: PermissionGroupDetailsFragment,
|
permissionGroup: PermissionGroupDetailsFragment | null | undefined,
|
||||||
user: UserFragment,
|
user: UserFragment | null | undefined,
|
||||||
) => {
|
) => {
|
||||||
|
if (!permissionGroup || !user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const groupPermissions = extractPermissionCodes(permissionGroup);
|
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 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 { useUser } from "@dashboard/auth";
|
||||||
|
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||||
import { WindowTitle } from "@dashboard/components/WindowTitle";
|
import { WindowTitle } from "@dashboard/components/WindowTitle";
|
||||||
import { usePermissionGroupCreateMutation } from "@dashboard/graphql";
|
import { usePermissionGroupCreateMutation } from "@dashboard/graphql";
|
||||||
import useNavigator from "@dashboard/hooks/useNavigator";
|
import useNavigator from "@dashboard/hooks/useNavigator";
|
||||||
|
@ -7,20 +7,33 @@ import useNotifier from "@dashboard/hooks/useNotifier";
|
||||||
import useShop from "@dashboard/hooks/useShop";
|
import useShop from "@dashboard/hooks/useShop";
|
||||||
import { extractMutationErrors } from "@dashboard/misc";
|
import { extractMutationErrors } from "@dashboard/misc";
|
||||||
import { PermissionData } from "@dashboard/permissionGroups/components/PermissionGroupDetailsPage";
|
import { PermissionData } from "@dashboard/permissionGroups/components/PermissionGroupDetailsPage";
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import PermissionGroupCreatePage, {
|
import {
|
||||||
PermissionGroupCreateFormData,
|
PermissionGroupCreateFormData,
|
||||||
|
PermissionGroupCreatePage,
|
||||||
} from "../../components/PermissionGroupCreatePage";
|
} from "../../components/PermissionGroupCreatePage";
|
||||||
import { permissionGroupDetailsUrl } from "../../urls";
|
import { permissionGroupDetailsUrl } from "../../urls";
|
||||||
|
import {
|
||||||
|
checkIfUserHasRestictedAccessToChannels,
|
||||||
|
getUserAccessibleChannelsOptions,
|
||||||
|
} from "../../utils";
|
||||||
|
|
||||||
const PermissionGroupCreateView: React.FC = () => {
|
export const PermissionGroupCreate: React.FC = () => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const shop = useShop();
|
const shop = useShop();
|
||||||
const user = useUser();
|
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] =
|
const [createPermissionGroup, createPermissionGroupResult] =
|
||||||
usePermissionGroupCreateMutation({
|
usePermissionGroupCreateMutation({
|
||||||
|
@ -33,9 +46,12 @@ const PermissionGroupCreateView: React.FC = () => {
|
||||||
defaultMessage: "Permission group created",
|
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 =
|
const errors =
|
||||||
createPermissionGroupResult?.data?.permissionGroupCreate?.errors || [];
|
createPermissionGroupResult?.data?.permissionGroupCreate?.errors || [];
|
||||||
|
|
||||||
const onSubmit = (formData: PermissionGroupCreateFormData) =>
|
const onSubmit = (formData: PermissionGroupCreateFormData) => {
|
||||||
extractMutationErrors(
|
const channelChoices = userAccessibleChannelsOptions.map(
|
||||||
|
channel => channel.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return extractMutationErrors(
|
||||||
createPermissionGroup({
|
createPermissionGroup({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
addPermissions: formData.hasFullAccess
|
addPermissions: formData.permissions,
|
||||||
? shop.permissions.map(perm => perm.code)
|
|
||||||
: formData.permissions,
|
|
||||||
addUsers: [],
|
addUsers: [],
|
||||||
name: formData.name,
|
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[] =
|
const permissions: PermissionData[] =
|
||||||
shop?.permissions.map(
|
shop?.permissions.map(
|
||||||
|
@ -80,15 +104,14 @@ const PermissionGroupCreateView: React.FC = () => {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<PermissionGroupCreatePage
|
<PermissionGroupCreatePage
|
||||||
errors={errors}
|
errors={errors as any}
|
||||||
disabled={createPermissionGroupResult.loading}
|
disabled={createPermissionGroupResult.loading}
|
||||||
permissions={permissions}
|
permissions={permissions}
|
||||||
|
channels={userAccessibleChannelsOptions}
|
||||||
|
hasRestrictedChannels={hasUserRestrictedAccessToChannels}
|
||||||
saveButtonBarState={createPermissionGroupResult.status}
|
saveButtonBarState={createPermissionGroupResult.status}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
PermissionGroupCreateView.displayName = "PermissionGroupCreateView";
|
|
||||||
|
|
||||||
export default PermissionGroupCreateView;
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export { default } from "./PermissionGroupCreate";
|
|
||||||
export * from "./PermissionGroupCreate";
|
export * from "./PermissionGroupCreate";
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import { useUser } from "@dashboard/auth";
|
import { useUser } from "@dashboard/auth";
|
||||||
|
import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext";
|
||||||
import { Button } from "@dashboard/components/Button";
|
import { Button } from "@dashboard/components/Button";
|
||||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@dashboard/config";
|
import { DEFAULT_INITIAL_SEARCH_DATA } from "@dashboard/config";
|
||||||
import {
|
import {
|
||||||
|
PermissionGroupDetailsQuery,
|
||||||
usePermissionGroupDetailsQuery,
|
usePermissionGroupDetailsQuery,
|
||||||
usePermissionGroupUpdateMutation,
|
usePermissionGroupUpdateMutation,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
|
@ -14,11 +15,6 @@ import useStateFromProps from "@dashboard/hooks/useStateFromProps";
|
||||||
import { commonMessages } from "@dashboard/intl";
|
import { commonMessages } from "@dashboard/intl";
|
||||||
import { extractMutationErrors } from "@dashboard/misc";
|
import { extractMutationErrors } from "@dashboard/misc";
|
||||||
import MembersErrorDialog from "@dashboard/permissionGroups/components/MembersErrorDialog";
|
import MembersErrorDialog from "@dashboard/permissionGroups/components/MembersErrorDialog";
|
||||||
import {
|
|
||||||
arePermissionsExceeded,
|
|
||||||
permissionsDiff,
|
|
||||||
usersDiff,
|
|
||||||
} from "@dashboard/permissionGroups/utils";
|
|
||||||
import useStaffMemberSearch from "@dashboard/searches/useStaffMemberSearch";
|
import useStaffMemberSearch from "@dashboard/searches/useStaffMemberSearch";
|
||||||
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers";
|
||||||
import createSortHandler from "@dashboard/utils/handlers/sortHandler";
|
import createSortHandler from "@dashboard/utils/handlers/sortHandler";
|
||||||
|
@ -28,7 +24,8 @@ import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import AssignMembersDialog from "../../components/AssignMembersDialog";
|
import AssignMembersDialog from "../../components/AssignMembersDialog";
|
||||||
import PermissionGroupDetailsPage, {
|
import {
|
||||||
|
PermissionGroupDetailsPage,
|
||||||
PermissionGroupDetailsPageFormData,
|
PermissionGroupDetailsPageFormData,
|
||||||
} from "../../components/PermissionGroupDetailsPage";
|
} from "../../components/PermissionGroupDetailsPage";
|
||||||
import UnassignMembersDialog from "../../components/UnassignMembersDialog";
|
import UnassignMembersDialog from "../../components/UnassignMembersDialog";
|
||||||
|
@ -37,12 +34,23 @@ import {
|
||||||
PermissionGroupDetailsUrlDialog,
|
PermissionGroupDetailsUrlDialog,
|
||||||
PermissionGroupDetailsUrlQueryParams,
|
PermissionGroupDetailsUrlQueryParams,
|
||||||
} from "../../urls";
|
} from "../../urls";
|
||||||
|
import {
|
||||||
|
arePermissionsExceeded,
|
||||||
|
channelsDiff,
|
||||||
|
checkIfUserBelongToPermissionGroup,
|
||||||
|
permissionsDiff,
|
||||||
|
usersDiff,
|
||||||
|
} from "../../utils";
|
||||||
|
|
||||||
interface PermissionGroupDetailsProps {
|
interface PermissionGroupDetailsProps {
|
||||||
id: string;
|
id: string;
|
||||||
params: PermissionGroupDetailsUrlQueryParams;
|
params: PermissionGroupDetailsUrlQueryParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Members = NonNullable<
|
||||||
|
NonNullable<PermissionGroupDetailsQuery["permissionGroup"]>["users"]
|
||||||
|
>;
|
||||||
|
|
||||||
export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
||||||
id,
|
id,
|
||||||
params,
|
params,
|
||||||
|
@ -55,11 +63,13 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
||||||
|
|
||||||
const { data, loading, refetch } = usePermissionGroupDetailsQuery({
|
const { data, loading, refetch } = usePermissionGroupDetailsQuery({
|
||||||
displayLoader: true,
|
displayLoader: true,
|
||||||
variables: { id, userId: user?.user.id },
|
variables: { id, userId: user?.user?.id ?? "" },
|
||||||
});
|
});
|
||||||
|
|
||||||
const [membersList, setMembersList] = useStateFromProps(
|
const { availableChannels } = useAppChannel(false);
|
||||||
data?.permissionGroup.users,
|
|
||||||
|
const [membersList, setMembersList] = useStateFromProps<Members>(
|
||||||
|
data?.permissionGroup?.users ?? [],
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -76,16 +86,30 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
||||||
|
|
||||||
const [permissionGroupUpdate, permissionGroupUpdateResult] =
|
const [permissionGroupUpdate, permissionGroupUpdateResult] =
|
||||||
usePermissionGroupUpdateMutation({
|
usePermissionGroupUpdateMutation({
|
||||||
onCompleted: data => {
|
onCompleted: updatedData => {
|
||||||
if (data.permissionGroupUpdate.errors.length === 0) {
|
if (updatedData?.permissionGroupUpdate?.errors?.length === 0) {
|
||||||
notify({
|
notify({
|
||||||
status: "success",
|
status: "success",
|
||||||
text: intl.formatMessage(commonMessages.savedChanges),
|
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();
|
refetch();
|
||||||
closeModal();
|
closeModal();
|
||||||
} else if (
|
} else if (
|
||||||
data.permissionGroupUpdate.errors.some(e => e.field === "removeUsers")
|
updatedData?.permissionGroupUpdate?.errors.some(
|
||||||
|
e => e.field === "removeUsers",
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
openModal("unassignError");
|
openModal("unassignError");
|
||||||
}
|
}
|
||||||
|
@ -104,22 +128,25 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const unassignMembers = () => {
|
const unassignMembers = () => {
|
||||||
setMembersList(membersList?.filter(m => !listElements.includes(m.id)));
|
setMembersList(
|
||||||
|
membersList?.filter(m => !listElements.includes(m.id)) ?? [],
|
||||||
|
);
|
||||||
closeModal();
|
closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isGroupEditable =
|
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(
|
.filter(
|
||||||
perm =>
|
perm =>
|
||||||
perm.sourcePermissionGroups.length === 1 &&
|
perm.sourcePermissionGroups?.length === 1 &&
|
||||||
perm.sourcePermissionGroups[0].id === id,
|
perm.sourcePermissionGroups?.[0].id === id,
|
||||||
)
|
)
|
||||||
.map(perm => perm.code);
|
.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 => ({
|
const permissions = (shop?.permissions || []).map(perm => ({
|
||||||
...perm,
|
...perm,
|
||||||
|
@ -129,9 +156,11 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
||||||
|
|
||||||
const permissionsExceeded = arePermissionsExceeded(
|
const permissionsExceeded = arePermissionsExceeded(
|
||||||
data?.permissionGroup,
|
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) =>
|
const handleSubmit = async (formData: PermissionGroupDetailsPageFormData) =>
|
||||||
extractMutationErrors(
|
extractMutationErrors(
|
||||||
|
@ -142,6 +171,13 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
...permissionsDiff(data?.permissionGroup, formData),
|
...permissionsDiff(data?.permissionGroup, formData),
|
||||||
...usersDiff(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
|
<PermissionGroupDetailsPage
|
||||||
permissionGroup={data?.permissionGroup}
|
permissionGroup={data?.permissionGroup}
|
||||||
permissionsExceeded={permissionsExceeded}
|
permissionsExceeded={permissionsExceeded}
|
||||||
members={membersList || []}
|
isUserAbleToEditChannels={!!isGroupEditable}
|
||||||
|
channels={availableChannels}
|
||||||
|
members={membersList}
|
||||||
onAssign={() => openModal("assign")}
|
onAssign={() => openModal("assign")}
|
||||||
onUnassign={ids => openModal("unassign", { ids })}
|
onUnassign={ids => openModal("unassign", { ids })}
|
||||||
errors={
|
errors={
|
||||||
permissionGroupUpdateResult?.data?.permissionGroupUpdate.errors || []
|
permissionGroupUpdateResult?.data?.permissionGroupUpdate?.errors ?? []
|
||||||
}
|
}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
permissions={permissions}
|
permissions={permissions}
|
||||||
|
@ -183,20 +221,17 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
||||||
/>
|
/>
|
||||||
<AssignMembersDialog
|
<AssignMembersDialog
|
||||||
loading={searchResult.loading}
|
loading={searchResult.loading}
|
||||||
staffMembers={mapEdgesToItems(searchResult?.data?.search)}
|
staffMembers={mapEdgesToItems(searchResult?.data?.search) ?? []}
|
||||||
onSearchChange={search}
|
onSearchChange={search}
|
||||||
onFetchMore={loadMore}
|
onFetchMore={loadMore}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
hasMore={searchResult?.data?.search.pageInfo.hasNextPage}
|
hasMore={searchResult?.data?.search?.pageInfo?.hasNextPage ?? false}
|
||||||
initialSearch=""
|
initialSearch=""
|
||||||
confirmButtonState={permissionGroupUpdateResult.status}
|
confirmButtonState={permissionGroupUpdateResult.status}
|
||||||
open={params.action === "assign"}
|
open={params.action === "assign"}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onSubmit={formData => {
|
onSubmit={formData => {
|
||||||
setMembersList([
|
setMembersList([...(membersList ?? []), ...formData] as Members);
|
||||||
...membersList,
|
|
||||||
...formData.filter(member => !membersList.includes(member)),
|
|
||||||
]);
|
|
||||||
closeModal();
|
closeModal();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -216,5 +251,3 @@ export const PermissionGroupDetails: React.FC<PermissionGroupDetailsProps> = ({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PermissionGroupDetails;
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export { default } from "./PermissionGroupDetails";
|
|
||||||
export * from "./PermissionGroupDetails";
|
export * from "./PermissionGroupDetails";
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import {
|
import {
|
||||||
PermissionGroupErrorFragment,
|
PermissionGroupErrorFragment,
|
||||||
usePermissionGroupDeleteMutation,
|
usePermissionGroupDeleteMutation,
|
||||||
|
@ -60,7 +59,7 @@ export const PermissionGroupList: React.FC<PermissionGroupListProps> = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const paginationValues = usePaginator({
|
const paginationValues = usePaginator({
|
||||||
pageInfo: data?.permissionGroups.pageInfo,
|
pageInfo: data?.permissionGroups?.pageInfo,
|
||||||
paginationState,
|
paginationState,
|
||||||
queryString: params,
|
queryString: params,
|
||||||
});
|
});
|
||||||
|
@ -76,13 +75,13 @@ export const PermissionGroupList: React.FC<PermissionGroupListProps> = ({
|
||||||
PermissionGroupListUrlQueryParams
|
PermissionGroupListUrlQueryParams
|
||||||
>(navigate, permissionGroupListUrl, params);
|
>(navigate, permissionGroupListUrl, params);
|
||||||
|
|
||||||
const permissionGroups = mapEdgesToItems(data?.permissionGroups);
|
const permissionGroups = mapEdgesToItems(data?.permissionGroups) ?? [];
|
||||||
const [deleteError, setDeleteError] =
|
const [deleteError, setDeleteError] =
|
||||||
React.useState<PermissionGroupErrorFragment>();
|
React.useState<PermissionGroupErrorFragment>();
|
||||||
|
|
||||||
const [permissionGroupDelete] = usePermissionGroupDeleteMutation({
|
const [permissionGroupDelete] = usePermissionGroupDeleteMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
if (data.permissionGroupDelete.errors.length === 0) {
|
if (data?.permissionGroupDelete?.errors?.length === 0) {
|
||||||
notify({
|
notify({
|
||||||
status: "success",
|
status: "success",
|
||||||
text: intl.formatMessage({
|
text: intl.formatMessage({
|
||||||
|
@ -94,7 +93,7 @@ export const PermissionGroupList: React.FC<PermissionGroupListProps> = ({
|
||||||
setDeleteError(undefined);
|
setDeleteError(undefined);
|
||||||
closeModal();
|
closeModal();
|
||||||
} else {
|
} else {
|
||||||
setDeleteError(data.permissionGroupDelete.errors[0]);
|
setDeleteError(data?.permissionGroupDelete?.errors?.[0]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -114,7 +113,7 @@ export const PermissionGroupList: React.FC<PermissionGroupListProps> = ({
|
||||||
onConfirm={() =>
|
onConfirm={() =>
|
||||||
permissionGroupDelete({
|
permissionGroupDelete({
|
||||||
variables: {
|
variables: {
|
||||||
id: params.id,
|
id: params?.id ?? "",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// @ts-strict-ignore
|
|
||||||
import { PermissionGroupSortField } from "@dashboard/graphql";
|
import { PermissionGroupSortField } from "@dashboard/graphql";
|
||||||
import { PermissionGroupListUrlSortField } from "@dashboard/permissionGroups/urls";
|
import { PermissionGroupListUrlSortField } from "@dashboard/permissionGroups/urls";
|
||||||
import { createGetSortQueryVariables } from "@dashboard/utils/sort";
|
import { createGetSortQueryVariables } from "@dashboard/utils/sort";
|
||||||
|
@ -10,7 +9,7 @@ export function getSortQueryField(
|
||||||
case PermissionGroupListUrlSortField.name:
|
case PermissionGroupListUrlSortField.name:
|
||||||
return PermissionGroupSortField.NAME;
|
return PermissionGroupSortField.NAME;
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return "" as PermissionGroupSortField;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ export interface SortPage<TSortKey extends string> {
|
||||||
|
|
||||||
export interface ListActionsWithoutToolbar {
|
export interface ListActionsWithoutToolbar {
|
||||||
toggle: (id: string) => void;
|
toggle: (id: string) => void;
|
||||||
toggleAll: (items: React.ReactNodeArray, selected: number) => void;
|
toggleAll: (items: Node[], selected: number) => void;
|
||||||
isChecked: (id: string) => boolean;
|
isChecked: (id: string) => boolean;
|
||||||
selected: number;
|
selected: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ const messages = defineMessages({
|
||||||
});
|
});
|
||||||
|
|
||||||
function getPermissionGroupErrorMessage(
|
function getPermissionGroupErrorMessage(
|
||||||
err: PermissionGroupErrorFragment,
|
err: PermissionGroupErrorFragment | undefined,
|
||||||
intl: IntlShape,
|
intl: IntlShape,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
Loading…
Reference in a new issue