Add permission-based restrictions in Customer views (#1879)

* Update customer queries - add permissions

* Filter the customer filter by permissions

* Code refactor + optionally render components based on permissions

* Update stories

* Minor code refactor

* Move useUserPermissions to RequirePermissions component

* Change user provider component name

* Change macaw-ui version
This commit is contained in:
Wojciech Mista 2022-03-01 09:38:23 +01:00 committed by GitHub
parent 8ab05ab8e1
commit 9a7c9a3bc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 257 additions and 453 deletions

View file

@ -28,7 +28,7 @@
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.58",
"@material-ui/styles": "^4.11.4",
"@saleor/macaw-ui": "^0.3.1",
"@saleor/macaw-ui": "0.3.1",
"@saleor/sdk": "^0.4.2",
"@sentry/react": "^6.0.0",
"@types/faker": "^5.1.6",

View file

@ -0,0 +1,3 @@
import { useUser } from "..";
export const useUserPermissions = () => useUser().user?.userPermissions;

View file

@ -1,5 +1,4 @@
import { Card, CardContent, Typography } from "@material-ui/core";
import { useUser } from "@saleor/auth";
import CardTitle from "@saleor/components/CardTitle";
import Hr from "@saleor/components/Hr";
import RequirePermissions from "@saleor/components/RequirePermissions";
@ -28,7 +27,6 @@ export const ChannelsAvailabilityWrapper: React.FC<ChannelsAvailabilityWrapperPr
} = props;
const intl = useIntl();
const classes = useStyles({});
const { user } = useUser();
const channelsAvailabilityText = intl.formatMessage(
{
defaultMessage:
@ -51,10 +49,7 @@ export const ChannelsAvailabilityWrapper: React.FC<ChannelsAvailabilityWrapperPr
description: "section header"
})}
toolbar={
<RequirePermissions
userPermissions={user?.userPermissions || []}
requiredPermissions={managePermissions}
>
<RequirePermissions requiredPermissions={managePermissions}>
<Button
onClick={openModal}
data-test-id="channels-availability-manage-button"

View file

@ -1,4 +1,5 @@
import { FetchMoreProps, SearchPageProps } from "@saleor/types";
import { PermissionEnum } from "@saleor/types/globalTypes";
import { MessageDescriptor } from "react-intl";
import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField";
@ -34,6 +35,7 @@ export interface IFilterElement<T extends string = string>
multipleFields?: IFilterElement[];
id?: string;
dependencies?: string[];
permissions?: PermissionEnum[];
}
export interface FilterBaseFieldProps<T extends string = string> {

View file

@ -1,3 +1,4 @@
import { useUserPermissions } from "@saleor/auth/hooks/useUserPermissions";
import { User_userPermissions } from "@saleor/fragments/types/User";
import { PermissionEnum } from "@saleor/types/globalTypes";
import React from "react";
@ -16,15 +17,19 @@ export function hasPermissions(
export interface RequirePermissionsProps {
children: React.ReactNode | React.ReactNodeArray;
requiredPermissions: PermissionEnum[];
userPermissions: User_userPermissions[];
}
const RequirePermissions: React.FC<RequirePermissionsProps> = ({
children,
requiredPermissions,
userPermissions
}) =>
hasPermissions(userPermissions, requiredPermissions) ? <>{children}</> : null;
requiredPermissions
}) => {
const userPermissions = useUserPermissions();
return userPermissions &&
hasPermissions(userPermissions, requiredPermissions) ? (
<>{children}</>
) : null;
};
RequirePermissions.displayName = "RequirePermissions";
export default RequirePermissions;

View file

