= ({
});
};
+ const isValueChecked = (displayValue: MultiAutocompleteChoiceType) =>
+ filterField.value.includes(displayValue.value);
+
+ const filteredValuesChecked = initialFieldDisplayValues.filter(
+ isValueChecked
+ );
+
+ const filteredValuesUnchecked = fieldDisplayValues.filter(
+ displayValue => !isValueChecked(displayValue)
+ );
+
+ const displayHr = !!filteredValuesChecked.length;
+
return (
= ({
}}
onChange={event => filterField.onSearchChange(event.target.value)}
/>
- {fieldDisplayValues.map(displayValue => (
+ {filteredValuesChecked.map(displayValue => (
= ({
)}
- {availableOptions.map(option => (
+ {filteredValuesUnchecked.map(option => (
{
- filters: IFilter
;
- onFilterPropertyChange: React.Dispatch>;
- onClear: () => void;
- onSubmit: () => void;
- currencySymbol?: string;
-}
-
-const useStyles = makeStyles(
- theme => ({
- actionBar: {
- alignItems: "center",
- display: "flex",
- justifyContent: "space-between",
- padding: theme.spacing(1, 3)
- },
- andLabel: {
- margin: theme.spacing(0, 2)
- },
- arrow: {
- marginRight: theme.spacing(2)
- },
- clear: {
- marginRight: theme.spacing(1)
- },
- filterFieldBar: {
- "&:not(:last-of-type)": {
- borderBottom: `1px solid ${theme.palette.divider}`
- },
- padding: theme.spacing(1, 2.5)
- },
- filterSettings: {
- background: fade(theme.palette.primary.main, 0.2),
- padding: theme.spacing(2, 3)
- },
- input: {
- padding: "12px 0 9px 12px"
- },
- inputRange: {
- alignItems: "center",
- display: "flex"
- },
- label: {
- fontWeight: 600
- },
- option: {
- left: -theme.spacing(0.5),
- position: "relative"
- },
- optionRadio: {
- left: -theme.spacing(0.25)
- }
- }),
- { name: "FilterContent" }
-);
-
-function getIsFilterMultipleChoices(
- intl: IntlShape
-): SingleAutocompleteChoiceType[] {
- return [
- {
- label: intl.formatMessage({
- defaultMessage: "equal to",
- description: "is filter range or value"
- }),
- value: FilterType.SINGULAR
- },
- {
- label: intl.formatMessage({
- defaultMessage: "between",
- description: "is filter range or value"
- }),
- value: FilterType.MULTIPLE
- }
- ];
-}
-
-const filterFieldTestingContext = "filter-field";
-
-const FilterContent: React.FC = ({
- currencySymbol,
- filters,
- onClear,
- onFilterPropertyChange,
- onSubmit
-}) => {
- const intl = useIntl();
- const classes = useStyles({});
- const [
- autocompleteDisplayValues,
- setAutocompleteDisplayValues
- ] = useStateFromProps>(
- filters.reduce((acc, filterField) => {
- if (filterField.type === FieldType.autocomplete) {
- acc[filterField.name] = filterField.displayValues;
- }
-
- return acc;
- }, {})
- );
-
- return (
-
-
-
- );
-};
-FilterContent.displayName = "FilterContent";
-export default FilterContent;
diff --git a/src/components/Filter/FilterContent/FilterContent.tsx b/src/components/Filter/FilterContent/FilterContent.tsx
new file mode 100644
index 000000000..3d587e465
--- /dev/null
+++ b/src/components/Filter/FilterContent/FilterContent.tsx
@@ -0,0 +1,158 @@
+import { Typography } from "@material-ui/core";
+import Paper from "@material-ui/core/Paper";
+import CollectionWithDividers from "@saleor/components/CollectionWithDividers";
+import Hr from "@saleor/components/Hr";
+import useStateFromProps from "@saleor/hooks/useStateFromProps";
+import React from "react";
+
+import { FilterAutocompleteDisplayValues } from "../FilterAutocompleteField";
+import { FilterReducerAction } from "../reducer";
+import {
+ FieldType,
+ FilterErrorMessages,
+ FilterErrors,
+ IFilter,
+ IFilterElement
+} from "../types";
+import FilterContentBody, { FilterContentBodyProps } from "./FilterContentBody";
+import FilterContentBodyNameField from "./FilterContentBodyNameField";
+import FilterContentHeader from "./FilterContentHeader";
+import FilterErrorsList from "./FilterErrorsList";
+
+export interface FilterContentProps {
+ filters: IFilter;
+ onFilterPropertyChange: React.Dispatch>;
+ onClear: () => void;
+ onSubmit: () => void;
+ currencySymbol?: string;
+ dataStructure: IFilter;
+ errors?: FilterErrors;
+ errorMessages?: FilterErrorMessages;
+}
+
+const FilterContent: React.FC = ({
+ currencySymbol,
+ errors,
+ errorMessages,
+ filters,
+ onClear,
+ onFilterPropertyChange,
+ onSubmit,
+ dataStructure
+}) => {
+ const getAutocompleteValuesWithNewValues = (
+ autocompleteDisplayValues: FilterAutocompleteDisplayValues,
+ filterField: IFilterElement
+ ) => {
+ if (filterField.type === FieldType.autocomplete) {
+ return {
+ ...autocompleteDisplayValues,
+ [filterField.name]: filterField.options
+ };
+ }
+
+ return autocompleteDisplayValues;
+ };
+
+ const initialAutocompleteDisplayValues = filters.reduce(
+ (acc, filterField) => {
+ if (filterField.multipleFields) {
+ return filterField.multipleFields.reduce(
+ getAutocompleteValuesWithNewValues,
+ {}
+ );
+ }
+
+ return getAutocompleteValuesWithNewValues(acc, filterField);
+ },
+ {}
+ );
+
+ const [
+ autocompleteDisplayValues,
+ setAutocompleteDisplayValues
+ ] = useStateFromProps(
+ initialAutocompleteDisplayValues
+ );
+
+ const commonFilterBodyProps: Omit<
+ FilterContentBodyProps,
+ "filter" | "onFilterPropertyChange"
+ > = {
+ currencySymbol,
+ autocompleteDisplayValues,
+ setAutocompleteDisplayValues,
+ initialAutocompleteDisplayValues
+ };
+
+ const handleMultipleFieldPropertyChange = function(
+ action: FilterReducerAction
+ ) {
+ const { update } = action.payload;
+
+ onFilterPropertyChange({
+ ...action,
+ payload: { ...action.payload, update: { ...update, active: true } }
+ });
+ };
+
+ const getFilterFromCurrentData = function(
+ filter: IFilterElement
+ ) {
+ return filters.find(({ name }) => filter.name === name);
+ };
+
+ return (
+
+
+
+ );
+};
+FilterContent.displayName = "FilterContent";
+export default FilterContent;
diff --git a/src/components/Filter/FilterContent/FilterContentBody.tsx b/src/components/Filter/FilterContent/FilterContentBody.tsx
new file mode 100644
index 000000000..651e86414
--- /dev/null
+++ b/src/components/Filter/FilterContent/FilterContentBody.tsx
@@ -0,0 +1,298 @@
+import {
+ fade,
+ FormControlLabel,
+ makeStyles,
+ Radio,
+ TextField
+} from "@material-ui/core";
+import FormSpacer from "@saleor/components/FormSpacer";
+import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
+import SingleSelectField from "@saleor/components/SingleSelectField";
+import classNames from "classnames";
+import React from "react";
+import { FormattedMessage, useIntl } from "react-intl";
+
+import Arrow from "../Arrow";
+import FilterAutocompleteField, {
+ FilterAutocompleteDisplayValues
+} from "../FilterAutocompleteField";
+import FilterOptionField from "../FilterOptionField";
+import { FilterReducerAction } from "../reducer";
+import { FieldType, FilterType, IFilterElement } from "../types";
+import { getIsFilterMultipleChoices } from "./utils";
+
+const useStyles = makeStyles(
+ theme => ({
+ andLabel: {
+ margin: theme.spacing(0, 2)
+ },
+ arrow: {
+ marginRight: theme.spacing(2)
+ },
+ filterSettings: {
+ background: fade(theme.palette.primary.main, 0.1),
+ padding: theme.spacing(2, 3)
+ },
+ input: {
+ padding: "12px 0 9px 12px"
+ },
+ inputRange: {
+ alignItems: "center",
+ display: "flex"
+ },
+
+ option: {
+ left: -theme.spacing(0.5),
+ position: "relative"
+ },
+ optionRadio: {
+ left: -theme.spacing(0.25)
+ }
+ }),
+ { name: "FilterContentBody" }
+);
+
+const filterTestingContext = "filter-field";
+
+export interface FilterContentBodyProps {
+ children?: React.ReactNode;
+ filter: IFilterElement;
+ currencySymbol?: string;
+ initialAutocompleteDisplayValues: FilterAutocompleteDisplayValues;
+ onFilterPropertyChange: React.Dispatch>;
+ autocompleteDisplayValues: FilterAutocompleteDisplayValues;
+ setAutocompleteDisplayValues: React.Dispatch<
+ React.SetStateAction>
+ >;
+}
+
+const FilterContentBody: React.FC = ({
+ filter,
+ children,
+ currencySymbol,
+ onFilterPropertyChange,
+ autocompleteDisplayValues,
+ setAutocompleteDisplayValues,
+ initialAutocompleteDisplayValues
+}) => {
+ const intl = useIntl();
+ const classes = useStyles({});
+
+ if (!filter?.active) {
+ return null;
+ }
+
+ return (
+
+ {children}
+ {filter.type === FieldType.text && (
+
+ onFilterPropertyChange({
+ payload: {
+ name: filter.name,
+ update: {
+ value: [event.target.value, filter.value[1]]
+ }
+ },
+ type: "set-property"
+ })
+ }
+ />
+ )}
+ {[FieldType.date, FieldType.price, FieldType.number].includes(
+ filter.type
+ ) && (
+ <>
+
+ onFilterPropertyChange({
+ payload: {
+ name: filter.name,
+ update: {
+ multiple: event.target.value === FilterType.MULTIPLE
+ }
+ },
+ type: "set-property"
+ })
+ }
+ />
+
+
+
+ {filter.multiple ? (
+ <>
+
+ onFilterPropertyChange({
+ payload: {
+ name: filter.name,
+ update: {
+ value: [event.target.value, filter.value[1]]
+ }
+ },
+ type: "set-property"
+ })
+ }
+ />
+
+
+
+
+ onFilterPropertyChange({
+ payload: {
+ name: filter.name,
+ update: {
+ value: [filter.value[0], event.target.value]
+ }
+ },
+ type: "set-property"
+ })
+ }
+ />
+ >
+ ) : (
+
+ onFilterPropertyChange({
+ payload: {
+ name: filter.name,
+ update: {
+ value: [event.target.value, filter.value[1]]
+ }
+ },
+ type: "set-property"
+ })
+ }
+ />
+ )}
+
+ >
+ )}
+ {filter.type === FieldType.options && (
+
+ )}
+ {filter.type === FieldType.boolean &&
+ filter.options.map(option => (
+
+
+ }
+ label={option.label}
+ name={filter.name}
+ onChange={() =>
+ onFilterPropertyChange({
+ payload: {
+ name: filter.name,
+ update: {
+ value: [option.value]
+ }
+ },
+ type: "set-property"
+ })
+ }
+ />
+
+ ))}
+ {filter.type === FieldType.autocomplete && filter.multiple && (
+
+ )}
+
+ );
+};
+
+export default FilterContentBody;
diff --git a/src/components/Filter/FilterContent/FilterContentBodyNameField.tsx b/src/components/Filter/FilterContent/FilterContentBodyNameField.tsx
new file mode 100644
index 000000000..6bd6849de
--- /dev/null
+++ b/src/components/Filter/FilterContent/FilterContentBodyNameField.tsx
@@ -0,0 +1,61 @@
+import { Checkbox, FormControlLabel, makeStyles } from "@material-ui/core";
+import React from "react";
+
+import { FilterReducerAction } from "../reducer";
+import { IFilterElement } from "../types";
+
+const useStyles = makeStyles(
+ theme => ({
+ container: {
+ "&:not(:last-of-type)": {
+ borderBottom: `1px solid ${theme.palette.divider}`
+ },
+ padding: theme.spacing(1, 2.5)
+ }
+ }),
+ { name: "FilterContentBodyNameField" }
+);
+
+export interface FilterContentBodyNameFieldProps {
+ filter: IFilterElement;
+ onFilterPropertyChange: React.Dispatch>;
+}
+
+const FilterContentBodyNameField: React.FC = ({
+ filter,
+ onFilterPropertyChange
+}) => {
+ const classes = useStyles({});
+
+ if (!filter) {
+ return null;
+ }
+
+ return (
+
+
+ }
+ label={filter.label}
+ onChange={() =>
+ onFilterPropertyChange({
+ payload: {
+ name: filter.name,
+ update: {
+ active: !filter.active
+ }
+ },
+ type: "set-property"
+ })
+ }
+ />
+
+ );
+};
+
+export default FilterContentBodyNameField;
diff --git a/src/components/Filter/FilterContent/FilterContentHeader.tsx b/src/components/Filter/FilterContent/FilterContentHeader.tsx
new file mode 100644
index 000000000..7b704ea95
--- /dev/null
+++ b/src/components/Filter/FilterContent/FilterContentHeader.tsx
@@ -0,0 +1,55 @@
+import { Button, makeStyles, Typography } from "@material-ui/core";
+import { buttonMessages } from "@saleor/intl";
+import React from "react";
+import { FormattedMessage } from "react-intl";
+
+const useStyles = makeStyles(
+ theme => ({
+ container: {
+ alignItems: "center",
+ display: "flex",
+ justifyContent: "space-between",
+ padding: theme.spacing(1, 3)
+ },
+ clear: {
+ marginRight: theme.spacing(1)
+ },
+ label: {
+ fontWeight: 600
+ }
+ }),
+ { name: "FilterContentHeader" }
+);
+
+interface FilterContentHeaderProps {
+ onClear: () => void;
+}
+
+const FilterContentHeader: React.FC = ({
+ onClear
+}) => {
+ const classes = useStyles({});
+
+ return (
+
+ );
+};
+
+export default FilterContentHeader;
diff --git a/src/components/Filter/FilterContent/FilterErrorsList.tsx b/src/components/Filter/FilterContent/FilterErrorsList.tsx
new file mode 100644
index 000000000..1d8e57525
--- /dev/null
+++ b/src/components/Filter/FilterContent/FilterErrorsList.tsx
@@ -0,0 +1,80 @@
+import { fade, makeStyles } from "@material-ui/core/styles";
+import Typography from "@material-ui/core/Typography";
+import InlineAlert from "@saleor/components/Alert/InlineAlert";
+import { useStyles as useDotStyles } from "@saleor/components/StatusLabel";
+import classNames from "classnames";
+import React from "react";
+import { useIntl } from "react-intl";
+
+import { FilterErrorMessages, FilterErrors, IFilterElement } from "../types";
+
+const useStyles = makeStyles(
+ theme => ({
+ container: {
+ backgroundColor: fade(theme.palette.primary.main, 0.1),
+ padding: theme.spacing(3, 3, 0, 3)
+ },
+ listItemTitle: {
+ color: theme.palette.primary.contrastText
+ },
+ dot: {
+ backgroundColor: theme.palette.primary.contrastText,
+ marginRight: theme.spacing(1)
+ },
+ itemContainer: {
+ display: "flex",
+ alignItems: "center"
+ }
+ }),
+ { name: "FilterErrorsList" }
+);
+
+interface FilterErrorsListProps {
+ filter: IFilterElement;
+ errors?: FilterErrors;
+ errorMessages?: FilterErrorMessages;
+}
+
+const FilterErrorsList: React.FC = ({
+ filter: { name, multipleFields },
+ errors = [],
+ errorMessages
+}) => {
+ const classes = useStyles({});
+ const dotClasses = useDotStyles({});
+ const intl = useIntl();
+
+ const hasError = (fieldName: string) =>
+ !!errors.find(errorName => errorName === fieldName);
+
+ const hasErrorsToShow = () => {
+ if (!!multipleFields?.length) {
+ return multipleFields.some(multipleField => hasError(multipleField.name));
+ }
+
+ return hasError(name);
+ };
+
+ if (!errors.length || !hasErrorsToShow()) {
+ return null;
+ }
+
+ return (
+
+ {!!errors.length && (
+
+ {errors.map(fieldName => (
+
+
+
+ {intl.formatMessage(errorMessages?.[fieldName])}
+
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default FilterErrorsList;
diff --git a/src/components/Filter/FilterContent/index.tsx b/src/components/Filter/FilterContent/index.tsx
new file mode 100644
index 000000000..21dd6c64b
--- /dev/null
+++ b/src/components/Filter/FilterContent/index.tsx
@@ -0,0 +1,2 @@
+export * from "./FilterContent";
+export { default } from "./FilterContent";
diff --git a/src/components/Filter/FilterContent/utils.ts b/src/components/Filter/FilterContent/utils.ts
new file mode 100644
index 000000000..961e30a81
--- /dev/null
+++ b/src/components/Filter/FilterContent/utils.ts
@@ -0,0 +1,27 @@
+import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
+import { IntlShape } from "react-intl";
+
+import { FilterType } from "../types";
+
+export function getIsFilterMultipleChoices(
+ intl: IntlShape
+): SingleAutocompleteChoiceType[] {
+ return [
+ {
+ label: intl.formatMessage({
+ defaultMessage: "equal to",
+ description: "is filter range or value",
+ id: "is filter range equal to value"
+ }),
+ value: FilterType.SINGULAR
+ },
+ {
+ label: intl.formatMessage({
+ defaultMessage: "between",
+ description: "is filter range or value",
+ id: "is filter range between value"
+ }),
+ value: FilterType.MULTIPLE
+ }
+ ];
+}
diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts
index f872a87f1..f157283c6 100644
--- a/src/components/Filter/types.ts
+++ b/src/components/Filter/types.ts
@@ -1,4 +1,5 @@
import { FetchMoreProps, SearchPageProps } from "@saleor/types";
+import { MessageDescriptor } from "react-intl";
import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField";
import { FilterReducerAction } from "./reducer";
@@ -28,7 +29,9 @@ export interface IFilterElement
group?: T;
label: string;
name: T;
- type: FieldType;
+ type?: FieldType;
+ required?: boolean;
+ multipleFields?: IFilterElement[];
}
export interface FilterBaseFieldProps {
@@ -36,6 +39,13 @@ export interface FilterBaseFieldProps {
onFilterPropertyChange: React.Dispatch>;
}
+export type FilterErrors = string[];
+
+export type FilterErrorMessages = Record<
+ T,
+ MessageDescriptor
+>;
+
export type IFilter = Array>;
export enum FilterType {
diff --git a/src/components/Filter/useFilter.ts b/src/components/Filter/useFilter.ts
index 0816cab13..925605bc6 100644
--- a/src/components/Filter/useFilter.ts
+++ b/src/components/Filter/useFilter.ts
@@ -9,15 +9,31 @@ export type UseFilter = [
() => void
];
+function getParsedInitialFilter(
+ initialFilter: IFilter
+): IFilter {
+ return initialFilter.reduce((resultFilter, filterField) => {
+ if (filterField.multipleFields) {
+ return resultFilter
+ .concat(filterField.multipleFields)
+ .concat([filterField]);
+ }
+
+ return resultFilter.concat(filterField);
+ }, []);
+}
+
function useFilter(initialFilter: IFilter): UseFilter {
+ const parsedInitialFilter = getParsedInitialFilter(initialFilter);
+
const [data, dispatchFilterAction] = useReducer<
React.Reducer, FilterReducerAction>
- >(reduceFilter, initialFilter);
+ >(reduceFilter, parsedInitialFilter);
const reset = () =>
dispatchFilterAction({
payload: {
- new: initialFilter.map(each => ({
+ new: parsedInitialFilter.map(each => ({
...each,
active: false,
value: []
@@ -29,7 +45,7 @@ function useFilter(initialFilter: IFilter): UseFilter {
const refresh = () =>
dispatchFilterAction({
payload: {
- new: initialFilter
+ new: parsedInitialFilter
},
type: "merge"
});
diff --git a/src/components/Filter/utils.ts b/src/components/Filter/utils.ts
new file mode 100644
index 000000000..371fb3b7d
--- /dev/null
+++ b/src/components/Filter/utils.ts
@@ -0,0 +1,68 @@
+import compact from "lodash-es/compact";
+
+import { FieldType, IFilterElement } from "./types";
+
+export const getByName = (nameToCompare: string) => (obj: { name: string }) =>
+ obj.name === nameToCompare;
+
+export const isAutocompleteFilterFieldValid = function({
+ value
+}: IFilterElement) {
+ return !!compact(value).length;
+};
+
+export const isFilterFieldValid = function(
+ filter: IFilterElement
+) {
+ const { type } = filter;
+
+ switch (type) {
+ case FieldType.boolean:
+ case FieldType.autocomplete:
+ return isAutocompleteFilterFieldValid(filter);
+
+ default:
+ return true;
+ }
+};
+
+export const isFilterValid = function(
+ resultFilters: Array>,
+ filter: IFilterElement
+) {
+ const { required, active } = filter;
+
+ if (!required || !active) {
+ return resultFilters;
+ }
+
+ return isFilterFieldValid(filter)
+ ? resultFilters
+ : [...resultFilters, filter];
+};
+
+export const extractInvalidFilters = function(
+ filtersData: Array>,
+ filtersDataStructure: Array>
+) {
+ return filtersDataStructure.reduce(
+ (resultFilters, { name, multipleFields }) => {
+ const filter = filtersData.find(getByName(name));
+
+ const shouldExtractChildrenFields =
+ filter.active && !!multipleFields?.length;
+
+ if (shouldExtractChildrenFields) {
+ return multipleFields
+ .map(field => {
+ const dataField = filtersData.find(getByName(field.name));
+ return { ...dataField, active: true };
+ })
+ .reduce(isFilterValid, resultFilters);
+ }
+
+ return isFilterValid(resultFilters, filter);
+ },
+ []
+ );
+};
diff --git a/src/components/FilterBar/FilterBar.tsx b/src/components/FilterBar/FilterBar.tsx
index 1d85f1ac1..8405825c9 100644
--- a/src/components/FilterBar/FilterBar.tsx
+++ b/src/components/FilterBar/FilterBar.tsx
@@ -5,7 +5,7 @@ import { FormattedMessage, useIntl } from "react-intl";
import { FilterProps } from "../../types";
import Filter from "../Filter";
-import { IFilter } from "../Filter/types";
+import { FilterErrorMessages, IFilter } from "../Filter/types";
import { SearchBarProps } from "../SearchBar";
import SearchInput from "../SearchBar/SearchInput";
import FilterTabs, { FilterTab } from "../TableFilter";
@@ -13,6 +13,7 @@ import FilterTabs, { FilterTab } from "../TableFilter";
export interface FilterBarProps
extends FilterProps,
SearchBarProps {
+ errorMessages?: FilterErrorMessages;
filterStructure: IFilter;
}
@@ -49,7 +50,8 @@ const FilterBar: React.FC = props => {
onFilterChange,
onTabChange,
onTabDelete,
- onTabSave
+ onTabSave,
+ errorMessages
} = props;
const classes = useStyles(props);
@@ -84,6 +86,7 @@ const FilterBar: React.FC = props => {
{
const dot = {
borderRadius: "100%",
- content: "''",
- display: "block",
height: 8,
- left: -theme.spacing(2),
- position: "absolute" as "absolute",
- top: "calc(50% - 5px)",
width: 8
};
return {
+ dot,
+ container: {
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center"
+ },
+ containerVertical: {
+ alignItems: "flex-start"
+ },
+ textContainer: {
+ marginLeft: theme.spacing(1),
+ display: "flex",
+ flexDirection: "column"
+ },
+ dotVertical: {
+ marginTop: theme.spacing(1)
+ },
alertDot: {
- "&:before": { backgroundColor: yellow[500], ...dot }
+ backgroundColor: yellow[500],
+ ...dot
},
errorDot: {
- "&:before": { backgroundColor: theme.palette.error.main, ...dot }
+ backgroundColor: theme.palette.error.main,
+ ...dot
},
neutralDot: {
- "&:before": { backgroundColor: grey[300], ...dot }
+ backgroundColor: grey[300],
+ ...dot
},
- root: {
- display: "inline-block",
- marginLeft: theme.spacing(1) + 8,
- position: "relative"
+ successDot: {
+ backgroundColor: theme.palette.primary.main,
+ ...dot
},
span: {
display: "inline"
- },
- successDot: {
- "&:before": { backgroundColor: theme.palette.primary.main, ...dot }
}
};
},
@@ -45,41 +57,43 @@ const useStyles = makeStyles(
);
interface StatusLabelProps {
- className?: string;
label: string | React.ReactNode;
status: "success" | "alert" | "neutral" | "error" | string;
- typographyProps?: TypographyProps;
+ subtitle?: string;
+ className?: string;
}
-const StatusLabel: React.FC = props => {
- const { className, label, status, typographyProps } = props;
-
- const classes = useStyles(props);
+const StatusLabel: React.FC = ({
+ className,
+ label,
+ status,
+ subtitle
+}) => {
+ const classes = useStyles({});
return (
- {typographyProps ? (
-
- {label}
-
- ) : (
- label
- )}
+
+
+ {label}
+ {subtitle && }
+
);
};
-StatusLabel.displayName = "StatusLabel";
+
export default StatusLabel;
diff --git a/src/components/StatusLabel/messages.ts b/src/components/StatusLabel/messages.ts
new file mode 100644
index 000000000..8b9e19047
--- /dev/null
+++ b/src/components/StatusLabel/messages.ts
@@ -0,0 +1,16 @@
+import { defineMessages } from "react-intl";
+
+export const statusLabelMessages = defineMessages({
+ active: {
+ defaultMessage: "Active",
+ description: "status label active"
+ },
+ inactive: {
+ defaultMessage: "Inactive",
+ description: "status label inactive"
+ },
+ deactivated: {
+ defaultMessage: "Deactivated",
+ description: "status label deactivated"
+ }
+});
diff --git a/src/components/TypeDeleteWarningDialog/TypeDeleteWarningDialog.stories.tsx b/src/components/TypeDeleteWarningDialog/TypeDeleteWarningDialog.stories.tsx
new file mode 100644
index 000000000..129a9fa17
--- /dev/null
+++ b/src/components/TypeDeleteWarningDialog/TypeDeleteWarningDialog.stories.tsx
@@ -0,0 +1,40 @@
+import CentralPlacementDecorator from "@saleor/storybook/CentralPlacementDecorator";
+import CommonDecorator from "@saleor/storybook/Decorator";
+import { storiesOf } from "@storybook/react";
+import React from "react";
+
+import * as messages from "../../pageTypes/hooks/usePageTypeDelete/messages";
+import TypeDeleteWarningDialog, {
+ TypeBaseData,
+ TypeDeleteWarningDialogProps
+} from "./TypeDeleteWarningDialog";
+
+const props: TypeDeleteWarningDialogProps = {
+ ...messages,
+ isOpen: true,
+ onClose: () => undefined,
+ onDelete: () => undefined,
+ typesData: [{ id: "id-1", name: "Interesting Pages" }],
+ isLoading: false,
+ assignedItemsCount: 4,
+ typesToDelete: ["id-1"],
+ viewAssignedItemsUrl: "some-url",
+ deleteButtonState: "default"
+};
+
+storiesOf("TypeDeleteWarningDialog.stories", module)
+ .addDecorator(CommonDecorator)
+ .addDecorator(CentralPlacementDecorator)
+ .add("loading", () => )
+ .add("single type no assigned items", () => (
+
+ ))
+ .add("single type some assigned items", () => (
+
+ ))
+ .add("multiple type no assigned items", () => (
+
+ ))
+ .add("multiple types some assigned items", () => (
+
+ ));
diff --git a/src/components/TypeDeleteWarningDialog/TypeDeleteWarningDialog.tsx b/src/components/TypeDeleteWarningDialog/TypeDeleteWarningDialog.tsx
new file mode 100644
index 000000000..59b582e0c
--- /dev/null
+++ b/src/components/TypeDeleteWarningDialog/TypeDeleteWarningDialog.tsx
@@ -0,0 +1,132 @@
+import { CardContent } from "@material-ui/core";
+import Card from "@material-ui/core/Card";
+import CircularProgress from "@material-ui/core/CircularProgress";
+import Modal from "@material-ui/core/Modal";
+import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
+import ModalTitle from "@saleor/orders/components/OrderDiscountCommonModal/ModalTitle";
+import { getById } from "@saleor/orders/components/OrderReturnPage/utils";
+import React from "react";
+import { useIntl } from "react-intl";
+
+import { useTypeDeleteWarningDialogStyles as useStyles } from "./styles";
+import ProductTypeDeleteWarningDialogContent from "./TypeDeleteWarningDialogContent";
+import {
+ CommonTypeDeleteWarningMessages,
+ TypeDeleteWarningMessages
+} from "./types";
+
+export interface TypeBaseData {
+ id: string;
+ name: string;
+}
+
+export interface TypeDeleteMessages {
+ baseMessages: CommonTypeDeleteWarningMessages;
+ singleWithItemsMessages: TypeDeleteWarningMessages;
+ singleWithoutItemsMessages: TypeDeleteWarningMessages;
+ multipleWithItemsMessages: TypeDeleteWarningMessages;
+ multipleWithoutItemsMessages: TypeDeleteWarningMessages;
+}
+
+export interface TypeDeleteWarningDialogProps
+ extends TypeDeleteMessages {
+ isOpen: boolean;
+ deleteButtonState: ConfirmButtonTransitionState;
+ onClose: () => void;
+ onDelete: () => void;
+ viewAssignedItemsUrl: string;
+ typesToDelete: string[];
+ assignedItemsCount: number | undefined;
+ isLoading?: boolean;
+ typesData: T[];
+ // temporary, until we add filters to pages list - SALEOR-3279
+ showViewAssignedItemsButton?: boolean;
+}
+
+function TypeDeleteWarningDialog({
+ isLoading = false,
+ isOpen,
+ baseMessages,
+ singleWithItemsMessages,
+ singleWithoutItemsMessages,
+ multipleWithItemsMessages,
+ multipleWithoutItemsMessages,
+ onClose,
+ onDelete,
+ assignedItemsCount,
+ viewAssignedItemsUrl,
+ typesToDelete,
+ typesData,
+ showViewAssignedItemsButton = true
+}: TypeDeleteWarningDialogProps) {
+ const intl = useIntl();
+ const classes = useStyles({});
+
+ const showMultiple = typesToDelete.length > 1;
+
+ const hasAssignedItems = !!assignedItemsCount;
+
+ const selectMessages = () => {
+ if (showMultiple) {
+ const multipleMessages = hasAssignedItems
+ ? multipleWithItemsMessages
+ : multipleWithoutItemsMessages;
+
+ return {
+ ...multipleMessages
+ };
+ }
+
+ const singleMessages = hasAssignedItems
+ ? singleWithItemsMessages
+ : singleWithoutItemsMessages;
+
+ return {
+ ...singleMessages
+ };
+ };
+
+ const { description, consentLabel } = selectMessages();
+
+ const singleItemSelectedId = typesToDelete[0];
+
+ const singleItemSelectedName = typesData.find(getById(singleItemSelectedId))
+ ?.name;
+
+ return (
+
+
+
+
+ {isLoading ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
+
+export default TypeDeleteWarningDialog;
diff --git a/src/components/TypeDeleteWarningDialog/TypeDeleteWarningDialogContent.tsx b/src/components/TypeDeleteWarningDialog/TypeDeleteWarningDialogContent.tsx
new file mode 100644
index 000000000..6c2b19a6c
--- /dev/null
+++ b/src/components/TypeDeleteWarningDialog/TypeDeleteWarningDialogContent.tsx
@@ -0,0 +1,95 @@
+import CardContent from "@material-ui/core/CardContent";
+import Typography from "@material-ui/core/Typography";
+import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
+import CardSpacer from "@saleor/components/CardSpacer";
+import ConfirmButton from "@saleor/components/ConfirmButton";
+import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
+import DeleteButton from "@saleor/components/DeleteButton";
+import useNavigator from "@saleor/hooks/useNavigator";
+import React, { ChangeEvent, useState } from "react";
+import { MessageDescriptor, useIntl } from "react-intl";
+
+import { useTypeDeleteWarningDialogStyles as useStyles } from "./styles";
+
+interface TypeDeleteWarningDialogContentProps {
+ singleItemSelectedName?: string;
+ viewAssignedItemsButtonLabel: MessageDescriptor;
+ description: MessageDescriptor;
+ consentLabel: MessageDescriptor;
+ viewAssignedItemsUrl: string;
+ hasAssignedItems: boolean;
+ assignedItemsCount: number | undefined;
+ onDelete: () => void;
+ // temporary, until we add filters to pages list - SALEOR-3279
+ showViewAssignedItemsButton?: boolean;
+}
+
+const TypeDeleteWarningDialogContent: React.FC = ({
+ description,
+ consentLabel,
+ viewAssignedItemsUrl,
+ viewAssignedItemsButtonLabel,
+ singleItemSelectedName,
+ hasAssignedItems,
+ assignedItemsCount,
+ onDelete,
+ showViewAssignedItemsButton
+}) => {
+ const classes = useStyles({});
+ const intl = useIntl();
+ const navigate = useNavigator();
+
+ const [isConsentChecked, setIsConsentChecked] = useState(false);
+
+ const handleConsentChange = ({ target }: ChangeEvent) =>
+ setIsConsentChecked(target.value);
+
+ const handleViewAssignedItems = () => navigate(viewAssignedItemsUrl);
+
+ const isDisbled = hasAssignedItems ? !isConsentChecked : false;
+
+ const shouldShowViewAssignedItemsButton =
+ showViewAssignedItemsButton && hasAssignedItems;
+
+ return (
+
+
+ {intl.formatMessage(description, {
+ typeName: singleItemSelectedName,
+ assignedItemsCount,
+ b: (...chunks) => {chunks}
+ })}
+
+
+ {consentLabel && (
+
+ {intl.formatMessage(consentLabel)}
+
+ }
+ />
+ )}
+
+
+ {shouldShowViewAssignedItemsButton && (
+ <>
+
+ {intl.formatMessage(viewAssignedItemsButtonLabel)}
+
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default TypeDeleteWarningDialogContent;
diff --git a/src/components/TypeDeleteWarningDialog/index.tsx b/src/components/TypeDeleteWarningDialog/index.tsx
new file mode 100644
index 000000000..73ce09808
--- /dev/null
+++ b/src/components/TypeDeleteWarningDialog/index.tsx
@@ -0,0 +1,2 @@
+export * from "./TypeDeleteWarningDialog";
+export { default } from "./TypeDeleteWarningDialog";
diff --git a/src/components/TypeDeleteWarningDialog/styles.ts b/src/components/TypeDeleteWarningDialog/styles.ts
new file mode 100644
index 000000000..0e5e83c14
--- /dev/null
+++ b/src/components/TypeDeleteWarningDialog/styles.ts
@@ -0,0 +1,23 @@
+import { makeStyles } from "@saleor/theme";
+
+export const useTypeDeleteWarningDialogStyles = makeStyles(
+ theme => ({
+ centerContainer: {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ height: "100%"
+ },
+ content: {
+ width: 600
+ },
+ consentLabel: {
+ color: theme.palette.primary.main
+ },
+ buttonsSection: {
+ display: "flex",
+ justifyContent: "flex-end"
+ }
+ }),
+ { name: "ProductTypeDeleteWarningDialog" }
+);
diff --git a/src/components/TypeDeleteWarningDialog/types.ts b/src/components/TypeDeleteWarningDialog/types.ts
new file mode 100644
index 000000000..d5c8cce8b
--- /dev/null
+++ b/src/components/TypeDeleteWarningDialog/types.ts
@@ -0,0 +1,10 @@
+import { MessageDescriptor } from "react-intl";
+
+export type CommonTypeDeleteWarningMessages = Record<
+ "title" | "viewAssignedItemsButtonLabel",
+ MessageDescriptor
+>;
+
+export type TypeDeleteWarningMessages = Partial<
+ Record<"description" | "consentLabel", MessageDescriptor>
+>;
diff --git a/src/fragments/plugins.ts b/src/fragments/plugins.ts
index 3a128af56..d88d0e413 100644
--- a/src/fragments/plugins.ts
+++ b/src/fragments/plugins.ts
@@ -1,11 +1,42 @@
import gql from "graphql-tag";
+export const configurationItemFragment = gql`
+ fragment ConfigurationItemFragment on ConfigurationItem {
+ name
+ value
+ type
+ helpText
+ label
+ }
+`;
+
+export const pluginConfigurationFragment = gql`
+ ${configurationItemFragment}
+ fragment PluginConfigurationFragment on PluginConfiguration {
+ active
+ channel {
+ id
+ name
+ slug
+ }
+ configuration {
+ ...ConfigurationItemFragment
+ }
+ }
+`;
+
export const pluginsFragment = gql`
+ ${pluginConfigurationFragment}
fragment PluginFragment on Plugin {
id
name
description
- active
+ globalConfiguration {
+ ...PluginConfigurationFragment
+ }
+ channelConfigurations {
+ ...PluginConfigurationFragment
+ }
}
`;
@@ -13,12 +44,5 @@ export const pluginsDetailsFragment = gql`
${pluginsFragment}
fragment PluginsDetailsFragment on Plugin {
...PluginFragment
- configuration {
- name
- type
- value
- helpText
- label
- }
}
`;
diff --git a/src/fragments/types/ConfigurationItemFragment.ts b/src/fragments/types/ConfigurationItemFragment.ts
new file mode 100644
index 000000000..81de8d0c1
--- /dev/null
+++ b/src/fragments/types/ConfigurationItemFragment.ts
@@ -0,0 +1,19 @@
+/* tslint:disable */
+/* eslint-disable */
+// @generated
+// This file was automatically generated and should not be edited.
+
+import { ConfigurationTypeFieldEnum } from "./../../types/globalTypes";
+
+// ====================================================
+// GraphQL fragment: ConfigurationItemFragment
+// ====================================================
+
+export interface ConfigurationItemFragment {
+ __typename: "ConfigurationItem";
+ name: string;
+ value: string | null;
+ type: ConfigurationTypeFieldEnum | null;
+ helpText: string | null;
+ label: string | null;
+}
diff --git a/src/fragments/types/PluginConfiguarionFragment.ts b/src/fragments/types/PluginConfiguarionFragment.ts
new file mode 100644
index 000000000..1a16e94dd
--- /dev/null
+++ b/src/fragments/types/PluginConfiguarionFragment.ts
@@ -0,0 +1,33 @@
+/* tslint:disable */
+/* eslint-disable */
+// @generated
+// This file was automatically generated and should not be edited.
+
+import { ConfigurationTypeFieldEnum } from "./../../types/globalTypes";
+
+// ====================================================
+// GraphQL fragment: PluginConfiguarionFragment
+// ====================================================
+
+export interface PluginConfiguarionFragment_channel {
+ __typename: "Channel";
+ id: string;
+ name: string;
+ slug: string;
+}
+
+export interface PluginConfiguarionFragment_configuration {
+ __typename: "ConfigurationItem";
+ name: string;
+ value: string | null;
+ type: ConfigurationTypeFieldEnum | null;
+ helpText: string | null;
+ label: string | null;
+}
+
+export interface PluginConfiguarionFragment {
+ __typename: "PluginConfiguration";
+ active: boolean;
+ channel: PluginConfiguarionFragment_channel | null;
+ configuration: (PluginConfiguarionFragment_configuration | null)[] | null;
+}
diff --git a/src/fragments/types/PluginConfigurationFragment.ts b/src/fragments/types/PluginConfigurationFragment.ts
new file mode 100644
index 000000000..6f75ac9a2
--- /dev/null
+++ b/src/fragments/types/PluginConfigurationFragment.ts
@@ -0,0 +1,33 @@
+/* tslint:disable */
+/* eslint-disable */
+// @generated
+// This file was automatically generated and should not be edited.
+
+import { ConfigurationTypeFieldEnum } from "./../../types/globalTypes";
+
+// ====================================================
+// GraphQL fragment: PluginConfigurationFragment
+// ====================================================
+
+export interface PluginConfigurationFragment_channel {
+ __typename: "Channel";
+ id: string;
+ name: string;
+ slug: string;
+}
+
+export interface PluginConfigurationFragment_configuration {
+ __typename: "ConfigurationItem";
+ name: string;
+ value: string | null;
+ type: ConfigurationTypeFieldEnum | null;
+ helpText: string | null;
+ label: string | null;
+}
+
+export interface PluginConfigurationFragment {
+ __typename: "PluginConfiguration";
+ active: boolean;
+ channel: PluginConfigurationFragment_channel | null;
+ configuration: (PluginConfigurationFragment_configuration | null)[] | null;
+}
diff --git a/src/fragments/types/PluginFragment.ts b/src/fragments/types/PluginFragment.ts
index c4a323dae..d546aed49 100644
--- a/src/fragments/types/PluginFragment.ts
+++ b/src/fragments/types/PluginFragment.ts
@@ -3,14 +3,63 @@
// @generated
// This file was automatically generated and should not be edited.
+import { ConfigurationTypeFieldEnum } from "./../../types/globalTypes";
+
// ====================================================
// GraphQL fragment: PluginFragment
// ====================================================
+export interface PluginFragment_globalConfiguration_channel {
+ __typename: "Channel";
+ id: string;
+ name: string;
+ slug: string;
+}
+
+export interface PluginFragment_globalConfiguration_configuration {
+ __typename: "ConfigurationItem";
+ name: string;
+ value: string | null;
+ type: ConfigurationTypeFieldEnum | null;
+ helpText: string | null;
+ label: string | null;
+}
+
+export interface PluginFragment_globalConfiguration {
+ __typename: "PluginConfiguration";
+ active: boolean;
+ channel: PluginFragment_globalConfiguration_channel | null;
+ configuration: (PluginFragment_globalConfiguration_configuration | null)[] | null;
+}
+
+export interface PluginFragment_channelConfigurations_channel {
+ __typename: "Channel";
+ id: string;
+ name: string;
+ slug: string;
+}
+
+export interface PluginFragment_channelConfigurations_configuration {
+ __typename: "ConfigurationItem";
+ name: string;
+ value: string | null;
+ type: ConfigurationTypeFieldEnum | null;
+ helpText: string | null;
+ label: string | null;
+}
+
+export interface PluginFragment_channelConfigurations {
+ __typename: "PluginConfiguration";
+ active: boolean;
+ channel: PluginFragment_channelConfigurations_channel | null;
+ configuration: (PluginFragment_channelConfigurations_configuration | null)[] | null;
+}
+
export interface PluginFragment {
__typename: "Plugin";
id: string;
name: string;
description: string;
- active: boolean;
+ globalConfiguration: PluginFragment_globalConfiguration | null;
+ channelConfigurations: PluginFragment_channelConfigurations[];
}
diff --git a/src/fragments/types/PluginsDetailsFragment.ts b/src/fragments/types/PluginsDetailsFragment.ts
index 6aeb21c29..2e4b15f59 100644
--- a/src/fragments/types/PluginsDetailsFragment.ts
+++ b/src/fragments/types/PluginsDetailsFragment.ts
@@ -9,20 +9,57 @@ import { ConfigurationTypeFieldEnum } from "./../../types/globalTypes";
// GraphQL fragment: PluginsDetailsFragment
// ====================================================
-export interface PluginsDetailsFragment_configuration {
+export interface PluginsDetailsFragment_globalConfiguration_channel {
+ __typename: "Channel";
+ id: string;
+ name: string;
+ slug: string;
+}
+
+export interface PluginsDetailsFragment_globalConfiguration_configuration {
__typename: "ConfigurationItem";
name: string;
- type: ConfigurationTypeFieldEnum | null;
value: string | null;
+ type: ConfigurationTypeFieldEnum | null;
helpText: string | null;
label: string | null;
}
+export interface PluginsDetailsFragment_globalConfiguration {
+ __typename: "PluginConfiguration";
+ active: boolean;
+ channel: PluginsDetailsFragment_globalConfiguration_channel | null;
+ configuration: (PluginsDetailsFragment_globalConfiguration_configuration | null)[] | null;
+}
+
+export interface PluginsDetailsFragment_channelConfigurations_channel {
+ __typename: "Channel";
+ id: string;
+ name: string;
+ slug: string;
+}
+
+export interface PluginsDetailsFragment_channelConfigurations_configuration {
+ __typename: "ConfigurationItem";
+ name: string;
+ value: string | null;
+ type: ConfigurationTypeFieldEnum | null;
+ helpText: string | null;
+ label: string | null;
+}
+
+export interface PluginsDetailsFragment_channelConfigurations {
+ __typename: "PluginConfiguration";
+ active: boolean;
+ channel: PluginsDetailsFragment_channelConfigurations_channel | null;
+ configuration: (PluginsDetailsFragment_channelConfigurations_configuration | null)[] | null;
+}
+
export interface PluginsDetailsFragment {
__typename: "Plugin";
id: string;
name: string;
description: string;
- active: boolean;
- configuration: (PluginsDetailsFragment_configuration | null)[] | null;
+ globalConfiguration: PluginsDetailsFragment_globalConfiguration | null;
+ channelConfigurations: PluginsDetailsFragment_channelConfigurations[];
}
diff --git a/src/hooks/useChannelsSearch.ts b/src/hooks/useChannelsSearch.ts
new file mode 100644
index 000000000..394c49f4f
--- /dev/null
+++ b/src/hooks/useChannelsSearch.ts
@@ -0,0 +1,21 @@
+import { Channel_channel } from "@saleor/channels/types/Channel";
+import { FetchMoreProps, Search, SearchProps } from "@saleor/types";
+import { filter } from "fuzzaldrin";
+import React from "react";
+
+export const useChannelsSearch = function(
+ channels: T[]
+) {
+ const [query, onQueryChange] = React.useState("");
+ const filteredChannels =
+ filter(channels, query, { key: "name" }) || [];
+
+ return { query, onQueryChange, filteredChannels };
+};
+
+export interface ChannelsWithLoadMoreProps
+ extends FetchMoreProps,
+ Search,
+ SearchProps {
+ channels: Channel_channel[];
+}
diff --git a/src/hooks/useChannelsSearchWithLoadMore.ts b/src/hooks/useChannelsSearchWithLoadMore.ts
new file mode 100644
index 000000000..9100415b1
--- /dev/null
+++ b/src/hooks/useChannelsSearchWithLoadMore.ts
@@ -0,0 +1,66 @@
+import { useBaseChannelsList } from "@saleor/channels/queries";
+import chunk from "lodash-es/chunk";
+import compact from "lodash-es/compact";
+import concat from "lodash-es/concat";
+import { useEffect, useState } from "react";
+
+import {
+ ChannelsWithLoadMoreProps,
+ useChannelsSearch
+} from "./useChannelsSearch";
+
+const DEFAULT_ITEMS_PER_PAGE = 6;
+const INITIAL_INDEX = 0;
+
+export const useChannelsSearchWithLoadMore = (
+ itemsPerPage: number = DEFAULT_ITEMS_PER_PAGE
+): ChannelsWithLoadMoreProps => {
+ const { data, loading } = useBaseChannelsList({});
+
+ const {
+ query,
+ onQueryChange: onSearchChange,
+ filteredChannels
+ } = useChannelsSearch(data?.channels);
+
+ const allChannelsChunks = chunk(filteredChannels, itemsPerPage);
+
+ const [currentIndex, setCurrentIndex] = useState(INITIAL_INDEX);
+ const [currentChannelsChunks, setCurrentChannelsChunks] = useState([]);
+
+ const handleAddInitialChunk = () => {
+ if (data?.channels && !loading) {
+ setCurrentChannelsChunks([allChannelsChunks[INITIAL_INDEX]]);
+ }
+ };
+
+ useEffect(handleAddInitialChunk, [loading, query]);
+
+ const onFetchMore = () => {
+ if (!hasMore) {
+ return;
+ }
+
+ const newIndex = currentIndex + 1;
+ setCurrentIndex(newIndex);
+
+ const newChunk = allChannelsChunks[newIndex];
+ setCurrentChannelsChunks([...currentChannelsChunks, newChunk]);
+ };
+
+ const hasMore = allChannelsChunks.length > currentChannelsChunks.length;
+
+ const channels = compact(concat([], ...currentChannelsChunks));
+
+ const totalCount = data?.channels.length;
+
+ return {
+ query,
+ onSearchChange,
+ channels,
+ hasMore,
+ totalCount,
+ onFetchMore,
+ loading
+ };
+};
diff --git a/src/orders/components/OrderCustomer/OrderCustomer.tsx b/src/orders/components/OrderCustomer/OrderCustomer.tsx
index 4f79e1aeb..55ee02ff8 100644
--- a/src/orders/components/OrderCustomer/OrderCustomer.tsx
+++ b/src/orders/components/OrderCustomer/OrderCustomer.tsx
@@ -184,7 +184,12 @@ const OrderCustomer: React.FC = props => {
)
) : (
<>
- {user.email}
+
+ {user.email}
+
= props => {
{canEditAddresses && (
@@ -5530,16 +5515,16 @@ exports[`Storyshots Generics / Filter default 1`] = `
>
@@ -50195,9 +50325,20 @@ exports[`Storyshots Views / Categories / Update category products 1`] = `
role="button"
>
@@ -50281,9 +50422,20 @@ exports[`Storyshots Views / Categories / Update category products 1`] = `
role="button"
>
@@ -50367,9 +50519,20 @@ exports[`Storyshots Views / Categories / Update category products 1`] = `
role="button"
>
@@ -50453,9 +50616,20 @@ exports[`Storyshots Views / Categories / Update category products 1`] = `
role="button"
>
@@ -55326,9 +55500,20 @@ exports[`Storyshots Views / Collections / Collection detailsCollection details d
role="button"
>
@@ -55431,9 +55616,20 @@ exports[`Storyshots Views / Collections / Collection detailsCollection details d
role="button"
>
@@ -55536,9 +55732,20 @@ exports[`Storyshots Views / Collections / Collection detailsCollection details d
role="button"
>
@@ -55641,9 +55848,20 @@ exports[`Storyshots Views / Collections / Collection detailsCollection details d
role="button"
>
@@ -56778,9 +56996,20 @@ exports[`Storyshots Views / Collections / Collection detailsCollection details f
role="button"
>
@@ -56883,9 +57112,20 @@ exports[`Storyshots Views / Collections / Collection detailsCollection details f
role="button"
>
@@ -56988,9 +57228,20 @@ exports[`Storyshots Views / Collections / Collection detailsCollection details f
role="button"
>
@@ -57093,9 +57344,20 @@ exports[`Storyshots Views / Collections / Collection detailsCollection details f
role="button"
>
@@ -59568,9 +59830,20 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
role="button"
>
@@ -70252,9 +70525,20 @@ exports[`Storyshots Views / Customers / Customer details default 1`] = `
class="MuiTableCell-root-id MuiTableCell-body-id"
>
|
|
|
|
|
|
|
@@ -87361,9 +87733,20 @@ exports[`Storyshots Views / Discounts / Sale details products 1`] = `
role="button"
>
@@ -87466,9 +87849,20 @@ exports[`Storyshots Views / Discounts / Sale details products 1`] = `
role="button"
>
@@ -87571,9 +87965,20 @@ exports[`Storyshots Views / Discounts / Sale details products 1`] = `
role="button"
>
@@ -110341,12 +110746,23 @@ exports[`Storyshots Views / Orders / Order details cancelled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Unfulfilled
+
- #9-2
+
+ Unfulfilled
+
+ #9-2
+
+
@@ -110599,13 +111026,24 @@ exports[`Storyshots Views / Orders / Order details cancelled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Unfulfilled
+
- #9-1
+
+ Unfulfilled
+
+ #9-1
+
+
@@ -110760,9 +111198,20 @@ exports[`Storyshots Views / Orders / Order details cancelled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -112294,13 +112765,24 @@ exports[`Storyshots Views / Orders / Order details default 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -112495,9 +112977,20 @@ exports[`Storyshots Views / Orders / Order details default 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -113525,6 +114019,7 @@ exports[`Storyshots Views / Orders / Order details default 1`] = `
>
@@ -113797,12 +114292,23 @@ exports[`Storyshots Views / Orders / Order details fulfilled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -114110,13 +114627,24 @@ exports[`Storyshots Views / Orders / Order details fulfilled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -114311,9 +114839,20 @@ exports[`Storyshots Views / Orders / Order details fulfilled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -115341,6 +115881,7 @@ exports[`Storyshots Views / Orders / Order details fulfilled 1`] = `
>
@@ -115908,6 +116449,7 @@ exports[`Storyshots Views / Orders / Order details loading 1`] = `
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -116495,13 +117060,24 @@ exports[`Storyshots Views / Orders / Order details no customer note 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -116696,9 +117272,20 @@ exports[`Storyshots Views / Orders / Order details no customer note 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -117726,6 +118314,7 @@ exports[`Storyshots Views / Orders / Order details no customer note 1`] = `
>
@@ -117998,12 +118587,23 @@ exports[`Storyshots Views / Orders / Order details no payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -118311,13 +118922,24 @@ exports[`Storyshots Views / Orders / Order details no payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -118512,9 +119134,20 @@ exports[`Storyshots Views / Orders / Order details no payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -119542,6 +120176,7 @@ exports[`Storyshots Views / Orders / Order details no payment 1`] = `
>
@@ -119814,12 +120449,23 @@ exports[`Storyshots Views / Orders / Order details no shipping address 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -120127,13 +120784,24 @@ exports[`Storyshots Views / Orders / Order details no shipping address 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -120328,9 +120996,20 @@ exports[`Storyshots Views / Orders / Order details no shipping address 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -121339,6 +122019,7 @@ exports[`Storyshots Views / Orders / Order details no shipping address 1`] = `
>
@@ -121630,12 +122311,23 @@ exports[`Storyshots Views / Orders / Order details partially fulfilled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -121943,13 +122646,24 @@ exports[`Storyshots Views / Orders / Order details partially fulfilled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -122144,9 +122858,20 @@ exports[`Storyshots Views / Orders / Order details partially fulfilled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -123174,6 +123900,7 @@ exports[`Storyshots Views / Orders / Order details partially fulfilled 1`] = `
>
@@ -123446,12 +124173,23 @@ exports[`Storyshots Views / Orders / Order details payment confirmed 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -123759,13 +124508,24 @@ exports[`Storyshots Views / Orders / Order details payment confirmed 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -123960,9 +124720,20 @@ exports[`Storyshots Views / Orders / Order details payment confirmed 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -124990,6 +125762,7 @@ exports[`Storyshots Views / Orders / Order details payment confirmed 1`] = `
>
@@ -125262,12 +126035,23 @@ exports[`Storyshots Views / Orders / Order details payment error 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -125575,13 +126370,24 @@ exports[`Storyshots Views / Orders / Order details payment error 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -125776,9 +126582,20 @@ exports[`Storyshots Views / Orders / Order details payment error 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -126806,6 +127624,7 @@ exports[`Storyshots Views / Orders / Order details payment error 1`] = `
>
@@ -127078,12 +127897,23 @@ exports[`Storyshots Views / Orders / Order details pending payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -127391,13 +128232,24 @@ exports[`Storyshots Views / Orders / Order details pending payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -127592,9 +128444,20 @@ exports[`Storyshots Views / Orders / Order details pending payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -128622,6 +129486,7 @@ exports[`Storyshots Views / Orders / Order details pending payment 1`] = `
>
@@ -128894,12 +129759,23 @@ exports[`Storyshots Views / Orders / Order details refunded payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -129207,13 +130094,24 @@ exports[`Storyshots Views / Orders / Order details refunded payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -129408,9 +130306,20 @@ exports[`Storyshots Views / Orders / Order details refunded payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -130438,6 +131348,7 @@ exports[`Storyshots Views / Orders / Order details refunded payment 1`] = `
>
@@ -130710,12 +131621,23 @@ exports[`Storyshots Views / Orders / Order details rejected payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -131023,13 +131956,24 @@ exports[`Storyshots Views / Orders / Order details rejected payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -131224,9 +132168,20 @@ exports[`Storyshots Views / Orders / Order details rejected payment 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -132254,6 +133210,7 @@ exports[`Storyshots Views / Orders / Order details rejected payment 1`] = `
>
@@ -132526,12 +133483,23 @@ exports[`Storyshots Views / Orders / Order details unfulfilled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-2
+
+ Fulfilled (1)
+
+ #9-2
+
+
@@ -132839,13 +133818,24 @@ exports[`Storyshots Views / Orders / Order details unfulfilled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
- Fulfilled (1)
+
- #9-1
+
+ Fulfilled (1)
+
+ #9-1
+
+
@@ -133040,9 +134030,20 @@ exports[`Storyshots Views / Orders / Order details unfulfilled 1`] = `
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
@@ -134070,6 +135072,7 @@ exports[`Storyshots Views / Orders / Order details unfulfilled 1`] = `
>
@@ -134837,6 +135840,7 @@ exports[`Storyshots Views / Orders / Order draft default 1`] = `
>
@@ -134873,6 +135877,7 @@ exports[`Storyshots Views / Orders / Order draft default 1`] = `
>
@@ -135128,6 +136133,7 @@ exports[`Storyshots Views / Orders / Order draft loading 1`] = `
>
@@ -135839,6 +136847,7 @@ exports[`Storyshots Views / Orders / Order draft no user permissions 1`] = `
>
@@ -136175,6 +137184,7 @@ exports[`Storyshots Views / Orders / Order draft without lines 1`] = `
>
@@ -136211,6 +137221,7 @@ exports[`Storyshots Views / Orders / Order draft without lines 1`] = `
>
@@ -136673,18 +137684,40 @@ exports[`Storyshots Views / Orders / Order list default 1`] = `
class="MuiTableCell-root-id MuiTableCell-body-id OrderList-colPayment-id"
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
|
- Partially fulfilled
+
+
+
+ Partially fulfilled
+
+
|
- Action
+
+ |
+
@@ -158993,7 +160757,7 @@ exports[`Storyshots Views / Plugins / Plugin list default 1`] = `
>