@ -5,6 +5,7 @@ import Grid from "@saleor/components/Grid";
import Metadata from "@saleor/components/Metadata/Metadata";
import { MetadataFormData } from "@saleor/components/Metadata/types";
import PageHeader from "@saleor/components/PageHeader";
import RequirePermissions from "@saleor/components/RequirePermissions";
import Savebar from "@saleor/components/Savebar";
import { UpdateCustomer_customerUpdate_errors } from "@saleor/customers/types/UpdateCustomer";
import { AccountErrorFragment } from "@saleor/fragments/types/AccountErrorFragment";
@ -13,6 +14,7 @@ import { SubmitPromise } from "@saleor/hooks/useForm";
import { sectionNames } from "@saleor/intl";
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
import { Backlink } from "@saleor/macaw-ui";
import { PermissionEnum } from "@saleor/types/globalTypes";
import { mapEdgesToItems, mapMetadataItemToInput } from "@saleor/utils/maps";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import React from "react";
@ -105,12 +107,16 @@ const CustomerDetailsPage: React.FC<CustomerDetailsPageProps> = ({
onChange={change}
/>
<CardSpacer />
<RequirePermissions
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
>
<CustomerOrders
orders={mapEdgesToItems(customer?.orders)}
onViewAllOrdersClick={onViewAllOrdersClick}
onRowClick={onRowClick}
/>
<CardSpacer />
</RequirePermissions>
<Metadata data={data} onChange={changeMetadata} />
</div>
<div>
@ -122,7 +128,11 @@ const CustomerDetailsPage: React.FC<CustomerDetailsPageProps> = ({
<CardSpacer />
<CustomerStats customer={customer} />
<CardSpacer />
<RequirePermissions
requiredPermissions={[PermissionEnum.MANAGE_GIFT_CARD]}
>
<CustomerGiftCardsCard />
</RequirePermissions>
</div>
</Grid>
<Savebar

View file

@ -1,5 +1,9 @@
import { TableBody, TableCell, TableFooter, TableRow } from "@material-ui/core";
import { useUserPermissions } from "@saleor/auth/hooks/useUserPermissions";
import Checkbox from "@saleor/components/Checkbox";
import RequirePermissions, {
hasPermissions
} from "@saleor/components/RequirePermissions";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Skeleton from "@saleor/components/Skeleton";
import TableCellHeader from "@saleor/components/TableCellHeader";
@ -7,8 +11,9 @@ import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination";
import { CustomerListUrlSortField } from "@saleor/customers/urls";
import { makeStyles } from "@saleor/macaw-ui";
import { getUserName, maybe, renderCollection } from "@saleor/misc";
import { getUserName, renderCollection } from "@saleor/misc";
import { ListActions, ListProps, SortPage } from "@saleor/types";
import { PermissionEnum } from "@saleor/types/globalTypes";
import { getArrowDirection } from "@saleor/utils/sort";
import React from "react";
import { FormattedMessage } from "react-intl";
@ -45,7 +50,7 @@ export interface CustomerListProps
customers: ListCustomers_customers_edges_node[];
}
const numberOfColumns = 4;
const numberOfColumns = 3;
const CustomerList: React.FC<CustomerListProps> = props => {
const {
@ -66,12 +71,18 @@ const CustomerList: React.FC<CustomerListProps> = props => {
isChecked
} = props;
const userPermissions = useUserPermissions();
const classes = useStyles(props);
return (
<ResponsiveTable>
<TableHead
colSpan={numberOfColumns}
colSpan={
hasPermissions(userPermissions, [PermissionEnum.MANAGE_ORDERS])
? numberOfColumns
: numberOfColumns - 1
}
selected={selected}
disabled={disabled}
items={customers}
@ -101,6 +112,9 @@ const CustomerList: React.FC<CustomerListProps> = props => {
>
<FormattedMessage defaultMessage="Customer Email" />
</TableCellHeader>
<RequirePermissions
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
>
<TableCellHeader
direction={
sort.sort === CustomerListUrlSortField.orders
@ -113,6 +127,7 @@ const CustomerList: React.FC<CustomerListProps> = props => {
>
<FormattedMessage defaultMessage="No. of Orders" />
</TableCellHeader>
</RequirePermissions>
</TableHead>
<TableFooter>
<TableRow>
@ -155,14 +170,15 @@ const CustomerList: React.FC<CustomerListProps> = props => {
{getUserName(customer)}
</TableCell>
<TableCell className={classes.colEmail}>
{maybe<React.ReactNode>(() => customer.email, <Skeleton />)}
{customer?.email ?? <Skeleton />}
</TableCell>
<RequirePermissions
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
>
<TableCell className={classes.colOrders}>
{maybe<React.ReactNode>(
() => customer.orders.totalCount,
<Skeleton />
)}
{customer?.orders?.totalCount ?? <Skeleton />}
</TableCell>
</RequirePermissions>
</TableRow>
);
},

View file

@ -1,4 +1,5 @@
import { Card } from "@material-ui/core";
import { useUserPermissions } from "@saleor/auth/hooks/useUserPermissions";
import Container from "@saleor/components/Container";
import FilterBar from "@saleor/components/FilterBar";
import PageHeader from "@saleor/components/PageHeader";
@ -48,7 +49,8 @@ const CustomerListPage: React.FC<CustomerListPageProps> = ({
}) => {
const intl = useIntl();
const structure = createFilterStructure(intl, filterOpts);
const userPermissions = useUserPermissions();
const structure = createFilterStructure(intl, filterOpts, userPermissions);
return (
<Container>

View file

@ -1,5 +1,8 @@
import { IFilter } from "@saleor/components/Filter";
import { hasPermissions } from "@saleor/components/RequirePermissions";
import { User_userPermissions } from "@saleor/fragments/types/User";
import { FilterOpts, MinMax } from "@saleor/types";
import { PermissionEnum } from "@saleor/types/globalTypes";
import {
createDateField,
createNumberField
@ -28,7 +31,8 @@ const messages = defineMessages({
export function createFilterStructure(
intl: IntlShape,
opts: CustomerListFilterOpts
opts: CustomerListFilterOpts,
userPermissions: User_userPermissions[]
): IFilter<CustomerFilterKeys> {
return [
{
@ -45,7 +49,8 @@ export function createFilterStructure(
intl.formatMessage(messages.numberOfOrders),
opts.numberOfOrders.value
),
active: opts.numberOfOrders.active
active: opts.numberOfOrders.active,
permissions: [PermissionEnum.MANAGE_ORDERS]
}
];
].filter(filter => hasPermissions(userPermissions, filter.permissions ?? []));
}

View file

@ -2,12 +2,13 @@ import { Card, CardContent, Typography } from "@material-ui/core";
import CardTitle from "@saleor/components/CardTitle";
import { DateTime } from "@saleor/components/Date";
import { Hr } from "@saleor/components/Hr";
import RequirePermissions from "@saleor/components/RequirePermissions";
import Skeleton from "@saleor/components/Skeleton";
import { makeStyles } from "@saleor/macaw-ui";
import { PermissionEnum } from "@saleor/types/globalTypes";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { maybe } from "../../../misc";
import { CustomerDetails_user } from "../../types/CustomerDetails";
const useStyles = makeStyles(
@ -44,8 +45,7 @@ const CustomerStats: React.FC<CustomerStatsProps> = props => {
<Typography className={classes.label} variant="caption">
<FormattedMessage defaultMessage="Last login" />
</Typography>
{maybe(
() => (
{customer ? (
<Typography variant="h6" className={classes.value}>
{customer.lastLogin === null ? (
"-"
@ -53,17 +53,17 @@ const CustomerStats: React.FC<CustomerStatsProps> = props => {
<DateTime date={customer.lastLogin} />
)}
</Typography>
),
) : (
<Skeleton />
)}
</CardContent>
<RequirePermissions requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}>
<Hr />
<CardContent>
<Typography className={classes.label} variant="caption">
<FormattedMessage defaultMessage="Last order" />
</Typography>
{maybe(
() => (
{customer && customer.lastPlacedOrder ? (
<Typography variant="h6" className={classes.value}>
{customer.lastPlacedOrder.edges.length === 0 ? (
"-"
@ -73,10 +73,11 @@ const CustomerStats: React.FC<CustomerStatsProps> = props => {
/>
)}
</Typography>
),
) : (
<Skeleton />
)}
</CardContent>
</RequirePermissions>
</Card>
);
};

View file

@ -27,6 +27,7 @@ const customerList = gql`
$last: Int
$filter: CustomerFilterInput
$sort: UserSortingInput
$PERMISSION_MANAGE_ORDERS: Boolean!
) {
customers(
after: $after
@ -39,7 +40,7 @@ const customerList = gql`
edges {
node {
...CustomerFragment
orders {
orders @include(if: $PERMISSION_MANAGE_ORDERS) {
totalCount
}
}
@ -60,10 +61,10 @@ export const useCustomerListQuery = makeQuery<
const customerDetails = gql`
${customerDetailsFragment}
query CustomerDetails($id: ID!) {
query CustomerDetails($id: ID!, $PERMISSION_MANAGE_ORDERS: Boolean!) {
user(id: $id) {
...CustomerDetailsFragment
orders(last: 5) {
orders(last: 5) @include(if: $PERMISSION_MANAGE_ORDERS) {
edges {
node {
id
@ -79,7 +80,7 @@ const customerDetails = gql`
}
}
}
lastPlacedOrder: orders(last: 1) {
lastPlacedOrder: orders(last: 1) @include(if: $PERMISSION_MANAGE_ORDERS) {
edges {
node {
id

View file

@ -135,4 +135,5 @@ export interface CustomerDetails {
export interface CustomerDetailsVariables {
id: string;
PERMISSION_MANAGE_ORDERS: boolean;
}

View file

@ -53,4 +53,5 @@ export interface ListCustomersVariables {
last?: number | null;
filter?: CustomerFilterInput | null;
sort?: UserSortingInput | null;
PERMISSION_MANAGE_ORDERS: boolean;
}

View file

@ -1,6 +1,7 @@
import { createFilterStructure } from "@saleor/customers/components/CustomerListPage";
import { CustomerListUrlFilters } from "@saleor/customers/urls";
import { date } from "@saleor/fixtures";
import { PermissionEnum } from "@saleor/types/globalTypes";
import { getFilterQueryParams } from "@saleor/utils/filters";
import { stringifyQs } from "@saleor/utils/urls";
import { getExistingKeys, setFilterOptsStatus } from "@test/filters";
@ -31,7 +32,9 @@ describe("Filtering query params", () => {
describe("Filtering URL params", () => {
const intl = createIntl(config);
const filters = createFilterStructure(intl, {
const filters = createFilterStructure(
intl,
{
joined: {
active: false,
value: {
@ -46,7 +49,20 @@ describe("Filtering URL params", () => {
min: "1"
}
}
});
},
[
{
code: PermissionEnum.MANAGE_USERS,
name: "Manage customers.",
__typename: "UserPermission"
},
{
code: PermissionEnum.MANAGE_ORDERS,
name: "Manage orders..",
__typename: "UserPermission"
}
]
);
it("should be empty if no active filters", () => {
const filterQueryParams = getFilterQueryParams(

View file

@ -10,7 +10,6 @@ import RequirePermissions from "@saleor/components/RequirePermissions";
import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Skeleton from "@saleor/components/Skeleton";
import { makeStyles } from "@saleor/macaw-ui";
import { UserPermissionProps } from "@saleor/types";
import { PermissionEnum } from "@saleor/types/globalTypes";
import React from "react";
import { useIntl } from "react-intl";
@ -33,7 +32,7 @@ const useStyles = makeStyles(
{ name: "HomeNotificationTable" }
);
interface HomeNotificationTableProps extends UserPermissionProps {
interface HomeNotificationTableProps {
ordersToCapture: number;
ordersToFulfill: number;
productsOutOfStock: number;
@ -53,7 +52,6 @@ const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
ordersToCapture,
ordersToFulfill,
productsOutOfStock,
userPermissions,
noChannel
} = props;
@ -67,7 +65,6 @@ const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
<TableBody className={classes.tableRow}>
{noChannel && (
<RequirePermissions
userPermissions={userPermissions}
requiredPermissions={[PermissionEnum.MANAGE_CHANNELS]}
>
<TableRow hover={true} onClick={onCreateNewChannelClick}>
@ -83,7 +80,6 @@ const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
</RequirePermissions>
)}
<RequirePermissions
userPermissions={userPermissions}
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
>
<TableRow hover={true} onClick={onOrdersToFulfillClick}>
@ -128,7 +124,6 @@ const HomeNotificationTable: React.FC<HomeNotificationTableProps> = props => {
</TableRow>
</RequirePermissions>
<RequirePermissions
userPermissions={userPermissions}
requiredPermissions={[PermissionEnum.MANAGE_PRODUCTS]}
>
<TableRow hover={true} onClick={onProductsOutOfStockClick}>

View file

@ -5,7 +5,6 @@ import Money from "@saleor/components/Money";
import RequirePermissions from "@saleor/components/RequirePermissions";
import Skeleton from "@saleor/components/Skeleton";
import { makeStyles } from "@saleor/macaw-ui";
import { UserPermissionProps } from "@saleor/types";
import { PermissionEnum } from "@saleor/types/globalTypes";
import React from "react";
@ -44,7 +43,7 @@ const useStyles = makeStyles(
{ name: "HomePage" }
);
export interface HomePageProps extends UserPermissionProps {
export interface HomePageProps {
activities: Home_activities_edges_node[];
orders: number | null;
ordersToCapture: number | null;
@ -76,7 +75,6 @@ const HomePage: React.FC<HomePageProps> = props => {
ordersToCapture = 0,
ordersToFulfill = 0,
productsOutOfStock = 0,
userPermissions = [],
noChannel
} = props;
@ -89,7 +87,6 @@ const HomePage: React.FC<HomePageProps> = props => {
<Grid>
<div>
<RequirePermissions
userPermissions={userPermissions}
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
>
<div className={classes.cardContainer}>
@ -141,13 +138,11 @@ const HomePage: React.FC<HomePageProps> = props => {
ordersToCapture={ordersToCapture}
ordersToFulfill={ordersToFulfill}
productsOutOfStock={productsOutOfStock}
userPermissions={userPermissions}
noChannel={noChannel}
/>
<CardSpacer />
{topProducts && (
<RequirePermissions
userPermissions={userPermissions}
requiredPermissions={[
PermissionEnum.MANAGE_ORDERS,
PermissionEnum.MANAGE_PRODUCTS
@ -165,7 +160,6 @@ const HomePage: React.FC<HomePageProps> = props => {
{activities && (
<div>
<RequirePermissions
userPermissions={userPermissions}
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
>
<HomeActivityCard

View file

@ -65,7 +65,6 @@ const HomeSection = () => {
ordersToFulfill={data?.ordersToFulfill?.totalCount}
productsOutOfStock={data?.productsOutOfStock.totalCount}
userName={getUserName(user, true)}
userPermissions={user?.userPermissions}
noChannel={noChannel}
/>
);

View file

@ -12,7 +12,7 @@ import useStateFromProps from "@saleor/hooks/useStateFromProps";
import { buttonMessages } from "@saleor/intl";
import { Button, makeStyles } from "@saleor/macaw-ui";
import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers";
import { FetchMoreProps, UserPermissionProps } from "@saleor/types";
import { FetchMoreProps } from "@saleor/types";
import { PermissionEnum } from "@saleor/types/globalTypes";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import React from "react";
@ -55,9 +55,7 @@ export interface CustomerEditData {
prevUserEmail?: string;
}
export interface OrderCustomerProps
extends Partial<FetchMoreProps>,
UserPermissionProps {
export interface OrderCustomerProps extends Partial<FetchMoreProps> {
order: OrderDetails_order;
users?: SearchCustomers_search_edges_node[];
loading?: boolean;
@ -79,7 +77,6 @@ const OrderCustomer: React.FC<OrderCustomerProps> = props => {
loading,
order,
users,
userPermissions,
onCustomerEdit,
onBillingAddressEdit,
onFetchMore: onFetchMoreUsers,
@ -131,7 +128,6 @@ const OrderCustomer: React.FC<OrderCustomerProps> = props => {
toolbar={
!!canEditCustomer && (
<RequirePermissions
userPermissions={userPermissions}
requiredPermissions={[PermissionEnum.MANAGE_ORDERS]}
>
<Button
@ -209,7 +205,6 @@ const OrderCustomer: React.FC<OrderCustomerProps> = props => {
{user.email}
</Typography>
<RequirePermissions
userPermissions={userPermissions}
requiredPermissions={[PermissionEnum.MANAGE_USERS]}
>
<div>

View file

@ -15,7 +15,6 @@ import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
import { Backlink } from "@saleor/macaw-ui";
import { makeStyles } from "@saleor/macaw-ui";
import OrderChannelSectionCard from "@saleor/orders/components/OrderChannelSectionCard";
import { UserPermissionProps } from "@saleor/types";
import { mapMetadataItemToInput } from "@saleor/utils/maps";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import React from "react";
@ -55,7 +54,7 @@ const useStyles = makeStyles(
}
);
export interface OrderDetailsPageProps extends UserPermissionProps {
export interface OrderDetailsPageProps {
order: OrderDetails_order;
shop: OrderDetails_shop;
shippingMethods?: Array<{
@ -114,7 +113,6 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
order,
shop,
saveButtonBarState,
userPermissions,
onBack,
onBillingAddressEdit,
onFulfillmentApprove,
@ -299,7 +297,6 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
canEditAddresses={canEditAddresses}
canEditCustomer={false}
order={order}
userPermissions={userPermissions}
onBillingAddressEdit={onBillingAddressEdit}
onShippingAddressEdit={onShippingAddressEdit}
onProfileView={onProfileView}

View file

@ -14,7 +14,7 @@ import { Backlink } from "@saleor/macaw-ui";
import { makeStyles } from "@saleor/macaw-ui";
import DraftOrderChannelSectionCard from "@saleor/orders/components/DraftOrderChannelSectionCard";
import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers";
import { FetchMoreProps, UserPermissionProps } from "@saleor/types";
import { FetchMoreProps } from "@saleor/types";
import React from "react";
import { useIntl } from "react-intl";
@ -37,9 +37,7 @@ const useStyles = makeStyles(
{ name: "OrderDraftPage" }
);
export interface OrderDraftPageProps
extends FetchMoreProps,
UserPermissionProps {
export interface OrderDraftPageProps extends FetchMoreProps {
disabled: boolean;
order: OrderDetails_order;
users: SearchCustomers_search_edges_node[];
@ -85,8 +83,7 @@ const OrderDraftPage: React.FC<OrderDraftPageProps> = props => {
onProfileView,
order,
users,
usersLoading,
userPermissions
usersLoading
} = props;
const classes = useStyles(props);
@ -147,7 +144,6 @@ const OrderDraftPage: React.FC<OrderDraftPageProps> = props => {
loading={usersLoading}
order={order}
users={users}
userPermissions={userPermissions}
onBillingAddressEdit={onBillingAddressEdit}
onCustomerEdit={onCustomerEdit}
onFetchMore={onFetchMore}

View file

@ -1,4 +1,3 @@
import { useUser } from "@saleor/auth";
import { WindowTitle } from "@saleor/components/WindowTitle";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import { useCustomerAddressesQuery } from "@saleor/customers/queries";
@ -84,7 +83,6 @@ export const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
}) => {
const order = data.order;
const navigate = useNavigator();
const { user } = useUser();
const {
loadMore,
@ -214,7 +212,6 @@ export const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
}
saveButtonBarState="default"
onProfileView={() => navigate(customerUrl(order.user.id))}
userPermissions={user?.userPermissions || []}
/>
</OrderLineDiscountProvider>
</OrderDiscountProvider>

View file

@ -1,4 +1,3 @@
import { useUser } from "@saleor/auth";
import { WindowTitle } from "@saleor/components/WindowTitle";
import { useCustomerAddressesQuery } from "@saleor/customers/queries";
import useNavigator from "@saleor/hooks/useNavigator";
@ -95,7 +94,6 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
const order = data?.order;
const shop = data?.shop;
const navigate = useNavigator();
const { user } = useUser();
const warehouses = useWarehouseList({
displayLoader: true,
@ -168,7 +166,6 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
]
)}
shippingMethods={data?.order?.shippingMethods || []}
userPermissions={user?.userPermissions || []}
onOrderCancel={() => openModal("cancel")}
onOrderFulfill={() => navigate(orderFulfillUrl(id))}
onFulfillmentApprove={fulfillmentId =>

View file

@ -1,4 +1,3 @@
import { useUser } from "@saleor/auth";
import { WindowTitle } from "@saleor/components/WindowTitle";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import { useCustomerAddressesQuery } from "@saleor/customers/queries";
@ -109,7 +108,6 @@ export const OrderUnconfirmedDetails: React.FC<OrderUnconfirmedDetailsProps> = (
const order = data.order;
const shop = data.shop;
const navigate = useNavigator();
const { user } = useUser();
const {
loadMore,
@ -201,7 +199,6 @@ export const OrderUnconfirmedDetails: React.FC<OrderUnconfirmedDetailsProps> = (
]
)}
shippingMethods={data?.order?.shippingMethods || []}
userPermissions={user?.userPermissions || []}
onOrderCancel={() => openModal("cancel")}
onOrderFulfill={() => navigate(orderFulfillUrl(id))}
onFulfillmentApprove={fulfillmentId =>

View file

@ -28,7 +28,6 @@ export interface ShippingZonesListPageProps
const ShippingZonesListPage: React.FC<ShippingZonesListPageProps> = ({
defaultWeightUnit,
disabled,
userPermissions,
onBack,
onSubmit,
...listProps
@ -52,7 +51,6 @@ const ShippingZonesListPage: React.FC<ShippingZonesListPageProps> = ({
</div>
<div>
<RequirePermissions
userPermissions={userPermissions}
requiredPermissions={[PermissionEnum.MANAGE_SETTINGS]}
>
<ShippingWeightUnitForm

View file

@ -73323,7 +73323,7 @@ exports[`Storyshots Views / Customers / Customer list default 1`] = `
>
<td
class="MuiTableCell-root-id MuiTableCell-footer-id"
colspan="4"
colspan="3"
>
<div
class="MuiToolbar-root-id MuiToolbar-regular-id Pagination-toolbar-id"
@ -74994,7 +74994,7 @@ exports[`Storyshots Views / Customers / Customer list loading 1`] = `
>
<td
class="MuiTableCell-root-id MuiTableCell-footer-id"
colspan="4"
colspan="3"
>
<div
class="MuiToolbar-root-id MuiToolbar-regular-id Pagination-toolbar-id"
@ -75348,7 +75348,7 @@ exports[`Storyshots Views / Customers / Customer list no data 1`] = `
>
<td
class="MuiTableCell-root-id MuiTableCell-footer-id"
colspan="4"
colspan="3"
>
<div
class="MuiToolbar-root-id MuiToolbar-regular-id Pagination-toolbar-id"
@ -75420,7 +75420,7 @@ exports[`Storyshots Views / Customers / Customer list no data 1`] = `
>
<td
class="MuiTableCell-root-id MuiTableCell-body-id"
colspan="4"
colspan="3"
>
No customers found
</td>
@ -235532,107 +235532,7 @@ exports[`Storyshots Views / Shipping / Shipping zones list default 1`] = `
</div>
</div>
</div>
<div>
<form>
<div
class="MuiPaper-root-id MuiCard-root-id MuiPaper-elevation0-id MuiPaper-rounded-id"
>
<div
class="MuiCardHeader-root-id"
>
<div
class="MuiCardHeader-content-id"
>
<span
class="MuiTypography-root-id MuiCardHeader-title-id MuiTypography-h5-id MuiTypography-displayBlock-id"
>
Configuration
</span>
</div>
</div>
<div
class="MuiCardContent-root-id"
>
<div
class="MuiFormControl-root-id SingleSelectField-formControl-id"
>
<label
class="MuiFormLabel-root-id MuiInputLabel-root-id SingleSelectField-label-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-filled-id MuiFormLabel-filled-id"
data-shrink="true"
>
Shipping Weight Unit
</label>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id"
>
<div
aria-haspopup="listbox"
aria-labelledby="mui-component-select-unit"
class="MuiSelect-root-id MuiSelect-select-id MuiSelect-selectMenu-id MuiSelect-outlined-id MuiInputBase-input-id MuiOutlinedInput-input-id"
id="mui-component-select-unit"
role="button"
tabindex="0"
>
KG
</div>
<input
aria-hidden="true"
class="MuiSelect-nativeInput-id"
name="unit"
tabindex="-1"
value="KG"
/>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id MuiSelect-icon-id MuiSelect-iconOutlined-id"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:143px"
>
<span>
</span>
</legend>
</fieldset>
</div>
<p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-filled-id"
>
This unit will be used as default shipping weight
</p>
</div>
</div>
<div
class="MuiCardActions-root-id MuiCardActions-spacing-id"
>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id MuiButton-outlinedSizeSmall-id MuiButton-sizeSmall-id"
data-test-id="save-unit"
tabindex="0"
type="button"
>
<span
class="MuiButton-label-id"
>
Save
</span>
</button>
</div>
</div>
</form>
</div>
<div />
</div>
</div>
</div>
@ -235953,107 +235853,7 @@ exports[`Storyshots Views / Shipping / Shipping zones list loading 1`] = `
</div>
</div>
</div>
<div>
<form>
<div
class="MuiPaper-root-id MuiCard-root-id MuiPaper-elevation0-id MuiPaper-rounded-id"
>
<div
class="MuiCardHeader-root-id"
>
<div
class="MuiCardHeader-content-id"
>
<span
class="MuiTypography-root-id MuiCardHeader-title-id MuiTypography-h5-id MuiTypography-displayBlock-id"
>
Configuration
</span>
</div>
</div>
<div
class="MuiCardContent-root-id"
>
<div
class="MuiFormControl-root-id SingleSelectField-formControl-id"
>
<label
class="MuiFormLabel-root-id MuiInputLabel-root-id SingleSelectField-label-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-filled-id MuiFormLabel-disabled-id MuiInputLabel-disabled-id MuiFormLabel-filled-id"
data-shrink="true"
>
Shipping Weight Unit
</label>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-disabled-id MuiOutlinedInput-disabled-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id"
>
<div
aria-disabled="true"
aria-haspopup="listbox"
aria-labelledby="mui-component-select-unit"
class="MuiSelect-root-id MuiSelect-select-id MuiSelect-selectMenu-id MuiSelect-outlined-id MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-disabled-id MuiOutlinedInput-disabled-id MuiSelect-disabled-id"
id="mui-component-select-unit"
role="button"
>
KG
</div>
<input
aria-hidden="true"
class="MuiSelect-nativeInput-id"
name="unit"
tabindex="-1"
value="KG"
/>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id MuiSelect-icon-id MuiSelect-iconOutlined-id MuiSelect-disabled-id"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:143px"
>
<span>
</span>
</legend>
</fieldset>
</div>
<p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-disabled-id MuiFormHelperText-filled-id"
>
This unit will be used as default shipping weight
</p>
</div>
</div>
<div
class="MuiCardActions-root-id MuiCardActions-spacing-id"
>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id MuiButton-outlinedSizeSmall-id MuiButton-sizeSmall-id"
data-test-id="save-unit"
tabindex="0"
type="button"
>
<span
class="MuiButton-label-id"
>
Save
</span>
</button>
</div>
</div>
</form>
</div>
<div />
</div>
</div>
</div>
@ -236255,107 +236055,7 @@ exports[`Storyshots Views / Shipping / Shipping zones list no data 1`] = `
</div>
</div>
</div>
<div>
<form>
<div
class="MuiPaper-root-id MuiCard-root-id MuiPaper-elevation0-id MuiPaper-rounded-id"
>
<div
class="MuiCardHeader-root-id"
>
<div
class="MuiCardHeader-content-id"
>
<span
class="MuiTypography-root-id MuiCardHeader-title-id MuiTypography-h5-id MuiTypography-displayBlock-id"
>
Configuration
</span>
</div>
</div>
<div
class="MuiCardContent-root-id"
>
<div
class="MuiFormControl-root-id SingleSelectField-formControl-id"
>
<label
class="MuiFormLabel-root-id MuiInputLabel-root-id SingleSelectField-label-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-filled-id MuiFormLabel-filled-id"
data-shrink="true"
>
Shipping Weight Unit
</label>
<div
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id"
>
<div
aria-haspopup="listbox"
aria-labelledby="mui-component-select-unit"
class="MuiSelect-root-id MuiSelect-select-id MuiSelect-selectMenu-id MuiSelect-outlined-id MuiInputBase-input-id MuiOutlinedInput-input-id"
id="mui-component-select-unit"
role="button"
tabindex="0"
>
KG
</div>
<input
aria-hidden="true"
class="MuiSelect-nativeInput-id"
name="unit"
tabindex="-1"
value="KG"
/>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id MuiSelect-icon-id MuiSelect-iconOutlined-id"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:143px"
>
<span>
</span>
</legend>
</fieldset>
</div>
<p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-filled-id"
>
This unit will be used as default shipping weight
</p>
</div>
</div>
<div
class="MuiCardActions-root-id MuiCardActions-spacing-id"
>
<button
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-outlined-id MuiButton-outlinedPrimary-id MuiButton-outlinedSizeSmall-id MuiButton-sizeSmall-id"
data-test-id="save-unit"
tabindex="0"
type="button"
>
<span
class="MuiButton-label-id"
>
Save
</span>
</button>
</div>
</div>
</form>
</div>
<div />
</div>
</div>
</div>

View file

@ -1,8 +1,15 @@
import ActionDialog from "@saleor/components/ActionDialog";
import ActionDialogComponent from "@saleor/components/ActionDialog";
import { storiesOf } from "@storybook/react";
import React from "react";
import Decorator from "../../Decorator";
import { MockedUserProvider } from "../customers/MockedUserProvider";
const ActionDialog = props => (
<MockedUserProvider>
<ActionDialogComponent {...props} />
</MockedUserProvider>
);
storiesOf("Generics / ActionDialog", module)
.addDecorator(Decorator)

View file

@ -2,11 +2,12 @@ import { AccountErrorCode } from "@saleor/types/globalTypes";
import { storiesOf } from "@storybook/react";
import React from "react";
import CustomerDetailsPage, {
import CustomerDetailsPageComponent, {
CustomerDetailsPageProps
} from "../../../customers/components/CustomerDetailsPage";
import { customer } from "../../../customers/fixtures";
import Decorator from "../../Decorator";
import { MockedUserProvider } from "./MockedUserProvider";
const props: Omit<CustomerDetailsPageProps, "classes"> = {
customer,
@ -28,6 +29,12 @@ interface CustomerDetailsPageErrors {
note: string;
}
const CustomerDetailsPage = props => (
<MockedUserProvider>
<CustomerDetailsPageComponent {...props} />
</MockedUserProvider>
);
storiesOf("Views / Customers / Customer details", module)
.addDecorator(Decorator)
.add("default", () => <CustomerDetailsPage {...props} />)

View file

@ -2,7 +2,7 @@ import { CustomerListUrlSortField } from "@saleor/customers/urls";
import { storiesOf } from "@storybook/react";
import React from "react";
import CustomerListPage, {
import CustomerListPageComponent, {
CustomerListPageProps
} from "../../../customers/components/CustomerListPage";
import { customerList } from "../../../customers/fixtures";
@ -15,6 +15,7 @@ import {
tabPageProps
} from "../../../fixtures";
import Decorator from "../../Decorator";
import { MockedUserProvider } from "./MockedUserProvider";
const props: CustomerListPageProps = {
...filterPageProps,
@ -46,6 +47,12 @@ const props: CustomerListPageProps = {
}
};
const CustomerListPage = props => (
<MockedUserProvider>
<CustomerListPageComponent {...props} />
</MockedUserProvider>
);
storiesOf("Views / Customers / Customer list", module)
.addDecorator(Decorator)
.add("default", () => <CustomerListPage {...props} />)

View file

@ -0,0 +1,31 @@
import { UserContext } from "@saleor/auth";
import { adminUserPermissions } from "@saleor/fixtures";
import { User_userPermissions } from "@saleor/fragments/types/User";
import * as React from "react";
export const MockedUserProvider: React.FC<{
customPermissions?: User_userPermissions[];
}> = ({ customPermissions, children }) => (
<UserContext.Provider
value={{
login: undefined,
loginByExternalPlugin: undefined,
logout: undefined,
requestLoginByExternalPlugin: undefined,
authenticating: false,
authenticated: false,
user: {
id: "0",
email: "email@email.me",
firstName: "user",
lastName: "user",
isStaff: true,
userPermissions: customPermissions ?? adminUserPermissions,
avatar: null,
__typename: "User"
}
}}
>
{children}
</UserContext.Provider>
);

View file

@ -5,9 +5,12 @@ import { mapEdgesToItems } from "@saleor/utils/maps";
import { storiesOf } from "@storybook/react";
import React from "react";
import HomePage, { HomePageProps } from "../../../home/components/HomePage";
import HomePageComponent, {
HomePageProps
} from "../../../home/components/HomePage";
import { shop as shopFixture } from "../../../home/fixtures";
import Decorator from "../../Decorator";
import { MockedUserProvider } from "../customers/MockedUserProvider";
const shop = shopFixture(placeholderImage);
@ -25,8 +28,17 @@ const homePageProps: Omit<HomePageProps, "classes"> = {
productsOutOfStock: shop.productsOutOfStock.totalCount,
sales: shop.salesToday.gross,
topProducts: mapEdgesToItems(shop.productTopToday),
userName: "admin@example.com",
userPermissions: adminUserPermissions
userName: "admin@example.com"
};
const HomePage = props => {
const customPermissions = props?.customPermissions;
return (
<MockedUserProvider customPermissions={customPermissions}>
<HomePageComponent {...props} />
</MockedUserProvider>
);
};
storiesOf("Views / HomePage", module)
@ -49,12 +61,12 @@ storiesOf("Views / HomePage", module)
<HomePage {...homePageProps} topProducts={[]} activities={[]} />
))
.add("no permissions", () => (
<HomePage {...homePageProps} userPermissions={[]} />
<HomePage {...homePageProps} customPermissions={[]} />
))
.add("product permissions", () => (
<HomePage
{...homePageProps}
userPermissions={adminUserPermissions.filter(
customPermissions={adminUserPermissions.filter(
perm => perm.code === PermissionEnum.MANAGE_PRODUCTS
)}
/>
@ -62,7 +74,7 @@ storiesOf("Views / HomePage", module)
.add("order permissions", () => (
<HomePage
{...homePageProps}
userPermissions={adminUserPermissions.filter(
customPermissions={adminUserPermissions.filter(
perm => perm.code === PermissionEnum.MANAGE_ORDERS
)}
/>

View file

@ -1,12 +1,12 @@
import { adminUserPermissions } from "@saleor/fixtures";
import { storiesOf } from "@storybook/react";
import React from "react";
import OrderCustomer, {
import OrderCustomerComponent, {
OrderCustomerProps
} from "../../../orders/components/OrderCustomer";
import { clients, order as orderFixture } from "../../../orders/fixtures";
import Decorator from "../../Decorator";
import { MockedUserProvider } from "../customers/MockedUserProvider";
const order = orderFixture("");
@ -19,10 +19,19 @@ const props: Omit<OrderCustomerProps, "classes"> = {
onProfileView: () => undefined,
onShippingAddressEdit: undefined,
order,
userPermissions: adminUserPermissions,
users: clients
};
const OrderCustomer = props => {
const customPermissions = props?.customPermissions;
return (
<MockedUserProvider customPermissions={customPermissions}>
<OrderCustomerComponent {...props} />
</MockedUserProvider>
);
};
storiesOf("Orders / OrderCustomer", module)
.addDecorator(Decorator)
.add("default", () => <OrderCustomer {...props} />)
@ -40,5 +49,5 @@ storiesOf("Orders / OrderCustomer", module)
<OrderCustomer {...props} canEditAddresses={true} canEditCustomer={true} />
))
.add("no user permissions", () => (
<OrderCustomer {...props} userPermissions={[]} />
<OrderCustomer {...props} customPermissions={[]} />
));

View file

@ -1,5 +1,4 @@
import placeholderImage from "@assets/images/placeholder60x60.png";
import { adminUserPermissions } from "@saleor/fixtures";
import { storiesOf } from "@storybook/react";
import React from "react";
@ -43,8 +42,7 @@ const props: Omit<OrderDetailsPageProps, "classes"> = {
onSubmit: () => undefined,
order,
shop: shopFixture,
saveButtonBarState: "default",
userPermissions: adminUserPermissions
saveButtonBarState: "default"
};
storiesOf("Views / Orders / Order details", module)

View file

@ -1,13 +1,14 @@
import placeholderImage from "@assets/images/placeholder60x60.png";
import { adminUserPermissions, fetchMoreProps } from "@saleor/fixtures";
import { fetchMoreProps } from "@saleor/fixtures";
import { storiesOf } from "@storybook/react";
import React from "react";
import OrderDraftPage, {
import OrderDraftPageComponent, {
OrderDraftPageProps
} from "../../../../orders/components/OrderDraftPage";
import { clients, draftOrder } from "../../../../orders/fixtures";
import Decorator from "../../../Decorator";
import { MockedUserProvider } from "../../customers/MockedUserProvider";
import { getDiscountsProvidersWrapper } from "./utils";
const order = draftOrder(placeholderImage);
@ -31,13 +32,22 @@ const props: Omit<OrderDraftPageProps, "classes"> = {
onShippingMethodEdit: undefined,
order,
saveButtonBarState: "default",
userPermissions: adminUserPermissions,
users: clients,
usersLoading: false
};
const DiscountsDecorator = getDiscountsProvidersWrapper(order);
const OrderDraftPage = props => {
const customPermissions = props?.customPermissions;
return (
<MockedUserProvider customPermissions={customPermissions}>
<OrderDraftPageComponent {...props} />
</MockedUserProvider>
);
};
storiesOf("Views / Orders / Order draft", module)
.addDecorator(Decorator)
.addDecorator(DiscountsDecorator)
@ -49,5 +59,5 @@ storiesOf("Views / Orders / Order draft", module)
<OrderDraftPage {...props} order={{ ...order, lines: [] }} />
))
.add("no user permissions", () => (
<OrderDraftPage {...props} userPermissions={[]} />
<OrderDraftPage {...props} customPermissions={[]} />
));